diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..0b6ffcf46 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,99 @@ +cmake_minimum_required(VERSION 2.6) +project(BADVPN C) + +set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules") + +include(TestBigEndian) +include(CheckIncludeFiles) + +find_package(OpenSSL REQUIRED) +set(LIBCRYPTO_INCLUDE_DIRS "${OpenSSL_INCLUDE_DIRS}") +set(LIBCRYPTO_LIBRARY_DIRS "${OpenSSL_LIBRARY_DIRS}") +set(LIBCRYPTO_LIBRARIES "${OpenSSL_LIBRARIES}") + +find_package(NSPR REQUIRED) +find_package(NSS REQUIRED) + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${LIBCRYPTO_INCLUDE_DIRS} + ${NSPR_INCLUDE_DIRS} + ${NSS_INCLUDE_DIRS} +) + +link_directories( + ${LIBCRYPTO_LIBRARY_DIRS} + ${NSPR_LIBRARY_DIRS} + ${NSS_LIBRARY_DIRS} +) + +test_big_endian(BIG_ENDIAN) + +add_definitions(-std=gnu99 -Werror=implicit-function-declaration -Wno-unused-value -Wno-parentheses -Wno-switch-enum) + +# platform-specific stuff +if (WIN32) + add_definitions(-DBADVPN_USE_WINAPI -D_WIN32_WINNT=0x501 -DWIN32_LEAN_AND_MEAN) +else () + link_libraries(rt) + + check_include_files(sys/signalfd.h HAVE_SYS_SIGNALFD_H) + if (NOT ${HAVE_SYS_SIGNALFD_H}) + message(FATAL_ERROR "signalfd is required") + endif () + + check_include_files(sys/epoll.h HAVE_SYS_EPOLL_H) + if (NOT ${HAVE_SYS_EPOLL_H}) + message(FATAL_ERROR "epoll is required") + endif () + + if (NOT DEFINED BADVPN_WITHOUT_CRYPTODEV) + check_include_files(crypto/cryptodev.h HAVE_CRYPTO_CRYPTODEV_H) + if (${HAVE_CRYPTO_CRYPTODEV_H}) + add_definitions(-DBADVPN_USE_CRYPTODEV) + elseif (DEFINED BADVPN_WITH_CRYPTODEV) + message(FATAL_ERROR "crypto/cryptodev.h not found") + endif () + endif () +endif () + +# add preprocessor definitions +if (${BIG_ENDIAN}) + add_definitions(-DBADVPN_BIG_ENDIAN) +else () + add_definitions(-DBADVPN_LITTLE_ENDIAN) +endif () + +# install man pages +install( + FILES badvpn.7 + DESTINATION share/man/man7 +) +install( + FILES badvpn-server.8 badvpn-client.8 + DESTINATION share/man/man8 +) + +# internal libraries +add_subdirectory(system) +add_subdirectory(flow) +add_subdirectory(tuntap) +add_subdirectory(predicate) +add_subdirectory(nspr_support) +add_subdirectory(server_connection) +add_subdirectory(listener) + +# example programs +add_subdirectory(examples) + +# tests +add_subdirectory(tests) + +# server +add_subdirectory(server) + +# client +add_subdirectory(client) + +# flooder +add_subdirectory(flooder) diff --git a/COPYNG b/COPYNG new file mode 100644 index 000000000..d511905c1 --- /dev/null +++ b/COPYNG @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser 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) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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) year 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 Lesser General +Public License instead of this License. diff --git a/INSTALL b/INSTALL new file mode 100644 index 000000000..e3810e14f --- /dev/null +++ b/INSTALL @@ -0,0 +1,73 @@ +1 Requirements + +1.1 Operating system + +Linux: +- kernel version >=2.6.22 (for epoll and signalfd) +- glibc >=2.8 (for epoll and signalfd) +- tested on x86, x86_64 and ARM architectures. Not tested on any big-endian architecture. + +Windows: +- Windows XP or newer; tested on Windows XP and Windows 7 + +Other systems are not supported. + +1.2 Compilers + +Linux: + - gcc + - clang + +Windows: + - gcc from the mingw-w64 project for 32-bit targets + - clang, properly built with mingw-w64, using mingw headers/libs + +C language features used: + - Standard (all part of C99): + - designated initializers + - variable length arrays as automatic variables + - stdint.h, inttypes.h, stddef.h + - intermingled declarations and code + - for loop initial declaration + - one-line "//" comments + - Extensions: + - statements and declarations in expressions + - packed structure attribute (to pack a structure and allow unaligned access) + - __alignof__ for querying the required alignment of types + +1.3 CMake + +The build system uses CMake. + +1.4 OpenSSL + +Libcrypto (part of OpenSSL) is used for block ciphers, hash functions and random data generation. + +1.5 Network Security Services (NSS) + +The NSS library from Mozilla is used for TLS support. NSS command-line tools are also needed +for setting up certificates. + +1.6 TAP-Win32 (Windows only) (runtime only) + +The TAP-Win32 driver, part of OpenVPN. + +2 Compilation + +2.1 Compiling on Linux + +$ tar xf badvpn-.tar.bz2 +$ mkdir build +$ cd build +$ cmake ../badvpn- -DCMAKE_INSTALL_PREFIX=/usr/local +$ make +If you want to install it, run as root: +# make install + +2.2 Compiling for Windows + +See the file INSTALL-WINDOWS for detailed instructions. + +3 Usage + +See the documentation in the man pages (badvpn(7), badvpn-server(8), badvpn-client(8)). diff --git a/INSTALL-WINDOWS b/INSTALL-WINDOWS new file mode 100644 index 000000000..ca85cf7ba --- /dev/null +++ b/INSTALL-WINDOWS @@ -0,0 +1,129 @@ +It is possible to compile BadVPN on Windows natively or to cross-compile from Linux +(but you need Windows to compile NSS). + +Here are detailed instructions: + +- Get a gcc compiler for compiling 32-bit Windows programs from the mingw-w64 project: + + - If you are compiling BadVPN from Linux: + + Download (for Linux x86_64) + http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/sezero_20101003/mingw-w32-bin_x86_64-linux_20101003_sezero.tar.gz/download + or (for Linux x86) + http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/sezero_20101003/mingw-w32-bin_i686-linux_20101003_sezero.tar.gz/download . + + Extract it to /home//mingw so that you have /home//mingw/cross_win32. + + - If you are compiling BadVPN from Windows: + + Download + http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/sezero_20101003/mingw-w32-bin_i686-mingw_20101003_sezero.zip/download . + + Extract it to c:\ and rename it from "mingw32" to "mingw" so that you have C:\mingw\bin. + Be sure not to overwrite something. It really must be called "mingw" because cmake looks there. + +- Create a folder where build dependencies will be stored. Make sure it doesn't have spaces in its path. + Call it . + +- Build required libraries: + + - OpenSSL: + - If you are compiling BadVPN from Linux: + (This is for building OpenSSL with GCC. It is also possible to build it with MSVC from Windows; see below.) + (This should also work in Windows under Cygwin with the Cygwin build of mingw-w64.) + + Download the OpenSSL source code. Extract it. Open a shell in the source dir. Run: + + $ export PATH="${PATH}":/home//mingw/cross_win32/bin + $ perl Configure mingw --cross-compile-prefix=i686-w64-mingw32- --prefix=/ shared disable-capieng + $ make depend + $ make + $ make INSTALL_PREFIX= install + + - If you are compiling BadVPN from Windows: + (This is for building OpenSSL with MSVC. It is also possible to build it with GCC from Linux or Cygwin; see above.) + + - Install "Windows SDK for Windows 7" (unless you have Visual Studio) and install at least the headers, + libraries and compilers. + + - Install ActivePerl. + + - Download the OpenSSL source code. + Extract is somewhere. + + - Open the SDK terminal (Programs -> Microsoft Windows SDK v7.1 -> Windows SDK 7.1 Command Prompt). + Enter the OpenSSL source folder. + Run: + + > perl Configure VC-WIN32 --prefix= no-asm + > ms\do_ms + > nmake -f ms\ntdll.mak + > nmake -f ms\ntdll.mak install + + - NSS: + - You need to build it from Windows. + + - Install "Windows SDK for Windows 7" (unless you have Visual Studio) and install at least the headers, + libraries and compilers. + + - Install MozillaBuild: + http://ftp.mozilla.org/pub/mozilla.org/mozilla/libraries/win32/MozillaBuildSetup-Latest.exe . + + - Download the NSS source code that includes NSPR. As of the time of writing the latest version is 3.12.8: + https://ftp.mozilla.org/pub/mozilla.org/security/nss/releases/NSS_3_12_8_RTM/src/nss-3.12.8-with-nspr-4.8.6.tar.gz . + + Extract it to c:\ so that you have C:\mozilla . + + - Open the SDK terminal (Programs -> Microsoft Windows SDK v7.1 -> Windows SDK 7.1 Command Prompt) and run: + + > c:\mozilla-build\start-l10n.bat + + A new terminal opens. In that terminal, run: + (here paths are written as /driveletter/...) + + $ export OS_TARGET=WINNT + $ export BUILD_OPT=1 + $ cd /mozilla/security/nss + $ make nss_build_all + $ /scripts/copy_nss ../../dist + + If you will be compiling BadVPN from Linux, use an empty folder for in the above command, + and copy its contents into on the Linux system. + +- Compile it: + Choose a folder where the resulting binaries will be copied. + + - If you are compiling BadVPN from Linux: + + Copy /scripts/toolchain.cmake to /toolchain.cmake. + + Copy /scripts/cmake to / . + In that file, substitute and . + + Create an empty folder, call it , and open a shell in that folder. + Now run: + + $ /cmake -DCMAKE_INSTALL_PREFIX= + $ make + $ make install + $ cp /bin/*.dll /bin/ + + - If you are compiling BadVPN from Windows: + + Install CMake if you don't have it already. Select the option to include cmake in PATH + to avoid having to type a long command. + + Create an empty folder, call it , and open a command prompt in that folder. + Now run: + + > cmake -G "MinGW Makefiles" -DCMAKE_FIND_ROOT_PATH= -DCMAKE_INSTALL_PREFIX= + > c:\mingw\bin\mingw32-make.exe + > c:\mingw\bin\mingw32-make.exe install + > copy \bin\*.dll \bin\ + +- Test it: + + Execute \bin\badvpn-server.exe (on Windows or with Wine). It should print something like this and wait forever: + + NOTICE(server): initializing BadVPN server + NOTICE(server): entering event loop diff --git a/TODO b/TODO new file mode 100644 index 000000000..2f4c7cdbd --- /dev/null +++ b/TODO @@ -0,0 +1,2 @@ +- in the server, somehow handle when a peer message is discarded +- detect non-working link and try more addresses diff --git a/badvpn-client.8 b/badvpn-client.8 new file mode 100644 index 000000000..116d86579 --- /dev/null +++ b/badvpn-client.8 @@ -0,0 +1,274 @@ +.TH badvpn-client 8 "6 October 2010" +.SH NAME +badvpn-client \- VPN node daemon for the BadVPN peer-to-peer VPN system +.SH SYNOPSIS +.B badvpn-client +.RS +.RB "[" --help "]" +.br +.RB "[" --version "]" +.br +.RB "[" --logger " ]" +.br +(logger=syslog? +.br +.RS +.br +.RB "[" --syslog-facility " ]" +.br +.RB "[" --syslog-ident " ]" +.br +.RE +) +.br +.RB "[" --loglevel " <0-5/none/error/warning/notice/info/debug>]" +.br +.RB "[" --channel-loglevel " <0-5/none/error/warning/notice/info/debug>] ..." +.br +.RB "[" --ssl " " --nssdb " " --client-cert-name " ]" +.br +.RB "[" --server-name " ]" +.br +.BR --server-addr " " +.br +.RB "[" --tapdev " ]" +.br +.RB "[" --scope " ] ..." +.br +[ +.br +.RS +.BR --bind-addr " " +.br +.RB "(transport-mode=udp? " --num-ports " )" +.br +.RB "[" --ext-addr " ] ..." +.br +.RE +] +.br +.BR --transport-mode " " +.br +(transport-mode=udp? +.br +.RS +.BR --encryption-mode " " +.br +.BR --hash-mode " " +.br +.RB "[" --otp " ]" +.br +.RB "[" --fragmentation-latency " ]" +.br +.RE +) +.br +(transport-mode=tcp? +.br +.RS +.RB "(ssl? [" --peer-ssl "])" +.br +.RE +) +.br +.RB "[" --send-buffer-size " ]" +.br +.RB "[" --send-buffer-relay-size " ]" +.br +.RE +.SH INTRODUCTION +.P +This page documents the BadVPN client, a daemon for a node in a BadVPN VPN network. +For a general description of BadVPN, see +.BR badvpn (7). +.SH DESCRIPTION +.P +The BadVPN client is a daemon that runs on a VPN node. It opens the TAP device, connects to +the server, then keeps running while attempting to establish data connection to peers and +tranferring data between the TAP device and the peers. Once it initializes, the program only +terminates if it loses connection to the server, or if a signal is received. +.SH OPTIONS +.P +The BadVPN client is configured entirely from command line. +.TP +.BR --help +Print version and command line syntax and exit. +.TP +.BR --version +Print version and exit. +.TP +.BR --logger " " +Select where to log messages. Default is stdout. Syslog is not available on Windows. +.TP +.BR --syslog-facility " " +When logging to syslog, set the logging facility. The facility name must be in lower case. +.TP +.BR --syslog-ident " " +When logging to syslog, set the ident. +.TP +.BR --loglevel " <0-5/none/error/warning/notice/info/debug>" +Set the default logging level. +.TP +.BR --channel-loglevel " <0-5/none/error/warning/notice/info/debug>" +Set the logging level for a specific logging channel. +.TP +.BR --ssl +Use TLS. Requires --nssdb and --server-cert-name. +.TP +.BR --nssdb " " +When using TLS, the NSS database to use. Probably something like sql:/some/folder. +.TP +.BR --client-cert-name " " +When using TLS, the name of the certificate to use. The certificate must be readily accessible. +.TP +.BR --server-name " " +Set the name of the server used for validating the server's certificate. The server name defaults +to the the name in the server address (or a numeric address). +.TP +.BR --server-addr " " +Set the address for the server to listen on. See below for address format. +.TP +.BR --tapdev " " +Set the TAP device to use. See below on how to configure the device. A TAP device is a virtual card +in the operating system, but rather than receiving from and sending frames to a piece of hardware, +a program (this one) opens it to read from and write frames into. If the VPN network is set up correctly, +the TAP devices on the VPN nodes will act as if they were all connected into a network switch. +.TP +.BR --scope " " +Add an address scope allowed for connecting to peers. May be specified multiple times to add multiple +scopes. The order of the scopes is irrelevant. Note that it must actually be possible to connect +to addresses in the given scope; when another peer binds for us to connect to, we choose the first +external address whose scope we recognize, and do not attempt further external addresses, even if +establishing the connection fails. +.TP +.BR --bind-addr " " +Add an address to allow binding on. See below for address format. When attempting to bind in order +for some peer to connect to us, the addresses will be tried in the order they are specified. If UDP +data transport is being used, a --num-ports option must follow to specify how many continuous ports +to allow binding to. For the address to be useful, one or more --ext-addr options must follow. +Note that when two peers need to establish a data connection, it is arbitrary which one will attempt +to bind first. +.TP +.BR --num-ports " " +When using UDP transport, set the number of continuous ports for a previously specified bind address. +Must follow a previous --bind-addr option. +.TP +.BR --ext-addr " " +Add an external address for a previously specified bind address. Must follow a previous --bind-addr +option. May be specified multiple times to add multiple external addresses. See below for address +format. Additionally, the IP address part can be {server_reported} to use the IPv4 address as the +server sees us. The external addresses are tried by the connecting peer in the order they are specified. +Note that the connecting peer only attempts to connect to the first address whose scope it recognizes +and does not try other addresses. This means that all addresses must work for be able to communicate. +.TP +.BR --transport-mode " " +Sets the transport protocol for data connections. UDP is recommended and works best for most networks. +TCP can be used instead if the underlying network has high packet loss which your virtual network +cannot tolerate. Must match on all peers. +.TP +.BR --encryption-mode " " +When using UDP transport, sets the encryption mode. None means no encryption, other options mean +a specific cipher. Note that encryption is only useful if clients use TLS to connect to the server. +The encryption mode must match on all peers. +.TP +.BR --hash-mode " " +When using UDP transport, sets the hashing mode. None means no hashes, other options mean a specific +type of hash. Note that hashing is only useful if encryption is used as well. The hash mode must +match on all peers. +.TP +.BR --otp " " +When using UDP transport, enables one-time passwords. The first argument specifies a block cipher +used to generate passwords from a seed. The second argument specifies how many passwords are +generated from a single seed. The third argument specifies after how many passwords used up for +sending packets an attempt is made to negotiate a new seed with the other peer. num must be >0, +and num-warn must be >0 and <=num. The difference (num - num-warn) should be large enough to allow +a new seed to be negotiated before the sender runs out of passwords. Negotiating a seed involves +the sending peer sending it to the receiving peer via the server and the receiving peer confirming +it via the server. Note that one-time passwords are only useful if clients use TLS to connect to the +server. The OTP option must match on all peers, except for num-warn. +.TP +.BR --fragmentation-latency " " +When using UDP transport, sets the maximum latency to sacrifice in order to pack frames into data +packets more efficiently. If it is >=0, a timer of that many milliseconds is used to wait for further +frames to put into an incomplete packet since the first chunk of the packet was written. If it is +<0, packets are sent out immediately. Defaults to 0, which is the recommended setting. +.TP +.BR --peer-ssl +When using TCP transport, enables TLS for data connections. Requires using TLS for server connection. +For this to work, the peers must trust each others' cerificates, and the cerificates must grant the +TLS server usage context. This option must match on all peers. +.TP +.BR --send-buffer-size " " +Sets the minimum size of the peers' send buffers for sending frames originating from this system, in +number of packets. +.TP +.BR --send-buffer-relay-size " " +Sets the minimum size of the peers' send buffers for relaying frames from other peers, in number of +packets. +.SH "EXIT CODE" +.P +If initialization fails, exits with code 1. Otherwise runs until termination is requested or server connection +is broken and exits with code 1. +.SH "ADDRESS FORMAT" +.P +Addresses have the form ipaddr:port, where ipaddr is either an IPv4 address (name or numeric), or an +IPv6 address enclosed in brackets [] (name or numeric again). +.SH "TAP DEVICE CONFIGURATION" +.P +To use this program, you first have to configure a TAP network device that will act as an endpoint for +the virtual network. The configuration depends on your operating system. +.P +Note that the client program does not configure the TAP device in any way; it only reads and writes +frames from/to it. You are responsible for configuring it (e.g. putting it up and setting its IP address). +.P +.B Linux +.P +You need to enable the kernel configuration option CONFIG_TUN. If you enabled it as a module, you may +have to load it (`modprobe tun`) before you can create the device. +.P +Then you should create a persistent TAP device for the VPN client program to open. This can be done with +either the +.B tunctl +or the +.B openvpn +program. The device will be associated with a user account that will have permission to use it, which should +be the same user as the client program will run as (not root!). To create the device with tunctl, use `tunctl -u -t tapN`, +and to create it with openvpn, use `openvpn --mktun --user --dev tapN`, where N is a number that identifies the +TAP device. +.P +Once the TAP device is created, pass `--tapdev tapN` to the client program to make it use this device. Note that the +device will not be preserved across a shutdown of the system; consult your OS documentaton if you want to automate +the creation or configuration of the device. +.P +.B Windows +.P +Windows does not come with a TAP driver. The client program uses the TAP-Win32 driver, which is part of OpenVPN. +You need to install the OpenVPN open source (!) version, and in the installer enable at least the +`TAP Virtual Ethernet Adapter` and `Add Shortcuts to Start Menu` options. +You can get the installer at +.br +. +.P +The OpenVPN installer automatically creates one TAP device on your system when it's run for the first time. +To create another device, use `Programs -> OpenVPN -> Utilities -> Add a new TAP virtual ethernet adapter`. +You may have to install OpenVPN once again to make this shortcut appear. +.P +Once you have a TAP device, you can configure it like a physical network card. You can recognize TAP devices +by their `Device Name` field. +.P +To use the device, pass `--tapdev ":"` to the client program, where is the name of +the TAP driver (tap0901 for OpenVPN 2.1 and 2.2) (case sensitive), and is the (human) name of the TAP +network interface (e.g. `Local Area Connection 2`). +.SH "EXAMPLES" +.P +For examples of using BadVPN, see +.BR badvpn (7). +.SH "SEE ALSO" +.BR badvpn-server (8), +.BR badvpn (7) +.SH COPYRIGHT +Copyright (C) 2010 Ambroz Bizjak. BadVPN is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 as published by the +Free Software Foundation. +.SH AUTHORS +Ambroz Bizjak diff --git a/badvpn-server.8 b/badvpn-server.8 new file mode 100644 index 000000000..a01b221e5 --- /dev/null +++ b/badvpn-server.8 @@ -0,0 +1,187 @@ +.TH badvpn-server 8 "6 October 2010" +.SH NAME +badvpn-server \- chat server for the BadVPN peer-to-peer VPN system +.SH SYNOPSIS +.B badvpn-server +.RS +.RB "[" --help "]" +.br +.RB "[" --version "]" +.br +.RB "[" --logger " ]" +.br +(logger=syslog? +.br +.RS +.br +.RB "[" --syslog-facility " ]" +.br +.RB "[" --syslog-ident " ]" +.br +.RE +) +.br +.RB "[" --loglevel " <0-5/none/error/warning/notice/info/debug>]" +.br +.RB "[" --channel-loglevel " <0-5/none/error/warning/notice/info/debug>] ..." +.br +.RB "[" --listen-addr " ] ..." +.br +.RB "[" --ssl " " --nssdb " " --server-cert-name " ]" +.br +.RB "[" --comm-predicate " ]" +.br +.RB "[" --relay-predicate " ]" +.br +.RE +.SH INTRODUCTION +.P +This page documents the BadVPN server, which is used in a BadVPN VPN network by peers to +talk to each other in order to establish data connections. For a general description of +BadVPN, see +.BR badvpn (7). +.SH DESCRIPTION +.P +The BadVPN server is a chat server used by nodes in the VPN network to talk to each other +in order to establish data connections. Once it initializes, the server only terminates +if a signal is received. +.SH OPTIONS +.P +The BadVPN server is configured entirely from command line. +.TP +.BR --help +Print version and command line syntax and exit. +.TP +.BR --version +Print version and exit. +.TP +.BR --logger " " +Select where to log messages. Default is stdout. Syslog is not available on Windows. +.TP +.BR --syslog-facility " " +When logging to syslog, set the logging facility. The facility name must be in lower case. +.TP +.BR --syslog-ident " " +When logging to syslog, set the ident. +.TP +.BR --loglevel " <0-5/none/error/warning/notice/info/debug>" +Set the default logging level. +.TP +.BR --channel-loglevel " <0-5/none/error/warning/notice/info/debug>" +Set the logging level for a specific logging channel. +.TP +.BR --listen-addr " " +Add an address for the server to listen on. See below for address format. +.TP +.BR --ssl +Use TLS. Requires --nssdb and --server-cert-name. +.TP +.BR --nssdb " " +When using TLS, the NSS database to use. Probably something like sql:/some/folder. +.TP +.BR --server-cert-name " " +When using TLS, the name of the certificate to use. The certificate must be readily accessible. +.TP +.BR --comm-predicate " " +Set a predicate to define which pairs of clients are allowed to commnicate. The predicate is a +logical expression; see below for details. Available functions: +.br +.BR p1name "(string)" +- true if the TLS common name of peer 1 equals the given string. If TLS is not used, the common +name is assumed to be an empty string. +.br +.BR p1addr "(string)" +- true if the IP address of peer 1 equals the given string. The string must not be a name. +.br +.BR p2name "(string)" +- true if the TLS common name of peer 2 equals the given string. If TLS is not used, the common +name is assumed to be an empty string. +.br +.BR p2addr "(string)" +- true if the IP address of peer 2 equals the given string. The string must not be a name. +.br +There is no rule as to which is peer 1 and which peer 2. When the server needs to determine +whether to allow two peers to communicate, it evaluates the predicate once and in no specific order. +.TP +.BR --relay-predicate " " +Set a predicate to define how peers can relay data through other peers. The predicate is a +logical expression; see below for details. If the predicate evaluates to true, peer P can relay data +through peer R. Available functions: +.br +.BR pname "(string)" +- true if the TLS common name of peer P peer equals the given string. If TLS is not used, the common +name is assumed to be an empty string. +.br +.BR paddr "(string)" +- true if the IP address of peer P equals the given string. The string must not be a name. +.br +.BR rname "(string)" +- true if the TLS common name of peer R peer equals the given string. If TLS is not used, the common +name is assumed to be an empty string. +.br +.BR raddr "(string)" +- true if the IP address of peer R equals the given string. The string must not be a name. +.br +.SH "EXIT CODE" +.P +If initialization fails, exits with code 1. Otherwise runs until termination is requested and exits with code 1. +.SH "ADDRESS FORMAT" +.P +Addresses have the form ipaddr:port, where ipaddr is either an IPv4 address (name or numeric), or an +IPv6 address enclosed in brackets [] (name or numeric again). +.SH PREDICATES +.P +The BadVPN server includes a small predicate language used to define certain policies. +Syntax and semantics of the language are described here. +.TP +.BR true +Logical true constant. Evaluates to 1. +.TP +.BR false +Logical false constant. Evaluates to 0. +.TP +.BR NOT " expression" +Logical negation. If the expression evaluates to error, the +negation evaluates to error. +.TP +.RB "expression " OR " expression" +Logical disjunction. The second expression is only evaluated +if the first expression evaluates to false. If a sub-expression +evaluates to error, the disjunction evaluates to error. +.TP +.RB "expression " AND " expression" +Logical conjunction. The second expression is only evaluated +if the first expression evaluates to true. If a sub-expression +evaluates to error, the conjunction evaluates to error. +.TP +.RB function "(" "arg" "," " ..." "," " arg" ")" +Evaluation of a user-provided function (function is the name of the +function, [a-zA-Z0-9_]+). +If the function with the given name does not exist, it evaluates to +error. +Arguments are evaluated from left to right. Each argument can either +be a logical expression or a string (characters enclosed in double +quotes, without any double quote). +If an argument is encountered, but all needed arguments have already +been evaluated, the function evaluates to error. +If an argument is of wrong type, it is not evaluated and the function +evaluates to error. +If an argument evaluates to error, the function evaluates to error. +If after all arguments have been evaluated, the function needs more +arguments, it evaluates to error. +Then the handler function is called. If it returns anything other +than 1 and 0, the function evaluates to error. Otherwise it evaluates +to what the handler function returned. +.SH "EXAMPLES" +.P +For examples of using BadVPN, see +.BR badvpn (7). +.SH "SEE ALSO" +.BR badvpn-client (8), +.BR badvpn (7) +.SH COPYRIGHT +Copyright (C) 2010 Ambroz Bizjak. BadVPN is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 as published by the +Free Software Foundation. +.SH AUTHORS +Ambroz Bizjak diff --git a/badvpn.7 b/badvpn.7 new file mode 100644 index 000000000..5231b469d --- /dev/null +++ b/badvpn.7 @@ -0,0 +1,321 @@ +.TH badvpn 7 "6 October 2010" +.SH NAME +BadVPN - peer-to-peer VPN system +.SH DESCRIPTION +.P +BadVPN is a peer-to-peer VPN system. It provides a Layer 2 (Ethernet) network between +the peers (VPN network nodes). The peers connect to a central server which acts as a chat +server for them to establish direct connections between each other (data connections). +These connections are used for transferring network data (Ethernet frames). +.SS "Features" +.P +.B "Data connections" +.P +Peers can transfer network data either over UDP or TCP. For both there are ways of +securing the data (see below). +.P +.B "IPv6 support" +.P +IPv6 can be used for both server connections and data connections, alongside with IPv4. +Additionally, both can be combined to allow gradual migration to IPv6. +.P +.B "Address selection" +.P +Because NATs and firewalls are widespread, it is harder for peer-to-peer services to operate. +In general, for two computers to be able to communicate, one computer must +.I bind +to one of its addresses, and the other computer must +.I connect +to the computer that binded (both for TCP and UDP). In a network with point-to-point +connectivity, the connecting computer can connect to the same address as the binding computer +bound to, so it is sufficient for the binding computer to send its address to the connecting +computer. However, NATs and firewalls break point-to-point connectivity. When a network is +behind a NAT, it is, by default, impossible for computers outside of that network to connect +to computers inside the network. This is because computers inside the network have no externally +visible IP address, and only communicate with the outside world through the external IP address +of the NAT router. It is however possible to manually configure the NAT router to +.I forward +a specific port number on its external IP address to a specific computer inside the network. +This makes it possible for a computer outside of the network to connect to a computer inside +a network, however, it must connect to the external address of the NAT router (rather than +the address the computer inside bound to, which is its internal address). So there needs +to be some way for the connecting peer to know what address to connect to. +.P +BadVPN solves this problem with so-called +.IR "address scopes" "." +The peer that binds must have a list of external addresses for each address it can bind to, +possibly ordered from best to worst. Each external address has its scope name. A scope name +represents part of a network from which an external address can be reached. On the other hand, +the peer that connects must have a list of scopes which it can reach. When a peer binds to an +address, it sends the other peer a list of external addresses along with scope names. That peer +than chooses the first external address whose scope it recognizes and attempts to connect to it +(if there is one). +.P +BadVPN also allows a peer to have multiple addresses for binding to. It is possible to specify +both an IPv4 and an IPv6 address to work in a multi-protocol environment. +.P +.B "Relaying" +.P +BadVPN can be configured to allow pairs of peers that cannot communicate directly (i.e. because of +NATs or firewalls) to relay network data through a third peer. Relaying is only attempted if +none of the two peers recognize any of the other peer's external addresses (or there are none). +For relaying to work, for each of the two peers (P1, other one P2) there must be at least one +third peer (R) that P1 it is allowed to relay through and can communicate directly with, and all +such peers R must be able to communicate directly with P2. +.P +.B "IGMP snooping" +.P +BadVPN nodes perform IGMP snooping in order to efficiently deliver multicast frames. For example, +this makes it possible to use BadVPN as a tunnel into an IPTV network of an Internet Service Provider +for you to watch TV from wherever you want (given sufficient link quality). +.P +.B "Code quality" +.P +BadVPN has great focus on code quality and reliability. BadVPN is written in the C programming +language. It is a single-threaded event-driven program. This allows for low resource usage and +fast response times. Even though C is a relatively low-level language, the programs are made of +small, highly cohesive and loosely coupled modules that are combined into a complete program on +a high level. Modules are accesed and communicate through small, simple and to-the-point interfaces. +It utilizes a flow-based design which greatly simplifies processing of data and input and output +of the programs. +.SS "Security features" +.P +BadVPN contains many security features, all of which are optional. The included security +features are described here. +.P +.B TLS for client-server connections +.P +It is possible for the peers to communicate with the chat server securely with TLS. It is +highly recommended that this feature is used if any security whatsoever is needed. Not +using it renders all other security features useless, since clients exchange keys +unencrypted via the server. When enabled, the chat server requires each client to identify +itself with a certificate. +.P +BadVPN uses Mozilla's NSS library for TLS support. This means that the required certificates +and keys must be available in a NSS database. The database and certificates can be +generated with the +.B certutil +command. See the examples section on how to generate and distribute the certificates. +.P +.B TLS for TCP data connections +.P +If TCP is used for data connections between the peers, the data connections can be secured +with TLS. This requires using TLS for client-server connections. The clients need to trust +each others' certificates to be able to connect. Additionally, each client must identify to +its peers with the same certificates it used for connecting to the server. +.P +.B Encryption for UDP data connections +.P +If UDP is used for data connections, it is possible for each pair of peers to encrypt their +UDP packets with a symmetric block cipher. Note that the encryption keys are transmitted +through the server unencrypted, so for this to be useful, server connections must be secured +with TLS. The encryption aims to prevent third parties from seeing the real contents of +the network data being transfered. +.P +.B Hashes for UDP data connections +.P +If UDP is used for data connections, it is possible to include hashes in packets. Note that +hashes are only useful together with encryption. If enabled, the hash is calculated on the +packet with the hash field zeroed and then written to the hash field. Hashes are calculated +and included before encryption (if enabled). Combined with encryption, hashes aim to prevent +third parties from tampering with the packets and injecting them into the network. +.P +.B One-time passwords for UDP data connections +.P +If UDP is used for data connections, it is possible to include one-time passwords in packets. +Note that for this to be useful, server connections must be secured with TLS. +One-time passwords are generated from a seed value by encrypting zero data with a block cipher. +The seed contains the encryption key for the block cipher and the initialization vector. +Only a fixed number of passwords are used from a single seed. The peers exchange seeds through +the server. One-time passwords aim to prevent replay attacks. +.P +.B Control over peer communication +.P +It is possible to instruct the chat server to only allow certain peers to communicate. This +will break end-to-end connectivity in the virtual network. It is useful in certain cases +to improve security, for example when the VPN is used only to allow clients to securely connect +to a central service. +.SH "EXAMPLES" +.SS "Setting up certificates" +.P +If you want to use TLS for server connections (recommended), the server and all the peers will +need certificates. This section explains how to generate and distribute the certificates using +NSS command line tools. +.P +.B Setting up the Certificate Authority (CA) +.P +On the system that will host the CA, create a NSS database for the CA and generate a CA certificate +valid for 24 months: +.P +vpnca $ certutil -d sql:/home/vpnca/nssdb -N +.br +vpnca $ certutil -d sql:/home/vpnca/nssdb -S -n "vpnca" -s "CN=vpnca" -t "TC,," -x -2 -v 24 +.br +> Is this a CA certificate [y/N]? y +.br +> Enter the path length constraint, enter to skip [<0 for unlimited path]: > -1 +.br +> Is this a critical extension [y/N]? n +.P +Export the public CA certificate (this file is public): +.P +vpnca $ certutil -d sql:/home/vpnca/nssdb -L -n vpnca -a > ca.pem +.P +.B Setting up the server certificate +.P +On the CA system, generate a certificate for the server valid for 24 months, with TLS server usage context: +.P +vpnca $ certutil -d sql:/home/vpnca/nssdb -S -n "" -s "CN=" -c "vpnca" -t ",," -2 -6 -v 24 +.br +> 0 +.br +> -1 +.br +> Is this a critical extension [y/N]? n +.br +> Is this a CA certificate [y/N]? n +.br +> Enter the path length constraint, enter to skip [<0 for unlimited path]: > +.br +> Is this a critical extension [y/N]? n +.P +Export the server certificate to a PKCS#12 file (this file must be kept secret): +.P +vpnca $ pk12util -d sql:/home/vpnca/nssdb -o server.p12 -n "" +.P +On the system that will run the server, create a NSS database and import the CA certificate +and the server cerificate: +.P +vpnserver $ certutil -d sql:/home/vpnserver/nssdb -N +.br +vpnserver $ certutil -d sql:/home/vpnserver/nssdb -A -t "CT,," -n "vpnca" -i /path/to/ca.pem +.br +vpnserver $ pk12util -d sql:/home/vpnserver/nssdb -i /path/to/server.p12 +.P +.B Setting up peer certificates +.P +On the CA system, generate a certificate for the peer valid for 24 months, with TLS client and +TLS server usage contexts: +.P +vpnca $ certutil -d sql:/home/vpnca/nssdb -S -n "peer-" -s "CN=peer-" -c "vpnca" -t ",," -2 -6 -v 24 +.br +> 0 +.br +> 1 +.br +> -1 +.br +> Is this a critical extension [y/N]? n +.br +> Is this a CA certificate [y/N]? n +.br +> Enter the path length constraint, enter to skip [<0 for unlimited path]: > +.br +> Is this a critical extension [y/N]? n +.P +Export the peer certificate to a PKCS#12 file (this file must be kept secret): +.P +vpnca $ pk12util -d sql:/home/vpnca/nssdb -o peer-.p12 -n "peer-" +.P +On the system that will run the VPN client, create a NSS database and import the CA certificate +and the peer cerificate: +.P +vpnclient $ certutil -d sql:/home/vpnclient/nssdb -N +.br +vpnclient $ certutil -d sql:/home/vpnclient/nssdb -A -t "CT,," -n "vpnca" -i /path/to/ca.pem +.br +vpnclient $ pk12util -d sql:/home/vpnclient/nssdb -i /path/to/peer-.p12 +.SS "Setting up TAP devices" +.P +You need to create and configure TAP devices on all computers that will participate in the virtual network +(i.e. run the client program). See +.BR badvpn-client (8), +section `TAP DEVICE CONFIGURATION` for details. +.SS "Example: Local IPv4 network, UDP transport, zero security" +.P +.B Starting the server: +.P +badvpn-server --listen-addr 0.0.0.0:7000 +.P +.B Starting the peers: +.P +badvpn-client +.RS +--server-addr :7000 +.br +--transport-mode udp --encryption-mode none --hash-mode none +.br +--scope local1 +.br +--bind-addr 0.0.0.0:8000 --num-ports 30 --ext-addr {server_reported}:8000 local1 +.br +--tapdev tap0 +.RE +.SS "Example: Adding TLS and UDP security" +.P +.B Starting the server (other options as above): +.P +badvpn-server ... +.RS +--ssl --nssdb sql:/home/vpnserver/nssdb --server-cert-name "" +.RE +.P +.B Starting the peers (other options as above): +.P +badvpn-client ... +.RS +--ssl --nssdb sql:/home/vpnclient/nssdb --client-cert-name "peer-" +.br +--encryption-mode blowfish --hash-mode md5 --otp blowfish 3000 2000 +.RE +.SS "Example: Multiple local networks behind NATs, all connected to the Internet" +.P +For each peer in the existing local network, configure the NAT router to forward its +range of ports to it (assuming their port ranges do not overlap). The clients also need +to know the external IP address of the NAT router. If you don't have a static one, +you'll need to discover it before starting the clients. Also forward the server port to +the server. +.P +.B Starting the peers in the local network (other options as above): +.P +badvpn-client +.RS +.RB "..." +.br +--scope internet +.br +.RB "..." +.br +--ext-addr : internet +.br +.RB "..." +.RE +.P +The --ext-addr option applies to the previously specified --bind-addr option, and must come after +the first --ext-addr option which specifies a local address. +.P +Now perform a similar setup in some other local network behind a NAT. However: +.br +- Don't set up a new server, instead make the peers connect to the existing server in the first +local network. +.br +- You can't use {server_reported} for the local address --ext-addr options, because the server +would report the NAT router's external address rather than the peer's internal address. Instead +each peer has to know its internal IP address. +.br +- Use a different scope name for it, e.g. "local2" instead of "local1". +.P +If setup correctly, all peers will be able to communicate: those in the same local network will +communicate directly through local addresses, and those in different local networks will +communicate through the Internet. +.SH "PROTOCOL" +The protocols used in BadVPN are described in the source code in the protocol/ directory. +.SH "SEE ALSO" +.BR badvpn-server (8), +.BR badvpn-client (8) +.SH COPYRIGHT +Copyright (C) 2010 Ambroz Bizjak. BadVPN is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 as published by the +Free Software Foundation. +.SH AUTHORS +Ambroz Bizjak diff --git a/blog_channels.txt b/blog_channels.txt new file mode 100644 index 000000000..939a7853a --- /dev/null +++ b/blog_channels.txt @@ -0,0 +1,12 @@ +server 4 +client 4 +StreamPeerIO 4 +DatagramPeerIO 4 +BReactor 3 +BSignal 3 +FragmentProtoAssembler 4 +BPredicate 3 +ServerConnection 4 +flooder 4 +Listener 4 +DataProto 4 diff --git a/blog_generator/blog.php b/blog_generator/blog.php new file mode 100644 index 000000000..4b565e70e --- /dev/null +++ b/blog_generator/blog.php @@ -0,0 +1,121 @@ + Input channels file. + --output-dir Destination directory for generated files. + +EOD; +} + +$input_file = ""; +$output_dir = ""; + +for ($i = 1; $i < $argc;) { + $arg = $argv[$i++]; + switch ($arg) { + case "--input-file": + $input_file = $argv[$i++]; + break; + case "--output-dir": + $output_dir = $argv[$i++]; + break; + case "--help": + print_help($argv[0]); + exit(0); + default: + fatal_error("Unknown option: {$arg}"); + } +} + +if ($input_file == "") { + fatal_error("--input-file missing"); +} + +if ($output_dir == "") { + fatal_error("--output-dir missing"); +} + +if (($data = file_get_contents($input_file)) === FALSE) { + fatal_error("Failed to read input file"); +} + +if (!tokenize($data, $tokens)) { + fatal_error("Failed to tokenize"); +} + +$i = 0; +$channels_defines = ""; +$channels_list = ""; + +reset($tokens); + +while (1) { + if (($ch_name = current($tokens)) === FALSE) { + break; + } + next($tokens); + if (($ch_priority = current($tokens)) === FALSE) { + fatal_error("missing priority"); + } + next($tokens); + if ($ch_name[0] != "name") { + fatal_error("name is not a name"); + } + if ($ch_priority[0] != "number") { + fatal_error("priority is not a number"); + } + + $channel_file = << 0) { + if (preg_match('/^\\/\\/.*/', $str, $matches)) { + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^\\s+/', $str, $matches)) { + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^[0-9]+/', $str, $matches)) { + $out[] = array('number', $matches[0]); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*/', $str, $matches)) { + $out[] = array('name', $matches[0]); + $str = substr($str, strlen($matches[0])); + } + else { + return FALSE; + } + } + + return TRUE; +} + +function fatal_error ($message) +{ + fwrite(STDERR, "Fatal error: $message\n"); + + ob_get_clean(); + exit(1); +} diff --git a/bproto/BProto.h b/bproto/BProto.h new file mode 100644 index 000000000..347df37c5 --- /dev/null +++ b/bproto/BProto.h @@ -0,0 +1,64 @@ +/** + * @file BProto.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Definitions for BProto serialization. + */ + +#ifndef BADVPN_BPROTO_BPROTO_H +#define BADVPN_BPROTO_BPROTO_H + +#include + +#define BPROTO_TYPE_UINT8 1 +#define BPROTO_TYPE_UINT16 2 +#define BPROTO_TYPE_UINT32 3 +#define BPROTO_TYPE_UINT64 4 +#define BPROTO_TYPE_DATA 5 +#define BPROTO_TYPE_CONSTDATA 6 + +struct BProto_header_s { + uint16_t id; + uint16_t type; +} __attribute__((packed)); + +struct BProto_uint8_s { + uint8_t v; +} __attribute__((packed)); + +struct BProto_uint16_s { + uint16_t v; +} __attribute__((packed)); + +struct BProto_uint32_s { + uint32_t v; +} __attribute__((packed)); + +struct BProto_uint64_s { + uint64_t v; +} __attribute__((packed)); + +struct BProto_data_header_s { + uint32_t len; +} __attribute__((packed)); + +#endif diff --git a/bproto_generator/ProtoParser.lime b/bproto_generator/ProtoParser.lime new file mode 100644 index 000000000..f039e1d34 --- /dev/null +++ b/bproto_generator/ProtoParser.lime @@ -0,0 +1,99 @@ +%class ProtoParser +%start file + +file = + directives messages { + $$ = array( + "directives" => $1, + "messages" => $2 + ); + }. + +directives = + { + $$ = array(); + } | + directive semicolon directives { + $$ = array_merge(array($1), $3); + }. + +directive = + include string { + $$ = array( + "type" => "include", + "file" => $2 + ); + }. + +messages = + msgspec { + $$ = array($1); + } | + msgspec messages { + $$ = array_merge(array($1), $2); + }. + +msgspec = + message name spar entries epar semicolon { + $$ = array( + "name" => $2, + "entries" => $4 + ); +}. + +entries = + entry { + $$ = array($1); + } | + entry entries { + $$ = array_merge(array($1), $2); + }. + +entry = + cardinality type name equals number semicolon { + $$ = array( + "cardinality" => $1, + "type" => $2, + "name" => $3, + "id" => $5 + ); + }. + +cardinality = + repeated { + $$ = "repeated"; + } | + optional { + $$ = "optional"; + } | + required { + $$ = "required"; + } | + required repeated { + $$ = "required repeated"; + }. + +type = + uint { + $$ = array( + "type" => "uint", + "size" => $1 + ); + } | + data { + $$ = array( + "type" => "data" + ); + } | + data srpar string erpar { + $$ = array( + "type" => "constdata", + "size" => $3 + ); + } | + message name { + $$ = array( + "type" => "message", + "message" => $2 + ); + }. diff --git a/bproto_generator/ProtoParser.php b/bproto_generator/ProtoParser.php new file mode 100644 index 000000000..0477dbaf2 --- /dev/null +++ b/bproto_generator/ProtoParser.php @@ -0,0 +1,560 @@ + + array ( + 'directives' => 's 1', + 'directive' => 's 30', + 'include' => 's 33', + 'file' => 's 35', + '\'start\'' => 'a \'start\'', + 'message' => 'r 1', + ), + 1 => + array ( + 'messages' => 's 2', + 'msgspec' => 's 3', + 'message' => 's 5', + ), + 2 => + array ( + '#' => 'r 0', + ), + 3 => + array ( + 'msgspec' => 's 3', + 'messages' => 's 4', + 'message' => 's 5', + '#' => 'r 4', + ), + 4 => + array ( + '#' => 'r 5', + ), + 5 => + array ( + 'name' => 's 6', + ), + 6 => + array ( + 'spar' => 's 7', + ), + 7 => + array ( + 'entries' => 's 8', + 'entry' => 's 11', + 'cardinality' => 's 13', + 'repeated' => 's 26', + 'optional' => 's 27', + 'required' => 's 28', + ), + 8 => + array ( + 'epar' => 's 9', + ), + 9 => + array ( + 'semicolon' => 's 10', + ), + 10 => + array ( + 'message' => 'r 6', + '#' => 'r 6', + ), + 11 => + array ( + 'entry' => 's 11', + 'entries' => 's 12', + 'cardinality' => 's 13', + 'repeated' => 's 26', + 'optional' => 's 27', + 'required' => 's 28', + 'epar' => 'r 7', + ), + 12 => + array ( + 'epar' => 'r 8', + ), + 13 => + array ( + 'type' => 's 14', + 'uint' => 's 19', + 'data' => 's 20', + 'message' => 's 24', + ), + 14 => + array ( + 'name' => 's 15', + ), + 15 => + array ( + 'equals' => 's 16', + ), + 16 => + array ( + 'number' => 's 17', + ), + 17 => + array ( + 'semicolon' => 's 18', + ), + 18 => + array ( + 'repeated' => 'r 9', + 'optional' => 'r 9', + 'required' => 'r 9', + 'epar' => 'r 9', + ), + 19 => + array ( + 'name' => 'r 14', + ), + 20 => + array ( + 'srpar' => 's 21', + 'name' => 'r 15', + ), + 21 => + array ( + 'string' => 's 22', + ), + 22 => + array ( + 'erpar' => 's 23', + ), + 23 => + array ( + 'name' => 'r 16', + ), + 24 => + array ( + 'name' => 's 25', + ), + 25 => + array ( + 'name' => 'r 17', + ), + 26 => + array ( + 'uint' => 'r 10', + 'data' => 'r 10', + 'message' => 'r 10', + ), + 27 => + array ( + 'uint' => 'r 11', + 'data' => 'r 11', + 'message' => 'r 11', + ), + 28 => + array ( + 'repeated' => 's 29', + 'uint' => 'r 12', + 'data' => 'r 12', + 'message' => 'r 12', + ), + 29 => + array ( + 'uint' => 'r 13', + 'data' => 'r 13', + 'message' => 'r 13', + ), + 30 => + array ( + 'semicolon' => 's 31', + ), + 31 => + array ( + 'directive' => 's 30', + 'directives' => 's 32', + 'include' => 's 33', + 'message' => 'r 1', + ), + 32 => + array ( + 'message' => 'r 2', + ), + 33 => + array ( + 'string' => 's 34', + ), + 34 => + array ( + 'semicolon' => 'r 3', + ), + 35 => + array ( + '#' => 'r 18', + ), +); +function reduce_0_file_1($tokens, &$result) { +# +# (0) file := directives messages +# +$result = reset($tokens); + + $result = array( + "directives" => $tokens[0], + "messages" => $tokens[1] + ); + +} + +function reduce_1_directives_1($tokens, &$result) { +# +# (1) directives := +# +$result = reset($tokens); + + $result = array(); + +} + +function reduce_2_directives_2($tokens, &$result) { +# +# (2) directives := directive semicolon directives +# +$result = reset($tokens); + + $result = array_merge(array($tokens[0]), $tokens[2]); + +} + +function reduce_3_directive_1($tokens, &$result) { +# +# (3) directive := include string +# +$result = reset($tokens); + + $result = array( + "type" => "include", + "file" => $tokens[1] + ); + +} + +function reduce_4_messages_1($tokens, &$result) { +# +# (4) messages := msgspec +# +$result = reset($tokens); + + $result = array($tokens[0]); + +} + +function reduce_5_messages_2($tokens, &$result) { +# +# (5) messages := msgspec messages +# +$result = reset($tokens); + + $result = array_merge(array($tokens[0]), $tokens[1]); + +} + +function reduce_6_msgspec_1($tokens, &$result) { +# +# (6) msgspec := message name spar entries epar semicolon +# +$result = reset($tokens); + + $result = array( + "name" => $tokens[1], + "entries" => $tokens[3] + ); + +} + +function reduce_7_entries_1($tokens, &$result) { +# +# (7) entries := entry +# +$result = reset($tokens); + + $result = array($tokens[0]); + +} + +function reduce_8_entries_2($tokens, &$result) { +# +# (8) entries := entry entries +# +$result = reset($tokens); + + $result = array_merge(array($tokens[0]), $tokens[1]); + +} + +function reduce_9_entry_1($tokens, &$result) { +# +# (9) entry := cardinality type name equals number semicolon +# +$result = reset($tokens); + + $result = array( + "cardinality" => $tokens[0], + "type" => $tokens[1], + "name" => $tokens[2], + "id" => $tokens[4] + ); + +} + +function reduce_10_cardinality_1($tokens, &$result) { +# +# (10) cardinality := repeated +# +$result = reset($tokens); + + $result = "repeated"; + +} + +function reduce_11_cardinality_2($tokens, &$result) { +# +# (11) cardinality := optional +# +$result = reset($tokens); + + $result = "optional"; + +} + +function reduce_12_cardinality_3($tokens, &$result) { +# +# (12) cardinality := required +# +$result = reset($tokens); + + $result = "required"; + +} + +function reduce_13_cardinality_4($tokens, &$result) { +# +# (13) cardinality := required repeated +# +$result = reset($tokens); + + $result = "required repeated"; + +} + +function reduce_14_type_1($tokens, &$result) { +# +# (14) type := uint +# +$result = reset($tokens); + + $result = array( + "type" => "uint", + "size" => $tokens[0] + ); + +} + +function reduce_15_type_2($tokens, &$result) { +# +# (15) type := data +# +$result = reset($tokens); + + $result = array( + "type" => "data" + ); + +} + +function reduce_16_type_3($tokens, &$result) { +# +# (16) type := data srpar string erpar +# +$result = reset($tokens); + + $result = array( + "type" => "constdata", + "size" => $tokens[2] + ); + +} + +function reduce_17_type_4($tokens, &$result) { +# +# (17) type := message name +# +$result = reset($tokens); + + $result = array( + "type" => "message", + "message" => $tokens[1] + ); + +} + +function reduce_18_start_1($tokens, &$result) { +# +# (18) 'start' := file +# +$result = reset($tokens); + +} + +var $method = array ( + 0 => 'reduce_0_file_1', + 1 => 'reduce_1_directives_1', + 2 => 'reduce_2_directives_2', + 3 => 'reduce_3_directive_1', + 4 => 'reduce_4_messages_1', + 5 => 'reduce_5_messages_2', + 6 => 'reduce_6_msgspec_1', + 7 => 'reduce_7_entries_1', + 8 => 'reduce_8_entries_2', + 9 => 'reduce_9_entry_1', + 10 => 'reduce_10_cardinality_1', + 11 => 'reduce_11_cardinality_2', + 12 => 'reduce_12_cardinality_3', + 13 => 'reduce_13_cardinality_4', + 14 => 'reduce_14_type_1', + 15 => 'reduce_15_type_2', + 16 => 'reduce_16_type_3', + 17 => 'reduce_17_type_4', + 18 => 'reduce_18_start_1', +); +var $a = array ( + 0 => + array ( + 'symbol' => 'file', + 'len' => 2, + 'replace' => true, + ), + 1 => + array ( + 'symbol' => 'directives', + 'len' => 0, + 'replace' => true, + ), + 2 => + array ( + 'symbol' => 'directives', + 'len' => 3, + 'replace' => true, + ), + 3 => + array ( + 'symbol' => 'directive', + 'len' => 2, + 'replace' => true, + ), + 4 => + array ( + 'symbol' => 'messages', + 'len' => 1, + 'replace' => true, + ), + 5 => + array ( + 'symbol' => 'messages', + 'len' => 2, + 'replace' => true, + ), + 6 => + array ( + 'symbol' => 'msgspec', + 'len' => 6, + 'replace' => true, + ), + 7 => + array ( + 'symbol' => 'entries', + 'len' => 1, + 'replace' => true, + ), + 8 => + array ( + 'symbol' => 'entries', + 'len' => 2, + 'replace' => true, + ), + 9 => + array ( + 'symbol' => 'entry', + 'len' => 6, + 'replace' => true, + ), + 10 => + array ( + 'symbol' => 'cardinality', + 'len' => 1, + 'replace' => true, + ), + 11 => + array ( + 'symbol' => 'cardinality', + 'len' => 1, + 'replace' => true, + ), + 12 => + array ( + 'symbol' => 'cardinality', + 'len' => 1, + 'replace' => true, + ), + 13 => + array ( + 'symbol' => 'cardinality', + 'len' => 2, + 'replace' => true, + ), + 14 => + array ( + 'symbol' => 'type', + 'len' => 1, + 'replace' => true, + ), + 15 => + array ( + 'symbol' => 'type', + 'len' => 1, + 'replace' => true, + ), + 16 => + array ( + 'symbol' => 'type', + 'len' => 4, + 'replace' => true, + ), + 17 => + array ( + 'symbol' => 'type', + 'len' => 2, + 'replace' => true, + ), + 18 => + array ( + 'symbol' => '\'start\'', + 'len' => 1, + 'replace' => true, + ), +); +} diff --git a/bproto_generator/bproto.php b/bproto_generator/bproto.php new file mode 100644 index 000000000..c86c365d6 --- /dev/null +++ b/bproto_generator/bproto.php @@ -0,0 +1,106 @@ + Output file prefix. + --input-file Message file to generate source for. + --output-dir Destination directory for generated files. + +EOD; +} + +$name = ""; +$input_file = ""; +$output_dir = ""; + +for ($i = 1; $i < $argc;) { + $arg = $argv[$i++]; + switch ($arg) { + case "--name": + $name = $argv[$i++]; + break; + case "--input-file": + $input_file = $argv[$i++]; + break; + case "--output-dir": + $output_dir = $argv[$i++]; + break; + case "--help": + print_help($argv[0]); + exit(0); + default: + fatal_error("Unknown option: {$arg}"); + } +} + +if ($name == "") { + fatal_error("--name missing"); +} + +if ($input_file == "") { + fatal_error("--input-file missing"); +} + +if ($output_dir == "") { + fatal_error("--output-dir missing"); +} + +if (($data = file_get_contents($input_file)) === FALSE) { + fatal_error("Failed to read input file"); +} + +if (!tokenize($data, $tokens)) { + fatal_error("Failed to tokenize"); +} + +$parser = new parse_engine(new ProtoParser()); + +try { + foreach ($tokens as $token) { + $parser->eat($token[0], $token[1]); + } + $parser->eat_eof(); +} catch (parse_error $e) { + fatal_error("$input_file: Parse error: ".$e->getMessage()); +} + +$data = generate_header($name, $parser->semantic["directives"], $parser->semantic["messages"]); +if (file_put_contents("{$output_dir}/{$name}.h", $data) === NULL) { + fatal_error("{$input_file}: Failed to write .h file"); +} diff --git a/bproto_generator/bproto_functions.php b/bproto_generator/bproto_functions.php new file mode 100644 index 000000000..e8686a9e8 --- /dev/null +++ b/bproto_generator/bproto_functions.php @@ -0,0 +1,748 @@ + 0) { + if (preg_match('/^\\/\\/.*/', $str, $matches)) { + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^\\s+/', $str, $matches)) { + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^include/', $str, $matches)) { + $out[] = array('include', null); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^message/', $str, $matches)) { + $out[] = array('message', null); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^repeated/', $str, $matches)) { + $out[] = array('repeated', null); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^required/', $str, $matches)) { + $out[] = array('required', null); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^optional/', $str, $matches)) { + $out[] = array('optional', null); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^{/', $str, $matches)) { + $out[] = array('spar', null); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^}/', $str, $matches)) { + $out[] = array('epar', null); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^\(/', $str, $matches)) { + $out[] = array('srpar', null); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^\)/', $str, $matches)) { + $out[] = array('erpar', null); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^=/', $str, $matches)) { + $out[] = array('equals', null); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^;/', $str, $matches)) { + $out[] = array('semicolon', null); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^uint(8|16|32|64)/', $str, $matches)) { + $out[] = array('uint', $matches[1]); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^data/', $str, $matches)) { + $out[] = array('data', null); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^[0-9]+/', $str, $matches)) { + $out[] = array('number', $matches[0]); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*/', $str, $matches)) { + $out[] = array('name', $matches[0]); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^"([^"]*)"/', $str, $matches)) { + $out[] = array('string', $matches[1]); + $str = substr($str, strlen($matches[0])); + } + else { + return FALSE; + } + } + + return TRUE; +} + +function fatal_error ($message) +{ + fwrite(STDERR, "Fatal error: $message\n"); + + ob_get_clean(); + exit(1); +} + +function make_writer_decl ($msg, $entry) +{ + switch ($entry["type"]["type"]) { + case "uint": + return "void {$msg["name"]}Writer_Add{$entry["name"]} ({$msg["name"]}Writer *o, uint{$entry["type"]["size"]}_t v)"; + case "data": + return "uint8_t * {$msg["name"]}Writer_Add{$entry["name"]} ({$msg["name"]}Writer *o, int len)"; + case "constdata": + return "uint8_t * {$msg["name"]}Writer_Add{$entry["name"]} ({$msg["name"]}Writer *o)"; + default: + assert(0); + } +} + +function make_parser_decl ($msg, $entry) +{ + switch ($entry["type"]["type"]) { + case "uint": + return "int {$msg["name"]}Parser_Get{$entry["name"]} ({$msg["name"]}Parser *o, uint{$entry["type"]["size"]}_t *v)"; + case "data": + return "int {$msg["name"]}Parser_Get{$entry["name"]} ({$msg["name"]}Parser *o, uint8_t **data, int *data_len)"; + case "constdata": + return "int {$msg["name"]}Parser_Get{$entry["name"]} ({$msg["name"]}Parser *o, uint8_t **data)"; + default: + assert(0); + } +} + +function make_parser_reset_decl ($msg, $entry) +{ + return "void {$msg["name"]}Parser_Reset{$entry["name"]} ({$msg["name"]}Parser *o)"; +} + +function make_parser_forward_decl ($msg, $entry) +{ + return "void {$msg["name"]}Parser_Forward{$entry["name"]} ({$msg["name"]}Parser *o)"; +} + +function make_type_name ($msg, $entry) +{ + switch ($entry["type"]["type"]) { + case "uint": + return "BPROTO_TYPE_UINT{$entry["type"]["size"]}"; + case "data": + return "BPROTO_TYPE_DATA"; + case "constdata": + return "BPROTO_TYPE_CONSTDATA"; + default: + assert(0); + } +} + +function make_finish_assert ($msg, $entry) +{ + switch ($entry["cardinality"]) { + case "repeated": + return "ASSERT(o->{$entry["name"]}_count >= 0)"; + case "required repeated": + return "ASSERT(o->{$entry["name"]}_count >= 1)"; + case "optional": + return "ASSERT(o->{$entry["name"]}_count >= 0 && o->{$entry["name"]}_count <= 1)"; + case "required": + return "ASSERT(o->{$entry["name"]}_count == 1)"; + default: + assert(0); + } +} + +function make_add_count_assert ($msg, $entry) +{ + if (in_array($entry["cardinality"], array("optional", "required"))) { + return "ASSERT(o->{$entry["name"]}_count == 0)"; + } + return ""; +} + +function make_add_length_assert ($msg, $entry) +{ + if ($entry["type"]["type"] == "data") { + return "ASSERT(len >= 0 && len <= UINT32_MAX)"; + } + return ""; +} + +function make_size_define ($msg, $entry) +{ + switch ($entry["type"]["type"]) { + case "uint": + return "#define {$msg["name"]}_SIZE{$entry["name"]} (sizeof(struct BProto_header_s) + sizeof(struct BProto_uint{$entry["type"]["size"]}_s))"; + case "data": + return "#define {$msg["name"]}_SIZE{$entry["name"]}(_len) (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (_len))"; + case "constdata": + return "#define {$msg["name"]}_SIZE{$entry["name"]} (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + ({$entry["type"]["size"]}))"; + default: + assert(0); + } +} + +function generate_header ($name, $directives, $messages) { + ob_start(); + + echo << + +#include +#include +#include + + +EOD; + + foreach ($directives as $directive) { + if ($directive["type"] == "include") { + echo <<out = out; + o->used = 0; + +EOD; + + foreach ($msg["entries"] as $entry) { + echo <<{$entry["name"]}_count = 0; + +EOD; + } + + echo <<used >= 0) + +EOD; + + foreach ($msg["entries"] as $entry) { + $ass = make_finish_assert($msg, $entry); + echo <<used; +} + + +EOD; + + foreach ($msg["entries"] as $entry) { + $decl = make_writer_decl($msg, $entry); + $type = make_type_name($msg, $entry); + $add_count_assert = make_add_count_assert($msg, $entry); + $add_length_assert = make_add_length_assert($msg, $entry); + + echo <<used >= 0) + {$add_count_assert} + {$add_length_assert} + + ((struct BProto_header_s *)(o->out + o->used))->id = htol16({$entry["id"]}); + ((struct BProto_header_s *)(o->out + o->used))->type = htol16({$type}); + o->used += sizeof(struct BProto_header_s); + + +EOD; + switch ($entry["type"]["type"]) { + case "uint": + echo <<out + o->used))->v = htol{$entry["type"]["size"]}(v); + o->used += sizeof(struct BProto_uint{$entry["type"]["size"]}_s); + +EOD; + break; + case "data": + echo <<out + o->used))->len = htol32(len); + o->used += sizeof(struct BProto_data_header_s); + + uint8_t *dest = (o->out + o->used); + o->used += len; + +EOD; + break; + case "constdata": + echo <<out + o->used))->len = htol32({$entry["type"]["size"]}); + o->used += sizeof(struct BProto_data_header_s); + + uint8_t *dest = (o->out + o->used); + o->used += ({$entry["type"]["size"]}); + +EOD; + break; + default: + assert(0); + } + + echo <<{$entry["name"]}_count++; + +EOD; + if (in_array($entry["type"]["type"], array("data", "constdata"))) { + echo <<= 0) + + o->buf = buf; + o->buf_len = buf_len; + +EOD; + + foreach ($msg["entries"] as $entry) { + echo <<{$entry["name"]}_start = o->buf_len; + o->{$entry["name"]}_span = 0; + o->{$entry["name"]}_pos = 0; + +EOD; + } + + echo <<buf_len; + + while (left > 0) { + int entry_pos = pos; + + if (!(left >= sizeof(struct BProto_header_s))) { + return 0; + } + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + pos); + pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + +EOD; + + foreach (array(8, 16, 32, 64) as $bits) { + echo <<= sizeof(struct BProto_uint{$bits}_s))) { + return 0; + } + struct BProto_uint{$bits}_s *val = (struct BProto_uint{$bits}_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint{$bits}_s); + left -= sizeof(struct BProto_uint{$bits}_s); + + switch (id) { + +EOD; + + foreach ($msg["entries"] as $entry) { + if (!($entry["type"]["type"] == "uint" && $entry["type"]["size"] == $bits)) { + continue; + } + $type = make_type_name($msg, $entry); + echo <<{$entry["name"]}_start == o->buf_len) { + o->{$entry["name"]}_start = entry_pos; + } + o->{$entry["name"]}_span = pos - o->{$entry["name"]}_start; + {$entry["name"]}_count++; + break; + +EOD; + } + + echo <<= sizeof(struct BProto_data_header_s))) { + return 0; + } + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + pos); + pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + if (!(left >= payload_len)) { + return 0; + } + pos += payload_len; + left -= payload_len; + + switch (id) { + +EOD; + + foreach ($msg["entries"] as $entry) { + if (!in_array($entry["type"]["type"], array("data", "constdata"))) { + continue; + } + $type = make_type_name($msg, $entry); + echo <<{$entry["name"]}_start == o->buf_len) { + o->{$entry["name"]}_start = entry_pos; + } + o->{$entry["name"]}_span = pos - o->{$entry["name"]}_start; + {$entry["name"]}_count++; + break; + +EOD; + } + + echo <<= 1"; + break; + case "optional": + $cond = "{$entry["name"]}_count <= 1"; + break; + case "required": + $cond = "{$entry["name"]}_count == 1"; + break; + default: + assert(0); + } + if ($cond) { + echo <<{$entry["name"]}_pos == o->{$entry["name"]}_span + +EOD; + } + + + echo <<{$entry["name"]}_pos >= 0) + ASSERT(o->{$entry["name"]}_pos <= o->{$entry["name"]}_span) + + int left = o->{$entry["name"]}_span - o->{$entry["name"]}_pos; + + while (left > 0) { + ASSERT(left >= sizeof(struct BProto_header_s)) + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + o->{$entry["name"]}_start + o->{$entry["name"]}_pos); + o->{$entry["name"]}_pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + +EOD; + + foreach (array(8, 16, 32, 64) as $bits) { + echo <<= sizeof(struct BProto_uint{$bits}_s)) + struct BProto_uint{$bits}_s *val = (struct BProto_uint{$bits}_s *)(o->buf + o->{$entry["name"]}_start + o->{$entry["name"]}_pos); + o->{$entry["name"]}_pos += sizeof(struct BProto_uint{$bits}_s); + left -= sizeof(struct BProto_uint{$bits}_s); + +EOD; + if ($entry["type"]["type"] == "uint" && $entry["type"]["size"] == $bits) { + echo <<v); + return 1; + } + +EOD; + } + + echo <<= sizeof(struct BProto_data_header_s)) + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + o->{$entry["name"]}_start + o->{$entry["name"]}_pos); + o->{$entry["name"]}_pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + ASSERT(left >= payload_len) + uint8_t *payload = o->buf + o->{$entry["name"]}_start + o->{$entry["name"]}_pos; + o->{$entry["name"]}_pos += payload_len; + left -= payload_len; + +EOD; + if ($entry["type"]["type"] == "data") { + echo <<{$entry["name"]}_pos = 0; +} + +{$forward_decl} +{ + o->{$entry["name"]}_pos = o->{$entry["name"]}_span; +} + + +EOD; + } + } + + return ob_get_clean(); +} diff --git a/bstruct_generator/StructParser.lime b/bstruct_generator/StructParser.lime new file mode 100644 index 000000000..84fc93d79 --- /dev/null +++ b/bstruct_generator/StructParser.lime @@ -0,0 +1,93 @@ +%class StructParser +%start file + +file = + directives structures { + $$ = array( + "directives" => $1, + "structures" => $2 + ); +}. + +directives = + { + $$ = array(); + } | + directive semicolon directives { + $$ = array_merge(array($1), $3); + }. + +directive = + include string { + $$ = array( + "type" => "include", + "file" => $2 + ); + }. + +structures = + structspec { + $$ = array($1); + } | + structspec structures { + $$ = array_merge(array($1), $2); + }. + +structspec = + structure name srpar string erpar spar entries epar semicolon { + $$ = array( + "name" => $2, + "parameters" => $4, + "entries" => $7 + ); +}. + +entries = + entry { + $$ = array($1); + } | + entry entries { + $$ = array_merge(array($1), $2); + }. + +entry = + typespec name semicolon { + $$ = array( + "type" => $1, + "name" => $2, + "num" => "1" + ); + } | + typespec name sbracket string ebracket semicolon { + $$ = array( + "type" => $1, + "name" => $2, + "num" => $4 + ); + }. + +typespec = + string { + $$ = array( + "type" => "sizealign", + "ctype" => $1, + "size" => "sizeof($1)", + "align" => "__alignof__($1)" + ); + } | + size string align string { + $$ = array( + "type" => "sizealign", + "ctype" => "void", + "size" => $2, + "align" => $4 + ); + } | + structure name srpar string erpar { + $$ = array( + "type" => "structure", + "ctype" => $2, + "name" => $2, + "parameters" => $4 + ); + }. diff --git a/bstruct_generator/StructParser.php b/bstruct_generator/StructParser.php new file mode 100644 index 000000000..f31b7560a --- /dev/null +++ b/bstruct_generator/StructParser.php @@ -0,0 +1,503 @@ + + array ( + 'directives' => 's 1', + 'directive' => 's 33', + 'include' => 's 36', + 'file' => 's 38', + '\'start\'' => 'a \'start\'', + 'structure' => 'r 1', + ), + 1 => + array ( + 'structures' => 's 2', + 'structspec' => 's 3', + 'structure' => 's 5', + ), + 2 => + array ( + '#' => 'r 0', + ), + 3 => + array ( + 'structspec' => 's 3', + 'structures' => 's 4', + 'structure' => 's 5', + '#' => 'r 4', + ), + 4 => + array ( + '#' => 'r 5', + ), + 5 => + array ( + 'name' => 's 6', + ), + 6 => + array ( + 'srpar' => 's 7', + ), + 7 => + array ( + 'string' => 's 8', + ), + 8 => + array ( + 'erpar' => 's 9', + ), + 9 => + array ( + 'spar' => 's 10', + ), + 10 => + array ( + 'entries' => 's 11', + 'entry' => 's 14', + 'typespec' => 's 16', + 'string' => 's 23', + 'size' => 's 24', + 'structure' => 's 28', + ), + 11 => + array ( + 'epar' => 's 12', + ), + 12 => + array ( + 'semicolon' => 's 13', + ), + 13 => + array ( + 'structure' => 'r 6', + '#' => 'r 6', + ), + 14 => + array ( + 'entry' => 's 14', + 'entries' => 's 15', + 'typespec' => 's 16', + 'string' => 's 23', + 'size' => 's 24', + 'structure' => 's 28', + 'epar' => 'r 7', + ), + 15 => + array ( + 'epar' => 'r 8', + ), + 16 => + array ( + 'name' => 's 17', + ), + 17 => + array ( + 'semicolon' => 's 18', + 'sbracket' => 's 19', + ), + 18 => + array ( + 'string' => 'r 9', + 'size' => 'r 9', + 'structure' => 'r 9', + 'epar' => 'r 9', + ), + 19 => + array ( + 'string' => 's 20', + ), + 20 => + array ( + 'ebracket' => 's 21', + ), + 21 => + array ( + 'semicolon' => 's 22', + ), + 22 => + array ( + 'string' => 'r 10', + 'size' => 'r 10', + 'structure' => 'r 10', + 'epar' => 'r 10', + ), + 23 => + array ( + 'name' => 'r 11', + ), + 24 => + array ( + 'string' => 's 25', + ), + 25 => + array ( + 'align' => 's 26', + ), + 26 => + array ( + 'string' => 's 27', + ), + 27 => + array ( + 'name' => 'r 12', + ), + 28 => + array ( + 'name' => 's 29', + ), + 29 => + array ( + 'srpar' => 's 30', + ), + 30 => + array ( + 'string' => 's 31', + ), + 31 => + array ( + 'erpar' => 's 32', + ), + 32 => + array ( + 'name' => 'r 13', + ), + 33 => + array ( + 'semicolon' => 's 34', + ), + 34 => + array ( + 'directive' => 's 33', + 'directives' => 's 35', + 'include' => 's 36', + 'structure' => 'r 1', + ), + 35 => + array ( + 'structure' => 'r 2', + ), + 36 => + array ( + 'string' => 's 37', + ), + 37 => + array ( + 'semicolon' => 'r 3', + ), + 38 => + array ( + '#' => 'r 14', + ), +); +function reduce_0_file_1($tokens, &$result) { +# +# (0) file := directives structures +# +$result = reset($tokens); + + $result = array( + "directives" => $tokens[0], + "structures" => $tokens[1] + ); + +} + +function reduce_1_directives_1($tokens, &$result) { +# +# (1) directives := +# +$result = reset($tokens); + + $result = array(); + +} + +function reduce_2_directives_2($tokens, &$result) { +# +# (2) directives := directive semicolon directives +# +$result = reset($tokens); + + $result = array_merge(array($tokens[0]), $tokens[2]); + +} + +function reduce_3_directive_1($tokens, &$result) { +# +# (3) directive := include string +# +$result = reset($tokens); + + $result = array( + "type" => "include", + "file" => $tokens[1] + ); + +} + +function reduce_4_structures_1($tokens, &$result) { +# +# (4) structures := structspec +# +$result = reset($tokens); + + $result = array($tokens[0]); + +} + +function reduce_5_structures_2($tokens, &$result) { +# +# (5) structures := structspec structures +# +$result = reset($tokens); + + $result = array_merge(array($tokens[0]), $tokens[1]); + +} + +function reduce_6_structspec_1($tokens, &$result) { +# +# (6) structspec := structure name srpar string erpar spar entries epar semicolon +# +$result = reset($tokens); + + $result = array( + "name" => $tokens[1], + "parameters" => $tokens[3], + "entries" => $tokens[6] + ); + +} + +function reduce_7_entries_1($tokens, &$result) { +# +# (7) entries := entry +# +$result = reset($tokens); + + $result = array($tokens[0]); + +} + +function reduce_8_entries_2($tokens, &$result) { +# +# (8) entries := entry entries +# +$result = reset($tokens); + + $result = array_merge(array($tokens[0]), $tokens[1]); + +} + +function reduce_9_entry_1($tokens, &$result) { +# +# (9) entry := typespec name semicolon +# +$result = reset($tokens); + + $result = array( + "type" => $tokens[0], + "name" => $tokens[1], + "num" => "1" + ); + +} + +function reduce_10_entry_2($tokens, &$result) { +# +# (10) entry := typespec name sbracket string ebracket semicolon +# +$result = reset($tokens); + + $result = array( + "type" => $tokens[0], + "name" => $tokens[1], + "num" => $tokens[3] + ); + +} + +function reduce_11_typespec_1($tokens, &$result) { +# +# (11) typespec := string +# +$result = reset($tokens); + + $result = array( + "type" => "sizealign", + "ctype" => $tokens[0], + "size" => "sizeof($tokens[0])", + "align" => "__alignof__($tokens[0])" + ); + +} + +function reduce_12_typespec_2($tokens, &$result) { +# +# (12) typespec := size string align string +# +$result = reset($tokens); + + $result = array( + "type" => "sizealign", + "ctype" => "void", + "size" => $tokens[1], + "align" => $tokens[3] + ); + +} + +function reduce_13_typespec_3($tokens, &$result) { +# +# (13) typespec := structure name srpar string erpar +# +$result = reset($tokens); + + $result = array( + "type" => "structure", + "ctype" => $tokens[1], + "name" => $tokens[1], + "parameters" => $tokens[3] + ); + +} + +function reduce_14_start_1($tokens, &$result) { +# +# (14) 'start' := file +# +$result = reset($tokens); + +} + +var $method = array ( + 0 => 'reduce_0_file_1', + 1 => 'reduce_1_directives_1', + 2 => 'reduce_2_directives_2', + 3 => 'reduce_3_directive_1', + 4 => 'reduce_4_structures_1', + 5 => 'reduce_5_structures_2', + 6 => 'reduce_6_structspec_1', + 7 => 'reduce_7_entries_1', + 8 => 'reduce_8_entries_2', + 9 => 'reduce_9_entry_1', + 10 => 'reduce_10_entry_2', + 11 => 'reduce_11_typespec_1', + 12 => 'reduce_12_typespec_2', + 13 => 'reduce_13_typespec_3', + 14 => 'reduce_14_start_1', +); +var $a = array ( + 0 => + array ( + 'symbol' => 'file', + 'len' => 2, + 'replace' => true, + ), + 1 => + array ( + 'symbol' => 'directives', + 'len' => 0, + 'replace' => true, + ), + 2 => + array ( + 'symbol' => 'directives', + 'len' => 3, + 'replace' => true, + ), + 3 => + array ( + 'symbol' => 'directive', + 'len' => 2, + 'replace' => true, + ), + 4 => + array ( + 'symbol' => 'structures', + 'len' => 1, + 'replace' => true, + ), + 5 => + array ( + 'symbol' => 'structures', + 'len' => 2, + 'replace' => true, + ), + 6 => + array ( + 'symbol' => 'structspec', + 'len' => 9, + 'replace' => true, + ), + 7 => + array ( + 'symbol' => 'entries', + 'len' => 1, + 'replace' => true, + ), + 8 => + array ( + 'symbol' => 'entries', + 'len' => 2, + 'replace' => true, + ), + 9 => + array ( + 'symbol' => 'entry', + 'len' => 3, + 'replace' => true, + ), + 10 => + array ( + 'symbol' => 'entry', + 'len' => 6, + 'replace' => true, + ), + 11 => + array ( + 'symbol' => 'typespec', + 'len' => 1, + 'replace' => true, + ), + 12 => + array ( + 'symbol' => 'typespec', + 'len' => 4, + 'replace' => true, + ), + 13 => + array ( + 'symbol' => 'typespec', + 'len' => 5, + 'replace' => true, + ), + 14 => + array ( + 'symbol' => '\'start\'', + 'len' => 1, + 'replace' => true, + ), +); +} diff --git a/bstruct_generator/bstruct.php b/bstruct_generator/bstruct.php new file mode 100644 index 000000000..4cf288240 --- /dev/null +++ b/bstruct_generator/bstruct.php @@ -0,0 +1,106 @@ + Message file to generate source for. + --output-dir Destination directory for generated files. + [--file-prefix ] Name prefix for generated files. Default: "struct_". + +EOD; +} + +$name = ""; +$input_file = ""; +$output_dir = ""; + +for ($i = 1; $i < $argc;) { + $arg = $argv[$i++]; + switch ($arg) { + case "--name": + $name = $argv[$i++]; + break; + case "--input-file": + $input_file = $argv[$i++]; + break; + case "--output-dir": + $output_dir = $argv[$i++]; + break; + case "--help": + print_help($argv[0]); + exit(0); + default: + fatal_error("Unknown option: {$arg}"); + } +} + +if ($name == "") { + fatal_error("--name missing"); +} + +if ($input_file == "") { + fatal_error("--input-file missing"); +} + +if ($output_dir == "") { + fatal_error("--output-dir missing"); +} + +if (($data = file_get_contents($input_file)) === FALSE) { + fatal_error("Failed to read input file"); +} + +if (!tokenize($data, $tokens)) { + fatal_error("Failed to tokenize"); +} + +$parser = new parse_engine(new StructParser()); + +try { + foreach ($tokens as $token) { + $parser->eat($token[0], $token[1]); + } + $parser->eat_eof(); +} catch (parse_error $e) { + fatal_error("$input_file: Parse error: ".$e->getMessage()); +} + +$data = generate_header($name, $parser->semantic["directives"], $parser->semantic["structures"]); +if (file_put_contents("{$output_dir}/{$name}.h", $data) === NULL) { + fatal_error("{$input_file}: Failed to write .h file"); +} diff --git a/bstruct_generator/bstruct_functions.php b/bstruct_generator/bstruct_functions.php new file mode 100644 index 000000000..4f02509e2 --- /dev/null +++ b/bstruct_generator/bstruct_functions.php @@ -0,0 +1,269 @@ + 0) { + if (preg_match('/^\\/\\/.*/', $str, $matches)) { + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^\\s+/', $str, $matches)) { + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^include/', $str, $matches)) { + $out[] = array('include', null); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^structure/', $str, $matches)) { + $out[] = array('structure', null); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^size/', $str, $matches)) { + $out[] = array('size', null); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^align/', $str, $matches)) { + $out[] = array('align', null); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^{/', $str, $matches)) { + $out[] = array('spar', null); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^}/', $str, $matches)) { + $out[] = array('epar', null); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^\(/', $str, $matches)) { + $out[] = array('srpar', null); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^\)/', $str, $matches)) { + $out[] = array('erpar', null); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^\[/', $str, $matches)) { + $out[] = array('sbracket', null); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^\]/', $str, $matches)) { + $out[] = array('ebracket', null); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^;/', $str, $matches)) { + $out[] = array('semicolon', null); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^,/', $str, $matches)) { + $out[] = array('comma', null); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*/', $str, $matches)) { + $out[] = array('name', $matches[0]); + $str = substr($str, strlen($matches[0])); + } + else if (preg_match('/^"([^"]*)"/', $str, $matches)) { + $out[] = array('string', $matches[1]); + $str = substr($str, strlen($matches[0])); + } + else { + return FALSE; + } + } + + return TRUE; +} + +function fatal_error ($message) +{ + fwrite(STDERR, "Fatal error: $message\n"); + + ob_get_clean(); + exit(1); +} + +function make_size ($entry) +{ + switch ($entry["type"]["type"]) { + case "sizealign": + return "({$entry["type"]["size"]})"; + case "structure": + return "o->{$entry["name"]}_params.len"; + default: + assert(0); + } +} + +function make_align ($entry) +{ + switch ($entry["type"]["type"]) { + case "sizealign": + return "({$entry["type"]["align"]})"; + case "structure": + return "o->{$entry["name"]}_params.align"; + default: + assert(0); + } +} + +function generate_header ($name, $directives, $structures) +{ + ob_start(); + + echo << + +#include + + +EOD; + + foreach ($directives as $directive) { + if ($directive["type"] == "include") { + echo <<len = 0; + o->align = 1; + + +EOD; + + // Calculate the alignment of the structure as the maximum of alignments of its entries. + // This assumes the alignments are powers of two; in general we would need the least + // common multiple of the alignments. + + $prev = NULL; + foreach ($struct["entries"] as $entry) { + if ($entry["type"]["type"] == "structure") { + if ($entry["type"]["parameters"] == "") { + $init_add_parameters = ""; + } else { + $init_add_parameters = ", {$entry["type"]["parameters"]}"; + } + echo <<{$entry["name"]}_params{$init_add_parameters}); + +EOD; + } + + $size = make_size($entry); + $align = make_align($entry); + + + + echo <<{$entry["name"]}_off = {$off}; + o->{$entry["name"]}_size = cur_size; + #ifndef NDEBUG + o->{$entry["name"]}_count = cur_count; + #endif + o->len = o->{$entry["name"]}_off + {$len}; + o->align = (cur_align > o->align ? cur_align : o->align); + + +EOD; + } + + echo <<{$entry["name"]}_off); +} + +static {$entry["type"]["ctype"]} * {$struct["name"]}_{$entry["name"]}_at ({$struct["name"]}Params *o, {$struct["name"]} *s, int i) +{ + ASSERT(i >= 0) + ASSERT(i < o->{$entry["name"]}_count) + + return ({$entry["type"]["ctype"]} *)((uint8_t *)s + o->{$entry["name"]}_off + i * o->{$entry["name"]}_size); +} + + +EOD; + } + + echo << + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#define DATAPROTO_TIMEOUT 30000 + +struct dp_relay_flow { + DataProtoRelaySource *rs; + DataProtoDest *dp; + BestEffortPacketWriteInterface *ainput_if; + PacketBufferAsyncInput ainput; + PacketBuffer buffer; + PacketPassInactivityMonitor monitor; + PacketPassFairQueueFlow qflow; + LinkedList2Node source_list_node; + BAVLNode source_tree_node; + LinkedList2Node dp_list_node; +}; + +static int peerid_comparator (void *user, peerid_t *val1, peerid_t *val2); +static struct dp_relay_flow * create_relay_flow (DataProtoRelaySource *rs, DataProtoDest *dp, int num_packets); +static void dealloc_relay_flow (struct dp_relay_flow *flow); +static int release_relay_flow (struct dp_relay_flow *flow); +static void flow_monitor_handler (struct dp_relay_flow *flow); +static void monitor_handler (DataProtoDest *o); +static int send_keepalive (DataProtoDest *o); +static void receive_timer_handler (DataProtoDest *o); +static void notifier_handler (DataProtoDest *o, uint8_t *data, int data_len); +static int pointer_comparator (void *user, void **val1, void **val2); +static void keepalive_job_handler (DataProtoDest *o); + +int peerid_comparator (void *user, peerid_t *val1, peerid_t *val2) +{ + if (*val1 < *val2) { + return -1; + } + if (*val1 > *val2) { + return 1; + } + return 0; +} + +struct dp_relay_flow * create_relay_flow (DataProtoRelaySource *rs, DataProtoDest *dp, int num_packets) +{ + ASSERT(!BAVL_LookupExact(&rs->relay_flows_tree, &dp)) + ASSERT(num_packets > 0) + ASSERT(!dp->d_freeing) + ASSERT(!PacketPassInterface_InClient(dp->d_output)) + + // allocate flow structure + struct dp_relay_flow *flow = malloc(sizeof(struct dp_relay_flow)); + if (!flow) { + BLog(BLOG_ERROR, "failed to allocate flow structure for relay flow from peer %d to %d", (int)rs->source_id, (int)dp->dest_id); + goto fail0; + } + + // set source and dp + flow->rs = rs; + flow->dp = dp; + + // init queue flow + PacketPassFairQueueFlow_Init(&flow->qflow, &dp->queue); + + // init inacitvity monitor + PacketPassInactivityMonitor_Init(&flow->monitor, PacketPassFairQueueFlow_GetInput(&flow->qflow), dp->reactor, DATAPROTO_TIMEOUT, (PacketPassInactivityMonitor_handler)flow_monitor_handler, flow); + + // init async input + PacketBufferAsyncInput_Init(&flow->ainput, dp->mtu); + flow->ainput_if = PacketBufferAsyncInput_GetInput(&flow->ainput); + + // init buffer + if (!PacketBuffer_Init(&flow->buffer, PacketBufferAsyncInput_GetOutput(&flow->ainput), PacketPassInactivityMonitor_GetInput(&flow->monitor), num_packets, BReactor_PendingGroup(dp->reactor))) { + BLog(BLOG_ERROR, "PacketBuffer_Init failed for relay flow from peer %d to %d", (int)rs->source_id, (int)dp->dest_id); + goto fail1; + } + + // insert to source list + LinkedList2_Append(&rs->relay_flows_list, &flow->source_list_node); + + // insert to source tree + ASSERT_EXECUTE(BAVL_Insert(&rs->relay_flows_tree, &flow->source_tree_node, NULL)) + + // insert to dp list + LinkedList2_Append(&dp->relay_flows_list, &flow->dp_list_node); + + BLog(BLOG_NOTICE, "created relay flow from peer %d to %d", (int)rs->source_id, (int)dp->dest_id); + + return flow; + +fail1: + PacketBufferAsyncInput_Free(&flow->ainput); + PacketPassInactivityMonitor_Free(&flow->monitor); + PacketPassFairQueueFlow_Free(&flow->qflow); + free(flow); +fail0: + return NULL; +} + +void dealloc_relay_flow (struct dp_relay_flow *flow) +{ + #ifndef NDEBUG + if (!flow->dp->d_freeing) { + ASSERT(!PacketPassFairQueueFlow_IsBusy(&flow->qflow)) + ASSERT(!PacketPassInterface_InClient(flow->dp->d_output)) + } + #endif + + DataProtoDest *o = flow->dp; + + // remove from dp list + LinkedList2_Remove(&flow->dp->relay_flows_list, &flow->dp_list_node); + + // remove from source tree + BAVL_Remove(&flow->rs->relay_flows_tree, &flow->source_tree_node); + + // remove from source list + LinkedList2_Remove(&flow->rs->relay_flows_list, &flow->source_list_node); + + // free buffer + PacketBuffer_Free(&flow->buffer); + + // free async input + PacketBufferAsyncInput_Free(&flow->ainput); + + // free inacitvity monitor + PacketPassInactivityMonitor_Free(&flow->monitor); + + // free queue flow + PacketPassFairQueueFlow_Free(&flow->qflow); + + // free flow structure + free(flow); +} + +int release_relay_flow (struct dp_relay_flow *flow) +{ + ASSERT(!flow->dp->d_freeing) + ASSERT(!PacketPassInterface_InClient(flow->dp->d_output)) + + DataProtoDest *o = flow->dp; + + if (PacketPassFairQueueFlow_IsBusy(&flow->qflow)) { + // release it + DEAD_ENTER(o->dead) + PacketPassFairQueueFlow_Release(&flow->qflow); + if (DEAD_LEAVE(o->dead)) { + return -1; + } + } + + // remove flow + dealloc_relay_flow(flow); + + return 0; +} + +void flow_monitor_handler (struct dp_relay_flow *flow) +{ + ASSERT(!flow->dp->d_freeing) + ASSERT(!PacketPassInterface_InClient(flow->dp->d_output)) + + BLog(BLOG_NOTICE, "relay flow from peer %d to %d timed out", (int)flow->rs->source_id, (int)flow->dp->dest_id); + + release_relay_flow(flow); + return; +} + +void monitor_handler (DataProtoDest *o) +{ + ASSERT(!o->d_freeing) + ASSERT(!PacketPassInterface_InClient(o->d_output)) + DebugObject_Access(&o->d_obj); + + send_keepalive(o); + return; +} + +int send_keepalive (DataProtoDest *o) +{ + ASSERT(!o->d_freeing) + ASSERT(!PacketPassInterface_InClient(o->d_output)) + + BLog(BLOG_DEBUG, "sending keepalive to peer %d", (int)o->dest_id); + + DEAD_ENTER(o->dead) + PacketRecvBlocker_AllowBlockedPacket(&o->ka_blocker); + if (DEAD_LEAVE(o->dead)) { + return -1; + } + + return 0; +} + +void receive_timer_handler (DataProtoDest *o) +{ + DebugObject_Access(&o->d_obj); + + BLog(BLOG_DEBUG, "receive timer triggered for peer %d", (int)o->dest_id); + + int prev_up = o->up; + + // consider down + o->up = 0; + + // call handler if up state changed + if (o->handler && o->up != prev_up) { + o->handler(o->user, o->up); + return; + } +} + +void notifier_handler (DataProtoDest *o, uint8_t *data, int data_len) +{ + ASSERT(data_len >= sizeof(struct dataproto_header)) + DebugObject_Access(&o->d_obj); + + // modify existing packet here + struct dataproto_header *header = (struct dataproto_header *)data; + header->flags = 0; + + // if we are receiving keepalives, set the flag + if (BTimer_IsRunning(&o->receive_timer)) { + header->flags |= DATAPROTO_FLAGS_RECEIVING_KEEPALIVES; + } +} + +int pointer_comparator (void *user, void **val1, void **val2) +{ + if (*val1 < *val2) { + return -1; + } + if (*val1 > *val2) { + return 1; + } + return 0; +} + +void keepalive_job_handler (DataProtoDest *o) +{ + ASSERT(!o->d_freeing) + ASSERT(!PacketPassInterface_InClient(o->d_output)) + DebugObject_Access(&o->d_obj); + + send_keepalive(o); + return; +} + +int DataProtoDest_Init (DataProtoDest *o, BReactor *reactor, peerid_t dest_id, PacketPassInterface *output, btime_t keepalive_time, btime_t tolerance_time, DataProtoDest_handler handler, void *user) +{ + ASSERT(PacketPassInterface_HasCancel(output)) + ASSERT(PacketPassInterface_GetMTU(output) >= sizeof(struct dataproto_header) + sizeof(struct dataproto_peer_id)) + + // init arguments + o->reactor = reactor; + o->dest_id = dest_id; + o->handler = handler; + o->user = user; + + // init dead var + DEAD_INIT(o->dead); + + // set MTU + o->mtu = PacketPassInterface_GetMTU(output); + + // set frame MTU + o->frame_mtu = o->mtu - (sizeof(struct dataproto_header) + sizeof(struct dataproto_peer_id)); + + // init notifier + PacketPassNotifier_Init(&o->notifier, output); + PacketPassNotifier_SetHandler(&o->notifier, (PacketPassNotifier_handler_notify)notifier_handler, o); + + // init monitor + PacketPassInactivityMonitor_Init(&o->monitor, PacketPassNotifier_GetInput(&o->notifier), o->reactor, keepalive_time, (PacketPassInactivityMonitor_handler)monitor_handler, o); + + // init queue + PacketPassFairQueue_Init(&o->queue, PacketPassInactivityMonitor_GetInput(&o->monitor), BReactor_PendingGroup(o->reactor)); + PacketPassFairQueue_EnableCancel(&o->queue); + + // init keepalive queue flow + PacketPassFairQueueFlow_Init(&o->ka_qflow, &o->queue); + + // init keepalive source + DataProtoKeepaliveSource_Init(&o->ka_source); + + // init keepalive blocker + PacketRecvBlocker_Init(&o->ka_blocker, DataProtoKeepaliveSource_GetOutput(&o->ka_source)); + + // init keepalive buffer + if (!SinglePacketBuffer_Init(&o->ka_buffer, PacketRecvBlocker_GetOutput(&o->ka_blocker), PacketPassFairQueueFlow_GetInput(&o->ka_qflow), BReactor_PendingGroup(o->reactor))) { + BLog(BLOG_ERROR, "SinglePacketBuffer_Init failed"); + goto fail0; + } + + // init receive timer + BTimer_Init(&o->receive_timer, tolerance_time, (BTimer_handler)receive_timer_handler, o); + + // set not up + o->up = 0; + + // init relay flows list + LinkedList2_Init(&o->relay_flows_list); + + // init keepalive job + BPending_Init(&o->keepalive_job, BReactor_PendingGroup(o->reactor), (BPending_handler)keepalive_job_handler, o); + BPending_Set(&o->keepalive_job); + + // init flows counter + DebugCounter_Init(&o->flows_counter); + + // init debug object + DebugObject_Init(&o->d_obj); + + #ifndef NDEBUG + o->d_output = output; + o->d_freeing = 0; + #endif + + return 1; + +fail0: + PacketRecvBlocker_Free(&o->ka_blocker); + DataProtoKeepaliveSource_Free(&o->ka_source); + PacketPassFairQueueFlow_Free(&o->ka_qflow); + PacketPassFairQueue_Free(&o->queue); + PacketPassInactivityMonitor_Free(&o->monitor); + PacketPassNotifier_Free(&o->notifier); + return 0; +} + +void DataProtoDest_Free (DataProtoDest *o) +{ + DebugCounter_Free(&o->flows_counter); + DebugObject_Free(&o->d_obj); + + // free keepalive job + BPending_Free(&o->keepalive_job); + + // allow freeing queue flows + PacketPassFairQueue_PrepareFree(&o->queue); + + // free relay flows + LinkedList2Node *node; + while (node = LinkedList2_GetFirst(&o->relay_flows_list)) { + struct dp_relay_flow *flow = UPPER_OBJECT(node, struct dp_relay_flow, dp_list_node); + dealloc_relay_flow(flow); + } + + // free receive timer + BReactor_RemoveTimer(o->reactor, &o->receive_timer); + + // free keepalive buffer + SinglePacketBuffer_Free(&o->ka_buffer); + + // free keepalive blocker + PacketRecvBlocker_Free(&o->ka_blocker); + + // free keepalive source + DataProtoKeepaliveSource_Free(&o->ka_source); + + // free keepalive queue flow + PacketPassFairQueueFlow_Free(&o->ka_qflow); + + // free queue + PacketPassFairQueue_Free(&o->queue); + + // free monitor + PacketPassInactivityMonitor_Free(&o->monitor); + + // free notifier + PacketPassNotifier_Free(&o->notifier); + + // free dead var + DEAD_KILL(o->dead); +} + +void DataProtoDest_PrepareFree (DataProtoDest *o) +{ + DebugObject_Access(&o->d_obj); + + // allow freeing queue flows + PacketPassFairQueue_PrepareFree(&o->queue); + + #ifndef NDEBUG + o->d_freeing = 1; + #endif +} + +void DataProtoDest_SubmitRelayFrame (DataProtoDest *o, DataProtoRelaySource *rs, uint8_t *data, int data_len, int buffer_num_packets) +{ + ASSERT(data_len >= 0) + ASSERT(data_len <= o->frame_mtu) + ASSERT(buffer_num_packets > 0) + ASSERT(!o->d_freeing) + ASSERT(!PacketPassInterface_InClient(o->d_output)) + DebugObject_Access(&rs->d_obj); + DebugObject_Access(&o->d_obj); + + // lookup relay flow from source to this DataProto + struct dp_relay_flow *flow; + BAVLNode *node = BAVL_LookupExact(&rs->relay_flows_tree, &o); + if (!node) { + // create new flow + if (!(flow = create_relay_flow(rs, o, buffer_num_packets))) { + return; + } + } else { + flow = UPPER_OBJECT(node, struct dp_relay_flow, source_tree_node); + } + + // get a buffer + uint8_t *out; + // safe because of PacketBufferAsyncInput + if (!BestEffortPacketWriteInterface_Sender_StartPacket(flow->ainput_if, &out)) { + BLog(BLOG_NOTICE, "out of buffer for relayed frame from peer %d to %d", (int)rs->source_id, (int)o->dest_id); + return; + } + + // write header + struct dataproto_header *header = (struct dataproto_header *)out; + // don't set flags, it will be set in notifier_handler + header->from_id = htol16(rs->source_id); + header->num_peer_ids = htol16(1); + struct dataproto_peer_id *id = (struct dataproto_peer_id *)(out + sizeof(struct dataproto_header)); + id->id = htol16(o->dest_id); + + // write data + memcpy(out + sizeof(struct dataproto_header) + sizeof(struct dataproto_peer_id), data, data_len); + + // submit it + BestEffortPacketWriteInterface_Sender_EndPacket(flow->ainput_if, sizeof(struct dataproto_header) + sizeof(struct dataproto_peer_id) + data_len); + return; +} + +void DataProtoDest_Received (DataProtoDest *o, int peer_receiving) +{ + ASSERT(peer_receiving == 0 || peer_receiving == 1) + ASSERT(!o->d_freeing) + ASSERT(!PacketPassInterface_InClient(o->d_output)) + DebugObject_Access(&o->d_obj); + + int prev_up = o->up; + + // reset receive timer + BReactor_SetTimer(o->reactor, &o->receive_timer); + + if (!peer_receiving) { + // peer reports not receiving, consider down + o->up = 0; + // send keep-alive to converge faster + if (send_keepalive(o) < 0) { + return; + } + } else { + // consider up + o->up = 1; + } + + // call handler if up state changed + if (o->handler && o->up != prev_up) { + o->handler(o->user, o->up); + return; + } +} + +int DataProtoLocalSource_Init (DataProtoLocalSource *o, int frame_mtu, peerid_t source_id, peerid_t dest_id, int num_packets, BReactor *reactor) +{ + ASSERT(frame_mtu >= 0) + ASSERT(frame_mtu <= INT_MAX - (sizeof(struct dataproto_header) + sizeof(struct dataproto_peer_id))) + ASSERT(num_packets > 0) + + // init arguments + o->frame_mtu = frame_mtu; + o->source_id = source_id; + o->dest_id = dest_id; + + // calculate packet MTU + int packet_mtu = o->frame_mtu + sizeof(struct dataproto_header) + sizeof(struct dataproto_peer_id); + + // init dead var + DEAD_INIT(o->dead); + + // init connector + PacketPassConnector_Init(&o->connector, packet_mtu, BReactor_PendingGroup(reactor)); + + // init async input + PacketBufferAsyncInput_Init(&o->ainput, packet_mtu); + o->ainput_if = PacketBufferAsyncInput_GetInput(&o->ainput); + + // init buffer + if (!PacketBuffer_Init(&o->buffer, PacketBufferAsyncInput_GetOutput(&o->ainput), PacketPassConnector_GetInput(&o->connector), num_packets, BReactor_PendingGroup(reactor))) { + BLog(BLOG_ERROR, "PacketBuffer_Init failed"); + goto fail1; + } + + // set no DataProto + o->dp = NULL; + + // init debug object + DebugObject_Init(&o->d_obj); + + return 1; + +fail1: + PacketBufferAsyncInput_Free(&o->ainput); + PacketPassConnector_Free(&o->connector); +fail0: + return 0; +} + +void DataProtoLocalSource_Free (DataProtoLocalSource *o) +{ + ASSERT(!o->dp) + DebugObject_Free(&o->d_obj); + + // free buffer + PacketBuffer_Free(&o->buffer); + + // free async input + PacketBufferAsyncInput_Free(&o->ainput); + + // free connector + PacketPassConnector_Free(&o->connector); + + // free dead var + DEAD_KILL(o->dead); +} + +void DataProtoLocalSource_SubmitFrame (DataProtoLocalSource *o, uint8_t *data, int data_len) +{ + ASSERT(data_len >= 0) + ASSERT(data_len <= o->frame_mtu) + if (o->dp) { + ASSERT(!o->d_dp_released) + ASSERT(!o->dp->d_freeing) + ASSERT(!PacketPassInterface_InClient(o->dp->d_output)) + } + DebugObject_Access(&o->d_obj); + + // get a buffer + uint8_t *out; + // safe because of PacketBufferAsyncInput + if (!BestEffortPacketWriteInterface_Sender_StartPacket(o->ainput_if, &out)) { + BLog(BLOG_NOTICE, "out of buffer for frame from peer %d to %d", (int)o->source_id, (int)o->dest_id); + return; + } + + // write header + struct dataproto_header *header = (struct dataproto_header *)out; + // don't set flags, it will be set in notifier_handler + header->from_id = htol16(o->source_id); + header->num_peer_ids = htol16(1); + struct dataproto_peer_id *id = (struct dataproto_peer_id *)(out + sizeof(struct dataproto_header)); + id->id = htol16(o->dest_id); + + // write data + memcpy(out + sizeof(struct dataproto_header) + sizeof(struct dataproto_peer_id), data, data_len); + + // submit it + BestEffortPacketWriteInterface_Sender_EndPacket(o->ainput_if, sizeof(struct dataproto_header) + sizeof(struct dataproto_peer_id) + data_len); + return; +} + +void DataProtoLocalSource_Attach (DataProtoLocalSource *o, DataProtoDest *dp) +{ + ASSERT(dp) + ASSERT(!o->dp) + ASSERT(o->frame_mtu <= dp->frame_mtu) + ASSERT(!dp->d_freeing) + ASSERT(!PacketPassInterface_InClient(dp->d_output)) + DebugObject_Access(&o->d_obj); + DebugObject_Access(&dp->d_obj); + + // set DataProto + o->dp = dp; + + // init queue flow + PacketPassFairQueueFlow_Init(&o->dp_qflow, &dp->queue); + + // connect to queue flow + PacketPassConnector_ConnectOutput(&o->connector, PacketPassFairQueueFlow_GetInput(&o->dp_qflow)); + + // increment flows counter + DebugCounter_Increment(&dp->flows_counter); + + #ifndef NDEBUG + o->d_dp_released = 0; + #endif +} + +void DataProtoLocalSource_Release (DataProtoLocalSource *o) +{ + ASSERT(o->dp) + ASSERT(!o->d_dp_released) + ASSERT(!o->dp->d_freeing) + ASSERT(!PacketPassInterface_InClient(o->dp->d_output)) + DebugObject_Access(&o->d_obj); + + if (PacketPassFairQueueFlow_IsBusy(&o->dp_qflow)) { + DEAD_ENTER(o->dead) + PacketPassFairQueueFlow_Release(&o->dp_qflow); + if (DEAD_LEAVE(o->dead)) { + return; + } + } + + #ifndef NDEBUG + o->d_dp_released = 1; + #endif +} + +void DataProtoLocalSource_Detach (DataProtoLocalSource *o) +{ + #ifndef NDEBUG + ASSERT(o->dp) + ASSERT(o->d_dp_released || o->dp->d_freeing) + if (!o->dp->d_freeing) { + ASSERT(!PacketPassInterface_InClient(o->dp->d_output)) + } + #endif + DebugObject_Access(&o->d_obj); + + DataProtoDest *dp = o->dp; + + // decrement flows counter + DebugCounter_Decrement(&dp->flows_counter); + + // disconnect from queue flow + PacketPassConnector_DisconnectOutput(&o->connector); + + // free queue flow + PacketPassFairQueueFlow_Free(&o->dp_qflow); + + // set no DataProto + o->dp = NULL; +} + +void DataProtoRelaySource_Init (DataProtoRelaySource *o, peerid_t source_id) +{ + // init arguments + o->source_id = source_id; + + // init dead var + DEAD_INIT(o->dead); + + // init relay flows list + LinkedList2_Init(&o->relay_flows_list); + + // init relay flows tree + BAVL_Init(&o->relay_flows_tree, OFFSET_DIFF(struct dp_relay_flow, dp, source_tree_node), (BAVL_comparator)pointer_comparator, NULL); + + // init debug object + DebugObject_Init(&o->d_obj); +} + +void DataProtoRelaySource_Free (DataProtoRelaySource *o) +{ + ASSERT(BAVL_IsEmpty(&o->relay_flows_tree)) + ASSERT(LinkedList2_IsEmpty(&o->relay_flows_list)) + DebugObject_Free(&o->d_obj); + + // free dead var + DEAD_KILL(o->dead); +} + +int DataProtoRelaySource_IsEmpty (DataProtoRelaySource *o) +{ + DebugObject_Access(&o->d_obj); + + return LinkedList2_IsEmpty(&o->relay_flows_list); +} + +void DataProtoRelaySource_Release (DataProtoRelaySource *o) +{ + DebugObject_Access(&o->d_obj); + + LinkedList2Node *node; + while (node = LinkedList2_GetFirst(&o->relay_flows_list)) { + struct dp_relay_flow *flow = UPPER_OBJECT(node, struct dp_relay_flow, source_list_node); + + DEAD_ENTER(o->dead) + release_relay_flow(flow); + if (DEAD_LEAVE(o->dead)) { + return; + } + } +} + +void DataProtoRelaySource_FreeRelease (DataProtoRelaySource *o) +{ + DebugObject_Access(&o->d_obj); + + LinkedList2Node *node; + while (node = LinkedList2_GetFirst(&o->relay_flows_list)) { + struct dp_relay_flow *flow = UPPER_OBJECT(node, struct dp_relay_flow, source_list_node); + + DataProtoDest_PrepareFree(flow->dp); + dealloc_relay_flow(flow); + } +} diff --git a/client/DataProto.h b/client/DataProto.h new file mode 100644 index 000000000..817184b5f --- /dev/null +++ b/client/DataProto.h @@ -0,0 +1,297 @@ +/** + * @file DataProto.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Mudule for frame sending used in the VPN client program. + */ + +#ifndef BADVPN_CLIENT_DATAPROTO_H +#define BADVPN_CLIENT_DATAPROTO_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef void (*DataProtoDest_handler) (void *user, int up); + +/** + * Frame destination. + * Represents a peer as a destination for sending frames to. + */ +typedef struct { + dead_t dead; + BReactor *reactor; + peerid_t dest_id; + int mtu; + int frame_mtu; + PacketPassFairQueue queue; + PacketPassInactivityMonitor monitor; + PacketPassNotifier notifier; + DataProtoKeepaliveSource ka_source; + PacketRecvBlocker ka_blocker; + SinglePacketBuffer ka_buffer; + PacketPassFairQueueFlow ka_qflow; + BTimer receive_timer; + int up; + DataProtoDest_handler handler; + void *user; + LinkedList2 relay_flows_list; + BPending keepalive_job; + DebugCounter flows_counter; + DebugObject d_obj; + #ifndef NDEBUG + PacketPassInterface *d_output; + int d_freeing; + #endif +} DataProtoDest; + +/** + * Local frame source. + * Buffers frames received from the TAP device, addressed to a particular peer. + */ +typedef struct { + dead_t dead; + int frame_mtu; + peerid_t source_id; + peerid_t dest_id; + BestEffortPacketWriteInterface *ainput_if; + PacketBufferAsyncInput ainput; + PacketBuffer buffer; + PacketPassConnector connector; + DataProtoDest *dp; + PacketPassFairQueueFlow dp_qflow; + DebugObject d_obj; + #ifndef NDEBUG + int d_dp_released; + #endif +} DataProtoLocalSource; + +/** + * Relay frame source. + * Represents relaying of frames from one particular peer to other peers. + */ +typedef struct { + dead_t dead; + peerid_t source_id; + LinkedList2 relay_flows_list; + BAVL relay_flows_tree; + DebugObject d_obj; +} DataProtoRelaySource; + +/** + * Initializes the object. + * + * @param o the object + * @param reactor reactor we live in + * @param dest_id ID of the peer this object sends to + * @param output output interface. Must support cancel functionality. Its MTU must be + * >=sizeof(struct dataproto_header)+sizeof(struct dataproto_peer_id). + * @param keepalive_time keepalive time + * @param tolerance_time after how long of not having received anything from the peer + * to consider the link down + * @param handler up state handler + * @param user value to pass to handler + * @return 1 on success, 0 on failure + */ +int DataProtoDest_Init (DataProtoDest *o, BReactor *reactor, peerid_t dest_id, PacketPassInterface *output, btime_t keepalive_time, btime_t tolerance_time, DataProtoDest_handler handler, void *user) WARN_UNUSED; + +/** + * Frees the object. + * There must be no local sources attached. + * + * @param o the object + */ +void DataProtoDest_Free (DataProtoDest *o); + +/** + * Prepares for freeing the object by allowing freeing of local sources. + * The object enters freeing state. + * The object must be freed before returning control to the reactor, + * and before any further I/O (output or submitting frames). + * + * @param o the object + */ +void DataProtoDest_PrepareFree (DataProtoDest *o); + +/** + * Submits a relayed frame. + * Must not be in freeing state. + * Must not be called from output Send calls. + * + * @param o the object + * @param rs relay source object representing the peer this frame came from + * @param data frame data + * @param data_len frame length. Must be >=0. + * Must be <= (output MTU) - (sizeof(struct dataproto_header) + sizeof(struct dataproto_peer_id)). + * @param buffer_num_packets number of packets the relay buffer should hold, in case it doesn't exist. + * Must be >0. + */ +void DataProtoDest_SubmitRelayFrame (DataProtoDest *o, DataProtoRelaySource *rs, uint8_t *data, int data_len, int buffer_num_packets); + +/** + * Notifies the object that a packet was received from the peer. + * Must not be in freeing state. + * Must not be called from output Send calls. + * May call the up state handler. + * May invoke output I/O. + * + * @param o the object + * @param peer_receiving whether the DATAPROTO_FLAGS_RECEIVING_KEEPALIVES flag was set in the packet. + * Must be 0 or 1. + */ +void DataProtoDest_Received (DataProtoDest *o, int peer_receiving); + +/** + * Initializes the object. + * The object is initialized in not attached state. + * + * @param o the object + * @param frame_mtu maximum frame size. Must be >=0. + * Must be <= INT_MAX - (sizeof(struct dataproto_header) + sizeof(struct dataproto_peer_id)). + * @param source_id ID of the peer from which the frames submitted to this object originate from, + * i.e. our ID + * @param dest_id ID of the peer to which the frames are to be delivered to + * @param num_packets number of packets the buffer should hold. Must be >0. + * @param reactor reactor we live in. Must be the same as with all destinations this + * source will be attached to. + * @return 1 on success, 0 on failure + */ +int DataProtoLocalSource_Init (DataProtoLocalSource *o, int frame_mtu, peerid_t source_id, peerid_t dest_id, int num_packets, BReactor *reactor) WARN_UNUSED; + +/** + * Frees the object. + * The object must be in not attached state. + * + * @param o the object + */ +void DataProtoLocalSource_Free (DataProtoLocalSource *o); + +/** + * Submits a frame. + * If the object is in attached state: + * - The object must be in not released state. + * - The destination must not be in freeing state. + * - Must not be called from destination's output Send calls. + * - May invoke the destination's output I/O. + * + * @param o the object + * @param data frame data + * @param data_len frame length. Must be >=0 and <=frame_mtu. + */ +void DataProtoLocalSource_SubmitFrame (DataProtoLocalSource *o, uint8_t *data, int data_len); + +/** + * Attaches the object to a destination. + * The object must be in not attached state. + * The object enters attached and not released state. + * + * @param o the object + * @param dp destination to attach to. This object's frame_mtu must be <= destination's + * (output MTU)-(sizeof(struct dataproto_header)+sizeof(struct dataproto_peer_id)). + */ +void DataProtoLocalSource_Attach (DataProtoLocalSource *o, DataProtoDest *dp); + +/** + * Releases the object to allow detaching it from the destination. + * The object must be in attached and not released state. + * The destination must not be in freeing state. + * The object enters attached and released state. + * Must not be called from destination's output Send calls. + * May invoke the destination's output Cancel call. + * + * @param o the object + */ +void DataProtoLocalSource_Release (DataProtoLocalSource *o); + +/** + * Detaches the object from a destination. + * The object must be in attached state. + * Either the object must be in released state, or the destination must be in freeing state. + * Unless the destination is in freeing state, must not be called from destination's + * output Send calls. + * + * @param o the object + */ +void DataProtoLocalSource_Detach (DataProtoLocalSource *o); + +/** + * Initializes the object + * + * @param o the object + * @param source_id ID of the peer whose relayed frames this object represents + */ +void DataProtoRelaySource_Init (DataProtoRelaySource *o, peerid_t source_id); + +/** + * Frees the object. + * The object must have no relay flows (guaranteed if no frames have been submitted + * with it using {@link DataProtoDest_SubmitRelayFrame}). + * + * @param o the object + */ +void DataProtoRelaySource_Free (DataProtoRelaySource *o); + +/** + * Checks if the object has no relay flows. + * + * @param o the object + * @return 1 if there are no relay flows, 0 if at least one + */ +int DataProtoRelaySource_IsEmpty (DataProtoRelaySource *o); + +/** + * Removes all relay flows by releasing them. + * None of the destinations must be in freeing state. + * Must not be called from any of the destinations' output Send calls. + * May invoke the destinations' output Cancel calls. + * + * @param o the object + */ +void DataProtoRelaySource_Release (DataProtoRelaySource *o); + +/** + * Removes all relay flows by putting destinations into freeing state. + * May put destinations into freeing state (as if {@link DataProtoDest_PrepareFree} + * was called on them). + * This should only be used while freeing the entire frame sending system. + * + * @param o the object + */ +void DataProtoRelaySource_FreeRelease (DataProtoRelaySource *o); + +#endif diff --git a/client/DatagramPeerIO.c b/client/DatagramPeerIO.c new file mode 100644 index 000000000..e006a6b0a --- /dev/null +++ b/client/DatagramPeerIO.c @@ -0,0 +1,511 @@ +/** + * @file DatagramPeerIO.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include +#include + +#include + +#include + +#define DATAGRAMPEERIO_MODE_NONE 0 +#define DATAGRAMPEERIO_MODE_CONNECT 1 +#define DATAGRAMPEERIO_MODE_BIND 2 + +#define DATAGRAMPEERIO_COMPONENT_SINK 1 +#define DATAGRAMPEERIO_COMPONENT_SOURCE 2 + +static int init_persistent_io (DatagramPeerIO *o, btime_t latency, PacketPassInterface *recv_userif); +static void free_persistent_io (DatagramPeerIO *o); +static void init_sending (DatagramPeerIO *o, BAddr addr, BIPAddr local_addr); +static void free_sending (DatagramPeerIO *o); +static void init_receiving (DatagramPeerIO *o); +static void free_receiving (DatagramPeerIO *o); +static void error_handler (DatagramPeerIO *o, int component, const void *data); +static void reset_mode (DatagramPeerIO *o); +static void recv_decoder_notifier_handler (DatagramPeerIO *o, uint8_t *data, int data_len); +static void send_encoder_notifier_handler (DatagramPeerIO *o, uint8_t *data, int data_len); + +int init_persistent_io (DatagramPeerIO *o, btime_t latency, PacketPassInterface *recv_userif) +{ + // init error domain + FlowErrorDomain_Init(&o->domain, (FlowErrorDomain_handler)error_handler, o); + + // init encoder group + if (!SPProtoEncoderGroup_Init(&o->encoder_group, o->sp_params)) { + goto fail0; + } + + // init sending base + + // init disassembler + FragmentProtoDisassembler_Init(&o->send_disassembler, o->reactor, o->payload_mtu, o->spproto_payload_mtu, -1, latency); + + // init encoder + if (!SPProtoEncoder_Init(&o->send_encoder, &o->encoder_group, FragmentProtoDisassembler_GetOutput(&o->send_disassembler), BReactor_PendingGroup(o->reactor))) { + BLog(BLOG_ERROR, "SPProtoEncoder_Init failed"); + goto fail1; + } + + // init notifier + PacketRecvNotifier_Init(&o->send_notifier, SPProtoEncoder_GetOutput(&o->send_encoder)); + if (SPPROTO_HAVE_OTP(o->sp_params)) { + PacketRecvNotifier_SetHandler(&o->send_notifier, (PacketRecvNotifier_handler_notify)send_encoder_notifier_handler, o); + } + + // init connector + PacketPassConnector_Init(&o->send_connector, o->effective_socket_mtu, BReactor_PendingGroup(o->reactor)); + + // init buffer + if (!SinglePacketBuffer_Init(&o->send_buffer, PacketRecvNotifier_GetOutput(&o->send_notifier), PacketPassConnector_GetInput(&o->send_connector), BReactor_PendingGroup(o->reactor))) { + BLog(BLOG_ERROR, "SinglePacketBuffer_Init failed"); + goto fail2; + } + + // init receiving + + // init assembler + if (!FragmentProtoAssembler_Init(&o->recv_assembler, o->spproto_payload_mtu, recv_userif, 1, fragmentproto_max_chunks_for_frame(o->spproto_payload_mtu, o->payload_mtu))) { + goto fail3; + } + + // init notifier + PacketPassNotifier_Init(&o->recv_notifier, FragmentProtoAssembler_GetInput(&o->recv_assembler)); + + // init decoder + if (!SPProtoDecoder_Init(&o->recv_decoder, PacketPassNotifier_GetInput(&o->recv_notifier), o->sp_params, 2)) { + goto fail4; + } + + // init connector + PacketRecvConnector_Init(&o->recv_connector, o->effective_socket_mtu, BReactor_PendingGroup(o->reactor)); + + // init buffer + if (!SinglePacketBuffer_Init(&o->recv_buffer, PacketRecvConnector_GetOutput(&o->recv_connector), SPProtoDecoder_GetInput(&o->recv_decoder), BReactor_PendingGroup(o->reactor))) { + goto fail5; + } + + return 1; + +fail5: + PacketRecvConnector_Free(&o->recv_connector); + SPProtoDecoder_Free(&o->recv_decoder); +fail4: + PacketPassNotifier_Free(&o->recv_notifier); + FragmentProtoAssembler_Free(&o->recv_assembler); +fail3: + SinglePacketBuffer_Free(&o->send_buffer); +fail2: + PacketPassConnector_Free(&o->send_connector); + PacketRecvNotifier_Free(&o->send_notifier); + SPProtoEncoder_Free(&o->send_encoder); +fail1: + FragmentProtoDisassembler_Free(&o->send_disassembler); +fail0a: + SPProtoEncoderGroup_Free(&o->encoder_group); +fail0: + return 0; +} + +void free_persistent_io (DatagramPeerIO *o) +{ + // free receiving + SinglePacketBuffer_Free(&o->recv_buffer); + PacketRecvConnector_Free(&o->recv_connector); + SPProtoDecoder_Free(&o->recv_decoder); + PacketPassNotifier_Free(&o->recv_notifier); + FragmentProtoAssembler_Free(&o->recv_assembler); + + // free sending base + SinglePacketBuffer_Free(&o->send_buffer); + PacketPassConnector_Free(&o->send_connector); + PacketRecvNotifier_Free(&o->send_notifier); + SPProtoEncoder_Free(&o->send_encoder); + FragmentProtoDisassembler_Free(&o->send_disassembler); + + // free encoder group + SPProtoEncoderGroup_Free(&o->encoder_group); +} + +void init_sending (DatagramPeerIO *o, BAddr addr, BIPAddr local_addr) +{ + // init sink + DatagramSocketSink_Init(&o->send_sink, FlowErrorReporter_Create(&o->domain, DATAGRAMPEERIO_COMPONENT_SINK), &o->sock, o->effective_socket_mtu, addr, local_addr); + + // connect sink + PacketPassConnector_ConnectOutput(&o->send_connector, DatagramSocketSink_GetInput(&o->send_sink)); +} + +void free_sending (DatagramPeerIO *o) +{ + // disconnect sink + PacketPassConnector_DisconnectOutput(&o->send_connector); + + // free sink + DatagramSocketSink_Free(&o->send_sink); +} + +void init_receiving (DatagramPeerIO *o) +{ + // init source + DatagramSocketSource_Init(&o->recv_source, FlowErrorReporter_Create(&o->domain, DATAGRAMPEERIO_COMPONENT_SOURCE), &o->sock, o->effective_socket_mtu); + + // connect source + PacketRecvConnector_ConnectInput(&o->recv_connector, DatagramSocketSource_GetOutput(&o->recv_source)); +} + +void free_receiving (DatagramPeerIO *o) +{ + // disconnect source + PacketRecvConnector_DisconnectInput(&o->recv_connector); + + // free source + DatagramSocketSource_Free(&o->recv_source); +} + +void error_handler (DatagramPeerIO *o, int component, const void *data) +{ + ASSERT(o->mode == DATAGRAMPEERIO_MODE_CONNECT || o->mode == DATAGRAMPEERIO_MODE_BIND) + + int error = *((int *)data); + + switch (component) { + case DATAGRAMPEERIO_COMPONENT_SINK: + switch (error) { + case DATAGRAMSOCKETSINK_ERROR_BSOCKET: + BLog(BLOG_NOTICE, "sink BSocket error %d", BSocket_GetError(&o->sock)); + break; + case DATAGRAMSOCKETSINK_ERROR_WRONGSIZE: + BLog(BLOG_NOTICE, "sink wrong size error"); + break; + default: + ASSERT(0); + } + break; + case DATAGRAMPEERIO_COMPONENT_SOURCE: + switch (error) { + case DATAGRAMSOCKETSOURCE_ERROR_BSOCKET: + BLog(BLOG_NOTICE, "source BSocket error %d", BSocket_GetError(&o->sock)); + break; + default: + ASSERT(0); + } + break; + default: + ASSERT(0); + } +} + +void reset_mode (DatagramPeerIO *o) +{ + switch (o->mode) { + case DATAGRAMPEERIO_MODE_NONE: + break; + case DATAGRAMPEERIO_MODE_CONNECT: + // kill mode dead var + DEAD_KILL(o->mode_dead); + // set default mode + o->mode = DATAGRAMPEERIO_MODE_NONE; + // free receiving + free_receiving(o); + // free sending + free_sending(o); + // free socket + BSocket_Free(&o->sock); + break; + case DATAGRAMPEERIO_MODE_BIND: + // kill mode dead var + DEAD_KILL(o->mode_dead); + // set default mode + o->mode = DATAGRAMPEERIO_MODE_NONE; + // remove recv notifier handler + PacketPassNotifier_SetHandler(&o->recv_notifier, NULL, NULL); + // free receiving + free_receiving(o); + // free sending + if (o->bind_sending_up) { + free_sending(o); + } + // free socket + BSocket_Free(&o->sock); + break; + default: + ASSERT(0); + } +} + +void recv_decoder_notifier_handler (DatagramPeerIO *o, uint8_t *data, int data_len) +{ + ASSERT(o->mode == DATAGRAMPEERIO_MODE_BIND) + + // obtain addresses from last received packet + BAddr addr; + BIPAddr local_addr; + DatagramSocketSource_GetLastAddresses(&o->recv_source, &addr, &local_addr); + + if (!o->bind_sending_up) { + // init sending + init_sending(o, addr, local_addr); + + // set sending up + o->bind_sending_up = 1; + } else { + // update addresses + DatagramSocketSink_SetAddresses(&o->send_sink, addr, local_addr); + } +} + +void send_encoder_notifier_handler (DatagramPeerIO *o, uint8_t *data, int data_len) +{ + ASSERT(SPPROTO_HAVE_OTP(o->sp_params)) + + if (o->handler_otp_warning && SPProtoEncoderGroup_GetOTPPosition(&o->encoder_group) == o->handler_otp_warning_num_used) { + o->handler_otp_warning(o->handler_otp_warning_user); + return; + } +} + +int DatagramPeerIO_Init (DatagramPeerIO *o, BReactor *reactor, int payload_mtu, int socket_mtu, struct spproto_security_params sp_params, btime_t latency, PacketPassInterface *recv_userif) +{ + ASSERT(payload_mtu >= 0) + ASSERT(payload_mtu <= UINT16_MAX) + ASSERT(socket_mtu >= 0) + ASSERT(spproto_validate_security_params(sp_params)) + ASSERT(spproto_payload_mtu_for_carrier_mtu(sp_params, socket_mtu) > sizeof(struct fragmentproto_chunk_header)) + ASSERT(PacketPassInterface_GetMTU(recv_userif) >= payload_mtu) + + // set parameters + o->reactor = reactor; + o->payload_mtu = payload_mtu; + o->sp_params = sp_params; + + // init dead var + DEAD_INIT(o->dead); + + // calculate SPProto payload MTU + o->spproto_payload_mtu = spproto_payload_mtu_for_carrier_mtu(o->sp_params, socket_mtu); + + // calculate effective socket MTU + o->effective_socket_mtu = spproto_carrier_mtu_for_payload_mtu(o->sp_params, o->spproto_payload_mtu); + + // set no OTP warning handler + if (SPPROTO_HAVE_OTP(o->sp_params)) { + o->handler_otp_warning = NULL; + } + + // set mode none + o->mode = DATAGRAMPEERIO_MODE_NONE; + + // init persistent I/O objects + if (!init_persistent_io(o, latency, recv_userif)) { + goto fail1; + } + + // init debug object + DebugObject_Init(&o->d_obj); + + return 1; + +fail1: + return 0; +} + +void DatagramPeerIO_Free (DatagramPeerIO *o) +{ + // free debug object + DebugObject_Free(&o->d_obj); + + // reset mode + reset_mode(o); + + // free persistent I/O objects + free_persistent_io(o); + + // free dead var + DEAD_KILL(o->dead); +} + +PacketPassInterface * DatagramPeerIO_GetSendInput (DatagramPeerIO *o) +{ + return FragmentProtoDisassembler_GetInput(&o->send_disassembler); +} + +void DatagramPeerIO_Disconnect (DatagramPeerIO *o) +{ + // reset mode + reset_mode(o); +} + +int DatagramPeerIO_Connect (DatagramPeerIO *o, BAddr addr) +{ + ASSERT(BAddr_IsRecognized(&addr) && !BAddr_IsInvalid(&addr)) + + // reset mode + reset_mode(o); + + // init socket + if (BSocket_Init(&o->sock, o->reactor, addr.type, BSOCKET_TYPE_DGRAM) < 0) { + BLog(BLOG_ERROR, "BSocket_Init failed"); + goto fail1; + } + + // connect the socket + // Windows needs this or receive will fail + if (BSocket_Connect(&o->sock, &addr) < 0) { + BLog(BLOG_ERROR, "BSocket_Connect failed"); + goto fail2; + } + + // init sending + BIPAddr local_addr; + BIPAddr_InitInvalid(&local_addr); + init_sending(o, addr, local_addr); + + // init receiving + init_receiving(o); + + // set mode + o->mode = DATAGRAMPEERIO_MODE_CONNECT; + + // init mode dead var + DEAD_INIT(o->mode_dead); + + return 1; + +fail2: + BSocket_Free(&o->sock); +fail1: + return 0; +} + +int DatagramPeerIO_Bind (DatagramPeerIO *o, BAddr addr) +{ + ASSERT(BAddr_IsRecognized(&addr) && !BAddr_IsInvalid(&addr)) + + // reset mode + reset_mode(o); + + // init socket + if (BSocket_Init(&o->sock, o->reactor, addr.type, BSOCKET_TYPE_DGRAM) < 0) { + BLog(BLOG_ERROR, "BSocket_Init failed"); + goto fail1; + } + + // bind socket + if (BSocket_Bind(&o->sock, &addr) < 0) { + BLog(BLOG_INFO, "BSocket_Bind failed"); + goto fail2; + } + + // init receiving + init_receiving(o); + + // set recv notifier handler + PacketPassNotifier_SetHandler(&o->recv_notifier, (PacketPassNotifier_handler_notify)recv_decoder_notifier_handler, o); + + // set mode + o->mode = DATAGRAMPEERIO_MODE_BIND; + + // init mode dead var + DEAD_INIT(o->mode_dead); + + // set sending not up + o->bind_sending_up = 0; + + return 1; + +fail2: + BSocket_Free(&o->sock); +fail1: + return 0; +} + +void DatagramPeerIO_Flush (DatagramPeerIO *o) +{ + BLog(BLOG_ERROR, "Flushing not implemented"); +} + +void DatagramPeerIO_SetEncryptionKey (DatagramPeerIO *o, uint8_t *encryption_key) +{ + ASSERT(o->sp_params.encryption_mode != SPPROTO_ENCRYPTION_MODE_NONE) + + // set sending key + SPProtoEncoderGroup_SetEncryptionKey(&o->encoder_group, encryption_key); + + // set receiving key + SPProtoDecoder_SetEncryptionKey(&o->recv_decoder, encryption_key); +} + +void DatagramPeerIO_RemoveEncryptionKey (DatagramPeerIO *o) +{ + ASSERT(o->sp_params.encryption_mode != SPPROTO_ENCRYPTION_MODE_NONE) + + // remove sending key + SPProtoEncoderGroup_RemoveEncryptionKey(&o->encoder_group); + + // remove receiving key + SPProtoDecoder_RemoveEncryptionKey(&o->recv_decoder); +} + +void DatagramPeerIO_SetOTPSendSeed (DatagramPeerIO *o, uint16_t seed_id, uint8_t *key, uint8_t *iv) +{ + ASSERT(SPPROTO_HAVE_OTP(o->sp_params)) + + // set sending seed + SPProtoEncoderGroup_SetOTPSeed(&o->encoder_group, seed_id, key, iv); +} + +void DatagramPeerIO_RemoveOTPSendSeed (DatagramPeerIO *o) +{ + ASSERT(SPPROTO_HAVE_OTP(o->sp_params)) + + // remove sending seed + SPProtoEncoderGroup_RemoveOTPSeed(&o->encoder_group); +} + +void DatagramPeerIO_AddOTPRecvSeed (DatagramPeerIO *o, uint16_t seed_id, uint8_t *key, uint8_t *iv) +{ + ASSERT(SPPROTO_HAVE_OTP(o->sp_params)) + + // add receiving seed + SPProtoDecoder_AddOTPSeed(&o->recv_decoder, seed_id, key, iv); +} + +void DatagramPeerIO_RemoveOTPRecvSeeds (DatagramPeerIO *o) +{ + ASSERT(SPPROTO_HAVE_OTP(o->sp_params)) + + // remove receiving seeds + SPProtoDecoder_RemoveOTPSeeds(&o->recv_decoder); +} + +void DatagramPeerIO_SetOTPWarningHandler (DatagramPeerIO *o, DatagramPeerIO_handler_otp_warning handler, void *user, int num_used) +{ + ASSERT(SPPROTO_HAVE_OTP(o->sp_params)) + ASSERT(!handler || num_used > 0) + + o->handler_otp_warning = handler; + o->handler_otp_warning_user = user; + o->handler_otp_warning_num_used = num_used; +} diff --git a/client/DatagramPeerIO.h b/client/DatagramPeerIO.h new file mode 100644 index 000000000..813bcb8de --- /dev/null +++ b/client/DatagramPeerIO.h @@ -0,0 +1,275 @@ +/** + * @file DatagramPeerIO.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Object for comminicating with a peer using a datagram socket. + */ + +#ifndef BADVPN_CLIENT_DATAGRAMPEERIO_H +#define BADVPN_CLIENT_DATAGRAMPEERIO_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Handler function invoked when the number of used OTPs has reached + * the specified warning number in {@link DatagramPeerIO_SetOTPWarningHandler}. + * May be called from within a sending Send call. + * + * @param user as in {@link DatagramPeerIO_SetOTPWarningHandler} + */ +typedef void (*DatagramPeerIO_handler_otp_warning) (void *user); + +/** + * Object for comminicating with a peer using a datagram socket. + * + * The user provides data for sending to the peer through {@link PacketPassInterface}. + * Received data is provided to the user through {@link PacketPassInterface}. + * + * The object has a logical state called a mode, which is one of the following: + * - default - nothing is send or received + * - connecting - an address was provided by the user for sending datagrams to. + * Datagrams are being sent to that address through a socket, + * and datagrams are being received on the same socket. + * - binding - an address was provided by the user to bind a socket to. + * Datagrams are being received on the socket. Datagrams are not being + * sent initially. When a datagram is received, its source address is + * used as a destination address for sending datagrams. + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + BReactor *reactor; + int payload_mtu; + struct spproto_security_params sp_params; + int spproto_payload_mtu; + int effective_socket_mtu; + + // flow error domain + FlowErrorDomain domain; + + // encoder group + SPProtoEncoderGroup encoder_group; + + // persistent I/O objects + + // sending base + FragmentProtoDisassembler send_disassembler; + SPProtoEncoder send_encoder; + PacketRecvNotifier send_notifier; + SinglePacketBuffer send_buffer; + PacketPassConnector send_connector; + + // receiving + PacketRecvConnector recv_connector; + SinglePacketBuffer recv_buffer; + SPProtoDecoder recv_decoder; + PacketPassNotifier recv_notifier; + FragmentProtoAssembler recv_assembler; + + // OTP warning handler + DatagramPeerIO_handler_otp_warning handler_otp_warning; + void *handler_otp_warning_user; + int handler_otp_warning_num_used; + + // mode + int mode; + dead_t mode_dead; + + // in binded mode, whether sending is up + int bind_sending_up; + + // datagram socket + BSocket sock; + + // non-persistent sending objects + DatagramSocketSink send_sink; + + // non-persistent receiving objects + DatagramSocketSource recv_source; +} DatagramPeerIO; + +/** + * Initializes the object. + * The interface is initialized in default mode. + * {@link BLog_Init} must have been done. + * + * @param o the object + * @param reactor {@link BReactor} we live in + * @param payload_mtu maximum payload size. Must be >=0. + * @param socket_mtu maximum datagram size for the socket. Must be >=0. Must be large enough so it is possible to + * send a FragmentProto chunk with one byte of data over SPProto, i.e. the following has to hold: + * spproto_payload_mtu_for_carrier_mtu(sp_params, socket_mtu) > sizeof(struct fragmentproto_chunk_header) + * @param sp_params SPProto security parameters. Must be valid according to {@link spproto_validate_security_params}. + * @param latency latency parameter to {@link FragmentProtoDisassembler_Init}. + * @param recv_userif interface to pass received packets to the user. Its MTU must be >=payload_mtu. + * @return 1 on success, 0 on failure + */ +int DatagramPeerIO_Init (DatagramPeerIO *o, BReactor *reactor, int payload_mtu, int socket_mtu, struct spproto_security_params sp_params, btime_t latency, PacketPassInterface *recv_userif) WARN_UNUSED; + +/** + * Frees the object. + * + * @param o the object + */ +void DatagramPeerIO_Free (DatagramPeerIO *o); + +/** + * Returns an interface the user should use to send packets. + * The OTP warning handler may be called from within Send calls + * to the interface. + * + * @param o the object + * @return sending interface + */ +PacketPassInterface * DatagramPeerIO_GetSendInput (DatagramPeerIO *o); + +/** + * Breaks down the connection if one is configured. + * The interface enters default mode. + * + * @param o the object + */ +void DatagramPeerIO_Disconnect (DatagramPeerIO *o); + +/** + * Attempts to establish connection to the peer which has bound to an address. + * On success, the interface enters connecting mode. + * On failure, the interface enters default mode. + * + * @param o the object + * @param addr address to send packets to. Must be recognized and not invalid. + * @return 1 on success, 0 on failure + */ +int DatagramPeerIO_Connect (DatagramPeerIO *o, BAddr addr) WARN_UNUSED; + +/** + * Attempts to establish connection to the peer by binding to an address. + * On success, the interface enters connecting mode. + * On failure, the interface enters default mode. + * + * @param o the object + * @param addr address to bind to. Must be recognized and not invalid. + * @return 1 on success, 0 on failure + */ +int DatagramPeerIO_Bind (DatagramPeerIO *o, BAddr addr) WARN_UNUSED; + +/** + * Removes any internally buffered packets for sending. + * This can be used when configuring a new connecion to prevent packets encoded with + * previous parameters from being sent over the new connection. + * + * @param o the object + */ +void DatagramPeerIO_Flush (DatagramPeerIO *o); + +/** + * Sets the encryption key to use for sending and receiving. + * Encryption must be enabled. + * + * @param o the object + * @param encryption_key key to use + */ +void DatagramPeerIO_SetEncryptionKey (DatagramPeerIO *o, uint8_t *encryption_key); + +/** + * Removed the encryption key to use for sending and receiving. + * Encryption must be enabled. + * + * @param o the object + */ +void DatagramPeerIO_RemoveEncryptionKey (DatagramPeerIO *o); + +/** + * Sets the OTP seed for sending. + * OTPs must be enabled. + * + * @param o the object + * @param seed_id seed identifier + * @param key OTP encryption key + * @param iv OTP initialization vector + */ +void DatagramPeerIO_SetOTPSendSeed (DatagramPeerIO *o, uint16_t seed_id, uint8_t *key, uint8_t *iv); + +/** + * Removes the OTP seed for sending of one is configured. + * OTPs must be enabled. + * + * @param o the object + */ +void DatagramPeerIO_RemoveOTPSendSeed (DatagramPeerIO *o); + +/** + * Adds an OTP seed for reciving. + * OTPs must be enabled. + * + * @param o the object + * @param seed_id seed identifier + * @param key OTP encryption key + * @param iv OTP initialization vector + */ +void DatagramPeerIO_AddOTPRecvSeed (DatagramPeerIO *o, uint16_t seed_id, uint8_t *key, uint8_t *iv); + +/** + * Removes all OTP seeds for reciving. + * OTPs must be enabled. + * + * @param o the object + */ +void DatagramPeerIO_RemoveOTPRecvSeeds (DatagramPeerIO *o); + +/** + * Sets the OTP warning handler. + * OTPs must be enabled. + * + * @param o the object + * @param handler handler function. NULL to disable handler. + * @param user value passed to handler function + * @param num_used after how many used OTPs to invoke the handler. Must be >0 unless handler is NULL. + * The handler will be invoked when exactly that many OTPs have been used. If the handler + * is configured when the warning level has already been reached, it will not be called + * until a new send seed is set or the handler is reconfigured. + */ +void DatagramPeerIO_SetOTPWarningHandler (DatagramPeerIO *o, DatagramPeerIO_handler_otp_warning handler, void *user, int num_used); + +#endif diff --git a/client/PasswordListener.c b/client/PasswordListener.c new file mode 100644 index 000000000..3a3be0d0d --- /dev/null +++ b/client/PasswordListener.c @@ -0,0 +1,378 @@ +/** + * @file PasswordListener.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include + +#include + +#include + +#include +#include +#include +#include +#include + +#include + +static int password_comparator (void *user, uint64_t *p1, uint64_t *p2); +static void cleanup_client (PasswordListener *l, struct PasswordListenerClient *client); +static void listener_handler (PasswordListener *l); +static void client_try_read (struct PasswordListenerClient *client); +static void client_read_handler (struct PasswordListenerClient *client, int event); +static void client_read_handler_ssl (struct PasswordListenerClient *client, PRInt16 event); + +int password_comparator (void *user, uint64_t *p1, uint64_t *p2) +{ + if (*p1 < *p2) { + return -1; + } + if (*p1 > *p2) { + return 1; + } + return 0; +} + +void cleanup_client (PasswordListener *l, struct PasswordListenerClient *client) +{ + if (l->ssl) { + BPRFileDesc_Free(&client->sock->ssl_bprfd); + ASSERT_FORCE(PR_Close(client->sock->ssl_prfd) == PR_SUCCESS) + } + BSocket_Free(&client->sock->sock); + free(client->sock); +} + +void listener_handler (PasswordListener *l) +{ + // grab client entry + LinkedList2Node *node; + struct PasswordListenerClient *client; + if (node = LinkedList2_GetFirst(&l->clients_free)) { + client = UPPER_OBJECT(node, struct PasswordListenerClient, list_node); + LinkedList2_Remove(&l->clients_free, &client->list_node); + } else { + node = LinkedList2_GetFirst(&l->clients_used); + ASSERT(node) + client = UPPER_OBJECT(node, struct PasswordListenerClient, list_node); + cleanup_client(l, client); + LinkedList2_Remove(&l->clients_used, &client->list_node); + } + + if (!(client->sock = malloc(sizeof(sslsocket)))) { + DEBUG("cannot allocate sslsocket"); + goto fail0; + } + + // accept a client + if (!Listener_Accept(&l->listener, &client->sock->sock, NULL)) { + DEBUG("Listener_Accept failed"); + goto fail1; + } + + DEBUG("Connection accepted"); + + if (l->ssl) { + // create BSocket NSPR file descriptor + BSocketPRFileDesc_Create(&client->sock->bottom_prfd, &client->sock->sock); + + // create SSL file descriptor from the socket's BSocketPRFileDesc + if (!(client->sock->ssl_prfd = SSL_ImportFD(l->model_prfd, &client->sock->bottom_prfd))) { + ASSERT_FORCE(PR_Close(&client->sock->bottom_prfd) == PR_SUCCESS) + goto fail2; + } + + // set server mode + if (SSL_ResetHandshake(client->sock->ssl_prfd, PR_TRUE) != SECSuccess) { + DEBUG("SSL_ResetHandshake failed"); + goto fail3; + } + + // set require client certificate + if (SSL_OptionSet(client->sock->ssl_prfd, SSL_REQUEST_CERTIFICATE, PR_TRUE) != SECSuccess) { + DEBUG("SSL_OptionSet(SSL_REQUEST_CERTIFICATE) failed"); + goto fail3; + } + if (SSL_OptionSet(client->sock->ssl_prfd, SSL_REQUIRE_CERTIFICATE, PR_TRUE) != SECSuccess) { + DEBUG("SSL_OptionSet(SSL_REQUIRE_CERTIFICATE) failed"); + goto fail3; + } + + // initialize BPRFileDesc on SSL file descriptor + BPRFileDesc_Init(&client->sock->ssl_bprfd, client->sock->ssl_prfd); + + // set read handler + BPRFileDesc_AddEventHandler(&client->sock->ssl_bprfd, PR_POLL_READ, (BPRFileDesc_handler)client_read_handler_ssl, client); + } else { + // set read handler + BSocket_AddEventHandler(&client->sock->sock, BSOCKET_READ, (BSocket_handler)client_read_handler, client); + } + + // init buffer + client->recv_buffer_pos = 0; + + // add to used list + LinkedList2_Append(&l->clients_used, &client->list_node); + + // start receiving password + // NOTE: listener and connection can die + client_try_read(client); + return; + + // cleanup on error +fail3: + if (l->ssl) { + ASSERT_FORCE(PR_Close(client->sock->ssl_prfd) == PR_SUCCESS) + } +fail2: + BSocket_Free(&client->sock->sock); +fail1: + free(client->sock); +fail0: + LinkedList2_Append(&l->clients_free, &client->list_node); +} + +void client_try_read (struct PasswordListenerClient *client) +{ + PasswordListener *l = client->l; + + if (l->ssl) { + while (client->recv_buffer_pos < sizeof(client->recv_buffer)) { + PRInt32 recvd = PR_Read( + client->sock->ssl_prfd, + (uint8_t *)&client->recv_buffer + client->recv_buffer_pos, + sizeof(client->recv_buffer) - client->recv_buffer_pos + ); + if (recvd < 0) { + PRErrorCode error = PR_GetError(); + if (error == PR_WOULD_BLOCK_ERROR) { + BPRFileDesc_EnableEvent(&client->sock->ssl_bprfd, PR_POLL_READ); + return; + } + DEBUG("PR_Read failed (%d)", (int)error); + goto free_client; + } + if (recvd == 0) { + DEBUG("Connection terminated"); + goto free_client; + } + client->recv_buffer_pos += recvd; + } + } else { + while (client->recv_buffer_pos < sizeof(client->recv_buffer)) { + int recvd = BSocket_Recv( + &client->sock->sock, + (uint8_t *)&client->recv_buffer + client->recv_buffer_pos, + sizeof(client->recv_buffer) - client->recv_buffer_pos + ); + if (recvd < 0) { + int error = BSocket_GetError(&client->sock->sock); + if (error == BSOCKET_ERROR_LATER) { + BSocket_EnableEvent(&client->sock->sock, BSOCKET_READ); + return; + } + DEBUG("BSocket_Recv failed (%d)", error); + goto free_client; + } + if (recvd == 0) { + DEBUG("Connection terminated"); + goto free_client; + } + client->recv_buffer_pos += recvd; + } + } + + // check password + uint64_t received_pass = ltoh64(client->recv_buffer); + BAVLNode *pw_tree_node = BAVL_LookupExact(&l->passwords, &received_pass); + if (!pw_tree_node) { + DEBUG("WARNING: unknown password"); + goto free_client; + } + PasswordListener_pwentry *pw_entry = UPPER_OBJECT(pw_tree_node, PasswordListener_pwentry, tree_node); + + DEBUG("Password recognized"); + + // remove password entry + BAVL_Remove(&l->passwords, &pw_entry->tree_node); + + // move client entry to free list + LinkedList2_Remove(&l->clients_used, &client->list_node); + LinkedList2_Append(&l->clients_free, &client->list_node); + + if (l->ssl) { + // remove event handler + BPRFileDesc_RemoveEventHandler(&client->sock->ssl_bprfd, PR_POLL_READ); + } else { + // remove event handler + BSocket_RemoveEventHandler(&client->sock->sock, BSOCKET_READ); + } + + // give the socket to the handler + // NOTE: listener can die + pw_entry->handler_client(pw_entry->user, client->sock); + return; + +free_client: + cleanup_client(l, client); + LinkedList2_Remove(&l->clients_used, &client->list_node); + LinkedList2_Append(&l->clients_free, &client->list_node); +} + +void client_read_handler (struct PasswordListenerClient *client, int event) +{ + ASSERT(event == BSOCKET_READ) + BSocket_DisableEvent(&client->sock->sock, BSOCKET_READ); + + // NOTE: listener and connection can die + client_try_read(client); +} + +void client_read_handler_ssl (struct PasswordListenerClient *client, PRInt16 event) +{ + ASSERT(event == PR_POLL_READ) + + // NOTE: listener and connection can die + client_try_read(client); +} + +int PasswordListener_Init (PasswordListener *l, BReactor *bsys, BAddr listen_addr, int max_clients, int ssl, CERTCertificate *cert, SECKEYPrivateKey *key) +{ + ASSERT(!BAddr_IsInvalid(&listen_addr)) + ASSERT(max_clients > 0) + ASSERT(ssl == 0 || ssl == 1) + + l->bsys = bsys; + l->ssl = ssl; + + // allocate client entries + if (!(l->clients_data = malloc(max_clients * sizeof(struct PasswordListenerClient)))) { + goto fail0; + } + + if (l->ssl) { + // initialize model SSL fd + DummyPRFileDesc_Create(&l->model_dprfd); + if (!(l->model_prfd = SSL_ImportFD(NULL, &l->model_dprfd))) { + DEBUG("SSL_ImportFD failed"); + ASSERT_FORCE(PR_Close(&l->model_dprfd) == PR_SUCCESS) + goto fail1; + } + + // set server certificate + if (SSL_ConfigSecureServer(l->model_prfd, cert, key, NSS_FindCertKEAType(cert)) != SECSuccess) { + DEBUG("SSL_ConfigSecureServer failed"); + goto fail2; + } + } + + // initialize client entries + LinkedList2_Init(&l->clients_free); + LinkedList2_Init(&l->clients_used); + int i; + for (i = 0; i < max_clients; i++) { + struct PasswordListenerClient *conn = &l->clients_data[i]; + conn->l = l; + LinkedList2_Append(&l->clients_free, &conn->list_node); + } + + // initialize passwords tree + BAVL_Init(&l->passwords, OFFSET_DIFF(PasswordListener_pwentry, password, tree_node), (BAVL_comparator)password_comparator, NULL); + + // initialize listener + if (!Listener_Init(&l->listener, l->bsys, listen_addr, (Listener_handler)listener_handler, l)) { + DEBUG("Listener_Init failed"); + goto fail2; + } + + // initialize dead variable + DEAD_INIT(l->dead); + + // init debug object + DebugObject_Init(&l->d_obj); + + return 1; + + // cleanup +fail2: + if (l->ssl) { + ASSERT_FORCE(PR_Close(l->model_prfd) == PR_SUCCESS) + } +fail1: + free(l->clients_data); +fail0: + return 0; +} + +void PasswordListener_Free (PasswordListener *l) +{ + // free debug object + DebugObject_Free(&l->d_obj); + + // free clients + LinkedList2Iterator it; + LinkedList2Iterator_InitForward(&it, &l->clients_used); + LinkedList2Node *node; + while (node = LinkedList2Iterator_Next(&it)) { + struct PasswordListenerClient *client = UPPER_OBJECT(node, struct PasswordListenerClient, list_node); + cleanup_client(l, client); + } + + // kill dead variable + DEAD_KILL(l->dead); + + // free listener + Listener_Free(&l->listener); + + // free model SSL file descriptor + if (l->ssl) { + ASSERT_FORCE(PR_Close(l->model_prfd) == PR_SUCCESS) + } + + // free client entries + free(l->clients_data); +} + +uint64_t PasswordListener_AddEntry (PasswordListener *l, PasswordListener_pwentry *entry, PasswordListener_handler_client handler_client, void *user) +{ + while (1) { + // generate password + DEBUG_ZERO_MEMORY(&entry->password, sizeof(entry->password)); + ASSERT_FORCE(RAND_bytes((uint8_t *)&entry->password, sizeof(entry->password)) == 1) + // try inserting + if (BAVL_Insert(&l->passwords, &entry->tree_node, NULL)) { + break; + } + } + + entry->handler_client = handler_client; + entry->user = user; + + return entry->password; +} + +void PasswordListener_RemoveEntry (PasswordListener *l, PasswordListener_pwentry *entry) +{ + BAVL_Remove(&l->passwords, &entry->tree_node); +} diff --git a/client/PasswordListener.h b/client/PasswordListener.h new file mode 100644 index 000000000..c335a2efb --- /dev/null +++ b/client/PasswordListener.h @@ -0,0 +1,143 @@ +/** + * @file PasswordListener.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Object used to listen on a socket, accept clients and identify them + * based on a number they send. + */ + +#ifndef BADVPN_CLIENT_PASSWORDLISTENER_H +#define BADVPN_CLIENT_PASSWORDLISTENER_H + +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Handler function called when a client identifies itself with a password + * belonging to one of the password entries. + * The password entry is unregistered before the handler is called + * and must not be unregistered again. + * + * @param user as in {@link PasswordListener_AddEntry} + * @param sock structure that contains the socket ({@link BSocket}) and, if TLS + * is enabled, the SSL socket (PRFileDesc and {@link BPRFileDesc}). + * The structure was allocated with malloc() and the user + * is responsible for freeing it. + */ +typedef void (*PasswordListener_handler_client) (void *user, sslsocket *sock); + +struct PasswordListenerClient; + +/** + * Object used to listen on a socket, accept clients and identify them + * based on a number they send. + */ +typedef struct { + DebugObject d_obj; + BReactor *bsys; + int ssl; + PRFileDesc model_dprfd; + PRFileDesc *model_prfd; + struct PasswordListenerClient *clients_data; + LinkedList2 clients_free; + LinkedList2 clients_used; + BAVL passwords; + Listener listener; + dead_t dead; +} PasswordListener; + +typedef struct { + uint64_t password; + BAVLNode tree_node; + PasswordListener_handler_client handler_client; + void *user; +} PasswordListener_pwentry; + +struct PasswordListenerClient { + PasswordListener *l; + LinkedList2Node list_node; + sslsocket *sock; + uint64_t recv_buffer; + int recv_buffer_pos; +}; + +/** + * Initializes the object. + * + * @param l the object + * @param bsys reactor we live in + * @param listen_addr address to listen on. Must not be invalid. + * @param max_clients maximum number of client to hold until they are identified. + * Must be >0. + * @param ssl whether to use TLS. Must be 1 or 0. + * @param cert if using TLS, the server certificate + * @param key if using TLS, the private key + * @return 1 on success, 0 on failure + */ +int PasswordListener_Init (PasswordListener *l, BReactor *bsys, BAddr listen_addr, int max_clients, int ssl, CERTCertificate *cert, SECKEYPrivateKey *key) WARN_UNUSED; + +/** + * Frees the object. + * + * @param l the object + */ +void PasswordListener_Free (PasswordListener *l); + +/** + * Registers a password entry. + * + * @param l the object + * @param entry uninitialized entry structure + * @param handler_client handler function to call when a client identifies + * with the password which this function returns + * @param user value to pass to handler function + * @return password which a client should send to be recognized and + * dispatched to the handler function. Should be treated as a numeric + * value, which a client should as a little-endian 64-bit unsigned integer + * when it connects. + */ +uint64_t PasswordListener_AddEntry (PasswordListener *l, PasswordListener_pwentry *entry, PasswordListener_handler_client handler_client, void *user); + +/** + * Unregisters a password entry. + * Note that when a client is dispatched, its entry is unregistered + * automatically and must not be unregistered again here. + * + * @param l the object + * @param entry entry to unregister + */ +void PasswordListener_RemoveEntry (PasswordListener *l, PasswordListener_pwentry *entry); + +#endif diff --git a/client/StreamPeerIO.c b/client/StreamPeerIO.c new file mode 100644 index 000000000..88790c72e --- /dev/null +++ b/client/StreamPeerIO.c @@ -0,0 +1,806 @@ +/** + * @file StreamPeerIO.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#define STREAMPEERIO_COMPONENT_SEND_SINK 0 +#define STREAMPEERIO_COMPONENT_RECEIVE_SOURCE 1 +#define STREAMPEERIO_COMPONENT_RECEIVE_DECODER 2 + +#define MODE_NONE 0 +#define MODE_CONNECT 1 +#define MODE_LISTEN 2 + +#define CONNECT_STATE_CONNECTING 0 +#define CONNECT_STATE_HANDSHAKE 1 +#define CONNECT_STATE_SENDING 2 +#define CONNECT_STATE_FINISHED 3 + +#define LISTEN_STATE_LISTENER 0 +#define LISTEN_STATE_GOTCLIENT 1 +#define LISTEN_STATE_FINISHED 2 + +static int init_persistent_io (StreamPeerIO *pio, PacketPassInterface *user_recv_if); +static void free_persistent_io (StreamPeerIO *pio); +static void connecting_connect_handler (StreamPeerIO *pio, int event); +static SECStatus client_auth_certificate_callback (StreamPeerIO *pio, PRFileDesc *fd, PRBool checkSig, PRBool isServer); +static SECStatus client_client_auth_data_callback (StreamPeerIO *pio, PRFileDesc *fd, CERTDistNames *caNames, CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey); +static void connecting_try_handshake (StreamPeerIO *pio); +static void connecting_handshake_read_handler (StreamPeerIO *pio, PRInt16 event); +static void connecting_try_send_ssl (StreamPeerIO *pio); +static void connecting_try_send_plain (StreamPeerIO *pio); +static void connecting_socket_write_handler_ssl (StreamPeerIO *pio, PRInt16 event); +static void connecting_socket_write_handler_plain (StreamPeerIO *pio, int event); +static void error_handler (StreamPeerIO *pio, int component, const void *data); +static void listener_handler_client (StreamPeerIO *pio, sslsocket *sock); +static int init_io (StreamPeerIO *pio, sslsocket *sock); +static void free_io (StreamPeerIO *pio); +static int compare_certificate (StreamPeerIO *pio, CERTCertificate *cert); +static void reset_state (StreamPeerIO *pio); +static void cleanup_socket (sslsocket *sock, int ssl); + +#define COMPONENT_SOURCE 1 +#define COMPONENT_SINK 2 +#define COMPONENT_DECODER 3 + +void connecting_connect_handler (StreamPeerIO *pio, int event) +{ + ASSERT(event == BSOCKET_CONNECT) + ASSERT(pio->mode == MODE_CONNECT) + ASSERT(pio->connect.state == CONNECT_STATE_CONNECTING) + + // remove connect event handler + BSocket_RemoveEventHandler(&pio->connect.sock.sock, BSOCKET_CONNECT); + + // check connection result + int res = BSocket_GetConnectResult(&pio->connect.sock.sock); + if (res != 0) { + BLog(BLOG_NOTICE, "Connection failed (%d)", res); + goto fail0; + } + + if (pio->ssl) { + // create BSocket NSPR file descriptor + BSocketPRFileDesc_Create(&pio->connect.sock.bottom_prfd, &pio->connect.sock.sock); + + // create SSL file descriptor from the socket's BSocketPRFileDesc + if (!(pio->connect.sock.ssl_prfd = SSL_ImportFD(NULL, &pio->connect.sock.bottom_prfd))) { + ASSERT_FORCE(PR_Close(&pio->connect.sock.bottom_prfd) == PR_SUCCESS) + goto fail0; + } + + // set client mode + if (SSL_ResetHandshake(pio->connect.sock.ssl_prfd, PR_FALSE) != SECSuccess) { + BLog(BLOG_ERROR, "SSL_ResetHandshake failed"); + goto fail_ssl1; + } + + // set verify peer certificate hook + if (SSL_AuthCertificateHook(pio->connect.sock.ssl_prfd, (SSLAuthCertificate)client_auth_certificate_callback, pio) != SECSuccess) { + BLog(BLOG_ERROR, "SSL_AuthCertificateHook failed"); + goto fail_ssl1; + } + + // set client certificate callback + if (SSL_GetClientAuthDataHook(pio->connect.sock.ssl_prfd, (SSLGetClientAuthData)client_client_auth_data_callback, pio) != SECSuccess) { + BLog(BLOG_ERROR, "SSL_GetClientAuthDataHook failed"); + goto fail_ssl1; + } + + // initialize BPRFileDesc on SSL file descriptor + BPRFileDesc_Init(&pio->connect.sock.ssl_bprfd, pio->connect.sock.ssl_prfd); + + // add event handler for driving handshake + BPRFileDesc_AddEventHandler(&pio->connect.sock.ssl_bprfd, PR_POLL_READ, (BPRFileDesc_handler)connecting_handshake_read_handler, pio); + + // change state + pio->connect.state = CONNECT_STATE_HANDSHAKE; + + // start handshake + connecting_try_handshake(pio); + return; + } else { + // add write handler + BSocket_AddEventHandler(&pio->connect.sock.sock, BSOCKET_WRITE, (BSocket_handler)connecting_socket_write_handler_plain, pio); + + // haven't sent anything yet + pio->connect.connecting_sending_sent = 0; + + // change state + pio->connect.state = CONNECT_STATE_SENDING; + + // start sending password + connecting_try_send_plain(pio); + return; + } + + // cleanup +fail_ssl1: + ASSERT_FORCE(PR_Close(pio->connect.sock.ssl_prfd) == PR_SUCCESS) +fail0: + reset_state(pio); + // report error + pio->handler_error(pio->user); + return; +} + +SECStatus client_auth_certificate_callback (StreamPeerIO *pio, PRFileDesc *fd, PRBool checkSig, PRBool isServer) +{ + ASSERT(pio->ssl) + ASSERT(pio->mode == MODE_CONNECT) + ASSERT(pio->connect.state == CONNECT_STATE_HANDSHAKE) + + // This callback is used to bypass checking the server's domain name, as peers + // don't have domain names. We byte-compare the certificate to the one reported + // by the server anyway. + + CERTCertificate *server_cert = SSL_PeerCertificate(pio->connect.sock.ssl_prfd); + if (!server_cert) { + BLog(BLOG_ERROR, "SSL_PeerCertificate failed"); + PORT_SetError(SSL_ERROR_BAD_CERTIFICATE); + return SECFailure; + } + + SECStatus verify_result = CERT_VerifyCertNow(CERT_GetDefaultCertDB(), server_cert, PR_TRUE, certUsageSSLServer, SSL_RevealPinArg(pio->connect.sock.ssl_prfd)); + if (verify_result == SECFailure) { + goto out; + } + + // compare to certificate provided by the server + if (!compare_certificate(pio, server_cert)) { + verify_result = SECFailure; + PORT_SetError(SSL_ERROR_BAD_CERTIFICATE); + goto out; + } + +out: + CERT_DestroyCertificate(server_cert); + return verify_result; +} + +SECStatus client_client_auth_data_callback (StreamPeerIO *pio, PRFileDesc *fd, CERTDistNames *caNames, CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey) +{ + ASSERT(pio->ssl) + ASSERT(pio->mode == MODE_CONNECT) + ASSERT(pio->connect.state == CONNECT_STATE_HANDSHAKE) + + if (!(*pRetCert = CERT_DupCertificate(pio->connect.ssl_cert))) { + return SECFailure; + } + + if (!(*pRetKey = SECKEY_CopyPrivateKey(pio->connect.ssl_key))) { + CERT_DestroyCertificate(*pRetCert); + return SECFailure; + } + + return SECSuccess; +} + +void connecting_try_handshake (StreamPeerIO *pio) +{ + ASSERT(pio->ssl) + ASSERT(pio->mode == MODE_CONNECT) + ASSERT(pio->connect.state == CONNECT_STATE_HANDSHAKE) + + if (SSL_ForceHandshake(pio->connect.sock.ssl_prfd) != SECSuccess) { + PRErrorCode error = PR_GetError(); + if (error == PR_WOULD_BLOCK_ERROR) { + BPRFileDesc_EnableEvent(&pio->connect.sock.ssl_bprfd, PR_POLL_READ); + return; + } + BLog(BLOG_NOTICE, "SSL_ForceHandshake failed (%d)", (int)error); + goto fail0; + } + + // remove client certificate callback + if (SSL_GetClientAuthDataHook(pio->connect.sock.ssl_prfd, NULL, NULL) != SECSuccess) { + BLog(BLOG_ERROR, "SSL_GetClientAuthDataHook failed"); + goto fail0; + } + + // remove verify peer certificate callback + if (SSL_AuthCertificateHook(pio->connect.sock.ssl_prfd, NULL, NULL) != SECSuccess) { + BLog(BLOG_ERROR, "SSL_AuthCertificateHook failed"); + goto fail0; + } + + // remove read handler + BPRFileDesc_RemoveEventHandler(&pio->connect.sock.ssl_bprfd, PR_POLL_READ); + + // add write handler + BPRFileDesc_AddEventHandler(&pio->connect.sock.ssl_bprfd, PR_POLL_WRITE, (BPRFileDesc_handler)connecting_socket_write_handler_ssl, pio); + + // haven't send anything yet + pio->connect.connecting_sending_sent = 0; + + // change state + pio->connect.state = CONNECT_STATE_SENDING; + + // start sending password + connecting_try_send_ssl(pio); + return; + + // cleanup +fail0: + reset_state(pio); + // report error + pio->handler_error(pio->user); + return; +} + +void connecting_handshake_read_handler (StreamPeerIO *pio, PRInt16 event) +{ + connecting_try_handshake(pio); + return; +} + +void connecting_try_send_ssl (StreamPeerIO *pio) +{ + ASSERT(pio->ssl) + ASSERT(pio->mode == MODE_CONNECT) + ASSERT(pio->connect.state == CONNECT_STATE_SENDING) + + while (pio->connect.connecting_sending_sent < sizeof(pio->connect.connecting_password)) { + PRInt32 sent = PR_Write( + pio->connect.sock.ssl_prfd, + (uint8_t *)&pio->connect.connecting_password + pio->connect.connecting_sending_sent, + sizeof(pio->connect.connecting_password) - pio->connect.connecting_sending_sent + ); + if (sent < 0) { + PRErrorCode error = PR_GetError(); + if (error == PR_WOULD_BLOCK_ERROR) { + BPRFileDesc_EnableEvent(&pio->connect.sock.ssl_bprfd, PR_POLL_WRITE); + return; + } + BLog(BLOG_NOTICE, "PR_Write failed (%d)", (int)error); + goto fail0; + } + pio->connect.connecting_sending_sent += sent; + } + + // remove write handler + BPRFileDesc_RemoveEventHandler(&pio->connect.sock.ssl_bprfd, PR_POLL_WRITE); + + // setup i/o + if (!init_io(pio, &pio->connect.sock)) { + goto fail0; + } + + // change state + pio->connect.state = CONNECT_STATE_FINISHED; + + return; + + // cleanup +fail0: + reset_state(pio); + // report error + pio->handler_error(pio->user); + return; +} + +void connecting_try_send_plain (StreamPeerIO *pio) +{ + ASSERT(!pio->ssl) + ASSERT(pio->mode == MODE_CONNECT) + ASSERT(pio->connect.state == CONNECT_STATE_SENDING) + + while (pio->connect.connecting_sending_sent < sizeof(pio->connect.connecting_password)) { + int sent = BSocket_Send( + &pio->connect.sock.sock, + (uint8_t *)&pio->connect.connecting_password + pio->connect.connecting_sending_sent, + sizeof(pio->connect.connecting_password) - pio->connect.connecting_sending_sent + ); + if (sent < 0) { + int error = BSocket_GetError(&pio->connect.sock.sock); + if (error == BSOCKET_ERROR_LATER) { + BSocket_EnableEvent(&pio->connect.sock.sock, BSOCKET_WRITE); + return; + } + BLog(BLOG_NOTICE, "BSocket_Send failed (%d)", error); + goto fail0; + } + pio->connect.connecting_sending_sent += sent; + } + + // remove write event handler + BSocket_RemoveEventHandler(&pio->connect.sock.sock, BSOCKET_WRITE); + + // setup i/o + if (!init_io(pio, &pio->connect.sock)) { + goto fail0; + } + + // change state + pio->connect.state = CONNECT_STATE_FINISHED; + + return; + + // cleanup +fail0: + reset_state(pio); + // report error + pio->handler_error(pio->user); + return; +} + +void connecting_socket_write_handler_ssl (StreamPeerIO *pio, PRInt16 event) +{ + ASSERT(event == PR_POLL_WRITE) + + // try to send data + connecting_try_send_ssl(pio); + return; +} + +void connecting_socket_write_handler_plain (StreamPeerIO *pio, int event) +{ + ASSERT(event == BSOCKET_WRITE) + + // disable write event + BSocket_DisableEvent(&pio->connect.sock.sock, BSOCKET_WRITE); + + // try to send data + connecting_try_send_plain(pio); + return; +} + +void error_handler (StreamPeerIO *pio, int component, const void *data) +{ + ASSERT(pio->sock) + + switch (component) { + case COMPONENT_SOURCE: + case COMPONENT_SINK: + BLog(BLOG_NOTICE,"BSocket error %d", BSocket_GetError(&pio->sock->sock)); + if (pio->ssl) { + BLog(BLOG_NOTICE, "NSPR error %d", (int)PR_GetError()); + } + break; + case COMPONENT_DECODER: + BLog(BLOG_NOTICE, "decoder error %d", *((int *)data)); + break; + default: + ASSERT(0); + } + + // cleanup + reset_state(pio); + // report error + pio->handler_error(pio->user); + return; +} + +void listener_handler_client (StreamPeerIO *pio, sslsocket *sock) +{ + ASSERT(pio->mode == MODE_LISTEN) + ASSERT(pio->listen.state == LISTEN_STATE_LISTENER) + + // remember socket + pio->listen.sock = sock; + + // change state + pio->listen.state = LISTEN_STATE_GOTCLIENT; + + // check ceritficate + if (pio->ssl) { + CERTCertificate *peer_cert = SSL_PeerCertificate(pio->listen.sock->ssl_prfd); + if (!peer_cert) { + BLog(BLOG_ERROR, "SSL_PeerCertificate failed"); + goto fail0; + } + + // compare certificate to the one provided by the server + if (!compare_certificate(pio, peer_cert)) { + CERT_DestroyCertificate(peer_cert); + goto fail0; + } + + CERT_DestroyCertificate(peer_cert); + } + + // setup i/o + if (!init_io(pio, pio->listen.sock)) { + goto fail0; + } + + // change state + pio->listen.state = LISTEN_STATE_FINISHED; + + return; + + // cleanup +fail0: + reset_state(pio); + // report error + pio->handler_error(pio->user); + return; +} + +int init_persistent_io (StreamPeerIO *pio, PacketPassInterface *user_recv_if) +{ + // init error domain + FlowErrorDomain_Init(&pio->ioerrdomain, (FlowErrorDomain_handler)error_handler, pio); + + // init sending objects + PacketCopier_Init(&pio->output_user_copier, pio->payload_mtu); + PacketProtoEncoder_Init(&pio->output_user_ppe, PacketCopier_GetOutput(&pio->output_user_copier)); + PacketPassConnector_Init(&pio->output_connector, PACKETPROTO_ENCLEN(pio->payload_mtu), BReactor_PendingGroup(pio->reactor)); + if (!SinglePacketBuffer_Init(&pio->output_user_spb, PacketProtoEncoder_GetOutput(&pio->output_user_ppe), PacketPassConnector_GetInput(&pio->output_connector), BReactor_PendingGroup(pio->reactor))) { + goto fail1; + } + + // init receiveing objects + StreamRecvConnector_Init(&pio->input_connector, BReactor_PendingGroup(pio->reactor)); + if (!PacketProtoDecoder_Init( + &pio->input_decoder, + FlowErrorReporter_Create(&pio->ioerrdomain, COMPONENT_DECODER), + StreamRecvConnector_GetOutput(&pio->input_connector), + user_recv_if, + BReactor_PendingGroup(pio->reactor) + )) { + goto fail2; + } + + return 1; + + // free receiveing objects +fail2: + StreamRecvConnector_Free(&pio->input_connector); + // free sending objects + SinglePacketBuffer_Free(&pio->output_user_spb); +fail1: + PacketPassConnector_Free(&pio->output_connector); + PacketProtoEncoder_Free(&pio->output_user_ppe); + PacketCopier_Free(&pio->output_user_copier); + + return 0; +} + +void free_persistent_io (StreamPeerIO *pio) +{ + // free receiveing objects + PacketProtoDecoder_Free(&pio->input_decoder); + StreamRecvConnector_Free(&pio->input_connector); + + // free sending objects + SinglePacketBuffer_Free(&pio->output_user_spb); + PacketPassConnector_Free(&pio->output_connector); + PacketProtoEncoder_Free(&pio->output_user_ppe); + PacketCopier_Free(&pio->output_user_copier); +} + +int init_io (StreamPeerIO *pio, sslsocket *sock) +{ + ASSERT(!pio->sock) + + // init sending + StreamPassInterface *sink_interface; + if (pio->ssl) { + PRStreamSink_Init( + &pio->output_sink.ssl, + FlowErrorReporter_Create(&pio->ioerrdomain, COMPONENT_SINK), + &sock->ssl_bprfd + ); + sink_interface = PRStreamSink_GetInput(&pio->output_sink.ssl); + } else { + StreamSocketSink_Init( + &pio->output_sink.plain, + FlowErrorReporter_Create(&pio->ioerrdomain, COMPONENT_SINK), + &sock->sock + ); + sink_interface = StreamSocketSink_GetInput(&pio->output_sink.plain); + } + PacketStreamSender_Init(&pio->output_pss, sink_interface, PACKETPROTO_ENCLEN(pio->payload_mtu)); + PacketPassConnector_ConnectOutput(&pio->output_connector, PacketStreamSender_GetInput(&pio->output_pss)); + + // init receiving + StreamRecvInterface *source_interface; + if (pio->ssl) { + PRStreamSource_Init( + &pio->input_source.ssl, + FlowErrorReporter_Create(&pio->ioerrdomain, COMPONENT_SOURCE), + &sock->ssl_bprfd + ); + source_interface = PRStreamSource_GetOutput(&pio->input_source.ssl); + } else { + StreamSocketSource_Init( + &pio->input_source.plain, + FlowErrorReporter_Create(&pio->ioerrdomain, COMPONENT_SOURCE), + &sock->sock + ); + source_interface = StreamSocketSource_GetOutput(&pio->input_source.plain); + } + StreamRecvConnector_ConnectInput(&pio->input_connector, source_interface); + + pio->sock = sock; + + return 1; +} + +void free_io (StreamPeerIO *pio) +{ + ASSERT(pio->sock) + + // reset decoder + PacketProtoDecoder_Reset(&pio->input_decoder); + + // free receiving + StreamRecvConnector_DisconnectInput(&pio->input_connector); + if (pio->ssl) { + PRStreamSource_Free(&pio->input_source.ssl); + } else { + StreamSocketSource_Free(&pio->input_source.plain); + } + + // free sending + PacketPassConnector_DisconnectOutput(&pio->output_connector); + PacketStreamSender_Free(&pio->output_pss); + if (pio->ssl) { + PRStreamSink_Free(&pio->output_sink.ssl); + } else { + StreamSocketSink_Free(&pio->output_sink.plain); + } + + pio->sock = NULL; +} + +int compare_certificate (StreamPeerIO *pio, CERTCertificate *cert) +{ + ASSERT(pio->ssl) + + PRArenaPool *arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (!arena) { + BLog(BLOG_ERROR, "WARNING: PORT_NewArena failed"); + return 0; + } + + // encode server certificate + SECItem der; + der.len = 0; + der.data = NULL; + if (!SEC_ASN1EncodeItem(arena, &der, cert, SEC_ASN1_GET(CERT_CertificateTemplate))) { + BLog(BLOG_ERROR, "SEC_ASN1EncodeItem failed"); + PORT_FreeArena(arena, PR_FALSE); + return 0; + } + + // byte compare + if (der.len != pio->ssl_peer_cert_len || memcmp(der.data, pio->ssl_peer_cert, der.len)) { + BLog(BLOG_NOTICE, "Client certificate doesn't match"); + PORT_FreeArena(arena, PR_FALSE); + return 0; + } + + PORT_FreeArena(arena, PR_FALSE); + return 1; +} + +void reset_state (StreamPeerIO *pio) +{ + // free resources + switch (pio->mode) { + case MODE_NONE: + break; + case MODE_LISTEN: + switch (pio->listen.state) { + case LISTEN_STATE_FINISHED: + free_io(pio); + case LISTEN_STATE_GOTCLIENT: + cleanup_socket(pio->listen.sock, pio->ssl); + free(pio->listen.sock); + break; + case LISTEN_STATE_LISTENER: + PasswordListener_RemoveEntry(pio->listen.listener, &pio->listen.pwentry); + break; + default: + ASSERT(0); + } + DEAD_KILL(pio->mode_dead); + pio->mode = MODE_NONE; + break; + case MODE_CONNECT: + switch (pio->connect.state) { + case CONNECT_STATE_FINISHED: + free_io(pio); + case CONNECT_STATE_SENDING: + case CONNECT_STATE_HANDSHAKE: + if (pio->ssl) { + BPRFileDesc_Free(&pio->connect.sock.ssl_bprfd); + ASSERT_FORCE(PR_Close(pio->connect.sock.ssl_prfd) == PR_SUCCESS) + } + case CONNECT_STATE_CONNECTING: + BSocket_Free(&pio->connect.sock.sock); + break; + default: + ASSERT(0); + } + DEAD_KILL(pio->mode_dead); + pio->mode = MODE_NONE; + break; + default: + ASSERT(0); + } + + ASSERT(!pio->sock) +} + +void cleanup_socket (sslsocket *sock, int ssl) +{ + if (ssl) { + // free BPRFileDesc + BPRFileDesc_Free(&sock->ssl_bprfd); + // free SSL NSPR file descriptor + ASSERT_FORCE(PR_Close(sock->ssl_prfd) == PR_SUCCESS) + } + + // free socket + BSocket_Free(&sock->sock); +} + +int StreamPeerIO_Init ( + StreamPeerIO *pio, + BReactor *reactor, + int ssl, + uint8_t *ssl_peer_cert, + int ssl_peer_cert_len, + int payload_mtu, + PacketPassInterface *user_recv_if, + StreamPeerIO_handler_error handler_error, + void *user +) +{ + ASSERT(ssl == 0 || ssl == 1) + ASSERT(payload_mtu >= 0) + ASSERT(PacketPassInterface_GetMTU(user_recv_if) >= payload_mtu) + + // init arguments + pio->reactor = reactor; + pio->ssl = ssl; + if (pio->ssl) { + pio->ssl_peer_cert = ssl_peer_cert; + pio->ssl_peer_cert_len = ssl_peer_cert_len; + } + pio->payload_mtu = payload_mtu; + pio->handler_error = handler_error; + pio->user = user; + + // init dead variable + DEAD_INIT(pio->dead); + + // init persistent I/O modules + if (!init_persistent_io(pio, user_recv_if)) { + return 0; + } + + // set mode none + pio->mode = MODE_NONE; + + // set no socket + pio->sock = NULL; + + // init debug object + DebugObject_Init(&pio->d_obj); + + return 1; +} + +void StreamPeerIO_Free (StreamPeerIO *pio) +{ + // free debug object + DebugObject_Free(&pio->d_obj); + + // reset state + reset_state(pio); + + // free persistent I/O modules + free_persistent_io(pio); + + // kill dead variable + DEAD_KILL(pio->dead); +} + +PacketPassInterface * StreamPeerIO_GetSendInput (StreamPeerIO *pio) +{ + return PacketCopier_GetInput(&pio->output_user_copier); +} + +int StreamPeerIO_Connect (StreamPeerIO *pio, BAddr addr, uint64_t password, CERTCertificate *ssl_cert, SECKEYPrivateKey *ssl_key) +{ + ASSERT(BAddr_IsRecognized(&addr) && !BAddr_IsInvalid(&addr)) + + // reset state + reset_state(pio); + + // create socket + if (BSocket_Init(&pio->connect.sock.sock, pio->reactor, addr.type, BSOCKET_TYPE_STREAM) < 0) { + BLog(BLOG_ERROR, "BSocket_Init failed"); + goto fail0; + } + + // attempt connection + if (BSocket_Connect(&pio->connect.sock.sock, &addr) >= 0 || BSocket_GetError(&pio->connect.sock.sock) != BSOCKET_ERROR_IN_PROGRESS) { + BLog(BLOG_NOTICE, "BSocket_Connect failed"); + goto fail1; + } + + // waiting for connection result + BSocket_AddEventHandler(&pio->connect.sock.sock, BSOCKET_CONNECT, (BSocket_handler)connecting_connect_handler, pio); + BSocket_EnableEvent(&pio->connect.sock.sock, BSOCKET_CONNECT); + + // remember data + if (pio->ssl) { + pio->connect.ssl_cert = ssl_cert; + pio->connect.ssl_key = ssl_key; + } + pio->connect.connecting_password = htol64(password); + + // set state + pio->mode = MODE_CONNECT; + DEAD_INIT(pio->mode_dead); + pio->connect.state = CONNECT_STATE_CONNECTING; + + return 1; + +fail1: + BSocket_Free(&pio->connect.sock.sock); +fail0: + return 0; +} + +void StreamPeerIO_Listen (StreamPeerIO *pio, PasswordListener *listener, uint64_t *password) +{ + ASSERT(listener->ssl == pio->ssl) + + // reset state + reset_state(pio); + + // add PasswordListener entry + uint64_t newpass = PasswordListener_AddEntry(listener, &pio->listen.pwentry, (PasswordListener_handler_client)listener_handler_client, pio); + + // remember data + pio->listen.listener = listener; + + // set state + pio->mode = MODE_LISTEN; + DEAD_INIT(pio->mode_dead); + pio->listen.state = LISTEN_STATE_LISTENER; + + *password = newpass; +} diff --git a/client/StreamPeerIO.h b/client/StreamPeerIO.h new file mode 100644 index 000000000..5bdf338fe --- /dev/null +++ b/client/StreamPeerIO.h @@ -0,0 +1,217 @@ +/** + * @file StreamPeerIO.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Object used for communicating with a peer over TCP. + */ + +#ifndef BADVPN_CLIENT_STREAMPEERIO_H +#define BADVPN_CLIENT_STREAMPEERIO_H + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/** + * Callback function invoked when an error occurs with the peer connection. + * The object has entered default state. + * May be called from within a sending Send call. + * + * @param user value given to {@link StreamPeerIO_Init}. + */ +typedef void (*StreamPeerIO_handler_error) (void *user); + +/** + * Object used for communicating with a peer over TCP. + * The object has a logical state which can be one of the following: + * - default state + * - listening state + * - connecting state + */ +typedef struct { + // debug object + DebugObject d_obj; + // dead variable + dead_t dead; + + // common arguments + BReactor *reactor; + int ssl; + uint8_t *ssl_peer_cert; + int ssl_peer_cert_len; + int payload_mtu; + StreamPeerIO_handler_error handler_error; + void *user; + + // persistent I/O modules + + // I/O error domain + FlowErrorDomain ioerrdomain; + + // base sending objects + PacketCopier output_user_copier; + PacketProtoEncoder output_user_ppe; + SinglePacketBuffer output_user_spb; + PacketPassConnector output_connector; + + // receiving objects + StreamRecvConnector input_connector; + PacketProtoDecoder input_decoder; + + // connection side + int mode; + dead_t mode_dead; + + union { + // listening data + struct { + int state; + PasswordListener *listener; + PasswordListener_pwentry pwentry; + sslsocket *sock; + } listen; + // connecting data + struct { + int state; + CERTCertificate *ssl_cert; + SECKEYPrivateKey *ssl_key; + sslsocket sock; + uint64_t connecting_password; + int connecting_sending_sent; + } connect; + }; + + // socket data + sslsocket *sock; + + // sending objects + PacketStreamSender output_pss; + union { + StreamSocketSink plain; + PRStreamSink ssl; + } output_sink; + + // receiving objects + union { + StreamSocketSource plain; + PRStreamSource ssl; + } input_source; +} StreamPeerIO; + +/** + * Initializes the object. + * The object is initialized in default state. + * {@link BLog_Init} must have been done. + * + * @param pio the object + * @param reactor reactor we live in + * @param ssl if nonzero, SSL will be used for peer connection + * @param ssl_peer_cert if using SSL, the certificate we expect the peer to have + * @param ssl_peer_cert_len if using SSL, the length of the certificate + * @param payload_mtu maximum packet size as seen from the user. Must be >=0. + * @param user_recv_if interface to use for submitting received packets. Its MTU + * must be >=payload_mtu. + * @param handler_error handler function invoked when a connection error occurs + * @param user value to pass to handler functions + * @return 1 on success, 0 on failure + */ +int StreamPeerIO_Init ( + StreamPeerIO *pio, + BReactor *reactor, + int ssl, + uint8_t *ssl_peer_cert, + int ssl_peer_cert_len, + int payload_mtu, + PacketPassInterface *user_recv_if, + StreamPeerIO_handler_error handler_error, + void *user +) WARN_UNUSED; + +/** + * Frees the object. + * + * @param pio the object + */ +void StreamPeerIO_Free (StreamPeerIO *pio); + +/** + * Returns the interface for sending packets to the peer. + * The OTP warning handler may be called from within Send calls + * to the interface. + * + * @param pio the object + * @return interface for sending packets to the peer + */ +PacketPassInterface * StreamPeerIO_GetSendInput (StreamPeerIO *pio); + +/** + * Starts an attempt to connect to the peer. + * On success, the object enters connecting state. + * On failure, the object enters default state. + * + * @param pio the object + * @param addr address to connect to. Must be recognized and not invalid. + * @param password identification code to send to the peer + * @param ssl_cert if using SSL, the client certificate to use. This object does not + * take ownership of the certificate; it must remain valid until + * the object is reset. + * @param ssl_key if using SSL, the private key to use. This object does not take + * ownership of the key; it must remain valid until the object is reset. + * @return 1 on success, 0 on failure + */ +int StreamPeerIO_Connect (StreamPeerIO *pio, BAddr addr, uint64_t password, CERTCertificate *ssl_cert, SECKEYPrivateKey *ssl_key) WARN_UNUSED; + +/** + * Starts an attempt to accept a connection from the peer. + * The object enters listening state. + * + * @param pio the object + * @param listener {@link PasswordListener} object to use for accepting a connection. + * The listener must have SSL enabled if and only if this object has + * SSL enabled. The listener must be available until the object is + * reset or {@link StreamPeerIO_handler_up} is called. + * @param password will return the identification code the peer should send when connecting + */ +void StreamPeerIO_Listen (StreamPeerIO *pio, PasswordListener *listener, uint64_t *password); + +#endif diff --git a/client/client.c b/client/client.c new file mode 100644 index 000000000..993c326f0 --- /dev/null +++ b/client/client.c @@ -0,0 +1,3718 @@ +/** + * @file client.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef BADVPN_USE_WINAPI +#include +#endif + +#include + +#include + +#define TRANSPORT_MODE_UDP 0 +#define TRANSPORT_MODE_TCP 1 + +#define LOGGER_STDOUT 1 +#define LOGGER_SYSLOG 2 + +// declares and initializes a pointer x to y +#define POINTER(x, y) typeof (y) *(x) = &(y); + +// program dead variable +dead_t dead; + +// command-line options +struct { + int help; + int version; + int logger; + #ifndef BADVPN_USE_WINAPI + char *logger_syslog_facility; + char *logger_syslog_ident; + #endif + int loglevel; + int loglevels[BLOG_NUM_CHANNELS]; + int ssl; + char *nssdb; + char *client_cert_name; + char *server_name; + char *server_addr; + int num_bind_addrs; + struct { + char *addr; + int num_ports; + int num_ext_addrs; + struct { + char *addr; + char *scope; + } ext_addrs[MAX_EXT_ADDRS]; + } bind_addrs[MAX_BIND_ADDRS]; + char *tapdev; + int transport_mode; + int encryption_mode; + int hash_mode; + int otp_mode; + int otp_num; + int otp_num_warn; + int fragmentation_latency; + int peer_ssl; + char *scopes[MAX_SCOPES]; + int num_scopes; + int send_buffer_size; + int send_buffer_relay_size; +} options; + +// bind addresses +int num_bind_addrs; +struct { + BAddr addr; + int num_ports; + int num_ext_addrs; + struct { + int server_reported_port; + BAddr addr; // if server_reported_port>=0, defined only after hello received + char scope[64]; + } ext_addrs[MAX_EXT_ADDRS]; +} bind_addrs[MAX_BIND_ADDRS]; + +// TCP listeners +PasswordListener listeners[MAX_BIND_ADDRS]; + +// SPProto parameters (UDP only) +struct spproto_security_params sp_params; + +// server address we connect to +BAddr server_addr; + +// server name to use for SSL +char server_name[256]; + +// reactor +BReactor ss; + +// client certificate if using SSL +CERTCertificate *client_cert; + +// client private key if using SSL +SECKEYPrivateKey *client_key; + +// device data +struct device_data device; + +// data communication MTU +int data_mtu; + +// peers list +LinkedList2 peers; +int num_peers; + +// peers by ID hash table +HashTable peers_by_id; +uint32_t peers_by_id_initval; + +// MAC addresses hash table +HashTable mac_table; +uint32_t mac_table_initval; + +// multicast MAC address hash table +HashTable multicast_table; +uint32_t multicast_table_initval; + +// multicast entries +LinkedList2 multicast_entries_free; +struct multicast_table_entry multicast_entries_data[MAX_PEERS*PEER_MAX_GROUPS]; + +// peers that can be user as relays +LinkedList2 relays; + +// peers than need a relay +LinkedList2 waiting_relay_peers; + +// server connection +ServerConnection server; + +// whether server is ready +int server_ready; + +// my ID, defined only after server_ready +peerid_t my_id; + +// cleans everything up that can be cleaned in order to return +// from the event loop and exit +static void terminate (void); + +// prints program name and version to standard output +static void print_help (const char *name); + +// prints program name and version to standard output +static void print_version (void); + +// parses the command line +static int parse_arguments (int argc, char *argv[]); + +// processes certain command line options +static int resolve_arguments (void); + +// handler for program termination request +static void signal_handler (void *unused); + +// provides a buffer for sending a packet to the server +static int server_start_msg (void **data, peerid_t peer_id, int type, int len); + +// submits a written packet to the server +static int server_end_msg (void); + +// adds a new peer +static int peer_add (peerid_t id, int flags, const uint8_t *cert, int cert_len); + +// removes a peer +static int peer_remove (struct peer_data *peer); + +// deallocates peer resources +static void peer_dealloc (struct peer_data *peer); + +// passes a message to the logger, prepending it info about the peer +static void peer_log (struct peer_data *peer, int level, const char *fmt, ...); + +// see if we are the master relative to this peer +static int peer_am_master (struct peer_data *peer); + +// initializes the link +static int peer_init_link (struct peer_data *peer); + +// frees link resources +static void peer_free_link (struct peer_data *peer); + +// creates a fresh link +static int peer_new_link (struct peer_data *peer); + +// registers the peer as a relay provider +static int peer_enable_relay_provider (struct peer_data *peer); + +// unregisters the peer as a relay provider +static int peer_disable_relay_provider (struct peer_data *peer); + +// deallocates peer relay provider resources. Inserts relay users to the +// need relay list. Used while freeing a peer. +static void peer_dealloc_relay_provider (struct peer_data *peer); + +// install relaying for a peer +static int peer_install_relay (struct peer_data *peer, struct peer_data *relay); + +// uninstall relaying for a peer +static int peer_uninstall_relay (struct peer_data *peer); + +// deallocates relaying for a peer. Used when the relay is beeing freed, +// and when uninstalling relaying after having released the connection. +static void peer_dealloc_relay (struct peer_data *peer); + +// handle a peer that needs a relay +static int peer_need_relay (struct peer_data *peer); + +// inserts the peer into the need relay list +static void peer_register_need_relay (struct peer_data *peer); + +// removes the peer from the need relay list +static void peer_unregister_need_relay (struct peer_data *peer); + +// handle a link setup failure +static int peer_reset (struct peer_data *peer); + +// associates a MAC address with a peer +static void peer_add_mac_address (struct peer_data *peer, uint8_t *mac); + +// associate an IPv4 multicast address with a peer +static void peer_join_group (struct peer_data *peer, uint32_t group); + +// disassociate an IPv4 multicast address from a peer +static void peer_leave_group (struct peer_data *peer, uint32_t group); + +// handle incoming peer messages +static void peer_msg (struct peer_data *peer, uint8_t *data, int data_len); + +// handlers for different message types +static void peer_msg_youconnect (struct peer_data *peer, uint8_t *data, int data_len); +static void peer_msg_cannotconnect (struct peer_data *peer, uint8_t *data, int data_len); +static void peer_msg_cannotbind (struct peer_data *peer, uint8_t *data, int data_len); +static void peer_msg_seed (struct peer_data *peer, uint8_t *data, int data_len); +static void peer_msg_confirmseed (struct peer_data *peer, uint8_t *data, int data_len); +static void peer_msg_youretry (struct peer_data *peer, uint8_t *data, int data_len); + +// handler from DatagramPeerIO when we should generate a new OTP send seed +static void peer_udp_pio_handler_seed_warning (struct peer_data *peer); + +// handler from StreamPeerIO when an error occurs on the connection +static void peer_tcp_pio_handler_error (struct peer_data *peer); + +// peer retry timer handler. The timer is used only on the master side, +// wither when we detect an error, or the peer reports an error. +static void peer_reset_timer_handler (struct peer_data *peer); + +// PacketPassInterface handler for receiving packets from the link +static int peer_recv_handler_send (struct peer_data *peer, uint8_t *data, int data_len); + +// processs a packet received on the link. +static int peer_process_received_packet (struct peer_data *peer, uint8_t *data, int data_len); + +// start binding, according to the protocol +static int peer_start_binding (struct peer_data *peer); + +// tries binding on one address, according to the protocol +static int peer_bind (struct peer_data *peer); + +static int peer_udp_bind (struct peer_data *peer, int addr_index); + +static int peer_tcp_bind (struct peer_data *peer, int addr_index); + +static int peer_udp_connect (struct peer_data *peer, BAddr addr, uint8_t *encryption_key); + +static int peer_tcp_connect (struct peer_data *peer, BAddr addr, uint64_t password); + +static int peer_udp_send_connect_info (struct peer_data *peer, int addr_index, int port_adjust, uint8_t *enckey); + +static int peer_tcp_send_connect_info (struct peer_data *peer, int addr_index, uint64_t pass); + +// generates an OTP send seed and sends it to the peer +static int peer_udp_send_seed (struct peer_data *peer); + +// sends a message with no payload to the peer +static int peer_send_simple (struct peer_data *peer, int msgid); + +// submits a relayed frame for sending to the peer +static int peer_submit_relayed_frame (struct peer_data *peer, struct peer_data *source_peer, uint8_t *frame, int frame_len); + +// handler for group timers +static void peer_group_timer_handler (struct peer_group_entry *entry); + +// processes a frame received from a peer addressed to us (rather than to another peer for relaying) +static int peer_process_received_frame (struct peer_data *peer, uint8_t *data, int data_len); + +// handler for peer DataProto up state changes +static void peer_dataproto_handler (struct peer_data *peer, int up); + +// looks for a peer with the given ID +static struct peer_data * find_peer_by_id (peerid_t id); + +// multicast table operations +static void multicast_table_add_entry (struct peer_group_entry *entry); +static void multicast_table_remove_entry (struct peer_group_entry *entry); + +// hash table callback functions +static int peer_groups_table_key_comparator (uint32_t *group1, uint32_t *group2); +static int peer_groups_table_hash_function (uint32_t *group, int modulo); +static int mac_table_key_comparator (uint8_t *mac1, uint8_t *mac2); +static int mac_table_hash_function (uint8_t *mac, int modulo); +static int multicast_table_key_comparator (uint32_t *sig1, uint32_t *sig2); +static int multicast_table_hash_function (uint32_t *sig, int modulo); +static int peers_by_id_key_comparator (peerid_t *id1, peerid_t *id2); +static int peers_by_id_hash_function (peerid_t *id, int modulo); + +// device error handler +static void device_error_handler (void *unused); + +// PacketPassInterfacre handler for packets from the device +static int device_input_handler_send (void *unused, uint8_t *data, int data_len); + +// submits a local frame for sending to the peer. The frame is taken from the device frame buffer. +static int submit_frame_to_peer (struct peer_data *peer); + +// submits the current frame to all peers +static int flood_frame (void); + +// processes the current frame, submitting it to peers +static int device_process_frame (void); + +// inspects a frame read from the device and determines how +// it should be handled. Used for IGMP snooping. +static int hook_outgoing (uint8_t *pos, int len); + +#define HOOK_OUT_DEFAULT 0 +#define HOOK_OUT_FLOOD 1 + +// inpects an incoming frame. Used for IGMP snooping. +static void peer_hook_incoming (struct peer_data *peer, uint8_t *pos, int len); + +// lowers every group entry timer to IGMP_LAST_MEMBER_QUERY_TIME if it's larger +static void lower_group_timers_to_lmqt (uint32_t group); + +// check an IPv4 packet +static int check_ipv4_packet (uint8_t *data, int data_len, struct ipv4_header **out_header, uint8_t **out_payload, int *out_payload_len); + +// assign relays to clients waiting for them +static int assign_relays (void); + +// checks if the given address scope is known (i.e. we can connect to an address in it) +static char * address_scope_known (uint8_t *name, int name_len); + +// handlers for server messages +static void server_handler_error (void *user); +static void server_handler_ready (void *user, peerid_t param_my_id, uint32_t ext_ip); +static void server_handler_newclient (void *user, peerid_t peer_id, int flags, const uint8_t *cert, int cert_len); +static void server_handler_endclient (void *user, peerid_t peer_id); +static void server_handler_message (void *user, peerid_t peer_id, uint8_t *data, int data_len); + +int main (int argc, char *argv[]) +{ + if (argc <= 0) { + return 1; + } + + // init dead variable + DEAD_INIT(dead); + + // parse command-line arguments + if (!parse_arguments(argc, argv)) { + fprintf(stderr, "Failed to parse arguments\n"); + print_help(argv[0]); + goto fail0; + } + + // handle --help and --version + if (options.help) { + print_version(); + print_help(argv[0]); + return 0; + } + if (options.version) { + print_version(); + return 0; + } + + // initialize logger + switch (options.logger) { + case LOGGER_STDOUT: + BLog_InitStdout(); + break; + #ifndef BADVPN_USE_WINAPI + case LOGGER_SYSLOG: + if (!BLog_InitSyslog(options.logger_syslog_ident, options.logger_syslog_facility)) { + fprintf(stderr, "Failed to initialize syslog logger\n"); + goto fail0; + } + break; + #endif + default: + ASSERT(0); + } + + // configure logger channels + for (int i = 0; i < BLOG_NUM_CHANNELS; i++) { + if (options.loglevels[i] >= 0) { + BLog_SetChannelLoglevel(i, options.loglevels[i]); + } + else if (options.loglevel >= 0) { + BLog_SetChannelLoglevel(i, options.loglevel); + } + } + + BLog(BLOG_NOTICE, "initializing "GLOBAL_PRODUCT_NAME" client "GLOBAL_VERSION); + + // initialize sockets + if (BSocket_GlobalInit() < 0) { + BLog(BLOG_ERROR, "BSocket_GlobalInit failed"); + goto fail1; + } + + // init time + BTime_Init(); + + // resolve addresses + if (!resolve_arguments()) { + BLog(BLOG_ERROR, "Failed to resolve arguments"); + goto fail1; + } + + // init reactor + if (!BReactor_Init(&ss)) { + BLog(BLOG_ERROR, "BReactor_Init failed"); + goto fail1; + } + + // setup signal handler + if (!BSignal_Init()) { + BLog(BLOG_ERROR, "BSignal_Init failed"); + goto fail1b; + } + BSignal_Capture(); + if (!BSignal_SetHandler(&ss, signal_handler, NULL)) { + BLog(BLOG_ERROR, "BSignal_SetHandler failed"); + goto fail1b; + } + + if (options.ssl) { + // init NSPR + PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0); + + // register local NSPR file types + if (!DummyPRFileDesc_GlobalInit()) { + BLog(BLOG_ERROR, "DummyPRFileDesc_GlobalInit failed"); + goto fail2; + } + if (!BSocketPRFileDesc_GlobalInit()) { + BLog(BLOG_ERROR, "BSocketPRFileDesc_GlobalInit failed"); + goto fail2; + } + + // init NSS + if (NSS_Init(options.nssdb) != SECSuccess) { + BLog(BLOG_ERROR, "NSS_Init failed (%d)", (int)PR_GetError()); + goto fail2; + } + + // set cipher policy + if (NSS_SetDomesticPolicy() != SECSuccess) { + BLog(BLOG_ERROR, "NSS_SetDomesticPolicy failed (%d)", (int)PR_GetError()); + goto fail3; + } + + // init server cache + if (SSL_ConfigServerSessionIDCache(0, 0, 0, NULL) != SECSuccess) { + BLog(BLOG_ERROR, "SSL_ConfigServerSessionIDCache failed (%d)", (int)PR_GetError()); + goto fail3; + } + + // open server certificate and private key + if (!open_nss_cert_and_key(options.client_cert_name, &client_cert, &client_key)) { + BLog(BLOG_ERROR, "Cannot open certificate and key"); + goto fail4; + } + } + + // init listeners + int num_listeners = 0; + if (options.transport_mode == TRANSPORT_MODE_TCP) { + while (num_listeners < num_bind_addrs) { + POINTER(addr, bind_addrs[num_listeners]) + if (!PasswordListener_Init( + &listeners[num_listeners], &ss, addr->addr, 50, options.peer_ssl, + (options.peer_ssl ? client_cert : NULL), + (options.peer_ssl ? client_key : NULL) + )) { + BLog(BLOG_ERROR, "PasswordListener_Init failed"); + goto fail4a; + } + num_listeners++; + } + } + + // init device + if (!BTap_Init(&device.btap, &ss, options.tapdev, device_error_handler, NULL)) { + BLog(BLOG_ERROR, "BTap_Init failed"); + goto fail5; + } + + // remember device MTU + device.mtu = sizeof(struct ethernet_header) + BTap_GetDeviceMTU(&device.btap); + + BLog(BLOG_INFO, "device MTU is %d", device.mtu); + + // init device input + PacketPassInterface_Init(&device.input_interface, device.mtu, device_input_handler_send, NULL); + if (!SinglePacketBuffer_Init(&device.input_buffer, BTap_GetOutput(&device.btap), &device.input_interface, BReactor_PendingGroup(&ss))) { + goto fail5a; + } + device.framelen = -1; + + // init device output + device.output_interface = BTap_GetInput(&device.btap); + PacketPassInterface_Sender_Init(device.output_interface, NULL, NULL); + + // calculate data MTU + data_mtu = DATAPROTO_MAX_OVERHEAD + device.mtu; + + // init peers list + LinkedList2_Init(&peers); + num_peers = 0; + + // init peers by ID hash table + brandom_randomize((uint8_t *)&peers_by_id_initval, sizeof(peers_by_id_initval)); + if (!HashTable_Init( + &peers_by_id, + OFFSET_DIFF(struct peer_data, id, table_node), + (HashTable_comparator)peers_by_id_key_comparator, + (HashTable_hash_function)peers_by_id_hash_function, + MAX_PEERS + )) { + BLog(BLOG_ERROR, "HashTable_Init failed"); + goto fail7; + } + + // init MAC address table + brandom_randomize((uint8_t *)&mac_table_initval, sizeof(mac_table_initval)); + if (!HashTable_Init( + &mac_table, + OFFSET_DIFF(struct mac_table_entry, mac, table_node), + (HashTable_comparator)mac_table_key_comparator, + (HashTable_hash_function)mac_table_hash_function, + MAX_PEERS * PEER_MAX_MACS + )) { + BLog(BLOG_ERROR, "HashTable_Init failed"); + goto fail8; + } + + // init multicast MAC address table + brandom_randomize((uint8_t *)&multicast_table_initval, sizeof(multicast_table_initval)); + if (!HashTable_Init( + &multicast_table, + OFFSET_DIFF(struct multicast_table_entry, sig, table_node), + (HashTable_comparator)multicast_table_key_comparator, + (HashTable_hash_function)multicast_table_hash_function, + MAX_PEERS * PEER_MAX_GROUPS + )) { + BLog(BLOG_ERROR, "HashTable_Init failed"); + goto fail9; + } + + // init multicast entries + LinkedList2_Init(&multicast_entries_free); + int i; + for (i = 0; i < MAX_PEERS*PEER_MAX_GROUPS; i++) { + struct multicast_table_entry *multicast_entry = &multicast_entries_data[i]; + LinkedList2_Append(&multicast_entries_free, &multicast_entry->free_list_node); + } + + // init relays list + LinkedList2_Init(&relays); + + // init need relay list + LinkedList2_Init(&waiting_relay_peers); + + // start connecting to server + if (!ServerConnection_Init( + &server, &ss, server_addr, SC_KEEPALIVE_INTERVAL, SERVER_BUFFER_MIN_PACKETS, options.ssl, client_cert, client_key, server_name, NULL, + server_handler_error, server_handler_ready, server_handler_newclient, server_handler_endclient, server_handler_message + )) { + BLog(BLOG_ERROR, "ServerConnection_Init failed"); + goto fail10; + } + + // set server not ready + server_ready = 0; + + goto event_loop; + + // cleanup on error +fail10: + HashTable_Free(&multicast_table); +fail9: + HashTable_Free(&mac_table); +fail8: + HashTable_Free(&peers_by_id); +fail7: + SinglePacketBuffer_Free(&device.input_buffer); +fail5a: + PacketPassInterface_Free(&device.input_interface); + BTap_Free(&device.btap); +fail5: + if (options.transport_mode == TRANSPORT_MODE_TCP) { + while (num_listeners-- > 0) { + PasswordListener_Free(&listeners[num_listeners]); + } + } +fail4a: + if (options.ssl) { + CERT_DestroyCertificate(client_cert); + SECKEY_DestroyPrivateKey(client_key); +fail4: + SSL_ShutdownServerSessionIDCache(); +fail3: + SSL_ClearSessionCache(); + ASSERT_FORCE(NSS_Shutdown() == SECSuccess) +fail2: + ASSERT_FORCE(PR_Cleanup() == PR_SUCCESS) + PL_ArenaFinish(); + } + BSignal_RemoveHandler(); +fail1b: + BReactor_Free(&ss); +fail1: + BLog(BLOG_ERROR, "initialization failed"); + BLog_Free(); +fail0: + // finish objects + DebugObjectGlobal_Finish(); + return 1; + +event_loop: + // enter event loop + BLog(BLOG_NOTICE, "entering event loop"); + int ret = BReactor_Exec(&ss); + + // free reactor + BReactor_Free(&ss); + + // free logger + BLog(BLOG_NOTICE, "exiting"); + BLog_Free(); + + // finish objects + DebugObjectGlobal_Finish(); + + return ret; +} + +void terminate (void) +{ + BLog(BLOG_NOTICE, "tearing down"); + + // free peers + LinkedList2Node *node; + while (node = LinkedList2_GetFirst(&peers)) { + struct peer_data *peer = UPPER_OBJECT(node, struct peer_data, list_node); + + // free relaying + if (peer->have_relaying) { + struct peer_data *relay = peer->relaying_peer; + ASSERT(relay->is_relay) + ASSERT(relay->have_link) + + // free relay provider + peer_dealloc_relay_provider(relay); + } + + // free relay provider + if (peer->is_relay) { + peer_dealloc_relay_provider(peer); + } + + // free relay source + if (!DataProtoRelaySource_IsEmpty(&peer->relay_source)) { + DataProtoRelaySource_FreeRelease(&peer->relay_source); + } + + // deallocate peer + peer_dealloc(peer); + } + + // free server + ServerConnection_Free(&server); + + // free hash tables + HashTable_Free(&multicast_table); + HashTable_Free(&mac_table); + HashTable_Free(&peers_by_id); + + // free device input + SinglePacketBuffer_Free(&device.input_buffer); + PacketPassInterface_Free(&device.input_interface); + + // free device + BTap_Free(&device.btap); + + // free listeners + if (options.transport_mode == TRANSPORT_MODE_TCP) { + for (int i = num_bind_addrs - 1; i >= 0; i--) { + PasswordListener_Free(&listeners[i]); + } + } + + if (options.ssl) { + // free client certificate and private key + CERT_DestroyCertificate(client_cert); + SECKEY_DestroyPrivateKey(client_key); + + // free server cache + ASSERT_FORCE(SSL_ShutdownServerSessionIDCache() == SECSuccess) + + // free client cache + SSL_ClearSessionCache(); + + // free NSS + ASSERT_FORCE(NSS_Shutdown() == SECSuccess) + + // free NSPR + ASSERT_FORCE(PR_Cleanup() == PR_SUCCESS) + PL_ArenaFinish(); + } + + // remove signal handler + BSignal_RemoveHandler(); + + // kill dead variable + DEAD_KILL(dead); + + // exit reactor + BReactor_Quit(&ss, 1); +} + +void print_help (const char *name) +{ + printf( + "Usage:\n" + " %s\n" + " [--help]\n" + " [--version]\n" + " [--logger <"LOGGERS_STRING">]\n" + #ifndef BADVPN_USE_WINAPI + " (logger=syslog?\n" + " [--syslog-facility ]\n" + " [--syslog-ident ]\n" + " )\n" + #endif + " [--loglevel <0-5/none/error/warning/notice/info/debug>]\n" + " [--channel-loglevel <0-5/none/error/warning/notice/info/debug>] ...\n" + " [--ssl --nssdb --client-cert-name ]\n" + " [--server-name ]\n" + " --server-addr \n" + " [--tapdev ]\n" + " [--scope ] ...\n" + " [\n" + " --bind-addr \n" + " (transport-mode=udp? --num-ports )\n" + " [--ext-addr ] ...\n" + " ] ...\n" + " --transport-mode \n" + " (transport-mode=udp?\n" + " --encryption-mode \n" + " --hash-mode \n" + " [--otp ]\n" + " [--fragmentation-latency ]\n" + " )\n" + " (transport-mode=tcp?\n" + " (ssl? [--peer-ssl])\n" + " )\n" + " [--send-buffer-size ]\n" + " [--send-buffer-relay-size ]\n" + "Address format is a.b.c.d:port (IPv4) or [addr]:port (IPv6).\n", + name + ); +} + +void print_version (void) +{ + printf(GLOBAL_PRODUCT_NAME" "PROGRAM_NAME" "GLOBAL_VERSION"\n"GLOBAL_COPYRIGHT_NOTICE"\n"); +} + +int parse_arguments (int argc, char *argv[]) +{ + if (argc <= 0) { + return 0; + } + + options.help = 0; + options.version = 0; + options.logger = LOGGER_STDOUT; + #ifndef BADVPN_USE_WINAPI + options.logger_syslog_facility = "daemon"; + options.logger_syslog_ident = argv[0]; + #endif + options.loglevel = -1; + for (int i = 0; i < BLOG_NUM_CHANNELS; i++) { + options.loglevels[i] = -1; + } + options.ssl = 0; + options.nssdb = NULL; + options.client_cert_name = NULL; + options.server_name = NULL; + options.server_addr = NULL; + options.tapdev = NULL; + options.num_scopes = 0; + options.num_bind_addrs = 0; + options.transport_mode = -1; + options.encryption_mode = -1; + options.hash_mode = -1; + options.otp_mode = SPPROTO_OTP_MODE_NONE; + options.fragmentation_latency = PEER_DEFAULT_FRAGMENTATION_LATENCY; + options.peer_ssl = 0; + options.send_buffer_size = PEER_DEFAULT_SEND_BUFFER_SIZE; + options.send_buffer_relay_size = PEER_DEFAULT_SEND_BUFFER_RELAY_SIZE; + + int have_fragmentation_latency = 0; + + int i; + for (i = 1; i < argc; i++) { + char *arg = argv[i]; + if (!strcmp(arg, "--help")) { + options.help = 1; + } + else if (!strcmp(arg, "--version")) { + options.version = 1; + } + else if (!strcmp(arg, "--logger")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + char *arg2 = argv[i + 1]; + if (!strcmp(arg2, "stdout")) { + options.logger = LOGGER_STDOUT; + } + #ifndef BADVPN_USE_WINAPI + else if (!strcmp(arg2, "syslog")) { + options.logger = LOGGER_SYSLOG; + } + #endif + else { + fprintf(stderr, "%s: wrong argument\n", arg); + return 0; + } + i++; + } + #ifndef BADVPN_USE_WINAPI + else if (!strcmp(arg, "--syslog-facility")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + options.logger_syslog_facility = argv[i + 1]; + i++; + } + else if (!strcmp(arg, "--syslog-ident")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + options.logger_syslog_ident = argv[i + 1]; + i++; + } + #endif + else if (!strcmp(arg, "--loglevel")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + if ((options.loglevel = parse_loglevel(argv[i + 1])) < 0) { + fprintf(stderr, "%s: wrong argument\n", arg); + return 0; + } + i++; + } + else if (!strcmp(arg, "--channel-loglevel")) { + if (2 >= argc - i) { + fprintf(stderr, "%s: requires two arguments\n", arg); + return 0; + } + int channel = BLogGlobal_GetChannelByName(argv[i + 1]); + if (channel < 0) { + fprintf(stderr, "%s: wrong channel argument\n", arg); + return 0; + } + int loglevel = parse_loglevel(argv[i + 2]); + if (loglevel < 0) { + fprintf(stderr, "%s: wrong loglevel argument\n", arg); + return 0; + } + options.loglevels[channel] = loglevel; + i += 2; + } + else if (!strcmp(arg, "--ssl")) { + options.ssl = 1; + } + else if (!strcmp(arg, "--nssdb")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + options.nssdb = argv[i + 1]; + i++; + } + else if (!strcmp(arg, "--client-cert-name")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + options.client_cert_name = argv[i + 1]; + i++; + } + else if (!strcmp(arg, "--server-name")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + options.server_name = argv[i + 1]; + i++; + } + else if (!strcmp(arg, "--server-addr")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + options.server_addr = argv[i + 1]; + i++; + } + else if (!strcmp(arg, "--tapdev")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + options.tapdev = argv[i + 1]; + i++; + } + else if (!strcmp(arg, "--scope")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + if (options.num_scopes == MAX_SCOPES) { + fprintf(stderr, "%s: too many\n", arg); + return 0; + } + options.scopes[options.num_scopes] = argv[i + 1]; + options.num_scopes++; + i++; + } + else if (!strcmp(arg, "--bind-addr")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + if (options.num_bind_addrs == MAX_BIND_ADDRS) { + fprintf(stderr, "%s: too many\n", arg); + return 0; + } + POINTER(addr, options.bind_addrs[options.num_bind_addrs]) + addr->addr = argv[i + 1]; + addr->num_ports = -1; + addr->num_ext_addrs = 0; + options.num_bind_addrs++; + i++; + } + else if (!strcmp(arg, "--num-ports")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + if (options.num_bind_addrs == 0) { + fprintf(stderr, "%s: must folow --bind-addr\n", arg); + return 0; + } + POINTER(addr, options.bind_addrs[options.num_bind_addrs - 1]) + if ((addr->num_ports = atoi(argv[i + 1])) < 0) { + fprintf(stderr, "%s: wrong argument\n", arg); + return 0; + } + i++; + } + else if (!strcmp(arg, "--ext-addr")) { + if (2 >= argc - i) { + fprintf(stderr, "%s: requires two arguments\n", arg); + return 0; + } + if (options.num_bind_addrs == 0) { + fprintf(stderr, "%s: must folow --bind-addr\n", arg); + return 0; + } + POINTER(addr, options.bind_addrs[options.num_bind_addrs - 1]) + if (addr->num_ext_addrs == MAX_EXT_ADDRS) { + fprintf(stderr, "%s: too many\n", arg); + return 0; + } + POINTER(eaddr, addr->ext_addrs[addr->num_ext_addrs]) + eaddr->addr = argv[i + 1]; + eaddr->scope = argv[i + 2]; + addr->num_ext_addrs++; + i += 2; + } + else if (!strcmp(arg, "--transport-mode")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + char *arg2 = argv[i + 1]; + if (!strcmp(arg2, "udp")) { + options.transport_mode = TRANSPORT_MODE_UDP; + } + else if (!strcmp(arg2, "tcp")) { + options.transport_mode = TRANSPORT_MODE_TCP; + } + else { + fprintf(stderr, "%s: wrong argument\n", arg); + return 0; + } + i++; + } + else if (!strcmp(arg, "--encryption-mode")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + char *arg2 = argv[i + 1]; + if (!strcmp(arg2, "none")) { + options.encryption_mode = SPPROTO_ENCRYPTION_MODE_NONE; + } + else if (!strcmp(arg2, "blowfish")) { + options.encryption_mode = BENCRYPTION_CIPHER_BLOWFISH; + } + else if (!strcmp(arg2, "aes")) { + options.encryption_mode = BENCRYPTION_CIPHER_AES; + } + else { + fprintf(stderr, "%s: wrong argument\n", arg); + return 0; + } + i++; + } + else if (!strcmp(arg, "--hash-mode")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + char *arg2 = argv[i + 1]; + if (!strcmp(arg2, "none")) { + options.hash_mode = SPPROTO_HASH_MODE_NONE; + } + else if (!strcmp(arg2, "md5")) { + options.hash_mode = BHASH_TYPE_MD5; + } + else if (!strcmp(arg2, "sha1")) { + options.hash_mode = BHASH_TYPE_SHA1; + } + else { + fprintf(stderr, "%s: wrong argument\n", arg); + return 0; + } + i++; + } + else if (!strcmp(arg, "--otp")) { + if (3 >= argc - i) { + fprintf(stderr, "%s: requires three arguments\n", arg); + return 0; + } + char *otp_mode = argv[i + 1]; + char *otp_num = argv[i + 2]; + char *otp_num_warn = argv[i + 3]; + if (!strcmp(otp_mode, "blowfish")) { + options.otp_mode = BENCRYPTION_CIPHER_BLOWFISH; + } + else if (!strcmp(otp_mode, "aes")) { + options.otp_mode = BENCRYPTION_CIPHER_AES; + } + else { + fprintf(stderr, "%s: wrong mode\n", arg); + return 0; + } + if ((options.otp_num = atoi(otp_num)) <= 0) { + fprintf(stderr, "%s: wrong num\n", arg); + return 0; + } + options.otp_num_warn = atoi(otp_num_warn); + if (options.otp_num_warn <= 0 || options.otp_num_warn > options.otp_num) { + fprintf(stderr, "%s: wrong num warn\n", arg); + return 0; + } + i += 3; + } + else if (!strcmp(arg, "--fragmentation-latency")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + options.fragmentation_latency = atoi(argv[i + 1]); + have_fragmentation_latency = 1; + i++; + } + else if (!strcmp(arg, "--peer-ssl")) { + options.peer_ssl = 1; + } + else if (!strcmp(arg, "--send-buffer-size")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + if ((options.send_buffer_size = atoi(argv[i + 1])) <= 0) { + fprintf(stderr, "%s: wrong argument\n", arg); + return 0; + } + i++; + } + else if (!strcmp(arg, "--send-buffer-relay-size")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + if ((options.send_buffer_relay_size = atoi(argv[i + 1])) <= 0) { + fprintf(stderr, "%s: wrong argument\n", arg); + return 0; + } + i++; + } + else { + fprintf(stderr, "unknown option: %s\n", arg); + return 0; + } + } + + if (options.help || options.version) { + return 1; + } + + if (options.ssl != !!options.nssdb) { + fprintf(stderr, "False: --ssl <=> --nssdb\n"); + return 0; + } + + if (options.ssl != !!options.client_cert_name) { + fprintf(stderr, "False: --ssl <=> --client-cert-name\n"); + return 0; + } + + if (!options.server_addr) { + fprintf(stderr, "False: --server-addr\n"); + return 0; + } + + if (options.transport_mode < 0) { + fprintf(stderr, "False: --transport-mode\n"); + return 0; + } + + if ((options.transport_mode == TRANSPORT_MODE_UDP) != (options.encryption_mode >= 0)) { + fprintf(stderr, "False: UDP <=> --encryption-mode\n"); + return 0; + } + + if ((options.transport_mode == TRANSPORT_MODE_UDP) != (options.hash_mode >= 0)) { + fprintf(stderr, "False: UDP <=> --hash-mode\n"); + return 0; + } + + if (!(!(options.otp_mode != SPPROTO_OTP_MODE_NONE) || (options.transport_mode == TRANSPORT_MODE_UDP))) { + fprintf(stderr, "False: --otp => UDP\n"); + return 0; + } + + if (!(!have_fragmentation_latency || (options.transport_mode == TRANSPORT_MODE_UDP))) { + fprintf(stderr, "False: --fragmentation-latency => UDP\n"); + return 0; + } + + if (!(!options.peer_ssl || (options.ssl && options.transport_mode == TRANSPORT_MODE_TCP))) { + fprintf(stderr, "False: --peer-ssl => (--ssl && TCP)\n"); + return 0; + } + + return 1; +} + +int resolve_arguments (void) +{ + // resolve server address + ASSERT(options.server_addr) + if (!BAddr_Parse(&server_addr, options.server_addr, server_name, sizeof(server_name))) { + BLog(BLOG_ERROR, "server addr: BAddr_Parse failed"); + return 0; + } + if (!addr_supported(server_addr)) { + BLog(BLOG_ERROR, "server addr: not supported"); + return 0; + } + + // override server name if requested + if (options.server_name) { + snprintf(server_name, sizeof(server_name), "%s", options.server_name); + } + + // resolve bind addresses and external addresses + num_bind_addrs = 0; + for (int i = 0; i < options.num_bind_addrs; i++) { + POINTER(addr, options.bind_addrs[i]) + POINTER(out, bind_addrs[num_bind_addrs]) + + // read addr + if (!BAddr_Parse(&out->addr, addr->addr, NULL, 0)) { + BLog(BLOG_ERROR, "bind addr: BAddr_Parse failed"); + return 0; + } + + // read num ports + if (options.transport_mode == TRANSPORT_MODE_UDP) { + if (addr->num_ports < 0) { + BLog(BLOG_ERROR, "bind addr: num ports missing"); + return 0; + } + out->num_ports = addr->num_ports; + } + else if (addr->num_ports >= 0) { + BLog(BLOG_ERROR, "bind addr: num ports given, but not using UDP"); + return 0; + } + + // read ext addrs + out->num_ext_addrs = 0; + for (int j = 0; j < addr->num_ext_addrs; j++) { + POINTER(eaddr, addr->ext_addrs[j]) + POINTER(eout, out->ext_addrs[out->num_ext_addrs]) + + // read addr + char *colon = strstr(eaddr->addr, ":"); + if (!colon) { + BLog(BLOG_ERROR, "ext addr: no colon"); + return 0; + } + char addrstr[colon - eaddr->addr + 1]; + memcpy(addrstr, eaddr->addr, colon - eaddr->addr); + addrstr[colon - eaddr->addr] = '\0'; + if (!strcmp(addrstr, "{server_reported}")) { + if ((eout->server_reported_port = atoi(colon + 1)) < 0) { + BLog(BLOG_ERROR, "ext addr: wrong port"); + return 0; + } + } else { + eout->server_reported_port = -1; + if (!BAddr_Parse(&eout->addr, eaddr->addr, NULL, 0)) { + BLog(BLOG_ERROR, "ext addr: BAddr_Parse failed"); + return 0; + } + } + + // read scope + snprintf(eout->scope, sizeof(eout->scope), "%s", eaddr->scope); + + out->num_ext_addrs++; + } + + num_bind_addrs++; + } + + // initialize SPProto parameters + if (options.transport_mode == TRANSPORT_MODE_UDP) { + sp_params.encryption_mode = options.encryption_mode; + sp_params.hash_mode = options.hash_mode; + sp_params.otp_mode = options.otp_mode; + if (options.otp_mode > 0) { + sp_params.otp_num = options.otp_num; + } + } + + return 1; +} + +void signal_handler (void *unused) +{ + BLog(BLOG_NOTICE, "termination requested"); + + terminate(); + return; +} + +int server_start_msg (void **data, peerid_t peer_id, int type, int len) +{ + ASSERT(server_ready) + ASSERT(len >= 0) + ASSERT(len <= MSG_MAX_PAYLOAD) + ASSERT(!(len > 0) || data) + + uint8_t *packet; + DEAD_ENTER(dead) + int res = ServerConnection_StartMessage(&server, (void **)&packet, peer_id, msg_SIZEtype + msg_SIZEpayload(len)); + if (DEAD_LEAVE(dead)) { + return -1; + } + if (!res) { + BLog(BLOG_ERROR, "out of server buffer, exiting"); + terminate(); + return -1; + } + + msgWriter writer; + msgWriter_Init(&writer, packet); + msgWriter_Addtype(&writer, type); + uint8_t *payload_dst = msgWriter_Addpayload(&writer, len); + msgWriter_Finish(&writer); + + if (data) { + *data = payload_dst; + } + + return 0; +} + +int server_end_msg (void) +{ + ASSERT(server_ready) + + DEAD_ENTER(dead) + ServerConnection_EndMessage(&server); + if (DEAD_LEAVE(dead)) { + return -1; + } + + return 0; +} + +int peer_add (peerid_t id, int flags, const uint8_t *cert, int cert_len) +{ + ASSERT(server_ready) + ASSERT(num_peers < MAX_PEERS) + ASSERT(!find_peer_by_id(id)) + ASSERT(id != my_id) + ASSERT(cert_len >= 0) + ASSERT(cert_len <= SCID_NEWCLIENT_MAX_CERT_LEN) + + // allocate structure + struct peer_data *peer = malloc(sizeof(*peer)); + if (!peer) { + BLog(BLOG_ERROR, "peer %d: failed to allocate memory", (int)id); + goto fail0; + } + + // remember id + peer->id = id; + + // remember flags + peer->flags = flags; + + // remember certificate if using SSL + if (options.ssl) { + memcpy(peer->cert, cert, cert_len); + peer->cert_len = cert_len; + } + + // init local flow + if (!DataProtoLocalSource_Init(&peer->local_dpflow, device.mtu, my_id, peer->id, options.send_buffer_size, &ss)) { + BLog(BLOG_ERROR, "peer %d: DataProtoLocalSource_Init failed", (int)id); + goto fail1; + } + + // init relay source + DataProtoRelaySource_Init(&peer->relay_source, peer->id); + + // have no link + peer->have_link = 0; + + // allocate OTP seed buffers + if (options.transport_mode == TRANSPORT_MODE_UDP && SPPROTO_HAVE_OTP(sp_params)) { + if (!(peer->pio.udp.sendseed_sent_key = malloc(BEncryption_cipher_key_size(sp_params.otp_mode)))) { + goto fail3; + } + if (!(peer->pio.udp.sendseed_sent_iv = malloc(BEncryption_cipher_block_size(sp_params.otp_mode)))) { + goto fail4; + } + } + + // have no relaying + peer->have_relaying = 0; + + // not waiting for relay + peer->waiting_relay = 0; + + // init retry timer + BTimer_Init(&peer->reset_timer, PEER_RETRY_TIME, (BTimer_handler)peer_reset_timer_handler, peer); + + // init MAC lists + LinkedList2_Init(&peer->macs_used); + LinkedList2_Init(&peer->macs_free); + // init MAC entries and add them to the free list + int i; + for (i = 0; i < PEER_MAX_MACS; i++) { + struct mac_table_entry *entry = &peer->macs_data[i]; + entry->peer = peer; + LinkedList2_Append(&peer->macs_free, &entry->list_node); + } + + // init groups lists + LinkedList2_Init(&peer->groups_used); + LinkedList2_Init(&peer->groups_free); + // init group entries and add to unused list + for (i = 0; i < PEER_MAX_GROUPS; i++) { + struct peer_group_entry *entry = &peer->groups_data[i]; + entry->peer = peer; + LinkedList2_Append(&peer->groups_free, &entry->list_node); + BTimer_Init(&entry->timer, 0, (BTimer_handler)peer_group_timer_handler, entry); + } + // init groups hash table + if (!HashTable_Init( + &peer->groups_hashtable, + OFFSET_DIFF(struct peer_group_entry, group, table_node), + (HashTable_comparator)peer_groups_table_key_comparator, + (HashTable_hash_function)peer_groups_table_hash_function, + PEER_MAX_GROUPS + )) { + BLog(BLOG_ERROR, "peer %d: HashTable_Init failed", (int)id); + goto fail5; + } + + // add to peers linked list + LinkedList2_Append(&peers, &peer->list_node); + + // add to peers-by-ID hash table + ASSERT_EXECUTE(HashTable_Insert(&peers_by_id, &peer->table_node)) + + // increment number of peers + num_peers++; + + // is not relay server + peer->is_relay = 0; + + // init binding + peer->binding = 0; + + peer_log(peer, BLOG_INFO, "initialized"); + + // start setup process + if (peer_am_master(peer)) { + return peer_start_binding(peer); + } else { + return 0; + } + +fail5: + if (options.transport_mode == TRANSPORT_MODE_UDP && SPPROTO_HAVE_OTP(sp_params)) { + free(peer->pio.udp.sendseed_sent_iv); +fail4: + free(peer->pio.udp.sendseed_sent_key); + } +fail3: + DataProtoRelaySource_Free(&peer->relay_source); + DataProtoLocalSource_Free(&peer->local_dpflow); +fail1: + free(peer); +fail0: + return 0; +} + +int peer_remove (struct peer_data *peer) +{ + peer_log(peer, BLOG_INFO, "removing"); + + // uninstall relaying + if (peer->have_relaying) { + if (peer_uninstall_relay(peer) < 0) { + return -1; + } + } + + // disable relay provider + // this inserts former relay users into the need relay list + if (peer->is_relay) { + peer_dealloc_relay_provider(peer); + } + + // release relay flows + if (!DataProtoRelaySource_IsEmpty(&peer->relay_source)) { + DEAD_ENTER(dead) + DataProtoRelaySource_Release(&peer->relay_source); + if (DEAD_LEAVE(dead)) { + return -1; + } + } + + // deallocate peer + peer_dealloc(peer); + + // assign relays because former relay users are disconnected above + if (assign_relays() < 0) { + return -1; + } + + return 0; +} + +void peer_dealloc (struct peer_data *peer) +{ + ASSERT(!peer->have_relaying) + ASSERT(!peer->is_relay) + ASSERT(DataProtoRelaySource_IsEmpty(&peer->relay_source)) + + LinkedList2Iterator it; + LinkedList2Node *node; + + // remove from waiting relay list + if (peer->waiting_relay) { + peer_unregister_need_relay(peer); + } + + // free link + if (peer->have_link) { + peer_free_link(peer); + } + + // free group entries + LinkedList2Iterator_InitForward(&it, &peer->groups_used); + while (node = LinkedList2Iterator_Next(&it)) { + struct peer_group_entry *group_entry = UPPER_OBJECT(node, struct peer_group_entry, list_node); + ASSERT(group_entry->peer == peer) + multicast_table_remove_entry(group_entry); + BReactor_RemoveTimer(&ss, &group_entry->timer); + } + + // free MAC addresses + LinkedList2Iterator_InitForward(&it, &peer->macs_used); + while (node = LinkedList2Iterator_Next(&it)) { + struct mac_table_entry *mac_entry = UPPER_OBJECT(node, struct mac_table_entry, list_node); + ASSERT(mac_entry->peer == peer) + ASSERT_EXECUTE(HashTable_Remove(&mac_table, mac_entry->mac)) + } + + // decrement number of peers + num_peers--; + + // remove from peers-by-ID hash table + ASSERT_EXECUTE(HashTable_Remove(&peers_by_id, &peer->id)) + + // remove from peers linked list + LinkedList2_Remove(&peers, &peer->list_node); + + // free groups table + HashTable_Free(&peer->groups_hashtable); + + // free retry timer + BReactor_RemoveTimer(&ss, &peer->reset_timer); + + // free OTP seed buffers + if (options.transport_mode == TRANSPORT_MODE_UDP && SPPROTO_HAVE_OTP(sp_params)) { + free(peer->pio.udp.sendseed_sent_iv); + free(peer->pio.udp.sendseed_sent_key); + } + + // free relay source + DataProtoRelaySource_Free(&peer->relay_source); + + // free local flow + DataProtoLocalSource_Free(&peer->local_dpflow); + + // free peer structure + free(peer); +} + +void peer_log (struct peer_data *peer, int level, const char *fmt, ...) +{ + va_list vl; + va_start(vl, fmt); + BLog_Append("peer %d: ", (int)peer->id); + BLog_LogToChannelVarArg(BLOG_CURRENT_CHANNEL, level, fmt, vl); + va_end(vl); +} + +int peer_am_master (struct peer_data *peer) +{ + return (my_id > peer->id); +} + +int peer_init_link (struct peer_data *peer) +{ + ASSERT(!peer->have_link) + ASSERT(!peer->have_relaying) + ASSERT(!peer->waiting_relay) + + ASSERT(!peer->is_relay) + + // init link receive interface + PacketPassInterface_Init(&peer->recv_ppi, data_mtu, (PacketPassInterface_handler_send)peer_recv_handler_send, peer); + + // init transport-specific link objects + PacketPassInterface *link_if; + if (options.transport_mode == TRANSPORT_MODE_UDP) { + // init DatagramPeerIO + if (!DatagramPeerIO_Init( + &peer->pio.udp.pio, &ss, data_mtu, CLIENT_UDP_MTU, + sp_params, options.fragmentation_latency, + &peer->recv_ppi + )) { + peer_log(peer, BLOG_ERROR, "DatagramPeerIO_Init failed"); + goto fail1; + } + + // init OTP warning handler + if (SPPROTO_HAVE_OTP(sp_params)) { + DatagramPeerIO_SetOTPWarningHandler(&peer->pio.udp.pio, (DatagramPeerIO_handler_otp_warning)peer_udp_pio_handler_seed_warning, peer, options.otp_num_warn); + } + + // init send seed state + if (SPPROTO_HAVE_OTP(sp_params)) { + peer->pio.udp.sendseed_nextid = 0; + peer->pio.udp.sendseed_sent = 0; + } + + link_if = DatagramPeerIO_GetSendInput(&peer->pio.udp.pio); + } else { + // init StreamPeerIO + if (!StreamPeerIO_Init( + &peer->pio.tcp.pio, &ss, options.peer_ssl, + (options.peer_ssl ? peer->cert : NULL), + (options.peer_ssl ? peer->cert_len : -1), + data_mtu, &peer->recv_ppi, + (StreamPeerIO_handler_error)peer_tcp_pio_handler_error, peer + )) { + peer_log(peer, BLOG_ERROR, "StreamPeerIO_Init failed"); + goto fail1; + } + link_if = StreamPeerIO_GetSendInput(&peer->pio.tcp.pio); + } + + // init sending + if (!DataProtoDest_Init(&peer->send_dp, &ss, peer->id, link_if, PEER_KEEPALIVE_INTERVAL, PEER_KEEPALIVE_RECEIVE_TIMER, (DataProtoDest_handler)peer_dataproto_handler, peer)) { + peer_log(peer, BLOG_ERROR, "DataProto_Init failed"); + goto fail2; + } + + // attach local flow to our DataProto + DataProtoLocalSource_Attach(&peer->local_dpflow, &peer->send_dp); + + peer->have_link = 1; + + return 1; + +fail2: + if (options.transport_mode == TRANSPORT_MODE_UDP) { + DatagramPeerIO_Free(&peer->pio.udp.pio); + } else { + StreamPeerIO_Free(&peer->pio.tcp.pio); + } +fail1: + PacketPassInterface_Free(&peer->recv_ppi); + return 0; +} + +void peer_free_link (struct peer_data *peer) +{ + ASSERT(peer->have_link) + ASSERT(!peer->is_relay) + + ASSERT(!peer->have_relaying) + ASSERT(!peer->waiting_relay) + + // allow detaching DataProto flows + DataProtoDest_PrepareFree(&peer->send_dp); + + // detach local flow from our DataProto + DataProtoLocalSource_Detach(&peer->local_dpflow); + + // free sending + DataProtoDest_Free(&peer->send_dp); + + // free transport-specific link objects + if (options.transport_mode == TRANSPORT_MODE_UDP) { + // free DatagramPeerIO + DatagramPeerIO_Free(&peer->pio.udp.pio); + } else { + // free StreamPeerIO + StreamPeerIO_Free(&peer->pio.tcp.pio); + } + + // free common link objects + PacketPassInterface_Free(&peer->recv_ppi); + + peer->have_link = 0; +} + +int peer_new_link (struct peer_data *peer) +{ + if (peer->have_link) { + if (peer->is_relay) { + if (peer_disable_relay_provider(peer) < 0) { + return -1; + } + } + + peer_free_link(peer); + } + else if (peer->have_relaying) { + if (peer_uninstall_relay(peer) < 0) { + return -1; + } + } + else if (peer->waiting_relay) { + peer_unregister_need_relay(peer); + } + + if (!peer_init_link(peer)) { + return 0; + } + + return 1; +} + +int peer_enable_relay_provider (struct peer_data *peer) +{ + ASSERT(peer->have_link) + ASSERT(!peer->is_relay) + + ASSERT(!peer->have_relaying) + ASSERT(!peer->waiting_relay) + + // add to relays list + LinkedList2_Append(&relays, &peer->relay_list_node); + + // init users list + LinkedList2_Init(&peer->relay_users); + + peer->is_relay = 1; + + // assign relays + if (assign_relays() < 0) { + return -1; + } + + return 0; +} + +int peer_disable_relay_provider (struct peer_data *peer) +{ + ASSERT(peer->is_relay) + + ASSERT(peer->have_link) + ASSERT(!peer->have_relaying) + ASSERT(!peer->waiting_relay) + + // disconnect relay users + LinkedList2Node *list_node; + while (list_node = LinkedList2_GetFirst(&peer->relay_users)) { + struct peer_data *relay_user = UPPER_OBJECT(list_node, struct peer_data, relaying_list_node); + ASSERT(relay_user->have_relaying) + ASSERT(relay_user->relaying_peer == peer) + + // disconnect relay user + if (peer_uninstall_relay(relay_user) < 0) { + return -1; + } + + // add it to need relay list + peer_register_need_relay(relay_user); + } + + // remove from relays list + LinkedList2_Remove(&relays, &peer->relay_list_node); + + peer->is_relay = 0; + + // assign relays + if (assign_relays() < 0) { + return -1; + } + + return 0; +} + +void peer_dealloc_relay_provider (struct peer_data *peer) +{ + ASSERT(peer->is_relay) + + ASSERT(peer->have_link) + ASSERT(!peer->have_relaying) + ASSERT(!peer->waiting_relay) + + // allow detaching DataProto flows from the relay peer + DataProtoDest_PrepareFree(&peer->send_dp); + + // disconnect relay users + LinkedList2Node *list_node; + while (list_node = LinkedList2_GetFirst(&peer->relay_users)) { + struct peer_data *relay_user = UPPER_OBJECT(list_node, struct peer_data, relaying_list_node); + ASSERT(relay_user->have_relaying) + ASSERT(relay_user->relaying_peer == peer) + + // disconnect relay user + peer_dealloc_relay(relay_user); + + // add it to need relay list + peer_register_need_relay(relay_user); + } + + // remove from relays list + LinkedList2_Remove(&relays, &peer->relay_list_node); + + peer->is_relay = 0; +} + +int peer_install_relay (struct peer_data *peer, struct peer_data *relay) +{ + ASSERT(!peer->have_relaying) + ASSERT(!peer->have_link) + ASSERT(!peer->waiting_relay) + ASSERT(relay->is_relay) + + ASSERT(!peer->is_relay) + ASSERT(relay->have_link) + + peer_log(peer, BLOG_INFO, "installing relaying through %d", (int)relay->id); + + // remember relaying peer + peer->relaying_peer = relay; + + // add to relay's users list + LinkedList2_Append(&relay->relay_users, &peer->relaying_list_node); + + // attach local flow to relay + DataProtoLocalSource_Attach(&peer->local_dpflow, &relay->send_dp); + + peer->have_relaying = 1; + + return 0; +} + +int peer_uninstall_relay (struct peer_data *peer) +{ + ASSERT(peer->have_relaying) + + ASSERT(!peer->have_link) + ASSERT(!peer->waiting_relay) + + struct peer_data *relay = peer->relaying_peer; + ASSERT(relay->is_relay) + ASSERT(relay->have_link) + + peer_log(peer, BLOG_INFO, "uninstalling relaying through %d", (int)relay->id); + + // release local flow before detaching it + DEAD_ENTER(dead) + DataProtoLocalSource_Release(&peer->local_dpflow); + if (DEAD_LEAVE(dead)) { + return -1; + } + + // link out relay + peer_dealloc_relay(peer); + + return 0; +} + +void peer_dealloc_relay (struct peer_data *peer) +{ + ASSERT(peer->have_relaying) + + ASSERT(!peer->have_link) + ASSERT(!peer->waiting_relay) + + struct peer_data *relay = peer->relaying_peer; + ASSERT(relay->is_relay) + ASSERT(relay->have_link) + + // detach local flow from relay + DataProtoLocalSource_Detach(&peer->local_dpflow); + + // remove from relay's users list + LinkedList2_Remove(&relay->relay_users, &peer->relaying_list_node); + + peer->have_relaying = 0; +} + +int peer_need_relay (struct peer_data *peer) +{ + ASSERT(!peer->is_relay) + + if (peer->have_link) { + peer_free_link(peer); + } + + if (peer->have_relaying) { + if (peer_uninstall_relay(peer) < 0) { + return -1; + } + } + + if (peer->waiting_relay) { + // already waiting for relay, do nothing + return 0; + } + + // register the peer as needing a relay + peer_register_need_relay(peer); + + // assign relays + if (assign_relays() < 0) { + return -1; + } + + return 0; +} + +void peer_register_need_relay (struct peer_data *peer) +{ + ASSERT(!peer->waiting_relay) + ASSERT(!peer->have_link) + ASSERT(!peer->have_relaying) + + ASSERT(!peer->is_relay) + + // add to need relay list + LinkedList2_Append(&waiting_relay_peers, &peer->waiting_relay_list_node); + + peer->waiting_relay = 1; +} + +void peer_unregister_need_relay (struct peer_data *peer) +{ + ASSERT(peer->waiting_relay) + + ASSERT(!peer->have_link) + ASSERT(!peer->have_relaying) + ASSERT(!peer->is_relay) + + // remove from need relay list + LinkedList2_Remove(&waiting_relay_peers, &peer->waiting_relay_list_node); + + peer->waiting_relay = 0; +} + +int peer_reset (struct peer_data *peer) +{ + peer_log(peer, BLOG_NOTICE, "resetting"); + + if (peer_am_master(peer)) { + // if we're the master, schedule retry + BReactor_SetTimer(&ss, &peer->reset_timer); + } else { + // if we're the slave, report to master + if (peer_send_simple(peer, MSGID_YOURETRY) < 0) { + return -1; + } + } + + return 0; +} + +void peer_add_mac_address (struct peer_data *peer, uint8_t *mac) +{ + // check if the MAC address is already present in the global table + HashTableNode *old_table_node; + if (HashTable_Lookup(&mac_table, mac, &old_table_node)) { + struct mac_table_entry *old_entry = UPPER_OBJECT(old_table_node, struct mac_table_entry, table_node); + ASSERT(!memcmp(old_entry->mac, mac, 6)) + + // if the MAC is already associated with this peer, only move it to the end of the list + if (old_entry->peer == peer) { + LinkedList2_Remove(&peer->macs_used, &old_entry->list_node); + LinkedList2_Append(&peer->macs_used, &old_entry->list_node); + return; + } + + // remove entry from global hash table + ASSERT_EXECUTE(HashTable_Remove(&mac_table, old_entry->mac)) + + // move entry to peer's unused list + LinkedList2_Remove(&old_entry->peer->macs_used, &old_entry->list_node); + LinkedList2_Append(&old_entry->peer->macs_free, &old_entry->list_node); + } + + peer_log(peer, BLOG_INFO, "adding MAC %02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8"", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + + // aquire MAC address entry, if there are no free ones reuse the oldest used one + LinkedList2Node *node; + struct mac_table_entry *entry; + if (node = LinkedList2_GetFirst(&peer->macs_free)) { + entry = UPPER_OBJECT(node, struct mac_table_entry, list_node); + ASSERT(entry->peer == peer) + + // remove from unused list + LinkedList2_Remove(&peer->macs_free, &entry->list_node); + } else { + node = LinkedList2_GetFirst(&peer->macs_used); + ASSERT(node) + entry = UPPER_OBJECT(node, struct mac_table_entry, list_node); + ASSERT(entry->peer == peer) + + // remove from used list + LinkedList2_Remove(&peer->macs_used, &entry->list_node); + + // remove from global hash table + ASSERT_EXECUTE(HashTable_Remove(&mac_table, entry->mac)) + } + + // copy MAC to entry + memcpy(entry->mac, mac, 6); + + // add entry to used list + LinkedList2_Append(&peer->macs_used, &entry->list_node); + + // add entry to global hash table + ASSERT_EXECUTE(HashTable_Insert(&mac_table, &entry->table_node)) +} + +void peer_join_group (struct peer_data *peer, uint32_t group) +{ + struct peer_group_entry *group_entry; + + HashTableNode *old_table_node; + if (HashTable_Lookup(&peer->groups_hashtable, &group, &old_table_node)) { + group_entry = UPPER_OBJECT(old_table_node, struct peer_group_entry, table_node); + + // move to end of used list + LinkedList2_Remove(&peer->groups_used, &group_entry->list_node); + LinkedList2_Append(&peer->groups_used, &group_entry->list_node); + } else { + peer_log(peer, BLOG_INFO, "joined group %"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8"", + ((uint8_t *)&group)[0], ((uint8_t *)&group)[1], ((uint8_t *)&group)[2], ((uint8_t *)&group)[3] + ); + + // aquire group entry, if there are no free ones reuse the earliest used one + LinkedList2Node *node; + if (node = LinkedList2_GetFirst(&peer->groups_free)) { + group_entry = UPPER_OBJECT(node, struct peer_group_entry, list_node); + + // remove from free list + LinkedList2_Remove(&peer->groups_free, &group_entry->list_node); + } else { + node = LinkedList2_GetFirst(&peer->groups_used); + ASSERT(node) + group_entry = UPPER_OBJECT(node, struct peer_group_entry, list_node); + + // remove from used list + LinkedList2_Remove(&peer->groups_used, &group_entry->list_node); + + // remove from groups hash table + ASSERT_EXECUTE(HashTable_Remove(&peer->groups_hashtable, &group_entry->group)) + + // remove from global multicast table + multicast_table_remove_entry(group_entry); + } + + // add entry to used list + LinkedList2_Append(&peer->groups_used, &group_entry->list_node); + + // set group address in entry + group_entry->group = group; + + // add entry to groups hash table + ASSERT_EXECUTE(HashTable_Insert(&peer->groups_hashtable, &group_entry->table_node)) + + // add entry to global multicast table + multicast_table_add_entry(group_entry); + } + + // start timer + group_entry->timer_endtime = btime_gettime() + IGMP_DEFAULT_GROUP_MEMBERSHIP_INTERVAL; + BReactor_SetTimerAbsolute(&ss, &group_entry->timer, group_entry->timer_endtime); +} + +void peer_leave_group (struct peer_data *peer, uint32_t group) +{ + HashTableNode *table_node; + if (!HashTable_Lookup(&peer->groups_hashtable, &group, &table_node)) { + return; + } + struct peer_group_entry *group_entry = UPPER_OBJECT(table_node, struct peer_group_entry, table_node); + + peer_log(peer, BLOG_INFO, "left group %"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8"", + ((uint8_t *)&group)[0], ((uint8_t *)&group)[1], ((uint8_t *)&group)[2], ((uint8_t *)&group)[3] + ); + + // move to free list + LinkedList2_Remove(&peer->groups_used, &group_entry->list_node); + LinkedList2_Append(&peer->groups_free, &group_entry->list_node); + + // stop timer + BReactor_RemoveTimer(&ss, &group_entry->timer); + + // remove from groups hash table + ASSERT_EXECUTE(HashTable_Remove(&peer->groups_hashtable, &group_entry->group)) + + // remove from global multicast table + multicast_table_remove_entry(group_entry); +} + +void peer_msg (struct peer_data *peer, uint8_t *data, int data_len) +{ + ASSERT(server_ready) + + msgParser parser; + if (!msgParser_Init(&parser, data, data_len)) { + peer_log(peer, BLOG_NOTICE, "msg: failed to parse"); + return; + } + + // read message + uint16_t type; + ASSERT_EXECUTE(msgParser_Gettype(&parser, &type)) + uint8_t *payload; + int payload_len; + ASSERT_EXECUTE(msgParser_Getpayload(&parser, &payload, &payload_len)) + + switch (type) { + case MSGID_YOUCONNECT: + peer_msg_youconnect(peer, payload, payload_len); + return; + case MSGID_CANNOTCONNECT: + peer_msg_cannotconnect(peer, payload, payload_len); + return; + case MSGID_CANNOTBIND: + peer_msg_cannotbind(peer, payload, payload_len); + return; + case MSGID_YOURETRY: + peer_msg_youretry(peer, payload, payload_len); + return; + case MSGID_SEED: + peer_msg_seed(peer, payload, payload_len); + return; + case MSGID_CONFIRMSEED: + peer_msg_confirmseed(peer, payload, payload_len); + return; + default: + BLog(BLOG_NOTICE, "msg: unknown type"); + return; + } +} + +void peer_msg_youconnect (struct peer_data *peer, uint8_t *data, int data_len) +{ + // init parser + msg_youconnectParser parser; + if (!msg_youconnectParser_Init(&parser, data, data_len)) { + peer_log(peer, BLOG_WARNING, "msg_youconnect: failed to parse"); + return; + } + + // try addresses + BAddr addr; + while (1) { + // get address message + uint8_t *addrmsg_data; + int addrmsg_len; + if (!msg_youconnectParser_Getaddr(&parser, &addrmsg_data, &addrmsg_len)) { + peer_log(peer, BLOG_NOTICE, "msg_youconnect: no usable addresses"); + peer_send_simple(peer, MSGID_CANNOTCONNECT); + return; + } + + // parse address message + msg_youconnect_addrParser aparser; + if (!msg_youconnect_addrParser_Init(&aparser, addrmsg_data, addrmsg_len)) { + peer_log(peer, BLOG_WARNING, "msg_youconnect: failed to parse address message"); + return; + } + + // check if the address scope is known + uint8_t *name_data; + int name_len; + ASSERT_EXECUTE(msg_youconnect_addrParser_Getname(&aparser, &name_data, &name_len)) + char *name; + if (!(name = address_scope_known(name_data, name_len))) { + continue; + } + + // read address + uint8_t *addr_data; + int addr_len; + ASSERT_EXECUTE(msg_youconnect_addrParser_Getaddr(&aparser, &addr_data, &addr_len)) + if (!addr_read(addr_data, addr_len, &addr)) { + peer_log(peer, BLOG_WARNING, "msg_youconnect: failed to read address"); + return; + } + + peer_log(peer, BLOG_NOTICE, "msg_youconnect: using address in scope '%s'", name); + break; + } + + // discard further addresses + msg_youconnectParser_Forwardaddr(&parser); + + uint8_t *key; + uint64_t password; + + // read additonal parameters + if (options.transport_mode == TRANSPORT_MODE_UDP) { + if (SPPROTO_HAVE_ENCRYPTION(sp_params)) { + int key_len; + if (!msg_youconnectParser_Getkey(&parser, &key, &key_len)) { + peer_log(peer, BLOG_WARNING, "msg_youconnect: no key"); + return; + } + if (key_len != BEncryption_cipher_key_size(sp_params.encryption_mode)) { + peer_log(peer, BLOG_WARNING, "msg_youconnect: wrong key size"); + return; + } + } + } else { + if (!msg_youconnectParser_Getpassword(&parser, &password)) { + peer_log(peer, BLOG_WARNING, "msg_youconnect: no password"); + return; + } + } + + if (!msg_youconnectParser_GotEverything(&parser)) { + peer_log(peer, BLOG_WARNING, "msg_youconnect: stray data"); + return; + } + + // get a fresh link + int res; + if ((res = peer_new_link(peer)) < 0) { + return; + } + if (!res) { + peer_log(peer, BLOG_ERROR, "msg_youconnect: cannot get link"); + + // retry negotiation + peer_reset(peer); + return; + } + + peer_log(peer, BLOG_INFO, "connecting"); + + if (options.transport_mode == TRANSPORT_MODE_UDP) { + peer_udp_connect(peer, addr, key); + return; + } else { + peer_tcp_connect(peer, addr, password); + return; + } +} + +void peer_msg_cannotconnect (struct peer_data *peer, uint8_t *data, int data_len) +{ + if (data_len != 0) { + peer_log(peer, BLOG_WARNING, "msg_cannotconnect: invalid length"); + return; + } + + if (!peer->binding) { + peer_log(peer, BLOG_WARNING, "msg_cannotconnect: not binding"); + return; + } + + peer_log(peer, BLOG_INFO, "peer could not connect"); + + // continue trying bind addresses + peer_bind(peer); + return; +} + +void peer_msg_cannotbind (struct peer_data *peer, uint8_t *data, int data_len) +{ + if (data_len != 0) { + peer_log(peer, BLOG_WARNING, "msg_cannotbind: invalid length"); + return; + } + + peer_log(peer, BLOG_INFO, "peer cannot bind"); + + if (!peer_am_master(peer)) { + peer_start_binding(peer); + return; + } else { + if (!peer->is_relay) { + peer_need_relay(peer); + return; + } + } +} + +void peer_msg_seed (struct peer_data *peer, uint8_t *data, int data_len) +{ + msg_seedParser parser; + if (!msg_seedParser_Init(&parser, data, data_len)) { + peer_log(peer, BLOG_WARNING, "msg_seed: failed to parse"); + return; + } + + // read message + uint16_t seed_id; + ASSERT_EXECUTE(msg_seedParser_Getseed_id(&parser, &seed_id)) + uint8_t *key; + int key_len; + ASSERT_EXECUTE(msg_seedParser_Getkey(&parser, &key, &key_len)) + uint8_t *iv; + int iv_len; + ASSERT_EXECUTE(msg_seedParser_Getiv(&parser, &iv, &iv_len)) + + if (options.transport_mode != TRANSPORT_MODE_UDP) { + peer_log(peer, BLOG_WARNING, "msg_seed: not in UDP mode"); + return; + } + + if (!SPPROTO_HAVE_OTP(sp_params)) { + peer_log(peer, BLOG_WARNING, "msg_seed: OTPs disabled"); + return; + } + + if (key_len != BEncryption_cipher_key_size(sp_params.otp_mode)) { + peer_log(peer, BLOG_WARNING, "msg_seed: wrong key length"); + return; + } + + if (iv_len != BEncryption_cipher_block_size(sp_params.otp_mode)) { + peer_log(peer, BLOG_WARNING, "msg_seed: wrong IV length"); + return; + } + + if (!peer->have_link) { + peer_log(peer, BLOG_WARNING, "msg_seed: have no link"); + return; + } + + peer_log(peer, BLOG_DEBUG, "received OTP receive seed"); + + // add receive seed + DatagramPeerIO_AddOTPRecvSeed(&peer->pio.udp.pio, seed_id, key, iv); + + // send confirmation + int msg_len = msg_confirmseed_SIZEseed_id; + uint8_t *msg; + if (server_start_msg((void **)&msg, peer->id, MSGID_CONFIRMSEED, msg_len) < 0) { + return; + } + msg_confirmseedWriter writer; + msg_confirmseedWriter_Init(&writer, msg); + msg_confirmseedWriter_Addseed_id(&writer, seed_id); + msg_confirmseedWriter_Finish(&writer); + if (server_end_msg() < 0) { + return; + } +} + +void peer_msg_confirmseed (struct peer_data *peer, uint8_t *data, int data_len) +{ + msg_confirmseedParser parser; + if (!msg_confirmseedParser_Init(&parser, data, data_len)) { + peer_log(peer, BLOG_WARNING, "msg_confirmseed: failed to parse"); + return; + } + + // read message + uint16_t seed_id; + ASSERT_EXECUTE(msg_confirmseedParser_Getseed_id(&parser, &seed_id)) + + if (options.transport_mode != TRANSPORT_MODE_UDP) { + peer_log(peer, BLOG_WARNING, "msg_confirmseed: not in UDP mode"); + return; + } + + if (!SPPROTO_HAVE_OTP(sp_params)) { + peer_log(peer, BLOG_WARNING, "msg_confirmseed: OTPs disabled"); + return; + } + + if (!peer->have_link) { + peer_log(peer, BLOG_WARNING, "msg_confirmseed: have no link"); + return; + } + + if (!peer->pio.udp.sendseed_sent) { + peer_log(peer, BLOG_WARNING, "msg_confirmseed: no seed has been sent"); + return; + } + + if (seed_id != peer->pio.udp.sendseed_sent_id) { + peer_log(peer, BLOG_WARNING, "msg_confirmseed: invalid seed: expecting %d, received %d", (int)peer->pio.udp.sendseed_sent_id, (int)seed_id); + return; + } + + peer_log(peer, BLOG_DEBUG, "OTP send seed confirmed"); + + // no longer waiting for confirmation + peer->pio.udp.sendseed_sent = 0; + + // start using the seed + DatagramPeerIO_SetOTPSendSeed(&peer->pio.udp.pio, peer->pio.udp.sendseed_sent_id, peer->pio.udp.sendseed_sent_key, peer->pio.udp.sendseed_sent_iv); +} + +void peer_msg_youretry (struct peer_data *peer, uint8_t *data, int data_len) +{ + if (data_len != 0) { + peer_log(peer, BLOG_WARNING, "msg_youretry: invalid length"); + return; + } + + if (!peer_am_master(peer)) { + peer_log(peer, BLOG_WARNING, "msg_youretry: we are not master"); + return; + } + + peer_log(peer, BLOG_NOTICE, "requests reset"); + + peer_reset(peer); + return; +} + +void peer_udp_pio_handler_seed_warning (struct peer_data *peer) +{ + ASSERT(options.transport_mode == TRANSPORT_MODE_UDP) + ASSERT(SPPROTO_HAVE_OTP(sp_params)) + ASSERT(peer->have_link) + + // this may come from inside the Send call to the link, so don't send it + // any data here (PacketPassFairQueue can't do that and so can't DataProto) + + if (!peer->pio.udp.sendseed_sent) { + peer_udp_send_seed(peer); + return; + } +} + +void peer_tcp_pio_handler_error (struct peer_data *peer) +{ + ASSERT(options.transport_mode == TRANSPORT_MODE_TCP) + ASSERT(peer->have_link) + + peer_log(peer, BLOG_NOTICE, "TCP connection failed"); + + peer_reset(peer); + return; +} + +void peer_reset_timer_handler (struct peer_data *peer) +{ + ASSERT(peer_am_master(peer)) + + BLog(BLOG_NOTICE, "retry timer expired"); + + // start setup process + peer_start_binding(peer); + return; +} + +int peer_recv_handler_send (struct peer_data *peer, uint8_t *data, int data_len) +{ + ASSERT(peer->have_link) + ASSERT(data_len >= 0) + ASSERT(data_len <= data_mtu) + + if (peer_process_received_packet(peer, data, data_len) < 0) { + return -1; + } + + return 1; +} + +int peer_process_received_packet (struct peer_data *peer, uint8_t *data, int data_len) +{ + ASSERT(peer->have_link) + ASSERT(data_len >= 0) + ASSERT(data_len <= data_mtu) + + uint8_t *orig_data = data; + int orig_data_len = data_len; + + // check dataproto header + if (data_len < sizeof(struct dataproto_header)) { + peer_log(peer, BLOG_NOTICE, "receive: no dataproto header"); + return 0; + } + struct dataproto_header *header = (struct dataproto_header *)data; + data += sizeof(struct dataproto_header); + data_len -= sizeof(struct dataproto_header); + uint8_t flags = header->flags; + peerid_t from_id = ltoh16(header->from_id); + int num_ids = ltoh16(header->num_peer_ids); + + // check destination IDs + if (num_ids > DATAPROTO_MAX_PEER_IDS) { + peer_log(peer, BLOG_NOTICE, "receive: too many destination IDs"); + return 0; + } + if (data_len < num_ids * sizeof(struct dataproto_peer_id)) { + peer_log(peer, BLOG_NOTICE, "receive: invalid length for destination IDs"); + return 0; + } + struct dataproto_peer_id *ids = (struct dataproto_peer_id *)data; + data += num_ids * sizeof(struct dataproto_peer_id); + data_len -= num_ids * sizeof(struct dataproto_peer_id); + + // check remaining data + if (data_len > device.mtu) { + peer_log(peer, BLOG_NOTICE, "receive: frame too large"); + return 0; + } + + // inform DataProto of received packet + + DEAD_ENTER(dead) + DataProtoDest_Received(&peer->send_dp, !!(flags & DATAPROTO_FLAGS_RECEIVING_KEEPALIVES)); + if (DEAD_LEAVE(dead)) { + return -1; + } + + // the frame is still accessible because the link can only be freed from + // message handlers and the retry timer + + if (num_ids > 0) { + // find source peer + struct peer_data *src_peer = find_peer_by_id(from_id); + if (!src_peer) { + peer_log(peer, BLOG_NOTICE, "receive: source peer %d not known", (int)from_id); + return 0; + } + + // iterate over destination IDs + for (int i = 0; i < num_ids; i++) { + peerid_t id = ltoh16(ids[i].id); + if (id == my_id) { + // frame is for us + if (peer_process_received_frame(src_peer, data, data_len) < 0) { + return -1; + } + } else { + // frame is for someone else + do { + // make sure the client is allowed to relay though us + if (!(peer->flags & SCID_NEWCLIENT_FLAG_RELAY_CLIENT)) { + peer_log(peer, BLOG_NOTICE, "relaying not allowed"); + break; + } + + // lookup destination peer + struct peer_data *dest_peer = find_peer_by_id(id); + if (!dest_peer) { + peer_log(peer, BLOG_NOTICE, "relay destination peer not known"); + break; + } + + // check if the destination peer has link + if (!dest_peer->have_link) { + peer_log(peer, BLOG_NOTICE, "relay destination peer has no link"); + break; + } + + // submit the frame for relaying + if (peer_submit_relayed_frame(dest_peer, src_peer, data, data_len) < 0) { + return -1; + } + } while (0); + } + } + } + + return 0; +} + +int peer_start_binding (struct peer_data *peer) +{ + peer->binding = 1; + peer->binding_addrpos = 0; + + return peer_bind(peer); +} + +int peer_bind (struct peer_data *peer) +{ + ASSERT(peer->binding) + ASSERT(peer->binding_addrpos >= 0) + ASSERT(peer->binding_addrpos <= num_bind_addrs) + + int res; + + while (peer->binding_addrpos < num_bind_addrs) { + // if there are no external addresses, skip bind address + if (bind_addrs[peer->binding_addrpos].num_ext_addrs == 0) { + peer->binding_addrpos++; + continue; + } + + // get a fresh link + if ((res = peer_new_link(peer)) < 0) { + return -1; + } + if (!res) { + peer_log(peer, BLOG_ERROR, "cannot get link"); + + // no longer binding + peer->binding = 0; + + // retry negotiation + return peer_reset(peer); + } + + // try to bind + if (options.transport_mode == TRANSPORT_MODE_UDP) { + if ((res = peer_udp_bind(peer, peer->binding_addrpos)) < 0) { + return -1; + } + } else { + if ((res = peer_tcp_bind(peer, peer->binding_addrpos)) < 0) { + return -1; + } + } + + // increment address counter + peer->binding_addrpos++; + + if (res) { + peer_log(peer, BLOG_NOTICE, "bound to address number %d", (peer->binding_addrpos - (int)1)); + return 0; + } + } + + peer_log(peer, BLOG_NOTICE, "no more addresses to bind to"); + + // no longer binding + peer->binding = 0; + + // tell the peer we failed to bind + if (peer_send_simple(peer, MSGID_CANNOTBIND) < 0) { + return -1; + } + + // if we are the slave, setup relaying + if (!peer_am_master(peer)) { + if (!peer->is_relay) { + if (peer_need_relay(peer) < 0) { + return -1; + } + } + } + + return 0; +} + +int peer_udp_bind (struct peer_data *peer, int addr_index) +{ + ASSERT(options.transport_mode == TRANSPORT_MODE_UDP) + ASSERT(addr_index >= 0) + ASSERT(addr_index < num_bind_addrs) + ASSERT(bind_addrs[addr_index].num_ext_addrs > 0) + ASSERT(peer->have_link) + + // get addr + POINTER(addr, bind_addrs[addr_index]); + + // try binding to all ports in the range + int port_add; + for (port_add = 0; port_add < addr->num_ports; port_add++) { + BAddr tryaddr = addr->addr; + BAddr_SetPort(&tryaddr, hton16(ntoh16(BAddr_GetPort(&tryaddr)) + port_add)); + if (DatagramPeerIO_Bind(&peer->pio.udp.pio, tryaddr)) { + break; + } + } + if (port_add == addr->num_ports) { + BLog(BLOG_NOTICE, "failed to bind to any port"); + return 0; + } + + uint8_t key[SPPROTO_HAVE_ENCRYPTION(sp_params) ? BEncryption_cipher_key_size(sp_params.encryption_mode) : 0]; + + // generate and set encryption key + if (SPPROTO_HAVE_ENCRYPTION(sp_params)) { + brandom_randomize(key, sizeof(key)); + DatagramPeerIO_SetEncryptionKey(&peer->pio.udp.pio, key); + } + + // send information to the peer + if (peer_udp_send_connect_info(peer, addr_index, port_add, key) < 0) { + return -1; + } + + // generate and send initial send seed + if (SPPROTO_HAVE_OTP(sp_params)) { + if (peer_udp_send_seed(peer) < 0) { + return -1; + } + } + + return 1; +} + +int peer_tcp_bind (struct peer_data *peer, int addr_index) +{ + ASSERT(options.transport_mode == TRANSPORT_MODE_TCP) + ASSERT(peer->have_link) + ASSERT(addr_index >= 0) + ASSERT(addr_index < num_bind_addrs) + ASSERT(bind_addrs[addr_index].num_ext_addrs > 0) + + // order StreamPeerIO to listen + uint64_t pass; + StreamPeerIO_Listen(&peer->pio.tcp.pio, &listeners[addr_index], &pass); + + // send our address and password to the peer + if (peer_tcp_send_connect_info(peer, addr_index, pass) < 0) { + return -1; + } + + return 1; +} + +int peer_udp_connect (struct peer_data *peer, BAddr addr, uint8_t *encryption_key) +{ + ASSERT(options.transport_mode == TRANSPORT_MODE_UDP) + ASSERT(!BAddr_IsInvalid(&addr)) + ASSERT(peer->have_link) + + // order DatagramPeerIO to connect + if (!DatagramPeerIO_Connect(&peer->pio.udp.pio, addr)) { + peer_log(peer, BLOG_NOTICE, "DatagramPeerIO_Connect failed"); + + // retry negotiation + return peer_reset(peer); + } + + // set encryption key + if (SPPROTO_HAVE_ENCRYPTION(sp_params)) { + DatagramPeerIO_SetEncryptionKey(&peer->pio.udp.pio, encryption_key); + } + + // generate and send initial send seed + if (SPPROTO_HAVE_OTP(sp_params)) { + if (peer_udp_send_seed(peer) < 0) { + return -1; + } + } + + return 0; +} + +int peer_tcp_connect (struct peer_data *peer, BAddr addr, uint64_t password) +{ + ASSERT(options.transport_mode == TRANSPORT_MODE_TCP) + ASSERT(!BAddr_IsInvalid(&addr)) + ASSERT(peer->have_link) + + // order StreamPeerIO to connect + if (!StreamPeerIO_Connect( + &peer->pio.tcp.pio, addr, password, + (options.peer_ssl ? client_cert : NULL), + (options.peer_ssl ? client_key : NULL) + )) { + peer_log(peer, BLOG_NOTICE, "StreamPeerIO_Connect failed"); + + // retry negotiation + return peer_reset(peer); + } + + return 0; +} + +int peer_udp_send_connect_info (struct peer_data *peer, int addr_index, int port_adjust, uint8_t *enckey) +{ + ASSERT(options.transport_mode == TRANSPORT_MODE_UDP) + ASSERT(addr_index >= 0) + ASSERT(addr_index < num_bind_addrs) + ASSERT(bind_addrs[addr_index].num_ext_addrs > 0) + + // remember encryption key size + int key_size; + if (SPPROTO_HAVE_ENCRYPTION(sp_params)) { + key_size = BEncryption_cipher_key_size(sp_params.encryption_mode); + } + + // get address + POINTER(bind_addr, bind_addrs[addr_index]); + + // calculate message length + int msg_len = 0; + for (int i = 0; i < bind_addr->num_ext_addrs; i++) { + int addrmsg_len = + msg_youconnect_addr_SIZEname(strlen(bind_addr->ext_addrs[i].scope)) + + msg_youconnect_addr_SIZEaddr(addr_size(bind_addr->ext_addrs[i].addr)); + msg_len += msg_youconnect_SIZEaddr(addrmsg_len); + } + if (SPPROTO_HAVE_ENCRYPTION(sp_params)) { + msg_len += msg_youconnect_SIZEkey(key_size); + } + + // check if it's too big (because of the addresses) + if (msg_len > MSG_MAX_PAYLOAD) { + BLog(BLOG_ERROR, "cannot send too big youconnect message"); + return 0; + } + + // start message + uint8_t *msg; + if (server_start_msg((void **)&msg, peer->id, MSGID_YOUCONNECT, msg_len) < 0) { + return -1; + } + + // init writer + msg_youconnectWriter writer; + msg_youconnectWriter_Init(&writer, msg); + + // write addresses + for (int i = 0; i < bind_addr->num_ext_addrs; i++) { + int name_len = strlen(bind_addr->ext_addrs[i].scope); + int addr_len = addr_size(bind_addr->ext_addrs[i].addr); + + // get a pointer for writing the address + int addrmsg_len = + msg_youconnect_addr_SIZEname(name_len) + + msg_youconnect_addr_SIZEaddr(addr_len); + uint8_t *addrmsg_dst = msg_youconnectWriter_Addaddr(&writer, addrmsg_len); + + // init address writer + msg_youconnect_addrWriter awriter; + msg_youconnect_addrWriter_Init(&awriter, addrmsg_dst); + + // write scope + uint8_t *name_dst = msg_youconnect_addrWriter_Addname(&awriter, name_len); + memcpy(name_dst, bind_addr->ext_addrs[i].scope, name_len); + + // write address with adjusted port + BAddr addr = bind_addr->ext_addrs[i].addr; + BAddr_SetPort(&addr, hton16(ntoh16(BAddr_GetPort(&addr)) + port_adjust)); + uint8_t *addr_dst = msg_youconnect_addrWriter_Addaddr(&awriter, addr_len); + addr_write(addr_dst, addr); + + // finish address writer + msg_youconnect_addrWriter_Finish(&awriter); + } + + // write encryption key + if (SPPROTO_HAVE_ENCRYPTION(sp_params)) { + uint8_t *key_dst = msg_youconnectWriter_Addkey(&writer, key_size); + memcpy(key_dst, enckey, key_size); + } + + // finish writer + msg_youconnectWriter_Finish(&writer); + + // end message + if (server_end_msg() < 0) { + return -1; + } + + return 0; +} + +int peer_tcp_send_connect_info (struct peer_data *peer, int addr_index, uint64_t pass) +{ + ASSERT(options.transport_mode == TRANSPORT_MODE_TCP) + ASSERT(addr_index >= 0) + ASSERT(addr_index < num_bind_addrs) + ASSERT(bind_addrs[addr_index].num_ext_addrs > 0) + + // get address + POINTER(bind_addr, bind_addrs[addr_index]); + + // calculate message length + int msg_len = 0; + for (int i = 0; i < bind_addr->num_ext_addrs; i++) { + int addrmsg_len = + msg_youconnect_addr_SIZEname(strlen(bind_addr->ext_addrs[i].scope)) + + msg_youconnect_addr_SIZEaddr(addr_size(bind_addr->ext_addrs[i].addr)); + msg_len += msg_youconnect_SIZEaddr(addrmsg_len); + } + msg_len += msg_youconnect_SIZEpassword; + + // check if it's too big (because of the addresses) + if (msg_len > MSG_MAX_PAYLOAD) { + BLog(BLOG_ERROR, "cannot send too big youconnect message"); + return 0; + } + + // start message + uint8_t *msg; + if (server_start_msg((void **)&msg, peer->id, MSGID_YOUCONNECT, msg_len) < 0) { + return -1; + } + + // init writer + msg_youconnectWriter writer; + msg_youconnectWriter_Init(&writer, msg); + + // write addresses + for (int i = 0; i < bind_addr->num_ext_addrs; i++) { + int name_len = strlen(bind_addr->ext_addrs[i].scope); + int addr_len = addr_size(bind_addr->ext_addrs[i].addr); + + // get a pointer for writing the address + int addrmsg_len = + msg_youconnect_addr_SIZEname(name_len) + + msg_youconnect_addr_SIZEaddr(addr_len); + uint8_t *addrmsg_dst = msg_youconnectWriter_Addaddr(&writer, addrmsg_len); + + // init address writer + msg_youconnect_addrWriter awriter; + msg_youconnect_addrWriter_Init(&awriter, addrmsg_dst); + + // write scope + uint8_t *name_dst = msg_youconnect_addrWriter_Addname(&awriter, name_len); + memcpy(name_dst, bind_addr->ext_addrs[i].scope, name_len); + + // write address + uint8_t *addr_dst = msg_youconnect_addrWriter_Addaddr(&awriter, addr_len); + addr_write(addr_dst, bind_addr->ext_addrs[i].addr); + + // finish address writer + msg_youconnect_addrWriter_Finish(&awriter); + } + + // write password + msg_youconnectWriter_Addpassword(&writer, pass); + + // finish writer + msg_youconnectWriter_Finish(&writer); + + // end message + if (server_end_msg() < 0) { + return -1; + } + + return 0; +} + +int peer_udp_send_seed (struct peer_data *peer) +{ + ASSERT(options.transport_mode == TRANSPORT_MODE_UDP) + ASSERT(SPPROTO_HAVE_OTP(sp_params)) + ASSERT(peer->have_link) + ASSERT(!peer->pio.udp.sendseed_sent) + + peer_log(peer, BLOG_DEBUG, "sending OTP send seed"); + + int key_len = BEncryption_cipher_key_size(sp_params.otp_mode); + int iv_len = BEncryption_cipher_block_size(sp_params.otp_mode); + + // generate seed + peer->pio.udp.sendseed_sent_id = peer->pio.udp.sendseed_nextid; + brandom_randomize(peer->pio.udp.sendseed_sent_key, key_len); + brandom_randomize(peer->pio.udp.sendseed_sent_iv, iv_len); + + // set as sent, increment next seed ID + peer->pio.udp.sendseed_sent = 1; + peer->pio.udp.sendseed_nextid++; + + // send seed to the peer + int msg_len = msg_seed_SIZEseed_id + msg_seed_SIZEkey(key_len) + msg_seed_SIZEiv(iv_len); + uint8_t *msg; + if (server_start_msg((void **)&msg, peer->id, MSGID_SEED, msg_len) < 0) { + return -1; + } + msg_seedWriter writer; + msg_seedWriter_Init(&writer, msg); + msg_seedWriter_Addseed_id(&writer, peer->pio.udp.sendseed_sent_id); + uint8_t *key_dst = msg_seedWriter_Addkey(&writer, key_len); + memcpy(key_dst, peer->pio.udp.sendseed_sent_key, key_len); + uint8_t *iv_dst = msg_seedWriter_Addiv(&writer, iv_len); + memcpy(iv_dst, peer->pio.udp.sendseed_sent_iv, iv_len); + msg_seedWriter_Finish(&writer); + if (server_end_msg() < 0) { + return -1; + } + + return 0; +} + +int peer_send_simple (struct peer_data *peer, int msgid) +{ + if (server_start_msg(NULL, peer->id, msgid, 0) < 0) { + return -1; + } + if (server_end_msg() < 0) { + return -1; + } + + return 0; +} + +int peer_submit_relayed_frame (struct peer_data *peer, struct peer_data *source_peer, uint8_t *frame, int frame_len) +{ + ASSERT(peer->have_link) + ASSERT(frame_len >= 0) + ASSERT(frame_len <= device.mtu) + + DEAD_ENTER(dead) + DataProtoDest_SubmitRelayFrame(&peer->send_dp, &source_peer->relay_source, frame, frame_len, options.send_buffer_relay_size); + if (DEAD_LEAVE(dead)) { + return -1; + } + + return 0; +} + +void peer_group_timer_handler (struct peer_group_entry *entry) +{ + struct peer_data *peer = entry->peer; + + peer_leave_group(peer, entry->group); +} + +int peer_process_received_frame (struct peer_data *peer, uint8_t *data, int data_len) +{ + ASSERT(data_len >= 0) + ASSERT(data_len <= device.mtu) + + // check ethernet header + if (data_len < sizeof(struct ethernet_header)) { + peer_log(peer, BLOG_INFO, "received frame without ethernet header"); + return 0; + } + struct ethernet_header *header = (struct ethernet_header *)data; + + // associate source address with peer + peer_add_mac_address(peer, header->source); + + // invoke incoming hook + peer_hook_incoming(peer, data, data_len); + + // write frame to the device + DEAD_ENTER(dead) + int res = PacketPassInterface_Sender_Send(device.output_interface, data, data_len); + if (DEAD_LEAVE(dead)) { + return -1; + } + + ASSERT(res == 0 || res == 1) + + ASSERT_FORCE(res == 1) // TODO? + + return 0; +} + +void peer_dataproto_handler (struct peer_data *peer, int up) +{ + ASSERT(peer->have_link) + + if (up) { + peer_log(peer, BLOG_INFO, "up"); + + // if it can be a relay provided, enable it + if ((peer->flags&SCID_NEWCLIENT_FLAG_RELAY_SERVER) && !peer->is_relay) { + if (peer_enable_relay_provider(peer) < 0) { + return; + } + } + } else { + peer_log(peer, BLOG_INFO, "down"); + + // if it is a relay provider, disable it + if (peer->is_relay) { + if (peer_disable_relay_provider(peer) < 0) { + return; + } + } + } +} + +struct peer_data * find_peer_by_id (peerid_t id) +{ + HashTableNode *node; + if (!HashTable_Lookup(&peers_by_id, &id, &node)) { + return NULL; + } + struct peer_data *peer = UPPER_OBJECT(node, struct peer_data, table_node); + + return peer; +} + +void multicast_table_add_entry (struct peer_group_entry *group_entry) +{ + // key is 23 network byte order least-significant bits of group address + uint32_t sig = hton32(ntoh32(group_entry->group)&0x7FFFFF); + + // lookup entry in multicast table + struct multicast_table_entry *multicast_entry; + HashTableNode *table_node; + if (HashTable_Lookup(&multicast_table, &sig, &table_node)) { + multicast_entry = UPPER_OBJECT(table_node, struct multicast_table_entry, table_node); + } else { + // grab entry from free multicast entries list + LinkedList2Node *free_list_node = LinkedList2_GetFirst(&multicast_entries_free); + ASSERT(free_list_node) // there are as many multicast entries as maximum number of groups + multicast_entry = UPPER_OBJECT(free_list_node, struct multicast_table_entry, free_list_node); + LinkedList2_Remove(&multicast_entries_free, &multicast_entry->free_list_node); + + // set key + multicast_entry->sig = sig; + + // insert into hash table + ASSERT_EXECUTE(HashTable_Insert(&multicast_table, &multicast_entry->table_node)) + + // init list of group entries + LinkedList2_Init(&multicast_entry->group_entries); + } + + // add to list of group entries + LinkedList2_Append(&multicast_entry->group_entries, &group_entry->multicast_list_node); + + // write multicast entry pointer to group entry for fast removal of groups + group_entry->multicast_entry = multicast_entry; +} + +void multicast_table_remove_entry (struct peer_group_entry *group_entry) +{ + struct multicast_table_entry *multicast_entry = group_entry->multicast_entry; + + // remove group entry from linked list in multicast entry + LinkedList2_Remove(&multicast_entry->group_entries, &group_entry->multicast_list_node); + + // if the multicast entry has no more group entries, remove it from the hash table + if (LinkedList2_IsEmpty(&multicast_entry->group_entries)) { + // remove from multicast table + ASSERT_EXECUTE(HashTable_Remove(&multicast_table, &multicast_entry->sig)) + + // add to free list + LinkedList2_Append(&multicast_entries_free, &multicast_entry->free_list_node); + } +} + +int peer_groups_table_key_comparator (uint32_t *group1, uint32_t *group2) +{ + return (*group1 == *group2); +} + +int peer_groups_table_hash_function (uint32_t *group, int modulo) +{ + return (jenkins_lookup2_hash((uint8_t *)group, sizeof(*group), 0) % modulo); +} + +int mac_table_key_comparator (uint8_t *mac1, uint8_t *mac2) +{ + return (memcmp(mac1, mac2, 6) == 0); +} + +int mac_table_hash_function (uint8_t *mac, int modulo) +{ + return (jenkins_lookup2_hash(mac, 6, mac_table_initval) % modulo); +} + +int multicast_table_key_comparator (uint32_t *sig1, uint32_t *sig2) +{ + return (*sig1 == *sig2); +} + +int multicast_table_hash_function (uint32_t *sig, int modulo) +{ + return (jenkins_lookup2_hash((uint8_t *)sig, sizeof(*sig), multicast_table_initval) % modulo); +} + +int peers_by_id_key_comparator (peerid_t *id1, peerid_t *id2) +{ + return (*id1 == *id2); +} + +int peers_by_id_hash_function (peerid_t *id, int modulo) +{ + return (jenkins_lookup2_hash((uint8_t *)id, sizeof(*id), peers_by_id_initval) % modulo); +} + +void device_error_handler (void *unused) +{ + BLog(BLOG_ERROR, "device error"); + + terminate(); + return; +} + +int device_input_handler_send (void *unused, uint8_t *data, int data_len) +{ + ASSERT(device.framelen == -1) + ASSERT(data_len >= 0) + ASSERT(data_len <= device.mtu) + + device.framebuf = data; + device.framelen = data_len; + + // process frame + if (device_process_frame() < 0) { + return -1; + } + + device.framelen = -1; + + return 1; +} + +int submit_frame_to_peer (struct peer_data *peer) +{ + ASSERT(device.framelen >= 0) + + DEAD_ENTER(dead) + DataProtoLocalSource_SubmitFrame(&peer->local_dpflow, device.framebuf, device.framelen); + if (DEAD_LEAVE(dead)) { + return -1; + } + + return 0; +} + +int flood_frame (void) +{ + ASSERT(device.framelen >= 0) + + LinkedList2Iterator it; + LinkedList2Iterator_InitForward(&it, &peers); + LinkedList2Node *peer_list_node; + while (peer_list_node = LinkedList2Iterator_Next(&it)) { + struct peer_data *peer = UPPER_OBJECT(peer_list_node, struct peer_data, list_node); + if (submit_frame_to_peer(peer) < 0) { + return -1; + } + } + + return 0; +} + +int device_process_frame (void) +{ + ASSERT(device.framelen >= 0) + + uint8_t *data = device.framebuf; + int data_len = device.framelen; + + const uint8_t broadcast_mac[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + const uint8_t multicast_header[] = {0x01, 0x00, 0x5e}; + + if (data_len < sizeof(struct ethernet_header)) { + BLog(BLOG_INFO, "device: frame too small (%d)", data_len); + return 0; + } + + struct ethernet_header *header = (struct ethernet_header *)data; + + // invoke outgoing hook + int hook_result = hook_outgoing(data, data_len); + + switch (hook_result) { + case HOOK_OUT_DEFAULT: + // is it multicast? + if (!memcmp(header->dest, multicast_header, 3)) { + // obtain multicast group bits from MAC address + uint32_t sig; + memcpy(&sig, &header->dest[2], 4); + sig = hton32(ntoh32(sig)&0x7FFFFF); + // lookup multicast entry + HashTableNode *multicast_table_node; + if (HashTable_Lookup(&multicast_table, &sig, &multicast_table_node)) { + struct multicast_table_entry *multicast_entry = UPPER_OBJECT(multicast_table_node, struct multicast_table_entry, table_node); + // send to all peers with groups matching the known bits of the group address + LinkedList2Iterator it; + LinkedList2Iterator_InitForward(&it, &multicast_entry->group_entries); + LinkedList2Node *group_entries_list_node; + while (group_entries_list_node = LinkedList2Iterator_Next(&it)) { + struct peer_group_entry *group_entry = UPPER_OBJECT(group_entries_list_node, struct peer_group_entry, multicast_list_node); + if (submit_frame_to_peer(group_entry->peer) < 0) { + return -1; + } + } + } + } else { + // should we flood it? + HashTableNode *mac_table_node; + if (!memcmp(header->dest, broadcast_mac, 6) || !HashTable_Lookup(&mac_table, header->dest, &mac_table_node)) { + if (flood_frame() < 0) { + return -1; + } + } + // unicast it + else { + struct mac_table_entry *mac_entry = UPPER_OBJECT(mac_table_node, struct mac_table_entry, table_node); + if (submit_frame_to_peer(mac_entry->peer) < 0) { + return -1; + } + } + } + break; + case HOOK_OUT_FLOOD: + if (flood_frame() < 0) { + return -1; + } + break; + default: + ASSERT(0); + } + + return 0; +} + +int hook_outgoing (uint8_t *pos, int len) +{ + ASSERT(len >= sizeof(struct ethernet_header)) + + struct ethernet_header *eth_header = (struct ethernet_header *)pos; + pos += sizeof(struct ethernet_header); + len -= sizeof(struct ethernet_header); + + switch (ntoh16(eth_header->type)) { + case ETHERTYPE_IPV4: { + struct ipv4_header *ipv4_header; + if (!check_ipv4_packet(pos, len, &ipv4_header, &pos, &len)) { + BLog(BLOG_INFO, "hook outgoing: wrong IP packet"); + goto out; + } + if (ipv4_header->protocol != IPV4_PROTOCOL_IGMP) { + goto out; + } + if (len < sizeof(struct igmp_base)) { + BLog(BLOG_INFO, "hook outgoing: IGMP: short packet"); + goto out; + } + struct igmp_base *igmp_base = (struct igmp_base *)pos; + pos += sizeof(struct igmp_base); + len -= sizeof(struct igmp_base); + switch (igmp_base->type) { + case IGMP_TYPE_MEMBERSHIP_QUERY: { + if (len == sizeof(struct igmp_v2_extra) && igmp_base->max_resp_code != 0) { + // V2 query + struct igmp_v2_extra *query = (struct igmp_v2_extra *)pos; + pos += sizeof(struct igmp_v2_extra); + len -= sizeof(struct igmp_v2_extra); + if (ntoh32(query->group) != 0) { + // got a Group Specific Query, lower group timers to LMQT + lower_group_timers_to_lmqt(query->group); + } + } + else if (len >= sizeof(struct igmp_v3_query_extra)) { + // V3 query + struct igmp_v3_query_extra *query = (struct igmp_v3_query_extra *)pos; + pos += sizeof(struct igmp_v3_query_extra); + len -= sizeof(struct igmp_v3_query_extra); + uint16_t num_sources = ntoh16(query->number_of_sources); + int i; + for (i = 0; i < num_sources; i++) { + if (len < sizeof(struct igmp_source)) { + BLog(BLOG_NOTICE, "hook outgoing: IGMP: short source"); + goto out_igmp; + } + pos += sizeof(struct igmp_source); + len -= sizeof(struct igmp_source); + } + if (i < num_sources) { + BLog(BLOG_NOTICE, "hook outgoing: IGMP: not all sources present"); + goto out_igmp; + } + if (ntoh32(query->group) != 0 && num_sources == 0) { + // got a Group Specific Query, lower group timers to LMQT + lower_group_timers_to_lmqt(query->group); + } + } + } break; + } + out_igmp: + // flood IGMP frames to allow all peers to learn group membership + return HOOK_OUT_FLOOD; + } break; + } + +out: + return HOOK_OUT_DEFAULT; +} + +void peer_hook_incoming (struct peer_data *peer, uint8_t *pos, int len) +{ + ASSERT(len >= sizeof(struct ethernet_header)) + + struct ethernet_header *eth_header = (struct ethernet_header *)pos; + pos += sizeof(struct ethernet_header); + len -= sizeof(struct ethernet_header); + + switch (ntoh16(eth_header->type)) { + case ETHERTYPE_IPV4: { + struct ipv4_header *ipv4_header; + if (!check_ipv4_packet(pos, len, &ipv4_header, &pos, &len)) { + BLog(BLOG_INFO, "hook incoming: wrong IP packet"); + goto out; + } + if (ipv4_header->protocol != IPV4_PROTOCOL_IGMP) { + goto out; + } + if (len < sizeof(struct igmp_base)) { + BLog(BLOG_INFO, "hook incoming: IGMP: short"); + goto out; + } + struct igmp_base *igmp_base = (struct igmp_base *)pos; + pos += sizeof(struct igmp_base); + len -= sizeof(struct igmp_base); + switch (igmp_base->type) { + case IGMP_TYPE_V2_MEMBERSHIP_REPORT: { + if (len < sizeof(struct igmp_v2_extra)) { + BLog(BLOG_INFO, "hook incoming: IGMP: short v2 report"); + goto out; + } + struct igmp_v2_extra *report = (struct igmp_v2_extra *)pos; + pos += sizeof(struct igmp_v2_extra); + len -= sizeof(struct igmp_v2_extra); + peer_join_group(peer, report->group); + } break; + case IGMP_TYPE_V3_MEMBERSHIP_REPORT: { + if (len < sizeof(struct igmp_v3_report_extra)) { + BLog(BLOG_INFO, "hook incoming: IGMP: short v3 report"); + goto out; + } + struct igmp_v3_report_extra *report = (struct igmp_v3_report_extra *)pos; + pos += sizeof(struct igmp_v3_report_extra); + len -= sizeof(struct igmp_v3_report_extra); + uint16_t num_records = ntoh16(report->number_of_group_records); + int i; + for (i = 0; i < num_records; i++) { + if (len < sizeof(struct igmp_v3_report_record)) { + BLog(BLOG_INFO, "hook incoming: IGMP: short record header"); + goto out; + } + struct igmp_v3_report_record *record = (struct igmp_v3_report_record *)pos; + pos += sizeof(struct igmp_v3_report_record); + len -= sizeof(struct igmp_v3_report_record); + uint16_t num_sources = ntoh16(record->number_of_sources); + int j; + for (j = 0; j < num_sources; j++) { + if (len < sizeof(struct igmp_source)) { + BLog(BLOG_INFO, "hook incoming: IGMP: short source"); + goto out; + } + struct igmp_source *source = (struct igmp_source *)pos; + pos += sizeof(struct igmp_source); + len -= sizeof(struct igmp_source); + } + if (j < num_sources) { + goto out; + } + uint16_t aux_len = ntoh16(record->aux_data_len); + if (len < aux_len) { + BLog(BLOG_INFO, "hook incoming: IGMP: short record aux data"); + goto out; + } + pos += aux_len; + len -= aux_len; + switch (record->type) { + case IGMP_RECORD_TYPE_MODE_IS_INCLUDE: + case IGMP_RECORD_TYPE_CHANGE_TO_INCLUDE_MODE: + if (num_sources != 0) { + peer_join_group(peer, record->group); + } + break; + case IGMP_RECORD_TYPE_MODE_IS_EXCLUDE: + case IGMP_RECORD_TYPE_CHANGE_TO_EXCLUDE_MODE: + peer_join_group(peer, record->group); + break; + } + } + if (i < num_records) { + BLog(BLOG_INFO, "hook incoming: IGMP: not all records present"); + } + } break; + } + } break; + } + +out:; +} + +void lower_group_timers_to_lmqt (uint32_t group) +{ + // lookup the group in every peer's group entries hash table + LinkedList2Iterator it; + LinkedList2Iterator_InitForward(&it, &peers); + LinkedList2Node *peer_list_node; + while (peer_list_node = LinkedList2Iterator_Next(&it)) { + struct peer_data *peer = UPPER_OBJECT(peer_list_node, struct peer_data, list_node); + HashTableNode *groups_table_node; + if (HashTable_Lookup(&peer->groups_hashtable, &group, &groups_table_node)) { + struct peer_group_entry *group_entry = UPPER_OBJECT(groups_table_node, struct peer_group_entry, table_node); + ASSERT(group_entry->peer == peer) + btime_t now = btime_gettime(); + if (group_entry->timer_endtime > now + IGMP_LAST_MEMBER_QUERY_TIME) { + group_entry->timer_endtime = now + IGMP_LAST_MEMBER_QUERY_TIME; + BReactor_SetTimerAbsolute(&ss, &group_entry->timer, group_entry->timer_endtime); + } + } + } +} + +int check_ipv4_packet (uint8_t *data, int data_len, struct ipv4_header **out_header, uint8_t **out_payload, int *out_payload_len) +{ + // check base header + if (data_len < sizeof(struct ipv4_header)) { + BLog(BLOG_DEBUG, "check ipv4: packet too short (base header)"); + return 0; + } + struct ipv4_header *header = (struct ipv4_header *)data; + + // check version + if (IPV4_GET_VERSION(*header) != 4) { + BLog(BLOG_DEBUG, "check ipv4: version not 4"); + return 0; + } + + // check options + int header_len = IPV4_GET_IHL(*header) * 4; + if (header_len < sizeof(struct ipv4_header)) { + BLog(BLOG_DEBUG, "check ipv4: ihl too small"); + return 0; + } + if (header_len > data_len) { + BLog(BLOG_DEBUG, "check ipv4: packet too short for ihl"); + return 0; + } + + // check total length + uint16_t total_length = ntoh16(header->total_length); + if (total_length < header_len) { + BLog(BLOG_DEBUG, "check ipv4: total length too small"); + return 0; + } + if (total_length > data_len) { + BLog(BLOG_DEBUG, "check ipv4: total length too large"); + return 0; + } + + *out_header = header; + *out_payload = data + header_len; + *out_payload_len = total_length - header_len; + + return 1; +} + +int assign_relays (void) +{ + LinkedList2Node *list_node; + while (list_node = LinkedList2_GetFirst(&waiting_relay_peers)) { + struct peer_data *peer = UPPER_OBJECT(list_node, struct peer_data, waiting_relay_list_node); + ASSERT(peer->waiting_relay) + + // get a relay + LinkedList2Node *list_node2 = LinkedList2_GetFirst(&relays); + if (!list_node2) { + BLog(BLOG_NOTICE, "no relays"); + return 0; + } + struct peer_data *relay = UPPER_OBJECT(list_node2, struct peer_data, relay_list_node); + ASSERT(relay->is_relay) + + // no longer waiting for relay + peer_unregister_need_relay(peer); + + // install the relay + if (peer_install_relay(peer, relay) < 0) { + return -1; + } + } + + return 0; +} + +char * address_scope_known (uint8_t *name, int name_len) +{ + ASSERT(name_len >= 0) + + for (int i = 0; i < options.num_scopes; i++) { + if (name_len == strlen(options.scopes[i]) && !memcmp(name, options.scopes[i], name_len)) { + return options.scopes[i]; + } + } + + return NULL; +} + +void server_handler_error (void *user) +{ + BLog(BLOG_ERROR, "server connection failed, exiting"); + + terminate(); + return; +} + +void server_handler_ready (void *user, peerid_t param_my_id, uint32_t ext_ip) +{ + ASSERT(!server_ready) + + // remember our ID + my_id = param_my_id; + + // store server reported addresses + for (int i = 0; i < num_bind_addrs; i++) { + POINTER(addr, bind_addrs[i]); + for (int j = 0; j < addr->num_ext_addrs; j++) { + POINTER(eaddr, addr->ext_addrs[j]); + if (eaddr->server_reported_port >= 0) { + if (ext_ip == 0) { + BLog(BLOG_ERROR, "server did not provide our address"); + terminate(); + return; + } + BAddr_InitIPv4(&eaddr->addr, ext_ip, hton16(eaddr->server_reported_port)); + char str[BADDR_MAX_PRINT_LEN]; + BAddr_Print(&eaddr->addr, str); + BLog(BLOG_INFO, "external address (%d,%d): server reported %s", i, j, str); + } + } + } + + // set server ready + server_ready = 1; + + BLog(BLOG_INFO, "server: ready, my ID is %d", (int)my_id); +} + +void server_handler_newclient (void *user, peerid_t peer_id, int flags, const uint8_t *cert, int cert_len) +{ + ASSERT(server_ready) + ASSERT(cert_len >= 0) + ASSERT(cert_len <= SCID_NEWCLIENT_MAX_CERT_LEN) + + // check if the peer already exists + if (find_peer_by_id(peer_id)) { + BLog(BLOG_WARNING, "server: newclient: peer already known"); + return; + } + + // make sure it's not the same ID as us + if (peer_id == my_id) { + BLog(BLOG_WARNING, "server: newclient: peer has our ID"); + return; + } + + // check if there is spece for the peer + if (num_peers >= MAX_PEERS) { + BLog(BLOG_WARNING, "server: newclient: no space for new peer (maximum number reached)"); + return; + } + + if (!options.ssl && cert_len > 0) { + BLog(BLOG_WARNING, "server: newclient: certificate supplied, but not using TLS"); + return; + } + + peer_add(peer_id, flags, cert, cert_len); + return; +} + +void server_handler_endclient (void *user, peerid_t peer_id) +{ + ASSERT(server_ready) + + // find peer + struct peer_data *peer = find_peer_by_id(peer_id); + if (!peer) { + BLog(BLOG_WARNING, "server: endclient: peer %d not known", (int)peer_id); + return; + } + + // remove peer + peer_remove(peer); + return; +} + +void server_handler_message (void *user, peerid_t peer_id, uint8_t *data, int data_len) +{ + ASSERT(server_ready) + ASSERT(data_len >= 0) + ASSERT(data_len <= SC_MAX_MSGLEN) + + // find peer + struct peer_data *peer = find_peer_by_id(peer_id); + if (!peer) { + BLog(BLOG_WARNING, "server: message: peer not known"); + return; + } + + // process peer message + peer_msg(peer, data, data_len); + return; +} diff --git a/client/client.h b/client/client.h new file mode 100644 index 000000000..52d16216f --- /dev/null +++ b/client/client.h @@ -0,0 +1,212 @@ +/** + * @file client.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// NOTE: all time values are in milliseconds + +// name of the program +#define PROGRAM_NAME "client" + +// server output buffer size +#define SERVER_BUFFER_MIN_PACKETS 200 + +// maximum UDP payload size +#define CLIENT_UDP_MTU 1472 + +// maximum number of peers +#define MAX_PEERS 29 +// maximum number of peer's MAC addresses to remember +#define PEER_MAX_MACS 16 +// maximum number of multicast addresses per peer +#define PEER_MAX_GROUPS 16 +// how long we wait for a packet to reach full size before sending it (see FragmentProtoDisassembler latency argument) +#define PEER_DEFAULT_FRAGMENTATION_LATENCY 0 +// keep-alive packet interval for p2p communication +#define PEER_KEEPALIVE_INTERVAL 10000 +// keep-alive receive timer for p2p communication (after how long to consider the link down) +#define PEER_KEEPALIVE_RECEIVE_TIMER 22000 +// size of frame send buffer, in number of frames +#define PEER_DEFAULT_SEND_BUFFER_SIZE 32 +// size of frame send buffer for relayed packets, in number of frames +#define PEER_DEFAULT_SEND_BUFFER_RELAY_SIZE 32 +// retry time +#define PEER_RETRY_TIME 5000 + +// number of MAC seeds to keep for checking received packets +#define MACPOOL_NUM_RECV_SEEDS 2 + +// for how long a peer can send no Membership Reports for a group +// before the peer and group are disassociated +#define IGMP_DEFAULT_GROUP_MEMBERSHIP_INTERVAL 260000 +// how long to wait for joins after a Group Specific query has been +// forwarded to a peer before assuming there are no listeners at the peer +#define IGMP_LAST_MEMBER_QUERY_TIME 2000 + +// maximum bind addresses +#define MAX_BIND_ADDRS 8 + +// maximum external addresses per bind address +#define MAX_EXT_ADDRS 8 + +// maximum scopes +#define MAX_SCOPES 8 + +struct device_data { + BTap btap; + int mtu; + SinglePacketBuffer input_buffer; + PacketPassInterface input_interface; + PacketPassInterface *output_interface; + uint8_t *framebuf; + int framelen; +}; + +struct peer_data; + +// entry in global MAC hash table +struct mac_table_entry { + struct peer_data *peer; + LinkedList2Node list_node; // node in macs_used or macs_free + // defined when used: + uint8_t mac[6]; // MAC address + HashTableNode table_node; // node in global MAC address table +}; + +// entry in global multicast hash table +struct multicast_table_entry { + // defined when free: + LinkedList2Node free_list_node; // node in free entries list + // defined when used: + uint32_t sig; // last 23 bits of group address + HashTableNode table_node; // node in global multicast hash table + LinkedList2 group_entries; // list of peers' group entries that match this multicast entry +}; + +// multicast group entry in peers +struct peer_group_entry { + struct peer_data *peer; + LinkedList2Node list_node; // node in peer's free or used groups list + BTimer timer; // timer for removing the group, running when group entry is used + // defined when used: + // basic group data + uint32_t group; // group address + HashTableNode table_node; // node in peer's groups hash table + btime_t timer_endtime; + // multicast table entry data + LinkedList2Node multicast_list_node; // node in list of multicast MACs that may mean this group + struct multicast_table_entry *multicast_entry; // pointer to entry in multicast hash table +}; + +struct peer_data { + // peer identifier + peerid_t id; + + // flags provided by the server + int flags; + + // certificate reported by the server, defined only if using SSL + uint8_t cert[SCID_NEWCLIENT_MAX_CERT_LEN]; + int cert_len; + + // local flow + DataProtoLocalSource local_dpflow; + + // relay source + DataProtoRelaySource relay_source; + + // flag if link objects are initialized + int have_link; + + // link sending + DataProtoDest send_dp; + + // link receive interface + PacketPassInterface recv_ppi; + + // transport-specific link objects + union { + struct { + DatagramPeerIO pio; + uint16_t sendseed_nextid; + int sendseed_sent; + uint16_t sendseed_sent_id; + uint8_t *sendseed_sent_key; + uint8_t *sendseed_sent_iv; + } udp; + struct { + StreamPeerIO pio; + } tcp; + } pio; + + // flag if relaying is installed + int have_relaying; + + // relaying objects + struct peer_data *relaying_peer; // peer through which we are relaying + LinkedList2Node relaying_list_node; // node in relay peer's relay_users + + // waiting for relay data + int waiting_relay; + LinkedList2Node waiting_relay_list_node; + + // retry timer + BTimer reset_timer; + + // MAC address entries + struct mac_table_entry macs_data[PEER_MAX_MACS]; + // used entries, in global mac table + LinkedList2 macs_used; + // free entries + LinkedList2 macs_free; + + // IPv4 multicast groups the peer is a destination for + struct peer_group_entry groups_data[PEER_MAX_GROUPS]; + LinkedList2 groups_used; + LinkedList2 groups_free; + HashTable groups_hashtable; + + // peers linked list node + LinkedList2Node list_node; + // peers-by-ID hash table node + HashTableNode table_node; + + // relay server specific + int is_relay; + LinkedList2Node relay_list_node; + LinkedList2 relay_users; + + // binding state + int binding; + int binding_addrpos; +}; diff --git a/cmake/modules/COPYING-CMAKE-SCRIPTS b/cmake/modules/COPYING-CMAKE-SCRIPTS new file mode 100644 index 000000000..4b417765f --- /dev/null +++ b/cmake/modules/COPYING-CMAKE-SCRIPTS @@ -0,0 +1,22 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/cmake/modules/FindLibraryWithDebug.cmake b/cmake/modules/FindLibraryWithDebug.cmake new file mode 100644 index 000000000..58cd73086 --- /dev/null +++ b/cmake/modules/FindLibraryWithDebug.cmake @@ -0,0 +1,113 @@ +# +# FIND_LIBRARY_WITH_DEBUG +# -> enhanced FIND_LIBRARY to allow the search for an +# optional debug library with a WIN32_DEBUG_POSTFIX similar +# to CMAKE_DEBUG_POSTFIX when creating a shared lib +# it has to be the second and third argument + +# Copyright (c) 2007, Christian Ehrlicher, +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +MACRO(FIND_LIBRARY_WITH_DEBUG var_name win32_dbg_postfix_name dgb_postfix libname) + + IF(NOT "${win32_dbg_postfix_name}" STREQUAL "WIN32_DEBUG_POSTFIX") + + # no WIN32_DEBUG_POSTFIX -> simply pass all arguments to FIND_LIBRARY + FIND_LIBRARY(${var_name} + ${win32_dbg_postfix_name} + ${dgb_postfix} + ${libname} + ${ARGN} + ) + + ELSE(NOT "${win32_dbg_postfix_name}" STREQUAL "WIN32_DEBUG_POSTFIX") + + IF(NOT WIN32) + # on non-win32 we don't need to take care about WIN32_DEBUG_POSTFIX + + FIND_LIBRARY(${var_name} ${libname} ${ARGN}) + + ELSE(NOT WIN32) + + # 1. get all possible libnames + SET(args ${ARGN}) + SET(newargs "") + SET(libnames_release "") + SET(libnames_debug "") + + LIST(LENGTH args listCount) + + IF("${libname}" STREQUAL "NAMES") + SET(append_rest 0) + LIST(APPEND args " ") + + FOREACH(i RANGE ${listCount}) + LIST(GET args ${i} val) + + IF(append_rest) + LIST(APPEND newargs ${val}) + ELSE(append_rest) + IF("${val}" STREQUAL "PATHS") + LIST(APPEND newargs ${val}) + SET(append_rest 1) + ELSE("${val}" STREQUAL "PATHS") + LIST(APPEND libnames_release "${val}") + LIST(APPEND libnames_debug "${val}${dgb_postfix}") + ENDIF("${val}" STREQUAL "PATHS") + ENDIF(append_rest) + + ENDFOREACH(i) + + ELSE("${libname}" STREQUAL "NAMES") + + # just one name + LIST(APPEND libnames_release "${libname}") + LIST(APPEND libnames_debug "${libname}${dgb_postfix}") + + SET(newargs ${args}) + + ENDIF("${libname}" STREQUAL "NAMES") + + # search the release lib + FIND_LIBRARY(${var_name}_RELEASE + NAMES ${libnames_release} + ${newargs} + ) + + # search the debug lib + FIND_LIBRARY(${var_name}_DEBUG + NAMES ${libnames_debug} + ${newargs} + ) + + IF(${var_name}_RELEASE AND ${var_name}_DEBUG) + + # both libs found + SET(${var_name} optimized ${${var_name}_RELEASE} + debug ${${var_name}_DEBUG}) + + ELSE(${var_name}_RELEASE AND ${var_name}_DEBUG) + + IF(${var_name}_RELEASE) + + # only release found + SET(${var_name} ${${var_name}_RELEASE}) + + ELSE(${var_name}_RELEASE) + + # only debug (or nothing) found + SET(${var_name} ${${var_name}_DEBUG}) + + ENDIF(${var_name}_RELEASE) + + ENDIF(${var_name}_RELEASE AND ${var_name}_DEBUG) + + MARK_AS_ADVANCED(${var_name}_RELEASE) + MARK_AS_ADVANCED(${var_name}_DEBUG) + + ENDIF(NOT WIN32) + + ENDIF(NOT "${win32_dbg_postfix_name}" STREQUAL "WIN32_DEBUG_POSTFIX") + +ENDMACRO(FIND_LIBRARY_WITH_DEBUG) diff --git a/cmake/modules/FindNSPR.cmake b/cmake/modules/FindNSPR.cmake new file mode 100644 index 000000000..6e8fed926 --- /dev/null +++ b/cmake/modules/FindNSPR.cmake @@ -0,0 +1,57 @@ +# - Try to find the NSPR library +# Once done this will define +# +# NSPR_FOUND - system has the NSPR library +# NSPR_INCLUDE_DIRS - Include paths needed +# NSPR_LIBRARY_DIRS - Linker paths needed +# NSPR_LIBRARIES - Libraries needed + +# Copyright (c) 2010, Ambroz Bizjak, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +include(FindLibraryWithDebug) + +if (NSPR_LIBRARIES) + set(NSPR_FIND_QUIETLY TRUE) +endif () + +set(NSPR_FOUND FALSE) + +if (WIN32) + find_path(NSPR_FIND_INCLUDE_DIR prerror.h) + + FIND_LIBRARY_WITH_DEBUG(NSPR_FIND_LIBRARIES_PLDS WIN32_DEBUG_POSTFIX d NAMES plds4 libplds4) + FIND_LIBRARY_WITH_DEBUG(NSPR_FIND_LIBRARIES_PLC WIN32_DEBUG_POSTFIX d NAMES plc4 libplc4) + FIND_LIBRARY_WITH_DEBUG(NSPR_FIND_LIBRARIES_NSPR WIN32_DEBUG_POSTFIX d NAMES nspr4 libnspr4) + + if (NSPR_FIND_INCLUDE_DIR AND NSPR_FIND_LIBRARIES_PLDS AND NSPR_FIND_LIBRARIES_PLC AND NSPR_FIND_LIBRARIES_NSPR) + set(NSPR_FOUND TRUE) + set(NSPR_INCLUDE_DIRS "${NSPR_FIND_INCLUDE_DIR}" CACHE STRING "NSPR include dirs") + set(NSPR_LIBRARY_DIRS "" CACHE STRING "NSPR library dirs") + set(NSPR_LIBRARIES "${NSPR_FIND_LIBRARIES_PLDS};${NSPR_FIND_LIBRARIES_PLC};${NSPR_FIND_LIBRARIES_NSPR}" CACHE STRING "NSPR libraries") + endif () +else () + find_package(PkgConfig REQUIRED) + pkg_check_modules(NSPR_PC nspr) + + if (NSPR_PC_FOUND) + set(NSPR_FOUND TRUE) + set(NSPR_INCLUDE_DIRS "${NSPR_PC_INCLUDE_DIRS}" CACHE STRING "NSPR include dirs") + set(NSPR_LIBRARY_DIRS "${NSPR_PC_LIBRARY_DIRS}" CACHE STRING "NSPR library dirs") + set(NSPR_LIBRARIES "${NSPR_PC_LIBRARIES}" CACHE STRING "NSPR libraries") + endif () +endif () + +if (NSPR_FOUND) + if (NOT NSPR_FIND_QUIETLY) + MESSAGE(STATUS "Found NSPR: ${NSPR_INCLUDE_DIRS} ${NSPR_LIBRARY_DIRS} ${NSPR_LIBRARIES}") + endif () +else () + if (NSPR_FIND_REQUIRED) + message(FATAL_ERROR "Could NOT find NSPR") + endif () +endif () + +mark_as_advanced(NSPR_INCLUDE_DIRS NSPR_LIBRARY_DIRS NSPR_LIBRARIES) diff --git a/cmake/modules/FindNSS.cmake b/cmake/modules/FindNSS.cmake new file mode 100644 index 000000000..17fd45ab9 --- /dev/null +++ b/cmake/modules/FindNSS.cmake @@ -0,0 +1,57 @@ +# - Try to find the NSS library +# Once done this will define +# +# NSS_FOUND - system has the NSS library +# NSS_INCLUDE_DIRS - Include paths needed +# NSS_LIBRARY_DIRS - Linker paths needed +# NSS_LIBRARIES - Libraries needed + +# Copyright (c) 2010, Ambroz Bizjak, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +include(FindLibraryWithDebug) + +if (NSS_LIBRARIES) + set(NSS_FIND_QUIETLY TRUE) +endif () + +set(NSS_FOUND FALSE) + +if (WIN32) + find_path(NSS_FIND_INCLUDE_DIR nss.h) + + FIND_LIBRARY_WITH_DEBUG(NSS_FIND_LIBRARIES_SSL WIN32_DEBUG_POSTFIX d NAMES ssl3) + FIND_LIBRARY_WITH_DEBUG(NSS_FIND_LIBRARIES_SMIME WIN32_DEBUG_POSTFIX d NAMES smime3) + FIND_LIBRARY_WITH_DEBUG(NSS_FIND_LIBRARIES_NSS WIN32_DEBUG_POSTFIX d NAMES nss3) + + if (NSS_FIND_INCLUDE_DIR AND NSS_FIND_LIBRARIES_SSL AND NSS_FIND_LIBRARIES_SMIME AND NSS_FIND_LIBRARIES_NSS) + set(NSS_FOUND TRUE) + set(NSS_INCLUDE_DIRS "${NSS_FIND_INCLUDE_DIR}" CACHE STRING "NSS include dirs") + set(NSS_LIBRARY_DIRS "" CACHE STRING "NSS library dirs") + set(NSS_LIBRARIES "${NSS_FIND_LIBRARIES_SSL};${NSS_FIND_LIBRARIES_SMIME};${NSS_FIND_LIBRARIES_NSS}" CACHE STRING "NSS libraries") + endif () +else () + find_package(PkgConfig REQUIRED) + pkg_check_modules(NSS_PC nss) + + if (NSS_PC_FOUND) + set(NSS_FOUND TRUE) + set(NSS_INCLUDE_DIRS "${NSS_PC_INCLUDE_DIRS}" CACHE STRING "NSS include dirs") + set(NSS_LIBRARY_DIRS "${NSS_PC_LIBRARY_DIRS}" CACHE STRING "NSS library dirs") + set(NSS_LIBRARIES "${NSS_PC_LIBRARIES}" CACHE STRING "NSS libraries") + endif () +endif () + +if (NSS_FOUND) + if (NOT NSS_FIND_QUIETLY) + MESSAGE(STATUS "Found NSS: ${NSS_INCLUDE_DIRS} ${NSS_LIBRARY_DIRS} ${NSS_LIBRARIES}") + endif () +else () + if (NSS_FIND_REQUIRED) + message(FATAL_ERROR "Could NOT find NSS") + endif () +endif () + +mark_as_advanced(NSS_INCLUDE_DIRS NSS_LIBRARY_DIRS NSS_LIBRARIES) diff --git a/cmake/modules/FindOpenSSL.cmake b/cmake/modules/FindOpenSSL.cmake new file mode 100644 index 000000000..4434e95bc --- /dev/null +++ b/cmake/modules/FindOpenSSL.cmake @@ -0,0 +1,72 @@ +# - Try to find the OpenSSL library +# Once done this will define +# +# OpenSSL_FOUND - system has the OpenSSL library +# OpenSSL_INCLUDE_DIRS - Include paths needed +# OpenSSL_LIBRARY_DIRS - Linker paths needed +# OpenSSL_LIBRARIES - Libraries needed + +# Copyright (c) 2010, Ambroz Bizjak, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +include(FindLibraryWithDebug) + +if (OpenSSL_LIBRARIES) + set(OpenSSL_FIND_QUIETLY TRUE) +endif () + +set(OpenSSL_FOUND FALSE) + +if (WIN32) + find_path(OpenSSL_FIND_INCLUDE_DIR openssl/ssl.h) + + if (OpenSSL_FIND_INCLUDE_DIR) + # look for libraries built with GCC + find_library(OpenSSL_FIND_LIBRARIES_SSL NAMES ssl) + find_library(OpenSSL_FIND_LIBRARIES_CRYPTO NAMES crypto) + + if (OpenSSL_FIND_LIBRARIES_SSL AND OpenSSL_FIND_LIBRARIES_CRYPTO) + set(OpenSSL_FOUND TRUE) + set(OpenSSL_LIBRARY_DIRS "" CACHE STRING "OpenSSL library dirs") + set(OpenSSL_LIBRARIES "${OpenSSL_FIND_LIBRARIES_SSL};${OpenSSL_FIND_LIBRARIES_CRYPTO}" CACHE STRING "OpenSSL libraries") + else () + # look for libraries built with MSVC + FIND_LIBRARY_WITH_DEBUG(OpenSSL_FIND_LIBRARIES_SSL WIN32_DEBUG_POSTFIX d NAMES ssl ssleay ssleay32 libssleay32 ssleay32MD) + FIND_LIBRARY_WITH_DEBUG(OpenSSL_FIND_LIBRARIES_EAY WIN32_DEBUG_POSTFIX d NAMES eay libeay libeay32 libeay32MD) + + if (OpenSSL_FIND_LIBRARIES_SSL AND OpenSSL_FIND_LIBRARIES_EAY) + set(OpenSSL_FOUND TRUE) + set(OpenSSL_LIBRARY_DIRS "" CACHE STRING "OpenSSL library dirs") + set(OpenSSL_LIBRARIES "${OpenSSL_FIND_LIBRARIES_SSL};${OpenSSL_FIND_LIBRARIES_EAY}" CACHE STRING "OpenSSL libraries") + endif () + endif () + + if (OpenSSL_FOUND) + set(OpenSSL_INCLUDE_DIRS "${OpenSSL_FIND_INCLUDE_DIR}" CACHE STRING "OpenSSL include dirs") + endif () + endif () +else () + find_package(PkgConfig REQUIRED) + pkg_check_modules(OpenSSL_PC openssl) + + if (OpenSSL_PC_FOUND) + set(OpenSSL_FOUND TRUE) + set(OpenSSL_INCLUDE_DIRS "${OpenSSL_PC_INCLUDE_DIRS}" CACHE STRING "OpenSSL include dirs") + set(OpenSSL_LIBRARY_DIRS "${OpenSSL_PC_LIBRARY_DIRS}" CACHE STRING "OpenSSL library dirs") + set(OpenSSL_LIBRARIES "${OpenSSL_PC_LIBRARIES}" CACHE STRING "OpenSSL libraries") + endif () +endif () + +if (OpenSSL_FOUND) + if (NOT OpenSSL_FIND_QUIETLY) + MESSAGE(STATUS "Found OpenSSL: ${OpenSSL_INCLUDE_DIRS} ${OpenSSL_LIBRARY_DIRS} ${OpenSSL_LIBRARIES}") + endif () +else () + if (OpenSSL_FIND_REQUIRED) + message(FATAL_ERROR "Could NOT find OpenSSL") + endif () +endif () + +mark_as_advanced(OpenSSL_INCLUDE_DIRS OpenSSL_LIBRARY_DIRS OpenSSL_LIBRARIES) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 000000000..6d9b6bf02 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,25 @@ +add_executable(linkedlist2_example linkedlist2_example.c) + +add_executable(hashtable_example hashtable_example.c) +target_link_libraries(hashtable_example system) + +add_executable(btimer_example btimer_example.c) +target_link_libraries(btimer_example system) + +add_executable(predicate_test predicate_test.c) +target_link_libraries(predicate_test predicate) + +add_executable(fairqueue_test fairqueue_test.c) +target_link_libraries(fairqueue_test system flow) + +add_executable(fairqueue_test2 fairqueue_test2.c) +target_link_libraries(fairqueue_test2 system flow ${LIBCRYPTO_LIBRARIES}) + +add_executable(bheap_test bheap_test.c) +target_link_libraries(bheap_test ${LIBCRYPTO_LIBRARIES}) + +add_executable(bavl_test bavl_test.c) +target_link_libraries(bavl_test ${LIBCRYPTO_LIBRARIES}) + +add_executable(hashtable_bench hashtable_bench.c) +target_link_libraries(hashtable_bench system ${LIBCRYPTO_LIBRARIES}) diff --git a/examples/FastPacketSource.h b/examples/FastPacketSource.h new file mode 100644 index 000000000..56dd52ee4 --- /dev/null +++ b/examples/FastPacketSource.h @@ -0,0 +1,100 @@ +/** + * @file FastPacketSource.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _FASTPACKETSOURCE_H +#define _FASTPACKETSOURCE_H + +#include +#include + +#include +#include +#include +#include + +typedef struct { + dead_t dead; + PacketPassInterface *output; + int psize; + uint8_t *data; + int data_len; + BPending start_job; +} FastPacketSource; + +static void _FastPacketSource_send (FastPacketSource *s) +{ + while (1) { + DEAD_ENTER(s->dead) + int res = PacketPassInterface_Sender_Send(s->output, s->data, s->data_len); + if (DEAD_LEAVE(s->dead)) { + return; + } + ASSERT(res == 0 || res == 1) + if (res == 0) { + return; + } + } +} + +static void _FastPacketSource_output_handler_done (FastPacketSource *s) +{ + _FastPacketSource_send(s); + return; +} + +static void _FastPacketSource_job_handler (FastPacketSource *s) +{ + _FastPacketSource_send(s); + return; +} + +static void FastPacketSource_Init (FastPacketSource *s, PacketPassInterface *output, uint8_t *data, int data_len, BPendingGroup *pg) +{ + ASSERT(data_len >= 0) + ASSERT(data_len <= PacketPassInterface_GetMTU(output)); + + // init arguments + s->output = output; + s->data = data; + s->data_len = data_len; + + // init dead var + DEAD_INIT(s->dead); + + // init output + PacketPassInterface_Sender_Init(s->output, (PacketPassInterface_handler_done)_FastPacketSource_output_handler_done, s); + + // init start job + BPending_Init(&s->start_job, pg, (BPending_handler)_FastPacketSource_job_handler, s); + BPending_Set(&s->start_job); +} + +static void FastPacketSource_Free (FastPacketSource *s) +{ + // free start job + BPending_Free(&s->start_job); + + // free dead var + DEAD_KILL(s->dead); +} + +#endif diff --git a/examples/RandomPacketSink.h b/examples/RandomPacketSink.h new file mode 100644 index 000000000..bcc91bfa6 --- /dev/null +++ b/examples/RandomPacketSink.h @@ -0,0 +1,85 @@ +/** + * @file RandomPacketSink.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _RANDOMPACKETSINK_H +#define _RANDOMPACKETSINK_H + +#include + +#include +#include +#include + +typedef struct { + BReactor *reactor; + PacketPassInterface input; + BTimer timer; +} RandomPacketSink; + +static int _RandomPacketSink_input_handler_send (RandomPacketSink *s, uint8_t *data, int data_len) +{ + printf("sink: send '"); + fwrite(data, data_len, 1, stdout); + + uint8_t r; + brandom_randomize(&r, sizeof(r)); + if (r&(uint8_t)1) { + printf("' accepting\n"); + return 1; + } + + printf("' delaying\n"); + BReactor_SetTimer(s->reactor, &s->timer); + return 0; +} + +static void _RandomPacketSink_input_handler_cancel (RandomPacketSink *s) +{ + printf("sink: cancelled\n"); + BReactor_RemoveTimer(s->reactor, &s->timer); +} + +static void _RandomPacketSink_timer_handler (RandomPacketSink *s) +{ + PacketPassInterface_Done(&s->input); +} + +static void RandomPacketSink_Init (RandomPacketSink *s, BReactor *reactor, int mtu, int ms) +{ + s->reactor = reactor; + PacketPassInterface_Init(&s->input, mtu, (PacketPassInterface_handler_send)_RandomPacketSink_input_handler_send, s); + PacketPassInterface_EnableCancel(&s->input, (PacketPassInterface_handler_cancel)_RandomPacketSink_input_handler_cancel); + BTimer_Init(&s->timer, ms, (BTimer_handler)_RandomPacketSink_timer_handler, s); +} + +static void RandomPacketSink_Free (RandomPacketSink *s) +{ + BReactor_RemoveTimer(s->reactor, &s->timer); + PacketPassInterface_Free(&s->input); +} + +static PacketPassInterface * RandomPacketSink_GetInput (RandomPacketSink *s) +{ + return &s->input; +} + +#endif diff --git a/examples/TimerPacketSink.h b/examples/TimerPacketSink.h new file mode 100644 index 000000000..9480bf170 --- /dev/null +++ b/examples/TimerPacketSink.h @@ -0,0 +1,89 @@ +/** + * @file TimerPacketSink.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _TIMERPACKETSINK_H +#define _TIMERPACKETSINK_H + +#include + +#include +#include + +typedef struct { + BReactor *reactor; + PacketPassInterface input; + BTimer timer; +} TimerPacketSink; + +static int _TimerPacketSink_input_handler_send (TimerPacketSink *s, uint8_t *data, int data_len) +{ + printf("sink: send '"); + fwrite(data, data_len, 1, stdout); + printf("'\n"); + + BReactor_SetTimer(s->reactor, &s->timer); + return 0; +} + +static void _TimerPacketSink_input_handler_cancel (TimerPacketSink *s) +{ + printf("sink: cancelled\n"); + + BReactor_RemoveTimer(s->reactor, &s->timer); +} + +static void _TimerPacketSink_timer_handler (TimerPacketSink *s) +{ + printf("sink: done\n"); + + PacketPassInterface_Done(&s->input); + return; +} + +static void TimerPacketSink_Init (TimerPacketSink *s, BReactor *reactor, int mtu, int ms) +{ + // init arguments + s->reactor = reactor; + + // init input + PacketPassInterface_Init(&s->input, mtu, (PacketPassInterface_handler_send)_TimerPacketSink_input_handler_send, s); + PacketPassInterface_EnableCancel(&s->input, (PacketPassInterface_handler_cancel)_TimerPacketSink_input_handler_cancel); + + // init timer + BTimer_Init(&s->timer, ms, (BTimer_handler)_TimerPacketSink_timer_handler, s); +} + +static void TimerPacketSink_Free (TimerPacketSink *s) +{ + // free timer + BReactor_RemoveTimer(s->reactor, &s->timer); + + // free input + PacketPassInterface_Free(&s->input); +} + +static PacketPassInterface * TimerPacketSink_GetInput (TimerPacketSink *s) +{ + return &s->input; +} + +#endif diff --git a/examples/bavl_test.c b/examples/bavl_test.c new file mode 100644 index 000000000..a248b6f2c --- /dev/null +++ b/examples/bavl_test.c @@ -0,0 +1,139 @@ +/** + * @file bavl_test.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include +#include +#include +#include + +struct mynode { + int used; + int num; + BAVLNode avl_node; +}; + +static int int_comparator (void *user, int *val1, int *val2) +{ + if (*val1 < *val2) { + return -1; + } + if (*val1 > *val2) { + return 1; + } + return 0; +} + +static void print_indent (int indent) +{ + for (int i = 0; i < indent; i++) { + printf(" "); + } +} + +static void print_avl_recurser (BAVLNode *node, int indent) +{ + print_indent(indent); + + if (!node) { + printf("null\n"); + } else { + struct mynode *mnode = UPPER_OBJECT(node, struct mynode, avl_node); + printf("(%d) %d %p\n", node->balance, mnode->num, node); + print_avl_recurser(node->link[0], indent + 1); + print_avl_recurser(node->link[1], indent + 1); + } +} + +static void print_avl (BAVL *tree) +{ + print_avl_recurser(tree->root, 0); +} + +int main (int argc, char **argv) +{ + int num_nodes; + int num_random_delete; + + if (argc != 3 || (num_nodes = atoi(argv[1])) <= 0 || (num_random_delete = atoi(argv[2])) < 0) { + fprintf(stderr, "Usage: %s \n", (argc > 0 ? argv[0] : NULL)); + return 1; + } + + struct mynode *nodes = malloc(num_nodes * sizeof(*nodes)); + if (!nodes) { + fprintf(stderr, "malloc failed\n"); + return 1; + } + + int *values_ins = malloc(num_nodes * sizeof(int)); + ASSERT_FORCE(values_ins) + + int *values = malloc(num_random_delete * sizeof(int)); + ASSERT_FORCE(values) + + BAVL avl; + BAVL_Init(&avl, OFFSET_DIFF(struct mynode, num, avl_node), (BAVL_comparator)int_comparator, NULL); + + /* + printf("Inserting in reverse order...\n"); + for (int i = num_nodes - 1; i >= 0; i--) { + nodes[i].used = 1; + nodes[i].num = i; + int res = BAVL_Insert(&avl, &nodes[i].avl_node); + ASSERT(res == 1) + } + */ + + printf("Inserting random values...\n"); + brandom_randomize((uint8_t *)values_ins, num_nodes * sizeof(int)); + for (int i = 0; i < num_nodes; i++) { + nodes[i].num = values_ins[i]; + if (BAVL_Insert(&avl, &nodes[i].avl_node, NULL)) { + nodes[i].used = 1; + } else { + nodes[i].used = 0; + printf("Insert collision!\n"); + } + } + + printf("Removing random entries...\n"); + int removed = 0; + brandom_randomize((uint8_t *)values, num_random_delete * sizeof(int)); + for (int i = 0; i < num_random_delete; i++) { + int index = (((unsigned int *)values)[i] % num_nodes); + struct mynode *node = nodes + index; + if (node->used) { + BAVL_Remove(&avl, &node->avl_node); + node->used = 0; + removed++; + } + } + + printf("Removed %d entries\n", removed); + + free(nodes); + free(values); + + return 0; +} diff --git a/examples/bheap_test.c b/examples/bheap_test.c new file mode 100644 index 000000000..a17a95893 --- /dev/null +++ b/examples/bheap_test.c @@ -0,0 +1,130 @@ +/** + * @file bheap_test.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include + +struct mynode { + int used; + int num; + BHeapNode heap_node; +}; + +static int int_comparator (void *user, int *val1, int *val2) +{ + if (*val1 < *val2) { + return -1; + } + if (*val1 > *val2) { + return 1; + } + return 0; +} + +static void print_indent (int indent) +{ + for (int i = 0; i < indent; i++) { + printf(" "); + } +} + +static void print_heap_recurser (BHeapNode *node, int indent) +{ + print_indent(indent); + + if (!node) { + printf("null\n"); + } else { + struct mynode *mnode = UPPER_OBJECT(node, struct mynode, heap_node); + printf("%d %p\n", mnode->num, node); + print_heap_recurser(node->link[0], indent + 1); + print_heap_recurser(node->link[1], indent + 1); + } +} + +static void print_heap (BHeap *heap) +{ + print_heap_recurser(heap->root, 0); +} + +int main (int argc, char **argv) +{ + int num_nodes; + int num_random_delete; + + if (argc != 3 || (num_nodes = atoi(argv[1])) <= 0 || (num_random_delete = atoi(argv[2])) < 0) { + fprintf(stderr, "Usage: %s \n", (argc > 0 ? argv[0] : NULL)); + return 1; + } + + struct mynode *nodes = malloc(num_nodes * sizeof(*nodes)); + if (!nodes) { + fprintf(stderr, "malloc failed\n"); + return 1; + } + + int *values = malloc(num_random_delete * sizeof(int)); + ASSERT_FORCE(values) + + BHeap heap; + BHeap_Init(&heap, OFFSET_DIFF(struct mynode, num, heap_node), (BHeap_comparator)int_comparator, NULL); + + printf("Inserting in reverse order...\n"); + for (int i = num_nodes - 1; i >= 0; i--) { + nodes[i].used = 1; + nodes[i].num = i; + BHeap_Insert(&heap, &nodes[i].heap_node); + } + + //print_heap(&heap); + + printf("Removing random entries...\n"); + brandom_randomize((uint8_t *)values, num_random_delete * sizeof(int)); + for (int i = 0; i < num_random_delete; i++) { + int index = (((unsigned int *)values)[i] % num_nodes); + struct mynode *node = nodes + index; + if (node->used) { + //printf("Removing index %d value %d\n", index, node->num); + BHeap_Remove(&heap, &node->heap_node); + node->used = 0; + } + } + + //print_heap(&heap); + + printf("Removing remaining entries...\n"); + BHeapNode *heap_node; + while (heap_node = BHeap_GetFirst(&heap)) { + struct mynode *node = UPPER_OBJECT(heap_node, struct mynode, heap_node); + //printf("Removing value %d\n", node->num); + BHeap_Remove(&heap, &node->heap_node); + node->used = 0; + } + + //print_heap(&heap); + + free(nodes); + free(values); + + return 0; +} diff --git a/examples/btimer_example.c b/examples/btimer_example.c new file mode 100644 index 000000000..852ce4298 --- /dev/null +++ b/examples/btimer_example.c @@ -0,0 +1,77 @@ +/** + * @file btimer_example.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include + +#include +#include +#include + +// gives average firing rate 100kHz +#define TIMER_NUM 500 +#define TIMER_MODULO 10 + +BReactor sys; + +void handle_timer (BTimer *bt) +{ + #ifdef BADVPN_USE_WINAPI + btime_t time = btime_gettime() + rand()%TIMER_MODULO; + #else + btime_t time = btime_gettime() + random()%TIMER_MODULO; + #endif + BReactor_SetTimerAbsolute(&sys, bt, time); +} + +int main () +{ + BLog_InitStdout(); + + #ifdef BADVPN_USE_WINAPI + srand(time(NULL)); + #else + srandom(time(NULL)); + #endif + + // init time + BTime_Init(); + + if (!BReactor_Init(&sys)) { + DEBUG("BReactor_Init failed"); + return 1; + } + + BTimer timers[TIMER_NUM]; + + int i; + for (i=0; i + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define OUTPUT_INTERVAL 0 +#define REMOVE_INTERVAL 1 +#define NUM_INPUTS 3 + +BReactor reactor; +TimerPacketSink sink; +PacketPassFairQueue fq; +PacketPassFairQueueFlow flows[NUM_INPUTS]; +FastPacketSource sources[NUM_INPUTS]; +char *data[] = {"0 data", "1 datadatadata", "2 datadatadatadatadata"}; +BTimer timer; +int current_cancel; + +static void init_input (int i) +{ + PacketPassFairQueueFlow_Init(&flows[i], &fq); + FastPacketSource_Init(&sources[i], PacketPassFairQueueFlow_GetInput(&flows[i]), (uint8_t *)data[i], strlen(data[i]), BReactor_PendingGroup(&reactor)); +} + +static void free_input (int i) +{ + FastPacketSource_Free(&sources[i]); + PacketPassFairQueueFlow_Free(&flows[i]); +} + +static void timer_handler (void *user) +{ + printf("removing %d\n", current_cancel); + + // release flow + if (PacketPassFairQueueFlow_IsBusy(&flows[current_cancel])) { + PacketPassFairQueueFlow_Release(&flows[current_cancel]); + } + + // remove flow + free_input(current_cancel); + + // init flow + init_input(current_cancel); + + // increment cancel + current_cancel = (current_cancel + 1) % NUM_INPUTS; + + // reset timer + BReactor_SetTimer(&reactor, &timer); +} + +int main () +{ + // initialize logging + BLog_InitStdout(); + + // init time + BTime_Init(); + + // initialize reactor + if (!BReactor_Init(&reactor)) { + DEBUG("BReactor_Init failed"); + return 1; + } + + // initialize sink + TimerPacketSink_Init(&sink, &reactor, 500, OUTPUT_INTERVAL); + + // initialize queue + PacketPassFairQueue_Init(&fq, TimerPacketSink_GetInput(&sink), BReactor_PendingGroup(&reactor)); + PacketPassFairQueue_EnableCancel(&fq); + + // initialize inputs + for (int i = 0; i < NUM_INPUTS; i++) { + init_input(i); + } + + // init cancel timer + BTimer_Init(&timer, REMOVE_INTERVAL, timer_handler, NULL); + BReactor_SetTimer(&reactor, &timer); + + // init cancel counter + current_cancel = 0; + + // run reactor + int ret = BReactor_Exec(&reactor); + BReactor_Free(&reactor); + return ret; +} diff --git a/examples/fairqueue_test2.c b/examples/fairqueue_test2.c new file mode 100644 index 000000000..bb9ccf9a1 --- /dev/null +++ b/examples/fairqueue_test2.c @@ -0,0 +1,81 @@ +/** + * @file fairqueue_test2.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +int main () +{ + // initialize logging + BLog_InitStdout(); + + // init time + BTime_Init(); + + // initialize reactor + BReactor reactor; + if (!BReactor_Init(&reactor)) { + DEBUG("BReactor_Init failed"); + return 1; + } + + // initialize sink + RandomPacketSink sink; + RandomPacketSink_Init(&sink, &reactor, 500, 0); + + // initialize queue + PacketPassFairQueue fq; + PacketPassFairQueue_Init(&fq, RandomPacketSink_GetInput(&sink), BReactor_PendingGroup(&reactor)); + + // initialize source 1 + PacketPassFairQueueFlow flow1; + PacketPassFairQueueFlow_Init(&flow1, &fq); + FastPacketSource source1; + char data1[] = "data1"; + FastPacketSource_Init(&source1, PacketPassFairQueueFlow_GetInput(&flow1), (uint8_t *)data1, strlen(data1), BReactor_PendingGroup(&reactor)); + + // initialize source 2 + PacketPassFairQueueFlow flow2; + PacketPassFairQueueFlow_Init(&flow2, &fq); + FastPacketSource source2; + char data2[] = "data2data2"; + FastPacketSource_Init(&source2, PacketPassFairQueueFlow_GetInput(&flow2), (uint8_t *)data2, strlen(data2), BReactor_PendingGroup(&reactor)); + + // initialize source 3 + PacketPassFairQueueFlow flow3; + PacketPassFairQueueFlow_Init(&flow3, &fq); + FastPacketSource source3; + char data3[] = "data3data3data3data3data3data3data3data3data3"; + FastPacketSource_Init(&source3, PacketPassFairQueueFlow_GetInput(&flow3), (uint8_t *)data3, strlen(data3), BReactor_PendingGroup(&reactor)); + + // run reactor + int ret = BReactor_Exec(&reactor); + BReactor_Free(&reactor); + return ret; +} diff --git a/examples/hashtable_bench.c b/examples/hashtable_bench.c new file mode 100644 index 000000000..373d1d473 --- /dev/null +++ b/examples/hashtable_bench.c @@ -0,0 +1,119 @@ +/** + * @file hashtable_bench.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include +#include +#include +#include +#include + +struct mynode { + int used; + int num; + HashTableNode hash_node; +}; + +static int key_comparator (int *key1, int *key2) +{ + return (*key1 == *key2); +} + +static int hash_function (int *key, int modulo) +{ + return (jenkins_one_at_a_time_hash((uint8_t *)key, sizeof(int)) % modulo); +} + +static void print_indent (int indent) +{ + for (int i = 0; i < indent; i++) { + printf(" "); + } +} + +int main (int argc, char **argv) +{ + int num_nodes; + int num_random_delete; + + if (argc != 3 || (num_nodes = atoi(argv[1])) <= 0 || (num_random_delete = atoi(argv[2])) < 0) { + fprintf(stderr, "Usage: %s \n", (argc > 0 ? argv[0] : NULL)); + return 1; + } + + struct mynode *nodes = malloc(num_nodes * sizeof(*nodes)); + if (!nodes) { + fprintf(stderr, "malloc failed\n"); + return 1; + } + + int *values_ins = malloc(num_nodes * sizeof(int)); + ASSERT_FORCE(values_ins) + + int *values = malloc(num_random_delete * sizeof(int)); + ASSERT_FORCE(values) + + HashTable ht; + if (!HashTable_Init( + &ht, + OFFSET_DIFF(struct mynode, num, hash_node), + (HashTable_comparator)key_comparator, + (HashTable_hash_function)hash_function, + num_nodes * 2 + )) { + fprintf(stderr, "HashTable_Init failed\n"); + return 1; + } + + printf("Inserting random values...\n"); + brandom_randomize((uint8_t *)values_ins, num_nodes * sizeof(int)); + for (int i = 0; i < num_nodes; i++) { + nodes[i].num = values_ins[i]; + if (HashTable_Insert(&ht, &nodes[i].hash_node)) { + nodes[i].used = 1; + } else { + nodes[i].used = 0; + printf("Insert collision!\n"); + } + } + + printf("Removing random entries...\n"); + int removed = 0; + brandom_randomize((uint8_t *)values, num_random_delete * sizeof(int)); + for (int i = 0; i < num_random_delete; i++) { + int index = (((unsigned int *)values)[i] % num_nodes); + struct mynode *node = nodes + index; + if (node->used) { + ASSERT_EXECUTE(HashTable_Remove(&ht, &node->num)) + node->used = 0; + removed++; + } + } + + printf("Removed %d entries\n", removed); + + free(nodes); + free(values); + + return 0; +} diff --git a/examples/hashtable_example.c b/examples/hashtable_example.c new file mode 100644 index 000000000..38a58cf3c --- /dev/null +++ b/examples/hashtable_example.c @@ -0,0 +1,94 @@ +/** + * @file hashtable_example.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include +#include +#include + +int key_comparator (int *key1, int *key2) +{ + return (*key1 == *key2); +} + +int hash_function (int *key, int modulo) +{ + return (jenkins_one_at_a_time_hash((uint8_t *)key, sizeof(int)) % modulo); +} + +struct entry { + HashTableNode node; + int value; +}; + +int main () +{ + HashTable table; + if (HashTable_Init( + &table, + (offsetof(struct entry, value) - offsetof(struct entry, node)), + (HashTable_comparator)key_comparator, + (HashTable_hash_function)hash_function, + 20 + ) != 1) { + return 1; + } + + struct entry entries[10]; + + // insert entries + int i; + for (i=0; i<10; i++) { + struct entry *entry = &entries[i]; + // must initialize value before inserting + entry->value = i; + // insert + int res = HashTable_Insert(&table, &entry->node); + ASSERT(res == 1) + } + + // lookup entries + for (i=0; i<10; i++) { + HashTableNode *node; + int res = HashTable_Lookup(&table, &i, &node); + ASSERT(res == 1) + struct entry *entry = (struct entry *)((uint8_t *)node - offsetof(struct entry, node)); + ASSERT(entry == &entries[i]) + } + + // remove entries + for (i=0; i<10; i++) { + int res = HashTable_Remove(&table, &i); + ASSERT(res == 1) + } + + // remove entries again + for (i=0; i<10; i++) { + int res = HashTable_Remove(&table, &i); + ASSERT(res == 0) + } + + HashTable_Free(&table); + + return 0; +} diff --git a/examples/linkedlist2_example.c b/examples/linkedlist2_example.c new file mode 100644 index 000000000..508a5fcab --- /dev/null +++ b/examples/linkedlist2_example.c @@ -0,0 +1,161 @@ +/** + * @file linkedlist2_example.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include + +#include + +struct elem +{ + int i; + LinkedList2Node list_node; +}; + +void printnode (LinkedList2Node *node) +{ + if (!node) { + printf("(null) "); + } else { + struct elem *e = (struct elem *)((uint8_t *)node-offsetof(struct elem, list_node)); + printf("%d ", e->i); + } +} + +void printall (LinkedList2 *list) +{ + printf("List: "); + LinkedList2Iterator it; + LinkedList2Iterator_InitForward(&it, list); + LinkedList2Node *node; + while (node = LinkedList2Iterator_Next(&it)) { + printnode(node); + } + printf("\n"); +} + +void removeall (LinkedList2 *list) +{ + LinkedList2Iterator it; + LinkedList2Iterator_InitForward(&it, list); + LinkedList2Node *node; + while (node = LinkedList2Iterator_Next(&it)) { + LinkedList2_Remove(list, node); + } +} + +int main () +{ + struct elem elems[10]; + + LinkedList2 list; + LinkedList2_Init(&list); + + int i; + for (i=0; i<10; i++) { + elems[i].i = i; + LinkedList2_Append(&list, &elems[i].list_node); + } + + printall(&list); + + LinkedList2Iterator it1; + LinkedList2Iterator it2; + LinkedList2Iterator it3; + LinkedList2Iterator it4; + + LinkedList2Iterator_InitForward(&it1, &list); + LinkedList2Iterator_InitForward(&it2, &list); + LinkedList2Iterator_InitBackward(&it3, &list); + LinkedList2Iterator_InitBackward(&it4, &list); + + LinkedList2_Remove(&list, &elems[0].list_node); + LinkedList2_Remove(&list, &elems[1].list_node); + LinkedList2_Remove(&list, &elems[2].list_node); + LinkedList2_Remove(&list, &elems[3].list_node); + + LinkedList2_Remove(&list, &elems[9].list_node); + LinkedList2_Remove(&list, &elems[8].list_node); + LinkedList2_Remove(&list, &elems[7].list_node); + LinkedList2_Remove(&list, &elems[6].list_node); + + LinkedList2Node *node1; + LinkedList2Node *node2; + LinkedList2Node *node3; + LinkedList2Node *node4; + + node1 = LinkedList2Iterator_Next(&it1); + node2 = LinkedList2Iterator_Next(&it2); + printnode(node1); + printnode(node2); + printf("\n"); + + node3 = LinkedList2Iterator_Next(&it3); + node4 = LinkedList2Iterator_Next(&it4); + printnode(node3); + printnode(node4); + printf("\n"); + + printall(&list); + + node1 = LinkedList2Iterator_Next(&it1); + printnode(node1); + printf("\n"); + + node3 = LinkedList2Iterator_Next(&it3); + printnode(node3); + printf("\n"); + + printall(&list); + + LinkedList2_Prepend(&list, &elems[3].list_node); + LinkedList2_Append(&list, &elems[6].list_node); + + printall(&list); + + node1 = LinkedList2Iterator_Next(&it1); + node2 = LinkedList2Iterator_Next(&it2); + printnode(node1); + printnode(node2); + printf("\n"); + + node3 = LinkedList2Iterator_Next(&it3); + node4 = LinkedList2Iterator_Next(&it4); + printnode(node3); + printnode(node4); + printf("\n"); + + node1 = LinkedList2Iterator_Next(&it1); + node2 = LinkedList2Iterator_Next(&it2); + printnode(node1); + printnode(node2); + printf("\n"); + + node3 = LinkedList2Iterator_Next(&it3); + node4 = LinkedList2Iterator_Next(&it4); + printnode(node3); + printnode(node4); + printf("\n"); + + return 0; +} diff --git a/examples/predicate_test.c b/examples/predicate_test.c new file mode 100644 index 000000000..f2b6dbf33 --- /dev/null +++ b/examples/predicate_test.c @@ -0,0 +1,106 @@ +/** + * @file predicate_test.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include + +#include +#include + +static int func_hello (void *user, void **args) +{ + return 1; +} + +static int func_neg (void *user, void **args) +{ + int arg = *((int *)args[0]); + + return !arg; +} + +static int func_conj (void *user, void **args) +{ + int arg1 = *((int *)args[0]); + int arg2 = *((int *)args[1]); + + return (arg1 && arg2); +} + +static int func_strcmp (void *user, void **args) +{ + char *arg1 = args[0]; + char *arg2 = args[1]; + + return (!strcmp(arg1, arg2)); +} + +static int func_error (void *user, void **args) +{ + return -1; +} + +int main (int argc, char **argv) +{ + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + // init logger + BLog_InitStdout(); + + // init predicate + BPredicate pr; + if (!BPredicate_Init(&pr, argv[1])) { + fprintf(stderr, "BPredicate_Init failed\n"); + return 1; + } + + // init functions + BPredicateFunction f_hello; + BPredicateFunction_Init(&f_hello, &pr, "hello", NULL, 0, func_hello, NULL); + BPredicateFunction f_neg; + BPredicateFunction_Init(&f_neg, &pr, "neg", (int []){PREDICATE_TYPE_BOOL}, 1, func_neg, NULL); + BPredicateFunction f_conj; + BPredicateFunction_Init(&f_conj, &pr, "conj", (int []){PREDICATE_TYPE_BOOL, PREDICATE_TYPE_BOOL}, 2, func_conj, NULL); + BPredicateFunction f_strcmp; + BPredicateFunction_Init(&f_strcmp, &pr, "strcmp", (int []){PREDICATE_TYPE_STRING, PREDICATE_TYPE_STRING}, 2, func_strcmp, NULL); + BPredicateFunction f_error; + BPredicateFunction_Init(&f_error, &pr, "error", NULL, 0, func_error, NULL); + + // evaluate + int result = BPredicate_Eval(&pr); + printf("%d\n", result); + + // free functions + BPredicateFunction_Free(&f_hello); + BPredicateFunction_Free(&f_neg); + BPredicateFunction_Free(&f_conj); + BPredicateFunction_Free(&f_strcmp); + BPredicateFunction_Free(&f_error); + + // free predicate + BPredicate_Free(&pr); + + return 0; +} diff --git a/flooder/CMakeLists.txt b/flooder/CMakeLists.txt new file mode 100644 index 000000000..6d28b5ecb --- /dev/null +++ b/flooder/CMakeLists.txt @@ -0,0 +1,7 @@ +add_executable(badvpn-flooder flooder.c) +target_link_libraries(badvpn-flooder system flow server_conection ${LIBCRYPTO_LIBRARIES} ${NSPR_LIBRARIES} ${NSS_LIBRARIES}) + +install( + TARGETS badvpn-flooder + RUNTIME DESTINATION bin +) diff --git a/flooder/flooder.c b/flooder/flooder.c new file mode 100644 index 000000000..b0efb55b5 --- /dev/null +++ b/flooder/flooder.c @@ -0,0 +1,719 @@ +/** + * @file flooder.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef BADVPN_USE_WINAPI +#include +#endif + +#include + +#include + +#define LOGGER_STDOUT 1 +#define LOGGER_SYSLOG 2 + +// program dead variable +dead_t dead; + +// command-line options +struct { + int help; + int version; + int logger; + #ifndef BADVPN_USE_WINAPI + char *logger_syslog_facility; + char *logger_syslog_ident; + #endif + int loglevel; + int loglevels[BLOG_NUM_CHANNELS]; + int ssl; + char *nssdb; + char *client_cert_name; + char *server_name; + char *server_addr; + peerid_t floods[MAX_FLOODS]; + int num_floods; +} options; + +// server address we connect to +BAddr server_addr; + +// server name to use for SSL +char server_name[256]; + +// reactor +BReactor ss; + +// client certificate if using SSL +CERTCertificate *client_cert; + +// client private key if using SSL +SECKEYPrivateKey *client_key; + +// server connection +ServerConnection server; + +// whether server is ready +int server_ready; + +// my ID, defined only after server_ready +peerid_t my_id; + +// flooding output +PacketRecvInterface flood_source; +PacketProtoEncoder flood_encoder; +SinglePacketBuffer flood_buffer; + +// whether we were asked for a packet and blocked +int flood_blocking; + +// index of next peer to send packet too +int flood_next; + +/** + * Cleans up everything that can be cleaned up from inside the event loop. + */ +static void terminate (void); + +/** + * Prints command line help. + */ +static void print_help (const char *name); + +/** + * Prints program name, version and copyright notice. + */ +static void print_version (void); + +/** + * Parses command line options into the options strucute. + * + * @return 1 on success, 0 on failure + */ +static int parse_arguments (int argc, char *argv[]); + +/** + * Processes command line options. + * + * @return 1 on success, 0 on failure + */ +static int resolve_arguments (void); + +/** + * Handler invoked when program termination is requested. + */ +static void signal_handler (void *unused); + +static void server_handler_error (void *user); +static void server_handler_ready (void *user, peerid_t param_my_id, uint32_t ext_ip); +static void server_handler_newclient (void *user, peerid_t peer_id, int flags, const uint8_t *cert, int cert_len); +static void server_handler_endclient (void *user, peerid_t peer_id); +static void server_handler_message (void *user, peerid_t peer_id, uint8_t *data, int data_len); + +static int flood_source_handler_recv (void *user, uint8_t *data, int *data_len); + +int main (int argc, char *argv[]) +{ + if (argc <= 0) { + return 1; + } + + // init dead variable + DEAD_INIT(dead); + + // parse command-line arguments + if (!parse_arguments(argc, argv)) { + fprintf(stderr, "Failed to parse arguments\n"); + print_help(argv[0]); + goto fail0; + } + + // handle --help and --version + if (options.help) { + print_version(); + print_help(argv[0]); + return 0; + } + if (options.version) { + print_version(); + return 0; + } + + // initialize logger + switch (options.logger) { + case LOGGER_STDOUT: + BLog_InitStdout(); + break; + #ifndef BADVPN_USE_WINAPI + case LOGGER_SYSLOG: + if (!BLog_InitSyslog(options.logger_syslog_ident, options.logger_syslog_facility)) { + fprintf(stderr, "Failed to initialize syslog logger\n"); + goto fail0; + } + break; + #endif + default: + ASSERT(0); + } + + // configure logger channels + for (int i = 0; i < BLOG_NUM_CHANNELS; i++) { + if (options.loglevels[i] >= 0) { + BLog_SetChannelLoglevel(i, options.loglevels[i]); + } + else if (options.loglevel >= 0) { + BLog_SetChannelLoglevel(i, options.loglevel); + } + } + + BLog(BLOG_NOTICE, "initializing "GLOBAL_PRODUCT_NAME" client "GLOBAL_VERSION); + + // initialize sockets + if (BSocket_GlobalInit() < 0) { + BLog(BLOG_ERROR, "BSocket_GlobalInit failed"); + goto fail1; + } + + // init time + BTime_Init(); + + // resolve addresses + if (!resolve_arguments()) { + BLog(BLOG_ERROR, "Failed to resolve arguments"); + goto fail1; + } + + // init reactor + if (!BReactor_Init(&ss)) { + BLog(BLOG_ERROR, "BReactor_Init failed"); + goto fail1; + } + + // setup signal handler + if (!BSignal_Init()) { + BLog(BLOG_ERROR, "BSignal_Init failed"); + goto fail1a; + } + BSignal_Capture(); + if (!BSignal_SetHandler(&ss, signal_handler, NULL)) { + BLog(BLOG_ERROR, "BSignal_SetHandler failed"); + goto fail1a; + } + + if (options.ssl) { + // init NSPR + PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0); + + // register local NSPR file types + if (!DummyPRFileDesc_GlobalInit()) { + BLog(BLOG_ERROR, "DummyPRFileDesc_GlobalInit failed"); + goto fail2; + } + if (!BSocketPRFileDesc_GlobalInit()) { + BLog(BLOG_ERROR, "BSocketPRFileDesc_GlobalInit failed"); + goto fail2; + } + + // init NSS + if (NSS_Init(options.nssdb) != SECSuccess) { + BLog(BLOG_ERROR, "NSS_Init failed (%d)", (int)PR_GetError()); + goto fail2; + } + + // set cipher policy + if (NSS_SetDomesticPolicy() != SECSuccess) { + BLog(BLOG_ERROR, "NSS_SetDomesticPolicy failed (%d)", (int)PR_GetError()); + goto fail3; + } + + // init server cache + if (SSL_ConfigServerSessionIDCache(0, 0, 0, NULL) != SECSuccess) { + BLog(BLOG_ERROR, "SSL_ConfigServerSessionIDCache failed (%d)", (int)PR_GetError()); + goto fail3; + } + + // open server certificate and private key + if (!open_nss_cert_and_key(options.client_cert_name, &client_cert, &client_key)) { + BLog(BLOG_ERROR, "Cannot open certificate and key"); + goto fail4; + } + } + + // start connecting to server + if (!ServerConnection_Init( + &server, &ss, server_addr, SC_KEEPALIVE_INTERVAL, SERVER_BUFFER_MIN_PACKETS, options.ssl, client_cert, client_key, server_name, NULL, + server_handler_error, server_handler_ready, server_handler_newclient, server_handler_endclient, server_handler_message + )) { + BLog(BLOG_ERROR, "ServerConnection_Init failed"); + goto fail5; + } + + // set server not ready + server_ready = 0; + + goto event_loop; + + // cleanup on error +fail5: + if (options.ssl) { + CERT_DestroyCertificate(client_cert); + SECKEY_DestroyPrivateKey(client_key); +fail4: + SSL_ShutdownServerSessionIDCache(); +fail3: + SSL_ClearSessionCache(); + ASSERT_FORCE(NSS_Shutdown() == SECSuccess) +fail2: + ASSERT_FORCE(PR_Cleanup() == PR_SUCCESS) + PL_ArenaFinish(); + } + BSignal_RemoveHandler(); +fail1a: + BReactor_Free(&ss); +fail1: + BLog(BLOG_ERROR, "initialization failed"); + BLog_Free(); +fail0: + // finish objects + DebugObjectGlobal_Finish(); + return 1; + +event_loop: + // enter event loop + BLog(BLOG_NOTICE, "entering event loop"); + int ret = BReactor_Exec(&ss); + + // free reactor + BReactor_Free(&ss); + + // free logger + BLog(BLOG_NOTICE, "exiting"); + BLog_Free(); + + // finish objects + DebugObjectGlobal_Finish(); + + return ret; +} + +void terminate (void) +{ + BLog(BLOG_NOTICE, "tearing down"); + + if (server_ready) { + // free flooding + + // free buffer + SinglePacketBuffer_Free(&flood_buffer); + + // free encoder + PacketProtoEncoder_Free(&flood_encoder); + + // free source + PacketRecvInterface_Free(&flood_source); + } + + // free server + ServerConnection_Free(&server); + + if (options.ssl) { + // free client certificate and private key + CERT_DestroyCertificate(client_cert); + SECKEY_DestroyPrivateKey(client_key); + + // free server cache + ASSERT_FORCE(SSL_ShutdownServerSessionIDCache() == SECSuccess) + + // free client cache + SSL_ClearSessionCache(); + + // free NSS + ASSERT_FORCE(NSS_Shutdown() == SECSuccess) + + // free NSPR + ASSERT_FORCE(PR_Cleanup() == PR_SUCCESS) + PL_ArenaFinish(); + } + + // remove signal handler + BSignal_RemoveHandler(); + + // kill dead variable + DEAD_KILL(dead); + + // exit reactor + BReactor_Quit(&ss, 1); +} + +void print_help (const char *name) +{ + printf( + "Usage:\n" + " %s\n" + " [--help]\n" + " [--version]\n" + " [--logger <"LOGGERS_STRING">]\n" + #ifndef BADVPN_USE_WINAPI + " (logger=syslog?\n" + " [--syslog-facility ]\n" + " [--syslog-ident ]\n" + " )\n" + #endif + " [--loglevel <0-5/none/error/warning/notice/info/debug>]\n" + " [--channel-loglevel <0-5/none/error/warning/notice/info/debug>] ...\n" + " [--ssl --nssdb --client-cert-name ]\n" + " [--server-name ]\n" + " --server-addr \n" + " [--flood-id ...]\n" + "Address format is a.b.c.d:port (IPv4) or [addr]:port (IPv6).\n", + name + ); +} + +void print_version (void) +{ + printf(GLOBAL_PRODUCT_NAME" "PROGRAM_NAME" "GLOBAL_VERSION"\n"GLOBAL_COPYRIGHT_NOTICE"\n"); +} + +int parse_arguments (int argc, char *argv[]) +{ + if (argc <= 0) { + return 0; + } + + options.help = 0; + options.version = 0; + options.logger = LOGGER_STDOUT; + #ifndef BADVPN_USE_WINAPI + options.logger_syslog_facility = "daemon"; + options.logger_syslog_ident = argv[0]; + #endif + options.loglevel = -1; + for (int i = 0; i < BLOG_NUM_CHANNELS; i++) { + options.loglevels[i] = -1; + } + options.ssl = 0; + options.nssdb = NULL; + options.client_cert_name = NULL; + options.server_name = NULL; + options.server_addr = NULL; + options.num_floods = 0; + + int i; + for (i = 1; i < argc; i++) { + char *arg = argv[i]; + if (!strcmp(arg, "--help")) { + options.help = 1; + } + else if (!strcmp(arg, "--version")) { + options.version = 1; + } + else if (!strcmp(arg, "--logger")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + char *arg2 = argv[i + 1]; + if (!strcmp(arg2, "stdout")) { + options.logger = LOGGER_STDOUT; + } + #ifndef BADVPN_USE_WINAPI + else if (!strcmp(arg2, "syslog")) { + options.logger = LOGGER_SYSLOG; + } + #endif + else { + fprintf(stderr, "%s: wrong argument\n", arg); + return 0; + } + i++; + } + #ifndef BADVPN_USE_WINAPI + else if (!strcmp(arg, "--syslog-facility")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + options.logger_syslog_facility = argv[i + 1]; + i++; + } + else if (!strcmp(arg, "--syslog-ident")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + options.logger_syslog_ident = argv[i + 1]; + i++; + } + #endif + else if (!strcmp(arg, "--loglevel")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + if ((options.loglevel = parse_loglevel(argv[i + 1])) < 0) { + fprintf(stderr, "%s: wrong argument\n", arg); + return 0; + } + i++; + } + else if (!strcmp(arg, "--channel-loglevel")) { + if (2 >= argc - i) { + fprintf(stderr, "%s: requires two arguments\n", arg); + return 0; + } + int channel = BLogGlobal_GetChannelByName(argv[i + 1]); + if (channel < 0) { + fprintf(stderr, "%s: wrong channel argument\n", arg); + return 0; + } + int loglevel = parse_loglevel(argv[i + 2]); + if (loglevel < 0) { + fprintf(stderr, "%s: wrong loglevel argument\n", arg); + return 0; + } + options.loglevels[channel] = loglevel; + i += 2; + } + else if (!strcmp(arg, "--ssl")) { + options.ssl = 1; + } + else if (!strcmp(arg, "--nssdb")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + options.nssdb = argv[i + 1]; + i++; + } + else if (!strcmp(arg, "--client-cert-name")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + options.client_cert_name = argv[i + 1]; + i++; + } + else if (!strcmp(arg, "--server-name")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + options.server_name = argv[i + 1]; + i++; + } + else if (!strcmp(arg, "--server-addr")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + options.server_addr = argv[i + 1]; + i++; + } + else if (!strcmp(arg, "--flood-id")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + if (options.num_floods == MAX_FLOODS) { + fprintf(stderr, "%s: too many\n", arg); + return 0; + } + options.floods[options.num_floods] = atoi(argv[i + 1]); + options.num_floods++; + i++; + } + else { + fprintf(stderr, "unknown option: %s\n", arg); + return 0; + } + } + + if (options.help || options.version) { + return 1; + } + + if (options.ssl != !!options.nssdb) { + fprintf(stderr, "False: --ssl <=> --nssdb\n"); + return 0; + } + + if (options.ssl != !!options.client_cert_name) { + fprintf(stderr, "False: --ssl <=> --client-cert-name\n"); + return 0; + } + + if (!options.server_addr) { + fprintf(stderr, "False: --server-addr\n"); + return 0; + } + + return 1; +} + +int resolve_arguments (void) +{ + // resolve server address + ASSERT(options.server_addr) + if (!BAddr_Parse(&server_addr, options.server_addr, server_name, sizeof(server_name))) { + BLog(BLOG_ERROR, "server addr: BAddr_Parse failed"); + return 0; + } + if (!addr_supported(server_addr)) { + BLog(BLOG_ERROR, "server addr: not supported"); + return 0; + } + + // override server name if requested + if (options.server_name) { + snprintf(server_name, sizeof(server_name), "%s", options.server_name); + } + + return 1; +} + +void signal_handler (void *unused) +{ + BLog(BLOG_NOTICE, "termination requested"); + + terminate(); +} + +void server_handler_error (void *user) +{ + BLog(BLOG_ERROR, "server connection failed, exiting"); + + terminate(); +} + +void server_handler_ready (void *user, peerid_t param_my_id, uint32_t ext_ip) +{ + ASSERT(!server_ready) + + // remember our ID + my_id = param_my_id; + + // init flooding + + // init source + PacketRecvInterface_Init(&flood_source, SC_MAX_ENC, flood_source_handler_recv, NULL); + + // init encoder + PacketProtoEncoder_Init(&flood_encoder, &flood_source); + + // init buffer + if (!SinglePacketBuffer_Init(&flood_buffer, PacketProtoEncoder_GetOutput(&flood_encoder), ServerConnection_GetSendInterface(&server), BReactor_PendingGroup(&ss))) { + BLog(BLOG_ERROR, "SinglePacketBuffer_Init failed, exiting"); + goto fail1; + } + + // set not blocking + flood_blocking = 0; + + // set server ready + server_ready = 1; + + BLog(BLOG_INFO, "server: ready, my ID is %d", (int)my_id); + + return; + +fail1: + PacketProtoEncoder_Free(&flood_encoder); + PacketRecvInterface_Free(&flood_source); + terminate(); +} + +void server_handler_newclient (void *user, peerid_t peer_id, int flags, const uint8_t *cert, int cert_len) +{ + ASSERT(server_ready) + + BLog(BLOG_INFO, "newclient %d", (int)peer_id); +} + +void server_handler_endclient (void *user, peerid_t peer_id) +{ + ASSERT(server_ready) + + BLog(BLOG_INFO, "endclient %d", (int)peer_id); +} + +void server_handler_message (void *user, peerid_t peer_id, uint8_t *data, int data_len) +{ + ASSERT(server_ready) + ASSERT(data_len >= 0) + ASSERT(data_len <= SC_MAX_MSGLEN) + + BLog(BLOG_INFO, "message from %d", (int)peer_id); +} + +int flood_source_handler_recv (void *user, uint8_t *data, int *data_len) +{ + ASSERT(server_ready) + ASSERT(!flood_blocking) + if (options.num_floods > 0) { + ASSERT(flood_next >= 0) + ASSERT(flood_next < options.num_floods) + } + + if (options.num_floods == 0) { + flood_blocking = 1; + return 0; + } + + peerid_t peer_id = options.floods[flood_next]; + flood_next = (flood_next + 1) % options.num_floods; + + BLog(BLOG_INFO, "message to %d", (int)peer_id); + + struct sc_header *header = (struct sc_header *)data; + header->type = SCID_OUTMSG; + + struct sc_client_outmsg *msg = (struct sc_client_outmsg *)(data + sizeof(struct sc_header)); + msg->clientid = htol16(peer_id); + + memset(data + sizeof(struct sc_header) + sizeof(struct sc_client_outmsg), 0, SC_MAX_MSGLEN); + + *data_len = sizeof(struct sc_header) + sizeof(struct sc_client_outmsg) + SC_MAX_MSGLEN; + return 1; +} diff --git a/flooder/flooder.h b/flooder/flooder.h new file mode 100644 index 000000000..abf506d45 --- /dev/null +++ b/flooder/flooder.h @@ -0,0 +1,30 @@ +/** + * @file flooder.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +// name of the program +#define PROGRAM_NAME "flooder" + +// server output buffer size +#define SERVER_BUFFER_MIN_PACKETS 200 + +// maximum number of peers to flood +#define MAX_FLOODS 64 diff --git a/flow/BestEffortPacketWriteInterface.h b/flow/BestEffortPacketWriteInterface.h new file mode 100644 index 000000000..cd6992643 --- /dev/null +++ b/flow/BestEffortPacketWriteInterface.h @@ -0,0 +1,214 @@ +/** + * @file BestEffortPacketWriteInterface.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Interface which allows a sender to write packets to a buffer provided by the receiver + * in a best-effort fashion. + */ + +#ifndef BADVPN_FLOW_BESTEFFORTPACKETWRITEINTERFACE +#define BADVPN_FLOW_BESTEFFORTPACKETWRITEINTERFACE + +#include +#include + +#include +#include +#include + +/** + * Callback function invoked at the receiver when the sender requests a memory location + * for writing a packet. + * The interface was not in writing state. + * + * @param user value given to {@link BestEffortPacketWriteInterface_Init} + * @param data if a memory location was provided, it must be returned here. It must have + * MTU bytes available. It may be set to NULL if MTU is 0. + * @param return 1 - a memory location was provided. The interface will enter writing state. + * 0 - a memory location was not provided + */ +typedef int (*BestEffortPacketWriteInterface_handler_startpacket) (void *user, uint8_t **data); + +/** + * Callback function invoked at the receiver when the sender has finished writing a packet. + * The interface was in writing state before. + * The interface enters not writing state after this function returns. + * + * @param user value given to {@link BestEffortPacketWriteInterface_Init} + * @param len length of the packet written. Will be >=0 and <=MTU. + */ +typedef void (*BestEffortPacketWriteInterface_handler_endpacket) (void *user, int len); + +/** + * Interface which allows a sender to write packets to a buffer provided by the receiver + * in a best-effort fashion. + */ +typedef struct { + DebugObject d_obj; + int mtu; + BestEffortPacketWriteInterface_handler_startpacket handler_startpacket; + BestEffortPacketWriteInterface_handler_endpacket handler_endpacket; + void *user; + #ifndef NDEBUG + int sending; + int in_call; + dead_t dead; + #endif +} BestEffortPacketWriteInterface; + +/** + * Initializes the interface. + * The interface is initialized in not writing state. + * + * @param i the object + * @param mtu maximum packet size. Must be >=0. + * @param handler_startpacket callback function invoked at the receiver when + * the sender wants a memory location for writing a packet + * @param handler_endpacket callback function invoked at the receiver when the sender + * has finished writing a packet + * @param user value passed to receiver callback functions + */ +static void BestEffortPacketWriteInterface_Init ( + BestEffortPacketWriteInterface *i, + int mtu, + BestEffortPacketWriteInterface_handler_startpacket handler_startpacket, + BestEffortPacketWriteInterface_handler_endpacket handler_endpacket, + void *user +); + +/** + * Frees the interface. + * + * @param i the object + */ +static void BestEffortPacketWriteInterface_Free (BestEffortPacketWriteInterface *i); + +/** + * Requests a memory location for writing a packet to the receiver. + * The interface must be in not writing state. + * + * @param i the object + * @param data if the function returns 1, will be set to the memory location where the + * * packet should be written. May be set to NULL if MTU is 0. + * @param return - 1 a memory location was provided. The interface enters writing state. + * - 0 a memory location was not provided + */ +static int BestEffortPacketWriteInterface_Sender_StartPacket (BestEffortPacketWriteInterface *i, uint8_t **data); + +/** + * Sumbits a packet written to the memory location provided by the receiver. + * The interface must be in writing state. + * The interface enters not writing state. + * + * @param i the object + * @param len length of the packet written. Must be >=0 and <=MTU. + */ +static void BestEffortPacketWriteInterface_Sender_EndPacket (BestEffortPacketWriteInterface *i, int len); + +void BestEffortPacketWriteInterface_Init ( + BestEffortPacketWriteInterface *i, + int mtu, + BestEffortPacketWriteInterface_handler_startpacket handler_startpacket, + BestEffortPacketWriteInterface_handler_endpacket handler_endpacket, + void *user +) +{ + ASSERT(mtu >= 0) + + // init arguments + i->mtu = mtu; + i->handler_startpacket = handler_startpacket; + i->handler_endpacket = handler_endpacket; + i->user = user; + + // init debug object + DebugObject_Init(&i->d_obj); + + // init debugging + #ifndef NDEBUG + i->sending = 0; + i->in_call = 0; + DEAD_INIT(i->dead); + #endif +} + +void BestEffortPacketWriteInterface_Free (BestEffortPacketWriteInterface *i) +{ + // free debug object + DebugObject_Free(&i->d_obj); + + // free debugging + #ifndef NDEBUG + DEAD_KILL(i->dead); + #endif +} + +int BestEffortPacketWriteInterface_Sender_StartPacket (BestEffortPacketWriteInterface *i, uint8_t **data) +{ + ASSERT(!i->sending) + ASSERT(!i->in_call) + + #ifndef NDEBUG + i->in_call = 1; + DEAD_ENTER(i->dead) + #endif + + int res = i->handler_startpacket(i->user, data); + + #ifndef NDEBUG + if (DEAD_LEAVE(i->dead)) { + return -1; + } + ASSERT(res == 0 || res == 1) + i->in_call = 0; + if (res) { + i->sending = 1; + } + #endif + + return res; +} + +void BestEffortPacketWriteInterface_Sender_EndPacket (BestEffortPacketWriteInterface *i, int len) +{ + ASSERT(len >= 0) + ASSERT(len <= i->mtu) + ASSERT(i->sending) + ASSERT(!i->in_call) + + #ifndef NDEBUG + i->in_call = 1; + DEAD_ENTER(i->dead) + #endif + + i->handler_endpacket(i->user, len); + + #ifndef NDEBUG + if (DEAD_LEAVE(i->dead)) { + return; + } + i->in_call = 0; + i->sending = 0; + #endif +} + +#endif diff --git a/flow/CMakeLists.txt b/flow/CMakeLists.txt new file mode 100644 index 000000000..c46e84b2d --- /dev/null +++ b/flow/CMakeLists.txt @@ -0,0 +1,30 @@ +add_library(flow + PacketPassFairQueue.c + PacketPassPriorityQueue.c + PacketPassInactivityMonitor.c + PacketPassConnector.c + PacketRecvConnector.c + StreamRecvConnector.c + PacketRecvBlocker.c + PacketRecvNotifier.c + PacketPassNotifier.c + PacketBuffer.c + SinglePacketBuffer.c + PacketCopier.c + PacketStreamSender.c + KeepaliveIO.c + SCKeepaliveSource.c + StreamSocketSource.c + StreamSocketSink.c + DatagramSocketSource.c + DatagramSocketSink.c + PacketProtoEncoder.c + PacketProtoDecoder.c + FragmentProtoDisassembler.c + SPProtoEncoder.c + SPProtoDecoder.c + FragmentProtoAssembler.c + DataProtoKeepaliveSource.c + PacketProtoFlow.c +) +target_link_libraries(flow system) diff --git a/flow/DataProtoKeepaliveSource.c b/flow/DataProtoKeepaliveSource.c new file mode 100644 index 000000000..aff9abecb --- /dev/null +++ b/flow/DataProtoKeepaliveSource.c @@ -0,0 +1,60 @@ +/** + * @file DataProtoKeepaliveSource.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include + +#include + +static int output_handler_recv (DataProtoKeepaliveSource *o, uint8_t *data, int *data_len) +{ + struct dataproto_header *header = (struct dataproto_header *)data; + header->flags = 0; + header->from_id = htol16(0); + header->num_peer_ids = htol16(0); + + *data_len = sizeof(struct dataproto_header); + return 1; +} + +void DataProtoKeepaliveSource_Init (DataProtoKeepaliveSource *o) +{ + // init output + PacketRecvInterface_Init(&o->output, sizeof(struct dataproto_header), (PacketRecvInterface_handler_recv)output_handler_recv, o); + + // init debug object + DebugObject_Init(&o->d_obj); +} + +void DataProtoKeepaliveSource_Free (DataProtoKeepaliveSource *o) +{ + // free debug object + DebugObject_Free(&o->d_obj); + + // free output + PacketRecvInterface_Free(&o->output); +} + +PacketRecvInterface * DataProtoKeepaliveSource_GetOutput (DataProtoKeepaliveSource *o) +{ + return &o->output; +} diff --git a/flow/DataProtoKeepaliveSource.h b/flow/DataProtoKeepaliveSource.h new file mode 100644 index 000000000..07d5d046d --- /dev/null +++ b/flow/DataProtoKeepaliveSource.h @@ -0,0 +1,65 @@ +/** + * @file DataProtoKeepaliveSource.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * A {@link PacketRecvInterface} source which provides DataProto keepalive packets. + */ + +#ifndef BADVPN_FLOW_DATAPROTOKEEPALIVESOURCE_H +#define BADVPN_FLOW_DATAPROTOKEEPALIVESOURCE_H + +#include +#include + +/** + * A {@link PacketRecvInterface} source which provides DataProto keepalive packets. + * These packets have no payload, no destination peers and flags zero. + */ +typedef struct { + DebugObject d_obj; + PacketRecvInterface output; +} DataProtoKeepaliveSource; + +/** + * Initializes the object. + * + * @param o the object + */ +void DataProtoKeepaliveSource_Init (DataProtoKeepaliveSource *o); + +/** + * Frees the object. + * + * @param o the object + */ +void DataProtoKeepaliveSource_Free (DataProtoKeepaliveSource *o); + +/** + * Returns the output interface. + * The MTU of the output interface will be sizeof(struct dataproto_header). + * + * @param o the object + * @return output interface + */ +PacketRecvInterface * DataProtoKeepaliveSource_GetOutput (DataProtoKeepaliveSource *o); + +#endif diff --git a/flow/DatagramSocketSink.c b/flow/DatagramSocketSink.c new file mode 100644 index 000000000..bdb6f84ed --- /dev/null +++ b/flow/DatagramSocketSink.c @@ -0,0 +1,165 @@ +/** + * @file DatagramSocketSink.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include + +static int report_error (DatagramSocketSink *s, int error) +{ + #ifndef NDEBUG + s->in_error = 1; + #endif + + DEAD_ENTER(s->dead) + FlowErrorReporter_ReportError(&s->rep, &error); + if (DEAD_LEAVE(s->dead)) { + return -1; + } + + #ifndef NDEBUG + s->in_error = 0; + #endif + + return 0; +} + +static int input_handler_send (DatagramSocketSink *s, uint8_t *data, int data_len) +{ + ASSERT(s->in_len == -1) + ASSERT(data_len >= 0) + ASSERT(!s->in_error) + + int res = BSocket_SendToFrom(s->bsock, data, data_len, &s->addr, &s->local_addr); + if (res < 0) { + int error = BSocket_GetError(s->bsock); + if (error == BSOCKET_ERROR_LATER) { + s->in_len = data_len; + s->in = data; + BSocket_EnableEvent(s->bsock, BSOCKET_WRITE); + return 0; + } + if (report_error(s, DATAGRAMSOCKETSINK_ERROR_BSOCKET) < 0) { + return -1; + } + } else { + if (res != data_len) { + if (report_error(s, DATAGRAMSOCKETSINK_ERROR_WRONGSIZE) < 0) { + return -1; + } + } + } + + return 1; +} + +static void socket_handler (DatagramSocketSink *s, int event) +{ + ASSERT(s->in_len >= 0) + ASSERT(event == BSOCKET_WRITE) + ASSERT(!s->in_error) + + int res = BSocket_SendToFrom(s->bsock, s->in, s->in_len, &s->addr, &s->local_addr); + if (res < 0) { + int error = BSocket_GetError(s->bsock); + if (error == BSOCKET_ERROR_LATER) { + return; + } + if (report_error(s, DATAGRAMSOCKETSINK_ERROR_BSOCKET) < 0) { + return; + } + } else { + if (res != s->in_len) { + if (report_error(s, DATAGRAMSOCKETSINK_ERROR_WRONGSIZE) < 0) { + return; + } + } + } + + BSocket_DisableEvent(s->bsock, BSOCKET_WRITE); + s->in_len = -1; + + PacketPassInterface_Done(&s->input); + return; +} + +void DatagramSocketSink_Init (DatagramSocketSink *s, FlowErrorReporter rep, BSocket *bsock, int mtu, BAddr addr, BIPAddr local_addr) +{ + ASSERT(mtu >= 0) + ASSERT(BAddr_IsRecognized(&addr) && !BAddr_IsInvalid(&addr)) + ASSERT(BIPAddr_IsRecognized(&local_addr)) + + // init arguments + s->rep = rep; + s->bsock = bsock; + s->addr = addr; + s->local_addr = local_addr; + + // init dead var + DEAD_INIT(s->dead); + + // add socket event handler + BSocket_AddEventHandler(s->bsock, BSOCKET_WRITE, (BSocket_handler)socket_handler, s); + + // init input + PacketPassInterface_Init(&s->input, mtu, (PacketPassInterface_handler_send)input_handler_send, s); + + // have no input packet + s->in_len = -1; + + // init debugging + #ifndef NDEBUG + s->in_error = 0; + #endif + + // init debug object + DebugObject_Init(&s->d_obj); +} + +void DatagramSocketSink_Free (DatagramSocketSink *s) +{ + // free debug object + DebugObject_Free(&s->d_obj); + + // free input + PacketPassInterface_Free(&s->input); + + // remove socket event handler + BSocket_RemoveEventHandler(s->bsock, BSOCKET_WRITE); + + // free dead var + DEAD_KILL(s->dead); +} + +PacketPassInterface * DatagramSocketSink_GetInput (DatagramSocketSink *s) +{ + return &s->input; +} + +void DatagramSocketSink_SetAddresses (DatagramSocketSink *s, BAddr addr, BIPAddr local_addr) +{ + ASSERT(BAddr_IsRecognized(&addr) && !BAddr_IsInvalid(&addr)) + ASSERT(BIPAddr_IsRecognized(&local_addr)) + + s->addr = addr; + s->local_addr = local_addr; +} diff --git a/flow/DatagramSocketSink.h b/flow/DatagramSocketSink.h new file mode 100644 index 000000000..fea360d75 --- /dev/null +++ b/flow/DatagramSocketSink.h @@ -0,0 +1,104 @@ +/** + * @file DatagramSocketSink.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * A {@link PacketPassInterface} sink which sends packets to a datagram socket. + */ + +#ifndef BADVPN_FLOW_DATAGRAMSOCKETSINK_H +#define BADVPN_FLOW_DATAGRAMSOCKETSINK_H + +#include + +#include +#include +#include +#include +#include + +#define DATAGRAMSOCKETSINK_ERROR_BSOCKET 1 +#define DATAGRAMSOCKETSINK_ERROR_WRONGSIZE 2 + +/** + * A {@link PacketPassInterface} sink which sends packets to a datagram socket. + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + FlowErrorReporter rep; + BSocket *bsock; + BAddr addr; + BIPAddr local_addr; + PacketPassInterface input; + int in_len; + uint8_t *in; + #ifndef NDEBUG + int in_error; + #endif +} DatagramSocketSink; + +/** + * Initializes the sink. + * + * @param s the object + * @param rep error reporting data. Error code is an int. Possible error codes: + * - DATAGRAMSOCKETSINK_ERROR_BSOCKET: {@link BSocket_SendToFrom} failed + * with an unhandled error code + * - DATAGRAMSOCKETSINK_ERROR_WRONGSIZE: {@link BSocket_SendToFrom} succeeded, + * but did not send all of the packet + * On error, the object will continue to operate unless it is destroyed from + * the error handler. + * @param bsock datagram socket to write packets to. Registers a BSOCKET_WRITE handler which + * must not be registered. + * @param mtu maximum packet size. Must be >=0. + * @param addr remote address. Must be recognized and valid. Passed to {@link BSocket_SendToFrom}. + * @param local_addr source address. Must be recognized. + * Passed to {@link BSocket_SendToFrom}. + */ +void DatagramSocketSink_Init (DatagramSocketSink *s, FlowErrorReporter rep, BSocket *bsock, int mtu, BAddr addr, BIPAddr local_addr); + +/** + * Frees the sink. + * + * @param s the object + */ +void DatagramSocketSink_Free (DatagramSocketSink *s); + +/** + * Returns the input interface. + * + * @param s the object + * @return input interface + */ +PacketPassInterface * DatagramSocketSink_GetInput (DatagramSocketSink *s); + +/** + * Sets sending addresses. + * + * @param s the object + * @param addr remote address. Must be recognized and valid. Passed to {@link BSocket_SendToFrom}. + * @param local_addr source address. Must be recognized. + * Passed to {@link BSocket_SendToFrom}. + */ +void DatagramSocketSink_SetAddresses (DatagramSocketSink *s, BAddr addr, BIPAddr local_addr); + +#endif diff --git a/flow/DatagramSocketSource.c b/flow/DatagramSocketSource.c new file mode 100644 index 000000000..5a3c0aad9 --- /dev/null +++ b/flow/DatagramSocketSource.c @@ -0,0 +1,178 @@ +/** + * @file DatagramSocketSource.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include + +#include + +static int report_error (DatagramSocketSource *s, int error) +{ + #ifndef NDEBUG + s->in_error = 1; + #endif + + DEAD_ENTER(s->dead) + FlowErrorReporter_ReportError(&s->rep, &error); + if (DEAD_LEAVE(s->dead)) { + return -1; + } + + #ifndef NDEBUG + s->in_error = 0; + #endif + + return 0; +} + +static int output_handler_recv (DatagramSocketSource *s, uint8_t *data, int *data_len) +{ + ASSERT(!s->out_have) + ASSERT(!s->in_error) + + int res; + + while (1) { + res = BSocket_RecvFromTo(s->bsock, data, s->mtu, &s->last_addr, &s->last_local_addr); + if (res < 0) { + int error = BSocket_GetError(s->bsock); + if (error == BSOCKET_ERROR_LATER) { + s->out_have = 1; + s->out = data; + BSocket_EnableEvent(s->bsock, BSOCKET_READ); + return 0; + } + if (report_error(s, DATAGRAMSOCKETSOURCE_ERROR_BSOCKET) < 0) { + return -1; + } + continue; + } + break; + } + + #ifndef NDEBUG + s->have_last_addr = 1; + #endif + + *data_len = res; + return 1; +} + +static void socket_handler (DatagramSocketSource *s, int event) +{ + ASSERT(s->out_have) + ASSERT(event == BSOCKET_READ) + ASSERT(!s->in_error) + + int res; + + while (1) { + res = BSocket_RecvFromTo(s->bsock, s->out, s->mtu, &s->last_addr, &s->last_local_addr); + if (res < 0) { + int error = BSocket_GetError(s->bsock); + if (error == BSOCKET_ERROR_LATER) { + // nothing to receive, continue in socket_handler + return; + } + if (report_error(s, DATAGRAMSOCKETSOURCE_ERROR_BSOCKET) < 0) { + return; + } + continue; + } + break; + } + + BSocket_DisableEvent(s->bsock, BSOCKET_READ); + s->out_have = 0; + + #ifndef NDEBUG + s->have_last_addr = 1; + #endif + + PacketRecvInterface_Done(&s->output, res); + return; +} + +void DatagramSocketSource_Init (DatagramSocketSource *s, FlowErrorReporter rep, BSocket *bsock, int mtu) +{ + ASSERT(mtu >= 0) + + // init arguments + s->rep = rep; + s->bsock = bsock; + s->mtu = mtu; + + // init dead var + DEAD_INIT(s->dead); + + // add socket event handler + BSocket_AddEventHandler(s->bsock, BSOCKET_READ, (BSocket_handler)socket_handler, s); + + // init output + PacketRecvInterface_Init(&s->output, mtu, (PacketRecvInterface_handler_recv)output_handler_recv, s); + + // have no output packet + s->out_have = 0; + + // init debugging + #ifndef NDEBUG + s->have_last_addr = 0; + s->in_error = 0; + #endif + + // init debug object + DebugObject_Init(&s->d_obj); +} + +void DatagramSocketSource_Free (DatagramSocketSource *s) +{ + // free debug object + DebugObject_Free(&s->d_obj); + + // free output + PacketRecvInterface_Free(&s->output); + + // remove socket event handler + BSocket_RemoveEventHandler(s->bsock, BSOCKET_READ); + + // free dead var + DEAD_KILL(s->dead); +} + +PacketRecvInterface * DatagramSocketSource_GetOutput (DatagramSocketSource *s) +{ + return &s->output; +} + +void DatagramSocketSource_GetLastAddresses (DatagramSocketSource *s, BAddr *addr, BIPAddr *local_addr) +{ + ASSERT(s->have_last_addr) + + if (addr) { + *addr = s->last_addr; + } + + if (local_addr) { + *local_addr = s->last_local_addr; + } +} diff --git a/flow/DatagramSocketSource.h b/flow/DatagramSocketSource.h new file mode 100644 index 000000000..c127dc6dc --- /dev/null +++ b/flow/DatagramSocketSource.h @@ -0,0 +1,100 @@ +/** + * @file DatagramSocketSource.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * A {@link PacketRecvInterface} source which receives packets from a datagram socket. + */ + +#ifndef BADVPN_FLOW_DATAGRAMSOCKETSOURCE_H +#define BADVPN_FLOW_DATAGRAMSOCKETSOURCE_H + +#include +#include +#include +#include +#include + +#define DATAGRAMSOCKETSOURCE_ERROR_BSOCKET 1 + +/** + * A {@link PacketRecvInterface} source which receives packets from a datagram socket. + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + FlowErrorReporter rep; + BSocket *bsock; + int mtu; + PacketRecvInterface output; + int out_have; + uint8_t *out; + BAddr last_addr; + BIPAddr last_local_addr; + #ifndef NDEBUG + int have_last_addr; + int in_error; + #endif +} DatagramSocketSource; + +/** + * Initializes the object. + * + * @param s the object + * @param rep error reporting data. Error code is an int. Possible error codes: + * - DATAGRAMSOCKETSOURCE_ERROR_BSOCKET: {@link BSocket_RecvFromTo} failed + * with an unhandled error code + * On error, the object will continue to operate unless it is destroyed from + * the error handler. + * @param bsock datagram socket to read data from. The BSOCKET_READ event must be disabled. + * * Takes over reading on the socket. + * @param mtu maximum packet size. Must be >=0. + */ +void DatagramSocketSource_Init (DatagramSocketSource *s, FlowErrorReporter rep, BSocket *bsock, int mtu); + +/** + * Frees the object. + * + * @param s the object + */ +void DatagramSocketSource_Free (DatagramSocketSource *s); + +/** + * Returns the output interface. + * + * @param s the object + * @return output interface + */ +PacketRecvInterface * DatagramSocketSource_GetOutput (DatagramSocketSource *s); + +/** + * Returns the remote and local address of the last received packet. + * At least one packet must have been received. + * + * @param s the object + * @param addr where to put the remote address, if not NULL. The returned address + * will be valid. + * @param local_addr where to put the local address, if not NULL. The returned + * address may be an invalid address. + */ +void DatagramSocketSource_GetLastAddresses (DatagramSocketSource *s, BAddr *addr, BIPAddr *local_addr); + +#endif diff --git a/flow/FragmentProtoAssembler.c b/flow/FragmentProtoAssembler.c new file mode 100644 index 000000000..e5d4a189c --- /dev/null +++ b/flow/FragmentProtoAssembler.c @@ -0,0 +1,522 @@ +/** + * @file FragmentProtoAssembler.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#define FPA_MAX_TIME UINT32_MAX + +static int frame_id_comparator (void *unused, fragmentproto_frameid *v1, fragmentproto_frameid *v2) +{ + if (*v1 < *v2) { + return -1; + } + if (*v1 > *v2) { + return 1; + } + return 0; +} + +static void free_frame (FragmentProtoAssembler *o, struct FragmentProtoAssembler_frame *frame) +{ + // remove from used list + LinkedList2_Remove(&o->frames_used, &frame->list_node); + // remove from used tree + BAVL_Remove(&o->frames_used_tree, &frame->tree_node); + + // append to free list + LinkedList2_Append(&o->frames_free, &frame->list_node); +} + +static void free_oldest_frame (FragmentProtoAssembler *o) +{ + ASSERT(!LinkedList2_IsEmpty(&o->frames_used)) + + // obtain oldest frame (first on the list) + LinkedList2Node *list_node = LinkedList2_GetFirst(&o->frames_used); + ASSERT(list_node) + struct FragmentProtoAssembler_frame *frame = UPPER_OBJECT(list_node, struct FragmentProtoAssembler_frame, list_node); + + // free frame + free_frame(o, frame); +} + +static struct FragmentProtoAssembler_frame * allocate_new_frame (FragmentProtoAssembler *o, fragmentproto_frameid id) +{ + ASSERT(!BAVL_LookupExact(&o->frames_used_tree, &id)) + + // if there are no free entries, free the oldest used one + if (LinkedList2_IsEmpty(&o->frames_free)) { + BLog(BLOG_INFO, "freeing used frame"); + free_oldest_frame(o); + } + + // obtain frame entry + LinkedList2Node *list_node = LinkedList2_GetFirst(&o->frames_free); + ASSERT(list_node) + struct FragmentProtoAssembler_frame *frame = UPPER_OBJECT(list_node, struct FragmentProtoAssembler_frame, list_node); + + // remove from free list + LinkedList2_Remove(&o->frames_free, &frame->list_node); + + // initialize values + frame->id = id; + frame->time = o->time; + frame->num_chunks = 0; + frame->sum = 0; + frame->length = -1; + frame->length_so_far = 0; + + // append to used list + LinkedList2_Append(&o->frames_used, &frame->list_node); + // insert to used tree + ASSERT_EXECUTE(BAVL_Insert(&o->frames_used_tree, &frame->tree_node, NULL)) + + return frame; +} + +static int chunks_overlap (int c1_start, int c1_len, int c2_start, int c2_len) +{ + return (c1_start + c1_len > c2_start && c2_start + c2_len > c1_start); +} + +static int frame_is_timed_out (FragmentProtoAssembler *o, struct FragmentProtoAssembler_frame *frame) +{ + ASSERT(frame->time <= o->time) + + return (o->time - frame->time > (uint32_t)o->time_tolerance); +} + +static void reduce_times (FragmentProtoAssembler *o) +{ + // find the frame with minimal time, removing timed out frames + struct FragmentProtoAssembler_frame *minframe = NULL; + LinkedList2Iterator it; + LinkedList2Iterator_InitForward(&it, &o->frames_used); + LinkedList2Node *list_node; + while (list_node = LinkedList2Iterator_Next(&it)) { + struct FragmentProtoAssembler_frame *frame = UPPER_OBJECT(list_node, struct FragmentProtoAssembler_frame, list_node); + if (frame_is_timed_out(o, frame)) { + BLog(BLOG_INFO, "freeing timed out frame (while reducing times)"); + free_frame(o, frame); + } else { + if (!minframe || frame->time < minframe->time) { + minframe = frame; + } + } + } + + if (!minframe) { + // have no frames, set packet time to zero + o->time = 0; + return; + } + + uint32_t min_time = minframe->time; + + // subtract minimal time from all frames + LinkedList2Iterator_InitForward(&it, &o->frames_used); + while (list_node = LinkedList2Iterator_Next(&it)) { + struct FragmentProtoAssembler_frame *frame = UPPER_OBJECT(list_node, struct FragmentProtoAssembler_frame, list_node); + frame->time -= min_time; + } + + // subtract minimal time from packet time + o->time -= min_time; +} + +static int process_chunk (FragmentProtoAssembler *o, fragmentproto_frameid frame_id, int chunk_start, int chunk_len, int is_last, uint8_t *payload) +{ + ASSERT(!o->output_blocking) + ASSERT(chunk_start >= 0) + ASSERT(chunk_len >= 0) + ASSERT(is_last == 0 || is_last == 1) + + // verify chunk + + // check start + if (chunk_start > o->output_mtu) { + BLog(BLOG_INFO, "chunk starts outside"); + return 0; + } + + // check frame size bound + if (chunk_len > o->output_mtu - chunk_start) { + BLog(BLOG_INFO, "chunk ends outside"); + return 0; + } + + // calculate end + int chunk_end = chunk_start + chunk_len; + + // lookup frame + struct FragmentProtoAssembler_frame *frame; + BAVLNode *tree_node; + if (!(tree_node = BAVL_LookupExact(&o->frames_used_tree, &frame_id))) { + // frame not found, add a new one + frame = allocate_new_frame(o, frame_id); + } else { + // have existing frame with that ID + frame = UPPER_OBJECT(tree_node, struct FragmentProtoAssembler_frame, tree_node); + // check frame time + if (frame_is_timed_out(o, frame)) { + // frame is timed out, remove it and use a new one + BLog(BLOG_INFO, "freeing timed out frame (while processing chunk)"); + free_frame(o, frame); + frame = allocate_new_frame(o, frame_id); + } + } + + ASSERT(frame->num_chunks < o->num_chunks) + + // check if the chunk overlaps with any existing chunks + for (int i = 0; i < frame->num_chunks; i++) { + struct FragmentProtoAssembler_chunk *chunk = &frame->chunks[i]; + if (chunks_overlap(chunk->start, chunk->len, chunk_start, chunk_len)) { + BLog(BLOG_INFO, "chunk overlaps with existing chunk"); + goto fail_frame; + } + } + + if (is_last) { + // this chunk is marked as last + if (frame->length >= 0) { + BLog(BLOG_INFO, "got last chunk, but already have one"); + goto fail_frame; + } + // check if frame size according to this packet is consistent + // with existing chunks + if (frame->length_so_far > chunk_end) { + BLog(BLOG_INFO, "got last chunk, but already have data over its bound"); + goto fail_frame; + } + } else { + // if we have length, chunk must be in its bound + if (frame->length >= 0) { + if (chunk_end > frame->length) { + BLog(BLOG_INFO, "chunk out of length bound"); + goto fail_frame; + } + } + } + + // chunk is good, add it + + // update frame time + frame->time = o->time; + + // add chunk entry + struct FragmentProtoAssembler_chunk *chunk = &frame->chunks[frame->num_chunks]; + chunk->start = chunk_start; + chunk->len = chunk_len; + frame->num_chunks++; + + // update sum + frame->sum += chunk_len; + + // update length + if (is_last) { + frame->length = chunk_end; + } else { + if (frame->length < 0) { + if (frame->length_so_far < chunk_end) { + frame->length_so_far = chunk_end; + } + } + } + + // copy chunk payload to buffer + memcpy(frame->buffer + chunk_start, payload, chunk_len); + + // is frame incomplete? + if (frame->length < 0 || frame->sum < frame->length) { + // if all chunks are used, fail it + if (frame->num_chunks == o->num_chunks) { + BLog(BLOG_INFO, "all chunks used, but frame not complete"); + goto fail_frame; + } + return 0; + } + + ASSERT(frame->sum == frame->length) + + BLog(BLOG_DEBUG, "frame complete"); + + // free frame entry + free_frame(o, frame); + + // submit frame to output + // this is fine even though the frame entry was freed + DEAD_ENTER(o->dead) + int res = PacketPassInterface_Sender_Send(o->output, frame->buffer, frame->length); + if (DEAD_LEAVE(o->dead)) { + return -1; + } + + ASSERT(res == 0 || res == 1) + + // if output blocked, don't accept any new input until it's done + if (!res) { + o->output_blocking = 1; + return 0; + } + + return 0; + +fail_frame: + free_frame(o, frame); + return 0; +} + +static int process_input (FragmentProtoAssembler *o) +{ + ASSERT(o->in_len >= 0) + ASSERT(!o->output_blocking) + + // read chunks + while (o->in_pos < o->in_len) { + // obtain header + if (o->in_len - o->in_pos < sizeof(struct fragmentproto_chunk_header)) { + BLog(BLOG_INFO, "too little data for chunk header"); + break; + } + struct fragmentproto_chunk_header *header = (struct fragmentproto_chunk_header *)(o->in + o->in_pos); + o->in_pos += sizeof(struct fragmentproto_chunk_header); + fragmentproto_frameid frame_id = ltoh16(header->frame_id); + int chunk_start = ltoh16(header->chunk_start); + int chunk_len = ltoh16(header->chunk_len); + + // check is_last field + if (!(header->is_last == 0 || header->is_last == 1)) { + BLog(BLOG_INFO, "chunk is_last wrong"); + break; + } + + // obtain data + if (o->in_len - o->in_pos < chunk_len) { + BLog(BLOG_INFO, "too little data for chunk data"); + break; + } + + // process chunk + if (process_chunk(o, frame_id, chunk_start, chunk_len, header->is_last, o->in + o->in_pos) < 0) { + return -1; + } + o->in_pos += chunk_len; + + // if output is blocking, stop processing input + if (o->output_blocking) { + return 0; + } + } + + // all input processed + o->in_len = -1; + + // increment packet time + if (o->time == FPA_MAX_TIME) { + reduce_times(o); + if (!LinkedList2_IsEmpty(&o->frames_used)) { + ASSERT(o->time < FPA_MAX_TIME) // If there was a frame with zero time, it was removed because + // time_tolerance < FPA_MAX_TIME. So something >0 was subtracted. + o->time++; + } else { + // it was set to zero by reduce_times + ASSERT(o->time == 0) + } + } else { + o->time++; + } + + return 0; +} + +static int input_handler_send (FragmentProtoAssembler *o, uint8_t *data, int data_len) +{ + ASSERT(o->in_len == -1) + ASSERT(!o->output_blocking) + ASSERT(data_len >= 0) + ASSERT(data_len <= o->input_mtu) + + // save input packet + o->in_len = data_len; + o->in = data; + o->in_pos = 0; + + // process input + if (process_input(o) < 0) { + return -1; + } + + ASSERT((o->in_len >= 0) == o->output_blocking) + + // if not all input was processed (output is blocking), block input + if (o->in_len >= 0) { + return 0; + } + + // all input was processed + return 1; +} + +static void output_handler_done (FragmentProtoAssembler *o) +{ + ASSERT(o->in_len >= 0) + ASSERT(o->output_blocking) + + // output no longer blocking + o->output_blocking = 0; + + // process any further input + if (process_input(o) < 0) { + return; + } + + ASSERT((o->in_len >= 0) == o->output_blocking) + + // if output is again blocking, keep input blocked + if (o->in_len >= 0) { + return; + } + + // tell input we're done + PacketPassInterface_Done(&o->input); + return; +} + +int FragmentProtoAssembler_Init (FragmentProtoAssembler *o, int input_mtu, PacketPassInterface *output, int num_frames, int num_chunks) +{ + ASSERT(input_mtu >= 0) + ASSERT(num_frames > 0) + ASSERT(num_frames < FPA_MAX_TIME) // needed so we can always subtract times when packet time is maximum + ASSERT(num_chunks > 0) + + // init arguments + o->input_mtu = input_mtu; + o->output = output; + o->num_frames = num_frames; + o->num_chunks = num_chunks; + + // init dead var + DEAD_INIT(o->dead); + + // init input + PacketPassInterface_Init(&o->input, o->input_mtu, (PacketPassInterface_handler_send)input_handler_send, o); + + // init output + PacketPassInterface_Sender_Init(o->output, (PacketPassInterface_handler_done)output_handler_done, o); + + // remebmer output MTU + o->output_mtu = PacketPassInterface_GetMTU(o->output); + + // set packet time to zero + o->time = 0; + + // set time tolerance to num_frames + o->time_tolerance = o->num_frames; + + // allocate frames + if (!(o->frames_entries = malloc(o->num_frames * sizeof(struct FragmentProtoAssembler_frame)))) { + goto fail1; + } + + // allocate chunks + if (!(o->frames_chunks = malloc(o->num_frames * o->num_chunks * sizeof(struct FragmentProtoAssembler_chunk)))) { + goto fail2; + } + + // allocate buffers + if (!(o->frames_buffer = malloc(o->num_frames * o->output_mtu))) { + goto fail3; + } + + // init frame lists + LinkedList2_Init(&o->frames_free); + LinkedList2_Init(&o->frames_used); + + // initialize frame entries + for (int i = 0; i < num_frames; i++) { + struct FragmentProtoAssembler_frame *frame = &o->frames_entries[i]; + // set chunks array pointer + frame->chunks = o->frames_chunks + i * o->num_chunks; + // set buffer pointer + frame->buffer = o->frames_buffer + i * o->output_mtu; + // add to free list + LinkedList2_Append(&o->frames_free, &frame->list_node); + } + + // init tree + BAVL_Init(&o->frames_used_tree, OFFSET_DIFF(struct FragmentProtoAssembler_frame, id, tree_node), (BAVL_comparator)frame_id_comparator, NULL); + + // have no input packet + o->in_len = -1; + + // output not blocking + o->output_blocking = 0; + + // init debug object + DebugObject_Init(&o->d_obj); + + return 1; + +fail3: + free(o->frames_chunks); +fail2: + free(o->frames_entries); +fail1: + PacketPassInterface_Free(&o->input); + return 0; +} + +void FragmentProtoAssembler_Free (FragmentProtoAssembler *o) +{ + // free debug object + DebugObject_Free(&o->d_obj); + + // free buffers + free(o->frames_buffer); + + // free chunks + free(o->frames_chunks); + + // free frames + free(o->frames_entries); + + // free input + PacketPassInterface_Free(&o->input); + + // free dead var + DEAD_KILL(o->dead); +} + +PacketPassInterface * FragmentProtoAssembler_GetInput (FragmentProtoAssembler *o) +{ + return &o->input; +} diff --git a/flow/FragmentProtoAssembler.h b/flow/FragmentProtoAssembler.h new file mode 100644 index 000000000..45c1cf6c7 --- /dev/null +++ b/flow/FragmentProtoAssembler.h @@ -0,0 +1,116 @@ +/** + * @file FragmentProtoAssembler.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Object which decodes packets according to FragmentProto. + */ + +#ifndef BADVPN_FLOW_FRAGMENTPROTOASSEMBLER_H +#define BADVPN_FLOW_FRAGMENTPROTOASSEMBLER_H + +#include + +#include +#include +#include +#include +#include +#include +#include + +struct FragmentProtoAssembler_chunk { + int start; + int len; +}; + +struct FragmentProtoAssembler_frame { + LinkedList2Node list_node; // node in free or used list + struct FragmentProtoAssembler_chunk *chunks; // array of chunks, up to num_chunks + uint8_t *buffer; // buffer with frame data, size output_mtu + // everything below only defined when frame entry is used + fragmentproto_frameid id; // frame identifier + uint32_t time; // packet time when the last chunk was received + BAVLNode tree_node; // node in tree for searching frames by id + int num_chunks; // number of valid chunks + int sum; // sum of all chunks' lengths + int length; // length of the frame, or -1 if not yet known + int length_so_far; // if length=-1, current data set's upper bound +}; + +/** + * Object which decodes packets according to FragmentProto. + * + * Input is with {@link PacketPassInterface}. + * Output is with {@link PacketPassInterface}. + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + PacketPassInterface input; + int input_mtu; + PacketPassInterface *output; + int output_mtu; + int num_frames; + int num_chunks; + uint32_t time; + int time_tolerance; + struct FragmentProtoAssembler_frame *frames_entries; + struct FragmentProtoAssembler_chunk *frames_chunks; + uint8_t *frames_buffer; + LinkedList2 frames_free; + LinkedList2 frames_used; + BAVL frames_used_tree; + int in_len; + uint8_t *in; + int in_pos; + int output_blocking; +} FragmentProtoAssembler; + +/** + * Initializes the object. + * {@link BLog_Init} must have been done. + * + * @param o the object + * @param input_mtu maximum input packet size. Must be >=0. + * @param output output interface + * @param num_frames number of frames we can hold. Must be >0 and < UINT32_MAX. + * @param num_chunks maximum number of chunks a frame can come in. Must be >0. + * @return 1 on success, 0 on failure + */ +int FragmentProtoAssembler_Init (FragmentProtoAssembler *o, int input_mtu, PacketPassInterface *output, int num_frames, int num_chunks) WARN_UNUSED; + +/** + * Frees the object. + * + * @param o the object + */ +void FragmentProtoAssembler_Free (FragmentProtoAssembler *o); + +/** + * Returns the input interface. + * + * @param o the object + * @return input interface + */ +PacketPassInterface * FragmentProtoAssembler_GetInput (FragmentProtoAssembler *o); + +#endif diff --git a/flow/FragmentProtoDisassembler.c b/flow/FragmentProtoDisassembler.c new file mode 100644 index 000000000..e57cc9436 --- /dev/null +++ b/flow/FragmentProtoDisassembler.c @@ -0,0 +1,290 @@ +/** + * @file FragmentProtoDisassembler.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include + +#include +#include + +#include + +static void write_chunks (FragmentProtoDisassembler *o) +{ + ASSERT(o->in_len >= 0) + ASSERT(o->out) + ASSERT(o->output_mtu - o->out_used >= sizeof(struct fragmentproto_chunk_header)) + + int in_avail = o->in_len - o->in_used; + int out_avail = (o->output_mtu - o->out_used) - sizeof(struct fragmentproto_chunk_header); + + // write chunks to output packet + do { + ASSERT(in_avail >= 0) + ASSERT(!(in_avail == 0) || out_avail >= 0) + + // check if we have space in the output packet + // (if this is a zero input packet, only one chunk is written, which + // is always possible) + if (in_avail > 0 && out_avail <= 0) { + break; + } + + // calculate chunk length + int chunk_len = in_avail; + if (chunk_len > out_avail) { + chunk_len = out_avail; + } + if (o->chunk_mtu > 0) { + if (chunk_len > o->chunk_mtu) { + chunk_len = o->chunk_mtu; + } + } + + // write chunk header + struct fragmentproto_chunk_header *header = (struct fragmentproto_chunk_header *)(o->out + o->out_used); + header->frame_id = htol16(o->frame_id); + header->chunk_start = htol16(o->in_used); + header->chunk_len = htol16(chunk_len); + header->is_last = (chunk_len == in_avail); + + // write chunk data + memcpy(o->out + o->out_used + sizeof(struct fragmentproto_chunk_header), o->in + o->in_used, chunk_len); + + // increment pointers + o->in_used += chunk_len; + o->out_used += sizeof(struct fragmentproto_chunk_header) + chunk_len; + + in_avail = o->in_len - o->in_used; + out_avail = (o->output_mtu - o->out_used) - sizeof(struct fragmentproto_chunk_header); + } while (in_avail > 0); + + // have we finished the input packet? + if (in_avail == 0) { + o->in_len = -1; + o->frame_id++; + } + + // should we finish the output packet? + if ( + out_avail < 0 || + (in_avail > 0 && out_avail <= 0) || + o->latency < 0 + ) { + // finish output packet + o->out = NULL; + // stop timer (if it's running) + if (o->latency >= 0) { + BReactor_RemoveTimer(o->reactor, &o->timer); + } + } else { + // start timer if we have output and it's not running (output was empty before) + if (!BTimer_IsRunning(&o->timer)) { + BReactor_SetTimer(o->reactor, &o->timer); + } + } + + ASSERT(o->in_len < 0 || !o->out) +} + +static int input_handler_send (FragmentProtoDisassembler *o, uint8_t *data, int data_len) +{ + ASSERT(o->in_len == -1) + ASSERT(!o->doing_send) + ASSERT(data_len >= 0) + ASSERT(data_len <= o->input_mtu) + + // set input packet + o->in_len = data_len; + o->in = data; + o->in_used = 0; + + // if there is no output, block input + if (!o->out) { + return 0; + } + + // write input to output + write_chunks(o); + + // if we finished the output packet and are not in recv, notify output + if (!o->out && !o->doing_recv) { + o->doing_send = 1; + DEAD_ENTER(o->dead) + PacketRecvInterface_Done(&o->output, o->out_used); + if (DEAD_LEAVE(o->dead)) { + return -1; + } + o->doing_send = 0; + } + + // if we still have some input, block input + if (o->in_len >= 0) { + return 0; + } + + // all input was processed, accept packet + return 1; +} + +static void input_handler_cancel (FragmentProtoDisassembler *o) +{ + ASSERT(o->in_len >= 0) + ASSERT(!o->out) + ASSERT(!o->doing_send) + + o->in_len = -1; +} + +static int output_handler_recv (FragmentProtoDisassembler *o, uint8_t *data, int *data_len) +{ + ASSERT(!o->out) + ASSERT(!o->doing_recv) + ASSERT(data) + + // set output packet + o->out = data; + o->out_used = 0; + + // if there is no input, block output + if (o->in_len < 0) { + return 0; + } + + // write input to output + write_chunks(o); + + // if we finished the input packet and are not in send, notify input + if (o->in_len < 0 && !o->doing_send) { + o->doing_recv = 1; + DEAD_ENTER(o->dead) + PacketPassInterface_Done(&o->input); + if (DEAD_LEAVE(o->dead)) { + return -1; + } + o->doing_recv = 0; + } + + // if we are not going to finish the output packet now, block output + if (o->out) { + return 0; + } + + // return packet now + *data_len = o->out_used; + return 1; +} + +static void timer_handler (FragmentProtoDisassembler *o) +{ + ASSERT(o->latency >= 0) + ASSERT(o->out) + ASSERT(o->in_len = -1) + ASSERT(!o->doing_send) + ASSERT(!o->doing_recv) + + // finish output packet + o->out = NULL; + + // inform output + PacketRecvInterface_Done(&o->output, o->out_used); + return; +} + +void FragmentProtoDisassembler_Init (FragmentProtoDisassembler *o, BReactor *reactor, int input_mtu, int output_mtu, int chunk_mtu, btime_t latency) +{ + ASSERT(input_mtu >= 0) + ASSERT(input_mtu <= UINT16_MAX) + ASSERT(output_mtu > sizeof(struct fragmentproto_chunk_header)) + ASSERT(chunk_mtu > 0 || chunk_mtu < 0) + + // init arguments + o->reactor = reactor; + o->input_mtu = input_mtu; + o->output_mtu = output_mtu; + o->chunk_mtu = chunk_mtu; + o->latency = latency; + + // init dead var + DEAD_INIT(o->dead); + + // init input + PacketPassInterface_Init(&o->input, o->input_mtu, (PacketPassInterface_handler_send)input_handler_send, o); + PacketPassInterface_EnableCancel(&o->input, (PacketPassInterface_handler_cancel)input_handler_cancel); + + // init output + PacketRecvInterface_Init(&o->output, o->output_mtu, (PacketRecvInterface_handler_recv)output_handler_recv, o); + + // init timer + if (o->latency >= 0) { + BTimer_Init(&o->timer, o->latency, (BTimer_handler)timer_handler, o); + } + + // have no input packet + o->in_len = -1; + + // have no output packet + o->out = NULL; + + // start with zero frame ID + o->frame_id = 0; + + // not callback from send + o->doing_send = 0; + + // not callback from recv + o->doing_recv = 0; + + // init debug object + DebugObject_Init(&o->d_obj); +} + +void FragmentProtoDisassembler_Free (FragmentProtoDisassembler *o) +{ + // free debug object + DebugObject_Free(&o->d_obj); + + // free timer + if (o->latency >= 0) { + BReactor_RemoveTimer(o->reactor, &o->timer); + } + + // free output + PacketRecvInterface_Free(&o->output); + + // free input + PacketPassInterface_Free(&o->input); + + // free dead var + DEAD_KILL(o->dead); +} + +PacketPassInterface * FragmentProtoDisassembler_GetInput (FragmentProtoDisassembler *o) +{ + return &o->input; +} + +PacketRecvInterface * FragmentProtoDisassembler_GetOutput (FragmentProtoDisassembler *o) +{ + return &o->output; +} diff --git a/flow/FragmentProtoDisassembler.h b/flow/FragmentProtoDisassembler.h new file mode 100644 index 000000000..e2b25ec83 --- /dev/null +++ b/flow/FragmentProtoDisassembler.h @@ -0,0 +1,107 @@ +/** + * @file FragmentProtoDisassembler.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Object which encodes packets into packets composed of chunks + * according to FragmentProto. + */ + +#ifndef BADVPN_FLOW_CCPROTODISASSEMBLER_H +#define BADVPN_FLOW_CCPROTODISASSEMBLER_H + +#include + +#include +#include +#include +#include +#include +#include +#include + +/** + * Object which encodes packets into packets composed of chunks + * according to FragmentProto. + * + * Input is with {@link PacketPassInterface}. + * Output is with {@link PacketRecvInterface}. + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + BReactor *reactor; + int input_mtu; + int output_mtu; + int chunk_mtu; + btime_t latency; + PacketPassInterface input; + PacketRecvInterface output; + BTimer timer; + int in_len; + uint8_t *in; + int in_used; + uint8_t *out; + int out_used; + fragmentproto_frameid frame_id; + int doing_send; + int doing_recv; +} FragmentProtoDisassembler; + +/** + * Initializes the object. + * + * @param o the object + * @param reactor reactor we live in + * @param input_mtu maximum input packet size. Must be >=0 and <2^16 + * @param output_mtu maximum output packet size. Must be >sizeof(struct fragmentproto_chunk_header). + * @param chunk_mtu maximum chunk size. Must be >0, or <0 for no explicit limit. + * @param latency maximum time a pending output packet with some data can wait for more data + * before being sent out. If nonnegative, a timer will be used. If negative, + * packets will always be sent out immediately. If low latency is desired, + * prefer setting this to zero rather than negative. + */ +void FragmentProtoDisassembler_Init (FragmentProtoDisassembler *o, BReactor *reactor, int input_mtu, int output_mtu, int chunk_mtu, btime_t latency); + +/** + * Frees the object. + * + * @param o the object + */ +void FragmentProtoDisassembler_Free (FragmentProtoDisassembler *o); + +/** + * Returns the input interface. + * + * @param o the object + * @return input interface + */ +PacketPassInterface * FragmentProtoDisassembler_GetInput (FragmentProtoDisassembler *o); + +/** + * Returns the output interface. + * + * @param o the object + * @return output interface + */ +PacketRecvInterface * FragmentProtoDisassembler_GetOutput (FragmentProtoDisassembler *o); + +#endif diff --git a/flow/KeepaliveIO.c b/flow/KeepaliveIO.c new file mode 100644 index 000000000..5a683e906 --- /dev/null +++ b/flow/KeepaliveIO.c @@ -0,0 +1,110 @@ +/** + * @file KeepaliveIO.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include + +static void keepalive_handler (KeepaliveIO *o) +{ + PacketRecvBlocker_AllowBlockedPacket(&o->ka_blocker); + return; +} + +int KeepaliveIO_Init (KeepaliveIO *o, BReactor *reactor, PacketPassInterface *output, PacketRecvInterface *keepalive_input, btime_t keepalive_interval_ms) +{ + ASSERT(PacketRecvInterface_GetMTU(keepalive_input) <= PacketPassInterface_GetMTU(output)) + ASSERT(keepalive_interval_ms > 0) + + // set arguments + o->reactor = reactor; + + // init dead var + DEAD_INIT(o->dead); + + // init keep-alive sender + PacketPassInactivityMonitor_Init(&o->kasender, output, o->reactor, keepalive_interval_ms, (PacketPassInactivityMonitor_handler)keepalive_handler, o); + + // init queue + PacketPassPriorityQueue_Init(&o->queue, PacketPassInactivityMonitor_GetInput(&o->kasender), BReactor_PendingGroup(o->reactor)); + + // init keepalive flow + PacketPassPriorityQueueFlow_Init(&o->ka_qflow, &o->queue, -1); + + // init keepalive blocker + PacketRecvBlocker_Init(&o->ka_blocker, keepalive_input); + + // init keepalive buffer + if (!SinglePacketBuffer_Init(&o->ka_buffer, PacketRecvBlocker_GetOutput(&o->ka_blocker), PacketPassPriorityQueueFlow_GetInput(&o->ka_qflow), BReactor_PendingGroup(o->reactor))) { + goto fail1; + } + + // init user flow + PacketPassPriorityQueueFlow_Init(&o->user_qflow, &o->queue, 0); + + // init debug object + DebugObject_Init(&o->d_obj); + + return 1; + +fail1: + PacketRecvBlocker_Free(&o->ka_blocker); + PacketPassPriorityQueueFlow_Free(&o->ka_qflow); + PacketPassPriorityQueue_Free(&o->queue); + PacketPassInactivityMonitor_Free(&o->kasender); + return 0; +} + +void KeepaliveIO_Free (KeepaliveIO *o) +{ + // free debug object + DebugObject_Free(&o->d_obj); + + // allow freeing queue flows + PacketPassPriorityQueue_PrepareFree(&o->queue); + + // free user flow + PacketPassPriorityQueueFlow_Free(&o->user_qflow); + + // free keepalive buffer + SinglePacketBuffer_Free(&o->ka_buffer); + + // free keepalive blocker + PacketRecvBlocker_Free(&o->ka_blocker); + + // free keepalive flow + PacketPassPriorityQueueFlow_Free(&o->ka_qflow); + + // free queue + PacketPassPriorityQueue_Free(&o->queue); + + // free keep-alive sender + PacketPassInactivityMonitor_Free(&o->kasender); + + // free dead var + DEAD_KILL(o->dead); +} + +PacketPassInterface * KeepaliveIO_GetInput (KeepaliveIO *o) +{ + return PacketPassPriorityQueueFlow_GetInput(&o->user_qflow); +} diff --git a/flow/KeepaliveIO.h b/flow/KeepaliveIO.h new file mode 100644 index 000000000..65dac1a1c --- /dev/null +++ b/flow/KeepaliveIO.h @@ -0,0 +1,83 @@ +/** + * @file KeepaliveIO.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * A {@link PacketPassInterface} layer for sending keep-alive packets. + */ + +#ifndef BADVPN_FLOW_KEEPALIVEIO +#define BADVPN_FLOW_KEEPALIVEIO + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * A {@link PacketPassInterface} layer for sending keep-alive packets. + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + BReactor *reactor; + PacketPassInactivityMonitor kasender; + PacketPassPriorityQueue queue; + PacketPassPriorityQueueFlow user_qflow; + PacketPassPriorityQueueFlow ka_qflow; + SinglePacketBuffer ka_buffer; + PacketRecvBlocker ka_blocker; +} KeepaliveIO; + +/** + * Initializes the object. + * + * @param o the object + * @param reactor reactor we live in + * @param output output interface + * @param keepalive_input keepalive input interface. Its MTU must be <= MTU of output. + * @param keepalive_interval_ms keepalive interval in milliseconds. Must be >0. + * @return 1 on success, 0 on failure + */ +int KeepaliveIO_Init (KeepaliveIO *o, BReactor *reactor, PacketPassInterface *output, PacketRecvInterface *keepalive_input, btime_t keepalive_interval_ms) WARN_UNUSED; + +/** + * Frees the object. + * + * @param o the object + */ +void KeepaliveIO_Free (KeepaliveIO *o); + +/** + * Returns the input interface. + * + * @param o the object + * @return input interface + */ +PacketPassInterface * KeepaliveIO_GetInput (KeepaliveIO *o); + +#endif diff --git a/flow/PacketBuffer.c b/flow/PacketBuffer.c new file mode 100644 index 000000000..8248e1ac7 --- /dev/null +++ b/flow/PacketBuffer.c @@ -0,0 +1,248 @@ +/** + * @file PacketBuffer.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include + +#include + +static int call_recv (PacketBuffer *buf, uint8_t *data, int *len); +static int call_send (PacketBuffer *buf, uint8_t *data, int len); +static int try_recv (PacketBuffer *buf); +static int try_send (PacketBuffer *buf); +static void input_handler_done (PacketBuffer *buf, int in_len); +static void output_handler_done (PacketBuffer *buf); +static void job_handler (PacketBuffer *buf); + +int call_recv (PacketBuffer *buf, uint8_t *data, int *len) +{ + ASSERT(!PacketRecvInterface_InClient(buf->input)) + + DEAD_ENTER(buf->dead) + int res = PacketRecvInterface_Receiver_Recv(buf->input, data, len); + if (DEAD_LEAVE(buf->dead)) { + return -1; + } + + ASSERT(res == 0 || res == 1) + if (res) { + ASSERT(*len >= 0) + ASSERT(*len <= buf->input_mtu) + } + + return res; +} + +int call_send (PacketBuffer *buf, uint8_t *data, int len) +{ + ASSERT(len >= 0) + ASSERT(len <= buf->input_mtu) + ASSERT(!PacketPassInterface_InClient(buf->output)) + + DEAD_ENTER(buf->dead) + int res = PacketPassInterface_Sender_Send(buf->output, data, len); + if (DEAD_LEAVE(buf->dead)) { + return -1; + } + + ASSERT(res == 0 || res == 1) + + return res; +} + +int try_recv (PacketBuffer *buf) +{ + ASSERT(buf->buf.input_avail >= buf->input_mtu) + ASSERT(!PacketRecvInterface_InClient(buf->input)) + ASSERT(!PacketPassInterface_InClient(buf->output)) + + do { + // receive packet + int in_len; + int res; + if ((res = call_recv(buf, buf->buf.input_dest, &in_len)) < 0) { + return -1; + } + + if (!res) { + // input busy, continue in input_handler_done + return 0; + } + + // remember if buffer is empty + int was_empty = (buf->buf.output_avail < 0); + + // submit packet to buffer + ChunkBuffer2_SubmitPacket(&buf->buf, in_len); + + // if buffer was empty, start sending + if (was_empty) { + if (try_send(buf) < 0) { + return -1; + } + } + } while (buf->buf.input_avail >= buf->input_mtu); + + return 0; +} + +int try_send (PacketBuffer *buf) +{ + ASSERT(buf->buf.output_avail >= 0) + ASSERT(!PacketRecvInterface_InClient(buf->input)) + ASSERT(!PacketPassInterface_InClient(buf->output)) + + do { + // send packet + int res; + if ((res = call_send(buf, buf->buf.output_dest, buf->buf.output_avail)) < 0) { + return -1; + } + + if (!res) { + // output busy, continue in output_handler_done + return 0; + } + + // remove packet from buffer + ChunkBuffer2_ConsumePacket(&buf->buf); + } while (buf->buf.output_avail >= 0); + + return 0; +} + +void input_handler_done (PacketBuffer *buf, int in_len) +{ + ASSERT(in_len >= 0) + ASSERT(in_len <= buf->input_mtu) + ASSERT(!PacketRecvInterface_InClient(buf->input)) + ASSERT(!PacketPassInterface_InClient(buf->output)) + + // remember if buffer is empty + int was_empty = (buf->buf.output_avail < 0); + + // submit packet to buffer + ChunkBuffer2_SubmitPacket(&buf->buf, in_len); + + // if buffer was empty, try sending + if (was_empty) { + if (try_send(buf) < 0) { + return; + } + } + + // try receiving more + if (buf->buf.input_avail >= buf->input_mtu) { + try_recv(buf); + return; + } +} + +void output_handler_done (PacketBuffer *buf) +{ + ASSERT(!PacketRecvInterface_InClient(buf->input)) + ASSERT(!PacketPassInterface_InClient(buf->output)) + + // remember if buffer is full + int was_full = (buf->buf.input_avail < buf->input_mtu); + + // remove packet from buffer + ChunkBuffer2_ConsumePacket(&buf->buf); + + // try sending more + if (buf->buf.output_avail >= 0) { + if (try_send(buf) < 0) { + return; + } + } + + // try receiving + if (was_full && buf->buf.input_avail >= buf->input_mtu) { + try_recv(buf); + return; + } +} + +void job_handler (PacketBuffer *buf) +{ + try_recv(buf); + return; +} + +int PacketBuffer_Init (PacketBuffer *buf, PacketRecvInterface *input, PacketPassInterface *output, int num_packets, BPendingGroup *pg) +{ + ASSERT(PacketPassInterface_GetMTU(output) >= PacketRecvInterface_GetMTU(input)) + ASSERT(num_packets > 0) + + // init arguments + buf->input = input; + buf->output = output; + + // init dead var + DEAD_INIT(buf->dead); + + // init input + PacketRecvInterface_Receiver_Init(buf->input, (PacketRecvInterface_handler_done)input_handler_done, buf); + + // set input MTU + buf->input_mtu = PacketRecvInterface_GetMTU(buf->input); + + // init output + PacketPassInterface_Sender_Init(buf->output, (PacketPassInterface_handler_done)output_handler_done, buf); + + // allocate buffer + int num_blocks = CHUNKBUFFER2_MAKE_NUMBLOCKS(buf->input_mtu, num_packets); + if (!(buf->buf_data = malloc(num_blocks * sizeof(struct ChunkBuffer2_block)))) { + goto fail0; + } + + // init buffer + ChunkBuffer2_Init(&buf->buf, buf->buf_data, num_blocks, buf->input_mtu); + + // init start job + BPending_Init(&buf->start_job, pg, (BPending_handler)job_handler, buf); + BPending_Set(&buf->start_job); + + // init debug object + DebugObject_Init(&buf->d_obj); + + return 1; + +fail0: + return 0; +} + +void PacketBuffer_Free (PacketBuffer *buf) +{ + // free debug object + DebugObject_Free(&buf->d_obj); + + // free start job + BPending_Free(&buf->start_job); + + // free buffer + free(buf->buf_data); + + // free dead var + DEAD_KILL(buf->dead); +} diff --git a/flow/PacketBuffer.h b/flow/PacketBuffer.h new file mode 100644 index 000000000..38201d472 --- /dev/null +++ b/flow/PacketBuffer.h @@ -0,0 +1,74 @@ +/** + * @file PacketBuffer.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Packet buffer with {@link PacketRecvInterface} input and {@link PacketPassInterface} output. + */ + +#ifndef BADVPN_FLOW_PACKETBUFFER_H +#define BADVPN_FLOW_PACKETBUFFER_H + +#include + +#include +#include +#include +#include +#include +#include +#include + +/** + * Packet buffer with {@link PacketRecvInterface} input and {@link PacketPassInterface} output. + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + PacketRecvInterface *input; + int input_mtu; + PacketPassInterface *output; + struct ChunkBuffer2_block *buf_data; + ChunkBuffer2 buf; + BPending start_job; +} PacketBuffer; + +/** + * Initializes the buffer. + * Output MTU must be >= input MTU. + * + * @param buf the object + * @param input input interface + * @param output output interface + * @param num_packets minimum number of packets the buffer must hold. Must be >0. + * @param pg pending group + * @return 1 on success, 0 on failure + */ +int PacketBuffer_Init (PacketBuffer *buf, PacketRecvInterface *input, PacketPassInterface *output, int num_packets, BPendingGroup *pg) WARN_UNUSED; + +/** + * Frees the buffer. + * + * @param buf the object + */ +void PacketBuffer_Free (PacketBuffer *buf); + +#endif diff --git a/flow/PacketBufferAsyncInput.h b/flow/PacketBufferAsyncInput.h new file mode 100644 index 000000000..213fb41f3 --- /dev/null +++ b/flow/PacketBufferAsyncInput.h @@ -0,0 +1,160 @@ +/** + * @file PacketBufferAsyncInput.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Object for writing packets to a {@link PacketRecvInterface} client + * via {@link BestEffortPacketWriteInterface}. + */ + +#ifndef BADVPN_FLOW_PACKETBUFFERASYNCINPUT_H +#define BADVPN_FLOW_PACKETBUFFERASYNCINPUT_H + +#include + +#include +#include +#include +#include + +typedef void (*PacketBufferAsyncInput_handler_keepalive) (void *user); + +/** + * Object for writing packets to a {@link PacketRecvInterface} client + * via {@link BestEffortPacketWriteInterface}. + */ +typedef struct { + DebugObject d_obj; + BestEffortPacketWriteInterface input; + PacketRecvInterface recv_interface; + int have_output_packet; + uint8_t *output_packet; +} PacketBufferAsyncInput; + +/** + * Initializes the object. + * + * @param f the object + */ +static void PacketBufferAsyncInput_Init (PacketBufferAsyncInput *f, int mtu); + +/** + * Frees the object. + * + * @param f the object + */ +static void PacketBufferAsyncInput_Free (PacketBufferAsyncInput *f); + +/** + * Returns the output interface. + * + * @param f the object + * @return output interface + */ +static PacketRecvInterface * PacketBufferAsyncInput_GetOutput (PacketBufferAsyncInput *f); + +/** + * Returns the input interface. + * + * @param f the object + * @return input interface + */ +static BestEffortPacketWriteInterface * PacketBufferAsyncInput_GetInput (PacketBufferAsyncInput *f); + +static int _PacketBufferAsyncInput_output_handler_recv (PacketBufferAsyncInput *f, uint8_t *data, int *data_len) +{ + ASSERT(!f->have_output_packet) + + // store destination + f->have_output_packet = 1; + f->output_packet = data; + + // block + return 0; +} + +static int _PacketBufferAsyncInput_handler_startpacket (PacketBufferAsyncInput *f, uint8_t **data) +{ + if (!f->have_output_packet) { + // buffer full + return 0; + } + + if (data) { + *data = f->output_packet; + } + + return 1; +} + +static void _PacketBufferAsyncInput_handler_endpacket (PacketBufferAsyncInput *f, int len) +{ + f->have_output_packet = 0; + + PacketRecvInterface_Done(&f->recv_interface, len); + return; +} + +void PacketBufferAsyncInput_Init (PacketBufferAsyncInput *f, int mtu) +{ + ASSERT(mtu >= 0) + + PacketRecvInterface_Init( + &f->recv_interface, + mtu, + (PacketRecvInterface_handler_recv)_PacketBufferAsyncInput_output_handler_recv, + f + ); + + BestEffortPacketWriteInterface_Init( + &f->input, + mtu, + (BestEffortPacketWriteInterface_handler_startpacket)_PacketBufferAsyncInput_handler_startpacket, + (BestEffortPacketWriteInterface_handler_endpacket)_PacketBufferAsyncInput_handler_endpacket, + f + ); + + f->have_output_packet = 0; + + // init debug object + DebugObject_Init(&f->d_obj); +} + +void PacketBufferAsyncInput_Free (PacketBufferAsyncInput *f) +{ + // free debug object + DebugObject_Free(&f->d_obj); + + BestEffortPacketWriteInterface_Free(&f->input); + PacketRecvInterface_Free(&f->recv_interface); +} + +PacketRecvInterface * PacketBufferAsyncInput_GetOutput (PacketBufferAsyncInput *f) +{ + return &f->recv_interface; +} + +BestEffortPacketWriteInterface * PacketBufferAsyncInput_GetInput (PacketBufferAsyncInput *f) +{ + return &f->input; +} + +#endif diff --git a/flow/PacketCopier.c b/flow/PacketCopier.c new file mode 100644 index 000000000..a0fd5aaf9 --- /dev/null +++ b/flow/PacketCopier.c @@ -0,0 +1,134 @@ +/** + * @file PacketCopier.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include + +#include + +static int input_handler_send (PacketCopier *o, uint8_t *data, int data_len) +{ + ASSERT(o->in_len == -1) + ASSERT(data_len >= 0) + + if (!o->out_have) { + o->in_len = data_len; + o->in = data; + return 0; + } + + memcpy(o->out, data, data_len); + + o->out_have = 0; + + DEAD_ENTER(o->dead) + PacketRecvInterface_Done(&o->output, data_len); + if (DEAD_LEAVE(o->dead)) { + return -1; + } + + return 1; +} + +static void input_handler_cancel (PacketCopier *o) +{ + ASSERT(o->in_len >= 0) + ASSERT(!o->out_have) + + o->in_len = -1; +} + +static int output_handler_recv (PacketCopier *o, uint8_t *data, int *data_len) +{ + ASSERT(!o->out_have) + + if (o->in_len < 0) { + o->out_have = 1; + o->out = data; + return 0; + } + + int len = o->in_len; + + memcpy(data, o->in, len); + + o->in_len = -1; + + DEAD_ENTER(o->dead) + PacketPassInterface_Done(&o->input); + if (DEAD_LEAVE(o->dead)) { + return -1; + } + + *data_len = len; + return 1; +} + +void PacketCopier_Init (PacketCopier *o, int mtu) +{ + ASSERT(mtu >= 0) + + // init dead var + DEAD_INIT(o->dead); + + // init input + PacketPassInterface_Init(&o->input, mtu, (PacketPassInterface_handler_send)input_handler_send, o); + PacketPassInterface_EnableCancel(&o->input, (PacketPassInterface_handler_cancel)input_handler_cancel); + + // init output + PacketRecvInterface_Init(&o->output, mtu, (PacketRecvInterface_handler_recv)output_handler_recv, o); + + // set no input packet + o->in_len = -1; + + // set no output packet + o->out_have = 0; + + // init debug object + DebugObject_Init(&o->d_obj); +} + +void PacketCopier_Free (PacketCopier *o) +{ + // free debug object + DebugObject_Free(&o->d_obj); + + // free output + PacketRecvInterface_Free(&o->output); + + // free input + PacketPassInterface_Free(&o->input); + + // free dead var + DEAD_KILL(o->dead); +} + +PacketPassInterface * PacketCopier_GetInput (PacketCopier *o) +{ + return &o->input; +} + +PacketRecvInterface * PacketCopier_GetOutput (PacketCopier *o) +{ + return &o->output; +} diff --git a/flow/PacketCopier.h b/flow/PacketCopier.h new file mode 100644 index 000000000..91f2fea8b --- /dev/null +++ b/flow/PacketCopier.h @@ -0,0 +1,84 @@ +/** + * @file PacketCopier.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Object which copies packets. + */ + +#ifndef BADVPN_FLOW_PACKETCOPIER_H +#define BADVPN_FLOW_PACKETCOPIER_H + +#include + +#include +#include +#include + +/** + * Object which copies packets. + * Input is via {@link PacketPassInterface}. + * Output is via {@link PacketRecvInterface}. + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + PacketPassInterface input; + PacketRecvInterface output; + int in_len; + uint8_t *in; + int out_have; + uint8_t *out; +} PacketCopier; + +/** + * Initializes the object. + * + * @param o the object + * @param mtu maximum packet size. Must be >=0. + */ +void PacketCopier_Init (PacketCopier *o, int mtu); + +/** + * Frees the object. + * + * @param o the object + */ +void PacketCopier_Free (PacketCopier *o); + +/** + * Returns the input interface. + * The MTU of the interface will as in {@link PacketCopier_Init}. + * The interface will support cancel functionality. + * + * @return input interface + */ +PacketPassInterface * PacketCopier_GetInput (PacketCopier *o); + +/** + * Returns the output interface. + * The MTU of the interface will be as in {@link PacketCopier_Init}. + * + * @return output interface + */ +PacketRecvInterface * PacketCopier_GetOutput (PacketCopier *o); + +#endif diff --git a/flow/PacketPassConnector.c b/flow/PacketPassConnector.c new file mode 100644 index 000000000..7ea637462 --- /dev/null +++ b/flow/PacketPassConnector.c @@ -0,0 +1,220 @@ +/** + * @file PacketPassConnector.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include + +#include + +static int input_handler_send (PacketPassConnector *o, uint8_t *data, int data_len) +{ + ASSERT(o->in_len == -1) + ASSERT(!(o->output) || !o->out_blocking) + ASSERT(data_len >= 0) + ASSERT(data_len <= o->input_mtu) + + // if we have no output, remember input packet + if (!o->output) { + o->in_len = data_len; + o->in = data; + return 0; + } + + // try to send the packet + int res; + while (1) { + DEAD_ENTER_N(obj, o->dead) + DEAD_ENTER_N(out, o->output_dead) + res = PacketPassInterface_Sender_Send(o->output, data, data_len); + DEAD_LEAVE_N(obj, o->dead); + DEAD_LEAVE_N(out, o->output_dead); + if (DEAD_KILLED_N(obj)) { + return -1; + } + if (DEAD_KILLED_N(out)) { + if (!o->output) { + // lost output + o->in_len = data_len; + o->in = data; + return 0; + } + // got a new output, retry + continue; + } + break; + } + + ASSERT(res == 0 || res == 1) + + if (!res) { + // output blocking + o->in_len = data_len; + o->in = data; + o->out_blocking = 1; + return 0; + } + + return 1; +} + +static void output_handler_done (PacketPassConnector *o) +{ + ASSERT(o->in_len >= 0) + ASSERT(o->output) + ASSERT(o->out_blocking) + + // have no input packet + o->in_len = -1; + + // output not blocking any more + o->out_blocking = 0; + + // allow input to send more packets + PacketPassInterface_Done(&o->input); + return; +} + +static void job_handler (PacketPassConnector *o) +{ + ASSERT(o->output) + ASSERT(!o->out_blocking) + ASSERT(o->in_len >= 0) + + // try to send the packet + DEAD_ENTER_N(obj, o->dead) + DEAD_ENTER_N(out, o->output_dead) + int res = PacketPassInterface_Sender_Send(o->output, o->in, o->in_len); + DEAD_LEAVE_N(obj, o->dead); + DEAD_LEAVE_N(out, o->output_dead); + if (DEAD_KILLED_N(obj)) { + return; + } + if (DEAD_KILLED_N(out)) { + // lost current output. Do nothing here. + // If we gained a new one, its own job is responsible for it. + return; + } + + ASSERT(res == 0 || res == 1) + + if (!res) { + // output blocking + o->out_blocking = 1; + return; + } + + // have no input packet + o->in_len = -1; + + // allow input to send more packets + PacketPassInterface_Done(&o->input); + return; +} + +void PacketPassConnector_Init (PacketPassConnector *o, int mtu, BPendingGroup *pg) +{ + ASSERT(mtu >= 0) + + // init arguments + o->input_mtu = mtu; + + // init dead var + DEAD_INIT(o->dead); + + // init input + PacketPassInterface_Init(&o->input, o->input_mtu, (PacketPassInterface_handler_send)input_handler_send, o); + + // have no input packet + o->in_len = -1; + + // have no output + o->output = NULL; + + // init continue job + BPending_Init(&o->continue_job, pg, (BPending_handler)job_handler, o); + + // init debug object + DebugObject_Init(&o->d_obj); +} + +void PacketPassConnector_Free (PacketPassConnector *o) +{ + // free debug object + DebugObject_Free(&o->d_obj); + + // free continue job + BPending_Free(&o->continue_job); + + // free output dead var + if (o->output) { + DEAD_KILL(o->output_dead); + } + + // free input + PacketPassInterface_Free(&o->input); + + // free dead var + DEAD_KILL(o->dead); +} + +PacketPassInterface * PacketPassConnector_GetInput (PacketPassConnector *o) +{ + return &o->input; +} + +void PacketPassConnector_ConnectOutput (PacketPassConnector *o, PacketPassInterface *output) +{ + ASSERT(!o->output) + ASSERT(PacketPassInterface_GetMTU(output) >= o->input_mtu) + + // set output + o->output = output; + + // init output + PacketPassInterface_Sender_Init(o->output, (PacketPassInterface_handler_done)output_handler_done, o); + + // init output dead var + DEAD_INIT(o->output_dead); + + // set output not blocking + o->out_blocking = 0; + + // if we have an input packet, set continue job + if (o->in_len >= 0) { + BPending_Set(&o->continue_job); + } +} + +void PacketPassConnector_DisconnectOutput (PacketPassConnector *o) +{ + ASSERT(o->output) + + // unset continue job (in case it wasn't called yet) + BPending_Unset(&o->continue_job); + + // free dead var + DEAD_KILL(o->output_dead); + + // set no output + o->output = NULL; +} diff --git a/flow/PacketPassConnector.h b/flow/PacketPassConnector.h new file mode 100644 index 000000000..bde2a9870 --- /dev/null +++ b/flow/PacketPassConnector.h @@ -0,0 +1,101 @@ +/** + * @file PacketPassConnector.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * A {@link PacketPassInterface} layer which allows the output to be + * connected and disconnected on the fly. + */ + +#ifndef BADVPN_FLOW_PACKETPASSCONNECTOR_H +#define BADVPN_FLOW_PACKETPASSCONNECTOR_H + +#include + +#include +#include +#include +#include + +/** + * A {@link PacketPassInterface} layer which allows the output to be + * connected and disconnected on the fly. + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + PacketPassInterface input; + int input_mtu; + int in_len; + uint8_t *in; + PacketPassInterface *output; + dead_t output_dead; + int out_blocking; + BPending continue_job; +} PacketPassConnector; + +/** + * Initializes the object. + * The object is initialized in not connected state. + * + * @param o the object + * @param mtu maximum input packet size. Must be >=0. + * @param pg pending group + */ +void PacketPassConnector_Init (PacketPassConnector *o, int mtu, BPendingGroup *pg); + +/** + * Frees the object. + * + * @param o the object + */ +void PacketPassConnector_Free (PacketPassConnector *o); + +/** + * Returns the input interface. + * The MTU of the interface will be as in {@link PacketPassConnector_Init}. + * + * @param o the object + * @return input interface + */ +PacketPassInterface * PacketPassConnector_GetInput (PacketPassConnector *o); + +/** + * Connects output. + * The object must be in not connected state. + * The object enters connected state. + * + * @param o the object + * @param output output to connect. Its MTU must be >= MTU specified in + * {@link PacketPassConnector_Init}. + */ +void PacketPassConnector_ConnectOutput (PacketPassConnector *o, PacketPassInterface *output); + +/** + * Disconnects output. + * The object must be in connected state. + * The object enters not connected state. + * + * @param o the object + */ +void PacketPassConnector_DisconnectOutput (PacketPassConnector *o); + +#endif diff --git a/flow/PacketPassFairQueue.c b/flow/PacketPassFairQueue.c new file mode 100644 index 000000000..2fbc5e8f9 --- /dev/null +++ b/flow/PacketPassFairQueue.c @@ -0,0 +1,459 @@ +/** + * @file PacketPassFairQueue.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include +#include + +#include + +static int call_send (PacketPassFairQueue *m, uint8_t *data, int data_len) +{ + DebugIn_GoIn(&m->in_output); + DEAD_ENTER(m->dead) + int res = PacketPassInterface_Sender_Send(m->output, data, data_len); + if (DEAD_LEAVE(m->dead)) { + return -1; + } + DebugIn_GoOut(&m->in_output); + + ASSERT(!m->freeing) + ASSERT(res == 0 || res == 1) + + return res; +} + +static int call_cancel (PacketPassFairQueue *m) +{ + DebugIn_GoIn(&m->in_output); + DEAD_ENTER(m->dead) + PacketPassInterface_Sender_Cancel(m->output); + if (DEAD_LEAVE(m->dead)) { + return -1; + } + DebugIn_GoOut(&m->in_output); + + ASSERT(!m->freeing) + + return 0; +} + +static int call_done (PacketPassFairQueue *m, PacketPassFairQueueFlow *flow) +{ + DEAD_ENTER_N(m, m->dead) + DEAD_ENTER_N(flow, flow->dead) + PacketPassInterface_Done(&flow->input); + DEAD_LEAVE_N(m, m->dead); + DEAD_LEAVE_N(flow, flow->dead); + + if (DEAD_KILLED_N(m)) { + return -1; + } + + ASSERT(!m->freeing) + + if (!DEAD_KILLED_N(flow)) { + ASSERT(flow->have_time) + if (flow != m->sending_flow && !flow->is_queued) { + flow->have_time = 0; + } + } + + return 0; +} + +static void increment_sent_flow (PacketPassFairQueueFlow *flow, int iamount) +{ + ASSERT(iamount >= 0) + ASSERT(iamount <= UINT64_MAX) + ASSERT(flow->have_time) + ASSERT(!flow->is_queued) + ASSERT(!flow->m->sending_flow) + + PacketPassFairQueue *m = flow->m; + uint64_t amount = iamount; + + // does time overflow? + if (!(flow->time + amount < flow->time)) { + flow->time += amount; + } else { + // get flow with lowest time + BHeapNode *heap_node = BHeap_GetFirst(&m->queued_heap); + if (!heap_node) { + flow->time = amount; + } else { + PacketPassFairQueueFlow *first_flow = UPPER_OBJECT(heap_node, PacketPassFairQueueFlow, queued.heap_node); + ASSERT(first_flow->is_queued) + ASSERT(first_flow->have_time) + // subtract lowest time from all queued flows + uint64_t subtract = first_flow->time; + LinkedList2Iterator it; + LinkedList2Iterator_InitForward(&it, &m->queued_list); + LinkedList2Node *list_node; + while (list_node = LinkedList2Iterator_Next(&it)) { + PacketPassFairQueueFlow *queue_flow = UPPER_OBJECT(list_node, PacketPassFairQueueFlow, queued.list_node); + ASSERT(queue_flow->is_queued) + ASSERT(queue_flow->have_time) + queue_flow->time -= subtract; + } + // update the given flow's time; note we subtract because it isn't in the queue + // TODO: prove this is correct + flow->time = flow->time - subtract + amount; + } + } +} + +static void process_queue (PacketPassFairQueue *m) +{ + ASSERT(!m->freeing) + ASSERT(!m->sending_flow) + + do { + // get first queued flow + BHeapNode *heap_node = BHeap_GetFirst(&m->queued_heap); + if (!heap_node) { + return; + } + PacketPassFairQueueFlow *qflow = UPPER_OBJECT(heap_node, PacketPassFairQueueFlow, queued.heap_node); + ASSERT(qflow->is_queued) + ASSERT(qflow->have_time) + + // remove flow from queue + BHeap_Remove(&m->queued_heap, &qflow->queued.heap_node); + LinkedList2_Remove(&m->queued_list, &qflow->queued.list_node); + qflow->is_queued = 0; + + // try to send the packet + int res = call_send(m, qflow->queued.data, qflow->queued.data_len); + if (res < 0) { + return; + } + + if (res == 0) { + // sending in progress + m->sending_flow = qflow; + m->sending_len = qflow->queued.data_len; + return; + } + + // increment flow time + increment_sent_flow(qflow, qflow->queued.data_len); + + // notify sender + if (call_done(m, qflow) < 0) { + return; + } + } while (!m->sending_flow); +} + +static int time_comparator (void *user, uint64_t *time1, uint64_t *time2) +{ + if (*time1 < *time2) { + return -1; + } + if (*time1 > *time2) { + return 1; + } + return 0; +} + +static int input_handler_send (PacketPassFairQueueFlow *flow, uint8_t *data, int data_len) +{ + ASSERT(!flow->m->freeing) + ASSERT(flow != flow->m->sending_flow) + ASSERT(!flow->is_queued) + DebugIn_AmOut(&flow->m->in_output); + + PacketPassFairQueue *m = flow->m; + + // assign time if needed + int had_time = flow->have_time; + if (!flow->have_time) { + flow->time = (m->sending_flow ? m->sending_flow->time : 0); + flow->have_time = 1; + } + + // if nothing is being sent and queue is empty, send immediately without queueing + if (!m->sending_flow && !BHeap_GetFirst(&m->queued_heap)) { + int res = call_send(m, data, data_len); + if (res < 0) { + return -1; + } + + if (res == 0) { + // output busy, continue in output_handler_done + m->sending_flow = flow; + m->sending_len = data_len; + return 0; + } + + // if flow had no time before it shouldn't have after + if (!had_time) { + flow->have_time = 0; + } + + return 1; + } + + // add flow to queue + flow->queued.data = data; + flow->queued.data_len = data_len; + BHeap_Insert(&m->queued_heap, &flow->queued.heap_node); + LinkedList2_Append(&m->queued_list, &flow->queued.list_node); + flow->is_queued = 1; + + return 0; +} + +static void output_handler_done (PacketPassFairQueue *m) +{ + ASSERT(!m->freeing) + ASSERT(m->sending_flow) + ASSERT(!m->sending_flow->is_queued) + ASSERT(m->sending_flow->have_time) + DebugIn_AmOut(&m->in_output); + + PacketPassFairQueueFlow *flow = m->sending_flow; + + // sending finished + m->sending_flow = NULL; + + // update flow time by packet size + increment_sent_flow(flow, m->sending_len); + + // call busy handler if set + if (flow->handler_busy) { + // handler is one-shot, unset it before calling + PacketPassFairQueue_handler_busy handler = flow->handler_busy; + flow->handler_busy = NULL; + + // call handler + DEAD_ENTER_N(m, m->dead) + DEAD_ENTER_N(flow, flow->dead) + handler(flow->user); + DEAD_LEAVE_N(m, m->dead); + DEAD_LEAVE_N(flow, flow->dead); + if (DEAD_KILLED_N(m)) { + return; + } + if (DEAD_KILLED_N(flow)) { + flow = NULL; + } + + ASSERT(!m->freeing) + } + + // report completion to sender + if (flow) { + if (call_done(m, flow) < 0) { + return; + } + } + + // process queued flows + if (!m->sending_flow) { + process_queue(m); + return; + } +} + +static void job_handler (PacketPassFairQueue *m) +{ + ASSERT(!m->freeing) + + if (!m->sending_flow) { + process_queue(m); + return; + } +} + +void PacketPassFairQueue_Init (PacketPassFairQueue *m, PacketPassInterface *output, BPendingGroup *pg) +{ + // init arguments + m->output = output; + + // init dead var + DEAD_INIT(m->dead); + + // init output + PacketPassInterface_Sender_Init(m->output, (PacketPassInterface_handler_done)output_handler_done, m); + + // not sending + m->sending_flow = NULL; + + // init queued heap + BHeap_Init(&m->queued_heap, OFFSET_DIFF(PacketPassFairQueueFlow, time, queued.heap_node), (BHeap_comparator)time_comparator, NULL); + + // init queued list + LinkedList2_Init(&m->queued_list); + + // not freeing + m->freeing = 0; + + // not using cancel + m->use_cancel = 0; + + // init continue job + BPending_Init(&m->continue_job, pg, (BPending_handler)job_handler, m); + + // init debug counter + DebugCounter_Init(&m->d_ctr); + + // init debug in output + DebugIn_Init(&m->in_output); + + // init debug object + DebugObject_Init(&m->d_obj); +} + +void PacketPassFairQueue_Free (PacketPassFairQueue *m) +{ + ASSERT(!BHeap_GetFirst(&m->queued_heap)) + ASSERT(LinkedList2_IsEmpty(&m->queued_list)) + ASSERT(!m->sending_flow) + DebugCounter_Free(&m->d_ctr); + DebugObject_Free(&m->d_obj); + + // free continue job + BPending_Free(&m->continue_job); + + // free dead var + DEAD_KILL(m->dead); +} + +void PacketPassFairQueue_EnableCancel (PacketPassFairQueue *m) +{ + ASSERT(!m->use_cancel) + ASSERT(PacketPassInterface_HasCancel(m->output)) + + // using cancel + m->use_cancel = 1; +} + +void PacketPassFairQueue_PrepareFree (PacketPassFairQueue *m) +{ + m->freeing = 1; +} + +void PacketPassFairQueueFlow_Init (PacketPassFairQueueFlow *flow, PacketPassFairQueue *m) +{ + ASSERT(!m->freeing) + DebugIn_AmOut(&m->in_output); + + // init arguments + flow->m = m; + + // init dead var + DEAD_INIT(flow->dead); + + // have no canfree handler + flow->handler_busy = NULL; + + // init input + PacketPassInterface_Init(&flow->input, PacketPassInterface_GetMTU(flow->m->output), (PacketPassInterface_handler_send)input_handler_send, flow); + + // doesn't have time + flow->have_time = 0; + + // is not queued + flow->is_queued = 0; + + // increment debug counter + DebugCounter_Increment(&m->d_ctr); + + // init debug object + DebugObject_Init(&flow->d_obj); +} + +void PacketPassFairQueueFlow_Free (PacketPassFairQueueFlow *flow) +{ + if (!flow->m->freeing) { + ASSERT(flow != flow->m->sending_flow) + DebugIn_AmOut(&flow->m->in_output); + } + DebugCounter_Decrement(&flow->m->d_ctr); + DebugObject_Free(&flow->d_obj); + + PacketPassFairQueue *m = flow->m; + + // remove current flow + if (flow == flow->m->sending_flow) { + flow->m->sending_flow = NULL; + } + + // remove from queue + if (flow->is_queued) { + BHeap_Remove(&m->queued_heap, &flow->queued.heap_node); + LinkedList2_Remove(&m->queued_list, &flow->queued.list_node); + } + + // free input + PacketPassInterface_Free(&flow->input); + + // free dead var + DEAD_KILL(flow->dead); +} + +int PacketPassFairQueueFlow_IsBusy (PacketPassFairQueueFlow *flow) +{ + ASSERT(!flow->m->freeing) + DebugIn_AmOut(&flow->m->in_output); + + return (flow == flow->m->sending_flow); +} + +void PacketPassFairQueueFlow_Release (PacketPassFairQueueFlow *flow) +{ + ASSERT(flow->m->use_cancel) + ASSERT(flow == flow->m->sending_flow) + ASSERT(!flow->m->freeing) + DebugIn_AmOut(&flow->m->in_output); + + PacketPassFairQueue *m = flow->m; + + // cancel current packet + if (call_cancel(m) < 0) { + return; + } + + // set no sending flow + m->sending_flow = NULL; + + // set continue job + BPending_Set(&m->continue_job); +} + +void PacketPassFairQueueFlow_SetBusyHandler (PacketPassFairQueueFlow *flow, PacketPassFairQueue_handler_busy handler, void *user) +{ + ASSERT(flow == flow->m->sending_flow) + ASSERT(!flow->m->freeing) + DebugIn_AmOut(&flow->m->in_output); + + flow->handler_busy = handler; + flow->user = user; +} + +PacketPassInterface * PacketPassFairQueueFlow_GetInput (PacketPassFairQueueFlow *flow) +{ + return &flow->input; +} diff --git a/flow/PacketPassFairQueue.h b/flow/PacketPassFairQueue.h new file mode 100644 index 000000000..af642aea7 --- /dev/null +++ b/flow/PacketPassFairQueue.h @@ -0,0 +1,184 @@ +/** + * @file PacketPassFairQueue.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Fair queue using {@link PacketPassInterface}. + */ + +#ifndef BADVPN_FLOW_PACKETPASSFAIRQUEUE_H +#define BADVPN_FLOW_PACKETPASSFAIRQUEUE_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +typedef void (*PacketPassFairQueue_handler_busy) (void *user); + +struct PacketPassFairQueueFlow_s; + +/** + * Fair queue using {@link PacketPassInterface}. + */ +typedef struct { + dead_t dead; + PacketPassInterface *output; + struct PacketPassFairQueueFlow_s *sending_flow; + int sending_len; + BHeap queued_heap; + LinkedList2 queued_list; + int freeing; + int use_cancel; + BPending continue_job; + DebugCounter d_ctr; + DebugIn in_output; + DebugObject d_obj; +} PacketPassFairQueue; + +typedef struct PacketPassFairQueueFlow_s { + dead_t dead; + PacketPassFairQueue *m; + PacketPassFairQueue_handler_busy handler_busy; + void *user; + PacketPassInterface input; + int have_time; + uint64_t time; + int is_queued; + struct { + BHeapNode heap_node; + LinkedList2Node list_node; + uint8_t *data; + int data_len; + } queued; + DebugObject d_obj; +} PacketPassFairQueueFlow; + +/** + * Initializes the queue. + * + * @param m the object + * @param output output interface + * @param pg pending group + */ +void PacketPassFairQueue_Init (PacketPassFairQueue *m, PacketPassInterface *output, BPendingGroup *pg); + +/** + * Frees the queue. + * All flows must have been freed. + * + * @param m the object + */ +void PacketPassFairQueue_Free (PacketPassFairQueue *m); + +/** + * Enables cancel functionality. + * This allows freeing flows even if they're busy by releasing them. + * Output must support {@link PacketPassInterface} cancel functionality. + * May only be called once. + */ +void PacketPassFairQueue_EnableCancel (PacketPassFairQueue *m); + +/** + * Prepares for freeing the entire queue. Must be called to allow freeing + * the flows in the process of freeing the entire queue. + * After this function is called, flows and the queue must be freed + * before any further I/O. + * May be called multiple times. + * The queue enters freeing state. + * + * @param m the object + */ +void PacketPassFairQueue_PrepareFree (PacketPassFairQueue *m); + +/** + * Initializes a queue flow. + * Queue must not be in freeing state. + * Must not be called from queue calls to output. + * + * @param flow the object + * @param m queue to attach to + */ +void PacketPassFairQueueFlow_Init (PacketPassFairQueueFlow *flow, PacketPassFairQueue *m); + +/** + * Frees a queue flow. + * Unless the queue is in freeing state: + * - The flow must not be busy as indicated by {@link PacketPassFairQueueFlow_IsBusy}. + * - Must not be called from queue calls to output. + * + * @param flow the object + */ +void PacketPassFairQueueFlow_Free (PacketPassFairQueueFlow *flow); + +/** + * Determines if the flow is busy. If the flow is considered busy, it must not + * be freed. + * Queue must not be in freeing state. + * Must not be called from queue calls to output. + * + * @param flow the object + * @return 0 if not busy, 1 is busy + */ +int PacketPassFairQueueFlow_IsBusy (PacketPassFairQueueFlow *flow); + +/** + * Cancels the packet that is currently being sent to output in order + * to allow freeing the flow. + * Cancel functionality must be enabled for the queue. + * The flow must be busy as indicated by {@link PacketPassFairQueueFlow_IsBusy}. + * Queue must not be in freeing state. + * Must not be called from queue calls to output. + * Will call Cancel on output. Will not invoke any input I/O. + * After this, {@link PacketPassFairQueueFlow_IsBusy} will report the flow as not busy. + * The flow's input's Done will never be called (the flow will become inoperable). + * + * @param flow the object + */ +void PacketPassFairQueueFlow_Release (PacketPassFairQueueFlow *flow); + +/** + * Sets up a callback to be called when the flow is no longer busy. + * The flow must be busy as indicated by {@link PacketPassFairQueueFlow_IsBusy}. + * Queue must not be in freeing state. + * Must not be called from queue calls to output. + * + * @param flow the object + * @param handler callback function. NULL to disable. + * @param user value passed to callback function. Ignored if handler is NULL. + */ +void PacketPassFairQueueFlow_SetBusyHandler (PacketPassFairQueueFlow *flow, PacketPassFairQueue_handler_busy handler, void *user); + +/** + * Returns the input interface of the flow. + * + * @param flow the object + * @return input interface + */ +PacketPassInterface * PacketPassFairQueueFlow_GetInput (PacketPassFairQueueFlow *flow); + +#endif diff --git a/flow/PacketPassInactivityMonitor.c b/flow/PacketPassInactivityMonitor.c new file mode 100644 index 000000000..1012283e2 --- /dev/null +++ b/flow/PacketPassInactivityMonitor.c @@ -0,0 +1,131 @@ +/** + * @file PacketPassInactivityMonitor.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +static int input_handler_send (PacketPassInactivityMonitor *o, uint8_t *data, int data_len) +{ + // set send called + o->send_called = 1; + + // call send + DEAD_ENTER(o->dead) + int res = PacketPassInterface_Sender_Send(o->output, data, data_len); + if (DEAD_LEAVE(o->dead)) { + return -1; + } + + ASSERT(res == 0 || res == 1) + + if (res == 0) { + // output busy, stop timer + BReactor_RemoveTimer(o->reactor, &o->timer); + } else { + // output accepted packet, restart timer + BReactor_SetTimer(o->reactor, &o->timer); + } + + return res; +} + +static void input_handler_cancel (PacketPassInactivityMonitor *o) +{ + // output no longer busy, restart timer + BReactor_SetTimer(o->reactor, &o->timer); + + // call cancel + PacketPassInterface_Sender_Cancel(o->output); + return; +} + +static void output_handler_done (PacketPassInactivityMonitor *o) +{ + // output no longer busy, restart timer + BReactor_SetTimer(o->reactor, &o->timer); + + // call done + PacketPassInterface_Done(&o->input); + return; +} + +static void timer_handler (PacketPassInactivityMonitor *o) +{ + // restart timer + BReactor_SetTimer(o->reactor, &o->timer); + + // call handler + o->handler(o->user); + return; +} + +void PacketPassInactivityMonitor_Init (PacketPassInactivityMonitor *o, PacketPassInterface *output, BReactor *reactor, btime_t interval, PacketPassInactivityMonitor_handler handler, void *user) +{ + ASSERT(interval > 0) + + // init arguments + o->output = output; + o->reactor = reactor; + o->handler = handler; + o->user = user; + + // init dead var + DEAD_INIT(o->dead); + + // init input + PacketPassInterface_Init(&o->input, PacketPassInterface_GetMTU(o->output), (PacketPassInterface_handler_send)input_handler_send, o); + if (PacketPassInterface_HasCancel(o->output)) { + PacketPassInterface_EnableCancel(&o->input, (PacketPassInterface_handler_cancel)input_handler_cancel); + } + + // init output + PacketPassInterface_Sender_Init(o->output, (PacketPassInterface_handler_done)output_handler_done, o); + + // init timer and start it + BTimer_Init(&o->timer, interval, (BTimer_handler)timer_handler, o); + BReactor_SetTimer(o->reactor, &o->timer); + + // set send not called + o->send_called = 0; + + // init debug object + DebugObject_Init(&o->d_obj); +} + +void PacketPassInactivityMonitor_Free (PacketPassInactivityMonitor *o) +{ + // free debug object + DebugObject_Free(&o->d_obj); + + // free timer + BReactor_RemoveTimer(o->reactor, &o->timer); + + // free input + PacketPassInterface_Free(&o->input); + + // free dead var + DEAD_KILL(o->dead); +} + +PacketPassInterface * PacketPassInactivityMonitor_GetInput (PacketPassInactivityMonitor *o) +{ + return &o->input; +} diff --git a/flow/PacketPassInactivityMonitor.h b/flow/PacketPassInactivityMonitor.h new file mode 100644 index 000000000..02553dd6a --- /dev/null +++ b/flow/PacketPassInactivityMonitor.h @@ -0,0 +1,104 @@ +/** + * @file PacketPassInactivityMonitor.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * A {@link PacketPassInterface} layer for detecting inactivity. + */ + +#ifndef BADVPN_FLOW_PACKETPASSINACTIVITYMONITOR_H +#define BADVPN_FLOW_PACKETPASSINACTIVITYMONITOR_H + +#include +#include +#include +#include + +/** + * Handler function invoked when inactivity is detected. + * It is guaranteed that the interfaces are in not sending state. + * + * @param user value given to {@link PacketPassInactivityMonitor_Init} + */ +typedef void (*PacketPassInactivityMonitor_handler) (void *user); + +/** + * A {@link PacketPassInterface} layer for detecting inactivity. + * It reports inactivity to a user provided handler function. + * + * The object behaves like that: + * ("timer set" means started with the given timeout whether if was running or not, + * "timer unset" means stopped if it was running) + * - There is a timer. + * - The timer is set when the object is initialized. + * - When the input calls Send, the call is passed on to the output. + * If the output accepted the packet, the timer is set. If the output + * blocked the packet, the timer is unset. + * - When the output calls Done, the timer is set, and the call is + * passed on to the input. + * - When the input calls Cancel, the timer is set, and the call is + * passed on to the output. + * - When the timer expires, the timer is set, ant the user's handler + * function is invoked. + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + PacketPassInterface *output; + BReactor *reactor; + PacketPassInactivityMonitor_handler handler; + void *user; + PacketPassInterface input; + BTimer timer; + int send_called; +} PacketPassInactivityMonitor; + +/** + * Initializes the object. + * See {@link PacketPassInactivityMonitor} for details. + * + * @param o the object + * @param output output interface + * @param reactor reactor we live in + * @param interval timer interval is milliseconds. Must be >0. + * @param handler handler function for reporting inactivity + * @param user value passed to handler functions + */ +void PacketPassInactivityMonitor_Init (PacketPassInactivityMonitor *o, PacketPassInterface *output, BReactor *reactor, btime_t interval, PacketPassInactivityMonitor_handler handler, void *user); + +/** + * Frees the object. + * + * @param o the object + */ +void PacketPassInactivityMonitor_Free (PacketPassInactivityMonitor *o); + +/** + * Returns the input interface. + * The MTU of the interface will be the same as of the output interface. + * The interface supports cancel functionality if the output interface supports it. + * + * @param o the object + * @return input interface + */ +PacketPassInterface * PacketPassInactivityMonitor_GetInput (PacketPassInactivityMonitor *o); + +#endif diff --git a/flow/PacketPassInterface.h b/flow/PacketPassInterface.h new file mode 100644 index 000000000..5ca3f26a9 --- /dev/null +++ b/flow/PacketPassInterface.h @@ -0,0 +1,373 @@ +/** + * @file PacketPassInterface.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Interface allowing a packet sender to pass data packets to a packet receiver. + */ + +#ifndef BADVPN_FLOW_PACKETPASSINTERFACE_H +#define BADVPN_FLOW_PACKETPASSINTERFACE_H + +#include +#include + +#include +#include +#include + +/** + * Handler called at the receiver when {@link PacketPassInterface_Sender_Send} is called + * from the sender. + * It is guaranteed that the interface is in not sending state. + * It is guaranteed that the handler is not being called from within Send or Cancel handlers. + * + * @param user value supplied to {@link PacketPassInterface_Init} + * @param data pointer to packet being sent. May be NULL if data_len=0. + * @param data_len length of the packet being sent. Will be >=0 and <=MTU. + * @return - 1 if the receiver accepts the packet immediately. The interface remains in + * not sending state. The receiver may not use the provided data after the handler + * returns. + * - 0 if the receiver cannot accept the packet immediately. The interface enters + * sending state as the handler returns. The receiver may use the provided data + * as long as it needs to. When it's done processing the packet and doesn't need + * the data any more, it must call {@link PacketPassInterface_Done}. + */ +typedef int (*PacketPassInterface_handler_send) (void *user, uint8_t *data, int data_len); + +/** + * Handler called at the receiver when {@link PacketPassInterface_Sender_Cancel} is called + * from the sender. + * The buffer is still available inside the handler. It is no longer available + * after the handler returns. + * It is guaranteed that the interface is in sending state. + * The interface enters not sending state as the handler returns. + * It is guaranteed that the handler is not being called from within Send or Cancel handlers. + * + * @param user value supplied to {@link PacketPassInterface_Init} + */ +typedef void (*PacketPassInterface_handler_cancel) (void *user); + +/** + * Handler called at the sender when {@link PacketPassInterface_Done} is called from the receiver. + * The receiver will no longer use the packet it was provided with. + * It is guaranteed that the interface was in sending state. + * The interface enters not sending state before the handler is called. + * It is guaranteed that the handler is not being called from within Send, Cancel or Done handlers. + * + * @param user value supplied to {@link PacketPassInterface_Sender_Init} + */ +typedef void (*PacketPassInterface_handler_done) (void *user); + +/** + * Interface allowing a packet sender to pass data packets to a packet receiver. + * The sender passes a packet by providing the receiver with a pointer + * to a packet. The receiver may then either accept the packet immediately, + * or tell the sender to wait for the packet to be processed and inform it + * when it's done. + */ +typedef struct { + DebugObject d_obj; + + // receiver data + int mtu; + PacketPassInterface_handler_send handler_send; + PacketPassInterface_handler_cancel handler_cancel; + void *user_receiver; + + // sender data + PacketPassInterface_handler_done handler_done; + void *user_sender; + + // debug vars + #ifndef NDEBUG + dead_t debug_dead; + int debug_busy; + int debug_in_send; + int debug_in_done; + #endif +} PacketPassInterface; + +/** + * Initializes the interface. The sender portion must also be initialized + * with {@link PacketPassInterface_Sender_Init} before I/O can start. + * The interface is initialized in not sending state. + * + * @param i the object + * @param mtu maximum packet size the receiver can accept. Must be >=0. + * @param handler_send handler called when the sender wants to send a packet + * @param user arbitrary value that will be passed to receiver callback functions + */ +static void PacketPassInterface_Init (PacketPassInterface *i, int mtu, PacketPassInterface_handler_send handler_send, void *user); + +/** + * Frees the interface. + * + * @param i the object + */ +static void PacketPassInterface_Free (PacketPassInterface *i); + +/** + * Enables cancel functionality for the interface. + * May only be called once for the interface. + * + * @param i the object + * @param handler_cancel callback function invoked when the sender wants to cancel sending + */ +static void PacketPassInterface_EnableCancel (PacketPassInterface *i, PacketPassInterface_handler_cancel handler_cancel); + +/** + * Notifies the sender that the receiver has finished processing the packet being sent. + * The receiver must not use the data it was provided any more. + * The interface must be in sending state. + * The interface enters not sending state before notifying the sender. + * Must not be called from within Send, Cancel or Done handlers. + * + * Be aware that the sender may attempt to send packets from within this function. + * + * @param i the object + */ +static void PacketPassInterface_Done (PacketPassInterface *i); + +/** + * Returns the maximum packet size the receiver can accept. + * + * @return maximum packet size. Will be >=0. + */ +static int PacketPassInterface_GetMTU (PacketPassInterface *i); + +/** + * Initializes the sender portion of the interface. + * + * @param i the object + * @param handler_done handler called when the receiver has finished processing a packet + * @param user arbitrary value that will be passed to sender callback functions + */ +static void PacketPassInterface_Sender_Init (PacketPassInterface *i, PacketPassInterface_handler_done handler_done, void *user); + +/** + * Attempts to send a packet. + * The interface must be in not sending state. + * Must not be called from within Send or Cancel handlers. + * + * @param i the object + * @param data pointer to the packet to send. If the size of the packet is zero, this argument + * is ignored. + * @param data_len length of the packet. Must be >=0 and <=MTU. + * @return - 1 if the packet was accepted by the receiver. The packet is no longer needed. + * The interface remains in not sending state. + * - 0 if the packet could not be accepted immediately and is being processed. + * The interface enters sending state, and the packet must stay accessible while the + * receiver is processing it. When the receiver is done processing it, the + * {@link PacketPassInterface_handler_done} handler will be called. + */ +static int PacketPassInterface_Sender_Send (PacketPassInterface *i, uint8_t *data, int data_len); + +/** + * Cancels sending a packet. + * Cancel functionality must be available for the interface. + * The buffer must still be available while calling this. + * The buffer is no longer needed after this function returns. + * The interface must be in sending state. + * The interface enters not sending state. + * Must not be called from within Send or Cancel handlers. + * + * @param i the object + */ +static void PacketPassInterface_Sender_Cancel (PacketPassInterface *i); + +/** + * Determines if the interface supports cancel functionality. + * + * @param i the object + * @return 1 if the interface supports cancel functionality, 0 if not + */ +static int PacketPassInterface_HasCancel (PacketPassInterface *i); + +#ifndef NDEBUG + +/** + * Determines if we are in a Send or Cancel call. + * Only available if NDEBUG is not defined. + * + * @param i the object + * @return 1 if in a Send or Cancel call, 0 if not + */ +static int PacketPassInterface_InClient (PacketPassInterface *i); + +/** + * Determines if we are in a Done call. + * Only available if NDEBUG is not defined. + * + * @param i the object + * @return 1 if in a Done call, 0 if not + */ +static int PacketPassInterface_InDone (PacketPassInterface *i); + +#endif + +void PacketPassInterface_Init (PacketPassInterface *i, int mtu, PacketPassInterface_handler_send handler_send, void *user) +{ + ASSERT(mtu >= 0) + + i->mtu = mtu; + i->handler_send = handler_send; + i->handler_cancel = NULL; + i->user_receiver = user; + i->handler_done = NULL; + i->user_sender = NULL; + + // init debugging + #ifndef NDEBUG + DEAD_INIT(i->debug_dead); + i->debug_busy = 0; + i->debug_in_send = 0; + i->debug_in_done = 0; + #endif + + // init debug object + DebugObject_Init(&i->d_obj); +} + +void PacketPassInterface_Free (PacketPassInterface *i) +{ + // free debug object + DebugObject_Free(&i->d_obj); + + // free debugging + #ifndef NDEBUG + DEAD_KILL(i->debug_dead); + #endif +} + +void PacketPassInterface_EnableCancel (PacketPassInterface *i, PacketPassInterface_handler_cancel handler_cancel) +{ + ASSERT(!i->handler_cancel) + ASSERT(handler_cancel) + + i->handler_cancel = handler_cancel; +} + +void PacketPassInterface_Done (PacketPassInterface *i) +{ + ASSERT(i->debug_busy) + ASSERT(!i->debug_in_send) + ASSERT(!i->debug_in_done) + + #ifndef NDEBUG + i->debug_busy = 0; + i->debug_in_done = 1; + DEAD_ENTER(i->debug_dead) + #endif + + i->handler_done(i->user_sender); + + #ifndef NDEBUG + if (DEAD_LEAVE(i->debug_dead)) { + return; + } + i->debug_in_done = 0; + #endif +} + +int PacketPassInterface_GetMTU (PacketPassInterface *i) +{ + return i->mtu; +} + +void PacketPassInterface_Sender_Init (PacketPassInterface *i, PacketPassInterface_handler_done handler_done, void *user) +{ + i->handler_done = handler_done; + i->user_sender = user; +} + +int PacketPassInterface_Sender_Send (PacketPassInterface *i, uint8_t *data, int data_len) +{ + ASSERT(!i->debug_busy) + ASSERT(!i->debug_in_send) + ASSERT(data_len >= 0) + ASSERT(data_len <= i->mtu) + ASSERT(!(data_len > 0) || data) + + #ifndef NDEBUG + i->debug_in_send = 1; + DEAD_ENTER(i->debug_dead) + #endif + + int res = i->handler_send(i->user_receiver, data, data_len); + + #ifndef NDEBUG + if (DEAD_LEAVE(i->debug_dead)) { + return -1; + } + i->debug_in_send = 0; + ASSERT(res == 0 || res == 1) + if (!res) { + i->debug_busy = 1; + } + #endif + + return res; +} + +void PacketPassInterface_Sender_Cancel (PacketPassInterface *i) +{ + ASSERT(i->handler_cancel) + ASSERT(i->debug_busy) + ASSERT(!i->debug_in_send) + + #ifndef NDEBUG + i->debug_in_send = 1; + DEAD_ENTER(i->debug_dead) + #endif + + i->handler_cancel(i->user_receiver); + + #ifndef NDEBUG + if (DEAD_LEAVE(i->debug_dead)) { + return; + } + ASSERT(i->debug_in_send) + i->debug_in_send = 0; + i->debug_busy = 0; + #endif +} + +int PacketPassInterface_HasCancel (PacketPassInterface *i) +{ + return !!i->handler_cancel; +} + +#ifndef NDEBUG + +int PacketPassInterface_InClient (PacketPassInterface *i) +{ + return i->debug_in_send; +} + +int PacketPassInterface_InDone (PacketPassInterface *i) +{ + return i->debug_in_done; +} + +#endif + +#endif diff --git a/flow/PacketPassNotifier.c b/flow/PacketPassNotifier.c new file mode 100644 index 000000000..adbc9e0d1 --- /dev/null +++ b/flow/PacketPassNotifier.c @@ -0,0 +1,163 @@ +/** + * @file PacketPassNotifier.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include + +static int call_handler (PacketPassNotifier *o, uint8_t *data, int data_len); +static int input_handler_send (PacketPassNotifier *o, uint8_t *data, int data_len); +static void input_handler_cancel (PacketPassNotifier *o); +static void output_handler_done (PacketPassNotifier *o); + +int call_handler (PacketPassNotifier *o, uint8_t *data, int data_len) +{ + ASSERT(o->handler) + ASSERT(!o->in_handler) + + #ifndef NDEBUG + o->in_handler = 1; + #endif + + DEAD_ENTER(o->dead) + o->handler(o->handler_user, data, data_len); + if (DEAD_LEAVE(o->dead)) { + return -1; + } + + #ifndef NDEBUG + o->in_handler = 0; + #endif + + return 0; +} + +int input_handler_send (PacketPassNotifier *o, uint8_t *data, int data_len) +{ + ASSERT(!o->in_have) + ASSERT(!o->in_handler) + + // if we have a handler, call it + if (o->handler) { + if (call_handler(o, data, data_len) < 0) { + return -1; + } + } + + // call send on output + DEAD_ENTER(o->dead) + int res = PacketPassInterface_Sender_Send(o->output, data, data_len); + if (DEAD_LEAVE(o->dead)) { + return -1; + } + + ASSERT(res == 0 || res == 1) + + if (!res) { + // output blocking, continue in output_handler_done + #ifndef NDEBUG + o->in_have = 1; + #endif + return 0; + } + + return 1; +} + +void input_handler_cancel (PacketPassNotifier *o) +{ + ASSERT(o->in_have) + ASSERT(!o->in_handler) + + #ifndef NDEBUG + o->in_have = 0; + #endif + + PacketPassInterface_Sender_Cancel(o->output); + return; +} + +void output_handler_done (PacketPassNotifier *o) +{ + ASSERT(o->in_have) + ASSERT(!o->in_handler) + + #ifndef NDEBUG + o->in_have = 0; + #endif + + PacketPassInterface_Done(&o->input); + return; +} + +void PacketPassNotifier_Init (PacketPassNotifier *o, PacketPassInterface *output) +{ + // init arguments + o->output = output; + + // init dead var + DEAD_INIT(o->dead); + + // init input + PacketPassInterface_Init(&o->input, PacketPassInterface_GetMTU(o->output), (PacketPassInterface_handler_send)input_handler_send, o); + if (PacketPassInterface_HasCancel(o->output)) { + PacketPassInterface_EnableCancel(&o->input, (PacketPassInterface_handler_cancel)input_handler_cancel); + } + + // init output + PacketPassInterface_Sender_Init(o->output, (PacketPassInterface_handler_done)output_handler_done, o); + + // set no handler + o->handler = NULL; + + // init debugging + #ifndef NDEBUG + o->in_have = 0; + o->in_handler = 0; + #endif + + // init debug object + DebugObject_Init(&o->d_obj); +} + +void PacketPassNotifier_Free (PacketPassNotifier *o) +{ + // free debug object + DebugObject_Free(&o->d_obj); + + // free input + PacketPassInterface_Free(&o->input); + + // free dead var + DEAD_KILL(o->dead); +} + +PacketPassInterface * PacketPassNotifier_GetInput (PacketPassNotifier *o) +{ + return &o->input; +} + +void PacketPassNotifier_SetHandler (PacketPassNotifier *o, PacketPassNotifier_handler_notify handler, void *user) +{ + o->handler = handler; + o->handler_user = user; +} diff --git a/flow/PacketPassNotifier.h b/flow/PacketPassNotifier.h new file mode 100644 index 000000000..236a35f5f --- /dev/null +++ b/flow/PacketPassNotifier.h @@ -0,0 +1,97 @@ +/** + * @file PacketPassNotifier.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * A {@link PacketPassInterface} layer which calles a handler function before + * passing a packet from input to output. + */ + +#ifndef BADVPN_FLOW_PACKETPASSNOTIFIER_H +#define BADVPN_FLOW_PACKETPASSNOTIFIER_H + +#include + +#include +#include +#include + +/** + * Handler function called when input calls Send, but before the call is passed on to output. + * + * @param user value specified in {@link PacketPassNotifier_SetHandler} + * @param data packet provided by input + * @param data_len size of the packet + */ +typedef void (*PacketPassNotifier_handler_notify) (void *user, uint8_t *data, int data_len); + +/** + * A {@link PacketPassInterface} layer which calles a handler function before + * passing a packet from input to output. + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + PacketPassInterface input; + PacketPassInterface *output; + PacketPassNotifier_handler_notify handler; + void *handler_user; + #ifndef NDEBUG + int in_have; + int in_handler; + #endif +} PacketPassNotifier; + +/** + * Initializes the object. + * + * @param o the object + * @param output output interface + */ +void PacketPassNotifier_Init (PacketPassNotifier *o, PacketPassInterface *output); + +/** + * Frees the object. + * + * @param o the object + */ +void PacketPassNotifier_Free (PacketPassNotifier *o); + +/** + * Returns the input interface. + * The MTU of the interface will be the same as of the output interface. + * The interface supports cancel functionality if the output interface supports it. + * + * @param o the object + * @return input interface + */ +PacketPassInterface * PacketPassNotifier_GetInput (PacketPassNotifier *o); + +/** + * Configures a handler function to call before passing input packets to output. + * + * @param o the object + * @param handler handler function, or NULL to disable. + * @param user value to pass to handler function. Ignored if handler is NULL. + */ +void PacketPassNotifier_SetHandler (PacketPassNotifier *o, PacketPassNotifier_handler_notify handler, void *user); + +#endif diff --git a/flow/PacketPassPriorityQueue.c b/flow/PacketPassPriorityQueue.c new file mode 100644 index 000000000..9360b6817 --- /dev/null +++ b/flow/PacketPassPriorityQueue.c @@ -0,0 +1,378 @@ +/** + * @file PacketPassPriorityQueue.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include +#include + +#include + +static int call_send (PacketPassPriorityQueue *m, uint8_t *data, int data_len) +{ + DebugIn_GoIn(&m->in_output); + DEAD_ENTER(m->dead) + int res = PacketPassInterface_Sender_Send(m->output, data, data_len); + if (DEAD_LEAVE(m->dead)) { + return -1; + } + DebugIn_GoOut(&m->in_output); + + ASSERT(!m->freeing) + ASSERT(res == 0 || res == 1) + + return res; +} + +static int call_cancel (PacketPassPriorityQueue *m) +{ + DebugIn_GoIn(&m->in_output); + DEAD_ENTER(m->dead) + PacketPassInterface_Sender_Cancel(m->output); + if (DEAD_LEAVE(m->dead)) { + return -1; + } + DebugIn_GoOut(&m->in_output); + + ASSERT(!m->freeing) + + return 0; +} + +static int call_done (PacketPassPriorityQueue *m, PacketPassPriorityQueueFlow *flow) +{ + DEAD_ENTER(m->dead) + PacketPassInterface_Done(&flow->input); + if (DEAD_LEAVE(m->dead)) { + return -1; + } + + ASSERT(!m->freeing) + + return 0; +} + +static void process_queue (PacketPassPriorityQueue *m) +{ + ASSERT(!m->freeing) + ASSERT(!m->sending_flow) + + do { + // get first queued flow + BHeapNode *heap_node = BHeap_GetFirst(&m->queued_heap); + if (!heap_node) { + return; + } + PacketPassPriorityQueueFlow *qflow = UPPER_OBJECT(heap_node, PacketPassPriorityQueueFlow, queued.heap_node); + ASSERT(qflow->is_queued) + + // remove flow from queue + BHeap_Remove(&m->queued_heap, &qflow->queued.heap_node); + qflow->is_queued = 0; + + // try to send the packet + int res = call_send(m, qflow->queued.data, qflow->queued.data_len); + if (res < 0) { + return; + } + + if (res == 0) { + // sending in progress + m->sending_flow = qflow; + m->sending_len = qflow->queued.data_len; + return; + } + + // notify sender + if (call_done(m, qflow) < 0) { + return; + } + } while (!m->sending_flow); +} + +static int int_comparator (void *user, int *prio1, int *prio2) +{ + if (*prio1 < *prio2) { + return -1; + } + if (*prio1 > *prio2) { + return 1; + } + return 0; +} + +static int input_handler_send (PacketPassPriorityQueueFlow *flow, uint8_t *data, int data_len) +{ + ASSERT(!flow->m->freeing) + ASSERT(flow != flow->m->sending_flow) + ASSERT(!flow->is_queued) + DebugIn_AmOut(&flow->m->in_output); + + PacketPassPriorityQueue *m = flow->m; + + // if nothing is being sent and queue is empty, send immediately without queueing + if (!m->sending_flow && !BHeap_GetFirst(&m->queued_heap)) { + int res = call_send(m, data, data_len); + if (res < 0) { + return -1; + } + + if (res == 0) { + // output busy, continue in output_handler_done + m->sending_flow = flow; + m->sending_len = data_len; + return 0; + } + + return 1; + } + + // add flow to queue + flow->queued.data = data; + flow->queued.data_len = data_len; + BHeap_Insert(&m->queued_heap, &flow->queued.heap_node); + flow->is_queued = 1; + + return 0; +} + +static void output_handler_done (PacketPassPriorityQueue *m) +{ + ASSERT(!m->freeing) + ASSERT(m->sending_flow) + ASSERT(!m->sending_flow->is_queued) + DebugIn_AmOut(&m->in_output); + + PacketPassPriorityQueueFlow *flow = m->sending_flow; + + // sending finished + m->sending_flow = NULL; + + // call busy handler if set + if (flow->handler_busy) { + // handler is one-shot, unset it before calling + PacketPassPriorityQueue_handler_busy handler = flow->handler_busy; + flow->handler_busy = NULL; + + // call handler + DEAD_ENTER_N(m, m->dead) + DEAD_ENTER_N(flow, flow->dead) + handler(flow->user); + DEAD_LEAVE_N(m, m->dead); + DEAD_LEAVE_N(flow, flow->dead); + if (DEAD_KILLED_N(m)) { + return; + } + if (DEAD_KILLED_N(flow)) { + flow = NULL; + } + + ASSERT(!m->freeing) + } + + // report completion to sender + if (flow) { + if (call_done(m, flow) < 0) { + return; + } + } + + // process queued flows + if (!m->sending_flow) { + process_queue(m); + return; + } +} + +static void job_handler (PacketPassPriorityQueue *m) +{ + ASSERT(!m->freeing) + + if (!m->sending_flow) { + process_queue(m); + return; + } +} + +void PacketPassPriorityQueue_Init (PacketPassPriorityQueue *m, PacketPassInterface *output, BPendingGroup *pg) +{ + // init arguments + m->output = output; + + // init dead var + DEAD_INIT(m->dead); + + // init output + PacketPassInterface_Sender_Init(m->output, (PacketPassInterface_handler_done)output_handler_done, m); + + // not sending + m->sending_flow = NULL; + + // init queued heap + BHeap_Init(&m->queued_heap, OFFSET_DIFF(PacketPassPriorityQueueFlow, priority, queued.heap_node), (BHeap_comparator)int_comparator, NULL); + + // not freeing + m->freeing = 0; + + // not using cancel + m->use_cancel = 0; + + // init continue job + BPending_Init(&m->continue_job, pg, (BPending_handler)job_handler, m); + + // init debug counter + DebugCounter_Init(&m->d_ctr); + + // init debug in output + DebugIn_Init(&m->in_output); + + // init debug object + DebugObject_Init(&m->d_obj); +} + +void PacketPassPriorityQueue_Free (PacketPassPriorityQueue *m) +{ + ASSERT(!BHeap_GetFirst(&m->queued_heap)) + ASSERT(!m->sending_flow) + DebugCounter_Free(&m->d_ctr); + DebugObject_Free(&m->d_obj); + + // free continue job + BPending_Free(&m->continue_job); + + // free dead var + DEAD_KILL(m->dead); +} + +void PacketPassPriorityQueue_EnableCancel (PacketPassPriorityQueue *m) +{ + ASSERT(!m->use_cancel) + ASSERT(PacketPassInterface_HasCancel(m->output)) + + // using cancel + m->use_cancel = 1; +} + +void PacketPassPriorityQueue_PrepareFree (PacketPassPriorityQueue *m) +{ + m->freeing = 1; +} + +void PacketPassPriorityQueueFlow_Init (PacketPassPriorityQueueFlow *flow, PacketPassPriorityQueue *m, int priority) +{ + ASSERT(!m->freeing) + DebugIn_AmOut(&m->in_output); + + // init arguments + flow->m = m; + flow->priority = priority; + + // init dead var + DEAD_INIT(flow->dead); + + // have no canfree handler + flow->handler_busy = NULL; + + // init input + PacketPassInterface_Init(&flow->input, PacketPassInterface_GetMTU(flow->m->output), (PacketPassInterface_handler_send)input_handler_send, flow); + + // is not queued + flow->is_queued = 0; + + // increment debug counter + DebugCounter_Increment(&m->d_ctr); + + // init debug object + DebugObject_Init(&flow->d_obj); +} + +void PacketPassPriorityQueueFlow_Free (PacketPassPriorityQueueFlow *flow) +{ + if (!flow->m->freeing) { + ASSERT(flow != flow->m->sending_flow) + DebugIn_AmOut(&flow->m->in_output); + } + DebugCounter_Decrement(&flow->m->d_ctr); + DebugObject_Free(&flow->d_obj); + + PacketPassPriorityQueue *m = flow->m; + + // remove current flow + if (flow == flow->m->sending_flow) { + flow->m->sending_flow = NULL; + } + + // remove from queue + if (flow->is_queued) { + BHeap_Remove(&m->queued_heap, &flow->queued.heap_node); + } + + // free input + PacketPassInterface_Free(&flow->input); + + // free dead var + DEAD_KILL(flow->dead); +} + +int PacketPassPriorityQueueFlow_IsBusy (PacketPassPriorityQueueFlow *flow) +{ + ASSERT(!flow->m->freeing) + DebugIn_AmOut(&flow->m->in_output); + + return (flow == flow->m->sending_flow); +} + +void PacketPassPriorityQueueFlow_Release (PacketPassPriorityQueueFlow *flow) +{ + ASSERT(flow->m->use_cancel) + ASSERT(flow == flow->m->sending_flow) + ASSERT(!flow->m->freeing) + DebugIn_AmOut(&flow->m->in_output); + + PacketPassPriorityQueue *m = flow->m; + + // cancel current packet + if (call_cancel(m) < 0) { + return; + } + + // set no sending flow + m->sending_flow = NULL; + + // set continue job + BPending_Set(&m->continue_job); +} + +void PacketPassPriorityQueueFlow_SetBusyHandler (PacketPassPriorityQueueFlow *flow, PacketPassPriorityQueue_handler_busy handler, void *user) +{ + ASSERT(flow == flow->m->sending_flow) + ASSERT(!flow->m->freeing) + DebugIn_AmOut(&flow->m->in_output); + + flow->handler_busy = handler; + flow->user = user; +} + +PacketPassInterface * PacketPassPriorityQueueFlow_GetInput (PacketPassPriorityQueueFlow *flow) +{ + return &flow->input; +} diff --git a/flow/PacketPassPriorityQueue.h b/flow/PacketPassPriorityQueue.h new file mode 100644 index 000000000..a94506ad5 --- /dev/null +++ b/flow/PacketPassPriorityQueue.h @@ -0,0 +1,181 @@ +/** + * @file PacketPassPriorityQueue.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Priority queue using {@link PacketPassInterface}. + */ + +#ifndef BADVPN_FLOW_PACKETPASSPRIORITYQUEUE_H +#define BADVPN_FLOW_PACKETPASSPRIORITYQUEUE_H + +#include + +#include +#include +#include +#include +#include +#include +#include + +typedef void (*PacketPassPriorityQueue_handler_busy) (void *user); + +struct PacketPassPriorityQueueFlow_s; + +/** + * Priority queue using {@link PacketPassInterface}. + */ +typedef struct { + dead_t dead; + PacketPassInterface *output; + struct PacketPassPriorityQueueFlow_s *sending_flow; + int sending_len; + BHeap queued_heap; + int freeing; + int use_cancel; + BPending continue_job; + DebugCounter d_ctr; + DebugIn in_output; + DebugObject d_obj; +} PacketPassPriorityQueue; + +typedef struct PacketPassPriorityQueueFlow_s { + dead_t dead; + PacketPassPriorityQueue *m; + PacketPassPriorityQueue_handler_busy handler_busy; + void *user; + PacketPassInterface input; + int priority; + int is_queued; + struct { + BHeapNode heap_node; + uint8_t *data; + int data_len; + } queued; + DebugObject d_obj; +} PacketPassPriorityQueueFlow; + +/** + * Initializes the queue. + * + * @param m the object + * @param output output interface + * @param pg pending group + */ +void PacketPassPriorityQueue_Init (PacketPassPriorityQueue *m, PacketPassInterface *output, BPendingGroup *pg); + +/** + * Frees the queue. + * All flows must have been freed. + * + * @param m the object + */ +void PacketPassPriorityQueue_Free (PacketPassPriorityQueue *m); + +/** + * Enables cancel functionality. + * This allows freeing flows even if they're busy by releasing them. + * Output must support {@link PacketPassInterface} cancel functionality. + * May only be called once. + */ +void PacketPassPriorityQueue_EnableCancel (PacketPassPriorityQueue *m); + +/** + * Prepares for freeing the entire queue. Must be called to allow freeing + * the flows in the process of freeing the entire queue. + * After this function is called, flows and the queue must be freed + * before any further I/O. + * May be called multiple times. + * The queue enters freeing state. + * + * @param m the object + */ +void PacketPassPriorityQueue_PrepareFree (PacketPassPriorityQueue *m); + +/** + * Initializes a queue flow. + * Queue must not be in freeing state. + * Must not be called from queue calls to output. + * + * @param flow the object + * @param m queue to attach to + * @param priority flow priority. Lower value means higher priority. + */ +void PacketPassPriorityQueueFlow_Init (PacketPassPriorityQueueFlow *flow, PacketPassPriorityQueue *m, int priority); + +/** + * Frees a queue flow. + * Unless the queue is in freeing state: + * - The flow must not be busy as indicated by {@link PacketPassPriorityQueueFlow_IsBusy}. + * - Must not be called from queue calls to output. + * + * @param flow the object + */ +void PacketPassPriorityQueueFlow_Free (PacketPassPriorityQueueFlow *flow); + +/** + * Determines if the flow is busy. If the flow is considered busy, it must not + * be freed. + * Queue must not be in freeing state. + * Must not be called from queue calls to output. + * + * @param flow the object + * @return 0 if not busy, 1 is busy + */ +int PacketPassPriorityQueueFlow_IsBusy (PacketPassPriorityQueueFlow *flow); + +/** + * Cancels the packet that is currently being sent to output in order + * to allow freeing the flow. + * Cancel functionality must be enabled for the queue. + * The flow must be busy as indicated by {@link PacketPassPriorityQueueFlow_IsBusy}. + * Queue must not be in freeing state. + * Must not be called from queue calls to output. + * Will call Cancel on output. Will not invoke any input I/O. + * After this, {@link PacketPassPriorityQueueFlow_IsBusy} will report the flow as not busy. + * The flow's input's Done will never be called (the flow will become inoperable). + * + * @param flow the object + */ +void PacketPassPriorityQueueFlow_Release (PacketPassPriorityQueueFlow *flow); + +/** + * Sets up a callback to be called when the flow is no longer busy. + * The flow must be busy as indicated by {@link PacketPassPriorityQueueFlow_IsBusy}. + * Queue must not be in freeing state. + * Must not be called from queue calls to output. + * + * @param flow the object + * @param handler callback function. NULL to disable. + * @param user value passed to callback function. Ignored if handler is NULL. + */ +void PacketPassPriorityQueueFlow_SetBusyHandler (PacketPassPriorityQueueFlow *flow, PacketPassPriorityQueue_handler_busy handler, void *user); + +/** + * Returns the input interface of the flow. + * + * @param flow the object + * @return input interface + */ +PacketPassInterface * PacketPassPriorityQueueFlow_GetInput (PacketPassPriorityQueueFlow *flow); + +#endif diff --git a/flow/PacketProtoDecoder.c b/flow/PacketProtoDecoder.c new file mode 100644 index 000000000..a6b82b6ec --- /dev/null +++ b/flow/PacketProtoDecoder.c @@ -0,0 +1,306 @@ +/** + * @file PacketProtoDecoder.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include + +#include +#include +#include + +#include + +static void report_error (PacketProtoDecoder *enc, int error); +static int call_recv (PacketProtoDecoder *enc, uint8_t *data, int avail); +static int call_send (PacketProtoDecoder *enc, uint8_t *data, int len); +static void receive_data (PacketProtoDecoder *enc); +static void input_handler_done (PacketProtoDecoder *enc, int data_len); +static int parse_and_send (PacketProtoDecoder *enc); +static void output_handler_done (PacketProtoDecoder *enc); +static void job_handler (PacketProtoDecoder *enc); + +void report_error (PacketProtoDecoder *enc, int error) +{ + #ifndef NDEBUG + DEAD_ENTER(enc->dead) + #endif + + FlowErrorReporter_ReportError(&enc->rep, &error); + + #ifndef NDEBUG + ASSERT(DEAD_KILLED) + DEAD_LEAVE(enc->dead); + #endif +} + +int call_recv (PacketProtoDecoder *enc, uint8_t *data, int avail) +{ + ASSERT(avail > 0) + ASSERT(!StreamRecvInterface_InClient(enc->input)) + + DEAD_ENTER(enc->dead) + int res = StreamRecvInterface_Receiver_Recv(enc->input, data, avail); + if (DEAD_LEAVE(enc->dead)) { + return -1; + } + + ASSERT(res >= 0) + ASSERT(res <= avail) + + return res; +} + +int call_send (PacketProtoDecoder *enc, uint8_t *data, int len) +{ + ASSERT(len >= 0) + ASSERT(len <= enc->output_mtu) + ASSERT(!PacketPassInterface_InClient(enc->output)) + + DEAD_ENTER(enc->dead) + int res = PacketPassInterface_Sender_Send(enc->output, data, len); + if (DEAD_LEAVE(enc->dead)) { + return -1; + } + + ASSERT(res == 0 || res == 1) + + return res; +} + +void receive_data (PacketProtoDecoder *enc) +{ + ASSERT(!enc->receiving) + ASSERT(enc->buf_start + enc->buf_used < enc->buf_size) + ASSERT(!StreamRecvInterface_InClient(enc->input)) + ASSERT(!PacketPassInterface_InClient(enc->output)) + + do { + // receive data + int res; + if ((res = call_recv( + enc, + enc->buf + (enc->buf_start + enc->buf_used), + enc->buf_size - (enc->buf_start + enc->buf_used) + )) < 0) { + return; + } + + if (res == 0) { + // input busy, continue in input_handler_done + enc->receiving = 1; + break; + } + + // update buffer + enc->buf_used += res; + + // parse and send data + if (!enc->sending) { + if (parse_and_send(enc) < 0) { + return; + } + } + } while (enc->buf_start + enc->buf_used < enc->buf_size); + + ASSERT(enc->receiving || enc->buf_start + enc->buf_used == enc->buf_size) +} + +static void input_handler_done (PacketProtoDecoder *enc, int data_len) +{ + ASSERT(enc->receiving) + ASSERT(enc->buf_start + enc->buf_used < enc->buf_size) + ASSERT(data_len > 0) + ASSERT(data_len <= enc->buf_size - (enc->buf_start + enc->buf_used)) + ASSERT(!StreamRecvInterface_InClient(enc->input)) + ASSERT(!PacketPassInterface_InClient(enc->output)) + + // set not receiving + enc->receiving = 0; + + // update buffer + enc->buf_used += data_len; + + // parse and send data + if (!enc->sending) { + if (parse_and_send(enc) < 0) { + return; + } + } + + // continue receiving + if (enc->buf_start + enc->buf_used < enc->buf_size) { + receive_data(enc); + return; + } +} + +int parse_and_send (PacketProtoDecoder *enc) +{ + ASSERT(!enc->sending) + ASSERT(!StreamRecvInterface_InClient(enc->input)) + ASSERT(!PacketPassInterface_InClient(enc->output)) + + while (1) { + uint8_t *data = enc->buf + enc->buf_start; + int left = enc->buf_used; + + // check if header was received + if (left < sizeof(struct packetproto_header)) { + break; + } + struct packetproto_header *header = (struct packetproto_header *)data; + data += sizeof(struct packetproto_header); + left -= sizeof(struct packetproto_header); + int data_len = ltoh16(header->len); + + // check data length + if (data_len > enc->output_mtu) { + report_error(enc, PACKETPROTODECODER_ERROR_TOOLONG); + return -1; + } + + // check if whole packet was received + if (left < data_len) { + break; + } + + // update buffer + enc->buf_start += sizeof(struct packetproto_header) + data_len; + enc->buf_used -= sizeof(struct packetproto_header) + data_len; + + // submit packet + int res; + if ((res = call_send(enc, data, data_len)) < 0) { + return -1; + } + + if (!res) { + // output busy, continue in output_handler_done + enc->sending = 1; + return 0; + } + } + + // if we reached the end of the buffer, wrap around to allow more data to be received + if (enc->buf_start + enc->buf_used == enc->buf_size) { + memmove(enc->buf, enc->buf + enc->buf_start, enc->buf_used); + enc->buf_start = 0; + } + + return 0; +} + +void output_handler_done (PacketProtoDecoder *enc) +{ + ASSERT(enc->sending) + ASSERT(!StreamRecvInterface_InClient(enc->input)) + ASSERT(!PacketPassInterface_InClient(enc->output)) + + // set not sending + enc->sending = 0; + + // continue parsing and sending + if (parse_and_send(enc) < 0) { + return; + } + + // continue receiving + if (!enc->receiving && enc->buf_start + enc->buf_used < enc->buf_size) { + receive_data(enc); + return; + } +} + +void job_handler (PacketProtoDecoder *enc) +{ + receive_data(enc); + return; +} + +int PacketProtoDecoder_Init (PacketProtoDecoder *enc, FlowErrorReporter rep, StreamRecvInterface *input, PacketPassInterface *output, BPendingGroup *pg) +{ + // init arguments + enc->rep = rep; + enc->input = input; + enc->output = output; + + // init dead var + DEAD_INIT(enc->dead); + + // init input + StreamRecvInterface_Receiver_Init(enc->input, (StreamRecvInterface_handler_done)input_handler_done, enc); + + // init output + PacketPassInterface_Sender_Init(enc->output, (PacketPassInterface_handler_done)output_handler_done, enc); + + // set output MTU, limit by maximum payload size + enc->output_mtu = BMIN(PacketPassInterface_GetMTU(enc->output), PACKETPROTO_MAXPAYLOAD); + + // init buffer state + enc->buf_size = PACKETPROTO_ENCLEN(enc->output_mtu); + enc->buf_start = 0; + enc->buf_used = 0; + + // allocate buffer + if (!(enc->buf = malloc(enc->buf_size))) { + goto fail0; + } + + // set not receiving + enc->receiving = 0; + + // set not sending + enc->sending = 0; + + // init start job + BPending_Init(&enc->start_job, pg, (BPending_handler)job_handler, enc); + BPending_Set(&enc->start_job); + + // init debug object + DebugObject_Init(&enc->d_obj); + + return 1; + +fail0: + return 0; +} + +void PacketProtoDecoder_Free (PacketProtoDecoder *enc) +{ + // free debug object + DebugObject_Free(&enc->d_obj); + + // free start job + BPending_Free(&enc->start_job); + + // free buffer + free(enc->buf); + + // free dead var + DEAD_KILL(enc->dead); +} + +void PacketProtoDecoder_Reset (PacketProtoDecoder *enc) +{ + enc->buf_start += enc->buf_used; + enc->buf_used = 0; +} diff --git a/flow/PacketProtoDecoder.h b/flow/PacketProtoDecoder.h new file mode 100644 index 000000000..0bb1ebc4d --- /dev/null +++ b/flow/PacketProtoDecoder.h @@ -0,0 +1,104 @@ +/** + * @file PacketProtoDecoder.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Object which decodes a stream according to PacketProto. + */ + +#ifndef BADVPN_FLOW_PACKETPROTODECODER_H +#define BADVPN_FLOW_PACKETPROTODECODER_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PACKETPROTODECODER_ERROR_TOOLONG 1 +#define PACKETPROTODECODER_ERROR_HEADERPADDING 2 + +/** + * Object which decodes a stream according to PacketProto. + * + * Input is with {@link StreamRecvInterface}. + * Output is with {@link PacketPassInterface}. + * + * Errors are reported through {@link FlowErrorDomain}. All errors + * are fatal and the object must be freed from the error handler. + * Error code is an int which is one of the following: + * - PACKETPROTODECODER_ERROR_TOOLONG: the packet header contains + * a packet length value which is too big, + * - PACKETPROTODECODER_ERROR_HEADERPADDING: packet header padding + * is not zero, + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + FlowErrorReporter rep; + StreamRecvInterface *input; + PacketPassInterface *output; + int output_mtu; + int buf_size; + int buf_start; + int buf_used; + uint8_t *buf; + int receiving; + int sending; + BPending start_job; +} PacketProtoDecoder; + +/** + * Initializes the object. + * + * @param enc the object + * @param rep error reporting data + * @param input input interface. The decoder will accept packets with payload size up to its MTU + * (but the payload can never be more than PACKETPROTO_MAXPAYLOAD). + * @param output output interface + * @param pg pending group + * @return 1 on success, 0 on failure + */ +int PacketProtoDecoder_Init (PacketProtoDecoder *enc, FlowErrorReporter rep, StreamRecvInterface *input, PacketPassInterface *output, BPendingGroup *pg) WARN_UNUSED; + +/** + * Frees the object. + * + * @param enc the object + */ +void PacketProtoDecoder_Free (PacketProtoDecoder *enc); + +/** + * Clears the internal buffer. + * The next data received from the input will be treated as a new + * PacketProto stream. + * + * @param enc the object + */ +void PacketProtoDecoder_Reset (PacketProtoDecoder *enc); + +#endif diff --git a/flow/PacketProtoEncoder.c b/flow/PacketProtoEncoder.c new file mode 100644 index 000000000..aa710aab6 --- /dev/null +++ b/flow/PacketProtoEncoder.c @@ -0,0 +1,130 @@ +/** + * @file PacketProtoEncoder.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include +#include +#include +#include + +#include + +static int encode_packet (PacketProtoEncoder *enc, uint8_t *data, int in_len); +static int output_handler_recv (PacketProtoEncoder *enc, uint8_t *data, int *out_len); +static void input_handler_done (PacketProtoEncoder *enc, int in_len); + +int encode_packet (PacketProtoEncoder *enc, uint8_t *data, int in_len) +{ + // write header + struct packetproto_header *header = (struct packetproto_header *)data; + header->len = htol16(in_len); + + return PACKETPROTO_ENCLEN(in_len); +} + +int output_handler_recv (PacketProtoEncoder *enc, uint8_t *data, int *out_len) +{ + ASSERT(!enc->output_packet) + ASSERT(data) + + // call recv on input + int in_len; + DEAD_ENTER(enc->dead) + int res = PacketRecvInterface_Receiver_Recv(enc->input, data + sizeof(struct packetproto_header), &in_len); + if (DEAD_LEAVE(enc->dead)) { + return -1; + } + + ASSERT(res == 0 || res == 1) + + if (!res) { + // input busy, continue in input_handler_done + enc->output_packet = data; + return 0; + } + + // encode + *out_len = encode_packet(enc, data, in_len); + + return 1; +} + +void input_handler_done (PacketProtoEncoder *enc, int in_len) +{ + ASSERT(enc->output_packet) + + // encode + int out_len = encode_packet(enc, enc->output_packet, in_len); + + // set no output packet + enc->output_packet = NULL; + + // notify output + PacketRecvInterface_Done(&enc->output, out_len); + return; +} + +void PacketProtoEncoder_Init (PacketProtoEncoder *enc, PacketRecvInterface *input) +{ + ASSERT(PacketRecvInterface_GetMTU(input) <= PACKETPROTO_MAXPAYLOAD) + + // init arguments + enc->input = input; + + // init dead var + DEAD_INIT(enc->dead); + + // init input + PacketRecvInterface_Receiver_Init(enc->input, (PacketRecvInterface_handler_done)input_handler_done, enc); + + // init output + PacketRecvInterface_Init( + &enc->output, + PACKETPROTO_ENCLEN(PacketRecvInterface_GetMTU(enc->input)), + (PacketRecvInterface_handler_recv)output_handler_recv, + enc + ); + + // set no output packet + enc->output_packet = NULL; + + // init debug object + DebugObject_Init(&enc->d_obj); +} + +void PacketProtoEncoder_Free (PacketProtoEncoder *enc) +{ + // free debug object + DebugObject_Free(&enc->d_obj); + + // free input + PacketRecvInterface_Free(&enc->output); + + // free dead var + DEAD_KILL(enc->dead); +} + +PacketRecvInterface * PacketProtoEncoder_GetOutput (PacketProtoEncoder *enc) +{ + return &enc->output; +} diff --git a/flow/PacketProtoEncoder.h b/flow/PacketProtoEncoder.h new file mode 100644 index 000000000..27b71d4d6 --- /dev/null +++ b/flow/PacketProtoEncoder.h @@ -0,0 +1,74 @@ +/** + * @file PacketProtoEncoder.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Object which encodes packets according to PacketProto. + */ + +#ifndef BADVPN_FLOW_PACKETPROTOENCODER_H +#define BADVPN_FLOW_PACKETPROTOENCODER_H + +#include + +#include +#include +#include + +/** + * Object which encodes packets according to PacketProto. + * + * Input is with {@link PacketRecvInterface}. + * Output is with {@link PacketRecvInterface}. + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + PacketRecvInterface *input; + PacketRecvInterface output; + uint8_t *output_packet; +} PacketProtoEncoder; + +/** + * Initializes the object. + * + * @param enc the object + * @param input input interface. Its MTU must be <=PACKETPROTO_MAXPAYLOAD. + */ +void PacketProtoEncoder_Init (PacketProtoEncoder *enc, PacketRecvInterface *input); + +/** + * Frees the object. + * + * @param enc the object + */ +void PacketProtoEncoder_Free (PacketProtoEncoder *enc); + +/** + * Returns the output interface. + * The MTU of the output interface is PACKETPROTO_ENCLEN(MTU of input interface). + * + * @param enc the object + * @return output interface + */ +PacketRecvInterface * PacketProtoEncoder_GetOutput (PacketProtoEncoder *enc); + +#endif diff --git a/flow/PacketProtoFlow.c b/flow/PacketProtoFlow.c new file mode 100644 index 000000000..37a6b0968 --- /dev/null +++ b/flow/PacketProtoFlow.c @@ -0,0 +1,76 @@ +/** + * @file PacketProtoFlow.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include + +#include + +int PacketProtoFlow_Init (PacketProtoFlow *o, int input_mtu, int num_packets, PacketPassInterface *output, BPendingGroup *pg) +{ + ASSERT(input_mtu >= 0) + ASSERT(input_mtu <= PACKETPROTO_MAXPAYLOAD) + ASSERT(num_packets > 0) + ASSERT(PacketPassInterface_GetMTU(output) >= PACKETPROTO_ENCLEN(input_mtu)) + + // init async input + PacketBufferAsyncInput_Init(&o->ainput, input_mtu); + + // init encoder + PacketProtoEncoder_Init(&o->encoder, PacketBufferAsyncInput_GetOutput(&o->ainput)); + + // init buffer + if (!PacketBuffer_Init(&o->buffer, PacketProtoEncoder_GetOutput(&o->encoder), output, num_packets, pg)) { + goto fail0; + } + + // init debug object + DebugObject_Init(&o->d_obj); + + return 1; + +fail0: + PacketProtoEncoder_Free(&o->encoder); + PacketBufferAsyncInput_Free(&o->ainput); + return 0; +} + +void PacketProtoFlow_Free (PacketProtoFlow *o) +{ + DebugObject_Free(&o->d_obj); + + // free buffer + PacketBuffer_Free(&o->buffer); + + // free encoder + PacketProtoEncoder_Free(&o->encoder); + + // free async input + PacketBufferAsyncInput_Free(&o->ainput); +} + +BestEffortPacketWriteInterface * PacketProtoFlow_GetInput (PacketProtoFlow *o) +{ + DebugObject_Access(&o->d_obj); + + return PacketBufferAsyncInput_GetInput(&o->ainput); +} diff --git a/flow/PacketProtoFlow.h b/flow/PacketProtoFlow.h new file mode 100644 index 000000000..05d1c26c4 --- /dev/null +++ b/flow/PacketProtoFlow.h @@ -0,0 +1,78 @@ +/** + * @file PacketProtoFlow.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Buffer which encodes packets with PacketProto, with {@link BestEffortPacketWriteInterface} + * input and {@link PacketPassInterface} output. + */ + +#ifndef BADVPN_FLOW_PACKETPROTOFLOW_H +#define BADVPN_FLOW_PACKETPROTOFLOW_H + +#include +#include +#include +#include +#include + +/** + * Buffer which encodes packets with PacketProto, with {@link BestEffortPacketWriteInterface} + * input and {@link PacketPassInterface} output. + */ +typedef struct { + PacketBufferAsyncInput ainput; + PacketProtoEncoder encoder; + PacketBuffer buffer; + DebugObject d_obj; +} PacketProtoFlow; + +/** + * Initializes the object. + * + * @param o the object + * @param input_mtu maximum input packet size. Must be >=0 and <=PACKETPROTO_MAXPAYLOAD. + * @param num_packets minimum number of packets the buffer should hold. Must be >0. + * @param output output interface. Its MTU must be >=PACKETPROTO_ENCLEN(input_mtu). + * @param pg pending group + * @return 1 on success, 0 on failure + */ +int PacketProtoFlow_Init (PacketProtoFlow *o, int input_mtu, int num_packets, PacketPassInterface *output, BPendingGroup *pg) WARN_UNUSED; + +/** + * Frees the object. + * + * @param o the object + */ +void PacketProtoFlow_Free (PacketProtoFlow *o); + +/** + * Returns the input interface. + * Its MTU will be as in {@link PacketProtoFlow_Init}. + * Be aware that it may not be possible to write packets immediately after initializing + * the object; the object starts its internal I/O with a {@link BPending} job. + * + * @param o the object + * @return input interface + */ +BestEffortPacketWriteInterface * PacketProtoFlow_GetInput (PacketProtoFlow *o); + +#endif diff --git a/flow/PacketRecvBlocker.c b/flow/PacketRecvBlocker.c new file mode 100644 index 000000000..dd2df98dd --- /dev/null +++ b/flow/PacketRecvBlocker.c @@ -0,0 +1,120 @@ +/** + * @file PacketRecvBlocker.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include + +static int output_handler_recv (PacketRecvBlocker *o, uint8_t *data, int *data_len) +{ + ASSERT(!o->out_have) + + // remember packet + o->out_have = 1; + o->out = data; + o->out_input_blocking = 0; + + return 0; +} + +static void input_handler_done (PacketRecvBlocker *o, int data_len) +{ + ASSERT(o->out_have) + ASSERT(o->out_input_blocking) + + // have no output packet + o->out_have = 0; + + // inform output we received something + PacketRecvInterface_Done(&o->output, data_len); + return; +} + +void PacketRecvBlocker_Init (PacketRecvBlocker *o, PacketRecvInterface *input) +{ + // init arguments + o->input = input; + + // init dead var + DEAD_INIT(o->dead); + + // init output + PacketRecvInterface_Init(&o->output, PacketRecvInterface_GetMTU(o->input), (PacketRecvInterface_handler_recv)output_handler_recv, o); + + // have no output packet + o->out_have = 0; + + // init input + PacketRecvInterface_Receiver_Init(o->input, (PacketRecvInterface_handler_done)input_handler_done, o); + + // init debug object + DebugObject_Init(&o->d_obj); +} + +void PacketRecvBlocker_Free (PacketRecvBlocker *o) +{ + // free debug object + DebugObject_Free(&o->d_obj); + + // free output + PacketRecvInterface_Free(&o->output); + + // free dead var + DEAD_KILL(o->dead); +} + +PacketRecvInterface * PacketRecvBlocker_GetOutput (PacketRecvBlocker *o) +{ + return &o->output; +} + +void PacketRecvBlocker_AllowBlockedPacket (PacketRecvBlocker *o) +{ + ASSERT(!PacketRecvInterface_InClient(o->input)) + + if (!o->out_have || o->out_input_blocking) { + return; + } + + // receive from input + int in_len; + DEAD_ENTER(o->dead) + int res = PacketRecvInterface_Receiver_Recv(o->input, o->out, &in_len); + if (DEAD_LEAVE(o->dead)) { + return; + } + + ASSERT(res == 0 || res == 1) + + if (!res) { + // input blocking, continue in input_handler_done + o->out_input_blocking = 1; + return; + } + + // have no output packet + o->out_have = 0; + + // inform output we received something + PacketRecvInterface_Done(&o->output, in_len); + return; +} diff --git a/flow/PacketRecvBlocker.h b/flow/PacketRecvBlocker.h new file mode 100644 index 000000000..273728098 --- /dev/null +++ b/flow/PacketRecvBlocker.h @@ -0,0 +1,85 @@ +/** + * @file PacketRecvBlocker.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * {@link PacketRecvInterface} layer which blocks all output recv calls and only + * passes a single blocked call on to input when the user wants so. + */ + +#ifndef BADVPN_FLOW_PACKETRECVBLOCKER_H +#define BADVPN_FLOW_PACKETRECVBLOCKER_H + +#include + +#include +#include +#include + +/** + * {@link PacketRecvInterface} layer which blocks all output recv calls and only + * passes a single blocked call on to input when the user wants so. + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + PacketRecvInterface output; + int out_have; + uint8_t *out; + int out_input_blocking; + PacketRecvInterface *input; +} PacketRecvBlocker; + +/** + * Initializes the object. + * + * @param o the object + * @param input input interface + */ +void PacketRecvBlocker_Init (PacketRecvBlocker *o, PacketRecvInterface *input); + +/** + * Frees the object. + * + * @param o the object + */ +void PacketRecvBlocker_Free (PacketRecvBlocker *o); + +/** + * Returns the output interface. + * The MTU of the output interface will be the same as of the input interface. + * + * @param o the object + * @return output interface + */ +PacketRecvInterface * PacketRecvBlocker_GetOutput (PacketRecvBlocker *o); + +/** + * Passes a blocked output recv call to input if there is one and it has not + * been passed yet. Otherwise it does nothing. + * Must not be called from input Recv calls. + * This function may invoke I/O. + * + * @param o the object + */ +void PacketRecvBlocker_AllowBlockedPacket (PacketRecvBlocker *o); + +#endif diff --git a/flow/PacketRecvConnector.c b/flow/PacketRecvConnector.c new file mode 100644 index 000000000..f5ea5675e --- /dev/null +++ b/flow/PacketRecvConnector.c @@ -0,0 +1,227 @@ +/** + * @file PacketRecvConnector.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include + +#include + +static int output_handler_recv (PacketRecvConnector *o, uint8_t *data, int *data_len) +{ + ASSERT(!o->out_have) + ASSERT(!o->input || !o->in_blocking) + + // if we have no input, remember output packet + if (!o->input) { + o->out_have = 1; + o->out = data; + return 0; + } + + // try to receive the packet + int res; + while (1) { + DEAD_ENTER_N(obj, o->dead) + DEAD_ENTER_N(inp, o->input_dead) + res = PacketRecvInterface_Receiver_Recv(o->input, data, data_len); + DEAD_LEAVE_N(obj, o->dead); + DEAD_LEAVE_N(inp, o->input_dead); + if (DEAD_KILLED_N(obj)) { + return -1; + } + if (DEAD_KILLED_N(inp)) { + if (!o->input) { + // lost input + o->out_have = 1; + o->out = data; + return 0; + } + // got a new input, retry + continue; + } + break; + }; + + ASSERT(res == 0 || res == 1) + if (res) { + ASSERT(*data_len >= 0) + ASSERT(*data_len <= o->output_mtu) + } + + if (!res) { + // input blocking + o->out_have = 1; + o->out = data; + o->in_blocking = 1; + return 0; + } + + return 1; +} + +static void input_handler_done (PacketRecvConnector *o, int data_len) +{ + ASSERT(o->out_have) + ASSERT(o->input) + ASSERT(o->in_blocking) + + // have no output packet + o->out_have = 0; + + // input not blocking any more + o->in_blocking = 0; + + // allow output to receive more packets + PacketRecvInterface_Done(&o->output, data_len); + return; +} + +static void job_handler (PacketRecvConnector *o) +{ + ASSERT(o->input) + ASSERT(!o->in_blocking) + ASSERT(o->out_have) + + // try to receive the packet + int in_len; + DEAD_ENTER_N(obj, o->dead) + DEAD_ENTER_N(inp, o->input_dead) + int res = PacketRecvInterface_Receiver_Recv(o->input, o->out, &in_len); + DEAD_LEAVE_N(obj, o->dead); + DEAD_LEAVE_N(inp, o->input_dead); + if (DEAD_KILLED_N(obj)) { + return; + } + if (DEAD_KILLED_N(inp)) { + // lost current input. Do nothing here. + // If we gained a new one, its own job is responsible for it. + return; + } + + ASSERT(res == 0 || res == 1) + if (res) { + ASSERT(in_len >= 0) + ASSERT(in_len <= o->output_mtu) + } + + if (!res) { + // input blocking + o->in_blocking = 1; + return; + } + + // have no output packet + o->out_have = 0; + + // allow output to receive more packets + PacketRecvInterface_Done(&o->output, in_len); + return; +} + +void PacketRecvConnector_Init (PacketRecvConnector *o, int mtu, BPendingGroup *pg) +{ + ASSERT(mtu >= 0) + + // init arguments + o->output_mtu = mtu; + + // init dead var + DEAD_INIT(o->dead); + + // init output + PacketRecvInterface_Init(&o->output, o->output_mtu, (PacketRecvInterface_handler_recv)output_handler_recv, o); + + // have no output packet + o->out_have = 0; + + // have no input + o->input = NULL; + + // init continue job + BPending_Init(&o->continue_job, pg, (BPending_handler)job_handler, o); + + // init debug object + DebugObject_Init(&o->d_obj); +} + +void PacketRecvConnector_Free (PacketRecvConnector *o) +{ + // free debug object + DebugObject_Free(&o->d_obj); + + // free continue job + BPending_Free(&o->continue_job); + + // free input dead var + if (o->input) { + DEAD_KILL(o->input_dead); + } + + // free output + PacketRecvInterface_Free(&o->output); + + // free dead var + DEAD_KILL(o->dead); +} + +PacketRecvInterface * PacketRecvConnector_GetOutput (PacketRecvConnector *o) +{ + return &o->output; +} + +void PacketRecvConnector_ConnectInput (PacketRecvConnector *o, PacketRecvInterface *input) +{ + ASSERT(!o->input) + ASSERT(PacketRecvInterface_GetMTU(input) <= o->output_mtu) + + // set input + o->input = input; + + // init input + PacketRecvInterface_Receiver_Init(o->input, (PacketRecvInterface_handler_done)input_handler_done, o); + + // init input dead var + DEAD_INIT(o->input_dead); + + // set input not blocking + o->in_blocking = 0; + + // if we have an input packet, set continue job + if (o->out_have) { + BPending_Set(&o->continue_job); + } +} + +void PacketRecvConnector_DisconnectInput (PacketRecvConnector *o) +{ + ASSERT(o->input) + + // unset continue job (in case it wasn't called yet) + BPending_Unset(&o->continue_job); + + // free dead var + DEAD_KILL(o->input_dead); + + // set no input + o->input = NULL; +} diff --git a/flow/PacketRecvConnector.h b/flow/PacketRecvConnector.h new file mode 100644 index 000000000..5d4caec9b --- /dev/null +++ b/flow/PacketRecvConnector.h @@ -0,0 +1,101 @@ +/** + * @file PacketRecvConnector.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * A {@link PacketRecvInterface} layer which allows the input to be + * connected and disconnected on the fly. + */ + +#ifndef BADVPN_FLOW_PACKETRECVCONNECTOR_H +#define BADVPN_FLOW_PACKETRECVCONNECTOR_H + +#include + +#include +#include +#include +#include + +/** + * A {@link PacketRecvInterface} layer which allows the input to be + * connected and disconnected on the fly. + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + PacketRecvInterface output; + int output_mtu; + int out_have; + uint8_t *out; + PacketRecvInterface *input; + dead_t input_dead; + int in_blocking; + BPending continue_job; +} PacketRecvConnector; + +/** + * Initializes the object. + * The object is initialized in not connected state. + * + * @param o the object + * @param mtu maximum output packet size. Must be >=0. + * @param pg pending group + */ +void PacketRecvConnector_Init (PacketRecvConnector *o, int mtu, BPendingGroup *pg); + +/** + * Frees the object. + * + * @param o the object + */ +void PacketRecvConnector_Free (PacketRecvConnector *o); + +/** + * Returns the output interface. + * The MTU of the interface will be as in {@link PacketRecvConnector_Init}. + * + * @param o the object + * @return output interface + */ +PacketRecvInterface * PacketRecvConnector_GetOutput (PacketRecvConnector *o); + +/** + * Connects input. + * The object must be in not connected state. + * The object enters connected state. + * + * @param o the object + * @param output input to connect. Its MTU must be <= MTU specified in + * {@link PacketRecvConnector_Init}. + */ +void PacketRecvConnector_ConnectInput (PacketRecvConnector *o, PacketRecvInterface *input); + +/** + * Disconnects input. + * The object must be in connected state. + * The object enters not connected state. + * + * @param o the object + */ +void PacketRecvConnector_DisconnectInput (PacketRecvConnector *o); + +#endif diff --git a/flow/PacketRecvInterface.h b/flow/PacketRecvInterface.h new file mode 100644 index 000000000..c6f9435c9 --- /dev/null +++ b/flow/PacketRecvInterface.h @@ -0,0 +1,296 @@ +/** + * @file PacketRecvInterface.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Interface allowing a packet receiver to receive data packets from a packet sender. + */ + +#ifndef BADVPN_FLOW_PACKETRECVINTERFACE_H +#define BADVPN_FLOW_PACKETRECVINTERFACE_H + +#include +#include + +#include +#include +#include + +/** + * Handler called at the sender when {@link PacketRecvInterface_Receiver_Recv} is called + * from the receiver. + * It is guaranteed that the interface is in not receiving state. + * It is guaranteed that the handler is not being called from within Recv or Cancel handlers. + * + * @param user value supplied to {@link PacketRecvInterface_Init} + * @param data pointer to the buffer where the packet is to be written. Will have space + * for MTU bytes. May be NULL if MTU is 0. + * @param data_len if the packet was written immediately, must be set to its length + * @return - 1 if the sender provides a packet immediately. The interface remains in + * not receiving state. The sender may not use the provided buffer after the handler + * returns. + * - 0 if the sender cannot provide a packet immediately. The interface enters + * receiving state as the handler returns. The sender must write a packet to the + * provided buffer and call {@link PacketRecvInterface_Done} when it's done. + */ +typedef int (*PacketRecvInterface_handler_recv) (void *user, uint8_t *data, int *data_len); + +/** + * Handler called at the receiver when {@link PacketRecvInterface_Done} is called from the sender. + * The sender will no longer use the buffer it was provided with. + * It is guaranteed that the interface was in receiving state. + * The interface enters not receiving state before the handler is called. + * It is guaranteed that the handler is not being called from within Recv, Cancel or Done handlers. + * + * @param user value supplied to {@link PacketRecvInterface_Receiver_Init} + * @param data_len size of the packet that was written to the buffer. Will be >=0 and <=MTU. + */ +typedef void (*PacketRecvInterface_handler_done) (void *user, int data_len); + +/** + * Interface allowing a packet receiver to receive data packets from a packet sender. + * The receiver receives a packet by providing the sender with a buffer. The sender + * may then either provide the packet immediately, or tell the receiver to wait for + * the packet to be available and inform it when it's done. + */ +typedef struct { + DebugObject d_obj; + + // sender data + int mtu; + PacketRecvInterface_handler_recv handler_recv; + void *user_sender; + + // receiver data + PacketRecvInterface_handler_done handler_done; + void *user_receiver; + + // debug vars + #ifndef NDEBUG + dead_t debug_dead; + int debug_busy; + int debug_in_recv; + int debug_in_done; + #endif +} PacketRecvInterface; + +/** + * Initializes the interface. The receiver portion must also be initialized + * with {@link PacketRecvInterface_Receiver_Init} before I/O can start. + * The interface is initialized in not receiving state. + * + * @param i the object + * @param mtu maximum packet size the sender can provide. Must be >=0. + * @param handler_recv handler called when the receiver wants to receive a packet + * @param user arbitrary value that will be passed to sender callback functions + */ +static void PacketRecvInterface_Init (PacketRecvInterface *i, int mtu, PacketRecvInterface_handler_recv handler_recv, void *user); + +/** + * Frees the interface. + * + * @param i the object + */ +static void PacketRecvInterface_Free (PacketRecvInterface *i); + +/** + * Notifies the receiver that the sender has finished providing the packet being received. + * The sender must not use the buffer it was provided any more. + * The interface must be in receiving state. + * The interface enters not receiving state before notifying the receiver. + * Must not be called from within Recv, Cancel or Done handlers. + * + * Be aware that the receiver may attempt to receive packets from within this function. + * + * @param i the object + * @param data_len size of the packet written to the buffer. Must be >=0 and <=MTU. + */ +static void PacketRecvInterface_Done (PacketRecvInterface *i, int data_len); + +/** + * Returns the maximum packet size the sender can provide. + * + * @return maximum packet size. Will be >=0. + */ +static int PacketRecvInterface_GetMTU (PacketRecvInterface *i); + +/** + * Initializes the receiver portion of the interface. + * + * @param i the object + * @param handler_done handler called when the sender has finished providing a packet + * @param user arbitrary value that will be passed to receiver callback functions + */ +static void PacketRecvInterface_Receiver_Init (PacketRecvInterface *i, PacketRecvInterface_handler_done handler_done, void *user); + +/** + * Attempts to receive a packet. + * The interface must be in not receiving state. + * Must not be called from within Recv or Cancel handlers. + * + * @param i the object + * @param data pointer to the buffer where the packet is to be written. Must have space + * for MTU bytes. Ignored if MTU is 0. + * @param data_len will contain the size of the packet if it was provided immediately + * @return - 1 if a packet was provided by the sender immediately. The buffer is no longer needed. + * The interface remains in not receiving state. + * - 0 if a packet could not be provided immediately. + * The interface enters receiving state, and the buffer must stay accessible while the + * sender is providing the packet. When the sender is done providing it, the + * {@link PacketRecvInterface_handler_done} handler will be called. + */ +static int PacketRecvInterface_Receiver_Recv (PacketRecvInterface *i, uint8_t *data, int *data_len); + +#ifndef NDEBUG + +/** + * Determines if we are in a Recv call. + * Only available if NDEBUG is not defined. + * + * @param i the object + * @return 1 if in a Recv call, 0 if not + */ +static int PacketRecvInterface_InClient (PacketRecvInterface *i); + +/** + * Determines if we are in a Done call. + * Only available if NDEBUG is not defined. + * + * @param i the object + * @return 1 if in a Done call, 0 if not + */ +static int PacketRecvInterface_InDone (PacketRecvInterface *i); + +#endif + +void PacketRecvInterface_Init (PacketRecvInterface *i, int mtu, PacketRecvInterface_handler_recv handler_recv, void *user) +{ + ASSERT(mtu >= 0) + + i->mtu = mtu; + i->handler_recv = handler_recv; + i->user_sender = user; + i->handler_done = NULL; + i->user_receiver = NULL; + + // init debugging + #ifndef NDEBUG + DEAD_INIT(i->debug_dead); + i->debug_busy = 0; + i->debug_in_recv = 0; + i->debug_in_done = 0; + #endif + + // init debug object + DebugObject_Init(&i->d_obj); +} + +void PacketRecvInterface_Free (PacketRecvInterface *i) +{ + // free debug object + DebugObject_Free(&i->d_obj); + + // free debugging + #ifndef NDEBUG + DEAD_KILL(i->debug_dead); + #endif +} + +void PacketRecvInterface_Done (PacketRecvInterface *i, int data_len) +{ + ASSERT(i->debug_busy) + ASSERT(!i->debug_in_recv) + ASSERT(!i->debug_in_done) + ASSERT(data_len >= 0) + ASSERT(data_len <= i->mtu) + + #ifndef NDEBUG + i->debug_busy = 0; + i->debug_in_done = 1; + DEAD_ENTER(i->debug_dead) + #endif + + i->handler_done(i->user_receiver, data_len); + + #ifndef NDEBUG + if (DEAD_LEAVE(i->debug_dead)) { + return; + } + i->debug_in_done = 0; + #endif +} + +int PacketRecvInterface_GetMTU (PacketRecvInterface *i) +{ + return i->mtu; +} + +void PacketRecvInterface_Receiver_Init (PacketRecvInterface *i, PacketRecvInterface_handler_done handler_done, void *user) +{ + i->handler_done = handler_done; + i->user_receiver = user; +} + +int PacketRecvInterface_Receiver_Recv (PacketRecvInterface *i, uint8_t *data, int *data_len) +{ + ASSERT(!i->debug_busy) + ASSERT(!i->debug_in_recv) + ASSERT(!(i->mtu > 0) || data) + ASSERT(data_len) + + #ifndef NDEBUG + i->debug_in_recv = 1; + DEAD_ENTER(i->debug_dead) + #endif + + int res = i->handler_recv(i->user_sender, data, data_len); + + #ifndef NDEBUG + if (DEAD_LEAVE(i->debug_dead)) { + return -1; + } + ASSERT(i->debug_in_recv) + i->debug_in_recv = 0; + ASSERT(res == 0 || res == 1) + ASSERT(!(res == 1) || (*data_len >= 0 && *data_len <= i->mtu)) + if (!res) { + i->debug_busy = 1; + } + #endif + + return res; +} + +#ifndef NDEBUG + +int PacketRecvInterface_InClient (PacketRecvInterface *i) +{ + return i->debug_in_recv; +} + +int PacketRecvInterface_InDone (PacketRecvInterface *i) +{ + return i->debug_in_done; +} + +#endif + +#endif diff --git a/flow/PacketRecvNotifier.c b/flow/PacketRecvNotifier.c new file mode 100644 index 000000000..b34a195d3 --- /dev/null +++ b/flow/PacketRecvNotifier.c @@ -0,0 +1,156 @@ +/** + * @file PacketRecvNotifier.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include + +static int call_handler (PacketRecvNotifier *o, uint8_t *data, int data_len); +static int output_handler_recv (PacketRecvNotifier *o, uint8_t *data, int *data_len); +static void input_handler_done (PacketRecvNotifier *o, int data_len); + +int call_handler (PacketRecvNotifier *o, uint8_t *data, int data_len) +{ + ASSERT(o->handler) + ASSERT(!o->in_handler) + + #ifndef NDEBUG + o->in_handler = 1; + #endif + + DEAD_ENTER(o->dead) + o->handler(o->handler_user, data, data_len); + if (DEAD_LEAVE(o->dead)) { + return -1; + } + + #ifndef NDEBUG + o->in_handler = 0; + #endif + + return 0; +} + +int output_handler_recv (PacketRecvNotifier *o, uint8_t *data, int *data_len) +{ + ASSERT(!o->out_have) + ASSERT(!o->in_handler) + + DEAD_DECLARE + + // call recv on input + DEAD_ENTER2(o->dead) + int res = PacketRecvInterface_Receiver_Recv(o->input, data, data_len); + if (DEAD_LEAVE(o->dead)) { + return -1; + } + + ASSERT(res == 0 || res == 1) + + if (!res) { + // input blocking, continue in input_handler_done + #ifndef NDEBUG + o->out_have = 1; + #endif + o->out = data; + return 0; + } + + // if we have a handler, call it + if (o->handler) { + if (call_handler(o, data, *data_len) < 0) { + return -1; + } + } + + return 1; +} + +void input_handler_done (PacketRecvNotifier *o, int data_len) +{ + ASSERT(o->out_have) + ASSERT(!o->in_handler) + + #ifndef NDEBUG + o->out_have = 0; + #endif + + // if we have a handler, call it + if (o->handler) { + if (call_handler(o, o->out, data_len) < 0) { + return; + } + } + + PacketRecvInterface_Done(&o->output, data_len); + return; +} + +void PacketRecvNotifier_Init (PacketRecvNotifier *o, PacketRecvInterface *input) +{ + // set arguments + o->input = input; + + // init dead var + DEAD_INIT(o->dead); + + // init output + PacketRecvInterface_Init(&o->output, PacketRecvInterface_GetMTU(o->input), (PacketRecvInterface_handler_recv)output_handler_recv, o); + + // init input + PacketRecvInterface_Receiver_Init(o->input, (PacketRecvInterface_handler_done)input_handler_done, o); + + // set no handler + o->handler = NULL; + + // init debugging + #ifndef NDEBUG + o->out_have = 0; + o->in_handler = 0; + #endif + + // init debug object + DebugObject_Init(&o->d_obj); +} + +void PacketRecvNotifier_Free (PacketRecvNotifier *o) +{ + // free debug object + DebugObject_Free(&o->d_obj); + + // free output + PacketRecvInterface_Free(&o->output); + + // free dead var + DEAD_KILL(o->dead); +} + +PacketRecvInterface * PacketRecvNotifier_GetOutput (PacketRecvNotifier *o) +{ + return &o->output; +} + +void PacketRecvNotifier_SetHandler (PacketRecvNotifier *o, PacketRecvNotifier_handler_notify handler, void *user) +{ + o->handler = handler; + o->handler_user = user; +} diff --git a/flow/PacketRecvNotifier.h b/flow/PacketRecvNotifier.h new file mode 100644 index 000000000..9c77933d1 --- /dev/null +++ b/flow/PacketRecvNotifier.h @@ -0,0 +1,98 @@ +/** + * @file PacketRecvNotifier.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * A {@link PacketRecvInterface} layer that calls a handler function before + * providing a packet to output. + */ + +#ifndef BADVPN_FLOW_PACKETRECVNOTIFIER_H +#define BADVPN_FLOW_PACKETRECVNOTIFIER_H + +#include + +#include +#include +#include + +/** + * Handler function called when input has provided a packet (i.e. by returning + * 1 from Recv or calling Done), but before passing the packet on to output. + * + * @param user value specified in {@link PacketRecvNotifier_SetHandler} + * @param data packet provided by output (buffer provided by input) + * @param data_len size of the packet + */ +typedef void (*PacketRecvNotifier_handler_notify) (void *user, uint8_t *data, int data_len); + +/** + * A {@link PacketRecvInterface} layer that calls a handler function before + * providing a packet to output. + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + PacketRecvInterface output; + PacketRecvInterface *input; + PacketRecvNotifier_handler_notify handler; + void *handler_user; + uint8_t *out; + #ifndef NDEBUG + int out_have; + int in_handler; + #endif +} PacketRecvNotifier; + +/** + * Initializes the object. + * + * @param o the object + * @param input input interface + */ +void PacketRecvNotifier_Init (PacketRecvNotifier *o, PacketRecvInterface *input); + +/** + * Frees the object. + * + * @param o the object + */ +void PacketRecvNotifier_Free (PacketRecvNotifier *o); + +/** + * Returns the output interface. + * The MTU of the output interface will be the same as of the input interface. + * + * @param o the object + * @return output interface + */ +PacketRecvInterface * PacketRecvNotifier_GetOutput (PacketRecvNotifier *o); + +/** + * Configures a handler function to invoke before passing output packets to input. + * + * @param o the object + * @param handler handler function, or NULL to disable. + * @param user value to pass to handler function. Ignored if handler is NULL. + */ +void PacketRecvNotifier_SetHandler (PacketRecvNotifier *o, PacketRecvNotifier_handler_notify handler, void *user); + +#endif diff --git a/flow/PacketStreamSender.c b/flow/PacketStreamSender.c new file mode 100644 index 000000000..21dfc8d02 --- /dev/null +++ b/flow/PacketStreamSender.c @@ -0,0 +1,144 @@ +/** + * @file PacketStreamSender.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include + +#include + +static int send_data (PacketStreamSender *s) +{ + ASSERT(s->in_len >= 0) + + while (s->in_used < s->in_len) { + // attempt to send something + DEAD_ENTER(s->dead) + int res = StreamPassInterface_Sender_Send(s->output, s->in + s->in_used, s->in_len - s->in_used); + if (DEAD_LEAVE(s->dead)) { + return -1; + } + + ASSERT(res >= 0) + ASSERT(res <= s->in_len - s->in_used) + + if (res == 0) { + // output busy, continue in output_handler_done + return 0; + } + + // update number of bytes sent + s->in_used += res; + } + + // everything sent + s->in_len = -1; + + return 0; +} + +static int input_handler_send (PacketStreamSender *s, uint8_t *data, int data_len) +{ + ASSERT(s->in_len == -1) + ASSERT(data_len >= 0) + + // set input packet + s->in_len = data_len; + s->in = data; + s->in_used = 0; + + // try sending + if (send_data(s) < 0) { + return -1; + } + + // if we couldn't send everything, block input + if (s->in_len >= 0) { + return 0; + } + + return 1; +} + +static void output_handler_done (PacketStreamSender *s, int data_len) +{ + ASSERT(s->in_len >= 0) + ASSERT(data_len > 0) + ASSERT(data_len <= s->in_len - s->in_used) + + // update number of bytes sent + s->in_used += data_len; + + // continue sending + if (send_data(s) < 0) { + return; + } + + // if we couldn't send everything, keep input blocked + if (s->in_len >= 0) { + return; + } + + // allow more input + PacketPassInterface_Done(&s->input); + return; +} + +void PacketStreamSender_Init (PacketStreamSender *s, StreamPassInterface *output, int mtu) +{ + ASSERT(mtu >= 0) + + // init arguments + s->output = output; + + // init dead var + DEAD_INIT(s->dead); + + // init input + PacketPassInterface_Init(&s->input, mtu, (PacketPassInterface_handler_send)input_handler_send, s); + + // init output + StreamPassInterface_Sender_Init(s->output, (StreamPassInterface_handler_done)output_handler_done, s); + + // have no input packet + s->in_len = -1; + + // init debug object + DebugObject_Init(&s->d_obj); +} + +void PacketStreamSender_Free (PacketStreamSender *s) +{ + // free debug object + DebugObject_Free(&s->d_obj); + + // free input + PacketPassInterface_Free(&s->input); + + // free dead var + DEAD_KILL(s->dead); +} + +PacketPassInterface * PacketStreamSender_GetInput (PacketStreamSender *s) +{ + return &s->input; +} diff --git a/flow/PacketStreamSender.h b/flow/PacketStreamSender.h new file mode 100644 index 000000000..6ea258c69 --- /dev/null +++ b/flow/PacketStreamSender.h @@ -0,0 +1,77 @@ +/** + * @file PacketStreamSender.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Object which forwards packets obtained with {@link PacketPassInterface} + * as a stream with {@link StreamPassInterface} (i.e. it concatenates them). + */ + +#ifndef BADVPN_FLOW_PACKETSTREAMSENDER_H +#define BADVPN_FLOW_PACKETSTREAMSENDER_H + +#include + +#include +#include +#include +#include + +/** + * Object which forwards packets obtained with {@link PacketPassInterface} + * as a stream with {@link StreamPassInterface} (i.e. it concatenates them). + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + PacketPassInterface input; + StreamPassInterface *output; + int in_len; + uint8_t *in; + int in_used; +} PacketStreamSender; + +/** + * Initializes the object. + * + * @param s the object + * @param output output interface + * @param mtu input MTU. Must be >=0. + */ +void PacketStreamSender_Init (PacketStreamSender *s, StreamPassInterface *output, int mtu); + +/** + * Frees the object. + * + * @param s the object + */ +void PacketStreamSender_Free (PacketStreamSender *s); + +/** + * Returns the input interface. + * Its MTU will be as in {@link PacketStreamSender_Init}. + * + * @param s the object + * @return input interface + */ +PacketPassInterface * PacketStreamSender_GetInput (PacketStreamSender *s); + +#endif diff --git a/flow/SCKeepaliveSource.c b/flow/SCKeepaliveSource.c new file mode 100644 index 000000000..d2bda6dba --- /dev/null +++ b/flow/SCKeepaliveSource.c @@ -0,0 +1,58 @@ +/** + * @file SCKeepaliveSource.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include + +#include + +static int output_handler_recv (SCKeepaliveSource *o, uint8_t *data, int *data_len) +{ + struct sc_header *header = (struct sc_header *)data; + header->type = SCID_KEEPALIVE; + + *data_len = sizeof(struct sc_header); + return 1; +} + +void SCKeepaliveSource_Init (SCKeepaliveSource *o) +{ + // init output + PacketRecvInterface_Init(&o->output, sizeof(struct sc_header), (PacketRecvInterface_handler_recv)output_handler_recv, o); + + // init debug object + DebugObject_Init(&o->d_obj); +} + +void SCKeepaliveSource_Free (SCKeepaliveSource *o) +{ + // free debug object + DebugObject_Free(&o->d_obj); + + // free output + PacketRecvInterface_Free(&o->output); +} + +PacketRecvInterface * SCKeepaliveSource_GetOutput (SCKeepaliveSource *o) +{ + return &o->output; +} diff --git a/flow/SCKeepaliveSource.h b/flow/SCKeepaliveSource.h new file mode 100644 index 000000000..b453f2efb --- /dev/null +++ b/flow/SCKeepaliveSource.h @@ -0,0 +1,64 @@ +/** + * @file SCKeepaliveSource.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * A {@link PacketRecvInterface} source which provides SCProto keepalive packets. + */ + +#ifndef BADVPN_FLOW_SCKEEPALIVESOURCE_H +#define BADVPN_FLOW_SCKEEPALIVESOURCE_H + +#include +#include + +/** + * A {@link PacketRecvInterface} source which provides SCProto keepalive packets. + */ +typedef struct { + DebugObject d_obj; + PacketRecvInterface output; +} SCKeepaliveSource; + +/** + * Initializes the object. + * + * @param o the object + */ +void SCKeepaliveSource_Init (SCKeepaliveSource *o); + +/** + * Frees the object. + * + * @param o the object + */ +void SCKeepaliveSource_Free (SCKeepaliveSource *o); + +/** + * Returns the output interface. + * The MTU of the output interface will be sizeof(struct sc_header). + * + * @param o the object + * @return output interface + */ +PacketRecvInterface * SCKeepaliveSource_GetOutput (SCKeepaliveSource *o); + +#endif diff --git a/flow/SPProtoDecoder.c b/flow/SPProtoDecoder.c new file mode 100644 index 000000000..f713cc1dc --- /dev/null +++ b/flow/SPProtoDecoder.c @@ -0,0 +1,309 @@ +/** + * @file SPProtoDecoder.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include +#include +#include +#include + +#include + +static int decode_packet (SPProtoDecoder *o, uint8_t *in, int in_len, uint8_t **out, int *out_len) +{ + ASSERT(in_len >= 0) + ASSERT(in_len <= o->input_mtu) + + uint8_t *plaintext; + int plaintext_len; + + // decrypt if needed + if (!SPPROTO_HAVE_ENCRYPTION(o->sp_params)) { + plaintext = in; + plaintext_len = in_len; + } else { + // check length + if (in_len % o->enc_block_size != 0) { + DEBUG("packet size not a multiple of block size"); + return 0; + } + if (in_len < o->enc_block_size) { + DEBUG("packet does not have an IV"); + return 0; + } + // check if we have encryption key + if (!o->have_encryption_key) { + DEBUG("have no encryption key"); + return 0; + } + // copy IV as BEncryption_Decrypt changes the IV + uint8_t iv[o->enc_block_size]; + memcpy(iv, in, o->enc_block_size); + // decrypt + uint8_t *ciphertext = in + o->enc_block_size; + int ciphertext_len = in_len - o->enc_block_size; + plaintext = o->buf; + BEncryption_Decrypt(&o->encryptor, ciphertext, plaintext, ciphertext_len, iv); + // read padding + if (ciphertext_len < o->enc_block_size) { + DEBUG("packet does not have a padding block"); + return 0; + } + int i; + for (i = ciphertext_len - 1; i >= ciphertext_len - o->enc_block_size; i--) { + if (plaintext[i] == 1) { + break; + } + if (plaintext[i] != 0) { + DEBUG("packet padding wrong (nonzero byte)"); + return 0; + } + } + if (i < ciphertext_len - o->enc_block_size) { + DEBUG("packet padding wrong (all zeroes)"); + return 0; + } + plaintext_len = i; + } + + // check for header + if (plaintext_len < SPPROTO_HEADER_LEN(o->sp_params)) { + DEBUG("packet has no header"); + return 0; + } + uint8_t *header = plaintext; + + // check data length + if (plaintext_len - SPPROTO_HEADER_LEN(o->sp_params) > o->output_mtu) { + DEBUG("packet too long"); + return 0; + } + + // check OTP + if (SPPROTO_HAVE_OTP(o->sp_params)) { + struct spproto_otpdata *header_otpd = (struct spproto_otpdata *)(header + SPPROTO_HEADER_OTPDATA_OFF(o->sp_params)); + if (!OTPChecker_CheckOTP(&o->otpchecker, ltoh16(header_otpd->seed_id), header_otpd->otp)) { + DEBUG("packet has wrong OTP"); + return 0; + } + } + + // check hash + if (SPPROTO_HAVE_HASH(o->sp_params)) { + uint8_t *header_hash = header + SPPROTO_HEADER_HASH_OFF(o->sp_params); + // read hash + uint8_t hash[o->hash_size]; + memcpy(hash, header_hash, o->hash_size); + // zero hash in packet + memset(header_hash, 0, o->hash_size); + // calculate hash + uint8_t hash_calc[o->hash_size]; + BHash_calculate(o->sp_params.hash_mode, plaintext, plaintext_len, hash_calc); + // set hash field to its original value + memcpy(header_hash, hash, o->hash_size); + // compare hashes + if (memcmp(hash, hash_calc, o->hash_size)) { + DEBUG("packet has wrong hash"); + return 0; + } + } + + // return packet + *out = plaintext + SPPROTO_HEADER_LEN(o->sp_params); + *out_len = plaintext_len - SPPROTO_HEADER_LEN(o->sp_params); + return 1; +} + +static int input_handler_send (SPProtoDecoder *o, uint8_t *data, int data_len) +{ + ASSERT(data_len >= 0) + ASSERT(data_len <= o->input_mtu) + + // attempt to decode packet + uint8_t *out; + int out_len; + if (!decode_packet(o, data, data_len, &out, &out_len)) { + return 1; + } + + // submit decoded packet to output + DEAD_ENTER(o->dead) + int res = PacketPassInterface_Sender_Send(o->output, out, out_len); + if (DEAD_LEAVE(o->dead)) { + return -1; + } + + ASSERT(res == 0 || res == 1) + + return res; +} + +static void output_handler_done (SPProtoDecoder *o) +{ + PacketPassInterface_Done(&o->input); + return; +} + +int SPProtoDecoder_Init (SPProtoDecoder *o, PacketPassInterface *output, struct spproto_security_params sp_params, int num_otp_seeds) +{ + ASSERT(spproto_validate_security_params(sp_params)) + ASSERT(!SPPROTO_HAVE_OTP(sp_params) || num_otp_seeds >= 2) + + // init arguments + o->output = output; + o->sp_params = sp_params; + + // init dead var + DEAD_INIT(o->dead); + + // init output + PacketPassInterface_Sender_Init(o->output, (PacketPassInterface_handler_done)output_handler_done, o); + + // remember output MTU + o->output_mtu = PacketPassInterface_GetMTU(o->output); + + // calculate hash size + if (SPPROTO_HAVE_HASH(o->sp_params)) { + o->hash_size = BHash_size(o->sp_params.hash_mode); + } + + // calculate encryption block and key sizes + if (SPPROTO_HAVE_ENCRYPTION(o->sp_params)) { + o->enc_block_size = BEncryption_cipher_block_size(o->sp_params.encryption_mode); + o->enc_key_size = BEncryption_cipher_key_size(o->sp_params.encryption_mode); + } + + // calculate input MTU + o->input_mtu = spproto_carrier_mtu_for_payload_mtu(o->sp_params, o->output_mtu); + + // allocate plaintext buffer + if (SPPROTO_HAVE_ENCRYPTION(o->sp_params)) { + int buf_size = BALIGN_UP_N((SPPROTO_HEADER_LEN(o->sp_params) + o->output_mtu + 1), o->enc_block_size); + if (!(o->buf = malloc(buf_size))) { + goto fail0; + } + } + + // init input + PacketPassInterface_Init(&o->input, o->input_mtu, (PacketPassInterface_handler_send)input_handler_send, o); + + // init OTP checker + if (SPPROTO_HAVE_OTP(o->sp_params)) { + if (!OTPChecker_Init(&o->otpchecker, o->sp_params.otp_num, o->sp_params.otp_mode, num_otp_seeds)) { + goto fail1; + } + } + + // have no encryption key + if (SPPROTO_HAVE_ENCRYPTION(o->sp_params)) { + o->have_encryption_key = 0; + } + + // init debug object + DebugObject_Init(&o->d_obj); + + return 1; + +fail1: + PacketPassInterface_Free(&o->input); + if (SPPROTO_HAVE_ENCRYPTION(o->sp_params)) { + free(o->buf); + } +fail0: + return 0; +} + +void SPProtoDecoder_Free (SPProtoDecoder *o) +{ + // free debug object + DebugObject_Free(&o->d_obj); + + // free encryptor + if (SPPROTO_HAVE_ENCRYPTION(o->sp_params) && o->have_encryption_key) { + BEncryption_Free(&o->encryptor); + } + + // free OTP checker + if (SPPROTO_HAVE_OTP(o->sp_params)) { + OTPChecker_Free(&o->otpchecker); + } + + // free input + PacketPassInterface_Free(&o->input); + + // free plaintext buffer + if (SPPROTO_HAVE_ENCRYPTION(o->sp_params)) { + free(o->buf); + } + + // kill dead var + DEAD_KILL(o->dead); +} + +PacketPassInterface * SPProtoDecoder_GetInput (SPProtoDecoder *o) +{ + return &o->input; +} + +void SPProtoDecoder_SetEncryptionKey (SPProtoDecoder *o, uint8_t *encryption_key) +{ + ASSERT(SPPROTO_HAVE_ENCRYPTION(o->sp_params)) + + // free encryptor + if (o->have_encryption_key) { + BEncryption_Free(&o->encryptor); + } + + // init encryptor + BEncryption_Init(&o->encryptor, BENCRYPTION_MODE_DECRYPT, o->sp_params.encryption_mode, encryption_key); + + // have encryption key + o->have_encryption_key = 1; +} + +void SPProtoDecoder_RemoveEncryptionKey (SPProtoDecoder *o) +{ + ASSERT(SPPROTO_HAVE_ENCRYPTION(o->sp_params)) + + if (o->have_encryption_key) { + // free encryptor + BEncryption_Free(&o->encryptor); + + // have no encryption key + o->have_encryption_key = 0; + } +} + +void SPProtoDecoder_AddOTPSeed (SPProtoDecoder *o, uint16_t seed_id, uint8_t *key, uint8_t *iv) +{ + ASSERT(SPPROTO_HAVE_OTP(o->sp_params)) + + OTPChecker_AddSeed(&o->otpchecker, seed_id, key, iv); +} + +void SPProtoDecoder_RemoveOTPSeeds (SPProtoDecoder *o) +{ + ASSERT(SPPROTO_HAVE_OTP(o->sp_params)) + + OTPChecker_RemoveSeeds(&o->otpchecker); +} diff --git a/flow/SPProtoDecoder.h b/flow/SPProtoDecoder.h new file mode 100644 index 000000000..95f103471 --- /dev/null +++ b/flow/SPProtoDecoder.h @@ -0,0 +1,127 @@ +/** + * @file SPProtoDecoder.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Object which decodes packets according to SPProto. + */ + +#ifndef BADVPN_FLOW_SPPROTODECODER_H +#define BADVPN_FLOW_SPPROTODECODER_H + +#include + +#include +#include +#include +#include +#include +#include + +/** + * Object which decodes packets according to SPProto. + * Input is with {@link PacketPassInterface}. + * Output is with {@link PacketPassInterface}. + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + PacketPassInterface *output; + int output_mtu; + struct spproto_security_params sp_params; + int hash_size; + int enc_block_size; + int enc_key_size; + int input_mtu; + uint8_t *buf; + PacketPassInterface input; + OTPChecker otpchecker; + int have_encryption_key; + BEncryption encryptor; +} SPProtoDecoder; + +/** + * Initializes the object. + * + * @param o the object + * @param output output interface + * @param sp_params CCProto parameters. Must be valid. + * @param encryption_key if using encryption, the encryption key + * @param num_otp_seeds if using OTPs, how many OTP seeds to keep for checking + * receiving packets. Must be >=2 if using OTPs. + * @return 1 on success, 0 on failure + */ +int SPProtoDecoder_Init (SPProtoDecoder *o, PacketPassInterface *output, struct spproto_security_params sp_params, int num_otp_seeds) WARN_UNUSED; + +/** + * Frees the object. + * + * @param o the object + */ +void SPProtoDecoder_Free (SPProtoDecoder *o); + +/** + * Returns the input interface. + * The MTU of the input interface will depend on the output MTU and security parameters, + * that is spproto_carrier_mtu_for_payload_mtu(sp_params, output MTU). + * + * @param o the object + * @return input interface + */ +PacketPassInterface * SPProtoDecoder_GetInput (SPProtoDecoder *o); + +/** + * Sets an encryption key for decrypting packets. + * Encryption must be enabled. + * + * @param o the object + * @param encryption_key key to use + */ +void SPProtoDecoder_SetEncryptionKey (SPProtoDecoder *o, uint8_t *encryption_key); + +/** + * Removes an encryption key if one is configured. + * Encryption must be enabled. + * + * @param o the object + */ +void SPProtoDecoder_RemoveEncryptionKey (SPProtoDecoder *o); + +/** + * Adds a new OTP seed to check received packets against. + * OTPs must be enabled. + * + * @param o the object + * @param seed_id seed identifier + * @param key OTP encryption key + * @param iv OTP initialization vector + */ +void SPProtoDecoder_AddOTPSeed (SPProtoDecoder *o, uint16_t seed_id, uint8_t *key, uint8_t *iv); + +/** + * Removes all OTP seeds for checking received packets against. + * OTPs must be enabled. + * + * @param o the object + */ +void SPProtoDecoder_RemoveOTPSeeds (SPProtoDecoder *o); + +#endif diff --git a/flow/SPProtoEncoder.c b/flow/SPProtoEncoder.c new file mode 100644 index 000000000..b1c6b6ce4 --- /dev/null +++ b/flow/SPProtoEncoder.c @@ -0,0 +1,391 @@ +/** + * @file SPProtoEncoder.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include + +static int can_encode (SPProtoEncoder *o) +{ + ASSERT(o->in_len >= 0) + ASSERT(o->out_have) + + return ( + (!SPPROTO_HAVE_OTP(o->group->sp_params) || OTPGenerator_GetPosition(&o->group->otpgen) < o->group->sp_params.otp_num) && + (!SPPROTO_HAVE_ENCRYPTION(o->group->sp_params) || o->group->have_encryption_key) + ); +} + +static int encode_packet (SPProtoEncoder *o) +{ + ASSERT(o->in_len >= 0) + ASSERT(o->in_len <= o->input_mtu) + ASSERT(o->out_have) + ASSERT(can_encode(o)) + + // plaintext is either output packet or our buffer + uint8_t *plaintext = (!SPPROTO_HAVE_ENCRYPTION(o->group->sp_params) ? o->out : o->buf); + + // plaintext begins with header + uint8_t *header = plaintext; + + // plaintext is header + payload + int plaintext_len = SPPROTO_HEADER_LEN(o->group->sp_params) + o->in_len; + + // write OTP + if (SPPROTO_HAVE_OTP(o->group->sp_params)) { + struct spproto_otpdata *header_otpd = (struct spproto_otpdata *)(header + SPPROTO_HEADER_OTPDATA_OFF(o->group->sp_params)); + header_otpd->seed_id = o->group->otpgen_seed_id; + header_otpd->otp = OTPGenerator_GetOTP(&o->group->otpgen); + } + + // write hash + if (SPPROTO_HAVE_HASH(o->group->sp_params)) { + uint8_t *header_hash = header + SPPROTO_HEADER_HASH_OFF(o->group->sp_params); + // zero hash field + memset(header_hash, 0, o->group->hash_size); + // calculate hash + uint8_t hash[o->group->hash_size]; + BHash_calculate(o->group->sp_params.hash_mode, plaintext, plaintext_len, hash); + // set hash field + memcpy(header_hash, hash, o->group->hash_size); + } + + int out_len; + + if (SPPROTO_HAVE_ENCRYPTION(o->group->sp_params)) { + // encrypting pad(header + payload) + int cyphertext_len = BALIGN_UP_N((plaintext_len + 1), o->group->enc_block_size); + // write padding + plaintext[plaintext_len] = 1; + for (int i = plaintext_len + 1; i < cyphertext_len; i++) { + plaintext[i] = 0; + } + // generate IV + brandom_randomize(o->out, o->group->enc_block_size); + // copy IV because BEncryption_Encrypt changes the IV + uint8_t iv[o->group->enc_block_size]; + memcpy(iv, o->out, o->group->enc_block_size); + // encrypt + BEncryption_Encrypt(&o->group->encryptor, plaintext, o->out + o->group->enc_block_size, cyphertext_len, iv); + out_len = o->group->enc_block_size + cyphertext_len; + } else { + out_len = plaintext_len; + } + + o->in_len = -1; + o->out_have = 0; + + return out_len; +} + +static int output_handler_recv (SPProtoEncoder *o, uint8_t *data, int *data_len) +{ + ASSERT(o->in_len == -1) + ASSERT(!o->out_have) + + // remember output packet + o->out_have = 1; + o->out = data; + + // determine plaintext location + uint8_t *plaintext = (!SPPROTO_HAVE_ENCRYPTION(o->group->sp_params) ? o->out : o->buf); + + // try to receive input packet + int in_len; + DEAD_ENTER(o->dead) + int res = PacketRecvInterface_Receiver_Recv(o->input, plaintext + SPPROTO_HEADER_LEN(o->group->sp_params), &in_len); + if (DEAD_LEAVE(o->dead)) { + return -1; + } + + ASSERT(res == 0 || res == 1) + + if (!res) { + return 0; + } + + ASSERT(in_len >= 0 && in_len <= o->input_mtu) + + // remember input packet + o->in_len = in_len; + + // check if we can encode + if (!can_encode(o)) { + return 0; + } + + // encode + *data_len = encode_packet(o); + + return 1; +} + +static void input_handler_done (SPProtoEncoder *o, int in_len) +{ + ASSERT(o->in_len == -1) + ASSERT(o->out_have) + ASSERT(in_len >= 0 && in_len <= o->input_mtu) + + // remember input packet + o->in_len = in_len; + + // check if we can encode + if (!can_encode(o)) { + return; + } + + // encode + int out_len = encode_packet(o); + + // inform output + PacketRecvInterface_Done(&o->output, out_len); + return; +} + +static void job_handler (SPProtoEncoder *o) +{ + if (o->in_len >= 0 && o->out_have && can_encode(o)) { + // encode + int out_len = encode_packet(o); + + // inform output + PacketRecvInterface_Done(&o->output, out_len); + return; + } +} + +static void schedule_jobs (SPProtoEncoderGroup *o) +{ + LinkedList2Iterator it; + LinkedList2Iterator_InitForward(&it, &o->encoders_list); + LinkedList2Node *node; + while (node = LinkedList2Iterator_Next(&it)) { + SPProtoEncoder *enc = UPPER_OBJECT(node, SPProtoEncoder, group_list_node); + BPending_Set(&enc->continue_job); + } +} + +int SPProtoEncoderGroup_Init (SPProtoEncoderGroup *o, struct spproto_security_params sp_params) +{ + ASSERT(spproto_validate_security_params(sp_params)) + + // init parameters + o->sp_params = sp_params; + + // calculate hash size + if (SPPROTO_HAVE_HASH(o->sp_params)) { + o->hash_size = BHash_size(o->sp_params.hash_mode); + } + + // calculate encryption block and key sizes + if (SPPROTO_HAVE_ENCRYPTION(o->sp_params)) { + o->enc_block_size = BEncryption_cipher_block_size(o->sp_params.encryption_mode); + o->enc_key_size = BEncryption_cipher_key_size(o->sp_params.encryption_mode); + } + + // init otp generator + if (SPPROTO_HAVE_OTP(o->sp_params)) { + if (!OTPGenerator_Init(&o->otpgen, o->sp_params.otp_num, o->sp_params.otp_mode)) { + goto fail1; + } + } + + // have no encryption key + if (SPPROTO_HAVE_ENCRYPTION(o->sp_params)) { + o->have_encryption_key = 0; + } + + // init encoders list + LinkedList2_Init(&o->encoders_list); + + // init debug object + DebugObject_Init(&o->d_obj); + + return 1; + +fail1: + return 0; +} + +void SPProtoEncoderGroup_Free (SPProtoEncoderGroup *o) +{ + ASSERT(LinkedList2_IsEmpty(&o->encoders_list)) + DebugObject_Free(&o->d_obj); + + // free encryptor + if (SPPROTO_HAVE_ENCRYPTION(o->sp_params) && o->have_encryption_key) { + BEncryption_Free(&o->encryptor); + } + + // free otp generator + if (SPPROTO_HAVE_OTP(o->sp_params)) { + OTPGenerator_Free(&o->otpgen); + } +} + +void SPProtoEncoderGroup_SetEncryptionKey (SPProtoEncoderGroup *o, uint8_t *encryption_key) +{ + ASSERT(SPPROTO_HAVE_ENCRYPTION(o->sp_params)) + + // free encryptor + if (o->have_encryption_key) { + BEncryption_Free(&o->encryptor); + } + + // init encryptor + BEncryption_Init(&o->encryptor, BENCRYPTION_MODE_ENCRYPT, o->sp_params.encryption_mode, encryption_key); + + // have encryption key + o->have_encryption_key = 1; + + // set jobs + schedule_jobs(o); +} + +void SPProtoEncoderGroup_RemoveEncryptionKey (SPProtoEncoderGroup *o) +{ + ASSERT(SPPROTO_HAVE_ENCRYPTION(o->sp_params)) + + if (o->have_encryption_key) { + // free encryptor + BEncryption_Free(&o->encryptor); + + // have no encryption key + o->have_encryption_key = 0; + } +} + +void SPProtoEncoderGroup_SetOTPSeed (SPProtoEncoderGroup *o, uint16_t seed_id, uint8_t *key, uint8_t *iv) +{ + ASSERT(SPPROTO_HAVE_OTP(o->sp_params)) + + // give seed to OTP generator + OTPGenerator_SetSeed(&o->otpgen, key, iv); + + // remember seed ID + o->otpgen_seed_id = seed_id; + + // set jobs + schedule_jobs(o); +} + +void SPProtoEncoderGroup_RemoveOTPSeed (SPProtoEncoderGroup *o) +{ + ASSERT(SPPROTO_HAVE_OTP(o->sp_params)) + + // reset OTP generator + OTPGenerator_Reset(&o->otpgen); +} + +int SPProtoEncoderGroup_GetOTPPosition (SPProtoEncoderGroup *o) +{ + ASSERT(SPPROTO_HAVE_OTP(o->sp_params)) + + return OTPGenerator_GetPosition(&o->otpgen); +} + +int SPProtoEncoder_Init (SPProtoEncoder *o, SPProtoEncoderGroup *group, PacketRecvInterface *input, BPendingGroup *pg) +{ + // init parameters + o->group = group; + o->input = input; + + // init dead var + DEAD_INIT(o->dead); + + // remember input MTU + o->input_mtu = PacketRecvInterface_GetMTU(o->input); + + // calculate output MTU + o->output_mtu = spproto_carrier_mtu_for_payload_mtu(o->group->sp_params, o->input_mtu); + + // init input + PacketRecvInterface_Receiver_Init(o->input, (PacketRecvInterface_handler_done)input_handler_done, o); + + // have no input in buffer + o->in_len = -1; + + // init output + PacketRecvInterface_Init(&o->output, o->output_mtu, (PacketRecvInterface_handler_recv)output_handler_recv, o); + + // have no output available + o->out_have = 0; + + // allocate plaintext buffer + if (SPPROTO_HAVE_ENCRYPTION(o->group->sp_params)) { + int buf_size = BALIGN_UP_N((SPPROTO_HEADER_LEN(o->group->sp_params) + o->input_mtu + 1), o->group->enc_block_size); + if (!(o->buf = malloc(buf_size))) { + goto fail1; + } + } + + // insert to group list + LinkedList2_Append(&o->group->encoders_list, &o->group_list_node); + + // init pending job + BPending_Init(&o->continue_job, pg, (BPending_handler)job_handler, o); + + // init debug object + DebugObject_Init(&o->d_obj); + + return 1; + +fail1: + PacketRecvInterface_Free(&o->output); + return 0; +} + +void SPProtoEncoder_Free (SPProtoEncoder *o) +{ + // free debug object + DebugObject_Free(&o->d_obj); + + // free pending job + BPending_Free(&o->continue_job); + + // remove from group list + LinkedList2_Remove(&o->group->encoders_list, &o->group_list_node); + + // free plaintext buffer + if (SPPROTO_HAVE_ENCRYPTION(o->group->sp_params)) { + free(o->buf); + } + + // free output + PacketRecvInterface_Free(&o->output); + + // kill dead var + DEAD_KILL(o->dead); +} + +PacketRecvInterface * SPProtoEncoder_GetOutput (SPProtoEncoder *o) +{ + return &o->output; +} diff --git a/flow/SPProtoEncoder.h b/flow/SPProtoEncoder.h new file mode 100644 index 000000000..b2572bea4 --- /dev/null +++ b/flow/SPProtoEncoder.h @@ -0,0 +1,172 @@ +/** + * @file SPProtoEncoder.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Object which encodes packets according to SPProto. + */ + +#ifndef BADVPN_FLOW_SPPROTOENCODER_H +#define BADVPN_FLOW_SPPROTOENCODER_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Object shared between {@link SPProtoEncoder} objects using the same security parameters and resources. + */ +typedef struct { + DebugObject d_obj; + struct spproto_security_params sp_params; + int hash_size; + int enc_block_size; + int enc_key_size; + OTPGenerator otpgen; + uint16_t otpgen_seed_id; + int have_encryption_key; + BEncryption encryptor; + LinkedList2 encoders_list; +} SPProtoEncoderGroup; + +/** + * Initializes the object. + * + * @param o the object + * @param sp_params SPProto security parameters. Must be valid according to {@link spproto_validate_security_params}. + * @return 1 on success, 0 on failure + */ +int SPProtoEncoderGroup_Init (SPProtoEncoderGroup *o, struct spproto_security_params sp_params) WARN_UNUSED; + +/** + * Frees the object. + * There must be no encoders using this group. + * + * @param o the object + */ +void SPProtoEncoderGroup_Free (SPProtoEncoderGroup *o); + +/** + * Sets an encryption key to use. + * Encryption must be enabled. + * + * @param o the object + * @param encryption_key key to use + */ +void SPProtoEncoderGroup_SetEncryptionKey (SPProtoEncoderGroup *o, uint8_t *encryption_key); + +/** + * Removes an encryption key if one is configured. + * Encryption must be enabled. + * + * @param o the object + */ +void SPProtoEncoderGroup_RemoveEncryptionKey (SPProtoEncoderGroup *o); + +/** + * Sets an OTP seed to use. + * OTPs must be enabled. + * + * @param o the object + * @param seed_id seed identifier + * @param key OTP encryption key + * @param iv OTP initialization vector + */ +void SPProtoEncoderGroup_SetOTPSeed (SPProtoEncoderGroup *o, uint16_t seed_id, uint8_t *key, uint8_t *iv); + +/** + * Removes the OTP seed if one is configured. + * OTPs must be enabled. + * + * @param o the object + */ +void SPProtoEncoderGroup_RemoveOTPSeed (SPProtoEncoderGroup *o); + +/** + * Returns the number of OTPs used so far, or total number if + * no seed has been set yet. + * OTPs must be enabled. + * + * @param o the object + * @return OTP position + */ +int SPProtoEncoderGroup_GetOTPPosition (SPProtoEncoderGroup *o); + +/** + * Object which encodes packets according to SPProto. + * + * Input is with {@link PacketRecvInterface}. + * Output is with {@link PacketRecvInterface}. + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + SPProtoEncoderGroup *group; + int input_mtu; + int output_mtu; + PacketRecvInterface *input; + int in_len; + PacketRecvInterface output; + int out_have; + uint8_t *out; + uint8_t *buf; + LinkedList2Node group_list_node; + BPending continue_job; +} SPProtoEncoder; + +/** + * Initializes the object. + * The object is initialized in blocked state. + * + * @param o the object + * @param group {@link SPProtoEncoderGroup} object to use for security parameters and resources + * @param input input interface + * @param pg pending group + * @return 1 on success, 0 on failure + */ +int SPProtoEncoder_Init (SPProtoEncoder *o, SPProtoEncoderGroup *group, PacketRecvInterface *input, BPendingGroup *pg) WARN_UNUSED; + +/** + * Frees the object. + * + * @param o the object + */ +void SPProtoEncoder_Free (SPProtoEncoder *o); + +/** + * Returns the output interface. + * The MTU of the output interface will depend on the input MTU and security parameters, + * that is spproto_carrier_mtu_for_payload_mtu(sp_params, input MTU). + * + * @param o the object + * @return output interface + */ +PacketRecvInterface * SPProtoEncoder_GetOutput (SPProtoEncoder *o); + +#endif diff --git a/flow/SinglePacketBuffer.c b/flow/SinglePacketBuffer.c new file mode 100644 index 000000000..907ba1ada --- /dev/null +++ b/flow/SinglePacketBuffer.c @@ -0,0 +1,146 @@ +/** + * @file SinglePacketBuffer.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include + +#include + +static int io_loop (SinglePacketBuffer *o) +{ + DEAD_DECLARE + int res; + + while (1) { + // receive packet + int in_len; + DEAD_ENTER2(o->dead) + res = PacketRecvInterface_Receiver_Recv(o->input, o->buf, &in_len); + if (DEAD_LEAVE(o->dead)) { + return -1; + } + + ASSERT(res == 0 || res == 1) + + if (!res) { + // input blocking, continue in input_handler_done + return 0; + } + + // send packet + DEAD_ENTER2(o->dead) + res = PacketPassInterface_Sender_Send(o->output, o->buf, in_len); + if (DEAD_LEAVE(o->dead)) { + return -1; + } + + ASSERT(res == 0 || res == 1) + + if (!res) { + // output blocking, continue in output_handler_done + return 0; + } + } +} + +static void input_handler_done (SinglePacketBuffer *o, int in_len) +{ + // send packet + DEAD_ENTER(o->dead) + int res = PacketPassInterface_Sender_Send(o->output, o->buf, in_len); + if (DEAD_LEAVE(o->dead)) { + return; + } + + ASSERT(res == 0 || res == 1) + + if (!res) { + // output blocking, continue in output_handler_done + return; + } + + io_loop(o); + return; +} + +static void output_handler_done (SinglePacketBuffer *o) +{ + io_loop(o); + return; +} + +static void job_handler (SinglePacketBuffer *o) +{ + io_loop(o); + return; +} + +int SinglePacketBuffer_Init (SinglePacketBuffer *o, PacketRecvInterface *input, PacketPassInterface *output, BPendingGroup *pg) +{ + ASSERT(PacketPassInterface_GetMTU(output) >= PacketRecvInterface_GetMTU(input)) + + // init arguments + o->input = input; + o->output = output; + + // init dead var + DEAD_INIT(o->dead); + + // init input + PacketRecvInterface_Receiver_Init(o->input, (PacketRecvInterface_handler_done)input_handler_done, o); + + // init output + PacketPassInterface_Sender_Init(o->output, (PacketPassInterface_handler_done)output_handler_done, o); + + // init buffer + if (!(o->buf = malloc(PacketRecvInterface_GetMTU(o->input)))) { + goto fail1; + } + + // init start job + BPending_Init(&o->start_job, pg, (BPending_handler)job_handler, o); + BPending_Set(&o->start_job); + + // init debug object + DebugObject_Init(&o->d_obj); + + return 1; + +fail1: + return 0; +} + +void SinglePacketBuffer_Free (SinglePacketBuffer *o) +{ + // free debug object + DebugObject_Free(&o->d_obj); + + // free start job + BPending_Free(&o->start_job); + + // free buffer + free(o->buf); + + // free dead var + DEAD_KILL(o->dead); +} diff --git a/flow/SinglePacketBuffer.h b/flow/SinglePacketBuffer.h new file mode 100644 index 000000000..8ebf9418b --- /dev/null +++ b/flow/SinglePacketBuffer.h @@ -0,0 +1,72 @@ +/** + * @file SinglePacketBuffer.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Packet buffer with {@link PacketRecvInterface} input and {@link PacketPassInterface} output + * than can store only a single packet. + */ + +#ifndef BADVPN_FLOW_SINGLEPACKETBUFFER_H +#define BADVPN_FLOW_SINGLEPACKETBUFFER_H + +#include + +#include +#include +#include +#include +#include +#include + +/** + * Packet buffer with {@link PacketRecvInterface} input and {@link PacketPassInterface} output + * than can store only a single packet. + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + PacketRecvInterface *input; + PacketPassInterface *output; + uint8_t *buf; + BPending start_job; +} SinglePacketBuffer; + +/** + * Initializes the object. + * Output MTU must be >= input MTU. + * + * @param o the object + * @param input input interface + * @param output output interface + * @param pg pending group + * @return 1 on success, 0 on failure + */ +int SinglePacketBuffer_Init (SinglePacketBuffer *o, PacketRecvInterface *input, PacketPassInterface *output, BPendingGroup *pg) WARN_UNUSED; + +/** + * Frees the object + * + * @param o the object + */ +void SinglePacketBuffer_Free (SinglePacketBuffer *o); + +#endif diff --git a/flow/StreamPassInterface.h b/flow/StreamPassInterface.h new file mode 100644 index 000000000..d262653cc --- /dev/null +++ b/flow/StreamPassInterface.h @@ -0,0 +1,287 @@ +/** + * @file StreamPassInterface.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Interface allowing a stream sender to pass stream data to a stream receiver. + * + * Note that this interface behaves exactly the same and has the same code as + * {@link StreamRecvInterface} if names and its external semantics are disregarded. + * If you modify this file, you should probably modify {@link StreamRecvInterface} + * too. + */ + +#ifndef BADVPN_FLOW_STREAMPASSINTERFACE_H +#define BADVPN_FLOW_STREAMPASSINTERFACE_H + +#include +#include + +#include +#include +#include + +/** + * Handler called at the receiver when {@link StreamPassInterface_Sender_Send} is called + * from the sender. + * It is guaranteed that the interface is in not sending state. + * It is guaranteed that the handler is not being called from within the Send handler. + * + * @param user value supplied to {@link StreamPassInterface_Init} + * @param data pointer to data being sent + * @param data_len amount of data being sent. Will be >0. + * @return - >0 if the receiver accepts some data immediately, indicating how much of the + * data was accepted. The interface remains in not sending state. The receiver + * may not use the provided data after the handler returns. + * - 0 if the receiver cannot accept any data immediately. The interface enters + * sending state as the handler returns. The receiver may use the provided data + * as long as it needs to. When it's done processing some data and doesn't need + * the data any more, it must call {@link StreamPassInterface_Done}. + */ +typedef int (*StreamPassInterface_handler_send) (void *user, uint8_t *data, int data_len); + +/** + * Handler called at the sender when {@link StreamPassInterface_Done} is called from the receiver. + * The receiver will no longer use the data it was provided with. + * It is guaranteed that the interface was in sending state. + * The interface enters not sending state before the handler is called. + * It is guaranteed that the handler is not being called from within Send or Done handlers. + * + * @param user value supplied to {@link StreamPassInterface_Sender_Init} + * @param data_len amount of data the receiver processed. Will be >0 and not exceed the amount + * of data submitted in {@link StreamPassInterface_Sender_Send}. + */ +typedef void (*StreamPassInterface_handler_done) (void *user, int data_len); + +/** + * Interface allowing a stream sender to pass stream data to a stream receiver. + * The sender passes some data by providing the receiver with a pointer + * to the data. The receiver may then either accept some of the data immediately, + * or tell the sender to wait for some data to be processed and inform it + * when it's done. + */ +typedef struct +{ + DebugObject d_obj; + + // receiver data + StreamPassInterface_handler_send handler_send; + void *user_receiver; + + // sender data + StreamPassInterface_handler_done handler_done; + void *user_sender; + + // debug vars + #ifndef NDEBUG + dead_t debug_dead; + int debug_busy_len; + int debug_in_send; + int debug_in_done; + #endif +} StreamPassInterface; + +/** + * Initializes the interface. The sender portion must also be initialized + * with {@link StreamPassInterface_Sender_Init} before I/O can start. + * The interface is initialized in not sending state. + * + * @param i the object + * @param handler_send handler called when the sender wants to send some data + * @param user arbitrary value that will be passed to receiver callback functions + */ +static void StreamPassInterface_Init (StreamPassInterface *i, StreamPassInterface_handler_send handler_send, void *user); + +/** + * Frees the interface. + * + * @param i the object + */ +static void StreamPassInterface_Free (StreamPassInterface *i); + +/** + * Notifies the sender that the receiver has finished processing some of the data being sent. + * The receiver must not use the data it was provided any more. + * The interface must be in sending state. + * The interface enters not sending state before notifying the sender. + * Must not be called from within Send or Done handlers. + * + * Be aware that the sender may attempt to send data from within this function. + * + * @param i the object + * @param data_len amount of data processed. Must be >0 and not exceed the amount of data + * the receiver was provided with in {@link StreamPassInterface_handler_send}. + */ +static void StreamPassInterface_Done (StreamPassInterface *i, int data_len); + +/** + * Initializes the sender portion of the interface. + * + * @param i the object + * @param handler_done handler called when the receiver has finished processing some data + * @param user arbitrary value that will be passed to sender callback functions + */ +static void StreamPassInterface_Sender_Init (StreamPassInterface *i, StreamPassInterface_handler_done handler_done, void *user); + +/** + * Attempts to send some data. + * The interface must be in not sending state. + * Must not be called from within the Send handler. + * + * @param i the object + * @param data pointer to data to send + * @param data_len amount of data to send. Must be >0. + * @return - >0 if some data was accepted by the receiver immediately, indicating how much of + * the data was accepted. The data is no longer needed. + * The interface remains in not sending state. + * - 0 if no data could not be accepted immediately and is being processed. + * The interface enters sending state, and the data must stay accessible while the + * receiver is processing it. When the receiver is done processing it, the + * {@link StreamPassInterface_handler_done} handler will be called. + */ +static int StreamPassInterface_Sender_Send (StreamPassInterface *i, uint8_t *data, int data_len); + +#ifndef NDEBUG + +/** + * Determines if we are in a Send call. + * Only available if NDEBUG is not defined. + * + * @param i the object + * @return 1 if in a Send call, 0 if not + */ +static int StreamPassInterface_InClient (StreamPassInterface *i); + +/** + * Determines if we are in a Done call. + * Only available if NDEBUG is not defined. + * + * @param i the object + * @return 1 if in a Done call, 0 if not + */ +static int StreamPassInterface_InDone (StreamPassInterface *i); + +#endif + +void StreamPassInterface_Init (StreamPassInterface *i, StreamPassInterface_handler_send handler_send, void *user) +{ + i->handler_send = handler_send; + i->user_receiver = user; + i->handler_done = NULL; + i->user_sender = NULL; + + // init debugging + #ifndef NDEBUG + DEAD_INIT(i->debug_dead); + i->debug_busy_len = -1; + i->debug_in_send = 0; + i->debug_in_done = 0; + #endif + + // init debug object + DebugObject_Init(&i->d_obj); +} + +void StreamPassInterface_Free (StreamPassInterface *i) +{ + // free debug object + DebugObject_Free(&i->d_obj); + + // free debugging + #ifndef NDEBUG + DEAD_KILL(i->debug_dead); + #endif +} + +void StreamPassInterface_Done (StreamPassInterface *i, int data_len) +{ + ASSERT(i->debug_busy_len > 0) + ASSERT(!i->debug_in_send) + ASSERT(!i->debug_in_done) + ASSERT(data_len > 0) + ASSERT(data_len <= i->debug_busy_len) + + #ifndef NDEBUG + i->debug_busy_len = -1; + i->debug_in_done = 1; + DEAD_ENTER(i->debug_dead) + #endif + + i->handler_done(i->user_sender, data_len); + + #ifndef NDEBUG + if (DEAD_LEAVE(i->debug_dead)) { + return; + } + i->debug_in_done = 0; + #endif +} + +void StreamPassInterface_Sender_Init (StreamPassInterface *i, StreamPassInterface_handler_done handler_done, void *user) +{ + i->handler_done = handler_done; + i->user_sender = user; +} + +int StreamPassInterface_Sender_Send (StreamPassInterface *i, uint8_t *data, int data_len) +{ + ASSERT(i->debug_busy_len == -1) + ASSERT(!i->debug_in_send) + ASSERT(data_len > 0) + ASSERT(data) + + #ifndef NDEBUG + i->debug_in_send = 1; + DEAD_ENTER(i->debug_dead) + #endif + + int res = i->handler_send(i->user_receiver, data, data_len); + + #ifndef NDEBUG + if (DEAD_LEAVE(i->debug_dead)) { + return -1; + } + i->debug_in_send = 0; + ASSERT(res >= 0) + ASSERT(res <= data_len) + if (res == 0) { + i->debug_busy_len = data_len; + } + #endif + + return res; +} + +#ifndef NDEBUG + +int StreamPassInterface_InClient (StreamPassInterface *i) +{ + return i->debug_in_send; +} + +int StreamPassInterface_InDone (StreamPassInterface *i) +{ + return i->debug_in_done; +} + +#endif + +#endif diff --git a/flow/StreamRecvConnector.c b/flow/StreamRecvConnector.c new file mode 100644 index 000000000..da539e972 --- /dev/null +++ b/flow/StreamRecvConnector.c @@ -0,0 +1,217 @@ +/** + * @file StreamRecvConnector.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include + +#include + +static int output_handler_recv (StreamRecvConnector *o, uint8_t *data, int data_avail) +{ + ASSERT(o->out_avail == -1) + ASSERT(!o->input || !o->in_blocking) + ASSERT(data_avail > 0) + + // if we have no input, remember output data + if (!o->input) { + o->out_avail = data_avail; + o->out = data; + return 0; + } + + // try to receive data + int res; + while (1) { + DEAD_ENTER_N(obj, o->dead) + DEAD_ENTER_N(inp, o->input_dead) + res = StreamRecvInterface_Receiver_Recv(o->input, data, data_avail); + DEAD_LEAVE_N(obj, o->dead); + DEAD_LEAVE_N(inp, o->input_dead); + if (DEAD_KILLED_N(obj)) { + return -1; + } + if (DEAD_KILLED_N(inp)) { + if (!o->input) { + // lost input + o->out_avail = data_avail; + o->out = data; + return 0; + } + // got a new input, retry + continue; + } + break; + }; + + ASSERT(res >= 0) + ASSERT(res <= data_avail) + + if (res == 0) { + // input blocking + o->out_avail = data_avail; + o->out = data; + o->in_blocking = 1; + return 0; + } + + return res; +} + +static void input_handler_done (StreamRecvConnector *o, int data_len) +{ + ASSERT(o->out_avail > 0) + ASSERT(o->input) + ASSERT(o->in_blocking) + ASSERT(data_len > 0) + ASSERT(data_len <= o->out_avail) + + // have no output packet + o->out_avail = -1; + + // input not blocking any more + o->in_blocking = 0; + + // allow output to receive more packets + StreamRecvInterface_Done(&o->output, data_len); + return; +} + +static void job_handler (StreamRecvConnector *o) +{ + ASSERT(o->input) + ASSERT(!o->in_blocking) + ASSERT(o->out_avail > 0) + + // try to receive data + DEAD_ENTER_N(obj, o->dead) + DEAD_ENTER_N(inp, o->input_dead) + int res = StreamRecvInterface_Receiver_Recv(o->input, o->out, o->out_avail); + DEAD_LEAVE_N(obj, o->dead); + DEAD_LEAVE_N(inp, o->input_dead); + if (DEAD_KILLED_N(obj)) { + return; + } + if (DEAD_KILLED_N(inp)) { + // lost current input. Do nothing here. + // If we gained a new one, its own job is responsible for it. + return; + } + + ASSERT(res >= 0) + ASSERT(res <= o->out_avail) + + if (res == 0) { + // input blocking + o->in_blocking = 1; + return; + } + + // have no output packet + o->out_avail = -1; + + // allow output to receive more data + StreamRecvInterface_Done(&o->output, res); + return; +} + +void StreamRecvConnector_Init (StreamRecvConnector *o, BPendingGroup *pg) +{ + // init dead var + DEAD_INIT(o->dead); + + // init output + StreamRecvInterface_Init(&o->output, (StreamRecvInterface_handler_recv)output_handler_recv, o); + + // have no output packet + o->out_avail = -1; + + // have no input + o->input = NULL; + + // init continue job + BPending_Init(&o->continue_job, pg, (BPending_handler)job_handler, o); + + // init debug object + DebugObject_Init(&o->d_obj); +} + +void StreamRecvConnector_Free (StreamRecvConnector *o) +{ + // free debug object + DebugObject_Free(&o->d_obj); + + // free continue job + BPending_Free(&o->continue_job); + + // free input dead var + if (o->input) { + DEAD_KILL(o->input_dead); + } + + // free output + StreamRecvInterface_Free(&o->output); + + // free dead var + DEAD_KILL(o->dead); +} + +StreamRecvInterface * StreamRecvConnector_GetOutput (StreamRecvConnector *o) +{ + return &o->output; +} + +void StreamRecvConnector_ConnectInput (StreamRecvConnector *o, StreamRecvInterface *input) +{ + ASSERT(!o->input) + + // set input + o->input = input; + + // init input + StreamRecvInterface_Receiver_Init(o->input, (StreamRecvInterface_handler_done)input_handler_done, o); + + // init input dead var + DEAD_INIT(o->input_dead); + + // set input not blocking + o->in_blocking = 0; + + // if we have an input packet, set continue job + if (o->out_avail > 0) { + BPending_Set(&o->continue_job); + } +} + +void StreamRecvConnector_DisconnectInput (StreamRecvConnector *o) +{ + ASSERT(o->input) + + // unset continue job (in case it wasn't called yet) + BPending_Unset(&o->continue_job); + + // free dead var + DEAD_KILL(o->input_dead); + + // set no input + o->input = NULL; +} diff --git a/flow/StreamRecvConnector.h b/flow/StreamRecvConnector.h new file mode 100644 index 000000000..48b24b538 --- /dev/null +++ b/flow/StreamRecvConnector.h @@ -0,0 +1,97 @@ +/** + * @file StreamRecvConnector.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * A {@link StreamRecvInterface} layer which allows the input to be + * connected and disconnected on the fly. + */ + +#ifndef BADVPN_FLOW_STREAMRECVCONNECTOR_H +#define BADVPN_FLOW_STREAMRECVCONNECTOR_H + +#include + +#include +#include +#include +#include + +/** + * A {@link StreamRecvInterface} layer which allows the input to be + * connected and disconnected on the fly. + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + StreamRecvInterface output; + int out_avail; + uint8_t *out; + StreamRecvInterface *input; + dead_t input_dead; + int in_blocking; + BPending continue_job; +} StreamRecvConnector; + +/** + * Initializes the object. + * The object is initialized in not connected state. + * + * @param o the object + * @param pg pending group + */ +void StreamRecvConnector_Init (StreamRecvConnector *o, BPendingGroup *pg); + +/** + * Frees the object. + * + * @param o the object + */ +void StreamRecvConnector_Free (StreamRecvConnector *o); + +/** + * Returns the output interface. + * + * @param o the object + * @return output interface + */ +StreamRecvInterface * StreamRecvConnector_GetOutput (StreamRecvConnector *o); + +/** + * Connects input. + * The object must be in not connected state. + * The object enters connected state. + * + * @param o the object + * @param output input to connect + */ +void StreamRecvConnector_ConnectInput (StreamRecvConnector *o, StreamRecvInterface *input); + +/** + * Disconnects input. + * The object must be in connected state. + * The object enters not connected state. + * + * @param o the object + */ +void StreamRecvConnector_DisconnectInput (StreamRecvConnector *o); + +#endif diff --git a/flow/StreamRecvInterface.h b/flow/StreamRecvInterface.h new file mode 100644 index 000000000..1c7b825a2 --- /dev/null +++ b/flow/StreamRecvInterface.h @@ -0,0 +1,286 @@ +/** + * @file StreamRecvInterface.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Interface allowing a stream receiver to receive stream data from a stream sender. + * + * Note that this interface behaves exactly the same and has the same code as + * {@link StreamPassInterface} if names and its external semantics are disregarded. + * If you modify this file, you should probably modify {@link StreamPassInterface} + * too. + */ + +#ifndef BADVPN_FLOW_STREAMRECVINTERFACE_H +#define BADVPN_FLOW_STREAMRECVINTERFACE_H + +#include +#include + +#include +#include +#include + +/** + * Handler called at the sender when {@link StreamRecvInterface_Receiver_Recv} is called + * from the receiver. + * It is guaranteed that the interface is in not receiving state. + * It is guaranteed that the handler is not being called from within Recv or Cancel handlers. + * + * @param user value supplied to {@link StreamRecvInterface_Init} + * @param data pointer to the buffer where data is to be written + * @param data_avail size of the buffer. Will be >0. + * @return - >0 if the sender provides some data immediately, indicating how much data was + * written to the buffer. The interface remains in not receiving state. + * The sender may not use the provided buffer after the handler returns. + * - 0 if the sender cannot provide any data immediately. The interface enters + * receiving state as the handler returns. The sender must write some data to the + * provided buffer and call {@link StreamRecvInterface_Done} when it's done. + */ +typedef int (*StreamRecvInterface_handler_recv) (void *user, uint8_t *data, int data_avail); + +/** + * Handler called at the receiver when {@link StreamRecvInterface_Done} is called from the sender. + * The sender will no longer use the buffer it was provided with. + * It is guaranteed that the interface was in receiving state. + * The interface enters not receiving state before the handler is called. + * It is guaranteed that the handler is not being called from within Recv or Done handlers. + * + * @param user value supplied to {@link StreamRecvInterface_Receiver_Init} + * @param data_len number of bytes written. Will be >0 and <= the size of the buffer + * that was provided in the previous {@link StreamRecvInterface_Receiver_Recv} + * call. + */ +typedef void (*StreamRecvInterface_handler_done) (void *user, int data_len); + +/** + * Interface allowing a stream receiver to receive stream data from a stream sender. + * The receiver receives data by providing the sender with a buffer. The sender + * may then either provide some data immediately, or tell the receiver to wait for + * some data to be available and inform it when it's done. + */ +typedef struct { + DebugObject d_obj; + + // sender data + StreamRecvInterface_handler_recv handler_recv; + void *user_sender; + + // receiver data + StreamRecvInterface_handler_done handler_done; + void *user_receiver; + + // debug vars + #ifndef NDEBUG + dead_t debug_dead; + int debug_busy_len; + int debug_in_recv; + int debug_in_done; + #endif +} StreamRecvInterface; + +/** + * Initializes the interface. The receiver portion must also be initialized + * with {@link StreamRecvInterface_Receiver_Init} before I/O can start. + * The interface is initialized in not receiving state. + * + * @param i the object + * @param handler_recv handler called when the receiver wants to receive data + * @param user arbitrary value that will be passed to sender callback functions + */ +static void StreamRecvInterface_Init (StreamRecvInterface *i, StreamRecvInterface_handler_recv handler_recv, void *user); + +/** + * Frees the interface. + * + * @param i the object + */ +static void StreamRecvInterface_Free (StreamRecvInterface *i); + +/** + * Notifies the receiver that the sender has finished providing some data. + * The sender must not use the buffer it was provided any more. + * The interface must be in receiving state. + * The interface enters not receiving state before notifying the receiver. + * Must not be called from within Recv or Done handlers. + * + * Be aware that the receiver may attempt to receive data from within this function. + * + * @param i the object + * @param data_len number of bytes written. Must be >0 and <= the size of the buffer + * that was provided in the previous {@link StreamRecvInterface_handler_recv} + * call. + */ +static void StreamRecvInterface_Done (StreamRecvInterface *i, int data_len); + +/** + * Initializes the receiver portion of the interface. + * + * @param i the object + * @param handler_done handler called when the sender has finished providing a packet + * @param user arbitrary value that will be passed to receiver callback functions + */ +static void StreamRecvInterface_Receiver_Init (StreamRecvInterface *i, StreamRecvInterface_handler_done handler_done, void *user); + +/** + * Attempts to receive some data. + * The interface must be in not receiving state. + * Must not be called from within the Recv handler. + * + * @param i the object + * @param data pointer to the buffer where data is to be written + * @param data_avail size of the buffer. Must be >0. + * @return - >0 if some data was provided by the sender imediately, indicating how much data was + * provided. The buffer is no longer needed. + * The interface remains in not receiving state. + * - 0 if no data could not be provided immediately. + * The interface enters receiving state, and the buffer must stay accessible while the + * sender is providing the data. When the sender is done providing it, the + * {@link StreamRecvInterface_handler_done} handler will be called. + */ +static int StreamRecvInterface_Receiver_Recv (StreamRecvInterface *i, uint8_t *data, int data_avail); + +#ifndef NDEBUG + +/** + * Determines if we are in a Recv call. + * Only available if NDEBUG is not defined. + * + * @param i the object + * @return 1 if in a Recv call, 0 if not + */ +static int StreamRecvInterface_InClient (StreamRecvInterface *i); + +/** + * Determines if we are in a Done call. + * Only available if NDEBUG is not defined. + * + * @param i the object + * @return 1 if in a Done call, 0 if not + */ +static int StreamRecvInterface_InDone (StreamRecvInterface *i); + +#endif + +void StreamRecvInterface_Init (StreamRecvInterface *i, StreamRecvInterface_handler_recv handler_recv, void *user) +{ + i->handler_recv = handler_recv; + i->user_sender = user; + i->handler_done = NULL; + i->user_receiver = NULL; + + // init debugging + #ifndef NDEBUG + DEAD_INIT(i->debug_dead); + i->debug_busy_len = -1; + i->debug_in_recv = 0; + i->debug_in_done = 0; + #endif + + // init debug object + DebugObject_Init(&i->d_obj); +} + +void StreamRecvInterface_Free (StreamRecvInterface *i) +{ + // free debug object + DebugObject_Free(&i->d_obj); + + // free debugging + #ifndef NDEBUG + DEAD_KILL(i->debug_dead); + #endif +} + +void StreamRecvInterface_Done (StreamRecvInterface *i, int data_len) +{ + ASSERT(i->debug_busy_len > 0) + ASSERT(!i->debug_in_recv) + ASSERT(!i->debug_in_done) + ASSERT(data_len > 0) + ASSERT(data_len <= i->debug_busy_len) + + #ifndef NDEBUG + i->debug_busy_len = -1; + i->debug_in_done = 1; + DEAD_ENTER(i->debug_dead) + #endif + + i->handler_done(i->user_receiver, data_len); + + #ifndef NDEBUG + if (DEAD_LEAVE(i->debug_dead)) { + return; + } + i->debug_in_done = 0; + #endif +} + +void StreamRecvInterface_Receiver_Init (StreamRecvInterface *i, StreamRecvInterface_handler_done handler_done, void *user) +{ + i->handler_done = handler_done; + i->user_receiver = user; +} + +int StreamRecvInterface_Receiver_Recv (StreamRecvInterface *i, uint8_t *data, int data_avail) +{ + ASSERT(i->debug_busy_len == -1) + ASSERT(!i->debug_in_recv) + ASSERT(data_avail > 0) + ASSERT(data) + + #ifndef NDEBUG + i->debug_in_recv = 1; + DEAD_ENTER(i->debug_dead) + #endif + + int res = i->handler_recv(i->user_sender, data, data_avail); + + #ifndef NDEBUG + if (DEAD_LEAVE(i->debug_dead)) { + return -1; + } + i->debug_in_recv = 0; + ASSERT(res >= 0) + ASSERT(res <= data_avail) + if (res == 0) { + i->debug_busy_len = data_avail; + } + #endif + + return res; +} + +#ifndef NDEBUG + +int StreamRecvInterface_InClient (StreamRecvInterface *i) +{ + return i->debug_in_recv; +} + +int StreamRecvInterface_InDone (StreamRecvInterface *i) +{ + return i->debug_in_done; +} + +#endif + +#endif diff --git a/flow/StreamSocketSink.c b/flow/StreamSocketSink.c new file mode 100644 index 000000000..06edc080c --- /dev/null +++ b/flow/StreamSocketSink.c @@ -0,0 +1,136 @@ +/** + * @file StreamSocketSink.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include + +static void report_error (StreamSocketSink *s, int error) +{ + #ifndef NDEBUG + s->in_error = 1; + DEAD_ENTER(s->dead) + #endif + + FlowErrorReporter_ReportError(&s->rep, &error); + + #ifndef NDEBUG + ASSERT(DEAD_KILLED) + DEAD_LEAVE(s->dead); + #endif +} + +static int input_handler_send (StreamSocketSink *s, uint8_t *data, int data_len) +{ + ASSERT(s->in_len == -1) + ASSERT(data_len > 0) + ASSERT(!s->in_error) + + int res = BSocket_Send(s->bsock, data, data_len); + if (res < 0) { + int error = BSocket_GetError(s->bsock); + if (error == BSOCKET_ERROR_LATER) { + s->in_len = data_len; + s->in = data; + BSocket_EnableEvent(s->bsock, BSOCKET_WRITE); + return 0; + } + report_error(s, STREAMSOCKETSINK_ERROR_BSOCKET); + return -1; + } + + ASSERT(res > 0) + + return res; +} + +static void socket_handler (StreamSocketSink *s, int event) +{ + ASSERT(s->in_len > 0) + ASSERT(event == BSOCKET_WRITE) + ASSERT(!s->in_error) + + int res = BSocket_Send(s->bsock, s->in, s->in_len); + if (res < 0) { + int error = BSocket_GetError(s->bsock); + if (error == BSOCKET_ERROR_LATER) { + return; + } + report_error(s, STREAMSOCKETSINK_ERROR_BSOCKET); + return; + } + + ASSERT(res > 0) + + BSocket_DisableEvent(s->bsock, BSOCKET_WRITE); + s->in_len = -1; + + StreamPassInterface_Done(&s->input, res); + return; +} + +void StreamSocketSink_Init (StreamSocketSink *s, FlowErrorReporter rep, BSocket *bsock) +{ + // init arguments + s->rep = rep; + s->bsock = bsock; + + // init dead var + DEAD_INIT(s->dead); + + // add socket event handler + BSocket_AddEventHandler(s->bsock, BSOCKET_WRITE, (BSocket_handler)socket_handler, s); + + // init input + StreamPassInterface_Init(&s->input, (StreamPassInterface_handler_send)input_handler_send, s); + + // have no input packet + s->in_len = -1; + + // init debugging + #ifndef NDEBUG + s->in_error = 0; + #endif + + // init debug object + DebugObject_Init(&s->d_obj); +} + +void StreamSocketSink_Free (StreamSocketSink *s) +{ + // free debug object + DebugObject_Free(&s->d_obj); + + // free input + StreamPassInterface_Free(&s->input); + + // remove socket event handler + BSocket_RemoveEventHandler(s->bsock, BSOCKET_WRITE); + + // free dead var + DEAD_KILL(s->dead); +} + +StreamPassInterface * StreamSocketSink_GetInput (StreamSocketSink *s) +{ + return &s->input; +} diff --git a/flow/StreamSocketSink.h b/flow/StreamSocketSink.h new file mode 100644 index 000000000..36fa5e777 --- /dev/null +++ b/flow/StreamSocketSink.h @@ -0,0 +1,84 @@ +/** + * @file StreamSocketSink.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * A {@link StreamPassInterface} sink which sends data to a stream socket. + */ + +#ifndef BADVPN_FLOW_STREAMSOCKETSINK_H +#define BADVPN_FLOW_STREAMSOCKETSINK_H + +#include + +#include +#include +#include +#include +#include + +#define STREAMSOCKETSINK_ERROR_BSOCKET 1 + +/** + * A {@link StreamPassInterface} sink which sends data to a stream socket. + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + FlowErrorReporter rep; + BSocket *bsock; + StreamPassInterface input; + int in_len; + uint8_t *in; + #ifndef NDEBUG + int in_error; + #endif +} StreamSocketSink; + +/** + * Initializes the sink. + * + * @param s the object + * @param rep error reporting data. Error code is an int. Possible error codes: + * - STREAMSOCKETSINK_ERROR_BSOCKET: {@link BSocket_Send} failed + * with an unhandled error code + * The object must be freed from the error handler. + * @param bsock stream socket to write data to. Registers a BSOCKET_WRITE handler which + * must not be registered. + */ +void StreamSocketSink_Init (StreamSocketSink *s, FlowErrorReporter rep, BSocket *bsock); + +/** + * Frees the sink. + * + * @param s the object + */ +void StreamSocketSink_Free (StreamSocketSink *s); + +/** + * Returns the input interface. + * + * @param s the object + * @return input interface + */ +StreamPassInterface * StreamSocketSink_GetInput (StreamSocketSink *s); + +#endif diff --git a/flow/StreamSocketSource.c b/flow/StreamSocketSource.c new file mode 100644 index 000000000..b6bed8004 --- /dev/null +++ b/flow/StreamSocketSource.c @@ -0,0 +1,144 @@ +/** + * @file StreamSocketSource.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include + +#include + +static void report_error (StreamSocketSource *s, int error) +{ + #ifndef NDEBUG + s->in_error = 1; + DEAD_ENTER(s->dead) + #endif + + FlowErrorReporter_ReportError(&s->rep, &error); + + #ifndef NDEBUG + ASSERT(DEAD_KILLED) + DEAD_LEAVE(s->dead); + #endif +} + +static int output_handler_recv (StreamSocketSource *s, uint8_t *data, int data_avail) +{ + ASSERT(s->out_avail == -1) + ASSERT(data_avail > 0) + ASSERT(!s->in_error) + + int res = BSocket_Recv(s->bsock, data, data_avail); + if (res < 0) { + int error = BSocket_GetError(s->bsock); + if (error == BSOCKET_ERROR_LATER) { + s->out_avail = data_avail; + s->out = data; + BSocket_EnableEvent(s->bsock, BSOCKET_READ); + return 0; + } + report_error(s, STREAMSOCKETSOURCE_ERROR_BSOCKET); + return -1; + } + + if (res == 0) { + report_error(s, STREAMSOCKETSOURCE_ERROR_CLOSED); + return -1; + } + + return res; +} + +static void socket_handler (StreamSocketSource *s, int event) +{ + ASSERT(s->out_avail > 0) + ASSERT(event == BSOCKET_READ) + ASSERT(!s->in_error) + + int res = BSocket_Recv(s->bsock, s->out, s->out_avail); + if (res < 0) { + int error = BSocket_GetError(s->bsock); + if (error == BSOCKET_ERROR_LATER) { + return; + } + report_error(s, STREAMSOCKETSOURCE_ERROR_BSOCKET); + return; + } + + if (res == 0) { + report_error(s, STREAMSOCKETSOURCE_ERROR_CLOSED); + return; + } + + BSocket_DisableEvent(s->bsock, BSOCKET_READ); + s->out_avail = -1; + + StreamRecvInterface_Done(&s->output, res); + return; +} + +void StreamSocketSource_Init (StreamSocketSource *s, FlowErrorReporter rep, BSocket *bsock) +{ + // init arguments + s->rep = rep; + s->bsock = bsock; + + // init dead var + DEAD_INIT(s->dead); + + // add socket event handler + BSocket_AddEventHandler(s->bsock, BSOCKET_READ, (BSocket_handler)socket_handler, s); + + // init output + StreamRecvInterface_Init(&s->output, (StreamRecvInterface_handler_recv)output_handler_recv, s); + + // have no output packet + s->out_avail = -1; + + // init debugging + #ifndef NDEBUG + s->in_error = 0; + #endif + + // init debug object + DebugObject_Init(&s->d_obj); +} + +void StreamSocketSource_Free (StreamSocketSource *s) +{ + // free debug object + DebugObject_Free(&s->d_obj); + + // free output + StreamRecvInterface_Free(&s->output); + + // remove socket event handler + BSocket_RemoveEventHandler(s->bsock, BSOCKET_READ); + + // free dead var + DEAD_KILL(s->dead); +} + +StreamRecvInterface * StreamSocketSource_GetOutput (StreamSocketSource *s) +{ + return &s->output; +} diff --git a/flow/StreamSocketSource.h b/flow/StreamSocketSource.h new file mode 100644 index 000000000..cea7aa8e7 --- /dev/null +++ b/flow/StreamSocketSource.h @@ -0,0 +1,84 @@ +/** + * @file StreamSocketSource.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * A {@link StreamRecvInterface} source which receives data from a stream socket. + */ + +#ifndef BADVPN_FLOW_STREAMSOCKETSOURCE_H +#define BADVPN_FLOW_STREAMSOCKETSOURCE_H + +#include +#include +#include +#include +#include + +#define STREAMSOCKETSOURCE_ERROR_CLOSED 0 +#define STREAMSOCKETSOURCE_ERROR_BSOCKET 1 + +/** + * A {@link StreamRecvInterface} source which receives data from a stream socket. + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + FlowErrorReporter rep; + BSocket *bsock; + StreamRecvInterface output; + int out_avail; + uint8_t *out; + #ifndef NDEBUG + int in_error; + #endif +} StreamSocketSource; + +/** + * Initializes the source. + * + * @param s the object + * @param rep error reporting data. Error code is an int. Possible error codes: + * - STREAMSOCKETSOURCE_ERROR_CLOSED: {@link BSocket_Recv} returned 0 + * - STREAMSOCKETSOURCE_ERROR_BSOCKET: {@link BSocket_Recv} failed + * with an unhandled error code + * The object must be freed from the error handler. + * @param bsock stream socket to read data from. Registers a BSOCKET_READ handler which + * must not be registered. + */ +void StreamSocketSource_Init (StreamSocketSource *s, FlowErrorReporter rep, BSocket *bsock); + +/** + * Frees the source. + * + * @param s the object + */ +void StreamSocketSource_Free (StreamSocketSource *s); + +/** + * Returns the output interface. + * + * @param s the object + * @return output interface + */ +StreamRecvInterface * StreamSocketSource_GetOutput (StreamSocketSource *s); + +#endif diff --git a/flow/error.h b/flow/error.h new file mode 100644 index 000000000..e7f452116 --- /dev/null +++ b/flow/error.h @@ -0,0 +1,104 @@ +/** + * @file error.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Flow error handling. + */ + +#ifndef BADVPN_FLOW_ERROR_H +#define BADVPN_FLOW_ERROR_H + +#include + +/** + * Callback function invoked when {@link FlowErrorReporter_ReportError} is called. + * + * @param user value specified to {@link FlowErrorDomain_Init} + * @param component identifier of the component reporting the error, as in + * {@link FlowErrorReporter_Create} + * @param data component-specific error data, as in {@link FlowErrorReporter_ReportError} + */ +typedef void (*FlowErrorDomain_handler) (void *user, int component, const void *data); + +/** + * Object used to report errors from multiple sources to the same error handler. + */ +typedef struct { + FlowErrorDomain_handler handler; + void *user; +} FlowErrorDomain; + +/** + * Initializes the error domain. + * + * @param d the object + * @param handler callback function invoked when {@link FlowErrorReporter_ReportError} is called + * @param user value passed to callback functions + */ +static void FlowErrorDomain_Init (FlowErrorDomain *d, FlowErrorDomain_handler handler, void *user); + +/** + * Structure that can be passed to flow components to ease error reporting. + */ +typedef struct { + FlowErrorDomain *domain; + int component; +} FlowErrorReporter; + +/** + * Creates a {@link FlowErrorReporter} structure. + * + * @param domain error domain + * @param component component identifier + * @return a {@link FlowErrorReporter} structure with the specifed error domain and component. + */ +static FlowErrorReporter FlowErrorReporter_Create (FlowErrorDomain *domain, int component); + +/** + * Reports an error. + * + * @param reporter a {@link FlowErrorReporter} structure containing the error domain and + * component identifier user to report the error + * @param data component-specific error data + */ +static void FlowErrorReporter_ReportError (FlowErrorReporter *reporter, const void *data); + +void FlowErrorDomain_Init (FlowErrorDomain *d, FlowErrorDomain_handler handler, void *user) +{ + d->handler = handler; + d->user = user; +} + +FlowErrorReporter FlowErrorReporter_Create (FlowErrorDomain *domain, int component) +{ + FlowErrorReporter r; + r.domain = domain; + r.component = component; + return r; +} + +void FlowErrorReporter_ReportError (FlowErrorReporter *reporter, const void *data) +{ + reporter->domain->handler(reporter->domain->user, reporter->component, data); +} + +#endif diff --git a/generate_files b/generate_files new file mode 100755 index 000000000..fc5f3cce6 --- /dev/null +++ b/generate_files @@ -0,0 +1,44 @@ +#!/bin/bash + +set -e + +PHP_CMD=( php ) +FLEX_CMD=( flex ) +BISON_CMD=( bison ) + +OUT_DIR="generated/" + +function bproto() { + local input="$1" + local name="$2" + "${PHP_CMD[@]}" bproto_generator/bproto.php --input-file "${input}" --output-dir "${OUT_DIR}" --name "bproto_${name}" +} + +function bstruct() { + local input="$1" + local name="$2" + "${PHP_CMD[@]}" bstruct_generator/bstruct.php --input-file "${input}" --output-dir "${OUT_DIR}" --name "bstruct_${name}" +} + +function do_flex() { + local input="$1" + local name="$2" + "${FLEX_CMD[@]}" -o "${OUT_DIR}/flex_${name}.c" --header-file="${OUT_DIR}/flex_${name}.h" "${input}" +} + +function do_bison() { + local input="$1" + local name="$2" + "${BISON_CMD[@]}" -d -o "${OUT_DIR}/bison_${name}.c" "${input}" +} + +mkdir -p generated + +bproto tests/bproto_test.bproto bproto_test +bproto protocol/msgproto.bproto msgproto +bproto protocol/addr.bproto addr +bstruct tests/bstruct_test.bstruct bstruct_test +bstruct security/OTPChecker.bstruct OTPChecker +do_flex predicate/BPredicate.l BPredicate +do_bison predicate/BPredicate.y BPredicate +"${PHP_CMD[@]}" blog_generator/blog.php --input-file blog_channels.txt --output-dir "${OUT_DIR}" diff --git a/generated/bison_BPredicate.c b/generated/bison_BPredicate.c new file mode 100644 index 000000000..a3a901825 --- /dev/null +++ b/generated/bison_BPredicate.c @@ -0,0 +1,2086 @@ +/* A Bison parser, made by GNU Bison 2.4.3. */ + +/* Skeleton implementation for Bison's Yacc-like parsers in C + + Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006, + 2009, 2010 Free Software Foundation, Inc. + + 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 3 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, see . */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* C LALR(1) parser skeleton written by Richard Stallman, by + simplifying the original so-called "semantic" parser. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output. */ +#define YYBISON 1 + +/* Bison version. */ +#define YYBISON_VERSION "2.4.3" + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 1 + +/* Push parsers. */ +#define YYPUSH 0 + +/* Pull parsers. */ +#define YYPULL 1 + +/* Using locations. */ +#define YYLSP_NEEDED 1 + + + +/* Copy the first part of user declarations. */ + +/* Line 189 of yacc.c */ +#line 27 "predicate/BPredicate.y" + + +#include + +#include +#include + +#define YYLEX_PARAM scanner + +static struct predicate_node * make_constant (int val) +{ + struct predicate_node *n = malloc(sizeof(*n)); + if (!n) { + return NULL; + } + + n->type = NODE_CONSTANT; + n->constant.val = val; + + return n; +} + +static struct predicate_node * make_negation (struct predicate_node *op) +{ + if (!op) { + goto fail; + } + + struct predicate_node *n = malloc(sizeof(*n)); + if (!n) { + goto fail; + } + + n->type = NODE_NEG; + n->neg.op = op; + + return n; + +fail: + if (op) { + free_predicate_node(op); + } + return NULL; +} + +static struct predicate_node * make_conjunction (struct predicate_node *op1, struct predicate_node *op2) +{ + if (!op1 || !op2) { + goto fail; + } + + struct predicate_node *n = malloc(sizeof(*n)); + if (!n) { + goto fail; + } + + n->type = NODE_CONJUNCT; + n->conjunct.op1 = op1; + n->conjunct.op2 = op2; + + return n; + +fail: + if (op1) { + free_predicate_node(op1); + } + if (op2) { + free_predicate_node(op2); + } + return NULL; +} + +static struct predicate_node * make_disjunction (struct predicate_node *op1, struct predicate_node *op2) +{ + if (!op1 || !op2) { + goto fail; + } + + struct predicate_node *n = malloc(sizeof(*n)); + if (!n) { + goto fail; + } + + n->type = NODE_DISJUNCT; + n->disjunct.op1 = op1; + n->disjunct.op2 = op2; + + return n; + +fail: + if (op1) { + free_predicate_node(op1); + } + if (op2) { + free_predicate_node(op2); + } + return NULL; +} + +static struct predicate_node * make_function (char *name, struct arguments_node *args, int need_args) +{ + if (!name || (!args && need_args)) { + goto fail; + } + + struct predicate_node *n = malloc(sizeof(*n)); + if (!n) { + goto fail; + } + + n->type = NODE_FUNCTION; + n->function.name = name; + n->function.args = args; + + return n; + +fail: + if (name) { + free(name); + } + if (args) { + free_arguments_node(args); + } + return NULL; +} + +static struct arguments_node * make_arguments (struct arguments_arg arg, struct arguments_node *next, int need_next) +{ + if (arg.type == ARGUMENT_INVALID || (!next && need_next)) { + goto fail; + } + + struct arguments_node *n = malloc(sizeof(*n)); + if (!n) { + goto fail; + } + + n->arg = arg; + n->next = next; + + return n; + +fail: + free_argument(arg); + if (next) { + free_arguments_node(next); + } + return NULL; +} + +static struct arguments_arg make_argument_predicate (struct predicate_node *pr) +{ + struct arguments_arg ret; + + if (!pr) { + goto fail; + } + + ret.type = ARGUMENT_PREDICATE; + ret.predicate = pr; + + return ret; + +fail: + ret.type = ARGUMENT_INVALID; + return ret; +} + +static struct arguments_arg make_argument_string (char *string) +{ + struct arguments_arg ret; + + if (!string) { + goto fail; + } + + ret.type = ARGUMENT_STRING; + ret.string = string; + + return ret; + +fail: + ret.type = ARGUMENT_INVALID; + return ret; +} + + + +/* Line 189 of yacc.c */ +#line 261 "generated//bison_BPredicate.c" + +/* Enabling traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif + +/* Enabling verbose error messages. */ +#ifdef YYERROR_VERBOSE +# undef YYERROR_VERBOSE +# define YYERROR_VERBOSE 1 +#else +# define YYERROR_VERBOSE 0 +#endif + +/* Enabling the token table. */ +#ifndef YYTOKEN_TABLE +# define YYTOKEN_TABLE 0 +#endif + + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + STRING = 258, + NAME = 259, + PEER1_NAME = 260, + PEER2_NAME = 261, + AND = 262, + OR = 263, + NOT = 264, + SPAR = 265, + EPAR = 266, + CONSTANT_TRUE = 267, + CONSTANT_FALSE = 268, + COMMA = 269 + }; +#endif + + + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE +{ + +/* Line 214 of yacc.c */ +#line 220 "predicate/BPredicate.y" + + char *text; + struct predicate_node *node; + struct arguments_node *arg_node; + struct predicate_node nfaw; + struct arguments_arg arg_arg; + + + +/* Line 214 of yacc.c */ +#line 321 "generated//bison_BPredicate.c" +} YYSTYPE; +# define YYSTYPE_IS_TRIVIAL 1 +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +#endif + +#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED +typedef struct YYLTYPE +{ + int first_line; + int first_column; + int last_line; + int last_column; +} YYLTYPE; +# define yyltype YYLTYPE /* obsolescent; will be withdrawn */ +# define YYLTYPE_IS_DECLARED 1 +# define YYLTYPE_IS_TRIVIAL 1 +#endif + + +/* Copy the second part of user declarations. */ + + +/* Line 264 of yacc.c */ +#line 346 "generated//bison_BPredicate.c" + +#ifdef short +# undef short +#endif + +#ifdef YYTYPE_UINT8 +typedef YYTYPE_UINT8 yytype_uint8; +#else +typedef unsigned char yytype_uint8; +#endif + +#ifdef YYTYPE_INT8 +typedef YYTYPE_INT8 yytype_int8; +#elif (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +typedef signed char yytype_int8; +#else +typedef short int yytype_int8; +#endif + +#ifdef YYTYPE_UINT16 +typedef YYTYPE_UINT16 yytype_uint16; +#else +typedef unsigned short int yytype_uint16; +#endif + +#ifdef YYTYPE_INT16 +typedef YYTYPE_INT16 yytype_int16; +#else +typedef short int yytype_int16; +#endif + +#ifndef YYSIZE_T +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif ! defined YYSIZE_T && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned int +# endif +#endif + +#define YYSIZE_MAXIMUM ((YYSIZE_T) -1) + +#ifndef YY_ +# if defined YYENABLE_NLS && YYENABLE_NLS +# if ENABLE_NLS +# include /* INFRINGES ON USER NAME SPACE */ +# define YY_(msgid) dgettext ("bison-runtime", msgid) +# endif +# endif +# ifndef YY_ +# define YY_(msgid) msgid +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YYUSE(e) ((void) (e)) +#else +# define YYUSE(e) /* empty */ +#endif + +/* Identity function, used to suppress warnings about constant conditions. */ +#ifndef lint +# define YYID(n) (n) +#else +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static int +YYID (int yyi) +#else +static int +YYID (yyi) + int yyi; +#endif +{ + return yyi; +} +#endif + +#if ! defined yyoverflow || YYERROR_VERBOSE + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef _STDLIB_H +# define _STDLIB_H 1 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's `empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (YYID (0)) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + invoke alloca (N) if N exceeds 4096. Use a slightly smaller number + to allow for a few compiler-allocated temporary stack slots. */ +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# if (defined __cplusplus && ! defined _STDLIB_H \ + && ! ((defined YYMALLOC || defined malloc) \ + && (defined YYFREE || defined free))) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef _STDLIB_H +# define _STDLIB_H 1 +# endif +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# endif +#endif /* ! defined yyoverflow || YYERROR_VERBOSE */ + + +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL \ + && defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + yytype_int16 yyss_alloc; + YYSTYPE yyvs_alloc; + YYLTYPE yyls_alloc; +}; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE) + sizeof (YYLTYPE)) \ + + 2 * YYSTACK_GAP_MAXIMUM) + +/* Copy COUNT objects from FROM to TO. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(To, From, Count) \ + __builtin_memcpy (To, From, (Count) * sizeof (*(From))) +# else +# define YYCOPY(To, From, Count) \ + do \ + { \ + YYSIZE_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (To)[yyi] = (From)[yyi]; \ + } \ + while (YYID (0)) +# endif +# endif + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack_alloc, Stack) \ + do \ + { \ + YYSIZE_T yynewbytes; \ + YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ + Stack = &yyptr->Stack_alloc; \ + yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / sizeof (*yyptr); \ + } \ + while (YYID (0)) + +#endif + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 17 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 37 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 15 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 11 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 20 +/* YYNRULES -- Number of states. */ +#define YYNSTATES 31 + +/* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */ +#define YYUNDEFTOK 2 +#define YYMAXUTOK 269 + +#define YYTRANSLATE(YYX) \ + ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) + +/* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX. */ +static const yytype_uint8 yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 +}; + +#if YYDEBUG +/* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in + YYRHS. */ +static const yytype_uint8 yyprhs[] = +{ + 0, 0, 3, 5, 7, 9, 11, 13, 15, 17, + 19, 21, 25, 28, 32, 36, 40, 45, 47, 51, + 53 +}; + +/* YYRHS -- A `-1'-separated list of the rules' RHS. */ +static const yytype_int8 yyrhs[] = +{ + 16, 0, -1, 17, -1, 18, -1, 19, -1, 20, + -1, 21, -1, 22, -1, 23, -1, 12, -1, 13, + -1, 10, 17, 11, -1, 9, 17, -1, 17, 7, + 17, -1, 17, 8, 17, -1, 4, 10, 11, -1, + 4, 10, 24, 11, -1, 25, -1, 25, 14, 24, + -1, 17, -1, 3, -1 +}; + +/* YYRLINE[YYN] -- source line where rule number YYN was defined. */ +static const yytype_uint16 yyrline[] = +{ + 0, 269, 269, 274, 274, 274, 274, 274, 274, 277, + 281, 287, 293, 299, 305, 311, 315, 321, 325, 331, + 335 +}; +#endif + +#if YYDEBUG || YYERROR_VERBOSE || YYTOKEN_TABLE +/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "$end", "error", "$undefined", "STRING", "NAME", "PEER1_NAME", + "PEER2_NAME", "AND", "OR", "NOT", "SPAR", "EPAR", "CONSTANT_TRUE", + "CONSTANT_FALSE", "COMMA", "$accept", "input", "predicate", "constant", + "parentheses", "neg", "conjunct", "disjunct", "function", "arguments", + "argument", 0 +}; +#endif + +# ifdef YYPRINT +/* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to + token YYLEX-NUM. */ +static const yytype_uint16 yytoknum[] = +{ + 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, + 265, 266, 267, 268, 269 +}; +# endif + +/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const yytype_uint8 yyr1[] = +{ + 0, 15, 16, 17, 17, 17, 17, 17, 17, 18, + 18, 19, 20, 21, 22, 23, 23, 24, 24, 25, + 25 +}; + +/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */ +static const yytype_uint8 yyr2[] = +{ + 0, 2, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 3, 2, 3, 3, 3, 4, 1, 3, 1, + 1 +}; + +/* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state + STATE-NUM when YYTABLE doesn't specify something else to do. Zero + means the default is an error. */ +static const yytype_uint8 yydefact[] = +{ + 0, 0, 0, 0, 9, 10, 0, 2, 3, 4, + 5, 6, 7, 8, 0, 12, 0, 1, 0, 0, + 20, 15, 19, 0, 17, 11, 13, 14, 16, 0, + 18 +}; + +/* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int8 yydefgoto[] = +{ + -1, 6, 22, 8, 9, 10, 11, 12, 13, 23, + 24 +}; + +/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +#define YYPACT_NINF -10 +static const yytype_int8 yypact[] = +{ + 19, -9, 19, 19, -10, -10, 8, -1, -10, -10, + -10, -10, -10, -10, 1, -10, 26, -10, 19, 19, + -10, -10, -1, -2, 3, -10, -10, 13, -10, 12, + -10 +}; + +/* YYPGOTO[NTERM-NUM]. */ +static const yytype_int8 yypgoto[] = +{ + -10, -10, 0, -10, -10, -10, -10, -10, -10, -3, + -10 +}; + +/* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule which + number is the opposite. If zero, do what YYDEFACT says. + If YYTABLE_NINF, syntax error. */ +#define YYTABLE_NINF -1 +static const yytype_uint8 yytable[] = +{ + 7, 14, 15, 16, 20, 1, 18, 19, 17, 28, + 2, 3, 21, 4, 5, 20, 1, 29, 26, 27, + 18, 2, 3, 1, 4, 5, 30, 0, 2, 3, + 0, 4, 5, 18, 19, 0, 0, 25 +}; + +static const yytype_int8 yycheck[] = +{ + 0, 10, 2, 3, 3, 4, 7, 8, 0, 11, + 9, 10, 11, 12, 13, 3, 4, 14, 18, 19, + 7, 9, 10, 4, 12, 13, 29, -1, 9, 10, + -1, 12, 13, 7, 8, -1, -1, 11 +}; + +/* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_uint8 yystos[] = +{ + 0, 4, 9, 10, 12, 13, 16, 17, 18, 19, + 20, 21, 22, 23, 10, 17, 17, 0, 7, 8, + 3, 11, 17, 24, 25, 11, 17, 17, 11, 14, + 24 +}; + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY (-2) +#define YYEOF 0 + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +/* Like YYERROR except do call yyerror. This remains here temporarily + to ease the transition to the new meaning of YYERROR, for GCC. + Once GCC version 2 has supplanted version 1, this can go. However, + YYFAIL appears to be in use. Nevertheless, it is formally deprecated + in Bison 2.4.2's NEWS entry, where a plan to phase it out is + discussed. */ + +#define YYFAIL goto yyerrlab +#if defined YYFAIL + /* This is here to suppress warnings from the GCC cpp's + -Wunused-macros. Normally we don't worry about that warning, but + some users do, and we want to make it easy for users to remove + YYFAIL uses, which will produce warnings from Bison 2.5. */ +#endif + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ +do \ + if (yychar == YYEMPTY && yylen == 1) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + yytoken = YYTRANSLATE (yychar); \ + YYPOPSTACK (1); \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (&yylloc, scanner, result, YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ +while (YYID (0)) + + +#define YYTERROR 1 +#define YYERRCODE 256 + + +/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. + If N is 0, then set CURRENT to the empty location which ends + the previous symbol: RHS[0] (always defined). */ + +#define YYRHSLOC(Rhs, K) ((Rhs)[K]) +#ifndef YYLLOC_DEFAULT +# define YYLLOC_DEFAULT(Current, Rhs, N) \ + do \ + if (YYID (N)) \ + { \ + (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ + (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ + (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ + (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ + } \ + else \ + { \ + (Current).first_line = (Current).last_line = \ + YYRHSLOC (Rhs, 0).last_line; \ + (Current).first_column = (Current).last_column = \ + YYRHSLOC (Rhs, 0).last_column; \ + } \ + while (YYID (0)) +#endif + + +/* YY_LOCATION_PRINT -- Print the location on the stream. + This macro was not mandated originally: define only if we know + we won't break user code: when these are the locations we know. */ + +#ifndef YY_LOCATION_PRINT +# if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL +# define YY_LOCATION_PRINT(File, Loc) \ + fprintf (File, "%d.%d-%d.%d", \ + (Loc).first_line, (Loc).first_column, \ + (Loc).last_line, (Loc).last_column) +# else +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +# endif +#endif + + +/* YYLEX -- calling `yylex' with the right arguments. */ + +#ifdef YYLEX_PARAM +# define YYLEX yylex (&yylval, &yylloc, YYLEX_PARAM) +#else +# define YYLEX yylex (&yylval, &yylloc) +#endif + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (YYID (0)) + +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Type, Value, Location, scanner, result); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (YYID (0)) + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, void *scanner, struct predicate_node **result) +#else +static void +yy_symbol_value_print (yyoutput, yytype, yyvaluep, yylocationp, scanner, result) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; + YYLTYPE const * const yylocationp; + void *scanner; + struct predicate_node **result; +#endif +{ + if (!yyvaluep) + return; + YYUSE (yylocationp); + YYUSE (scanner); + YYUSE (result); +# ifdef YYPRINT + if (yytype < YYNTOKENS) + YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); +# else + YYUSE (yyoutput); +# endif + switch (yytype) + { + default: + break; + } +} + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, void *scanner, struct predicate_node **result) +#else +static void +yy_symbol_print (yyoutput, yytype, yyvaluep, yylocationp, scanner, result) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; + YYLTYPE const * const yylocationp; + void *scanner; + struct predicate_node **result; +#endif +{ + if (yytype < YYNTOKENS) + YYFPRINTF (yyoutput, "token %s (", yytname[yytype]); + else + YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]); + + YY_LOCATION_PRINT (yyoutput, *yylocationp); + YYFPRINTF (yyoutput, ": "); + yy_symbol_value_print (yyoutput, yytype, yyvaluep, yylocationp, scanner, result); + YYFPRINTF (yyoutput, ")"); +} + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_stack_print (yytype_int16 *yybottom, yytype_int16 *yytop) +#else +static void +yy_stack_print (yybottom, yytop) + yytype_int16 *yybottom; + yytype_int16 *yytop; +#endif +{ + YYFPRINTF (stderr, "Stack now"); + for (; yybottom <= yytop; yybottom++) + { + int yybot = *yybottom; + YYFPRINTF (stderr, " %d", yybot); + } + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (YYID (0)) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_reduce_print (YYSTYPE *yyvsp, YYLTYPE *yylsp, int yyrule, void *scanner, struct predicate_node **result) +#else +static void +yy_reduce_print (yyvsp, yylsp, yyrule, scanner, result) + YYSTYPE *yyvsp; + YYLTYPE *yylsp; + int yyrule; + void *scanner; + struct predicate_node **result; +#endif +{ + int yynrhs = yyr2[yyrule]; + int yyi; + unsigned long int yylno = yyrline[yyrule]; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) + { + YYFPRINTF (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, yyrhs[yyprhs[yyrule] + yyi], + &(yyvsp[(yyi + 1) - (yynrhs)]) + , &(yylsp[(yyi + 1) - (yynrhs)]) , scanner, result); + YYFPRINTF (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyvsp, yylsp, Rule, scanner, result); \ +} while (YYID (0)) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + + +#if YYERROR_VERBOSE + +# ifndef yystrlen +# if defined __GLIBC__ && defined _STRING_H +# define yystrlen strlen +# else +/* Return the length of YYSTR. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static YYSIZE_T +yystrlen (const char *yystr) +#else +static YYSIZE_T +yystrlen (yystr) + const char *yystr; +#endif +{ + YYSIZE_T yylen; + for (yylen = 0; yystr[yylen]; yylen++) + continue; + return yylen; +} +# endif +# endif + +# ifndef yystpcpy +# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE +# define yystpcpy stpcpy +# else +/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in + YYDEST. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static char * +yystpcpy (char *yydest, const char *yysrc) +#else +static char * +yystpcpy (yydest, yysrc) + char *yydest; + const char *yysrc; +#endif +{ + char *yyd = yydest; + const char *yys = yysrc; + + while ((*yyd++ = *yys++) != '\0') + continue; + + return yyd - 1; +} +# endif +# endif + +# ifndef yytnamerr +/* Copy to YYRES the contents of YYSTR after stripping away unnecessary + quotes and backslashes, so that it's suitable for yyerror. The + heuristic is that double-quoting is unnecessary unless the string + contains an apostrophe, a comma, or backslash (other than + backslash-backslash). YYSTR is taken from yytname. If YYRES is + null, do not copy; instead, return the length of what the result + would have been. */ +static YYSIZE_T +yytnamerr (char *yyres, const char *yystr) +{ + if (*yystr == '"') + { + YYSIZE_T yyn = 0; + char const *yyp = yystr; + + for (;;) + switch (*++yyp) + { + case '\'': + case ',': + goto do_not_strip_quotes; + + case '\\': + if (*++yyp != '\\') + goto do_not_strip_quotes; + /* Fall through. */ + default: + if (yyres) + yyres[yyn] = *yyp; + yyn++; + break; + + case '"': + if (yyres) + yyres[yyn] = '\0'; + return yyn; + } + do_not_strip_quotes: ; + } + + if (! yyres) + return yystrlen (yystr); + + return yystpcpy (yyres, yystr) - yyres; +} +# endif + +/* Copy into YYRESULT an error message about the unexpected token + YYCHAR while in state YYSTATE. Return the number of bytes copied, + including the terminating null byte. If YYRESULT is null, do not + copy anything; just return the number of bytes that would be + copied. As a special case, return 0 if an ordinary "syntax error" + message will do. Return YYSIZE_MAXIMUM if overflow occurs during + size calculation. */ +static YYSIZE_T +yysyntax_error (char *yyresult, int yystate, int yychar) +{ + int yyn = yypact[yystate]; + + if (! (YYPACT_NINF < yyn && yyn <= YYLAST)) + return 0; + else + { + int yytype = YYTRANSLATE (yychar); + YYSIZE_T yysize0 = yytnamerr (0, yytname[yytype]); + YYSIZE_T yysize = yysize0; + YYSIZE_T yysize1; + int yysize_overflow = 0; + enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; + char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; + int yyx; + +# if 0 + /* This is so xgettext sees the translatable formats that are + constructed on the fly. */ + YY_("syntax error, unexpected %s"); + YY_("syntax error, unexpected %s, expecting %s"); + YY_("syntax error, unexpected %s, expecting %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"); +# endif + char *yyfmt; + char const *yyf; + static char const yyunexpected[] = "syntax error, unexpected %s"; + static char const yyexpecting[] = ", expecting %s"; + static char const yyor[] = " or %s"; + char yyformat[sizeof yyunexpected + + sizeof yyexpecting - 1 + + ((YYERROR_VERBOSE_ARGS_MAXIMUM - 2) + * (sizeof yyor - 1))]; + char const *yyprefix = yyexpecting; + + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. */ + int yyxbegin = yyn < 0 ? -yyn : 0; + + /* Stay within bounds of both yycheck and yytname. */ + int yychecklim = YYLAST - yyn + 1; + int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + int yycount = 1; + + yyarg[0] = yytname[yytype]; + yyfmt = yystpcpy (yyformat, yyunexpected); + + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR) + { + if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) + { + yycount = 1; + yysize = yysize0; + yyformat[sizeof yyunexpected - 1] = '\0'; + break; + } + yyarg[yycount++] = yytname[yyx]; + yysize1 = yysize + yytnamerr (0, yytname[yyx]); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + yyfmt = yystpcpy (yyfmt, yyprefix); + yyprefix = yyor; + } + + yyf = YY_(yyformat); + yysize1 = yysize + yystrlen (yyf); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + + if (yysize_overflow) + return YYSIZE_MAXIMUM; + + if (yyresult) + { + /* Avoid sprintf, as that infringes on the user's name space. + Don't have undefined behavior even if the translation + produced a string with the wrong number of "%s"s. */ + char *yyp = yyresult; + int yyi = 0; + while ((*yyp = *yyf) != '\0') + { + if (*yyp == '%' && yyf[1] == 's' && yyi < yycount) + { + yyp += yytnamerr (yyp, yyarg[yyi++]); + yyf += 2; + } + else + { + yyp++; + yyf++; + } + } + } + return yysize; + } +} +#endif /* YYERROR_VERBOSE */ + + +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep, YYLTYPE *yylocationp, void *scanner, struct predicate_node **result) +#else +static void +yydestruct (yymsg, yytype, yyvaluep, yylocationp, scanner, result) + const char *yymsg; + int yytype; + YYSTYPE *yyvaluep; + YYLTYPE *yylocationp; + void *scanner; + struct predicate_node **result; +#endif +{ + YYUSE (yyvaluep); + YYUSE (yylocationp); + YYUSE (scanner); + YYUSE (result); + + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); + + switch (yytype) + { + case 3: /* "STRING" */ + +/* Line 1009 of yacc.c */ +#line 233 "predicate/BPredicate.y" + { + free((yyvaluep->text)); +}; + +/* Line 1009 of yacc.c */ +#line 1295 "generated//bison_BPredicate.c" + break; + case 4: /* "NAME" */ + +/* Line 1009 of yacc.c */ +#line 233 "predicate/BPredicate.y" + { + free((yyvaluep->text)); +}; + +/* Line 1009 of yacc.c */ +#line 1306 "generated//bison_BPredicate.c" + break; + case 17: /* "predicate" */ + +/* Line 1009 of yacc.c */ +#line 243 "predicate/BPredicate.y" + { + if ((yyvaluep->node)) { + free_predicate_node((yyvaluep->node)); + } +}; + +/* Line 1009 of yacc.c */ +#line 1319 "generated//bison_BPredicate.c" + break; + case 18: /* "constant" */ + +/* Line 1009 of yacc.c */ +#line 243 "predicate/BPredicate.y" + { + if ((yyvaluep->node)) { + free_predicate_node((yyvaluep->node)); + } +}; + +/* Line 1009 of yacc.c */ +#line 1332 "generated//bison_BPredicate.c" + break; + case 19: /* "parentheses" */ + +/* Line 1009 of yacc.c */ +#line 243 "predicate/BPredicate.y" + { + if ((yyvaluep->node)) { + free_predicate_node((yyvaluep->node)); + } +}; + +/* Line 1009 of yacc.c */ +#line 1345 "generated//bison_BPredicate.c" + break; + case 20: /* "neg" */ + +/* Line 1009 of yacc.c */ +#line 243 "predicate/BPredicate.y" + { + if ((yyvaluep->node)) { + free_predicate_node((yyvaluep->node)); + } +}; + +/* Line 1009 of yacc.c */ +#line 1358 "generated//bison_BPredicate.c" + break; + case 21: /* "conjunct" */ + +/* Line 1009 of yacc.c */ +#line 243 "predicate/BPredicate.y" + { + if ((yyvaluep->node)) { + free_predicate_node((yyvaluep->node)); + } +}; + +/* Line 1009 of yacc.c */ +#line 1371 "generated//bison_BPredicate.c" + break; + case 22: /* "disjunct" */ + +/* Line 1009 of yacc.c */ +#line 243 "predicate/BPredicate.y" + { + if ((yyvaluep->node)) { + free_predicate_node((yyvaluep->node)); + } +}; + +/* Line 1009 of yacc.c */ +#line 1384 "generated//bison_BPredicate.c" + break; + case 23: /* "function" */ + +/* Line 1009 of yacc.c */ +#line 243 "predicate/BPredicate.y" + { + if ((yyvaluep->node)) { + free_predicate_node((yyvaluep->node)); + } +}; + +/* Line 1009 of yacc.c */ +#line 1397 "generated//bison_BPredicate.c" + break; + case 24: /* "arguments" */ + +/* Line 1009 of yacc.c */ +#line 250 "predicate/BPredicate.y" + { + if ((yyvaluep->arg_node)) { + free_arguments_node((yyvaluep->arg_node)); + } +}; + +/* Line 1009 of yacc.c */ +#line 1410 "generated//bison_BPredicate.c" + break; + case 25: /* "argument" */ + +/* Line 1009 of yacc.c */ +#line 257 "predicate/BPredicate.y" + { + free_argument((yyvaluep->arg_arg)); +}; + +/* Line 1009 of yacc.c */ +#line 1421 "generated//bison_BPredicate.c" + break; + + default: + break; + } +} + +/* Prevent warnings from -Wmissing-prototypes. */ +#ifdef YYPARSE_PARAM +#if defined __STDC__ || defined __cplusplus +int yyparse (void *YYPARSE_PARAM); +#else +int yyparse (); +#endif +#else /* ! YYPARSE_PARAM */ +#if defined __STDC__ || defined __cplusplus +int yyparse (void *scanner, struct predicate_node **result); +#else +int yyparse (); +#endif +#endif /* ! YYPARSE_PARAM */ + + + + + +/*-------------------------. +| yyparse or yypush_parse. | +`-------------------------*/ + +#ifdef YYPARSE_PARAM +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (void *YYPARSE_PARAM) +#else +int +yyparse (YYPARSE_PARAM) + void *YYPARSE_PARAM; +#endif +#else /* ! YYPARSE_PARAM */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (void *scanner, struct predicate_node **result) +#else +int +yyparse (scanner, result) + void *scanner; + struct predicate_node **result; +#endif +#endif +{ +/* The lookahead symbol. */ +int yychar; + +/* The semantic value of the lookahead symbol. */ +YYSTYPE yylval; + +/* Location data for the lookahead symbol. */ +YYLTYPE yylloc; + + /* Number of syntax errors so far. */ + int yynerrs; + + int yystate; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus; + + /* The stacks and their tools: + `yyss': related to states. + `yyvs': related to semantic values. + `yyls': related to locations. + + Refer to the stacks thru separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* The state stack. */ + yytype_int16 yyssa[YYINITDEPTH]; + yytype_int16 *yyss; + yytype_int16 *yyssp; + + /* The semantic value stack. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs; + YYSTYPE *yyvsp; + + /* The location stack. */ + YYLTYPE yylsa[YYINITDEPTH]; + YYLTYPE *yyls; + YYLTYPE *yylsp; + + /* The locations where the error started and ended. */ + YYLTYPE yyerror_range[3]; + + YYSIZE_T yystacksize; + + int yyn; + int yyresult; + /* Lookahead token as an internal (translated) token number. */ + int yytoken; + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + YYLTYPE yyloc; + +#if YYERROR_VERBOSE + /* Buffer for error messages, and its allocated size. */ + char yymsgbuf[128]; + char *yymsg = yymsgbuf; + YYSIZE_T yymsg_alloc = sizeof yymsgbuf; +#endif + +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N), yylsp -= (N)) + + /* The number of symbols on the RHS of the reduced rule. + Keep to zero when no symbol should be popped. */ + int yylen = 0; + + yytoken = 0; + yyss = yyssa; + yyvs = yyvsa; + yyls = yylsa; + yystacksize = YYINITDEPTH; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yystate = 0; + yyerrstatus = 0; + yynerrs = 0; + yychar = YYEMPTY; /* Cause a token to be read. */ + + /* Initialize stack pointers. + Waste one element of value and location stack + so that they stay on the same level as the state stack. + The wasted elements are never initialized. */ + yyssp = yyss; + yyvsp = yyvs; + yylsp = yyls; + +#if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL + /* Initialize the default location before parsing starts. */ + yylloc.first_line = yylloc.last_line = 1; + yylloc.first_column = yylloc.last_column = 1; +#endif + + goto yysetstate; + +/*------------------------------------------------------------. +| yynewstate -- Push a new state, which is found in yystate. | +`------------------------------------------------------------*/ + yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. So pushing a state here evens the stacks. */ + yyssp++; + + yysetstate: + *yyssp = yystate; + + if (yyss + yystacksize - 1 <= yyssp) + { + /* Get the current used size of the three stacks, in elements. */ + YYSIZE_T yysize = yyssp - yyss + 1; + +#ifdef yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + YYSTYPE *yyvs1 = yyvs; + yytype_int16 *yyss1 = yyss; + YYLTYPE *yyls1 = yyls; + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * sizeof (*yyssp), + &yyvs1, yysize * sizeof (*yyvsp), + &yyls1, yysize * sizeof (*yylsp), + &yystacksize); + + yyls = yyls1; + yyss = yyss1; + yyvs = yyvs1; + } +#else /* no yyoverflow */ +# ifndef YYSTACK_RELOCATE + goto yyexhaustedlab; +# else + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyexhaustedlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yytype_int16 *yyss1 = yyss; + union yyalloc *yyptr = + (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + if (! yyptr) + goto yyexhaustedlab; + YYSTACK_RELOCATE (yyss_alloc, yyss); + YYSTACK_RELOCATE (yyvs_alloc, yyvs); + YYSTACK_RELOCATE (yyls_alloc, yyls); +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif +#endif /* no yyoverflow */ + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + yylsp = yyls + yysize - 1; + + YYDPRINTF ((stderr, "Stack size increased to %lu\n", + (unsigned long int) yystacksize)); + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } + + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + + if (yystate == YYFINAL) + YYACCEPT; + + goto yybackup; + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + + /* Do appropriate processing given the current state. Read a + lookahead token if we need one and don't already have one. */ + + /* First try to decide what to do without reference to lookahead token. */ + yyn = yypact[yystate]; + if (yyn == YYPACT_NINF) + goto yydefault; + + /* Not known => get a lookahead token if don't already have one. */ + + /* YYCHAR is either YYEMPTY or YYEOF or a valid lookahead symbol. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token: ")); + yychar = YYLEX; + } + + if (yychar <= YYEOF) + { + yychar = yytoken = YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yyn == 0 || yyn == YYTABLE_NINF) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + /* Shift the lookahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + + /* Discard the shifted token. */ + yychar = YYEMPTY; + + yystate = yyn; + *++yyvsp = yylval; + *++yylsp = yylloc; + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- Do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + `$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + /* Default location. */ + YYLLOC_DEFAULT (yyloc, (yylsp - yylen), yylen); + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 2: + +/* Line 1464 of yacc.c */ +#line 269 "predicate/BPredicate.y" + { + *result = (yyvsp[(1) - (1)].node); + ;} + break; + + case 9: + +/* Line 1464 of yacc.c */ +#line 277 "predicate/BPredicate.y" + { + (yyval.node) = make_constant(1); + ;} + break; + + case 10: + +/* Line 1464 of yacc.c */ +#line 281 "predicate/BPredicate.y" + { + (yyval.node) = make_constant(0); + ;} + break; + + case 11: + +/* Line 1464 of yacc.c */ +#line 287 "predicate/BPredicate.y" + { + (yyval.node) = (yyvsp[(2) - (3)].node); + ;} + break; + + case 12: + +/* Line 1464 of yacc.c */ +#line 293 "predicate/BPredicate.y" + { + (yyval.node) = make_negation((yyvsp[(2) - (2)].node)); + ;} + break; + + case 13: + +/* Line 1464 of yacc.c */ +#line 299 "predicate/BPredicate.y" + { + (yyval.node) = make_conjunction((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].node)); + ;} + break; + + case 14: + +/* Line 1464 of yacc.c */ +#line 305 "predicate/BPredicate.y" + { + (yyval.node) = make_disjunction((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].node)); + ;} + break; + + case 15: + +/* Line 1464 of yacc.c */ +#line 311 "predicate/BPredicate.y" + { + (yyval.node) = make_function((yyvsp[(1) - (3)].text), NULL, 0); + ;} + break; + + case 16: + +/* Line 1464 of yacc.c */ +#line 315 "predicate/BPredicate.y" + { + (yyval.node) = make_function((yyvsp[(1) - (4)].text), (yyvsp[(3) - (4)].arg_node), 1); + ;} + break; + + case 17: + +/* Line 1464 of yacc.c */ +#line 321 "predicate/BPredicate.y" + { + (yyval.arg_node) = make_arguments((yyvsp[(1) - (1)].arg_arg), NULL, 0); + ;} + break; + + case 18: + +/* Line 1464 of yacc.c */ +#line 325 "predicate/BPredicate.y" + { + (yyval.arg_node) = make_arguments((yyvsp[(1) - (3)].arg_arg), (yyvsp[(3) - (3)].arg_node), 1); + ;} + break; + + case 19: + +/* Line 1464 of yacc.c */ +#line 331 "predicate/BPredicate.y" + { + (yyval.arg_arg) = make_argument_predicate((yyvsp[(1) - (1)].node)); + ;} + break; + + case 20: + +/* Line 1464 of yacc.c */ +#line 335 "predicate/BPredicate.y" + { + (yyval.arg_arg) = make_argument_string((yyvsp[(1) - (1)].text)); + ;} + break; + + + +/* Line 1464 of yacc.c */ +#line 1870 "generated//bison_BPredicate.c" + default: break; + } + YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); + + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + + *++yyvsp = yyval; + *++yylsp = yyloc; + + /* Now `shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + + yyn = yyr1[yyn]; + + yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; + if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTOKENS]; + + goto yynewstate; + + +/*------------------------------------. +| yyerrlab -- here on detecting error | +`------------------------------------*/ +yyerrlab: + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; +#if ! YYERROR_VERBOSE + yyerror (&yylloc, scanner, result, YY_("syntax error")); +#else + { + YYSIZE_T yysize = yysyntax_error (0, yystate, yychar); + if (yymsg_alloc < yysize && yymsg_alloc < YYSTACK_ALLOC_MAXIMUM) + { + YYSIZE_T yyalloc = 2 * yysize; + if (! (yysize <= yyalloc && yyalloc <= YYSTACK_ALLOC_MAXIMUM)) + yyalloc = YYSTACK_ALLOC_MAXIMUM; + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); + yymsg = (char *) YYSTACK_ALLOC (yyalloc); + if (yymsg) + yymsg_alloc = yyalloc; + else + { + yymsg = yymsgbuf; + yymsg_alloc = sizeof yymsgbuf; + } + } + + if (0 < yysize && yysize <= yymsg_alloc) + { + (void) yysyntax_error (yymsg, yystate, yychar); + yyerror (&yylloc, scanner, result, yymsg); + } + else + { + yyerror (&yylloc, scanner, result, YY_("syntax error")); + if (yysize != 0) + goto yyexhaustedlab; + } + } +#endif + } + + yyerror_range[1] = yylloc; + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse lookahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* Return failure if at end of input. */ + if (yychar == YYEOF) + YYABORT; + } + else + { + yydestruct ("Error: discarding", + yytoken, &yylval, &yylloc, scanner, result); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse lookahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + + /* Pacify compilers like GCC when the user code never invokes + YYERROR and the label yyerrorlab therefore never appears in user + code. */ + if (/*CONSTCOND*/ 0) + goto yyerrorlab; + + yyerror_range[1] = yylsp[1-yylen]; + /* Do not reclaim the symbols of the rule which action triggered + this YYERROR. */ + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + for (;;) + { + yyn = yypact[yystate]; + if (yyn != YYPACT_NINF) + { + yyn += YYTERROR; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + yyerror_range[1] = *yylsp; + yydestruct ("Error: popping", + yystos[yystate], yyvsp, yylsp, scanner, result); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + *++yyvsp = yylval; + + yyerror_range[2] = yylloc; + /* Using YYLLOC is tempting, but would change the location of + the lookahead. YYLOC is available though. */ + YYLLOC_DEFAULT (yyloc, yyerror_range, 2); + *++yylsp = yyloc; + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturn; + +#if !defined(yyoverflow) || YYERROR_VERBOSE +/*-------------------------------------------------. +| yyexhaustedlab -- memory exhaustion comes here. | +`-------------------------------------------------*/ +yyexhaustedlab: + yyerror (&yylloc, scanner, result, YY_("memory exhausted")); + yyresult = 2; + /* Fall through. */ +#endif + +yyreturn: + if (yychar != YYEMPTY) + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval, &yylloc, scanner, result); + /* Do not reclaim the symbols of the rule which action triggered + this YYABORT or YYACCEPT. */ + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) + { + yydestruct ("Cleanup: popping", + yystos[*yyssp], yyvsp, yylsp, scanner, result); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif +#if YYERROR_VERBOSE + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); +#endif + /* Make sure YYID is used. */ + return YYID (yyresult); +} + + + diff --git a/generated/bison_BPredicate.h b/generated/bison_BPredicate.h new file mode 100644 index 000000000..0ba80051a --- /dev/null +++ b/generated/bison_BPredicate.h @@ -0,0 +1,97 @@ +/* A Bison parser, made by GNU Bison 2.4.3. */ + +/* Skeleton interface for Bison's Yacc-like parsers in C + + Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006, + 2009, 2010 Free Software Foundation, Inc. + + 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 3 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, see . */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + STRING = 258, + NAME = 259, + PEER1_NAME = 260, + PEER2_NAME = 261, + AND = 262, + OR = 263, + NOT = 264, + SPAR = 265, + EPAR = 266, + CONSTANT_TRUE = 267, + CONSTANT_FALSE = 268, + COMMA = 269 + }; +#endif + + + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE +{ + +/* Line 1685 of yacc.c */ +#line 220 "predicate/BPredicate.y" + + char *text; + struct predicate_node *node; + struct arguments_node *arg_node; + struct predicate_node nfaw; + struct arguments_arg arg_arg; + + + +/* Line 1685 of yacc.c */ +#line 75 "generated//bison_BPredicate.h" +} YYSTYPE; +# define YYSTYPE_IS_TRIVIAL 1 +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +#endif + + + +#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED +typedef struct YYLTYPE +{ + int first_line; + int first_column; + int last_line; + int last_column; +} YYLTYPE; +# define yyltype YYLTYPE /* obsolescent; will be withdrawn */ +# define YYLTYPE_IS_DECLARED 1 +# define YYLTYPE_IS_TRIVIAL 1 +#endif + + + diff --git a/generated/blog_channel_BPredicate.h b/generated/blog_channel_BPredicate.h new file mode 100644 index 000000000..1a683f135 --- /dev/null +++ b/generated/blog_channel_BPredicate.h @@ -0,0 +1,4 @@ +#ifdef BLOG_CURRENT_CHANNEL +#undef BLOG_CURRENT_CHANNEL +#endif +#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_BPredicate diff --git a/generated/blog_channel_BReactor.h b/generated/blog_channel_BReactor.h new file mode 100644 index 000000000..d111dc79d --- /dev/null +++ b/generated/blog_channel_BReactor.h @@ -0,0 +1,4 @@ +#ifdef BLOG_CURRENT_CHANNEL +#undef BLOG_CURRENT_CHANNEL +#endif +#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_BReactor diff --git a/generated/blog_channel_BSignal.h b/generated/blog_channel_BSignal.h new file mode 100644 index 000000000..2820ebce1 --- /dev/null +++ b/generated/blog_channel_BSignal.h @@ -0,0 +1,4 @@ +#ifdef BLOG_CURRENT_CHANNEL +#undef BLOG_CURRENT_CHANNEL +#endif +#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_BSignal diff --git a/generated/blog_channel_DataProto.h b/generated/blog_channel_DataProto.h new file mode 100644 index 000000000..a6f900a39 --- /dev/null +++ b/generated/blog_channel_DataProto.h @@ -0,0 +1,4 @@ +#ifdef BLOG_CURRENT_CHANNEL +#undef BLOG_CURRENT_CHANNEL +#endif +#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_DataProto diff --git a/generated/blog_channel_DatagramPeerIO.h b/generated/blog_channel_DatagramPeerIO.h new file mode 100644 index 000000000..16e37b5b2 --- /dev/null +++ b/generated/blog_channel_DatagramPeerIO.h @@ -0,0 +1,4 @@ +#ifdef BLOG_CURRENT_CHANNEL +#undef BLOG_CURRENT_CHANNEL +#endif +#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_DatagramPeerIO diff --git a/generated/blog_channel_FragmentProtoAssembler.h b/generated/blog_channel_FragmentProtoAssembler.h new file mode 100644 index 000000000..25289efbf --- /dev/null +++ b/generated/blog_channel_FragmentProtoAssembler.h @@ -0,0 +1,4 @@ +#ifdef BLOG_CURRENT_CHANNEL +#undef BLOG_CURRENT_CHANNEL +#endif +#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_FragmentProtoAssembler diff --git a/generated/blog_channel_Listener.h b/generated/blog_channel_Listener.h new file mode 100644 index 000000000..f61bfb3db --- /dev/null +++ b/generated/blog_channel_Listener.h @@ -0,0 +1,4 @@ +#ifdef BLOG_CURRENT_CHANNEL +#undef BLOG_CURRENT_CHANNEL +#endif +#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_Listener diff --git a/generated/blog_channel_ServerConnection.h b/generated/blog_channel_ServerConnection.h new file mode 100644 index 000000000..faea1dddc --- /dev/null +++ b/generated/blog_channel_ServerConnection.h @@ -0,0 +1,4 @@ +#ifdef BLOG_CURRENT_CHANNEL +#undef BLOG_CURRENT_CHANNEL +#endif +#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ServerConnection diff --git a/generated/blog_channel_StreamPeerIO.h b/generated/blog_channel_StreamPeerIO.h new file mode 100644 index 000000000..0359736e7 --- /dev/null +++ b/generated/blog_channel_StreamPeerIO.h @@ -0,0 +1,4 @@ +#ifdef BLOG_CURRENT_CHANNEL +#undef BLOG_CURRENT_CHANNEL +#endif +#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_StreamPeerIO diff --git a/generated/blog_channel_client.h b/generated/blog_channel_client.h new file mode 100644 index 000000000..c851b77bd --- /dev/null +++ b/generated/blog_channel_client.h @@ -0,0 +1,4 @@ +#ifdef BLOG_CURRENT_CHANNEL +#undef BLOG_CURRENT_CHANNEL +#endif +#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_client diff --git a/generated/blog_channel_flooder.h b/generated/blog_channel_flooder.h new file mode 100644 index 000000000..94f595eb2 --- /dev/null +++ b/generated/blog_channel_flooder.h @@ -0,0 +1,4 @@ +#ifdef BLOG_CURRENT_CHANNEL +#undef BLOG_CURRENT_CHANNEL +#endif +#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_flooder diff --git a/generated/blog_channel_server.h b/generated/blog_channel_server.h new file mode 100644 index 000000000..acb3ed0d1 --- /dev/null +++ b/generated/blog_channel_server.h @@ -0,0 +1,4 @@ +#ifdef BLOG_CURRENT_CHANNEL +#undef BLOG_CURRENT_CHANNEL +#endif +#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_server diff --git a/generated/blog_channels_defines.h b/generated/blog_channels_defines.h new file mode 100644 index 000000000..20d76cf48 --- /dev/null +++ b/generated/blog_channels_defines.h @@ -0,0 +1,13 @@ +#define BLOG_CHANNEL_server 0 +#define BLOG_CHANNEL_client 1 +#define BLOG_CHANNEL_StreamPeerIO 2 +#define BLOG_CHANNEL_DatagramPeerIO 3 +#define BLOG_CHANNEL_BReactor 4 +#define BLOG_CHANNEL_BSignal 5 +#define BLOG_CHANNEL_FragmentProtoAssembler 6 +#define BLOG_CHANNEL_BPredicate 7 +#define BLOG_CHANNEL_ServerConnection 8 +#define BLOG_CHANNEL_flooder 9 +#define BLOG_CHANNEL_Listener 10 +#define BLOG_CHANNEL_DataProto 11 +#define BLOG_NUM_CHANNELS 12 diff --git a/generated/blog_channels_list.h b/generated/blog_channels_list.h new file mode 100644 index 000000000..359bfd07c --- /dev/null +++ b/generated/blog_channels_list.h @@ -0,0 +1,12 @@ +{.name = "server", .loglevel = 4}, +{.name = "client", .loglevel = 4}, +{.name = "StreamPeerIO", .loglevel = 4}, +{.name = "DatagramPeerIO", .loglevel = 4}, +{.name = "BReactor", .loglevel = 3}, +{.name = "BSignal", .loglevel = 3}, +{.name = "FragmentProtoAssembler", .loglevel = 4}, +{.name = "BPredicate", .loglevel = 3}, +{.name = "ServerConnection", .loglevel = 4}, +{.name = "flooder", .loglevel = 4}, +{.name = "Listener", .loglevel = 4}, +{.name = "DataProto", .loglevel = 4}, diff --git a/generated/bproto_addr.h b/generated/bproto_addr.h new file mode 100644 index 000000000..b4d589e04 --- /dev/null +++ b/generated/bproto_addr.h @@ -0,0 +1,667 @@ +/* + DO NOT EDIT THIS FILE! + This file was automatically generated by the bproto generator. +*/ + +#include + +#include +#include +#include + + +#define addr_SIZEtype (sizeof(struct BProto_header_s) + sizeof(struct BProto_uint8_s)) +#define addr_SIZEip_port (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (2)) +#define addr_SIZEipv4_addr (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (4)) +#define addr_SIZEipv6_addr (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (16)) + +typedef struct { + uint8_t *out; + int used; + int type_count; + int ip_port_count; + int ipv4_addr_count; + int ipv6_addr_count; +} addrWriter; + +static void addrWriter_Init (addrWriter *o, uint8_t *out); +static int addrWriter_Finish (addrWriter *o); +static void addrWriter_Addtype (addrWriter *o, uint8_t v); +static uint8_t * addrWriter_Addip_port (addrWriter *o); +static uint8_t * addrWriter_Addipv4_addr (addrWriter *o); +static uint8_t * addrWriter_Addipv6_addr (addrWriter *o); + +typedef struct { + uint8_t *buf; + int buf_len; + int type_start; + int type_span; + int type_pos; + int ip_port_start; + int ip_port_span; + int ip_port_pos; + int ipv4_addr_start; + int ipv4_addr_span; + int ipv4_addr_pos; + int ipv6_addr_start; + int ipv6_addr_span; + int ipv6_addr_pos; +} addrParser; + +static int addrParser_Init (addrParser *o, uint8_t *buf, int buf_len); +static int addrParser_GotEverything (addrParser *o); +static int addrParser_Gettype (addrParser *o, uint8_t *v); +static void addrParser_Resettype (addrParser *o); +static void addrParser_Forwardtype (addrParser *o); +static int addrParser_Getip_port (addrParser *o, uint8_t **data); +static void addrParser_Resetip_port (addrParser *o); +static void addrParser_Forwardip_port (addrParser *o); +static int addrParser_Getipv4_addr (addrParser *o, uint8_t **data); +static void addrParser_Resetipv4_addr (addrParser *o); +static void addrParser_Forwardipv4_addr (addrParser *o); +static int addrParser_Getipv6_addr (addrParser *o, uint8_t **data); +static void addrParser_Resetipv6_addr (addrParser *o); +static void addrParser_Forwardipv6_addr (addrParser *o); + +void addrWriter_Init (addrWriter *o, uint8_t *out) +{ + o->out = out; + o->used = 0; + o->type_count = 0; + o->ip_port_count = 0; + o->ipv4_addr_count = 0; + o->ipv6_addr_count = 0; +} + +int addrWriter_Finish (addrWriter *o) +{ + ASSERT(o->used >= 0) + ASSERT(o->type_count == 1) + ASSERT(o->ip_port_count >= 0 && o->ip_port_count <= 1) + ASSERT(o->ipv4_addr_count >= 0 && o->ipv4_addr_count <= 1) + ASSERT(o->ipv6_addr_count >= 0 && o->ipv6_addr_count <= 1) + + return o->used; +} + +void addrWriter_Addtype (addrWriter *o, uint8_t v) +{ + ASSERT(o->used >= 0) + ASSERT(o->type_count == 0) + + + ((struct BProto_header_s *)(o->out + o->used))->id = htol16(1); + ((struct BProto_header_s *)(o->out + o->used))->type = htol16(BPROTO_TYPE_UINT8); + o->used += sizeof(struct BProto_header_s); + + ((struct BProto_uint8_s *)(o->out + o->used))->v = htol8(v); + o->used += sizeof(struct BProto_uint8_s); + + o->type_count++; +} + +uint8_t * addrWriter_Addip_port (addrWriter *o) +{ + ASSERT(o->used >= 0) + ASSERT(o->ip_port_count == 0) + + + ((struct BProto_header_s *)(o->out + o->used))->id = htol16(2); + ((struct BProto_header_s *)(o->out + o->used))->type = htol16(BPROTO_TYPE_CONSTDATA); + o->used += sizeof(struct BProto_header_s); + + ((struct BProto_data_header_s *)(o->out + o->used))->len = htol32(2); + o->used += sizeof(struct BProto_data_header_s); + + uint8_t *dest = (o->out + o->used); + o->used += (2); + + o->ip_port_count++; + + return dest; +} + +uint8_t * addrWriter_Addipv4_addr (addrWriter *o) +{ + ASSERT(o->used >= 0) + ASSERT(o->ipv4_addr_count == 0) + + + ((struct BProto_header_s *)(o->out + o->used))->id = htol16(3); + ((struct BProto_header_s *)(o->out + o->used))->type = htol16(BPROTO_TYPE_CONSTDATA); + o->used += sizeof(struct BProto_header_s); + + ((struct BProto_data_header_s *)(o->out + o->used))->len = htol32(4); + o->used += sizeof(struct BProto_data_header_s); + + uint8_t *dest = (o->out + o->used); + o->used += (4); + + o->ipv4_addr_count++; + + return dest; +} + +uint8_t * addrWriter_Addipv6_addr (addrWriter *o) +{ + ASSERT(o->used >= 0) + ASSERT(o->ipv6_addr_count == 0) + + + ((struct BProto_header_s *)(o->out + o->used))->id = htol16(4); + ((struct BProto_header_s *)(o->out + o->used))->type = htol16(BPROTO_TYPE_CONSTDATA); + o->used += sizeof(struct BProto_header_s); + + ((struct BProto_data_header_s *)(o->out + o->used))->len = htol32(16); + o->used += sizeof(struct BProto_data_header_s); + + uint8_t *dest = (o->out + o->used); + o->used += (16); + + o->ipv6_addr_count++; + + return dest; +} + +int addrParser_Init (addrParser *o, uint8_t *buf, int buf_len) +{ + ASSERT(buf_len >= 0) + + o->buf = buf; + o->buf_len = buf_len; + o->type_start = o->buf_len; + o->type_span = 0; + o->type_pos = 0; + o->ip_port_start = o->buf_len; + o->ip_port_span = 0; + o->ip_port_pos = 0; + o->ipv4_addr_start = o->buf_len; + o->ipv4_addr_span = 0; + o->ipv4_addr_pos = 0; + o->ipv6_addr_start = o->buf_len; + o->ipv6_addr_span = 0; + o->ipv6_addr_pos = 0; + + int type_count = 0; + int ip_port_count = 0; + int ipv4_addr_count = 0; + int ipv6_addr_count = 0; + + int pos = 0; + int left = o->buf_len; + + while (left > 0) { + int entry_pos = pos; + + if (!(left >= sizeof(struct BProto_header_s))) { + return 0; + } + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + pos); + pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + if (!(left >= sizeof(struct BProto_uint8_s))) { + return 0; + } + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + + switch (id) { + case 1: + if (o->type_start == o->buf_len) { + o->type_start = entry_pos; + } + o->type_span = pos - o->type_start; + type_count++; + break; + default: + return 0; + } + } break; + case BPROTO_TYPE_UINT16: { + if (!(left >= sizeof(struct BProto_uint16_s))) { + return 0; + } + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + + switch (id) { + default: + return 0; + } + } break; + case BPROTO_TYPE_UINT32: { + if (!(left >= sizeof(struct BProto_uint32_s))) { + return 0; + } + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + + switch (id) { + default: + return 0; + } + } break; + case BPROTO_TYPE_UINT64: { + if (!(left >= sizeof(struct BProto_uint64_s))) { + return 0; + } + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + + switch (id) { + default: + return 0; + } + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + if (!(left >= sizeof(struct BProto_data_header_s))) { + return 0; + } + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + pos); + pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + if (!(left >= payload_len)) { + return 0; + } + pos += payload_len; + left -= payload_len; + + switch (id) { + case 2: + if (!(type == BPROTO_TYPE_CONSTDATA)) { + return 0; + } + if (!(payload_len == (2))) { + return 0; + } + if (o->ip_port_start == o->buf_len) { + o->ip_port_start = entry_pos; + } + o->ip_port_span = pos - o->ip_port_start; + ip_port_count++; + break; + case 3: + if (!(type == BPROTO_TYPE_CONSTDATA)) { + return 0; + } + if (!(payload_len == (4))) { + return 0; + } + if (o->ipv4_addr_start == o->buf_len) { + o->ipv4_addr_start = entry_pos; + } + o->ipv4_addr_span = pos - o->ipv4_addr_start; + ipv4_addr_count++; + break; + case 4: + if (!(type == BPROTO_TYPE_CONSTDATA)) { + return 0; + } + if (!(payload_len == (16))) { + return 0; + } + if (o->ipv6_addr_start == o->buf_len) { + o->ipv6_addr_start = entry_pos; + } + o->ipv6_addr_span = pos - o->ipv6_addr_start; + ipv6_addr_count++; + break; + default: + return 0; + } + } break; + default: + return 0; + } + } + + if (!(type_count == 1)) { + return 0; + } + if (!(ip_port_count <= 1)) { + return 0; + } + if (!(ipv4_addr_count <= 1)) { + return 0; + } + if (!(ipv6_addr_count <= 1)) { + return 0; + } + + return 1; +} + +int addrParser_GotEverything (addrParser *o) +{ + return ( + o->type_pos == o->type_span + && + o->ip_port_pos == o->ip_port_span + && + o->ipv4_addr_pos == o->ipv4_addr_span + && + o->ipv6_addr_pos == o->ipv6_addr_span + ); +} + +int addrParser_Gettype (addrParser *o, uint8_t *v) +{ + ASSERT(o->type_pos >= 0) + ASSERT(o->type_pos <= o->type_span) + + int left = o->type_span - o->type_pos; + + while (left > 0) { + ASSERT(left >= sizeof(struct BProto_header_s)) + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + o->type_start + o->type_pos); + o->type_pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + ASSERT(left >= sizeof(struct BProto_uint8_s)) + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + o->type_start + o->type_pos); + o->type_pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + + if (id == 1) { + *v = ltoh8(val->v); + return 1; + } + } break; + case BPROTO_TYPE_UINT16: { + ASSERT(left >= sizeof(struct BProto_uint16_s)) + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + o->type_start + o->type_pos); + o->type_pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + } break; + case BPROTO_TYPE_UINT32: { + ASSERT(left >= sizeof(struct BProto_uint32_s)) + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + o->type_start + o->type_pos); + o->type_pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + } break; + case BPROTO_TYPE_UINT64: { + ASSERT(left >= sizeof(struct BProto_uint64_s)) + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + o->type_start + o->type_pos); + o->type_pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + ASSERT(left >= sizeof(struct BProto_data_header_s)) + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + o->type_start + o->type_pos); + o->type_pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + ASSERT(left >= payload_len) + uint8_t *payload = o->buf + o->type_start + o->type_pos; + o->type_pos += payload_len; + left -= payload_len; + } break; + default: + ASSERT(0); + } + } + + return 0; +} + +void addrParser_Resettype (addrParser *o) +{ + o->type_pos = 0; +} + +void addrParser_Forwardtype (addrParser *o) +{ + o->type_pos = o->type_span; +} + +int addrParser_Getip_port (addrParser *o, uint8_t **data) +{ + ASSERT(o->ip_port_pos >= 0) + ASSERT(o->ip_port_pos <= o->ip_port_span) + + int left = o->ip_port_span - o->ip_port_pos; + + while (left > 0) { + ASSERT(left >= sizeof(struct BProto_header_s)) + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + o->ip_port_start + o->ip_port_pos); + o->ip_port_pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + ASSERT(left >= sizeof(struct BProto_uint8_s)) + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + o->ip_port_start + o->ip_port_pos); + o->ip_port_pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + } break; + case BPROTO_TYPE_UINT16: { + ASSERT(left >= sizeof(struct BProto_uint16_s)) + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + o->ip_port_start + o->ip_port_pos); + o->ip_port_pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + } break; + case BPROTO_TYPE_UINT32: { + ASSERT(left >= sizeof(struct BProto_uint32_s)) + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + o->ip_port_start + o->ip_port_pos); + o->ip_port_pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + } break; + case BPROTO_TYPE_UINT64: { + ASSERT(left >= sizeof(struct BProto_uint64_s)) + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + o->ip_port_start + o->ip_port_pos); + o->ip_port_pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + ASSERT(left >= sizeof(struct BProto_data_header_s)) + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + o->ip_port_start + o->ip_port_pos); + o->ip_port_pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + ASSERT(left >= payload_len) + uint8_t *payload = o->buf + o->ip_port_start + o->ip_port_pos; + o->ip_port_pos += payload_len; + left -= payload_len; + + if (type == BPROTO_TYPE_CONSTDATA && id == 2) { + *data = payload; + return 1; + } + } break; + default: + ASSERT(0); + } + } + + return 0; +} + +void addrParser_Resetip_port (addrParser *o) +{ + o->ip_port_pos = 0; +} + +void addrParser_Forwardip_port (addrParser *o) +{ + o->ip_port_pos = o->ip_port_span; +} + +int addrParser_Getipv4_addr (addrParser *o, uint8_t **data) +{ + ASSERT(o->ipv4_addr_pos >= 0) + ASSERT(o->ipv4_addr_pos <= o->ipv4_addr_span) + + int left = o->ipv4_addr_span - o->ipv4_addr_pos; + + while (left > 0) { + ASSERT(left >= sizeof(struct BProto_header_s)) + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + o->ipv4_addr_start + o->ipv4_addr_pos); + o->ipv4_addr_pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + ASSERT(left >= sizeof(struct BProto_uint8_s)) + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + o->ipv4_addr_start + o->ipv4_addr_pos); + o->ipv4_addr_pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + } break; + case BPROTO_TYPE_UINT16: { + ASSERT(left >= sizeof(struct BProto_uint16_s)) + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + o->ipv4_addr_start + o->ipv4_addr_pos); + o->ipv4_addr_pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + } break; + case BPROTO_TYPE_UINT32: { + ASSERT(left >= sizeof(struct BProto_uint32_s)) + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + o->ipv4_addr_start + o->ipv4_addr_pos); + o->ipv4_addr_pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + } break; + case BPROTO_TYPE_UINT64: { + ASSERT(left >= sizeof(struct BProto_uint64_s)) + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + o->ipv4_addr_start + o->ipv4_addr_pos); + o->ipv4_addr_pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + ASSERT(left >= sizeof(struct BProto_data_header_s)) + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + o->ipv4_addr_start + o->ipv4_addr_pos); + o->ipv4_addr_pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + ASSERT(left >= payload_len) + uint8_t *payload = o->buf + o->ipv4_addr_start + o->ipv4_addr_pos; + o->ipv4_addr_pos += payload_len; + left -= payload_len; + + if (type == BPROTO_TYPE_CONSTDATA && id == 3) { + *data = payload; + return 1; + } + } break; + default: + ASSERT(0); + } + } + + return 0; +} + +void addrParser_Resetipv4_addr (addrParser *o) +{ + o->ipv4_addr_pos = 0; +} + +void addrParser_Forwardipv4_addr (addrParser *o) +{ + o->ipv4_addr_pos = o->ipv4_addr_span; +} + +int addrParser_Getipv6_addr (addrParser *o, uint8_t **data) +{ + ASSERT(o->ipv6_addr_pos >= 0) + ASSERT(o->ipv6_addr_pos <= o->ipv6_addr_span) + + int left = o->ipv6_addr_span - o->ipv6_addr_pos; + + while (left > 0) { + ASSERT(left >= sizeof(struct BProto_header_s)) + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + o->ipv6_addr_start + o->ipv6_addr_pos); + o->ipv6_addr_pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + ASSERT(left >= sizeof(struct BProto_uint8_s)) + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + o->ipv6_addr_start + o->ipv6_addr_pos); + o->ipv6_addr_pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + } break; + case BPROTO_TYPE_UINT16: { + ASSERT(left >= sizeof(struct BProto_uint16_s)) + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + o->ipv6_addr_start + o->ipv6_addr_pos); + o->ipv6_addr_pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + } break; + case BPROTO_TYPE_UINT32: { + ASSERT(left >= sizeof(struct BProto_uint32_s)) + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + o->ipv6_addr_start + o->ipv6_addr_pos); + o->ipv6_addr_pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + } break; + case BPROTO_TYPE_UINT64: { + ASSERT(left >= sizeof(struct BProto_uint64_s)) + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + o->ipv6_addr_start + o->ipv6_addr_pos); + o->ipv6_addr_pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + ASSERT(left >= sizeof(struct BProto_data_header_s)) + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + o->ipv6_addr_start + o->ipv6_addr_pos); + o->ipv6_addr_pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + ASSERT(left >= payload_len) + uint8_t *payload = o->buf + o->ipv6_addr_start + o->ipv6_addr_pos; + o->ipv6_addr_pos += payload_len; + left -= payload_len; + + if (type == BPROTO_TYPE_CONSTDATA && id == 4) { + *data = payload; + return 1; + } + } break; + default: + ASSERT(0); + } + } + + return 0; +} + +void addrParser_Resetipv6_addr (addrParser *o) +{ + o->ipv6_addr_pos = 0; +} + +void addrParser_Forwardipv6_addr (addrParser *o) +{ + o->ipv6_addr_pos = o->ipv6_addr_span; +} + diff --git a/generated/bproto_bproto_test.h b/generated/bproto_bproto_test.h new file mode 100644 index 000000000..a600a4a98 --- /dev/null +++ b/generated/bproto_bproto_test.h @@ -0,0 +1,1011 @@ +/* + DO NOT EDIT THIS FILE! + This file was automatically generated by the bproto generator. +*/ + +#include + +#include +#include +#include + + +#define msg1_SIZEa (sizeof(struct BProto_header_s) + sizeof(struct BProto_uint16_s)) +#define msg1_SIZEb (sizeof(struct BProto_header_s) + sizeof(struct BProto_uint32_s)) +#define msg1_SIZEc (sizeof(struct BProto_header_s) + sizeof(struct BProto_uint64_s)) +#define msg1_SIZEd (sizeof(struct BProto_header_s) + sizeof(struct BProto_uint16_s)) +#define msg1_SIZEe (sizeof(struct BProto_header_s) + sizeof(struct BProto_uint8_s)) +#define msg1_SIZEf(_len) (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (_len)) +#define msg1_SIZEg (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (4)) + +typedef struct { + uint8_t *out; + int used; + int a_count; + int b_count; + int c_count; + int d_count; + int e_count; + int f_count; + int g_count; +} msg1Writer; + +static void msg1Writer_Init (msg1Writer *o, uint8_t *out); +static int msg1Writer_Finish (msg1Writer *o); +static void msg1Writer_Adda (msg1Writer *o, uint16_t v); +static void msg1Writer_Addb (msg1Writer *o, uint32_t v); +static void msg1Writer_Addc (msg1Writer *o, uint64_t v); +static void msg1Writer_Addd (msg1Writer *o, uint16_t v); +static void msg1Writer_Adde (msg1Writer *o, uint8_t v); +static uint8_t * msg1Writer_Addf (msg1Writer *o, int len); +static uint8_t * msg1Writer_Addg (msg1Writer *o); + +typedef struct { + uint8_t *buf; + int buf_len; + int a_start; + int a_span; + int a_pos; + int b_start; + int b_span; + int b_pos; + int c_start; + int c_span; + int c_pos; + int d_start; + int d_span; + int d_pos; + int e_start; + int e_span; + int e_pos; + int f_start; + int f_span; + int f_pos; + int g_start; + int g_span; + int g_pos; +} msg1Parser; + +static int msg1Parser_Init (msg1Parser *o, uint8_t *buf, int buf_len); +static int msg1Parser_GotEverything (msg1Parser *o); +static int msg1Parser_Geta (msg1Parser *o, uint16_t *v); +static void msg1Parser_Reseta (msg1Parser *o); +static void msg1Parser_Forwarda (msg1Parser *o); +static int msg1Parser_Getb (msg1Parser *o, uint32_t *v); +static void msg1Parser_Resetb (msg1Parser *o); +static void msg1Parser_Forwardb (msg1Parser *o); +static int msg1Parser_Getc (msg1Parser *o, uint64_t *v); +static void msg1Parser_Resetc (msg1Parser *o); +static void msg1Parser_Forwardc (msg1Parser *o); +static int msg1Parser_Getd (msg1Parser *o, uint16_t *v); +static void msg1Parser_Resetd (msg1Parser *o); +static void msg1Parser_Forwardd (msg1Parser *o); +static int msg1Parser_Gete (msg1Parser *o, uint8_t *v); +static void msg1Parser_Resete (msg1Parser *o); +static void msg1Parser_Forwarde (msg1Parser *o); +static int msg1Parser_Getf (msg1Parser *o, uint8_t **data, int *data_len); +static void msg1Parser_Resetf (msg1Parser *o); +static void msg1Parser_Forwardf (msg1Parser *o); +static int msg1Parser_Getg (msg1Parser *o, uint8_t **data); +static void msg1Parser_Resetg (msg1Parser *o); +static void msg1Parser_Forwardg (msg1Parser *o); + +void msg1Writer_Init (msg1Writer *o, uint8_t *out) +{ + o->out = out; + o->used = 0; + o->a_count = 0; + o->b_count = 0; + o->c_count = 0; + o->d_count = 0; + o->e_count = 0; + o->f_count = 0; + o->g_count = 0; +} + +int msg1Writer_Finish (msg1Writer *o) +{ + ASSERT(o->used >= 0) + ASSERT(o->a_count == 1) + ASSERT(o->b_count >= 0 && o->b_count <= 1) + ASSERT(o->c_count >= 1) + ASSERT(o->d_count >= 0) + ASSERT(o->e_count == 1) + ASSERT(o->f_count == 1) + ASSERT(o->g_count == 1) + + return o->used; +} + +void msg1Writer_Adda (msg1Writer *o, uint16_t v) +{ + ASSERT(o->used >= 0) + ASSERT(o->a_count == 0) + + + ((struct BProto_header_s *)(o->out + o->used))->id = htol16(5); + ((struct BProto_header_s *)(o->out + o->used))->type = htol16(BPROTO_TYPE_UINT16); + o->used += sizeof(struct BProto_header_s); + + ((struct BProto_uint16_s *)(o->out + o->used))->v = htol16(v); + o->used += sizeof(struct BProto_uint16_s); + + o->a_count++; +} + +void msg1Writer_Addb (msg1Writer *o, uint32_t v) +{ + ASSERT(o->used >= 0) + ASSERT(o->b_count == 0) + + + ((struct BProto_header_s *)(o->out + o->used))->id = htol16(6); + ((struct BProto_header_s *)(o->out + o->used))->type = htol16(BPROTO_TYPE_UINT32); + o->used += sizeof(struct BProto_header_s); + + ((struct BProto_uint32_s *)(o->out + o->used))->v = htol32(v); + o->used += sizeof(struct BProto_uint32_s); + + o->b_count++; +} + +void msg1Writer_Addc (msg1Writer *o, uint64_t v) +{ + ASSERT(o->used >= 0) + + + + ((struct BProto_header_s *)(o->out + o->used))->id = htol16(7); + ((struct BProto_header_s *)(o->out + o->used))->type = htol16(BPROTO_TYPE_UINT64); + o->used += sizeof(struct BProto_header_s); + + ((struct BProto_uint64_s *)(o->out + o->used))->v = htol64(v); + o->used += sizeof(struct BProto_uint64_s); + + o->c_count++; +} + +void msg1Writer_Addd (msg1Writer *o, uint16_t v) +{ + ASSERT(o->used >= 0) + + + + ((struct BProto_header_s *)(o->out + o->used))->id = htol16(8); + ((struct BProto_header_s *)(o->out + o->used))->type = htol16(BPROTO_TYPE_UINT16); + o->used += sizeof(struct BProto_header_s); + + ((struct BProto_uint16_s *)(o->out + o->used))->v = htol16(v); + o->used += sizeof(struct BProto_uint16_s); + + o->d_count++; +} + +void msg1Writer_Adde (msg1Writer *o, uint8_t v) +{ + ASSERT(o->used >= 0) + ASSERT(o->e_count == 0) + + + ((struct BProto_header_s *)(o->out + o->used))->id = htol16(9); + ((struct BProto_header_s *)(o->out + o->used))->type = htol16(BPROTO_TYPE_UINT8); + o->used += sizeof(struct BProto_header_s); + + ((struct BProto_uint8_s *)(o->out + o->used))->v = htol8(v); + o->used += sizeof(struct BProto_uint8_s); + + o->e_count++; +} + +uint8_t * msg1Writer_Addf (msg1Writer *o, int len) +{ + ASSERT(o->used >= 0) + ASSERT(o->f_count == 0) + ASSERT(len >= 0 && len <= UINT32_MAX) + + ((struct BProto_header_s *)(o->out + o->used))->id = htol16(10); + ((struct BProto_header_s *)(o->out + o->used))->type = htol16(BPROTO_TYPE_DATA); + o->used += sizeof(struct BProto_header_s); + + ((struct BProto_data_header_s *)(o->out + o->used))->len = htol32(len); + o->used += sizeof(struct BProto_data_header_s); + + uint8_t *dest = (o->out + o->used); + o->used += len; + + o->f_count++; + + return dest; +} + +uint8_t * msg1Writer_Addg (msg1Writer *o) +{ + ASSERT(o->used >= 0) + ASSERT(o->g_count == 0) + + + ((struct BProto_header_s *)(o->out + o->used))->id = htol16(11); + ((struct BProto_header_s *)(o->out + o->used))->type = htol16(BPROTO_TYPE_CONSTDATA); + o->used += sizeof(struct BProto_header_s); + + ((struct BProto_data_header_s *)(o->out + o->used))->len = htol32(4); + o->used += sizeof(struct BProto_data_header_s); + + uint8_t *dest = (o->out + o->used); + o->used += (4); + + o->g_count++; + + return dest; +} + +int msg1Parser_Init (msg1Parser *o, uint8_t *buf, int buf_len) +{ + ASSERT(buf_len >= 0) + + o->buf = buf; + o->buf_len = buf_len; + o->a_start = o->buf_len; + o->a_span = 0; + o->a_pos = 0; + o->b_start = o->buf_len; + o->b_span = 0; + o->b_pos = 0; + o->c_start = o->buf_len; + o->c_span = 0; + o->c_pos = 0; + o->d_start = o->buf_len; + o->d_span = 0; + o->d_pos = 0; + o->e_start = o->buf_len; + o->e_span = 0; + o->e_pos = 0; + o->f_start = o->buf_len; + o->f_span = 0; + o->f_pos = 0; + o->g_start = o->buf_len; + o->g_span = 0; + o->g_pos = 0; + + int a_count = 0; + int b_count = 0; + int c_count = 0; + int d_count = 0; + int e_count = 0; + int f_count = 0; + int g_count = 0; + + int pos = 0; + int left = o->buf_len; + + while (left > 0) { + int entry_pos = pos; + + if (!(left >= sizeof(struct BProto_header_s))) { + return 0; + } + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + pos); + pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + if (!(left >= sizeof(struct BProto_uint8_s))) { + return 0; + } + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + + switch (id) { + case 9: + if (o->e_start == o->buf_len) { + o->e_start = entry_pos; + } + o->e_span = pos - o->e_start; + e_count++; + break; + default: + return 0; + } + } break; + case BPROTO_TYPE_UINT16: { + if (!(left >= sizeof(struct BProto_uint16_s))) { + return 0; + } + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + + switch (id) { + case 5: + if (o->a_start == o->buf_len) { + o->a_start = entry_pos; + } + o->a_span = pos - o->a_start; + a_count++; + break; + case 8: + if (o->d_start == o->buf_len) { + o->d_start = entry_pos; + } + o->d_span = pos - o->d_start; + d_count++; + break; + default: + return 0; + } + } break; + case BPROTO_TYPE_UINT32: { + if (!(left >= sizeof(struct BProto_uint32_s))) { + return 0; + } + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + + switch (id) { + case 6: + if (o->b_start == o->buf_len) { + o->b_start = entry_pos; + } + o->b_span = pos - o->b_start; + b_count++; + break; + default: + return 0; + } + } break; + case BPROTO_TYPE_UINT64: { + if (!(left >= sizeof(struct BProto_uint64_s))) { + return 0; + } + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + + switch (id) { + case 7: + if (o->c_start == o->buf_len) { + o->c_start = entry_pos; + } + o->c_span = pos - o->c_start; + c_count++; + break; + default: + return 0; + } + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + if (!(left >= sizeof(struct BProto_data_header_s))) { + return 0; + } + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + pos); + pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + if (!(left >= payload_len)) { + return 0; + } + pos += payload_len; + left -= payload_len; + + switch (id) { + case 10: + if (!(type == BPROTO_TYPE_DATA)) { + return 0; + } + if (o->f_start == o->buf_len) { + o->f_start = entry_pos; + } + o->f_span = pos - o->f_start; + f_count++; + break; + case 11: + if (!(type == BPROTO_TYPE_CONSTDATA)) { + return 0; + } + if (!(payload_len == (4))) { + return 0; + } + if (o->g_start == o->buf_len) { + o->g_start = entry_pos; + } + o->g_span = pos - o->g_start; + g_count++; + break; + default: + return 0; + } + } break; + default: + return 0; + } + } + + if (!(a_count == 1)) { + return 0; + } + if (!(b_count <= 1)) { + return 0; + } + if (!(c_count >= 1)) { + return 0; + } + if (!(e_count == 1)) { + return 0; + } + if (!(f_count == 1)) { + return 0; + } + if (!(g_count == 1)) { + return 0; + } + + return 1; +} + +int msg1Parser_GotEverything (msg1Parser *o) +{ + return ( + o->a_pos == o->a_span + && + o->b_pos == o->b_span + && + o->c_pos == o->c_span + && + o->d_pos == o->d_span + && + o->e_pos == o->e_span + && + o->f_pos == o->f_span + && + o->g_pos == o->g_span + ); +} + +int msg1Parser_Geta (msg1Parser *o, uint16_t *v) +{ + ASSERT(o->a_pos >= 0) + ASSERT(o->a_pos <= o->a_span) + + int left = o->a_span - o->a_pos; + + while (left > 0) { + ASSERT(left >= sizeof(struct BProto_header_s)) + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + o->a_start + o->a_pos); + o->a_pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + ASSERT(left >= sizeof(struct BProto_uint8_s)) + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + o->a_start + o->a_pos); + o->a_pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + } break; + case BPROTO_TYPE_UINT16: { + ASSERT(left >= sizeof(struct BProto_uint16_s)) + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + o->a_start + o->a_pos); + o->a_pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + + if (id == 5) { + *v = ltoh16(val->v); + return 1; + } + } break; + case BPROTO_TYPE_UINT32: { + ASSERT(left >= sizeof(struct BProto_uint32_s)) + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + o->a_start + o->a_pos); + o->a_pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + } break; + case BPROTO_TYPE_UINT64: { + ASSERT(left >= sizeof(struct BProto_uint64_s)) + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + o->a_start + o->a_pos); + o->a_pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + ASSERT(left >= sizeof(struct BProto_data_header_s)) + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + o->a_start + o->a_pos); + o->a_pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + ASSERT(left >= payload_len) + uint8_t *payload = o->buf + o->a_start + o->a_pos; + o->a_pos += payload_len; + left -= payload_len; + } break; + default: + ASSERT(0); + } + } + + return 0; +} + +void msg1Parser_Reseta (msg1Parser *o) +{ + o->a_pos = 0; +} + +void msg1Parser_Forwarda (msg1Parser *o) +{ + o->a_pos = o->a_span; +} + +int msg1Parser_Getb (msg1Parser *o, uint32_t *v) +{ + ASSERT(o->b_pos >= 0) + ASSERT(o->b_pos <= o->b_span) + + int left = o->b_span - o->b_pos; + + while (left > 0) { + ASSERT(left >= sizeof(struct BProto_header_s)) + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + o->b_start + o->b_pos); + o->b_pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + ASSERT(left >= sizeof(struct BProto_uint8_s)) + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + o->b_start + o->b_pos); + o->b_pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + } break; + case BPROTO_TYPE_UINT16: { + ASSERT(left >= sizeof(struct BProto_uint16_s)) + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + o->b_start + o->b_pos); + o->b_pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + } break; + case BPROTO_TYPE_UINT32: { + ASSERT(left >= sizeof(struct BProto_uint32_s)) + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + o->b_start + o->b_pos); + o->b_pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + + if (id == 6) { + *v = ltoh32(val->v); + return 1; + } + } break; + case BPROTO_TYPE_UINT64: { + ASSERT(left >= sizeof(struct BProto_uint64_s)) + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + o->b_start + o->b_pos); + o->b_pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + ASSERT(left >= sizeof(struct BProto_data_header_s)) + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + o->b_start + o->b_pos); + o->b_pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + ASSERT(left >= payload_len) + uint8_t *payload = o->buf + o->b_start + o->b_pos; + o->b_pos += payload_len; + left -= payload_len; + } break; + default: + ASSERT(0); + } + } + + return 0; +} + +void msg1Parser_Resetb (msg1Parser *o) +{ + o->b_pos = 0; +} + +void msg1Parser_Forwardb (msg1Parser *o) +{ + o->b_pos = o->b_span; +} + +int msg1Parser_Getc (msg1Parser *o, uint64_t *v) +{ + ASSERT(o->c_pos >= 0) + ASSERT(o->c_pos <= o->c_span) + + int left = o->c_span - o->c_pos; + + while (left > 0) { + ASSERT(left >= sizeof(struct BProto_header_s)) + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + o->c_start + o->c_pos); + o->c_pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + ASSERT(left >= sizeof(struct BProto_uint8_s)) + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + o->c_start + o->c_pos); + o->c_pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + } break; + case BPROTO_TYPE_UINT16: { + ASSERT(left >= sizeof(struct BProto_uint16_s)) + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + o->c_start + o->c_pos); + o->c_pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + } break; + case BPROTO_TYPE_UINT32: { + ASSERT(left >= sizeof(struct BProto_uint32_s)) + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + o->c_start + o->c_pos); + o->c_pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + } break; + case BPROTO_TYPE_UINT64: { + ASSERT(left >= sizeof(struct BProto_uint64_s)) + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + o->c_start + o->c_pos); + o->c_pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + + if (id == 7) { + *v = ltoh64(val->v); + return 1; + } + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + ASSERT(left >= sizeof(struct BProto_data_header_s)) + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + o->c_start + o->c_pos); + o->c_pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + ASSERT(left >= payload_len) + uint8_t *payload = o->buf + o->c_start + o->c_pos; + o->c_pos += payload_len; + left -= payload_len; + } break; + default: + ASSERT(0); + } + } + + return 0; +} + +void msg1Parser_Resetc (msg1Parser *o) +{ + o->c_pos = 0; +} + +void msg1Parser_Forwardc (msg1Parser *o) +{ + o->c_pos = o->c_span; +} + +int msg1Parser_Getd (msg1Parser *o, uint16_t *v) +{ + ASSERT(o->d_pos >= 0) + ASSERT(o->d_pos <= o->d_span) + + int left = o->d_span - o->d_pos; + + while (left > 0) { + ASSERT(left >= sizeof(struct BProto_header_s)) + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + o->d_start + o->d_pos); + o->d_pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + ASSERT(left >= sizeof(struct BProto_uint8_s)) + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + o->d_start + o->d_pos); + o->d_pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + } break; + case BPROTO_TYPE_UINT16: { + ASSERT(left >= sizeof(struct BProto_uint16_s)) + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + o->d_start + o->d_pos); + o->d_pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + + if (id == 8) { + *v = ltoh16(val->v); + return 1; + } + } break; + case BPROTO_TYPE_UINT32: { + ASSERT(left >= sizeof(struct BProto_uint32_s)) + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + o->d_start + o->d_pos); + o->d_pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + } break; + case BPROTO_TYPE_UINT64: { + ASSERT(left >= sizeof(struct BProto_uint64_s)) + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + o->d_start + o->d_pos); + o->d_pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + ASSERT(left >= sizeof(struct BProto_data_header_s)) + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + o->d_start + o->d_pos); + o->d_pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + ASSERT(left >= payload_len) + uint8_t *payload = o->buf + o->d_start + o->d_pos; + o->d_pos += payload_len; + left -= payload_len; + } break; + default: + ASSERT(0); + } + } + + return 0; +} + +void msg1Parser_Resetd (msg1Parser *o) +{ + o->d_pos = 0; +} + +void msg1Parser_Forwardd (msg1Parser *o) +{ + o->d_pos = o->d_span; +} + +int msg1Parser_Gete (msg1Parser *o, uint8_t *v) +{ + ASSERT(o->e_pos >= 0) + ASSERT(o->e_pos <= o->e_span) + + int left = o->e_span - o->e_pos; + + while (left > 0) { + ASSERT(left >= sizeof(struct BProto_header_s)) + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + o->e_start + o->e_pos); + o->e_pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + ASSERT(left >= sizeof(struct BProto_uint8_s)) + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + o->e_start + o->e_pos); + o->e_pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + + if (id == 9) { + *v = ltoh8(val->v); + return 1; + } + } break; + case BPROTO_TYPE_UINT16: { + ASSERT(left >= sizeof(struct BProto_uint16_s)) + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + o->e_start + o->e_pos); + o->e_pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + } break; + case BPROTO_TYPE_UINT32: { + ASSERT(left >= sizeof(struct BProto_uint32_s)) + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + o->e_start + o->e_pos); + o->e_pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + } break; + case BPROTO_TYPE_UINT64: { + ASSERT(left >= sizeof(struct BProto_uint64_s)) + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + o->e_start + o->e_pos); + o->e_pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + ASSERT(left >= sizeof(struct BProto_data_header_s)) + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + o->e_start + o->e_pos); + o->e_pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + ASSERT(left >= payload_len) + uint8_t *payload = o->buf + o->e_start + o->e_pos; + o->e_pos += payload_len; + left -= payload_len; + } break; + default: + ASSERT(0); + } + } + + return 0; +} + +void msg1Parser_Resete (msg1Parser *o) +{ + o->e_pos = 0; +} + +void msg1Parser_Forwarde (msg1Parser *o) +{ + o->e_pos = o->e_span; +} + +int msg1Parser_Getf (msg1Parser *o, uint8_t **data, int *data_len) +{ + ASSERT(o->f_pos >= 0) + ASSERT(o->f_pos <= o->f_span) + + int left = o->f_span - o->f_pos; + + while (left > 0) { + ASSERT(left >= sizeof(struct BProto_header_s)) + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + o->f_start + o->f_pos); + o->f_pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + ASSERT(left >= sizeof(struct BProto_uint8_s)) + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + o->f_start + o->f_pos); + o->f_pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + } break; + case BPROTO_TYPE_UINT16: { + ASSERT(left >= sizeof(struct BProto_uint16_s)) + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + o->f_start + o->f_pos); + o->f_pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + } break; + case BPROTO_TYPE_UINT32: { + ASSERT(left >= sizeof(struct BProto_uint32_s)) + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + o->f_start + o->f_pos); + o->f_pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + } break; + case BPROTO_TYPE_UINT64: { + ASSERT(left >= sizeof(struct BProto_uint64_s)) + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + o->f_start + o->f_pos); + o->f_pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + ASSERT(left >= sizeof(struct BProto_data_header_s)) + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + o->f_start + o->f_pos); + o->f_pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + ASSERT(left >= payload_len) + uint8_t *payload = o->buf + o->f_start + o->f_pos; + o->f_pos += payload_len; + left -= payload_len; + + if (type == BPROTO_TYPE_DATA && id == 10) { + *data = payload; + *data_len = payload_len; + return 1; + } + } break; + default: + ASSERT(0); + } + } + + return 0; +} + +void msg1Parser_Resetf (msg1Parser *o) +{ + o->f_pos = 0; +} + +void msg1Parser_Forwardf (msg1Parser *o) +{ + o->f_pos = o->f_span; +} + +int msg1Parser_Getg (msg1Parser *o, uint8_t **data) +{ + ASSERT(o->g_pos >= 0) + ASSERT(o->g_pos <= o->g_span) + + int left = o->g_span - o->g_pos; + + while (left > 0) { + ASSERT(left >= sizeof(struct BProto_header_s)) + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + o->g_start + o->g_pos); + o->g_pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + ASSERT(left >= sizeof(struct BProto_uint8_s)) + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + o->g_start + o->g_pos); + o->g_pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + } break; + case BPROTO_TYPE_UINT16: { + ASSERT(left >= sizeof(struct BProto_uint16_s)) + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + o->g_start + o->g_pos); + o->g_pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + } break; + case BPROTO_TYPE_UINT32: { + ASSERT(left >= sizeof(struct BProto_uint32_s)) + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + o->g_start + o->g_pos); + o->g_pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + } break; + case BPROTO_TYPE_UINT64: { + ASSERT(left >= sizeof(struct BProto_uint64_s)) + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + o->g_start + o->g_pos); + o->g_pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + ASSERT(left >= sizeof(struct BProto_data_header_s)) + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + o->g_start + o->g_pos); + o->g_pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + ASSERT(left >= payload_len) + uint8_t *payload = o->buf + o->g_start + o->g_pos; + o->g_pos += payload_len; + left -= payload_len; + + if (type == BPROTO_TYPE_CONSTDATA && id == 11) { + *data = payload; + return 1; + } + } break; + default: + ASSERT(0); + } + } + + return 0; +} + +void msg1Parser_Resetg (msg1Parser *o) +{ + o->g_pos = 0; +} + +void msg1Parser_Forwardg (msg1Parser *o) +{ + o->g_pos = o->g_span; +} + diff --git a/generated/bproto_msgproto.h b/generated/bproto_msgproto.h new file mode 100644 index 000000000..6c9a5da6e --- /dev/null +++ b/generated/bproto_msgproto.h @@ -0,0 +1,2105 @@ +/* + DO NOT EDIT THIS FILE! + This file was automatically generated by the bproto generator. +*/ + +#include + +#include +#include +#include + + +#define msg_SIZEtype (sizeof(struct BProto_header_s) + sizeof(struct BProto_uint16_s)) +#define msg_SIZEpayload(_len) (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (_len)) + +typedef struct { + uint8_t *out; + int used; + int type_count; + int payload_count; +} msgWriter; + +static void msgWriter_Init (msgWriter *o, uint8_t *out); +static int msgWriter_Finish (msgWriter *o); +static void msgWriter_Addtype (msgWriter *o, uint16_t v); +static uint8_t * msgWriter_Addpayload (msgWriter *o, int len); + +typedef struct { + uint8_t *buf; + int buf_len; + int type_start; + int type_span; + int type_pos; + int payload_start; + int payload_span; + int payload_pos; +} msgParser; + +static int msgParser_Init (msgParser *o, uint8_t *buf, int buf_len); +static int msgParser_GotEverything (msgParser *o); +static int msgParser_Gettype (msgParser *o, uint16_t *v); +static void msgParser_Resettype (msgParser *o); +static void msgParser_Forwardtype (msgParser *o); +static int msgParser_Getpayload (msgParser *o, uint8_t **data, int *data_len); +static void msgParser_Resetpayload (msgParser *o); +static void msgParser_Forwardpayload (msgParser *o); + +void msgWriter_Init (msgWriter *o, uint8_t *out) +{ + o->out = out; + o->used = 0; + o->type_count = 0; + o->payload_count = 0; +} + +int msgWriter_Finish (msgWriter *o) +{ + ASSERT(o->used >= 0) + ASSERT(o->type_count == 1) + ASSERT(o->payload_count == 1) + + return o->used; +} + +void msgWriter_Addtype (msgWriter *o, uint16_t v) +{ + ASSERT(o->used >= 0) + ASSERT(o->type_count == 0) + + + ((struct BProto_header_s *)(o->out + o->used))->id = htol16(1); + ((struct BProto_header_s *)(o->out + o->used))->type = htol16(BPROTO_TYPE_UINT16); + o->used += sizeof(struct BProto_header_s); + + ((struct BProto_uint16_s *)(o->out + o->used))->v = htol16(v); + o->used += sizeof(struct BProto_uint16_s); + + o->type_count++; +} + +uint8_t * msgWriter_Addpayload (msgWriter *o, int len) +{ + ASSERT(o->used >= 0) + ASSERT(o->payload_count == 0) + ASSERT(len >= 0 && len <= UINT32_MAX) + + ((struct BProto_header_s *)(o->out + o->used))->id = htol16(2); + ((struct BProto_header_s *)(o->out + o->used))->type = htol16(BPROTO_TYPE_DATA); + o->used += sizeof(struct BProto_header_s); + + ((struct BProto_data_header_s *)(o->out + o->used))->len = htol32(len); + o->used += sizeof(struct BProto_data_header_s); + + uint8_t *dest = (o->out + o->used); + o->used += len; + + o->payload_count++; + + return dest; +} + +int msgParser_Init (msgParser *o, uint8_t *buf, int buf_len) +{ + ASSERT(buf_len >= 0) + + o->buf = buf; + o->buf_len = buf_len; + o->type_start = o->buf_len; + o->type_span = 0; + o->type_pos = 0; + o->payload_start = o->buf_len; + o->payload_span = 0; + o->payload_pos = 0; + + int type_count = 0; + int payload_count = 0; + + int pos = 0; + int left = o->buf_len; + + while (left > 0) { + int entry_pos = pos; + + if (!(left >= sizeof(struct BProto_header_s))) { + return 0; + } + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + pos); + pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + if (!(left >= sizeof(struct BProto_uint8_s))) { + return 0; + } + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + + switch (id) { + default: + return 0; + } + } break; + case BPROTO_TYPE_UINT16: { + if (!(left >= sizeof(struct BProto_uint16_s))) { + return 0; + } + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + + switch (id) { + case 1: + if (o->type_start == o->buf_len) { + o->type_start = entry_pos; + } + o->type_span = pos - o->type_start; + type_count++; + break; + default: + return 0; + } + } break; + case BPROTO_TYPE_UINT32: { + if (!(left >= sizeof(struct BProto_uint32_s))) { + return 0; + } + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + + switch (id) { + default: + return 0; + } + } break; + case BPROTO_TYPE_UINT64: { + if (!(left >= sizeof(struct BProto_uint64_s))) { + return 0; + } + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + + switch (id) { + default: + return 0; + } + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + if (!(left >= sizeof(struct BProto_data_header_s))) { + return 0; + } + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + pos); + pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + if (!(left >= payload_len)) { + return 0; + } + pos += payload_len; + left -= payload_len; + + switch (id) { + case 2: + if (!(type == BPROTO_TYPE_DATA)) { + return 0; + } + if (o->payload_start == o->buf_len) { + o->payload_start = entry_pos; + } + o->payload_span = pos - o->payload_start; + payload_count++; + break; + default: + return 0; + } + } break; + default: + return 0; + } + } + + if (!(type_count == 1)) { + return 0; + } + if (!(payload_count == 1)) { + return 0; + } + + return 1; +} + +int msgParser_GotEverything (msgParser *o) +{ + return ( + o->type_pos == o->type_span + && + o->payload_pos == o->payload_span + ); +} + +int msgParser_Gettype (msgParser *o, uint16_t *v) +{ + ASSERT(o->type_pos >= 0) + ASSERT(o->type_pos <= o->type_span) + + int left = o->type_span - o->type_pos; + + while (left > 0) { + ASSERT(left >= sizeof(struct BProto_header_s)) + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + o->type_start + o->type_pos); + o->type_pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + ASSERT(left >= sizeof(struct BProto_uint8_s)) + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + o->type_start + o->type_pos); + o->type_pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + } break; + case BPROTO_TYPE_UINT16: { + ASSERT(left >= sizeof(struct BProto_uint16_s)) + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + o->type_start + o->type_pos); + o->type_pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + + if (id == 1) { + *v = ltoh16(val->v); + return 1; + } + } break; + case BPROTO_TYPE_UINT32: { + ASSERT(left >= sizeof(struct BProto_uint32_s)) + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + o->type_start + o->type_pos); + o->type_pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + } break; + case BPROTO_TYPE_UINT64: { + ASSERT(left >= sizeof(struct BProto_uint64_s)) + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + o->type_start + o->type_pos); + o->type_pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + ASSERT(left >= sizeof(struct BProto_data_header_s)) + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + o->type_start + o->type_pos); + o->type_pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + ASSERT(left >= payload_len) + uint8_t *payload = o->buf + o->type_start + o->type_pos; + o->type_pos += payload_len; + left -= payload_len; + } break; + default: + ASSERT(0); + } + } + + return 0; +} + +void msgParser_Resettype (msgParser *o) +{ + o->type_pos = 0; +} + +void msgParser_Forwardtype (msgParser *o) +{ + o->type_pos = o->type_span; +} + +int msgParser_Getpayload (msgParser *o, uint8_t **data, int *data_len) +{ + ASSERT(o->payload_pos >= 0) + ASSERT(o->payload_pos <= o->payload_span) + + int left = o->payload_span - o->payload_pos; + + while (left > 0) { + ASSERT(left >= sizeof(struct BProto_header_s)) + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + o->payload_start + o->payload_pos); + o->payload_pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + ASSERT(left >= sizeof(struct BProto_uint8_s)) + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + o->payload_start + o->payload_pos); + o->payload_pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + } break; + case BPROTO_TYPE_UINT16: { + ASSERT(left >= sizeof(struct BProto_uint16_s)) + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + o->payload_start + o->payload_pos); + o->payload_pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + } break; + case BPROTO_TYPE_UINT32: { + ASSERT(left >= sizeof(struct BProto_uint32_s)) + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + o->payload_start + o->payload_pos); + o->payload_pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + } break; + case BPROTO_TYPE_UINT64: { + ASSERT(left >= sizeof(struct BProto_uint64_s)) + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + o->payload_start + o->payload_pos); + o->payload_pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + ASSERT(left >= sizeof(struct BProto_data_header_s)) + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + o->payload_start + o->payload_pos); + o->payload_pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + ASSERT(left >= payload_len) + uint8_t *payload = o->buf + o->payload_start + o->payload_pos; + o->payload_pos += payload_len; + left -= payload_len; + + if (type == BPROTO_TYPE_DATA && id == 2) { + *data = payload; + *data_len = payload_len; + return 1; + } + } break; + default: + ASSERT(0); + } + } + + return 0; +} + +void msgParser_Resetpayload (msgParser *o) +{ + o->payload_pos = 0; +} + +void msgParser_Forwardpayload (msgParser *o) +{ + o->payload_pos = o->payload_span; +} + +#define msg_youconnect_SIZEaddr(_len) (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (_len)) +#define msg_youconnect_SIZEkey(_len) (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (_len)) +#define msg_youconnect_SIZEpassword (sizeof(struct BProto_header_s) + sizeof(struct BProto_uint64_s)) + +typedef struct { + uint8_t *out; + int used; + int addr_count; + int key_count; + int password_count; +} msg_youconnectWriter; + +static void msg_youconnectWriter_Init (msg_youconnectWriter *o, uint8_t *out); +static int msg_youconnectWriter_Finish (msg_youconnectWriter *o); +static uint8_t * msg_youconnectWriter_Addaddr (msg_youconnectWriter *o, int len); +static uint8_t * msg_youconnectWriter_Addkey (msg_youconnectWriter *o, int len); +static void msg_youconnectWriter_Addpassword (msg_youconnectWriter *o, uint64_t v); + +typedef struct { + uint8_t *buf; + int buf_len; + int addr_start; + int addr_span; + int addr_pos; + int key_start; + int key_span; + int key_pos; + int password_start; + int password_span; + int password_pos; +} msg_youconnectParser; + +static int msg_youconnectParser_Init (msg_youconnectParser *o, uint8_t *buf, int buf_len); +static int msg_youconnectParser_GotEverything (msg_youconnectParser *o); +static int msg_youconnectParser_Getaddr (msg_youconnectParser *o, uint8_t **data, int *data_len); +static void msg_youconnectParser_Resetaddr (msg_youconnectParser *o); +static void msg_youconnectParser_Forwardaddr (msg_youconnectParser *o); +static int msg_youconnectParser_Getkey (msg_youconnectParser *o, uint8_t **data, int *data_len); +static void msg_youconnectParser_Resetkey (msg_youconnectParser *o); +static void msg_youconnectParser_Forwardkey (msg_youconnectParser *o); +static int msg_youconnectParser_Getpassword (msg_youconnectParser *o, uint64_t *v); +static void msg_youconnectParser_Resetpassword (msg_youconnectParser *o); +static void msg_youconnectParser_Forwardpassword (msg_youconnectParser *o); + +void msg_youconnectWriter_Init (msg_youconnectWriter *o, uint8_t *out) +{ + o->out = out; + o->used = 0; + o->addr_count = 0; + o->key_count = 0; + o->password_count = 0; +} + +int msg_youconnectWriter_Finish (msg_youconnectWriter *o) +{ + ASSERT(o->used >= 0) + ASSERT(o->addr_count >= 1) + ASSERT(o->key_count >= 0 && o->key_count <= 1) + ASSERT(o->password_count >= 0 && o->password_count <= 1) + + return o->used; +} + +uint8_t * msg_youconnectWriter_Addaddr (msg_youconnectWriter *o, int len) +{ + ASSERT(o->used >= 0) + + ASSERT(len >= 0 && len <= UINT32_MAX) + + ((struct BProto_header_s *)(o->out + o->used))->id = htol16(1); + ((struct BProto_header_s *)(o->out + o->used))->type = htol16(BPROTO_TYPE_DATA); + o->used += sizeof(struct BProto_header_s); + + ((struct BProto_data_header_s *)(o->out + o->used))->len = htol32(len); + o->used += sizeof(struct BProto_data_header_s); + + uint8_t *dest = (o->out + o->used); + o->used += len; + + o->addr_count++; + + return dest; +} + +uint8_t * msg_youconnectWriter_Addkey (msg_youconnectWriter *o, int len) +{ + ASSERT(o->used >= 0) + ASSERT(o->key_count == 0) + ASSERT(len >= 0 && len <= UINT32_MAX) + + ((struct BProto_header_s *)(o->out + o->used))->id = htol16(2); + ((struct BProto_header_s *)(o->out + o->used))->type = htol16(BPROTO_TYPE_DATA); + o->used += sizeof(struct BProto_header_s); + + ((struct BProto_data_header_s *)(o->out + o->used))->len = htol32(len); + o->used += sizeof(struct BProto_data_header_s); + + uint8_t *dest = (o->out + o->used); + o->used += len; + + o->key_count++; + + return dest; +} + +void msg_youconnectWriter_Addpassword (msg_youconnectWriter *o, uint64_t v) +{ + ASSERT(o->used >= 0) + ASSERT(o->password_count == 0) + + + ((struct BProto_header_s *)(o->out + o->used))->id = htol16(3); + ((struct BProto_header_s *)(o->out + o->used))->type = htol16(BPROTO_TYPE_UINT64); + o->used += sizeof(struct BProto_header_s); + + ((struct BProto_uint64_s *)(o->out + o->used))->v = htol64(v); + o->used += sizeof(struct BProto_uint64_s); + + o->password_count++; +} + +int msg_youconnectParser_Init (msg_youconnectParser *o, uint8_t *buf, int buf_len) +{ + ASSERT(buf_len >= 0) + + o->buf = buf; + o->buf_len = buf_len; + o->addr_start = o->buf_len; + o->addr_span = 0; + o->addr_pos = 0; + o->key_start = o->buf_len; + o->key_span = 0; + o->key_pos = 0; + o->password_start = o->buf_len; + o->password_span = 0; + o->password_pos = 0; + + int addr_count = 0; + int key_count = 0; + int password_count = 0; + + int pos = 0; + int left = o->buf_len; + + while (left > 0) { + int entry_pos = pos; + + if (!(left >= sizeof(struct BProto_header_s))) { + return 0; + } + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + pos); + pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + if (!(left >= sizeof(struct BProto_uint8_s))) { + return 0; + } + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + + switch (id) { + default: + return 0; + } + } break; + case BPROTO_TYPE_UINT16: { + if (!(left >= sizeof(struct BProto_uint16_s))) { + return 0; + } + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + + switch (id) { + default: + return 0; + } + } break; + case BPROTO_TYPE_UINT32: { + if (!(left >= sizeof(struct BProto_uint32_s))) { + return 0; + } + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + + switch (id) { + default: + return 0; + } + } break; + case BPROTO_TYPE_UINT64: { + if (!(left >= sizeof(struct BProto_uint64_s))) { + return 0; + } + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + + switch (id) { + case 3: + if (o->password_start == o->buf_len) { + o->password_start = entry_pos; + } + o->password_span = pos - o->password_start; + password_count++; + break; + default: + return 0; + } + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + if (!(left >= sizeof(struct BProto_data_header_s))) { + return 0; + } + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + pos); + pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + if (!(left >= payload_len)) { + return 0; + } + pos += payload_len; + left -= payload_len; + + switch (id) { + case 1: + if (!(type == BPROTO_TYPE_DATA)) { + return 0; + } + if (o->addr_start == o->buf_len) { + o->addr_start = entry_pos; + } + o->addr_span = pos - o->addr_start; + addr_count++; + break; + case 2: + if (!(type == BPROTO_TYPE_DATA)) { + return 0; + } + if (o->key_start == o->buf_len) { + o->key_start = entry_pos; + } + o->key_span = pos - o->key_start; + key_count++; + break; + default: + return 0; + } + } break; + default: + return 0; + } + } + + if (!(addr_count >= 1)) { + return 0; + } + if (!(key_count <= 1)) { + return 0; + } + if (!(password_count <= 1)) { + return 0; + } + + return 1; +} + +int msg_youconnectParser_GotEverything (msg_youconnectParser *o) +{ + return ( + o->addr_pos == o->addr_span + && + o->key_pos == o->key_span + && + o->password_pos == o->password_span + ); +} + +int msg_youconnectParser_Getaddr (msg_youconnectParser *o, uint8_t **data, int *data_len) +{ + ASSERT(o->addr_pos >= 0) + ASSERT(o->addr_pos <= o->addr_span) + + int left = o->addr_span - o->addr_pos; + + while (left > 0) { + ASSERT(left >= sizeof(struct BProto_header_s)) + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + o->addr_start + o->addr_pos); + o->addr_pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + ASSERT(left >= sizeof(struct BProto_uint8_s)) + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + o->addr_start + o->addr_pos); + o->addr_pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + } break; + case BPROTO_TYPE_UINT16: { + ASSERT(left >= sizeof(struct BProto_uint16_s)) + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + o->addr_start + o->addr_pos); + o->addr_pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + } break; + case BPROTO_TYPE_UINT32: { + ASSERT(left >= sizeof(struct BProto_uint32_s)) + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + o->addr_start + o->addr_pos); + o->addr_pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + } break; + case BPROTO_TYPE_UINT64: { + ASSERT(left >= sizeof(struct BProto_uint64_s)) + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + o->addr_start + o->addr_pos); + o->addr_pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + ASSERT(left >= sizeof(struct BProto_data_header_s)) + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + o->addr_start + o->addr_pos); + o->addr_pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + ASSERT(left >= payload_len) + uint8_t *payload = o->buf + o->addr_start + o->addr_pos; + o->addr_pos += payload_len; + left -= payload_len; + + if (type == BPROTO_TYPE_DATA && id == 1) { + *data = payload; + *data_len = payload_len; + return 1; + } + } break; + default: + ASSERT(0); + } + } + + return 0; +} + +void msg_youconnectParser_Resetaddr (msg_youconnectParser *o) +{ + o->addr_pos = 0; +} + +void msg_youconnectParser_Forwardaddr (msg_youconnectParser *o) +{ + o->addr_pos = o->addr_span; +} + +int msg_youconnectParser_Getkey (msg_youconnectParser *o, uint8_t **data, int *data_len) +{ + ASSERT(o->key_pos >= 0) + ASSERT(o->key_pos <= o->key_span) + + int left = o->key_span - o->key_pos; + + while (left > 0) { + ASSERT(left >= sizeof(struct BProto_header_s)) + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + o->key_start + o->key_pos); + o->key_pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + ASSERT(left >= sizeof(struct BProto_uint8_s)) + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + o->key_start + o->key_pos); + o->key_pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + } break; + case BPROTO_TYPE_UINT16: { + ASSERT(left >= sizeof(struct BProto_uint16_s)) + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + o->key_start + o->key_pos); + o->key_pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + } break; + case BPROTO_TYPE_UINT32: { + ASSERT(left >= sizeof(struct BProto_uint32_s)) + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + o->key_start + o->key_pos); + o->key_pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + } break; + case BPROTO_TYPE_UINT64: { + ASSERT(left >= sizeof(struct BProto_uint64_s)) + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + o->key_start + o->key_pos); + o->key_pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + ASSERT(left >= sizeof(struct BProto_data_header_s)) + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + o->key_start + o->key_pos); + o->key_pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + ASSERT(left >= payload_len) + uint8_t *payload = o->buf + o->key_start + o->key_pos; + o->key_pos += payload_len; + left -= payload_len; + + if (type == BPROTO_TYPE_DATA && id == 2) { + *data = payload; + *data_len = payload_len; + return 1; + } + } break; + default: + ASSERT(0); + } + } + + return 0; +} + +void msg_youconnectParser_Resetkey (msg_youconnectParser *o) +{ + o->key_pos = 0; +} + +void msg_youconnectParser_Forwardkey (msg_youconnectParser *o) +{ + o->key_pos = o->key_span; +} + +int msg_youconnectParser_Getpassword (msg_youconnectParser *o, uint64_t *v) +{ + ASSERT(o->password_pos >= 0) + ASSERT(o->password_pos <= o->password_span) + + int left = o->password_span - o->password_pos; + + while (left > 0) { + ASSERT(left >= sizeof(struct BProto_header_s)) + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + o->password_start + o->password_pos); + o->password_pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + ASSERT(left >= sizeof(struct BProto_uint8_s)) + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + o->password_start + o->password_pos); + o->password_pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + } break; + case BPROTO_TYPE_UINT16: { + ASSERT(left >= sizeof(struct BProto_uint16_s)) + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + o->password_start + o->password_pos); + o->password_pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + } break; + case BPROTO_TYPE_UINT32: { + ASSERT(left >= sizeof(struct BProto_uint32_s)) + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + o->password_start + o->password_pos); + o->password_pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + } break; + case BPROTO_TYPE_UINT64: { + ASSERT(left >= sizeof(struct BProto_uint64_s)) + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + o->password_start + o->password_pos); + o->password_pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + + if (id == 3) { + *v = ltoh64(val->v); + return 1; + } + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + ASSERT(left >= sizeof(struct BProto_data_header_s)) + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + o->password_start + o->password_pos); + o->password_pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + ASSERT(left >= payload_len) + uint8_t *payload = o->buf + o->password_start + o->password_pos; + o->password_pos += payload_len; + left -= payload_len; + } break; + default: + ASSERT(0); + } + } + + return 0; +} + +void msg_youconnectParser_Resetpassword (msg_youconnectParser *o) +{ + o->password_pos = 0; +} + +void msg_youconnectParser_Forwardpassword (msg_youconnectParser *o) +{ + o->password_pos = o->password_span; +} + +#define msg_youconnect_addr_SIZEname(_len) (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (_len)) +#define msg_youconnect_addr_SIZEaddr(_len) (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (_len)) + +typedef struct { + uint8_t *out; + int used; + int name_count; + int addr_count; +} msg_youconnect_addrWriter; + +static void msg_youconnect_addrWriter_Init (msg_youconnect_addrWriter *o, uint8_t *out); +static int msg_youconnect_addrWriter_Finish (msg_youconnect_addrWriter *o); +static uint8_t * msg_youconnect_addrWriter_Addname (msg_youconnect_addrWriter *o, int len); +static uint8_t * msg_youconnect_addrWriter_Addaddr (msg_youconnect_addrWriter *o, int len); + +typedef struct { + uint8_t *buf; + int buf_len; + int name_start; + int name_span; + int name_pos; + int addr_start; + int addr_span; + int addr_pos; +} msg_youconnect_addrParser; + +static int msg_youconnect_addrParser_Init (msg_youconnect_addrParser *o, uint8_t *buf, int buf_len); +static int msg_youconnect_addrParser_GotEverything (msg_youconnect_addrParser *o); +static int msg_youconnect_addrParser_Getname (msg_youconnect_addrParser *o, uint8_t **data, int *data_len); +static void msg_youconnect_addrParser_Resetname (msg_youconnect_addrParser *o); +static void msg_youconnect_addrParser_Forwardname (msg_youconnect_addrParser *o); +static int msg_youconnect_addrParser_Getaddr (msg_youconnect_addrParser *o, uint8_t **data, int *data_len); +static void msg_youconnect_addrParser_Resetaddr (msg_youconnect_addrParser *o); +static void msg_youconnect_addrParser_Forwardaddr (msg_youconnect_addrParser *o); + +void msg_youconnect_addrWriter_Init (msg_youconnect_addrWriter *o, uint8_t *out) +{ + o->out = out; + o->used = 0; + o->name_count = 0; + o->addr_count = 0; +} + +int msg_youconnect_addrWriter_Finish (msg_youconnect_addrWriter *o) +{ + ASSERT(o->used >= 0) + ASSERT(o->name_count == 1) + ASSERT(o->addr_count == 1) + + return o->used; +} + +uint8_t * msg_youconnect_addrWriter_Addname (msg_youconnect_addrWriter *o, int len) +{ + ASSERT(o->used >= 0) + ASSERT(o->name_count == 0) + ASSERT(len >= 0 && len <= UINT32_MAX) + + ((struct BProto_header_s *)(o->out + o->used))->id = htol16(1); + ((struct BProto_header_s *)(o->out + o->used))->type = htol16(BPROTO_TYPE_DATA); + o->used += sizeof(struct BProto_header_s); + + ((struct BProto_data_header_s *)(o->out + o->used))->len = htol32(len); + o->used += sizeof(struct BProto_data_header_s); + + uint8_t *dest = (o->out + o->used); + o->used += len; + + o->name_count++; + + return dest; +} + +uint8_t * msg_youconnect_addrWriter_Addaddr (msg_youconnect_addrWriter *o, int len) +{ + ASSERT(o->used >= 0) + ASSERT(o->addr_count == 0) + ASSERT(len >= 0 && len <= UINT32_MAX) + + ((struct BProto_header_s *)(o->out + o->used))->id = htol16(2); + ((struct BProto_header_s *)(o->out + o->used))->type = htol16(BPROTO_TYPE_DATA); + o->used += sizeof(struct BProto_header_s); + + ((struct BProto_data_header_s *)(o->out + o->used))->len = htol32(len); + o->used += sizeof(struct BProto_data_header_s); + + uint8_t *dest = (o->out + o->used); + o->used += len; + + o->addr_count++; + + return dest; +} + +int msg_youconnect_addrParser_Init (msg_youconnect_addrParser *o, uint8_t *buf, int buf_len) +{ + ASSERT(buf_len >= 0) + + o->buf = buf; + o->buf_len = buf_len; + o->name_start = o->buf_len; + o->name_span = 0; + o->name_pos = 0; + o->addr_start = o->buf_len; + o->addr_span = 0; + o->addr_pos = 0; + + int name_count = 0; + int addr_count = 0; + + int pos = 0; + int left = o->buf_len; + + while (left > 0) { + int entry_pos = pos; + + if (!(left >= sizeof(struct BProto_header_s))) { + return 0; + } + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + pos); + pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + if (!(left >= sizeof(struct BProto_uint8_s))) { + return 0; + } + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + + switch (id) { + default: + return 0; + } + } break; + case BPROTO_TYPE_UINT16: { + if (!(left >= sizeof(struct BProto_uint16_s))) { + return 0; + } + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + + switch (id) { + default: + return 0; + } + } break; + case BPROTO_TYPE_UINT32: { + if (!(left >= sizeof(struct BProto_uint32_s))) { + return 0; + } + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + + switch (id) { + default: + return 0; + } + } break; + case BPROTO_TYPE_UINT64: { + if (!(left >= sizeof(struct BProto_uint64_s))) { + return 0; + } + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + + switch (id) { + default: + return 0; + } + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + if (!(left >= sizeof(struct BProto_data_header_s))) { + return 0; + } + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + pos); + pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + if (!(left >= payload_len)) { + return 0; + } + pos += payload_len; + left -= payload_len; + + switch (id) { + case 1: + if (!(type == BPROTO_TYPE_DATA)) { + return 0; + } + if (o->name_start == o->buf_len) { + o->name_start = entry_pos; + } + o->name_span = pos - o->name_start; + name_count++; + break; + case 2: + if (!(type == BPROTO_TYPE_DATA)) { + return 0; + } + if (o->addr_start == o->buf_len) { + o->addr_start = entry_pos; + } + o->addr_span = pos - o->addr_start; + addr_count++; + break; + default: + return 0; + } + } break; + default: + return 0; + } + } + + if (!(name_count == 1)) { + return 0; + } + if (!(addr_count == 1)) { + return 0; + } + + return 1; +} + +int msg_youconnect_addrParser_GotEverything (msg_youconnect_addrParser *o) +{ + return ( + o->name_pos == o->name_span + && + o->addr_pos == o->addr_span + ); +} + +int msg_youconnect_addrParser_Getname (msg_youconnect_addrParser *o, uint8_t **data, int *data_len) +{ + ASSERT(o->name_pos >= 0) + ASSERT(o->name_pos <= o->name_span) + + int left = o->name_span - o->name_pos; + + while (left > 0) { + ASSERT(left >= sizeof(struct BProto_header_s)) + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + o->name_start + o->name_pos); + o->name_pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + ASSERT(left >= sizeof(struct BProto_uint8_s)) + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + o->name_start + o->name_pos); + o->name_pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + } break; + case BPROTO_TYPE_UINT16: { + ASSERT(left >= sizeof(struct BProto_uint16_s)) + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + o->name_start + o->name_pos); + o->name_pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + } break; + case BPROTO_TYPE_UINT32: { + ASSERT(left >= sizeof(struct BProto_uint32_s)) + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + o->name_start + o->name_pos); + o->name_pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + } break; + case BPROTO_TYPE_UINT64: { + ASSERT(left >= sizeof(struct BProto_uint64_s)) + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + o->name_start + o->name_pos); + o->name_pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + ASSERT(left >= sizeof(struct BProto_data_header_s)) + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + o->name_start + o->name_pos); + o->name_pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + ASSERT(left >= payload_len) + uint8_t *payload = o->buf + o->name_start + o->name_pos; + o->name_pos += payload_len; + left -= payload_len; + + if (type == BPROTO_TYPE_DATA && id == 1) { + *data = payload; + *data_len = payload_len; + return 1; + } + } break; + default: + ASSERT(0); + } + } + + return 0; +} + +void msg_youconnect_addrParser_Resetname (msg_youconnect_addrParser *o) +{ + o->name_pos = 0; +} + +void msg_youconnect_addrParser_Forwardname (msg_youconnect_addrParser *o) +{ + o->name_pos = o->name_span; +} + +int msg_youconnect_addrParser_Getaddr (msg_youconnect_addrParser *o, uint8_t **data, int *data_len) +{ + ASSERT(o->addr_pos >= 0) + ASSERT(o->addr_pos <= o->addr_span) + + int left = o->addr_span - o->addr_pos; + + while (left > 0) { + ASSERT(left >= sizeof(struct BProto_header_s)) + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + o->addr_start + o->addr_pos); + o->addr_pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + ASSERT(left >= sizeof(struct BProto_uint8_s)) + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + o->addr_start + o->addr_pos); + o->addr_pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + } break; + case BPROTO_TYPE_UINT16: { + ASSERT(left >= sizeof(struct BProto_uint16_s)) + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + o->addr_start + o->addr_pos); + o->addr_pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + } break; + case BPROTO_TYPE_UINT32: { + ASSERT(left >= sizeof(struct BProto_uint32_s)) + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + o->addr_start + o->addr_pos); + o->addr_pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + } break; + case BPROTO_TYPE_UINT64: { + ASSERT(left >= sizeof(struct BProto_uint64_s)) + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + o->addr_start + o->addr_pos); + o->addr_pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + ASSERT(left >= sizeof(struct BProto_data_header_s)) + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + o->addr_start + o->addr_pos); + o->addr_pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + ASSERT(left >= payload_len) + uint8_t *payload = o->buf + o->addr_start + o->addr_pos; + o->addr_pos += payload_len; + left -= payload_len; + + if (type == BPROTO_TYPE_DATA && id == 2) { + *data = payload; + *data_len = payload_len; + return 1; + } + } break; + default: + ASSERT(0); + } + } + + return 0; +} + +void msg_youconnect_addrParser_Resetaddr (msg_youconnect_addrParser *o) +{ + o->addr_pos = 0; +} + +void msg_youconnect_addrParser_Forwardaddr (msg_youconnect_addrParser *o) +{ + o->addr_pos = o->addr_span; +} + +#define msg_seed_SIZEseed_id (sizeof(struct BProto_header_s) + sizeof(struct BProto_uint16_s)) +#define msg_seed_SIZEkey(_len) (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (_len)) +#define msg_seed_SIZEiv(_len) (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (_len)) + +typedef struct { + uint8_t *out; + int used; + int seed_id_count; + int key_count; + int iv_count; +} msg_seedWriter; + +static void msg_seedWriter_Init (msg_seedWriter *o, uint8_t *out); +static int msg_seedWriter_Finish (msg_seedWriter *o); +static void msg_seedWriter_Addseed_id (msg_seedWriter *o, uint16_t v); +static uint8_t * msg_seedWriter_Addkey (msg_seedWriter *o, int len); +static uint8_t * msg_seedWriter_Addiv (msg_seedWriter *o, int len); + +typedef struct { + uint8_t *buf; + int buf_len; + int seed_id_start; + int seed_id_span; + int seed_id_pos; + int key_start; + int key_span; + int key_pos; + int iv_start; + int iv_span; + int iv_pos; +} msg_seedParser; + +static int msg_seedParser_Init (msg_seedParser *o, uint8_t *buf, int buf_len); +static int msg_seedParser_GotEverything (msg_seedParser *o); +static int msg_seedParser_Getseed_id (msg_seedParser *o, uint16_t *v); +static void msg_seedParser_Resetseed_id (msg_seedParser *o); +static void msg_seedParser_Forwardseed_id (msg_seedParser *o); +static int msg_seedParser_Getkey (msg_seedParser *o, uint8_t **data, int *data_len); +static void msg_seedParser_Resetkey (msg_seedParser *o); +static void msg_seedParser_Forwardkey (msg_seedParser *o); +static int msg_seedParser_Getiv (msg_seedParser *o, uint8_t **data, int *data_len); +static void msg_seedParser_Resetiv (msg_seedParser *o); +static void msg_seedParser_Forwardiv (msg_seedParser *o); + +void msg_seedWriter_Init (msg_seedWriter *o, uint8_t *out) +{ + o->out = out; + o->used = 0; + o->seed_id_count = 0; + o->key_count = 0; + o->iv_count = 0; +} + +int msg_seedWriter_Finish (msg_seedWriter *o) +{ + ASSERT(o->used >= 0) + ASSERT(o->seed_id_count == 1) + ASSERT(o->key_count == 1) + ASSERT(o->iv_count == 1) + + return o->used; +} + +void msg_seedWriter_Addseed_id (msg_seedWriter *o, uint16_t v) +{ + ASSERT(o->used >= 0) + ASSERT(o->seed_id_count == 0) + + + ((struct BProto_header_s *)(o->out + o->used))->id = htol16(1); + ((struct BProto_header_s *)(o->out + o->used))->type = htol16(BPROTO_TYPE_UINT16); + o->used += sizeof(struct BProto_header_s); + + ((struct BProto_uint16_s *)(o->out + o->used))->v = htol16(v); + o->used += sizeof(struct BProto_uint16_s); + + o->seed_id_count++; +} + +uint8_t * msg_seedWriter_Addkey (msg_seedWriter *o, int len) +{ + ASSERT(o->used >= 0) + ASSERT(o->key_count == 0) + ASSERT(len >= 0 && len <= UINT32_MAX) + + ((struct BProto_header_s *)(o->out + o->used))->id = htol16(2); + ((struct BProto_header_s *)(o->out + o->used))->type = htol16(BPROTO_TYPE_DATA); + o->used += sizeof(struct BProto_header_s); + + ((struct BProto_data_header_s *)(o->out + o->used))->len = htol32(len); + o->used += sizeof(struct BProto_data_header_s); + + uint8_t *dest = (o->out + o->used); + o->used += len; + + o->key_count++; + + return dest; +} + +uint8_t * msg_seedWriter_Addiv (msg_seedWriter *o, int len) +{ + ASSERT(o->used >= 0) + ASSERT(o->iv_count == 0) + ASSERT(len >= 0 && len <= UINT32_MAX) + + ((struct BProto_header_s *)(o->out + o->used))->id = htol16(3); + ((struct BProto_header_s *)(o->out + o->used))->type = htol16(BPROTO_TYPE_DATA); + o->used += sizeof(struct BProto_header_s); + + ((struct BProto_data_header_s *)(o->out + o->used))->len = htol32(len); + o->used += sizeof(struct BProto_data_header_s); + + uint8_t *dest = (o->out + o->used); + o->used += len; + + o->iv_count++; + + return dest; +} + +int msg_seedParser_Init (msg_seedParser *o, uint8_t *buf, int buf_len) +{ + ASSERT(buf_len >= 0) + + o->buf = buf; + o->buf_len = buf_len; + o->seed_id_start = o->buf_len; + o->seed_id_span = 0; + o->seed_id_pos = 0; + o->key_start = o->buf_len; + o->key_span = 0; + o->key_pos = 0; + o->iv_start = o->buf_len; + o->iv_span = 0; + o->iv_pos = 0; + + int seed_id_count = 0; + int key_count = 0; + int iv_count = 0; + + int pos = 0; + int left = o->buf_len; + + while (left > 0) { + int entry_pos = pos; + + if (!(left >= sizeof(struct BProto_header_s))) { + return 0; + } + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + pos); + pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + if (!(left >= sizeof(struct BProto_uint8_s))) { + return 0; + } + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + + switch (id) { + default: + return 0; + } + } break; + case BPROTO_TYPE_UINT16: { + if (!(left >= sizeof(struct BProto_uint16_s))) { + return 0; + } + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + + switch (id) { + case 1: + if (o->seed_id_start == o->buf_len) { + o->seed_id_start = entry_pos; + } + o->seed_id_span = pos - o->seed_id_start; + seed_id_count++; + break; + default: + return 0; + } + } break; + case BPROTO_TYPE_UINT32: { + if (!(left >= sizeof(struct BProto_uint32_s))) { + return 0; + } + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + + switch (id) { + default: + return 0; + } + } break; + case BPROTO_TYPE_UINT64: { + if (!(left >= sizeof(struct BProto_uint64_s))) { + return 0; + } + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + + switch (id) { + default: + return 0; + } + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + if (!(left >= sizeof(struct BProto_data_header_s))) { + return 0; + } + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + pos); + pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + if (!(left >= payload_len)) { + return 0; + } + pos += payload_len; + left -= payload_len; + + switch (id) { + case 2: + if (!(type == BPROTO_TYPE_DATA)) { + return 0; + } + if (o->key_start == o->buf_len) { + o->key_start = entry_pos; + } + o->key_span = pos - o->key_start; + key_count++; + break; + case 3: + if (!(type == BPROTO_TYPE_DATA)) { + return 0; + } + if (o->iv_start == o->buf_len) { + o->iv_start = entry_pos; + } + o->iv_span = pos - o->iv_start; + iv_count++; + break; + default: + return 0; + } + } break; + default: + return 0; + } + } + + if (!(seed_id_count == 1)) { + return 0; + } + if (!(key_count == 1)) { + return 0; + } + if (!(iv_count == 1)) { + return 0; + } + + return 1; +} + +int msg_seedParser_GotEverything (msg_seedParser *o) +{ + return ( + o->seed_id_pos == o->seed_id_span + && + o->key_pos == o->key_span + && + o->iv_pos == o->iv_span + ); +} + +int msg_seedParser_Getseed_id (msg_seedParser *o, uint16_t *v) +{ + ASSERT(o->seed_id_pos >= 0) + ASSERT(o->seed_id_pos <= o->seed_id_span) + + int left = o->seed_id_span - o->seed_id_pos; + + while (left > 0) { + ASSERT(left >= sizeof(struct BProto_header_s)) + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + o->seed_id_start + o->seed_id_pos); + o->seed_id_pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + ASSERT(left >= sizeof(struct BProto_uint8_s)) + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + o->seed_id_start + o->seed_id_pos); + o->seed_id_pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + } break; + case BPROTO_TYPE_UINT16: { + ASSERT(left >= sizeof(struct BProto_uint16_s)) + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + o->seed_id_start + o->seed_id_pos); + o->seed_id_pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + + if (id == 1) { + *v = ltoh16(val->v); + return 1; + } + } break; + case BPROTO_TYPE_UINT32: { + ASSERT(left >= sizeof(struct BProto_uint32_s)) + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + o->seed_id_start + o->seed_id_pos); + o->seed_id_pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + } break; + case BPROTO_TYPE_UINT64: { + ASSERT(left >= sizeof(struct BProto_uint64_s)) + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + o->seed_id_start + o->seed_id_pos); + o->seed_id_pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + ASSERT(left >= sizeof(struct BProto_data_header_s)) + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + o->seed_id_start + o->seed_id_pos); + o->seed_id_pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + ASSERT(left >= payload_len) + uint8_t *payload = o->buf + o->seed_id_start + o->seed_id_pos; + o->seed_id_pos += payload_len; + left -= payload_len; + } break; + default: + ASSERT(0); + } + } + + return 0; +} + +void msg_seedParser_Resetseed_id (msg_seedParser *o) +{ + o->seed_id_pos = 0; +} + +void msg_seedParser_Forwardseed_id (msg_seedParser *o) +{ + o->seed_id_pos = o->seed_id_span; +} + +int msg_seedParser_Getkey (msg_seedParser *o, uint8_t **data, int *data_len) +{ + ASSERT(o->key_pos >= 0) + ASSERT(o->key_pos <= o->key_span) + + int left = o->key_span - o->key_pos; + + while (left > 0) { + ASSERT(left >= sizeof(struct BProto_header_s)) + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + o->key_start + o->key_pos); + o->key_pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + ASSERT(left >= sizeof(struct BProto_uint8_s)) + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + o->key_start + o->key_pos); + o->key_pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + } break; + case BPROTO_TYPE_UINT16: { + ASSERT(left >= sizeof(struct BProto_uint16_s)) + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + o->key_start + o->key_pos); + o->key_pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + } break; + case BPROTO_TYPE_UINT32: { + ASSERT(left >= sizeof(struct BProto_uint32_s)) + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + o->key_start + o->key_pos); + o->key_pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + } break; + case BPROTO_TYPE_UINT64: { + ASSERT(left >= sizeof(struct BProto_uint64_s)) + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + o->key_start + o->key_pos); + o->key_pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + ASSERT(left >= sizeof(struct BProto_data_header_s)) + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + o->key_start + o->key_pos); + o->key_pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + ASSERT(left >= payload_len) + uint8_t *payload = o->buf + o->key_start + o->key_pos; + o->key_pos += payload_len; + left -= payload_len; + + if (type == BPROTO_TYPE_DATA && id == 2) { + *data = payload; + *data_len = payload_len; + return 1; + } + } break; + default: + ASSERT(0); + } + } + + return 0; +} + +void msg_seedParser_Resetkey (msg_seedParser *o) +{ + o->key_pos = 0; +} + +void msg_seedParser_Forwardkey (msg_seedParser *o) +{ + o->key_pos = o->key_span; +} + +int msg_seedParser_Getiv (msg_seedParser *o, uint8_t **data, int *data_len) +{ + ASSERT(o->iv_pos >= 0) + ASSERT(o->iv_pos <= o->iv_span) + + int left = o->iv_span - o->iv_pos; + + while (left > 0) { + ASSERT(left >= sizeof(struct BProto_header_s)) + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + o->iv_start + o->iv_pos); + o->iv_pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + ASSERT(left >= sizeof(struct BProto_uint8_s)) + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + o->iv_start + o->iv_pos); + o->iv_pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + } break; + case BPROTO_TYPE_UINT16: { + ASSERT(left >= sizeof(struct BProto_uint16_s)) + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + o->iv_start + o->iv_pos); + o->iv_pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + } break; + case BPROTO_TYPE_UINT32: { + ASSERT(left >= sizeof(struct BProto_uint32_s)) + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + o->iv_start + o->iv_pos); + o->iv_pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + } break; + case BPROTO_TYPE_UINT64: { + ASSERT(left >= sizeof(struct BProto_uint64_s)) + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + o->iv_start + o->iv_pos); + o->iv_pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + ASSERT(left >= sizeof(struct BProto_data_header_s)) + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + o->iv_start + o->iv_pos); + o->iv_pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + ASSERT(left >= payload_len) + uint8_t *payload = o->buf + o->iv_start + o->iv_pos; + o->iv_pos += payload_len; + left -= payload_len; + + if (type == BPROTO_TYPE_DATA && id == 3) { + *data = payload; + *data_len = payload_len; + return 1; + } + } break; + default: + ASSERT(0); + } + } + + return 0; +} + +void msg_seedParser_Resetiv (msg_seedParser *o) +{ + o->iv_pos = 0; +} + +void msg_seedParser_Forwardiv (msg_seedParser *o) +{ + o->iv_pos = o->iv_span; +} + +#define msg_confirmseed_SIZEseed_id (sizeof(struct BProto_header_s) + sizeof(struct BProto_uint16_s)) + +typedef struct { + uint8_t *out; + int used; + int seed_id_count; +} msg_confirmseedWriter; + +static void msg_confirmseedWriter_Init (msg_confirmseedWriter *o, uint8_t *out); +static int msg_confirmseedWriter_Finish (msg_confirmseedWriter *o); +static void msg_confirmseedWriter_Addseed_id (msg_confirmseedWriter *o, uint16_t v); + +typedef struct { + uint8_t *buf; + int buf_len; + int seed_id_start; + int seed_id_span; + int seed_id_pos; +} msg_confirmseedParser; + +static int msg_confirmseedParser_Init (msg_confirmseedParser *o, uint8_t *buf, int buf_len); +static int msg_confirmseedParser_GotEverything (msg_confirmseedParser *o); +static int msg_confirmseedParser_Getseed_id (msg_confirmseedParser *o, uint16_t *v); +static void msg_confirmseedParser_Resetseed_id (msg_confirmseedParser *o); +static void msg_confirmseedParser_Forwardseed_id (msg_confirmseedParser *o); + +void msg_confirmseedWriter_Init (msg_confirmseedWriter *o, uint8_t *out) +{ + o->out = out; + o->used = 0; + o->seed_id_count = 0; +} + +int msg_confirmseedWriter_Finish (msg_confirmseedWriter *o) +{ + ASSERT(o->used >= 0) + ASSERT(o->seed_id_count == 1) + + return o->used; +} + +void msg_confirmseedWriter_Addseed_id (msg_confirmseedWriter *o, uint16_t v) +{ + ASSERT(o->used >= 0) + ASSERT(o->seed_id_count == 0) + + + ((struct BProto_header_s *)(o->out + o->used))->id = htol16(1); + ((struct BProto_header_s *)(o->out + o->used))->type = htol16(BPROTO_TYPE_UINT16); + o->used += sizeof(struct BProto_header_s); + + ((struct BProto_uint16_s *)(o->out + o->used))->v = htol16(v); + o->used += sizeof(struct BProto_uint16_s); + + o->seed_id_count++; +} + +int msg_confirmseedParser_Init (msg_confirmseedParser *o, uint8_t *buf, int buf_len) +{ + ASSERT(buf_len >= 0) + + o->buf = buf; + o->buf_len = buf_len; + o->seed_id_start = o->buf_len; + o->seed_id_span = 0; + o->seed_id_pos = 0; + + int seed_id_count = 0; + + int pos = 0; + int left = o->buf_len; + + while (left > 0) { + int entry_pos = pos; + + if (!(left >= sizeof(struct BProto_header_s))) { + return 0; + } + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + pos); + pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + if (!(left >= sizeof(struct BProto_uint8_s))) { + return 0; + } + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + + switch (id) { + default: + return 0; + } + } break; + case BPROTO_TYPE_UINT16: { + if (!(left >= sizeof(struct BProto_uint16_s))) { + return 0; + } + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + + switch (id) { + case 1: + if (o->seed_id_start == o->buf_len) { + o->seed_id_start = entry_pos; + } + o->seed_id_span = pos - o->seed_id_start; + seed_id_count++; + break; + default: + return 0; + } + } break; + case BPROTO_TYPE_UINT32: { + if (!(left >= sizeof(struct BProto_uint32_s))) { + return 0; + } + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + + switch (id) { + default: + return 0; + } + } break; + case BPROTO_TYPE_UINT64: { + if (!(left >= sizeof(struct BProto_uint64_s))) { + return 0; + } + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + pos); + pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + + switch (id) { + default: + return 0; + } + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + if (!(left >= sizeof(struct BProto_data_header_s))) { + return 0; + } + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + pos); + pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + if (!(left >= payload_len)) { + return 0; + } + pos += payload_len; + left -= payload_len; + + switch (id) { + default: + return 0; + } + } break; + default: + return 0; + } + } + + if (!(seed_id_count == 1)) { + return 0; + } + + return 1; +} + +int msg_confirmseedParser_GotEverything (msg_confirmseedParser *o) +{ + return ( + o->seed_id_pos == o->seed_id_span + ); +} + +int msg_confirmseedParser_Getseed_id (msg_confirmseedParser *o, uint16_t *v) +{ + ASSERT(o->seed_id_pos >= 0) + ASSERT(o->seed_id_pos <= o->seed_id_span) + + int left = o->seed_id_span - o->seed_id_pos; + + while (left > 0) { + ASSERT(left >= sizeof(struct BProto_header_s)) + struct BProto_header_s *header = (struct BProto_header_s *)(o->buf + o->seed_id_start + o->seed_id_pos); + o->seed_id_pos += sizeof(struct BProto_header_s); + left -= sizeof(struct BProto_header_s); + uint16_t type = ltoh16(header->type); + uint16_t id = ltoh16(header->id); + + switch (type) { + case BPROTO_TYPE_UINT8: { + ASSERT(left >= sizeof(struct BProto_uint8_s)) + struct BProto_uint8_s *val = (struct BProto_uint8_s *)(o->buf + o->seed_id_start + o->seed_id_pos); + o->seed_id_pos += sizeof(struct BProto_uint8_s); + left -= sizeof(struct BProto_uint8_s); + } break; + case BPROTO_TYPE_UINT16: { + ASSERT(left >= sizeof(struct BProto_uint16_s)) + struct BProto_uint16_s *val = (struct BProto_uint16_s *)(o->buf + o->seed_id_start + o->seed_id_pos); + o->seed_id_pos += sizeof(struct BProto_uint16_s); + left -= sizeof(struct BProto_uint16_s); + + if (id == 1) { + *v = ltoh16(val->v); + return 1; + } + } break; + case BPROTO_TYPE_UINT32: { + ASSERT(left >= sizeof(struct BProto_uint32_s)) + struct BProto_uint32_s *val = (struct BProto_uint32_s *)(o->buf + o->seed_id_start + o->seed_id_pos); + o->seed_id_pos += sizeof(struct BProto_uint32_s); + left -= sizeof(struct BProto_uint32_s); + } break; + case BPROTO_TYPE_UINT64: { + ASSERT(left >= sizeof(struct BProto_uint64_s)) + struct BProto_uint64_s *val = (struct BProto_uint64_s *)(o->buf + o->seed_id_start + o->seed_id_pos); + o->seed_id_pos += sizeof(struct BProto_uint64_s); + left -= sizeof(struct BProto_uint64_s); + } break; + case BPROTO_TYPE_DATA: + case BPROTO_TYPE_CONSTDATA: + { + ASSERT(left >= sizeof(struct BProto_data_header_s)) + struct BProto_data_header_s *val = (struct BProto_data_header_s *)(o->buf + o->seed_id_start + o->seed_id_pos); + o->seed_id_pos += sizeof(struct BProto_data_header_s); + left -= sizeof(struct BProto_data_header_s); + + int payload_len = ltoh32(val->len); + ASSERT(left >= payload_len) + uint8_t *payload = o->buf + o->seed_id_start + o->seed_id_pos; + o->seed_id_pos += payload_len; + left -= payload_len; + } break; + default: + ASSERT(0); + } + } + + return 0; +} + +void msg_confirmseedParser_Resetseed_id (msg_confirmseedParser *o) +{ + o->seed_id_pos = 0; +} + +void msg_confirmseedParser_Forwardseed_id (msg_confirmseedParser *o) +{ + o->seed_id_pos = o->seed_id_span; +} + diff --git a/generated/bstruct_OTPChecker.h b/generated/bstruct_OTPChecker.h new file mode 100644 index 000000000..d5dddd887 --- /dev/null +++ b/generated/bstruct_OTPChecker.h @@ -0,0 +1,138 @@ +/* + DO NOT EDIT THIS FILE! + This file was automatically generated by the bstruct generator. +*/ + +#include + +#include + + +typedef struct oc_table_struct oc_table; + +typedef struct { + int id_off; + int id_size; + #ifndef NDEBUG + int id_count; + #endif + + int entries_off; + int entries_size; + #ifndef NDEBUG + int entries_count; + #endif + + int len; + int align; +} oc_tableParams; + +static void oc_tableParams_Init (oc_tableParams *o, int num_entries) +{ + int cur_size; + int cur_align; + int cur_count; + + o->len = 0; + o->align = 1; + + cur_size = (sizeof(uint16_t)); + cur_align = (__alignof__(uint16_t)); + cur_count = (1); + o->id_off = BALIGN_UP_N(o->len, cur_align); + o->id_size = cur_size; + #ifndef NDEBUG + o->id_count = cur_count; + #endif + o->len = o->id_off + (cur_count * cur_size); + o->align = (cur_align > o->align ? cur_align : o->align); + + cur_size = (sizeof(struct OTPChecker_entry)); + cur_align = (__alignof__(struct OTPChecker_entry)); + cur_count = (num_entries); + o->entries_off = BALIGN_UP_N(o->len, cur_align); + o->entries_size = cur_size; + #ifndef NDEBUG + o->entries_count = cur_count; + #endif + o->len = o->entries_off + (cur_count * cur_size); + o->align = (cur_align > o->align ? cur_align : o->align); + +} + +static uint16_t * oc_table_id (oc_tableParams *o, oc_table *s) +{ + return (uint16_t *)((uint8_t *)s + o->id_off); +} + +static uint16_t * oc_table_id_at (oc_tableParams *o, oc_table *s, int i) +{ + ASSERT(i >= 0) + ASSERT(i < o->id_count) + + return (uint16_t *)((uint8_t *)s + o->id_off + i * o->id_size); +} + +static struct OTPChecker_entry * oc_table_entries (oc_tableParams *o, oc_table *s) +{ + return (struct OTPChecker_entry *)((uint8_t *)s + o->entries_off); +} + +static struct OTPChecker_entry * oc_table_entries_at (oc_tableParams *o, oc_table *s, int i) +{ + ASSERT(i >= 0) + ASSERT(i < o->entries_count) + + return (struct OTPChecker_entry *)((uint8_t *)s + o->entries_off + i * o->entries_size); +} + +typedef struct oc_tables_struct oc_tables; + +typedef struct { + oc_tableParams tables_params; + int tables_off; + int tables_size; + #ifndef NDEBUG + int tables_count; + #endif + + int len; + int align; +} oc_tablesParams; + +static void oc_tablesParams_Init (oc_tablesParams *o, int num_tables, int num_entries) +{ + int cur_size; + int cur_align; + int cur_count; + + o->len = 0; + o->align = 1; + + oc_tableParams_Init(&o->tables_params, num_entries); + cur_size = o->tables_params.len; + cur_align = o->tables_params.align; + cur_count = (num_tables); + o->tables_off = BALIGN_UP_N(o->len, cur_align); + o->tables_size = cur_size; + #ifndef NDEBUG + o->tables_count = cur_count; + #endif + o->len = o->tables_off + (cur_count * cur_size); + o->align = (cur_align > o->align ? cur_align : o->align); + +} + +static oc_table * oc_tables_tables (oc_tablesParams *o, oc_tables *s) +{ + return (oc_table *)((uint8_t *)s + o->tables_off); +} + +static oc_table * oc_tables_tables_at (oc_tablesParams *o, oc_tables *s, int i) +{ + ASSERT(i >= 0) + ASSERT(i < o->tables_count) + + return (oc_table *)((uint8_t *)s + o->tables_off + i * o->tables_size); +} + diff --git a/generated/bstruct_bstruct_test.h b/generated/bstruct_bstruct_test.h new file mode 100644 index 000000000..1e79a7a19 --- /dev/null +++ b/generated/bstruct_bstruct_test.h @@ -0,0 +1,198 @@ +/* + DO NOT EDIT THIS FILE! + This file was automatically generated by the bstruct generator. +*/ + +#include + +#include + + +typedef struct str0_struct str0; + +typedef struct { + int x_off; + int x_size; + #ifndef NDEBUG + int x_count; + #endif + + int len; + int align; +} str0Params; + +static void str0Params_Init (str0Params *o, int n) +{ + int cur_size; + int cur_align; + int cur_count; + + o->len = 0; + o->align = 1; + + cur_size = (sizeof(int)); + cur_align = (__alignof__(int)); + cur_count = (n); + o->x_off = BALIGN_UP_N(o->len, cur_align); + o->x_size = cur_size; + #ifndef NDEBUG + o->x_count = cur_count; + #endif + o->len = o->x_off + (cur_count * cur_size); + o->align = (cur_align > o->align ? cur_align : o->align); + +} + +static int * str0_x (str0Params *o, str0 *s) +{ + return (int *)((uint8_t *)s + o->x_off); +} + +static int * str0_x_at (str0Params *o, str0 *s, int i) +{ + ASSERT(i >= 0) + ASSERT(i < o->x_count) + + return (int *)((uint8_t *)s + o->x_off + i * o->x_size); +} + +typedef struct str1_struct str1; + +typedef struct { + int a_off; + int a_size; + #ifndef NDEBUG + int a_count; + #endif + + int b_off; + int b_size; + #ifndef NDEBUG + int b_count; + #endif + + int c_off; + int c_size; + #ifndef NDEBUG + int c_count; + #endif + + str0Params d_params; + int d_off; + int d_size; + #ifndef NDEBUG + int d_count; + #endif + + int len; + int align; +} str1Params; + +static void str1Params_Init (str1Params *o, int nb, int nc, int m) +{ + int cur_size; + int cur_align; + int cur_count; + + o->len = 0; + o->align = 1; + + cur_size = (sizeof(int)); + cur_align = (__alignof__(int)); + cur_count = (1); + o->a_off = BALIGN_UP_N(o->len, cur_align); + o->a_size = cur_size; + #ifndef NDEBUG + o->a_count = cur_count; + #endif + o->len = o->a_off + (cur_count * cur_size); + o->align = (cur_align > o->align ? cur_align : o->align); + + cur_size = (sizeof(char)); + cur_align = (__alignof__(char)); + cur_count = (nb); + o->b_off = BALIGN_UP_N(o->len, cur_align); + o->b_size = cur_size; + #ifndef NDEBUG + o->b_count = cur_count; + #endif + o->len = o->b_off + (cur_count * cur_size); + o->align = (cur_align > o->align ? cur_align : o->align); + + cur_size = (sizeof(double)); + cur_align = (__alignof__(double)); + cur_count = (nc); + o->c_off = BALIGN_UP_N(o->len, cur_align); + o->c_size = cur_size; + #ifndef NDEBUG + o->c_count = cur_count; + #endif + o->len = o->c_off + (cur_count * cur_size); + o->align = (cur_align > o->align ? cur_align : o->align); + + str0Params_Init(&o->d_params, m); + cur_size = o->d_params.len; + cur_align = o->d_params.align; + cur_count = (1); + o->d_off = BALIGN_UP_N(o->len, cur_align); + o->d_size = cur_size; + #ifndef NDEBUG + o->d_count = cur_count; + #endif + o->len = o->d_off + (cur_count * cur_size); + o->align = (cur_align > o->align ? cur_align : o->align); + +} + +static int * str1_a (str1Params *o, str1 *s) +{ + return (int *)((uint8_t *)s + o->a_off); +} + +static int * str1_a_at (str1Params *o, str1 *s, int i) +{ + ASSERT(i >= 0) + ASSERT(i < o->a_count) + + return (int *)((uint8_t *)s + o->a_off + i * o->a_size); +} + +static char * str1_b (str1Params *o, str1 *s) +{ + return (char *)((uint8_t *)s + o->b_off); +} + +static char * str1_b_at (str1Params *o, str1 *s, int i) +{ + ASSERT(i >= 0) + ASSERT(i < o->b_count) + + return (char *)((uint8_t *)s + o->b_off + i * o->b_size); +} + +static double * str1_c (str1Params *o, str1 *s) +{ + return (double *)((uint8_t *)s + o->c_off); +} + +static double * str1_c_at (str1Params *o, str1 *s, int i) +{ + ASSERT(i >= 0) + ASSERT(i < o->c_count) + + return (double *)((uint8_t *)s + o->c_off + i * o->c_size); +} + +static str0 * str1_d (str1Params *o, str1 *s) +{ + return (str0 *)((uint8_t *)s + o->d_off); +} + +static str0 * str1_d_at (str1Params *o, str1 *s, int i) +{ + ASSERT(i >= 0) + ASSERT(i < o->d_count) + + return (str0 *)((uint8_t *)s + o->d_off + i * o->d_size); +} + diff --git a/generated/flex_BPredicate.c b/generated/flex_BPredicate.c new file mode 100644 index 000000000..8dd752efa --- /dev/null +++ b/generated/flex_BPredicate.c @@ -0,0 +1,2148 @@ +#line 2 "generated//flex_BPredicate.c" + +#line 4 "generated//flex_BPredicate.c" + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 5 +#define YY_FLEX_SUBMINOR_VERSION 35 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; +#endif /* ! C99 */ + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#endif /* ! FLEXINT_H */ + +#ifdef __cplusplus + +/* The "const" storage-class-modifier is valid. */ +#define YY_USE_CONST + +#else /* ! __cplusplus */ + +/* C99 requires __STDC__ to be defined as 1. */ +#if defined (__STDC__) + +#define YY_USE_CONST + +#endif /* defined (__STDC__) */ +#endif /* ! __cplusplus */ + +#ifdef YY_USE_CONST +#define yyconst const +#else +#define yyconst +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an unsigned + * integer for use as an array index. If the signed char is negative, + * we want to instead treat it as an 8-bit unsigned char, hence the + * double cast. + */ +#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c) + +/* An opaque pointer. */ +#ifndef YY_TYPEDEF_YY_SCANNER_T +#define YY_TYPEDEF_YY_SCANNER_T +typedef void* yyscan_t; +#endif + +/* For convenience, these vars (plus the bison vars far below) + are macros in the reentrant scanner. */ +#define yyin yyg->yyin_r +#define yyout yyg->yyout_r +#define yyextra yyg->yyextra_r +#define yyleng yyg->yyleng_r +#define yytext yyg->yytext_r +#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno) +#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) +#define yy_flex_debug yyg->yy_flex_debug_r + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN yyg->yy_start = 1 + 2 * + +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START ((yyg->yy_start - 1) / 2) +#define YYSTATE YY_START + +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) + +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE yyrestart(yyin ,yyscanner ) + +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#define YY_BUF_SIZE 16384 +#endif + +/* The state buf must be large enough to hold one state per character in the main buffer. + */ +#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + #define YY_LESS_LINENO(n) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = yyg->yy_hold_char; \ + YY_RESTORE_YY_MORE_OFFSET \ + yyg->yy_c_buf_p = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yytext again */ \ + } \ + while ( 0 ) + +#define unput(c) yyunput( c, yyg->yytext_ptr , yyscanner ) + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + yy_size_t yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via yyrestart()), so that the user can continue scanning by + * just pointing yyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( yyg->yy_buffer_stack \ + ? yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] \ + : NULL) + +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] + +void yyrestart (FILE *input_file ,yyscan_t yyscanner ); +void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner ); +YY_BUFFER_STATE yy_create_buffer (FILE *file,int size ,yyscan_t yyscanner ); +void yy_delete_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner ); +void yy_flush_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner ); +void yypush_buffer_state (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner ); +void yypop_buffer_state (yyscan_t yyscanner ); + +static void yyensure_buffer_stack (yyscan_t yyscanner ); +static void yy_load_buffer_state (yyscan_t yyscanner ); +static void yy_init_buffer (YY_BUFFER_STATE b,FILE *file ,yyscan_t yyscanner ); + +#define YY_FLUSH_BUFFER yy_flush_buffer(YY_CURRENT_BUFFER ,yyscanner) + +YY_BUFFER_STATE yy_scan_buffer (char *base,yy_size_t size ,yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_string (yyconst char *yy_str ,yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_bytes (yyconst char *bytes,int len ,yyscan_t yyscanner ); + +void *yyalloc (yy_size_t ,yyscan_t yyscanner ); +void *yyrealloc (void *,yy_size_t ,yyscan_t yyscanner ); +void yyfree (void * ,yyscan_t yyscanner ); + +#define yy_new_buffer yy_create_buffer + +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } + +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } + +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +/* Begin user sect3 */ + +#define yywrap(n) 1 +#define YY_SKIP_YYWRAP + +typedef unsigned char YY_CHAR; + +typedef int yy_state_type; + +#define yytext_ptr yytext_r + +static yy_state_type yy_get_previous_state (yyscan_t yyscanner ); +static yy_state_type yy_try_NUL_trans (yy_state_type current_state ,yyscan_t yyscanner); +static int yy_get_next_buffer (yyscan_t yyscanner ); +static void yy_fatal_error (yyconst char msg[] ,yyscan_t yyscanner ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + yyg->yytext_ptr = yy_bp; \ + yyleng = (size_t) (yy_cp - yy_bp); \ + yyg->yy_hold_char = *yy_cp; \ + *yy_cp = '\0'; \ + yyg->yy_c_buf_p = yy_cp; + +#define YY_NUM_RULES 13 +#define YY_END_OF_BUFFER 14 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static yyconst flex_int16_t yy_accept[34] = + { 0, + 0, 0, 14, 12, 11, 11, 12, 1, 2, 3, + 9, 9, 9, 9, 9, 9, 11, 0, 10, 9, + 9, 9, 5, 9, 9, 4, 6, 9, 9, 9, + 7, 8, 0 + } ; + +static yyconst flex_int32_t yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 1, 4, 1, 1, 1, 1, 1, 5, + 6, 1, 1, 7, 1, 1, 1, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, + 1, 1, 1, 1, 9, 8, 8, 10, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 11, 12, 8, + 8, 13, 8, 14, 8, 8, 8, 8, 8, 8, + 1, 1, 1, 1, 8, 1, 15, 8, 8, 8, + + 16, 17, 8, 8, 8, 8, 8, 18, 8, 8, + 8, 8, 8, 19, 20, 21, 22, 8, 8, 8, + 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1 + } ; + +static yyconst flex_int32_t yy_meta[23] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2 + } ; + +static yyconst flex_int16_t yy_base[36] = + { 0, + 0, 0, 46, 47, 21, 23, 41, 47, 47, 47, + 0, 33, 31, 29, 26, 21, 25, 35, 47, 0, + 28, 23, 0, 18, 13, 0, 0, 14, 17, 16, + 0, 0, 47, 28, 29 + } ; + +static yyconst flex_int16_t yy_def[36] = + { 0, + 33, 1, 33, 33, 33, 33, 34, 33, 33, 33, + 35, 35, 35, 35, 35, 35, 33, 34, 33, 35, + 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, + 35, 35, 0, 33, 33 + } ; + +static yyconst flex_int16_t yy_nxt[70] = + { 0, + 4, 5, 6, 7, 8, 9, 10, 11, 12, 11, + 13, 14, 11, 11, 11, 11, 15, 11, 11, 11, + 16, 11, 17, 17, 17, 17, 17, 17, 18, 18, + 20, 32, 31, 30, 29, 28, 27, 26, 19, 25, + 24, 23, 22, 21, 19, 33, 3, 33, 33, 33, + 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, + 33, 33, 33, 33, 33, 33, 33, 33, 33 + } ; + +static yyconst flex_int16_t yy_chk[70] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 5, 5, 6, 6, 17, 17, 34, 34, + 35, 30, 29, 28, 25, 24, 22, 21, 18, 16, + 15, 14, 13, 12, 7, 3, 33, 33, 33, 33, + 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, + 33, 33, 33, 33, 33, 33, 33, 33, 33 + } ; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +#line 1 "predicate/BPredicate.l" +/** + * @file BPredicate.l + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * {@link BPredicate} lexer file. + */ +#line 28 "predicate/BPredicate.l" + +#include +#include + +#include +#include + +#include + +#define YY_EXTRA_TYPE LexMemoryBufferInput * + +#define YY_INPUT(buffer, res, max_size) \ + int bytes_read = LexMemoryBufferInput_Read(yyget_extra(yyscanner), buffer, max_size); \ + res = (bytes_read == 0 ? YY_NULL : bytes_read); + +#line 496 "generated//flex_BPredicate.c" + +#define INITIAL 0 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +/* Holds the entire state of the reentrant scanner. */ +struct yyguts_t + { + + /* User-defined. Not touched by flex. */ + YY_EXTRA_TYPE yyextra_r; + + /* The rest are the same as the globals declared in the non-reentrant scanner. */ + FILE *yyin_r, *yyout_r; + size_t yy_buffer_stack_top; /**< index of top of stack. */ + size_t yy_buffer_stack_max; /**< capacity of stack. */ + YY_BUFFER_STATE * yy_buffer_stack; /**< Stack as an array. */ + char yy_hold_char; + int yy_n_chars; + int yyleng_r; + char *yy_c_buf_p; + int yy_init; + int yy_start; + int yy_did_buffer_switch_on_eof; + int yy_start_stack_ptr; + int yy_start_stack_depth; + int *yy_start_stack; + yy_state_type yy_last_accepting_state; + char* yy_last_accepting_cpos; + + int yylineno_r; + int yy_flex_debug_r; + + char *yytext_r; + int yy_more_flag; + int yy_more_len; + + YYSTYPE * yylval_r; + + YYLTYPE * yylloc_r; + + }; /* end struct yyguts_t */ + +static int yy_init_globals (yyscan_t yyscanner ); + + /* This must go here because YYSTYPE and YYLTYPE are included + * from bison output in section 1.*/ + # define yylval yyg->yylval_r + + # define yylloc yyg->yylloc_r + +int yylex_init (yyscan_t* scanner); + +int yylex_init_extra (YY_EXTRA_TYPE user_defined,yyscan_t* scanner); + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int yylex_destroy (yyscan_t yyscanner ); + +int yyget_debug (yyscan_t yyscanner ); + +void yyset_debug (int debug_flag ,yyscan_t yyscanner ); + +YY_EXTRA_TYPE yyget_extra (yyscan_t yyscanner ); + +void yyset_extra (YY_EXTRA_TYPE user_defined ,yyscan_t yyscanner ); + +FILE *yyget_in (yyscan_t yyscanner ); + +void yyset_in (FILE * in_str ,yyscan_t yyscanner ); + +FILE *yyget_out (yyscan_t yyscanner ); + +void yyset_out (FILE * out_str ,yyscan_t yyscanner ); + +int yyget_leng (yyscan_t yyscanner ); + +char *yyget_text (yyscan_t yyscanner ); + +int yyget_lineno (yyscan_t yyscanner ); + +void yyset_lineno (int line_number ,yyscan_t yyscanner ); + +YYSTYPE * yyget_lval (yyscan_t yyscanner ); + +void yyset_lval (YYSTYPE * yylval_param ,yyscan_t yyscanner ); + + YYLTYPE *yyget_lloc (yyscan_t yyscanner ); + + void yyset_lloc (YYLTYPE * yylloc_param ,yyscan_t yyscanner ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap (yyscan_t yyscanner ); +#else +extern int yywrap (yyscan_t yyscanner ); +#endif +#endif + + static void yyunput (int c,char *buf_ptr ,yyscan_t yyscanner); + +#ifndef yytext_ptr +static void yy_flex_strncpy (char *,yyconst char *,int ,yyscan_t yyscanner); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * ,yyscan_t yyscanner); +#endif + +#ifndef YY_NO_INPUT + +#ifdef __cplusplus +static int yyinput (yyscan_t yyscanner ); +#else +static int input (yyscan_t yyscanner ); +#endif + +#endif + + static void yy_push_state (int new_state ,yyscan_t yyscanner); + + static void yy_pop_state (yyscan_t yyscanner ); + + static int yy_top_state (yyscan_t yyscanner ); + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#define YY_READ_BUF_SIZE 8192 +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO fwrite( yytext, yyleng, 1, yyout ) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ + { \ + int c = '*'; \ + int n; \ + for ( n = 0; n < max_size && \ + (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( yyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = fread(buf, 1, max_size, yyin))==0 && ferror(yyin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(yyin); \ + } \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg , yyscanner) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int yylex \ + (YYSTYPE * yylval_param,YYLTYPE * yylloc_param ,yyscan_t yyscanner); + +#define YY_DECL int yylex \ + (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after yytext and yyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK break; +#endif + +#define YY_RULE_SETUP \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + register yy_state_type yy_current_state; + register char *yy_cp, *yy_bp; + register int yy_act; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + +#line 47 "predicate/BPredicate.l" + +#line 745 "generated//flex_BPredicate.c" + + yylval = yylval_param; + + yylloc = yylloc_param; + + if ( !yyg->yy_init ) + { + yyg->yy_init = 1; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! yyg->yy_start ) + yyg->yy_start = 1; /* first start state */ + + if ( ! yyin ) + yyin = stdin; + + if ( ! yyout ) + yyout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); + } + + yy_load_buffer_state(yyscanner ); + } + + while ( 1 ) /* loops until end-of-file is reached */ + { + yy_cp = yyg->yy_c_buf_p; + + /* Support of yytext. */ + *yy_cp = yyg->yy_hold_char; + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = yyg->yy_start; +yy_match: + do + { + register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)]; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 34 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + ++yy_cp; + } + while ( yy_base[yy_current_state] != 47 ); + +yy_find_action: + yy_act = yy_accept[yy_current_state]; + if ( yy_act == 0 ) + { /* have to back up */ + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + yy_act = yy_accept[yy_current_state]; + } + + YY_DO_BEFORE_ACTION; + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ + case 0: /* must back up */ + /* undo the effects of YY_DO_BEFORE_ACTION */ + *yy_cp = yyg->yy_hold_char; + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + goto yy_find_action; + +case 1: +YY_RULE_SETUP +#line 48 "predicate/BPredicate.l" +return SPAR; + YY_BREAK +case 2: +YY_RULE_SETUP +#line 49 "predicate/BPredicate.l" +return EPAR; + YY_BREAK +case 3: +YY_RULE_SETUP +#line 50 "predicate/BPredicate.l" +return COMMA; + YY_BREAK +case 4: +YY_RULE_SETUP +#line 51 "predicate/BPredicate.l" +return AND; + YY_BREAK +case 5: +YY_RULE_SETUP +#line 52 "predicate/BPredicate.l" +return OR; + YY_BREAK +case 6: +YY_RULE_SETUP +#line 53 "predicate/BPredicate.l" +return NOT; + YY_BREAK +case 7: +YY_RULE_SETUP +#line 54 "predicate/BPredicate.l" +return CONSTANT_TRUE; + YY_BREAK +case 8: +YY_RULE_SETUP +#line 55 "predicate/BPredicate.l" +return CONSTANT_FALSE; + YY_BREAK +case 9: +YY_RULE_SETUP +#line 56 "predicate/BPredicate.l" +{ + int l = strlen(yytext); + char *p = malloc(l + 1); + if (p) { + memcpy(p, yytext, l); + p[l] = '\0'; + } + yylval->text = p; + return NAME; + } + YY_BREAK +case 10: +/* rule 10 can match eol */ +YY_RULE_SETUP +#line 66 "predicate/BPredicate.l" +{ + int l = strlen(yytext); + char *p = malloc(l - 1); + if (p) { + memcpy(p, yytext + 1, l - 2); + p[l - 2] = '\0'; + } + yylval->text = p; + return STRING; + } + YY_BREAK +case 11: +/* rule 11 can match eol */ +YY_RULE_SETUP +#line 76 "predicate/BPredicate.l" +; + YY_BREAK +case 12: +YY_RULE_SETUP +#line 77 "predicate/BPredicate.l" +LexMemoryBufferInput_SetError(yyget_extra(yyscanner)); return 0; // remember failure and report EOF + YY_BREAK +case 13: +YY_RULE_SETUP +#line 78 "predicate/BPredicate.l" +ECHO; + YY_BREAK +#line 917 "generated//flex_BPredicate.c" +case YY_STATE_EOF(INITIAL): + yyterminate(); + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - yyg->yytext_ptr) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = yyg->yy_hold_char; + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed yyin at a new source and called + * yylex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( yyg->yy_c_buf_p <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + yyg->yy_c_buf_p = yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state , yyscanner); + + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++yyg->yy_c_buf_p; + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = yyg->yy_c_buf_p; + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_END_OF_FILE: + { + yyg->yy_did_buffer_switch_on_eof = 0; + + if ( yywrap(yyscanner ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + yyg->yy_c_buf_p = yyg->yytext_ptr + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = + yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + yyg->yy_c_buf_p = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars]; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ +} /* end of yylex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + register char *source = yyg->yytext_ptr; + register int number_to_move, i; + int ret_val; + + if ( yyg->yy_c_buf_p > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( yyg->yy_c_buf_p - yyg->yytext_ptr - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr) - 1; + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars = 0; + + else + { + int num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER; + + int yy_c_buf_p_offset = + (int) (yyg->yy_c_buf_p - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + int new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + yyrealloc((void *) b->yy_ch_buf,b->yy_buf_size + 2 ,yyscanner ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = 0; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + yyg->yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + yyg->yy_n_chars, (size_t) num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + if ( yyg->yy_n_chars == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + yyrestart(yyin ,yyscanner); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + if ((yy_size_t) (yyg->yy_n_chars + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { + /* Extend the array by 50%, plus the number we really need. */ + yy_size_t new_size = yyg->yy_n_chars + number_to_move + (yyg->yy_n_chars >> 1); + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc((void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf,new_size ,yyscanner ); + if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); + } + + yyg->yy_n_chars += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR; + + yyg->yytext_ptr = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (yyscan_t yyscanner) +{ + register yy_state_type yy_current_state; + register char *yy_cp; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_current_state = yyg->yy_start; + + for ( yy_cp = yyg->yytext_ptr + YY_MORE_ADJ; yy_cp < yyg->yy_c_buf_p; ++yy_cp ) + { + register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 34 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state , yyscan_t yyscanner) +{ + register int yy_is_jam; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be unused depending upon options. */ + register char *yy_cp = yyg->yy_c_buf_p; + + register YY_CHAR yy_c = 1; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 34 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + yy_is_jam = (yy_current_state == 33); + + return yy_is_jam ? 0 : yy_current_state; +} + + static void yyunput (int c, register char * yy_bp , yyscan_t yyscanner) +{ + register char *yy_cp; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_cp = yyg->yy_c_buf_p; + + /* undo effects of setting up yytext */ + *yy_cp = yyg->yy_hold_char; + + if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) + { /* need to shift things up to make room */ + /* +2 for EOB chars. */ + register int number_to_move = yyg->yy_n_chars + 2; + register char *dest = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size + 2]; + register char *source = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]; + + while ( source > YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + *--dest = *--source; + + yy_cp += (int) (dest - source); + yy_bp += (int) (dest - source); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_buf_size; + + if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) + YY_FATAL_ERROR( "flex scanner push-back overflow" ); + } + + *--yy_cp = (char) c; + + yyg->yytext_ptr = yy_bp; + yyg->yy_hold_char = *yy_cp; + yyg->yy_c_buf_p = yy_cp; +} + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (yyscan_t yyscanner) +#else + static int input (yyscan_t yyscanner) +#endif + +{ + int c; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + *yyg->yy_c_buf_p = yyg->yy_hold_char; + + if ( *yyg->yy_c_buf_p == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( yyg->yy_c_buf_p < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + /* This was really a NUL. */ + *yyg->yy_c_buf_p = '\0'; + + else + { /* need more input */ + int offset = yyg->yy_c_buf_p - yyg->yytext_ptr; + ++yyg->yy_c_buf_p; + + switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + yyrestart(yyin ,yyscanner); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( yywrap(yyscanner ) ) + return EOF; + + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(yyscanner); +#else + return input(yyscanner); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = yyg->yytext_ptr + offset; + break; + } + } + } + + c = *(unsigned char *) yyg->yy_c_buf_p; /* cast for 8-bit char's */ + *yyg->yy_c_buf_p = '\0'; /* preserve yytext */ + yyg->yy_hold_char = *++yyg->yy_c_buf_p; + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * @param yyscanner The scanner object. + * @note This function does not reset the start condition to @c INITIAL . + */ + void yyrestart (FILE * input_file , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! YY_CURRENT_BUFFER ){ + yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); + } + + yy_init_buffer(YY_CURRENT_BUFFER,input_file ,yyscanner); + yy_load_buffer_state(yyscanner ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * @param yyscanner The scanner object. + */ + void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* TODO. We should be able to replace this entire function body + * with + * yypop_buffer_state(); + * yypush_buffer_state(new_buffer); + */ + yyensure_buffer_stack (yyscanner); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + yy_load_buffer_state(yyscanner ); + + /* We don't actually know whether we did this switch during + * EOF (yywrap()) processing, but the only time this flag + * is looked at is after yywrap() is called, so it's safe + * to go ahead and always set it. + */ + yyg->yy_did_buffer_switch_on_eof = 1; +} + +static void yy_load_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + yyg->yytext_ptr = yyg->yy_c_buf_p = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + yyg->yy_hold_char = *yyg->yy_c_buf_p; +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * @param yyscanner The scanner object. + * @return the allocated buffer state. + */ + YY_BUFFER_STATE yy_create_buffer (FILE * file, int size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) yyalloc(sizeof( struct yy_buffer_state ) ,yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) yyalloc(b->yy_buf_size + 2 ,yyscanner ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + yy_init_buffer(b,file ,yyscanner); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with yy_create_buffer() + * @param yyscanner The scanner object. + */ + void yy_delete_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + yyfree((void *) b->yy_ch_buf ,yyscanner ); + + yyfree((void *) b ,yyscanner ); +} + +#ifndef _UNISTD_H /* assume unistd.h has isatty() for us */ +#ifdef __cplusplus +extern "C" { +#endif +#ifdef __THROW /* this is a gnuism */ +extern int isatty (int ) __THROW; +#else +extern int isatty (int ); +#endif +#ifdef __cplusplus +} +#endif +#endif + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a yyrestart() or at EOF. + */ + static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file , yyscan_t yyscanner) + +{ + int oerrno = errno; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_flush_buffer(b ,yyscanner); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then yy_init_buffer was _probably_ + * called from yyrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * @param yyscanner The scanner object. + */ + void yy_flush_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + yy_load_buffer_state(yyscanner ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * @param yyscanner The scanner object. + */ +void yypush_buffer_state (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (new_buffer == NULL) + return; + + yyensure_buffer_stack(yyscanner); + + /* This block is copied from yy_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + yyg->yy_buffer_stack_top++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from yy_switch_to_buffer. */ + yy_load_buffer_state(yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * @param yyscanner The scanner object. + */ +void yypop_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (!YY_CURRENT_BUFFER) + return; + + yy_delete_buffer(YY_CURRENT_BUFFER ,yyscanner); + YY_CURRENT_BUFFER_LVALUE = NULL; + if (yyg->yy_buffer_stack_top > 0) + --yyg->yy_buffer_stack_top; + + if (YY_CURRENT_BUFFER) { + yy_load_buffer_state(yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void yyensure_buffer_stack (yyscan_t yyscanner) +{ + int num_to_alloc; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (!yyg->yy_buffer_stack) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; + yyg->yy_buffer_stack = (struct yy_buffer_state**)yyalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + if ( ! yyg->yy_buffer_stack ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + memset(yyg->yy_buffer_stack, 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + yyg->yy_buffer_stack_max = num_to_alloc; + yyg->yy_buffer_stack_top = 0; + return; + } + + if (yyg->yy_buffer_stack_top >= (yyg->yy_buffer_stack_max) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + int grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = yyg->yy_buffer_stack_max + grow_size; + yyg->yy_buffer_stack = (struct yy_buffer_state**)yyrealloc + (yyg->yy_buffer_stack, + num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + if ( ! yyg->yy_buffer_stack ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + /* zero only the new slots.*/ + memset(yyg->yy_buffer_stack + yyg->yy_buffer_stack_max, 0, grow_size * sizeof(struct yy_buffer_state*)); + yyg->yy_buffer_stack_max = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return 0; + + b = (YY_BUFFER_STATE) yyalloc(sizeof( struct yy_buffer_state ) ,yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" ); + + b->yy_buf_size = size - 2; /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = 0; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + yy_switch_to_buffer(b ,yyscanner ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to yylex() will + * scan from a @e copy of @a str. + * @param yystr a NUL-terminated string to scan + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * yy_scan_bytes() instead. + */ +YY_BUFFER_STATE yy_scan_string (yyconst char * yystr , yyscan_t yyscanner) +{ + + return yy_scan_bytes(yystr,strlen(yystr) ,yyscanner); +} + +/** Setup the input buffer state to scan the given bytes. The next call to yylex() will + * scan from a @e copy of @a bytes. + * @param bytes the byte buffer to scan + * @param len the number of bytes in the buffer pointed to by @a bytes. + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_bytes (yyconst char * yybytes, int _yybytes_len , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + int i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = _yybytes_len + 2; + buf = (char *) yyalloc(n ,yyscanner ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); + + for ( i = 0; i < _yybytes_len; ++i ) + buf[i] = yybytes[i]; + + buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; + + b = yy_scan_buffer(buf,n ,yyscanner); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + + static void yy_push_state (int new_state , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if ( yyg->yy_start_stack_ptr >= yyg->yy_start_stack_depth ) + { + yy_size_t new_size; + + yyg->yy_start_stack_depth += YY_START_STACK_INCR; + new_size = yyg->yy_start_stack_depth * sizeof( int ); + + if ( ! yyg->yy_start_stack ) + yyg->yy_start_stack = (int *) yyalloc(new_size ,yyscanner ); + + else + yyg->yy_start_stack = (int *) yyrealloc((void *) yyg->yy_start_stack,new_size ,yyscanner ); + + if ( ! yyg->yy_start_stack ) + YY_FATAL_ERROR( "out of memory expanding start-condition stack" ); + } + + yyg->yy_start_stack[yyg->yy_start_stack_ptr++] = YY_START; + + BEGIN(new_state); +} + + static void yy_pop_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if ( --yyg->yy_start_stack_ptr < 0 ) + YY_FATAL_ERROR( "start-condition stack underflow" ); + + BEGIN(yyg->yy_start_stack[yyg->yy_start_stack_ptr]); +} + + static int yy_top_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyg->yy_start_stack[yyg->yy_start_stack_ptr - 1]; +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yy_fatal_error (yyconst char* msg , yyscan_t yyscanner) +{ + (void) fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + yytext[yyleng] = yyg->yy_hold_char; \ + yyg->yy_c_buf_p = yytext + yyless_macro_arg; \ + yyg->yy_hold_char = *yyg->yy_c_buf_p; \ + *yyg->yy_c_buf_p = '\0'; \ + yyleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the user-defined data for this scanner. + * @param yyscanner The scanner object. + */ +YY_EXTRA_TYPE yyget_extra (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyextra; +} + +/** Get the current line number. + * @param yyscanner The scanner object. + */ +int yyget_lineno (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yylineno; +} + +/** Get the current column number. + * @param yyscanner The scanner object. + */ +int yyget_column (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yycolumn; +} + +/** Get the input stream. + * @param yyscanner The scanner object. + */ +FILE *yyget_in (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyin; +} + +/** Get the output stream. + * @param yyscanner The scanner object. + */ +FILE *yyget_out (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyout; +} + +/** Get the length of the current token. + * @param yyscanner The scanner object. + */ +int yyget_leng (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyleng; +} + +/** Get the current token. + * @param yyscanner The scanner object. + */ + +char *yyget_text (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yytext; +} + +/** Set the user-defined data. This data is never touched by the scanner. + * @param user_defined The data to be associated with this scanner. + * @param yyscanner The scanner object. + */ +void yyset_extra (YY_EXTRA_TYPE user_defined , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyextra = user_defined ; +} + +/** Set the current line number. + * @param line_number + * @param yyscanner The scanner object. + */ +void yyset_lineno (int line_number , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* lineno is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + yy_fatal_error( "yyset_lineno called with no buffer" , yyscanner); + + yylineno = line_number; +} + +/** Set the current column. + * @param line_number + * @param yyscanner The scanner object. + */ +void yyset_column (int column_no , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* column is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + yy_fatal_error( "yyset_column called with no buffer" , yyscanner); + + yycolumn = column_no; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param in_str A readable stream. + * @param yyscanner The scanner object. + * @see yy_switch_to_buffer + */ +void yyset_in (FILE * in_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyin = in_str ; +} + +void yyset_out (FILE * out_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyout = out_str ; +} + +int yyget_debug (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yy_flex_debug; +} + +void yyset_debug (int bdebug , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yy_flex_debug = bdebug ; +} + +/* Accessor methods for yylval and yylloc */ + +YYSTYPE * yyget_lval (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yylval; +} + +void yyset_lval (YYSTYPE * yylval_param , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yylval = yylval_param; +} + +YYLTYPE *yyget_lloc (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yylloc; +} + +void yyset_lloc (YYLTYPE * yylloc_param , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yylloc = yylloc_param; +} + +/* User-visible API */ + +/* yylex_init is special because it creates the scanner itself, so it is + * the ONLY reentrant function that doesn't take the scanner as the last argument. + * That's why we explicitly handle the declaration, instead of using our macros. + */ + +int yylex_init(yyscan_t* ptr_yy_globals) + +{ + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), NULL ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + return yy_init_globals ( *ptr_yy_globals ); +} + +/* yylex_init_extra has the same functionality as yylex_init, but follows the + * convention of taking the scanner as the last argument. Note however, that + * this is a *pointer* to a scanner, as it will be allocated by this call (and + * is the reason, too, why this function also must handle its own declaration). + * The user defined value in the first argument will be available to yyalloc in + * the yyextra field. + */ + +int yylex_init_extra(YY_EXTRA_TYPE yy_user_defined,yyscan_t* ptr_yy_globals ) + +{ + struct yyguts_t dummy_yyguts; + + yyset_extra (yy_user_defined, &dummy_yyguts); + + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), &dummy_yyguts ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in + yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + yyset_extra (yy_user_defined, *ptr_yy_globals); + + return yy_init_globals ( *ptr_yy_globals ); +} + +static int yy_init_globals (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + /* Initialization is the same as for the non-reentrant scanner. + * This function is called from yylex_destroy(), so don't allocate here. + */ + + yyg->yy_buffer_stack = 0; + yyg->yy_buffer_stack_top = 0; + yyg->yy_buffer_stack_max = 0; + yyg->yy_c_buf_p = (char *) 0; + yyg->yy_init = 0; + yyg->yy_start = 0; + + yyg->yy_start_stack_ptr = 0; + yyg->yy_start_stack_depth = 0; + yyg->yy_start_stack = NULL; + +/* Defined in main.c */ +#ifdef YY_STDINIT + yyin = stdin; + yyout = stdout; +#else + yyin = (FILE *) 0; + yyout = (FILE *) 0; +#endif + + /* For future reference: Set errno on error, since we are called by + * yylex_init() + */ + return 0; +} + +/* yylex_destroy is for both reentrant and non-reentrant scanners. */ +int yylex_destroy (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + yy_delete_buffer(YY_CURRENT_BUFFER ,yyscanner ); + YY_CURRENT_BUFFER_LVALUE = NULL; + yypop_buffer_state(yyscanner); + } + + /* Destroy the stack itself. */ + yyfree(yyg->yy_buffer_stack ,yyscanner); + yyg->yy_buffer_stack = NULL; + + /* Destroy the start condition stack. */ + yyfree(yyg->yy_start_stack ,yyscanner ); + yyg->yy_start_stack = NULL; + + /* Reset the globals. This is important in a non-reentrant scanner so the next time + * yylex() is called, initialization will occur. */ + yy_init_globals( yyscanner); + + /* Destroy the main struct (reentrant only). */ + yyfree ( yyscanner , yyscanner ); + yyscanner = NULL; + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, yyconst char * s2, int n , yyscan_t yyscanner) +{ + register int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * s , yyscan_t yyscanner) +{ + register int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *yyalloc (yy_size_t size , yyscan_t yyscanner) +{ + return (void *) malloc( size ); +} + +void *yyrealloc (void * ptr, yy_size_t size , yyscan_t yyscanner) +{ + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return (void *) realloc( (char *) ptr, size ); +} + +void yyfree (void * ptr , yyscan_t yyscanner) +{ + free( (char *) ptr ); /* see yyrealloc() for (char *) cast */ +} + +#define YYTABLES_NAME "yytables" + +#line 78 "predicate/BPredicate.l" + + + diff --git a/generated/flex_BPredicate.h b/generated/flex_BPredicate.h new file mode 100644 index 000000000..3adb2320b --- /dev/null +++ b/generated/flex_BPredicate.h @@ -0,0 +1,345 @@ +#ifndef yyHEADER_H +#define yyHEADER_H 1 +#define yyIN_HEADER 1 + +#line 6 "generated//flex_BPredicate.h" + +#line 8 "generated//flex_BPredicate.h" + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 5 +#define YY_FLEX_SUBMINOR_VERSION 35 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; +#endif /* ! C99 */ + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#endif /* ! FLEXINT_H */ + +#ifdef __cplusplus + +/* The "const" storage-class-modifier is valid. */ +#define YY_USE_CONST + +#else /* ! __cplusplus */ + +/* C99 requires __STDC__ to be defined as 1. */ +#if defined (__STDC__) + +#define YY_USE_CONST + +#endif /* defined (__STDC__) */ +#endif /* ! __cplusplus */ + +#ifdef YY_USE_CONST +#define yyconst const +#else +#define yyconst +#endif + +/* An opaque pointer. */ +#ifndef YY_TYPEDEF_YY_SCANNER_T +#define YY_TYPEDEF_YY_SCANNER_T +typedef void* yyscan_t; +#endif + +/* For convenience, these vars (plus the bison vars far below) + are macros in the reentrant scanner. */ +#define yyin yyg->yyin_r +#define yyout yyg->yyout_r +#define yyextra yyg->yyextra_r +#define yyleng yyg->yyleng_r +#define yytext yyg->yytext_r +#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno) +#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) +#define yy_flex_debug yyg->yy_flex_debug_r + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#define YY_BUF_SIZE 16384 +#endif + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + yy_size_t yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +void yyrestart (FILE *input_file ,yyscan_t yyscanner ); +void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner ); +YY_BUFFER_STATE yy_create_buffer (FILE *file,int size ,yyscan_t yyscanner ); +void yy_delete_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner ); +void yy_flush_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner ); +void yypush_buffer_state (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner ); +void yypop_buffer_state (yyscan_t yyscanner ); + +YY_BUFFER_STATE yy_scan_buffer (char *base,yy_size_t size ,yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_string (yyconst char *yy_str ,yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_bytes (yyconst char *bytes,int len ,yyscan_t yyscanner ); + +void *yyalloc (yy_size_t ,yyscan_t yyscanner ); +void *yyrealloc (void *,yy_size_t ,yyscan_t yyscanner ); +void yyfree (void * ,yyscan_t yyscanner ); + +/* Begin user sect3 */ + +#define yywrap(n) 1 +#define YY_SKIP_YYWRAP + +#define yytext_ptr yytext_r + +#ifdef YY_HEADER_EXPORT_START_CONDITIONS +#define INITIAL 0 + +#endif + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +int yylex_init (yyscan_t* scanner); + +int yylex_init_extra (YY_EXTRA_TYPE user_defined,yyscan_t* scanner); + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int yylex_destroy (yyscan_t yyscanner ); + +int yyget_debug (yyscan_t yyscanner ); + +void yyset_debug (int debug_flag ,yyscan_t yyscanner ); + +YY_EXTRA_TYPE yyget_extra (yyscan_t yyscanner ); + +void yyset_extra (YY_EXTRA_TYPE user_defined ,yyscan_t yyscanner ); + +FILE *yyget_in (yyscan_t yyscanner ); + +void yyset_in (FILE * in_str ,yyscan_t yyscanner ); + +FILE *yyget_out (yyscan_t yyscanner ); + +void yyset_out (FILE * out_str ,yyscan_t yyscanner ); + +int yyget_leng (yyscan_t yyscanner ); + +char *yyget_text (yyscan_t yyscanner ); + +int yyget_lineno (yyscan_t yyscanner ); + +void yyset_lineno (int line_number ,yyscan_t yyscanner ); + +YYSTYPE * yyget_lval (yyscan_t yyscanner ); + +void yyset_lval (YYSTYPE * yylval_param ,yyscan_t yyscanner ); + + YYLTYPE *yyget_lloc (yyscan_t yyscanner ); + + void yyset_lloc (YYLTYPE * yylloc_param ,yyscan_t yyscanner ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap (yyscan_t yyscanner ); +#else +extern int yywrap (yyscan_t yyscanner ); +#endif +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy (char *,yyconst char *,int ,yyscan_t yyscanner); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * ,yyscan_t yyscanner); +#endif + +#ifndef YY_NO_INPUT + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#define YY_READ_BUF_SIZE 8192 +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int yylex \ + (YYSTYPE * yylval_param,YYLTYPE * yylloc_param ,yyscan_t yyscanner); + +#define YY_DECL int yylex \ + (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner) +#endif /* !YY_DECL */ + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + +#undef YY_NEW_FILE +#undef YY_FLUSH_BUFFER +#undef yy_set_bol +#undef yy_new_buffer +#undef yy_set_interactive +#undef YY_DO_BEFORE_ACTION + +#ifdef YY_DECL_IS_OURS +#undef YY_DECL_IS_OURS +#undef YY_DECL +#endif + +#line 78 "predicate/BPredicate.l" + + +#line 344 "generated//flex_BPredicate.h" +#undef yyIN_HEADER +#endif /* yyHEADER_H */ diff --git a/lime/HOWTO b/lime/HOWTO new file mode 100644 index 000000000..f447c84fa --- /dev/null +++ b/lime/HOWTO @@ -0,0 +1,70 @@ +Lime: An LALR(1) parser generator in and for PHP. + +Interpretter pattern got you down? Time to use a real parser? Welcome to Lime. + +If you're familiar with BISON or YACC, you may want to read the metagrammar. +It's written in the Lime input language, so you'll get a head-start on +understanding how to use Lime. + +0. If you're not running Linux on an IA32 box, then you will have to rebuild + lime_scan_tokens for your system. It should be enough to erase it, + and then type "CFLAGS=-O2 make lime_scan_tokens" at the bash prompt. + +1. Stare at the file lime/metagrammar to understand the syntax. You're seeing + slightly modified and tweaked Backus-Naur forms. The main differences + are that you get to name your components, instead of refering to them + by numbers the way that BISON demands. This idea was stolen from the + C-based "Lemon" parser from which Lime derives its name. Incidentally, + the author of Lemon disclaimed copyright, so you get a copy of the C + code that taught me LALR(1) parsing better than any book, despite the + obvious difficulties in understanding it. Oh, and one other thing: + symbols are terminal if the scanner feeds them to the parser. They + are non-terminal if they appear on the left side of a production rule. + Lime names semantic categories using strings instead of the numbers + that BISON-based parsers use, so you don't have to declare any list of + terminal symbols anywhere. + +2. Look at the file lime/lime.php to see what pragmas are defined. To be more + specific, you might look at the method lime::pragma(), which at the + time of this writing, supports "%left", "%right", "%nonassoc", + "%start", and "%class". The first three are for operator precedence. + The last two declare the start symbol and the name of a PHP class to + generate which will hold all the bottom-up parsing tables. + +3. Write a grammar file. + +4. php /path/to/lime/lime.php list-of-grammar-files > my_parser.php + +5. Read the function parse_lime_grammar() in lime.php to understand + how to integrate your parser into your program. + +6. Integrate your parser as follows: + +--------------- CUT --------------- + +include_once "lime/parse_engine.php"; +include_once "my_parser.php"; +# +# Later: +# +$parser = new parse_engine(new my_parser()); +# +# And still later: +# +try { + while (..something..) { + $parser->eat($type, $val); + # You figure out how to get the parameters. + } + # And after the last token has been eaten: + $parser->eat_eof(); +} catch (parse_error $e) { + die($e->getMessage()); +} +return $parser->semantic; + +--------------- CUT --------------- + +7. You now have the computed semantic value of whatever you parsed. Add salt + and pepper to taste, and serve. + diff --git a/lime/flex_token_stream.php b/lime/flex_token_stream.php new file mode 100644 index 000000000..c21e411fb --- /dev/null +++ b/lime/flex_token_stream.php @@ -0,0 +1,34 @@ +executable(); + $tokens = explode("\0", `$scanner < "\$PHP_LIME_SCAN_STDIN"`); + array_pop($tokens); + $this->tokens = $tokens; + $this->lineno = 1; + } + function next() { + if (list($key, $token) = each($this->tokens)) { + list($this->lineno, $type, $text) = explode("\1", $token); + return array($type, $text); + } + } + function feed($parser) { + while (list($type, $text) = $this->next()) { + $parser->eat($type, $text); + } + return $parser->eat_eof(); + } +} diff --git a/lime/lemon.c b/lime/lemon.c new file mode 100644 index 000000000..708b3538d --- /dev/null +++ b/lime/lemon.c @@ -0,0 +1,4588 @@ +/* +** This file contains all sources (including headers) to the LEMON +** LALR(1) parser generator. The sources have been combined into a +** single file to make it easy to include LEMON in the source tree +** and Makefile of another program. +** +** The author of this program disclaims copyright. +*/ +#include +#include +#include +#include +#include + +#ifndef __WIN32__ +# if defined(_WIN32) || defined(WIN32) +# define __WIN32__ +# endif +#endif + +/* #define PRIVATE static */ +#define PRIVATE + +#ifdef TEST +#define MAXRHS 5 /* Set low to exercise exception code */ +#else +#define MAXRHS 1000 +#endif + +char *msort(); +extern void *malloc(); + +/******** From the file "action.h" *************************************/ +struct action *Action_new(); +struct action *Action_sort(); + +/********* From the file "assert.h" ************************************/ +void myassert(); +#ifndef NDEBUG +# define assert(X) if(!(X))myassert(__FILE__,__LINE__) +#else +# define assert(X) +#endif + +/********** From the file "build.h" ************************************/ +void FindRulePrecedences(); +void FindFirstSets(); +void FindStates(); +void FindLinks(); +void FindFollowSets(); +void FindActions(); + +/********* From the file "configlist.h" *********************************/ +void Configlist_init(/* void */); +struct config *Configlist_add(/* struct rule *, int */); +struct config *Configlist_addbasis(/* struct rule *, int */); +void Configlist_closure(/* void */); +void Configlist_sort(/* void */); +void Configlist_sortbasis(/* void */); +struct config *Configlist_return(/* void */); +struct config *Configlist_basis(/* void */); +void Configlist_eat(/* struct config * */); +void Configlist_reset(/* void */); + +/********* From the file "error.h" ***************************************/ +void ErrorMsg(const char *, int,const char *, ...); + +/****** From the file "option.h" ******************************************/ +struct s_options { + enum { OPT_FLAG=1, OPT_INT, OPT_DBL, OPT_STR, + OPT_FFLAG, OPT_FINT, OPT_FDBL, OPT_FSTR} type; + char *label; + char *arg; + char *message; +}; +int OptInit(/* char**,struct s_options*,FILE* */); +int OptNArgs(/* void */); +char *OptArg(/* int */); +void OptErr(/* int */); +void OptPrint(/* void */); + +/******** From the file "parse.h" *****************************************/ +void Parse(/* struct lemon *lemp */); + +/********* From the file "plink.h" ***************************************/ +struct plink *Plink_new(/* void */); +void Plink_add(/* struct plink **, struct config * */); +void Plink_copy(/* struct plink **, struct plink * */); +void Plink_delete(/* struct plink * */); + +/********** From the file "report.h" *************************************/ +void Reprint(/* struct lemon * */); +void ReportOutput(/* struct lemon * */); +void ReportTable(/* struct lemon * */); +void ReportHeader(/* struct lemon * */); +void CompressTables(/* struct lemon * */); + +/********** From the file "set.h" ****************************************/ +void SetSize(/* int N */); /* All sets will be of size N */ +char *SetNew(/* void */); /* A new set for element 0..N */ +void SetFree(/* char* */); /* Deallocate a set */ + +int SetAdd(/* char*,int */); /* Add element to a set */ +int SetUnion(/* char *A,char *B */); /* A <- A U B, thru element N */ + +#define SetFind(X,Y) (X[Y]) /* True if Y is in set X */ + +/********** From the file "struct.h" *************************************/ +/* +** Principal data structures for the LEMON parser generator. +*/ + +typedef enum {B_FALSE=0, B_TRUE} Boolean; + +/* Symbols (terminals and nonterminals) of the grammar are stored +** in the following: */ +struct symbol { + char *name; /* Name of the symbol */ + int index; /* Index number for this symbol */ + enum { + TERMINAL, + NONTERMINAL + } type; /* Symbols are all either TERMINALS or NTs */ + struct rule *rule; /* Linked list of rules of this (if an NT) */ + struct symbol *fallback; /* fallback token in case this token doesn't parse */ + int prec; /* Precedence if defined (-1 otherwise) */ + enum e_assoc { + LEFT, + RIGHT, + NONE, + UNK + } assoc; /* Associativity if predecence is defined */ + char *firstset; /* First-set for all rules of this symbol */ + Boolean lambda; /* True if NT and can generate an empty string */ + char *destructor; /* Code which executes whenever this symbol is + ** popped from the stack during error processing */ + int destructorln; /* Line number of destructor code */ + char *datatype; /* The data type of information held by this + ** object. Only used if type==NONTERMINAL */ + int dtnum; /* The data type number. In the parser, the value + ** stack is a union. The .yy%d element of this + ** union is the correct data type for this object */ +}; + +/* Each production rule in the grammar is stored in the following +** structure. */ +struct rule { + struct symbol *lhs; /* Left-hand side of the rule */ + char *lhsalias; /* Alias for the LHS (NULL if none) */ + int ruleline; /* Line number for the rule */ + int nrhs; /* Number of RHS symbols */ + struct symbol **rhs; /* The RHS symbols */ + char **rhsalias; /* An alias for each RHS symbol (NULL if none) */ + int line; /* Line number at which code begins */ + char *code; /* The code executed when this rule is reduced */ + struct symbol *precsym; /* Precedence symbol for this rule */ + int index; /* An index number for this rule */ + Boolean canReduce; /* True if this rule is ever reduced */ + struct rule *nextlhs; /* Next rule with the same LHS */ + struct rule *next; /* Next rule in the global list */ +}; + +/* A configuration is a production rule of the grammar together with +** a mark (dot) showing how much of that rule has been processed so far. +** Configurations also contain a follow-set which is a list of terminal +** symbols which are allowed to immediately follow the end of the rule. +** Every configuration is recorded as an instance of the following: */ +struct config { + struct rule *rp; /* The rule upon which the configuration is based */ + int dot; /* The parse point */ + char *fws; /* Follow-set for this configuration only */ + struct plink *fplp; /* Follow-set forward propagation links */ + struct plink *bplp; /* Follow-set backwards propagation links */ + struct state *stp; /* Pointer to state which contains this */ + enum { + COMPLETE, /* The status is used during followset and */ + INCOMPLETE /* shift computations */ + } status; + struct config *next; /* Next configuration in the state */ + struct config *bp; /* The next basis configuration */ +}; + +/* Every shift or reduce operation is stored as one of the following */ +struct action { + struct symbol *sp; /* The look-ahead symbol */ + enum e_action { + SHIFT, + ACCEPT, + REDUCE, + ERROR, + CONFLICT, /* Was a reduce, but part of a conflict */ + SH_RESOLVED, /* Was a shift. Precedence resolved conflict */ + RD_RESOLVED, /* Was reduce. Precedence resolved conflict */ + NOT_USED /* Deleted by compression */ + } type; + union { + struct state *stp; /* The new state, if a shift */ + struct rule *rp; /* The rule, if a reduce */ + } x; + struct action *next; /* Next action for this state */ + struct action *collide; /* Next action with the same hash */ +}; + +/* Each state of the generated parser's finite state machine +** is encoded as an instance of the following structure. */ +struct state { + struct config *bp; /* The basis configurations for this state */ + struct config *cfp; /* All configurations in this set */ + int index; /* Sequencial number for this state */ + struct action *ap; /* Array of actions for this state */ + int nTknAct, nNtAct; /* Number of actions on terminals and nonterminals */ + int iTknOfst, iNtOfst; /* yy_action[] offset for terminals and nonterms */ + int iDflt; /* Default action */ +}; +#define NO_OFFSET (-2147483647) + +/* A followset propagation link indicates that the contents of one +** configuration followset should be propagated to another whenever +** the first changes. */ +struct plink { + struct config *cfp; /* The configuration to which linked */ + struct plink *next; /* The next propagate link */ +}; + +/* The state vector for the entire parser generator is recorded as +** follows. (LEMON uses no global variables and makes little use of +** static variables. Fields in the following structure can be thought +** of as begin global variables in the program.) */ +struct lemon { + struct state **sorted; /* Table of states sorted by state number */ + struct rule *rule; /* List of all rules */ + int nstate; /* Number of states */ + int nrule; /* Number of rules */ + int nsymbol; /* Number of terminal and nonterminal symbols */ + int nterminal; /* Number of terminal symbols */ + struct symbol **symbols; /* Sorted array of pointers to symbols */ + int errorcnt; /* Number of errors */ + struct symbol *errsym; /* The error symbol */ + char *name; /* Name of the generated parser */ + char *arg; /* Declaration of the 3th argument to parser */ + char *tokentype; /* Type of terminal symbols in the parser stack */ + char *vartype; /* The default type of non-terminal symbols */ + char *start; /* Name of the start symbol for the grammar */ + char *stacksize; /* Size of the parser stack */ + char *include; /* Code to put at the start of the C file */ + int includeln; /* Line number for start of include code */ + char *error; /* Code to execute when an error is seen */ + int errorln; /* Line number for start of error code */ + char *overflow; /* Code to execute on a stack overflow */ + int overflowln; /* Line number for start of overflow code */ + char *failure; /* Code to execute on parser failure */ + int failureln; /* Line number for start of failure code */ + char *accept; /* Code to execute when the parser excepts */ + int acceptln; /* Line number for the start of accept code */ + char *extracode; /* Code appended to the generated file */ + int extracodeln; /* Line number for the start of the extra code */ + char *tokendest; /* Code to execute to destroy token data */ + int tokendestln; /* Line number for token destroyer code */ + char *vardest; /* Code for the default non-terminal destructor */ + int vardestln; /* Line number for default non-term destructor code*/ + char *filename; /* Name of the input file */ + char *outname; /* Name of the current output file */ + char *tokenprefix; /* A prefix added to token names in the .h file */ + int nconflict; /* Number of parsing conflicts */ + int tablesize; /* Size of the parse tables */ + int basisflag; /* Print only basis configurations */ + int has_fallback; /* True if any %fallback is seen in the grammer */ + char *argv0; /* Name of the program */ +}; + +#define MemoryCheck(X) if((X)==0){ \ + extern void memory_error(); \ + memory_error(); \ +} + +/**************** From the file "table.h" *********************************/ +/* +** All code in this file has been automatically generated +** from a specification in the file +** "table.q" +** by the associative array code building program "aagen". +** Do not edit this file! Instead, edit the specification +** file, then rerun aagen. +*/ +/* +** Code for processing tables in the LEMON parser generator. +*/ + +/* Routines for handling a strings */ + +char *Strsafe(); + +void Strsafe_init(/* void */); +int Strsafe_insert(/* char * */); +char *Strsafe_find(/* char * */); + +/* Routines for handling symbols of the grammar */ + +struct symbol *Symbol_new(); +int Symbolcmpp(/* struct symbol **, struct symbol ** */); +void Symbol_init(/* void */); +int Symbol_insert(/* struct symbol *, char * */); +struct symbol *Symbol_find(/* char * */); +struct symbol *Symbol_Nth(/* int */); +int Symbol_count(/* */); +struct symbol **Symbol_arrayof(/* */); + +/* Routines to manage the state table */ + +int Configcmp(/* struct config *, struct config * */); +struct state *State_new(); +void State_init(/* void */); +int State_insert(/* struct state *, struct config * */); +struct state *State_find(/* struct config * */); +struct state **State_arrayof(/* */); + +/* Routines used for efficiency in Configlist_add */ + +void Configtable_init(/* void */); +int Configtable_insert(/* struct config * */); +struct config *Configtable_find(/* struct config * */); +void Configtable_clear(/* int(*)(struct config *) */); +/****************** From the file "action.c" *******************************/ +/* +** Routines processing parser actions in the LEMON parser generator. +*/ + +/* Allocate a new parser action */ +struct action *Action_new(){ + static struct action *freelist = 0; + struct action *new; + + if( freelist==0 ){ + int i; + int amt = 100; + freelist = (struct action *)malloc( sizeof(struct action)*amt ); + if( freelist==0 ){ + fprintf(stderr,"Unable to allocate memory for a new parser action."); + exit(1); + } + for(i=0; inext; + return new; +} + +/* Compare two actions */ +static int actioncmp(ap1,ap2) +struct action *ap1; +struct action *ap2; +{ + int rc; + rc = ap1->sp->index - ap2->sp->index; + if( rc==0 ) rc = (int)ap1->type - (int)ap2->type; + if( rc==0 ){ + assert( ap1->type==REDUCE || ap1->type==RD_RESOLVED || ap1->type==CONFLICT); + assert( ap2->type==REDUCE || ap2->type==RD_RESOLVED || ap2->type==CONFLICT); + rc = ap1->x.rp->index - ap2->x.rp->index; + } + return rc; +} + +/* Sort parser actions */ +struct action *Action_sort(ap) +struct action *ap; +{ + ap = (struct action *)msort((char *)ap,(char **)&ap->next,actioncmp); + return ap; +} + +void Action_add(app,type,sp,arg) +struct action **app; +enum e_action type; +struct symbol *sp; +char *arg; +{ + struct action *new; + new = Action_new(); + new->next = *app; + *app = new; + new->type = type; + new->sp = sp; + if( type==SHIFT ){ + new->x.stp = (struct state *)arg; + }else{ + new->x.rp = (struct rule *)arg; + } +} +/********************** New code to implement the "acttab" module ***********/ +/* +** This module implements routines use to construct the yy_action[] table. +*/ + +/* +** The state of the yy_action table under construction is an instance of +** the following structure +*/ +typedef struct acttab acttab; +struct acttab { + int nAction; /* Number of used slots in aAction[] */ + int nActionAlloc; /* Slots allocated for aAction[] */ + struct { + int lookahead; /* Value of the lookahead token */ + int action; /* Action to take on the given lookahead */ + } *aAction, /* The yy_action[] table under construction */ + *aLookahead; /* A single new transaction set */ + int mnLookahead; /* Minimum aLookahead[].lookahead */ + int mnAction; /* Action associated with mnLookahead */ + int mxLookahead; /* Maximum aLookahead[].lookahead */ + int nLookahead; /* Used slots in aLookahead[] */ + int nLookaheadAlloc; /* Slots allocated in aLookahead[] */ +}; + +/* Return the number of entries in the yy_action table */ +#define acttab_size(X) ((X)->nAction) + +/* The value for the N-th entry in yy_action */ +#define acttab_yyaction(X,N) ((X)->aAction[N].action) + +/* The value for the N-th entry in yy_lookahead */ +#define acttab_yylookahead(X,N) ((X)->aAction[N].lookahead) + +/* Free all memory associated with the given acttab */ +void acttab_free(acttab *p){ + free( p->aAction ); + free( p->aLookahead ); + free( p ); +} + +/* Allocate a new acttab structure */ +acttab *acttab_alloc(void){ + acttab *p = malloc( sizeof(*p) ); + if( p==0 ){ + fprintf(stderr,"Unable to allocate memory for a new acttab."); + exit(1); + } + memset(p, 0, sizeof(*p)); + return p; +} + +/* Add a new action to the current transaction set +*/ +void acttab_action(acttab *p, int lookahead, int action){ + if( p->nLookahead>=p->nLookaheadAlloc ){ + p->nLookaheadAlloc += 25; + p->aLookahead = realloc( p->aLookahead, + sizeof(p->aLookahead[0])*p->nLookaheadAlloc ); + if( p->aLookahead==0 ){ + fprintf(stderr,"malloc failed\n"); + exit(1); + } + } + if( p->nLookahead==0 ){ + p->mxLookahead = lookahead; + p->mnLookahead = lookahead; + p->mnAction = action; + }else{ + if( p->mxLookaheadmxLookahead = lookahead; + if( p->mnLookahead>lookahead ){ + p->mnLookahead = lookahead; + p->mnAction = action; + } + } + p->aLookahead[p->nLookahead].lookahead = lookahead; + p->aLookahead[p->nLookahead].action = action; + p->nLookahead++; +} + +/* +** Add the transaction set built up with prior calls to acttab_action() +** into the current action table. Then reset the transaction set back +** to an empty set in preparation for a new round of acttab_action() calls. +** +** Return the offset into the action table of the new transaction. +*/ +int acttab_insert(acttab *p){ + int i, j, k, n; + assert( p->nLookahead>0 ); + + /* Make sure we have enough space to hold the expanded action table + ** in the worst case. The worst case occurs if the transaction set + ** must be appended to the current action table + */ + n = p->mxLookahead + 1; + if( p->nAction + n >= p->nActionAlloc ){ + int oldAlloc = p->nActionAlloc; + p->nActionAlloc = p->nAction + n + p->nActionAlloc + 20; + p->aAction = realloc( p->aAction, + sizeof(p->aAction[0])*p->nActionAlloc); + if( p->aAction==0 ){ + fprintf(stderr,"malloc failed\n"); + exit(1); + } + for(i=oldAlloc; inActionAlloc; i++){ + p->aAction[i].lookahead = -1; + p->aAction[i].action = -1; + } + } + + /* Scan the existing action table looking for an offset where we can + ** insert the current transaction set. Fall out of the loop when that + ** offset is found. In the worst case, we fall out of the loop when + ** i reaches p->nAction, which means we append the new transaction set. + ** + ** i is the index in p->aAction[] where p->mnLookahead is inserted. + */ + for(i=0; inAction+p->mnLookahead; i++){ + if( p->aAction[i].lookahead<0 ){ + for(j=0; jnLookahead; j++){ + k = p->aLookahead[j].lookahead - p->mnLookahead + i; + if( k<0 ) break; + if( p->aAction[k].lookahead>=0 ) break; + } + if( jnLookahead ) continue; + for(j=0; jnAction; j++){ + if( p->aAction[j].lookahead==j+p->mnLookahead-i ) break; + } + if( j==p->nAction ){ + break; /* Fits in empty slots */ + } + }else if( p->aAction[i].lookahead==p->mnLookahead ){ + if( p->aAction[i].action!=p->mnAction ) continue; + for(j=0; jnLookahead; j++){ + k = p->aLookahead[j].lookahead - p->mnLookahead + i; + if( k<0 || k>=p->nAction ) break; + if( p->aLookahead[j].lookahead!=p->aAction[k].lookahead ) break; + if( p->aLookahead[j].action!=p->aAction[k].action ) break; + } + if( jnLookahead ) continue; + n = 0; + for(j=0; jnAction; j++){ + if( p->aAction[j].lookahead<0 ) continue; + if( p->aAction[j].lookahead==j+p->mnLookahead-i ) n++; + } + if( n==p->nLookahead ){ + break; /* Same as a prior transaction set */ + } + } + } + /* Insert transaction set at index i. */ + for(j=0; jnLookahead; j++){ + k = p->aLookahead[j].lookahead - p->mnLookahead + i; + p->aAction[k] = p->aLookahead[j]; + if( k>=p->nAction ) p->nAction = k+1; + } + p->nLookahead = 0; + + /* Return the offset that is added to the lookahead in order to get the + ** index into yy_action of the action */ + return i - p->mnLookahead; +} + +/********************** From the file "assert.c" ****************************/ +/* +** A more efficient way of handling assertions. +*/ +void myassert(file,line) +char *file; +int line; +{ + fprintf(stderr,"Assertion failed on line %d of file \"%s\"\n",line,file); + exit(1); +} +/********************** From the file "build.c" *****************************/ +/* +** Routines to construction the finite state machine for the LEMON +** parser generator. +*/ + +/* Find a precedence symbol of every rule in the grammar. +** +** Those rules which have a precedence symbol coded in the input +** grammar using the "[symbol]" construct will already have the +** rp->precsym field filled. Other rules take as their precedence +** symbol the first RHS symbol with a defined precedence. If there +** are not RHS symbols with a defined precedence, the precedence +** symbol field is left blank. +*/ +void FindRulePrecedences(xp) +struct lemon *xp; +{ + struct rule *rp; + for(rp=xp->rule; rp; rp=rp->next){ + if( rp->precsym==0 ){ + int i; + for(i=0; inrhs; i++){ + if( rp->rhs[i]->prec>=0 ){ + rp->precsym = rp->rhs[i]; + break; + } + } + } + } + return; +} + +/* Find all nonterminals which will generate the empty string. +** Then go back and compute the first sets of every nonterminal. +** The first set is the set of all terminal symbols which can begin +** a string generated by that nonterminal. +*/ +void FindFirstSets(lemp) +struct lemon *lemp; +{ + int i; + struct rule *rp; + int progress; + + for(i=0; insymbol; i++){ + lemp->symbols[i]->lambda = B_FALSE; + } + for(i=lemp->nterminal; insymbol; i++){ + lemp->symbols[i]->firstset = SetNew(); + } + + /* First compute all lambdas */ + do{ + progress = 0; + for(rp=lemp->rule; rp; rp=rp->next){ + if( rp->lhs->lambda ) continue; + for(i=0; inrhs; i++){ + if( rp->rhs[i]->lambda==B_FALSE ) break; + } + if( i==rp->nrhs ){ + rp->lhs->lambda = B_TRUE; + progress = 1; + } + } + }while( progress ); + + /* Now compute all first sets */ + do{ + struct symbol *s1, *s2; + progress = 0; + for(rp=lemp->rule; rp; rp=rp->next){ + s1 = rp->lhs; + for(i=0; inrhs; i++){ + s2 = rp->rhs[i]; + if( s2->type==TERMINAL ){ + progress += SetAdd(s1->firstset,s2->index); + break; + }else if( s1==s2 ){ + if( s1->lambda==B_FALSE ) break; + }else{ + progress += SetUnion(s1->firstset,s2->firstset); + if( s2->lambda==B_FALSE ) break; + } + } + } + }while( progress ); + return; +} + +/* Compute all LR(0) states for the grammar. Links +** are added to between some states so that the LR(1) follow sets +** can be computed later. +*/ +PRIVATE struct state *getstate(/* struct lemon * */); /* forward reference */ +void FindStates(lemp) +struct lemon *lemp; +{ + struct symbol *sp; + struct rule *rp; + + Configlist_init(); + + /* Find the start symbol */ + if( lemp->start ){ + sp = Symbol_find(lemp->start); + if( sp==0 ){ + ErrorMsg(lemp->filename,0, +"The specified start symbol \"%s\" is not \ +in a nonterminal of the grammar. \"%s\" will be used as the start \ +symbol instead.",lemp->start,lemp->rule->lhs->name); + lemp->errorcnt++; + sp = lemp->rule->lhs; + } + }else{ + sp = lemp->rule->lhs; + } + + /* Make sure the start symbol doesn't occur on the right-hand side of + ** any rule. Report an error if it does. (YACC would generate a new + ** start symbol in this case.) */ + for(rp=lemp->rule; rp; rp=rp->next){ + int i; + for(i=0; inrhs; i++){ + if( rp->rhs[i]==sp ){ + ErrorMsg(lemp->filename,0, +"The start symbol \"%s\" occurs on the \ +right-hand side of a rule. This will result in a parser which \ +does not work properly.",sp->name); + lemp->errorcnt++; + } + } + } + + /* The basis configuration set for the first state + ** is all rules which have the start symbol as their + ** left-hand side */ + for(rp=sp->rule; rp; rp=rp->nextlhs){ + struct config *newcfp; + newcfp = Configlist_addbasis(rp,0); + SetAdd(newcfp->fws,0); + } + + /* Compute the first state. All other states will be + ** computed automatically during the computation of the first one. + ** The returned pointer to the first state is not used. */ + (void)getstate(lemp); + return; +} + +/* Return a pointer to a state which is described by the configuration +** list which has been built from calls to Configlist_add. +*/ +PRIVATE void buildshifts(/* struct lemon *, struct state * */); /* Forwd ref */ +PRIVATE struct state *getstate(lemp) +struct lemon *lemp; +{ + struct config *cfp, *bp; + struct state *stp; + + /* Extract the sorted basis of the new state. The basis was constructed + ** by prior calls to "Configlist_addbasis()". */ + Configlist_sortbasis(); + bp = Configlist_basis(); + + /* Get a state with the same basis */ + stp = State_find(bp); + if( stp ){ + /* A state with the same basis already exists! Copy all the follow-set + ** propagation links from the state under construction into the + ** preexisting state, then return a pointer to the preexisting state */ + struct config *x, *y; + for(x=bp, y=stp->bp; x && y; x=x->bp, y=y->bp){ + Plink_copy(&y->bplp,x->bplp); + Plink_delete(x->fplp); + x->fplp = x->bplp = 0; + } + cfp = Configlist_return(); + Configlist_eat(cfp); + }else{ + /* This really is a new state. Construct all the details */ + Configlist_closure(lemp); /* Compute the configuration closure */ + Configlist_sort(); /* Sort the configuration closure */ + cfp = Configlist_return(); /* Get a pointer to the config list */ + stp = State_new(); /* A new state structure */ + MemoryCheck(stp); + stp->bp = bp; /* Remember the configuration basis */ + stp->cfp = cfp; /* Remember the configuration closure */ + stp->index = lemp->nstate++; /* Every state gets a sequence number */ + stp->ap = 0; /* No actions, yet. */ + State_insert(stp,stp->bp); /* Add to the state table */ + buildshifts(lemp,stp); /* Recursively compute successor states */ + } + return stp; +} + +/* Construct all successor states to the given state. A "successor" +** state is any state which can be reached by a shift action. +*/ +PRIVATE void buildshifts(lemp,stp) +struct lemon *lemp; +struct state *stp; /* The state from which successors are computed */ +{ + struct config *cfp; /* For looping thru the config closure of "stp" */ + struct config *bcfp; /* For the inner loop on config closure of "stp" */ + struct config *new; /* */ + struct symbol *sp; /* Symbol following the dot in configuration "cfp" */ + struct symbol *bsp; /* Symbol following the dot in configuration "bcfp" */ + struct state *newstp; /* A pointer to a successor state */ + + /* Each configuration becomes complete after it contibutes to a successor + ** state. Initially, all configurations are incomplete */ + for(cfp=stp->cfp; cfp; cfp=cfp->next) cfp->status = INCOMPLETE; + + /* Loop through all configurations of the state "stp" */ + for(cfp=stp->cfp; cfp; cfp=cfp->next){ + if( cfp->status==COMPLETE ) continue; /* Already used by inner loop */ + if( cfp->dot>=cfp->rp->nrhs ) continue; /* Can't shift this config */ + Configlist_reset(); /* Reset the new config set */ + sp = cfp->rp->rhs[cfp->dot]; /* Symbol after the dot */ + + /* For every configuration in the state "stp" which has the symbol "sp" + ** following its dot, add the same configuration to the basis set under + ** construction but with the dot shifted one symbol to the right. */ + for(bcfp=cfp; bcfp; bcfp=bcfp->next){ + if( bcfp->status==COMPLETE ) continue; /* Already used */ + if( bcfp->dot>=bcfp->rp->nrhs ) continue; /* Can't shift this one */ + bsp = bcfp->rp->rhs[bcfp->dot]; /* Get symbol after dot */ + if( bsp!=sp ) continue; /* Must be same as for "cfp" */ + bcfp->status = COMPLETE; /* Mark this config as used */ + new = Configlist_addbasis(bcfp->rp,bcfp->dot+1); + Plink_add(&new->bplp,bcfp); + } + + /* Get a pointer to the state described by the basis configuration set + ** constructed in the preceding loop */ + newstp = getstate(lemp); + + /* The state "newstp" is reached from the state "stp" by a shift action + ** on the symbol "sp" */ + Action_add(&stp->ap,SHIFT,sp,(char *)newstp); + } +} + +/* +** Construct the propagation links +*/ +void FindLinks(lemp) +struct lemon *lemp; +{ + int i; + struct config *cfp, *other; + struct state *stp; + struct plink *plp; + + /* Housekeeping detail: + ** Add to every propagate link a pointer back to the state to + ** which the link is attached. */ + for(i=0; instate; i++){ + stp = lemp->sorted[i]; + for(cfp=stp->cfp; cfp; cfp=cfp->next){ + cfp->stp = stp; + } + } + + /* Convert all backlinks into forward links. Only the forward + ** links are used in the follow-set computation. */ + for(i=0; instate; i++){ + stp = lemp->sorted[i]; + for(cfp=stp->cfp; cfp; cfp=cfp->next){ + for(plp=cfp->bplp; plp; plp=plp->next){ + other = plp->cfp; + Plink_add(&other->fplp,cfp); + } + } + } +} + +/* Compute all followsets. +** +** A followset is the set of all symbols which can come immediately +** after a configuration. +*/ +void FindFollowSets(lemp) +struct lemon *lemp; +{ + int i; + struct config *cfp; + struct plink *plp; + int progress; + int change; + + for(i=0; instate; i++){ + for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){ + cfp->status = INCOMPLETE; + } + } + + do{ + progress = 0; + for(i=0; instate; i++){ + for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){ + if( cfp->status==COMPLETE ) continue; + for(plp=cfp->fplp; plp; plp=plp->next){ + change = SetUnion(plp->cfp->fws,cfp->fws); + if( change ){ + plp->cfp->status = INCOMPLETE; + progress = 1; + } + } + cfp->status = COMPLETE; + } + } + }while( progress ); +} + +static int resolve_conflict(); + +/* Compute the reduce actions, and resolve conflicts. +*/ +void FindActions(lemp) +struct lemon *lemp; +{ + int i,j; + struct config *cfp; + struct state *stp; + struct symbol *sp; + struct rule *rp; + + /* Add all of the reduce actions + ** A reduce action is added for each element of the followset of + ** a configuration which has its dot at the extreme right. + */ + for(i=0; instate; i++){ /* Loop over all states */ + stp = lemp->sorted[i]; + for(cfp=stp->cfp; cfp; cfp=cfp->next){ /* Loop over all configurations */ + if( cfp->rp->nrhs==cfp->dot ){ /* Is dot at extreme right? */ + for(j=0; jnterminal; j++){ + if( SetFind(cfp->fws,j) ){ + /* Add a reduce action to the state "stp" which will reduce by the + ** rule "cfp->rp" if the lookahead symbol is "lemp->symbols[j]" */ + Action_add(&stp->ap,REDUCE,lemp->symbols[j],(char *)cfp->rp); + } + } + } + } + } + + /* Add the accepting token */ + if( lemp->start ){ + sp = Symbol_find(lemp->start); + if( sp==0 ) sp = lemp->rule->lhs; + }else{ + sp = lemp->rule->lhs; + } + /* Add to the first state (which is always the starting state of the + ** finite state machine) an action to ACCEPT if the lookahead is the + ** start nonterminal. */ + Action_add(&lemp->sorted[0]->ap,ACCEPT,sp,0); + + /* Resolve conflicts */ + for(i=0; instate; i++){ + struct action *ap, *nap; + struct state *stp; + stp = lemp->sorted[i]; + assert( stp->ap ); + stp->ap = Action_sort(stp->ap); + for(ap=stp->ap; ap && ap->next; ap=ap->next){ + for(nap=ap->next; nap && nap->sp==ap->sp; nap=nap->next){ + /* The two actions "ap" and "nap" have the same lookahead. + ** Figure out which one should be used */ + lemp->nconflict += resolve_conflict(ap,nap,lemp->errsym); + } + } + } + + /* Report an error for each rule that can never be reduced. */ + for(rp=lemp->rule; rp; rp=rp->next) rp->canReduce = B_FALSE; + for(i=0; instate; i++){ + struct action *ap; + for(ap=lemp->sorted[i]->ap; ap; ap=ap->next){ + if( ap->type==REDUCE ) ap->x.rp->canReduce = B_TRUE; + } + } + for(rp=lemp->rule; rp; rp=rp->next){ + if( rp->canReduce ) continue; + ErrorMsg(lemp->filename,rp->ruleline,"This rule can not be reduced.\n"); + lemp->errorcnt++; + } +} + +/* Resolve a conflict between the two given actions. If the +** conflict can't be resolve, return non-zero. +** +** NO LONGER TRUE: +** To resolve a conflict, first look to see if either action +** is on an error rule. In that case, take the action which +** is not associated with the error rule. If neither or both +** actions are associated with an error rule, then try to +** use precedence to resolve the conflict. +** +** If either action is a SHIFT, then it must be apx. This +** function won't work if apx->type==REDUCE and apy->type==SHIFT. +*/ +static int resolve_conflict(apx,apy,errsym) +struct action *apx; +struct action *apy; +struct symbol *errsym; /* The error symbol (if defined. NULL otherwise) */ +{ + struct symbol *spx, *spy; + int errcnt = 0; + assert( apx->sp==apy->sp ); /* Otherwise there would be no conflict */ + if( apx->type==SHIFT && apy->type==REDUCE ){ + spx = apx->sp; + spy = apy->x.rp->precsym; + if( spy==0 || spx->prec<0 || spy->prec<0 ){ + /* Not enough precedence information. */ + apy->type = CONFLICT; + errcnt++; + }else if( spx->prec>spy->prec ){ /* Lower precedence wins */ + apy->type = RD_RESOLVED; + }else if( spx->precprec ){ + apx->type = SH_RESOLVED; + }else if( spx->prec==spy->prec && spx->assoc==RIGHT ){ /* Use operator */ + apy->type = RD_RESOLVED; /* associativity */ + }else if( spx->prec==spy->prec && spx->assoc==LEFT ){ /* to break tie */ + apx->type = SH_RESOLVED; + }else{ + assert( spx->prec==spy->prec && spx->assoc==NONE ); + apy->type = CONFLICT; + errcnt++; + } + }else if( apx->type==REDUCE && apy->type==REDUCE ){ + spx = apx->x.rp->precsym; + spy = apy->x.rp->precsym; + if( spx==0 || spy==0 || spx->prec<0 || + spy->prec<0 || spx->prec==spy->prec ){ + apy->type = CONFLICT; + errcnt++; + }else if( spx->prec>spy->prec ){ + apy->type = RD_RESOLVED; + }else if( spx->precprec ){ + apx->type = RD_RESOLVED; + } + }else{ + assert( + apx->type==SH_RESOLVED || + apx->type==RD_RESOLVED || + apx->type==CONFLICT || + apy->type==SH_RESOLVED || + apy->type==RD_RESOLVED || + apy->type==CONFLICT + ); + /* The REDUCE/SHIFT case cannot happen because SHIFTs come before + ** REDUCEs on the list. If we reach this point it must be because + ** the parser conflict had already been resolved. */ + } + return errcnt; +} +/********************* From the file "configlist.c" *************************/ +/* +** Routines to processing a configuration list and building a state +** in the LEMON parser generator. +*/ + +static struct config *freelist = 0; /* List of free configurations */ +static struct config *current = 0; /* Top of list of configurations */ +static struct config **currentend = 0; /* Last on list of configs */ +static struct config *basis = 0; /* Top of list of basis configs */ +static struct config **basisend = 0; /* End of list of basis configs */ + +/* Return a pointer to a new configuration */ +PRIVATE struct config *newconfig(){ + struct config *new; + if( freelist==0 ){ + int i; + int amt = 3; + freelist = (struct config *)malloc( sizeof(struct config)*amt ); + if( freelist==0 ){ + fprintf(stderr,"Unable to allocate memory for a new configuration."); + exit(1); + } + for(i=0; inext; + return new; +} + +/* The configuration "old" is no longer used */ +PRIVATE void deleteconfig(old) +struct config *old; +{ + old->next = freelist; + freelist = old; +} + +/* Initialized the configuration list builder */ +void Configlist_init(){ + current = 0; + currentend = ¤t; + basis = 0; + basisend = &basis; + Configtable_init(); + return; +} + +/* Initialized the configuration list builder */ +void Configlist_reset(){ + current = 0; + currentend = ¤t; + basis = 0; + basisend = &basis; + Configtable_clear(0); + return; +} + +/* Add another configuration to the configuration list */ +struct config *Configlist_add(rp,dot) +struct rule *rp; /* The rule */ +int dot; /* Index into the RHS of the rule where the dot goes */ +{ + struct config *cfp, model; + + assert( currentend!=0 ); + model.rp = rp; + model.dot = dot; + cfp = Configtable_find(&model); + if( cfp==0 ){ + cfp = newconfig(); + cfp->rp = rp; + cfp->dot = dot; + cfp->fws = SetNew(); + cfp->stp = 0; + cfp->fplp = cfp->bplp = 0; + cfp->next = 0; + cfp->bp = 0; + *currentend = cfp; + currentend = &cfp->next; + Configtable_insert(cfp); + } + return cfp; +} + +/* Add a basis configuration to the configuration list */ +struct config *Configlist_addbasis(rp,dot) +struct rule *rp; +int dot; +{ + struct config *cfp, model; + + assert( basisend!=0 ); + assert( currentend!=0 ); + model.rp = rp; + model.dot = dot; + cfp = Configtable_find(&model); + if( cfp==0 ){ + cfp = newconfig(); + cfp->rp = rp; + cfp->dot = dot; + cfp->fws = SetNew(); + cfp->stp = 0; + cfp->fplp = cfp->bplp = 0; + cfp->next = 0; + cfp->bp = 0; + *currentend = cfp; + currentend = &cfp->next; + *basisend = cfp; + basisend = &cfp->bp; + Configtable_insert(cfp); + } + return cfp; +} + +/* Compute the closure of the configuration list */ +void Configlist_closure(lemp) +struct lemon *lemp; +{ + struct config *cfp, *newcfp; + struct rule *rp, *newrp; + struct symbol *sp, *xsp; + int i, dot; + + assert( currentend!=0 ); + for(cfp=current; cfp; cfp=cfp->next){ + rp = cfp->rp; + dot = cfp->dot; + if( dot>=rp->nrhs ) continue; + sp = rp->rhs[dot]; + if( sp->type==NONTERMINAL ){ + if( sp->rule==0 && sp!=lemp->errsym ){ + ErrorMsg(lemp->filename,rp->line,"Nonterminal \"%s\" has no rules.", + sp->name); + lemp->errorcnt++; + } + for(newrp=sp->rule; newrp; newrp=newrp->nextlhs){ + newcfp = Configlist_add(newrp,0); + for(i=dot+1; inrhs; i++){ + xsp = rp->rhs[i]; + if( xsp->type==TERMINAL ){ + SetAdd(newcfp->fws,xsp->index); + break; + }else{ + SetUnion(newcfp->fws,xsp->firstset); + if( xsp->lambda==B_FALSE ) break; + } + } + if( i==rp->nrhs ) Plink_add(&cfp->fplp,newcfp); + } + } + } + return; +} + +/* Sort the configuration list */ +void Configlist_sort(){ + current = (struct config *)msort((char *)current,(char **)&(current->next),Configcmp); + currentend = 0; + return; +} + +/* Sort the basis configuration list */ +void Configlist_sortbasis(){ + basis = (struct config *)msort((char *)current,(char **)&(current->bp),Configcmp); + basisend = 0; + return; +} + +/* Return a pointer to the head of the configuration list and +** reset the list */ +struct config *Configlist_return(){ + struct config *old; + old = current; + current = 0; + currentend = 0; + return old; +} + +/* Return a pointer to the head of the configuration list and +** reset the list */ +struct config *Configlist_basis(){ + struct config *old; + old = basis; + basis = 0; + basisend = 0; + return old; +} + +/* Free all elements of the given configuration list */ +void Configlist_eat(cfp) +struct config *cfp; +{ + struct config *nextcfp; + for(; cfp; cfp=nextcfp){ + nextcfp = cfp->next; + assert( cfp->fplp==0 ); + assert( cfp->bplp==0 ); + if( cfp->fws ) SetFree(cfp->fws); + deleteconfig(cfp); + } + return; +} +/***************** From the file "error.c" *********************************/ +/* +** Code for printing error message. +*/ + +/* Find a good place to break "msg" so that its length is at least "min" +** but no more than "max". Make the point as close to max as possible. +*/ +static int findbreak(msg,min,max) +char *msg; +int min; +int max; +{ + int i,spot; + char c; + for(i=spot=min; i<=max; i++){ + c = msg[i]; + if( c=='\t' ) msg[i] = ' '; + if( c=='\n' ){ msg[i] = ' '; spot = i; break; } + if( c==0 ){ spot = i; break; } + if( c=='-' && i0 ){ + sprintf(prefix,"%.*s:%d: ",PREFIXLIMIT-10,filename,lineno); + }else{ + sprintf(prefix,"%.*s: ",PREFIXLIMIT-10,filename); + } + prefixsize = strlen(prefix); + availablewidth = LINEWIDTH - prefixsize; + + /* Generate the error message */ + vsprintf(errmsg,format,ap); + va_end(ap); + errmsgsize = strlen(errmsg); + /* Remove trailing '\n's from the error message. */ + while( errmsgsize>0 && errmsg[errmsgsize-1]=='\n' ){ + errmsg[--errmsgsize] = 0; + } + + /* Print the error message */ + base = 0; + while( errmsg[base]!=0 ){ + end = restart = findbreak(&errmsg[base],0,availablewidth); + restart += base; + while( errmsg[restart]==' ' ) restart++; + fprintf(stdout,"%s%.*s\n",prefix,end,&errmsg[base]); + base = restart; + } +} +/**************** From the file "main.c" ************************************/ +/* +** Main program file for the LEMON parser generator. +*/ + +/* Report an out-of-memory condition and abort. This function +** is used mostly by the "MemoryCheck" macro in struct.h +*/ +void memory_error(){ + fprintf(stderr,"Out of memory. Aborting...\n"); + exit(1); +} + +static int nDefine = 0; /* Number of -D options on the command line */ +static char **azDefine = 0; /* Name of the -D macros */ + +/* This routine is called with the argument to each -D command-line option. +** Add the macro defined to the azDefine array. +*/ +static void handle_D_option(char *z){ + char **paz; + nDefine++; + azDefine = realloc(azDefine, sizeof(azDefine[0])*nDefine); + if( azDefine==0 ){ + fprintf(stderr,"out of memory\n"); + exit(1); + } + paz = &azDefine[nDefine-1]; + *paz = malloc( strlen(z)+1 ); + if( *paz==0 ){ + fprintf(stderr,"out of memory\n"); + exit(1); + } + strcpy(*paz, z); + for(z=*paz; *z && *z!='='; z++){} + *z = 0; +} + + +/* The main program. Parse the command line and do it... */ +int main(argc,argv) +int argc; +char **argv; +{ + static int version = 0; + static int rpflag = 0; + static int basisflag = 0; + static int compress = 0; + static int quiet = 0; + static int statistics = 0; + static int mhflag = 0; + static struct s_options options[] = { + {OPT_FLAG, "b", (char*)&basisflag, "Print only the basis in report."}, + {OPT_FLAG, "c", (char*)&compress, "Don't compress the action table."}, + {OPT_FSTR, "D", (char*)handle_D_option, "Define an %ifdef macro."}, + {OPT_FLAG, "g", (char*)&rpflag, "Print grammar without actions."}, + {OPT_FLAG, "m", (char*)&mhflag, "Output a makeheaders compatible file"}, + {OPT_FLAG, "q", (char*)&quiet, "(Quiet) Don't print the report file."}, + {OPT_FLAG, "s", (char*)&statistics, + "Print parser stats to standard output."}, + {OPT_FLAG, "x", (char*)&version, "Print the version number."}, + {OPT_FLAG,0,0,0} + }; + int i; + struct lemon lem; + + OptInit(argv,options,stderr); + if( version ){ + printf("Lemon version 1.0\n"); + exit(0); + } + if( OptNArgs()!=1 ){ + fprintf(stderr,"Exactly one filename argument is required.\n"); + exit(1); + } + lem.errorcnt = 0; + + /* Initialize the machine */ + Strsafe_init(); + Symbol_init(); + State_init(); + lem.argv0 = argv[0]; + lem.filename = OptArg(0); + lem.basisflag = basisflag; + lem.has_fallback = 0; + lem.nconflict = 0; + lem.name = lem.include = lem.arg = lem.tokentype = lem.start = 0; + lem.vartype = 0; + lem.stacksize = 0; + lem.error = lem.overflow = lem.failure = lem.accept = lem.tokendest = + lem.tokenprefix = lem.outname = lem.extracode = 0; + lem.vardest = 0; + lem.tablesize = 0; + Symbol_new("$"); + lem.errsym = Symbol_new("error"); + + /* Parse the input file */ + Parse(&lem); + if( lem.errorcnt ) exit(lem.errorcnt); + if( lem.rule==0 ){ + fprintf(stderr,"Empty grammar.\n"); + exit(1); + } + + /* Count and index the symbols of the grammar */ + lem.nsymbol = Symbol_count(); + Symbol_new("{default}"); + lem.symbols = Symbol_arrayof(); + for(i=0; i<=lem.nsymbol; i++) lem.symbols[i]->index = i; + qsort(lem.symbols,lem.nsymbol+1,sizeof(struct symbol*), + (int(*)())Symbolcmpp); + for(i=0; i<=lem.nsymbol; i++) lem.symbols[i]->index = i; + for(i=1; isupper(lem.symbols[i]->name[0]); i++); + lem.nterminal = i; + + /* Generate a reprint of the grammar, if requested on the command line */ + if( rpflag ){ + Reprint(&lem); + }else{ + /* Initialize the size for all follow and first sets */ + SetSize(lem.nterminal); + + /* Find the precedence for every production rule (that has one) */ + FindRulePrecedences(&lem); + + /* Compute the lambda-nonterminals and the first-sets for every + ** nonterminal */ + FindFirstSets(&lem); + + /* Compute all LR(0) states. Also record follow-set propagation + ** links so that the follow-set can be computed later */ + lem.nstate = 0; + FindStates(&lem); + lem.sorted = State_arrayof(); + + /* Tie up loose ends on the propagation links */ + FindLinks(&lem); + + /* Compute the follow set of every reducible configuration */ + FindFollowSets(&lem); + + /* Compute the action tables */ + FindActions(&lem); + + /* Compress the action tables */ + if( compress==0 ) CompressTables(&lem); + + /* Generate a report of the parser generated. (the "y.output" file) */ + if( !quiet ) ReportOutput(&lem); + + /* Generate the source code for the parser */ + ReportTable(&lem, mhflag); + + /* Produce a header file for use by the scanner. (This step is + ** omitted if the "-m" option is used because makeheaders will + ** generate the file for us.) */ + if( !mhflag ) ReportHeader(&lem); + } + if( statistics ){ + printf("Parser statistics: %d terminals, %d nonterminals, %d rules\n", + lem.nterminal, lem.nsymbol - lem.nterminal, lem.nrule); + printf(" %d states, %d parser table entries, %d conflicts\n", + lem.nstate, lem.tablesize, lem.nconflict); + } + if( lem.nconflict ){ + fprintf(stderr,"%d parsing conflicts.\n",lem.nconflict); + } + exit(lem.errorcnt + lem.nconflict); + return (lem.errorcnt + lem.nconflict); +} +/******************** From the file "msort.c" *******************************/ +/* +** A generic merge-sort program. +** +** USAGE: +** Let "ptr" be a pointer to some structure which is at the head of +** a null-terminated list. Then to sort the list call: +** +** ptr = msort(ptr,&(ptr->next),cmpfnc); +** +** In the above, "cmpfnc" is a pointer to a function which compares +** two instances of the structure and returns an integer, as in +** strcmp. The second argument is a pointer to the pointer to the +** second element of the linked list. This address is used to compute +** the offset to the "next" field within the structure. The offset to +** the "next" field must be constant for all structures in the list. +** +** The function returns a new pointer which is the head of the list +** after sorting. +** +** ALGORITHM: +** Merge-sort. +*/ + +/* +** Return a pointer to the next structure in the linked list. +*/ +#define NEXT(A) (*(char**)(((unsigned long)A)+offset)) + +/* +** Inputs: +** a: A sorted, null-terminated linked list. (May be null). +** b: A sorted, null-terminated linked list. (May be null). +** cmp: A pointer to the comparison function. +** offset: Offset in the structure to the "next" field. +** +** Return Value: +** A pointer to the head of a sorted list containing the elements +** of both a and b. +** +** Side effects: +** The "next" pointers for elements in the lists a and b are +** changed. +*/ +static char *merge(a,b,cmp,offset) +char *a; +char *b; +int (*cmp)(); +int offset; +{ + char *ptr, *head; + + if( a==0 ){ + head = b; + }else if( b==0 ){ + head = a; + }else{ + if( (*cmp)(a,b)<0 ){ + ptr = a; + a = NEXT(a); + }else{ + ptr = b; + b = NEXT(b); + } + head = ptr; + while( a && b ){ + if( (*cmp)(a,b)<0 ){ + NEXT(ptr) = a; + ptr = a; + a = NEXT(a); + }else{ + NEXT(ptr) = b; + ptr = b; + b = NEXT(b); + } + } + if( a ) NEXT(ptr) = a; + else NEXT(ptr) = b; + } + return head; +} + +/* +** Inputs: +** list: Pointer to a singly-linked list of structures. +** next: Pointer to pointer to the second element of the list. +** cmp: A comparison function. +** +** Return Value: +** A pointer to the head of a sorted list containing the elements +** orginally in list. +** +** Side effects: +** The "next" pointers for elements in list are changed. +*/ +#define LISTSIZE 30 +char *msort(list,next,cmp) +char *list; +char **next; +int (*cmp)(); +{ + unsigned long offset; + char *ep; + char *set[LISTSIZE]; + int i; + offset = (unsigned long)next - (unsigned long)list; + for(i=0; istate = WAITING_FOR_DECL_KEYWORD; + }else if( islower(x[0]) ){ + psp->lhs = Symbol_new(x); + psp->nrhs = 0; + psp->lhsalias = 0; + psp->state = WAITING_FOR_ARROW; + }else if( x[0]=='{' ){ + if( psp->prevrule==0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, +"There is not prior rule opon which to attach the code \ +fragment which begins on this line."); + psp->errorcnt++; + }else if( psp->prevrule->code!=0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, +"Code fragment beginning on this line is not the first \ +to follow the previous rule."); + psp->errorcnt++; + }else{ + psp->prevrule->line = psp->tokenlineno; + psp->prevrule->code = &x[1]; + } + }else if( x[0]=='[' ){ + psp->state = PRECEDENCE_MARK_1; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Token \"%s\" should be either \"%%\" or a nonterminal name.", + x); + psp->errorcnt++; + } + break; + case PRECEDENCE_MARK_1: + if( !isupper(x[0]) ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "The precedence symbol must be a terminal."); + psp->errorcnt++; + }else if( psp->prevrule==0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "There is no prior rule to assign precedence \"[%s]\".",x); + psp->errorcnt++; + }else if( psp->prevrule->precsym!=0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, +"Precedence mark on this line is not the first \ +to follow the previous rule."); + psp->errorcnt++; + }else{ + psp->prevrule->precsym = Symbol_new(x); + } + psp->state = PRECEDENCE_MARK_2; + break; + case PRECEDENCE_MARK_2: + if( x[0]!=']' ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Missing \"]\" on precedence mark."); + psp->errorcnt++; + } + psp->state = WAITING_FOR_DECL_OR_RULE; + break; + case WAITING_FOR_ARROW: + if( x[0]==':' && x[1]==':' && x[2]=='=' ){ + psp->state = IN_RHS; + }else if( x[0]=='(' ){ + psp->state = LHS_ALIAS_1; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Expected to see a \":\" following the LHS symbol \"%s\".", + psp->lhs->name); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case LHS_ALIAS_1: + if( isalpha(x[0]) ){ + psp->lhsalias = x; + psp->state = LHS_ALIAS_2; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "\"%s\" is not a valid alias for the LHS \"%s\"\n", + x,psp->lhs->name); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case LHS_ALIAS_2: + if( x[0]==')' ){ + psp->state = LHS_ALIAS_3; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Missing \")\" following LHS alias name \"%s\".",psp->lhsalias); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case LHS_ALIAS_3: + if( x[0]==':' && x[1]==':' && x[2]=='=' ){ + psp->state = IN_RHS; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Missing \"->\" following: \"%s(%s)\".", + psp->lhs->name,psp->lhsalias); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case IN_RHS: + if( x[0]=='.' ){ + struct rule *rp; + rp = (struct rule *)malloc( sizeof(struct rule) + + sizeof(struct symbol*)*psp->nrhs + sizeof(char*)*psp->nrhs ); + if( rp==0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Can't allocate enough memory for this rule."); + psp->errorcnt++; + psp->prevrule = 0; + }else{ + int i; + rp->ruleline = psp->tokenlineno; + rp->rhs = (struct symbol**)&rp[1]; + rp->rhsalias = (char**)&(rp->rhs[psp->nrhs]); + for(i=0; inrhs; i++){ + rp->rhs[i] = psp->rhs[i]; + rp->rhsalias[i] = psp->alias[i]; + } + rp->lhs = psp->lhs; + rp->lhsalias = psp->lhsalias; + rp->nrhs = psp->nrhs; + rp->code = 0; + rp->precsym = 0; + rp->index = psp->gp->nrule++; + rp->nextlhs = rp->lhs->rule; + rp->lhs->rule = rp; + rp->next = 0; + if( psp->firstrule==0 ){ + psp->firstrule = psp->lastrule = rp; + }else{ + psp->lastrule->next = rp; + psp->lastrule = rp; + } + psp->prevrule = rp; + } + psp->state = WAITING_FOR_DECL_OR_RULE; + }else if( isalpha(x[0]) ){ + if( psp->nrhs>=MAXRHS ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Too many symbol on RHS or rule beginning at \"%s\".", + x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + }else{ + psp->rhs[psp->nrhs] = Symbol_new(x); + psp->alias[psp->nrhs] = 0; + psp->nrhs++; + } + }else if( x[0]=='(' && psp->nrhs>0 ){ + psp->state = RHS_ALIAS_1; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Illegal character on RHS of rule: \"%s\".",x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case RHS_ALIAS_1: + if( isalpha(x[0]) ){ + psp->alias[psp->nrhs-1] = x; + psp->state = RHS_ALIAS_2; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "\"%s\" is not a valid alias for the RHS symbol \"%s\"\n", + x,psp->rhs[psp->nrhs-1]->name); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case RHS_ALIAS_2: + if( x[0]==')' ){ + psp->state = IN_RHS; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Missing \")\" following LHS alias name \"%s\".",psp->lhsalias); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case WAITING_FOR_DECL_KEYWORD: + if( isalpha(x[0]) ){ + psp->declkeyword = x; + psp->declargslot = 0; + psp->decllnslot = 0; + psp->state = WAITING_FOR_DECL_ARG; + if( strcmp(x,"name")==0 ){ + psp->declargslot = &(psp->gp->name); + }else if( strcmp(x,"include")==0 ){ + psp->declargslot = &(psp->gp->include); + psp->decllnslot = &psp->gp->includeln; + }else if( strcmp(x,"code")==0 ){ + psp->declargslot = &(psp->gp->extracode); + psp->decllnslot = &psp->gp->extracodeln; + }else if( strcmp(x,"token_destructor")==0 ){ + psp->declargslot = &psp->gp->tokendest; + psp->decllnslot = &psp->gp->tokendestln; + }else if( strcmp(x,"default_destructor")==0 ){ + psp->declargslot = &psp->gp->vardest; + psp->decllnslot = &psp->gp->vardestln; + }else if( strcmp(x,"token_prefix")==0 ){ + psp->declargslot = &psp->gp->tokenprefix; + }else if( strcmp(x,"syntax_error")==0 ){ + psp->declargslot = &(psp->gp->error); + psp->decllnslot = &psp->gp->errorln; + }else if( strcmp(x,"parse_accept")==0 ){ + psp->declargslot = &(psp->gp->accept); + psp->decllnslot = &psp->gp->acceptln; + }else if( strcmp(x,"parse_failure")==0 ){ + psp->declargslot = &(psp->gp->failure); + psp->decllnslot = &psp->gp->failureln; + }else if( strcmp(x,"stack_overflow")==0 ){ + psp->declargslot = &(psp->gp->overflow); + psp->decllnslot = &psp->gp->overflowln; + }else if( strcmp(x,"extra_argument")==0 ){ + psp->declargslot = &(psp->gp->arg); + }else if( strcmp(x,"token_type")==0 ){ + psp->declargslot = &(psp->gp->tokentype); + }else if( strcmp(x,"default_type")==0 ){ + psp->declargslot = &(psp->gp->vartype); + }else if( strcmp(x,"stack_size")==0 ){ + psp->declargslot = &(psp->gp->stacksize); + }else if( strcmp(x,"start_symbol")==0 ){ + psp->declargslot = &(psp->gp->start); + }else if( strcmp(x,"left")==0 ){ + psp->preccounter++; + psp->declassoc = LEFT; + psp->state = WAITING_FOR_PRECEDENCE_SYMBOL; + }else if( strcmp(x,"right")==0 ){ + psp->preccounter++; + psp->declassoc = RIGHT; + psp->state = WAITING_FOR_PRECEDENCE_SYMBOL; + }else if( strcmp(x,"nonassoc")==0 ){ + psp->preccounter++; + psp->declassoc = NONE; + psp->state = WAITING_FOR_PRECEDENCE_SYMBOL; + }else if( strcmp(x,"destructor")==0 ){ + psp->state = WAITING_FOR_DESTRUCTOR_SYMBOL; + }else if( strcmp(x,"type")==0 ){ + psp->state = WAITING_FOR_DATATYPE_SYMBOL; + }else if( strcmp(x,"fallback")==0 ){ + psp->fallback = 0; + psp->state = WAITING_FOR_FALLBACK_ID; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Unknown declaration keyword: \"%%%s\".",x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + } + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Illegal declaration keyword: \"%s\".",x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + } + break; + case WAITING_FOR_DESTRUCTOR_SYMBOL: + if( !isalpha(x[0]) ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Symbol name missing after %destructor keyword"); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + }else{ + struct symbol *sp = Symbol_new(x); + psp->declargslot = &sp->destructor; + psp->decllnslot = &sp->destructorln; + psp->state = WAITING_FOR_DECL_ARG; + } + break; + case WAITING_FOR_DATATYPE_SYMBOL: + if( !isalpha(x[0]) ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Symbol name missing after %destructor keyword"); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + }else{ + struct symbol *sp = Symbol_new(x); + psp->declargslot = &sp->datatype; + psp->decllnslot = 0; + psp->state = WAITING_FOR_DECL_ARG; + } + break; + case WAITING_FOR_PRECEDENCE_SYMBOL: + if( x[0]=='.' ){ + psp->state = WAITING_FOR_DECL_OR_RULE; + }else if( isupper(x[0]) ){ + struct symbol *sp; + sp = Symbol_new(x); + if( sp->prec>=0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Symbol \"%s\" has already be given a precedence.",x); + psp->errorcnt++; + }else{ + sp->prec = psp->preccounter; + sp->assoc = psp->declassoc; + } + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Can't assign a precedence to \"%s\".",x); + psp->errorcnt++; + } + break; + case WAITING_FOR_DECL_ARG: + if( (x[0]=='{' || x[0]=='\"' || isalnum(x[0])) ){ + if( *(psp->declargslot)!=0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "The argument \"%s\" to declaration \"%%%s\" is not the first.", + x[0]=='\"' ? &x[1] : x,psp->declkeyword); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + }else{ + *(psp->declargslot) = (x[0]=='\"' || x[0]=='{') ? &x[1] : x; + if( psp->decllnslot ) *psp->decllnslot = psp->tokenlineno; + psp->state = WAITING_FOR_DECL_OR_RULE; + } + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Illegal argument to %%%s: %s",psp->declkeyword,x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + } + break; + case WAITING_FOR_FALLBACK_ID: + if( x[0]=='.' ){ + psp->state = WAITING_FOR_DECL_OR_RULE; + }else if( !isupper(x[0]) ){ + ErrorMsg(psp->filename, psp->tokenlineno, + "%%fallback argument \"%s\" should be a token", x); + psp->errorcnt++; + }else{ + struct symbol *sp = Symbol_new(x); + if( psp->fallback==0 ){ + psp->fallback = sp; + }else if( sp->fallback ){ + ErrorMsg(psp->filename, psp->tokenlineno, + "More than one fallback assigned to token %s", x); + psp->errorcnt++; + }else{ + sp->fallback = psp->fallback; + psp->gp->has_fallback = 1; + } + } + break; + case RESYNC_AFTER_RULE_ERROR: +/* if( x[0]=='.' ) psp->state = WAITING_FOR_DECL_OR_RULE; +** break; */ + case RESYNC_AFTER_DECL_ERROR: + if( x[0]=='.' ) psp->state = WAITING_FOR_DECL_OR_RULE; + if( x[0]=='%' ) psp->state = WAITING_FOR_DECL_KEYWORD; + break; + } +} + +/* Run the proprocessor over the input file text. The global variables +** azDefine[0] through azDefine[nDefine-1] contains the names of all defined +** macros. This routine looks for "%ifdef" and "%ifndef" and "%endif" and +** comments them out. Text in between is also commented out as appropriate. +*/ +static preprocess_input(char *z){ + int i, j, k, n; + int exclude = 0; + int start; + int lineno = 1; + int start_lineno; + for(i=0; z[i]; i++){ + if( z[i]=='\n' ) lineno++; + if( z[i]!='%' || (i>0 && z[i-1]!='\n') ) continue; + if( strncmp(&z[i],"%endif",6)==0 && isspace(z[i+6]) ){ + if( exclude ){ + exclude--; + if( exclude==0 ){ + for(j=start; jfilename; + ps.errorcnt = 0; + ps.state = INITIALIZE; + + /* Begin by reading the input file */ + fp = fopen(ps.filename,"rb"); + if( fp==0 ){ + ErrorMsg(ps.filename,0,"Can't open this file for reading."); + gp->errorcnt++; + return; + } + fseek(fp,0,2); + filesize = ftell(fp); + rewind(fp); + filebuf = (char *)malloc( filesize+1 ); + if( filebuf==0 ){ + ErrorMsg(ps.filename,0,"Can't allocate %d of memory to hold this file.", + filesize+1); + gp->errorcnt++; + return; + } + if( fread(filebuf,1,filesize,fp)!=filesize ){ + ErrorMsg(ps.filename,0,"Can't read in all %d bytes of this file.", + filesize); + free(filebuf); + gp->errorcnt++; + return; + } + fclose(fp); + filebuf[filesize] = 0; + + /* Make an initial pass through the file to handle %ifdef and %ifndef */ + preprocess_input(filebuf); + + /* Now scan the text of the input file */ + lineno = 1; + for(cp=filebuf; (c= *cp)!=0; ){ + if( c=='\n' ) lineno++; /* Keep track of the line number */ + if( isspace(c) ){ cp++; continue; } /* Skip all white space */ + if( c=='/' && cp[1]=='/' ){ /* Skip C++ style comments */ + cp+=2; + while( (c= *cp)!=0 && c!='\n' ) cp++; + continue; + } + if( c=='/' && cp[1]=='*' ){ /* Skip C style comments */ + cp+=2; + while( (c= *cp)!=0 && (c!='/' || cp[-1]!='*') ){ + if( c=='\n' ) lineno++; + cp++; + } + if( c ) cp++; + continue; + } + ps.tokenstart = cp; /* Mark the beginning of the token */ + ps.tokenlineno = lineno; /* Linenumber on which token begins */ + if( c=='\"' ){ /* String literals */ + cp++; + while( (c= *cp)!=0 && c!='\"' ){ + if( c=='\n' ) lineno++; + cp++; + } + if( c==0 ){ + ErrorMsg(ps.filename,startline, +"String starting on this line is not terminated before the end of the file."); + ps.errorcnt++; + nextcp = cp; + }else{ + nextcp = cp+1; + } + }else if( c=='{' ){ /* A block of C code */ + int level; + cp++; + for(level=1; (c= *cp)!=0 && (level>1 || c!='}'); cp++){ + if( c=='\n' ) lineno++; + else if( c=='{' ) level++; + else if( c=='}' ) level--; + else if( c=='/' && cp[1]=='*' ){ /* Skip comments */ + int prevc; + cp = &cp[2]; + prevc = 0; + while( (c= *cp)!=0 && (c!='/' || prevc!='*') ){ + if( c=='\n' ) lineno++; + prevc = c; + cp++; + } + }else if( c=='/' && cp[1]=='/' ){ /* Skip C++ style comments too */ + cp = &cp[2]; + while( (c= *cp)!=0 && c!='\n' ) cp++; + if( c ) lineno++; + }else if( c=='\'' || c=='\"' ){ /* String a character literals */ + int startchar, prevc; + startchar = c; + prevc = 0; + for(cp++; (c= *cp)!=0 && (c!=startchar || prevc=='\\'); cp++){ + if( c=='\n' ) lineno++; + if( prevc=='\\' ) prevc = 0; + else prevc = c; + } + } + } + if( c==0 ){ + ErrorMsg(ps.filename,ps.tokenlineno, +"C code starting on this line is not terminated before the end of the file."); + ps.errorcnt++; + nextcp = cp; + }else{ + nextcp = cp+1; + } + }else if( isalnum(c) ){ /* Identifiers */ + while( (c= *cp)!=0 && (isalnum(c) || c=='_') ) cp++; + nextcp = cp; + }else if( c==':' && cp[1]==':' && cp[2]=='=' ){ /* The operator "::=" */ + cp += 3; + nextcp = cp; + }else{ /* All other (one character) operators */ + cp++; + nextcp = cp; + } + c = *cp; + *cp = 0; /* Null terminate the token */ + parseonetoken(&ps); /* Parse the token */ + *cp = c; /* Restore the buffer */ + cp = nextcp; + } + free(filebuf); /* Release the buffer after parsing */ + gp->rule = ps.firstrule; + gp->errorcnt = ps.errorcnt; +} +/*************************** From the file "plink.c" *********************/ +/* +** Routines processing configuration follow-set propagation links +** in the LEMON parser generator. +*/ +static struct plink *plink_freelist = 0; + +/* Allocate a new plink */ +struct plink *Plink_new(){ + struct plink *new; + + if( plink_freelist==0 ){ + int i; + int amt = 100; + plink_freelist = (struct plink *)malloc( sizeof(struct plink)*amt ); + if( plink_freelist==0 ){ + fprintf(stderr, + "Unable to allocate memory for a new follow-set propagation link.\n"); + exit(1); + } + for(i=0; inext; + return new; +} + +/* Add a plink to a plink list */ +void Plink_add(plpp,cfp) +struct plink **plpp; +struct config *cfp; +{ + struct plink *new; + new = Plink_new(); + new->next = *plpp; + *plpp = new; + new->cfp = cfp; +} + +/* Transfer every plink on the list "from" to the list "to" */ +void Plink_copy(to,from) +struct plink **to; +struct plink *from; +{ + struct plink *nextpl; + while( from ){ + nextpl = from->next; + from->next = *to; + *to = from; + from = nextpl; + } +} + +/* Delete every plink on the list */ +void Plink_delete(plp) +struct plink *plp; +{ + struct plink *nextpl; + + while( plp ){ + nextpl = plp->next; + plp->next = plink_freelist; + plink_freelist = plp; + plp = nextpl; + } +} +/*********************** From the file "report.c" **************************/ +/* +** Procedures for generating reports and tables in the LEMON parser generator. +*/ + +/* Generate a filename with the given suffix. Space to hold the +** name comes from malloc() and must be freed by the calling +** function. +*/ +PRIVATE char *file_makename(lemp,suffix) +struct lemon *lemp; +char *suffix; +{ + char *name; + char *cp; + + name = malloc( strlen(lemp->filename) + strlen(suffix) + 5 ); + if( name==0 ){ + fprintf(stderr,"Can't allocate space for a filename.\n"); + exit(1); + } + strcpy(name,lemp->filename); + cp = strrchr(name,'.'); + if( cp ) *cp = 0; + strcat(name,suffix); + return name; +} + +/* Open a file with a name based on the name of the input file, +** but with a different (specified) suffix, and return a pointer +** to the stream */ +PRIVATE FILE *file_open(lemp,suffix,mode) +struct lemon *lemp; +char *suffix; +char *mode; +{ + FILE *fp; + + if( lemp->outname ) free(lemp->outname); + lemp->outname = file_makename(lemp, suffix); + fp = fopen(lemp->outname,mode); + if( fp==0 && *mode=='w' ){ + fprintf(stderr,"Can't open file \"%s\".\n",lemp->outname); + lemp->errorcnt++; + return 0; + } + return fp; +} + +/* Duplicate the input file without comments and without actions +** on rules */ +void Reprint(lemp) +struct lemon *lemp; +{ + struct rule *rp; + struct symbol *sp; + int i, j, maxlen, len, ncolumns, skip; + printf("// Reprint of input file \"%s\".\n// Symbols:\n",lemp->filename); + maxlen = 10; + for(i=0; insymbol; i++){ + sp = lemp->symbols[i]; + len = strlen(sp->name); + if( len>maxlen ) maxlen = len; + } + ncolumns = 76/(maxlen+5); + if( ncolumns<1 ) ncolumns = 1; + skip = (lemp->nsymbol + ncolumns - 1)/ncolumns; + for(i=0; insymbol; j+=skip){ + sp = lemp->symbols[j]; + assert( sp->index==j ); + printf(" %3d %-*.*s",j,maxlen,maxlen,sp->name); + } + printf("\n"); + } + for(rp=lemp->rule; rp; rp=rp->next){ + printf("%s",rp->lhs->name); +/* if( rp->lhsalias ) printf("(%s)",rp->lhsalias); */ + printf(" ::="); + for(i=0; inrhs; i++){ + printf(" %s",rp->rhs[i]->name); +/* if( rp->rhsalias[i] ) printf("(%s)",rp->rhsalias[i]); */ + } + printf("."); + if( rp->precsym ) printf(" [%s]",rp->precsym->name); +/* if( rp->code ) printf("\n %s",rp->code); */ + printf("\n"); + } +} + +void ConfigPrint(fp,cfp) +FILE *fp; +struct config *cfp; +{ + struct rule *rp; + int i; + rp = cfp->rp; + fprintf(fp,"%s ::=",rp->lhs->name); + for(i=0; i<=rp->nrhs; i++){ + if( i==cfp->dot ) fprintf(fp," *"); + if( i==rp->nrhs ) break; + fprintf(fp," %s",rp->rhs[i]->name); + } +} + +/* #define TEST */ +#ifdef TEST +/* Print a set */ +PRIVATE void SetPrint(out,set,lemp) +FILE *out; +char *set; +struct lemon *lemp; +{ + int i; + char *spacer; + spacer = ""; + fprintf(out,"%12s[",""); + for(i=0; interminal; i++){ + if( SetFind(set,i) ){ + fprintf(out,"%s%s",spacer,lemp->symbols[i]->name); + spacer = " "; + } + } + fprintf(out,"]\n"); +} + +/* Print a plink chain */ +PRIVATE void PlinkPrint(out,plp,tag) +FILE *out; +struct plink *plp; +char *tag; +{ + while( plp ){ + fprintf(out,"%12s%s (state %2d) ","",tag,plp->cfp->stp->index); + ConfigPrint(out,plp->cfp); + fprintf(out,"\n"); + plp = plp->next; + } +} +#endif + +/* Print an action to the given file descriptor. Return FALSE if +** nothing was actually printed. +*/ +int PrintAction(struct action *ap, FILE *fp, int indent){ + int result = 1; + switch( ap->type ){ + case SHIFT: + fprintf(fp,"%*s shift %d",indent,ap->sp->name,ap->x.stp->index); + break; + case REDUCE: + fprintf(fp,"%*s reduce %d",indent,ap->sp->name,ap->x.rp->index); + break; + case ACCEPT: + fprintf(fp,"%*s accept",indent,ap->sp->name); + break; + case ERROR: + fprintf(fp,"%*s error",indent,ap->sp->name); + break; + case CONFLICT: + fprintf(fp,"%*s reduce %-3d ** Parsing conflict **", + indent,ap->sp->name,ap->x.rp->index); + break; + case SH_RESOLVED: + case RD_RESOLVED: + case NOT_USED: + result = 0; + break; + } + return result; +} + +/* Generate the "y.output" log file */ +void ReportOutput(lemp) +struct lemon *lemp; +{ + int i; + struct state *stp; + struct config *cfp; + struct action *ap; + FILE *fp; + + fp = file_open(lemp,".out","wb"); + if( fp==0 ) return; + fprintf(fp," \b"); + for(i=0; instate; i++){ + stp = lemp->sorted[i]; + fprintf(fp,"State %d:\n",stp->index); + if( lemp->basisflag ) cfp=stp->bp; + else cfp=stp->cfp; + while( cfp ){ + char buf[20]; + if( cfp->dot==cfp->rp->nrhs ){ + sprintf(buf,"(%d)",cfp->rp->index); + fprintf(fp," %5s ",buf); + }else{ + fprintf(fp," "); + } + ConfigPrint(fp,cfp); + fprintf(fp,"\n"); +#ifdef TEST + SetPrint(fp,cfp->fws,lemp); + PlinkPrint(fp,cfp->fplp,"To "); + PlinkPrint(fp,cfp->bplp,"From"); +#endif + if( lemp->basisflag ) cfp=cfp->bp; + else cfp=cfp->next; + } + fprintf(fp,"\n"); + for(ap=stp->ap; ap; ap=ap->next){ + if( PrintAction(ap,fp,30) ) fprintf(fp,"\n"); + } + fprintf(fp,"\n"); + } + fclose(fp); + return; +} + +/* Search for the file "name" which is in the same directory as +** the exacutable */ +PRIVATE char *pathsearch(argv0,name,modemask) +char *argv0; +char *name; +int modemask; +{ + char *pathlist; + char *path,*cp; + char c; + extern int access(); + +#ifdef __WIN32__ + cp = strrchr(argv0,'\\'); +#else + cp = strrchr(argv0,'/'); +#endif + if( cp ){ + c = *cp; + *cp = 0; + path = (char *)malloc( strlen(argv0) + strlen(name) + 2 ); + if( path ) sprintf(path,"%s/%s",argv0,name); + *cp = c; + }else{ + extern char *getenv(); + pathlist = getenv("PATH"); + if( pathlist==0 ) pathlist = ".:/bin:/usr/bin"; + path = (char *)malloc( strlen(pathlist)+strlen(name)+2 ); + if( path!=0 ){ + while( *pathlist ){ + cp = strchr(pathlist,':'); + if( cp==0 ) cp = &pathlist[strlen(pathlist)]; + c = *cp; + *cp = 0; + sprintf(path,"%s/%s",pathlist,name); + *cp = c; + if( c==0 ) pathlist = ""; + else pathlist = &cp[1]; + if( access(path,modemask)==0 ) break; + } + } + } + return path; +} + +/* Given an action, compute the integer value for that action +** which is to be put in the action table of the generated machine. +** Return negative if no action should be generated. +*/ +PRIVATE int compute_action(lemp,ap) +struct lemon *lemp; +struct action *ap; +{ + int act; + switch( ap->type ){ + case SHIFT: act = ap->x.stp->index; break; + case REDUCE: act = ap->x.rp->index + lemp->nstate; break; + case ERROR: act = lemp->nstate + lemp->nrule; break; + case ACCEPT: act = lemp->nstate + lemp->nrule + 1; break; + default: act = -1; break; + } + return act; +} + +#define LINESIZE 1000 +/* The next cluster of routines are for reading the template file +** and writing the results to the generated parser */ +/* The first function transfers data from "in" to "out" until +** a line is seen which begins with "%%". The line number is +** tracked. +** +** if name!=0, then any word that begin with "Parse" is changed to +** begin with *name instead. +*/ +PRIVATE void tplt_xfer(name,in,out,lineno) +char *name; +FILE *in; +FILE *out; +int *lineno; +{ + int i, iStart; + char line[LINESIZE]; + while( fgets(line,LINESIZE,in) && (line[0]!='%' || line[1]!='%') ){ + (*lineno)++; + iStart = 0; + if( name ){ + for(i=0; line[i]; i++){ + if( line[i]=='P' && strncmp(&line[i],"Parse",5)==0 + && (i==0 || !isalpha(line[i-1])) + ){ + if( i>iStart ) fprintf(out,"%.*s",i-iStart,&line[iStart]); + fprintf(out,"%s",name); + i += 4; + iStart = i+1; + } + } + } + fprintf(out,"%s",&line[iStart]); + } +} + +/* The next function finds the template file and opens it, returning +** a pointer to the opened file. */ +PRIVATE FILE *tplt_open(lemp) +struct lemon *lemp; +{ + static char templatename[] = "lempar.c"; + char buf[1000]; + FILE *in; + char *tpltname; + char *cp; + + cp = strrchr(lemp->filename,'.'); + if( cp ){ + sprintf(buf,"%.*s.lt",(int)(cp-lemp->filename),lemp->filename); + }else{ + sprintf(buf,"%s.lt",lemp->filename); + } + if( access(buf,004)==0 ){ + tpltname = buf; + }else if( access(templatename,004)==0 ){ + tpltname = templatename; + }else{ + tpltname = pathsearch(lemp->argv0,templatename,0); + } + if( tpltname==0 ){ + fprintf(stderr,"Can't find the parser driver template file \"%s\".\n", + templatename); + lemp->errorcnt++; + return 0; + } + in = fopen(tpltname,"rb"); + if( in==0 ){ + fprintf(stderr,"Can't open the template file \"%s\".\n",templatename); + lemp->errorcnt++; + return 0; + } + return in; +} + +/* Print a #line directive line to the output file. */ +PRIVATE void tplt_linedir(out,lineno,filename) +FILE *out; +int lineno; +char *filename; +{ + fprintf(out,"#line %d \"",lineno); + while( *filename ){ + if( *filename == '\\' ) putc('\\',out); + putc(*filename,out); + filename++; + } + fprintf(out,"\"\n"); +} + +/* Print a string to the file and keep the linenumber up to date */ +PRIVATE void tplt_print(out,lemp,str,strln,lineno) +FILE *out; +struct lemon *lemp; +char *str; +int strln; +int *lineno; +{ + if( str==0 ) return; + tplt_linedir(out,strln,lemp->filename); + (*lineno)++; + while( *str ){ + if( *str=='\n' ) (*lineno)++; + putc(*str,out); + str++; + } + if( str[-1]!='\n' ){ + putc('\n',out); + (*lineno)++; + } + tplt_linedir(out,*lineno+2,lemp->outname); + (*lineno)+=2; + return; +} + +/* +** The following routine emits code for the destructor for the +** symbol sp +*/ +void emit_destructor_code(out,sp,lemp,lineno) +FILE *out; +struct symbol *sp; +struct lemon *lemp; +int *lineno; +{ + char *cp = 0; + + int linecnt = 0; + if( sp->type==TERMINAL ){ + cp = lemp->tokendest; + if( cp==0 ) return; + tplt_linedir(out,lemp->tokendestln,lemp->filename); + fprintf(out,"{"); + }else if( sp->destructor ){ + cp = sp->destructor; + tplt_linedir(out,sp->destructorln,lemp->filename); + fprintf(out,"{"); + }else if( lemp->vardest ){ + cp = lemp->vardest; + if( cp==0 ) return; + tplt_linedir(out,lemp->vardestln,lemp->filename); + fprintf(out,"{"); + }else{ + assert( 0 ); /* Cannot happen */ + } + for(; *cp; cp++){ + if( *cp=='$' && cp[1]=='$' ){ + fprintf(out,"(yypminor->yy%d)",sp->dtnum); + cp++; + continue; + } + if( *cp=='\n' ) linecnt++; + fputc(*cp,out); + } + (*lineno) += 3 + linecnt; + fprintf(out,"}\n"); + tplt_linedir(out,*lineno,lemp->outname); + return; +} + +/* +** Return TRUE (non-zero) if the given symbol has a destructor. +*/ +int has_destructor(sp, lemp) +struct symbol *sp; +struct lemon *lemp; +{ + int ret; + if( sp->type==TERMINAL ){ + ret = lemp->tokendest!=0; + }else{ + ret = lemp->vardest!=0 || sp->destructor!=0; + } + return ret; +} + +/* +** Append text to a dynamically allocated string. If zText is 0 then +** reset the string to be empty again. Always return the complete text +** of the string (which is overwritten with each call). +** +** n bytes of zText are stored. If n==0 then all of zText up to the first +** \000 terminator is stored. zText can contain up to two instances of +** %d. The values of p1 and p2 are written into the first and second +** %d. +** +** If n==-1, then the previous character is overwritten. +*/ +PRIVATE char *append_str(char *zText, int n, int p1, int p2){ + static char *z = 0; + static int alloced = 0; + static int used = 0; + int c; + char zInt[40]; + + if( zText==0 ){ + used = 0; + return z; + } + if( n<=0 ){ + if( n<0 ){ + used += n; + assert( used>=0 ); + } + n = strlen(zText); + } + if( n+sizeof(zInt)*2+used >= alloced ){ + alloced = n + sizeof(zInt)*2 + used + 200; + z = realloc(z, alloced); + } + if( z==0 ) return ""; + while( n-- > 0 ){ + c = *(zText++); + if( c=='%' && zText[0]=='d' ){ + sprintf(zInt, "%d", p1); + p1 = p2; + strcpy(&z[used], zInt); + used += strlen(&z[used]); + zText++; + n--; + }else{ + z[used++] = c; + } + } + z[used] = 0; + return z; +} + +/* +** zCode is a string that is the action associated with a rule. Expand +** the symbols in this string so that the refer to elements of the parser +** stack. +*/ +PRIVATE void translate_code(struct lemon *lemp, struct rule *rp){ + char *cp, *xp; + int i; + char lhsused = 0; /* True if the LHS element has been used */ + char used[MAXRHS]; /* True for each RHS element which is used */ + + for(i=0; inrhs; i++) used[i] = 0; + lhsused = 0; + + append_str(0,0,0,0); + for(cp=rp->code; *cp; cp++){ + if( isalpha(*cp) && (cp==rp->code || (!isalnum(cp[-1]) && cp[-1]!='_')) ){ + char saved; + for(xp= &cp[1]; isalnum(*xp) || *xp=='_'; xp++); + saved = *xp; + *xp = 0; + if( rp->lhsalias && strcmp(cp,rp->lhsalias)==0 ){ + append_str("yygotominor.yy%d",0,rp->lhs->dtnum,0); + cp = xp; + lhsused = 1; + }else{ + for(i=0; inrhs; i++){ + if( rp->rhsalias[i] && strcmp(cp,rp->rhsalias[i])==0 ){ + if( cp!=rp->code && cp[-1]=='@' ){ + /* If the argument is of the form @X then substituted + ** the token number of X, not the value of X */ + append_str("yymsp[%d].major",-1,i-rp->nrhs+1,0); + }else{ + append_str("yymsp[%d].minor.yy%d",0, + i-rp->nrhs+1,rp->rhs[i]->dtnum); + } + cp = xp; + used[i] = 1; + break; + } + } + } + *xp = saved; + } + append_str(cp, 1, 0, 0); + } /* End loop */ + + /* Check to make sure the LHS has been used */ + if( rp->lhsalias && !lhsused ){ + ErrorMsg(lemp->filename,rp->ruleline, + "Label \"%s\" for \"%s(%s)\" is never used.", + rp->lhsalias,rp->lhs->name,rp->lhsalias); + lemp->errorcnt++; + } + + /* Generate destructor code for RHS symbols which are not used in the + ** reduce code */ + for(i=0; inrhs; i++){ + if( rp->rhsalias[i] && !used[i] ){ + ErrorMsg(lemp->filename,rp->ruleline, + "Label %s for \"%s(%s)\" is never used.", + rp->rhsalias[i],rp->rhs[i]->name,rp->rhsalias[i]); + lemp->errorcnt++; + }else if( rp->rhsalias[i]==0 ){ + if( has_destructor(rp->rhs[i],lemp) ){ + append_str(" yy_destructor(%d,&yymsp[%d].minor);\n", 0, + rp->rhs[i]->index,i-rp->nrhs+1); + }else{ + /* No destructor defined for this term */ + } + } + } + cp = append_str(0,0,0,0); + rp->code = Strsafe(cp); +} + +/* +** Generate code which executes when the rule "rp" is reduced. Write +** the code to "out". Make sure lineno stays up-to-date. +*/ +PRIVATE void emit_code(out,rp,lemp,lineno) +FILE *out; +struct rule *rp; +struct lemon *lemp; +int *lineno; +{ + char *cp; + int linecnt = 0; + + /* Generate code to do the reduce action */ + if( rp->code ){ + tplt_linedir(out,rp->line,lemp->filename); + fprintf(out,"{%s",rp->code); + for(cp=rp->code; *cp; cp++){ + if( *cp=='\n' ) linecnt++; + } /* End loop */ + (*lineno) += 3 + linecnt; + fprintf(out,"}\n"); + tplt_linedir(out,*lineno,lemp->outname); + } /* End if( rp->code ) */ + + return; +} + +/* +** Print the definition of the union used for the parser's data stack. +** This union contains fields for every possible data type for tokens +** and nonterminals. In the process of computing and printing this +** union, also set the ".dtnum" field of every terminal and nonterminal +** symbol. +*/ +void print_stack_union(out,lemp,plineno,mhflag) +FILE *out; /* The output stream */ +struct lemon *lemp; /* The main info structure for this parser */ +int *plineno; /* Pointer to the line number */ +int mhflag; /* True if generating makeheaders output */ +{ + int lineno = *plineno; /* The line number of the output */ + char **types; /* A hash table of datatypes */ + int arraysize; /* Size of the "types" array */ + int maxdtlength; /* Maximum length of any ".datatype" field. */ + char *stddt; /* Standardized name for a datatype */ + int i,j; /* Loop counters */ + int hash; /* For hashing the name of a type */ + char *name; /* Name of the parser */ + + /* Allocate and initialize types[] and allocate stddt[] */ + arraysize = lemp->nsymbol * 2; + types = (char**)malloc( arraysize * sizeof(char*) ); + for(i=0; ivartype ){ + maxdtlength = strlen(lemp->vartype); + } + for(i=0; insymbol; i++){ + int len; + struct symbol *sp = lemp->symbols[i]; + if( sp->datatype==0 ) continue; + len = strlen(sp->datatype); + if( len>maxdtlength ) maxdtlength = len; + } + stddt = (char*)malloc( maxdtlength*2 + 1 ); + if( types==0 || stddt==0 ){ + fprintf(stderr,"Out of memory.\n"); + exit(1); + } + + /* Build a hash table of datatypes. The ".dtnum" field of each symbol + ** is filled in with the hash index plus 1. A ".dtnum" value of 0 is + ** used for terminal symbols. If there is no %default_type defined then + ** 0 is also used as the .dtnum value for nonterminals which do not specify + ** a datatype using the %type directive. + */ + for(i=0; insymbol; i++){ + struct symbol *sp = lemp->symbols[i]; + char *cp; + if( sp==lemp->errsym ){ + sp->dtnum = arraysize+1; + continue; + } + if( sp->type!=NONTERMINAL || (sp->datatype==0 && lemp->vartype==0) ){ + sp->dtnum = 0; + continue; + } + cp = sp->datatype; + if( cp==0 ) cp = lemp->vartype; + j = 0; + while( isspace(*cp) ) cp++; + while( *cp ) stddt[j++] = *cp++; + while( j>0 && isspace(stddt[j-1]) ) j--; + stddt[j] = 0; + hash = 0; + for(j=0; stddt[j]; j++){ + hash = hash*53 + stddt[j]; + } + hash = (hash & 0x7fffffff)%arraysize; + while( types[hash] ){ + if( strcmp(types[hash],stddt)==0 ){ + sp->dtnum = hash + 1; + break; + } + hash++; + if( hash>=arraysize ) hash = 0; + } + if( types[hash]==0 ){ + sp->dtnum = hash + 1; + types[hash] = (char*)malloc( strlen(stddt)+1 ); + if( types[hash]==0 ){ + fprintf(stderr,"Out of memory.\n"); + exit(1); + } + strcpy(types[hash],stddt); + } + } + + /* Print out the definition of YYTOKENTYPE and YYMINORTYPE */ + name = lemp->name ? lemp->name : "Parse"; + lineno = *plineno; + if( mhflag ){ fprintf(out,"#if INTERFACE\n"); lineno++; } + fprintf(out,"#define %sTOKENTYPE %s\n",name, + lemp->tokentype?lemp->tokentype:"void*"); lineno++; + if( mhflag ){ fprintf(out,"#endif\n"); lineno++; } + fprintf(out,"typedef union {\n"); lineno++; + fprintf(out," %sTOKENTYPE yy0;\n",name); lineno++; + for(i=0; ierrsym->dtnum); lineno++; + free(stddt); + free(types); + fprintf(out,"} YYMINORTYPE;\n"); lineno++; + *plineno = lineno; +} + +/* +** Return the name of a C datatype able to represent values between +** lwr and upr, inclusive. +*/ +static const char *minimum_size_type(int lwr, int upr){ + if( lwr>=0 ){ + if( upr<=255 ){ + return "unsigned char"; + }else if( upr<65535 ){ + return "unsigned short int"; + }else{ + return "unsigned int"; + } + }else if( lwr>=-127 && upr<=127 ){ + return "signed char"; + }else if( lwr>=-32767 && upr<32767 ){ + return "short"; + }else{ + return "int"; + } +} + +/* +** Each state contains a set of token transaction and a set of +** nonterminal transactions. Each of these sets makes an instance +** of the following structure. An array of these structures is used +** to order the creation of entries in the yy_action[] table. +*/ +struct axset { + struct state *stp; /* A pointer to a state */ + int isTkn; /* True to use tokens. False for non-terminals */ + int nAction; /* Number of actions */ +}; + +/* +** Compare to axset structures for sorting purposes +*/ +static int axset_compare(const void *a, const void *b){ + struct axset *p1 = (struct axset*)a; + struct axset *p2 = (struct axset*)b; + return p2->nAction - p1->nAction; +} + +/* Generate C source code for the parser */ +void ReportTable(lemp, mhflag) +struct lemon *lemp; +int mhflag; /* Output in makeheaders format if true */ +{ + FILE *out, *in; + char line[LINESIZE]; + int lineno; + struct state *stp; + struct action *ap; + struct rule *rp; + struct acttab *pActtab; + int i, j, n; + char *name; + int mnTknOfst, mxTknOfst; + int mnNtOfst, mxNtOfst; + struct axset *ax; + + in = tplt_open(lemp); + if( in==0 ) return; + out = file_open(lemp,".c","wb"); + if( out==0 ){ + fclose(in); + return; + } + lineno = 1; + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate the include code, if any */ + tplt_print(out,lemp,lemp->include,lemp->includeln,&lineno); + if( mhflag ){ + char *name = file_makename(lemp, ".h"); + fprintf(out,"#include \"%s\"\n", name); lineno++; + free(name); + } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate #defines for all tokens */ + if( mhflag ){ + char *prefix; + fprintf(out,"#if INTERFACE\n"); lineno++; + if( lemp->tokenprefix ) prefix = lemp->tokenprefix; + else prefix = ""; + for(i=1; interminal; i++){ + fprintf(out,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i); + lineno++; + } + fprintf(out,"#endif\n"); lineno++; + } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate the defines */ + fprintf(out,"#define YYCODETYPE %s\n", + minimum_size_type(0, lemp->nsymbol+5)); lineno++; + fprintf(out,"#define YYNOCODE %d\n",lemp->nsymbol+1); lineno++; + fprintf(out,"#define YYACTIONTYPE %s\n", + minimum_size_type(0, lemp->nstate+lemp->nrule+5)); lineno++; + print_stack_union(out,lemp,&lineno,mhflag); + if( lemp->stacksize ){ + if( atoi(lemp->stacksize)<=0 ){ + ErrorMsg(lemp->filename,0, +"Illegal stack size: [%s]. The stack size should be an integer constant.", + lemp->stacksize); + lemp->errorcnt++; + lemp->stacksize = "100"; + } + fprintf(out,"#define YYSTACKDEPTH %s\n",lemp->stacksize); lineno++; + }else{ + fprintf(out,"#define YYSTACKDEPTH 100\n"); lineno++; + } + if( mhflag ){ + fprintf(out,"#if INTERFACE\n"); lineno++; + } + name = lemp->name ? lemp->name : "Parse"; + if( lemp->arg && lemp->arg[0] ){ + int i; + i = strlen(lemp->arg); + while( i>=1 && isspace(lemp->arg[i-1]) ) i--; + while( i>=1 && (isalnum(lemp->arg[i-1]) || lemp->arg[i-1]=='_') ) i--; + fprintf(out,"#define %sARG_SDECL %s;\n",name,lemp->arg); lineno++; + fprintf(out,"#define %sARG_PDECL ,%s\n",name,lemp->arg); lineno++; + fprintf(out,"#define %sARG_FETCH %s = yypParser->%s\n", + name,lemp->arg,&lemp->arg[i]); lineno++; + fprintf(out,"#define %sARG_STORE yypParser->%s = %s\n", + name,&lemp->arg[i],&lemp->arg[i]); lineno++; + }else{ + fprintf(out,"#define %sARG_SDECL\n",name); lineno++; + fprintf(out,"#define %sARG_PDECL\n",name); lineno++; + fprintf(out,"#define %sARG_FETCH\n",name); lineno++; + fprintf(out,"#define %sARG_STORE\n",name); lineno++; + } + if( mhflag ){ + fprintf(out,"#endif\n"); lineno++; + } + fprintf(out,"#define YYNSTATE %d\n",lemp->nstate); lineno++; + fprintf(out,"#define YYNRULE %d\n",lemp->nrule); lineno++; + fprintf(out,"#define YYERRORSYMBOL %d\n",lemp->errsym->index); lineno++; + fprintf(out,"#define YYERRSYMDT yy%d\n",lemp->errsym->dtnum); lineno++; + if( lemp->has_fallback ){ + fprintf(out,"#define YYFALLBACK 1\n"); lineno++; + } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate the action table and its associates: + ** + ** yy_action[] A single table containing all actions. + ** yy_lookahead[] A table containing the lookahead for each entry in + ** yy_action. Used to detect hash collisions. + ** yy_shift_ofst[] For each state, the offset into yy_action for + ** shifting terminals. + ** yy_reduce_ofst[] For each state, the offset into yy_action for + ** shifting non-terminals after a reduce. + ** yy_default[] Default action for each state. + */ + + /* Compute the actions on all states and count them up */ + ax = malloc( sizeof(ax[0])*lemp->nstate*2 ); + if( ax==0 ){ + fprintf(stderr,"malloc failed\n"); + exit(1); + } + for(i=0; instate; i++){ + stp = lemp->sorted[i]; + stp->nTknAct = stp->nNtAct = 0; + stp->iDflt = lemp->nstate + lemp->nrule; + stp->iTknOfst = NO_OFFSET; + stp->iNtOfst = NO_OFFSET; + for(ap=stp->ap; ap; ap=ap->next){ + if( compute_action(lemp,ap)>=0 ){ + if( ap->sp->indexnterminal ){ + stp->nTknAct++; + }else if( ap->sp->indexnsymbol ){ + stp->nNtAct++; + }else{ + stp->iDflt = compute_action(lemp, ap); + } + } + } + ax[i*2].stp = stp; + ax[i*2].isTkn = 1; + ax[i*2].nAction = stp->nTknAct; + ax[i*2+1].stp = stp; + ax[i*2+1].isTkn = 0; + ax[i*2+1].nAction = stp->nNtAct; + } + mxTknOfst = mnTknOfst = 0; + mxNtOfst = mnNtOfst = 0; + + /* Compute the action table. In order to try to keep the size of the + ** action table to a minimum, the heuristic of placing the largest action + ** sets first is used. + */ + qsort(ax, lemp->nstate*2, sizeof(ax[0]), axset_compare); + pActtab = acttab_alloc(); + for(i=0; instate*2 && ax[i].nAction>0; i++){ + stp = ax[i].stp; + if( ax[i].isTkn ){ + for(ap=stp->ap; ap; ap=ap->next){ + int action; + if( ap->sp->index>=lemp->nterminal ) continue; + action = compute_action(lemp, ap); + if( action<0 ) continue; + acttab_action(pActtab, ap->sp->index, action); + } + stp->iTknOfst = acttab_insert(pActtab); + if( stp->iTknOfstiTknOfst; + if( stp->iTknOfst>mxTknOfst ) mxTknOfst = stp->iTknOfst; + }else{ + for(ap=stp->ap; ap; ap=ap->next){ + int action; + if( ap->sp->indexnterminal ) continue; + if( ap->sp->index==lemp->nsymbol ) continue; + action = compute_action(lemp, ap); + if( action<0 ) continue; + acttab_action(pActtab, ap->sp->index, action); + } + stp->iNtOfst = acttab_insert(pActtab); + if( stp->iNtOfstiNtOfst; + if( stp->iNtOfst>mxNtOfst ) mxNtOfst = stp->iNtOfst; + } + } + free(ax); + + /* Output the yy_action table */ + fprintf(out,"static const YYACTIONTYPE yy_action[] = {\n"); lineno++; + n = acttab_size(pActtab); + for(i=j=0; insymbol + lemp->nrule + 2; + if( j==0 ) fprintf(out," /* %5d */ ", i); + fprintf(out, " %4d,", action); + if( j==9 || i==n-1 ){ + fprintf(out, "\n"); lineno++; + j = 0; + }else{ + j++; + } + } + fprintf(out, "};\n"); lineno++; + + /* Output the yy_lookahead table */ + fprintf(out,"static const YYCODETYPE yy_lookahead[] = {\n"); lineno++; + for(i=j=0; insymbol; + if( j==0 ) fprintf(out," /* %5d */ ", i); + fprintf(out, " %4d,", la); + if( j==9 || i==n-1 ){ + fprintf(out, "\n"); lineno++; + j = 0; + }else{ + j++; + } + } + fprintf(out, "};\n"); lineno++; + + /* Output the yy_shift_ofst[] table */ + fprintf(out, "#define YY_SHIFT_USE_DFLT (%d)\n", mnTknOfst-1); lineno++; + fprintf(out, "static const %s yy_shift_ofst[] = {\n", + minimum_size_type(mnTknOfst-1, mxTknOfst)); lineno++; + n = lemp->nstate; + for(i=j=0; isorted[i]; + ofst = stp->iTknOfst; + if( ofst==NO_OFFSET ) ofst = mnTknOfst - 1; + if( j==0 ) fprintf(out," /* %5d */ ", i); + fprintf(out, " %4d,", ofst); + if( j==9 || i==n-1 ){ + fprintf(out, "\n"); lineno++; + j = 0; + }else{ + j++; + } + } + fprintf(out, "};\n"); lineno++; + + /* Output the yy_reduce_ofst[] table */ + fprintf(out, "#define YY_REDUCE_USE_DFLT (%d)\n", mnNtOfst-1); lineno++; + fprintf(out, "static const %s yy_reduce_ofst[] = {\n", + minimum_size_type(mnNtOfst-1, mxNtOfst)); lineno++; + n = lemp->nstate; + for(i=j=0; isorted[i]; + ofst = stp->iNtOfst; + if( ofst==NO_OFFSET ) ofst = mnNtOfst - 1; + if( j==0 ) fprintf(out," /* %5d */ ", i); + fprintf(out, " %4d,", ofst); + if( j==9 || i==n-1 ){ + fprintf(out, "\n"); lineno++; + j = 0; + }else{ + j++; + } + } + fprintf(out, "};\n"); lineno++; + + /* Output the default action table */ + fprintf(out, "static const YYACTIONTYPE yy_default[] = {\n"); lineno++; + n = lemp->nstate; + for(i=j=0; isorted[i]; + if( j==0 ) fprintf(out," /* %5d */ ", i); + fprintf(out, " %4d,", stp->iDflt); + if( j==9 || i==n-1 ){ + fprintf(out, "\n"); lineno++; + j = 0; + }else{ + j++; + } + } + fprintf(out, "};\n"); lineno++; + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate the table of fallback tokens. + */ + if( lemp->has_fallback ){ + for(i=0; interminal; i++){ + struct symbol *p = lemp->symbols[i]; + if( p->fallback==0 ){ + fprintf(out, " 0, /* %10s => nothing */\n", p->name); + }else{ + fprintf(out, " %3d, /* %10s => %s */\n", p->fallback->index, + p->name, p->fallback->name); + } + lineno++; + } + } + tplt_xfer(lemp->name, in, out, &lineno); + + /* Generate a table containing the symbolic name of every symbol + */ + for(i=0; insymbol; i++){ + sprintf(line,"\"%s\",",lemp->symbols[i]->name); + fprintf(out," %-15s",line); + if( (i&3)==3 ){ fprintf(out,"\n"); lineno++; } + } + if( (i&3)!=0 ){ fprintf(out,"\n"); lineno++; } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate a table containing a text string that describes every + ** rule in the rule set of the grammer. This information is used + ** when tracing REDUCE actions. + */ + for(i=0, rp=lemp->rule; rp; rp=rp->next, i++){ + assert( rp->index==i ); + fprintf(out," /* %3d */ \"%s ::=", i, rp->lhs->name); + for(j=0; jnrhs; j++) fprintf(out," %s",rp->rhs[j]->name); + fprintf(out,"\",\n"); lineno++; + } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate code which executes every time a symbol is popped from + ** the stack while processing errors or while destroying the parser. + ** (In other words, generate the %destructor actions) + */ + if( lemp->tokendest ){ + for(i=0; insymbol; i++){ + struct symbol *sp = lemp->symbols[i]; + if( sp==0 || sp->type!=TERMINAL ) continue; + fprintf(out," case %d:\n",sp->index); lineno++; + } + for(i=0; insymbol && lemp->symbols[i]->type!=TERMINAL; i++); + if( insymbol ){ + emit_destructor_code(out,lemp->symbols[i],lemp,&lineno); + fprintf(out," break;\n"); lineno++; + } + } + for(i=0; insymbol; i++){ + struct symbol *sp = lemp->symbols[i]; + if( sp==0 || sp->type==TERMINAL || sp->destructor==0 ) continue; + fprintf(out," case %d:\n",sp->index); lineno++; + + /* Combine duplicate destructors into a single case */ + for(j=i+1; jnsymbol; j++){ + struct symbol *sp2 = lemp->symbols[j]; + if( sp2 && sp2->type!=TERMINAL && sp2->destructor + && sp2->dtnum==sp->dtnum + && strcmp(sp->destructor,sp2->destructor)==0 ){ + fprintf(out," case %d:\n",sp2->index); lineno++; + sp2->destructor = 0; + } + } + + emit_destructor_code(out,lemp->symbols[i],lemp,&lineno); + fprintf(out," break;\n"); lineno++; + } + if( lemp->vardest ){ + struct symbol *dflt_sp = 0; + for(i=0; insymbol; i++){ + struct symbol *sp = lemp->symbols[i]; + if( sp==0 || sp->type==TERMINAL || + sp->index<=0 || sp->destructor!=0 ) continue; + fprintf(out," case %d:\n",sp->index); lineno++; + dflt_sp = sp; + } + if( dflt_sp!=0 ){ + emit_destructor_code(out,dflt_sp,lemp,&lineno); + fprintf(out," break;\n"); lineno++; + } + } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate code which executes whenever the parser stack overflows */ + tplt_print(out,lemp,lemp->overflow,lemp->overflowln,&lineno); + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate the table of rule information + ** + ** Note: This code depends on the fact that rules are number + ** sequentually beginning with 0. + */ + for(rp=lemp->rule; rp; rp=rp->next){ + fprintf(out," { %d, %d },\n",rp->lhs->index,rp->nrhs); lineno++; + } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate code which execution during each REDUCE action */ + for(rp=lemp->rule; rp; rp=rp->next){ + if( rp->code ) translate_code(lemp, rp); + } + for(rp=lemp->rule; rp; rp=rp->next){ + struct rule *rp2; + if( rp->code==0 ) continue; + fprintf(out," case %d:\n",rp->index); lineno++; + for(rp2=rp->next; rp2; rp2=rp2->next){ + if( rp2->code==rp->code ){ + fprintf(out," case %d:\n",rp2->index); lineno++; + rp2->code = 0; + } + } + emit_code(out,rp,lemp,&lineno); + fprintf(out," break;\n"); lineno++; + } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate code which executes if a parse fails */ + tplt_print(out,lemp,lemp->failure,lemp->failureln,&lineno); + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate code which executes when a syntax error occurs */ + tplt_print(out,lemp,lemp->error,lemp->errorln,&lineno); + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate code which executes when the parser accepts its input */ + tplt_print(out,lemp,lemp->accept,lemp->acceptln,&lineno); + tplt_xfer(lemp->name,in,out,&lineno); + + /* Append any addition code the user desires */ + tplt_print(out,lemp,lemp->extracode,lemp->extracodeln,&lineno); + + fclose(in); + fclose(out); + return; +} + +/* Generate a header file for the parser */ +void ReportHeader(lemp) +struct lemon *lemp; +{ + FILE *out, *in; + char *prefix; + char line[LINESIZE]; + char pattern[LINESIZE]; + int i; + + if( lemp->tokenprefix ) prefix = lemp->tokenprefix; + else prefix = ""; + in = file_open(lemp,".h","rb"); + if( in ){ + for(i=1; interminal && fgets(line,LINESIZE,in); i++){ + sprintf(pattern,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i); + if( strcmp(line,pattern) ) break; + } + fclose(in); + if( i==lemp->nterminal ){ + /* No change in the file. Don't rewrite it. */ + return; + } + } + out = file_open(lemp,".h","wb"); + if( out ){ + for(i=1; interminal; i++){ + fprintf(out,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i); + } + fclose(out); + } + return; +} + +/* Reduce the size of the action tables, if possible, by making use +** of defaults. +** +** In this version, we take the most frequent REDUCE action and make +** it the default. Only default a reduce if there are more than one. +*/ +void CompressTables(lemp) +struct lemon *lemp; +{ + struct state *stp; + struct action *ap, *ap2; + struct rule *rp, *rp2, *rbest; + int nbest, n; + int i; + + for(i=0; instate; i++){ + stp = lemp->sorted[i]; + nbest = 0; + rbest = 0; + + for(ap=stp->ap; ap; ap=ap->next){ + if( ap->type!=REDUCE ) continue; + rp = ap->x.rp; + if( rp==rbest ) continue; + n = 1; + for(ap2=ap->next; ap2; ap2=ap2->next){ + if( ap2->type!=REDUCE ) continue; + rp2 = ap2->x.rp; + if( rp2==rbest ) continue; + if( rp2==rp ) n++; + } + if( n>nbest ){ + nbest = n; + rbest = rp; + } + } + + /* Do not make a default if the number of rules to default + ** is not at least 2 */ + if( nbest<2 ) continue; + + + /* Combine matching REDUCE actions into a single default */ + for(ap=stp->ap; ap; ap=ap->next){ + if( ap->type==REDUCE && ap->x.rp==rbest ) break; + } + assert( ap ); + ap->sp = Symbol_new("{default}"); + for(ap=ap->next; ap; ap=ap->next){ + if( ap->type==REDUCE && ap->x.rp==rbest ) ap->type = NOT_USED; + } + stp->ap = Action_sort(stp->ap); + } +} + +/***************** From the file "set.c" ************************************/ +/* +** Set manipulation routines for the LEMON parser generator. +*/ + +static int size = 0; + +/* Set the set size */ +void SetSize(n) +int n; +{ + size = n+1; +} + +/* Allocate a new set */ +char *SetNew(){ + char *s; + int i; + s = (char*)malloc( size ); + if( s==0 ){ + extern void memory_error(); + memory_error(); + } + for(i=0; isize = 1024; + x1a->count = 0; + x1a->tbl = (x1node*)malloc( + (sizeof(x1node) + sizeof(x1node*))*1024 ); + if( x1a->tbl==0 ){ + free(x1a); + x1a = 0; + }else{ + int i; + x1a->ht = (x1node**)&(x1a->tbl[1024]); + for(i=0; i<1024; i++) x1a->ht[i] = 0; + } + } +} +/* Insert a new record into the array. Return TRUE if successful. +** Prior data with the same key is NOT overwritten */ +int Strsafe_insert(data) +char *data; +{ + x1node *np; + int h; + int ph; + + if( x1a==0 ) return 0; + ph = strhash(data); + h = ph & (x1a->size-1); + np = x1a->ht[h]; + while( np ){ + if( strcmp(np->data,data)==0 ){ + /* An existing entry with the same key is found. */ + /* Fail because overwrite is not allows. */ + return 0; + } + np = np->next; + } + if( x1a->count>=x1a->size ){ + /* Need to make the hash table bigger */ + int i,size; + struct s_x1 array; + array.size = size = x1a->size*2; + array.count = x1a->count; + array.tbl = (x1node*)malloc( + (sizeof(x1node) + sizeof(x1node*))*size ); + if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ + array.ht = (x1node**)&(array.tbl[size]); + for(i=0; icount; i++){ + x1node *oldnp, *newnp; + oldnp = &(x1a->tbl[i]); + h = strhash(oldnp->data) & (size-1); + newnp = &(array.tbl[i]); + if( array.ht[h] ) array.ht[h]->from = &(newnp->next); + newnp->next = array.ht[h]; + newnp->data = oldnp->data; + newnp->from = &(array.ht[h]); + array.ht[h] = newnp; + } + free(x1a->tbl); + *x1a = array; + } + /* Insert the new data */ + h = ph & (x1a->size-1); + np = &(x1a->tbl[x1a->count++]); + np->data = data; + if( x1a->ht[h] ) x1a->ht[h]->from = &(np->next); + np->next = x1a->ht[h]; + x1a->ht[h] = np; + np->from = &(x1a->ht[h]); + return 1; +} + +/* Return a pointer to data assigned to the given key. Return NULL +** if no such key. */ +char *Strsafe_find(key) +char *key; +{ + int h; + x1node *np; + + if( x1a==0 ) return 0; + h = strhash(key) & (x1a->size-1); + np = x1a->ht[h]; + while( np ){ + if( strcmp(np->data,key)==0 ) break; + np = np->next; + } + return np ? np->data : 0; +} + +/* Return a pointer to the (terminal or nonterminal) symbol "x". +** Create a new symbol if this is the first time "x" has been seen. +*/ +struct symbol *Symbol_new(x) +char *x; +{ + struct symbol *sp; + + sp = Symbol_find(x); + if( sp==0 ){ + sp = (struct symbol *)malloc( sizeof(struct symbol) ); + MemoryCheck(sp); + sp->name = Strsafe(x); + sp->type = isupper(*x) ? TERMINAL : NONTERMINAL; + sp->rule = 0; + sp->fallback = 0; + sp->prec = -1; + sp->assoc = UNK; + sp->firstset = 0; + sp->lambda = B_FALSE; + sp->destructor = 0; + sp->datatype = 0; + Symbol_insert(sp,sp->name); + } + return sp; +} + +/* Compare two symbols for working purposes +** +** Symbols that begin with upper case letters (terminals or tokens) +** must sort before symbols that begin with lower case letters +** (non-terminals). Other than that, the order does not matter. +** +** We find experimentally that leaving the symbols in their original +** order (the order they appeared in the grammar file) gives the +** smallest parser tables in SQLite. +*/ +int Symbolcmpp(struct symbol **a, struct symbol **b){ + int i1 = (**a).index + 10000000*((**a).name[0]>'Z'); + int i2 = (**b).index + 10000000*((**b).name[0]>'Z'); + return i1-i2; +} + +/* There is one instance of the following structure for each +** associative array of type "x2". +*/ +struct s_x2 { + int size; /* The number of available slots. */ + /* Must be a power of 2 greater than or */ + /* equal to 1 */ + int count; /* Number of currently slots filled */ + struct s_x2node *tbl; /* The data stored here */ + struct s_x2node **ht; /* Hash table for lookups */ +}; + +/* There is one instance of this structure for every data element +** in an associative array of type "x2". +*/ +typedef struct s_x2node { + struct symbol *data; /* The data */ + char *key; /* The key */ + struct s_x2node *next; /* Next entry with the same hash */ + struct s_x2node **from; /* Previous link */ +} x2node; + +/* There is only one instance of the array, which is the following */ +static struct s_x2 *x2a; + +/* Allocate a new associative array */ +void Symbol_init(){ + if( x2a ) return; + x2a = (struct s_x2*)malloc( sizeof(struct s_x2) ); + if( x2a ){ + x2a->size = 128; + x2a->count = 0; + x2a->tbl = (x2node*)malloc( + (sizeof(x2node) + sizeof(x2node*))*128 ); + if( x2a->tbl==0 ){ + free(x2a); + x2a = 0; + }else{ + int i; + x2a->ht = (x2node**)&(x2a->tbl[128]); + for(i=0; i<128; i++) x2a->ht[i] = 0; + } + } +} +/* Insert a new record into the array. Return TRUE if successful. +** Prior data with the same key is NOT overwritten */ +int Symbol_insert(data,key) +struct symbol *data; +char *key; +{ + x2node *np; + int h; + int ph; + + if( x2a==0 ) return 0; + ph = strhash(key); + h = ph & (x2a->size-1); + np = x2a->ht[h]; + while( np ){ + if( strcmp(np->key,key)==0 ){ + /* An existing entry with the same key is found. */ + /* Fail because overwrite is not allows. */ + return 0; + } + np = np->next; + } + if( x2a->count>=x2a->size ){ + /* Need to make the hash table bigger */ + int i,size; + struct s_x2 array; + array.size = size = x2a->size*2; + array.count = x2a->count; + array.tbl = (x2node*)malloc( + (sizeof(x2node) + sizeof(x2node*))*size ); + if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ + array.ht = (x2node**)&(array.tbl[size]); + for(i=0; icount; i++){ + x2node *oldnp, *newnp; + oldnp = &(x2a->tbl[i]); + h = strhash(oldnp->key) & (size-1); + newnp = &(array.tbl[i]); + if( array.ht[h] ) array.ht[h]->from = &(newnp->next); + newnp->next = array.ht[h]; + newnp->key = oldnp->key; + newnp->data = oldnp->data; + newnp->from = &(array.ht[h]); + array.ht[h] = newnp; + } + free(x2a->tbl); + *x2a = array; + } + /* Insert the new data */ + h = ph & (x2a->size-1); + np = &(x2a->tbl[x2a->count++]); + np->key = key; + np->data = data; + if( x2a->ht[h] ) x2a->ht[h]->from = &(np->next); + np->next = x2a->ht[h]; + x2a->ht[h] = np; + np->from = &(x2a->ht[h]); + return 1; +} + +/* Return a pointer to data assigned to the given key. Return NULL +** if no such key. */ +struct symbol *Symbol_find(key) +char *key; +{ + int h; + x2node *np; + + if( x2a==0 ) return 0; + h = strhash(key) & (x2a->size-1); + np = x2a->ht[h]; + while( np ){ + if( strcmp(np->key,key)==0 ) break; + np = np->next; + } + return np ? np->data : 0; +} + +/* Return the n-th data. Return NULL if n is out of range. */ +struct symbol *Symbol_Nth(n) +int n; +{ + struct symbol *data; + if( x2a && n>0 && n<=x2a->count ){ + data = x2a->tbl[n-1].data; + }else{ + data = 0; + } + return data; +} + +/* Return the size of the array */ +int Symbol_count() +{ + return x2a ? x2a->count : 0; +} + +/* Return an array of pointers to all data in the table. +** The array is obtained from malloc. Return NULL if memory allocation +** problems, or if the array is empty. */ +struct symbol **Symbol_arrayof() +{ + struct symbol **array; + int i,size; + if( x2a==0 ) return 0; + size = x2a->count; + array = (struct symbol **)malloc( sizeof(struct symbol *)*size ); + if( array ){ + for(i=0; itbl[i].data; + } + return array; +} + +/* Compare two configurations */ +int Configcmp(a,b) +struct config *a; +struct config *b; +{ + int x; + x = a->rp->index - b->rp->index; + if( x==0 ) x = a->dot - b->dot; + return x; +} + +/* Compare two states */ +PRIVATE int statecmp(a,b) +struct config *a; +struct config *b; +{ + int rc; + for(rc=0; rc==0 && a && b; a=a->bp, b=b->bp){ + rc = a->rp->index - b->rp->index; + if( rc==0 ) rc = a->dot - b->dot; + } + if( rc==0 ){ + if( a ) rc = 1; + if( b ) rc = -1; + } + return rc; +} + +/* Hash a state */ +PRIVATE int statehash(a) +struct config *a; +{ + int h=0; + while( a ){ + h = h*571 + a->rp->index*37 + a->dot; + a = a->bp; + } + return h; +} + +/* Allocate a new state structure */ +struct state *State_new() +{ + struct state *new; + new = (struct state *)malloc( sizeof(struct state) ); + MemoryCheck(new); + return new; +} + +/* There is one instance of the following structure for each +** associative array of type "x3". +*/ +struct s_x3 { + int size; /* The number of available slots. */ + /* Must be a power of 2 greater than or */ + /* equal to 1 */ + int count; /* Number of currently slots filled */ + struct s_x3node *tbl; /* The data stored here */ + struct s_x3node **ht; /* Hash table for lookups */ +}; + +/* There is one instance of this structure for every data element +** in an associative array of type "x3". +*/ +typedef struct s_x3node { + struct state *data; /* The data */ + struct config *key; /* The key */ + struct s_x3node *next; /* Next entry with the same hash */ + struct s_x3node **from; /* Previous link */ +} x3node; + +/* There is only one instance of the array, which is the following */ +static struct s_x3 *x3a; + +/* Allocate a new associative array */ +void State_init(){ + if( x3a ) return; + x3a = (struct s_x3*)malloc( sizeof(struct s_x3) ); + if( x3a ){ + x3a->size = 128; + x3a->count = 0; + x3a->tbl = (x3node*)malloc( + (sizeof(x3node) + sizeof(x3node*))*128 ); + if( x3a->tbl==0 ){ + free(x3a); + x3a = 0; + }else{ + int i; + x3a->ht = (x3node**)&(x3a->tbl[128]); + for(i=0; i<128; i++) x3a->ht[i] = 0; + } + } +} +/* Insert a new record into the array. Return TRUE if successful. +** Prior data with the same key is NOT overwritten */ +int State_insert(data,key) +struct state *data; +struct config *key; +{ + x3node *np; + int h; + int ph; + + if( x3a==0 ) return 0; + ph = statehash(key); + h = ph & (x3a->size-1); + np = x3a->ht[h]; + while( np ){ + if( statecmp(np->key,key)==0 ){ + /* An existing entry with the same key is found. */ + /* Fail because overwrite is not allows. */ + return 0; + } + np = np->next; + } + if( x3a->count>=x3a->size ){ + /* Need to make the hash table bigger */ + int i,size; + struct s_x3 array; + array.size = size = x3a->size*2; + array.count = x3a->count; + array.tbl = (x3node*)malloc( + (sizeof(x3node) + sizeof(x3node*))*size ); + if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ + array.ht = (x3node**)&(array.tbl[size]); + for(i=0; icount; i++){ + x3node *oldnp, *newnp; + oldnp = &(x3a->tbl[i]); + h = statehash(oldnp->key) & (size-1); + newnp = &(array.tbl[i]); + if( array.ht[h] ) array.ht[h]->from = &(newnp->next); + newnp->next = array.ht[h]; + newnp->key = oldnp->key; + newnp->data = oldnp->data; + newnp->from = &(array.ht[h]); + array.ht[h] = newnp; + } + free(x3a->tbl); + *x3a = array; + } + /* Insert the new data */ + h = ph & (x3a->size-1); + np = &(x3a->tbl[x3a->count++]); + np->key = key; + np->data = data; + if( x3a->ht[h] ) x3a->ht[h]->from = &(np->next); + np->next = x3a->ht[h]; + x3a->ht[h] = np; + np->from = &(x3a->ht[h]); + return 1; +} + +/* Return a pointer to data assigned to the given key. Return NULL +** if no such key. */ +struct state *State_find(key) +struct config *key; +{ + int h; + x3node *np; + + if( x3a==0 ) return 0; + h = statehash(key) & (x3a->size-1); + np = x3a->ht[h]; + while( np ){ + if( statecmp(np->key,key)==0 ) break; + np = np->next; + } + return np ? np->data : 0; +} + +/* Return an array of pointers to all data in the table. +** The array is obtained from malloc. Return NULL if memory allocation +** problems, or if the array is empty. */ +struct state **State_arrayof() +{ + struct state **array; + int i,size; + if( x3a==0 ) return 0; + size = x3a->count; + array = (struct state **)malloc( sizeof(struct state *)*size ); + if( array ){ + for(i=0; itbl[i].data; + } + return array; +} + +/* Hash a configuration */ +PRIVATE int confighash(a) +struct config *a; +{ + int h=0; + h = h*571 + a->rp->index*37 + a->dot; + return h; +} + +/* There is one instance of the following structure for each +** associative array of type "x4". +*/ +struct s_x4 { + int size; /* The number of available slots. */ + /* Must be a power of 2 greater than or */ + /* equal to 1 */ + int count; /* Number of currently slots filled */ + struct s_x4node *tbl; /* The data stored here */ + struct s_x4node **ht; /* Hash table for lookups */ +}; + +/* There is one instance of this structure for every data element +** in an associative array of type "x4". +*/ +typedef struct s_x4node { + struct config *data; /* The data */ + struct s_x4node *next; /* Next entry with the same hash */ + struct s_x4node **from; /* Previous link */ +} x4node; + +/* There is only one instance of the array, which is the following */ +static struct s_x4 *x4a; + +/* Allocate a new associative array */ +void Configtable_init(){ + if( x4a ) return; + x4a = (struct s_x4*)malloc( sizeof(struct s_x4) ); + if( x4a ){ + x4a->size = 64; + x4a->count = 0; + x4a->tbl = (x4node*)malloc( + (sizeof(x4node) + sizeof(x4node*))*64 ); + if( x4a->tbl==0 ){ + free(x4a); + x4a = 0; + }else{ + int i; + x4a->ht = (x4node**)&(x4a->tbl[64]); + for(i=0; i<64; i++) x4a->ht[i] = 0; + } + } +} +/* Insert a new record into the array. Return TRUE if successful. +** Prior data with the same key is NOT overwritten */ +int Configtable_insert(data) +struct config *data; +{ + x4node *np; + int h; + int ph; + + if( x4a==0 ) return 0; + ph = confighash(data); + h = ph & (x4a->size-1); + np = x4a->ht[h]; + while( np ){ + if( Configcmp(np->data,data)==0 ){ + /* An existing entry with the same key is found. */ + /* Fail because overwrite is not allows. */ + return 0; + } + np = np->next; + } + if( x4a->count>=x4a->size ){ + /* Need to make the hash table bigger */ + int i,size; + struct s_x4 array; + array.size = size = x4a->size*2; + array.count = x4a->count; + array.tbl = (x4node*)malloc( + (sizeof(x4node) + sizeof(x4node*))*size ); + if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ + array.ht = (x4node**)&(array.tbl[size]); + for(i=0; icount; i++){ + x4node *oldnp, *newnp; + oldnp = &(x4a->tbl[i]); + h = confighash(oldnp->data) & (size-1); + newnp = &(array.tbl[i]); + if( array.ht[h] ) array.ht[h]->from = &(newnp->next); + newnp->next = array.ht[h]; + newnp->data = oldnp->data; + newnp->from = &(array.ht[h]); + array.ht[h] = newnp; + } + free(x4a->tbl); + *x4a = array; + } + /* Insert the new data */ + h = ph & (x4a->size-1); + np = &(x4a->tbl[x4a->count++]); + np->data = data; + if( x4a->ht[h] ) x4a->ht[h]->from = &(np->next); + np->next = x4a->ht[h]; + x4a->ht[h] = np; + np->from = &(x4a->ht[h]); + return 1; +} + +/* Return a pointer to data assigned to the given key. Return NULL +** if no such key. */ +struct config *Configtable_find(key) +struct config *key; +{ + int h; + x4node *np; + + if( x4a==0 ) return 0; + h = confighash(key) & (x4a->size-1); + np = x4a->ht[h]; + while( np ){ + if( Configcmp(np->data,key)==0 ) break; + np = np->next; + } + return np ? np->data : 0; +} + +/* Remove all data from the table. Pass each data to the function "f" +** as it is removed. ("f" may be null to avoid this step.) */ +void Configtable_clear(f) +int(*f)(/* struct config * */); +{ + int i; + if( x4a==0 || x4a->count==0 ) return; + if( f ) for(i=0; icount; i++) (*f)(x4a->tbl[i].data); + for(i=0; isize; i++) x4a->ht[i] = 0; + x4a->count = 0; + return; +} diff --git a/lime/lime.bootstrap b/lime/lime.bootstrap new file mode 100644 index 000000000..63791c718 --- /dev/null +++ b/lime/lime.bootstrap @@ -0,0 +1,31 @@ +There is nothing to see here. Go and look at the file called "metagrammar". + +: $$ = new lime(); +grammar pragma toklist stop : $$->pragma($2, $3); +grammar rewrite stop : $2->update($$); +to grammar +: {$$=array();} +toklist sym : $$[] = $2; +toklist lit : $$[] = $2; +to toklist +sym '=' rhs : $$ = new lime_rewrite($1); $$->add_rhs($3); +rewrite '|' rhs : $$->add_rhs($3); +to rewrite +list : $$ = new lime_rhs($1, ''); +list action : $$ = new lime_rhs($1, $2); +to rhs +action : $$ = new lime_action($1, NULL); +action lambda : $$ = new lime_action($1, $2); +sym : $$ = new lime_glyph($1, NULL); +sym lambda : $$ = new lime_glyph($1, $2); +lit : $$ = new lime_glyph($1, NULL); +to slot +: $$ = new lime_rhs(); +rhs slot : $$->add($2); +to rhs +'{' code '}' : $$ = $2; +to action +: +code php : $$.=$2; +code '{' code '}' : $$.='{'.$3.'}'; +to code diff --git a/lime/lime.php b/lime/lime.php new file mode 100644 index 000000000..b049225eb --- /dev/null +++ b/lime/lime.php @@ -0,0 +1,910 @@ +code=$code; } +} +class step { + /* + Base class for parse table instructions. The main idea is to make the + subclasses responsible for conflict resolution among themselves. It also + forms a sort of interface to the parse table. + */ + function __construct($sym) { + bug_unless($sym instanceof sym); + $this->sym = $sym; + } + function glyph() { return $this->sym->name; } +} +class error extends step { + function sane() { return false; } + function instruction() { bug("This should not happen."); } + function decide($that) { return $this; /* An error shall remain one. */ } +} +class shift extends step { + function __construct($sym, $q) { + parent::__construct($sym); + $this->q = $q; + } + function sane() { return true; } + function instruction() { return "s $this->q"; } + function decide($that) { + # shift-shift conflicts are impossible. + # shift-accept conflicts are a bug. + # so we can infer: + bug_unless($that instanceof reduce); + + # That being said, the resolution is a matter of precedence. + $shift_prec = $this->sym->right_prec; + $reduce_prec = $that->rule->prec; + + # If we don't have defined precedence levels for both options, + # then we default to shifting: + if (!($shift_prec and $reduce_prec)) return $this; + + # Otherwise, use the step with higher precedence. + if ($shift_prec > $reduce_prec) return $this; + if ($reduce_prec > $shift_prec) return $that; + + # The "nonassoc" works by giving equal precedence to both options, + # which means to put an error instruction in the parse table. + return new error($this->sym); + } +} +class reduce extends step { + function __construct($sym, $rule) { + bug_unless($rule instanceof rule); + parent::__construct($sym); + $this->rule = $rule; + } + function sane() { return true; } + function instruction() { return 'r '.$this->rule->id; } + function decide($that) { + # This means that the input grammar has a reduce-reduce conflict. + # Such things are considered an error in the input. + throw new RRC($this, $that); + #exit(1); + # BISON would go with the first encountered reduce thus: + # return $this; + } +} +class accept extends step { + function __construct($sym) { parent::__construct($sym); } + function sane() { return true; } + function instruction() { return 'a '.$this->sym->name; } +} +class RRC extends Exception { + function __construct($a, $b) { + parent::__construct("Reduce-Reduce Conflict"); + $this->a = $a; + $this->b = $b; + } + function make_noise() { + emit(sprintf( + "Reduce-Reduce Conflict:\n%s\n%s\nLookahead is (%s)", + $this->a->rule->text(), + $this->b->rule->text(), + $this->a->glyph() + )); + } +} +class state { + function __construct($id, $key, $close) { + $this->id = $id; + $this->key = $key; + $this->close = $close; # config key -> object + ksort($this->close); + $this->action = array(); + } + function dump() { + echo " * ".$this->id.' / '.$this->key."\n"; + foreach ($this->close as $config) $config->dump(); + } + function add_shift($sym, $state) { + $this->add_instruction(new shift($sym, $state->id)); + } + function add_reduce($sym, $rule) { + $this->add_instruction(new reduce($sym, $rule)); + } + function add_accept($sym) { + $this->add_instruction(new accept($sym)); + } + function add_instruction($step) { + bug_unless($step instanceof step); + $this->action[] = $step; + } + function find_reductions($lime) { + # rightmost configurations followset yields reduce. + foreach($this->close as $c) { + if ($c->rightmost) { + foreach ($c->follow->all() as $glyph) $this->add_reduce($lime->sym($glyph), $c->rule); + } + } + } + function resolve_conflicts() { + # For each possible lookahead, find one (and only one) step to take. + $table = array(); + foreach ($this->action as $step) { + $glyph = $step->glyph(); + if (isset($table[$glyph])) { + # There's a conflict. The shifts all came first, which + # simplifies the coding for the step->decide() methods. + try { + $table[$glyph] = $table[$glyph]->decide($step); + } catch (RRC $e) { + emit("State $this->id:"); + $e->make_noise(); + } + } else { + # This glyph is yet unprocessed, so the step at hand is + # our best current guess at what the grammar indicates. + $table[$glyph] = $step; + } + } + + # Now that we have the correct steps chosen, this routine is oddly + # also responsible for turning that table into the form that will + # eventually be passed to the parse engine. (So FIXME?) + $out = array(); + foreach ($table as $glyph => $step) { + if ($step->sane()) $out[$glyph] = $step->instruction(); + } + return $out; + } + function segment_config() { + # Filter $this->close into categories based on the symbol_after_the_dot. + $f = array(); + foreach ($this->close as $c) { + $p = $c->symbol_after_the_dot; + if (!$p) continue; + $f[$p->name][] = $c; + } + return $f; + } +} +class sym { + function __construct($name, $id) { + $this->name=$name; + $this->id=$id; + $this->term = true; # Until proven otherwise. + $this->rule = array(); + $this->config = array(); + $this->lambda = false; + $this->first = new set(); + $this->left_prec = $this->right_prec = 0; + } + function summary() { + $out = ''; + foreach ($this->rule as $rule) $out .= $rule->text()."\n"; + return $out; + } +} +class rule { + function __construct($id, $sym, $rhs, $code, $look, $replace) { + $this->id = $id; + $this->sym = $sym; + $this->rhs = $rhs; + $this->code = $code; + $this->look = $look; + bug_unless(is_int($look)); + $this->replace = $replace; + #$this->prec_sym = $prec_sym; + $this->prec = 0; + $this->first = array(); + $this->epsilon = count($rhs); + } + function lhs_glyph() { return $this->sym->name; } + function determine_precedence() { + # We may eventually expand to allow explicit prec_symbol declarations. + # Until then, we'll go with the rightmost terminal, which is what + # BISON does. People probably expect that. The leftmost terminal + # is a reasonable alternative behaviour, but I don't see the big + # deal just now. + + #$prec_sym = $this->prec_sym; + #if (!$prec_sym) + $prec_sym = $this->rightmost_terminal(); + if (!$prec_sym) return; + $this->prec = $prec_sym->left_prec; + } + private function rightmost_terminal() { + $symbol = NULL; + $rhs = $this->rhs; + while ($rhs) { + $symbol = array_pop($rhs); + if ($symbol->term) break; + } + return $symbol; + } + function text() { + $t = "($this->id) ".$this->lhs_glyph().' :='; + foreach($this->rhs as $s) $t .= ' '.$s->name; + return $t; + } + function table(lime_language $lang) { + return array( + 'symbol' => $this->lhs_glyph(), + 'len' => $this->look, + 'replace' => $this->replace, + 'code' => $lang->fixup($this->code), + 'text' => $this->text(), + ); + } + function lambda() { + foreach ($this->rhs as $sym) if (!$sym->lambda) return false; + return true; + } + function find_first() { + $dot = count($this->rhs); + $last = $this->first[$dot] = new set(); + while ($dot) { + $dot--; + $symbol_after_the_dot = $this->rhs[$dot]; + $first = $symbol_after_the_dot->first->all(); + bug_if(empty($first) and !$symbol_after_the_dot->lambda); + $set = new set($first); + if ($symbol_after_the_dot->lambda) { + $set->union($last); + if ($this->epsilon == $dot+1) $this->epsilon = $dot; + } + $last = $this->first[$dot] = $set; + } + } + function teach_symbol_of_first_set() { + $go = false; + foreach ($this->rhs as $sym) { + if ($this->sym->first->union($sym->first)) $go = true; + if (!$sym->lambda) break; + } + return $go; + } + function lambda_from($dot) { + return $this->epsilon <= $dot; + } + function leftmost($follow) { + return new config($this, 0, $follow); + } + function dotted_text($dot) { + $out = $this->lhs_glyph().' :='; + $idx = -1; + foreach($this->rhs as $idx => $s) { + if ($idx == $dot) $out .= ' .'; + $out .= ' '.$s->name; + } + if ($dot > $idx) $out .= ' .'; + return $out; + } +} +class config { + function __construct($rule, $dot, $follow) { + $this->rule=$rule; + $this->dot = $dot; + $this->key = "$rule->id.$dot"; + $this->rightmost = count($rule->rhs) <= $dot; + $this->symbol_after_the_dot = $this->rightmost ? null : $rule->rhs[$dot]; + $this->_blink = array(); + $this->follow = new set($follow); + $this->_flink= array(); + bug_unless($this->rightmost or count($rule)); + } + function text() { + $out = $this->rule->dotted_text($this->dot); + $out .= ' [ '.implode(' ', $this->follow->all()).' ]'; + return $out; + } + function blink($config) { + $this->_blink[] = $config; + } + function next() { + bug_if($this->rightmost); + $c = new config($this->rule, $this->dot+1, array()); + # Anything in the follow set for this config will also be in the next. + # However, we link it backwards because we might wind up selecting a + # pre-existing state, and the housekeeping is easier in the first half + # of the program. We'll fix it before doing the propagation. + $c->blink($this); + return $c; + } + function copy_links_from($that) { + foreach($that->_blink as $c) $this->blink($c); + } + function lambda() { + return $this->rule->lambda_from($this->dot); + } + function simple_follow() { + return $this->rule->first[$this->dot+1]->all(); + } + function epsilon_follows() { + return $this->rule->lambda_from($this->dot+1); + } + function fixlinks() { + foreach ($this->_blink as $that) $that->_flink[] = $this; + $this->blink = array(); + } + function dump() { + echo " * "; + echo $this->key.' : '; + echo $this->rule->dotted_text($this->dot); + echo $this->follow->text(); + foreach ($this->_flink as $c) echo $c->key.' / '; + echo "\n"; + } +} +class lime { + var $parser_class = 'parser'; + function __construct() { + $this->p_next = 1; + $this->sym = array(); + $this->rule = array(); + $this->start_symbol_set = array(); + $this->state = array(); + $this->stop = $this->sym('#'); + #$err = $this->sym('error'); + $err->term = false; + $this->lang = new lime_language_php(); + } + function language() { return $this->lang; } + function build_parser() { + $this->add_start_rule(); + foreach ($this->rule as $r) $r->determine_precedence(); + $this->find_sym_lamdba(); + $this->find_sym_first(); + foreach ($this->rule as $rule) $rule->find_first(); + $initial = $this->find_states(); + $this->fixlinks(); + # $this->dump_configurations(); + $this->find_follow_sets(); + foreach($this->state as $s) $s->find_reductions($this); + $i = $this->resolve_conflicts(); + $a = $this->rule_table(); + $qi = $initial->id; + return $this->lang->ptab_to_class($this->parser_class, compact('a', 'qi', 'i')); + } + function rule_table() { + $s = array(); + foreach ($this->rule as $i => $r) { + $s[$i] = $r->table($this->lang); + } + return $s; + } + function add_rule($symbol, $rhs, $code) { + $this->add_raw_rule($symbol, $rhs, $code, count($rhs), true); + } + function trump_up_bogus_lhs($real) { + return "'$real'".count($this->rule); + } + function add_raw_rule($lhs, $rhs, $code, $look, $replace) { + $sym = $this->sym($lhs); + $sym->term=false; + if (empty($rhs)) $sym->lambda = true; + $rs = array(); + foreach ($rhs as $str) $rs[] = $this->sym($str); + $rid = count($this->rule); + $r = new rule($rid, $sym, $rs, $code, $look, $replace); + $this->rule[$rid] = $r; + $sym->rule[] = $r; + } + function sym($str) { + if (!isset($this->sym[$str])) $this->sym[$str] = new sym($str, count($this->sym)); + return $this->sym[$str]; + } + function summary() { + $out = ''; + foreach ($this->sym as $sym) if (!$sym->term) $out .= $sym->summary(); + return $out; + } + private function find_sym_lamdba() { + do { + $go = false; + foreach ($this->sym as $sym) if (!$sym->lambda) { + foreach ($sym->rule as $rule) if ($rule->lambda()) { + $go = true; + $sym->lambda = true; + } + } + } while ($go); + } + private function teach_terminals_first_set() { + foreach ($this->sym as $sym) if ($sym->term) $sym->first->add($sym->name); + } + private function find_sym_first() { + $this->teach_terminals_first_set(); + do { + $go = false; + foreach ($this->rule as $r) if ($r->teach_symbol_of_first_set()) $go = true; + } while ($go); + } + function add_start_rule() { + $rewrite = new lime_rewrite("'start'"); + $rhs = new lime_rhs(); + $rhs->add(new lime_glyph($this->deduce_start_symbol()->name, NULL)); + #$rhs->add(new lime_glyph($this->stop->name, NULL)); + $rewrite->add_rhs($rhs); + $rewrite->update($this); + } + private function deduce_start_symbol() { + $candidate = current($this->start_symbol_set); + # Did the person try to set a start symbol at all? + if (!$candidate) return $this->first_rule_lhs(); + # Do we actually have such a symbol on the left of a rule? + if ($candidate->terminal) return $this->first_rule_lhs(); + # Ok, it's a decent choice. We need to return the symbol entry. + return $this->sym($candidate); + } + private function first_rule_lhs() { + reset($this->rule); + $r = current($this->rule); + return $r->sym; + } + function find_states() { + /* + Build an initial state. This is a recursive process which digs out + the LR(0) state graph. + */ + $start_glyph = "'start'"; + $sym = $this->sym($start_glyph); + $basis = array(); + foreach($sym->rule as $rule) { + $c = $rule->leftmost(array('#')); + $basis[$c->key] = $c; + } + $initial = $this->get_state($basis); + $initial->add_accept($sym); + return $initial; + } + function get_state($basis) { + $key = array_keys($basis); + sort($key); + $key = implode(' ', $key); + if (isset($this->state[$key])) { + # Copy all the links around... + $state = $this->state[$key]; + foreach($basis as $config) $state->close[$config->key]->copy_links_from($config); + return $state; + } else { + $close = $this->state_closure($basis); + $this->state[$key] = $state = new state(count($this->state), $key, $close); + $this->build_shifts($state); + return $state; + } + } + private function state_closure($q) { + # $q is a list of config. + $close = array(); + while ($config = array_pop($q)) { + if (isset($close[$config->key])) { + $close[$config->key]->copy_links_from($config); + $close[$config->key]->follow->union($config->follow); + continue; + } + $close[$config->key] = $config; + + $symbol_after_the_dot = $config->symbol_after_the_dot; + if (!$symbol_after_the_dot) continue; + + if (! $symbol_after_the_dot->term) { + foreach ($symbol_after_the_dot->rule as $r) { + $station = $r->leftmost($config->simple_follow()); + if ($config->epsilon_follows()) $station->blink($config); + $q[] = $station; + } + # The following turned out to be wrong. Don't do it. + #if ($symbol_after_the_dot->lambda) { + # $q[] = $config->next(); + #} + } + + } + return $close; + } + function build_shifts($state) { + foreach ($state->segment_config() as $glyph => $segment) { + $basis = array(); + foreach ($segment as $preshift) { + $postshift = $preshift->next(); + $basis[$postshift->key] = $postshift; + } + $dest = $this->get_state($basis); + $state->add_shift($this->sym($glyph), $dest); + } + } + function fixlinks() { + foreach ($this->state as $s) foreach ($s->close as $c) $c->fixlinks(); + } + function find_follow_sets() { + $q = array(); + foreach ($this->state as $s) foreach ($s->close as $c) $q[] = $c; + while ($q) { + $c = array_shift($q); + foreach ($c->_flink as $d) { + if ($d->follow->union($c->follow)) $q[] = $d; + } + } + } + private function set_assoc($ss, $l, $r) { + $p = ($this->p_next++)*2; + foreach ($ss as $glyph) { + $s = $this->sym($glyph); + $s->left_prec = $p+$l; + $s->right_prec = $p+$r; + } + } + function left_assoc($ss) { $this->set_assoc($ss, 1, 0); } + function right_assoc($ss) { $this->set_assoc($ss, 0, 1); } + function non_assoc($ss) { $this->set_assoc($ss, 0, 0); } + private function resolve_conflicts() { + # For each state, try to find one and only one + # thing to do for any given lookahead. + $i = array(); + foreach ($this->state as $s) $i[$s->id] = $s->resolve_conflicts(); + return $i; + } + function dump_configurations() { + foreach ($this->state as $q) $q->dump(); + } + function dump_first_sets() { + foreach ($this->sym as $s) { + echo " * "; + echo $s->name.' : '; + echo $s->first->text(); + echo "\n"; + } + } + function add_rule_with_actions($lhs, $rhs) { + # First, make sure this thing is well-formed. + if(!is_object(end($rhs))) $rhs[] = new cf_action(''); + # Now, split it into chunks based on the actions. + $look = -1; + $subrule = array(); + $subsymbol = ''; + while (count($rhs)) { + $it = array_shift($rhs); + $look ++; + if (is_string($it)) { + $subrule[] = $it; + } else { + $code = $it->code; + # It's an action. + # Is it the last one? + if (count($rhs)) { + # no. + $subsymbol = $this->trump_up_bogus_lhs($lhs); + $this->add_raw_rule($subsymbol, $subrule, $code, $look, false); + $subrule = array($subsymbol); + } else { + # yes. + $this->add_raw_rule($lhs, $subrule, $code, $look, true); + } + } + } + } + function pragma($type, $args) { + switch ($type) { + case 'left': + $this->left_assoc($args); + break; + + case 'right': + $this->right_assoc($args); + break; + + case 'nonassoc': + $this->non_assoc($args); + break; + + case 'start': + $this->start_symbol_set = $args; + break; + + case 'class': + $this->parser_class = $args[0]; + break; + + default: + emit(sprintf("Bad Parser Pragma: (%s)", $type)); + exit(1); + } + } +} +class lime_language {} +class lime_language_php extends lime_language { + private function result_code($expr) { return "\$result = $expr;\n"; } + function default_result() { return $this->result_code('reset($tokens)'); } + function result_pos($pos) { return $this->result_code(lime_token_reference($pos)); } + function bind($name, $pos) { return "\$$name =& \$tokens[$pos];\n"; } + function fixup($code) { + $code = preg_replace_callback('/\\$(\d+)/', 'lime_token_reference_callback', $code); + $code = preg_replace('/\\$\\$/', '$result', $code); + return $code; + } + function to_php($code) { + return $code; + } + function ptab_to_class($parser_class, $ptab) { + $code = "class $parser_class extends lime_parser {\n"; + $code .= 'var $qi = '.var_export($ptab['qi'], true).";\n"; + $code .= 'var $i = '.var_export($ptab['i'], true).";\n"; + + + $rc = array(); + $method = array(); + $rules = array(); + foreach($ptab['a'] as $k => $a) { + $symbol = preg_replace('/[^\w]/', '', $a['symbol']); + $rn = ++$rc[$symbol]; + $mn = "reduce_${k}_${symbol}_${rn}"; + $method[$k] = $mn; + $comment = "#\n# $a[text]\n#\n"; + $php = $this->to_php($a['code']); + $code .= "function $mn(".LIME_CALL_PROTOCOL.") {\n$comment$php\n}\n\n"; + + + unset($a['code']); + unset($a['text']); + $rules[$k] = $a; + } + + $code .= 'var $method = '.var_export($method, true).";\n"; + $code .= 'var $a = '.var_export($rules, true).";\n"; + + + + $code .= "}\n"; + #echo $code; + return $code; + } +} +class lime_rhs { + function __construct() { + /** + Construct and add glyphs and actions in whatever order. + Then, add this to a lime_rewrite. + + Don't call install_rule. + The rewrite will do that for you when you "update" with it. + */ + $this->rhs = array(); + } + function add($slot) { + bug_unless($slot instanceof lime_slot); + $this->rhs[] = $slot; + } + function install_rule(lime $lime, $lhs) { + # This is the part that has to break the rule into subrules if necessary. + $rhs = $this->rhs; + # First, make sure this thing is well-formed. + if (!(end($rhs) instanceof lime_action)) $rhs[] = new lime_action('', NULL); + # Now, split it into chunks based on the actions. + + $lang = $lime->language(); + $result_code = $lang->default_result(); + $look = -1; + $subrule = array(); + $subsymbol = ''; + $preamble = ''; + while (count($rhs)) { + $it = array_shift($rhs); + $look ++; + if ($it instanceof lime_glyph) { + $subrule[] = $it->data; + } elseif ($it instanceof lime_action) { + $code = $it->data; + # It's an action. + # Is it the last one? + if (count($rhs)) { + # no. + $subsymbol = $lime->trump_up_bogus_lhs($lhs); + $action = $lang->default_result().$preamble.$code; + $lime->add_raw_rule($subsymbol, $subrule, $action, $look, false); + $subrule = array($subsymbol); + } else { + # yes. + $action = $result_code.$preamble.$code; + $lime->add_raw_rule($lhs, $subrule, $action, $look, true); + } + } else { + impossible(); + } + if ($it->name == '$') $result_code = $lang->result_pos($look); + elseif ($it->name) $preamble .= $lang->bind($it->name, $look); + } + } +} +class lime_rewrite { + function __construct($glyph) { + /** + Construct one of these with the name of the lhs. + Add some rhs-es to it. + Finally, "update" the lime you're building. + */ + $this->glyph = $glyph; + $this->rhs = array(); + } + function add_rhs($rhs) { + bug_unless($rhs instanceof lime_rhs); + $this->rhs[] = $rhs; + } + function update(lime $lime) { + foreach ($this->rhs as $rhs) { + $rhs->install_rule($lime, $this->glyph); + + } + } +} +class lime_slot { + /** + This keeps track of one position in an rhs. + We specialize to handle actions and glyphs. + If there is a name for the slot, we store it here. + Later on, this structure will be consulted in the formation of + actual production rules. + */ + function __construct($data, $name) { + $this->data = $data; + $this->name = $name; + } + function preamble($pos) { + if (strlen($this->name) > 0) { + return "\$$this->name =& \$tokens[$pos];\n"; + } + } +} +class lime_glyph extends lime_slot {} +class lime_action extends lime_slot {} +function lime_bootstrap() { + + /* + + This function isn't too terribly interesting to the casual observer. + You're probably better off looking at parse_lime_grammar() instead. + + Ok, if you insist, I'll explain. + + The input to Lime is a CFG parser definition. That definition is + written in some language. (The Lime language, to be exact.) + Anyway, I have to parse the Lime language and compile it into a + very complex data structure from which a parser is eventually + built. What better way than to use Lime itself to parse its own + language? Well, it's almost that simple, but not quite. + + The Lime language is fairly potent, but a restricted subset of + its features was used to write a metagrammar. Then, I hand-translated + that metagrammar into another form which is easy to snarf up. + In the process of reading that simplified form, this function + builds the same sort of data structure that later gets turned into + a parser. The last step is to run the parser generation algorithm, + eval() the resulting PHP code, and voila! With no hard work, I can + suddenly read and comprehend the full range of the Lime language + without ever having written an algorithm to do so. It feels like magic. + + */ + + $bootstrap = LIME_DIR."/lime.bootstrap"; + $lime = new lime(); + $lime->parser_class = 'lime_metaparser'; + $rhs = array(); + bug_unless(is_readable($bootstrap)); + foreach(file($bootstrap) as $l) { + $a = explode(":", $l, 2); + if (count($a) == 2) { + list($pattern, $code) = $a; + $sl = new lime_rhs(); + $pattern = trim($pattern); + if (strlen($pattern)>0) { + foreach (explode(' ', $pattern) as $glyph) $sl->add(new lime_glyph($glyph, NULL)); + } + $sl->add(new lime_action($code, NULL)); + $rhs[] = $sl; + } else { + $m = preg_match('/^to (\w+)$/', $l, $r); + if ($m == 0) continue; + $g = $r[1]; + $rw = new lime_rewrite($g); + foreach($rhs as $b) $rw->add_rhs($b); + $rw->update($lime); + $rhs = array(); + } + } + $parser_code = $lime->build_parser(); + eval($parser_code); +} + +class voodoo_scanner extends flex_scanner { + /* + + The voodoo is in the way I do lexical processing on grammar definition + files. They contain embedded bits of PHP, and it's important to keep + track of things like strings, comments, and matched braces. It seemed + like an ideal problem to solve with GNU flex, so I wrote a little + scanner in flex and C to dig out the tokens for me. Of course, I need + the tokens in PHP, so I designed a simple binary wrapper for them which + also contains line-number information, guaranteed to help out if you + write a grammar which surprises the parser in any manner. + + */ + function executable() { return LIME_DIR.'/lime_scan_tokens'; } +} + +function parse_lime_grammar($path) { + /* + + This is a good function to read because it teaches you how to interface + with a Lime parser. I've tried to isolate out the bits that aren't + instructive in that regard. + + */ + if (!class_exists('lime_metaparser')) lime_bootstrap(); + + $parse_engine = new parse_engine(new lime_metaparser()); + $scanner = new voodoo_scanner($path); + try { + # The result of parsing a Lime grammar is a Lime object. + $lime = $scanner->feed($parse_engine); + # Calling its build_parser() method gets the output PHP code. + return $lime->build_parser(); + } catch (parse_error $e) { + die ($e->getMessage()." in $path line $scanner->lineno.\n"); + } +} + + +if ($_SERVER['argv']) { + $code = ''; + array_shift($_SERVER['argv']); # Strip out the program name. + foreach ($_SERVER['argv'] as $path) { + $code .= parse_lime_grammar($path); + } + + echo " + +/* + +DON'T EDIT THIS FILE! + +This file was automatically generated by the Lime parser generator. +The real source code you should be looking at is in one or more +grammar files in the Lime format. + +THE ONLY REASON TO LOOK AT THIS FILE is to see where in the grammar +file that your error happened, because there are enough comments to +help you debug your grammar. + +If you ignore this warning, you're shooting yourself in the brain, +not the foot. + +*/ + +"{" { + lit(); + yy_push_state(code); +} + +. lit(); + + +{ +\n { + out("stop", "."); + yy_pop_state(); +} +[[:space:]] {} +{SYM} tok("sym"); +{LIT} tok("lit"); +. lit(); +} + +{ +"}" { + lit(); + yy_pop_state(); +} +'{SCHAR}*' php(); +\"{DCHAR}*\" php(); +{COM}.* php(); +{BLOCKCMT} php(); +[^{}'"#/]+ php(); +. php(); +} + +%% + +void lit() { + char lit[] = "'.'"; + lit[1] = *yytext; + out(lit, yytext); +} + +void tok(char*t) { + out(t, yytext); +} + +void php() { + out("php", yytext); +} + +void out(char*type, char*value) { + printf("%d\001%s\001%s", yylineno, type, value); + fputc(0, stdout); +} diff --git a/lime/metagrammar b/lime/metagrammar new file mode 100644 index 000000000..5d057c032 --- /dev/null +++ b/lime/metagrammar @@ -0,0 +1,58 @@ +# 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. + +# This is the grammar for all other grammar files that will work with the +# Lime LALR(1) Context-Free Grammar Parser Generator. +# You can read this to get an idea how things work, but this file is not +# actually used in the system. Rather, it's an implementation guide for the +# file "lime.bootstrap". + +%class lime_metaparser +%start grammar + +grammar += {$$ = new lime();} +| grammar/$ pragma/p toklist/t stop {$$->pragma($p, $t);} +| grammar/$ rewrite/r stop {$r->update($$);} +. + +rewrite += sym/s '=' rhs/r {$$ = new lime_rewrite($s); $$->add_rhs($r);} +| rewrite/$ '|' rhs/r {$$->add_rhs($r);} +. + +slot += action/a {$$ = new lime_action($a, NULL);} +| action/a lambda/l {$$ = new lime_action($a, $l);} +| sym/s {$$ = new lime_glyph($s, NULL);} +| sym/s lambda/l {$$ = new lime_glyph($s, $l);} +| lit/l {$$ = new lime_glyph($l, NULL);} +. + +rhs += {$$ = new lime_rhs();} +| rhs/$ slot/s {$$->add($s);} +. + +action = '{' code/$ '}' . + +toklist = {$$=array();} +| toklist/$ sym/s {$$[] = $s;} +| toklist/$ lit/l {$$[] = $l;} +. + +code = {} +| code/$ php/p {$$.=$p;} +| code/$ '{' code/c '}' {$$.='{'.$c.'}';} +. diff --git a/lime/parse_engine.php b/lime/parse_engine.php new file mode 100644 index 000000000..fd54cc462 --- /dev/null +++ b/lime/parse_engine.php @@ -0,0 +1,252 @@ +type = $type; + $this->state = $state; + } +} +class parse_premature_eof extends parse_error { + function __construct() { + parent::__construct("Premature EOF"); + } +} + + +class parse_stack { + function __construct($qi) { + $this->q = $qi; + $this->qs = array(); + $this->ss = array(); + } + function shift($q, $semantic) { + $this->ss[] = $semantic; + $this->qs[] = $this->q; + $this->q = $q; + # echo "Shift $q -- $semantic
\n"; + } + function top_n($n) { + if (!$n) return array(); + return array_slice($this->ss, 0-$n); + } + function pop_n($n) { + if (!$n) return array(); + $qq = array_splice($this->qs, 0-$n); + $this->q = $qq[0]; + return array_splice($this->ss, 0-$n); + } + function occupied() { return !empty($this->ss); } + function index($n) { + if ($n) $this->q = $this->qs[count($this->qs)-$n]; + } + function text() { + return $this->q." : ".implode(' . ', array_reverse($this->qs)); + } +} +class parse_engine { + function __construct($parser) { + $this->parser = $parser; + $this->qi = $parser->qi; + $this->rule = $parser->a; + $this->step = $parser->i; + #$this->prepare_callables(); + $this->reset(); + #$this->debug = false; + } + function reset() { + $this->accept = false; + $this->stack = new parse_stack($this->qi); + } + private function enter_error_tolerant_state() { + while ($this->stack->occupied()) { + if ($this->has_step_for('error')) return true; + $this->drop(); + }; + return false; + } + private function drop() { $this->stack->pop_n(1); } + function eat_eof() { + {/* + + So that I don't get any brilliant misguided ideas: + + The "accept" step happens when we try to eat a start symbol. + That happens because the reductions up the stack at the end + finally (and symetrically) tell the parser to eat a symbol + representing what they've just shifted off the end of the stack + and reduced. However, that doesn't put the parser into any + special different state. Therefore, it's back at the start + state. + + That being said, the parser is ready to reduce an EOF to the + empty program, if given a grammar that allows them. + + So anyway, if you literally tell the parser to eat an EOF + symbol, then after it's done reducing and accepting the prior + program, it's going to think it has another symbol to deal with. + That is the EOF symbol, which means to reduce the empty program, + accept it, and then continue trying to eat the terminal EOF. + + This infinte loop quickly runs out of memory. + + That's why the real EOF algorithm doesn't try to pretend that + EOF is a terminal. Like the invented start symbol, it's special. + + Instead, we pretend to want to eat EOF, but never actually + try to get it into the parse stack. (It won't fit.) In short, + we look up what reduction is indicated at each step in the + process of rolling up the parse stack. + + The repetition is because one reduction is not guaranteed to + cascade into another and clean up the entire parse stack. + Rather, it will instead shift each partial production as it + is forced to completion by the EOF lookahead. + */} + + # We must reduce as if having read the EOF symbol + do { + # and we have to try at least once, because if nothing + # has ever been shifted, then the stack will be empty + # at the start. + list($opcode, $operand) = $this->step_for('#'); + switch ($opcode) { + case 'r': $this->reduce($operand); break; + case 'e': $this->premature_eof(); break; + default: throw new parse_bug(); break; + } + } while ($this->stack->occupied()); + {/* + If the sentence is well-formed according to the grammar, then + this will eventually result in eating a start symbol, which + causes the "accept" instruction to fire. Otherwise, the + step('#') method will indicate an error in the syntax, which + here means a premature EOF. + + Incedentally, some tremendous amount of voodoo with the parse + stack might help find the beginning of some unfinished + production that the sentence was cut off during, but as a + general rule that would require deeper knowledge. + */} + if (!$this->accept) throw new parse_bug(); + return $this->semantic; + } + private function premature_eof() { + $seen = array(); + while ($this->enter_error_tolerant_state()) { + if (isset($seen[$this->state()])) { + // This means that it's pointless to try here. + // We're guaranteed that the stack is occupied. + $this->drop(); + continue; + } + $seen[$this->state()] = true; + + $this->eat('error', NULL); + if ($this->has_step_for('#')) { + // Good. We can continue as normal. + return; + } else { + // That attempt to resolve the error condition + // did not work. There's no point trying to + // figure out how much to slice off the stack. + // The rest of the algorithm will make it happen. + } + } + throw new parse_premature_eof(); + } + private function current_row() { return $this->step[$this->state()]; } + private function step_for($type) { + $row = $this->current_row(); + if (!isset($row[$type])) return array('e', $this->stack->q); + return explode(' ', $row[$type]); + } + private function has_step_for($type) { + $row = $this->current_row(); + return isset($row[$type]); + } + private function state() { return $this->stack->q; } + function eat($type, $semantic) { + # assert('$type == trim($type)'); + # if ($this->debug) echo "Trying to eat a ($type)\n"; + list($opcode, $operand) = $this->step_for($type); + switch ($opcode) { + case 's': + # if ($this->debug) echo "shift $type to state $operand\n"; + $this->stack->shift($operand, $semantic); + # echo $this->stack->text()." shift $type
\n"; + break; + + case 'r': + $this->reduce($operand); + $this->eat($type, $semantic); + # Yes, this is tail-recursive. It's also the simplest way. + break; + + case 'a': + if ($this->stack->occupied()) throw new parse_bug('Accept should happen with empty stack.'); + $this->accept = true; + #if ($this->debug) echo ("Accept\n\n"); + $this->semantic = $semantic; + break; + + case 'e': + # This is thought to be the uncommon, exceptional path, so + # it's OK that this algorithm will cause the stack to + # flutter while the parse engine waits for an edible token. + # if ($this->debug) echo "($type) causes a problem.\n"; + if ($this->enter_error_tolerant_state()) { + $this->eat('error', NULL); + if ($this->has_step_for($type)) $this->eat($type, $semantic); + } else { + # If that didn't work, give up: + throw new parse_error("Parse Error: ($type)($semantic) not expected"); + } + break; + + default: + throw new parse_bug("Bad parse table instruction ".htmlspecialchars($opcode)); + } + } + private function reduce($rule_id) { + $rule = $this->rule[$rule_id]; + $len = $rule['len']; + $semantic = $this->perform_action($rule_id, $this->stack->top_n($len)); + #echo $semantic.br(); + if ($rule['replace']) $this->stack->pop_n($len); + else $this->stack->index($len); + $this->eat($rule['symbol'], $semantic); + } + private function perform_action($rule_id, $slice) { + # we have this weird calling convention.... + $result = null; + $method = $this->parser->method[$rule_id]; + #if ($this->debug) echo "rule $id: $method\n"; + $this->parser->$method($slice, $result); + return $result; + } +} diff --git a/lime/set.so.php b/lime/set.so.php new file mode 100644 index 000000000..ef87c6cd7 --- /dev/null +++ b/lime/set.so.php @@ -0,0 +1,29 @@ +data = array_count_values($list); } + function has($item) { return isset($this->data[$item]); } + function add($item) { $this->data[$item] = true; } + function del($item) { unset($this->data[$item]); return $item;} + function all() { return array_keys($this->data); } + function one() { return key($this->data); } + function count() { return count($this->data); } + function pop() { return $this->del($this->one()); } + function union($that) { + $progress = false; + foreach ($that->all() as $item) if (!$this->has($item)) { + $this->add($item); + $progress = true; + } + return $progress; + } + function text() { + return ' { '.implode(' ', $this->all()).' } '; + } +} diff --git a/listener/CMakeLists.txt b/listener/CMakeLists.txt new file mode 100644 index 000000000..b45269967 --- /dev/null +++ b/listener/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(listener Listener.c) +target_link_libraries(listener system) diff --git a/listener/Listener.c b/listener/Listener.c new file mode 100644 index 000000000..b371ecade --- /dev/null +++ b/listener/Listener.c @@ -0,0 +1,127 @@ +/** + * @file Listener.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include +#include + +#include + +#include + +static void socket_handler (Listener *o, int event) +{ + ASSERT(event == BSOCKET_ACCEPT) + + o->accepted = 0; + + DebugIn_GoIn(&o->d_in_handler); + DEAD_ENTER(o->dead) + o->handler(o->user); + if (DEAD_LEAVE(o->dead)) { + return; + } + DebugIn_GoOut(&o->d_in_handler); + + // if there was no attempt to accept, do it now, discarding the client + if (!o->accepted) { + if (BSocket_Accept(&o->sock, NULL, NULL) < 0) { + BLog(BLOG_ERROR, "BSocket_Accept failed (%d)", BSocket_GetError(&o->sock)); + } + } +} + +int Listener_Init (Listener *o, BReactor *reactor, BAddr addr, Listener_handler handler, void *user) +{ + ASSERT(!BAddr_IsInvalid(&addr)) + + // init arguments + o->reactor = reactor; + o->handler = handler; + o->user = user; + + // init dead var + DEAD_INIT(o->dead); + + // create socket + if (BSocket_Init(&o->sock, o->reactor, addr.type, BSOCKET_TYPE_STREAM) < 0) { + BLog(BLOG_ERROR, "BSocket_Init failed"); + goto fail0; + } + + // bind socket + if (BSocket_Bind(&o->sock, &addr) < 0) { + BLog(BLOG_ERROR, "BSocket_Bind failed (%d)", BSocket_GetError(&o->sock)); + goto fail1; + } + + // listen socket + if (BSocket_Listen(&o->sock, -1) < 0) { + BLog(BLOG_ERROR, "BSocket_Listen failed (%d)", BSocket_GetError(&o->sock)); + goto fail1; + } + + // register socket event handler + BSocket_AddEventHandler(&o->sock, BSOCKET_ACCEPT, (BSocket_handler)socket_handler, o); + BSocket_EnableEvent(&o->sock, BSOCKET_ACCEPT); + + // init debug in handler + DebugIn_Init(&o->d_in_handler); + + // init debug object + DebugObject_Init(&o->d_obj); + + return 1; + +fail1: + BSocket_Free(&o->sock); +fail0: + return 0; +} + +void Listener_Free (Listener *o) +{ + // free debug object + DebugObject_Free(&o->d_obj); + + // free socket + BSocket_Free(&o->sock); + + // free dead var + DEAD_KILL(o->dead); +} + +int Listener_Accept (Listener *o, BSocket *sockout, BAddr *addrout) +{ + ASSERT(sockout) + ASSERT(DebugIn_In(&o->d_in_handler)) + + o->accepted = 1; + + if (BSocket_Accept(&o->sock, sockout, addrout) < 0) { + BLog(BLOG_ERROR, "BSocket_Accept failed (%d)", BSocket_GetError(&o->sock)); + return 0; + } + + return 1; +} diff --git a/listener/Listener.h b/listener/Listener.h new file mode 100644 index 000000000..781ba6652 --- /dev/null +++ b/listener/Listener.h @@ -0,0 +1,93 @@ +/** + * @file Listener.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Object used to listen on a socket and accept clients. + */ + +#ifndef BADVPN_LISTENER_LISTENER_H +#define BADVPN_LISTENER_LISTENER_H + +#include +#include +#include +#include +#include + +/** + * Handler function called when it may be possible to accept a client. + * The user can call {@link Listener_Accept} from this handler to accept + * clients. + * If the user does not call {@link Listener_Accept}, a newly connected + * client may be disconnected. + * + * @param user as in {@link Listener_Init} + */ +typedef void (*Listener_handler) (void *user); + +/** + * Object used to listen on a socket and accept clients. + */ +typedef struct { + dead_t dead; + BReactor *reactor; + BSocket sock; + Listener_handler handler; + void *user; + int accepted; + DebugIn d_in_handler; + DebugObject d_obj; +} Listener; + +/** + * Initializes the object. + * + * @param o the object + * @param reactor reactor we live in + * @param addr address to listen on. Must not be invalid. + * @param handler handler function called when a connection should be accepted + * @param user value to pass to handler function + * @return 1 on success, 0 on failure + */ +int Listener_Init (Listener *o, BReactor *reactor, BAddr addr, Listener_handler handler, void *user) WARN_UNUSED; + +/** + * Frees the object. + * + * @param o the object + */ +void Listener_Free (Listener *o); + +/** + * Accepts a connection. + * Must be called from within the {@link Listener_handler} handler. + * + * @param o the object + * @param sockout uninitialized {@link BSocket} structure to put the new socket in. + * Must not be NULL. + * @param addrout if not NULL, the address of the client will be returned here + * on success + * @return 1 on success, 0 on failure + */ +int Listener_Accept (Listener *o, BSocket *sockout, BAddr *addrout) WARN_UNUSED; + +#endif diff --git a/misc/balign.h b/misc/balign.h new file mode 100644 index 000000000..5a6fffa1b --- /dev/null +++ b/misc/balign.h @@ -0,0 +1,64 @@ +/** + * @file balign.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Integer alignment macros. + */ + +#ifndef BADVPN_MISC_BALIGN_H +#define BADVPN_MISC_BALIGN_H + +#include + +/** + * Aligns the integer x up to n bytes. + */ +#define BALIGN_UP_N(x,n) \ + ({\ + typeof (x) _x = (x);\ + typeof (n) _n = (n);\ + typeof (x) _r = _x % _n;\ + _r ? _x + (_n - _r) : _x;\ + }) + +/** + * Aligns the integer x down to n bytes. + */ +#define BALIGN_DOWN_N(x,n) \ + ({\ + typeof (x) _x = (x);\ + typeof (n) _n = (n);\ + _x - (_x % _n);\ + }) + +/** + * Calculates the quotient of integers a and b, rounded up. + */ +#define BDIVIDE_UP(a,b) \ + ({\ + typeof (a) _a = (a);\ + typeof (b) _b = (b);\ + typeof (a) _r = _a % _b;\ + _r > 0 ? _a / _b + 1 : _a / _b;\ + }) + +#endif diff --git a/misc/brandom.h b/misc/brandom.h new file mode 100644 index 000000000..0c324a516 --- /dev/null +++ b/misc/brandom.h @@ -0,0 +1,50 @@ +/** + * @file brandom.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Random data generation function. + */ + +#ifndef BADVPN_MISC_BRANDOM_H +#define BADVPN_MISC_BRANDOM_H + +#include + +#include + +#include + +/** + * Generates random data. + * + * @param buf buffer to write data into + * @param len number of bytes to generate. Must be >=0. + */ +static void brandom_randomize (uint8_t *buf, int len) +{ + ASSERT(len >= 0) + + DEBUG_ZERO_MEMORY(buf, len) + ASSERT_FORCE(RAND_bytes(buf, len) == 1) +} + +#endif diff --git a/misc/byteorder.h b/misc/byteorder.h new file mode 100644 index 000000000..69a6bc661 --- /dev/null +++ b/misc/byteorder.h @@ -0,0 +1,185 @@ +/** + * @file byteorder.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Byte order conversion functions. + * + * hton* functions convert from host to big-endian (network) byte order. + * htol* functions convert from host to little-endian byte order. + * ntoh* functions convert from big-endian (network) to host byte order. + * ltoh* functions convert from little-endian to host byte order. + */ + +#ifndef BADVPN_MISC_BYTEORDER_H +#define BADVPN_MISC_BYTEORDER_H + +#include + +static uint16_t badvpn_reverse16 (uint16_t x) +{ + uint16_t y; + *((uint8_t *)&y+0) = *((uint8_t *)&x+1); + *((uint8_t *)&y+1) = *((uint8_t *)&x+0); + return y; +} + +static uint32_t badvpn_reverse32 (uint32_t x) +{ + uint32_t y; + *((uint8_t *)&y+0) = *((uint8_t *)&x+3); + *((uint8_t *)&y+1) = *((uint8_t *)&x+2); + *((uint8_t *)&y+2) = *((uint8_t *)&x+1); + *((uint8_t *)&y+3) = *((uint8_t *)&x+0); + return y; +} + +static uint64_t badvpn_reverse64 (uint64_t x) +{ + uint64_t y; + *((uint8_t *)&y+0) = *((uint8_t *)&x+7); + *((uint8_t *)&y+1) = *((uint8_t *)&x+6); + *((uint8_t *)&y+2) = *((uint8_t *)&x+5); + *((uint8_t *)&y+3) = *((uint8_t *)&x+4); + *((uint8_t *)&y+4) = *((uint8_t *)&x+3); + *((uint8_t *)&y+5) = *((uint8_t *)&x+2); + *((uint8_t *)&y+6) = *((uint8_t *)&x+1); + *((uint8_t *)&y+7) = *((uint8_t *)&x+0); + return y; +} + +static uint8_t hton8 (uint8_t x) +{ + return x; +} + +static uint8_t htol8 (uint8_t x) +{ + return x; +} + +#if defined(BADVPN_LITTLE_ENDIAN) + +static uint16_t hton16 (uint16_t x) +{ + return badvpn_reverse16(x); +} + +static uint32_t hton32 (uint32_t x) +{ + return badvpn_reverse32(x); +} + +static uint64_t hton64 (uint64_t x) +{ + return badvpn_reverse64(x); +} + +static uint16_t htol16 (uint16_t x) +{ + return x; +} + +static uint32_t htol32 (uint32_t x) +{ + return x; +} + +static uint64_t htol64 (uint64_t x) +{ + return x; +} + +#elif defined(BADVPN_BIG_ENDIAN) + +static uint16_t hton16 (uint16_t x) +{ + return x; +} + +static uint32_t hton32 (uint32_t x) +{ + return x; +} + +static uint64_t hton64 (uint64_t x) +{ + return x; +} + +static uint16_t htol16 (uint16_t x) +{ + return badvpn_reverse16(x); +} + +static uint32_t htol32 (uint32_t x) +{ + return badvpn_reverse32(x); +} + +static uint64_t htol64 (uint64_t x) +{ + return badvpn_reverse64(x); +} + +#endif + +static uint8_t ntoh8 (uint8_t x) +{ + return hton8(x); +} + +static uint16_t ntoh16 (uint16_t x) +{ + return hton16(x); +} + +static uint32_t ntoh32 (uint32_t x) +{ + return hton32(x); +} + +static uint64_t ntoh64 (uint64_t x) +{ + return hton64(x); +} + +static uint8_t ltoh8 (uint8_t x) +{ + return htol8(x); +} + +static uint16_t ltoh16 (uint16_t x) +{ + return htol16(x); +} + +static uint32_t ltoh32 (uint32_t x) +{ + return htol32(x); +} + +static uint64_t ltoh64 (uint64_t x) +{ + return htol64(x); +} + +#endif diff --git a/misc/dead.h b/misc/dead.h new file mode 100644 index 000000000..6319be733 --- /dev/null +++ b/misc/dead.h @@ -0,0 +1,123 @@ +/** + * @file dead.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Dead mechanism definitions. + * + * The dead mechanism is a way for a piece of code to detect whether some + * specific event has occured during some operation (usually during calling + * a user-provided handler function), without requiring access to memory + * that might no longer be available because of the event. + * + * It works somehow like that: + * + * First a dead variable ({@link dead_t}) is allocated somewhere, and + * initialized with {@link DEAD_INIT}, e.g.: + * DEAD_INIT(dead); + * + * When the event that needs to be caught occurs, {@link DEAD_KILL} is + * called, e.g.: + * DEAD_KILL(dead); + * The memory used by the dead variable is no longer needed after + * that. + * + * If a piece of code needs to know whether the event occured during some + * operation (but it must not have occured before!), it puts {@link DEAD_ENTER}} + * in front of the operation, and does {@link DEAD_LEAVE} at the end. If + * {@link DEAD_LEAVE} returned nonzero, the event occured, otherwise it did + * not. Example: + * DEAD_ENTER(dead) + * HandlerFunction(); + * if (DEAD_LEAVE(dead)) { + * (event occured) + * } + * + * If is is needed to check for the event more than once in a single block, + * {@link DEAD_DECLARE} should be put somewhere before, and {@link DEAD_ENTER2} + * should be used instead of {@link DEAD_ENTER}. + * + * If it is needed to check for multiple events (dead variables) at the same + * time, DEAD_*_N macros should be used instead, specifying different + * identiers as the first argument for different dead variables. + */ + +#ifndef BADVPN_MISC_DEAD_H +#define BADVPN_MISC_DEAD_H + +#include + +/** + * Dead variable. + */ +typedef int *dead_t; + +/** + * Initializes a dead variable. + */ +#define DEAD_INIT(ptr) ({ptr = NULL;}) + +/** + * Kills the dead variable, + */ +#define DEAD_KILL(ptr) ({if (ptr) *(ptr) = 1;}) + +/** + * Declares dead catching variables. + */ +#define DEAD_DECLARE int __dead; dead_t __prev_ptr; + +/** + * Enters a dead catching using already declared dead catching variables. + * The dead variable must have been initialized with {@link DEAD_INIT}, + * and {@link DEAD_KILL} must not have been called yet. + * {@link DEAD_LEAVE} must be called before the current scope is left. + */ +#define DEAD_ENTER2(ptr) {__dead = 0; __prev_ptr = ptr; ptr = &__dead;} + +/** + * Declares dead catching variables and enters a dead catching. + * The dead variable must have been initialized with {@link DEAD_INIT}, + * and {@link DEAD_KILL} must not have been called yet. + * {@link DEAD_LEAVE} must be called before the current scope is left. + */ +#define DEAD_ENTER(ptr) DEAD_DECLARE DEAD_ENTER2(ptr) + +/** + * Leaves a dead catching. + * Returns 1 if {@link DEAD_KILL} was called for the dead variable, 0 otherwise. + * Must be called after entering a dead catching and before leaving it. + */ +#define DEAD_LEAVE(ptr) ({if (!__dead) ptr = __prev_ptr; if (__prev_ptr) *__prev_ptr = __dead; __dead;}) + +/** + * Returns 1 if {@link DEAD_KILL} was called for the dead variable, 0 otherwise. + * Must be called after entering a dead catching. + */ +#define DEAD_KILLED (__dead) + +#define DEAD_DECLARE_N(n) int __dead##n; dead_t __prev_ptr##n; +#define DEAD_ENTER2_N(n, ptr) {__dead##n = 0; __prev_ptr##n = ptr; ptr = &__dead##n;} +#define DEAD_ENTER_N(n, ptr) DEAD_DECLARE_N(n) DEAD_ENTER2_N(n, ptr) +#define DEAD_LEAVE_N(n, ptr) ({if (!__dead##n) ptr = __prev_ptr##n; if (__prev_ptr##n) *__prev_ptr##n = __dead##n; __dead##n;}) +#define DEAD_KILLED_N(n) (__dead##n) + +#endif diff --git a/misc/debug.h b/misc/debug.h new file mode 100644 index 000000000..2c6f1064b --- /dev/null +++ b/misc/debug.h @@ -0,0 +1,131 @@ +/** + * @file debug.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Debugging macros. + */ + +/** + * @def DEBUG + * + * Macro for printing debugging text. Use the same way as printf, + * but without a newline. + * Prepends "function_name: " and appends a newline. + */ + +/** + * @def ASSERT_FORCE + * + * Macro for forced assertions. + * Evaluates the argument and terminates the program abnormally + * if the result is false. + * Expands to an expression with the type and value of the result + * of the evaluation. + */ + +/** + * @def ASSERT + * + * Macro for assertions. + * The argument may or may not be evaluated. + * If the argument is evaluated, it must not evaluate to false. + * Expands to an expression of type void. + */ + +/** + * @def ASSERT_EXECUTE + * + * Macro for always-evaluated assertions. + * The argument is evaluated. + * The argument must not evaluate to false. + * Expands to an expression with the type and value of the result + * of the evaluation. + */ + +/** + * @def DEBUG_ZERO_MEMORY + * + * If debugging is enabled, zeroes the given memory region. + * First argument is pointer to the memory region, second is + * number of bytes. + */ + +/** + * @def WARN_UNUSED + * + * Tells the compiler that the result of a function should not be unused. + * Insert at the end of the declaration of a function before the semicolon. + */ + +#ifndef BADVPN_MISC_DEBUG_H +#define BADVPN_MISC_DEBUG_H + +#include +#include +#include +#include +#include + +#define DEBUG(...) \ + { \ + fprintf(stderr, "%s: ", __FUNCTION__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + } + +#define ASSERT_FORCE(e) \ + { \ + typeof (e) _assert_res = (e); \ + if (!_assert_res) { \ + fprintf(stderr, "%s:%d Assertion failed\n", __FILE__, __LINE__); \ + abort(); \ + } \ + _assert_res; \ + } + +#ifdef NDEBUG + #define DEBUG_ZERO_MEMORY(buf, len) + #define ASSERT(e) { ; } + #define ASSERT_EXECUTE(e) { (e); } +#else + #define DEBUG_ZERO_MEMORY(buf, len) { memset((buf), 0, (len)); } + #ifdef BADVPN_USE_C_ASSERT + #define ASSERT(e) { assert(e); ; } + #define ASSERT_EXECUTE(e) \ + { \ + typeof (e) _assert_res = (e); \ + assert(_assert_res); \ + _assert_res; \ + } + #else + #define ASSERT(e) { ASSERT_FORCE(e); ; } + #define ASSERT_EXECUTE(e) ASSERT_FORCE(e) + #endif +#endif + +#ifdef __GNUC__ + #define WARN_UNUSED __attribute((warn_unused_result)) +#else + #define WARN_UNUSED +#endif + +#endif diff --git a/misc/debugcounter.h b/misc/debugcounter.h new file mode 100644 index 000000000..5328b2b0a --- /dev/null +++ b/misc/debugcounter.h @@ -0,0 +1,111 @@ +/** + * @file debugcounter.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Counter for detecting leaks. + */ + +#ifndef BADVPN_MISC_DEBUGCOUNTER_H +#define BADVPN_MISC_DEBUGCOUNTER_H + +#include + +#include + +/** + * Counter for detecting leaks. + */ +typedef struct { +#ifndef NDEBUG + int32_t c; +#endif +} DebugCounter; + +#ifndef NDEBUG +#define DEBUGCOUNTER_STATIC { .c = 0 } +#else +#define DEBUGCOUNTER_STATIC {} +#endif + +/** + * Initializes the object. + * The object is initialized with counter value zero. + * + * @param obj the object + */ +static void DebugCounter_Init (DebugCounter *obj) +{ +#ifndef NDEBUG + obj->c = 0; +#endif +} + +/** + * Frees the object. + * This does not have to be called when the counter is no longer needed. + * The counter value must be zero. + * + * @param obj the object + */ +static void DebugCounter_Free (DebugCounter *obj) +{ +#ifndef NDEBUG + ASSERT(obj->c == 0 || obj->c == INT32_MAX) +#endif +} + +/** + * Increments the counter. + * Increments the counter value by one. + * + * @param obj the object + */ +static void DebugCounter_Increment (DebugCounter *obj) +{ +#ifndef NDEBUG + ASSERT(obj->c >= 0) + + if (obj->c != INT32_MAX) { + obj->c++; + } +#endif +} + +/** + * Decrements the counter. + * The counter value must be >0. + * Decrements the counter value by one. + * + * @param obj the object + */ +static void DebugCounter_Decrement (DebugCounter *obj) +{ +#ifndef NDEBUG + ASSERT(obj->c > 0) + + if (obj->c != INT32_MAX) { + obj->c--; + } +#endif +} + +#endif diff --git a/misc/debugin.h b/misc/debugin.h new file mode 100644 index 000000000..15d520260 --- /dev/null +++ b/misc/debugin.h @@ -0,0 +1,142 @@ +/** + * @file debugin.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Object for detecting wrong call paths. + */ + +#ifndef BADVPN_MISC_DEBUGIN_H +#define BADVPN_MISC_DEBUGIN_H + +#include + +/** + * Object for detecting wrong call paths. + */ +typedef struct { + #ifndef NDEBUG + int in; + #endif +} DebugIn; + +/** + * Initializes the object. + * The object is initialized in not in state. + * + * @param o the object + */ +static void DebugIn_Init (DebugIn *o); + +/** + * Puts the object into in state. + * The object must be in not in state. + * The object enters in state. + * + * @param o the object + */ +static void DebugIn_GoIn (DebugIn *o); + +/** + * Puts the object into not in state. + * The object must be in in state. + * The object enters not in state. + * + * @param o the object + */ +static void DebugIn_GoOut (DebugIn *o); + +/** + * Does nothing. + * The object must be in in state. + * + * @param o the object + */ +static void DebugIn_AmIn (DebugIn *o); + +/** + * Does nothing. + * The object must be in not in state. + * + * @param o the object + */ +static void DebugIn_AmOut (DebugIn *o); + +#ifndef NDEBUG + +/** + * Checks if the object is in in state. + * Only available if NDEBUG is not defined. + * + * @param o the object + * @return 1 if in in state, 0 if in not in state + */ +static int DebugIn_In (DebugIn *o); + +#endif + +void DebugIn_Init (DebugIn *o) +{ + #ifndef NDEBUG + o->in = 0; + #endif +} + +void DebugIn_GoIn (DebugIn *o) +{ + ASSERT(o->in == 0) + + #ifndef NDEBUG + o->in = 1; + #endif +} + +void DebugIn_GoOut (DebugIn *o) +{ + ASSERT(o->in == 1) + + #ifndef NDEBUG + o->in = 0; + #endif +} + +void DebugIn_AmIn (DebugIn *o) +{ + ASSERT(o->in == 1) +} + +void DebugIn_AmOut (DebugIn *o) +{ + ASSERT(o->in == 0) +} + +#ifndef NDEBUG + +int DebugIn_In (DebugIn *o) +{ + ASSERT(o->in == 0 || o->in == 1) + + return o->in; +} + +#endif + +#endif diff --git a/misc/ethernet_proto.h b/misc/ethernet_proto.h new file mode 100644 index 000000000..f9498c242 --- /dev/null +++ b/misc/ethernet_proto.h @@ -0,0 +1,40 @@ +/** + * @file ethernet_proto.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Definitions for the Ethernet protocol. + */ + +#ifndef BADVPN_MISC_ETHERNET_PROTO_H +#define BADVPN_MISC_ETHERNET_PROTO_H + +#include + +#define ETHERTYPE_IPV4 0x0800 + +struct ethernet_header { + uint8_t dest[6]; + uint8_t source[6]; + uint16_t type; +} __attribute__((packed)); + +#endif diff --git a/misc/igmp_proto.h b/misc/igmp_proto.h new file mode 100644 index 000000000..3f3ea196f --- /dev/null +++ b/misc/igmp_proto.h @@ -0,0 +1,81 @@ +/** + * @file igmp_proto.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Definitions for the IGMP protocol. + */ + +#ifndef BADVPN_MISC_IGMP_PROTO_H +#define BADVPN_MISC_IGMP_PROTO_H + +#include + +#define IGMP_TYPE_MEMBERSHIP_QUERY 0x11 +#define IGMP_TYPE_V1_MEMBERSHIP_REPORT 0x12 +#define IGMP_TYPE_V2_MEMBERSHIP_REPORT 0x16 +#define IGMP_TYPE_V3_MEMBERSHIP_REPORT 0x22 +#define IGMP_TYPE_V2_LEAVE_GROUP 0x17 + +#define IGMP_RECORD_TYPE_MODE_IS_INCLUDE 1 +#define IGMP_RECORD_TYPE_MODE_IS_EXCLUDE 2 +#define IGMP_RECORD_TYPE_CHANGE_TO_INCLUDE_MODE 3 +#define IGMP_RECORD_TYPE_CHANGE_TO_EXCLUDE_MODE 4 + +#define GET_MRC_EXP(_mrc) (((_mrc)&0x70)>>4) +#define GET_MRC_MANT(_mrc) (((_mrc)&0x0F)>>0) + +#define GET_MRC_MRT(_mrc) ((_mrc)<128?((uint16_t)(_mrc)):(((uint16_t)(GET_MRC_MANT(_mrc)|0x10))<<(GET_MRC_EXP(_mrc)+3))) + +struct igmp_source { + uint32_t addr; +} __attribute__((packed)); + +struct igmp_base { + uint8_t type; + uint8_t max_resp_code; + uint16_t checksum; +} __attribute__((packed)); + +struct igmp_v3_query_extra { + uint32_t group; + uint8_t reserved4_suppress1_qrv3; + uint8_t qqic; + uint16_t number_of_sources; +} __attribute__((packed)); + +struct igmp_v3_report_extra { + uint16_t reserved; + uint16_t number_of_group_records; +} __attribute__((packed)); + +struct igmp_v3_report_record { + uint8_t type; + uint8_t aux_data_len; + uint16_t number_of_sources; + uint32_t group; +} __attribute__((packed)); + +struct igmp_v2_extra { + uint32_t group; +} __attribute__((packed)); + +#endif diff --git a/misc/ipv4_proto.h b/misc/ipv4_proto.h new file mode 100644 index 000000000..f2cde884d --- /dev/null +++ b/misc/ipv4_proto.h @@ -0,0 +1,54 @@ +/** + * @file ipv4_proto.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Definitions for the IPv4 protocol. + */ + +#ifndef BADVPN_MISC_IPV4_PROTO_H +#define BADVPN_MISC_IPV4_PROTO_H + +#include + +#define IPV4_PROTOCOL_IGMP 2 + +struct ipv4_header { + uint8_t version4_ihl4; + uint8_t ds; + uint16_t total_length; + // + uint16_t identification; + uint16_t flags3_fragmentoffset13; + // + uint8_t ttl; + uint8_t protocol; + uint16_t checksum; + // + uint32_t source_address; + // + uint32_t destination_address; +} __attribute__((packed)); + +#define IPV4_GET_VERSION(_header) (((_header).version4_ihl4&0xF0)>>4) +#define IPV4_GET_IHL(_header) (((_header).version4_ihl4&0x0F)>>0) + +#endif diff --git a/misc/jenkins_hash.h b/misc/jenkins_hash.h new file mode 100644 index 000000000..7ab5e37c7 --- /dev/null +++ b/misc/jenkins_hash.h @@ -0,0 +1,111 @@ +/** + * @file jenkins_hash.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Jenkins hash functions for use in hash tables. + * The resulting hashes depend on the endianness of the machine. + */ + +#ifndef BADVPN_MISC_JENKINS_HASH_H +#define BADVPN_MISC_JENKINS_HASH_H + +#include + +static uint32_t jenkins_one_at_a_time_hash (uint8_t *key, int key_len); +static uint32_t jenkins_lookup2_hash (uint8_t *k, uint32_t length, uint32_t initval); + +uint32_t jenkins_one_at_a_time_hash (uint8_t *key, int key_len) +{ + uint32_t hash = 0; + int i; + + for (i = 0; i < key_len; i++) { + hash += key[i]; + hash += (hash << 10); + hash ^= (hash >> 6); + } + + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + + return hash; +} + +#define lookup2_hashsize(n) ((uint32_t)1<<(n)) +#define lookup2_hashmask(n) (hashsize(n)-1) +#define lookup2_mix(a,b,c) \ +{ \ + a -= b; a -= c; a ^= (c>>13); \ + b -= c; b -= a; b ^= (a<<8); \ + c -= a; c -= b; c ^= (b>>13); \ + a -= b; a -= c; a ^= (c>>12); \ + b -= c; b -= a; b ^= (a<<16); \ + c -= a; c -= b; c ^= (b>>5); \ + a -= b; a -= c; a ^= (c>>3); \ + b -= c; b -= a; b ^= (a<<10); \ + c -= a; c -= b; c ^= (b>>15); \ +} + +uint32_t jenkins_lookup2_hash (uint8_t *k, uint32_t length, uint32_t initval) +{ + uint32_t a,b,c,len; + + /* Set up the internal state */ + len = length; + a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */ + c = initval; /* the previous hash value */ + + /*---------------------------------------- handle most of the key */ + while (len >= 12) + { + a += (k[0] +((uint32_t)k[1]<<8) +((uint32_t)k[2]<<16) +((uint32_t)k[3]<<24)); + b += (k[4] +((uint32_t)k[5]<<8) +((uint32_t)k[6]<<16) +((uint32_t)k[7]<<24)); + c += (k[8] +((uint32_t)k[9]<<8) +((uint32_t)k[10]<<16)+((uint32_t)k[11]<<24)); + lookup2_mix(a,b,c); + k += 12; len -= 12; + } + + /*------------------------------------- handle the last 11 bytes */ + c += length; + switch(len) /* all the case statements fall through */ + { + case 11: c+=((uint32_t)k[10]<<24); + case 10: c+=((uint32_t)k[9]<<16); + case 9 : c+=((uint32_t)k[8]<<8); + /* the first byte of c is reserved for the length */ + case 8 : b+=((uint32_t)k[7]<<24); + case 7 : b+=((uint32_t)k[6]<<16); + case 6 : b+=((uint32_t)k[5]<<8); + case 5 : b+=k[4]; + case 4 : a+=((uint32_t)k[3]<<24); + case 3 : a+=((uint32_t)k[2]<<16); + case 2 : a+=((uint32_t)k[1]<<8); + case 1 : a+=k[0]; + /* case 0: nothing left to add */ + } + lookup2_mix(a,b,c); + /*-------------------------------------------- report the result */ + return c; +} + +#endif diff --git a/misc/loggers_string.h b/misc/loggers_string.h new file mode 100644 index 000000000..36b120353 --- /dev/null +++ b/misc/loggers_string.h @@ -0,0 +1,36 @@ +/** + * @file loggers_string.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * List of available loggers. + */ + +#ifndef BADVPN_MISC_LOGGERSSTRING_H +#define BADVPN_MISC_LOGGERSSTRING_H + +#ifdef BADVPN_USE_WINAPI +#define LOGGERS_STRING "stdout" +#else +#define LOGGERS_STRING "stdout/syslog" +#endif + +#endif diff --git a/misc/loglevel.h b/misc/loglevel.h new file mode 100644 index 000000000..854c0a44f --- /dev/null +++ b/misc/loglevel.h @@ -0,0 +1,73 @@ +/** + * @file loglevel.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Log level specification parsing function. + */ + +#ifndef BADVPN_MISC_LOGLEVEL_H +#define BADVPN_MISC_LOGLEVEL_H + +#include + +#include + +/** + * Parses the log level string. + * + * @param str log level string. Recognizes none, error, warning, notice, + * info, debug. + * @return 0 for none, one of BLOG_* for some log level, -1 for unrecognized + */ +static int parse_loglevel (char *str); + +int parse_loglevel (char *str) +{ + if (!strcmp(str, "none")) { + return 0; + } + if (!strcmp(str, "error")) { + return BLOG_ERROR; + } + if (!strcmp(str, "warning")) { + return BLOG_WARNING; + } + if (!strcmp(str, "notice")) { + return BLOG_NOTICE; + } + if (!strcmp(str, "info")) { + return BLOG_INFO; + } + if (!strcmp(str, "debug")) { + return BLOG_DEBUG; + } + + char *endptr; + int res = strtol(str, &endptr, 10); + if (*str && !*endptr && res >= 0 && res <= BLOG_DEBUG) { + return res; + } + + return -1; +} + +#endif diff --git a/misc/minmax.h b/misc/minmax.h new file mode 100644 index 000000000..1ee0da5dc --- /dev/null +++ b/misc/minmax.h @@ -0,0 +1,44 @@ +/** + * @file minmax.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Minimum and maximum macros. + */ + +#ifndef BADVPN_MISC_MINMAX_H +#define BADVPN_MISC_MINMAX_H + +#define BMIN(a,b) \ + ({\ + typeof (a) _a = (a);\ + typeof (b) _b = (b);\ + (_a < _b ? _a : _b);\ + }) + +#define BMAX(a,b) \ + ({\ + typeof (a) _a = (a);\ + typeof (b) _b = (b);\ + (_a > _b ? _a : _b);\ + }) + +#endif diff --git a/misc/modadd.h b/misc/modadd.h new file mode 100644 index 000000000..4fd67713e --- /dev/null +++ b/misc/modadd.h @@ -0,0 +1,48 @@ +/** + * @file modadd.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Modular addition macro. + */ + +#ifndef BADVPN_MISC_MODADD_H +#define BADVPN_MISC_MODADD_H + +#include + +/** + * Calculates (x + y) mod m, assuming + * 0 <= x < m and 0 <= y < m. + */ +#define BMODADD(x, y, m) \ + ({ \ + typeof (x) _modadd_x = (x); \ + typeof (y) _modadd_y = (y); \ + typeof (m) _modadd_m = (m); \ + ASSERT(_modadd_x >= 0) \ + ASSERT(_modadd_x < _modadd_m) \ + ASSERT(_modadd_y >= 0) \ + ASSERT(_modadd_y < _modadd_m) \ + (_modadd_y >= _modadd_m - _modadd_x ? _modadd_y - (_modadd_m - _modadd_x) : _modadd_x + _modadd_y); \ + }) + +#endif diff --git a/misc/mswsock.h b/misc/mswsock.h new file mode 100644 index 000000000..504b17a25 --- /dev/null +++ b/misc/mswsock.h @@ -0,0 +1,89 @@ +/** + * @file mswsock.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * WinSock MS extensions definitions. + * + * This file exists because MinGW's headers are unpredictable and don't + * contain everything we need. + */ + +#ifndef BADVPN_MISC_MSWSOCK_H +#define BADVPN_MISC_MSWSOCK_H + +#if !defined(__GNUC__) || defined(BADVPN_USE_SYSTEM_MSWSOCK_H) +#include +#else + +#include +#include + +#ifdef _WIN64 +#define BADVPN_MAX_NATURAL_ALIGNMENT sizeof(ULONGLONG) +#define BADVPN_MEMORY_ALLOCATION_ALIGNMENT 16 +#else +#define BADVPN_MAX_NATURAL_ALIGNMENT sizeof(DWORD) +#define BADVPN_MEMORY_ALLOCATION_ALIGNMENT 8 +#endif + +#ifdef __cplusplus +#define BADVPN_TYPE_ALIGNMENT(t) __alignof__ (t) +#else +#define BADVPN_TYPE_ALIGNMENT(t) FIELD_OFFSET(struct { char x; t test; },test) +#endif + +typedef struct _WSAMSG { +LPSOCKADDR name; +INT namelen; +LPWSABUF lpBuffers; +DWORD dwBufferCount; +WSABUF Control; +DWORD dwFlags; +} WSAMSG,*PWSAMSG,*LPWSAMSG; + +typedef struct _WSACMSGHDR { +SIZE_T cmsg_len; +INT cmsg_level; +INT cmsg_type; +} WSACMSGHDR,*PWSACMSGHDR,*LPWSACMSGHDR; + +#define WSA_CMSGHDR_ALIGN(length) (((length) + BADVPN_TYPE_ALIGNMENT(WSACMSGHDR)-1) & (~(BADVPN_TYPE_ALIGNMENT(WSACMSGHDR)-1))) +#define WSA_CMSGDATA_ALIGN(length) (((length) + BADVPN_MAX_NATURAL_ALIGNMENT-1) & (~(BADVPN_MAX_NATURAL_ALIGNMENT-1))) +#define WSA_CMSG_FIRSTHDR(msg) (((msg)->Control.len >= sizeof(WSACMSGHDR)) ? (LPWSACMSGHDR)(msg)->Control.buf : (LPWSACMSGHDR)NULL) +#define WSA_CMSG_NXTHDR(msg,cmsg) ((!(cmsg)) ? WSA_CMSG_FIRSTHDR(msg) : ((((u_char *)(cmsg) + WSA_CMSGHDR_ALIGN((cmsg)->cmsg_len) + sizeof(WSACMSGHDR)) > (u_char *)((msg)->Control.buf) + (msg)->Control.len) ? (LPWSACMSGHDR)NULL : (LPWSACMSGHDR)((u_char *)(cmsg) + WSA_CMSGHDR_ALIGN((cmsg)->cmsg_len)))) +#define WSA_CMSG_DATA(cmsg) ((u_char *)(cmsg) + WSA_CMSGDATA_ALIGN(sizeof(WSACMSGHDR))) +#define WSA_CMSG_SPACE(length) (WSA_CMSGDATA_ALIGN(sizeof(WSACMSGHDR) + WSA_CMSGHDR_ALIGN(length))) +#define WSA_CMSG_LEN(length) (WSA_CMSGDATA_ALIGN(sizeof(WSACMSGHDR)) + length) + +#define WSAID_WSARECVMSG {0xf689d7c8,0x6f1f,0x436b,{0x8a,0x53,0xe5,0x4f,0xe3,0x51,0xc3,0x22}} + +typedef INT (WINAPI *LPFN_WSARECVMSG)(SOCKET s, LPWSAMSG lpMsg, LPDWORD lpdwNumberOfBytesRecvd, + LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); + +#define WSAID_WSASENDMSG {0xa441e712,0x754f,0x43ca,{0x84,0xa7,0x0d,0xee,0x44,0xcf,0x60,0x6d}} + +typedef INT (WINAPI *LPFN_WSASENDMSG)(SOCKET s, LPWSAMSG lpMsg, DWORD dwFlags, LPDWORD lpNumberOfBytesSent, + LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); + +#endif + +#endif diff --git a/misc/nsskey.h b/misc/nsskey.h new file mode 100644 index 000000000..a38873681 --- /dev/null +++ b/misc/nsskey.h @@ -0,0 +1,109 @@ +/** + * @file nsskey.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Function for opening a NSS certificate and its private key. + */ + +#ifndef BADVPN_MISC_NSSKEY_H +#define BADVPN_MISC_NSSKEY_H + +#include + +#include +#include +#include +#include + +#include + +/** + * Opens a NSS certificate and its private key. + * + * @param name name of the certificate + * @param out_cert on success, the certificate will be returned here. Should be + * released with CERT_DestroyCertificate. + * @param out_key on success, the private key will be returned here. Should be + * released with SECKEY_DestroyPrivateKey. + * @return 1 on success, 0 on failure + */ +static int open_nss_cert_and_key (char *name, CERTCertificate **out_cert, SECKEYPrivateKey **out_key) WARN_UNUSED; + +static SECKEYPrivateKey * find_nss_private_key (char *name) +{ + SECKEYPrivateKey *key = NULL; + + PK11SlotList *slot_list = PK11_GetAllTokens(CKM_INVALID_MECHANISM, PR_FALSE, PR_FALSE, NULL); + if (!slot_list) { + return NULL; + } + + PK11SlotListElement *slot_entry; + for (slot_entry = slot_list->head; !key && slot_entry; slot_entry = slot_entry->next) { + SECKEYPrivateKeyList *key_list = PK11_ListPrivKeysInSlot(slot_entry->slot, name, NULL); + if (!key_list) { + DEBUG("PK11_ListPrivKeysInSlot failed"); + continue; + } + + SECKEYPrivateKeyListNode *key_node; + for (key_node = PRIVKEY_LIST_HEAD(key_list); !key && !PRIVKEY_LIST_END(key_node, key_list); key_node = PRIVKEY_LIST_NEXT(key_node)) { + char *key_name = PK11_GetPrivateKeyNickname(key_node->key); + if (!key_name || strcmp(key_name, name)) { + PORT_Free((void *)key_name); + continue; + } + PORT_Free((void *)key_name); + + key = SECKEY_CopyPrivateKey(key_node->key); + } + + SECKEY_DestroyPrivateKeyList(key_list); + } + + PK11_FreeSlotList(slot_list); + + return key; +} + +int open_nss_cert_and_key (char *name, CERTCertificate **out_cert, SECKEYPrivateKey **out_key) +{ + CERTCertificate *cert; + cert = CERT_FindCertByNicknameOrEmailAddr(CERT_GetDefaultCertDB(), name); + if (!cert) { + DEBUG("CERT_FindCertByName failed (%d)", (int)PR_GetError()); + return 0; + } + + SECKEYPrivateKey *key = find_nss_private_key(name); + if (!key) { + DEBUG("Failed to find private key"); + CERT_DestroyCertificate(cert); + return 0; + } + + *out_cert = cert; + *out_key = key; + return 1; +} + +#endif diff --git a/misc/offset.h b/misc/offset.h new file mode 100644 index 000000000..ad5def340 --- /dev/null +++ b/misc/offset.h @@ -0,0 +1,44 @@ +/** + * @file offset.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Macros for determining offsets of members in structs. + */ + +#ifndef BADVPN_MISC_OFFSET_H +#define BADVPN_MISC_OFFSET_H + +#include +#include + +/** + * Returns a pointer to a struct, given a pointer to its member. + */ +#define UPPER_OBJECT(_ptr, _object_type, _field_name) ((_object_type *)((uint8_t *)(_ptr) - offsetof(_object_type, _field_name))) + +/** + * Returns the offset of one struct member from another. + * Expands to an int. + */ +#define OFFSET_DIFF(_object_type, _field1, _field2) ((int)offsetof(_object_type, _field1) - (int)offsetof(_object_type, _field2)) + +#endif diff --git a/misc/overflow.h b/misc/overflow.h new file mode 100644 index 000000000..f67db83b0 --- /dev/null +++ b/misc/overflow.h @@ -0,0 +1,59 @@ +/** + * @file overflow.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Functions for checking for overflow of integer addition. + */ + +#ifndef BADVPN_MISC_OVERFLOW_H +#define BADVPN_MISC_OVERFLOW_H + +#include +#include + +#define __DEFINE_UNSIGNED_OVERFLOW(_name, _type, _max) \ +static int add_ ## _name ## _overflows (_type a, _type b) \ +{\ + return (b > _max - a); \ +} + +#define __DEFINE_SIGNED_OVERFLOW(_name, _type, _min, _max) \ +static int add_ ## _name ## _overflows (_type a, _type b) \ +{\ + if ((a < 0) ^ (b < 0)) return 0; \ + if (a < 0) return -(a < _min - b); \ + return (a > _max - b); \ +} + +__DEFINE_UNSIGNED_OVERFLOW(uint, unsigned int, UINT_MAX) +__DEFINE_UNSIGNED_OVERFLOW(uint8, uint8_t, UINT8_MAX) +__DEFINE_UNSIGNED_OVERFLOW(uint16, uint16_t, UINT16_MAX) +__DEFINE_UNSIGNED_OVERFLOW(uint32, uint32_t, UINT32_MAX) +__DEFINE_UNSIGNED_OVERFLOW(uint64, uint64_t, UINT64_MAX) + +__DEFINE_SIGNED_OVERFLOW(int, int, INT_MIN, INT_MAX) +__DEFINE_SIGNED_OVERFLOW(int8, int8_t, INT8_MIN, INT8_MAX) +__DEFINE_SIGNED_OVERFLOW(int16, int16_t, INT16_MIN, INT16_MAX) +__DEFINE_SIGNED_OVERFLOW(int32, int32_t, INT32_MIN, INT32_MAX) +__DEFINE_SIGNED_OVERFLOW(int64, int64_t, INT64_MIN, INT64_MAX) + +#endif diff --git a/misc/sslsocket.h b/misc/sslsocket.h new file mode 100644 index 000000000..7ddd700fa --- /dev/null +++ b/misc/sslsocket.h @@ -0,0 +1,42 @@ +/** + * @file sslsocket.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Structure for moving around sockets, possibly together with SSL compoments. + */ + +#ifndef BADVPN_MISC_SSLSOCKET_H +#define BADVPN_MISC_SSLSOCKET_H + +#include + +#include +#include + +typedef struct { + BSocket sock; + PRFileDesc bottom_prfd; + PRFileDesc *ssl_prfd; + BPRFileDesc ssl_bprfd; +} sslsocket; + +#endif diff --git a/misc/version.h b/misc/version.h new file mode 100644 index 000000000..9e52b1594 --- /dev/null +++ b/misc/version.h @@ -0,0 +1,34 @@ +/** + * @file version.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Product information definitions. + */ + +#ifndef BADVPN_MISC_VERSION_H +#define BADVPN_MISC_VERSION_H + +#define GLOBAL_PRODUCT_NAME "BadVPN" +#define GLOBAL_VERSION "1.999.93.1" +#define GLOBAL_COPYRIGHT_NOTICE "Copyright (C) 2010 Ambroz Bizjak " + +#endif diff --git a/nspr_support/BPRFileDesc.c b/nspr_support/BPRFileDesc.c new file mode 100644 index 000000000..cd54e7dfc --- /dev/null +++ b/nspr_support/BPRFileDesc.c @@ -0,0 +1,254 @@ +/** + * @file BPRFileDesc.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include +#include + +#include + +static void init_handlers (BPRFileDesc *obj); +static int get_event_index (int event); +static int get_bsocket_events (PRUint16 pr_events); +static void init_bottom (BPRFileDesc *obj); +static void free_bottom (BPRFileDesc *obj); +static void update_bottom (BPRFileDesc *obj); +static void set_bottom_events (BPRFileDesc *obj, PRInt16 new_events); +static void socket_handler (BPRFileDesc *obj, int event); + +void init_handlers (BPRFileDesc *obj) +{ + int i; + for (i = 0; i < 2; i++) { + obj->handlers[i] = NULL; + } +} + +int get_event_index (int event) +{ + switch (event) { + case PR_POLL_READ: + return 0; + case PR_POLL_WRITE: + return 1; + default: + ASSERT(0) + return 0; + } +} + +int get_bsocket_events (PRUint16 pr_events) +{ + int res = 0; + + if (pr_events&PR_POLL_READ) { + res |= BSOCKET_READ; + } + if (pr_events&PR_POLL_WRITE) { + res |= BSOCKET_WRITE; + } + + return res; +} + +void init_bottom (BPRFileDesc *obj) +{ + PRFileDesc *layer = obj->prfd; + do { + if (layer->identity == bsocketprfiledesc_identity) { + obj->bottom_type = BPRFILEDESC_BOTTOM_BSOCKET; + obj->bottom = layer; + BSocket_AddGlobalEventHandler((BSocket *)obj->bottom->secret, (BSocket_handler)socket_handler, obj); + return; + } + layer = layer->lower; + } while (layer); + + ASSERT(0) +} + +void free_bottom (BPRFileDesc *obj) +{ + switch (obj->bottom_type) { + case BPRFILEDESC_BOTTOM_BSOCKET: + BSocket_RemoveGlobalEventHandler((BSocket *)obj->bottom->secret); + break; + default: + ASSERT(0) + break; + } +} + +void update_bottom (BPRFileDesc *obj) +{ + // calculate bottom events + PRInt16 new_bottom_events = 0; + PRInt16 new_flags; + PRInt16 out_flags; + if (obj->waitEvents&PR_POLL_READ) { + new_flags = obj->prfd->methods->poll(obj->prfd, PR_POLL_READ, &out_flags); + if ((new_flags&out_flags) == 0) { + new_bottom_events |= new_flags; + } + } + if (obj->waitEvents&PR_POLL_WRITE) { + new_flags = obj->prfd->methods->poll(obj->prfd, PR_POLL_WRITE, &out_flags); + if ((new_flags&out_flags) == 0) { + new_bottom_events |= new_flags; + } + } + + switch (obj->bottom_type) { + case BPRFILEDESC_BOTTOM_BSOCKET: + BSocket_SetGlobalEvents((BSocket *)obj->bottom->secret, get_bsocket_events(new_bottom_events)); + break; + default: + ASSERT(0) + break; + } +} + +void socket_handler (BPRFileDesc *obj, int events) +{ + // make sure bottom events are not recalculated whenever an event + // is disabled or enabled, it's faster to do it only once + obj->in_handler = 1; + + // call handlers for all enabled events + + if (obj->waitEvents&PR_POLL_READ) { + BPRFileDesc_DisableEvent(obj, PR_POLL_READ); + DEAD_ENTER(obj->dead) + obj->handlers[0](obj->handlers_user[0], PR_POLL_READ); + if (DEAD_LEAVE(obj->dead)) { + return; + } + } + + if (obj->waitEvents&PR_POLL_WRITE) { + BPRFileDesc_DisableEvent(obj, PR_POLL_WRITE); + DEAD_ENTER(obj->dead) + obj->handlers[1](obj->handlers_user[1], PR_POLL_WRITE); + if (DEAD_LEAVE(obj->dead)) { + return; + } + } + + // recalculate bottom events + obj->in_handler = 0; + update_bottom(obj); +} + +void BPRFileDesc_Init (BPRFileDesc *obj, PRFileDesc *prfd) +{ + DEAD_INIT(obj->dead); + obj->prfd = prfd; + init_handlers(obj); + obj->waitEvents = 0; + init_bottom(obj); + obj->in_handler = 0; + + // init debug object + DebugObject_Init(&obj->d_obj); +} + +void BPRFileDesc_Free (BPRFileDesc *obj) +{ + // free debug object + DebugObject_Free(&obj->d_obj); + + free_bottom(obj); + DEAD_KILL(obj->dead); +} + +void BPRFileDesc_AddEventHandler (BPRFileDesc *obj, PRInt16 event, BPRFileDesc_handler handler, void *user) +{ + ASSERT(handler) + + // get index + int index = get_event_index(event); + + // event must not have handler + ASSERT(!obj->handlers[index]) + + // change handler + obj->handlers[index] = handler; + obj->handlers_user[index] = user; +} + +void BPRFileDesc_RemoveEventHandler (BPRFileDesc *obj, PRInt16 event) +{ + // get index + int index = get_event_index(event); + + // event must have handler + ASSERT(obj->handlers[index]) + + // disable event if enabled + if (obj->waitEvents&event) { + BPRFileDesc_DisableEvent(obj, event); + } + + // change handler + obj->handlers[index] = NULL; +} + +void BPRFileDesc_EnableEvent (BPRFileDesc *obj, PRInt16 event) +{ + // get index + int index = get_event_index(event); + + // event must have handler + ASSERT(obj->handlers[index]) + + // event must not be enabled + ASSERT(!(obj->waitEvents&event)) + + // update events + obj->waitEvents |= event; + + // update bottom + if (!obj->in_handler) { + update_bottom(obj); + } +} + +void BPRFileDesc_DisableEvent (BPRFileDesc *obj, PRInt16 event) +{ + // get index + int index = get_event_index(event); + + // event must have handler + ASSERT(obj->handlers[index]) + + // event must be enabled + ASSERT(obj->waitEvents&event) + + // update events + obj->waitEvents &= ~event; + + // update bottom + if (!obj->in_handler) { + update_bottom(obj); + } +} diff --git a/nspr_support/BPRFileDesc.h b/nspr_support/BPRFileDesc.h new file mode 100644 index 000000000..13aabdfa9 --- /dev/null +++ b/nspr_support/BPRFileDesc.h @@ -0,0 +1,127 @@ +/** + * @file BPRFileDesc.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Object used for obtaining notifications for available I/O operations + * on NSPR file descriptors (PRFileDesc) with supported bottom layers. + * Currently only the {@link BSocketPRFileDesc} bottom layer is supported. + */ + +#ifndef BADVPN_NSPRSUPPORT_BPRFILEDESC_H +#define BADVPN_NSPRSUPPORT_BPRFILEDESC_H + +#include +#include +#include + +#define BPRFILEDESC_BOTTOM_BSOCKET 1 + +/** + * Handler function called when an event occurs on the NSPR file descriptor. + * It is guaranteed that the event had a handler and was enabled. + * The event is disabled before the handler is called. + * + * @param user as in {@link BPRFileDesc_AddEventHandler} + * @param event event being reported + */ +typedef void (*BPRFileDesc_handler) (void *user, PRInt16 event); + +/** + * Object used for obtaining notifications for available I/O operations + * on NSPR file descriptors (PRFileDesc) with supported bottom layers. + * Currently only the {@link BSocketPRFileDesc} bottom layer is supported. + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + BReactor *reactor; + PRFileDesc *prfd; + BPRFileDesc_handler handlers[2]; + void *handlers_user[2]; + PRInt16 waitEvents; + int bottom_type; + PRFileDesc *bottom; + int in_handler; +} BPRFileDesc; + +/** + * Initializes the object. + * + * @param obj the object + * @param prfd NSPR file descriptor for which notifications are needed. + * Its bottom layer must be a {@link BSocketPRFileDesc}. + * The bottom {@link BSocket} must not have any event handlers + * registered (socket-global or event-specific). + * This object registers a socket-global event handler for + * the bottom {@link BSocket}. + */ +void BPRFileDesc_Init (BPRFileDesc *obj, PRFileDesc *prfd); + +/** + * Frees the object. + * @param obj the object + */ +void BPRFileDesc_Free (BPRFileDesc *obj); + +/** + * Registers a handler for an event. + * The event must not already have a handler. + * + * @param obj the object + * @param event NSPR event to register the handler for. Must be + * PR_POLL_READ or PR_POLL_WRITE. + * @param user value to pass to handler + */ +void BPRFileDesc_AddEventHandler (BPRFileDesc *obj, PRInt16 event, BPRFileDesc_handler handler, void *user); + +/** + * Unregisters a handler for an event. + * The event must have a handler. + * + * @param obj the object + * @param event NSPR event to unregister the handler for + */ +void BPRFileDesc_RemoveEventHandler (BPRFileDesc *obj, PRInt16 event); + +/** + * Enables monitoring of an event. + * The event must have a handler. + * The event must not be enabled. + * If the operation associated with the event can already be performed, + * the handler for the event may never be called. + * + * @param obj the object + * @param event NSPR event to enable monitoring for + */ +void BPRFileDesc_EnableEvent (BPRFileDesc *obj, PRInt16 event); + +/** + * Disables monitoring of an event. + * The event must have a handler. + * The event must be enabled. + * + * @param obj the object + * @param event NSPR event to disable monitoring for + */ +void BPRFileDesc_DisableEvent (BPRFileDesc *obj, PRInt16 event); + +#endif diff --git a/nspr_support/BSocketPRFileDesc.c b/nspr_support/BSocketPRFileDesc.c new file mode 100644 index 000000000..8924d6ae5 --- /dev/null +++ b/nspr_support/BSocketPRFileDesc.c @@ -0,0 +1,294 @@ +/** + * @file BSocketPRFileDesc.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include + +#include +#include + +#include +#include + +#include + +#ifndef NDEBUG +int bsocketprfiledesc_initialized = 0; +#endif +PRDescIdentity bsocketprfiledesc_identity; + +static int baddr_to_prnetaddr (PRNetAddr *out, BAddr addr) +{ + memset(out, 0, sizeof(PRNetAddr)); + + switch (addr.type) { + case BADDR_TYPE_IPV4: + out->inet.family = PR_AF_INET; + out->inet.port = addr.ipv4.port; + out->inet.ip = addr.ipv4.ip; + break; + case BADDR_TYPE_IPV6: + out->ipv6.family = PR_AF_INET6; + out->ipv6.port = addr.ipv6.port; + out->ipv6.flowinfo = 0; + memcpy(&out->ipv6.ip, addr.ipv6.ip, 16); + break; + default: + return 0; + } + + return 1; +} + +static PRStatus method_close (PRFileDesc *fd) +{ + return PR_SUCCESS; +} + +static PRInt32 method_read (PRFileDesc *fd, void *buf, PRInt32 amount) +{ + ASSERT(amount >= 0) + + BSocket *bsock = (BSocket *)fd->secret; + + if (amount > INT_MAX) { + amount = INT_MAX; + } + + int res = BSocket_Recv(bsock, buf, amount); + if (res < 0) { + switch (BSocket_GetError(bsock)) { + case BSOCKET_ERROR_LATER: + PR_SetError(PR_WOULD_BLOCK_ERROR, 0); + return -1; + default: + PR_SetError(PR_UNKNOWN_ERROR, 0); + return -1; + } + } + + return res; +} + +static PRInt32 method_write (PRFileDesc *fd, const void *buf, PRInt32 amount) +{ + ASSERT(amount >= 0) + + BSocket *bsock = (BSocket *)fd->secret; + + if (amount > INT_MAX) { + amount = INT_MAX; + } + + int res = BSocket_Send(bsock, (uint8_t *)buf, amount); + ASSERT(res != 0) + if (res < 0) { + switch (BSocket_GetError(bsock)) { + case BSOCKET_ERROR_LATER: + PR_SetError(PR_WOULD_BLOCK_ERROR, 0); + return -1; + default: + PR_SetError(PR_UNKNOWN_ERROR, 0); + return -1; + } + } + + return res; +} + +static PRStatus method_shutdown (PRFileDesc *fd, PRIntn how) +{ + PR_SetError(PR_INVALID_METHOD_ERROR, 0); + return PR_FAILURE; +} + +static PRInt32 method_recv (PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags, PRIntervalTime timeout) +{ + ASSERT(flags == 0) + + return method_read(fd, buf, amount); +} + +static PRInt32 method_send (PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags, PRIntervalTime timeout) +{ + ASSERT(flags == 0) + + return method_write(fd, buf, amount); +} + +static PRInt16 method_poll (PRFileDesc *fd, PRInt16 in_flags, PRInt16 *out_flags) +{ + *out_flags = 0; + return in_flags; +} + +static PRStatus method_getpeername (PRFileDesc *fd, PRNetAddr *addr) +{ + BSocket *bsock = (BSocket *)fd->secret; + + BAddr baddr; + if (BSocket_GetPeerName(bsock, &baddr) < 0) { + PR_SetError(PR_UNKNOWN_ERROR, 0); + return PR_FAILURE; + } + + if (!baddr_to_prnetaddr(addr, baddr)) { + PR_SetError(PR_UNKNOWN_ERROR, 0); + return PR_FAILURE; + } + + return PR_SUCCESS; +} + +static PRStatus method_getsocketoption (PRFileDesc *fd, PRSocketOptionData *data) +{ + BSocket *bsock = (BSocket *)fd->secret; + + switch (data->option) { + case PR_SockOpt_Nonblocking: + data->value.non_blocking = PR_TRUE; + return PR_SUCCESS; + } + + PR_SetError(PR_UNKNOWN_ERROR, 0); + return PR_FAILURE; +} + +static PRStatus method_setsocketoption (PRFileDesc *fd, const PRSocketOptionData *data) +{ + PR_SetError(PR_UNKNOWN_ERROR, 0); + return PR_FAILURE; +} + +static PRIntn _PR_InvalidIntn (void) +{ + ASSERT(0) + PR_SetError(PR_INVALID_METHOD_ERROR, 0); + return -1; +} + +static PRInt32 _PR_InvalidInt32 (void) +{ + ASSERT(0) + PR_SetError(PR_INVALID_METHOD_ERROR, 0); + return -1; +} + +static PRInt64 _PR_InvalidInt64 (void) +{ + ASSERT(0) + PR_SetError(PR_INVALID_METHOD_ERROR, 0); + return -1; +} + +static PROffset32 _PR_InvalidOffset32 (void) +{ + ASSERT(0) + PR_SetError(PR_INVALID_METHOD_ERROR, 0); + return -1; +} + +static PROffset64 _PR_InvalidOffset64 (void) +{ + ASSERT(0) + PR_SetError(PR_INVALID_METHOD_ERROR, 0); + return -1; +} + +static PRStatus _PR_InvalidStatus (void) +{ + ASSERT(0) + PR_SetError(PR_INVALID_METHOD_ERROR, 0); + return PR_FAILURE; +} + +static PRFileDesc *_PR_InvalidDesc (void) +{ + ASSERT(0) + PR_SetError(PR_INVALID_METHOD_ERROR, 0); + return NULL; +} + +static PRIOMethods methods = { + (PRDescType)0, + method_close, + method_read, + method_write, + (PRAvailableFN)_PR_InvalidInt32, + (PRAvailable64FN)_PR_InvalidInt64, + (PRFsyncFN)_PR_InvalidStatus, + (PRSeekFN)_PR_InvalidOffset32, + (PRSeek64FN)_PR_InvalidOffset64, + (PRFileInfoFN)_PR_InvalidStatus, + (PRFileInfo64FN)_PR_InvalidStatus, + (PRWritevFN)_PR_InvalidInt32, + (PRConnectFN)_PR_InvalidStatus, + (PRAcceptFN)_PR_InvalidDesc, + (PRBindFN)_PR_InvalidStatus, + (PRListenFN)_PR_InvalidStatus, + method_shutdown, + method_recv, + method_send, + (PRRecvfromFN)_PR_InvalidInt32, + (PRSendtoFN)_PR_InvalidInt32, + method_poll, + (PRAcceptreadFN)_PR_InvalidInt32, + (PRTransmitfileFN)_PR_InvalidInt32, + (PRGetsocknameFN)_PR_InvalidStatus, + method_getpeername, + (PRReservedFN)_PR_InvalidIntn, + (PRReservedFN)_PR_InvalidIntn, + method_getsocketoption, + method_setsocketoption, + (PRSendfileFN)_PR_InvalidInt32, + (PRConnectcontinueFN)_PR_InvalidStatus, + (PRReservedFN)_PR_InvalidIntn, + (PRReservedFN)_PR_InvalidIntn, + (PRReservedFN)_PR_InvalidIntn, + (PRReservedFN)_PR_InvalidIntn +}; + +int BSocketPRFileDesc_GlobalInit (void) +{ + ASSERT(!bsocketprfiledesc_initialized) + + if ((bsocketprfiledesc_identity = PR_GetUniqueIdentity("BSocketPRFileDesc")) == PR_INVALID_IO_LAYER) { + return 0; + } + + #ifndef NDEBUG + bsocketprfiledesc_initialized = 1; + #endif + + return 1; +} + +void BSocketPRFileDesc_Create (PRFileDesc *prfd, BSocket *bsock) +{ + ASSERT(bsocketprfiledesc_initialized) + + memset(prfd, 0, sizeof(prfd)); + prfd->methods = &methods; + prfd->secret = (PRFilePrivate *)bsock; + prfd->identity = bsocketprfiledesc_identity; +} diff --git a/nspr_support/BSocketPRFileDesc.h b/nspr_support/BSocketPRFileDesc.h new file mode 100644 index 000000000..6cf8e528f --- /dev/null +++ b/nspr_support/BSocketPRFileDesc.h @@ -0,0 +1,54 @@ +/** + * @file BSocketPRFileDesc.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * NSPR file descriptor (PRFileDesc) for {@link BSocket} stream sockets. + */ + +#ifndef BADVPN_NSPRSUPPORT_BSOCKETPRFILEDESC_H +#define BADVPN_NSPRSUPPORT_BSOCKETPRFILEDESC_H + +#include + +#include +#include + +extern PRDescIdentity bsocketprfiledesc_identity; + +/** + * Globally initializes the {@link BSocket} NSPR file descriptor backend. + * Must not have been called successfully. + * + * @return 1 on success, 0 on failure + */ +int BSocketPRFileDesc_GlobalInit (void) WARN_UNUSED; + +/** + * Creates a NSPR file descriptor using {@link BSocket} for I/O. + * {@link BSocketPRFileDesc_GlobalInit} must have been done. + * + * @param prfd uninitialized PRFileDesc structure + * @param bsock socket to use. The socket should be a stream socket. + */ +void BSocketPRFileDesc_Create (PRFileDesc *prfd, BSocket *bsock); + +#endif diff --git a/nspr_support/CMakeLists.txt b/nspr_support/CMakeLists.txt new file mode 100644 index 000000000..010c397fe --- /dev/null +++ b/nspr_support/CMakeLists.txt @@ -0,0 +1,8 @@ +add_library(nspr_support + BSocketPRFileDesc.c + DummyPRFileDesc.c + BPRFileDesc.c + PRStreamSource.c + PRStreamSink.c +) +target_link_libraries(nspr_support system ${NSPR_LIBRARIES}) diff --git a/nspr_support/DummyPRFileDesc.c b/nspr_support/DummyPRFileDesc.c new file mode 100644 index 000000000..a99be4127 --- /dev/null +++ b/nspr_support/DummyPRFileDesc.c @@ -0,0 +1,169 @@ +/** + * @file DummyPRFileDesc.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include + +#include +#include + +#include +#include + +#include + +#ifndef NDEBUG +int dummyprfiledesc_initialized = 0; +#endif +PRDescIdentity dummyprfiledesc_identity; + +static PRStatus method_close (PRFileDesc *fd) +{ + return PR_SUCCESS; +} + +static PRStatus method_getpeername (PRFileDesc *fd, PRNetAddr *addr) +{ + PR_SetError(PR_UNKNOWN_ERROR, 0); + return PR_FAILURE; +} + +static PRIntn _PR_InvalidIntn (void) +{ + ASSERT(0) + PR_SetError(PR_INVALID_METHOD_ERROR, 0); + return -1; +} + +static PRInt16 _PR_InvalidInt16 (void) +{ + ASSERT(0) + PR_SetError(PR_INVALID_METHOD_ERROR, 0); + return -1; +} + +static PRInt32 _PR_InvalidInt32 (void) +{ + ASSERT(0) + PR_SetError(PR_INVALID_METHOD_ERROR, 0); + return -1; +} + +static PRInt64 _PR_InvalidInt64 (void) +{ + ASSERT(0) + PR_SetError(PR_INVALID_METHOD_ERROR, 0); + return -1; +} + +static PROffset32 _PR_InvalidOffset32 (void) +{ + ASSERT(0) + PR_SetError(PR_INVALID_METHOD_ERROR, 0); + return -1; +} + +static PROffset64 _PR_InvalidOffset64 (void) +{ + ASSERT(0) + PR_SetError(PR_INVALID_METHOD_ERROR, 0); + return -1; +} + +static PRStatus _PR_InvalidStatus (void) +{ + ASSERT(0) + PR_SetError(PR_INVALID_METHOD_ERROR, 0); + return PR_FAILURE; +} + +static PRFileDesc *_PR_InvalidDesc (void) +{ + ASSERT(0) + PR_SetError(PR_INVALID_METHOD_ERROR, 0); + return NULL; +} + +static PRIOMethods methods = { + (PRDescType)0, + method_close, + (PRReadFN)_PR_InvalidInt32, + (PRWriteFN)_PR_InvalidInt32, + (PRAvailableFN)_PR_InvalidInt32, + (PRAvailable64FN)_PR_InvalidInt64, + (PRFsyncFN)_PR_InvalidStatus, + (PRSeekFN)_PR_InvalidOffset32, + (PRSeek64FN)_PR_InvalidOffset64, + (PRFileInfoFN)_PR_InvalidStatus, + (PRFileInfo64FN)_PR_InvalidStatus, + (PRWritevFN)_PR_InvalidInt32, + (PRConnectFN)_PR_InvalidStatus, + (PRAcceptFN)_PR_InvalidDesc, + (PRBindFN)_PR_InvalidStatus, + (PRListenFN)_PR_InvalidStatus, + (PRShutdownFN)_PR_InvalidStatus, + (PRRecvFN)_PR_InvalidInt32, + (PRSendFN)_PR_InvalidInt32, + (PRRecvfromFN)_PR_InvalidInt32, + (PRSendtoFN)_PR_InvalidInt32, + (PRPollFN)_PR_InvalidInt16, + (PRAcceptreadFN)_PR_InvalidInt32, + (PRTransmitfileFN)_PR_InvalidInt32, + (PRGetsocknameFN)_PR_InvalidStatus, + method_getpeername, + (PRReservedFN)_PR_InvalidIntn, + (PRReservedFN)_PR_InvalidIntn, + (PRGetsocketoptionFN)_PR_InvalidStatus, + (PRSetsocketoptionFN)_PR_InvalidStatus, + (PRSendfileFN)_PR_InvalidInt32, + (PRConnectcontinueFN)_PR_InvalidStatus, + (PRReservedFN)_PR_InvalidIntn, + (PRReservedFN)_PR_InvalidIntn, + (PRReservedFN)_PR_InvalidIntn, + (PRReservedFN)_PR_InvalidIntn +}; + +int DummyPRFileDesc_GlobalInit (void) +{ + ASSERT(!dummyprfiledesc_initialized) + + if ((dummyprfiledesc_identity = PR_GetUniqueIdentity("DummyPRFileDesc")) == PR_INVALID_IO_LAYER) { + return 0; + } + + #ifndef NDEBUG + dummyprfiledesc_initialized = 1; + #endif + + return 1; +} + +void DummyPRFileDesc_Create (PRFileDesc *prfd) +{ + ASSERT(dummyprfiledesc_initialized) + + memset(prfd, 0, sizeof(prfd)); + prfd->methods = &methods; + prfd->secret = NULL; + prfd->identity = dummyprfiledesc_identity; +} diff --git a/nspr_support/DummyPRFileDesc.h b/nspr_support/DummyPRFileDesc.h new file mode 100644 index 000000000..c24f1ec70 --- /dev/null +++ b/nspr_support/DummyPRFileDesc.h @@ -0,0 +1,54 @@ +/** + * @file DummyPRFileDesc.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Dummy NSPR file descriptor (PRFileDesc). + * Used for creating a model SSL file descriptor to cache various stuff + * to improve performance. + */ + +#ifndef BADVPN_NSPRSUPPORT_DUMMYPRFILEDESC_H +#define BADVPN_NSPRSUPPORT_DUMMYPRFILEDESC_H + +#include + +#include + +extern PRDescIdentity dummyprfiledesc_identity; + +/** + * Globally initialize the dummy NSPR file descriptor backend. + * Must not have been called successfully. + * + * @return 1 on success, 0 on failure + */ +int DummyPRFileDesc_GlobalInit (void) WARN_UNUSED; + +/** + * Creates a dummy NSPR file descriptor. + * {@link DummyPRFileDesc_GlobalInit} must have been done. + * + * @param prfd uninitialized PRFileDesc structure + */ +void DummyPRFileDesc_Create (PRFileDesc *prfd); + +#endif diff --git a/nspr_support/PRStreamSink.c b/nspr_support/PRStreamSink.c new file mode 100644 index 000000000..8d2330ef9 --- /dev/null +++ b/nspr_support/PRStreamSink.c @@ -0,0 +1,138 @@ +/** + * @file PRStreamSink.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include + +#include + +static void report_error (PRStreamSink *s, int error) +{ + #ifndef NDEBUG + s->in_error = 1; + DEAD_ENTER(s->dead) + #endif + + FlowErrorReporter_ReportError(&s->rep, &error); + + #ifndef NDEBUG + ASSERT(DEAD_KILLED) + DEAD_LEAVE(s->dead); + #endif +} + +static int input_handler_send (PRStreamSink *s, uint8_t *data, int data_len) +{ + ASSERT(s->in_len == -1) + ASSERT(data_len > 0) + ASSERT(!s->in_error) + + int res = PR_Write(s->bprfd->prfd, data, data_len); + if (res < 0) { + PRErrorCode error = PR_GetError(); + if (error == PR_WOULD_BLOCK_ERROR) { + s->in_len = data_len; + s->in = data; + BPRFileDesc_EnableEvent(s->bprfd, PR_POLL_WRITE); + return 0; + } + report_error(s, PRSTREAMSINK_ERROR_NSPR); + return -1; + } + + ASSERT(res > 0) + + return res; +} + +static void prfd_handler (PRStreamSink *s, PRInt16 event) +{ + ASSERT(s->in_len > 0) + ASSERT(event == PR_POLL_WRITE) + ASSERT(!s->in_error) + + int res = PR_Write(s->bprfd->prfd, s->in, s->in_len); + if (res < 0) { + PRErrorCode error = PR_GetError(); + if (error == PR_WOULD_BLOCK_ERROR) { + BPRFileDesc_EnableEvent(s->bprfd, PR_POLL_WRITE); + return; + } + report_error(s, PRSTREAMSINK_ERROR_NSPR); + return; + } + + ASSERT(res > 0) + + s->in_len = -1; + + StreamPassInterface_Done(&s->input, res); + return; +} + +void PRStreamSink_Init (PRStreamSink *s, FlowErrorReporter rep, BPRFileDesc *bprfd) +{ + // init arguments + s->rep = rep; + s->bprfd = bprfd; + + // init dead var + DEAD_INIT(s->dead); + + // add socket event handler + BPRFileDesc_AddEventHandler(s->bprfd, PR_POLL_WRITE, (BPRFileDesc_handler)prfd_handler, s); + + // init input + StreamPassInterface_Init(&s->input, (StreamPassInterface_handler_send)input_handler_send, s); + + // have no input packet + s->in_len = -1; + + // init debugging + #ifndef NDEBUG + s->in_error = 0; + #endif + + // init debug object + DebugObject_Init(&s->d_obj); +} + +void PRStreamSink_Free (PRStreamSink *s) +{ + // free debug object + DebugObject_Free(&s->d_obj); + + // free input + StreamPassInterface_Free(&s->input); + + // remove socket event handler + BPRFileDesc_RemoveEventHandler(s->bprfd, PR_POLL_WRITE); + + // free dead var + DEAD_KILL(s->dead); +} + +StreamPassInterface * PRStreamSink_GetInput (PRStreamSink *s) +{ + return &s->input; +} diff --git a/nspr_support/PRStreamSink.h b/nspr_support/PRStreamSink.h new file mode 100644 index 000000000..cc405a00b --- /dev/null +++ b/nspr_support/PRStreamSink.h @@ -0,0 +1,84 @@ +/** + * @file PRStreamSink.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * A {@link StreamPassInterface} sink for a NSPR file descriptor (PRFileDesc) via {@link BPRFileDesc}. + */ + +#ifndef BADVPN_NSPRSUPPORT_PRSTREAMSINK_H +#define BADVPN_NSPRSUPPORT_PRSTREAMSINK_H + +#include + +#include +#include +#include +#include +#include + +#define PRSTREAMSINK_ERROR_NSPR 1 + +/** + * A {@link StreamPassInterface} sink for a NSPR file descriptor (PRFileDesc) via {@link BPRFileDesc}. + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + FlowErrorReporter rep; + BPRFileDesc *bprfd; + StreamPassInterface input; + int in_len; + uint8_t *in; + #ifndef NDEBUG + int in_error; + #endif +} PRStreamSink; + +/** + * Initializes the object. + * + * @param s the object + * @param rep error reporting data. Error code is an int. Possible error codes: + * - PRSTREAMSINK_ERROR_NSPR: {@link PR_Write} failed + * with an unhandled error code + * The object must be freed from the error handler. + * @param bprfd the {@link BPRFileDesc} object to write data to. Registers a + * PR_POLL_WRITE handler which must not be registered. + */ +void PRStreamSink_Init (PRStreamSink *s, FlowErrorReporter rep, BPRFileDesc *bprfd); + +/** + * Frees the object. + * + * @param s the object + */ +void PRStreamSink_Free (PRStreamSink *s); + +/** + * Returns the input interface. + * + * @param s the object + * @return input interface + */ +StreamPassInterface * PRStreamSink_GetInput (PRStreamSink *s); + +#endif diff --git a/nspr_support/PRStreamSource.c b/nspr_support/PRStreamSource.c new file mode 100644 index 000000000..74860aee6 --- /dev/null +++ b/nspr_support/PRStreamSource.c @@ -0,0 +1,144 @@ +/** + * @file PRStreamSource.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include + +#include + +static void report_error (PRStreamSource *s, int error) +{ + #ifndef NDEBUG + s->in_error = 1; + DEAD_ENTER(s->dead) + #endif + + FlowErrorReporter_ReportError(&s->rep, &error); + + #ifndef NDEBUG + ASSERT(DEAD_KILLED) + DEAD_LEAVE(s->dead); + #endif +} + +static int output_handler_recv (PRStreamSource *s, uint8_t *data, int data_avail) +{ + ASSERT(s->out_avail == -1) + ASSERT(data_avail > 0) + ASSERT(!s->in_error) + + PRInt32 res = PR_Read(s->bprfd->prfd, data, data_avail); + if (res < 0) { + PRErrorCode error = PR_GetError(); + if (error == PR_WOULD_BLOCK_ERROR) { + s->out_avail = data_avail; + s->out = data; + BPRFileDesc_EnableEvent(s->bprfd, PR_POLL_READ); + return 0; + } + report_error(s, PRSTREAMSOURCE_ERROR_NSPR); + return -1; + } + + if (res == 0) { + report_error(s, PRSTREAMSOURCE_ERROR_CLOSED); + return -1; + } + + return res; +} + +static void prfd_handler (PRStreamSource *s, PRInt16 event) +{ + ASSERT(s->out_avail > 0) + ASSERT(event == PR_POLL_READ) + ASSERT(!s->in_error) + + PRInt32 res = PR_Read(s->bprfd->prfd, s->out, s->out_avail); + if (res < 0) { + PRErrorCode error = PR_GetError(); + if (error == PR_WOULD_BLOCK_ERROR) { + BPRFileDesc_EnableEvent(s->bprfd, PR_POLL_READ); + return; + } + report_error(s, PRSTREAMSOURCE_ERROR_NSPR); + return; + } + + if (res == 0) { + report_error(s, PRSTREAMSOURCE_ERROR_CLOSED); + return; + } + + s->out_avail = -1; + + StreamRecvInterface_Done(&s->output, res); + return; +} + +void PRStreamSource_Init (PRStreamSource *s, FlowErrorReporter rep, BPRFileDesc *bprfd) +{ + // init arguments + s->rep = rep; + s->bprfd = bprfd; + + // init dead var + DEAD_INIT(s->dead); + + // add socket event handler + BPRFileDesc_AddEventHandler(s->bprfd, PR_POLL_READ, (BPRFileDesc_handler)prfd_handler, s); + + // init output + StreamRecvInterface_Init(&s->output, (StreamRecvInterface_handler_recv)output_handler_recv, s); + + // have no output packet + s->out_avail = -1; + + // init debugging + #ifndef NDEBUG + s->in_error = 0; + #endif + + // init debug object + DebugObject_Init(&s->d_obj); +} + +void PRStreamSource_Free (PRStreamSource *s) +{ + // free debug object + DebugObject_Free(&s->d_obj); + + // free output + StreamRecvInterface_Free(&s->output); + + // remove socket event handler + BPRFileDesc_RemoveEventHandler(s->bprfd, PR_POLL_READ); + + // free dead var + DEAD_KILL(s->dead); +} + +StreamRecvInterface * PRStreamSource_GetOutput (PRStreamSource *s) +{ + return &s->output; +} diff --git a/nspr_support/PRStreamSource.h b/nspr_support/PRStreamSource.h new file mode 100644 index 000000000..23ac94c10 --- /dev/null +++ b/nspr_support/PRStreamSource.h @@ -0,0 +1,86 @@ +/** + * @file PRStreamSource.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * A {@link StreamRecvInterface} source for a NSPR file descriptor (PRFileDesc) via {@link BPRFileDesc}. + */ + +#ifndef BADVPN_NSPRSUPPORT_PRSTREAMSOURCE_H +#define BADVPN_NSPRSUPPORT_PRSTREAMSOURCE_H + +#include + +#include +#include +#include +#include +#include + +#define PRSTREAMSOURCE_ERROR_CLOSED 0 +#define PRSTREAMSOURCE_ERROR_NSPR 1 + +/** + * A {@link StreamRecvInterface} source for a NSPR file descriptor (PRFileDesc) via {@link BPRFileDesc}. + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + FlowErrorReporter rep; + BPRFileDesc *bprfd; + StreamRecvInterface output; + int out_avail; + uint8_t *out; + #ifndef NDEBUG + int in_error; + #endif +} PRStreamSource; + +/** + * Initializes the object. + * + * @param s the object + * @param rep error reporting data. Error code is an int. Possible error codes: + * - PRSTREAMSOURCE_ERROR_CLOSED: {@link PR_Read} returned 0 + * - PRSTREAMSOURCE_ERROR_NSPR: {@link PR_Read} failed + * with an unhandled error code + * The object must be freed from the error handler. + * @param bprfd the {@link BPRFileDesc} object to read data from. Registers a + * PR_POLL_READ handler which must not be registered. + */ +void PRStreamSource_Init (PRStreamSource *s, FlowErrorReporter rep, BPRFileDesc *bprfd); + +/** + * Frees the object. + * + * @param s the object + */ +void PRStreamSource_Free (PRStreamSource *s); + +/** + * Returns the output interface. + * + * @param s the object + * @return output interface + */ +StreamRecvInterface * PRStreamSource_GetOutput (PRStreamSource *s); + +#endif diff --git a/predicate/BPredicate.c b/predicate/BPredicate.c new file mode 100644 index 000000000..0f2351903 --- /dev/null +++ b/predicate/BPredicate.c @@ -0,0 +1,281 @@ +/** + * @file BPredicate.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include + +static int eval_predicate_node (BPredicate *p, struct predicate_node *root); + +void yyerror (YYLTYPE *yylloc, yyscan_t scanner, struct predicate_node **result, char *str) +{ +} + +static int string_comparator (void *user, char *s1, char *s2) +{ + int cmp = strcmp(s1, s2); + if (cmp < 0) { + return -1; + } + if (cmp > 0) { + return 1; + } + return 0; +} + +static int eval_function (BPredicate *p, struct predicate_node *root) +{ + ASSERT(root->type == NODE_FUNCTION) + + // lookup function by name + ASSERT(root->function.name) + BAVLNode *tree_node; + if (!(tree_node = BAVL_LookupExact(&p->functions_tree, root->function.name))) { + BLog(BLOG_WARNING, "unknown function"); + return 0; + } + BPredicateFunction *func = UPPER_OBJECT(tree_node, BPredicateFunction, tree_node); + + // evaluate arguments + struct arguments_node *arg = root->function.args; + void *args[func->num_args]; + for (int i = 0; i < func->num_args; i++) { + if (!arg) { + BLog(BLOG_WARNING, "not enough arguments"); + return 0; + } + switch (func->args[i]) { + case PREDICATE_TYPE_BOOL: + if (arg->arg.type != ARGUMENT_PREDICATE) { + BLog(BLOG_WARNING, "expecting predicate argument"); + return 0; + } + if (!eval_predicate_node(p, arg->arg.predicate)) { + return 0; + } + args[i] = &arg->arg.predicate->eval_value; + break; + case PREDICATE_TYPE_STRING: + if (arg->arg.type != ARGUMENT_STRING) { + BLog(BLOG_WARNING, "expecting string argument"); + return 0; + } + args[i] = arg->arg.string; + break; + default: + ASSERT(0); + } + arg = arg->next; + } + + if (arg) { + BLog(BLOG_WARNING, "too many arguments"); + return 0; + } + + // call callback + #ifndef NDEBUG + p->in_function = 1; + #endif + int res = func->callback(func->user, args); + #ifndef NDEBUG + p->in_function = 0; + #endif + if (res != 0 && res != 1) { + BLog(BLOG_WARNING, "callback returned non-boolean"); + return 0; + } + + root->eval_value = res; + return 1; +} + +int eval_predicate_node (BPredicate *p, struct predicate_node *root) +{ + ASSERT(root) + + switch (root->type) { + case NODE_CONSTANT: + root->eval_value = root->constant.val; + return 1; + case NODE_NEG: + if (!eval_predicate_node(p, root->neg.op)) { + return 0; + } + root->eval_value = !root->neg.op->eval_value; + return 1; + case NODE_CONJUNCT: + if (!eval_predicate_node(p, root->conjunct.op1)) { + return 0; + } + if (!root->conjunct.op1->eval_value) { + root->eval_value = 0; + return 1; + } + if (!eval_predicate_node(p, root->conjunct.op2)) { + return 0; + } + if (!root->conjunct.op2->eval_value) { + root->eval_value = 0; + return 1; + } + root->eval_value = 1; + return 1; + case NODE_DISJUNCT: + if (!eval_predicate_node(p, root->disjunct.op1)) { + return 0; + } + if (root->disjunct.op1->eval_value) { + root->eval_value = 1; + return 1; + } + if (!eval_predicate_node(p, root->disjunct.op2)) { + return 0; + } + if (root->disjunct.op2->eval_value) { + root->eval_value = 1; + return 1; + } + root->eval_value = 0; + return 1; + case NODE_FUNCTION: + return eval_function(p, root); + default: + ASSERT(0) + return 0; + } +} + +int BPredicate_Init (BPredicate *p, char *str) +{ + // initialize input buffer object + LexMemoryBufferInput input; + LexMemoryBufferInput_Init(&input, str, strlen(str)); + + // initialize lexical analyzer + yyscan_t scanner; + yylex_init_extra(&input, &scanner); + + // parse + struct predicate_node *root = NULL; + int result = yyparse(scanner, &root); + + // free lexical analyzer + yylex_destroy(scanner); + + // check for errors + if (LexMemoryBufferInput_HasError(&input) || result != 0 || !root) { + if (root) { + free_predicate_node(root); + } + return 0; + } + + // init tree + p->root = root; + + // init functions tree + BAVL_Init(&p->functions_tree, OFFSET_DIFF(BPredicateFunction, name, tree_node), (BAVL_comparator)string_comparator, NULL); + + // init debuggind + #ifndef NDEBUG + p->in_function = 0; + #endif + + // init debug object + DebugObject_Init(&p->d_obj); + + return 1; +} + +void BPredicate_Free (BPredicate *p) +{ + ASSERT(BAVL_IsEmpty(&p->functions_tree)) + ASSERT(!p->in_function) + + // free debug object + DebugObject_Free(&p->d_obj); + + // free tree + free_predicate_node(p->root); +} + +int BPredicate_Eval (BPredicate *p) +{ + ASSERT(!p->in_function) + + if (!eval_predicate_node(p, p->root)) { + return -1; + } + + return ((struct predicate_node *)p->root)->eval_value; +} + +void BPredicateFunction_Init (BPredicateFunction *o, BPredicate *p, char *name, int *args, int num_args, BPredicate_callback callback, void *user) +{ + ASSERT(strlen(name) <= PREDICATE_MAX_NAME) + ASSERT(!BAVL_LookupExact(&p->functions_tree, name)) + ASSERT(num_args >= 0) + ASSERT(num_args <= PREDICATE_MAX_ARGS) + for (int i = 0; i < num_args; i++) { + ASSERT(args[i] == PREDICATE_TYPE_BOOL || args[i] == PREDICATE_TYPE_STRING) + } + ASSERT(!p->in_function) + + // init arguments + o->p = p; + strcpy(o->name, name); + memcpy(o->args, args, num_args * sizeof(int)); + o->num_args = num_args; + o->callback = callback; + o->user = user; + + // add to tree + ASSERT_EXECUTE(BAVL_Insert(&p->functions_tree, &o->tree_node, NULL)) + + // init debug object + DebugObject_Init(&o->d_obj); +} + +void BPredicateFunction_Free (BPredicateFunction *o) +{ + ASSERT(!o->p->in_function) + + BPredicate *p = o->p; + + // free debug object + DebugObject_Free(&o->d_obj); + + // remove from tree + BAVL_Remove(&p->functions_tree, &o->tree_node); +} diff --git a/predicate/BPredicate.h b/predicate/BPredicate.h new file mode 100644 index 000000000..1fec8f2ac --- /dev/null +++ b/predicate/BPredicate.h @@ -0,0 +1,170 @@ +/** + * @file BPredicate.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Object that parses and evaluates a logical expression. + * Allows the user to define custom functions than can be + * used in the expression. + * + * Syntax and semantics for logical expressions: + * + * - true + * Logical true constant. Evaluates to 1. + * + * - false + * Logical false constant. Evaluates to 0. + * + * - NOT expression + * Logical negation. If the expression evaluates to error, the + * negation evaluates to error. + * + * - expression OR expression + * Logical disjunction. The second expression is only evaluated + * if the first expression evaluates to false. If a sub-expression + * evaluates to error, the disjunction evaluates to error. + * + * - expression AND expression + * Logical conjunction. The second expression is only evaluated + * if the first expression evaluates to true. If a sub-expression + * evaluates to error, the conjunction evaluates to error. + * + * - function(arg, ..., arg) + * Evaluation of a user-provided function (function is the name of the + * function, [a-zA-Z0-9_]+). + * If the function with the given name does not exist, it evaluates to + * error. + * Arguments are evaluated from left to right. Each argument can either + * be a logical expression or a string (characters enclosed in double + * quotes, without any double quote). + * If an argument is encountered, but all needed arguments have already + * been evaluated, the function evaluates to error. + * If an argument is of wrong type, it is not evaluated and the function + * evaluates to error. + * If an argument evaluates to error, the function evaluates to error. + * If after all arguments have been evaluated, the function needs more + * arguments, it evaluates to error. + * Then the handler function is called. If it returns anything other + * than 1 and 0, the function evaluates to error. Otherwise it evaluates + * to what the handler function returned. + */ + +#ifndef BADVPN_PREDICATE_BPREDICATE_H +#define BADVPN_PREDICATE_BPREDICATE_H + +#include +#include +#include + +#define PREDICATE_TYPE_BOOL 1 +#define PREDICATE_TYPE_STRING 2 + +#define PREDICATE_MAX_NAME 16 +#define PREDICATE_MAX_ARGS 16 + +/** + * Handler function called when evaluating a custom function in the predicate. + * + * @param user value passed to {@link BPredicateFunction_Init} + * @param args arguments to the function. Points to an array of pointers (as many as the + * function has arguments), where each pointer points to either to an int or + * a zero-terminated string (depending on the type of the argument). + * @return 1 for true, 0 for false, -1 for error + */ +typedef int (*BPredicate_callback) (void *user, void **args); + +/** + * Object that parses and evaluates a logical expression. + * Allows the user to define custom functions than can be + * used in the expression. + */ +typedef struct { + DebugObject d_obj; + void *root; + BAVL functions_tree; + #ifndef NDEBUG + int in_function; + #endif +} BPredicate; + +/** + * Object that represents a custom function in {@link BPredicate}. + */ +typedef struct { + DebugObject d_obj; + BPredicate *p; + char name[PREDICATE_MAX_NAME + 1]; + int args[PREDICATE_MAX_ARGS]; + int num_args; + BPredicate_callback callback; + void *user; + BAVLNode tree_node; +} BPredicateFunction; + +/** + * Initializes the object. + * + * @param p the object + * @param str logical expression + * @return 1 on success, 0 on failure + */ +int BPredicate_Init (BPredicate *p, char *str) WARN_UNUSED; + +/** + * Frees the object. + * Must have no custom functions. + * Must not be called from function handlers. + * + * @param p the object + */ +void BPredicate_Free (BPredicate *p); + +/** + * Evaluates the logical expression. + * Must not be called from function handlers. + * + * @param p the object + * @return 1 for true, 0 for false, -1 for error + */ +int BPredicate_Eval (BPredicate *p); + +/** + * Registers a custom function for {@link BPredicate}. + * Must not be called from function handlers. + * + * @param o the object + * @param p predicate to register the function for + * @param args array of argument types. Each type is either PREDICATE_TYPE_BOOL or PREDICATE_TYPE_STRING. + * @param num_args number of arguments for the function. Must be >=0 and <=PREDICATE_MAX_ARGS. + * @param callback handler to call to evaluate the function + * @param user value to pass to handler + */ +void BPredicateFunction_Init (BPredicateFunction *o, BPredicate *p, char *name, int *args, int num_args, BPredicate_callback callback, void *user); + +/** + * Removes a custom function for {@link BPredicate}. + * Must not be called from function handlers. + * + * @param o the object + */ +void BPredicateFunction_Free (BPredicateFunction *o); + +#endif diff --git a/predicate/BPredicate.l b/predicate/BPredicate.l new file mode 100644 index 000000000..606af48ab --- /dev/null +++ b/predicate/BPredicate.l @@ -0,0 +1,78 @@ +/** + * @file BPredicate.l + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * {@link BPredicate} lexer file. + */ + +%{ + +#include +#include + +#include +#include + +#include + +#define YY_EXTRA_TYPE LexMemoryBufferInput * + +#define YY_INPUT(buffer, res, max_size) \ + int bytes_read = LexMemoryBufferInput_Read(yyget_extra(yyscanner), buffer, max_size); \ + res = (bytes_read == 0 ? YY_NULL : bytes_read); + +%} + +%option reentrant stack noyywrap bison-bridge bison-locations + +%% +\( return SPAR; +\) return EPAR; +, return COMMA; +AND return AND; +OR return OR; +NOT return NOT; +true return CONSTANT_TRUE; +false return CONSTANT_FALSE; +[a-zA-Z0-9_]+ { + int l = strlen(yytext); + char *p = malloc(l + 1); + if (p) { + memcpy(p, yytext, l); + p[l] = '\0'; + } + yylval->text = p; + return NAME; + } +\"[^\"]*\" { + int l = strlen(yytext); + char *p = malloc(l - 1); + if (p) { + memcpy(p, yytext + 1, l - 2); + p[l - 2] = '\0'; + } + yylval->text = p; + return STRING; + } +[ \t\n]+ ; +. LexMemoryBufferInput_SetError(yyget_extra(yyscanner)); return 0; // remember failure and report EOF +%% diff --git a/predicate/BPredicate.y b/predicate/BPredicate.y new file mode 100644 index 000000000..9bf3701d0 --- /dev/null +++ b/predicate/BPredicate.y @@ -0,0 +1,338 @@ +/** + * @file BPredicate.y + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * {@link BPredicate} grammar file. + */ + +%{ + +#include + +#include +#include + +#define YYLEX_PARAM scanner + +static struct predicate_node * make_constant (int val) +{ + struct predicate_node *n = malloc(sizeof(*n)); + if (!n) { + return NULL; + } + + n->type = NODE_CONSTANT; + n->constant.val = val; + + return n; +} + +static struct predicate_node * make_negation (struct predicate_node *op) +{ + if (!op) { + goto fail; + } + + struct predicate_node *n = malloc(sizeof(*n)); + if (!n) { + goto fail; + } + + n->type = NODE_NEG; + n->neg.op = op; + + return n; + +fail: + if (op) { + free_predicate_node(op); + } + return NULL; +} + +static struct predicate_node * make_conjunction (struct predicate_node *op1, struct predicate_node *op2) +{ + if (!op1 || !op2) { + goto fail; + } + + struct predicate_node *n = malloc(sizeof(*n)); + if (!n) { + goto fail; + } + + n->type = NODE_CONJUNCT; + n->conjunct.op1 = op1; + n->conjunct.op2 = op2; + + return n; + +fail: + if (op1) { + free_predicate_node(op1); + } + if (op2) { + free_predicate_node(op2); + } + return NULL; +} + +static struct predicate_node * make_disjunction (struct predicate_node *op1, struct predicate_node *op2) +{ + if (!op1 || !op2) { + goto fail; + } + + struct predicate_node *n = malloc(sizeof(*n)); + if (!n) { + goto fail; + } + + n->type = NODE_DISJUNCT; + n->disjunct.op1 = op1; + n->disjunct.op2 = op2; + + return n; + +fail: + if (op1) { + free_predicate_node(op1); + } + if (op2) { + free_predicate_node(op2); + } + return NULL; +} + +static struct predicate_node * make_function (char *name, struct arguments_node *args, int need_args) +{ + if (!name || (!args && need_args)) { + goto fail; + } + + struct predicate_node *n = malloc(sizeof(*n)); + if (!n) { + goto fail; + } + + n->type = NODE_FUNCTION; + n->function.name = name; + n->function.args = args; + + return n; + +fail: + if (name) { + free(name); + } + if (args) { + free_arguments_node(args); + } + return NULL; +} + +static struct arguments_node * make_arguments (struct arguments_arg arg, struct arguments_node *next, int need_next) +{ + if (arg.type == ARGUMENT_INVALID || (!next && need_next)) { + goto fail; + } + + struct arguments_node *n = malloc(sizeof(*n)); + if (!n) { + goto fail; + } + + n->arg = arg; + n->next = next; + + return n; + +fail: + free_argument(arg); + if (next) { + free_arguments_node(next); + } + return NULL; +} + +static struct arguments_arg make_argument_predicate (struct predicate_node *pr) +{ + struct arguments_arg ret; + + if (!pr) { + goto fail; + } + + ret.type = ARGUMENT_PREDICATE; + ret.predicate = pr; + + return ret; + +fail: + ret.type = ARGUMENT_INVALID; + return ret; +} + +static struct arguments_arg make_argument_string (char *string) +{ + struct arguments_arg ret; + + if (!string) { + goto fail; + } + + ret.type = ARGUMENT_STRING; + ret.string = string; + + return ret; + +fail: + ret.type = ARGUMENT_INVALID; + return ret; +} + +%} + +%pure-parser +%locations +%parse-param {void *scanner} +%parse-param {struct predicate_node **result} + +%union { + char *text; + struct predicate_node *node; + struct arguments_node *arg_node; + struct predicate_node nfaw; + struct arguments_arg arg_arg; +}; + +// token types +%token STRING NAME +%token PEER1_NAME PEER2_NAME AND OR NOT SPAR EPAR CONSTANT_TRUE CONSTANT_FALSE COMMA + +// string token destructor +%destructor { + free($$); +} STRING NAME + +// return values +%type predicate constant parentheses neg conjunct disjunct function +%type arguments +%type argument + +// predicate node destructor +%destructor { + if ($$) { + free_predicate_node($$); + } +} predicate constant parentheses neg conjunct disjunct function + +// argument node destructor +%destructor { + if ($$) { + free_arguments_node($$); + } +} arguments + +// argument argument destructor +%destructor { + free_argument($$); +} argument + +%left OR +%left AND +%nonassoc NOT +%right COMMA + +%% + +input: + predicate { + *result = $1; + } + ; + +predicate: constant | parentheses | neg | conjunct | disjunct | function; + +constant: + CONSTANT_TRUE { + $$ = make_constant(1); + } + | + CONSTANT_FALSE { + $$ = make_constant(0); + } + ; + +parentheses: + SPAR predicate EPAR { + $$ = $2; + } + ; + +neg: + NOT predicate { + $$ = make_negation($2); + } + ; + +conjunct: + predicate AND predicate { + $$ = make_conjunction($1, $3); + } + ; + +disjunct: + predicate OR predicate { + $$ = make_disjunction($1, $3); + } + ; + +function: + NAME SPAR EPAR { + $$ = make_function($1, NULL, 0); + } + | + NAME SPAR arguments EPAR { + $$ = make_function($1, $3, 1); + } + ; + +arguments: + argument { + $$ = make_arguments($1, NULL, 0); + } + | + argument COMMA arguments { + $$ = make_arguments($1, $3, 1); + } + ; + +argument: + predicate { + $$ = make_argument_predicate($1); + } + | + STRING { + $$ = make_argument_string($1); + } + ; diff --git a/predicate/BPredicate_internal.h b/predicate/BPredicate_internal.h new file mode 100644 index 000000000..c0457142b --- /dev/null +++ b/predicate/BPredicate_internal.h @@ -0,0 +1,147 @@ +/** + * @file BPredicate_internal.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * {@link BPredicate} expression tree definitions and functions. + */ + +#ifndef BADVPN_PREDICATE_BPREDICATE_INTERNAL_H +#define BADVPN_PREDICATE_BPREDICATE_INTERNAL_H + +#include + +#define NODE_CONSTANT 0 +#define NODE_NEG 2 +#define NODE_CONJUNCT 3 +#define NODE_DISJUNCT 4 +#define NODE_FUNCTION 5 + +struct arguments_node; + +struct predicate_node { + int type; + union { + struct { + int val; + } constant; + struct { + struct predicate_node *op; + } neg; + struct { + struct predicate_node *op1; + struct predicate_node *op2; + } conjunct; + struct { + struct predicate_node *op1; + struct predicate_node *op2; + } disjunct; + struct { + char *name; + struct arguments_node *args; + } function; + }; + int eval_value; +}; + +#define ARGUMENT_INVALID 0 +#define ARGUMENT_PREDICATE 1 +#define ARGUMENT_STRING 2 + +struct arguments_arg { + int type; + union { + struct predicate_node *predicate; + char *string; + }; +}; + +struct arguments_node { + struct arguments_arg arg; + struct arguments_node *next; +}; + +static void free_predicate_node (struct predicate_node *root); +static void free_argument (struct arguments_arg arg); +static void free_arguments_node (struct arguments_node *n); + +void free_predicate_node (struct predicate_node *root) +{ + ASSERT(root) + + switch (root->type) { + case NODE_CONSTANT: + break; + case NODE_NEG: + free_predicate_node(root->neg.op); + break; + case NODE_CONJUNCT: + free_predicate_node(root->conjunct.op1); + free_predicate_node(root->conjunct.op2); + break; + case NODE_DISJUNCT: + free_predicate_node(root->disjunct.op1); + free_predicate_node(root->disjunct.op2); + break; + case NODE_FUNCTION: + free(root->function.name); + if (root->function.args) { + free_arguments_node(root->function.args); + } + break; + default: + ASSERT(0) + break; + } + + free(root); +} + +void free_argument (struct arguments_arg arg) +{ + switch (arg.type) { + case ARGUMENT_INVALID: + break; + case ARGUMENT_PREDICATE: + free_predicate_node(arg.predicate); + break; + case ARGUMENT_STRING: + free(arg.string); + break; + default: + ASSERT(0); + } +} + +void free_arguments_node (struct arguments_node *n) +{ + ASSERT(n) + + free_argument(n->arg); + + if (n->next) { + free_arguments_node(n->next); + } + + free(n); +} + +#endif diff --git a/predicate/BPredicate_parser.h b/predicate/BPredicate_parser.h new file mode 100644 index 000000000..47238fa8d --- /dev/null +++ b/predicate/BPredicate_parser.h @@ -0,0 +1,41 @@ +/** + * @file BPredicate_parser.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * {@link BPredicate} parser definitions. + */ + +#ifndef BADVPN_PREDICATE_BPREDICATE_PARSER_H +#define BADVPN_PREDICATE_BPREDICATE_PARSER_H + +#include + +#include +#include + +// implemented in BPredicate.c +void yyerror (YYLTYPE *yylloc, yyscan_t scanner, struct predicate_node **result, char *str); + +// implemented in parser +int yyparse (void *scanner, struct predicate_node **result); + +#endif diff --git a/predicate/CMakeLists.txt b/predicate/CMakeLists.txt new file mode 100644 index 000000000..c094c55ab --- /dev/null +++ b/predicate/CMakeLists.txt @@ -0,0 +1,6 @@ +add_library(predicate + BPredicate.c + ${PROJECT_SOURCE_DIR}/generated/flex_BPredicate.c + ${PROJECT_SOURCE_DIR}/generated/bison_BPredicate.c +) +target_link_libraries(predicate system) diff --git a/predicate/LexMemoryBufferInput.h b/predicate/LexMemoryBufferInput.h new file mode 100644 index 000000000..6caef8875 --- /dev/null +++ b/predicate/LexMemoryBufferInput.h @@ -0,0 +1,79 @@ +/** + * @file LexMemoryBufferInput.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Object that can be used by a lexer to read input from a memory buffer. + */ + +#ifndef BADVPN_PREDICATE_LEXMEMORYBUFFERINPUT_H +#define BADVPN_PREDICATE_LEXMEMORYBUFFERINPUT_H + +#include + +#include + +typedef struct { + char *buf; + int len; + int pos; + int error; +} LexMemoryBufferInput; + +static void LexMemoryBufferInput_Init (LexMemoryBufferInput *input, char *buf, int len) +{ + input->buf = buf; + input->len = len; + input->pos = 0; + input->error = 0; +} + +static int LexMemoryBufferInput_Read (LexMemoryBufferInput *input, char *dest, int len) +{ + ASSERT(dest) + ASSERT(len > 0) + + if (input->pos >= input->len) { + return 0; + } + + int to_read = input->len - input->pos; + if (to_read > len) { + to_read = len; + } + + memcpy(dest, input->buf + input->pos, to_read); + input->pos += to_read; + + return to_read; +} + +static void LexMemoryBufferInput_SetError (LexMemoryBufferInput *input) +{ + input->error = 1; +} + +static int LexMemoryBufferInput_HasError (LexMemoryBufferInput *input) +{ + return input->error; +} + +#endif diff --git a/protocol/addr.bproto b/protocol/addr.bproto new file mode 100644 index 000000000..f020350d6 --- /dev/null +++ b/protocol/addr.bproto @@ -0,0 +1,11 @@ +// message for an AddrProto address +message addr { + // address type. from addr.h + required uint8 type = 1; + // for IPv4 and IPv6 addresses, the port (network byte order) + optional data("2") ip_port = 2; + // for IPv4 addresses, the IP address + optional data("4") ipv4_addr = 3; + // for IPv6 addresses, the IP address + optional data("16") ipv6_addr = 4; +}; diff --git a/protocol/addr.h b/protocol/addr.h new file mode 100644 index 000000000..8db0cd153 --- /dev/null +++ b/protocol/addr.h @@ -0,0 +1,196 @@ +/** + * @file addr.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * AddrProto, a protocol for encoding network addresses. + * + * AddrProto is built with BProto, the protocol and code generator for building + * custom message protocols. The BProto specification file is addr.bproto. + */ + +#ifndef BADVPN_PROTOCOL_ADDR_H +#define BADVPN_PROTOCOL_ADDR_H + +#include +#include + +#include +#include + +#include + +#define ADDR_TYPE_IPV4 1 +#define ADDR_TYPE_IPV6 2 + +#define ADDR_SIZE_IPV4 (addr_SIZEtype + addr_SIZEip_port + addr_SIZEipv4_addr) +#define ADDR_SIZE_IPV6 (addr_SIZEtype + addr_SIZEip_port + addr_SIZEipv6_addr) + +/** + * Determines if the given address is supported by AddrProto. + * Depends only on the type of the address. + * + * @param addr address to check. Must be recognized according to {@link BAddr_IsRecognized}. + * @return 1 if supported, 0 if not + */ +static int addr_supported (BAddr addr); + +/** + * Determines the size of the given address when encoded by AddrProto. + * Depends only on the type of the address. + * + * @param addr address to check. Must be supported according to {@link addr_supported}. + * @return encoded size + */ +static int addr_size (BAddr addr); + +/** + * Encodes an address according to AddrProto. + * + * @param out output buffer. Must have at least addr_size(addr) space. + * @param addr address to encode. Must be supported according to {@link addr_supported}. + */ +static void addr_write (uint8_t *out, BAddr addr); + +/** + * Decodes an address according to AddrProto. + * + * @param data input buffer containing the address to decode + * @param data_len size of input. Must be >=0. + * @param out_addr the decoded address will be stored here on success + * @return 1 on success, 0 on failure + */ +static int addr_read (uint8_t *data, int data_len, BAddr *out_addr) WARN_UNUSED; + +int addr_supported (BAddr addr) +{ + ASSERT(BAddr_IsRecognized(&addr)) + + switch (addr.type) { + case BADDR_TYPE_IPV4: + case BADDR_TYPE_IPV6: + return 1; + default: + return 0; + } +} + +int addr_size (BAddr addr) +{ + ASSERT(addr_supported(addr)) + + switch (addr.type) { + case BADDR_TYPE_IPV4: + return ADDR_SIZE_IPV4; + case BADDR_TYPE_IPV6: + return ADDR_SIZE_IPV6; + default: + ASSERT(0) + return 0; + } +} + +void addr_write (uint8_t *out, BAddr addr) +{ + ASSERT(addr_supported(addr)) + + addrWriter writer; + addrWriter_Init(&writer, out); + + switch (addr.type) { + case BADDR_TYPE_IPV4: { + addrWriter_Addtype(&writer, ADDR_TYPE_IPV4); + uint8_t *out_port = addrWriter_Addip_port(&writer); + memcpy(out_port, &addr.ipv4.port, sizeof(addr.ipv4.port)); + uint8_t *out_addr = addrWriter_Addipv4_addr(&writer); + memcpy(out_addr, &addr.ipv4.ip, sizeof(addr.ipv4.ip)); + } break; + case BADDR_TYPE_IPV6: { + addrWriter_Addtype(&writer, ADDR_TYPE_IPV6); + uint8_t *out_port = addrWriter_Addip_port(&writer); + memcpy(out_port, &addr.ipv6.port, sizeof(addr.ipv6.port)); + uint8_t *out_addr = addrWriter_Addipv6_addr(&writer); + memcpy(out_addr, addr.ipv6.ip, sizeof(addr.ipv6.ip)); + } break; + default: + ASSERT(0); + } + + int len = addrWriter_Finish(&writer); + + ASSERT(len == addr_size(addr)) +} + +int addr_read (uint8_t *data, int data_len, BAddr *out_addr) +{ + ASSERT(data_len >= 0) + + addrParser parser; + if (!addrParser_Init(&parser, data, data_len)) { + DEBUG("failed to parse addr"); + return 0; + } + + uint8_t type; + addrParser_Gettype(&parser, &type); + + switch (type) { + case ADDR_TYPE_IPV4: { + uint8_t *port_data; + if (!addrParser_Getip_port(&parser, &port_data)) { + DEBUG("port missing for IPv4 address"); + return 0; + } + uint8_t *addr_data; + if (!addrParser_Getipv4_addr(&parser, &addr_data)) { + DEBUG("address missing for IPv4 address"); + return 0; + } + uint16_t port; + uint32_t addr; + memcpy(&port, port_data, sizeof(port)); + memcpy(&addr, addr_data, sizeof(addr)); + BAddr_InitIPv4(out_addr, addr, port); + } break; + case ADDR_TYPE_IPV6: { + uint8_t *port_data; + if (!addrParser_Getip_port(&parser, &port_data)) { + DEBUG("port missing for IPv6 address"); + return 0; + } + uint8_t *addr_data; + if (!addrParser_Getipv6_addr(&parser, &addr_data)) { + DEBUG("address missing for IPv6 address"); + return 0; + } + uint16_t port; + memcpy(&port, port_data, sizeof(port)); + BAddr_InitIPv6(out_addr, addr_data, port); + } break; + default: + DEBUG("unknown address type %d", (int)type); + return 0; + } + + return 1; +} + +#endif diff --git a/protocol/dataproto.h b/protocol/dataproto.h new file mode 100644 index 000000000..c6048f0b7 --- /dev/null +++ b/protocol/dataproto.h @@ -0,0 +1,79 @@ +/** + * @file dataproto.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Definitions for DataProto, the protocol for data transport between VPN peers. + * + * All multi-byte integers in structs are little-endian, unless stated otherwise. + * + * A DataProto packet consists of: + * - the header (struct {@link dataproto_header}) + * - between zero and DATAPROTO_MAX_PEER_IDS destination peer IDs (struct {@link dataproto_peer_id}) + * - the payload, e.g. Ethernet frame + */ + +#ifndef BADVPN_PROTOCOL_DATAPROTO_H +#define BADVPN_PROTOCOL_DATAPROTO_H + +#include + +#include + +#define DATAPROTO_MAX_PEER_IDS 1 + +#define DATAPROTO_FLAGS_RECEIVING_KEEPALIVES 1 + +/** + * DataProto header. + */ +struct dataproto_header { + /** + * Bitwise OR of flags. Possible flags: + * - DATAPROTO_FLAGS_RECEIVING_KEEPALIVES + * Indicates that when the peer sent this packet, it has received at least + * one packet from the other peer in the last keep-alive tolerance time. + */ + uint8_t flags; + + /** + * ID of the peer this frame originates from. + */ + peerid_t from_id; + + /** + * Number of destination peer IDs that follow. + * Must be <=DATAPROTO_MAX_PEER_IDS. + */ + peerid_t num_peer_ids; +} __attribute__((packed)); + +/** + * Structure for a destination peer ID in DataProto. + * Wraps a single peerid_t in a packed struct for easy access. + */ +struct dataproto_peer_id { + peerid_t id; +} __attribute__((packed)); + +#define DATAPROTO_MAX_OVERHEAD (sizeof(struct dataproto_header) + DATAPROTO_MAX_PEER_IDS * sizeof(struct dataproto_peer_id)) + +#endif diff --git a/protocol/fragmentproto.h b/protocol/fragmentproto.h new file mode 100644 index 000000000..bbecc3daf --- /dev/null +++ b/protocol/fragmentproto.h @@ -0,0 +1,89 @@ +/** + * @file fragmentproto.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Definitions for FragmentProto, a protocol that allows sending of arbitrarily sized packets over + * a link with a fixed MTU. + * + * All multi-byte integers in structs are little-endian, unless stated otherwise. + * + * A FragmentProto packet consists of a number of chunks. + * Each chunk consists of: + * - the chunk header (struct {@link fragmentproto_chunk_header}) + * - the chunk payload, i.e. part of the frame specified in the header + */ + +#ifndef BADVPN_PROTOCOL_FRAGMENTPROTO_H +#define BADVPN_PROTOCOL_FRAGMENTPROTO_H + +#include + +#include + +typedef uint16_t fragmentproto_frameid; + +/** + * FragmentProto chunk header. + */ +struct fragmentproto_chunk_header { + /** + * Identifier of the frame this chunk belongs to. + * Frames should be given ascending identifiers as they are encoded + * into chunks (except when the ID wraps to zero). + */ + fragmentproto_frameid frame_id; + + /** + * Position in the frame where this chunk starts. + */ + uint16_t chunk_start; + + /** + * Length of the chunk's payload. + */ + uint16_t chunk_len; + + /** + * Whether this is the last chunk of the frame, i.e. + * the total length of the frame is chunk_start + chunk_len. + */ + uint8_t is_last; +} __attribute__((packed)); + +/** + * Calculates how many chunks are needed at most for encoding one frame of the + * given maximum size with FragmentProto onto a carrier with a given MTU. + * This includes the case when the first chunk of a frame is not the first chunk + * in a FragmentProto packet. + * + * @param carrier_mtu MTU of the carrier, i.e. maximum length of FragmentProto packets. Must be >sizeof(struct fragmentproto_chunk_header). + * @param frame_mtu maximum frame size + * @return maximum number of chunks needed. Will be >0. + */ +static int fragmentproto_max_chunks_for_frame (int carrier_mtu, int frame_mtu) +{ + ASSERT(carrier_mtu > sizeof(struct fragmentproto_chunk_header)) + + return (BDIVIDE_UP(frame_mtu, (carrier_mtu - sizeof(struct fragmentproto_chunk_header))) + 1); +} + +#endif diff --git a/protocol/msgproto.bproto b/protocol/msgproto.bproto new file mode 100644 index 000000000..202931e23 --- /dev/null +++ b/protocol/msgproto.bproto @@ -0,0 +1,43 @@ +// message for all MsgProto messages +message msg { + // message type, from msgproto.h + required uint16 type = 1; + // message payload. Is itself one of the messages below + // for "youconnect", "seed" and "confirmseed" messages, + // and empty for other messages + required data payload = 2; +}; + +// "youconnect" message payload +message msg_youconnect { + // external addresses to try; one or more msg_youconnect_addr messages + required repeated data addr = 1; + // encryption key if using UDP and encryption is enabled + optional data key = 2; + // password if using TCP + optional uint64 password = 3; +}; + +// an external address +message msg_youconnect_addr { + // scope name for this address + required data name = 1; + // address according to AddrProto + required data addr = 2; +}; + +// "seed" message payload +message msg_seed { + // identifier for the seed being send + required uint16 seed_id = 1; + // seed encryption key + required data key = 2; + // seed IV + required data iv = 3; +}; + +// "confirmseed" message payload +message msg_confirmseed { + // identifier for the seed being confirmed + required uint16 seed_id = 1; +}; diff --git a/protocol/msgproto.h b/protocol/msgproto.h new file mode 100644 index 000000000..129c7733f --- /dev/null +++ b/protocol/msgproto.h @@ -0,0 +1,68 @@ +/** + * @file msgproto.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Definitions for MsgProto, the protocol the VPN peers communicate in via the server + * in order to establish data connections. + * + * MsgProto is built with BProto, the protocol and code generator for building + * custom message protocols. The BProto specification file is msgproto.bproto. + * + * It goes roughly like that: + * + * We name one peer the master and the other the slave. The master is the one with + * greater ID. + * When the peers get to know about each other, the master starts the binding procedure. + * It binds/listens to an address, and sends the slave the "youconnect" message. It + * contains a list of external addresses for that bind address and additional parameters. + * Each external address includes a string called a scope name. The slave, which receives + * the "youconnect" message, finds the first external address whose scope it recognizes, + * and attempts to establish connection to that address. If it finds an address, buf fails + * at connecting, it sends "youretry", which makes the master restart the binding procedure + * after some time. If it however does not recognize any external address, it sends + * "cannotconnect" back to the master. + * When the master receives the "cannotconnect", it tries the next bind address, as described + * above. When the master runs out of bind addresses, it sends "cannotbind" to the slave. + * When the slave receives the "cannotbind", it starts its own binding procedure, similarly + * to what is described above, with master and slave reversed. First difference is if the + * master fails to connect to a recognized address, it doesn't send "youretry", but rather + * simply restarts the whole procedure after some time. The other difference is when the + * slave runs out of bind addresses, it not only sends "cannotbind" to the master, but + * registers relaying to the master. And in this case, when the master receives the "cannotbind", + * it doesn't start the binding procedure all all over, but registers relaying to the slave. + */ + +#ifndef BADVPN_PROTOCOL_MSGPROTO_H +#define BADVPN_PROTOCOL_MSGPROTO_H + +#include + +#define MSGID_YOUCONNECT 1 +#define MSGID_CANNOTCONNECT 2 +#define MSGID_CANNOTBIND 3 +#define MSGID_YOURETRY 5 +#define MSGID_SEED 6 +#define MSGID_CONFIRMSEED 7 + +#define MSG_MAX_PAYLOAD (SC_MAX_MSGLEN - msg_SIZEtype - msg_SIZEpayload(0)) + +#endif diff --git a/protocol/packetproto.h b/protocol/packetproto.h new file mode 100644 index 000000000..5a80d08c6 --- /dev/null +++ b/protocol/packetproto.h @@ -0,0 +1,59 @@ +/** + * @file packetproto.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Definitions for PacketProto, a protocol that allows sending of packets + * over a reliable stream connection. + * + * All multi-byte integers in structs are little-endian, unless stated otherwise. + * + * Packets are encoded into a stream by representing each packet with: + * - a 16-bit little-endian unsigned integer representing the length + * of the payload + * - that many bytes of payload + */ + +#ifndef BADVPN_PROTOCOL_PACKETPROTO_H +#define BADVPN_PROTOCOL_PACKETPROTO_H + +#include +#include + +#include + +/** + * PacketProto packet header. + * Wraps a single uint16_t in a packed struct for easy access. + */ +struct packetproto_header +{ + /** + * Length of the packet payload that follows. + */ + uint16_t len; +} __attribute__((packed)); + +#define PACKETPROTO_ENCLEN(_len) (sizeof(struct packetproto_header) + (_len)) + +#define PACKETPROTO_MAXPAYLOAD UINT16_MAX + +#endif diff --git a/protocol/scproto.h b/protocol/scproto.h new file mode 100644 index 000000000..8cad7bf8d --- /dev/null +++ b/protocol/scproto.h @@ -0,0 +1,185 @@ +/** + * @file scproto.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Definitions for SCProto, the protocol that the clients communicate in + * with the server. + * + * All multi-byte integers in structs are little-endian, unless stated otherwise. + * + * A SCProto packet consists of: + * - a header (struct {@link sc_header}) which contains the type of the + * packet + * - the payload + * + * It goes roughly like that: + * + * When the client connects to the server, it sends a "clienthello" packet + * to the server. The packet contains the protocol version the client is using. + * When the server receives the "clienthello" packet, it checks the version. + * If it doesn't match, it disconnects the client. Otherwise the server sends + * the client a "serverhello" packet to the client. That packet contains + * the ID of the client and possibly its IPv4 address as the server sees it + * (zero if not applicable). + * The server than proceeds to synchronize the peers' knowledge of each other. + * It does that by sending a "newclient" messages to a client to inform it of + * another peer, and "endclient" messages to inform it that a peer is gone. + * The server forwards messages between synchronized peers to allow them to + * communicate. A peer sends a message to another peer by sending the "outmsg" + * packet to the server, and the server delivers a message to a peer by sending + * it the "inmsg" packet. + */ + +#ifndef BADVPN_PROTOCOL_SCPROTO_H +#define BADVPN_PROTOCOL_SCPROTO_H + +#include + +#define SC_VERSION 26 + +#define SC_KEEPALIVE_INTERVAL 10000 + +/** + * SCProto packet header. + * Follows up to SC_MAX_PAYLOAD bytes of payload. + */ +struct sc_header { + /** + * Message type. + */ + uint8_t type; +} __attribute__((packed)); + +#define SC_MAX_PAYLOAD 2000 +#define SC_MAX_ENC (sizeof(struct sc_header) + SC_MAX_PAYLOAD) + +typedef uint16_t peerid_t; + +#define SCID_KEEPALIVE 0 +#define SCID_CLIENTHELLO 1 +#define SCID_SERVERHELLO 2 +#define SCID_NEWCLIENT 3 +#define SCID_ENDCLIENT 4 +#define SCID_OUTMSG 5 +#define SCID_INMSG 6 + +/** + * "clienthello" client packet payload. + * Packet type is SCID_CLIENTHELLO. + */ +struct sc_client_hello { + /** + * Protocol version the client is using. + */ + uint16_t version; +} __attribute__((packed)); + +/** + * "serverhello" server packet payload. + * Packet type is SCID_SERVERHELLO. + */ +struct sc_server_hello { + /** + * Flags. Not used yet. + */ + uint16_t flags; + + /** + * Peer ID of the client. + */ + peerid_t id; + + /** + * IPv4 address of the client as seen by the server + * (network byte order). Zero if not applicable. + */ + uint32_t clientAddr; +} __attribute__((packed)); + +/** + * "newclient" server packet payload. + * Packet type is SCID_NEWCLIENT. + * If the server is using TLS, follows up to SCID_NEWCLIENT_MAX_CERT_LEN + * bytes of the new client's certificate (encoded in DER). + */ +struct sc_server_newclient { + /** + * ID of the new peer. + */ + peerid_t id; + + /** + * Flags. Possible flags: + * - SCID_NEWCLIENT_FLAG_RELAY_SERVER + * You can relay frames to other peers through this peer. + * - SCID_NEWCLIENT_FLAG_RELAY_CLIENT + * You must allow this peer to relay frames to other peers through you. + */ + uint16_t flags; +} __attribute__((packed)); + +#define SCID_NEWCLIENT_FLAG_RELAY_SERVER 1 +#define SCID_NEWCLIENT_FLAG_RELAY_CLIENT 2 + +#define SCID_NEWCLIENT_MAX_CERT_LEN (SC_MAX_PAYLOAD - sizeof(struct sc_server_newclient)) + +/** + * "endclient" server packet payload. + * Packet type is SCID_ENDCLIENT. + */ +struct sc_server_endclient { + /** + * ID of the removed peer. + */ + peerid_t id; +} __attribute__((packed)); + +/** + * "outmsg" client packet header. + * Packet type is SCID_OUTMSG. + * Follows up to SC_MAX_MSGLEN bytes of message payload. + */ +struct sc_client_outmsg { + /** + * ID of the destionation peer. + */ + peerid_t clientid; +} __attribute__((packed)); + +/** + * "inmsg" server packet payload. + * Packet type is SCID_INMSG. + * Follows up to SC_MAX_MSGLEN bytes of message payload. + */ +struct sc_server_inmsg { + /** + * ID of the source peer. + */ + peerid_t clientid; +} __attribute__((packed)); + +#define _SC_MAX_OUTMSGLEN (SC_MAX_PAYLOAD - sizeof(struct sc_client_outmsg)) +#define _SC_MAX_INMSGLEN (SC_MAX_PAYLOAD - sizeof(struct sc_server_inmsg)) + +#define SC_MAX_MSGLEN (_SC_MAX_OUTMSGLEN < _SC_MAX_INMSGLEN ? _SC_MAX_OUTMSGLEN : _SC_MAX_INMSGLEN) + +#endif diff --git a/protocol/spproto.h b/protocol/spproto.h new file mode 100644 index 000000000..d1e8bda6f --- /dev/null +++ b/protocol/spproto.h @@ -0,0 +1,180 @@ +/** + * @file spproto.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Protocol for securing datagram communication. + * + * Security features implemented: + * - Encryption. Encrypts packets with a block cipher. + * Protects against a third party from seeing the data + * being transmitted. + * - Hashes. Adds a hash of the packet into the packet. + * Combined with encryption, protects against tampering + * with packets and crafting new packets. + * - One-time passwords. Adds a password to each packet + * for the receiver to recognize. Protects agains replaying + * packets and crafting new packets. + * + * A SPProto plaintext packet contains the following, in order: + * - if OTPs are used, a struct {@link spproto_otpdata} which contains + * the seed ID and the OTP, + * - if hashes are used, the hash, + * - payload data. + * + * If encryption is used: + * - the plaintext is padded by appending a 0x01 byte and as many 0x00 + * bytes as needed to align to block size, + * - the padded plaintext is encrypted, and + * - the initialization vector (IV) is prepended. + */ + +#ifndef BADVPN_PROTOCOL_SPPROTO_H +#define BADVPN_PROTOCOL_SPPROTO_H + +#include + +#include +#include +#include +#include +#include + +#define SPPROTO_HASH_MODE_NONE 0 +#define SPPROTO_ENCRYPTION_MODE_NONE 0 +#define SPPROTO_OTP_MODE_NONE 0 + +/** + * Stores security parameters for SPProto. + */ +struct spproto_security_params { + /** + * Hash mode. + * Either SPPROTO_HASH_MODE_NONE for no hashes, or a valid bhash + * hash mode. + */ + int hash_mode; + + /** + * Encryption mode. + * Either SPPROTO_ENCRYPTION_MODE_NONE for no encryption, or a valid + * {@link BEncryption} cipher. + */ + int encryption_mode; + + /** + * One-time password (OTP) mode. + * Either SPPROTO_OTP_MODE_NONE for no OTPs, or a valid + * {@link BEncryption} cipher. + */ + int otp_mode; + + /** + * If OTPs are used (otp_mode != SPPROTO_OTP_MODE_NONE), number of + * OTPs generated from a single seed. + */ + int otp_num; +}; + +#define SPPROTO_HAVE_HASH(_params) ((_params).hash_mode != SPPROTO_HASH_MODE_NONE) +#define SPPROTO_HASH_SIZE(_params) ( \ + SPPROTO_HAVE_HASH(_params) ? \ + BHash_size((_params).hash_mode) : \ + 0 \ +) + +#define SPPROTO_HAVE_ENCRYPTION(_params) ((_params).encryption_mode != SPPROTO_ENCRYPTION_MODE_NONE) + +#define SPPROTO_HAVE_OTP(_params) ((_params).otp_mode != SPPROTO_OTP_MODE_NONE) + +struct spproto_otpdata { + uint16_t seed_id; + otp_t otp; +} __attribute__((packed)); + +#define SPPROTO_HEADER_OTPDATA_OFF(_params) 0 +#define SPPROTO_HEADER_OTPDATA_LEN(_params) (SPPROTO_HAVE_OTP(_params) ? sizeof(struct spproto_otpdata) : 0) +#define SPPROTO_HEADER_HASH_OFF(_params) (SPPROTO_HEADER_OTPDATA_OFF(_params) + SPPROTO_HEADER_OTPDATA_LEN(_params)) +#define SPPROTO_HEADER_HASH_LEN(_params) SPPROTO_HASH_SIZE(_params) +#define SPPROTO_HEADER_LEN(_params) (SPPROTO_HEADER_HASH_OFF(_params) + SPPROTO_HEADER_HASH_LEN(_params)) + +/** + * Checks if the given SPProto security parameters are valid. + * + * @param params security parameters to check + * @return 1 if valid, 0 if not + */ +static int spproto_validate_security_params (struct spproto_security_params params) +{ + return ( + (params.hash_mode == SPPROTO_HASH_MODE_NONE || BHash_type_valid(params.hash_mode)) && + (params.encryption_mode == SPPROTO_ENCRYPTION_MODE_NONE || BEncryption_cipher_valid(params.encryption_mode)) && + (params.otp_mode == SPPROTO_OTP_MODE_NONE || BEncryption_cipher_valid(params.otp_mode)) && + (params.otp_mode == SPPROTO_OTP_MODE_NONE || params.otp_num > 0) + ); +} + +/** + * Calculates the maximum payload size for SPProto given the + * security parameters and the maximum encoded packet size. + * + * @param params security parameters Must be valid according to + * {@link spproto_validate_security_params}. + * @param carrier_mtu maximum encoded packet size. Must be >=0. + * @return maximum payload size. Negative means is is impossible + * to encode anything. + */ +static int spproto_payload_mtu_for_carrier_mtu (struct spproto_security_params params, int carrier_mtu) +{ + ASSERT(spproto_validate_security_params(params)) + ASSERT(carrier_mtu >= 0) + + if (params.encryption_mode == SPPROTO_ENCRYPTION_MODE_NONE) { + return (carrier_mtu - SPPROTO_HEADER_LEN(params)); + } else { + int block_size = BEncryption_cipher_block_size(params.encryption_mode); + return (BALIGN_DOWN_N(carrier_mtu, block_size) - block_size - SPPROTO_HEADER_LEN(params) - 1); + } +} + +/** + * Calculates the maximum encoded packet size for SPProto given the + * security parameters and the maximum payload size. + * + * @param params security parameters Must be valid according to + * {@link spproto_validate_security_params}. + * @param payload_mtu maximum payload size. Must be >=0. + * @return maximum encoded packet size + */ +static int spproto_carrier_mtu_for_payload_mtu (struct spproto_security_params params, int payload_mtu) +{ + ASSERT(spproto_validate_security_params(params)) + ASSERT(payload_mtu >= 0) + + if (params.encryption_mode == SPPROTO_ENCRYPTION_MODE_NONE) { + return (SPPROTO_HEADER_LEN(params) + payload_mtu); + } else { + int block_size = BEncryption_cipher_block_size(params.encryption_mode); + return (block_size + BALIGN_UP_N((SPPROTO_HEADER_LEN(params) + payload_mtu + 1), block_size)); + } +} + +#endif diff --git a/scripts/cmake b/scripts/cmake new file mode 100755 index 000000000..51af7777a --- /dev/null +++ b/scripts/cmake @@ -0,0 +1,8 @@ +#!/bin/sh + +export ROOT="" +export MINGW="/home//mingw/cross_win32" + +export PATH="$MINGW/bin:$PATH" + +exec /usr/bin/cmake -DCMAKE_TOOLCHAIN_FILE="$ROOT/toolchain.cmake" "$@" diff --git a/scripts/copy_nss b/scripts/copy_nss new file mode 100755 index 000000000..6fd5b49de --- /dev/null +++ b/scripts/copy_nss @@ -0,0 +1,22 @@ +#!/bin/sh + +NSSDIST="$1" +DEST="$2" + +if [ -z "${NSSDIST}" ] || [ -z "${DEST}" ]; then + echo "Copies a Windows build of NSS such that it can be found by the BadVPN build system" + echo "Usage: $0 " + exit 1 +fi + +NSSOBJ="${NSSDIST}/WINNT5.1_OPT.OBJ" + +set -e + +mkdir -p "${DEST}"/include +cp -r "${NSSOBJ}/include"/* "${DEST}"/include/ +cp -r "${NSSDIST}/public/nss"/* "${DEST}"/include/ +mkdir -p "${DEST}"/lib +cp "${NSSOBJ}/lib"/{libnspr4,libplc4,libplds4,ssl3,smime3,nss3}.lib "${DEST}"/lib/ +mkdir -p "${DEST}"/bin +cp "${NSSOBJ}/lib"/*.dll "${DEST}"/bin/ diff --git a/scripts/toolchain.cmake b/scripts/toolchain.cmake new file mode 100644 index 000000000..5f4a90a60 --- /dev/null +++ b/scripts/toolchain.cmake @@ -0,0 +1,6 @@ +SET(CMAKE_SYSTEM_NAME Windows) +SET(CMAKE_C_COMPILER i686-w64-mingw32-gcc) +SET(CMAKE_FIND_ROOT_PATH $ENV{ROOT}) +SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) +SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/security/BEncryption.h b/security/BEncryption.h new file mode 100644 index 000000000..2f5f6a569 --- /dev/null +++ b/security/BEncryption.h @@ -0,0 +1,365 @@ +/** + * @file BEncryption.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Block cipher encryption abstraction. + */ + +#ifndef BADVPN_SECURITY_BENCRYPTION_H +#define BADVPN_SECURITY_BENCRYPTION_H + +#include +#include + +#ifdef BADVPN_USE_CRYPTODEV +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include +#include + +#include +#include + +#define BENCRYPTION_MODE_ENCRYPT 1 +#define BENCRYPTION_MODE_DECRYPT 2 + +#define BENCRYPTION_CIPHER_BLOWFISH 1 +#define BENCRYPTION_CIPHER_BLOWFISH_BLOCK_SIZE 8 +#define BENCRYPTION_CIPHER_BLOWFISH_KEY_SIZE 16 + +#define BENCRYPTION_CIPHER_AES 2 +#define BENCRYPTION_CIPHER_AES_BLOCK_SIZE 16 +#define BENCRYPTION_CIPHER_AES_KEY_SIZE 16 + +/** + * Block cipher encryption abstraction. + */ +typedef struct { + DebugObject d_obj; + int mode; + int cipher; + #ifdef BADVPN_USE_CRYPTODEV + int use_cryptodev; + #endif + union { + BF_KEY blowfish; + struct { + AES_KEY encrypt; + AES_KEY decrypt; + } aes; + #ifdef BADVPN_USE_CRYPTODEV + struct { + int fd; + int cfd; + int cipher; + uint32_t ses; + } cryptodev; + #endif + }; +} BEncryption; + +/** + * Checks if the given cipher number is valid. + * + * @param cipher cipher number + * @return 1 if valid, 0 if not + */ +static int BEncryption_cipher_valid (int cipher); + +/** + * Returns the block size of a cipher. + * + * @param cipher cipher number. Must be valid. + * @return block size in bytes + */ +static int BEncryption_cipher_block_size (int cipher); + +/** + * Returns the key size of a cipher. + * + * @param cipher cipher number. Must be valid. + * @return key size in bytes + */ +static int BEncryption_cipher_key_size (int cipher); + +/** + * Initializes the object. + * + * @param enc the object + * @param mode whether encryption or decryption is to be done, or both. + * Must be a bitwise-OR of at least one of BENCRYPTION_MODE_ENCRYPT + * and BENCRYPTION_MODE_DECRYPT. + * @param cipher cipher number. Must be valid. + * @param key encryption key + */ +static void BEncryption_Init (BEncryption *enc, int mode, int cipher, uint8_t *key); + +/** + * Frees the object. + * + * @param enc the object + */ +static void BEncryption_Free (BEncryption *enc); + +/** + * Encrypts data. + * The object must have been initialized with mode including + * BENCRYPTION_MODE_ENCRYPT. + * + * @param enc the object + * @param in data to encrypt + * @param out ciphertext output + * @param len number of bytes to encrypt. Must be >=0 and a multiple of + * block size. + * @param iv initialization vector. Updated such that continuing a previous encryption + * starting with the updated IV is equivalent to performing just one encryption. + */ +static void BEncryption_Encrypt (BEncryption *enc, uint8_t *in, uint8_t *out, int len, uint8_t *iv); + +/** + * Decrypts data. + * The object must have been initialized with mode including + * BENCRYPTION_MODE_DECRYPT. + * + * @param enc the object + * @param in data to decrypt + * @param out plaintext output + * @param len number of bytes to decrypt. Must be >=0 and a multiple of + * block size. + * @param iv initialization vector. Updated such that continuing a previous decryption + * starting with the updated IV is equivalent to performing just one decryption. + */ +static void BEncryption_Decrypt (BEncryption *enc, uint8_t *in, uint8_t *out, int len, uint8_t *iv); + +int BEncryption_cipher_valid (int cipher) +{ + switch (cipher) { + case BENCRYPTION_CIPHER_BLOWFISH: + case BENCRYPTION_CIPHER_AES: + return 1; + default: + return 0; + } +} + +int BEncryption_cipher_block_size (int cipher) +{ + switch (cipher) { + case BENCRYPTION_CIPHER_BLOWFISH: + return BENCRYPTION_CIPHER_BLOWFISH_BLOCK_SIZE; + case BENCRYPTION_CIPHER_AES: + return BENCRYPTION_CIPHER_AES_BLOCK_SIZE; + default: + ASSERT(0) + return 0; + } +} + +int BEncryption_cipher_key_size (int cipher) +{ + switch (cipher) { + case BENCRYPTION_CIPHER_BLOWFISH: + return BENCRYPTION_CIPHER_BLOWFISH_KEY_SIZE; + case BENCRYPTION_CIPHER_AES: + return BENCRYPTION_CIPHER_AES_KEY_SIZE; + default: + ASSERT(0) + return 0; + } +} + +void BEncryption_Init (BEncryption *enc, int mode, int cipher, uint8_t *key) +{ + ASSERT(!(mode&~(BENCRYPTION_MODE_ENCRYPT|BENCRYPTION_MODE_DECRYPT))) + ASSERT((mode&BENCRYPTION_MODE_ENCRYPT) || (mode&BENCRYPTION_MODE_DECRYPT)) + + enc->mode = mode; + enc->cipher = cipher; + + #ifdef BADVPN_USE_CRYPTODEV + + switch (enc->cipher) { + case BENCRYPTION_CIPHER_AES: + enc->cryptodev.cipher = CRYPTO_AES_CBC; + break; + default: + goto fail1; + } + + if ((enc->cryptodev.fd = open("/dev/crypto", O_RDWR, 0)) < 0) { + DEBUG("failed to open /dev/crypto"); + goto fail1; + } + + if (ioctl(enc->cryptodev.fd, CRIOGET, &enc->cryptodev.cfd)) { + DEBUG("failed ioctl(CRIOGET)"); + goto fail2; + } + + struct session_op sess; + memset(&sess, 0, sizeof(sess)); + sess.cipher = enc->cryptodev.cipher; + sess.keylen = BEncryption_cipher_key_size(enc->cipher); + sess.key = key; + if (ioctl(enc->cryptodev.cfd, CIOCGSESSION, &sess)) { + DEBUG("failed ioctl(CIOCGSESSION)"); + goto fail3; + } + + enc->cryptodev.ses = sess.ses; + enc->use_cryptodev = 1; + + goto success; + +fail3: + ASSERT_FORCE(close(enc->cryptodev.cfd) == 0) +fail2: + ASSERT_FORCE(close(enc->cryptodev.fd) == 0) +fail1: + + enc->use_cryptodev = 0; + + #endif + + int res; + + switch (enc->cipher) { + case BENCRYPTION_CIPHER_BLOWFISH: + BF_set_key(&enc->blowfish, BENCRYPTION_CIPHER_BLOWFISH_KEY_SIZE, key); + break; + case BENCRYPTION_CIPHER_AES: + if (enc->mode&BENCRYPTION_MODE_ENCRYPT) { + res = AES_set_encrypt_key(key, 128, &enc->aes.encrypt); + ASSERT(res >= 0) + } + if (enc->mode&BENCRYPTION_MODE_DECRYPT) { + res = AES_set_decrypt_key(key, 128, &enc->aes.decrypt); + ASSERT(res >= 0) + } + break; + default: + ASSERT(0) + ; + } + +success: + // init debug object + DebugObject_Init(&enc->d_obj); +} + +void BEncryption_Free (BEncryption *enc) +{ + // free debug object + DebugObject_Free(&enc->d_obj); + + #ifdef BADVPN_USE_CRYPTODEV + + if (enc->use_cryptodev) { + ASSERT_FORCE(ioctl(enc->cryptodev.cfd, CIOCFSESSION, &enc->cryptodev.ses) == 0) + ASSERT_FORCE(close(enc->cryptodev.cfd) == 0) + ASSERT_FORCE(close(enc->cryptodev.fd) == 0) + } + + #endif +} + +void BEncryption_Encrypt (BEncryption *enc, uint8_t *in, uint8_t *out, int len, uint8_t *iv) +{ + ASSERT(enc->mode&BENCRYPTION_MODE_ENCRYPT) + ASSERT(len >= 0) + ASSERT(len % BEncryption_cipher_block_size(enc->cipher) == 0) + + #ifdef BADVPN_USE_CRYPTODEV + + if (enc->use_cryptodev) { + struct crypt_op cryp; + memset(&cryp, 0, sizeof(cryp)); + cryp.ses = enc->cryptodev.ses; + cryp.len = len; + cryp.src = in; + cryp.dst = out; + cryp.iv = iv; + cryp.op = COP_ENCRYPT; + ASSERT_FORCE(ioctl(enc->cryptodev.cfd, CIOCCRYPT, &cryp) == 0) + + return; + } + + #endif + + switch (enc->cipher) { + case BENCRYPTION_CIPHER_BLOWFISH: + BF_cbc_encrypt(in, out, len, &enc->blowfish, iv, BF_ENCRYPT); + break; + case BENCRYPTION_CIPHER_AES: + AES_cbc_encrypt(in, out, len, &enc->aes.encrypt, iv, AES_ENCRYPT); + break; + default: + ASSERT(0); + } +} + +void BEncryption_Decrypt (BEncryption *enc, uint8_t *in, uint8_t *out, int len, uint8_t *iv) +{ + ASSERT(enc->mode&BENCRYPTION_MODE_DECRYPT) + ASSERT(len >= 0) + ASSERT(len % BEncryption_cipher_block_size(enc->cipher) == 0) + + #ifdef BADVPN_USE_CRYPTODEV + + if (enc->use_cryptodev) { + struct crypt_op cryp; + memset(&cryp, 0, sizeof(cryp)); + cryp.ses = enc->cryptodev.ses; + cryp.len = len; + cryp.src = in; + cryp.dst = out; + cryp.iv = iv; + cryp.op = COP_DECRYPT; + ASSERT_FORCE(ioctl(enc->cryptodev.cfd, CIOCCRYPT, &cryp) == 0) + + return; + } + + #endif + + switch (enc->cipher) { + case BENCRYPTION_CIPHER_BLOWFISH: + BF_cbc_encrypt(in, out, len, &enc->blowfish, iv, BF_DECRYPT); + break; + case BENCRYPTION_CIPHER_AES: + AES_cbc_encrypt(in, out, len, &enc->aes.decrypt, iv, AES_DECRYPT); + break; + default: + ASSERT(0); + } +} + +#endif diff --git a/security/OTPCalculator.h b/security/OTPCalculator.h new file mode 100644 index 000000000..ff7d5a2fa --- /dev/null +++ b/security/OTPCalculator.h @@ -0,0 +1,169 @@ +/** + * @file OTPCalculator.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Object that calculates OTPs. + */ + +#ifndef BADVPN_SECURITY_OTPCALCULATOR_H +#define BADVPN_SECURITY_OTPCALCULATOR_H + +#include +#include + +#include +#include +#include +#include +#include + +/** + * Type for an OTP. + */ +typedef uint32_t otp_t; + +/** + * Object that calculates OTPs. + */ +typedef struct { + DebugObject d_obj; + int num_otps; + int cipher; + int block_size; + int num_blocks; + otp_t *data; +} OTPCalculator; + +/** + * Initializes the calculator. + * + * @param calc the object + * @param num_otps number of OTPs to generate from a seed. Must be >=0. + * @param cipher encryption cipher for calculating the OTPs. Must be valid + * according to {@link BEncryption_cipher_valid}. + * @return 1 on success, 0 on failure + */ +static int OTPCalculator_Init (OTPCalculator *calc, int num_otps, int cipher) WARN_UNUSED; + +/** + * Frees the calculator. + * + * @param calc the object + */ +static void OTPCalculator_Free (OTPCalculator *calc); + +/** + * Generates OTPs from the given key and IV. + * + * @param calc the object + * @param key encryption key + * @param iv initialization vector + * @param shuffle whether to shuffle the OTPs. Must be 1 or 0. + * @return pointer to an array of 32-bit OPTs. Constains as many OTPs as was specified + * in {@link OTPCalculator_Init}. Valid until the next generation or + * until the object is freed. + */ +static otp_t * OTPCalculator_Generate (OTPCalculator *calc, uint8_t *key, uint8_t *iv, int shuffle); + +int OTPCalculator_Init (OTPCalculator *calc, int num_otps, int cipher) +{ + ASSERT(num_otps >= 0) + ASSERT(BEncryption_cipher_valid(cipher)) + + // init arguments + calc->num_otps = num_otps; + calc->cipher = cipher; + + // remember block size + calc->block_size = BEncryption_cipher_block_size(calc->cipher); + + // calculate number of blocks + calc->num_blocks = BDIVIDE_UP(calc->num_otps * sizeof(otp_t), calc->block_size); + + // allocate buffer + calc->data = malloc(calc->num_blocks * calc->block_size); + if (!calc->data) { + goto fail0; + } + + // init debug object + DebugObject_Init(&calc->d_obj); + + return 1; + +fail0: + return 0; +} + +void OTPCalculator_Free (OTPCalculator *calc) +{ + // free debug object + DebugObject_Free(&calc->d_obj); + + // free buffer + free(calc->data); +} + +otp_t * OTPCalculator_Generate (OTPCalculator *calc, uint8_t *key, uint8_t *iv, int shuffle) +{ + ASSERT(shuffle == 0 || shuffle == 1) + + // copy IV so it can be updated + uint8_t iv_work[calc->block_size]; + memcpy(iv_work, iv, calc->block_size); + + // create zero block + uint8_t zero[calc->block_size]; + memset(zero, 0, calc->block_size); + + // init encryptor + BEncryption encryptor; + BEncryption_Init(&encryptor, BENCRYPTION_MODE_ENCRYPT, calc->cipher, key); + + // encrypt zero blocks + for (int i = 0; i < calc->num_blocks; i++) { + BEncryption_Encrypt(&encryptor, zero, (uint8_t *)calc->data + i * calc->block_size, calc->block_size, iv_work); + } + + // free encryptor + BEncryption_Free(&encryptor); + + // shuffle if requested + if (shuffle) { + int i = 0; + while (i < calc->num_otps) { + uint16_t ints[256]; + brandom_randomize((uint8_t *)ints, sizeof(ints)); + for (int j = 0; j < 256 && i < calc->num_otps; j++) { + int newIndex = i + (ints[j] % (calc->num_otps - i)); + otp_t temp = calc->data[i]; + calc->data[i] = calc->data[newIndex]; + calc->data[newIndex] = temp; + i++; + } + } + } + + return calc->data; +} + +#endif diff --git a/security/OTPChecker.bstruct b/security/OTPChecker.bstruct new file mode 100644 index 000000000..bea43eb8c --- /dev/null +++ b/security/OTPChecker.bstruct @@ -0,0 +1,8 @@ +structure oc_table ("int num_entries") { + "uint16_t" id; + "struct OTPChecker_entry" entries["num_entries"]; +}; + +structure oc_tables ("int num_tables, int num_entries") { + structure oc_table("num_entries") tables["num_tables"]; +}; diff --git a/security/OTPChecker.h b/security/OTPChecker.h new file mode 100644 index 000000000..338a90dd4 --- /dev/null +++ b/security/OTPChecker.h @@ -0,0 +1,285 @@ +/** + * @file OTPChecker.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Object that checks OTPs agains known seeds. + */ + +#ifndef BADVPN_SECURITY_OTPCHECKER_H +#define BADVPN_SECURITY_OTPCHECKER_H + +#include + +#include +#include +#include +#include +#include + +struct OTPChecker_entry { + otp_t otp; + int avail; +}; + +#include + +/** + * Object that checks OTPs agains known seeds. + */ +typedef struct { + DebugObject d_obj; + int num_otps; + int num_entries; + int num_tables; + int tables_used; + int next_table; + OTPCalculator calc; + oc_tablesParams tables_params; + oc_tables *tables; +} OTPChecker; + +/** + * Initializes the checker. + * + * @param mc the object + * @param num_otps number of OTPs to generate from a seed. Must be >0. + * @param cipher encryption cipher for calculating the OTPs. Must be valid + * according to {@link BEncryption_cipher_valid}. + * @param num_tables number of tables to keep, each for one seed. Must be >0. + * @return 1 on success, 0 on failure + */ +static int OTPChecker_Init (OTPChecker *mc, int num_otps, int cipher, int num_tables) WARN_UNUSED; + +/** + * Frees the checker. + * + * @param mc the object + */ +static void OTPChecker_Free (OTPChecker *mc); + +/** + * Adds a seed whose OTPs should be recognized. + * + * @param mc the object + * @param seed_id seed identifier + * @param key encryption key + * @param iv initialization vector + */ +static void OTPChecker_AddSeed (OTPChecker *mc, uint16_t seed_id, uint8_t *key, uint8_t *iv); + +/** + * Removes all active seeds. + * + * @param mc the object + */ +static void OTPChecker_RemoveSeeds (OTPChecker *mc); + +/** + * Checks an OTP. + * + * @param mc the object + * @param seed_id identifer of seed whom the OTP is claimed to belong to + * @param otp OTP to check + * @return 1 if the OTP is valid, 0 if not + */ +static int OTPChecker_CheckOTP (OTPChecker *mc, uint16_t seed_id, otp_t otp); + +static void OTPChecker_Table_Empty (OTPChecker *mc, oc_table *t); +static void OTPChecker_Table_AddOTP (OTPChecker *mc, oc_table *t, otp_t otp); +static void OTPChecker_Table_Generate (OTPChecker *mc, oc_table *t, OTPCalculator *calc, uint8_t *key, uint8_t *iv); +static int OTPChecker_Table_CheckOTP (OTPChecker *mc, oc_table *t, otp_t otp); + +void OTPChecker_Table_Empty (OTPChecker *mc, oc_table *t) +{ + for (int i = 0; i < mc->num_entries; i++) { + oc_table_entries_at(&mc->tables_params.tables_params, t, i)->avail = -1; + } +} + +void OTPChecker_Table_AddOTP (OTPChecker *mc, oc_table *t, otp_t otp) +{ + // calculate starting index + int start_index = otp % mc->num_entries; + + // try indexes starting with the base position + for (int i = 0; i < mc->num_entries; i++) { + int index = BMODADD(start_index, i, mc->num_entries); + struct OTPChecker_entry *entry = oc_table_entries_at(&mc->tables_params.tables_params, t, index); + + // if we find a free index, use it + if (entry->avail < 0) { + entry->otp = otp; + entry->avail = 1; + return; + } + + // if we find a used index with the same mac, + // use it by incrementing its count + if (entry->otp == otp) { + entry->avail++; + return; + } + } + + // will never add more macs than we can hold + ASSERT(0) +} + +void OTPChecker_Table_Generate (OTPChecker *mc, oc_table *t, OTPCalculator *calc, uint8_t *key, uint8_t *iv) +{ + // calculate values + otp_t *otps = OTPCalculator_Generate(calc, key, iv, 0); + + // empty table + OTPChecker_Table_Empty(mc ,t); + + // add calculated values to table + for (int i = 0; i < mc->num_otps; i++) { + OTPChecker_Table_AddOTP(mc, t, otps[i]); + } +} + +int OTPChecker_Table_CheckOTP (OTPChecker *mc, oc_table *t, otp_t otp) +{ + // calculate starting index + int start_index = otp % mc->num_entries; + + // try indexes starting with the base position + for (int i = 0; i < mc->num_entries; i++) { + int index = BMODADD(start_index, i, mc->num_entries); + struct OTPChecker_entry *entry = oc_table_entries_at(&mc->tables_params.tables_params, t, index); + + // if we find an empty entry, there is no such mac + if (entry->avail < 0) { + return 0; + } + + // if we find a matching entry, check its count + if (entry->otp == otp) { + if (entry->avail > 0) { + entry->avail--; + return 1; + } + return 0; + } + } + + // there are always empty slots + ASSERT(0) + return 0; +} + +int OTPChecker_Init (OTPChecker *mc, int num_otps, int cipher, int num_tables) +{ + ASSERT(num_otps > 0) + ASSERT(BEncryption_cipher_valid(cipher)) + ASSERT(num_tables > 0) + + // init arguments + mc->num_otps = num_otps; + mc->num_tables = num_tables; + + // set number of entries + mc->num_entries = 2 * mc->num_otps; + + // set no tables used + mc->tables_used = 0; + mc->next_table = 0; + + // initialize calculator + if (!OTPCalculator_Init(&mc->calc, mc->num_otps, cipher)) { + goto fail0; + } + + // allocate tables + oc_tablesParams_Init(&mc->tables_params, mc->num_tables, mc->num_entries); + if (!(mc->tables = malloc(mc->tables_params.len))) { + goto fail1; + } + + // initialize tables + for (int i = 0; i < mc->num_tables; i++) { + OTPChecker_Table_Empty(mc, oc_tables_tables_at(&mc->tables_params, mc->tables, i)); + } + + // init debug object + DebugObject_Init(&mc->d_obj); + + return 1; + +fail1: + OTPCalculator_Free(&mc->calc); +fail0: + return 0; +} + +void OTPChecker_Free (OTPChecker *mc) +{ + // free debug object + DebugObject_Free(&mc->d_obj); + + // free tables + free(mc->tables); + + // free calculator + OTPCalculator_Free(&mc->calc); +} + +void OTPChecker_AddSeed (OTPChecker *mc, uint16_t seed_id, uint8_t *key, uint8_t *iv) +{ + ASSERT(mc->next_table >= 0) + ASSERT(mc->next_table < mc->num_tables) + + // initialize next table + oc_table *table = oc_tables_tables_at(&mc->tables_params, mc->tables, mc->next_table); + *oc_table_id(&mc->tables_params.tables_params, table) = seed_id; + OTPChecker_Table_Generate(mc, table, &mc->calc, key, iv); + + // update next table number + mc->next_table = BMODADD(mc->next_table, 1, mc->num_tables); + // update number of used tables if not all are used yet + if (mc->tables_used < mc->num_tables) { + mc->tables_used++; + } +} + +void OTPChecker_RemoveSeeds (OTPChecker *mc) +{ + mc->tables_used = 0; + mc->next_table = 0; +} + +int OTPChecker_CheckOTP (OTPChecker *mc, uint16_t seed_id, otp_t otp) +{ + // try tables in reverse order + for (int i = 1; i <= mc->tables_used; i++) { + int table_index = BMODADD(mc->next_table, mc->num_tables - i, mc->num_tables); + oc_table *table = oc_tables_tables_at(&mc->tables_params, mc->tables, table_index); + if (*oc_table_id(&mc->tables_params.tables_params, table) == seed_id) { + return OTPChecker_Table_CheckOTP(mc, table, otp); + } + } + + return 0; +} + +#endif diff --git a/security/OTPGenerator.h b/security/OTPGenerator.h new file mode 100644 index 000000000..8aabd884c --- /dev/null +++ b/security/OTPGenerator.h @@ -0,0 +1,156 @@ +/** + * @file OTPGenerator.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Object which generates OTPs for use in sending packets. + */ + +#ifndef BADVPN_SECURITY_OTPGENERATOR_H +#define BADVPN_SECURITY_OTPGENERATOR_H + +#include +#include +#include + +/** + * Object which generates OTPs for use in sending packets. + */ +typedef struct { + DebugObject d_obj; + int num_otps; + int position; + OTPCalculator calc; + otp_t *otps; +} OTPGenerator; + +/** + * Initializes the generator. + * The object is initialized with number of used OTPs = num_otps. + * + * @param g the object + * @param num_otps number of OTPs to generate from a seed. Must be >=0. + * @param cipher encryption cipher for calculating the OTPs. Must be valid + * according to {@link BEncryption_cipher_valid}. + * @return 1 on success, 0 on failure + */ +static int OTPGenerator_Init (OTPGenerator *g, int num_otps, int cipher) WARN_UNUSED; + +/** + * Frees the generator. + * + * @param g the object + */ +static void OTPGenerator_Free (OTPGenerator *g); + +/** + * Assigns a seed to use for generating OTPs. + * Sets the number of used OTPs to 0. + * + * @param g the object + * @param key encryption key + * @param iv initialization vector + */ +static void OTPGenerator_SetSeed (OTPGenerator *g, uint8_t *key, uint8_t *iv); + +/** + * Returns the number of OTPs used up from the current seed so far. + * If there is no seed yet, returns num_otps. + * + * @param g the object + * @return number of used OTPs + */ +static int OTPGenerator_GetPosition (OTPGenerator *g); + +/** + * Sets the number of used OTPs to num_otps. + * + * @param g the object + */ +static void OTPGenerator_Reset (OTPGenerator *g); + +/** + * Generates a single OTP. + * The number of used OTPs must be < num_otps. + * The number of used OTPs is incremented. + * + * @param g the object + */ +static otp_t OTPGenerator_GetOTP (OTPGenerator *g); + +int OTPGenerator_Init (OTPGenerator *g, int num_otps, int cipher) +{ + ASSERT(num_otps >= 0) + ASSERT(BEncryption_cipher_valid(cipher)) + + // init arguments + g->num_otps = num_otps; + + // init position + g->position = g->num_otps; + + // init calculator + if (!OTPCalculator_Init(&g->calc, g->num_otps, cipher)) { + goto fail0; + } + + // init debug object + DebugObject_Init(&g->d_obj); + + return 1; + +fail0: + return 0; +} + +void OTPGenerator_Free (OTPGenerator *g) +{ + // free debug object + DebugObject_Free(&g->d_obj); + + // free calculator + OTPCalculator_Free(&g->calc); +} + +void OTPGenerator_SetSeed (OTPGenerator *g, uint8_t *key, uint8_t *iv) +{ + g->otps = OTPCalculator_Generate(&g->calc, key, iv, 1); + g->position = 0; +} + +int OTPGenerator_GetPosition (OTPGenerator *g) +{ + return g->position; +} + +void OTPGenerator_Reset (OTPGenerator *g) +{ + g->position = g->num_otps; +} + +otp_t OTPGenerator_GetOTP (OTPGenerator *g) +{ + ASSERT(g->position < g->num_otps) + + return g->otps[g->position++]; +} + +#endif diff --git a/security/bhash.h b/security/bhash.h new file mode 100644 index 000000000..fcabe64db --- /dev/null +++ b/security/bhash.h @@ -0,0 +1,108 @@ +/** + * @file bhash.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Cryptographic hash funtions abstraction. + */ + +#ifndef BADVPN_SECURITY_BHASH_H +#define BADVPN_SECURITY_BHASH_H + +#include + +#include +#include + +#include + +#define BHASH_TYPE_MD5 1 +#define BHASH_TYPE_MD5_SIZE 16 + +#define BHASH_TYPE_SHA1 2 +#define BHASH_TYPE_SHA1_SIZE 20 + +/** + * Checks if the given hash type number is valid. + * + * @param type hash type number + * @return 1 if valid, 0 if not + */ +static int BHash_type_valid (int type); + +/** + * Returns the size of a hash. + * + * @param cipher hash type number. Must be valid. + * @return hash size in bytes + */ +static int BHash_size (int type); + +/** + * Calculates a hash. + * + * @param type hash type number. Must be valid. + * @param data data to calculate the hash of + * @param data_len length of data + * @param out the hash will be written here. Must not overlap with data. + */ +static void BHash_calculate (int type, uint8_t *data, int data_len, uint8_t *out); + +int BHash_type_valid (int type) +{ + switch (type) { + case BHASH_TYPE_MD5: + case BHASH_TYPE_SHA1: + return 1; + default: + return 0; + } +} + +int BHash_size (int type) +{ + switch (type) { + case BHASH_TYPE_MD5: + return BHASH_TYPE_MD5_SIZE; + case BHASH_TYPE_SHA1: + return BHASH_TYPE_SHA1_SIZE; + default: + ASSERT(0) + return 0; + } +} + +void BHash_calculate (int type, uint8_t *data, int data_len, uint8_t *out) +{ + switch (type) { + case BHASH_TYPE_MD5: + MD5(data, data_len, out); + break; + case BHASH_TYPE_SHA1: + SHA1(data, data_len, out); + break; + default: + ASSERT(0) + ; + } +} + +#endif diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt new file mode 100644 index 000000000..787c6a8a4 --- /dev/null +++ b/server/CMakeLists.txt @@ -0,0 +1,7 @@ +add_executable(badvpn-server server.c) +target_link_libraries(badvpn-server system flow nspr_support predicate listener ${LIBCRYPTO_LIBRARIES} ${NSPR_LIBRARIES} ${NSS_LIBRARIES}) + +install( + TARGETS badvpn-server + RUNTIME DESTINATION bin +) diff --git a/server/server.c b/server/server.c new file mode 100644 index 000000000..b48c16c0e --- /dev/null +++ b/server/server.c @@ -0,0 +1,1983 @@ +/** + * @file server.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include + +// NSPR and NSS +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// BadVPN +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef BADVPN_USE_WINAPI +#include +#endif + +#include + +#include + +#define COMPONENT_SOURCE 1 +#define COMPONENT_SINK 2 +#define COMPONENT_DECODER 3 + +#define LOGGER_STDOUT 1 +#define LOGGER_SYSLOG 2 + +// program dead variable +dead_t dead; + +// parsed command-line options +struct { + int help; + int version; + int logger; + #ifndef BADVPN_USE_WINAPI + char *logger_syslog_facility; + char *logger_syslog_ident; + #endif + int loglevel; + int loglevels[BLOG_NUM_CHANNELS]; + int ssl; + char *nssdb; + char *server_cert_name; + char *listen_addrs[MAX_LISTEN_ADDRS]; + int num_listen_addrs; + char *comm_predicate; + char *relay_predicate; +} options; + +// listen addresses +BAddr listen_addrs[MAX_LISTEN_ADDRS]; +int num_listen_addrs; + +// communication predicate +BPredicate comm_predicate; + +// communication predicate functions +BPredicateFunction comm_predicate_func_p1name; +BPredicateFunction comm_predicate_func_p2name; +BPredicateFunction comm_predicate_func_p1addr; +BPredicateFunction comm_predicate_func_p2addr; + +// variables when evaluating the predicate, adjusted before every evaluation +const char *comm_predicate_p1name; +const char *comm_predicate_p2name; +BIPAddr comm_predicate_p1addr; +BIPAddr comm_predicate_p2addr; + +// relay predicate +BPredicate relay_predicate; + +// gateway predicate functions +BPredicateFunction relay_predicate_func_pname; +BPredicateFunction relay_predicate_func_rname; +BPredicateFunction relay_predicate_func_paddr; +BPredicateFunction relay_predicate_func_raddr; + +// variables when evaluating the comm_predicate, adjusted before every evaluation +const char *relay_predicate_pname; +const char *relay_predicate_rname; +BIPAddr relay_predicate_paddr; +BIPAddr relay_predicate_raddr; + +// i/o system +BReactor ss; + +// server certificate if using SSL +CERTCertificate *server_cert; + +// server private key if using SSL +SECKEYPrivateKey *server_key; + +// model NSPR file descriptor to speed up client initialization +PRFileDesc model_dprfd; +PRFileDesc *model_prfd; + +// listeners +Listener listeners[MAX_LISTEN_ADDRS]; +int num_listeners; + +// number of connected clients +int clients_num; + +// ID assigned to last connected client +peerid_t clients_lastid; + +// clients list +LinkedList2 clients; + +// clients hash table by client ID +HashTable clients_by_id; +uint32_t clients_by_id_initval; + +// cleans everything up that can be cleaned in order to return +// from the event loop and exit +static void terminate (void); + +// prints help text to standard output +static void print_help (const char *name); + +// prints program name and version to standard output +static void print_version (void); + +// parses the command line +static int parse_arguments (int argc, char *argv[]); + +// processes certain command line options +static int resolve_arguments (void); + +// handler for program termination request +static void signal_handler (void *unused); + +// listener socket handler, accepts new clients +static void listener_handler (Listener *listener); + +// adds a client. The client structure must have the sock and addr members +// already initialized. +static void client_add (struct client_data *client); + +// removes a client +static void client_remove (struct client_data *client); + +// frees resources used by a client. Must have no outgoing flows. +static void client_dealloc (struct client_data *client); + +// passes a message to the logger, prepending about the client +static void client_log (struct client_data *client, int level, const char *fmt, ...); + +// client activity timer handler. Removes the client. +static void client_disconnect_timer_handler (struct client_data *client); + +// drives cline SSL handshake +static void client_try_handshake (struct client_data *client); + +// event handler for driving client SSL handshake +static void client_handshake_read_handler (struct client_data *client, PRInt16 event); + +// initializes the I/O porition of the client +static int client_init_io (struct client_data *client); + +// deallocates the I/O portion of the client. Must have no outgoing flows. +static void client_dealloc_io (struct client_data *client); + +// handler for client I/O errors. Removes the client. +static void client_error_handler (struct client_data *client, int component, const void *data); + +// provides a buffer for sending a control packet to the client +static int client_start_control_packet (struct client_data *client, void **data, int len); + +// submits a packet written after client_start_control_packet +static int client_end_control_packet (struct client_data *client, uint8_t id); + +// handler for packets received from the client +static int client_input_handler_send (struct client_data *client, uint8_t *data, int data_len); + +// creates a peer flow +static struct peer_flow * peer_flow_create (struct client_data *src_client, struct client_data *dest_client); + +// deallocates a peer flow +static void peer_flow_dealloc (struct peer_flow *flow); + +// disconnects the source client from a peer flow +static void peer_flow_disconnect (struct peer_flow *flow); + +// provides a buffer for sending a peer-to-peer packet +static int peer_flow_start_packet (struct peer_flow *flow, void **data, int len); + +// submits a peer-to-peer packet written after peer_flow_start_packet +static int peer_flow_end_packet (struct peer_flow *flow, uint8_t type); + +// handler called by the queue when a peer flow can be freed after its source has gone away +static void peer_flow_handler_canremove (struct peer_flow *flow); + +// processes hello packets from clients +static void process_packet_hello (struct client_data *client, uint8_t *data, int data_len); + +// processes outmsg packets from clients +static void process_packet_outmsg (struct client_data *client, uint8_t *data, int data_len); + +// sends a newclient message to a client +static int client_send_newclient (struct client_data *client, struct client_data *nc, int relay_server, int relay_client); + +// sends an endclient message to a client +static int client_send_endclient (struct client_data *client, peerid_t end_id); + +// informs two clients of each other after one of them has just come. Does nothing +// if they are not permitted by the communication predicate. +void connect_clients (struct client_data *clientA, struct client_data *clientB); + +// calls connect_clients for this client and all other finished peers +static int publish_client (struct client_data *client); + +// generates a client ID to be used for a newly connected client +static peerid_t new_client_id (void); + +// finds a client by its ID +static struct client_data * find_client_by_id (peerid_t id); + +// clients by ID hash table key comparator +static int clients_by_id_key_comparator (peerid_t *id1, peerid_t *id2); + +// clients by ID hash table hash function +static int clients_by_id_hash_function (peerid_t *id, int modulo); + +// checks if two clients are allowed to communicate. May depend on the order +// of the clients. +static int clients_allowed (struct client_data *client1, struct client_data *client2); + +// communication predicate function p1name +static int comm_predicate_func_p1name_cb (void *user, void **args); + +// communication predicate function p2name +static int comm_predicate_func_p2name_cb (void *user, void **args); + +// communication predicate function p1addr +static int comm_predicate_func_p1addr_cb (void *user, void **args); + +// communication predicate function p2addr +static int comm_predicate_func_p2addr_cb (void *user, void **args); + +// checks if relay is allowed for a client through another client +static int relay_allowed (struct client_data *client, struct client_data *relay); + +// relay predicate function pname +static int relay_predicate_func_pname_cb (void *user, void **args); + +// relay predicate function rname +static int relay_predicate_func_rname_cb (void *user, void **args); + +// relay predicate function paddr +static int relay_predicate_func_paddr_cb (void *user, void **args); + +// relay predicate function raddr +static int relay_predicate_func_raddr_cb (void *user, void **args); + +// comparator for peerid_t used in AVL tree +static int peerid_comparator (void *unused, peerid_t *p1, peerid_t *p2); + +int main (int argc, char *argv[]) +{ + if (argc <= 0) { + return 1; + } + + // init dead variable + DEAD_INIT(dead); + + // parse command-line arguments + if (!parse_arguments(argc, argv)) { + fprintf(stderr, "Failed to parse arguments\n"); + print_help(argv[0]); + goto fail0; + } + + // handle --help and --version + if (options.help) { + print_version(); + print_help(argv[0]); + return 0; + } + if (options.version) { + print_version(); + return 0; + } + + // initialize logger + switch (options.logger) { + case LOGGER_STDOUT: + BLog_InitStdout(); + break; + #ifndef BADVPN_USE_WINAPI + case LOGGER_SYSLOG: + if (!BLog_InitSyslog(options.logger_syslog_ident, options.logger_syslog_facility)) { + fprintf(stderr, "Failed to initialize syslog logger\n"); + goto fail0; + } + break; + #endif + default: + ASSERT(0); + } + for (int i = 0; i < BLOG_NUM_CHANNELS; i++) { + if (options.loglevels[i] >= 0) { + BLog_SetChannelLoglevel(i, options.loglevels[i]); + } + else if (options.loglevel >= 0) { + BLog_SetChannelLoglevel(i, options.loglevel); + } + } + + BLog(BLOG_NOTICE, "initializing "GLOBAL_PRODUCT_NAME" server "GLOBAL_VERSION); + + // initialize sockets + if (BSocket_GlobalInit() < 0) { + BLog(BLOG_ERROR, "BSocket_GlobalInit failed"); + goto fail1; + } + + // resolve addresses + if (!resolve_arguments()) { + BLog(BLOG_ERROR, "Failed to resolve arguments"); + goto fail1; + } + + // init communication predicate + if (options.comm_predicate) { + // init predicate + if (!BPredicate_Init(&comm_predicate, options.comm_predicate)) { + BLog(BLOG_ERROR, "BPredicate_Init failed"); + goto fail1; + } + + // init functions + BPredicateFunction_Init(&comm_predicate_func_p1name, &comm_predicate, "p1name", (int []){PREDICATE_TYPE_STRING}, 1, comm_predicate_func_p1name_cb, NULL); + BPredicateFunction_Init(&comm_predicate_func_p2name, &comm_predicate, "p2name", (int []){PREDICATE_TYPE_STRING}, 1, comm_predicate_func_p2name_cb, NULL); + BPredicateFunction_Init(&comm_predicate_func_p1addr, &comm_predicate, "p1addr", (int []){PREDICATE_TYPE_STRING}, 1, comm_predicate_func_p1addr_cb, NULL); + BPredicateFunction_Init(&comm_predicate_func_p2addr, &comm_predicate, "p2addr", (int []){PREDICATE_TYPE_STRING}, 1, comm_predicate_func_p2addr_cb, NULL); + } + + // init relay predicate + if (options.relay_predicate) { + // init predicate + if (!BPredicate_Init(&relay_predicate, options.relay_predicate)) { + BLog(BLOG_ERROR, "BPredicate_Init failed"); + goto fail1_1; + } + + // init functions + BPredicateFunction_Init(&relay_predicate_func_pname, &relay_predicate, "pname", (int []){PREDICATE_TYPE_STRING}, 1, relay_predicate_func_pname_cb, NULL); + BPredicateFunction_Init(&relay_predicate_func_rname, &relay_predicate, "rname", (int []){PREDICATE_TYPE_STRING}, 1, relay_predicate_func_rname_cb, NULL); + BPredicateFunction_Init(&relay_predicate_func_paddr, &relay_predicate, "paddr", (int []){PREDICATE_TYPE_STRING}, 1, relay_predicate_func_paddr_cb, NULL); + BPredicateFunction_Init(&relay_predicate_func_raddr, &relay_predicate, "raddr", (int []){PREDICATE_TYPE_STRING}, 1, relay_predicate_func_raddr_cb, NULL); + } + + // init time + BTime_Init(); + + // initialize reactor + if (!BReactor_Init(&ss)) { + BLog(BLOG_ERROR, "BReactor_Init failed"); + goto fail2; + } + + // setup signal handler + if (!BSignal_Init()) { + BLog(BLOG_ERROR, "BSignal_Init failed"); + goto fail2a; + } + BSignal_Capture(); + if (!BSignal_SetHandler(&ss, signal_handler, NULL)) { + BLog(BLOG_ERROR, "BSignal_SetHandler failed"); + goto fail2a; + } + + if (options.ssl) { + // initialize NSPR + PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0); + + // initialize i/o layer types + if (!DummyPRFileDesc_GlobalInit()) { + BLog(BLOG_ERROR, "DummyPRFileDesc_GlobalInit failed"); + goto fail3; + } + if (!BSocketPRFileDesc_GlobalInit()) { + BLog(BLOG_ERROR, "BSocketPRFileDesc_GlobalInit failed"); + goto fail3; + } + + // initialize NSS + if (NSS_Init(options.nssdb) != SECSuccess) { + BLog(BLOG_ERROR, "NSS_Init failed (%d)", (int)PR_GetError()); + goto fail3; + } + if (NSS_SetDomesticPolicy() != SECSuccess) { + BLog(BLOG_ERROR, "NSS_SetDomesticPolicy failed (%d)", (int)PR_GetError()); + goto fail4; + } + if (SSL_ConfigServerSessionIDCache(0, 0, 0, NULL) != SECSuccess) { + BLog(BLOG_ERROR, "SSL_ConfigServerSessionIDCache failed (%d)", (int)PR_GetError()); + goto fail4; + } + + // open server certificate and private key + if (!open_nss_cert_and_key(options.server_cert_name, &server_cert, &server_key)) { + BLog(BLOG_ERROR, "Cannot open certificate and key"); + goto fail4; + } + + // initialize model SSL fd + DummyPRFileDesc_Create(&model_dprfd); + if (!(model_prfd = SSL_ImportFD(NULL, &model_dprfd))) { + BLog(BLOG_ERROR, "SSL_ImportFD failed"); + ASSERT_FORCE(PR_Close(&model_dprfd) == PR_SUCCESS) + goto fail5; + } + + // set server certificate + if (SSL_ConfigSecureServer(model_prfd, server_cert, server_key, NSS_FindCertKEAType(server_cert)) != SECSuccess) { + BLog(BLOG_ERROR, "SSL_ConfigSecureServer failed"); + goto fail6; + } + } + + // initialize number of clients + clients_num = 0; + + // first client ID will be zero + clients_lastid = 65535; + + // initialize clients linked list + LinkedList2_Init(&clients); + + // initialize clients-by-id hash table + brandom_randomize((uint8_t *)&clients_by_id_initval, sizeof(clients_by_id_initval)); + if (!HashTable_Init( + &clients_by_id, + OFFSET_DIFF(struct client_data, id, table_node_id), + (HashTable_comparator)clients_by_id_key_comparator, + (HashTable_hash_function)clients_by_id_hash_function, + MAX_CLIENTS + )) { + BLog(BLOG_ERROR, "HashTable_Init failed"); + goto fail6; + } + + // initialize listeners + num_listeners = 0; + for (int i = 0; i < num_listen_addrs; i++) { + if (!Listener_Init(&listeners[num_listeners], &ss, listen_addrs[i], (Listener_handler)listener_handler, &listeners[num_listeners])) { + BLog(BLOG_ERROR, "Listener_Init failed"); + goto fail7; + } + num_listeners++; + } + + goto run_reactor; + + // cleanup on error +fail7: + while (num_listeners > 0) { + Listener_Free(&listeners[num_listeners - 1]); + num_listeners--; + } + HashTable_Free(&clients_by_id); +fail6: + if (options.ssl) { + ASSERT_FORCE(PR_Close(model_prfd) == PR_SUCCESS) +fail5: + CERT_DestroyCertificate(server_cert); + SECKEY_DestroyPrivateKey(server_key); +fail4: + ASSERT_FORCE(NSS_Shutdown() == SECSuccess) +fail3: + ASSERT_FORCE(PR_Cleanup() == PR_SUCCESS) + PL_ArenaFinish(); + } + BSignal_RemoveHandler(); +fail2a: + BReactor_Free(&ss); +fail2: + if (options.relay_predicate) { + BPredicateFunction_Free(&relay_predicate_func_raddr); + BPredicateFunction_Free(&relay_predicate_func_paddr); + BPredicateFunction_Free(&relay_predicate_func_rname); + BPredicateFunction_Free(&relay_predicate_func_pname); + BPredicate_Free(&relay_predicate); + } +fail1_1: + if (options.comm_predicate) { + BPredicateFunction_Free(&comm_predicate_func_p2addr); + BPredicateFunction_Free(&comm_predicate_func_p1addr); + BPredicateFunction_Free(&comm_predicate_func_p2name); + BPredicateFunction_Free(&comm_predicate_func_p1name); + BPredicate_Free(&comm_predicate); + } +fail1: + BLog(BLOG_ERROR, "initialization failed"); + BLog_Free(); +fail0: + DebugObjectGlobal_Finish(); + return 1; + +run_reactor: + // enter event loop + BLog(BLOG_NOTICE, "entering event loop"); + int ret = BReactor_Exec(&ss); + + // free reactor + BReactor_Free(&ss); + + // free logger + BLog(BLOG_NOTICE, "exiting"); + BLog_Free(); + + // finish objects + DebugObjectGlobal_Finish(); + + return ret; +} + +void terminate (void) +{ + BLog(BLOG_NOTICE, "tearing down"); + + // free clients + LinkedList2Node *node; + while (node = LinkedList2_GetFirst(&clients)) { + struct client_data *client = UPPER_OBJECT(node, struct client_data, list_node); + + // remove outgoing flows + LinkedList2Node *flow_node; + while (flow_node = LinkedList2_GetFirst(&client->peer_out_flows_list)) { + struct peer_flow *flow = UPPER_OBJECT(flow_node, struct peer_flow, src_list_node); + ASSERT(flow->src_client == client) + + // allow freeing queue flows at dest + PacketPassFairQueue_PrepareFree(&flow->dest_client->output_peers_fairqueue); + + // deallocate flow + peer_flow_dealloc(flow); + } + + // deallocate client + client_dealloc(client); + } + + // free listeners + while (num_listeners > 0) { + Listener_Free(&listeners[num_listeners - 1]); + num_listeners--; + } + + // free clients hash table + HashTable_Free(&clients_by_id); + + if (options.ssl) { + // free model + ASSERT_FORCE(PR_Close(model_prfd) == PR_SUCCESS) + + // free certificate and private key + CERT_DestroyCertificate(server_cert); + SECKEY_DestroyPrivateKey(server_key); + + // free server cache + SSL_ShutdownServerSessionIDCache(); + + // free NSS + ASSERT_FORCE(NSS_Shutdown() == SECSuccess) + + // free NSPR + ASSERT_FORCE(PR_Cleanup() == PR_SUCCESS) + PL_ArenaFinish(); + } + + // remove signal handler + BSignal_RemoveHandler(); + + // free relay predicate + if (options.relay_predicate) { + BPredicateFunction_Free(&relay_predicate_func_raddr); + BPredicateFunction_Free(&relay_predicate_func_paddr); + BPredicateFunction_Free(&relay_predicate_func_rname); + BPredicateFunction_Free(&relay_predicate_func_pname); + BPredicate_Free(&relay_predicate); + } + + // free communication predicate + if (options.comm_predicate) { + BPredicateFunction_Free(&comm_predicate_func_p2addr); + BPredicateFunction_Free(&comm_predicate_func_p1addr); + BPredicateFunction_Free(&comm_predicate_func_p2name); + BPredicateFunction_Free(&comm_predicate_func_p1name); + BPredicate_Free(&comm_predicate); + } + + // kill program dead variable + DEAD_KILL(dead); + + // exit event loop + BReactor_Quit(&ss, 1); +} + +void print_help (const char *name) +{ + printf( + "Usage:\n" + " %s\n" + " [--help]\n" + " [--version]\n" + " [--logger <"LOGGERS_STRING">]\n" + #ifndef BADVPN_USE_WINAPI + " (logger=syslog?\n" + " [--syslog-facility ]\n" + " [--syslog-ident ]\n" + " )\n" + #endif + " [--loglevel <0-5/none/error/warning/notice/info/debug>]\n" + " [--channel-loglevel <0-5/none/error/warning/notice/info/debug>] ...\n" + " [--listen-addr ] ...\n" + " [--ssl --nssdb --server-cert-name ]\n" + " [--comm-predicate ]\n" + " [--relay-predicate ]\n" + "Address format is a.b.c.d:port (IPv4) or [addr]:port (IPv6).\n", + name + ); +} + +void print_version (void) +{ + printf(GLOBAL_PRODUCT_NAME" "PROGRAM_NAME" "GLOBAL_VERSION"\n"GLOBAL_COPYRIGHT_NOTICE"\n"); +} + +int parse_arguments (int argc, char *argv[]) +{ + options.help = 0; + options.version = 0; + options.logger = LOGGER_STDOUT; + #ifndef BADVPN_USE_WINAPI + options.logger_syslog_facility = "daemon"; + options.logger_syslog_ident = argv[0]; + #endif + options.loglevel = -1; + for (int i = 0; i < BLOG_NUM_CHANNELS; i++) { + options.loglevels[i] = -1; + } + options.ssl = 0; + options.nssdb = NULL; + options.server_cert_name = NULL; + options.num_listen_addrs = 0; + options.comm_predicate = NULL; + options.relay_predicate = NULL; + + for (int i = 1; i < argc; i++) { + char *arg = argv[i]; + if (!strcmp(arg, "--help")) { + options.help = 1; + } + else if (!strcmp(arg, "--version")) { + options.version = 1; + } + else if (!strcmp(arg, "--logger")) { + if (i + 1 >= argc) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + char *arg2 = argv[i + 1]; + if (!strcmp(arg2, "stdout")) { + options.logger = LOGGER_STDOUT; + } + #ifndef BADVPN_USE_WINAPI + else if (!strcmp(arg2, "syslog")) { + options.logger = LOGGER_SYSLOG; + } + #endif + else { + fprintf(stderr, "%s: wrong argument\n", arg); + return 0; + } + i++; + } + #ifndef BADVPN_USE_WINAPI + else if (!strcmp(arg, "--syslog-facility")) { + if (i + 1 >= argc) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + options.logger_syslog_facility = argv[i + 1]; + i++; + } + else if (!strcmp(arg, "--syslog-ident")) { + if (i + 1 >= argc) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + options.logger_syslog_ident = argv[i + 1]; + i++; + } + #endif + else if (!strcmp(arg, "--loglevel")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + if ((options.loglevel = parse_loglevel(argv[i + 1])) < 0) { + fprintf(stderr, "%s: wrong argument\n", arg); + return 0; + } + i++; + } + else if (!strcmp(arg, "--channel-loglevel")) { + if (2 >= argc - i) { + fprintf(stderr, "%s: requires two arguments\n", arg); + return 0; + } + int channel = BLogGlobal_GetChannelByName(argv[i + 1]); + if (channel < 0) { + fprintf(stderr, "%s: wrong channel argument\n", arg); + return 0; + } + int loglevel = parse_loglevel(argv[i + 2]); + if (loglevel < 0) { + fprintf(stderr, "%s: wrong loglevel argument\n", arg); + return 0; + } + options.loglevels[channel] = loglevel; + i += 2; + } + else if (!strcmp(arg, "--ssl")) { + options.ssl = 1; + } + else if (!strcmp(arg, "--nssdb")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + options.nssdb = argv[i + 1]; + i++; + } + else if (!strcmp(arg, "--server-cert-name")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + options.server_cert_name = argv[i + 1]; + i++; + } + else if (!strcmp(arg, "--listen-addr")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + if (options.num_listen_addrs == MAX_LISTEN_ADDRS) { + fprintf(stderr, "%s: too many\n", arg); + return 0; + } + options.listen_addrs[options.num_listen_addrs] = argv[i + 1]; + options.num_listen_addrs++; + i++; + } + else if (!strcmp(arg, "--comm-predicate")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + options.comm_predicate = argv[i + 1]; + i++; + } + else if (!strcmp(arg, "--relay-predicate")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + options.relay_predicate = argv[i + 1]; + i++; + } + else { + fprintf(stderr, "%s: unknown option\n", arg); + return 0; + } + } + + if (options.help || options.version) { + return 1; + } + + if (!!options.nssdb != options.ssl) { + fprintf(stderr, "--ssl and --nssdb must be used together\n"); + return 0; + } + + if (!!options.server_cert_name != options.ssl) { + fprintf(stderr, "--ssl and --server-cert-name must be used together\n"); + return 0; + } + + return 1; +} + +int resolve_arguments (void) +{ + // resolve listen addresses + num_listen_addrs = 0; + for (int i = 0; i < options.num_listen_addrs; i++) { + if (!BAddr_Parse(&listen_addrs[num_listen_addrs], options.listen_addrs[i], NULL, 0)) { + BLog(BLOG_ERROR, "listen addr: BAddr_Parse failed"); + return 0; + } + num_listen_addrs++; + } + + return 1; +} + +void signal_handler (void *unused) +{ + BLog(BLOG_NOTICE, "termination requested"); + + terminate(); +} + +void listener_handler (Listener *listener) +{ + if (clients_num >= MAX_CLIENTS) { + BLog(BLOG_WARNING, "too many clients for new client"); + return; + } + + // allocate the client structure + struct client_data *client = malloc(sizeof(struct client_data)); + if (!client) { + BLog(BLOG_ERROR, "failed to allocate client"); + return; + } + + // accept it + if (!Listener_Accept(listener, &client->sock, &client->addr)) { + BLog(BLOG_NOTICE, "Listener_Accept failed"); + free(client); + return; + } + + client_add(client); + return; +} + +void client_add (struct client_data *client) +{ + ASSERT(clients_num < MAX_CLIENTS) + + // initialize dead variable + DEAD_INIT(client->dead); + + if (options.ssl) { + // create BSocket NSPR file descriptor + BSocketPRFileDesc_Create(&client->bottom_prfd, &client->sock); + + // create SSL file descriptor from the socket's BSocketPRFileDesc + if (!(client->ssl_prfd = SSL_ImportFD(model_prfd, &client->bottom_prfd))) { + ASSERT_FORCE(PR_Close(&client->bottom_prfd) == PR_SUCCESS) + goto fail0; + } + + // set server mode + if (SSL_ResetHandshake(client->ssl_prfd, PR_TRUE) != SECSuccess) { + BLog(BLOG_ERROR, "SSL_ResetHandshake failed"); + goto fail1; + } + + // set require client certificate + if (SSL_OptionSet(client->ssl_prfd, SSL_REQUEST_CERTIFICATE, PR_TRUE) != SECSuccess) { + BLog(BLOG_ERROR, "SSL_OptionSet(SSL_REQUEST_CERTIFICATE) failed"); + goto fail1; + } + if (SSL_OptionSet(client->ssl_prfd, SSL_REQUIRE_CERTIFICATE, PR_TRUE) != SECSuccess) { + BLog(BLOG_ERROR, "SSL_OptionSet(SSL_REQUIRE_CERTIFICATE) failed"); + goto fail1; + } + + // initialize BPRFileDesc on SSL file descriptor + BPRFileDesc_Init(&client->ssl_bprfd, client->ssl_prfd); + + // set client state + client->initstatus = INITSTATUS_HANDSHAKE; + } else { + // initialize i/o chains + if (!client_init_io(client)) { + goto fail0; + } + + // set client state + client->initstatus = INITSTATUS_WAITHELLO; + } + + // start disconnect timer + BTimer_Init(&client->disconnect_timer, CLIENT_NO_DATA_TIME_LIMIT, (BTimer_handler)client_disconnect_timer_handler, client); + BReactor_SetTimer(&ss, &client->disconnect_timer); + + // assign ID + // must be done before linking + client->id = new_client_id(); + + // link in + clients_num++; + LinkedList2_Append(&clients, &client->list_node); + ASSERT_EXECUTE(HashTable_Insert(&clients_by_id, &client->table_node_id)) + + // initialize peer flows from us list and tree (flows for sending messages to other clients) + LinkedList2_Init(&client->peer_out_flows_list); + BAVL_Init(&client->peer_out_flows_tree, OFFSET_DIFF(struct peer_flow, dest_client_id, src_tree_node), (BAVL_comparator)peerid_comparator, NULL); + + // set not dying + client->dying = 0; + + client_log(client, BLOG_INFO, "initialized"); + + // start I/O + if (options.ssl) { + // set read handler for driving handshake + BPRFileDesc_AddEventHandler(&client->ssl_bprfd, PR_POLL_READ, (BPRFileDesc_handler)client_handshake_read_handler, client); + + // start handshake + client_try_handshake(client); + return; + } else { + return; + } + + // cleanup on errors +fail1: + if (options.ssl) { + ASSERT_FORCE(PR_Close(client->ssl_prfd) == PR_SUCCESS) + } +fail0: + BSocket_Free(&client->sock); + free(client); +} + +void client_remove (struct client_data *client) +{ + ASSERT(!client->dying) + + client_log(client, BLOG_NOTICE, "removing"); + + // set dying to prevent sending this client anything + client->dying = 1; + + // remove outgoing flows and tell those who know about it that it's gone + // (outgoing flow also means that the target knows the source) + LinkedList2Node *node; + while (node = LinkedList2_GetFirst(&client->peer_out_flows_list)) { + struct peer_flow *flow = UPPER_OBJECT(node, struct peer_flow, src_list_node); + ASSERT(flow->src_client == client) + struct client_data *clientB = flow->dest_client; + ASSERT(clientB->initstatus == INITSTATUS_COMPLETE) + + // remove the flow + if (PacketPassFairQueueFlow_IsBusy(&flow->qflow)) { + client_log(client, BLOG_DEBUG, "removing flow later"); + peer_flow_disconnect(flow); + PacketPassFairQueueFlow_SetBusyHandler(&flow->qflow, (PacketPassFairQueue_handler_busy)peer_flow_handler_canremove, flow); + } else { + client_log(client, BLOG_DEBUG, "removing flow now"); + peer_flow_dealloc(flow); + } + + // inform the other peer this client is gone + if (!clientB->dying) { + DEAD_ENTER(dead) + client_send_endclient(clientB, client->id); + if (DEAD_LEAVE(dead)) { + return; + } + } + } + + // deallocate client + client_dealloc(client); +} + +void client_dealloc (struct client_data *client) +{ + ASSERT(LinkedList2_IsEmpty(&client->peer_out_flows_list)) + + // link out + ASSERT_EXECUTE(HashTable_Remove(&clients_by_id, &client->id)) + LinkedList2_Remove(&clients, &client->list_node); + clients_num--; + + // stop disconnect timer + BReactor_RemoveTimer(&ss, &client->disconnect_timer); + + if (client->initstatus >= INITSTATUS_WAITHELLO) { + // free I/O + client_dealloc_io(client); + + // free common name + if (options.ssl) { + PORT_Free(client->common_name); + } + } + + // free SSL + if (options.ssl) { + // free BPRFileDesc + BPRFileDesc_Free(&client->ssl_bprfd); + + // free SSL PRFD + ASSERT_FORCE(PR_Close(client->ssl_prfd) == PR_SUCCESS) + } + + // free socket + BSocket_Free(&client->sock); + + // free dead variable + DEAD_KILL(client->dead); + + // free memory + free(client); +} + +void client_log (struct client_data *client, int level, const char *fmt, ...) +{ + va_list vl; + va_start(vl, fmt); + char addr[BADDR_MAX_PRINT_LEN]; + BAddr_Print(&client->addr, addr); + BLog_Append("client %d (%s): ", (int)client->id, addr); + BLog_LogToChannelVarArg(BLOG_CURRENT_CHANNEL, level, fmt, vl); + va_end(vl); +} + +void client_disconnect_timer_handler (struct client_data *client) +{ + client_log(client, BLOG_NOTICE, "timed out"); + + client_remove(client); + return; +} + +void client_try_handshake (struct client_data *client) +{ + ASSERT(client->initstatus == INITSTATUS_HANDSHAKE) + + // attempt handshake + if (SSL_ForceHandshake(client->ssl_prfd) != SECSuccess) { + PRErrorCode error = PR_GetError(); + if (error == PR_WOULD_BLOCK_ERROR) { + // try again on read event + BPRFileDesc_EnableEvent(&client->ssl_bprfd, PR_POLL_READ); + return; + } + client_log(client, BLOG_NOTICE, "SSL_ForceHandshake failed (%d)", (int)error); + goto fail0; + } + + client_log(client, BLOG_INFO, "handshake complete"); + + // remove read handler + BPRFileDesc_RemoveEventHandler(&client->ssl_bprfd, PR_POLL_READ); + + // get client certificate + CERTCertificate *cert = SSL_PeerCertificate(client->ssl_prfd); + if (!cert) { + client_log(client, BLOG_ERROR, "SSL_PeerCertificate failed"); + goto fail0; + } + + PRArenaPool *arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (!arena) { + client_log(client, BLOG_ERROR, "PORT_NewArena failed"); + goto fail1; + } + + // encode certificate + SECItem der; + der.len = 0; + der.data = NULL; + if (!SEC_ASN1EncodeItem(arena, &der, cert, SEC_ASN1_GET(CERT_CertificateTemplate))) { + client_log(client, BLOG_ERROR, "SEC_ASN1EncodeItem failed"); + goto fail2; + } + + // store certificate + if (der.len > (unsigned int)sizeof(client->cert)) { + client_log(client, BLOG_NOTICE, "client certificate too big"); + goto fail2; + } + memcpy(client->cert, der.data, der.len); + client->cert_len = der.len; + + // remember common name + if (!(client->common_name = CERT_GetCommonName(&cert->subject))) { + client_log(client, BLOG_NOTICE, "CERT_GetCommonName failed"); + goto fail2; + } + + // init I/O chains + if (!client_init_io(client)) { + goto fail3; + } + + PORT_FreeArena(arena, PR_FALSE); + CERT_DestroyCertificate(cert); + + // set client state + client->initstatus = INITSTATUS_WAITHELLO; + + return; + + // handle errors +fail3: + PORT_Free(client->common_name); +fail2: + PORT_FreeArena(arena, PR_FALSE); +fail1: + CERT_DestroyCertificate(cert); +fail0: + client_remove(client); +} + +void client_handshake_read_handler (struct client_data *client, PRInt16 event) +{ + ASSERT(event == PR_POLL_READ) + + // restart no data timer + BReactor_SetTimer(&ss, &client->disconnect_timer); + + // continue handshake + client_try_handshake(client); + return; +} + +int client_init_io (struct client_data *client) +{ + // initialize error domain + FlowErrorDomain_Init(&client->domain, (FlowErrorDomain_handler)client_error_handler, client); + + // init input + + // init source + StreamRecvInterface *source_interface; + if (options.ssl) { + PRStreamSource_Init(&client->input_source.ssl, FlowErrorReporter_Create(&client->domain, COMPONENT_SOURCE), &client->ssl_bprfd); + source_interface = PRStreamSource_GetOutput(&client->input_source.ssl); + } else { + StreamSocketSource_Init(&client->input_source.plain, FlowErrorReporter_Create(&client->domain, COMPONENT_SOURCE), &client->sock); + source_interface = StreamSocketSource_GetOutput(&client->input_source.plain); + } + + // init interface + PacketPassInterface_Init(&client->input_interface, SC_MAX_ENC, (PacketPassInterface_handler_send)client_input_handler_send, client); + + // init decoder + if (!PacketProtoDecoder_Init( + &client->input_decoder, + FlowErrorReporter_Create(&client->domain, COMPONENT_DECODER), + source_interface, + &client->input_interface, + BReactor_PendingGroup(&ss) + )) { + client_log(client, BLOG_ERROR, "PacketProtoDecoder_Init failed"); + goto fail0; + } + + // init output common + + // init sink + StreamPassInterface *sink_input; + if (options.ssl) { + PRStreamSink_Init(&client->output_sink.ssl, FlowErrorReporter_Create(&client->domain, COMPONENT_SINK), &client->ssl_bprfd); + sink_input = PRStreamSink_GetInput(&client->output_sink.ssl); + } else { + StreamSocketSink_Init(&client->output_sink.plain, FlowErrorReporter_Create(&client->domain, COMPONENT_SINK), &client->sock); + sink_input = StreamSocketSink_GetInput(&client->output_sink.plain); + } + + // init sender + PacketStreamSender_Init(&client->output_sender, sink_input, PACKETPROTO_ENCLEN(SC_MAX_ENC)); + + // init queue + PacketPassPriorityQueue_Init(&client->output_priorityqueue, PacketStreamSender_GetInput(&client->output_sender), BReactor_PendingGroup(&ss)); + + // init output control flow + + // init queue flow + PacketPassPriorityQueueFlow_Init(&client->output_control_qflow, &client->output_priorityqueue, -1); + + // init PacketProtoFlow + if (!PacketProtoFlow_Init( + &client->output_control_oflow, + SC_MAX_ENC, + CLIENT_CONTROL_BUFFER_MIN_PACKETS, + PacketPassPriorityQueueFlow_GetInput(&client->output_control_qflow), + BReactor_PendingGroup(&ss) + )) { + client_log(client, BLOG_ERROR, "PacketProtoFlow_Init failed"); + goto fail1; + } + client->output_control_input = PacketBufferAsyncInput_GetInput(&client->output_control_oflow.ainput); + client->output_control_packet_len = -1; + + // init output peers flow + + // init queue flow + // use lower priority than control flow (higher number) + PacketPassPriorityQueueFlow_Init(&client->output_peers_qflow, &client->output_priorityqueue, 0); + + // init fair queue (for different peers) + PacketPassFairQueue_Init(&client->output_peers_fairqueue, PacketPassPriorityQueueFlow_GetInput(&client->output_peers_qflow), BReactor_PendingGroup(&ss)); + + // init list of flows + LinkedList2_Init(&client->output_peers_flows); + + return 1; + + // free output control flow +fail1: + PacketPassPriorityQueueFlow_Free(&client->output_control_qflow); + // free output common + PacketPassPriorityQueue_Free(&client->output_priorityqueue); + PacketStreamSender_Free(&client->output_sender); + if (options.ssl) { + PRStreamSink_Free(&client->output_sink.ssl); + } else { + StreamSocketSink_Free(&client->output_sink.plain); + } + // free input + PacketProtoDecoder_Free(&client->input_decoder); +fail0: + PacketPassInterface_Free(&client->input_interface); + if (options.ssl) { + PRStreamSource_Free(&client->input_source.ssl); + } else { + StreamSocketSource_Free(&client->input_source.plain); + } + return 0; +} + +void client_dealloc_io (struct client_data *client) +{ + // allow freeing fair queue flows + PacketPassFairQueue_PrepareFree(&client->output_peers_fairqueue); + + // remove flows to us + LinkedList2Node *node; + while (node = LinkedList2_GetFirst(&client->output_peers_flows)) { + struct peer_flow *flow = UPPER_OBJECT(node, struct peer_flow, dest_list_node); + ASSERT(flow->dest_client == client) + peer_flow_dealloc(flow); + } + + // allow freeing priority queue flows + PacketPassPriorityQueue_PrepareFree(&client->output_priorityqueue); + + // free output peers flow + PacketPassFairQueue_Free(&client->output_peers_fairqueue); + PacketPassPriorityQueueFlow_Free(&client->output_peers_qflow); + + // free output control flow + PacketProtoFlow_Free(&client->output_control_oflow); + PacketPassPriorityQueueFlow_Free(&client->output_control_qflow); + + // free output common + PacketPassPriorityQueue_Free(&client->output_priorityqueue); + PacketStreamSender_Free(&client->output_sender); + if (options.ssl) { + PRStreamSink_Free(&client->output_sink.ssl); + } else { + StreamSocketSink_Free(&client->output_sink.plain); + } + + // free input + PacketProtoDecoder_Free(&client->input_decoder); + PacketPassInterface_Free(&client->input_interface); + if (options.ssl) { + PRStreamSource_Free(&client->input_source.ssl); + } else { + StreamSocketSource_Free(&client->input_source.plain); + } +} + +void client_error_handler (struct client_data *client, int component, const void *data) +{ + ASSERT(INITSTATUS_HASLINK(client->initstatus)) + + switch (component) { + case COMPONENT_SOURCE: + case COMPONENT_SINK: + client_log(client, BLOG_NOTICE, "BSocket error %d", BSocket_GetError(&client->sock)); + if (options.ssl) { + client_log(client, BLOG_NOTICE, "NSPR error %d", (int)PR_GetError()); + } + break; + case COMPONENT_DECODER: + client_log(client, BLOG_NOTICE, "decoder error %d", *((int *)data)); + break; + default: + ASSERT(0); + } + + client_remove(client); + return; +} + +int client_start_control_packet (struct client_data *client, void **data, int len) +{ + ASSERT(INITSTATUS_HASLINK(client->initstatus)) + ASSERT(!client->dying) + ASSERT(client->output_control_packet_len == -1) + ASSERT(len >= 0) + ASSERT(len <= SC_MAX_PAYLOAD); + ASSERT(data || len == 0) + + // obtain location for writing the packet + DEAD_ENTER(client->dead) + int res = BestEffortPacketWriteInterface_Sender_StartPacket(client->output_control_input, &client->output_control_packet); + if (DEAD_LEAVE(client->dead)) { + return -1; + } + + ASSERT(res == 0 || res == 1) + + if (!res) { + // out of buffer, kill client + client_log(client, BLOG_NOTICE, "out of control buffer, removing"); + client_remove(client); + return -1; + } + + client->output_control_packet_len = len; + + if (data) { + *data = client->output_control_packet + sizeof(struct sc_header); + } + + return 0; +} + +int client_end_control_packet (struct client_data *client, uint8_t type) +{ + ASSERT(INITSTATUS_HASLINK(client->initstatus)) + ASSERT(!client->dying) + ASSERT(client->output_control_packet_len >= 0) + ASSERT(client->output_control_packet_len <= SC_MAX_PAYLOAD) + + // write header + struct sc_header *header = (struct sc_header *)client->output_control_packet; + header->type = type; + + // finish writing packet + DEAD_ENTER(client->dead) + BestEffortPacketWriteInterface_Sender_EndPacket(client->output_control_input, sizeof(struct sc_header) + client->output_control_packet_len); + if (DEAD_LEAVE(client->dead)) { + return -1; + } + + client->output_control_packet_len = -1; + + return 0; +} + +int client_input_handler_send (struct client_data *client, uint8_t *data, int data_len) +{ + ASSERT(INITSTATUS_HASLINK(client->initstatus)) + + // restart no data timer + BReactor_SetTimer(&ss, &client->disconnect_timer); + + if (data_len < sizeof(struct sc_header)) { + client_log(client, BLOG_NOTICE, "packet too short"); + client_remove(client); + return -1; + } + + struct sc_header *header = (struct sc_header *)data; + + uint8_t *sc_data = data + sizeof(struct sc_header); + int sc_data_len = data_len - sizeof(struct sc_header); + + #ifndef NDEBUG + DEAD_ENTER(client->dead) + #endif + + // perform action based on packet type + switch (header->type) { + case SCID_KEEPALIVE: + client_log(client, BLOG_DEBUG, "received keep-alive"); + break; + case SCID_CLIENTHELLO: + process_packet_hello(client, sc_data, sc_data_len); + break; + case SCID_OUTMSG: + process_packet_outmsg(client, sc_data, sc_data_len); + break; + default: + client_log(client, BLOG_NOTICE, "unknown packet type %d, removing", (int)header->type); + client_remove(client); + } + + #ifndef NDEBUG + if (DEAD_LEAVE(client->dead)) { + return -1; + } + #endif + + return 1; +} + +struct peer_flow * peer_flow_create (struct client_data *src_client, struct client_data *dest_client) +{ + ASSERT(dest_client->initstatus == INITSTATUS_COMPLETE) + ASSERT(!dest_client->dying) + ASSERT(!BAVL_LookupExact(&src_client->peer_out_flows_tree, &dest_client->id)) + + // allocate flow structure + struct peer_flow *flow = malloc(sizeof(*flow)); + if (!flow) { + goto fail0; + } + + // set source and destination + flow->src_client = src_client; + flow->dest_client = dest_client; + flow->dest_client_id = dest_client->id; + + // init dead variable + DEAD_INIT(flow->dead); + + // add to source list and hash table + LinkedList2_Append(&flow->src_client->peer_out_flows_list, &flow->src_list_node); + ASSERT_EXECUTE(BAVL_Insert(&flow->src_client->peer_out_flows_tree, &flow->src_tree_node, NULL)) + + // add to destination client list + LinkedList2_Append(&flow->dest_client->output_peers_flows, &flow->dest_list_node); + + // initialize I/O + PacketPassFairQueueFlow_Init(&flow->qflow, &flow->dest_client->output_peers_fairqueue); + if (!PacketProtoFlow_Init( + &flow->oflow, + SC_MAX_ENC, + CLIENT_PEER_FLOW_BUFFER_MIN_PACKETS, + PacketPassFairQueueFlow_GetInput(&flow->qflow), + BReactor_PendingGroup(&ss) + )) { + BLog(BLOG_ERROR, "PacketProtoFlow_Init failed"); + goto fail1; + } + flow->bepwi = PacketBufferAsyncInput_GetInput(&flow->oflow.ainput); + flow->packet_len = -1; + + return flow; + +fail1: + PacketPassFairQueueFlow_Free(&flow->qflow); + LinkedList2_Remove(&flow->dest_client->output_peers_flows, &flow->dest_list_node); + BAVL_Remove(&flow->src_client->peer_out_flows_tree, &flow->src_tree_node); + LinkedList2_Remove(&flow->src_client->peer_out_flows_list, &flow->src_list_node); + free(flow); +fail0: + return NULL; +} + +void peer_flow_dealloc (struct peer_flow *flow) +{ + // free I/O + PacketProtoFlow_Free(&flow->oflow); + PacketPassFairQueueFlow_Free(&flow->qflow); + + // remove from destination client list + LinkedList2_Remove(&flow->dest_client->output_peers_flows, &flow->dest_list_node); + + // remove from source list and hash table + if (flow->src_client) { + BAVL_Remove(&flow->src_client->peer_out_flows_tree, &flow->src_tree_node); + LinkedList2_Remove(&flow->src_client->peer_out_flows_list, &flow->src_list_node); + } + + // free dead variable + DEAD_KILL(flow->dead); + + // free memory + free(flow); +} + +void peer_flow_disconnect (struct peer_flow *flow) +{ + ASSERT(flow->src_client) + + // remove from source list and hash table + BAVL_Remove(&flow->src_client->peer_out_flows_tree, &flow->src_tree_node); + LinkedList2_Remove(&flow->src_client->peer_out_flows_list, &flow->src_list_node); + + // set no source + flow->src_client = NULL; +} + +int peer_flow_start_packet (struct peer_flow *flow, void **data, int len) +{ + ASSERT(flow->dest_client->initstatus == INITSTATUS_COMPLETE) + ASSERT(!flow->dest_client->dying) + ASSERT(flow->packet_len == -1) + ASSERT(len >= 0) + ASSERT(len <= SC_MAX_PAYLOAD) + ASSERT(data || len == 0) + + // obtain location for writing the packet + DEAD_ENTER(flow->dead) + int res = BestEffortPacketWriteInterface_Sender_StartPacket(flow->bepwi, &flow->packet); + if (DEAD_LEAVE(flow->dead)) { + return -1; + } + + ASSERT(res == 0 || res == 1) + + if (!res) { + BLog(BLOG_INFO, "out of flow buffer"); + return 0; + } + + flow->packet_len = len; + + if (data) { + *data = flow->packet + sizeof(struct sc_header); + } + + return 1; +} + +int peer_flow_end_packet (struct peer_flow *flow, uint8_t type) +{ + ASSERT(flow->dest_client->initstatus == INITSTATUS_COMPLETE) + ASSERT(!flow->dest_client->dying) + ASSERT(flow->packet_len >= 0) + ASSERT(flow->packet_len <= SC_MAX_PAYLOAD) + + // write header + struct sc_header *header = (struct sc_header *)flow->packet; + header->type = type; + + // finish writing packet + DEAD_ENTER(flow->dead) + BestEffortPacketWriteInterface_Sender_EndPacket(flow->bepwi, sizeof(struct sc_header) + flow->packet_len); + if (DEAD_LEAVE(flow->dead)) { + return -1; + } + + flow->packet_len = -1; + + return 0; +} + +void peer_flow_handler_canremove (struct peer_flow *flow) +{ + ASSERT(!flow->src_client) + + client_log(flow->dest_client, BLOG_DEBUG, "removing old flow"); + + peer_flow_dealloc(flow); + return; +} + +void process_packet_hello (struct client_data *client, uint8_t *data, int data_len) +{ + if (client->initstatus != INITSTATUS_WAITHELLO) { + client_log(client, BLOG_NOTICE, "hello: not expected"); + client_remove(client); + return; + } + + if (data_len != sizeof(struct sc_client_hello)) { + client_log(client, BLOG_NOTICE, "hello: invalid length"); + client_remove(client); + return; + } + + struct sc_client_hello *msg = (struct sc_client_hello *)data; + uint16_t version = ltoh16(msg->version); + + if (version != SC_VERSION) { + client_log(client, BLOG_NOTICE, "hello: unknown version"); + client_remove(client); + return; + } + + client_log(client, BLOG_INFO, "received hello"); + + // set client state to complete + client->initstatus = INITSTATUS_COMPLETE; + + // send hello + struct sc_server_hello *pack; + if (client_start_control_packet(client, (void **)&pack, sizeof(struct sc_server_hello)) < 0) { + return; + } + pack->flags = htol16(0); + pack->id = htol16(client->id); + pack->clientAddr = (client->addr.type == BADDR_TYPE_IPV4 ? client->addr.ipv4.ip : 0); + if (client_end_control_packet(client, SCID_SERVERHELLO) < 0) { + return; + } + + // send it the peer list and inform others + publish_client(client); + return; +} + +void process_packet_outmsg (struct client_data *client, uint8_t *data, int data_len) +{ + if (client->initstatus != INITSTATUS_COMPLETE) { + client_log(client, BLOG_NOTICE, "outmsg: not expected"); + client_remove(client); + return; + } + + if (data_len < sizeof(struct sc_client_outmsg)) { + client_log(client, BLOG_NOTICE, "outmsg: wrong size"); + client_remove(client); + return; + } + + struct sc_client_outmsg *msg = (struct sc_client_outmsg *)data; + peerid_t id = ltoh16(msg->clientid); + int payload_size = data_len - sizeof(struct sc_client_outmsg); + + if (payload_size > SC_MAX_MSGLEN) { + client_log(client, BLOG_NOTICE, "outmsg: too large payload"); + client_remove(client); + return; + } + + uint8_t *payload = data + sizeof(struct sc_client_outmsg); + + // lookup flow to destination client + BAVLNode *node = BAVL_LookupExact(&client->peer_out_flows_tree, &id); + if (!node) { + client_log(client, BLOG_NOTICE, "no flow for message to %d", (int)id); + return; + } + struct peer_flow *flow = UPPER_OBJECT(node, struct peer_flow, src_tree_node); + + // send packet + struct sc_server_inmsg *pack; + if (peer_flow_start_packet(flow, (void **)&pack, sizeof(struct sc_server_inmsg) + payload_size) <= 0) { + return; + } + pack->clientid = htol16(client->id); + memcpy((uint8_t *)pack + sizeof(struct sc_server_inmsg), payload, payload_size); + if (peer_flow_end_packet(flow, SCID_INMSG) < 0) { + return; + } + return; +} + +int client_send_newclient (struct client_data *client, struct client_data *nc, int relay_server, int relay_client) +{ + int flags = 0; + if (relay_server) { + flags |= SCID_NEWCLIENT_FLAG_RELAY_SERVER; + } + if (relay_client) { + flags |= SCID_NEWCLIENT_FLAG_RELAY_CLIENT; + } + + struct sc_server_newclient *pack; + if (client_start_control_packet(client, (void **)&pack, sizeof(struct sc_server_newclient) + (options.ssl ? nc->cert_len : 0)) < 0) { + return -1; + } + pack->id = htol16(nc->id); + pack->flags = htol16(flags); + if (options.ssl) { + memcpy(pack + 1, nc->cert, nc->cert_len); + } + if (client_end_control_packet(client, SCID_NEWCLIENT) < 0) { + return -1; + } + + return 0; +} + +int client_send_endclient (struct client_data *client, peerid_t end_id) +{ + struct sc_server_endclient *pack; + if (client_start_control_packet(client, (void **)&pack, sizeof(struct sc_server_endclient)) < 0) { + return -1; + } + pack->id = htol16(end_id); + if (client_end_control_packet(client, SCID_ENDCLIENT) < 0) { + return -1; + } + + return 0; +} + +void connect_clients (struct client_data *clientA, struct client_data *clientB) +{ + ASSERT(clientA->initstatus == INITSTATUS_COMPLETE) + ASSERT(clientB->initstatus == INITSTATUS_COMPLETE) + ASSERT(clientA != clientB) + + if (!clients_allowed(clientA, clientB)) { + return; + } + + client_log(clientA, BLOG_DEBUG, "connecting %d", (int)clientB->id); + + // determine relay relations + int relayAB = relay_allowed(clientA, clientB); + int relayBA = relay_allowed(clientB, clientA); + + // tell clientB about clientA + if (client_send_newclient(clientB, clientA, relayBA, relayAB) < 0) { + return; + } + + // create flow clientA -> clientB + struct peer_flow *flowAB = peer_flow_create(clientA, clientB); + if (!flowAB) { + client_send_endclient(clientB, clientA->id); + return; + } + + // tell clientA about clientB + if (client_send_newclient(clientA, clientB, relayAB, relayBA) < 0) { + return; + } + + // create flow clientB -> clientA + struct peer_flow *flowBA = peer_flow_create(clientB, clientA); + if (!flowBA) { + if (client_send_endclient(clientA, clientB->id) < 0) { + return; + } + peer_flow_dealloc(flowAB); + client_send_endclient(clientB, clientA->id); + return; + } +} + +int publish_client (struct client_data *client) +{ + ASSERT(client->initstatus == INITSTATUS_COMPLETE) + + // connect clients allowed to communicate + LinkedList2Iterator it; + LinkedList2Iterator_InitForward(&it, &clients); + LinkedList2Node *node; + while (node = LinkedList2Iterator_Next(&it)) { + struct client_data *client2 = UPPER_OBJECT(node, struct client_data, list_node); + if (client2->initstatus != INITSTATUS_COMPLETE || client2 == client) { + continue; + } + + DEAD_ENTER_N(server, dead) + DEAD_ENTER_N(client, client->dead) + connect_clients(client, client2); + if (DEAD_LEAVE_N(server, dead)) { + DEAD_LEAVE_N(client, client->dead); + return -1; + } + if (DEAD_LEAVE_N(client, client->dead)) { + LinkedList2Iterator_Free(&it); + return -1; + } + } + + return 0; +} + +peerid_t new_client_id (void) +{ + ASSERT(clients_num < MAX_CLIENTS) + + for (int i = 0; i < MAX_CLIENTS; i++) { + clients_lastid++; + if (!find_client_by_id(clients_lastid)) { + return clients_lastid; + } + } + + ASSERT(0) + return 42; +} + +struct client_data * find_client_by_id (peerid_t id) +{ + HashTableNode *node; + if (!HashTable_Lookup(&clients_by_id, &id, &node)) { + return NULL; + } + struct client_data *client = UPPER_OBJECT(node, struct client_data, table_node_id); + ASSERT(client->id == id) + + return client; +} + +int clients_by_id_key_comparator (peerid_t *id1, peerid_t *id2) +{ + return (*id1 == *id2); +} + +int clients_by_id_hash_function (peerid_t *id, int modulo) +{ + return (jenkins_lookup2_hash((uint8_t *)id, sizeof(peerid_t), clients_by_id_initval) % modulo); +} + +int clients_allowed (struct client_data *client1, struct client_data *client2) +{ + if (!options.comm_predicate) { + return 1; + } + + // set values to compare against + if (!options.ssl) { + comm_predicate_p1name = ""; + comm_predicate_p2name = ""; + } else { + comm_predicate_p1name = client1->common_name; + comm_predicate_p2name = client2->common_name; + } + BAddr_GetIPAddr(&client1->addr, &comm_predicate_p1addr); + BAddr_GetIPAddr(&client2->addr, &comm_predicate_p2addr); + + return BPredicate_Eval(&comm_predicate); +} + +int comm_predicate_func_p1name_cb (void *user, void **args) +{ + char *arg = args[0]; + + return (!strcmp(arg, comm_predicate_p1name)); +} + +int comm_predicate_func_p2name_cb (void *user, void **args) +{ + char *arg = args[0]; + + return (!strcmp(arg, comm_predicate_p2name)); +} + +int comm_predicate_func_p1addr_cb (void *user, void **args) +{ + char *arg = args[0]; + + BIPAddr addr; + if (!BIPAddr_Resolve(&addr, arg, 1)) { + BLog(BLOG_WARNING, "failed to parse address"); + return 0; + } + + return BIPAddr_Compare(&addr, &comm_predicate_p1addr); +} + +int comm_predicate_func_p2addr_cb (void *user, void **args) +{ + char *arg = args[0]; + + BIPAddr addr; + if (!BIPAddr_Resolve(&addr, arg, 1)) { + BLog(BLOG_WARNING, "failed to parse address"); + return 0; + } + + return BIPAddr_Compare(&addr, &comm_predicate_p2addr); +} + +int relay_allowed (struct client_data *client, struct client_data *relay) +{ + if (!options.relay_predicate) { + return 0; + } + + // set values to compare against + if (!options.ssl) { + relay_predicate_pname = ""; + relay_predicate_rname = ""; + } else { + relay_predicate_pname = client->common_name; + relay_predicate_rname = relay->common_name; + } + BAddr_GetIPAddr(&client->addr, &relay_predicate_paddr); + BAddr_GetIPAddr(&relay->addr, &relay_predicate_raddr); + + return BPredicate_Eval(&relay_predicate); +} + +int relay_predicate_func_pname_cb (void *user, void **args) +{ + char *arg = args[0]; + + return (!strcmp(arg, relay_predicate_pname)); +} + +int relay_predicate_func_rname_cb (void *user, void **args) +{ + char *arg = args[0]; + + return (!strcmp(arg, relay_predicate_rname)); +} + +int relay_predicate_func_paddr_cb (void *user, void **args) +{ + char *arg = args[0]; + + BIPAddr addr; + if (!BIPAddr_Resolve(&addr, arg, 1)) { + BLog(BLOG_ERROR, "paddr: failed to parse address"); + return 0; + } + + return BIPAddr_Compare(&addr, &relay_predicate_paddr); +} + +int relay_predicate_func_raddr_cb (void *user, void **args) +{ + char *arg = args[0]; + + BIPAddr addr; + if (!BIPAddr_Resolve(&addr, arg, 1)) { + BLog(BLOG_ERROR, "raddr: failed to parse address"); + return 0; + } + + return BIPAddr_Compare(&addr, &relay_predicate_raddr); +} + +int peerid_comparator (void *unused, peerid_t *p1, peerid_t *p2) +{ + if (*p1 < *p2) { + return -1; + } + if (*p1 > *p2) { + return 1; + } + return 0; +} diff --git a/server/server.h b/server/server.h new file mode 100644 index 000000000..6da72bffa --- /dev/null +++ b/server/server.h @@ -0,0 +1,162 @@ +/** + * @file server.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// name of the program +#define PROGRAM_NAME "server" + +// maxiumum number of connected clients. Must be <=2^16. +#define MAX_CLIENTS 30 +// client output control flow buffer size in packets +// it must hold: initdata, newclient's, endclient's (if other peers die when informing them) +// make it big enough to hold the initial packet burst (initdata, newclient's), +#define CLIENT_CONTROL_BUFFER_MIN_PACKETS (1 + 2*(MAX_CLIENTS - 1)) +// size of client-to-client buffers in packets +#define CLIENT_PEER_FLOW_BUFFER_MIN_PACKETS 10 +// after how long of not hearing anything from the client we disconnect it +#define CLIENT_NO_DATA_TIME_LIMIT 30000 + +// maxiumum listen addresses +#define MAX_LISTEN_ADDRS 16 + + +// performing SSL handshake +#define INITSTATUS_HANDSHAKE 1 +// waiting for clienthello +#define INITSTATUS_WAITHELLO 2 +// initialisation was complete +#define INITSTATUS_COMPLETE 3 + +#define INITSTATUS_HASLINK(status) ((status) == INITSTATUS_WAITHELLO || (status) == INITSTATUS_COMPLETE) + +struct client_data; + +struct peer_flow { + // dead variable + dead_t dead; + // source client + struct client_data *src_client; + // destination client + struct client_data *dest_client; + peerid_t dest_client_id; + // node in source client hash table (by destination), only when src_client != NULL + BAVLNode src_tree_node; + // node in source client list, only when src_client != NULL + LinkedList2Node src_list_node; + // node in destination client list + LinkedList2Node dest_list_node; + // output chain + PacketPassFairQueueFlow qflow; + PacketProtoFlow oflow; + BestEffortPacketWriteInterface *bepwi; + int packet_len; + uint8_t *packet; +}; + +struct client_data { + // dead variable + dead_t dead; + + // socket + BSocket sock; + BAddr addr; + + // SSL file descriptor + PRFileDesc bottom_prfd; + PRFileDesc *ssl_prfd; + BPRFileDesc ssl_bprfd; + + // initialization state + int initstatus; + + // client data if using SSL + uint8_t cert[SCID_NEWCLIENT_MAX_CERT_LEN]; + int cert_len; + char *common_name; + + // no data timer + BTimer disconnect_timer; + + // client ID + peerid_t id; + + // node in clients linked list + LinkedList2Node list_node; + // node in clients-by-id hash table + HashTableNode table_node_id; + + // flows from us + LinkedList2 peer_out_flows_list; + BAVL peer_out_flows_tree; + + // whether it's being removed + int dying; + + // error domain + FlowErrorDomain domain; + + // input + union { + StreamSocketSource plain; + PRStreamSource ssl; + } input_source; + PacketProtoDecoder input_decoder; + PacketPassInterface input_interface; + + // output common + union { + StreamSocketSink plain; + PRStreamSink ssl; + } output_sink; + PacketStreamSender output_sender; + PacketPassPriorityQueue output_priorityqueue; + + // output control flow + PacketPassPriorityQueueFlow output_control_qflow; + PacketProtoFlow output_control_oflow; + BestEffortPacketWriteInterface *output_control_input; + int output_control_packet_len; + uint8_t *output_control_packet; + + // output peers flow + PacketPassPriorityQueueFlow output_peers_qflow; + PacketPassFairQueue output_peers_fairqueue; + LinkedList2 output_peers_flows; +}; diff --git a/server_connection/CMakeLists.txt b/server_connection/CMakeLists.txt new file mode 100644 index 000000000..8bb48e47b --- /dev/null +++ b/server_connection/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(server_conection ServerConnection.c) +target_link_libraries(server_conection system flow nspr_support ${LIBCRYPTO_LIBRARIES} ${NSPR_LIBRARIES} ${NSS_LIBRARIES}) diff --git a/server_connection/ServerConnection.c b/server_connection/ServerConnection.c new file mode 100644 index 000000000..c99dcb004 --- /dev/null +++ b/server_connection/ServerConnection.c @@ -0,0 +1,684 @@ +/** + * @file ServerConnection.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include +#include + +#include + +#include + +#define STATE_CONNECTING 1 +#define STATE_WAITINIT 2 +#define STATE_COMPLETE 3 + +#define COMPONENT_SOURCE 1 +#define COMPONENT_SINK 2 +#define COMPONENT_DECODER 3 + +static void report_error (ServerConnection *o); +static void connect_handler (ServerConnection *o, int event); +static void pending_handler (ServerConnection *o); +static SECStatus client_auth_data_callback (ServerConnection *o, PRFileDesc *fd, CERTDistNames *caNames, CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey); +static void error_handler (ServerConnection *o, int component, const void *data); +static int input_handler_send (ServerConnection *o, uint8_t *data, int data_len); +static void packet_hello (ServerConnection *o, uint8_t *data, int data_len); +static void packet_newclient (ServerConnection *o, uint8_t *data, int data_len); +static void packet_endclient (ServerConnection *o, uint8_t *data, int data_len); +static void packet_inmsg (ServerConnection *o, uint8_t *data, int data_len); +static int start_packet (ServerConnection *o, void **data, int len); +static int end_packet (ServerConnection *o, uint8_t type); + +void report_error (ServerConnection *o) +{ + ASSERT(!o->error) + + o->error = 1; + + #ifndef NDEBUG + DEAD_ENTER(o->dead) + #endif + + o->handler_error(o->user); + + #ifndef NDEBUG + ASSERT(DEAD_KILLED) + DEAD_LEAVE(o->dead); + #endif +} + +void connect_handler (ServerConnection *o, int event) +{ + ASSERT(o->state == STATE_CONNECTING) + ASSERT(event == BSOCKET_CONNECT) + + // remove connect event handler + BSocket_RemoveEventHandler(&o->sock, BSOCKET_CONNECT); + + // check connection attempt result + int res = BSocket_GetConnectResult(&o->sock); + if (res != 0) { + BLog(BLOG_ERROR, "connection failed (BSocket error %d)", res); + goto fail0; + } + + BLog(BLOG_NOTICE, "connected"); + + if (o->have_ssl) { + // create BSocket NSPR file descriptor + BSocketPRFileDesc_Create(&o->bottom_prfd, &o->sock); + + // create SSL file descriptor from the socket's BSocketPRFileDesc + if (!(o->ssl_prfd = SSL_ImportFD(NULL, &o->bottom_prfd))) { + BLog(BLOG_ERROR, "SSL_ImportFD failed"); + ASSERT_FORCE(PR_Close(&o->bottom_prfd) == PR_SUCCESS) + goto fail0; + } + + // set client mode + if (SSL_ResetHandshake(o->ssl_prfd, PR_FALSE) != SECSuccess) { + BLog(BLOG_ERROR, "SSL_ResetHandshake failed"); + goto fail1; + } + + // set server name + if (SSL_SetURL(o->ssl_prfd, o->server_name) != SECSuccess) { + BLog(BLOG_ERROR, "SSL_SetURL failed"); + goto fail1; + } + + // set client certificate callback + if (SSL_GetClientAuthDataHook(o->ssl_prfd, (SSLGetClientAuthData)client_auth_data_callback, o) != SECSuccess) { + BLog(BLOG_ERROR, "SSL_GetClientAuthDataHook failed"); + goto fail1; + } + + // init BPRFileDesc + BPRFileDesc_Init(&o->ssl_bprfd, o->ssl_prfd); + } + + // init error domain + FlowErrorDomain_Init(&o->ioerrdomain, (FlowErrorDomain_handler)error_handler, o); + + // init keepalive output branch + SCKeepaliveSource_Init(&o->output_ka_zero); + PacketProtoEncoder_Init(&o->output_ka_encoder, SCKeepaliveSource_GetOutput(&o->output_ka_zero)); + + // init output common + + // init sink + StreamPassInterface *sink_interface; + if (o->have_ssl) { + PRStreamSink_Init(&o->output_sink.ssl, FlowErrorReporter_Create(&o->ioerrdomain, COMPONENT_SINK), &o->ssl_bprfd); + sink_interface = PRStreamSink_GetInput(&o->output_sink.ssl); + } else { + StreamSocketSink_Init(&o->output_sink.plain, FlowErrorReporter_Create(&o->ioerrdomain, COMPONENT_SINK), &o->sock); + sink_interface = StreamSocketSink_GetInput(&o->output_sink.plain); + } + + // init sender + PacketStreamSender_Init(&o->output_sender, sink_interface, PACKETPROTO_ENCLEN(SC_MAX_ENC)); + + // init keepalives + if (!KeepaliveIO_Init(&o->output_keepaliveio, o->reactor, PacketStreamSender_GetInput(&o->output_sender), PacketProtoEncoder_GetOutput(&o->output_ka_encoder), o->keepalive_interval)) { + BLog(BLOG_ERROR, "KeepaliveIO_Init failed"); + goto fail1a; + } + + // init queue + PacketPassPriorityQueue_Init(&o->output_queue, KeepaliveIO_GetInput(&o->output_keepaliveio), BReactor_PendingGroup(o->reactor)); + + // init output local flow + + // init queue flow + PacketPassPriorityQueueFlow_Init(&o->output_local_qflow, &o->output_queue, 0); + + // init PacketProtoFlow + if (!PacketProtoFlow_Init(&o->output_local_oflow, SC_MAX_ENC, o->buffer_size, PacketPassPriorityQueueFlow_GetInput(&o->output_local_qflow), BReactor_PendingGroup(o->reactor))) { + BLog(BLOG_ERROR, "PacketProtoFlow_Init failed"); + goto fail2; + } + o->output_local_if = PacketProtoFlow_GetInput(&o->output_local_oflow); + + // have no output packet + o->output_local_packet_len = -1; + + // init output user flow + PacketPassPriorityQueueFlow_Init(&o->output_user_qflow, &o->output_queue, 1); + + // init input chain + StreamRecvInterface *source_interface; + if (o->have_ssl) { + PRStreamSource_Init(&o->input_source.ssl, FlowErrorReporter_Create(&o->ioerrdomain, COMPONENT_SOURCE), &o->ssl_bprfd); + source_interface = PRStreamSource_GetOutput(&o->input_source.ssl); + } else { + StreamSocketSource_Init(&o->input_source.plain, FlowErrorReporter_Create(&o->ioerrdomain, COMPONENT_SOURCE), &o->sock); + source_interface = StreamSocketSource_GetOutput(&o->input_source.plain); + } + PacketPassInterface_Init(&o->input_interface, SC_MAX_ENC, (PacketPassInterface_handler_send)input_handler_send, o); + if (!PacketProtoDecoder_Init( + &o->input_decoder, + FlowErrorReporter_Create(&o->ioerrdomain, COMPONENT_DECODER), + source_interface, + &o->input_interface, + BReactor_PendingGroup(o->reactor) + )) { + BLog(BLOG_ERROR, "PacketProtoDecoder_Init failed"); + goto fail3; + } + + // set job to start I/O + BPending_Init(&o->start_job, BReactor_PendingGroup(o->reactor), (BPending_handler)pending_handler, o); + BPending_Set(&o->start_job); + + // update state + o->state = STATE_WAITINIT; + + return; + + // free input +fail3: + PacketPassInterface_Free(&o->input_interface); + if (o->have_ssl) { + PRStreamSource_Free(&o->input_source.ssl); + } else { + StreamSocketSource_Free(&o->input_source.plain); + } + // free output user flow + PacketPassPriorityQueueFlow_Free(&o->output_user_qflow); + // free output local flow + PacketProtoFlow_Free(&o->output_local_oflow); +fail2: + PacketPassPriorityQueueFlow_Free(&o->output_local_qflow); + // free output common + PacketPassPriorityQueue_Free(&o->output_queue); + KeepaliveIO_Free(&o->output_keepaliveio); +fail1a: + PacketStreamSender_Free(&o->output_sender); + if (o->have_ssl) { + PRStreamSink_Free(&o->output_sink.ssl); + } else { + StreamSocketSink_Free(&o->output_sink.plain); + } + // free output keep-alive branch + PacketProtoEncoder_Free(&o->output_ka_encoder); + SCKeepaliveSource_Free(&o->output_ka_zero); + // free SSL + if (o->have_ssl) { + BPRFileDesc_Free(&o->ssl_bprfd); +fail1: + ASSERT_FORCE(PR_Close(o->ssl_prfd) == PR_SUCCESS) + } +fail0: + // report error + report_error(o); +} + +void pending_handler (ServerConnection *o) +{ + ASSERT(o->state == STATE_WAITINIT) + + // send hello + int res; + struct sc_client_hello *packet; + if ((res = start_packet(o, (void **)&packet, sizeof(struct sc_client_hello))) < 0) { + return; + } + if (!res) { + BLog(BLOG_ERROR, "no buffer for hello"); + report_error(o); + return; + } + packet->version = htol16(SC_VERSION); + if (end_packet(o, SCID_CLIENTHELLO) < 0) { + return; + } +} + +SECStatus client_auth_data_callback (ServerConnection *o, PRFileDesc *fd, CERTDistNames *caNames, CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey) +{ + ASSERT(o->have_ssl) + + CERTCertificate *newcert; + if (!(newcert = CERT_DupCertificate(o->client_cert))) { + return SECFailure; + } + + SECKEYPrivateKey *newkey; + if (!(newkey = SECKEY_CopyPrivateKey(o->client_key))) { + CERT_DestroyCertificate(newcert); + return SECFailure; + } + + *pRetCert = newcert; + *pRetKey = newkey; + return SECSuccess; +} + +void error_handler (ServerConnection *o, int component, const void *data) +{ + ASSERT(o->state >= STATE_WAITINIT) + + switch (component) { + case COMPONENT_SOURCE: + case COMPONENT_SINK: + BLog(BLOG_ERROR, "BSocket error %d", BSocket_GetError(&o->sock)); + if (o->have_ssl) { + BLog(BLOG_ERROR, "NSPR error %d", (int)PR_GetError()); + } + break; + case COMPONENT_DECODER: + BLog(BLOG_ERROR, "decoder error %d", *((int *)data)); + break; + default: + ASSERT(0); + } + + BLog(BLOG_ERROR, "lost connection"); + + report_error(o); + return; +} + +int input_handler_send (ServerConnection *o, uint8_t *data, int data_len) +{ + ASSERT(o->state >= STATE_WAITINIT) + ASSERT(data_len >= 0) + ASSERT(data_len <= SC_MAX_ENC) + + if (data_len < sizeof(struct sc_header)) { + BLog(BLOG_ERROR, "packet too short (no sc header)"); + report_error(o); + return -1; + } + + struct sc_header *header = (struct sc_header *)data; + + uint8_t *sc_data = data + sizeof(struct sc_header); + int sc_data_len = data_len - sizeof(struct sc_header); + + #ifndef NDEBUG + DEAD_ENTER(o->dead) + #endif + + // call appropriate handler based on packet type + switch (header->type) { + case SCID_SERVERHELLO: + packet_hello(o, sc_data, sc_data_len); + break; + case SCID_NEWCLIENT: + packet_newclient(o, sc_data, sc_data_len); + break; + case SCID_ENDCLIENT: + packet_endclient(o, sc_data, sc_data_len); + break; + case SCID_INMSG: + packet_inmsg(o, sc_data, sc_data_len); + break; + default: + BLog(BLOG_ERROR, "unknown packet type %d", (int)header->type); + report_error(o); + } + + #ifndef NDEBUG + if (DEAD_LEAVE(o->dead)) { + return -1; + } + #endif + + return 1; +} + +void packet_hello (ServerConnection *o, uint8_t *data, int data_len) +{ + if (o->state != STATE_WAITINIT) { + BLog(BLOG_ERROR, "hello: not expected"); + report_error(o); + return; + } + + if (data_len != sizeof(struct sc_server_hello)) { + BLog(BLOG_ERROR, "hello: invalid length"); + report_error(o); + return; + } + + struct sc_server_hello *msg = (struct sc_server_hello *)data; + + // change state + o->state = STATE_COMPLETE; + + // report + o->handler_ready(o->user, ltoh16(msg->id), msg->clientAddr); + return; +} + +void packet_newclient (ServerConnection *o, uint8_t *data, int data_len) +{ + if (o->state != STATE_COMPLETE) { + BLog(BLOG_ERROR, "newclient: not expected"); + report_error(o); + return; + } + + if (data_len < sizeof(struct sc_server_newclient) || data_len > sizeof(struct sc_server_newclient) + SCID_NEWCLIENT_MAX_CERT_LEN) { + BLog(BLOG_ERROR, "newclient: invalid length"); + report_error(o); + return; + } + + struct sc_server_newclient *msg = (struct sc_server_newclient *)data; + peerid_t id = ltoh16(msg->id); + int flags = ltoh16(msg->flags); + + uint8_t *cert_data = (uint8_t *)msg + sizeof(struct sc_server_newclient); + int cert_len = data_len - sizeof(struct sc_server_newclient); + + // report + o->handler_newclient(o->user, id, flags, cert_data, cert_len); + return; +} + +void packet_endclient (ServerConnection *o, uint8_t *data, int data_len) +{ + if (o->state != STATE_COMPLETE) { + BLog(BLOG_ERROR, "endclient: not expected"); + report_error(o); + return; + } + + if (data_len != sizeof(struct sc_server_endclient)) { + BLog(BLOG_ERROR, "endclient: invalid length"); + report_error(o); + return; + } + + struct sc_server_endclient *msg = (struct sc_server_endclient *)data; + peerid_t id = ltoh16(msg->id); + + // report + o->handler_endclient(o->user, id); + return; +} + +void packet_inmsg (ServerConnection *o, uint8_t *data, int data_len) +{ + if (o->state != STATE_COMPLETE) { + BLog(BLOG_ERROR, "inmsg: not expected"); + report_error(o); + return; + } + + if (data_len < sizeof(struct sc_server_inmsg)) { + BLog(BLOG_ERROR, "inmsg: missing header"); + report_error(o); + return; + } + + if (data_len > sizeof(struct sc_server_inmsg) + SC_MAX_MSGLEN) { + BLog(BLOG_ERROR, "inmsg: too long"); + report_error(o); + return; + } + + struct sc_server_inmsg *msg = (struct sc_server_inmsg *)data; + peerid_t peer_id = ltoh16(msg->clientid); + uint8_t *payload = data + sizeof(struct sc_server_inmsg); + int payload_len = data_len - sizeof(struct sc_server_inmsg); + + // report + o->handler_message(o->user, peer_id, payload, payload_len); + return; +} + +int start_packet (ServerConnection *o, void **data, int len) +{ + ASSERT(o->state >= STATE_WAITINIT) + ASSERT(o->output_local_packet_len == -1) + ASSERT(len >= 0) + ASSERT(len <= SC_MAX_PAYLOAD) + ASSERT(data || len == 0) + + // obtain location for writing the packet + DEAD_ENTER(o->dead) + int res = BestEffortPacketWriteInterface_Sender_StartPacket(o->output_local_if, &o->output_local_packet); + if (DEAD_LEAVE(o->dead)) { + return -1; + } + + ASSERT(res == 0 || res == 1) + + if (!res) { + BLog(BLOG_ERROR, "out of buffer"); + return 0; + } + + o->output_local_packet_len = len; + + if (data) { + *data = o->output_local_packet + sizeof(struct sc_header); + } + + return 1; +} + +int end_packet (ServerConnection *o, uint8_t type) +{ + ASSERT(o->state >= STATE_WAITINIT) + ASSERT(o->output_local_packet_len >= 0) + ASSERT(o->output_local_packet_len <= SC_MAX_PAYLOAD) + + // write header + struct sc_header *header = (struct sc_header *)o->output_local_packet; + header->type = type; + + // finish writing packet + DEAD_ENTER(o->dead) + BestEffortPacketWriteInterface_Sender_EndPacket(o->output_local_if, sizeof(struct sc_header) + o->output_local_packet_len); + if (DEAD_LEAVE(o->dead)) { + return -1; + } + + o->output_local_packet_len = -1; + + return 0; +} + +int ServerConnection_Init ( + ServerConnection *o, + BReactor *reactor, + BAddr addr, + int keepalive_interval, + int buffer_size, + int have_ssl, + CERTCertificate *client_cert, + SECKEYPrivateKey *client_key, + const char *server_name, + void *user, + ServerConnection_handler_error handler_error, + ServerConnection_handler_ready handler_ready, + ServerConnection_handler_newclient handler_newclient, + ServerConnection_handler_endclient handler_endclient, + ServerConnection_handler_message handler_message +) +{ + ASSERT(keepalive_interval > 0) + ASSERT(buffer_size > 0) + ASSERT(have_ssl == 0 || have_ssl == 1) + + // init arguments + o->reactor = reactor; + o->keepalive_interval = keepalive_interval; + o->buffer_size = buffer_size; + o->have_ssl = have_ssl; + if (have_ssl) { + o->client_cert = client_cert; + o->client_key = client_key; + snprintf(o->server_name, sizeof(o->server_name), "%s", server_name); + } + o->user = user; + o->handler_error = handler_error; + o->handler_ready = handler_ready; + o->handler_newclient = handler_newclient; + o->handler_endclient = handler_endclient; + o->handler_message = handler_message; + + // init dead var + DEAD_INIT(o->dead); + + // init socket + if (BSocket_Init(&o->sock, o->reactor, addr.type, BSOCKET_TYPE_STREAM) < 0) { + BLog(BLOG_ERROR, "BSocket_Init failed (%d)", BSocket_GetError(&o->sock)); + goto fail0; + } + + // start connecting + int res = BSocket_Connect(&o->sock, &addr); + if (res != -1 || BSocket_GetError(&o->sock) != BSOCKET_ERROR_IN_PROGRESS) { + BLog(BLOG_ERROR, "BSocket_Connect failed (%d)", BSocket_GetError(&o->sock)); + goto fail1; + } + + // be informed of connection result + BSocket_AddEventHandler(&o->sock, BSOCKET_CONNECT, (BSocket_handler)connect_handler, o); + BSocket_EnableEvent(&o->sock, BSOCKET_CONNECT); + + // set state + o->state = STATE_CONNECTING; + + // set no error + o->error = 0; + + // init debug object + DebugObject_Init(&o->d_obj); + + return 1; + +fail1: + BSocket_Free(&o->sock); +fail0: + return 0; +} + +void ServerConnection_Free (ServerConnection *o) +{ + // free debug object + DebugObject_Free(&o->d_obj); + + if (o->state > STATE_CONNECTING) { + // free job + BPending_Free(&o->start_job); + + // free input chain + PacketProtoDecoder_Free(&o->input_decoder); + PacketPassInterface_Free(&o->input_interface); + if (o->have_ssl) { + PRStreamSource_Free(&o->input_source.ssl); + } else { + StreamSocketSource_Free(&o->input_source.plain); + } + + // allow freeing queue flows + PacketPassPriorityQueue_PrepareFree(&o->output_queue); + + // free output user flow + PacketPassPriorityQueueFlow_Free(&o->output_user_qflow); + + // free output local flow + PacketProtoFlow_Free(&o->output_local_oflow); + PacketPassPriorityQueueFlow_Free(&o->output_local_qflow); + + // free output common + PacketPassPriorityQueue_Free(&o->output_queue); + KeepaliveIO_Free(&o->output_keepaliveio); + PacketStreamSender_Free(&o->output_sender); + if (o->have_ssl) { + PRStreamSink_Free(&o->output_sink.ssl); + } else { + StreamSocketSink_Free(&o->output_sink.plain); + } + + // free output keep-alive branch + PacketProtoEncoder_Free(&o->output_ka_encoder); + SCKeepaliveSource_Free(&o->output_ka_zero); + + // free SSL + if (o->have_ssl) { + BPRFileDesc_Free(&o->ssl_bprfd); + ASSERT_FORCE(PR_Close(o->ssl_prfd) == PR_SUCCESS) + } + } + + // free socket + BSocket_Free(&o->sock); + + // free dead var + DEAD_KILL(o->dead); +} + +int ServerConnection_StartMessage (ServerConnection *o, void **data, peerid_t peer_id, int len) +{ + ASSERT(!o->error) + ASSERT(o->state == STATE_COMPLETE) + ASSERT(o->output_local_packet_len == -1) + ASSERT(len >= 0) + ASSERT(len <= SC_MAX_MSGLEN) + ASSERT(data || len == 0) + + uint8_t *packet; + int res; + if ((res = start_packet(o, (void **)&packet, sizeof(struct sc_client_outmsg) + len)) < 0) { + return -1; + } + if (!res) { + return 0; + } + + struct sc_client_outmsg *msg = (struct sc_client_outmsg *)packet; + msg->clientid = htol16(peer_id); + + if (data) { + *data = packet + sizeof(struct sc_client_outmsg); + } + + return 1; +} + +void ServerConnection_EndMessage (ServerConnection *o) +{ + ASSERT(!o->error) + ASSERT(o->state == STATE_COMPLETE) + ASSERT(o->output_local_packet_len >= 0) + + end_packet(o, SCID_OUTMSG); + return; +} + +PacketPassInterface * ServerConnection_GetSendInterface (ServerConnection *o) +{ + ASSERT(!o->error) + ASSERT(o->state == STATE_COMPLETE) + + return PacketPassPriorityQueueFlow_GetInput(&o->output_user_qflow); +} diff --git a/server_connection/ServerConnection.h b/server_connection/ServerConnection.h new file mode 100644 index 000000000..31e9ddaac --- /dev/null +++ b/server_connection/ServerConnection.h @@ -0,0 +1,299 @@ +/** + * @file ServerConnection.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Object used to communicate with a VPN chat server. + */ + +#ifndef BADVPN_SERVERCONNECTION_SERVERCONNECTION_H +#define BADVPN_SERVERCONNECTION_SERVERCONNECTION_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Handler function invoked when an error occurs. + * The object must be freed from withing this function. + * + * @param user value passed to {@link ServerConnection_Init} + */ +typedef void (*ServerConnection_handler_error) (void *user); + +/** + * Handler function invoked when the server becomes ready, i.e. + * the hello packet has been received. + * The object was in not ready state before. + * The object enters ready state before the handler is invoked. + * + * @param user value passed to {@link ServerConnection_Init} + * @param my_id our ID as reported by the server + * @param ext_ip the clientAddr field in the server's hello packet + */ +typedef void (*ServerConnection_handler_ready) (void *user, peerid_t my_id, uint32_t ext_ip); + +/** + * Handler function invoked when a newclient packet is received. + * The object was in ready state. + * + * @param user value passed to {@link ServerConnection_Init} + * @param peer_id ID of the peer + * @param flags flags field from the newclient message + * @param cert peer's certificate (if any) + * @param cert_len certificate length. Will be >=0. + */ +typedef void (*ServerConnection_handler_newclient) (void *user, peerid_t peer_id, int flags, const uint8_t *cert, int cert_len); + +/** + * Handler function invoked when an enclient packet is received. + * The object was in ready state. + * + * @param user value passed to {@link ServerConnection_Init} + * @param peer_id ID of the peer + */ +typedef void (*ServerConnection_handler_endclient) (void *user, peerid_t peer_id); + +/** + * Handler function invoked when an inmsg packet is received. + * The object was in ready state. + * + * @param user value passed to {@link ServerConnection_Init} + * @param peer_id ID of the peer from which the message came + * @param data message payload + * @param data_len message length. Will be >=0. + */ +typedef void (*ServerConnection_handler_message) (void *user, peerid_t peer_id, uint8_t *data, int data_len); + +/** + * Object used to communicate with a VPN chat server. + */ +typedef struct { + // debug object + DebugObject d_obj; + + // dead var + dead_t dead; + + // reactor + BReactor *reactor; + + // keepalive interval + int keepalive_interval; + + // send buffer size + int buffer_size; + + // whether we use SSL + int have_ssl; + + // client certificate if using SSL + CERTCertificate *client_cert; + + // client private key if using SSL + SECKEYPrivateKey *client_key; + + // server name if using SSL + char server_name[256]; + + // handlers + void *user; + ServerConnection_handler_error handler_error; + ServerConnection_handler_ready handler_ready; + ServerConnection_handler_newclient handler_newclient; + ServerConnection_handler_endclient handler_endclient; + ServerConnection_handler_message handler_message; + + // socket + BSocket sock; + + // state + int state; + + // whether an error is being reported + int error; + + // defined when state > SERVERCONNECTION_STATE_CONNECTING + + // SSL file descriptor, defined only if using SSL + PRFileDesc bottom_prfd; + PRFileDesc *ssl_prfd; + BPRFileDesc ssl_bprfd; + + // I/O error domain + FlowErrorDomain ioerrdomain; + + // keepalive output branch + SCKeepaliveSource output_ka_zero; + PacketProtoEncoder output_ka_encoder; + + // output common + PacketPassPriorityQueue output_queue; + KeepaliveIO output_keepaliveio; + PacketStreamSender output_sender; + union { + StreamSocketSink plain; + PRStreamSink ssl; + } output_sink; + + // output local flow + int output_local_packet_len; + uint8_t *output_local_packet; + BestEffortPacketWriteInterface *output_local_if; + PacketProtoFlow output_local_oflow; + PacketPassPriorityQueueFlow output_local_qflow; + + // output user flow + PacketPassPriorityQueueFlow output_user_qflow; + + // input + union { + StreamSocketSource plain; + PRStreamSource ssl; + } input_source; + PacketProtoDecoder input_decoder; + PacketPassInterface input_interface; + + // job to start client I/O + BPending start_job; +} ServerConnection; + +/** + * Initializes the object. + * The object is initialized in not ready state. + * {@link BLog_Init} must have been done. + * {@link BSocket_GlobalInit} must have been done. + * {@link BSocketPRFileDesc_GlobalInit} must have been done if using SSL. + * + * @param o the object + * @param reactor {@link BReactor} we live in + * @param addr address to connect to + * @param keepalive_interval keep-alive sending interval. Must be >0. + * @param buffer_size minimum size of send buffer in number of packets. Must be >0. + * @param have_ssl whether to use SSL for connecting to the server. Must be 1 or 0. + * @param client_cert if using SSL, client certificate to use. Must remain valid as + * long as this object is alive. + * @param client_key if using SSL, prvate ket to use. Must remain valid as + * long as this object is alive. + * @param server_name if using SSL, the name of the server. The string is copied. + * @param user value passed to callback functions + * @param handler_error error handler. The object must be freed from within the error + * handler before doing anything else with this object. + * @param handler_ready handler when the server becomes ready, i.e. the hello message has + * been received. + * @param handler_newclient handler when a newclient message has been received + * @param handler_endclient handler when an endclient message has been received + * @param handler_message handler when a peer message has been reveived + * @return 1 on success, 0 on failure + */ +int ServerConnection_Init ( + ServerConnection *o, + BReactor *reactor, + BAddr addr, + int keepalive_interval, + int buffer_size, + int have_ssl, + CERTCertificate *client_cert, + SECKEYPrivateKey *client_key, + const char *server_name, + void *user, + ServerConnection_handler_error handler_error, + ServerConnection_handler_ready handler_ready, + ServerConnection_handler_newclient handler_newclient, + ServerConnection_handler_endclient handler_endclient, + ServerConnection_handler_message handler_message +) WARN_UNUSED; + +/** + * Frees the object. + * + * @param o the object + */ +void ServerConnection_Free (ServerConnection *o); + +/** + * Provides a buffer for writing a message to be sent to a peer. + * The object must be in ready and not writing state. + * On success, the object enters writing state. + * Must not be called from the error handler. + * May invoke the error handler. + * + * @param o the object + * @param data the buffer will be returned here on success. Must not be NULL unless len is 0. + * @param peer_id ID of peer the message goes to + * @param len length of the message. Must be >=0 and <=SC_MAX_MSGLEN. + * @return 1 on success, 0 on out of buffer + */ +int ServerConnection_StartMessage (ServerConnection *o, void **data, peerid_t peer_id, int len) WARN_UNUSED; + +/** + * Submits a written message for sending to a peer. + * The object must be in ready and writing state. + * The object enters not writing state. + * Must not be called from the error handler. + * May invoke the error handler. + * + * @param o the object + */ +void ServerConnection_EndMessage (ServerConnection *o); + +/** + * Returns an interface for sending data to the server (just one). + * This goes directly into the link (i.e. TCP, possibly via SSL), so packets + * need to be manually encoded according to PacketProto. + * The interface must not be used after an error was reported. + * The object must be in ready and writing state. + * Must not be called from the error handler. + * + * @param o the object + * @return the interface + */ +PacketPassInterface * ServerConnection_GetSendInterface (ServerConnection *o); + +#endif diff --git a/structure/BAVL.h b/structure/BAVL.h new file mode 100644 index 000000000..2969bdef5 --- /dev/null +++ b/structure/BAVL.h @@ -0,0 +1,597 @@ +/** + * @file BAVL.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * AVL tree. + */ + +#ifndef BADVPN_STRUCTURE_BAVL_H +#define BADVPN_STRUCTURE_BAVL_H + +//#define BAVL_DEBUG + +#include +#include + +#include + +/** + * Handler function called by tree algorithms to compare two values. + * For any two values, the comparator must always return the same result. + * The <= relation defined by the comparator must be a total order. + * Values are obtained like that: + * - The value of a node in the tree, or a node that is being inserted is: + * (uint8_t *)node + offset. + * - The value being looked up is the same as given to the lookup function. + * + * @param user as in {@link BAVL_Init} + * @param val1 first value + * @param val2 second value + * @return -1 if val1 < val2, 0 if val1 = val2, 1 if val1 > val2 + */ +typedef int (*BAVL_comparator) (void *user, void *val1, void *val2); + +struct BAVLNode; + +/** + * AVL tree. + */ +typedef struct { + int offset; + BAVL_comparator comparator; + void *user; + struct BAVLNode *root; + #ifndef NDEBUG + int in_handler; + #endif +} BAVL; + +/** + * AVL tree node. + */ +typedef struct BAVLNode { + struct BAVLNode *parent; + struct BAVLNode *link[2]; + int balance; +} BAVLNode; + +/** + * Initializes the tree. + * + * @param o tree to initialize + * @param offset offset of a value from its node + * @param comparator value comparator handler function + * @param user value to pass to comparator + */ +static void BAVL_Init (BAVL *o, int offset, BAVL_comparator comparator, void *user); + +/** + * Inserts a node into the tree. + * Must not be called from comparator. + * + * @param o the tree + * @param node uninitialized node to insert. Must have a valid value (its value + * may be passed to the comparator during insertion). + * @param ref if not NULL, will return (regardless if insertion succeeded): + * - the greatest node lesser than the inserted value, or (not in order) + * - the smallest node greater than the inserted value, or + * - NULL meaning there were no nodes in the tree. + * @param 1 on success, 0 if an element with an equal value is already in the tree + */ +static int BAVL_Insert (BAVL *o, BAVLNode *node, BAVLNode **ref); + +/** + * Removes a node from the tree. + * Must not be called from comparator. + * + * @param o the tree + * @param node node to remove + */ +static void BAVL_Remove (BAVL *o, BAVLNode *node); + +/** + * Checks if the tree is empty. + * Must not be called from comparator. + * + * @param o the tree + * @return 1 if empty, 0 if not + */ +static int BAVL_IsEmpty (BAVL *o); + +/** + * Looks for a value in the tree. + * Must not be called from comparator. + * + * @param o the tree + * @param val value to look for + * @return If a node is in the thee with an equal value, that node. + * Else if the tree is not empty: + * - the greatest node lesser than the given value, or (not in order) + * - the smallest node greater than the given value. + * NULL if the tree is empty. + */ +static BAVLNode * BAVL_Lookup (BAVL *o, void *val); + +/** + * Looks for a value in the tree. + * Must not be called from comparator. + * + * @param o the tree + * @param val value to look for + * @return If a node is in the thee with an equal value, that node. + * Else NULL. + */ +static BAVLNode * BAVL_LookupExact (BAVL *o, void *val); + +#define BAVL_MAX(_a, _b) ((_a) > (_b) ? (_a) : (_b)) +#define BAVL_OPTNEG(_a, _neg) ((_neg) ? -(_a) : (_a)) + +static void * _BAVL_node_value (BAVL *o, BAVLNode *n) +{ + return ((uint8_t *)n + o->offset); +} + +static int _BAVL_compare_values (BAVL *o, void *v1, void *v2) +{ + #ifndef NDEBUG + o->in_handler = 1; + #endif + + int res = o->comparator(o->user, v1, v2); + + #ifndef NDEBUG + o->in_handler = 0; + #endif + + ASSERT(res == -1 || res == 0 || res == 1) + + return res; +} + +static int _BAVL_compare_nodes (BAVL *o, BAVLNode *n1, BAVLNode *n2) +{ + return _BAVL_compare_values(o, _BAVL_node_value(o, n1), _BAVL_node_value(o, n2)); +} + +#ifdef BAVL_DEBUG +#define BAVL_ASSERT(_h) _BAVL_assert(_h); +#else +#define BAVL_ASSERT(_h) +#endif + +#ifdef BAVL_DEBUG + +static int _BAVL_assert_recurser (BAVL *o, BAVLNode *n) +{ + ASSERT(n->balance >= -1 && n->balance <= 1) + + int height_left = 0; + int height_right = 0; + + // check left subtree + if (n->link[0]) { + // check parent link + ASSERT(n->link[0]->parent == n) + // check binary search tree + ASSERT(_BAVL_compare_nodes(o, n->link[0], n) == -1) + // recursively calculate height + height_left = _BAVL_assert_recurser(o, n->link[0]); + } + + // check right subtree + if (n->link[1]) { + // check parent link + ASSERT(n->link[1]->parent == n) + // check binary search tree + ASSERT(_BAVL_compare_nodes(o, n->link[1], n) == 1) + // recursively calculate height + height_right = _BAVL_assert_recurser(o, n->link[1]); + } + + // check balance factor + ASSERT(n->balance == height_right - height_left) + + return (BAVL_MAX(height_left, height_right) + 1); +} + +static void _BAVL_assert (BAVL *o) +{ + if (o->root) { + ASSERT(!o->root->parent) + _BAVL_assert_recurser(o, o->root); + } +} + +#endif + +static void _BAVL_rotate (BAVL *tree, BAVLNode *r, int dir) +{ + BAVLNode *nr = r->link[!dir]; + + r->link[!dir] = nr->link[dir]; + if (r->link[!dir]) { + r->link[!dir]->parent = r; + } + nr->link[dir] = r; + nr->parent = r->parent; + if (nr->parent) { + nr->parent->link[r == r->parent->link[1]] = nr; + } else { + tree->root = nr; + } + r->parent = nr; +} + +static BAVLNode * _BAVL_subtree_max (BAVLNode *n) +{ + ASSERT(n) + while (n->link[1]) { + n = n->link[1]; + } + return n; +} + +static void _BAVL_replace_subtree (BAVL *tree, BAVLNode *dest, BAVLNode *n) +{ + ASSERT(dest) + + if (dest->parent) { + dest->parent->link[dest == dest->parent->link[1]] = n; + } else { + tree->root = n; + } + if (n) { + n->parent = dest->parent; + } +} + +static void _BAVL_swap_nodes (BAVL *tree, BAVLNode *n1, BAVLNode *n2) +{ + if (n2->parent == n1 || n1->parent == n2) { + // when the nodes are directly connected we need special handling + // make sure n1 is above n2 + if (n1->parent == n2) { + BAVLNode *t = n1; + n1 = n2; + n2 = t; + } + + int side = (n2 == n1->link[1]); + BAVLNode *c = n1->link[!side]; + + if (n1->link[0] = n2->link[0]) { + n1->link[0]->parent = n1; + } + if (n1->link[1] = n2->link[1]) { + n1->link[1]->parent = n1; + } + + if (n2->parent = n1->parent) { + n2->parent->link[n1 == n1->parent->link[1]] = n2; + } else { + tree->root = n2; + } + + n2->link[side] = n1; + n1->parent = n2; + if (n2->link[!side] = c) { + c->parent = n2; + } + } else { + BAVLNode *temp; + + // swap parents + temp = n1->parent; + if (n1->parent = n2->parent) { + n1->parent->link[n2 == n2->parent->link[1]] = n1; + } else { + tree->root = n1; + } + if (n2->parent = temp) { + n2->parent->link[n1 == temp->link[1]] = n2; + } else { + tree->root = n2; + } + + // swap left children + temp = n1->link[0]; + if (n1->link[0] = n2->link[0]) { + n1->link[0]->parent = n1; + } + if (n2->link[0] = temp) { + n2->link[0]->parent = n2; + } + + // swap right children + temp = n1->link[1]; + if (n1->link[1] = n2->link[1]) { + n1->link[1]->parent = n1; + } + if (n2->link[1] = temp) { + n2->link[1]->parent = n2; + } + } + + // swap balance factors + int b = n1->balance; + n1->balance = n2->balance; + n2->balance = b; +} + +static void _BAVL_rebalance (BAVL *o, BAVLNode *node, int side, int deltac) +{ + ASSERT(side == 0 || side == 1) + ASSERT(deltac >= -1 && deltac <= 1) + ASSERT(node->balance >= -1 && node->balance <= 1) + + // if no subtree changed its height, no more rebalancing is needed + if (deltac == 0) { + return; + } + + // calculate how much our height changed + int delta = BAVL_MAX(deltac, BAVL_OPTNEG(node->balance, side)) - BAVL_MAX(0, BAVL_OPTNEG(node->balance, side)); + ASSERT(delta >= -1 && delta <= 1) + + // update our balance factor + node->balance -= BAVL_OPTNEG(deltac, side); + + BAVLNode *child; + BAVLNode *gchild; + + // perform transformations if the balance factor is wrong + if (node->balance == 2 || node->balance == -2) { + int bside; + int bsidef; + if (node->balance == 2) { + bside = 1; + bsidef = 1; + } else { + bside = 0; + bsidef = -1; + } + + ASSERT(node->link[bside]) + child = node->link[bside]; + switch (child->balance * bsidef) { + case 1: + _BAVL_rotate(o, node, !bside); + node->balance = 0; + child->balance = 0; + node = child; + delta -= 1; + break; + case 0: + _BAVL_rotate(o, node, !bside); + node->balance = 1 * bsidef; + child->balance = -1 * bsidef; + node = child; + break; + case -1: + ASSERT(child->link[!bside]) + gchild = child->link[!bside]; + _BAVL_rotate(o, child, bside); + _BAVL_rotate(o, node, !bside); + node->balance = -BAVL_MAX(0, gchild->balance * bsidef) * bsidef; + child->balance = BAVL_MAX(0, -gchild->balance * bsidef) * bsidef; + gchild->balance = 0; + node = gchild; + delta -= 1; + break; + default: + ASSERT(0); + } + } + + ASSERT(delta >= -1 && delta <= 1) + // Transformations above preserve this. Proof: + // - if a child subtree gained 1 height and rebalancing was needed, + // it was the heavier subtree. Then delta was was originally 1, because + // the heaviest subtree gained one height. If the transformation reduces + // delta by one, it becomes 0. + // - if a child subtree lost 1 height and rebalancing was needed, it + // was the lighter subtree. Then delta was originally 0, because + // the height of the heaviest subtree was unchanged. If the transformation + // reduces delta by one, it becomes -1. + + if (node->parent) { + _BAVL_rebalance(o, node->parent, node == node->parent->link[1], delta); + } +} + +void BAVL_Init (BAVL *o, int offset, BAVL_comparator comparator, void *user) +{ + o->offset = offset; + o->comparator = comparator; + o->user = user; + o->root = NULL; + + #ifndef NDEBUG + o->in_handler = 0; + #endif + + BAVL_ASSERT(o) +} + +int BAVL_Insert (BAVL *o, BAVLNode *node, BAVLNode **ref) +{ + ASSERT(!o->in_handler) + + // insert to root? + if (!o->root) { + o->root = node; + node->parent = NULL; + node->link[0] = NULL; + node->link[1] = NULL; + node->balance = 0; + + BAVL_ASSERT(o) + + if (ref) { + *ref = NULL; + } + return 1; + } + + // find node to insert to + BAVLNode *c = o->root; + int side; + while (1) { + // compare + int comp = _BAVL_compare_nodes(o, node, c); + + // have we found a node that compares equal? + if (comp == 0) { + if (ref) { + *ref = c; + } + return 0; + } + + side = (comp == 1); + + // have we reached a leaf? + if (!c->link[side]) { + break; + } + + c = c->link[side]; + } + + // insert + c->link[side] = node; + node->parent = c; + node->link[0] = NULL; + node->link[1] = NULL; + node->balance = 0; + + // rebalance + _BAVL_rebalance(o, c, side, 1); + + BAVL_ASSERT(o) + + if (ref) { + *ref = c; + } + return 1; +} + +void BAVL_Remove (BAVL *o, BAVLNode *node) +{ + ASSERT(!o->in_handler) + + // if we have both subtrees, swap the node and the largest node + // in the left subtree, so we have at most one subtree + if (node->link[0] && node->link[1]) { + // find the largest node in the left subtree + BAVLNode *max = _BAVL_subtree_max(node->link[0]); + // swap the nodes + _BAVL_swap_nodes(o, node, max); + } + + // have at most one child now + ASSERT(!(node->link[0] && node->link[1])) + + BAVLNode *parent = node->parent; + BAVLNode *child = (node->link[0] ? node->link[0] : node->link[1]); + + if (parent) { + // remember on which side node is + int side = (node == parent->link[1]); + // replace node with child + _BAVL_replace_subtree(o, node, child); + // rebalance + _BAVL_rebalance(o, parent, side, -1); + } else { + // replace node with child + _BAVL_replace_subtree(o, node, child); + } + + BAVL_ASSERT(o) +} + +int BAVL_IsEmpty (BAVL *o) +{ + ASSERT(!o->in_handler) + + return (!o->root); +} + +BAVLNode * BAVL_Lookup (BAVL *o, void *val) +{ + ASSERT(!o->in_handler) + + if (!o->root) { + return NULL; + } + + BAVLNode *c = o->root; + while (1) { + // compare + int comp = _BAVL_compare_values(o, val, _BAVL_node_value(o, c)); + + // have we found a node that compares equal? + if (comp == 0) { + return c; + } + + int side = (comp == 1); + + // have we reached a leaf? + if (!c->link[side]) { + return c; + } + + c = c->link[side]; + } +} + +BAVLNode * BAVL_LookupExact (BAVL *o, void *val) +{ + ASSERT(!o->in_handler) + + if (!o->root) { + return NULL; + } + + BAVLNode *c = o->root; + while (1) { + // compare + int comp = _BAVL_compare_values(o, val, _BAVL_node_value(o, c)); + + // have we found a node that compares equal? + if (comp == 0) { + return c; + } + + int side = (comp == 1); + + // have we reached a leaf? + if (!c->link[side]) { + return NULL; + } + + c = c->link[side]; + } +} + +#endif diff --git a/structure/BHeap.h b/structure/BHeap.h new file mode 100644 index 000000000..af10c1cc5 --- /dev/null +++ b/structure/BHeap.h @@ -0,0 +1,450 @@ +/** + * @file BHeap.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Binary heap. + */ + +#ifndef BADVPN_STRUCTURE_BHEAP_H +#define BADVPN_STRUCTURE_BHEAP_H + +//#define BHEAP_DEBUG + +#include +#include + +#include + +/** + * Handler function called by heap algorithms to compare two values. + * For any two values, the comparator must always return the same result. + * The <= relation defined by the comparator must be a total order. + * Values are obtained like that: + * - The value of a node in the heap, or a node that is being inserted is: + * (uint8_t *)node + offset. + * - The value being looked up is the same as given to the lookup function. + * + * @param user as in {@link BHeap_Init} + * @param val1 first value + * @param val2 second value + * @return -1 if val1 < val2, 0 if val1 = val2, 1 if val1 > val2 + */ +typedef int (*BHeap_comparator) (void *user, void *val1, void *val2); + +struct BHeapNode; + +/** + * Binary heap. + */ +typedef struct { + int offset; + BHeap_comparator comparator; + void *user; + struct BHeapNode *root; + struct BHeapNode *last; + #ifndef NDEBUG + int in_handler; + #endif +} BHeap; + +/** + * Binary heap node. + */ +typedef struct BHeapNode { + struct BHeapNode *parent; + struct BHeapNode *link[2]; +} BHeapNode; + +/** + * Initializes the heap. + * + * @param o heap to initialize + * @param offset offset of a value from its node + * @param comparator value comparator handler function + * @param user value to pass to comparator + */ +static void BHeap_Init (BHeap *h, int offset, BHeap_comparator comparator, void *user); + +/** + * Inserts a node into the heap. + * Must not be called from comparator. + * + * @param o the heap + * @param node uninitialized node to insert. Must have a valid value (its value + * may be passed to the comparator during insertion). + */ +static void BHeap_Insert (BHeap *h, BHeapNode *node); + +/** + * Removes a node from the heap. + * Must not be called from comparator. + * + * @param o the heap + * @param node node to remove + */ +static void BHeap_Remove (BHeap *h, BHeapNode *node); + +/** + * Returns one of the smallest nodes in the heap. + * Must not be called from comparator. + * + * @param o the heap + * @return one of the smallest nodes in the heap, or NULL if there + * are no nodes + */ +static BHeapNode * BHeap_GetFirst (BHeap *h); + +static void * _BHeap_node_value (BHeap *o, BHeapNode *n) +{ + return ((uint8_t *)n + o->offset); +} + +static int _BHeap_compare_values (BHeap *o, void *v1, void *v2) +{ + #ifndef NDEBUG + o->in_handler = 1; + #endif + + int res = o->comparator(o->user, v1, v2); + + #ifndef NDEBUG + o->in_handler = 0; + #endif + + ASSERT(res == -1 || res == 0 || res == 1) + + return res; +} + +static int _BHeap_compare_nodes (BHeap *o, BHeapNode *n1, BHeapNode *n2) +{ + return _BHeap_compare_values(o, _BHeap_node_value(o, n1), _BHeap_node_value(o, n2)); +} + +#ifdef BHEAP_DEBUG +#define BHEAP_ASSERT(_h) _BHeap_assert(_h); +#else +#define BHEAP_ASSERT(_h) +#endif + +#ifdef BHEAP_DEBUG + +struct _BHeap_assert_data { + int state; + int level; + BHeapNode *prev_leaf; +}; + +#define BHEAP_ASSERT_STATE_NODEPTH 1 +#define BHEAP_ASSERT_STATE_LOWEST 2 +#define BHEAP_ASSERT_STATE_LOWESTEND 3 + +static void _BHeap_assert_recurser (BHeap *h, BHeapNode *n, struct _BHeap_assert_data *ad, int level) +{ + if (!n->link[0] && !n->link[1]) { + if (ad->state == BHEAP_ASSERT_STATE_NODEPTH) { + // leftmost none, remember depth + ad->state = BHEAP_ASSERT_STATE_LOWEST; + ad->level = level; + } + } else { + // drop down + if (n->link[0]) { + ASSERT(_BHeap_compare_nodes(h, n, n->link[0]) <= 0) + ASSERT(n->link[0]->parent == n) + _BHeap_assert_recurser(h, n->link[0], ad, level + 1); + } + if (n->link[1]) { + ASSERT(_BHeap_compare_nodes(h, n, n->link[1]) <= 0) + ASSERT(n->link[1]->parent == n) + _BHeap_assert_recurser(h, n->link[1], ad, level + 1); + } + } + + ASSERT(ad->state == BHEAP_ASSERT_STATE_LOWEST || ad->state == BHEAP_ASSERT_STATE_LOWESTEND) + + if (level < ad->level - 1) { + ASSERT(n->link[0] && n->link[1]) + } + else if (level == ad->level - 1) { + switch (ad->state) { + case BHEAP_ASSERT_STATE_LOWEST: + if (!n->link[0]) { + ad->state = BHEAP_ASSERT_STATE_LOWESTEND; + ASSERT(!n->link[1]) + ASSERT(ad->prev_leaf == h->last) + } else { + if (!n->link[1]) { + ad->state = BHEAP_ASSERT_STATE_LOWESTEND; + ASSERT(ad->prev_leaf == h->last) + } + } + break; + case BHEAP_ASSERT_STATE_LOWESTEND: + ASSERT(!n->link[0] && !n->link[1]) + break; + } + } + else if (level == ad->level) { + ASSERT(ad->state == BHEAP_ASSERT_STATE_LOWEST) + ASSERT(!n->link[0] && !n->link[1]) + ad->prev_leaf = n; + } + else { + ASSERT(0) + } +} + +static void _BHeap_assert (BHeap *h) +{ + struct _BHeap_assert_data ad; + ad.state = BHEAP_ASSERT_STATE_NODEPTH; + ad.prev_leaf = NULL; + + if (h->root) { + ASSERT(h->last) + ASSERT(!h->root->parent) + _BHeap_assert_recurser(h, h->root, &ad, 0); + if (ad.state == BHEAP_ASSERT_STATE_LOWEST) { + ASSERT(ad.prev_leaf == h->last) + } + } else { + ASSERT(!h->last) + } +} + +#endif + +static void _BHeap_move_one_up (BHeap *h, BHeapNode *n) +{ + ASSERT(n->parent) + + BHeapNode *p = n->parent; + + if (p->parent) { + p->parent->link[p == p->parent->link[1]] = n; + } else { + h->root = n; + } + n->parent = p->parent; + + int nside = (n == p->link[1]); + BHeapNode *c = p->link[!nside]; + + p->link[0] = n->link[0]; + if (p->link[0]) { + p->link[0]->parent = p; + } + + p->link[1] = n->link[1]; + if (p->link[1]) { + p->link[1]->parent = p; + } + + n->link[nside] = p; + p->parent = n; + + n->link[!nside] = c; + if (c) { + c->parent = n; + } + + if (n == h->last) { + h->last = p; + } +} + +static void _BHeap_replace_node (BHeap *h, BHeapNode *d, BHeapNode *s) +{ + if (d->parent) { + d->parent->link[d == d->parent->link[1]] = s; + } else { + h->root = s; + } + s->parent = d->parent; + + s->link[0] = d->link[0]; + if (s->link[0]) { + s->link[0]->parent = s; + } + + s->link[1] = d->link[1]; + if (s->link[1]) { + s->link[1]->parent = s; + } +} + +void BHeap_Init (BHeap *h, int offset, BHeap_comparator comparator, void *user) +{ + h->offset = offset; + h->comparator = comparator; + h->user = user; + h->root = NULL; + h->last = NULL; + + #ifndef NDEBUG + h->in_handler = 0; + #endif + + BHEAP_ASSERT(h) +} + +void BHeap_Insert (BHeap *h, BHeapNode *node) +{ + ASSERT(!h->in_handler) + + if (!h->root) { + // insert to root + h->root = node; + h->last = node; + node->parent = NULL; + node->link[0] = NULL; + node->link[1] = NULL; + + BHEAP_ASSERT(h) + return; + } + + // find the node to insert to + + // start with current last node and walk up left as much as possible. + // That is, keep replacing the current node with the parent as long as it + // exists and the current node is its right child. + BHeapNode *cur = h->last; + while (cur->parent && cur == cur->parent->link[1]) { + cur = cur->parent; + } + + if (cur->parent) { + if (cur->parent->link[1]) { + // have parent and parent has right child. Attach the new node + // to the leftmost node of the parent's right subtree. + cur = cur->parent->link[1]; + while (cur->link[0]) { + cur = cur->link[0]; + } + } else { + // have parent, but parent has no right child. This can + // only happen when the last node is a right child. So + // attach the new node to its parent. + cur = cur->parent; + } + } else { + // have no parent, attach the new node to a new level. We're at the + // root, so drop down left to obtain the node where we'll attach + // the new node. + while (cur->link[0]) { + cur = cur->link[0]; + } + } + + ASSERT((!cur->link[0] && !cur->link[1]) || (cur->link[0] && !cur->link[1])) + + // attach new node + // the new node becomes the new last node + h->last = node; + cur->link[!!cur->link[0]] = node; + node->parent = cur; + node->link[0] = NULL; + node->link[1] = NULL; + + // restore heap property + while (node->parent && _BHeap_compare_nodes(h, node->parent, node) > 0) { + _BHeap_move_one_up(h, node); + } + + BHEAP_ASSERT(h) +} + +void BHeap_Remove (BHeap *h, BHeapNode *node) +{ + ASSERT(!h->in_handler) + + if (!node->parent && !node->link[0] && !node->link[1]) { + h->root = NULL; + h->last = NULL; + + BHEAP_ASSERT(h) + return; + } + + // locate the node before the last node + BHeapNode *cur = h->last; + while (cur->parent && cur == cur->parent->link[0]) { + cur = cur->parent; + } + if (cur->parent) { + ASSERT(cur->parent->link[0]) + cur = cur->parent->link[0]; + while (cur->link[1]) { + cur = cur->link[1]; + } + } else { + while (cur->link[1]) { + cur = cur->link[1]; + } + } + + // disconnect last + ASSERT(h->last->parent) + h->last->parent->link[h->last == h->last->parent->link[1]] = NULL; + + if (node == h->last) { + // deleting last; set new last + h->last = cur; + } else { + // not deleting last; move last to node's place + BHeapNode *srcnode = h->last; + _BHeap_replace_node(h, node, srcnode); + // set new last unless node=cur; in this case it stays the same + if (node != cur) { + h->last = cur; + } + + // restore heap property + if (srcnode->parent && _BHeap_compare_nodes(h, srcnode, srcnode->parent) < 0) { + do { + _BHeap_move_one_up(h, srcnode); + } while (srcnode->parent && _BHeap_compare_nodes(h, srcnode, srcnode->parent) < 0); + } else { + while (srcnode->link[0] || srcnode->link[1]) { + int side = (srcnode->link[1] && (_BHeap_compare_nodes(h, srcnode->link[0], srcnode->link[1]) >= 0)); + if (_BHeap_compare_nodes(h, srcnode, srcnode->link[side]) > 0) { + _BHeap_move_one_up(h, srcnode->link[side]); + } else { + break; + } + } + } + } + + BHEAP_ASSERT(h) +} + +BHeapNode * BHeap_GetFirst (BHeap *h) +{ + ASSERT(!h->in_handler) + + return h->root; +} + +#endif diff --git a/structure/ChunkBuffer2.h b/structure/ChunkBuffer2.h new file mode 100644 index 000000000..688580325 --- /dev/null +++ b/structure/ChunkBuffer2.h @@ -0,0 +1,279 @@ +/* + Circular packet buffer + Copyright (C) Ambroz Bizjak, 2009 + + This file is part of BadVPN. + + BadVPN is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + BadVPN is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef BADVPN_STRUCTURE_CHUNKBUFFER2_H +#define BADVPN_STRUCTURE_CHUNKBUFFER2_H + +#include +#include + +#include +#include + +#ifndef NDEBUG +#define CHUNKBUFFER2_ASSERT_BUFFER(_buf) _ChunkBuffer2_assert_buffer(_buf); +#define CHUNKBUFFER2_ASSERT_IO(_buf) _ChunkBuffer2_assert_io(_buf); +#else +#define CHUNKBUFFER2_ASSERT_BUFFER(_buf) +#define CHUNKBUFFER2_ASSERT_IO(_buf) +#endif + +struct ChunkBuffer2_block { + int len; +}; + +typedef struct { + struct ChunkBuffer2_block *buffer; + int size; + int wrap; + int start; + int used; + int mtu; + uint8_t *input_dest; + int input_avail; + uint8_t *output_dest; + int output_avail; +} ChunkBuffer2; + +// calculates a buffer size needed to hold at least 'cnum' packets long at least 'clen' +#define CHUNKBUFFER2_MAKE_NUMBLOCKS(_clen, _cnum) \ + ( \ + (1 + BDIVIDE_UP((_clen), sizeof(struct ChunkBuffer2_block))) * \ + ((_cnum) + 1) \ + ) + +// initialize +static void ChunkBuffer2_Init (ChunkBuffer2 *buf, struct ChunkBuffer2_block *buffer, int blocks, int mtu); + +// submit a packet written to the buffer +static void ChunkBuffer2_SubmitPacket (ChunkBuffer2 *buf, int len); + +// remove the first packet +static void ChunkBuffer2_ConsumePacket (ChunkBuffer2 *buf); + +static int _ChunkBuffer2_end (ChunkBuffer2 *buf) +{ + if (buf->used >= buf->wrap - buf->start) { + return (buf->used - (buf->wrap - buf->start)); + } else { + return (buf->start + buf->used); + } +} + +static void _ChunkBuffer2_assert_buffer (ChunkBuffer2 *buf) +{ + ASSERT(buf->size > 0) + ASSERT(buf->wrap > 0) + ASSERT(buf->wrap <= buf->size) + ASSERT(buf->start >= 0) + ASSERT(buf->start < buf->wrap) + ASSERT(buf->used >= 0) + ASSERT(buf->used <= buf->wrap) + ASSERT(buf->wrap == buf->size || buf->used >= buf->wrap - buf->start) + ASSERT(buf->mtu >= 0) +} + +static void _ChunkBuffer2_assert_io (ChunkBuffer2 *buf) +{ + // check input + + int end = _ChunkBuffer2_end(buf); + + if (buf->size - end - 1 < buf->mtu) { + // it will never be possible to write a MTU long packet here + ASSERT(!buf->input_dest) + ASSERT(buf->input_avail == -1) + } else { + // calculate number of free blocks + int free; + if (buf->used >= buf->wrap - buf->start) { + free = buf->start - end; + } else { + free = buf->size - end; + } + + if (free > 0) { + // got space at least for a header. More space will become available as packets are + // read from the buffer, up to MTU. + ASSERT(buf->input_dest == (uint8_t *)&buf->buffer[end + 1]) + ASSERT(buf->input_avail == (free - 1) * sizeof(struct ChunkBuffer2_block)) + } else { + // no space + ASSERT(!buf->input_dest) + ASSERT(buf->input_avail == -1) + } + } + + // check output + + if (buf->used > 0) { + int datalen = buf->buffer[buf->start].len; + ASSERT(datalen >= 0) + int blocklen = BDIVIDE_UP(datalen, sizeof(struct ChunkBuffer2_block)); + ASSERT(blocklen <= buf->used - 1) + ASSERT(blocklen <= buf->wrap - buf->start - 1) + ASSERT(buf->output_dest == (uint8_t *)&buf->buffer[buf->start + 1]) + ASSERT(buf->output_avail == datalen) + } else { + ASSERT(!buf->output_dest) + ASSERT(buf->output_avail == -1) + } +} + +static void _ChunkBuffer2_update_input (ChunkBuffer2 *buf) +{ + int end = _ChunkBuffer2_end(buf); + + if (buf->size - end - 1 < buf->mtu) { + // it will never be possible to write a MTU long packet here + buf->input_dest = NULL; + buf->input_avail = -1; + return; + } + + // calculate number of free blocks + int free; + if (buf->used >= buf->wrap - buf->start) { + free = buf->start - end; + } else { + free = buf->size - end; + } + + if (free > 0) { + // got space at least for a header. More space will become available as packets are + // read from the buffer, up to MTU. + buf->input_dest = (uint8_t *)&buf->buffer[end + 1]; + buf->input_avail = (free - 1) * sizeof(struct ChunkBuffer2_block); + } else { + // no space + buf->input_dest = NULL; + buf->input_avail = -1; + } +} + +static void _ChunkBuffer2_update_output (ChunkBuffer2 *buf) +{ + if (buf->used > 0) { + int datalen = buf->buffer[buf->start].len; + ASSERT(datalen >= 0) + int blocklen = BDIVIDE_UP(datalen, sizeof(struct ChunkBuffer2_block)); + ASSERT(blocklen <= buf->used - 1) + ASSERT(blocklen <= buf->wrap - buf->start - 1) + buf->output_dest = (uint8_t *)&buf->buffer[buf->start + 1]; + buf->output_avail = datalen; + } else { + buf->output_dest = NULL; + buf->output_avail = -1; + } +} + +void ChunkBuffer2_Init (ChunkBuffer2 *buf, struct ChunkBuffer2_block *buffer, int blocks, int mtu) +{ + ASSERT(blocks > 0) + ASSERT(mtu >= 0) + + buf->buffer = buffer; + buf->size = blocks; + buf->wrap = blocks; + buf->start = 0; + buf->used = 0; + buf->mtu = BDIVIDE_UP(mtu, sizeof(struct ChunkBuffer2_block)); + + CHUNKBUFFER2_ASSERT_BUFFER(buf) + + _ChunkBuffer2_update_input(buf); + _ChunkBuffer2_update_output(buf); + + CHUNKBUFFER2_ASSERT_IO(buf) +} + +void ChunkBuffer2_SubmitPacket (ChunkBuffer2 *buf, int len) +{ + ASSERT(buf->input_dest) + ASSERT(len >= 0) + ASSERT(len <= buf->input_avail) + + CHUNKBUFFER2_ASSERT_BUFFER(buf) + CHUNKBUFFER2_ASSERT_IO(buf) + + int end = _ChunkBuffer2_end(buf); + int blocklen = BDIVIDE_UP(len, sizeof(struct ChunkBuffer2_block)); + + ASSERT(blocklen <= buf->size - end - 1) + ASSERT(buf->used < buf->wrap - buf->start || blocklen <= buf->start - end - 1) + + buf->buffer[end].len = len; + buf->used += 1 + blocklen; + + if (buf->used <= buf->wrap - buf->start && buf->mtu > buf->size - (end + 1 + blocklen) - 1) { + buf->wrap = end + 1 + blocklen; + } + + CHUNKBUFFER2_ASSERT_BUFFER(buf) + + // update input + _ChunkBuffer2_update_input(buf); + + // update output + if (buf->used == 1 + blocklen) { + _ChunkBuffer2_update_output(buf); + } + + CHUNKBUFFER2_ASSERT_IO(buf) +} + +void ChunkBuffer2_ConsumePacket (ChunkBuffer2 *buf) +{ + ASSERT(buf->output_dest) + + CHUNKBUFFER2_ASSERT_BUFFER(buf) + CHUNKBUFFER2_ASSERT_IO(buf) + + ASSERT(1 <= buf->wrap - buf->start) + ASSERT(1 <= buf->used) + + int blocklen = BDIVIDE_UP(buf->buffer[buf->start].len, sizeof(struct ChunkBuffer2_block)); + + ASSERT(blocklen <= buf->wrap - buf->start - 1) + ASSERT(blocklen <= buf->used - 1) + + int data_wrapped = (buf->used >= buf->wrap - buf->start); + + buf->start += 1 + blocklen; + buf->used -= 1 + blocklen; + if (buf->start == buf->wrap) { + buf->start = 0; + buf->wrap = buf->size; + } + + CHUNKBUFFER2_ASSERT_BUFFER(buf) + + // update input + if (data_wrapped) { + _ChunkBuffer2_update_input(buf); + } + + // update output + _ChunkBuffer2_update_output(buf); + + CHUNKBUFFER2_ASSERT_IO(buf) +} + +#endif diff --git a/structure/HashTable.h b/structure/HashTable.h new file mode 100644 index 000000000..b623c5a55 --- /dev/null +++ b/structure/HashTable.h @@ -0,0 +1,293 @@ +/** + * @file HashTable.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Hash table using separate chaining. + */ + +#ifndef BADVPN_STRUCTURE_HASHTABLE_H +#define BADVPN_STRUCTURE_HASHTABLE_H + +#include +#include + +#include +#include + +/** + * Handler function used to compare values. + * For any two values, the comparator must always return the same result. + * Values are obtained like that: + * - The value of a node in the table, or a node that is being inserted is: + * (uint8_t *)node + offset. + * - The value being looked up is the same as given to the lookup function. + * + * @param val1 first value + * @param val2 second value + * @return 1 if values are equal, 0 if not + */ +typedef int (*HashTable_comparator) (void *val1, void *val2); + +/** + * Handler function used to compute the hash of a value, modulo some number. + * The value is obtained the same way as in {@link HashTable_comparator}. + * + * @param val value whose hash function is to be computed + * @param modulo an integer modulo which the hash must be taken before returning it + * @return hash of value modulo parameter + */ +typedef int (*HashTable_hash_function) (void *val, int modulo); + +struct HashTableNode_t; + +/** + * Hash table using separate chaining. + */ +typedef struct { + DebugObject d_obj; + int offset; + HashTable_comparator comparator; + HashTable_hash_function hash_function; + int num_buckets; + struct HashTableNode_t **buckets; + #ifndef NDEBUG + int in_handler; + #endif +} HashTable; + +/** + * Hash table node. + */ +typedef struct HashTableNode_t { + struct HashTableNode_t *next; +} HashTableNode; + +/** + * Initializes the hash table. + * + * @param t the object + * @param offset offset of a value from its node + * @param comparator value comparator handler function + * @param hash_function value hash function handler function + * @param size number of buckets in the hash table. Must be >0. + * @return 1 on success, 0 on failure + */ +static int HashTable_Init (HashTable *t, int offset, HashTable_comparator comparator, HashTable_hash_function hash_function, int size) WARN_UNUSED; + +/** + * Frees the hash table. + * Must not be called from handler functions. + * + * @param t the object + */ +static void HashTable_Free (HashTable *t); + +/** + * Inserts a node into the hash table. + * Must not be called from handler functions. + * + * @param t the object + * @param node uninitialized node to insert. Must have a valid value (its value + * may be passed to the comparator or hash function during insertion). + * @return 1 on success, 0 if an element with an equal value is already in the table + */ +static int HashTable_Insert (HashTable *t, HashTableNode *node); + +/** + * Removes a node from the table by value. + * The node must be in the hash table. + * Must not be called from handler functions. + * + * @param t the object + * @param val value of the node to be removed. + * @return 1 on success, 0 if there is no node with the given value + */ +static int HashTable_Remove (HashTable *t, void *val); + +/** + * Looks up the node of the value given. + * Must not be called from handler functions. + * + * @param t the object + * @param val value to look up + * @param node if not NULL, will be set to the node pointer on success + * @return 1 on success, 0 if there is no node with the given value + */ +static int HashTable_Lookup (HashTable *t, void *val, HashTableNode **node); + +static int _HashTable_compare_values (HashTable *t, void *v1, void *v2) +{ + #ifndef NDEBUG + t->in_handler = 1; + #endif + + int res = t->comparator(v1, v2); + + #ifndef NDEBUG + t->in_handler = 0; + #endif + + ASSERT(res == 0 || res == 1) + + return res; +} + +static int _HashTable_compute_hash (HashTable *t, void *v) +{ + #ifndef NDEBUG + t->in_handler = 1; + #endif + + int res = t->hash_function(v, t->num_buckets); + + #ifndef NDEBUG + t->in_handler = 0; + #endif + + ASSERT(res >= 0) + ASSERT(res < t->num_buckets) + + return res; +} + +int HashTable_Init (HashTable *t, int offset, HashTable_comparator comparator, HashTable_hash_function hash_function, int size) +{ + ASSERT(size > 0) + + // init arguments + t->offset = offset; + t->comparator = comparator; + t->hash_function = hash_function; + t->num_buckets = size; + + // allocate buckets + t->buckets = (HashTableNode **)malloc(t->num_buckets * sizeof(HashTableNode *)); + if (!t->buckets) { + return 0; + } + + // zero buckets + int i; + for (i = 0; i < t->num_buckets; i++) { + t->buckets[i] = NULL; + } + + // init debugging + #ifndef NDEBUG + t->in_handler = 0; + #endif + + // init debug object + DebugObject_Init(&t->d_obj); + + return 1; +} + +void HashTable_Free (HashTable *t) +{ + // free debug object + DebugObject_Free(&t->d_obj); + + ASSERT(!t->in_handler) + + // free buckets + free(t->buckets); +} + +int HashTable_Insert (HashTable *t, HashTableNode *node) +{ + ASSERT(!t->in_handler) + + // obtain value + void *val = (uint8_t *)node + t->offset; + + // obtain bucket index + int index = _HashTable_compute_hash(t, val); + + // look for existing entries with an equal value + HashTableNode *cur = t->buckets[index]; + while (cur) { + void *cur_val = (uint8_t *)cur + t->offset; + if (_HashTable_compare_values(t, cur_val, val)) { + return 0; + } + cur = cur->next; + } + + // prepend to linked list + node->next = t->buckets[index]; + t->buckets[index] = node; + + return 1; +} + +int HashTable_Remove (HashTable *t, void *val) +{ + ASSERT(!t->in_handler) + + // obtain bucket index + int index = _HashTable_compute_hash(t, val); + + // find node with an equal value + HashTableNode *prev = NULL; + HashTableNode *cur = t->buckets[index]; + while (cur) { + void *cur_val = (uint8_t *)cur + t->offset; + if (_HashTable_compare_values(t, cur_val, val)) { + // remove node from lined list + if (prev) { + prev->next = cur->next; + } else { + t->buckets[index] = cur->next; + } + return 1; + } + prev = cur; + cur = cur->next; + } + + return 0; +} + +int HashTable_Lookup (HashTable *t, void *val, HashTableNode **node) +{ + ASSERT(!t->in_handler) + + int index = _HashTable_compute_hash(t, val); + + // find node with an equal value + HashTableNode *cur = t->buckets[index]; + while (cur) { + void *cur_val = (uint8_t *)cur + t->offset; + if (_HashTable_compare_values(t, cur_val, val)) { + if (node) { + *node = cur; + } + return 1; + } + cur = cur->next; + } + + return 0; +} + +#endif diff --git a/structure/LinkedList2.h b/structure/LinkedList2.h new file mode 100644 index 000000000..ab1ec734f --- /dev/null +++ b/structure/LinkedList2.h @@ -0,0 +1,378 @@ +/** + * @file LinkedList2.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Doubly linked list that support multiple iterations and removing + * aritrary elements during iteration. + */ + +#ifndef BADVPN_STRUCTURE_LINKEDLIST2_H +#define BADVPN_STRUCTURE_LINKEDLIST2_H + +#include + +#include + +struct LinkedList2Iterator_t; + +/** + * Linked list node. + */ +typedef struct LinkedList2Node_t +{ + struct LinkedList2Node_t *p; + struct LinkedList2Node_t *n; + struct LinkedList2Iterator_t *it; +} LinkedList2Node; + +/** + * Doubly linked list that support multiple iterations and removing + * aritrary elements during iteration. + */ +typedef struct +{ + LinkedList2Node *first; + LinkedList2Node *last; +} LinkedList2; + +/** + * Linked list iterator. + */ +typedef struct LinkedList2Iterator_t +{ + LinkedList2 *list; + int dir; + struct LinkedList2Node_t *e; + struct LinkedList2Iterator_t *pi; + struct LinkedList2Iterator_t *ni; +} LinkedList2Iterator; + +/** + * Initializes the linked list. + * + * @param list list to initialize + */ +static void LinkedList2_Init (LinkedList2 *list); + +/** + * Determines if the list is empty. + * + * @param list the list + * @return 1 if empty, 0 if not + */ +static int LinkedList2_IsEmpty (LinkedList2 *list); + +/** + * Returns the first node of the list. + * + * @param list the list + * @return first node of the list, or NULL if the list is empty + */ +static LinkedList2Node * LinkedList2_GetFirst (LinkedList2 *list); + +/** + * Returns the last node of the list. + * + * @param list the list + * @return last node of the list, or NULL if the list is empty + */ +static LinkedList2Node * LinkedList2_GetLast (LinkedList2 *list); + +/** + * Inserts a node to the beginning of the list. + * + * @param list the list + * @param node uninitialized node to insert + */ +static void LinkedList2_Prepend (LinkedList2 *list, LinkedList2Node *node); + +/** + * Inserts a node to the end of the list. + * + * @param list the list + * @param node uninitialized node to insert + */ +static void LinkedList2_Append (LinkedList2 *list, LinkedList2Node *node); + +/** + * Inserts a node before a given node. + * + * @param list the list + * @param node uninitialized node to insert + * @param target node in the list to insert before + */ +static void LinkedList2_InsertBefore (LinkedList2 *list, LinkedList2Node *node, LinkedList2Node *target); + +/** + * Inserts a node after a given node. + * + * @param list the list + * @param node uninitialized node to insert + * @param target node in the list to insert after + */ +static void LinkedList2_InsertAfter (LinkedList2 *list, LinkedList2Node *node, LinkedList2Node *target); + +/** + * Removes a node from the list. + * + * @param list the list + * @param node node to remove + */ +static void LinkedList2_Remove (LinkedList2 *list, LinkedList2Node *node); + +/** + * Initializes a linked list iterator. + * The iterator memory must remain available until either of these occurs: + * - the list is no longer needed, or + * - the iterator is freed with {@link LinkedList2Iterator_Free}, or + * - the iterator reaches the end of iteration. + * + * @param it uninitialized iterator to initialize + * @param list the list + * @param dir direction of iteration. Must be 1 (forward) or -1 (backward). + * @param node current position of the iterator. Can be a node in the list + * or NULL for end of iteration. + */ +static void LinkedList2Iterator_Init (LinkedList2Iterator *it, LinkedList2 *list, int dir, LinkedList2Node *node); + +/** + * Frees a linked list iterator. + * + * @param it iterator to free + */ +static void LinkedList2Iterator_Free (LinkedList2Iterator *it); + +/** + * Initializes a linked list iterator with forward direction, with the current + * position at the first node (or at the end of iteration if there are no nodes). + * The iterator memory must remain available until either of these occurs: + * - the list is no longer needed, or + * - the iterator is freed with {@link LinkedList2Iterator_Free}, or + * - the iterator reaches the end of iteration. + * + * @param it uninitialized iterator to initialize + * @param list the list + */ +static void LinkedList2Iterator_InitForward (LinkedList2Iterator *it, LinkedList2 *list); + +/** + * Initializes a linked list iterator with backward direction, with the current + * position at the last node (or at the end of iteration if there are no nodes). + * The iterator memory must remain available until either of these occurs: + * - the list is no longer needed, or + * - the iterator is freed with {@link LinkedList2Iterator_Free}, or + * - the iterator reaches the end of iteration. + * + * @param it uninitialized iterator to initialize + * @param list the list + */ +static void LinkedList2Iterator_InitBackward (LinkedList2Iterator *it, LinkedList2 *list); + +/** + * Moves the iterator one node forward or backward (depending on its direction), or, + * if it's at the last or first node (depending on the direction), it reaches + * the end of iteration, or, if it's at the end of iteration, it remains there. + * Returns the the previous position. + * + * @param it the iterator + * @return node on the position of iterator before it was (possibly) moved, or NULL + * if it was at the end of iteration + */ +static LinkedList2Node * LinkedList2Iterator_Next (LinkedList2Iterator *it); + +void LinkedList2_Init (LinkedList2 *list) +{ + list->first = NULL; + list->last = NULL; +} + +int LinkedList2_IsEmpty (LinkedList2 *list) +{ + return (!list->first); +} + +LinkedList2Node * LinkedList2_GetFirst (LinkedList2 *list) +{ + return (list->first); +} + +LinkedList2Node * LinkedList2_GetLast (LinkedList2 *list) +{ + return (list->last); +} + +void LinkedList2_Prepend (LinkedList2 *list, LinkedList2Node *node) +{ + node->p = NULL; + node->n = list->first; + if (list->first) { + list->first->p = node; + } else { + list->last = node; + } + list->first = node; + + node->it = NULL; +} + +void LinkedList2_Append (LinkedList2 *list, LinkedList2Node *node) +{ + node->p = list->last; + node->n = NULL; + if (list->last) { + list->last->n = node; + } else { + list->first = node; + } + list->last = node; + + node->it = NULL; +} + +void LinkedList2_InsertBefore (LinkedList2 *list, LinkedList2Node *node, LinkedList2Node *target) +{ + node->p = target->p; + node->n = target; + if (target->p) { + target->p->n = node; + } else { + list->first = node; + } + target->p = node; + + node->it = NULL; +} + +void LinkedList2_InsertAfter (LinkedList2 *list, LinkedList2Node *node, LinkedList2Node *target) +{ + node->p = target; + node->n = target->n; + if (target->n) { + target->n->p = node; + } else { + list->last = node; + } + target->n = node; + + node->it = NULL; +} + +void LinkedList2_Remove (LinkedList2 *list, LinkedList2Node *node) +{ + LinkedList2Iterator *it; + LinkedList2Iterator *next; + + // jump iterators + while (node->it) { + LinkedList2Iterator_Next(node->it); + } + + // remove from list + if (node->p) { + node->p->n = node->n; + } else { + list->first = node->n; + } + if (node->n) { + node->n->p = node->p; + } else { + list->last = node->p; + } +} + +void LinkedList2Iterator_Init (LinkedList2Iterator *it, LinkedList2 *list, int dir, LinkedList2Node *node) +{ + ASSERT(dir == 1 || dir == -1) + + it->list = list; + it->dir = dir; + it->e = node; + + if (!it->e) { + it->pi = NULL; + it->ni = NULL; + } else { + // link into node's iterator list + it->pi = NULL; + it->ni = it->e->it; + if (it->e->it) { + it->e->it->pi = it; + } + it->e->it = it; + } +} + +void LinkedList2Iterator_Free (LinkedList2Iterator *it) +{ + if (it->e) { + // remove from node's iterator list + if (it->ni) { + it->ni->pi = it->pi; + } + if (it->pi) { + it->pi->ni = it->ni; + } else { + it->e->it = it->ni; + } + } +} + +void LinkedList2Iterator_InitForward (LinkedList2Iterator *it, LinkedList2 *list) +{ + LinkedList2Iterator_Init(it, list, 1, list->first); +} + +void LinkedList2Iterator_InitBackward (LinkedList2Iterator *it, LinkedList2 *list) +{ + LinkedList2Iterator_Init(it, list, -1, list->last); +} + +LinkedList2Node * LinkedList2Iterator_Next (LinkedList2Iterator *it) +{ + // remember original entry + LinkedList2Node *orig = it->e; + + // jump to next entry + if (it->e) { + // get next entry + LinkedList2Node *next; + switch (it->dir) { + case 1: + next = it->e->n; + break; + case -1: + next = it->e->p; + break; + default: + ASSERT(0); + } + // destroy interator + LinkedList2Iterator_Free(it); + // re-initialize at next entry + LinkedList2Iterator_Init(it, it->list, it->dir, next); + } + + // return original entry + return orig; +} + +#endif diff --git a/system/BAddr.h b/system/BAddr.h new file mode 100644 index 000000000..8b1405ed4 --- /dev/null +++ b/system/BAddr.h @@ -0,0 +1,604 @@ +/** + * @file BAddr.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Network address abstractions used by {@link BSocket}. + */ + +#ifndef BADVPN_SYSTEM_BADDR_H +#define BADVPN_SYSTEM_BADDR_H + +#include +#include +#include +#include +#include +#include + +#ifdef BADVPN_USE_WINAPI +#include +#else +#include +#include +#include +#endif + +#include +#include + +#define BADDR_TYPE_NONE 0 +#define BADDR_TYPE_IPV4 1 +#define BADDR_TYPE_IPV6 2 + +#define BADDR_MAX_ADDR_LEN 128 + +#define BIPADDR_MAX_PRINT_LEN 40 +#define BADDR_MAX_PRINT_LEN 46 + +typedef struct { + int type; + union { + uint32_t ipv4; + uint8_t ipv6[16]; + }; +} BIPAddr; + +static void BIPAddr_InitInvalid (BIPAddr *addr); + +static void BIPAddr_InitIPv4 (BIPAddr *addr, uint32_t ip); + +static void BIPAddr_InitIPv6 (BIPAddr *addr, uint8_t *ip); + +static int BIPAddr_IsRecognized (BIPAddr *addr); + +static int BIPAddr_IsInvalid (BIPAddr *addr); + +static int BIPAddr_Resolve (BIPAddr *addr, char *str, int noresolve) WARN_UNUSED; + +static int BIPAddr_Compare (BIPAddr *addr1, BIPAddr *addr2); + +/** + * Converts an IP address to human readable form. + * + * @param addr IP address to convert + * @param out destination buffer. Must be at least BIPADDR_MAX_PRINT_LEN characters long. + */ +static void BIPAddr_Print (BIPAddr *addr, char *out); + +/** + * Socket address - IP address and transport protocol port number + */ +typedef struct { + int type; + union { + struct { + uint32_t ip; + uint16_t port; + } ipv4; + struct { + uint8_t ip[16]; + uint16_t port; + } ipv6; + }; +} BAddr; + +/** + * Initializes an invalid address. + */ +static void BAddr_InitNone (BAddr *addr); + +/** + * Initializes an IPv4 address. + * + * @param addr the object + * @param ip IP address in network byte order + * @param port port number in network byte order + */ +static void BAddr_InitIPv4 (BAddr *addr, uint32_t ip, uint16_t port); + +/** + * Initializes an IPv6 address. + * + * @param addr the object + * @param ip 16-byte IP address in network byte order + * @param port port number in network byte order + */ +static void BAddr_InitIPv6 (BAddr *addr, uint8_t *ip, uint16_t port); + +/** + * Determines whether the address is recognized. + * + * @param addr the object + * @return 1 if recognized, 0 if not + */ +static int BAddr_IsRecognized (BAddr *addr); + +/** + * Determines whether the address is an invalid address. + * + * @param addr the object. Must be recognized according to {@link BAddr_IsRecognized}. + * @return 1 if invalid, 0 if invalid + **/ +static int BAddr_IsInvalid (BAddr *addr); + +/** + * Returns the port number in the address. + * + * @param addr the object. Must be recognized according to {@link BAddr_IsRecognized}, + * Must be an IPv4 or IPv6 address. + * @return port number, in network byte order + */ +static uint16_t BAddr_GetPort (BAddr *addr); + +/** + * Returns the IP address in the address. + * + * @param addr the object. Must be recognized according to {@link BAddr_IsRecognized}, + * Must be an IPv4 or IPv6 address. + * @param ipaddr IP address will be returned here + */ +static void BAddr_GetIPAddr (BAddr *addr, BIPAddr *ipaddr); + +/** + * Sets the port number in the address. + * + * @param addr the object. Must be recognized according to {@link BAddr_IsRecognized}, + * Must be an IPv4 or IPv6 address. + * @param port port number, in network byte order + */ +static void BAddr_SetPort (BAddr *addr, uint16_t port); + +/** + * Converts an IP address to human readable form. + * + * @param addr address to convert + * @param out destination buffer. Must be at least BADDR_MAX_PRINT_LEN characters long. + */ +static void BAddr_Print (BAddr *addr, char *out); + +/** + * Resolves an address string. + * Format is "addr:port" for IPv4, "[addr]:port" for IPv6. + * addr is be a numeric address or a name. + * port is a numeric port number. + * + * @param addr output address + * @param name if not NULL, the name portion of the address will be + * stored here + * @param name_len if name is not NULL, the size of the name buffer + * @param noresolve only accept numeric addresses. Avoids blocking the caller. + * @return 1 on success, 0 on parse error + */ +static int BAddr_Parse2 (BAddr *addr, char *str, char *name, int name_len, int noresolve) WARN_UNUSED; + +/** + * Resolves an address string. + * IPv4 input format is "a.b.c.d:p", where a.b.c.d is the IP address + * and d is the port number. + * IPv6 input format is "[addr]:p", where addr is an IPv6 addres in + * standard notation and p is the port number. + * + * @param addr output address + * @param name if not NULL, the name portion of the address will be + * stored here + * @param name_len if name is not NULL, the size of the name buffer + * @return 1 on success, 0 on parse error + */ +static int BAddr_Parse (BAddr *addr, char *str, char *name, int name_len) WARN_UNUSED; + +void BIPAddr_InitInvalid (BIPAddr *addr) +{ + addr->type = BADDR_TYPE_NONE; +} + +void BIPAddr_InitIPv4 (BIPAddr *addr, uint32_t ip) +{ + addr->type = BADDR_TYPE_IPV4; + addr->ipv4 = ip; +} + +void BIPAddr_InitIPv6 (BIPAddr *addr, uint8_t *ip) +{ + addr->type = BADDR_TYPE_IPV6; + memcpy(addr->ipv6, ip, 16); +} + +int BIPAddr_IsRecognized (BIPAddr *addr) +{ + switch (addr->type) { + case BADDR_TYPE_NONE: + case BADDR_TYPE_IPV4: + case BADDR_TYPE_IPV6: + return 1; + default: + return 0; + } +} + +int BIPAddr_IsInvalid (BIPAddr *addr) +{ + ASSERT(BIPAddr_IsRecognized(addr)) + + return (addr->type == BADDR_TYPE_NONE); +} + +int BIPAddr_Resolve (BIPAddr *addr, char *str, int noresolve) +{ + int len = strlen(str); + + char *addr_start; + int addr_len; + + // determine address type + if (len >= 1 && str[0] == '[' && str[len - 1] == ']') { + addr->type = BADDR_TYPE_IPV6; + addr_start = str + 1; + addr_len = len - 2; + } else { + addr->type = BADDR_TYPE_IPV4; + addr_start = str; + addr_len = len; + } + + // copy + char addr_str[BADDR_MAX_ADDR_LEN + 1]; + if (addr_len > BADDR_MAX_ADDR_LEN) { + return 0; + } + memcpy(addr_str, addr_start, addr_len); + addr_str[addr_len] = '\0'; + + // initialize hints + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + switch (addr->type) { + case BADDR_TYPE_IPV6: + hints.ai_family = AF_INET6; + break; + case BADDR_TYPE_IPV4: + hints.ai_family = AF_INET; + break; + } + if (noresolve) { + hints.ai_flags |= AI_NUMERICHOST; + } + + // call getaddrinfo + struct addrinfo *addrs; + int res; + if ((res = getaddrinfo(addr_str, NULL, &hints, &addrs)) != 0) { + return 0; + } + + // set address + switch (addr->type) { + case BADDR_TYPE_IPV6: + memcpy(addr->ipv6, ((struct sockaddr_in6 *)addrs->ai_addr)->sin6_addr.s6_addr, sizeof(addr->ipv6)); + break; + case BADDR_TYPE_IPV4: + addr->ipv4 = ((struct sockaddr_in *)addrs->ai_addr)->sin_addr.s_addr; + break; + } + + freeaddrinfo(addrs); + + return 1; +} + +int BIPAddr_Compare (BIPAddr *addr1, BIPAddr *addr2) +{ + ASSERT(BIPAddr_IsRecognized(addr1)) + ASSERT(BIPAddr_IsRecognized(addr2)) + + if (addr1->type != addr2->type) { + return 0; + } + + switch (addr1->type) { + case BADDR_TYPE_NONE: + return 0; + case BADDR_TYPE_IPV4: + return (addr1->ipv4 == addr2->ipv4); + case BADDR_TYPE_IPV6: + return (!memcmp(addr1->ipv6, addr2->ipv6, sizeof(addr1->ipv6))); + default: + ASSERT(0) + return 0; + } +} + +uint16_t BAddr_GetPort (BAddr *addr) +{ + ASSERT(BAddr_IsRecognized(addr)) + ASSERT(addr->type == BADDR_TYPE_IPV4 || addr->type == BADDR_TYPE_IPV6) + + switch (addr->type) { + case BADDR_TYPE_IPV4: + return addr->ipv4.port; + case BADDR_TYPE_IPV6: + return addr->ipv6.port; + default: + ASSERT(0) + return 0; + } +} + +void BAddr_GetIPAddr (BAddr *addr, BIPAddr *ipaddr) +{ + ASSERT(BAddr_IsRecognized(addr)) + ASSERT(addr->type == BADDR_TYPE_IPV4 || addr->type == BADDR_TYPE_IPV6) + + switch (addr->type) { + case BADDR_TYPE_IPV4: + BIPAddr_InitIPv4(ipaddr, addr->ipv4.ip); + return; + case BADDR_TYPE_IPV6: + BIPAddr_InitIPv6(ipaddr, addr->ipv6.ip); + return; + default: + ASSERT(0); + } +} + +void BAddr_SetPort (BAddr *addr, uint16_t port) +{ + ASSERT(BAddr_IsRecognized(addr)) + ASSERT(addr->type == BADDR_TYPE_IPV4 || addr->type == BADDR_TYPE_IPV6) + + switch (addr->type) { + case BADDR_TYPE_IPV4: + addr->ipv4.port = port; + break; + case BADDR_TYPE_IPV6: + addr->ipv6.port = port; + break; + default: + ASSERT(0); + } +} + +struct _BAddr_ipv6_addr +{ + uint16_t addr[8]; +} __attribute__((packed)); + +void BIPAddr_Print (BIPAddr *addr, char *out) +{ + switch (addr->type) { + case BADDR_TYPE_NONE: + sprintf(out, "(none)"); + break; + case BADDR_TYPE_IPV4: + sprintf(out, "%"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8, + *((uint8_t *)&addr->ipv4 + 0), + *((uint8_t *)&addr->ipv4 + 1), + *((uint8_t *)&addr->ipv4 + 2), + *((uint8_t *)&addr->ipv4 + 3) + ); + break; + case BADDR_TYPE_IPV6: { + struct _BAddr_ipv6_addr *s = (struct _BAddr_ipv6_addr *)addr->ipv6; + sprintf(out, + "%"PRIx16":%"PRIx16":%"PRIx16":%"PRIx16":" + "%"PRIx16":%"PRIx16":%"PRIx16":%"PRIx16, + ntoh16(s->addr[0]), + ntoh16(s->addr[1]), + ntoh16(s->addr[2]), + ntoh16(s->addr[3]), + ntoh16(s->addr[4]), + ntoh16(s->addr[5]), + ntoh16(s->addr[6]), + ntoh16(s->addr[7]) + ); + } break; + default: + ASSERT(0); + } +} + +void BAddr_InitNone (BAddr *addr) +{ + addr->type = BADDR_TYPE_NONE; +} + +void BAddr_InitIPv4 (BAddr *addr, uint32_t ip, uint16_t port) +{ + addr->type = BADDR_TYPE_IPV4; + addr->ipv4.ip = ip; + addr->ipv4.port = port; +} + +void BAddr_InitIPv6 (BAddr *addr, uint8_t *ip, uint16_t port) +{ + addr->type = BADDR_TYPE_IPV6; + memcpy(addr->ipv6.ip, ip, 16); + addr->ipv6.port = port; +} + +int BAddr_IsRecognized (BAddr *addr) +{ + switch (addr->type) { + case BADDR_TYPE_NONE: + case BADDR_TYPE_IPV4: + case BADDR_TYPE_IPV6: + return 1; + default: + return 0; + } +} + +int BAddr_IsInvalid (BAddr *addr) +{ + ASSERT(BAddr_IsRecognized(addr)) + + return (addr->type == BADDR_TYPE_NONE); +} + +void BAddr_Print (BAddr *addr, char *out) +{ + ASSERT(BAddr_IsRecognized(addr)) + + BIPAddr ipaddr; + + switch (addr->type) { + case BADDR_TYPE_NONE: + sprintf(out, "(none)"); + break; + case BADDR_TYPE_IPV4: + BIPAddr_InitIPv4(&ipaddr, addr->ipv4.ip); + BIPAddr_Print(&ipaddr, out); + sprintf(out + strlen(out), ":%"PRIu16, ntoh16(addr->ipv4.port)); + break; + case BADDR_TYPE_IPV6: + BIPAddr_InitIPv6(&ipaddr, addr->ipv6.ip); + BIPAddr_Print(&ipaddr, out); + sprintf(out + strlen(out), ":%"PRIu16, ntoh16(addr->ipv6.port)); + break; + default: + ASSERT(0); + } +} + +int BAddr_Parse2 (BAddr *addr, char *str, char *name, int name_len, int noresolve) +{ + BAddr result; + + int len = strlen(str); + if (len < 1 || len > 1000) { + return 0; + } + + int addr_start; + int addr_len; + int port_start; + int port_len; + + // leading '[' indicates an IPv6 address + if (str[0] == '[') { + addr->type = BADDR_TYPE_IPV6; + // find ']' + int i=1; + while (i < len && str[i] != ']') i++; + if (i >= len) { + return 0; + } + addr_start = 1; + addr_len = i - addr_start; + // follows ':' and port number + if (i + 1 >= len || str[i + 1] != ':') { + return 0; + } + port_start = i + 2; + port_len = len - port_start; + } + // otherwise it's an IPv4 address + else { + addr->type = BADDR_TYPE_IPV4; + // find ':' + int i=0; + while (i < len && str[i] != ':') i++; + if (i >= len) { + return 0; + } + addr_start = 0; + addr_len = i - addr_start; + port_start = i + 1; + port_len = len - port_start; + } + + // copy address and port to zero-terminated buffers + + char addr_str[128]; + if (addr_len >= sizeof(addr_str)) { + return 0; + } + memcpy(addr_str, str + addr_start, addr_len); + addr_str[addr_len] = '\0'; + + char port_str[6]; + if (port_len >= sizeof(port_str)) { + return 0; + } + memcpy(port_str, str + port_start, port_len); + port_str[port_len] = '\0'; + + // parse port + char *err; + long int conv_res = strtol(port_str, &err, 10); + if (port_str[0] == '\0' || *err != '\0') { + return 0; + } + if (conv_res < 0 || conv_res > UINT16_MAX) { + return 0; + } + uint16_t port = conv_res; + port = hton16(port); + + // initialize hints + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + switch (addr->type) { + case BADDR_TYPE_IPV6: + hints.ai_family = AF_INET6; + break; + case BADDR_TYPE_IPV4: + hints.ai_family = AF_INET; + break; + } + if (noresolve) { + hints.ai_flags |= AI_NUMERICHOST; + } + + // call getaddrinfo + struct addrinfo *addrs; + int res; + if ((res = getaddrinfo(addr_str, NULL, &hints, &addrs)) != 0) { + return 0; + } + + // set address + switch (addr->type) { + case BADDR_TYPE_IPV6: + memcpy(addr->ipv6.ip, ((struct sockaddr_in6 *)addrs->ai_addr)->sin6_addr.s6_addr, sizeof(addr->ipv6.ip)); + addr->ipv6.port = port; + break; + case BADDR_TYPE_IPV4: + addr->ipv4.ip = ((struct sockaddr_in *)addrs->ai_addr)->sin_addr.s_addr; + addr->ipv4.port = port; + break; + } + + freeaddrinfo(addrs); + + if (name) { + snprintf(name, name_len, "%s", addr_str); + } + + return 1; +} + +int BAddr_Parse (BAddr *addr, char *str, char *name, int name_len) +{ + return BAddr_Parse2(addr, str, name, name_len, 0); +} + +#endif diff --git a/system/BLog.c b/system/BLog.c new file mode 100644 index 000000000..b9d4e7c6f --- /dev/null +++ b/system/BLog.c @@ -0,0 +1,57 @@ +/** + * @file BLog.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include + +struct _BLog_channel blog_channel_list[] = { +#include +}; + +struct _BLog_global blog_global = { + #ifndef NDEBUG + .initialized = 0, + #endif +}; + +static char *level_names[] = { + [BLOG_ERROR] = "ERROR", + [BLOG_WARNING] = "WARNING", + [BLOG_NOTICE] = "NOTICE", + [BLOG_INFO] = "INFO", + [BLOG_DEBUG] = "DEBUG", +}; + +static void stdout_log (int channel, int level, const char *msg) +{ + printf("%s(%s): %s\n", level_names[level], blog_global.channels[channel].name, msg); +} + +static void stdout_free (void) +{ +} + +void BLog_InitStdout (void) +{ + BLog_Init(stdout_log, stdout_free); +} diff --git a/system/BLog.h b/system/BLog.h new file mode 100644 index 000000000..94ba1ee4c --- /dev/null +++ b/system/BLog.h @@ -0,0 +1,205 @@ +/** + * @file BLog.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * A global object for logging. + */ + +#ifndef BADVPN_SYSTEM_BLOG_H +#define BADVPN_SYSTEM_BLOG_H + +#include +#include + +#include + +// auto-generated channel numbers and number of channels +#include + +#define BLOG_ERROR 1 +#define BLOG_WARNING 2 +#define BLOG_NOTICE 3 +#define BLOG_INFO 4 +#define BLOG_DEBUG 5 + +#define BLog(...) BLog_LogToChannel(BLOG_CURRENT_CHANNEL, __VA_ARGS__) + +typedef void (*_BLog_log_func) (int channel, int level, const char *msg); +typedef void (*_BLog_free_func) (void); + +struct _BLog_channel { + const char *name; + int loglevel; +}; + +struct _BLog_global { + #ifndef NDEBUG + int initialized; // initialized statically + #endif + struct _BLog_channel channels[BLOG_NUM_CHANNELS]; + _BLog_log_func log_func; + _BLog_free_func free_func; + char logbuf[2048]; + int logbuf_pos; +}; + +extern struct _BLog_channel blog_channel_list[]; +extern struct _BLog_global blog_global; + +static int BLogGlobal_GetChannelByName (const char *channel_name); + +static void BLog_Init (_BLog_log_func log_func, _BLog_free_func free_func); +static void BLog_Free (void); +static void BLog_SetChannelLoglevel (int channel, int loglevel); +static void BLog_AppendVarArg (const char *fmt, va_list vl); +static void BLog_Append (const char *fmt, ...); +static void BLog_Finish (int channel, int level); +static void BLog_LogToChannelStr (int channel, int level, const char *msg); +static void BLog_LogToChannelVarArg (int channel, int level, const char *fmt, va_list vl); +static void BLog_LogToChannel (int channel, int level, const char *fmt, ...); + +void BLog_InitStdout (void); + +int BLogGlobal_GetChannelByName (const char *channel_name) +{ + int i; + for (i = 0; i < BLOG_NUM_CHANNELS; i++) { + if (!strcmp(blog_channel_list[i].name, channel_name)) { + return i; + } + } + + return -1; +} + +void BLog_Init (_BLog_log_func log_func, _BLog_free_func free_func) +{ + ASSERT(!blog_global.initialized) + + #ifndef NDEBUG + blog_global.initialized = 1; + #endif + + // initialize channels + memcpy(blog_global.channels, blog_channel_list, BLOG_NUM_CHANNELS * sizeof(struct _BLog_channel)); + + blog_global.log_func = log_func; + blog_global.free_func = free_func; + blog_global.logbuf_pos = 0; + blog_global.logbuf[0] = '\0'; +} + +void BLog_Free (void) +{ + ASSERT(blog_global.initialized) + + #ifndef NDEBUG + blog_global.initialized = 0; + #endif + + blog_global.free_func(); +} + +void BLog_SetChannelLoglevel (int channel, int loglevel) +{ + ASSERT(blog_global.initialized) + ASSERT(channel >= 0 && channel < BLOG_NUM_CHANNELS) + ASSERT(loglevel >= 0 && loglevel <= BLOG_DEBUG) + + blog_global.channels[channel].loglevel = loglevel; +} + +void BLog_AppendVarArg (const char *fmt, va_list vl) +{ + ASSERT(blog_global.initialized) + + ASSERT(blog_global.logbuf_pos >= 0 && blog_global.logbuf_pos < sizeof(blog_global.logbuf)) + + int w = vsnprintf(blog_global.logbuf + blog_global.logbuf_pos, sizeof(blog_global.logbuf) - blog_global.logbuf_pos, fmt, vl); + + if (w >= sizeof(blog_global.logbuf) - blog_global.logbuf_pos) { + blog_global.logbuf_pos = sizeof(blog_global.logbuf) - 1; + } else { + blog_global.logbuf_pos += w; + } +} + +void BLog_Append (const char *fmt, ...) +{ + ASSERT(blog_global.initialized) + + va_list vl; + va_start(vl, fmt); + BLog_AppendVarArg(fmt, vl); + va_end(vl); +} + +void BLog_Finish (int channel, int level) +{ + ASSERT(blog_global.initialized) + ASSERT(channel >= 0 && channel < BLOG_NUM_CHANNELS) + ASSERT(level >= BLOG_ERROR && level <= BLOG_DEBUG) + + ASSERT(blog_global.logbuf_pos >= 0 && blog_global.logbuf_pos < sizeof(blog_global.logbuf)) + ASSERT(blog_global.logbuf[blog_global.logbuf_pos] == '\0') + + if (level <= blog_global.channels[channel].loglevel) { + blog_global.log_func(channel, level, blog_global.logbuf); + } + + blog_global.logbuf_pos = 0; + blog_global.logbuf[0] = '\0'; +} + +void BLog_LogToChannelStr (int channel, int level, const char *msg) +{ + ASSERT(blog_global.initialized) + ASSERT(channel >= 0 && channel < BLOG_NUM_CHANNELS) + ASSERT(level >= BLOG_ERROR && level <= BLOG_DEBUG) + + BLog_Append("%s", msg); + BLog_Finish(channel, level); +} + +void BLog_LogToChannelVarArg (int channel, int level, const char *fmt, va_list vl) +{ + ASSERT(blog_global.initialized) + ASSERT(channel >= 0 && channel < BLOG_NUM_CHANNELS) + ASSERT(level >= BLOG_ERROR && level <= BLOG_DEBUG) + + BLog_AppendVarArg(fmt, vl); + BLog_Finish(channel, level); +} + +void BLog_LogToChannel (int channel, int level, const char *fmt, ...) +{ + ASSERT(blog_global.initialized) + ASSERT(channel >= 0 && channel < BLOG_NUM_CHANNELS) + ASSERT(level >= BLOG_ERROR && level <= BLOG_DEBUG) + + va_list vl; + va_start(vl, fmt); + BLog_LogToChannelVarArg(channel, level, fmt, vl); + va_end(vl); +} + +#endif diff --git a/system/BLog_syslog.c b/system/BLog_syslog.c new file mode 100644 index 000000000..68b07610e --- /dev/null +++ b/system/BLog_syslog.c @@ -0,0 +1,143 @@ +/** + * @file BLog_syslog.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include + +#include + +#include + +static int resolve_facility (char *str, int *out) +{ + if (!strcmp(str, "authpriv")) { + *out = LOG_AUTHPRIV; + } + else if (!strcmp(str, "cron")) { + *out = LOG_CRON; + } + else if (!strcmp(str, "daemon")) { + *out = LOG_DAEMON; + } + else if (!strcmp(str, "ftp")) { + *out = LOG_FTP; + } + else if (!strcmp(str, "local0")) { + *out = LOG_LOCAL0; + } + else if (!strcmp(str, "local1")) { + *out = LOG_LOCAL1; + } + else if (!strcmp(str, "local2")) { + *out = LOG_LOCAL2; + } + else if (!strcmp(str, "local3")) { + *out = LOG_LOCAL3; + } + else if (!strcmp(str, "local4")) { + *out = LOG_LOCAL4; + } + else if (!strcmp(str, "local5")) { + *out = LOG_LOCAL5; + } + else if (!strcmp(str, "local6")) { + *out = LOG_LOCAL6; + } + else if (!strcmp(str, "local7")) { + *out = LOG_LOCAL7; + } + else if (!strcmp(str, "lpr")) { + *out = LOG_LPR; + } + else if (!strcmp(str, "mail")) { + *out = LOG_MAIL; + } + else if (!strcmp(str, "news")) { + *out = LOG_NEWS; + } + else if (!strcmp(str, "syslog")) { + *out = LOG_SYSLOG; + } + else if (!strcmp(str, "user")) { + *out = LOG_USER; + } + else if (!strcmp(str, "uucp")) { + *out = LOG_UUCP; + } + else { + return 0; + } + + return 1; +} + +static int convert_level (int level) +{ + ASSERT(level >= BLOG_ERROR && level <= BLOG_DEBUG) + + switch (level) { + case BLOG_ERROR: + return LOG_ERR; + case BLOG_WARNING: + return LOG_WARNING; + case BLOG_NOTICE: + return LOG_NOTICE; + case BLOG_INFO: + return LOG_INFO; + case BLOG_DEBUG: + return LOG_DEBUG; + default: + ASSERT(0) + return 0; + } +} + +struct { + char ident[200]; +} syslog_global; + +static void syslog_log (int channel, int level, const char *msg) +{ + syslog(convert_level(level), "%s: %s", blog_global.channels[channel].name, msg); +} + +static void syslog_free (void) +{ + closelog(); +} + +int BLog_InitSyslog (char *ident, char *facility_str) +{ + int facility; + if (!resolve_facility(facility_str, &facility)) { + return 0; + } + + snprintf(syslog_global.ident, sizeof(syslog_global.ident), "%s", ident); + + openlog(syslog_global.ident, 0, facility); + + BLog_Init(syslog_log, syslog_free); + + return 1; +} diff --git a/system/BLog_syslog.h b/system/BLog_syslog.h new file mode 100644 index 000000000..4670b559d --- /dev/null +++ b/system/BLog_syslog.h @@ -0,0 +1,35 @@ +/** + * @file BLog_syslog.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * BLog syslog backend. + */ + +#ifndef BADVPN_SYSTEM_BLOG_SYSLOG_H +#define BADVPN_SYSTEM_BLOG_SYSLOG_H + +#include +#include + +int BLog_InitSyslog (char *ident, char *facility) WARN_UNUSED; + +#endif diff --git a/system/BPending.c b/system/BPending.c new file mode 100644 index 000000000..74b9f4967 --- /dev/null +++ b/system/BPending.c @@ -0,0 +1,131 @@ +/** + * @file BPending.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include +#include +#include + +void BPendingGroup_Init (BPendingGroup *g) +{ + // init jobs list + LinkedList2_Init(&g->jobs); + + // init pending counter + DebugCounter_Init(&g->pending_ctr); + + // init debug object + DebugObject_Init(&g->d_obj); +} + +void BPendingGroup_Free (BPendingGroup *g) +{ + DebugCounter_Free(&g->pending_ctr); + ASSERT(LinkedList2_IsEmpty(&g->jobs)) + DebugObject_Free(&g->d_obj); +} + +int BPendingGroup_HasJobs (BPendingGroup *g) +{ + DebugObject_Access(&g->d_obj); + + return !LinkedList2_IsEmpty(&g->jobs); +} + +void BPendingGroup_ExecuteJob (BPendingGroup *g) +{ + ASSERT(!LinkedList2_IsEmpty(&g->jobs)) + DebugObject_Access(&g->d_obj); + + // get a job + LinkedList2Node *node = LinkedList2_GetFirst(&g->jobs); + BPending *p = UPPER_OBJECT(node, BPending, pending_node); + ASSERT(p->pending) + + // remove from jobs list + LinkedList2_Remove(&g->jobs, &p->pending_node); + + // set not pending + p->pending = 0; + + // execute job + p->handler(p->user); + return; +} + +void BPending_Init (BPending *o, BPendingGroup *g, BPending_handler handler, void *user) +{ + // init arguments + o->g = g; + o->handler = handler; + o->user = user; + + // set not pending + o->pending = 0; + + // increment pending counter + DebugCounter_Increment(&o->g->pending_ctr); + + // init debug object + DebugObject_Init(&o->d_obj); +} + +void BPending_Free (BPending *o) +{ + DebugCounter_Decrement(&o->g->pending_ctr); + DebugObject_Free(&o->d_obj); + + // remove from jobs list + if (o->pending) { + LinkedList2_Remove(&o->g->jobs, &o->pending_node); + } +} + +void BPending_Set (BPending *o) +{ + DebugObject_Access(&o->d_obj); + + // remove from jobs list + if (o->pending) { + LinkedList2_Remove(&o->g->jobs, &o->pending_node); + } + + // insert to jobs list + LinkedList2_Append(&o->g->jobs, &o->pending_node); + + // set pending + o->pending = 1; +} + +void BPending_Unset (BPending *o) +{ + DebugObject_Access(&o->d_obj); + + if (o->pending) { + // remove from jobs list + LinkedList2_Remove(&o->g->jobs, &o->pending_node); + + // set not pending + o->pending = 0; + } +} diff --git a/system/BPending.h b/system/BPending.h new file mode 100644 index 000000000..6edde4172 --- /dev/null +++ b/system/BPending.h @@ -0,0 +1,136 @@ +/** + * @file BPending.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Module for managing a queue of jobs pending execution. + */ + +#ifndef BADVPN_SYSTEM_BPENDING_H +#define BADVPN_SYSTEM_BPENDING_H + +#include +#include +#include + +/** + * Job execution handler. + * It is guaranteed that the associated {@link BPending} object was + * in set state. + * The {@link BPending} object enters not set state before the handler + * is called. + * + * @param user as in {@link BPending_Init} + */ +typedef void (*BPending_handler) (void *user); + +/** + * Object that contains a queue of jobs pending execution. + */ +typedef struct { + LinkedList2 jobs; + DebugCounter pending_ctr; + DebugObject d_obj; +} BPendingGroup; + +/** + * Object for queuing a job for execution. + */ +typedef struct { + BPendingGroup *g; + BPending_handler handler; + void *user; + int pending; + LinkedList2Node pending_node; + DebugObject d_obj; +} BPending; + +/** + * Initializes the object. + * + * @param g the object + */ +void BPendingGroup_Init (BPendingGroup *g); + +/** + * Frees the object. + * There must be no {@link BPending} objects using this group. + * + * @param g the object + */ +void BPendingGroup_Free (BPendingGroup *g); + +/** + * Checks if there is at least one job in the queue. + * + * @param g the object + * @return 1 if there is at least one job, 0 if not + */ +int BPendingGroup_HasJobs (BPendingGroup *g); + +/** + * Executes a job. + * There must be at least one job in the queue. + * + * @param g the object + */ +void BPendingGroup_ExecuteJob (BPendingGroup *g); + +/** + * Initializes the object. + * The object is initialized in not set state. + * + * @param o the object + * @param g pending group to use + * @param handler job execution handler + * @param user value to pass to handler + */ +void BPending_Init (BPending *o, BPendingGroup *g, BPending_handler handler, void *user); + +/** + * Frees the object. + * The execution handler will not be called after the object + * is freed. + * + * @param o the object + */ +void BPending_Free (BPending *o); + +/** + * Enables the job, appending it to the end of the group's queue. + * If the object was already in set state, the job is removed from its + * current position in the queue before being appended. + * The object enters set state. + * + * @param o the object + */ +void BPending_Set (BPending *o); + +/** + * Disables the job, removing it from the group's queue. + * If the object was not in set state, nothing is done. + * The object enters not set state. + * + * @param o the object + */ +void BPending_Unset (BPending *o); + +#endif diff --git a/system/BReactor.c b/system/BReactor.c new file mode 100644 index 000000000..ce7f521b9 --- /dev/null +++ b/system/BReactor.c @@ -0,0 +1,734 @@ +/** + * @file BReactor.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include + +#ifdef BADVPN_USE_WINAPI +#include +#else +#include +#include +#include +#include +#endif + +#include +#include +#include +#include + +#include + +#include + +#ifdef BADVPN_USE_WINAPI +typedef DWORD btimeout_t; +#define BTIMEOUT_T_MAX ((DWORD)INFINITE - 1) +#define WAITRES_TIMED_OUT(_res) ((_res) == WAIT_TIMEOUT) +#else +typedef int btimeout_t; +#define BTIMEOUT_T_MAX INT_MAX +#define WAITRES_TIMED_OUT(_res) ((_res) == 0) +#endif + +static void dispatch_jobs (BReactor *bsys) +{ + while (!bsys->exiting && BPendingGroup_HasJobs(&bsys->pending_jobs)) { + BPendingGroup_ExecuteJob(&bsys->pending_jobs); + } +} + +static int timer_comparator (void *user, btime_t *val1, btime_t *val2) +{ + if (*val1 < *val2) { + return -1; + } + if (*val1 > *val2) { + return 1; + } + return 0; +} + +static int move_expired_timers (BReactor *bsys, btime_t now) +{ + int moved = 0; + + // move timed out timers to the expired list + BHeapNode *heap_node; + while (heap_node = BHeap_GetFirst(&bsys->timers_heap)) { + BTimer *timer = UPPER_OBJECT(heap_node, BTimer, heap_node); + ASSERT(timer->active) + + // if it's in the future, stop + if (timer->absTime > now) { + break; + } + moved = 1; + + // remove from running timers heap + BHeap_Remove(&bsys->timers_heap, &timer->heap_node); + + // add to expired timers list + LinkedList2_Append(&bsys->timers_expired_list, &timer->list_node); + + // set expired + timer->expired = 1; + } + + return moved; +} + +static void move_first_timers (BReactor *bsys) +{ + // get the time of the first timer + BHeapNode *heap_node = BHeap_GetFirst(&bsys->timers_heap); + ASSERT(heap_node) + BTimer *first_timer = UPPER_OBJECT(heap_node, BTimer, heap_node); + ASSERT(first_timer->active) + btime_t first_time = first_timer->absTime; + + // remove from running timers heap + BHeap_Remove(&bsys->timers_heap, &first_timer->heap_node); + + // add to expired timers list + LinkedList2_Append(&bsys->timers_expired_list, &first_timer->list_node); + + // set expired + first_timer->expired = 1; + + // also move other timers with the same timeout + while (heap_node = BHeap_GetFirst(&bsys->timers_heap)) { + BTimer *timer = UPPER_OBJECT(heap_node, BTimer, heap_node); + ASSERT(timer->active) + ASSERT(timer->absTime >= first_time) + + // if it's in the future, stop + if (timer->absTime > first_time) { + break; + } + + // remove from running timers heap + BHeap_Remove(&bsys->timers_heap, &timer->heap_node); + + // add to expired timers list + LinkedList2_Append(&bsys->timers_expired_list, &timer->list_node); + + // set expired + timer->expired = 1; + } +} + +static void dispatch_timers (BReactor *bsys) +{ + // call event hendlers for expired timers + // Handler functions are free to remove any timer, and if a pending + // expired timer is removed, it will not be reported. + + LinkedList2Node *list_node; + while (!bsys->exiting && (list_node = LinkedList2_GetFirst(&bsys->timers_expired_list))) { + BTimer *timer = UPPER_OBJECT(list_node, BTimer, list_node); + ASSERT(timer->active) + ASSERT(timer->expired) + + // remove from expired list + LinkedList2_Remove(&bsys->timers_expired_list, &timer->list_node); + + // set inactive + timer->active = 0; + + // call handler + BLog(BLOG_DEBUG, "Dispatching timer"); + timer->handler(timer->handler_pointer); + + // dispatch jobs + dispatch_jobs(bsys); + } +} + +#ifdef BADVPN_USE_WINAPI + +static void dispatch_io (BReactor *bsys) +{ + if (!bsys->exiting && bsys->returned_object) { + BHandle *bh = bsys->returned_object; + bsys->returned_object = NULL; + ASSERT(bh->active) + ASSERT(bh->position >= 0 && bh->position < bsys->enabled_num) + ASSERT(bh == bsys->enabled_objects[bh->position]) + ASSERT(bh->h == bsys->enabled_handles[bh->position]) + + // call handler + BLog(BLOG_DEBUG, "Dispatching handle"); + bh->handler(bh->user); + + // dispatch jobs + dispatch_jobs(bsys); + } +} + +#else + +static void set_fd_pointers (BReactor *bsys) +{ + // Write pointers to our entry pointers into file descriptors. + // If a handler function frees some other file descriptor, the + // free routine will set our pointer to NULL so we don't dispatch it. + for (int i = 0; i < bsys->epoll_results_num; i++) { + struct epoll_event *event = &bsys->epoll_results[i]; + ASSERT(event->data.ptr) + BFileDescriptor *bfd = (BFileDescriptor *)event->data.ptr; + ASSERT(bfd->active) + ASSERT(!bfd->epoll_returned_ptr) + bfd->epoll_returned_ptr = (BFileDescriptor **)&event->data.ptr; + } +} + +static void dispatch_io (BReactor *bsys) +{ + while (!bsys->exiting && bsys->epoll_results_pos < bsys->epoll_results_num) { + // grab event + struct epoll_event *event = &bsys->epoll_results[bsys->epoll_results_pos]; + bsys->epoll_results_pos++; + + // check if the BFileDescriptor was removed + if (!event->data.ptr) { + continue; + } + + // get BFileDescriptor + BFileDescriptor *bfd = (BFileDescriptor *)event->data.ptr; + ASSERT(bfd->active) + ASSERT(bfd->epoll_returned_ptr == (BFileDescriptor **)&event->data.ptr) + + // zero pointer to the epoll entry + bfd->epoll_returned_ptr = NULL; + + // calculate events to report + int events = 0; + if (bfd->waitEvents&BREACTOR_READ) { + if ((event->events&EPOLLIN) || (event->events&EPOLLERR) || (event->events&EPOLLHUP)) { + events |= BREACTOR_READ; + } + } + if (bfd->waitEvents&BREACTOR_WRITE) { + if ((event->events&EPOLLOUT) || (event->events&EPOLLERR) || (event->events&EPOLLHUP)) { + events |= BREACTOR_WRITE; + } + } + + // call handler + BLog(BLOG_DEBUG, "Dispatching file descriptor"); + bfd->handler(bfd->user, events); + + // dispatch jobs + dispatch_jobs(bsys); + } +} + +#endif + +static void wait_for_events (BReactor *bsys) +{ + // must have processed all pending events + ASSERT(!BPendingGroup_HasJobs(&bsys->pending_jobs)) + ASSERT(LinkedList2_IsEmpty(&bsys->timers_expired_list)) + #ifdef BADVPN_USE_WINAPI + ASSERT(!bsys->returned_object) + #else + ASSERT(bsys->epoll_results_pos == bsys->epoll_results_num) + #endif + + // clean up epoll results + #ifndef BADVPN_USE_WINAPI + bsys->epoll_results_num = 0; + bsys->epoll_results_pos = 0; + #endif + + // timeout vars + int have_timeout = 0; + btime_t timeout_abs; + btime_t now; + + // compute timeout + BHeapNode *first_node; + if (first_node = BHeap_GetFirst(&bsys->timers_heap)) { + // get current time + now = btime_gettime(); + + // if some timers have already timed out, return them immediately + if (move_expired_timers(bsys, now)) { + BLog(BLOG_DEBUG, "Got already expired timers"); + return; + } + + // timeout is first timer, remember absolute time + BTimer *first_timer = UPPER_OBJECT(first_node, BTimer, heap_node); + have_timeout = 1; + timeout_abs = first_timer->absTime; + } + + int timed_out; + + #ifdef BADVPN_USE_WINAPI + int handle_index; + #else + int epoll_num_results; + #endif + + // wait until the timeout is reached or the file descriptor / handle in ready + while (1) { + // compute timeout + btimeout_t timeout_arg; + btime_t timeout_rel; + if (have_timeout) { + timeout_rel = timeout_abs - now; + if (timeout_rel > BTIMEOUT_T_MAX) { + timeout_arg = BTIMEOUT_T_MAX; + } else { + timeout_arg = timeout_rel; + } + } + + // perform wait + + #ifdef BADVPN_USE_WINAPI + + BLog(BLOG_DEBUG, "Calling WaitForMultipleObjects on %d handles", bsys->enabled_num); + + DWORD waitres = WaitForMultipleObjects(bsys->enabled_num, bsys->enabled_handles, FALSE, (have_timeout ? timeout_arg : INFINITE)); + ASSERT_FORCE(waitres != WAIT_FAILED) + ASSERT_FORCE(!(waitres == WAIT_TIMEOUT) || have_timeout) + ASSERT_FORCE(!(waitres != WAIT_TIMEOUT) || (waitres >= WAIT_OBJECT_0 && waitres < WAIT_OBJECT_0 + bsys->enabled_num)) + + #else + + BLog(BLOG_DEBUG, "Calling epoll_wait"); + + int waitres = epoll_wait(bsys->efd, bsys->epoll_results, BSYSTEM_MAX_RESULTS, (have_timeout ? timeout_arg : -1)); + if (waitres < 0) { + int error = errno; + if (error == EINTR) { + BLog(BLOG_DEBUG, "epoll_wait interrupted"); + goto try_again; + } + perror("epoll_wait"); + ASSERT_FORCE(0) + } + + ASSERT_FORCE(!(waitres == 0) || have_timeout) + ASSERT_FORCE(waitres <= BSYSTEM_MAX_RESULTS) + + #endif + + if (!WAITRES_TIMED_OUT(waitres) || timeout_rel <= BTIMEOUT_T_MAX) { + timed_out = WAITRES_TIMED_OUT(waitres); + if (!timed_out) { + #ifdef BADVPN_USE_WINAPI + handle_index = waitres - WAIT_OBJECT_0; + #else + epoll_num_results = waitres; + #endif + } + break; + } + + try_again: + if (have_timeout) { + // get current time + now = btime_gettime(); + // check if we already reached the time we're waiting for + if (now >= timeout_abs) { + timed_out = 1; + break; + } + } + } + + if (timed_out) { + // timed out, expire first timers + BLog(BLOG_DEBUG, "Wait timed out"); + move_first_timers(bsys); + } else { + #ifdef BADVPN_USE_WINAPI + // user's handle got signalled + BLog(BLOG_DEBUG, "Wait returned handle %d", handle_index); + bsys->returned_object = bsys->enabled_objects[handle_index]; + #else + // setup returned file descriptors list + BLog(BLOG_DEBUG, "Wait returned %d file descriptors", epoll_num_results); + bsys->epoll_results_num = epoll_num_results; + set_fd_pointers(bsys); + #endif + } +} + +#ifdef BADVPN_USE_WINAPI + +void BHandle_Init (BHandle *bh, HANDLE handle, BHandle_handler handler, void *user) +{ + bh->h = handle; + bh->handler = handler; + bh->user = user; + bh->active = 0; +} + +#else + +void BFileDescriptor_Init (BFileDescriptor *bs, int fd, BFileDescriptor_handler handler, void *user) +{ + bs->fd = fd; + bs->handler = handler; + bs->user = user; + bs->active = 0; +} + +#endif + +void BTimer_Init (BTimer *bt, btime_t msTime, BTimer_handler handler, void *handler_pointer) +{ + bt->msTime = msTime; + bt->handler = handler; + bt->handler_pointer = handler_pointer; + + bt->active = 0; +} + +int BTimer_IsRunning (BTimer *bt) +{ + ASSERT(bt->active == 0 || bt->active == 1) + + return bt->active; +} + +int BReactor_Init (BReactor *bsys) +{ + BLog(BLOG_DEBUG, "Reactor initializing"); + + bsys->exiting = 0; + + // init jobs + BPendingGroup_Init(&bsys->pending_jobs); + + // init timers + BHeap_Init(&bsys->timers_heap, OFFSET_DIFF(BTimer, absTime, heap_node), (BHeap_comparator)timer_comparator, NULL); + LinkedList2_Init(&bsys->timers_expired_list); + + #ifdef BADVPN_USE_WINAPI + + bsys->num_handles = 0; + bsys->enabled_num = 0; + bsys->returned_object = NULL; + + #else + + // create epoll fd + if ((bsys->efd = epoll_create(10)) < 0) { + BLog(BLOG_ERROR, "epoll_create failed"); + goto fail0; + } + + // init results array + bsys->epoll_results_num = 0; + bsys->epoll_results_pos = 0; + + DebugCounter_Init(&bsys->d_fds_counter); + + #endif + + // init debug object + DebugObject_Init(&bsys->d_obj); + + return 1; + +fail0: + BPendingGroup_Free(&bsys->pending_jobs); + BLog(BLOG_ERROR, "Reactor failed to initialize"); + return 0; +} + +void BReactor_Free (BReactor *bsys) +{ + // {pending group has no BPending objects} + ASSERT(!BPendingGroup_HasJobs(&bsys->pending_jobs)) + ASSERT(!BHeap_GetFirst(&bsys->timers_heap)) + ASSERT(LinkedList2_IsEmpty(&bsys->timers_expired_list)) + DebugObject_Free(&bsys->d_obj); + #ifdef BADVPN_USE_WINAPI + ASSERT(bsys->num_handles == 0) + #else + DebugCounter_Free(&bsys->d_fds_counter); + #endif + + BLog(BLOG_DEBUG, "Reactor freeing"); + + #ifndef BADVPN_USE_WINAPI + + // close epoll fd + ASSERT_FORCE(close(bsys->efd) == 0) + + #endif + + // free jobs + BPendingGroup_Free(&bsys->pending_jobs); +} + +int BReactor_Exec (BReactor *bsys) +{ + BLog(BLOG_DEBUG, "Entering event loop"); + + bsys->exiting = 0; + + while (1) { + dispatch_jobs(bsys); + dispatch_timers(bsys); + dispatch_io(bsys); + + if (bsys->exiting) { + break; + } + + wait_for_events(bsys); + } + + BLog(BLOG_DEBUG, "Exiting event loop, exit code %d", bsys->exit_code); + + return bsys->exit_code; +} + +void BReactor_Quit (BReactor *bsys, int code) +{ + bsys->exiting = 1; + bsys->exit_code = code; +} + +void BReactor_SetTimer (BReactor *bsys, BTimer *bt) +{ + btime_t now = btime_gettime(); + + // handle overflow + int overflows = add_int64_overflows(now, bt->msTime); + btime_t absTime; + if (overflows != 0) { + if (overflows > 0) { + absTime = INT64_MAX; + } else { + absTime = INT64_MIN; + } + } else { + absTime = now + bt->msTime; + } + + BReactor_SetTimerAbsolute(bsys, bt, absTime); +} + +void BReactor_SetTimerAbsolute (BReactor *bsys, BTimer *bt, btime_t time) +{ + // unlink it if it's already in the list + BReactor_RemoveTimer(bsys, bt); + + // initialize timer + bt->active = 1; + bt->expired = 0; + bt->absTime = time; + + // insert to running timers heap + BHeap_Insert(&bsys->timers_heap, &bt->heap_node); +} + +void BReactor_RemoveTimer (BReactor *bsys, BTimer *bt) +{ + if (!bt->active) { + return; + } + + if (bt->expired) { + // remove from expired list + LinkedList2_Remove(&bsys->timers_expired_list, &bt->list_node); + } else { + // remove from running heap + BHeap_Remove(&bsys->timers_heap, &bt->heap_node); + } + + // set inactive + bt->active = 0; +} + +BPendingGroup * BReactor_PendingGroup (BReactor *bsys) +{ + return &bsys->pending_jobs; +} + +#ifdef BADVPN_USE_WINAPI + +int BReactor_AddHandle (BReactor *bsys, BHandle *bh) +{ + ASSERT(!bh->active) + + if (bsys->num_handles >= BSYSTEM_MAX_HANDLES) { + return 0; + } + + bh->active = 1; + bh->position = -1; + + bsys->num_handles++; + + return 1; +} + +void BReactor_RemoveHandle (BReactor *bsys, BHandle *bh) +{ + ASSERT(bh->active) + + if (bh->position >= 0) { + BReactor_DisableHandle(bsys, bh); + } + + bh->active = 0; + + ASSERT(bsys->num_handles > 0) + bsys->num_handles--; +} + +void BReactor_EnableHandle (BReactor *bsys, BHandle *bh) +{ + ASSERT(bh->active) + ASSERT(bh->position == -1) + + ASSERT(bsys->enabled_num < BSYSTEM_MAX_HANDLES) + bsys->enabled_handles[bsys->enabled_num] = bh->h; + bsys->enabled_objects[bsys->enabled_num] = bh; + bh->position = bsys->enabled_num; + bsys->enabled_num++; +} + +void BReactor_DisableHandle (BReactor *bsys, BHandle *bh) +{ + ASSERT(bh->active) + ASSERT(bh->position >= 0) + + ASSERT(bh->position < bsys->enabled_num) + ASSERT(bh == bsys->enabled_objects[bh->position]) + ASSERT(bh->h == bsys->enabled_handles[bh->position]) + + // if there are more handles after this one, move the last + // one into its position + if (bh->position < bsys->enabled_num - 1) { + int move_position = bsys->enabled_num - 1; + BHandle *move_handle = bsys->enabled_objects[move_position]; + + ASSERT(move_handle->active) + ASSERT(move_handle->position == move_position) + ASSERT(move_handle->h == bsys->enabled_handles[move_position]) + + bsys->enabled_handles[bh->position] = move_handle->h; + bsys->enabled_objects[bh->position] = move_handle; + move_handle->position = bh->position; + } + + bh->position = -1; + bsys->enabled_num--; + + // make sure the handler will not be called + if (bsys->returned_object == bh) { + bsys->returned_object = NULL; + } +} + +#else + +int BReactor_AddFileDescriptor (BReactor *bsys, BFileDescriptor *bs) +{ + ASSERT(!bs->active) + + // add epoll entry + struct epoll_event event; + memset(&event, 0, sizeof(event)); + event.events = 0; + event.data.ptr = bs; + if (epoll_ctl(bsys->efd, EPOLL_CTL_ADD, bs->fd, &event) < 0) { + int error = errno; + BLog(BLOG_ERROR, "epoll_ctl failed: %d", error); + return 0; + } + + bs->active = 1; + bs->waitEvents = 0; + bs->epoll_returned_ptr = NULL; + + DebugCounter_Increment(&bsys->d_fds_counter); + + return 1; +} + +void BReactor_RemoveFileDescriptor (BReactor *bsys, BFileDescriptor *bs) +{ + ASSERT(bs->active) + + bs->active = 0; + + // delete epoll entry + ASSERT_FORCE(epoll_ctl(bsys->efd, EPOLL_CTL_DEL, bs->fd, NULL) == 0) + + // The user can now free the file descriptor object, however the file descriptor + // can still be in the list of returned events. To prevent the event dispatcher + // from crashing, zero its pointer to the file descriptor. + if (bs->epoll_returned_ptr) { + *bs->epoll_returned_ptr = NULL; + } + + DebugCounter_Decrement(&bsys->d_fds_counter); +} + +void BReactor_SetFileDescriptorEvents (BReactor *bsys, BFileDescriptor *bs, int events) +{ + ASSERT(bs->active) + ASSERT(!(events&~(BREACTOR_READ|BREACTOR_WRITE))) + + if (bs->waitEvents == events) { + return; + } + + // update events + bs->waitEvents = events; + + // calculate epoll events + int eevents = 0; + if (bs->waitEvents&BREACTOR_READ) { + eevents |= EPOLLIN; + } + if (bs->waitEvents&BREACTOR_WRITE) { + eevents |= EPOLLOUT; + } + + // update epoll entry + struct epoll_event event; + memset(&event, 0, sizeof(event)); + event.events = eevents; + event.data.ptr = bs; + ASSERT_FORCE(epoll_ctl(bsys->efd, EPOLL_CTL_MOD, bs->fd, &event) == 0) +} + +#endif diff --git a/system/BReactor.h b/system/BReactor.h new file mode 100644 index 000000000..b8ac84ca6 --- /dev/null +++ b/system/BReactor.h @@ -0,0 +1,390 @@ +/** + * @file BReactor.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Event loop that supports file desciptor (Linux) or HANDLE (Windows) events + * and timers. + */ + +#ifndef BADVPN_SYSTEM_BREACTOR_H +#define BADVPN_SYSTEM_BREACTOR_H + +#ifdef BADVPN_USE_WINAPI +#include +#else +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include + +struct BTimer_t; + +/** + * Handler function invoked when the timer expires. + * The timer was in running state. + * The timer enters not running state before this function is invoked. + * This function is being called from within the timer's previosly + * associated reactor. + * + * @param user value passed to {@link BTimer_Init} + */ +typedef void (*BTimer_handler) (void *user); + +/** + * Timer object used with {@link BReactor}. + */ +typedef struct BTimer_t { + btime_t msTime; + BTimer_handler handler; + void *handler_pointer; + + uint8_t active; + uint8_t expired; + btime_t absTime; + union { + BHeapNode heap_node; + LinkedList2Node list_node; + }; +} BTimer; + +/** + * Initializes the timer object. + * The timer object is initialized in not running state. + * + * @param bt the object + * @param msTime default timeout in milliseconds + * @param handler handler function invoked when the timer expires + * @param user value to pass to the handler function + */ +void BTimer_Init (BTimer *bt, btime_t msTime, BTimer_handler handler, void *user); + +/** + * Checks if the timer is running. + * + * @param bt the object + * @return 1 if running, 0 if not running + */ +int BTimer_IsRunning (BTimer *bt); + +#ifdef BADVPN_USE_WINAPI + +/** + * Handler function invoked by the reactor when the handle is signalled + * The handle object is in active state, is being called from within + * the associated reactor, and is in monitored state. + * + * @param user value passed to {@link BHandle_Init} + */ +typedef void (*BHandle_handler) (void *user); + +struct BHandle_t; + +/** + * Windows handle object used with {@link BReactor}. + */ +typedef struct BHandle_t { + HANDLE h; + BHandle_handler handler; + void *user; + int active; + int position; +} BHandle; + +/** + * Initializes the handle object. + * The handle object is initialized in not active state. + * + * @param bh the object + * @param handle underlying Windows handle + * @param handler handler function invoked when the handle is signalled + * @param user value to pass to the handler function + */ +void BHandle_Init (BHandle *bh, HANDLE handle, BHandle_handler handler, void *user); + +#else + +struct BFileDescriptor_t; + +#define BREACTOR_READ 1 +#define BREACTOR_WRITE 2 + +/** + * Handler function invoked by the reactor when one or more monitored events + * are detected. + * The file descriptor object is in active state, being called from within + * the associated reactor. + * + * @param user value passed to {@link BFileDescriptor_Init} + * @param events bitmask of detected events (BREACTOR_READ, BREACTOR_WRITE). + * Will be nonzero, and a subset of current monitored events. + */ +typedef void (*BFileDescriptor_handler) (void *user, int events); + +/** + * File descriptor object used with {@link BReactor}. + */ +typedef struct BFileDescriptor_t { + int fd; + BFileDescriptor_handler handler; + void *user; + int active; + int waitEvents; + struct BFileDescriptor_t **epoll_returned_ptr; +} BFileDescriptor; + +/** + * Intializes the file descriptor object. + * The object is initialized in not active state. + * + * @param bs file descriptor object to initialize + * @param fb file descriptor to represent + * @param handler handler function invoked by the reactor when a monitored event is detected + * @param user value passed to the handler functuon + */ +void BFileDescriptor_Init (BFileDescriptor *bs, int fd, BFileDescriptor_handler handler, void *user); + +#endif + +// BReactor + +#define BSYSTEM_MAX_RESULTS 64 +#define BSYSTEM_MAX_HANDLES 64 + +/** + * Event loop that supports file desciptor (Linux) or HANDLE (Windows) events + * and timers. + */ +typedef struct { + DebugObject d_obj; + + int exiting; + int exit_code; + + // jobs + BPendingGroup pending_jobs; + + // timers + BHeap timers_heap; + LinkedList2 timers_expired_list; + + #ifdef BADVPN_USE_WINAPI + + int num_handles; // number of user handles + int enabled_num; // number of user handles in the enabled array + HANDLE enabled_handles[BSYSTEM_MAX_HANDLES]; // enabled user handles + BHandle *enabled_objects[BSYSTEM_MAX_HANDLES]; // objects corresponding to enabled handles + BHandle *returned_object; + + #else + + int efd; // epoll fd + struct epoll_event epoll_results[BSYSTEM_MAX_RESULTS]; // epoll returned events buffer + int epoll_results_num; // number of events in the array + int epoll_results_pos; // number of events processed so far + + DebugCounter d_fds_counter; + + #endif +} BReactor; + +/** + * Initializes the reactor. + * {@link BLog_Init} must have been done. + * {@link BTime_Init} must have been done. + * + * @param bsys the object + * @return 1 on success, 0 on failure + */ +int BReactor_Init (BReactor *bsys) WARN_UNUSED; + +/** + * Frees the reactor. + * Must not be called from within the event loop ({@link BReactor_Exec}). + * There must be no {@link BPending} objects using the pending group + * returned by {@link BReactor_PendingGroup}. + * There must be no running timers in this reactor. + * There must be no file descriptors or handles registered + * with this reactor. + * + * @param bsys the object + */ +void BReactor_Free (BReactor *bsys); + +/** + * Runs the event loop. + * + * @param bsys the object + * @return value passed to {@link BReactor_Quit} + */ +int BReactor_Exec (BReactor *bsys); + +/** + * Causes the event loop ({@link BReactor_Exec}) to cease + * dispatching events and return. + * If the event loop is not running, this function does nothing. + * + * @param bsys the object + * @param code value {@link BReactor_Exec} should return. If this is + * called more than once from an event loop, the last code + * will be returned. + */ +void BReactor_Quit (BReactor *bsys, int code); + +/** + * Starts a timer to expire after its default time. + * The timer must have been initialized with {@link BTimer_Init}. + * If the timer is in running state, it must be associated with this reactor. + * The timer enters running state, associated with this reactor. + * The timer's expiration time is set to the time argument. + * + * @param bsys the object + * @param bt timer to start + */ +void BReactor_SetTimer (BReactor *bsys, BTimer *bt); + +/** + * Starts a timer to expire at the specified time. + * The timer must have been initialized with {@link BTimer_Init}. + * If the timer is in running state, it must be associated with this reactor. + * The timer enters running state, associated with this reactor. + * The timer's expiration time is set to the time argument. + * + * @param bsys the object + * @param bt timer to start + * @param time absolute expiration time (according to {@link btime_gettime}) + */ +void BReactor_SetTimerAbsolute (BReactor *bsys, BTimer *bt, btime_t time); + +/** + * Stops a timer. + * If the timer is in running state, it must be associated with this reactor. + * The timer enters not running state. + * + * @param bsys the object + * @param bt timer to stop + */ +void BReactor_RemoveTimer (BReactor *bsys, BTimer *bt); + +/** + * Returns a {@link BPendingGroup} object that can be used to schedule jobs for + * the reactor to execute. These jobs have complete priority over other events + * (timers, file descriptors and Windows handles). + * The returned pending group may only be used as an argument to {@link BPending_Init}, + * and must not be accessed by other means. + * All {@link BPending} objects using this group must be freed before freeing + * the reactor. + * + * @param bsys the object + * @return pending group for scheduling jobs for the reactor to execute + */ +BPendingGroup * BReactor_PendingGroup (BReactor *bsys); + +#ifdef BADVPN_USE_WINAPI + +/** + * Registers a Windows handle to be monitored. + * + * @param bsys the object + * @param bh handle object. Must have been initialized with {@link BHandle_Init}. + * Must be in not active state. On success, the handle object enters + * active state, associated with this reactor, and is initialized + * to not monitored state. + * @return 1 on success, 0 on failure + */ +int BReactor_AddHandle (BReactor *bsys, BHandle *bh) WARN_UNUSED; + +/** + * Unregisters a Windows handle. + * + * @param bsys the object + * @param bh handle object. Must be in active state, associated with this reactor. + * The handle object enters not active state. + */ +void BReactor_RemoveHandle (BReactor *bsys, BHandle *bh); + +/** + * Starts monitoring a Windows handle. + * + * @param bsys the object + * @param bh handle object. Must be in active state, associated with this reactor. + * Must be in not monitored state. + * The handle object enters monitored state. + */ +void BReactor_EnableHandle (BReactor *bsys, BHandle *bh); + +/** + * Stops monitoring a Windows handle. + * + * @param bsys the object + * @param bh handle object. Must be in active state, associated with this reactor. + * Must be in monitored state. + * The handle object enters not monitored state. + */ +void BReactor_DisableHandle (BReactor *bsys, BHandle *bh); + +#else + +/** + * Starts monitoring a file descriptor. + * + * @param bsys the object + * @param bs file descriptor object. Must have been initialized with + * {@link BFileDescriptor_Init} Must be in not active state. + * On success, the file descriptor object enters active state, + * associated with this reactor. + * @return 1 on success, 0 on failure + */ +int BReactor_AddFileDescriptor (BReactor *bsys, BFileDescriptor *bs) WARN_UNUSED; + +/** + * Stops monitoring a file descriptor. + * + * @param bsys the object + * @param bs {@link BFileDescriptor} object. Must be in active state, + * associated with this reactor. The file descriptor object + * enters not active state. + */ +void BReactor_RemoveFileDescriptor (BReactor *bsys, BFileDescriptor *bs); + +/** + * Sets monitored file descriptor events. + * + * @param bsys the object + * @param bs {@link BFileDescriptor} object. Must be in active state, + * associated with this reactor. + * @param events events to watch for. Must not have any bits other than + * BREACTOR_READ and BREACTOR_WRITE. + * This overrides previosly monitored events. + */ +void BReactor_SetFileDescriptorEvents (BReactor *bsys, BFileDescriptor *bs, int events); + +#endif + +#endif diff --git a/system/BSignal.c b/system/BSignal.c new file mode 100644 index 000000000..39e7606e5 --- /dev/null +++ b/system/BSignal.c @@ -0,0 +1,317 @@ +/** + * @file BSignal.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef BADVPN_USE_WINAPI +#include +#else +#include +#include +#include +#include +#include +#endif + +#include +#include + +#include + +#include + +#define EMPTY_SIGSET(_set) sigemptyset(&_set); +#define FILL_SIGSET(_set) sigemptyset(&_set); sigaddset(&_set, SIGTERM); sigaddset(&_set, SIGINT); + +struct { + int initialized; + int capturing; + BSignal_handler handler; + void *handler_user; + BReactor *handler_reactor; + #ifdef BADVPN_USE_WINAPI + CRITICAL_SECTION handler_mutex; // mutex to make sure only one handler is working at a time + CRITICAL_SECTION state_mutex; // mutex for capturing and signal_pending + int signal_pending; + HANDLE signal_sem1; + HANDLE signal_sem2; + BHandle bhandle; + #else + int signal_fd; + BFileDescriptor bfd; + #endif +} bsignal_global = { + .initialized = 0, +}; + +#ifdef BADVPN_USE_WINAPI + +static void signal_handle_handler (void *user) +{ + ASSERT(bsignal_global.initialized) + ASSERT(bsignal_global.capturing) + ASSERT(bsignal_global.handler) + + ASSERT(bsignal_global.signal_pending) + bsignal_global.signal_pending = 0; + ASSERT_FORCE(ReleaseSemaphore(bsignal_global.signal_sem2, 1, NULL)) + + BLog(BLOG_DEBUG, "Dispatching signal"); + bsignal_global.handler(bsignal_global.handler_user); +} + +static BOOL ctrl_handler (DWORD type) +{ + ASSERT(bsignal_global.initialized) + + EnterCriticalSection(&bsignal_global.handler_mutex); + + EnterCriticalSection(&bsignal_global.state_mutex); + if (bsignal_global.capturing) { + ASSERT(!bsignal_global.signal_pending) + bsignal_global.signal_pending = 1; + ASSERT_FORCE(ReleaseSemaphore(bsignal_global.signal_sem1, 1, NULL)) + LeaveCriticalSection(&bsignal_global.state_mutex); + + ASSERT_FORCE(WaitForSingleObject(bsignal_global.signal_sem2, INFINITE) == WAIT_OBJECT_0) + } else { + LeaveCriticalSection(&bsignal_global.state_mutex); + } + + LeaveCriticalSection(&bsignal_global.handler_mutex); + + return TRUE; +} + +#else + +static void signal_fd_handler (void *user, int events) +{ + ASSERT(bsignal_global.initialized) + ASSERT(bsignal_global.capturing) + ASSERT(bsignal_global.handler) + + do { + struct signalfd_siginfo siginfo; + int bytes = read(bsignal_global.signal_fd, &siginfo, sizeof(siginfo)); + if (bytes < 0) { + int error = errno; + if (error == EAGAIN || error == EWOULDBLOCK) { + break; + } + ASSERT_FORCE(0) + } + ASSERT_FORCE(bytes == sizeof(siginfo)) + + BLog(BLOG_DEBUG, "Dispatching signal"); + bsignal_global.handler(bsignal_global.handler_user); + } while (bsignal_global.capturing && bsignal_global.handler); +} + +#endif + +int BSignal_Init (void) +{ + ASSERT(!bsignal_global.initialized) + + BLog(BLOG_DEBUG, "BSignal initializing"); + + #ifdef BADVPN_USE_WINAPI + + InitializeCriticalSection(&bsignal_global.handler_mutex); + InitializeCriticalSection(&bsignal_global.state_mutex); + + bsignal_global.signal_pending = 0; + + if (!(bsignal_global.signal_sem1 = CreateSemaphore(NULL, 0, 1, NULL))) { + BLog(BLOG_ERROR, "CreateSemaphore failed"); + goto fail1; + } + + if (!(bsignal_global.signal_sem2 = CreateSemaphore(NULL, 0, 1, NULL))) { + BLog(BLOG_ERROR, "CreateSemaphore failed"); + goto fail2; + } + + BHandle_Init(&bsignal_global.bhandle, bsignal_global.signal_sem1, signal_handle_handler, NULL); + + #else + + // create signalfd fd + sigset_t emptyset; + FILL_SIGSET(emptyset) + if ((bsignal_global.signal_fd = signalfd(-1, &emptyset, 0)) < 0) { + BLog(BLOG_ERROR, "signalfd failed"); + goto fail0; + } + + // set non-blocking + if (fcntl(bsignal_global.signal_fd, F_SETFL, O_NONBLOCK) < 0) { + DEBUG("cannot set non-blocking"); + goto fail1; + } + + // init BFileDescriptor + BFileDescriptor_Init(&bsignal_global.bfd, bsignal_global.signal_fd, signal_fd_handler, NULL); + + #endif + + bsignal_global.capturing = 0; + bsignal_global.initialized = 1; + + return 1; + + #ifdef BADVPN_USE_WINAPI +fail2: + ASSERT_FORCE(CloseHandle(bsignal_global.signal_sem1)) +fail1: + DeleteCriticalSection(&bsignal_global.state_mutex); + DeleteCriticalSection(&bsignal_global.handler_mutex); + #else +fail1: + ASSERT_FORCE(close(bsignal_global.signal_fd) == 0) + #endif + +fail0: + return 0; +} + +void BSignal_Capture (void) +{ + ASSERT(bsignal_global.initialized) + ASSERT(!bsignal_global.capturing) + + BLog(BLOG_DEBUG, "BSignal capturing"); + + #ifdef BADVPN_USE_WINAPI + + ASSERT(!bsignal_global.signal_pending) + + EnterCriticalSection(&bsignal_global.state_mutex); + bsignal_global.capturing = 1; + LeaveCriticalSection(&bsignal_global.state_mutex); + ASSERT_FORCE(SetConsoleCtrlHandler((PHANDLER_ROUTINE)ctrl_handler, TRUE)) + + #else + + sigset_t signals; + FILL_SIGSET(signals) + ASSERT_FORCE(sigprocmask(SIG_BLOCK, &signals, NULL) == 0) + + bsignal_global.capturing = 1; + + #endif + + bsignal_global.handler = NULL; +} + +void BSignal_Uncapture (void) +{ + ASSERT(bsignal_global.initialized) + ASSERT(bsignal_global.capturing) + ASSERT(!bsignal_global.handler) + + BLog(BLOG_DEBUG, "BSignal uncapturing"); + + #ifdef BADVPN_USE_WINAPI + + ASSERT_FORCE(SetConsoleCtrlHandler(NULL, FALSE)) + EnterCriticalSection(&bsignal_global.state_mutex); + + if (bsignal_global.signal_pending) { + bsignal_global.signal_pending = 0; + // consume sem1 which the handler incremented to prevent + // the event loop from reacting to the signal + ASSERT_FORCE(WaitForSingleObject(bsignal_global.signal_sem1, INFINITE) == WAIT_OBJECT_0) + // allow the handler to return + // no need to wait for it; it only waits on sem2, which nothing else + // needs until it returns + ASSERT_FORCE(ReleaseSemaphore(bsignal_global.signal_sem2, 1, NULL)) + } + + #else + + sigset_t signals; + FILL_SIGSET(signals) + ASSERT_FORCE(sigprocmask(SIG_UNBLOCK, &signals, NULL) == 0) + + #endif + + bsignal_global.capturing = 0; + + #ifdef BADVPN_USE_WINAPI + + LeaveCriticalSection(&bsignal_global.state_mutex); + + #endif +} + +int BSignal_SetHandler (BReactor *reactor, BSignal_handler handler, void *user) +{ + ASSERT(bsignal_global.initialized) + ASSERT(bsignal_global.capturing) + ASSERT(!bsignal_global.handler) + ASSERT(handler) + + #ifdef BADVPN_USE_WINAPI + + if (!BReactor_AddHandle(reactor, &bsignal_global.bhandle)) { + BLog(BLOG_ERROR, "BReactor_AddHandle failed"); + return 0; + } + + BReactor_EnableHandle(reactor, &bsignal_global.bhandle); + + #else + + if (!BReactor_AddFileDescriptor(reactor, &bsignal_global.bfd)) { + BLog(BLOG_ERROR, "BReactor_AddFileDescriptor failed"); + return 0; + } + + BReactor_SetFileDescriptorEvents(reactor, &bsignal_global.bfd, BREACTOR_READ); + + #endif + + bsignal_global.handler = handler; + bsignal_global.handler_user = user; + bsignal_global.handler_reactor = reactor; + + return 1; +} + +void BSignal_RemoveHandler (void) +{ + ASSERT(bsignal_global.initialized) + ASSERT(bsignal_global.capturing) + ASSERT(bsignal_global.handler) + + #ifdef BADVPN_USE_WINAPI + + BReactor_RemoveHandle(bsignal_global.handler_reactor, &bsignal_global.bhandle); + + #else + + BReactor_RemoveFileDescriptor(bsignal_global.handler_reactor, &bsignal_global.bfd); + + #endif + + bsignal_global.handler = NULL; +} diff --git a/system/BSignal.h b/system/BSignal.h new file mode 100644 index 000000000..9d8efc2cb --- /dev/null +++ b/system/BSignal.h @@ -0,0 +1,77 @@ +/** + * @file BSignal.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * A global object for catching program termination requests. + */ + +#ifndef BADVPN_SYSTEM_BSIGNAL_H +#define BADVPN_SYSTEM_BSIGNAL_H + +#include +#include + +typedef void (*BSignal_handler) (void *user); + +/** + * Initializes signal handling. + * The object is created in not capturing state. + * {@link BLog_Init} must have been done. + * + * @return 1 on success, 0 on failure + */ +int BSignal_Init (void) WARN_UNUSED; + +/** + * Starts capturing signals. + * The object must be in not capturing state. + * The object enters capturing state. + */ +void BSignal_Capture (void); + +/** + * Stops capturing signals. + * The object must be in capturing state. + * A signal handler must not be configured. + * The object enters not capturing state. + */ +void BSignal_Uncapture (void); + +/** + * Configures a reactor and a handler for signals. + * The object must be in capturing state. + * A handler must not be already configured. + * + * @param reactor {@link BReactor} from which the handler will be called + * @param handler callback function invoked from the reactor + * @param user value passed to callback function + * @return 1 on success, 0 on failure. + */ +int BSignal_SetHandler (BReactor *reactor, BSignal_handler handler, void *user) WARN_UNUSED; + +/** + * Deconfigures a signal reactor and handler. + * A handler must be configured. + */ +void BSignal_RemoveHandler (void); + +#endif diff --git a/system/BSocket.c b/system/BSocket.c new file mode 100644 index 000000000..268afeca2 --- /dev/null +++ b/system/BSocket.c @@ -0,0 +1,1431 @@ +/** + * @file BSocket.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef BADVPN_USE_WINAPI +#include +#include +#else +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include +#include + +#include + +#include + +#define HANDLER_READ 0 +#define HANDLER_WRITE 1 +#define HANDLER_ACCEPT 2 +#define HANDLER_CONNECT 3 + +static void init_handlers (BSocket *bs) +{ + bs->global_handler = NULL; + + int i; + for (i = 0; i < 4; i++) { + bs->handlers[i] = NULL; + } +} + +static int set_nonblocking (int s) +{ + #ifdef BADVPN_USE_WINAPI + unsigned long bl = 1; + int res = ioctlsocket(s, FIONBIO, &bl); + #else + int res = fcntl(s, F_SETFL, O_NONBLOCK); + #endif + return res; +} + +static int set_pktinfo (int s) +{ + #ifdef BADVPN_USE_WINAPI + DWORD opt = 1; + int res = setsockopt(s, IPPROTO_IP, IP_PKTINFO, (char *)&opt, sizeof(opt)); + #else + int opt = 1; + int res = setsockopt(s, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt)); + #endif + return res; +} + +static int set_pktinfo6 (int s) +{ + #ifdef BADVPN_USE_WINAPI + DWORD opt = 1; + int res = setsockopt(s, IPPROTO_IPV6, IPV6_PKTINFO, (char *)&opt, sizeof(opt)); + #else + int opt = 1; + int res = setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, &opt, sizeof(opt)); + #endif + return res; +} + +static void close_socket (int fd) +{ + #ifdef BADVPN_USE_WINAPI + int res = closesocket(fd); + #else + int res = close(fd); + #endif + ASSERT_FORCE(res == 0) +} + +struct sys_addr { + #ifdef BADVPN_USE_WINAPI + int len; + #else + socklen_t len; + #endif + union { + struct sockaddr generic; + struct sockaddr_in ipv4; + struct sockaddr_in6 ipv6; + } addr; +}; + +static void addr_socket_to_sys (struct sys_addr *out, BAddr *addr) +{ + switch (addr->type) { + case BADDR_TYPE_IPV4: + out->len = sizeof(out->addr.ipv4); + memset(&out->addr.ipv4, 0, sizeof(out->addr.ipv4)); + out->addr.ipv4.sin_family = AF_INET; + out->addr.ipv4.sin_port = addr->ipv4.port; + out->addr.ipv4.sin_addr.s_addr = addr->ipv4.ip; + break; + case BADDR_TYPE_IPV6: + out->len = sizeof(out->addr.ipv6); + memset(&out->addr.ipv6, 0, sizeof(out->addr.ipv6)); + out->addr.ipv6.sin6_family = AF_INET6; + out->addr.ipv6.sin6_port = addr->ipv6.port; + out->addr.ipv6.sin6_flowinfo = 0; + memcpy(out->addr.ipv6.sin6_addr.s6_addr, addr->ipv6.ip, 16); + out->addr.ipv6.sin6_scope_id = 0; + break; + default: + ASSERT(0) + break; + } +} + +static void addr_sys_to_socket (BAddr *out, struct sys_addr *addr) +{ + switch (addr->addr.generic.sa_family) { + case AF_INET: + ASSERT(addr->len == sizeof(struct sockaddr_in)) + out->type = BADDR_TYPE_IPV4; + out->ipv4.ip = addr->addr.ipv4.sin_addr.s_addr; + out->ipv4.port = addr->addr.ipv4.sin_port; + break; + case AF_INET6: + ASSERT(addr->len == sizeof(struct sockaddr_in6)) + out->type = BADDR_TYPE_IPV6; + memcpy(out->ipv6.ip, addr->addr.ipv6.sin6_addr.s6_addr, 16); + out->ipv6.port = addr->addr.ipv6.sin6_port; + break; + default: + ASSERT(0) + break; + } +} + +static int get_event_index (int event) +{ + switch (event) { + case BSOCKET_READ: + return HANDLER_READ; + case BSOCKET_WRITE: + return HANDLER_WRITE; + case BSOCKET_ACCEPT: + return HANDLER_ACCEPT; + case BSOCKET_CONNECT: + return HANDLER_CONNECT; + default: + ASSERT(0) + return 42; + } +} + +static void call_handlers (BSocket *bs, int returned_events) +{ + // reset recv number + bs->recv_num = 0; + + if (bs->global_handler) { + bs->global_handler(bs->global_handler_user, returned_events); + return; + } + + if (returned_events&BSOCKET_READ) { + ASSERT(bs->handlers[HANDLER_READ]) + DEAD_ENTER(bs->dead) + bs->handlers[HANDLER_READ](bs->handlers_user[HANDLER_READ], BSOCKET_READ); + if (DEAD_LEAVE(bs->dead)) { + return; + } + } + if (returned_events&BSOCKET_WRITE) { + ASSERT(bs->handlers[HANDLER_WRITE]) + DEAD_ENTER(bs->dead) + bs->handlers[HANDLER_WRITE](bs->handlers_user[HANDLER_WRITE], BSOCKET_WRITE); + if (DEAD_LEAVE(bs->dead)) { + return; + } + } + if (returned_events&BSOCKET_ACCEPT) { + ASSERT(bs->handlers[HANDLER_ACCEPT]) + DEAD_ENTER(bs->dead) + bs->handlers[HANDLER_ACCEPT](bs->handlers_user[HANDLER_ACCEPT], BSOCKET_ACCEPT); + if (DEAD_LEAVE(bs->dead)) { + return; + } + } + if (returned_events&BSOCKET_CONNECT) { + ASSERT(bs->handlers[HANDLER_CONNECT]) + DEAD_ENTER(bs->dead) + bs->handlers[HANDLER_CONNECT](bs->handlers_user[HANDLER_CONNECT], BSOCKET_CONNECT); + if (DEAD_LEAVE(bs->dead)) { + return; + } + } +} + +#ifdef BADVPN_USE_WINAPI + +static long get_wsa_events (int sock_events) +{ + long res = 0; + + if ((sock_events&BSOCKET_READ)) { + res |= FD_READ | FD_CLOSE; + } + if ((sock_events&BSOCKET_WRITE)) { + res |= FD_WRITE | FD_CLOSE; + } + if ((sock_events&BSOCKET_ACCEPT)) { + res |= FD_ACCEPT; + } + if ((sock_events&BSOCKET_CONNECT)) { + res |= FD_CONNECT; + } + + return res; +} + +static void handle_handler (BSocket *bs) +{ + // enumerate network events and reset event + WSANETWORKEVENTS events; + int res = WSAEnumNetworkEvents(bs->socket, bs->event, &events); + ASSERT_FORCE(res == 0) + + int returned_events = 0; + + if (bs->waitEvents&BSOCKET_READ) { + if ((events.lNetworkEvents&FD_READ) || (events.lNetworkEvents&FD_CLOSE)) { + returned_events |= BSOCKET_READ; + } + } + + if (bs->waitEvents&BSOCKET_WRITE) { + if ((events.lNetworkEvents&FD_WRITE) || (events.lNetworkEvents&FD_CLOSE)) { + returned_events |= BSOCKET_WRITE; + } + } + + if (bs->waitEvents&BSOCKET_ACCEPT) { + if (events.lNetworkEvents&FD_ACCEPT) { + returned_events |= BSOCKET_ACCEPT; + } + } + + if (bs->waitEvents&BSOCKET_CONNECT) { + if (events.lNetworkEvents&FD_CONNECT) { + // read connection attempt result + ASSERT(bs->connecting_status == 1) + bs->connecting_status = 2; + switch (events.iErrorCode[FD_CONNECT_BIT]) { + case 0: + bs->connecting_result = BSOCKET_ERROR_NONE; + break; + case WSAETIMEDOUT: + bs->connecting_result = BSOCKET_ERROR_CONNECTION_TIMED_OUT; + break; + case WSAECONNREFUSED: + bs->connecting_result = BSOCKET_ERROR_CONNECTION_REFUSED; + break; + default: + bs->connecting_result = BSOCKET_ERROR_UNKNOWN; + } + + returned_events |= BSOCKET_CONNECT; + } + } + + call_handlers(bs, returned_events); +} + +#else + +static int get_reactor_fd_events (int sock_events) +{ + int res = 0; + + if ((sock_events&BSOCKET_READ) || (sock_events&BSOCKET_ACCEPT)) { + res |= BREACTOR_READ; + } + if ((sock_events&BSOCKET_WRITE) || (sock_events&BSOCKET_CONNECT)) { + res |= BREACTOR_WRITE; + } + + return res; +} + +static void file_descriptor_handler (BSocket *bs, int events) +{ + int returned_events = 0; + + if ((bs->waitEvents&BSOCKET_READ) && (events&BREACTOR_READ)) { + returned_events |= BSOCKET_READ; + } + + if ((bs->waitEvents&BSOCKET_WRITE) && (events&BREACTOR_WRITE)) { + returned_events |= BSOCKET_WRITE; + } + + if ((bs->waitEvents&BSOCKET_ACCEPT) && (events&BREACTOR_READ)) { + returned_events |= BSOCKET_ACCEPT; + } + + if ((bs->waitEvents&BSOCKET_CONNECT) && (events&BREACTOR_WRITE)) { + returned_events |= BSOCKET_CONNECT; + + // read connection attempt result + ASSERT(bs->connecting_status == 1) + bs->connecting_status = 2; + int result; + socklen_t result_len = sizeof(result); + int res = getsockopt(bs->socket, SOL_SOCKET, SO_ERROR, &result, &result_len); + ASSERT_FORCE(res == 0) + switch (result) { + case 0: + bs->connecting_result = BSOCKET_ERROR_NONE; + break; + case ETIMEDOUT: + bs->connecting_result = BSOCKET_ERROR_CONNECTION_TIMED_OUT; + break; + case ECONNREFUSED: + bs->connecting_result = BSOCKET_ERROR_CONNECTION_REFUSED; + break; + default: + bs->connecting_result = BSOCKET_ERROR_UNKNOWN; + } + } + + call_handlers(bs, returned_events); +} + +#endif + +static int init_event_backend (BSocket *bs) +{ + #ifdef BADVPN_USE_WINAPI + if ((bs->event = WSACreateEvent()) == WSA_INVALID_EVENT) { + return 0; + } + BHandle_Init(&bs->bhandle, bs->event, (BHandle_handler)handle_handler, bs); + if (!BReactor_AddHandle(bs->bsys, &bs->bhandle)) { + ASSERT_FORCE(WSACloseEvent(bs->event)) + return 0; + } + BReactor_EnableHandle(bs->bsys, &bs->bhandle); + #else + BFileDescriptor_Init(&bs->fd, bs->socket, (BFileDescriptor_handler)file_descriptor_handler, bs); + if (!BReactor_AddFileDescriptor(bs->bsys, &bs->fd)) { + return 0; + } + #endif + + return 1; +} + +static void free_event_backend (BSocket *bs) +{ + #ifdef BADVPN_USE_WINAPI + BReactor_RemoveHandle(bs->bsys, &bs->bhandle); + ASSERT_FORCE(WSACloseEvent(bs->event)) + #else + BReactor_RemoveFileDescriptor(bs->bsys, &bs->fd); + #endif +} + +static void update_event_backend (BSocket *bs) +{ + #ifdef BADVPN_USE_WINAPI + ASSERT_FORCE(WSAEventSelect(bs->socket, bs->event, get_wsa_events(bs->waitEvents)) == 0) + #else + BReactor_SetFileDescriptorEvents(bs->bsys, &bs->fd, get_reactor_fd_events(bs->waitEvents)); + #endif +} + +static int limit_recv (BSocket *bs) +{ + if (bs->recv_max > 0) { + if (bs->recv_num >= bs->recv_max) { + return 1; + } + bs->recv_num++; + } + + return 0; +} + +int BSocket_GlobalInit (void) +{ + #ifdef BADVPN_USE_WINAPI + + WORD requested = MAKEWORD(2, 2); + WSADATA wsadata; + if (WSAStartup(requested, &wsadata) != 0) { + goto fail0; + } + if (wsadata.wVersion != requested) { + goto fail1; + } + + return 0; + +fail1: + WSACleanup(); +fail0: + return -1; + + #else + + return 0; + + #endif +} + +int BSocket_Init (BSocket *bs, BReactor *bsys, int domain, int type) +{ + // translate domain + int sys_domain; + switch (domain) { + case BADDR_TYPE_IPV4: + sys_domain = AF_INET; + break; + case BADDR_TYPE_IPV6: + sys_domain = AF_INET6; + break; + default: + ASSERT(0) + return -1; + } + + // translate type + int sys_type; + switch (type) { + case BSOCKET_TYPE_STREAM: + sys_type = SOCK_STREAM; + break; + case BSOCKET_TYPE_DGRAM: + sys_type = SOCK_DGRAM; + break; + default: + ASSERT(0) + return -1; + } + + // create socket + int fd = socket(sys_domain, sys_type, 0); + if (fd < 0) { + DEBUG("socket() failed"); + goto fail0; + } + + // set socket nonblocking + if (set_nonblocking(fd) != 0) { + DEBUG("set_nonblocking failed"); + goto fail1; + } + + // set pktinfo option + int have_pktinfo = 0; + if (type == BSOCKET_TYPE_DGRAM) { + switch (domain) { + case BADDR_TYPE_IPV4: + have_pktinfo = (set_pktinfo(fd) == 0); + break; + case BADDR_TYPE_IPV6: + have_pktinfo = (set_pktinfo6(fd) == 0); + break; + } + if (!have_pktinfo) { + DEBUG("WARNING: no pktinfo"); + } + } + + // initialize variables + DEAD_INIT(bs->dead); + bs->bsys = bsys; + bs->type = type; + bs->socket = fd; + bs->have_pktinfo = have_pktinfo; + bs->error = BSOCKET_ERROR_NONE; + init_handlers(bs); + bs->waitEvents = 0; + bs->connecting_status = 0; + bs->recv_max = BSOCKET_DEFAULT_RECV_MAX; + bs->recv_num = 0; + + // initialize event backend + if (!init_event_backend(bs)) { + DEBUG("WARNING: init_event_backend failed"); + goto fail1; + } + + // init debug object + DebugObject_Init(&bs->d_obj); + + return 0; + +fail1: + close_socket(fd); +fail0: + return -1; +} + +void BSocket_Free (BSocket *bs) +{ + // free debug object + DebugObject_Free(&bs->d_obj); + + // free event backend + free_event_backend(bs); + + // close socket + close_socket(bs->socket); + + // if we're being called indirectly from a socket event handler, + // allow the function invoking the handler to know that the socket was freed + DEAD_KILL(bs->dead); +} + +void BSocket_SetRecvMax (BSocket *bs, int max) +{ + ASSERT(max > 0 || max == -1) + + bs->recv_max = max; + bs->recv_num = 0; +} + +int BSocket_GetError (BSocket *bs) +{ + return bs->error; +} + +void BSocket_AddGlobalEventHandler (BSocket *bs, BSocket_handler handler, void *user) +{ + ASSERT(handler) + ASSERT(!bs->global_handler) + ASSERT(!bs->handlers[0]) + ASSERT(!bs->handlers[1]) + ASSERT(!bs->handlers[2]) + ASSERT(!bs->handlers[3]) + + bs->global_handler = handler; + bs->global_handler_user = user; +} + +void BSocket_RemoveGlobalEventHandler (BSocket *bs) +{ + ASSERT(bs->global_handler) + + bs->global_handler = NULL; + bs->waitEvents = 0; +} + +void BSocket_SetGlobalEvents (BSocket *bs, int events) +{ + ASSERT(bs->global_handler) + + // update events + bs->waitEvents = events; + + // give new events to event backend + update_event_backend(bs); +} + +void BSocket_AddEventHandler (BSocket *bs, uint8_t event, BSocket_handler handler, void *user) +{ + ASSERT(handler) + ASSERT(!bs->global_handler) + + // get index + int i = get_event_index(event); + + // event must not have handler + ASSERT(!bs->handlers[i]) + + // change handler + bs->handlers[i] = handler; + bs->handlers_user[i] = user; +} + +void BSocket_RemoveEventHandler (BSocket *bs, uint8_t event) +{ + // get table index + int i = get_event_index(event); + + // event must have handler + ASSERT(bs->handlers[i]) + + // disable event if enabled + if (bs->waitEvents&event) { + BSocket_DisableEvent(bs, event); + } + + // change handler + bs->handlers[i] = NULL; +} + +void BSocket_EnableEvent (BSocket *bs, uint8_t event) +{ + #ifndef NDEBUG + // check event and incompatible events + switch (event) { + case BSOCKET_READ: + case BSOCKET_WRITE: + ASSERT(!(bs->waitEvents&BSOCKET_ACCEPT)) + ASSERT(!(bs->waitEvents&BSOCKET_CONNECT)) + break; + case BSOCKET_ACCEPT: + ASSERT(!(bs->waitEvents&BSOCKET_READ)) + ASSERT(!(bs->waitEvents&BSOCKET_WRITE)) + ASSERT(!(bs->waitEvents&BSOCKET_CONNECT)) + break; + case BSOCKET_CONNECT: + ASSERT(!(bs->waitEvents&BSOCKET_READ)) + ASSERT(!(bs->waitEvents&BSOCKET_WRITE)) + ASSERT(!(bs->waitEvents&BSOCKET_ACCEPT)) + break; + default: + ASSERT(0) + break; + } + #endif + + // event must have handler + ASSERT(bs->handlers[get_event_index(event)]) + + // event must not be enabled + ASSERT(!(bs->waitEvents&event)) + + // update events + bs->waitEvents |= event; + + // give new events to event backend + update_event_backend(bs); +} + +void BSocket_DisableEvent (BSocket *bs, uint8_t event) +{ + // check event and get index + int index = get_event_index(event); + + // event must have handler + ASSERT(bs->handlers[index]) + + // event must be enabled + ASSERT(bs->waitEvents&event) + + // update events + bs->waitEvents &= ~event; + + // give new events to event backend + update_event_backend(bs); +} + +int BSocket_Connect (BSocket *bs, BAddr *addr) +{ + ASSERT(bs->connecting_status == 0) + + struct sys_addr sysaddr; + addr_socket_to_sys(&sysaddr, addr); + + if (connect(bs->socket, &sysaddr.addr.generic, sysaddr.len) < 0) { + #ifdef BADVPN_USE_WINAPI + switch (WSAGetLastError()) { + case WSAEWOULDBLOCK: + bs->connecting_status = 1; + bs->error = BSOCKET_ERROR_IN_PROGRESS; + return -1; + } + #else + switch (errno) { + case EINPROGRESS: + bs->connecting_status = 1; + bs->error = BSOCKET_ERROR_IN_PROGRESS; + return -1; + } + #endif + + bs->error = BSOCKET_ERROR_UNKNOWN; + return -1; + } + + bs->error = BSOCKET_ERROR_NONE; + return 0; +} + +int BSocket_GetConnectResult (BSocket *bs) +{ + ASSERT(bs->connecting_status == 2) + + bs->connecting_status = 0; + + return bs->connecting_result; +} + +int BSocket_Bind (BSocket *bs, BAddr *addr) +{ + struct sys_addr sysaddr; + addr_socket_to_sys(&sysaddr, addr); + + if (bs->type == BSOCKET_TYPE_STREAM) { + #ifdef BADVPN_USE_WINAPI + BOOL optval = TRUE; + #else + int optval = 1; + #endif + if (setsockopt(bs->socket, SOL_SOCKET, SO_REUSEADDR, (void *)&optval, sizeof(optval)) < 0) { + DEBUG("WARNING: setsockopt failed"); + } + } + + if (bind(bs->socket, &sysaddr.addr.generic, sysaddr.len) < 0) { + #ifdef BADVPN_USE_WINAPI + switch (WSAGetLastError()) { + case WSAEADDRNOTAVAIL: + bs->error = BSOCKET_ERROR_ADDRESS_NOT_AVAILABLE; + return -1; + case WSAEADDRINUSE: + bs->error = BSOCKET_ERROR_ADDRESS_IN_USE; + return -1; + } + #else + switch (errno) { + case EADDRNOTAVAIL: + bs->error = BSOCKET_ERROR_ADDRESS_NOT_AVAILABLE; + return -1; + case EADDRINUSE: + bs->error = BSOCKET_ERROR_ADDRESS_IN_USE; + return -1; + case EACCES: + bs->error = BSOCKET_ERROR_ACCESS_DENIED; + return -1; + } + #endif + + bs->error = BSOCKET_ERROR_UNKNOWN; + return -1; + } + + bs->error = BSOCKET_ERROR_NONE; + return 0; +} + +int BSocket_Listen (BSocket *bs, int backlog) +{ + if (backlog < 0) { + backlog = BSOCKET_DEFAULT_BACKLOG; + } + + if (listen(bs->socket, backlog) < 0) { + #ifdef BADVPN_USE_WINAPI + switch (WSAGetLastError()) { + case WSAEADDRINUSE: + bs->error = BSOCKET_ERROR_ADDRESS_IN_USE; + return -1; + } + #else + switch (errno) { + case EADDRINUSE: + bs->error = BSOCKET_ERROR_ADDRESS_IN_USE; + return -1; + } + #endif + + bs->error = BSOCKET_ERROR_UNKNOWN; + return -1; + } + + bs->error = BSOCKET_ERROR_NONE; + return 0; +} + +int BSocket_Accept (BSocket *bs, BSocket *newsock, BAddr *addr) +{ + struct sys_addr sysaddr; + sysaddr.len = sizeof(sysaddr.addr); + + int fd = accept(bs->socket, &sysaddr.addr.generic, &sysaddr.len); + if (fd < 0) { + #ifdef BADVPN_USE_WINAPI + switch (WSAGetLastError()) { + case WSAEWOULDBLOCK: + bs->error = BSOCKET_ERROR_LATER; + return -1; + } + #else + switch (errno) { + case EAGAIN: + #if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: + #endif + bs->error = BSOCKET_ERROR_LATER; + return -1; + } + #endif + + bs->error = BSOCKET_ERROR_UNKNOWN; + return -1; + } + + if (!newsock) { + close_socket(fd); + } else { + // set nonblocking + if (set_nonblocking(fd) != 0) { + DEBUG("WARNING: set_nonblocking failed"); + goto fail0; + } + + DEAD_INIT(newsock->dead); + newsock->bsys = bs->bsys; + newsock->type = bs->type; + newsock->socket = fd; + newsock->have_pktinfo = 0; + newsock->error = BSOCKET_ERROR_NONE; + init_handlers(newsock); + newsock->waitEvents = 0; + newsock->connecting_status = 0; + newsock->recv_max = BSOCKET_DEFAULT_RECV_MAX; + newsock->recv_num = 0; + + if (!init_event_backend(newsock)) { + DEBUG("WARNING: init_event_backend failed"); + goto fail0; + } + + // init debug object + DebugObject_Init(&newsock->d_obj); + } + + // return client addres + if (addr) { + addr_sys_to_socket(addr, &sysaddr); + } + + bs->error = BSOCKET_ERROR_NONE; + return 0; + +fail0: + close_socket(fd); + bs->error = BSOCKET_ERROR_UNKNOWN; + return -1; +} + +int BSocket_Send (BSocket *bs, uint8_t *data, int len) +{ + ASSERT(len >= 0) + + #ifdef BADVPN_USE_WINAPI + int flags = 0; + #else + int flags = MSG_NOSIGNAL; + #endif + + int bytes = send(bs->socket, data, len, flags); + if (bytes < 0) { + #ifdef BADVPN_USE_WINAPI + switch (WSAGetLastError()) { + case WSAEWOULDBLOCK: + bs->error = BSOCKET_ERROR_LATER; + return -1; + case WSAECONNRESET: + if (bs->type == BSOCKET_TYPE_DGRAM) { + bs->error = BSOCKET_ERROR_CONNECTION_REFUSED; + } else { + bs->error = BSOCKET_ERROR_CONNECTION_RESET; + } + return -1; + } + #else + switch (errno) { + case EAGAIN: + #if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: + #endif + bs->error = BSOCKET_ERROR_LATER; + return -1; + case ECONNREFUSED: + bs->error = BSOCKET_ERROR_CONNECTION_REFUSED; + return -1; + case ECONNRESET: + bs->error = BSOCKET_ERROR_CONNECTION_RESET; + return -1; + } + #endif + + bs->error = BSOCKET_ERROR_UNKNOWN; + return -1; + } + + bs->error = BSOCKET_ERROR_NONE; + return bytes; +} + +int BSocket_Recv (BSocket *bs, uint8_t *data, int len) +{ + ASSERT(len >= 0) + + if (limit_recv(bs)) { + bs->error = BSOCKET_ERROR_LATER; + return -1; + } + + int bytes = recv(bs->socket, data, len, 0); + if (bytes < 0) { + #ifdef BADVPN_USE_WINAPI + switch (WSAGetLastError()) { + case WSAEWOULDBLOCK: + bs->error = BSOCKET_ERROR_LATER; + return -1; + case WSAECONNRESET: + if (bs->type == BSOCKET_TYPE_DGRAM) { + bs->error = BSOCKET_ERROR_CONNECTION_REFUSED; + } else { + bs->error = BSOCKET_ERROR_CONNECTION_RESET; + } + return -1; + } + #else + switch (errno) { + case EAGAIN: + #if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: + #endif + bs->error = BSOCKET_ERROR_LATER; + return -1; + case ECONNREFUSED: + bs->error = BSOCKET_ERROR_CONNECTION_REFUSED; + return -1; + case ECONNRESET: + bs->error = BSOCKET_ERROR_CONNECTION_RESET; + return -1; + } + #endif + + bs->error = BSOCKET_ERROR_UNKNOWN; + return -1; + } + + bs->error = BSOCKET_ERROR_NONE; + return bytes; +} + +int BSocket_SendTo (BSocket *bs, uint8_t *data, int len, BAddr *addr) +{ + ASSERT(len >= 0) + ASSERT(addr) + + struct sys_addr remote_sysaddr; + addr_socket_to_sys(&remote_sysaddr, addr); + + #ifdef BADVPN_USE_WINAPI + int flags = 0; + #else + int flags = MSG_NOSIGNAL; + #endif + + int bytes = sendto(bs->socket, data, len, flags, &remote_sysaddr.addr.generic, remote_sysaddr.len); + if (bytes < 0) { + #ifdef BADVPN_USE_WINAPI + switch (WSAGetLastError()) { + case WSAEWOULDBLOCK: + bs->error = BSOCKET_ERROR_LATER; + return -1; + case WSAECONNRESET: + if (bs->type == BSOCKET_TYPE_DGRAM) { + bs->error = BSOCKET_ERROR_CONNECTION_REFUSED; + } else { + bs->error = BSOCKET_ERROR_CONNECTION_RESET; + } + return -1; + } + #else + switch (errno) { + case EAGAIN: + #if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: + #endif + bs->error = BSOCKET_ERROR_LATER; + return -1; + case ECONNREFUSED: + bs->error = BSOCKET_ERROR_CONNECTION_REFUSED; + return -1; + case ECONNRESET: + bs->error = BSOCKET_ERROR_CONNECTION_RESET; + return -1; + } + #endif + + bs->error = BSOCKET_ERROR_UNKNOWN; + return -1; + } + + bs->error = BSOCKET_ERROR_NONE; + return bytes; +} + +int BSocket_RecvFrom (BSocket *bs, uint8_t *data, int len, BAddr *addr) +{ + ASSERT(len >= 0) + ASSERT(addr) + + if (limit_recv(bs)) { + bs->error = BSOCKET_ERROR_LATER; + return -1; + } + + struct sys_addr remote_sysaddr; + remote_sysaddr.len = sizeof(remote_sysaddr.addr); + + int bytes = recvfrom(bs->socket, data, len, 0, &remote_sysaddr.addr.generic, &remote_sysaddr.len); + if (bytes < 0) { + #ifdef BADVPN_USE_WINAPI + switch (WSAGetLastError()) { + case WSAEWOULDBLOCK: + bs->error = BSOCKET_ERROR_LATER; + return -1; + case WSAECONNRESET: + if (bs->type == BSOCKET_TYPE_DGRAM) { + bs->error = BSOCKET_ERROR_CONNECTION_REFUSED; + } else { + bs->error = BSOCKET_ERROR_CONNECTION_RESET; + } + return -1; + } + #else + switch (errno) { + case EAGAIN: + #if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: + #endif + bs->error = BSOCKET_ERROR_LATER; + return -1; + case ECONNREFUSED: + bs->error = BSOCKET_ERROR_CONNECTION_REFUSED; + return -1; + case ECONNRESET: + bs->error = BSOCKET_ERROR_CONNECTION_RESET; + return -1; + } + #endif + + bs->error = BSOCKET_ERROR_UNKNOWN; + return -1; + } + + addr_sys_to_socket(addr, &remote_sysaddr); + + bs->error = BSOCKET_ERROR_NONE; + return bytes; +} + +int BSocket_SendToFrom (BSocket *bs, uint8_t *data, int len, BAddr *addr, BIPAddr *local_addr) +{ + ASSERT(len >= 0) + ASSERT(addr) + ASSERT(local_addr) + + if (!bs->have_pktinfo) { + return BSocket_SendTo(bs, data, len, addr); + } + + #ifdef BADVPN_USE_WINAPI + + // obtain WSASendMsg + GUID guid = WSAID_WSASENDMSG; + LPFN_WSASENDMSG WSASendMsg; + DWORD out_bytes; + if (WSAIoctl(bs->socket, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &WSASendMsg, sizeof(WSASendMsg), &out_bytes, NULL, NULL) != 0) { + return BSocket_SendTo(bs, data, len, addr); + } + + #endif + + struct sys_addr remote_sysaddr; + addr_socket_to_sys(&remote_sysaddr, addr); + + #ifdef BADVPN_USE_WINAPI + + WSABUF buf; + buf.len = len; + buf.buf = data; + + union { + char in[WSA_CMSG_SPACE(sizeof(struct in_pktinfo))]; + char in6[WSA_CMSG_SPACE(sizeof(struct in6_pktinfo))]; + } cdata; + + WSAMSG msg; + memset(&msg, 0, sizeof(msg)); + msg.name = &remote_sysaddr.addr.generic; + msg.namelen = remote_sysaddr.len; + msg.lpBuffers = &buf; + msg.dwBufferCount = 1; + msg.Control.buf = (char *)&cdata; + msg.Control.len = sizeof(cdata); + + int sum = 0; + + WSACMSGHDR *cmsg = WSA_CMSG_FIRSTHDR(&msg); + + switch (local_addr->type) { + case BADDR_TYPE_NONE: + break; + case BADDR_TYPE_IPV4: { + memset(cmsg, 0, WSA_CMSG_SPACE(sizeof(struct in_pktinfo))); + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_PKTINFO; + cmsg->cmsg_len = WSA_CMSG_LEN(sizeof(struct in_pktinfo)); + struct in_pktinfo *pktinfo = (struct in_pktinfo *)WSA_CMSG_DATA(cmsg); + pktinfo->ipi_addr.s_addr = local_addr->ipv4; + sum += WSA_CMSG_SPACE(sizeof(struct in_pktinfo)); + } break; + case BADDR_TYPE_IPV6: { + memset(cmsg, 0, WSA_CMSG_SPACE(sizeof(struct in6_pktinfo))); + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + cmsg->cmsg_len = WSA_CMSG_LEN(sizeof(struct in6_pktinfo)); + struct in6_pktinfo *pktinfo = (struct in6_pktinfo *)WSA_CMSG_DATA(cmsg); + memcpy(pktinfo->ipi6_addr.s6_addr, local_addr->ipv6, 16); + sum += WSA_CMSG_SPACE(sizeof(struct in6_pktinfo)); + } break; + default: + ASSERT(0); + } + + msg.Control.len = sum; + + DWORD bytes; + if (WSASendMsg(bs->socket, &msg, 0, &bytes, NULL, NULL) != 0) { + switch (WSAGetLastError()) { + case WSAEWOULDBLOCK: + bs->error = BSOCKET_ERROR_LATER; + return -1; + case WSAECONNRESET: + if (bs->type == BSOCKET_TYPE_DGRAM) { + bs->error = BSOCKET_ERROR_CONNECTION_REFUSED; + } else { + bs->error = BSOCKET_ERROR_CONNECTION_RESET; + } + return -1; + default: + bs->error = BSOCKET_ERROR_UNKNOWN; + return -1; + } + } + + #else + + struct iovec iov; + iov.iov_base = data; + iov.iov_len = len; + + union { + char in[CMSG_SPACE(sizeof(struct in_pktinfo))]; + char in6[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + } cdata; + + struct msghdr msg; + memset(&msg, 0, sizeof(msg)); + msg.msg_name = &remote_sysaddr.addr.generic; + msg.msg_namelen = remote_sysaddr.len; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = &cdata; + msg.msg_controllen = sizeof(cdata); + + int sum = 0; + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + + switch (local_addr->type) { + case BADDR_TYPE_NONE: + break; + case BADDR_TYPE_IPV4: { + memset(cmsg, 0, CMSG_SPACE(sizeof(struct in_pktinfo))); + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + struct in_pktinfo *pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsg); + pktinfo->ipi_spec_dst.s_addr = local_addr->ipv4; + sum += CMSG_SPACE(sizeof(struct in_pktinfo)); + } break; + case BADDR_TYPE_IPV6: { + memset(cmsg, 0, CMSG_SPACE(sizeof(struct in6_pktinfo))); + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); + struct in6_pktinfo *pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg); + memcpy(pktinfo->ipi6_addr.s6_addr, local_addr->ipv6, 16); + sum += CMSG_SPACE(sizeof(struct in6_pktinfo)); + } break; + default: + ASSERT(0); + } + + msg.msg_controllen = sum; + + int bytes = sendmsg(bs->socket, &msg, MSG_NOSIGNAL); + if (bytes < 0) { + switch (errno) { + case EAGAIN: + #if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: + #endif + bs->error = BSOCKET_ERROR_LATER; + return -1; + case ECONNREFUSED: + bs->error = BSOCKET_ERROR_CONNECTION_REFUSED; + return -1; + case ECONNRESET: + bs->error = BSOCKET_ERROR_CONNECTION_RESET; + return -1; + default: + bs->error = BSOCKET_ERROR_UNKNOWN; + return -1; + } + } + + #endif + + bs->error = BSOCKET_ERROR_NONE; + return bytes; +} + +static int recvfromto_fallback (BSocket *bs, uint8_t *data, int len, BAddr *addr, BIPAddr *local_addr) +{ + int res = BSocket_RecvFrom(bs, data, len, addr); + if (res >= 0) { + BIPAddr_InitInvalid(local_addr); + } + return res; +} + +int BSocket_RecvFromTo (BSocket *bs, uint8_t *data, int len, BAddr *addr, BIPAddr *local_addr) +{ + ASSERT(len >= 0) + ASSERT(addr) + ASSERT(local_addr) + + if (!bs->have_pktinfo) { + return recvfromto_fallback(bs, data, len, addr, local_addr); + } + + #ifdef BADVPN_USE_WINAPI + + // obtain WSARecvMsg + GUID guid = WSAID_WSARECVMSG; + LPFN_WSARECVMSG WSARecvMsg; + DWORD out_bytes; + if (WSAIoctl(bs->socket, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &WSARecvMsg, sizeof(WSARecvMsg), &out_bytes, NULL, NULL) != 0) { + return recvfromto_fallback(bs, data, len, addr, local_addr); + } + + #endif + + if (limit_recv(bs)) { + bs->error = BSOCKET_ERROR_LATER; + return -1; + } + + struct sys_addr remote_sysaddr; + remote_sysaddr.len = sizeof(remote_sysaddr.addr); + + #ifdef BADVPN_USE_WINAPI + + WSABUF buf; + buf.len = len; + buf.buf = data; + + union { + char in[WSA_CMSG_SPACE(sizeof(struct in_pktinfo))]; + char in6[WSA_CMSG_SPACE(sizeof(struct in6_pktinfo))]; + } cdata; + + WSAMSG msg; + memset(&msg, 0, sizeof(msg)); + msg.name = &remote_sysaddr.addr.generic; + msg.namelen = remote_sysaddr.len; + msg.lpBuffers = &buf; + msg.dwBufferCount = 1; + msg.Control.buf = (char *)&cdata; + msg.Control.len = sizeof(cdata); + + DWORD bytes; + if (WSARecvMsg(bs->socket, &msg, &bytes, NULL, NULL) != 0) { + switch (WSAGetLastError()) { + case WSAEWOULDBLOCK: + bs->error = BSOCKET_ERROR_LATER; + return -1; + case WSAECONNRESET: + if (bs->type == BSOCKET_TYPE_DGRAM) { + bs->error = BSOCKET_ERROR_CONNECTION_REFUSED; + } else { + bs->error = BSOCKET_ERROR_CONNECTION_RESET; + } + return -1; + default: + bs->error = BSOCKET_ERROR_UNKNOWN; + return -1; + } + } + + remote_sysaddr.len = msg.namelen; + + #else + + struct iovec iov; + iov.iov_base = data; + iov.iov_len = len; + + union { + char in[CMSG_SPACE(sizeof(struct in_pktinfo))]; + char in6[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + } cdata; + + struct msghdr msg; + memset(&msg, 0, sizeof(msg)); + msg.msg_name = &remote_sysaddr.addr.generic; + msg.msg_namelen = remote_sysaddr.len; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = &cdata; + msg.msg_controllen = sizeof(cdata); + + int bytes = recvmsg(bs->socket, &msg, 0); + if (bytes < 0) { + switch (errno) { + case EAGAIN: + #if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: + #endif + bs->error = BSOCKET_ERROR_LATER; + return -1; + case ECONNREFUSED: + bs->error = BSOCKET_ERROR_CONNECTION_REFUSED; + return -1; + case ECONNRESET: + bs->error = BSOCKET_ERROR_CONNECTION_RESET; + return -1; + default: + bs->error = BSOCKET_ERROR_UNKNOWN; + return -1; + } + } + + remote_sysaddr.len = msg.msg_namelen; + + #endif + + addr_sys_to_socket(addr, &remote_sysaddr); + BIPAddr_InitInvalid(local_addr); + + #ifdef BADVPN_USE_WINAPI + + WSACMSGHDR *cmsg; + for (cmsg = WSA_CMSG_FIRSTHDR(&msg); cmsg; cmsg = WSA_CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) { + struct in_pktinfo *pktinfo = (struct in_pktinfo *)WSA_CMSG_DATA(cmsg); + BIPAddr_InitIPv4(local_addr, pktinfo->ipi_addr.s_addr); + } + else if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) { + struct in6_pktinfo *pktinfo = (struct in6_pktinfo *)WSA_CMSG_DATA(cmsg); + BIPAddr_InitIPv6(local_addr, pktinfo->ipi6_addr.s6_addr); + } + } + + #else + + struct cmsghdr *cmsg; + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) { + struct in_pktinfo *pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsg); + BIPAddr_InitIPv4(local_addr, pktinfo->ipi_addr.s_addr); + } + else if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) { + struct in6_pktinfo *pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg); + BIPAddr_InitIPv6(local_addr, pktinfo->ipi6_addr.s6_addr); + } + } + + #endif + + bs->error = BSOCKET_ERROR_NONE; + return bytes; +} + +int BSocket_GetPeerName (BSocket *bs, BAddr *addr) +{ + ASSERT(addr) + + struct sys_addr sysaddr; + sysaddr.len = sizeof(sysaddr.addr); + + if (getpeername(bs->socket, &sysaddr.addr.generic, &sysaddr.len) < 0) { + bs->error = BSOCKET_ERROR_UNKNOWN; + return -1; + } + + addr_sys_to_socket(addr, &sysaddr); + + bs->error = BSOCKET_ERROR_NONE; + return 0; +} diff --git a/system/BSocket.h b/system/BSocket.h new file mode 100644 index 000000000..85d6508b5 --- /dev/null +++ b/system/BSocket.h @@ -0,0 +1,416 @@ +/** + * @file BSocket.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * A wrapper around OS-specific socket functions, integrated into + * the event system. + */ + +#ifndef BADVPN_SYSTEM_BSOCKET_H +#define BADVPN_SYSTEM_BSOCKET_H + +#include +#include +#include +#include +#include + +// errors +#define BSOCKET_ERROR_NONE 0 +#define BSOCKET_ERROR_UNKNOWN 1 +#define BSOCKET_ERROR_LATER 2 +#define BSOCKET_ERROR_IN_PROGRESS 3 +#define BSOCKET_ERROR_ACCESS_DENIED 4 +#define BSOCKET_ERROR_ADDRESS_NOT_AVAILABLE 5 +#define BSOCKET_ERROR_ADDRESS_IN_USE 6 +#define BSOCKET_ERROR_CONNECTION_REFUSED 7 +#define BSOCKET_ERROR_CONNECTION_TIMED_OUT 8 +#define BSOCKET_ERROR_CONNECTION_RESET 9 + +// socket types +#define BSOCKET_TYPE_STREAM 1 +#define BSOCKET_TYPE_DGRAM 2 + +// socket events +#define BSOCKET_READ 1 +#define BSOCKET_WRITE 2 +#define BSOCKET_ACCEPT 4 +#define BSOCKET_CONNECT 8 + +// default backlog if backlog is <0 +#define BSOCKET_DEFAULT_BACKLOG 128 + +// default limit for number of consecutive receive operations +// must be -1 (no limit) or >0 +#define BSOCKET_DEFAULT_RECV_MAX 2 + +struct BSocket_t; + +typedef void (*BSocket_handler) (void *user, int event); + +/** + * A wrapper around OS-specific socket functions, integrated into + * the event system. + * + * To simplify implementation, most functions just call the corresponding + * socket function. Only required and most common errors are translated. + */ +typedef struct BSocket_t { + DebugObject d_obj; + dead_t dead; + BReactor *bsys; + int type; + int socket; + int have_pktinfo; + int error; + BSocket_handler global_handler; + void *global_handler_user; + BSocket_handler handlers[4]; + void *handlers_user[4]; + uint8_t waitEvents; + int connecting_status; // 0 not connecting, 1 connecting, 2 finished + int connecting_result; + int recv_max; + int recv_num; + + #ifdef BADVPN_USE_WINAPI + WSAEVENT event; + BHandle bhandle; + #else + BFileDescriptor fd; + #endif +} BSocket; + +/** + * Initializes global socket data. + * This must be called once in program before sockets are used. + * + * @return 0 for success, -1 for failure + */ +int BSocket_GlobalInit (void) WARN_UNUSED; + +/** + * Initializes a socket. + * + * @param bs the object + * @param bsys {@link BReactor} to operate in + * @param domain domain, same as address type, must be one of BADDR_TYPE_IPV4 and BADDR_TYPE_IPV6 + * @param type socket type, must be one of BSOCKET_TYPE_STREAM and BSOCKET_TYPE_DGRAM + * @return 0 for success, + * -1 for failure + */ +int BSocket_Init (BSocket *bs, BReactor *bsys, int domain, int type) WARN_UNUSED; + +/** + * Frees a socket. + * + * @param bs the object + */ +void BSocket_Free (BSocket *bs); + +/** + * Sets the maximum number of consecutive receive operations. + * This limit prevents starvation that might occur when data is being + * received on a socket faster than in can be processed. + * The default limit is BSOCKET_DEFAULT_RECV_MAX. + * + * @param bs the object + * @param max number of consecutive receive operations allowed. Muse be >0, + * or -1 for no limit. + */ +void BSocket_SetRecvMax (BSocket *bs, int max); + +/** + * Returns the socket's current error code. + * + * @param bs the object + */ +int BSocket_GetError (BSocket *bs); + +/** + * Registers a socket-global event handler. + * The socket-global event handler must not be registered. + * No event-specific handlers must be registered. + * When the handler is invoked, it is passed a bitmask of events + * that occured, instead of a single event. + * + * @param bs the object + * @param handler event handler + * @param user value to be passed to event handler + */ +void BSocket_AddGlobalEventHandler (BSocket *bs, BSocket_handler handler, void *user); + +/** + * Unregisters the socket-global event handler. + * The socket-global event handler must be registered. + * + * @param bs the object + * @param handler event handler + * @param user value to be passed to event handler + */ +void BSocket_RemoveGlobalEventHandler (BSocket *bs); + +/** + * Sets events for the socket-global event handler. + * The socket-global event handler must be registered. + * + * @param bs the object + * @param events bitmask containing socket events the user is interested in + */ +void BSocket_SetGlobalEvents (BSocket *bs, int events); + +/** + * Registers an event handler for a socket event. + * When the handler is registered, the corresponding event will + * initially be disabled. + * The event must be valid and must not have a handler. + * The socket-global event handler must not be registered. + * + * @param bs the object + * @param event event to register handler for + * @param handler event handler + * @param user value to be passed to event handler + */ +void BSocket_AddEventHandler (BSocket *bs, uint8_t event, BSocket_handler handler, void *user); + +/** + * Unregisters an event handler for a socket event. + * The event must be valid and must have a handler. + * + * @param bs the object + * @param event event to unregister handler for + */ +void BSocket_RemoveEventHandler (BSocket *bs, uint8_t event); + +/** + * Enables a socket event. + * The event must be valid, must not be enabled, and must have a handler. + * + * @param bs the object + * @param event event to enable + */ +void BSocket_EnableEvent (BSocket *bs, uint8_t event); + +/** + * Disables a socket event. + * The event must be valid, must be enabled, and must have a handler. + * + * @param bs the object + * @param event event to enable + */ +void BSocket_DisableEvent (BSocket *bs, uint8_t event); + +/** + * Connects the socket to the specifed address, or starts a connection attempt. + * + * There must be no pending connection attempt. + * + * For stream sockets, the user will have to wait for the connection result. See the + * BSOCKET_ERROR_IN_PROGRESS error for details. + * + * Datagram sockets can be connected at any time, since connecting such a socket only means + * specifying an addres where datagrams will be sent and received from. + * An associated address can be removed by specifying a BADDR_TYPE_NONE address. + * + * @param bs the object + * @param addr remote address. + * @return 0 for immediate success, + * -1 for failure, where the error code can be: + * - BSOCKET_ERROR_IN_PROGRESS the socket is a stream socket and the connection attempt has started. + * The user should wait for the BSOCKET_CONNECT event and obtain the + * result of attempt with {@link BSocket_GetConnectResult}. + * - BSOCKET_ERROR_UNKNOWN unhandled error + */ +int BSocket_Connect (BSocket *bs, BAddr *addr) WARN_UNUSED; + +/** + * Retreives the result of a connection attempt. + * The socket must have completed a connection attempt whose result has not yet been retrieved. + * + * @param bs the object + * @return connection attempt result. Possible values: + * - 0 connection successful + * - BSOCKET_ERROR_CONNECTION_TIMED_OUT timeout while attempting connection + * - BSOCKET_ERROR_CONNECTION_REFUSED no one is listening on the remote address + * - BSOCKET_ERROR_UNKNOWN unhandled error + */ +int BSocket_GetConnectResult (BSocket *bs); + +/** + * Binds the socket to the specified address. + * + * @param bs the object + * @param addr local address. Must not be invalid. + * @return 0 for success, + * -1 for failure, where the error code can be: + * - BSOCKET_ERROR_ADDRESS_NOT_AVAILABLE the address is not a local address + * - BSOCKET_ERROR_ADDRESS_IN_USE the address is already in use + * - BSOCKET_ERROR_ACCESS_DENIED the address is protected + * - BSOCKET_ERROR_UNKNOWN unhandled error + */ +int BSocket_Bind (BSocket *bs, BAddr *addr) WARN_UNUSED; + +/** + * Marks the socket as a listening socket. + * + * @param bs the object + * @param backlog whatever this means in the system's listen() function. If it's + * negative, BSOCKET_DEFAULT_BACKLOG will be used. + * @return 0 for success, + * -1 for failure, where the error code can be: + * - BSOCKET_ERROR_ADDRESS_IN_USE the address is already in use + * - BSOCKET_ERROR_UNKNOWN unhandled error + */ +int BSocket_Listen (BSocket *bs, int backlog) WARN_UNUSED; + +/** + * Accepts a connection on a listening socket. + * + * @param bs the object + * @param newsock on success, the new socket will be stored here. If it is NULL and a connection + * was accepted, it is closed immediately (but the function succeeds). + * @param addr if not NULL, the client address will be stored here on success + * @return 0 for success, + * -1 for failure, where the error code can be: + * - BSOCKET_ERROR_LATER a connection cannot be accepted at the moment + * - BSOCKET_ERROR_UNKNOWN unhandled error + */ +int BSocket_Accept (BSocket *bs, BSocket *newsock, BAddr *addr) WARN_UNUSED; + +/** + * Sends data on a socket. + * + * @param bs the object + * @param data buffer to read data from + * @param len amount of data. Must be >=0. + * @return non-negative value for amount of data sent, + * -1 for failure, where the error code can be: + * - BSOCKET_ERROR_LATER no data can be sent at the moment + * - BSOCKET_ERROR_CONNECTION_REFUSED the remote host refused to allow the network connection. + * For UDP sockets, this means the remote sent an ICMP Port Unreachable packet. + * - BSOCKET_ERROR_CONNECTION_RESET connection was reset by the remote peer + * - BSOCKET_ERROR_UNKNOWN unhandled error + */ +int BSocket_Send (BSocket *bs, uint8_t *data, int len) WARN_UNUSED; + +/** + * Receives data on a socket. + * + * @param bs the object + * @param data buffer to write data to + * @param len maximum amount of data to read. Must be >=0. + * @return - non-negative value for amount of data read; on stream sockets the value 0 + * means that the peer has shutdown the connection gracefully + * - -1 for failure, where the error code can be: + * - BSOCKET_ERROR_LATER no data can be read at the moment + * - BSOCKET_ERROR_CONNECTION_REFUSED the remote host refused to allow the network connection. + * For UDP sockets, this means the remote sent an ICMP Port Unreachable packet. + * - BSOCKET_ERROR_CONNECTION_RESET connection was reset by the remote peer + * - BSOCKET_ERROR_UNKNOWN unhandled error + */ +int BSocket_Recv (BSocket *bs, uint8_t *data, int len) WARN_UNUSED; + +/** + * Sends a datagram on a datagram socket to the specified address. + * + * @param bs the object + * @param data buffer to read data from + * @param len amount of data. Must be >=0. + * @param addr remote address. Must be valid. + * @return non-negative value for amount of data sent, + * -1 for failure, where the error code can be: + * - BSOCKET_ERROR_LATER no data can be sent at the moment + * - BSOCKET_ERROR_CONNECTION_REFUSED the remote host refused to allow the network connection. + * For UDP sockets, this means the remote sent an ICMP Port Unreachable packet. + * - BSOCKET_ERROR_CONNECTION_RESET connection was reset by the remote peer + * - BSOCKET_ERROR_UNKNOWN unhandled error + */ +int BSocket_SendTo (BSocket *bs, uint8_t *data, int len, BAddr *addr) WARN_UNUSED; + +/** + * Receives a datagram on a datagram socket and returns the sender address. + * + * @param bs the object + * @param data buffer to write data to + * @param len maximum amount of data to read. Must be >=0. + * @param addr the sender address will be stored here on success. Must not be NULL. + * @return - non-negative value for amount of data read; on stream sockets the value 0 + * means that the peer has shutdown the connection gracefully + * - -1 for failure, where the error code can be: + * - BSOCKET_ERROR_LATER no data can be read at the moment + * - BSOCKET_ERROR_CONNECTION_REFUSED a remote host refused to allow the network connection. + * For UDP sockets, this means the remote sent an ICMP Port Unreachable packet. + * - BSOCKET_ERROR_CONNECTION_RESET connection was reset by the remote peer + * - BSOCKET_ERROR_UNKNOWN unhandled error + */ +int BSocket_RecvFrom (BSocket *bs, uint8_t *data, int len, BAddr *addr) WARN_UNUSED; + +/** + * Sends a datagram on a datagram socket to the specified address + * from the specified local source address. + * + * @param bs the object + * @param data buffer to read data from + * @param len amount of data. Must be >=0. + * @param addr remote address. Must be valid. + * @param local_addr source address. Must not be NULL, but may be invalid. + * @return non-negative value for amount of data sent, + * -1 for failure, where the error code can be: + * - BSOCKET_ERROR_LATER no data can be sent at the moment + * - BSOCKET_ERROR_CONNECTION_REFUSED the remote host refused to allow the network connection. + * For UDP sockets, this means the remote sent an ICMP Port Unreachable packet. + * - BSOCKET_ERROR_CONNECTION_RESET connection was reset by the remote peer + * - BSOCKET_ERROR_UNKNOWN unhandled error + */ +int BSocket_SendToFrom (BSocket *bs, uint8_t *data, int len, BAddr *addr, BIPAddr *local_addr) WARN_UNUSED; + +/** + * Receives a datagram on a datagram socket and returns the sender address + * and the local destination address. + * + * @param bs the object + * @param data buffer to write data to + * @param len maximum amount of data to read. Must be >=0. + * @param addr the sender address will be stored here on success. Must not be NULL. + * @param local_addr the destination address will be stored here on success. Must not be NULL. + * Returned address will be invalid if it could not be determined. + * @return - non-negative value for amount of data read; on stream sockets the value 0 + * means that the peer has shutdown the connection gracefully + * - -1 for failure, where the error code can be: + * - BSOCKET_ERROR_LATER no data can be read at the moment + * - BSOCKET_ERROR_CONNECTION_REFUSED a remote host refused to allow the network connection. + * For UDP sockets, this means the remote sent an ICMP Port Unreachable packet. + * - BSOCKET_ERROR_CONNECTION_RESET connection was reset by the remote peer + * - BSOCKET_ERROR_UNKNOWN unhandled error + */ +int BSocket_RecvFromTo (BSocket *bs, uint8_t *data, int len, BAddr *addr, BIPAddr *local_addr) WARN_UNUSED; + +/** + * Returns the address of the remote peer. + * + * @param bs the object + * @param addr where to store address. Must not be NULL. + * @return 0 for success, -1 for failure + */ +int BSocket_GetPeerName (BSocket *bs, BAddr *addr) WARN_UNUSED; + +#endif diff --git a/system/BTime.c b/system/BTime.c new file mode 100644 index 000000000..4bedafc14 --- /dev/null +++ b/system/BTime.c @@ -0,0 +1,29 @@ +/** + * @file BTime.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +struct _BTime_global btime_global = { + #ifndef NDEBUG + .initialized = 0, + #endif +}; diff --git a/system/BTime.h b/system/BTime.h new file mode 100644 index 000000000..c3efe906c --- /dev/null +++ b/system/BTime.h @@ -0,0 +1,87 @@ +/** + * @file BTime.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * System time abstraction used by {@link BReactor}. + */ + +#ifndef BADVPN_SYSTEM_BTIME_H +#define BADVPN_SYSTEM_BTIME_H + +#ifdef BADVPN_USE_WINAPI +#include +#else +#include +#endif + +#include + +#include + +typedef int64_t btime_t; + +struct _BTime_global { + #ifndef NDEBUG + int initialized; // initialized statically + #endif + #ifdef BADVPN_USE_WINAPI + LARGE_INTEGER start_time; + #endif +}; + +extern struct _BTime_global btime_global; + +static void BTime_Init (void) +{ + ASSERT(!btime_global.initialized) + + #ifdef BADVPN_USE_WINAPI + ASSERT_FORCE(QueryPerformanceCounter(&btime_global.start_time)) + #endif + + #ifndef NDEBUG + btime_global.initialized = 1; + #endif +} + +static btime_t btime_gettime () +{ + ASSERT(btime_global.initialized) + + #ifdef BADVPN_USE_WINAPI + + LARGE_INTEGER count; + LARGE_INTEGER freq; + ASSERT_FORCE(QueryPerformanceCounter(&count)) + ASSERT_FORCE(QueryPerformanceFrequency(&freq)) + return (((count.QuadPart - btime_global.start_time.QuadPart) * 1000) / freq.QuadPart); + + #else + + struct timespec ts; + ASSERT_FORCE(clock_gettime(CLOCK_MONOTONIC, &ts) == 0) + return ((int64_t)ts.tv_sec * 1000 + (int64_t)ts.tv_nsec/1000000); + + #endif +} + +#endif diff --git a/system/CMakeLists.txt b/system/CMakeLists.txt new file mode 100644 index 000000000..331b61c5b --- /dev/null +++ b/system/CMakeLists.txt @@ -0,0 +1,21 @@ +set(BSYSTEM_ADDITIONAL_LIBS) +if (WIN32) + list(APPEND BSYSTEM_ADDITIONAL_LIBS ws2_32) +endif () + +set(BSYSTEM_ADDITIONAL_SOURCES) +if (NOT WIN32) + list(APPEND BSYSTEM_ADDITIONAL_SOURCES BLog_syslog.c) +endif () + +add_library(system + BReactor.c + BSignal.c + BSocket.c + BLog.c + BTime.c + DebugObject.c + BPending.c + ${BSYSTEM_ADDITIONAL_SOURCES} +) +target_link_libraries(system ${BSYSTEM_ADDITIONAL_LIBS}) diff --git a/system/DebugObject.c b/system/DebugObject.c new file mode 100644 index 000000000..4741ee599 --- /dev/null +++ b/system/DebugObject.c @@ -0,0 +1,27 @@ +/** + * @file DebugObject.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#ifndef NDEBUG +DebugCounter debugobject_counter = DEBUGCOUNTER_STATIC; +#endif diff --git a/system/DebugObject.h b/system/DebugObject.h new file mode 100644 index 000000000..72e1fd930 --- /dev/null +++ b/system/DebugObject.h @@ -0,0 +1,105 @@ +/** + * @file DebugObject.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * Object used for detecting leaks. + */ + +#ifndef BADVPN_SYSTEM_DEBUGOBJECT_H +#define BADVPN_SYSTEM_DEBUGOBJECT_H + +#include + +#include +#include + +#define DEBUGOBJECT_VALID UINT32_C(0x31415926) + +/** + * Object used for detecting leaks. + */ +typedef struct { + #ifndef NDEBUG + uint32_t c; + #endif +} DebugObject; + +/** + * Initializes the object. + * + * @param obj the object + */ +static void DebugObject_Init (DebugObject *obj); + +/** + * Frees the object. + * + * @param obj the object + */ +static void DebugObject_Free (DebugObject *obj); + +/** + * Does nothing. + * + * @param obj the object + */ +static void DebugObject_Access (DebugObject *obj); + +/** + * Does nothing. + * There must be no {@link DebugObject}'s initialized. + */ +static void DebugObjectGlobal_Finish (void); + +extern DebugCounter debugobject_counter; + +void DebugObject_Init (DebugObject *obj) +{ + #ifndef NDEBUG + obj->c = DEBUGOBJECT_VALID; + DebugCounter_Increment(&debugobject_counter); + #endif +} + +void DebugObject_Free (DebugObject *obj) +{ + ASSERT(obj->c == DEBUGOBJECT_VALID) + + #ifndef NDEBUG + obj->c = 0; + DebugCounter_Decrement(&debugobject_counter); + #endif +} + +void DebugObject_Access (DebugObject *obj) +{ + ASSERT(obj->c == DEBUGOBJECT_VALID) +} + +void DebugObjectGlobal_Finish (void) +{ + #ifndef NDEBUG + DebugCounter_Free(&debugobject_counter); + #endif +} + +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 000000000..58f48fa93 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(chunkbuffer2_test chunkbuffer2_test.c) + +add_executable(bproto_test bproto_test.c) + +add_executable(bstruct_test bstruct_test.c) diff --git a/tests/TestPacketSink.h b/tests/TestPacketSink.h new file mode 100644 index 000000000..a29aa4415 --- /dev/null +++ b/tests/TestPacketSink.h @@ -0,0 +1,86 @@ +#ifndef _TESTPACKETSINK_H +#define _TESTPACKETSINK_H + +#include +#include +#include + +#include +#include +#include + +typedef struct { + DebugObject d_obj; + PacketPassInterface input; + int accpeting; + int have_packet; + const char *expect; +} TestPacketSink; + +static int _TestPacketSink_input_handler_send (TestPacketSink *s, uint8_t *data, int data_len) +{ + ASSERT(!s->have_packet) + ASSERT(s->expect) + ASSERT(strlen(s->expect) == data_len) + ASSERT(!memcmp(s->expect, data, data_len)) + + s->expect = NULL; + + if (s->accpeting) { + return 1; + } + + s->have_packet = 1; + + return 0; +} + +static void _TestPacketSink_input_handler_cancel (TestPacketSink *s) +{ + ASSERT(s->have_packet) + + s->have_packet = 0; +} + +static void TestPacketSink_Init (TestPacketSink *s, int mtu) +{ + PacketPassInterface_Init(&s->input, mtu, (PacketPassInterface_handler_send)_TestPacketSink_input_handler_send, s); + PacketPassInterface_EnableCancel(&s->input, (PacketPassInterface_handler_cancel)_TestPacketSink_input_handler_cancel); + s->accpeting = 1; + s->have_packet = 0; + s->expect = NULL; + DebugObject_Init(&s->d_obj); +} + +static void TestPacketSink_Free (TestPacketSink *s) +{ + DebugObject_Free(&s->d_obj); + PacketPassInterface_Free(&s->input); +} + +static PacketPassInterface * TestPacketSink_GetInput (TestPacketSink *s) +{ + return &s->input; +} + +static void TestPacketSink_SetAccepting (TestPacketSink *s, int accepting) +{ + s->accpeting = !!accepting; +} + +static void TestPacketSink_Done (TestPacketSink *s) +{ + ASSERT(s->have_packet) + + s->have_packet = 0; + + PacketPassInterface_Done(&s->input); + return; +} + +static void TestPacketSink_Expect (TestPacketSink *s, const char *str) +{ + s->expect = str; +} + +#endif diff --git a/tests/bproto_test.bproto b/tests/bproto_test.bproto new file mode 100644 index 000000000..4641a747c --- /dev/null +++ b/tests/bproto_test.bproto @@ -0,0 +1,9 @@ +message msg1 { + required uint16 a = 5; + optional uint32 b = 6; + required repeated uint64 c = 7; + repeated uint16 d = 8; + required uint8 e = 9; + required data f = 10; + required data("4") g = 11; +}; diff --git a/tests/bproto_test.c b/tests/bproto_test.c new file mode 100644 index 000000000..5409c28d6 --- /dev/null +++ b/tests/bproto_test.c @@ -0,0 +1,72 @@ +#include +#include +#include + +#include + +#include + +int main () +{ + uint16_t a = 17501; + uint64_t c = 82688926; + uint16_t d1 = 1517; + uint16_t d2 = 1518; + uint8_t e = 72; + const char *f = "hello world"; + const char *g = "helo"; + + // encode message + + int len = msg1_SIZEa + msg1_SIZEc + msg1_SIZEd + msg1_SIZEd + msg1_SIZEe + msg1_SIZEf(strlen(f)) + msg1_SIZEg; + + uint8_t msg[len]; + msg1Writer writer; + msg1Writer_Init(&writer, msg); + msg1Writer_Adda(&writer, a); + msg1Writer_Addc(&writer, c); + msg1Writer_Addd(&writer, d1); + msg1Writer_Addd(&writer, d2); + msg1Writer_Adde(&writer, e); + uint8_t *f_dst = msg1Writer_Addf(&writer, strlen(f)); + memcpy(f_dst, f, strlen(f)); + uint8_t *g_dst = msg1Writer_Addg(&writer); + memcpy(g_dst, g, strlen(g)); + int len2 = msg1Writer_Finish(&writer); + ASSERT(len2 == len) + + // parse message + + msg1Parser parser; + ASSERT_EXECUTE(msg1Parser_Init(&parser, msg, len)) + + // check parse results + + uint16_t p_a; + uint64_t p_c; + uint16_t p_d1; + uint16_t p_d2; + uint8_t p_e; + uint8_t *p_f; + int p_f_len; + uint8_t *p_g; + ASSERT_EXECUTE(msg1Parser_Geta(&parser, &p_a)) + ASSERT_EXECUTE(msg1Parser_Getc(&parser, &p_c)) + ASSERT_EXECUTE(msg1Parser_Getd(&parser, &p_d1)) + ASSERT_EXECUTE(msg1Parser_Getd(&parser, &p_d2)) + ASSERT_EXECUTE(msg1Parser_Gete(&parser, &p_e)) + ASSERT_EXECUTE(msg1Parser_Getf(&parser, &p_f, &p_f_len)) + ASSERT_EXECUTE(msg1Parser_Getg(&parser, &p_g)) + + ASSERT(p_a == a) + ASSERT(p_c == c) + ASSERT(p_d1 == d1) + ASSERT(p_d2 == d2) + ASSERT(p_e == e) + ASSERT(p_f_len == strlen(f) && !memcmp(p_f, f, p_f_len)) + ASSERT(!memcmp(p_g, g, strlen(g))) + + ASSERT(msg1Parser_GotEverything(&parser)) + + return 0; +} diff --git a/tests/bstruct_test.bstruct b/tests/bstruct_test.bstruct new file mode 100644 index 000000000..ab353506e --- /dev/null +++ b/tests/bstruct_test.bstruct @@ -0,0 +1,10 @@ +structure str0 ("int n") { + "int" x["n"]; +}; + +structure str1 ("int nb, int nc, int m") { + "int" a; + "char" b["nb"]; + "double" c["nc"]; + structure str0("m") d; +}; diff --git a/tests/bstruct_test.c b/tests/bstruct_test.c new file mode 100644 index 000000000..71dd678ff --- /dev/null +++ b/tests/bstruct_test.c @@ -0,0 +1,23 @@ +#include +#include + +#include + +#include + +int main () +{ + int nb = 3; + int nc = 2; + int m = 6; + + str1Params p; + str1Params_Init(&p, nb, nc, m); + + printf("len=%d align=%d\n", p.len, p.align); + + void *s = malloc(p.len); + ASSERT_FORCE(s) + + return 0; +} diff --git a/tests/chunkbuffer2_test.c b/tests/chunkbuffer2_test.c new file mode 100644 index 000000000..ff94bb353 --- /dev/null +++ b/tests/chunkbuffer2_test.c @@ -0,0 +1,86 @@ +#include +#include + +int main () +{ + struct ChunkBuffer2_block blocks[16]; + ChunkBuffer2 buf; + ChunkBuffer2_Init(&buf, blocks, 16, 4 * sizeof(struct ChunkBuffer2_block)); + + ASSERT_FORCE(buf.input_dest == (uint8_t *)&blocks[1]) + ASSERT_FORCE(buf.input_avail == 15 * sizeof(struct ChunkBuffer2_block)) + ASSERT_FORCE(buf.output_dest == NULL) + ASSERT_FORCE(buf.output_avail == -1) + + ChunkBuffer2_SubmitPacket(&buf, sizeof(struct ChunkBuffer2_block)); + + ASSERT_FORCE(buf.input_dest == (uint8_t *)&blocks[3]) + ASSERT_FORCE(buf.input_avail == 13 * sizeof(struct ChunkBuffer2_block)) + ASSERT_FORCE(buf.output_dest == (uint8_t *)&blocks[1]) + ASSERT_FORCE(buf.output_avail == 1 * sizeof(struct ChunkBuffer2_block)) + + ChunkBuffer2_SubmitPacket(&buf, 8 * sizeof(struct ChunkBuffer2_block)); + + ASSERT_FORCE(buf.input_dest == (uint8_t *)&blocks[12]) + ASSERT_FORCE(buf.input_avail == 4 * sizeof(struct ChunkBuffer2_block)) + ASSERT_FORCE(buf.output_dest == (uint8_t *)&blocks[1]) + ASSERT_FORCE(buf.output_avail == 1 * sizeof(struct ChunkBuffer2_block)) + + ChunkBuffer2_SubmitPacket(&buf, 4 * sizeof(struct ChunkBuffer2_block)); + + ASSERT_FORCE(buf.input_dest == NULL) + ASSERT_FORCE(buf.input_avail == -1) + ASSERT_FORCE(buf.output_dest == (uint8_t *)&blocks[1]) + ASSERT_FORCE(buf.output_avail == 1 * sizeof(struct ChunkBuffer2_block)) + + ChunkBuffer2_ConsumePacket(&buf); + + ASSERT_FORCE(buf.input_dest == (uint8_t *)&blocks[1]) + ASSERT_FORCE(buf.input_avail == 1 * sizeof(struct ChunkBuffer2_block)) + ASSERT_FORCE(buf.output_dest == (uint8_t *)&blocks[3]) + ASSERT_FORCE(buf.output_avail == 8 * sizeof(struct ChunkBuffer2_block)) + + ChunkBuffer2_ConsumePacket(&buf); + + ASSERT_FORCE(buf.input_dest == (uint8_t *)&blocks[1]) + ASSERT_FORCE(buf.input_avail == 10 * sizeof(struct ChunkBuffer2_block)) + ASSERT_FORCE(buf.output_dest == (uint8_t *)&blocks[12]) + ASSERT_FORCE(buf.output_avail == 4 * sizeof(struct ChunkBuffer2_block)) + + ChunkBuffer2_SubmitPacket(&buf, 9 * sizeof(struct ChunkBuffer2_block)); + + ASSERT_FORCE(buf.input_dest == (uint8_t *)&blocks[11]) + ASSERT_FORCE(buf.input_avail == 0 * sizeof(struct ChunkBuffer2_block)) + ASSERT_FORCE(buf.output_dest == (uint8_t *)&blocks[12]) + ASSERT_FORCE(buf.output_avail == 4 * sizeof(struct ChunkBuffer2_block)) + + ChunkBuffer2_ConsumePacket(&buf); + + ASSERT_FORCE(buf.input_dest == (uint8_t *)&blocks[11]) + ASSERT_FORCE(buf.input_avail == 5 * sizeof(struct ChunkBuffer2_block)) + ASSERT_FORCE(buf.output_dest == (uint8_t *)&blocks[1]) + ASSERT_FORCE(buf.output_avail == 9 * sizeof(struct ChunkBuffer2_block)) + + ChunkBuffer2_SubmitPacket(&buf, 1 * sizeof(struct ChunkBuffer2_block)); + + ASSERT_FORCE(buf.input_dest == NULL) + ASSERT_FORCE(buf.input_avail == -1) + ASSERT_FORCE(buf.output_dest == (uint8_t *)&blocks[1]) + ASSERT_FORCE(buf.output_avail == 9 * sizeof(struct ChunkBuffer2_block)) + + ChunkBuffer2_ConsumePacket(&buf); + + ASSERT_FORCE(buf.input_dest == (uint8_t *)&blocks[1]) + ASSERT_FORCE(buf.input_avail == 9 * sizeof(struct ChunkBuffer2_block)) + ASSERT_FORCE(buf.output_dest == (uint8_t *)&blocks[11]) + ASSERT_FORCE(buf.output_avail == 1 * sizeof(struct ChunkBuffer2_block)) + + ChunkBuffer2_ConsumePacket(&buf); + + ASSERT_FORCE(buf.input_dest == (uint8_t *)&blocks[1]) + ASSERT_FORCE(buf.input_avail == 15 * sizeof(struct ChunkBuffer2_block)) + ASSERT_FORCE(buf.output_dest == NULL) + ASSERT_FORCE(buf.output_avail == -1) + + return 0; +} diff --git a/tuntap/BTap.c b/tuntap/BTap.c new file mode 100644 index 000000000..b4e421765 --- /dev/null +++ b/tuntap/BTap.c @@ -0,0 +1,689 @@ +/** + * @file BTap.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include + +#ifdef BADVPN_USE_WINAPI +#include +#include +#include +#include +#include "wintap-common.h" +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include + +#define REGNAME_SIZE 256 + +static void report_error (BTap *o); +static int input_handler_send (BTap *o, uint8_t *data, int data_len); +static int output_handler_recv (BTap *o, uint8_t *data, int *data_len); + +#ifdef BADVPN_USE_WINAPI + +static void write_handle_handler (BTap *o) +{ + ASSERT(o->input_packet) + + // disable handle event + BReactor_DisableHandle(o->reactor, &o->input_bhandle); + + // set no input packet + o->input_packet = NULL; + + // read result + DWORD bytes; + if (!GetOverlappedResult(o->device, &o->input_ol, &bytes, FALSE)) { + DEBUG("WARNING: GetOverlappedResult (input) failed (%u)", GetLastError()); + } else if (bytes != o->input_packet_len) { + DEBUG("WARNING: written %d expected %d", (int)bytes, o->input_packet_len); + } + + // reset event + ASSERT_FORCE(ResetEvent(o->input_event)) + + // inform sender we finished the packet + PacketPassInterface_Done(&o->input); + return; +} + +static void read_handle_handler (BTap *o) +{ + ASSERT(o->output_packet) + + // disable handle event + BReactor_DisableHandle(o->reactor, &o->output_bhandle); + + // set no output packet + o->output_packet = NULL; + + // read result + DWORD bytes; + if (!GetOverlappedResult(o->device, &o->output_ol, &bytes, FALSE)) { + DEBUG("WARNING: GetOverlappedResult (output) failed (%u)", GetLastError()); + // report fatal error + report_error(o); + return; + } + + ASSERT_FORCE(bytes <= o->frame_mtu) + + // reset event + ASSERT_FORCE(ResetEvent(o->output_event)) + + // inform sender we finished the packet + PacketRecvInterface_Done(&o->output, bytes); + return; +} + +#else + +static void fd_handler (BTap *o, int events) +{ + ASSERT((events&BREACTOR_WRITE) || (events&BREACTOR_READ)) + DEAD_DECLARE + + if (events&BREACTOR_WRITE) do { + ASSERT(o->input_packet) + + int bytes = write(o->fd, o->input_packet, o->input_packet_len); + if (bytes < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // retry later + break; + } + // malformed packets will cause errors, ignore them and act like + // the packet was accepeted + } else { + if (bytes != o->input_packet_len) { + DEBUG("WARNING: written %d expected %d", bytes, o->input_packet_len); + } + } + + // set no input packet + o->input_packet = NULL; + + // update events + o->poll_events &= ~BREACTOR_WRITE; + BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, o->poll_events); + + // inform sender we finished the packet + DEAD_ENTER2(o->dead) + PacketPassInterface_Done(&o->input); + if (DEAD_LEAVE(o->dead)) { + return; + } + } while (0); + + if (events&BREACTOR_READ) do { + ASSERT(o->output_packet) + + // try reading into the buffer + int bytes = read(o->fd, o->output_packet, o->frame_mtu); + if (bytes < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // retry later + break; + } + // report fatal error + report_error(o); + return; + } + + ASSERT_FORCE(bytes <= o->frame_mtu) + + // set no output packet + o->output_packet = NULL; + + // update events + o->poll_events &= ~BREACTOR_READ; + BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, o->poll_events); + + // inform receiver we finished the packet + DEAD_ENTER2(o->dead) + PacketRecvInterface_Done(&o->output, bytes); + if (DEAD_LEAVE(o->dead)) { + return; + } + } while (0); +} + +#endif + +void report_error (BTap *o) +{ + #ifndef NDEBUG + DEAD_ENTER(o->dead) + #endif + + o->handler_error(o->handler_error_user); + + #ifndef NDEBUG + ASSERT(DEAD_KILLED) + DEAD_LEAVE(o->dead); + #endif +} + +int input_handler_send (BTap *o, uint8_t *data, int data_len) +{ + ASSERT(!o->input_packet) + + #ifdef BADVPN_USE_WINAPI + + // setup overlapped + memset(&o->input_ol, 0, sizeof(o->input_ol)); + o->input_ol.hEvent = o->input_event; + + // attempt write + if (!WriteFile(o->device, data, data_len, NULL, &o->input_ol)) { + DWORD error = GetLastError(); + if (error == ERROR_IO_PENDING) { + // write pending + o->input_packet = data; + o->input_packet_len = data_len; + BReactor_EnableHandle(o->reactor, &o->input_bhandle); + return 0; + } + DEBUG("WARNING: WriteFile failed (%u)", error); + return 1; + } + + // read result + DWORD bytes; + if (!GetOverlappedResult(o->device, &o->input_ol, &bytes, FALSE)) { + DEBUG("WARNING: GetOverlappedResult failed (%u)", GetLastError()); + } + else if (bytes != data_len) { + DEBUG("WARNING: written %d expected %d", (int)bytes, data_len); + } + + // reset event + ASSERT_FORCE(ResetEvent(o->input_event)) + + #else + + int bytes = write(o->fd, data, data_len); + if (bytes < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // retry later in fd_handler + // remember packet + o->input_packet = data; + o->input_packet_len = data_len; + // update events + o->poll_events |= BREACTOR_WRITE; + BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, o->poll_events); + return 0; + } + // malformed packets will cause errors, ignore them and act like + // the packet was accepeted + } else { + if (bytes != data_len) { + DEBUG("WARNING: written %d expected %d", bytes, data_len); + } + } + + #endif + + return 1; +} + +int output_handler_recv (BTap *o, uint8_t *data, int *data_len) +{ + ASSERT(!o->output_packet) + + #ifdef BADVPN_USE_WINAPI + + // setup overlapped + memset(&o->output_ol, 0, sizeof(o->output_ol)); + o->output_ol.hEvent = o->output_event; + + // attempt read + if (!ReadFile(o->device, data, o->frame_mtu, NULL, &o->output_ol)) { + DWORD error = GetLastError(); + if (error == ERROR_IO_PENDING) { + // read pending + o->output_packet = data; + BReactor_EnableHandle(o->reactor, &o->output_bhandle); + return 0; + } + // report fatal error + report_error(o); + return -1; + } + + // read result + DWORD bytes; + if (!GetOverlappedResult(o->device, &o->output_ol, &bytes, FALSE)) { + // report fatal error + report_error(o); + return -1; + } + + ASSERT_FORCE(bytes <= o->frame_mtu) + + // reset event + ASSERT_FORCE(ResetEvent(o->output_event)) + + #else + + // attempt read + int bytes = read(o->fd, data, o->frame_mtu); + if (bytes < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // retry later in fd_handler + // remember packet + o->output_packet = data; + // update events + o->poll_events |= BREACTOR_READ; + BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, o->poll_events); + return 0; + } + // report fatal error + report_error(o); + return -1; + } + + ASSERT_FORCE(bytes <= o->frame_mtu) + + #endif + + *data_len = bytes; + return 1; +} + +int BTap_Init (BTap *o, BReactor *reactor, char *devname, BTap_handler_error handler_error, void *handler_error_user) +{ + // init arguments + o->reactor = reactor; + o->handler_error = handler_error; + o->handler_error_user = handler_error_user; + + #ifdef BADVPN_USE_WINAPI + + char *device_component_id; + char *device_name; + + char strdata[(devname ? strlen(devname) + 1 : 0)]; + + if (!devname) { + device_component_id = TAP_COMPONENT_ID; + device_name = NULL; + } else { + strcpy(strdata, devname); + char *colon = strstr(strdata, ":"); + if (!colon) { + DEBUG("No colon in device string"); + goto fail0; + } + *colon = '\0'; + device_component_id = strdata; + device_name = colon + 1; + if (strlen(device_component_id) == 0) { + device_component_id = TAP_COMPONENT_ID; + } + if (strlen(device_name) == 0) { + device_name = NULL; + } + } + + DEBUG("Opening component ID %s, name %s", device_component_id, device_name); + + // open adapter key + // used to find all devices with the given ComponentId + HKEY adapter_key; + if (RegOpenKeyEx( + HKEY_LOCAL_MACHINE, + ADAPTER_KEY, + 0, + KEY_READ, + &adapter_key + ) != ERROR_SUCCESS) { + DEBUG("Error opening adapter key"); + goto fail0; + } + + char net_cfg_instance_id[REGNAME_SIZE]; + int found = 0; + + DWORD i; + for (i = 0;; i++) { + DWORD len; + DWORD type; + + char key_name[REGNAME_SIZE]; + len = sizeof(key_name); + if (RegEnumKeyEx(adapter_key, i, key_name, &len, NULL, NULL, NULL, NULL) != ERROR_SUCCESS) { + break; + } + + char unit_string[REGNAME_SIZE]; + snprintf(unit_string, sizeof(unit_string), "%s\\%s", ADAPTER_KEY, key_name); + HKEY unit_key; + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, unit_string, 0, KEY_READ, &unit_key) != ERROR_SUCCESS) { + continue; + } + + char component_id[REGNAME_SIZE]; + len = sizeof(component_id); + if (RegQueryValueEx(unit_key, "ComponentId", NULL, &type, component_id, &len) != ERROR_SUCCESS || type != REG_SZ) { + ASSERT_FORCE(RegCloseKey(unit_key) == ERROR_SUCCESS) + continue; + } + + len = sizeof(net_cfg_instance_id); + if (RegQueryValueEx(unit_key, "NetCfgInstanceId", NULL, &type, net_cfg_instance_id, &len) != ERROR_SUCCESS || type != REG_SZ) { + ASSERT_FORCE(RegCloseKey(unit_key) == ERROR_SUCCESS) + continue; + } + + RegCloseKey(unit_key); + + // check if ComponentId matches + if (!strcmp(component_id, device_component_id)) { + // if no name was given, use the first device with the given ComponentId + if (!device_name) { + found = 1; + break; + } + + // open connection key + char conn_string[REGNAME_SIZE]; + snprintf(conn_string, sizeof(conn_string), "%s\\%s\\Connection", NETWORK_CONNECTIONS_KEY, net_cfg_instance_id); + HKEY conn_key; + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, conn_string, 0, KEY_READ, &conn_key) != ERROR_SUCCESS) { + continue; + } + + // read name + char name[REGNAME_SIZE]; + len = sizeof(name); + if (RegQueryValueEx(conn_key, "Name", NULL, &type, name, &len) != ERROR_SUCCESS || type != REG_SZ) { + ASSERT_FORCE(RegCloseKey(conn_key) == ERROR_SUCCESS) + continue; + } + + ASSERT_FORCE(RegCloseKey(conn_key) == ERROR_SUCCESS) + + // check name + if (!strcmp(name, device_name)) { + found = 1; + break; + } + } + } + + ASSERT_FORCE(RegCloseKey(adapter_key) == ERROR_SUCCESS) + + if (!found) { + DEBUG("Could not find TAP device"); + goto fail0; + } + + char device_path[REGNAME_SIZE]; + snprintf(device_path, sizeof(device_path), "%s%s%s", USERMODEDEVICEDIR, net_cfg_instance_id, TAPSUFFIX); + + DEBUG("Opening device %s", device_path); + + if ((o->device = CreateFile( + device_path, + GENERIC_READ | GENERIC_WRITE, + 0, + 0, + OPEN_EXISTING, + FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, + 0 + )) == INVALID_HANDLE_VALUE) { + DEBUG("CreateFile failed"); + goto fail0; + } + + // get MTU + + ULONG umtu; + DWORD len; + if (!DeviceIoControl(o->device, TAP_IOCTL_GET_MTU, NULL, 0, &umtu, sizeof(umtu), &len, NULL)) { + DEBUG("DeviceIoControl(TAP_IOCTL_GET_MTU) failed"); + goto fail1; + } + + o->dev_mtu = umtu; + o->frame_mtu = o->dev_mtu + BTAP_ETHERNET_HEADER_LENGTH; + + // set connected + + ULONG upstatus = TRUE; + if (!DeviceIoControl(o->device, TAP_IOCTL_SET_MEDIA_STATUS, &upstatus, sizeof(upstatus), &upstatus, sizeof(upstatus), &len, NULL)) { + DEBUG("DeviceIoControl(TAP_IOCTL_SET_MEDIA_STATUS) failed"); + goto fail1; + } + + DEBUG("Device opened"); + + // init input/output + + if (!(o->input_event = CreateEvent(NULL, TRUE, FALSE, NULL))) { + DEBUG("CreateEvent failed"); + goto fail1; + } + + if (!(o->output_event = CreateEvent(NULL, TRUE, FALSE, NULL))) { + DEBUG("CreateEvent failed"); + goto fail2; + } + + BHandle_Init(&o->input_bhandle, o->input_event, (BHandle_handler)write_handle_handler, o); + BHandle_Init(&o->output_bhandle, o->output_event, (BHandle_handler)read_handle_handler, o); + + if (!BReactor_AddHandle(o->reactor, &o->input_bhandle)) { + goto fail3; + } + if (!BReactor_AddHandle(o->reactor, &o->output_bhandle)) { + goto fail4; + } + + goto success; + +fail4: + BReactor_RemoveHandle(o->reactor, &o->input_bhandle); +fail3: + ASSERT_FORCE(CloseHandle(o->output_event)) +fail2: + ASSERT_FORCE(CloseHandle(o->input_event)) +fail1: + ASSERT_FORCE(CloseHandle(o->device)) +fail0: + return 0; + + #else + + // open device + + if ((o->fd = open("/dev/net/tun", O_RDWR)) < 0) { + DEBUG("error opening device"); + return 0; + } + + // configure device + + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags |= IFF_TAP | IFF_NO_PI; + if (devname) { + snprintf(ifr.ifr_name, IFNAMSIZ, "%s", devname); + } + + if (ioctl(o->fd, TUNSETIFF, (void *)&ifr) < 0) { + DEBUG("error configuring device"); + goto fail1; + } + + strcpy(o->devname, ifr.ifr_name); + + // open dummy socket for ioctls + + int sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + DEBUG("socket failed"); + goto fail1; + } + + // get MTU + + memset(&ifr, 0, sizeof(ifr)); + strcpy(ifr.ifr_name, o->devname); + + if (ioctl(sock, SIOCGIFMTU, (void *)&ifr) < 0) { + DEBUG("error getting MTU"); + close(sock); + goto fail1; + } + + o->dev_mtu = ifr.ifr_mtu; + o->frame_mtu = o->dev_mtu + BTAP_ETHERNET_HEADER_LENGTH; + + close(sock); + + // set non-blocking + if (fcntl(o->fd, F_SETFL, O_NONBLOCK) < 0) { + DEBUG("cannot set non-blocking"); + goto fail1; + } + + // init file descriptor object + BFileDescriptor_Init(&o->bfd, o->fd, (BFileDescriptor_handler)fd_handler, o); + if (!BReactor_AddFileDescriptor(o->reactor, &o->bfd)) { + DEBUG("BReactor_AddFileDescriptor failed"); + goto fail1; + } + o->poll_events = 0; + + goto success; + +fail1: + ASSERT_FORCE(close(o->fd) == 0) + return 0; + + #endif + +success: + // init dead var + DEAD_INIT(o->dead); + + // init input + PacketPassInterface_Init(&o->input, o->frame_mtu, (PacketPassInterface_handler_send)input_handler_send, o); + + // init output + PacketRecvInterface_Init(&o->output, o->frame_mtu, (PacketRecvInterface_handler_recv)output_handler_recv, o); + + // set no input packet + o->input_packet = NULL; + + // set no output packet + o->output_packet = NULL; + + // init debug object + DebugObject_Init(&o->d_obj); + + return 1; +} + +void BTap_Free (BTap *o) +{ + // free debug object + DebugObject_Free(&o->d_obj); + + // free output + PacketRecvInterface_Free(&o->output); + + // free input + PacketPassInterface_Free(&o->input); + + // kill dead variable + DEAD_KILL(o->dead); + + #ifdef BADVPN_USE_WINAPI + + // wait for pending i/o + ASSERT_FORCE(CancelIo(o->device)) + DWORD bytes; + DWORD error; + if (o->input_packet) { + if (!GetOverlappedResult(o->device, &o->input_ol, &bytes, TRUE)) { + error = GetLastError(); + if (error != ERROR_OPERATION_ABORTED) { + DEBUG("WARNING: GetOverlappedResult (input) failed (%u)", error); + } + } + } + if (o->output_packet) { + if (!GetOverlappedResult(o->device, &o->output_ol, &bytes, TRUE)) { + error = GetLastError(); + if (error != ERROR_OPERATION_ABORTED) { + DEBUG("WARNING: GetOverlappedResult (output) failed (%u)", error); + } + } + } + + // free stuff + BReactor_RemoveHandle(o->reactor, &o->input_bhandle); + BReactor_RemoveHandle(o->reactor, &o->output_bhandle); + ASSERT_FORCE(CloseHandle(o->output_event)) + ASSERT_FORCE(CloseHandle(o->input_event)) + ASSERT_FORCE(CloseHandle(o->device)) + + #else + + // free BFileDescriptor + BReactor_RemoveFileDescriptor(o->reactor, &o->bfd); + + // close file descriptor + ASSERT_FORCE(close(o->fd) == 0) + + #endif +} + +int BTap_GetDeviceMTU (BTap *o) +{ + return o->dev_mtu; +} + +PacketPassInterface * BTap_GetInput (BTap *o) +{ + return &o->input; +} + +PacketRecvInterface * BTap_GetOutput (BTap *o) +{ + return &o->output; +} diff --git a/tuntap/BTap.h b/tuntap/BTap.h new file mode 100644 index 000000000..60f3ab347 --- /dev/null +++ b/tuntap/BTap.h @@ -0,0 +1,139 @@ +/** + * @file BTap.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @section DESCRIPTION + * + * TAP device abstraction. + */ + +#ifndef BADVPN_TUNTAP_BTAP_H +#define BADVPN_TUNTAP_BTAP_H + +#include + +#ifdef BADVPN_USE_WINAPI +#else +#include +#endif + +#include +#include +#include +#include +#include +#include + +#define BTAP_ETHERNET_HEADER_LENGTH 14 + +typedef void (*BTap_handler_error) (void *used); + +/** + * TAP device abstraction. + * + * Frames are written to the device using {@link PacketPassInterface} + * and read from the device using {@link PacketRecvInterface}. + */ +typedef struct { + DebugObject d_obj; + dead_t dead; + BReactor *reactor; + BTap_handler_error handler_error; + void *handler_error_user; + int dev_mtu; + int frame_mtu; + PacketPassInterface input; + PacketRecvInterface output; + uint8_t *input_packet; + int input_packet_len; + uint8_t *output_packet; + + #ifdef BADVPN_USE_WINAPI + HANDLE device; + HANDLE input_event; + HANDLE output_event; + BHandle input_bhandle; + BHandle output_bhandle; + OVERLAPPED input_ol; + OVERLAPPED output_ol; + #else + int fd; + BFileDescriptor bfd; + char devname[IFNAMSIZ]; + int poll_events; + #endif +} BTap; + +/** + * Initializes the TAP device. + * + * @param o the object + * @param BReactor {@link BReactor} we live in + * @param devname name of the devece to open. + * On Linux: a network interface name. If it is NULL, no + * specific device will be requested, and the operating system + * may create a new device. + * On Windows: a string "component_id:device_name", where + * component_id is a string identifying the driver, and device_name + * is the name of the network interface. If component_id is empty, + * a hardcoded default will be used instead. If device_name is empty, + * the first device found with a matching component_id will be used. + * Specifying a NULL devname is equivalent to specifying ":". + * @param handler_error error handler function + * @param handler_error_user value passed to error handler + * @return 1 on success, 0 on failure + */ +int BTap_Init (BTap *o, BReactor *bsys, char *devname, BTap_handler_error handler_error, void *handler_error_user) WARN_UNUSED; + +/** + * Frees the TAP device. + * + * @param o the object + */ +void BTap_Free (BTap *o); + +/** + * Returns the device's maximum transmission unit, excluding + * the Ethernet header. + * + * @param o the object + * @return device's MTU, excluding the Ethernet header + */ +int BTap_GetDeviceMTU (BTap *o); + +/** + * Returns a {@link PacketPassInterface} for writing packets to the device. + * The MTU of the interface will be {@link BTap_GetDeviceMTU} + BTAP_ETHERNET_HEADER_LENGTH. + * + * @param o the object + * @return input interface + */ +PacketPassInterface * BTap_GetInput (BTap *o); + +/** + * Returns a {@link PacketRecvInterface} for reading packets from the device. + * The MTU of the interface will be {@link BTap_GetDeviceMTU} + BTAP_ETHERNET_HEADER_LENGTH. + * + * @param o the object + * @return output interface + */ +PacketRecvInterface * BTap_GetOutput (BTap *o); + +#endif diff --git a/tuntap/CMakeLists.txt b/tuntap/CMakeLists.txt new file mode 100644 index 000000000..d94aef3c6 --- /dev/null +++ b/tuntap/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(tuntap BTap.c) +target_link_libraries(tuntap system flow) diff --git a/tuntap/wintap-common.h b/tuntap/wintap-common.h new file mode 100644 index 000000000..115c32560 --- /dev/null +++ b/tuntap/wintap-common.h @@ -0,0 +1,78 @@ +/* + * TAP-Win32/TAP-Win64 -- A kernel driver to provide virtual tap + * device functionality on Windows. + * + * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. + * + * This source code is Copyright (C) 2002-2010 OpenVPN Technologies, Inc., + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +//=============================================== +// This file is included both by OpenVPN and +// the TAP-Win32 driver and contains definitions +// common to both. +//=============================================== + +//============= +// TAP IOCTLs +//============= + +#define TAP_CONTROL_CODE(request,method) \ + CTL_CODE (FILE_DEVICE_UNKNOWN, request, method, FILE_ANY_ACCESS) + +// Present in 8.1 + +#define TAP_IOCTL_GET_MAC TAP_CONTROL_CODE (1, METHOD_BUFFERED) +#define TAP_IOCTL_GET_VERSION TAP_CONTROL_CODE (2, METHOD_BUFFERED) +#define TAP_IOCTL_GET_MTU TAP_CONTROL_CODE (3, METHOD_BUFFERED) +#define TAP_IOCTL_GET_INFO TAP_CONTROL_CODE (4, METHOD_BUFFERED) +#define TAP_IOCTL_CONFIG_POINT_TO_POINT TAP_CONTROL_CODE (5, METHOD_BUFFERED) +#define TAP_IOCTL_SET_MEDIA_STATUS TAP_CONTROL_CODE (6, METHOD_BUFFERED) +#define TAP_IOCTL_CONFIG_DHCP_MASQ TAP_CONTROL_CODE (7, METHOD_BUFFERED) +#define TAP_IOCTL_GET_LOG_LINE TAP_CONTROL_CODE (8, METHOD_BUFFERED) +#define TAP_IOCTL_CONFIG_DHCP_SET_OPT TAP_CONTROL_CODE (9, METHOD_BUFFERED) + +// Added in 8.2 + +/* obsoletes TAP_IOCTL_CONFIG_POINT_TO_POINT */ +#define TAP_IOCTL_CONFIG_TUN TAP_CONTROL_CODE (10, METHOD_BUFFERED) + +//================= +// Registry keys +//================= + +#define ADAPTER_KEY "SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}" + +#define NETWORK_CONNECTIONS_KEY "SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}" + +//====================== +// Filesystem prefixes +//====================== + +#define USERMODEDEVICEDIR "\\\\.\\Global\\" +#define SYSDEVICEDIR "\\Device\\" +#define USERDEVICEDIR "\\DosDevices\\Global\\" +#define TAPSUFFIX ".tap" + +//========================================================= +// TAP_COMPONENT_ID -- This string defines the TAP driver +// type -- different component IDs can reside in the system +// simultaneously. +//========================================================= + +#define TAP_COMPONENT_ID "tap0901"