Explorar el Código

Initial import

ambrop7 hace 15 años
padre
commit
198d6cd4b8
Se han modificado 100 ficheros con 21580 adiciones y 0 borrados
  1. 99 0
      CMakeLists.txt
  2. 339 0
      COPYNG
  3. 73 0
      INSTALL
  4. 129 0
      INSTALL-WINDOWS
  5. 2 0
      TODO
  6. 274 0
      badvpn-client.8
  7. 187 0
      badvpn-server.8
  8. 321 0
      badvpn.7
  9. 12 0
      blog_channels.txt
  10. 121 0
      blog_generator/blog.php
  11. 35 0
      blog_generator/blog_functions.php
  12. 64 0
      bproto/BProto.h
  13. 99 0
      bproto_generator/ProtoParser.lime
  14. 560 0
      bproto_generator/ProtoParser.php
  15. 106 0
      bproto_generator/bproto.php
  16. 748 0
      bproto_generator/bproto_functions.php
  17. 93 0
      bstruct_generator/StructParser.lime
  18. 503 0
      bstruct_generator/StructParser.php
  19. 106 0
      bstruct_generator/bstruct.php
  20. 269 0
      bstruct_generator/bstruct_functions.php
  21. 13 0
      client/CMakeLists.txt
  22. 731 0
      client/DataProto.c
  23. 297 0
      client/DataProto.h
  24. 511 0
      client/DatagramPeerIO.c
  25. 275 0
      client/DatagramPeerIO.h
  26. 378 0
      client/PasswordListener.c
  27. 143 0
      client/PasswordListener.h
  28. 806 0
      client/StreamPeerIO.c
  29. 217 0
      client/StreamPeerIO.h
  30. 3718 0
      client/client.c
  31. 212 0
      client/client.h
  32. 22 0
      cmake/modules/COPYING-CMAKE-SCRIPTS
  33. 113 0
      cmake/modules/FindLibraryWithDebug.cmake
  34. 57 0
      cmake/modules/FindNSPR.cmake
  35. 57 0
      cmake/modules/FindNSS.cmake
  36. 72 0
      cmake/modules/FindOpenSSL.cmake
  37. 25 0
      examples/CMakeLists.txt
  38. 100 0
      examples/FastPacketSource.h
  39. 85 0
      examples/RandomPacketSink.h
  40. 89 0
      examples/TimerPacketSink.h
  41. 139 0
      examples/bavl_test.c
  42. 130 0
      examples/bheap_test.c
  43. 77 0
      examples/btimer_example.c
  44. 118 0
      examples/fairqueue_test.c
  45. 81 0
      examples/fairqueue_test2.c
  46. 119 0
      examples/hashtable_bench.c
  47. 94 0
      examples/hashtable_example.c
  48. 161 0
      examples/linkedlist2_example.c
  49. 106 0
      examples/predicate_test.c
  50. 7 0
      flooder/CMakeLists.txt
  51. 719 0
      flooder/flooder.c
  52. 30 0
      flooder/flooder.h
  53. 214 0
      flow/BestEffortPacketWriteInterface.h
  54. 30 0
      flow/CMakeLists.txt
  55. 60 0
      flow/DataProtoKeepaliveSource.c
  56. 65 0
      flow/DataProtoKeepaliveSource.h
  57. 165 0
      flow/DatagramSocketSink.c
  58. 104 0
      flow/DatagramSocketSink.h
  59. 178 0
      flow/DatagramSocketSource.c
  60. 100 0
      flow/DatagramSocketSource.h
  61. 522 0
      flow/FragmentProtoAssembler.c
  62. 116 0
      flow/FragmentProtoAssembler.h
  63. 290 0
      flow/FragmentProtoDisassembler.c
  64. 107 0
      flow/FragmentProtoDisassembler.h
  65. 110 0
      flow/KeepaliveIO.c
  66. 83 0
      flow/KeepaliveIO.h
  67. 248 0
      flow/PacketBuffer.c
  68. 74 0
      flow/PacketBuffer.h
  69. 160 0
      flow/PacketBufferAsyncInput.h
  70. 134 0
      flow/PacketCopier.c
  71. 84 0
      flow/PacketCopier.h
  72. 220 0
      flow/PacketPassConnector.c
  73. 101 0
      flow/PacketPassConnector.h
  74. 459 0
      flow/PacketPassFairQueue.c
  75. 184 0
      flow/PacketPassFairQueue.h
  76. 131 0
      flow/PacketPassInactivityMonitor.c
  77. 104 0
      flow/PacketPassInactivityMonitor.h
  78. 373 0
      flow/PacketPassInterface.h
  79. 163 0
      flow/PacketPassNotifier.c
  80. 97 0
      flow/PacketPassNotifier.h
  81. 378 0
      flow/PacketPassPriorityQueue.c
  82. 181 0
      flow/PacketPassPriorityQueue.h
  83. 306 0
      flow/PacketProtoDecoder.c
  84. 104 0
      flow/PacketProtoDecoder.h
  85. 130 0
      flow/PacketProtoEncoder.c
  86. 74 0
      flow/PacketProtoEncoder.h
  87. 76 0
      flow/PacketProtoFlow.c
  88. 78 0
      flow/PacketProtoFlow.h
  89. 120 0
      flow/PacketRecvBlocker.c
  90. 85 0
      flow/PacketRecvBlocker.h
  91. 227 0
      flow/PacketRecvConnector.c
  92. 101 0
      flow/PacketRecvConnector.h
  93. 296 0
      flow/PacketRecvInterface.h
  94. 156 0
      flow/PacketRecvNotifier.c
  95. 98 0
      flow/PacketRecvNotifier.h
  96. 144 0
      flow/PacketStreamSender.c
  97. 77 0
      flow/PacketStreamSender.h
  98. 58 0
      flow/SCKeepaliveSource.c
  99. 64 0
      flow/SCKeepaliveSource.h
  100. 309 0
      flow/SPProtoDecoder.c

+ 99 - 0
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)

+ 339 - 0
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.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    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.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.

+ 73 - 0
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-<version>.tar.bz2
+$ mkdir build
+$ cd build
+$ cmake ../badvpn-<version> -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)).

+ 129 - 0
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/<user>/mingw so that you have /home/<user>/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 <root>.
+
+- 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/<user>/mingw/cross_win32/bin
+      $ perl Configure mingw --cross-compile-prefix=i686-w64-mingw32- --prefix=/ shared disable-capieng
+      $ make depend
+      $ make
+      $ make INSTALL_PREFIX=<root> 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=<root> 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 <nss_source_dir>/mozilla/security/nss
+        $ make nss_build_all
+        $ <badvpn_source_dir>/scripts/copy_nss ../../dist <root>
+
+      If you will be compiling BadVPN from Linux, use an empty folder for <root> in the above command,
+      and copy its contents into <root> on the Linux system.
+
+- Compile it:
+  Choose a folder <dest> where the resulting binaries will be copied.
+
+  - If you are compiling BadVPN from Linux:
+
+    Copy <badvpn_source_dir>/scripts/toolchain.cmake to <root>/toolchain.cmake.
+
+    Copy <badvpn_source_dir>/scripts/cmake to <root>/ .
+    In that file, substitute <root> and <user>.
+
+    Create an empty folder, call it <build>, and open a shell in that folder.
+    Now run:
+
+      $ <root>/cmake <path_to_badvpn_source> -DCMAKE_INSTALL_PREFIX=<dest>
+      $ make
+      $ make install
+      $ cp <root>/bin/*.dll <dest>/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 <build>, and open a command prompt in that folder.
+    Now run:
+
+      > cmake <badvpn_source_dir> -G "MinGW Makefiles" -DCMAKE_FIND_ROOT_PATH=<root> -DCMAKE_INSTALL_PREFIX=<dest>
+      > c:\mingw\bin\mingw32-make.exe
+      > c:\mingw\bin\mingw32-make.exe install
+      > copy <root>\bin\*.dll <dest>\bin\
+
+- Test it:
+
+  Execute <dest>\bin\badvpn-server.exe (on Windows or with Wine). It should print something like this and wait forever:
+
+    NOTICE(server): initializing BadVPN server <version>
+    NOTICE(server): entering event loop

+ 2 - 0
TODO

@@ -0,0 +1,2 @@
+- in the server, somehow handle when a peer message is discarded
+- detect non-working link and try more addresses

+ 274 - 0
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 " <stdout/syslog>]"
+.br
+(logger=syslog?
+.br
+.RS
+.br
+.RB "[" --syslog-facility " <string>]"
+.br
+.RB "[" --syslog-ident " <string>]"
+.br
+.RE
+)
+.br
+.RB "[" --loglevel " <0-5/none/error/warning/notice/info/debug>]"
+.br
+.RB "[" --channel-loglevel " <channel-name> <0-5/none/error/warning/notice/info/debug>] ..."
+.br
+.RB "[" --ssl " " --nssdb " <string> " --client-cert-name " <string>]"
+.br
+.RB "[" --server-name " <string>]"
+.br
+.BR --server-addr " <addr>"
+.br
+.RB "[" --tapdev " <name>]"
+.br
+.RB "[" --scope " <scope_name>] ..."
+.br
+[
+.br
+.RS
+.BR --bind-addr " <addr>"
+.br
+.RB "(transport-mode=udp? " --num-ports " <num>)"
+.br
+.RB "[" --ext-addr " <addr / {server_reported}:port> <scope_name>] ..."
+.br
+.RE
+]
+.br
+.BR --transport-mode " <udp/tcp>"
+.br
+(transport-mode=udp?
+.br
+.RS
+.BR --encryption-mode " <blowfish/aes/none>"
+.br
+.BR --hash-mode " <md5/sha1/none>"
+.br
+.RB "[" --otp " <blowfish/aes> <num> <num-warn>]"
+.br
+.RB "[" --fragmentation-latency " <milliseconds>]"
+.br
+.RE
+)
+.br
+(transport-mode=tcp?
+.br
+.RS
+.RB "(ssl? [" --peer-ssl "])"
+.br
+.RE
+)
+.br
+.RB "[" --send-buffer-size " <num-packets>]"
+.br
+.RB "[" --send-buffer-relay-size " <num-packets>]"
+.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 " <stdout/syslog>"
+Select where to log messages. Default is stdout. Syslog is not available on Windows.
+.TP
+.BR --syslog-facility " <string>"
+When logging to syslog, set the logging facility. The facility name must be in lower case.
+.TP
+.BR --syslog-ident " <string>"
+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 " <channel-name> <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 " <string>"
+When using TLS, the NSS database to use. Probably something like sql:/some/folder.
+.TP
+.BR --client-cert-name " <string>"
+When using TLS, the name of the certificate to use. The certificate must be readily accessible.
+.TP
+.BR --server-name " <string>"
+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 " <addr>"
+Set the address for the server to listen on. See below for address format.
+.TP
+.BR --tapdev " <name>"
+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 " <scope_name>"
+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 " <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 " <num>"
+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 " <addr / {server_reported}:port> <scope_name>"
+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 " <udp/tcp>"
+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 " <blowfish/aes/none>"
+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 " <md5/sha1/none>"
+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 " <blowfish/aes> <num> <num-warn>"
+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 " <milliseconds>"
+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 " <num-packets>"
+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 " <num-packets>"
+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 <user> -t tapN`,
+and to create it with openvpn, use `openvpn --mktun --user <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
+<http://openvpn.net/index.php/open-source/downloads.html>.
+.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 "<driver_name>:<interface_name>"` to the client program, where <driver_name> is the name of
+the TAP driver (tap0901 for OpenVPN 2.1 and 2.2) (case sensitive), and <interface_name> 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 <ambrop7@gmail.com>

+ 187 - 0
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 " <stdout/syslog>]"
+.br
+(logger=syslog?
+.br
+.RS
+.br
+.RB "[" --syslog-facility " <string>]"
+.br
+.RB "[" --syslog-ident " <string>]"
+.br
+.RE
+)
+.br
+.RB "[" --loglevel " <0-5/none/error/warning/notice/info/debug>]"
+.br
+.RB "[" --channel-loglevel " <channel-name> <0-5/none/error/warning/notice/info/debug>] ..."
+.br
+.RB "[" --listen-addr " <addr>] ..."
+.br
+.RB "[" --ssl " " --nssdb " <string> " --server-cert-name " <string>]"
+.br
+.RB "[" --comm-predicate " <string>]"
+.br
+.RB "[" --relay-predicate " <string>]"
+.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 " <stdout/syslog>"
+Select where to log messages. Default is stdout. Syslog is not available on Windows.
+.TP
+.BR --syslog-facility " <string>"
+When logging to syslog, set the logging facility. The facility name must be in lower case.
+.TP
+.BR --syslog-ident " <string>"
+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 " <channel-name> <0-5/none/error/warning/notice/info/debug>"
+Set the logging level for a specific logging channel.
+.TP
+.BR --listen-addr " <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 " <string>"
+When using TLS, the NSS database to use. Probably something like sql:/some/folder.
+.TP
+.BR --server-cert-name " <string>"
+When using TLS, the name of the certificate to use. The certificate must be readily accessible.
+.TP
+.BR --comm-predicate " <string>"
+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 " <string>"
+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 <ambrop7@gmail.com>

+ 321 - 0
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 "<insert_server_name>" -s "CN=<insert_server_name>" -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 "<insert_server_name>"
+.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-<insert_name>" -s "CN=peer-<insert_name>" -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-<insert_name>.p12 -n "peer-<insert_name>"
+.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-<insert_name>.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 <insert_server_local_address>: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 "<insert_server_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-<insert_name>"
+.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 <insert_NAT_routers_external_IP>:<insert_start_of_forwarded_port_range> 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 <ambrop7@gmail.com>

+ 12 - 0
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

+ 121 - 0
blog_generator/blog.php

@@ -0,0 +1,121 @@
+<?php
+
+require_once "blog_functions.php";
+
+function assert_failure ($script, $line, $message)
+{
+    if ($message == "") {
+        fatal_error("Assertion failure at {$script}:{$line}");
+    } else {
+        fatal_error("Assertion failure at {$script}:{$line}: {$message}");
+    }
+}
+
+assert_options(ASSERT_CALLBACK, "assert_failure");
+
+function print_help ($name)
+{
+    echo <<<EOD
+Usage: {$name}
+    --input-file <file>         Input channels file.
+    --output-dir <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 = <<<EOD
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_{$ch_name[1]}
+
+EOD;
+
+    $channels_defines .= <<<EOD
+#define BLOG_CHANNEL_{$ch_name[1]} {$i}
+
+EOD;
+
+    $channels_list .= <<<EOD
+{.name = "{$ch_name[1]}", .loglevel = {$ch_priority[1]}},
+
+EOD;
+
+    if (file_put_contents("{$output_dir}/blog_channel_{$ch_name[1]}.h", $channel_file) === NULL) {
+        fatal_error("{$input_file}: Failed to write channel file");
+    }
+
+    $i++;
+}
+
+$channels_defines .= <<<EOD
+#define BLOG_NUM_CHANNELS {$i}
+
+EOD;
+
+if (file_put_contents("{$output_dir}/blog_channels_defines.h", $channels_defines) === NULL) {
+    fatal_error("{$input_file}: Failed to write channels defines file");
+}
+
+if (file_put_contents("{$output_dir}/blog_channels_list.h", $channels_list) === NULL) {
+    fatal_error("{$input_file}: Failed to write channels list file");
+}
+

+ 35 - 0
blog_generator/blog_functions.php

@@ -0,0 +1,35 @@
+<?php
+
+function tokenize ($str, &$out) {
+    $out = array();
+
+    while (strlen($str) > 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);
+}

+ 64 - 0
bproto/BProto.h

@@ -0,0 +1,64 @@
+/**
+ * @file BProto.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+
+#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

+ 99 - 0
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
+        );
+    }.

+ 560 - 0
bproto_generator/ProtoParser.php

@@ -0,0 +1,560 @@
+<?php
+
+
+/*
+
+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.
+
+*/
+
+class ProtoParser extends lime_parser {
+var $qi = 0;
+var $i = array (
+  0 => 
+  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,
+  ),
+);
+}

+ 106 - 0
bproto_generator/bproto.php

@@ -0,0 +1,106 @@
+<?php
+/*
+    BProto generator
+    Copyright (C) Ambroz Bizjak, 2010
+
+    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.
+*/
+
+require_once "lime/parse_engine.php";
+require_once "ProtoParser.php";
+require_once "bproto_functions.php";
+
+function assert_failure ($script, $line, $message)
+{
+    if ($message == "") {
+        fatal_error("Assertion failure at {$script}:{$line}");
+    } else {
+        fatal_error("Assertion failure at {$script}:{$line}: {$message}");
+    }
+}
+
+assert_options(ASSERT_CALLBACK, "assert_failure");
+
+function print_help ($name)
+{
+    echo <<<EOD
+Usage: {$name}
+    --name <string>             Output file prefix.
+    --input-file <file>         Message file to generate source for.
+    --output-dir <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");
+}

+ 748 - 0
bproto_generator/bproto_functions.php

@@ -0,0 +1,748 @@
+<?php
+
+function tokenize ($str, &$out) {
+    $out = array();
+
+    while (strlen($str) > 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 <<<EOD
+/*
+    DO NOT EDIT THIS FILE!
+    This file was automatically generated by the bproto generator.
+*/
+
+#include <stdint.h>
+
+#include <misc/debug.h>
+#include <misc/byteorder.h>
+#include <bproto/BProto.h>
+
+
+EOD;
+
+    foreach ($directives as $directive) {
+        if ($directive["type"] == "include") {
+            echo <<<EOD
+#include "{$directive["file"]}"
+
+EOD;
+        }
+    }
+
+    echo <<<EOD
+
+
+EOD;
+
+    foreach ($messages as $msg) {
+
+        foreach ($msg["entries"] as $entry) {
+            $def = make_size_define($msg, $entry);
+            echo <<<EOD
+{$def}
+
+EOD;
+        }
+
+        echo <<<EOD
+
+typedef struct {
+    uint8_t *out;
+    int used;
+
+EOD;
+
+        foreach ($msg["entries"] as $entry) {
+            echo <<<EOD
+    int {$entry["name"]}_count;
+
+EOD;
+        }
+
+        echo <<<EOD
+} {$msg["name"]}Writer;
+
+static void {$msg["name"]}Writer_Init ({$msg["name"]}Writer *o, uint8_t *out);
+static int {$msg["name"]}Writer_Finish ({$msg["name"]}Writer *o);
+
+EOD;
+
+        foreach ($msg["entries"] as $entry) {
+            $decl = make_writer_decl($msg, $entry);
+            echo <<<EOD
+static {$decl};
+
+EOD;
+        }
+
+        echo <<<EOD
+
+typedef struct {
+    uint8_t *buf;
+    int buf_len;
+
+EOD;
+        foreach ($msg["entries"] as $entry) {
+            echo <<<EOD
+    int {$entry["name"]}_start;
+    int {$entry["name"]}_span;
+    int {$entry["name"]}_pos;
+
+EOD;
+        }
+
+        echo <<<EOD
+} {$msg["name"]}Parser;
+
+static int {$msg["name"]}Parser_Init ({$msg["name"]}Parser *o, uint8_t *buf, int buf_len);
+static int {$msg["name"]}Parser_GotEverything ({$msg["name"]}Parser *o);
+
+EOD;
+
+        foreach ($msg["entries"] as $entry) {
+            $decl = make_parser_decl($msg, $entry);
+            $reset_decl = make_parser_reset_decl($msg, $entry);
+            $forward_decl = make_parser_forward_decl($msg, $entry);
+            echo <<<EOD
+static {$decl};
+static {$reset_decl};
+static {$forward_decl};
+
+EOD;
+        }
+
+        echo <<<EOD
+
+void {$msg["name"]}Writer_Init ({$msg["name"]}Writer *o, uint8_t *out)
+{
+    o->out = out;
+    o->used = 0;
+
+EOD;
+
+        foreach ($msg["entries"] as $entry) {
+            echo <<<EOD
+    o->{$entry["name"]}_count = 0;
+
+EOD;
+        }
+
+        echo <<<EOD
+}
+
+int {$msg["name"]}Writer_Finish ({$msg["name"]}Writer *o)
+{
+    ASSERT(o->used >= 0)
+
+EOD;
+
+        foreach ($msg["entries"] as $entry) {
+            $ass = make_finish_assert($msg, $entry);
+            echo <<<EOD
+    {$ass}
+
+EOD;
+        }
+
+        echo <<<EOD
+
+    return o->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 <<<EOD
+{$decl}
+{
+    ASSERT(o->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 <<<EOD
+    ((struct BProto_uint{$entry["type"]["size"]}_s *)(o->out + o->used))->v = htol{$entry["type"]["size"]}(v);
+    o->used += sizeof(struct BProto_uint{$entry["type"]["size"]}_s);
+
+EOD;
+                    break;
+                case "data":
+                    echo <<<EOD
+    ((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;
+
+EOD;
+                    break;
+                case "constdata":
+                    echo <<<EOD
+    ((struct BProto_data_header_s *)(o->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 <<<EOD
+
+    o->{$entry["name"]}_count++;
+
+EOD;
+            if (in_array($entry["type"]["type"], array("data", "constdata"))) {
+                echo <<<EOD
+
+    return dest;
+
+EOD;
+            }
+
+            echo <<<EOD
+}
+
+
+EOD;
+        }
+
+        echo <<<EOD
+int {$msg["name"]}Parser_Init ({$msg["name"]}Parser *o, uint8_t *buf, int buf_len)
+{
+    ASSERT(buf_len >= 0)
+
+    o->buf = buf;
+    o->buf_len = buf_len;
+
+EOD;
+
+        foreach ($msg["entries"] as $entry) {
+            echo <<<EOD
+    o->{$entry["name"]}_start = o->buf_len;
+    o->{$entry["name"]}_span = 0;
+    o->{$entry["name"]}_pos = 0;
+
+EOD;
+        }
+
+        echo <<<EOD
+
+
+EOD;
+
+        foreach ($msg["entries"] as $entry) {
+            echo <<<EOD
+    int {$entry["name"]}_count = 0;
+
+EOD;
+        }
+
+        echo <<<EOD
+
+    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) {
+
+EOD;
+
+        foreach (array(8, 16, 32, 64) as $bits) {
+            echo <<<EOD
+            case BPROTO_TYPE_UINT{$bits}: {
+                if (!(left >= 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 <<<EOD
+                    case {$entry["id"]}:
+                        if (o->{$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 <<<EOD
+                    default:
+                        return 0;
+                }
+            } break;
+
+EOD;
+        }
+
+        echo <<<EOD
+            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) {
+
+EOD;
+
+        foreach ($msg["entries"] as $entry) {
+            if (!in_array($entry["type"]["type"], array("data", "constdata"))) {
+                continue;
+            }
+            $type = make_type_name($msg, $entry);
+            echo <<<EOD
+                    case {$entry["id"]}:
+                        if (!(type == {$type})) {
+                            return 0;
+                        }
+
+EOD;
+            if ($entry["type"]["type"] == "constdata") {
+                echo <<<EOD
+                        if (!(payload_len == ({$entry["type"]["size"]}))) {
+                            return 0;
+                        }
+
+EOD;
+            }
+            echo <<<EOD
+                        if (o->{$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 <<<EOD
+                    default:
+                        return 0;
+                }
+            } break;
+            default:
+                return 0;
+        }
+    }
+
+
+EOD;
+
+        foreach ($msg["entries"] as $entry) {
+            $cond = "";
+            switch ($entry["cardinality"]) {
+                case "repeated":
+                    break;
+                case "required repeated":
+                    $cond = "{$entry["name"]}_count >= 1";
+                    break;
+                case "optional":
+                    $cond = "{$entry["name"]}_count <= 1";
+                    break;
+                case "required":
+                    $cond = "{$entry["name"]}_count == 1";
+                    break;
+                default:
+                    assert(0);
+            }
+            if ($cond) {
+                echo <<<EOD
+    if (!({$cond})) {
+        return 0;
+    }
+
+EOD;
+            }
+        }
+
+        echo <<<EOD
+
+    return 1;
+}
+
+int {$msg["name"]}Parser_GotEverything ({$msg["name"]}Parser *o)
+{
+    return (
+
+EOD;
+
+        $first = 1;
+        foreach ($msg["entries"] as $entry) {
+            if ($first) {
+                $first = 0;
+            } else {
+                echo <<<EOD
+        &&
+
+EOD;
+            }
+            echo <<<EOD
+        o->{$entry["name"]}_pos == o->{$entry["name"]}_span
+
+EOD;
+        }
+    
+
+        echo <<<EOD
+    );
+}
+
+
+EOD;
+
+        foreach ($msg["entries"] as $entry) {
+            $decl = make_parser_decl($msg, $entry);
+            $reset_decl = make_parser_reset_decl($msg, $entry);
+            $forward_decl = make_parser_forward_decl($msg, $entry);
+            $type = make_type_name($msg, $entry);
+
+            echo <<<EOD
+{$decl}
+{
+    ASSERT(o->{$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 <<<EOD
+            case BPROTO_TYPE_UINT{$bits}: {
+                ASSERT(left >= 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 <<<EOD
+
+                if (id == {$entry["id"]}) {
+                    *v = ltoh{$bits}(val->v);
+                    return 1;
+                }
+
+EOD;
+                }
+
+                echo <<<EOD
+            } break;
+
+EOD;
+            }
+
+            echo <<<EOD
+            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->{$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 <<<EOD
+
+                if (type == BPROTO_TYPE_DATA && id == {$entry["id"]}) {
+                    *data = payload;
+                    *data_len = payload_len;
+                    return 1;
+                }
+
+EOD;
+            }
+            else if ($entry["type"]["type"] == "constdata") {
+                echo <<<EOD
+
+                if (type == BPROTO_TYPE_CONSTDATA && id == {$entry["id"]}) {
+                    *data = payload;
+                    return 1;
+                }
+
+EOD;
+            }
+
+            echo <<<EOD
+            } break;
+            default:
+                ASSERT(0);
+        }
+    }
+
+    return 0;
+}
+
+{$reset_decl}
+{
+    o->{$entry["name"]}_pos = 0;
+}
+
+{$forward_decl}
+{
+    o->{$entry["name"]}_pos = o->{$entry["name"]}_span;
+}
+
+
+EOD;
+        }
+    }
+
+    return ob_get_clean();
+}

+ 93 - 0
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
+        );
+    }.

+ 503 - 0
bstruct_generator/StructParser.php

@@ -0,0 +1,503 @@
+<?php
+
+
+/*
+
+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.
+
+*/
+
+class StructParser extends lime_parser {
+var $qi = 0;
+var $i = array (
+  0 => 
+  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,
+  ),
+);
+}

+ 106 - 0
bstruct_generator/bstruct.php

@@ -0,0 +1,106 @@
+<?php
+/*
+    BStruct generator
+    Copyright (C) Ambroz Bizjak, 2010
+
+    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.
+*/
+
+require_once "lime/parse_engine.php";
+require_once "StructParser.php";
+require_once "bstruct_functions.php";
+
+function assert_failure ($script, $line, $message)
+{
+    if ($message == "") {
+        fatal_error("Assertion failure at {$script}:{$line}");
+    } else {
+        fatal_error("Assertion failure at {$script}:{$line}: {$message}");
+    }
+}
+
+assert_options(ASSERT_CALLBACK, "assert_failure");
+
+function print_help ($name)
+{
+    echo <<<EOD
+Usage: {$name}
+    --input-file <file>         Message file to generate source for.
+    --output-dir <dir>          Destination directory for generated files.
+    [--file-prefix <string>]    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");
+}

+ 269 - 0
bstruct_generator/bstruct_functions.php

@@ -0,0 +1,269 @@
+<?php
+
+function tokenize ($str, &$out) {
+    $out = array();
+
+    while (strlen($str) > 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 <<<EOD
+/*
+    DO NOT EDIT THIS FILE!
+    This file was automatically generated by the bstruct generator.
+*/
+
+#include <stdint.h>
+
+#include <misc/balign.h>
+
+
+EOD;
+
+    foreach ($directives as $directive) {
+        if ($directive["type"] == "include") {
+            echo <<<EOD
+#include "{$directive["file"]}"
+
+EOD;
+        }
+    }
+
+    echo <<<EOD
+
+
+EOD;
+
+    foreach ($structures as $struct) {
+        if ($struct["parameters"] == "") {
+            $add_parameters = "";
+        } else {
+            $add_parameters = ", {$struct["parameters"]}";
+        }
+
+        echo <<<EOD
+typedef struct {$struct["name"]}_struct {$struct["name"]};
+
+typedef struct {
+
+EOD;
+
+        foreach ($struct["entries"] as $entry) {
+            if ($entry["type"]["type"] == "structure") {
+                echo <<<EOD
+    {$entry["type"]["name"]}Params {$entry["name"]}_params;
+
+EOD;
+            }
+
+            echo <<<EOD
+    int {$entry["name"]}_off;
+    int {$entry["name"]}_size;
+    #ifndef NDEBUG
+    int {$entry["name"]}_count;
+    #endif
+
+
+EOD;
+        }
+
+        echo <<<EOD
+    int len;
+    int align;
+} {$struct["name"]}Params;
+
+static void {$struct["name"]}Params_Init ({$struct["name"]}Params *o{$add_parameters})
+{
+    int cur_size;
+    int cur_align;
+    int cur_count;
+
+    o->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 <<<EOD
+    {$entry["type"]["name"]}Params_Init(&o->{$entry["name"]}_params{$init_add_parameters});
+
+EOD;
+            }
+
+            $size = make_size($entry);
+            $align = make_align($entry);
+
+
+
+            echo <<<EOD
+    cur_size = {$size};
+    cur_align = {$align};
+    cur_count = ({$entry["num"]});
+
+EOD;
+
+            $off = "BALIGN_UP_N(o->len, cur_align)";
+            $len = "(cur_count * cur_size)";
+
+            echo <<<EOD
+    o->{$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 <<<EOD
+}
+
+
+EOD;
+
+        $prev = NULL;
+        foreach ($struct["entries"] as $entry) {
+            echo <<<EOD
+static {$entry["type"]["ctype"]} * {$struct["name"]}_{$entry["name"]} ({$struct["name"]}Params *o, {$struct["name"]} *s)
+{
+    return ({$entry["type"]["ctype"]} *)((uint8_t *)s + o->{$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 <<<EOD
+
+EOD;
+
+    }
+
+    return ob_get_clean();
+}

+ 13 - 0
client/CMakeLists.txt

@@ -0,0 +1,13 @@
+add_executable(badvpn-client
+    client.c
+    StreamPeerIO.c
+    DatagramPeerIO.c
+    PasswordListener.c
+    DataProto.c
+)
+target_link_libraries(badvpn-client system flow tuntap server_conection listener ${LIBCRYPTO_LIBRARIES} ${NSPR_LIBRARIES} ${NSS_LIBRARIES})
+
+install(
+    TARGETS badvpn-client
+    RUNTIME DESTINATION bin
+)

+ 731 - 0
client/DataProto.c

@@ -0,0 +1,731 @@
+/**
+ * @file DataProto.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include <protocol/dataproto.h>
+#include <misc/offset.h>
+#include <misc/byteorder.h>
+#include <misc/debug.h>
+#include <system/BLog.h>
+
+#include <client/DataProto.h>
+
+#include <generated/blog_channel_DataProto.h>
+
+#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);
+    }
+}

+ 297 - 0
client/DataProto.h

@@ -0,0 +1,297 @@
+/**
+ * @file DataProto.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+
+#include <protocol/scproto.h>
+#include <misc/dead.h>
+#include <misc/debugcounter.h>
+#include <misc/debug.h>
+#include <structure/LinkedList2.h>
+#include <structure/BAVL.h>
+#include <system/DebugObject.h>
+#include <system/BReactor.h>
+#include <flow/PacketPassFairQueue.h>
+#include <flow/PacketPassInactivityMonitor.h>
+#include <flow/PacketPassNotifier.h>
+#include <flow/DataProtoKeepaliveSource.h>
+#include <flow/PacketRecvBlocker.h>
+#include <flow/SinglePacketBuffer.h>
+#include <flow/PacketBufferAsyncInput.h>
+#include <flow/PacketBuffer.h>
+#include <flow/PacketPassConnector.h>
+
+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

+ 511 - 0
client/DatagramPeerIO.c

@@ -0,0 +1,511 @@
+/**
+ * @file DatagramPeerIO.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+
+#include <misc/debug.h>
+#include <system/BLog.h>
+
+#include <client/DatagramPeerIO.h>
+
+#include <generated/blog_channel_DatagramPeerIO.h>
+
+#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;
+}

+ 275 - 0
client/DatagramPeerIO.h

@@ -0,0 +1,275 @@
+/**
+ * @file DatagramPeerIO.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+
+#include <misc/dead.h>
+#include <misc/debug.h>
+#include <protocol/spproto.h>
+#include <protocol/fragmentproto.h>
+#include <system/DebugObject.h>
+#include <system/BReactor.h>
+#include <system/BAddr.h>
+#include <system/BSocket.h>
+#include <system/BTime.h>
+#include <flow/PacketPassInterface.h>
+#include <flow/DatagramSocketSink.h>
+#include <flow/PacketPassConnector.h>
+#include <flow/SinglePacketBuffer.h>
+#include <flow/SPProtoEncoder.h>
+#include <flow/FragmentProtoDisassembler.h>
+#include <flow/DatagramSocketSource.h>
+#include <flow/PacketRecvConnector.h>
+#include <flow/SPProtoDecoder.h>
+#include <flow/FragmentProtoAssembler.h>
+#include <flow/PacketPassNotifier.h>
+#include <flow/PacketRecvNotifier.h>
+
+/**
+ * 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

+ 378 - 0
client/PasswordListener.c

@@ -0,0 +1,378 @@
+/**
+ * @file PasswordListener.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdlib.h>
+
+#include <openssl/rand.h>
+
+#include <prerror.h>
+
+#include <ssl.h>
+
+#include <misc/debug.h>
+#include <misc/offset.h>
+#include <misc/byteorder.h>
+#include <nspr_support/DummyPRFileDesc.h>
+#include <nspr_support/BSocketPRFileDesc.h>
+
+#include <client/PasswordListener.h>
+
+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);
+}

+ 143 - 0
client/PasswordListener.h

@@ -0,0 +1,143 @@
+/**
+ * @file PasswordListener.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+
+#include <prio.h>
+
+#include <cert.h>
+#include <keyhi.h>
+
+#include <misc/dead.h>
+#include <misc/debug.h>
+#include <system/DebugObject.h>
+#include <misc/sslsocket.h>
+#include <structure/LinkedList2.h>
+#include <structure/BAVL.h>
+#include <nspr_support/BPRFileDesc.h>
+#include <listener/Listener.h>
+
+/**
+ * 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

+ 806 - 0
client/StreamPeerIO.c

@@ -0,0 +1,806 @@
+/**
+ * @file StreamPeerIO.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdlib.h>
+
+#include <openssl/rand.h>
+
+#include <prio.h>
+
+#include <ssl.h>
+#include <sslerr.h>
+
+#include <misc/offset.h>
+#include <misc/debug.h>
+#include <misc/jenkins_hash.h>
+#include <misc/byteorder.h>
+#include <system/BLog.h>
+
+#include <client/StreamPeerIO.h>
+
+#include <generated/blog_channel_StreamPeerIO.h>
+
+#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;
+}

+ 217 - 0
client/StreamPeerIO.h

@@ -0,0 +1,217 @@
+/**
+ * @file StreamPeerIO.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+
+#include <cert.h>
+#include <keyhi.h>
+
+#include <misc/dead.h>
+#include <misc/debug.h>
+#include <system/DebugObject.h>
+#include <system/BReactor.h>
+#include <system/BSocket.h>
+#include <structure/LinkedList2.h>
+#include <flow/StreamSocketSource.h>
+#include <flow/PacketProtoDecoder.h>
+#include <flow/StreamSocketSink.h>
+#include <flow/PacketStreamSender.h>
+#include <flow/SinglePacketBuffer.h>
+#include <flow/PacketProtoEncoder.h>
+#include <flow/PacketCopier.h>
+#include <flow/PacketPassConnector.h>
+#include <flow/StreamRecvConnector.h>
+#include <nspr_support/PRStreamSink.h>
+#include <nspr_support/PRStreamSource.h>
+
+#include <client/PasswordListener.h>
+
+/**
+ * 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

+ 3718 - 0
client/client.c

@@ -0,0 +1,3718 @@
+/**
+ * @file client.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <protocol/msgproto.h>
+#include <protocol/addr.h>
+#include <protocol/dataproto.h>
+#include <misc/version.h>
+#include <misc/debug.h>
+#include <misc/offset.h>
+#include <misc/jenkins_hash.h>
+#include <misc/byteorder.h>
+#include <misc/ethernet_proto.h>
+#include <misc/ipv4_proto.h>
+#include <misc/igmp_proto.h>
+#include <misc/nsskey.h>
+#include <misc/brandom.h>
+#include <misc/loglevel.h>
+#include <misc/dead.h>
+#include <misc/loggers_string.h>
+#include <structure/LinkedList2.h>
+#include <nspr_support/DummyPRFileDesc.h>
+#include <nspr_support/BSocketPRFileDesc.h>
+#include <system/BLog.h>
+#include <system/BSignal.h>
+#include <system/BTime.h>
+#include <system/DebugObject.h>
+#include <server_connection/ServerConnection.h>
+
+#ifndef BADVPN_USE_WINAPI
+#include <system/BLog_syslog.h>
+#endif
+
+#include <client/client.h>
+
+#include <generated/blog_channel_client.h>
+
+#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 <string>]\n"
+        "            [--syslog-ident <string>]\n"
+        "        )\n"
+        #endif
+        "        [--loglevel <0-5/none/error/warning/notice/info/debug>]\n"
+        "        [--channel-loglevel <channel-name> <0-5/none/error/warning/notice/info/debug>] ...\n"
+        "        [--ssl --nssdb <string> --client-cert-name <string>]\n"
+        "        [--server-name <string>]\n"
+        "        --server-addr <addr>\n"
+        "        [--tapdev <name>]\n"
+        "        [--scope <scope_name>] ...\n"
+        "        [\n"
+        "            --bind-addr <addr>\n"
+        "            (transport-mode=udp? --num-ports <num>)\n"
+        "            [--ext-addr <addr / {server_reported}:port> <scope_name>] ...\n"
+        "        ] ...\n"
+        "        --transport-mode <udp/tcp>\n"
+        "        (transport-mode=udp?\n"
+        "            --encryption-mode <blowfish/aes/none>\n"
+        "            --hash-mode <md5/sha1/none>\n"
+        "            [--otp <blowfish/aes> <num> <num-warn>]\n"
+        "            [--fragmentation-latency <milliseconds>]\n"
+        "        )\n"
+        "        (transport-mode=tcp?\n"
+        "            (ssl? [--peer-ssl])\n"
+        "        )\n"
+        "        [--send-buffer-size <num-packets>]\n"
+        "        [--send-buffer-relay-size <num-packets>]\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;
+}

+ 212 - 0
client/client.h

@@ -0,0 +1,212 @@
+/**
+ * @file client.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdio.h>
+#include <stdint.h>
+
+#include <protocol/scproto.h>
+#include <structure/LinkedList2.h>
+#include <structure/HashTable.h>
+#include <structure/BAVL.h>
+#include <flow/SinglePacketBuffer.h>
+#include <tuntap/BTap.h>
+#include <client/DatagramPeerIO.h>
+#include <client/StreamPeerIO.h>
+#include <client/DataProto.h>
+
+// 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;
+};

+ 22 - 0
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.

+ 113 - 0
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, <ch.ehrlicher@gmx.de>
+# 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)

+ 57 - 0
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, <ambrop7@gmail.com>
+#
+# 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)

+ 57 - 0
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, <ambrop7@gmail.com>
+#
+# 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)

+ 72 - 0
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, <ambrop7@gmail.com>
+#
+# 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)

+ 25 - 0
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})

+ 100 - 0
examples/FastPacketSource.h

@@ -0,0 +1,100 @@
+/**
+ * @file FastPacketSource.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+#include <string.h>
+
+#include <misc/dead.h>
+#include <misc/debug.h>
+#include <system/BPending.h>
+#include <flow/PacketPassInterface.h>
+
+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

+ 85 - 0
examples/RandomPacketSink.h

@@ -0,0 +1,85 @@
+/**
+ * @file RandomPacketSink.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdio.h>
+
+#include <misc/brandom.h>
+#include <system/BReactor.h>
+#include <flow/PacketPassInterface.h>
+
+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

+ 89 - 0
examples/TimerPacketSink.h

@@ -0,0 +1,89 @@
+/**
+ * @file TimerPacketSink.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdio.h>
+
+#include <system/BReactor.h>
+#include <flow/PacketPassInterface.h>
+
+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

+ 139 - 0
examples/bavl_test.c

@@ -0,0 +1,139 @@
+/**
+ * @file bavl_test.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdlib.h>
+
+#include <misc/offset.h>
+#include <misc/brandom.h>
+#include <misc/debug.h>
+#include <structure/BAVL.h>
+
+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 <num> <numrandomdelete>\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;
+}

+ 130 - 0
examples/bheap_test.c

@@ -0,0 +1,130 @@
+/**
+ * @file bheap_test.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <misc/offset.h>
+#include <misc/brandom.h>
+#include <structure/BHeap.h>
+
+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 <num> <numrandomdelete>\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;
+}

+ 77 - 0
examples/btimer_example.c

@@ -0,0 +1,77 @@
+/**
+ * @file btimer_example.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include <system/BReactor.h>
+#include <system/BLog.h>
+#include <system/BTime.h>
+
+// 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<TIMER_NUM; i++) {
+        BTimer *timer = &timers[i];
+        BTimer_Init(timer, 0, (BTimer_handler)handle_timer, timer);
+        BReactor_SetTimer(&sys, timer);
+    }
+    
+    int ret = BReactor_Exec(&sys);
+    BReactor_Free(&sys);
+    return ret;
+}

+ 118 - 0
examples/fairqueue_test.c

@@ -0,0 +1,118 @@
+/**
+ * @file fairqueue_test.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <string.h>
+#include <stdio.h>
+
+#include <misc/debug.h>
+#include <system/BReactor.h>
+#include <system/BLog.h>
+#include <system/BTime.h>
+#include <flow/PacketPassFairQueue.h>
+#include <examples/FastPacketSource.h>
+#include <examples/TimerPacketSink.h>
+
+#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;
+}

+ 81 - 0
examples/fairqueue_test2.c

@@ -0,0 +1,81 @@
+/**
+ * @file fairqueue_test2.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <string.h>
+
+#include <misc/debug.h>
+#include <system/BReactor.h>
+#include <system/BLog.h>
+#include <system/BTime.h>
+#include <flow/PacketPassFairQueue.h>
+#include <examples/FastPacketSource.h>
+#include <examples/RandomPacketSink.h>
+
+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;
+}

+ 119 - 0
examples/hashtable_bench.c

@@ -0,0 +1,119 @@
+/**
+ * @file hashtable_bench.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdlib.h>
+
+#include <misc/offset.h>
+#include <misc/brandom.h>
+#include <misc/debug.h>
+#include <misc/jenkins_hash.h>
+#include <structure/HashTable.h>
+
+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 <num> <numrandomdelete>\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;
+}

+ 94 - 0
examples/hashtable_example.c

@@ -0,0 +1,94 @@
+/**
+ * @file hashtable_example.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stddef.h>
+
+#include <misc/debug.h>
+#include <misc/jenkins_hash.h>
+#include <structure/HashTable.h>
+
+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;
+}

+ 161 - 0
examples/linkedlist2_example.c

@@ -0,0 +1,161 @@
+/**
+ * @file linkedlist2_example.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdio.h>
+#include <stdint.h>
+#include <stddef.h>
+
+#include <structure/LinkedList2.h>
+
+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;
+}

+ 106 - 0
examples/predicate_test.c

@@ -0,0 +1,106 @@
+/**
+ * @file predicate_test.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdio.h>
+#include <string.h>
+
+#include <predicate/BPredicate.h>
+#include <system/BLog.h>
+
+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 <predicate>\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;
+}

+ 7 - 0
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
+)

+ 719 - 0
flooder/flooder.c

@@ -0,0 +1,719 @@
+/**
+ * @file flooder.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <protocol/addr.h>
+#include <protocol/scproto.h>
+#include <misc/loglevel.h>
+#include <misc/version.h>
+#include <misc/nsskey.h>
+#include <misc/byteorder.h>
+#include <misc/loggers_string.h>
+#include <misc/dead.h>
+#include <system/BLog.h>
+#include <system/BReactor.h>
+#include <system/BSignal.h>
+#include <flow/SinglePacketBuffer.h>
+#include <flow/PacketProtoEncoder.h>
+#include <nspr_support/DummyPRFileDesc.h>
+#include <nspr_support/BSocketPRFileDesc.h>
+#include <server_connection/ServerConnection.h>
+
+#ifndef BADVPN_USE_WINAPI
+#include <system/BLog_syslog.h>
+#endif
+
+#include <flooder/flooder.h>
+
+#include <generated/blog_channel_flooder.h>
+
+#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 <string>]\n"
+        "            [--syslog-ident <string>]\n"
+        "        )\n"
+        #endif
+        "        [--loglevel <0-5/none/error/warning/notice/info/debug>]\n"
+        "        [--channel-loglevel <channel-name> <0-5/none/error/warning/notice/info/debug>] ...\n"
+        "        [--ssl --nssdb <string> --client-cert-name <string>]\n"
+        "        [--server-name <string>]\n"
+        "        --server-addr <addr>\n"
+        "        [--flood-id <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;
+}

+ 30 - 0
flooder/flooder.h

@@ -0,0 +1,30 @@
+/**
+ * @file flooder.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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

+ 214 - 0
flow/BestEffortPacketWriteInterface.h

@@ -0,0 +1,214 @@
+/**
+ * @file BestEffortPacketWriteInterface.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+#include <stdlib.h>
+
+#include <misc/debug.h>
+#include <misc/dead.h>
+#include <system/DebugObject.h>
+
+/**
+ * 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

+ 30 - 0
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)

+ 60 - 0
flow/DataProtoKeepaliveSource.c

@@ -0,0 +1,60 @@
+/**
+ * @file DataProtoKeepaliveSource.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <protocol/dataproto.h>
+#include <misc/byteorder.h>
+
+#include <flow/DataProtoKeepaliveSource.h>
+
+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;
+}

+ 65 - 0
flow/DataProtoKeepaliveSource.h

@@ -0,0 +1,65 @@
+/**
+ * @file DataProtoKeepaliveSource.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <system/DebugObject.h>
+#include <flow/PacketRecvInterface.h>
+
+/**
+ * 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

+ 165 - 0
flow/DatagramSocketSink.c

@@ -0,0 +1,165 @@
+/**
+ * @file DatagramSocketSink.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <misc/debug.h>
+
+#include <flow/DatagramSocketSink.h>
+
+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;
+}

+ 104 - 0
flow/DatagramSocketSink.h

@@ -0,0 +1,104 @@
+/**
+ * @file DatagramSocketSink.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+
+#include <misc/dead.h>
+#include <system/DebugObject.h>
+#include <system/BSocket.h>
+#include <flow/PacketPassInterface.h>
+#include <flow/error.h>
+
+#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

+ 178 - 0
flow/DatagramSocketSource.c

@@ -0,0 +1,178 @@
+/**
+ * @file DatagramSocketSource.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdlib.h>
+
+#include <misc/debug.h>
+
+#include <flow/DatagramSocketSource.h>
+
+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;
+    }
+}

+ 100 - 0
flow/DatagramSocketSource.h

@@ -0,0 +1,100 @@
+/**
+ * @file DatagramSocketSource.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <misc/dead.h>
+#include <system/DebugObject.h>
+#include <system/BSocket.h>
+#include <flow/error.h>
+#include <flow/PacketRecvInterface.h>
+
+#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

+ 522 - 0
flow/FragmentProtoAssembler.c

@@ -0,0 +1,522 @@
+/**
+ * @file FragmentProtoAssembler.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdlib.h>
+#include <string.h>
+
+#include <misc/offset.h>
+#include <misc/debug.h>
+#include <misc/byteorder.h>
+#include <system/BLog.h>
+#include <flow/FragmentProtoAssembler.h>
+
+#include <generated/blog_channel_FragmentProtoAssembler.h>
+
+#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;
+}

+ 116 - 0
flow/FragmentProtoAssembler.h

@@ -0,0 +1,116 @@
+/**
+ * @file FragmentProtoAssembler.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+
+#include <protocol/fragmentproto.h>
+#include <misc/dead.h>
+#include <misc/debug.h>
+#include <system/DebugObject.h>
+#include <structure/LinkedList2.h>
+#include <structure/BAVL.h>
+#include <flow/PacketPassInterface.h>
+
+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

+ 290 - 0
flow/FragmentProtoDisassembler.c

@@ -0,0 +1,290 @@
+/**
+ * @file FragmentProtoDisassembler.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/debug.h>
+#include <misc/byteorder.h>
+
+#include <flow/FragmentProtoDisassembler.h>
+
+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;
+}

+ 107 - 0
flow/FragmentProtoDisassembler.h

@@ -0,0 +1,107 @@
+/**
+ * @file FragmentProtoDisassembler.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+
+#include <protocol/fragmentproto.h>
+#include <misc/dead.h>
+#include <system/DebugObject.h>
+#include <system/BReactor.h>
+#include <system/BTime.h>
+#include <flow/PacketPassInterface.h>
+#include <flow/PacketRecvInterface.h>
+
+/**
+ * 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

+ 110 - 0
flow/KeepaliveIO.c

@@ -0,0 +1,110 @@
+/**
+ * @file KeepaliveIO.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <misc/debug.h>
+
+#include <flow/KeepaliveIO.h>
+
+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);
+}

+ 83 - 0
flow/KeepaliveIO.h

@@ -0,0 +1,83 @@
+/**
+ * @file KeepaliveIO.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <misc/dead.h>
+#include <misc/debug.h>
+#include <system/DebugObject.h>
+#include <system/BReactor.h>
+#include <flow/PacketPassInterface.h>
+#include <flow/PacketRecvInterface.h>
+#include <flow/PacketPassPriorityQueue.h>
+#include <flow/SinglePacketBuffer.h>
+#include <flow/PacketRecvBlocker.h>
+#include <flow/PacketPassInactivityMonitor.h>
+
+/**
+ * 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

+ 248 - 0
flow/PacketBuffer.c

@@ -0,0 +1,248 @@
+/**
+ * @file PacketBuffer.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdlib.h>
+
+#include <misc/debug.h>
+
+#include <flow/PacketBuffer.h>
+
+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);
+}

+ 74 - 0
flow/PacketBuffer.h

@@ -0,0 +1,74 @@
+/**
+ * @file PacketBuffer.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+
+#include <misc/dead.h>
+#include <misc/debug.h>
+#include <system/DebugObject.h>
+#include <system/BPending.h>
+#include <structure/ChunkBuffer2.h>
+#include <flow/PacketRecvInterface.h>
+#include <flow/PacketPassInterface.h>
+
+/**
+ * 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

+ 160 - 0
flow/PacketBufferAsyncInput.h

@@ -0,0 +1,160 @@
+/**
+ * @file PacketBufferAsyncInput.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+
+#include <misc/debug.h>
+#include <system/DebugObject.h>
+#include <flow/PacketRecvInterface.h>
+#include <flow/BestEffortPacketWriteInterface.h>
+
+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

+ 134 - 0
flow/PacketCopier.c

@@ -0,0 +1,134 @@
+/**
+ * @file PacketCopier.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <string.h>
+
+#include <misc/debug.h>
+
+#include <flow/PacketCopier.h>
+
+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;
+}

+ 84 - 0
flow/PacketCopier.h

@@ -0,0 +1,84 @@
+/**
+ * @file PacketCopier.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+
+#include <misc/dead.h>
+#include <flow/PacketPassInterface.h>
+#include <flow/PacketRecvInterface.h>
+
+/**
+ * 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

+ 220 - 0
flow/PacketPassConnector.c

@@ -0,0 +1,220 @@
+/**
+ * @file PacketPassConnector.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stddef.h>
+
+#include <misc/debug.h>
+
+#include <flow/PacketPassConnector.h>
+
+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;
+}

+ 101 - 0
flow/PacketPassConnector.h

@@ -0,0 +1,101 @@
+/**
+ * @file PacketPassConnector.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+
+#include <misc/dead.h>
+#include <system/DebugObject.h>
+#include <system/BPending.h>
+#include <flow/PacketPassInterface.h>
+
+/**
+ * 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

+ 459 - 0
flow/PacketPassFairQueue.c

@@ -0,0 +1,459 @@
+/**
+ * @file PacketPassFairQueue.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdlib.h>
+
+#include <misc/debug.h>
+#include <misc/offset.h>
+
+#include <flow/PacketPassFairQueue.h>
+
+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;
+}

+ 184 - 0
flow/PacketPassFairQueue.h

@@ -0,0 +1,184 @@
+/**
+ * @file PacketPassFairQueue.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+
+#include <misc/dead.h>
+#include <misc/debugin.h>
+#include <system/DebugObject.h>
+#include <misc/debugcounter.h>
+#include <structure/BHeap.h>
+#include <structure/LinkedList2.h>
+#include <system/BPending.h>
+#include <flow/PacketPassInterface.h>
+
+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

+ 131 - 0
flow/PacketPassInactivityMonitor.c

@@ -0,0 +1,131 @@
+/**
+ * @file PacketPassInactivityMonitor.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <flow/PacketPassInactivityMonitor.h>
+
+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;
+}

+ 104 - 0
flow/PacketPassInactivityMonitor.h

@@ -0,0 +1,104 @@
+/**
+ * @file PacketPassInactivityMonitor.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <misc/dead.h>
+#include <system/DebugObject.h>
+#include <system/BReactor.h>
+#include <flow/PacketPassInterface.h>
+
+/**
+ * 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

+ 373 - 0
flow/PacketPassInterface.h

@@ -0,0 +1,373 @@
+/**
+ * @file PacketPassInterface.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+#include <stddef.h>
+
+#include <misc/dead.h>
+#include <misc/debug.h>
+#include <system/DebugObject.h>
+
+/**
+ * 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

+ 163 - 0
flow/PacketPassNotifier.c

@@ -0,0 +1,163 @@
+/**
+ * @file PacketPassNotifier.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stddef.h>
+
+#include <flow/PacketPassNotifier.h>
+
+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;
+}

+ 97 - 0
flow/PacketPassNotifier.h

@@ -0,0 +1,97 @@
+/**
+ * @file PacketPassNotifier.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+
+#include <misc/dead.h>
+#include <system/DebugObject.h>
+#include <flow/PacketPassInterface.h>
+
+/**
+ * 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

+ 378 - 0
flow/PacketPassPriorityQueue.c

@@ -0,0 +1,378 @@
+/**
+ * @file PacketPassPriorityQueue.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdlib.h>
+
+#include <misc/debug.h>
+#include <misc/offset.h>
+
+#include <flow/PacketPassPriorityQueue.h>
+
+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;
+}

+ 181 - 0
flow/PacketPassPriorityQueue.h

@@ -0,0 +1,181 @@
+/**
+ * @file PacketPassPriorityQueue.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+
+#include <misc/dead.h>
+#include <misc/debugin.h>
+#include <system/DebugObject.h>
+#include <misc/debugcounter.h>
+#include <structure/BHeap.h>
+#include <system/BPending.h>
+#include <flow/PacketPassInterface.h>
+
+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

+ 306 - 0
flow/PacketProtoDecoder.c

@@ -0,0 +1,306 @@
+/**
+ * @file PacketProtoDecoder.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdlib.h>
+#include <string.h>
+
+#include <misc/debug.h>
+#include <misc/byteorder.h>
+#include <misc/minmax.h>
+
+#include <flow/PacketProtoDecoder.h>
+
+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;
+}

+ 104 - 0
flow/PacketProtoDecoder.h

@@ -0,0 +1,104 @@
+/**
+ * @file PacketProtoDecoder.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+
+#include <misc/debug.h>
+#include <misc/dead.h>
+#include <misc/balign.h>
+#include <flow/StreamRecvInterface.h>
+#include <flow/PacketPassInterface.h>
+#include <flow/error.h>
+#include <protocol/packetproto.h>
+#include <system/DebugObject.h>
+#include <system/BPending.h>
+
+#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

+ 130 - 0
flow/PacketProtoEncoder.c

@@ -0,0 +1,130 @@
+/**
+ * @file PacketProtoEncoder.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stddef.h>
+
+#include <protocol/packetproto.h>
+#include <misc/balign.h>
+#include <misc/debug.h>
+#include <misc/byteorder.h>
+
+#include <flow/PacketProtoEncoder.h>
+
+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;
+}

+ 74 - 0
flow/PacketProtoEncoder.h

@@ -0,0 +1,74 @@
+/**
+ * @file PacketProtoEncoder.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+
+#include <misc/dead.h>
+#include <system/DebugObject.h>
+#include <flow/PacketRecvInterface.h>
+
+/**
+ * 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

+ 76 - 0
flow/PacketProtoFlow.c

@@ -0,0 +1,76 @@
+/**
+ * @file PacketProtoFlow.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <protocol/packetproto.h>
+#include <misc/debug.h>
+
+#include <flow/PacketProtoFlow.h>
+
+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);
+}

+ 78 - 0
flow/PacketProtoFlow.h

@@ -0,0 +1,78 @@
+/**
+ * @file PacketProtoFlow.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <system/DebugObject.h>
+#include <system/BPending.h>
+#include <flow/PacketBufferAsyncInput.h>
+#include <flow/PacketProtoEncoder.h>
+#include <flow/PacketBuffer.h>
+
+/**
+ * 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

+ 120 - 0
flow/PacketRecvBlocker.c

@@ -0,0 +1,120 @@
+/**
+ * @file PacketRecvBlocker.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <misc/debug.h>
+
+#include <flow/PacketRecvBlocker.h>
+
+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;
+}

+ 85 - 0
flow/PacketRecvBlocker.h

@@ -0,0 +1,85 @@
+/**
+ * @file PacketRecvBlocker.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+
+#include <misc/dead.h>
+#include <system/DebugObject.h>
+#include <flow/PacketRecvInterface.h>
+
+/**
+ * {@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

+ 227 - 0
flow/PacketRecvConnector.c

@@ -0,0 +1,227 @@
+/**
+ * @file PacketRecvConnector.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stddef.h>
+
+#include <misc/debug.h>
+
+#include <flow/PacketRecvConnector.h>
+
+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;
+}

+ 101 - 0
flow/PacketRecvConnector.h

@@ -0,0 +1,101 @@
+/**
+ * @file PacketRecvConnector.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+
+#include <misc/dead.h>
+#include <system/DebugObject.h>
+#include <system/BPending.h>
+#include <flow/PacketRecvInterface.h>
+
+/**
+ * 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

+ 296 - 0
flow/PacketRecvInterface.h

@@ -0,0 +1,296 @@
+/**
+ * @file PacketRecvInterface.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+#include <stddef.h>
+
+#include <misc/dead.h>
+#include <misc/debug.h>
+#include <system/DebugObject.h>
+
+/**
+ * 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

+ 156 - 0
flow/PacketRecvNotifier.c

@@ -0,0 +1,156 @@
+/**
+ * @file PacketRecvNotifier.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stddef.h>
+
+#include <flow/PacketRecvNotifier.h>
+
+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;
+}

+ 98 - 0
flow/PacketRecvNotifier.h

@@ -0,0 +1,98 @@
+/**
+ * @file PacketRecvNotifier.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+
+#include <misc/dead.h>
+#include <system/DebugObject.h>
+#include <flow/PacketRecvInterface.h>
+
+/**
+ * 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

+ 144 - 0
flow/PacketStreamSender.c

@@ -0,0 +1,144 @@
+/**
+ * @file PacketStreamSender.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdlib.h>
+
+#include <misc/debug.h>
+
+#include <flow/PacketStreamSender.h>
+
+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;
+}

+ 77 - 0
flow/PacketStreamSender.h

@@ -0,0 +1,77 @@
+/**
+ * @file PacketStreamSender.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <stdint.h>
+
+#include <misc/dead.h>
+#include <system/DebugObject.h>
+#include <flow/PacketPassInterface.h>
+#include <flow/StreamPassInterface.h>
+
+/**
+ * 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

+ 58 - 0
flow/SCKeepaliveSource.c

@@ -0,0 +1,58 @@
+/**
+ * @file SCKeepaliveSource.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <protocol/scproto.h>
+#include <misc/byteorder.h>
+
+#include <flow/SCKeepaliveSource.h>
+
+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;
+}

+ 64 - 0
flow/SCKeepaliveSource.h

@@ -0,0 +1,64 @@
+/**
+ * @file SCKeepaliveSource.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <system/DebugObject.h>
+#include <flow/PacketRecvInterface.h>
+
+/**
+ * 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

+ 309 - 0
flow/SPProtoDecoder.c

@@ -0,0 +1,309 @@
+/**
+ * @file SPProtoDecoder.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @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 <string.h>
+
+#include <misc/debug.h>
+#include <misc/balign.h>
+#include <misc/byteorder.h>
+#include <security/bhash.h>
+
+#include <flow/SPProtoDecoder.h>
+
+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);
+}

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio