Jelajahi Sumber

Merge master into multipath

Joseph Henry 6 tahun lalu
induk
melakukan
77ae929eb3
100 mengubah file dengan 10781 tambahan dan 9124 penghapusan
  1. 46 0
      .github/ISSUE_TEMPLATE/bug_report.md
  2. 17 0
      .github/ISSUE_TEMPLATE/feature_request.md
  3. 3 0
      .gitignore
  4. 7 7
      AUTHORS.md
  5. 1 1
      COPYING
  6. 48 48
      Jenkinsfile
  7. 1 1
      LICENSE.txt
  8. 0 16
      OFFICIAL-RELEASE-STEPS.md
  9. 14 19
      README.md
  10. 18 0
      RELEASE-NOTES.md
  11. TEMPAT SAMPAH
      artwork/AppIcon_90x90.png
  12. 0 1042
      attic/Cluster.cpp
  13. 0 463
      attic/Cluster.hpp
  14. 0 168
      attic/ClusterDefinition.hpp
  15. 0 243
      attic/ClusterGeoIpService.cpp
  16. 0 151
      attic/ClusterGeoIpService.hpp
  17. 0 101
      attic/FCV.hpp
  18. 0 650
      attic/OSXEthernetTap.cpp.pcap-with-bridge-test
  19. 0 831
      attic/OSXEthernetTap.cpp.utun-work-in-progress
  20. 0 96
      attic/OSXEthernetTap.hpp.pcap-with-bridge-test
  21. 0 101
      attic/OSXEthernetTap.hpp.utun-work-in-progress
  22. 0 4
      attic/README.md
  23. 0 18
      attic/kubernetes_docs/.zerotierCliSettings
  24. 0 19
      attic/kubernetes_docs/Dockerfile
  25. 0 150
      attic/kubernetes_docs/README.md
  26. 0 23
      attic/kubernetes_docs/entrypoint.sh
  27. 0 8
      attic/kubernetes_docs/server.js
  28. 0 25
      attic/lat_lon_to_xyz.js
  29. 3 1
      attic/world/build.sh
  30. 13 8
      attic/world/mkworld.cpp
  31. TEMPAT SAMPAH
      attic/world/old/earth-2015-11-16.bin
  32. TEMPAT SAMPAH
      attic/world/old/earth-2015-11-20.bin
  33. TEMPAT SAMPAH
      attic/world/old/earth-2015-12-17.bin
  34. TEMPAT SAMPAH
      attic/world/old/earth-2016-01-13.bin
  35. TEMPAT SAMPAH
      attic/world/world.bin
  36. 0 1
      attic/world/world.c
  37. 38 21
      controller/DB.cpp
  38. 32 30
      controller/DB.hpp
  39. 79 22
      controller/EmbeddedNetworkController.cpp
  40. 23 12
      controller/EmbeddedNetworkController.hpp
  41. 97 17
      controller/FileDB.cpp
  42. 16 3
      controller/FileDB.hpp
  43. 400 0
      controller/LFDB.cpp
  44. 102 0
      controller/LFDB.hpp
  45. 1571 0
      controller/PostgreSQL.cpp
  46. 117 0
      controller/PostgreSQL.hpp
  47. 2 10
      controller/README.md
  48. 107 0
      controller/RabbitMQ.cpp
  49. 79 0
      controller/RabbitMQ.hpp
  50. 0 497
      controller/RethinkDB.cpp
  51. 0 84
      controller/RethinkDB.hpp
  52. 9 0
      cycle_controllers.sh
  53. 6 0
      debian/changelog
  54. 19 0
      docker/Dockerfile
  55. 80 0
      docker/main.sh
  56. 3 1
      ext/arm32-neon-salsa2012-asm/salsa2012.h
  57. 0 36
      ext/bin/tap-mac/tap.kext/Contents/Info.plist
  58. TEMPAT SAMPAH
      ext/bin/tap-mac/tap.kext/Contents/MacOS/tap
  59. 0 105
      ext/bin/tap-mac/tap.kext/Contents/_CodeSignature/CodeResources
  60. 22 0
      ext/cpp-httplib/LICENSE
  61. 259 0
      ext/cpp-httplib/README.md
  62. 2779 0
      ext/cpp-httplib/httplib.h
  63. 1 4
      ext/ed25519-amd64-asm/sign.c
  64. 23 11
      ext/installfiles/linux/zerotier-containerized/Dockerfile
  65. 1 1
      ext/installfiles/linux/zerotier-containerized/main.sh
  66. 3 3
      ext/installfiles/mac/ZeroTier One.pkgproj
  67. 1 1
      ext/installfiles/mac/uninstall.sh
  68. 5 5
      ext/installfiles/windows/ZeroTier One.aip
  69. 0 11
      ext/installfiles/windows/chocolatey/zerotier-one/tools/LICENSE.txt
  70. 0 5
      ext/installfiles/windows/chocolatey/zerotier-one/tools/VERIFICATION.txt
  71. 0 8
      ext/installfiles/windows/chocolatey/zerotier-one/tools/chocolateyinstall.ps1
  72. 0 30
      ext/installfiles/windows/chocolatey/zerotier-one/tools/chocolateyuninstall.ps1
  73. 0 76
      ext/installfiles/windows/chocolatey/zerotier-one/zerotier-one.nuspec
  74. 220 81
      ext/json/README.md
  75. 766 823
      ext/json/json.hpp
  76. 2538 0
      ext/librabbitmq/centos_x64/include/amqp.h
  77. 1144 0
      ext/librabbitmq/centos_x64/include/amqp_framing.h
  78. 68 0
      ext/librabbitmq/centos_x64/include/amqp_tcp_socket.h
  79. TEMPAT SAMPAH
      ext/librabbitmq/centos_x64/lib/librabbitmq.a
  80. 0 11
      ext/librethinkdbxx/.travis.yml
  81. 0 16
      ext/librethinkdbxx/COPYRIGHT
  82. 0 126
      ext/librethinkdbxx/Makefile
  83. 0 72
      ext/librethinkdbxx/README.md
  84. 0 80
      ext/librethinkdbxx/reql/add_docs.py
  85. 0 33
      ext/librethinkdbxx/reql/gen.py
  86. 0 13
      ext/librethinkdbxx/reql/python_docs.txt
  87. 0 843
      ext/librethinkdbxx/reql/ql2.proto
  88. 0 434
      ext/librethinkdbxx/src/connection.cc
  89. 0 59
      ext/librethinkdbxx/src/connection.h
  90. 0 133
      ext/librethinkdbxx/src/connection_p.h
  91. 0 223
      ext/librethinkdbxx/src/cursor.cc
  92. 0 76
      ext/librethinkdbxx/src/cursor.h
  93. 0 29
      ext/librethinkdbxx/src/cursor_p.h
  94. 0 449
      ext/librethinkdbxx/src/datum.cc
  95. 0 287
      ext/librethinkdbxx/src/datum.h
  96. 0 46
      ext/librethinkdbxx/src/error.h
  97. 0 13
      ext/librethinkdbxx/src/exceptions.h
  98. 0 62
      ext/librethinkdbxx/src/json.cc
  99. 0 19
      ext/librethinkdbxx/src/json_p.h
  100. 0 8
      ext/librethinkdbxx/src/rapidjson-config.h

+ 46 - 0
.github/ISSUE_TEMPLATE/bug_report.md

@@ -0,0 +1,46 @@
+---
+name: Bug report
+about: Create a report to help us improve
+
+---
+**Alternative, faster ways to get help**
+If you have just started using ZeroTier, here are some places to get help:
+- my.zerotier.com has a _Community_ tab. It's a live chat with other users and the developers. 
+- [ZeroTier Knowledge Base](https://zerotier.atlassian.net/wiki/spaces/SD/overview)
+- www.zerotier.com has a Contact Us button
+- email [email protected]
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Create a Network '...'
+2. Install zerotier-one '....'
+3. '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots or console output to help explain your problem.
+
+**Desktop (please complete the following information):**
+ - OS: [e.g. Mac, Linux, Windows, BSD]
+ - OS/Distribution Version
+ - ZeroTier Version [e.g. 1.2.4]
+ - Hardware [e.g. raspberry pi 3]
+ 
+**Smartphone (please complete the following information):**
+ - Device: [e.g. iPhone6]
+ - OS: [e.g. iOS8.1]
+ - Version [e.g. 1.2.4]
+
+**Additional context**
+Add any other context about the problem here.
+- ZeroTier Network Configuration
+- Router Config
+- Firewall Config (try turning the firewall off)
+- General Network Environment: [ e.g Home, University Campus, Corporate LAN ]
+

+ 17 - 0
.github/ISSUE_TEMPLATE/feature_request.md

@@ -0,0 +1,17 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.

+ 3 - 0
.gitignore

@@ -51,6 +51,7 @@ enc_temp_folder
 /world/mkworld
 /world/*.c25519
 zt1-src.tar.gz
+/MacEthernetTapAgent
 
 # Miscellaneous temporaries, build files, etc.
 *.log
@@ -117,3 +118,5 @@ ext/librethinkdbxx/build
 .vscode
 __pycache__
 *~
+attic/world/*.c25519
+attic/world/mkworld

+ 7 - 7
AUTHORS.md

@@ -2,6 +2,7 @@
 
  * ZeroTier Core and ZeroTier One virtual networking service<br>
    Adam Ierymenko / [email protected]
+   Joseph Henry / [email protected] (QoS and multipath)
 
  * Java JNI Interface to enable Android application development, and Android app itself (code for that is elsewhere)<br>
    Grant Limberg / [email protected]
@@ -45,13 +46,6 @@ ZeroTier includes the following third party code, either in ext/ or incorporated
    * Home page: https://github.com/nlohmann/json
    * License grant: MIT
 
- * TunTapOSX by Mattias Nissler
-
-   * Files: ext/tap-mac/tuntap/*
-   * Home page: http://tuntaposx.sourceforge.net/
-   * License grant: BSD attribution no-endorsement
-   * ZeroTier Modifications: change interface name to zt#, increase max MTU, increase max devices
-
  * tap-windows6 by the OpenVPN project
 
    * Files: windows/TapDriver6/*
@@ -71,3 +65,9 @@ ZeroTier includes the following third party code, either in ext/ or incorporated
    * Files: ext/libnatpmp/* ext/miniupnpc/*
    * Home page: http://miniupnp.free.fr/
    * License grant: BSD attribution no-endorsement
+
+ * cpp-httplib by yhirose
+
+   * Files: ext/cpp-httplib/*
+   * Home page: https://github.com/yhirose/cpp-httplib
+   * License grant: MIT

+ 1 - 1
COPYING

@@ -1,5 +1,5 @@
 ZeroTier One, an endpoint server for the ZeroTier virtual network layer.
-Copyright © 2011–2018 ZeroTier, Inc.
+Copyright © 2011–2019 ZeroTier, Inc.
 
 ZeroTier One is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by

+ 48 - 48
Jenkinsfile

@@ -24,61 +24,61 @@ parallel 'centos7': {
             throw err
         }
     }
-}, 'android-ndk': {
-    node('android-ndk') {
-        try {
-            checkout scm
+// }, 'android-ndk': {
+//     node('android-ndk') {
+//         try {
+//             checkout scm
 	
-            stage('Build Android NDK') { 
-                sh "/android/android-ndk-r15b/ndk-build -C $WORKSPACE/java ZT1=${WORKSPACE}"
-            }
-        }
-        catch (err) {
-            currentBuild.result = "FAILURE"
-            mattermostSend color: '#ff0000', message: "${env.JOB_NAME} broken on Android NDK (<${env.BUILD_URL}|Open>)"
+//             stage('Build Android NDK') { 
+//                 sh "/android/android-ndk-r15b/ndk-build -C $WORKSPACE/java ZT1=${WORKSPACE}"
+//             }
+//         }
+//         catch (err) {
+//             currentBuild.result = "FAILURE"
+//             mattermostSend color: '#ff0000', message: "${env.JOB_NAME} broken on Android NDK (<${env.BUILD_URL}|Open>)"
 
-            throw err
-        }
-    }
-}, 'macOS': {
-    node('macOS') {
-        try {
-            checkout scm
+//             throw err
+//         }
+//     }
+// }, 'macOS': {
+//     node('macOS') {
+//         try {
+//             checkout scm
 
-            stage('Build macOS') {
-                sh 'make -f make-mac.mk'
-            }
+//             stage('Build macOS') {
+//                 sh 'make -f make-mac.mk'
+//             }
 
-            stage('Build macOS UI') {
-                sh 'cd macui && xcodebuild -target "ZeroTier One" -configuration Debug'
-            }
-        }
-        catch (err) {
-            currentBuild.result = "FAILURE"
-            mattermostSend color: '#ff0000', message: "${env.JOB_NAME} broken on macOS (<${env.BUILD_URL}|Open>)"
+//             stage('Build macOS UI') {
+//                 sh 'cd macui && xcodebuild -target "ZeroTier One" -configuration Debug'
+//             }
+//         }
+//         catch (err) {
+//             currentBuild.result = "FAILURE"
+//             mattermostSend color: '#ff0000', message: "${env.JOB_NAME} broken on macOS (<${env.BUILD_URL}|Open>)"
 
-            throw err
-        }
-    }
-}, 'windows': {
-    node('windows') {
-        try {
-            checkout scm
+//             throw err
+//         }
+//     }
+// }, 'windows': {
+//     node('windows') {
+//         try {
+//             checkout scm
             
-            stage('Build Windows') {
-                bat '''CALL "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat" amd64
-git clean -dfx
-msbuild windows\\ZeroTierOne.sln
-'''
-            }
-        }
-        catch (err) {
-            currentBuild.result = "FAILURE"
-            mattermostSend color: '#ff0000', message: "${env.JOB_NAME} broken on Windows (<${env.BUILD_URL}|Open>)"
+//             stage('Build Windows') {
+//                 bat '''CALL "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat" amd64
+// git clean -dfx
+// msbuild windows\\ZeroTierOne.sln
+// '''
+//             }
+//         }
+//         catch (err) {
+//             currentBuild.result = "FAILURE"
+//             mattermostSend color: '#ff0000', message: "${env.JOB_NAME} broken on Windows (<${env.BUILD_URL}|Open>)"
 
-            throw err
-        }
-    }
+//             throw err
+//         }
+//     }
 }
 
 mattermostSend color: "#00ff00", message: "${env.JOB_NAME} #${env.BUILD_NUMBER} Complete (<${env.BUILD_URL}|Show More...>)"

+ 1 - 1
LICENSE.txt

@@ -1,5 +1,5 @@
 ZeroTier One - Network Virtualization Everywhere
-Copyright (C) 2011-2017  ZeroTier, Inc.  https://www.zerotier.com/
+Copyright (C) 2011-2019  ZeroTier, Inc.  https://www.zerotier.com/
 
 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

+ 0 - 16
OFFICIAL-RELEASE-STEPS.md

@@ -13,7 +13,6 @@ The version must be incremented in all of the following files:
     /zerotier-one.spec
     /debian/changelog
     /ext/installfiles/mac/ZeroTier One.pkgproj
-    /ext/installfiles/windows/chocolatey/zerotier-one.nuspec
     /ext/installfiles/windows/ZeroTier One.aip
     /windows/WinUI/AboutView.xaml
 
@@ -29,21 +28,6 @@ Mac's easy. Just type:
 
 You will need [Packages](http://s.sudre.free.fr/Software/Packages/about.html) and our release signing key in the keychain.
 
-## Linux
-
-See `LinuxBuild` environment on `linux-build` VM and use: `chroots/mount-build.sh`, `chroots/build.sh`, and the scripts in `build/` to make APT and RPM repositories.
-
 ## Windows
 
 First load the Visual Studio solution and rebuild the UI and ZeroTier One in both x64 and i386 `Release` mode. Then load [Advanced Installer Enterprise](http://www.advancedinstaller.com/), check that the version is correct, and build. The build will fail if any build artifacts are missing, and Windows must have our product singing key (from DigiCert) available to sign the resulting MSI file. The MSI must then be tested on at least a few different CLEAN Windows VMs to ensure that the installer is valid and properly signed.
-
-*After the MSI is published to download.zerotier.com in the proper RELEASE/#.#.#/dist subfolder for its version* the Chocolatey package must be rebuilt and published. Open a command prompt, change to `ext/installfiles/windows/chocolatey`, and type `choco pack`. Then use `choco push` to push it to Chocolatey (API key required).
-
-    choco pack
-    choco push zerotier-one.#.#.#.nupkg -s https://chocolatey.org/
-
-Note that this does not cover rebuilding the drivers or their containing MSI projects, as this is typically not necessary and they are shipped in binary form in the repository for convenience.
-
-## iOS, Android
-
-... no docs here yet since this is done entirely out of band with regular installs.

+ 14 - 19
README.md

@@ -1,62 +1,57 @@
-ZeroTier - A Planetary Ethernet Switch
+ZeroTier - Global Area Networking
 ======
 
-ZeroTier is a smart programmable Ethernet switch for planet Earth.
+ZeroTier is a smart programmable Ethernet switch for planet Earth. It allows networked devices and applications to be managed as if the entire world is one data center or cloud region.
 
 It replaces the physical LAN/WAN boundary with a virtual one, allowing devices of any type at any location to be managed as if they all reside in the same cloud region or data center. All traffic is encrypted end-to-end and takes the most direct path available for minimum latency and maximum performance. The goals and design of ZeroTier are inspired by among other things the original [Google BeyondCorp](https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/43231.pdf) paper and the [Jericho Forum](https://en.wikipedia.org/wiki/Jericho_Forum).
 
-Visit [ZeroTier's site](https://www.zerotier.com/?pk_campaign=github_ZeroTierOne) for more information and [pre-built binary packages](https://www.zerotier.com/download.shtml?pk_campaign=github_ZeroTierOne). Apps for Android and iOS are available for free in the Google Play and Apple app stores.
+Visit [ZeroTier's site](https://www.zerotier.com/) for more information and [pre-built binary packages](https://www.zerotier.com/download/). Apps for Android and iOS are available for free in the Google Play and Apple app stores.
 
 ### Getting Started
 
 Everything in the ZeroTier world is controlled by two types of identifier: 40-bit/10-digit *ZeroTier addresses* and 64-bit/16-digit *network IDs*. A ZeroTier address identifies a node or "device" (laptop, phone, server, VM, app, etc.) while a network ID identifies a virtual Ethernet network that can be joined by devices.
 
-Another way of thinking about it is that ZeroTier addresses are port numbers on a giant planetary-sized smart switch while network IDs are VLANs to which these ports can be assigned. For more details read about VL1 and VL2 in [the ZeroTier manual](https://www.zerotier.com/manual.shtml).
+Another way of thinking about it is that ZeroTier addresses are port numbers on a giant planetary-sized smart switch while network IDs are VLANs to which these ports can be assigned. For more details read about VL1 and VL2 in [the ZeroTier manual](https://www.zerotier.com/manual/).
 
 *Network controllers* are ZeroTier nodes that act as access control certificate authorities and configuration managers for virtual networks. The first 40 bits (or 10 digits) of a network ID is the ZeroTier address of its controller. You can create networks with our [hosted controllers](https://my.zerotier.com/) and web UI/API or [host your own](controller/) if you don't mind posting some JSON configuration info or writing a script to do so.
 
 ### Project Layout
 
+The base path contains the ZeroTier One service main entry point (`one.cpp`), self test code, makefiles, etc.
+
  - `artwork/`: icons, logos, etc.
  - `attic/`: old stuff and experimental code that we want to keep around for reference.
  - `controller/`: the reference network controller implementation, which is built and included by default on desktop and server build targets.
  - `debian/`: files for building Debian packages on Linux.
  - `doc/`: manual pages and other documentation.
+ - `docker/`: Dockerfile to build as a container for containerized Linux systems and Kubernetes clusters.
  - `ext/`: third party libraries, binaries that we ship for convenience on some platforms (Mac and Windows), and installation support files.
  - `include/`: include files for the ZeroTier core.
  - `java/`: a JNI wrapper used with our Android mobile app. (The whole Android app is not open source but may be made so in the future.)
  - `macui/`: a Macintosh menu-bar app for controlling ZeroTier One, written in Objective C.
  - `node/`: the ZeroTier virtual Ethernet switch core, which is designed to be entirely separate from the rest of the code and able to be built as a stand-alone OS-independent library. Note to developers: do not use C++11 features in here, since we want this to build on old embedded platforms that lack C++11 support. C++11 can be used elsewhere.
  - `osdep/`: code to support and integrate with OSes, including platform-specific stuff only built for certain targets.
+ - `rule-compiler/`: JavaScript rules language compiler for defining network-level rules.
  - `service/`: the ZeroTier One service, which wraps the ZeroTier core and provides VPN-like connectivity to virtual networks for desktops, laptops, servers, VMs, and containers.
- - `tcp-proxy/`: TCP proxy code run by ZeroTier, Inc. to provide TCP fallback (this will die soon!).
  - `windows/`: Visual Studio solution files, Windows service code for ZeroTier One, and the Windows task bar app UI.
 
-The base path contains the ZeroTier One service main entry point (`one.cpp`), self test code, makefiles, etc.
-
 ### Build and Platform Notes
 
 To build on Mac and Linux just type `make`. On FreeBSD and OpenBSD `gmake` (GNU make) is required and can be installed from packages or ports. For Windows there is a Visual Studio solution in `windows/'.
 
  - **Mac**
-   - Xcode command line tools for OSX 10.7 or newer are required.
-   - Tap device driver kext source is in `ext/tap-mac` and a signed pre-built binary can be found in `ext/bin/tap-mac`. You should not need to build it yourself. It's a fork of [tuntaposx](http://tuntaposx.sourceforge.net) with device names changed to `zt#`, support for a larger MTU, and tun functionality removed.
+   - Xcode command line tools for OSX 10.8 or newer are required.
  - **Linux**
-   - The minimum compiler versions required are GCC/G++ 4.9.3 or CLANG/CLANG++ 3.4.2.
+   - The minimum compiler versions required are GCC/G++ 4.9.3 or CLANG/CLANG++ 3.4.2. (Install `clang` on CentOS 7 as G++ is too old.)
    - Linux makefiles automatically detect and prefer clang/clang++ if present as it produces smaller and slightly faster binaries in most cases. You can override by supplying CC and CXX variables on the make command line.
-   - CentOS 7 ships with a version of GCC/G++ that is too old, but a new enough version of CLANG can be found in the *epel* repositories. Type `yum install epel-release` and then `yum install clang` to build there.
  - **Windows**
    - Windows 7 or newer is supported. This *may* work on Vista but isn't officially supported there. It will not work on Windows XP.
-   - We build with Visual Studio 2015. Older versions may not work. Clang or MinGW will also probably work but may require some makefile hacking.
-   - Pre-built signed Windows drivers are included in `ext/bin/tap-windows-ndis6`. The MSI files found there will install them on 32-bit and 64-bit systems. We don't recommend trying to build Windows drivers from scratch unless you know what you're doing. One does not simply "build" a Windows driver.
+   - We build with Visual Studio 2017. Older versions may not work. Clang or MinGW will also probably work but may require some makefile hacking.
  - **FreeBSD**
-   - Tested most recently on FreeBSD-11. Older versions may work but we're not sure.
-   - GCC/G++ 4.9 and gmake are required. These can be installed from packages or ports. Type `gmake` to build.
+   - GNU make is required. Type `gmake` to build.
  - **OpenBSD**
-   - There is a limit of four network memberships on OpenBSD as there are only four tap devices (`/dev/tap0` through `/dev/tap3`). We're not sure if this can be increased.
-   - OpenBSD lacks `getifmaddrs` (or any equivalent method) to get interface multicast memberships. As a result multicast will only work on OpenBSD for ARP and NDP (IP/MAC lookup) and not for other purposes.
-   - Only tested on OpenBSD 6.0. Older versions may not work.
-   - GCC/G++ 4.9 and gmake are required and can be installed using `pkg_add` or from ports. They get installed in `/usr/local/bin` as `egcc` and `eg++` and our makefile is pre-configured to use them on OpenBSD.
+   - There is a limit of four network memberships on OpenBSD as there are only four tap devices (`/dev/tap0` through `/dev/tap3`).
+   - GNU make is required. Type `gmake` to build.
 
 Typing `make selftest` will build a *zerotier-selftest* binary which unit tests various internals and reports on a few aspects of the build environment. It's a good idea to try this on novel platforms or architectures.
 

+ 18 - 0
RELEASE-NOTES.md

@@ -1,6 +1,24 @@
 ZeroTier Release Notes
 ======
 
+# 2019-07-29 -- Version 1.4.0
+
+### Major Changes
+
+ * Mac version no longer requires a kernel extension, instead making use of the [feth interfaces](https://apple.stackexchange.com/questions/337715/fake-ethernet-interfaces-feth-if-fake-anyone-ever-seen-this).
+ * Added support for concurrent multipath (multiple paths at once) with traffic weighting by link quality and faster recovery from lost links.
+ * Added under-the-hood support for QoS (not yet exposed) that will eventually be configurable via our rules engine.
+
+### Minor Changes and Bug Fixes
+
+ * Experimental controller DB driver for [LF](https://github.com/zerotier/lf) to store network controller data (LFDB.cpp / LFDB.hpp).
+ * Modified credential push and direct path push timings and algorithms to somewhat reduce "chattiness" of the protocol when idle. More radical background overhead reductions will have to wait for the 2.x line.
+ * Removed our beta/half-baked integration of Central with the Windows UI. We're going to do a whole new UI of some kind in the future at least for Windows and Mac.
+ * Fixed stack overflow issues on Linux versions using musl libc.
+ * Fixed some alignment problems reported on ARM and ARM64, but some reports we could not reproduce so please report any issues with exact chip, OS/distro, and ZeroTier version in use.
+ * Fixed numerous other small issues and bugs such as ARM alignment issues causing crashes on some devices.
+ * Windows now sets the adapter name such that it is consistent in both the Windows UI and command line utilities.
+
 # 2018-07-27 -- Version 1.2.12
 
  * Fixed a bug that caused exits to take a long time on Mac due to huge numbers of redundant attempts to delete managed routes.

TEMPAT SAMPAH
artwork/AppIcon_90x90.png


+ 0 - 1042
attic/Cluster.cpp

@@ -1,1042 +0,0 @@
-/*
- * ZeroTier One - Network Virtualization Everywhere
- * Copyright (C) 2011-2017  ZeroTier, Inc.  https://www.zerotier.com/
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * --
- *
- * You can be released from the requirements of the license by purchasing
- * a commercial license. Buying such a license is mandatory as soon as you
- * develop commercial closed-source software that incorporates or links
- * directly against ZeroTier software without disclosing the source code
- * of your own application.
- */
-
-#ifdef ZT_ENABLE_CLUSTER
-
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <math.h>
-
-#include <map>
-#include <algorithm>
-#include <set>
-#include <utility>
-#include <list>
-#include <stdexcept>
-
-#include "../version.h"
-
-#include "Cluster.hpp"
-#include "RuntimeEnvironment.hpp"
-#include "MulticastGroup.hpp"
-#include "CertificateOfMembership.hpp"
-#include "Salsa20.hpp"
-#include "Poly1305.hpp"
-#include "Identity.hpp"
-#include "Topology.hpp"
-#include "Packet.hpp"
-#include "Switch.hpp"
-#include "Node.hpp"
-#include "Network.hpp"
-#include "Array.hpp"
-
-namespace ZeroTier {
-
-static inline double _dist3d(int x1,int y1,int z1,int x2,int y2,int z2)
-	throw()
-{
-	double dx = ((double)x2 - (double)x1);
-	double dy = ((double)y2 - (double)y1);
-	double dz = ((double)z2 - (double)z1);
-	return sqrt((dx * dx) + (dy * dy) + (dz * dz));
-}
-
-// An entry in _ClusterSendQueue
-struct _ClusterSendQueueEntry
-{
-	uint64_t timestamp;
-	Address fromPeerAddress;
-	Address toPeerAddress;
-	// if we ever support larger transport MTUs this must be increased
-	unsigned char data[ZT_CLUSTER_SEND_QUEUE_DATA_MAX];
-	unsigned int len;
-	bool unite;
-};
-
-// A multi-index map with entry memory pooling -- this allows our queue to
-// be O(log(N)) and is complex enough that it makes the code a lot cleaner
-// to break it out from Cluster.
-class _ClusterSendQueue
-{
-public:
-	_ClusterSendQueue() :
-		_poolCount(0) {}
-	~_ClusterSendQueue() {} // memory is automatically freed when _chunks is destroyed
-
-	inline void enqueue(uint64_t now,const Address &from,const Address &to,const void *data,unsigned int len,bool unite)
-	{
-		if (len > ZT_CLUSTER_SEND_QUEUE_DATA_MAX)
-			return;
-
-		Mutex::Lock _l(_lock);
-
-		// Delete oldest queue entry for this sender if this enqueue() would take them over the per-sender limit
-		{
-			std::set< std::pair<Address,_ClusterSendQueueEntry *> >::iterator qi(_bySrc.lower_bound(std::pair<Address,_ClusterSendQueueEntry *>(from,(_ClusterSendQueueEntry *)0)));
-			std::set< std::pair<Address,_ClusterSendQueueEntry *> >::iterator oldest(qi);
-			unsigned long countForSender = 0;
-			while ((qi != _bySrc.end())&&(qi->first == from)) {
-				if (qi->second->timestamp < oldest->second->timestamp)
-					oldest = qi;
-				++countForSender;
-				++qi;
-			}
-			if (countForSender >= ZT_CLUSTER_MAX_QUEUE_PER_SENDER) {
-				_byDest.erase(std::pair<Address,_ClusterSendQueueEntry *>(oldest->second->toPeerAddress,oldest->second));
-				_pool[_poolCount++] = oldest->second;
-				_bySrc.erase(oldest);
-			}
-		}
-
-		_ClusterSendQueueEntry *e;
-		if (_poolCount > 0) {
-			e = _pool[--_poolCount];
-		} else {
-			if (_chunks.size() >= ZT_CLUSTER_MAX_QUEUE_CHUNKS)
-				return; // queue is totally full!
-			_chunks.push_back(Array<_ClusterSendQueueEntry,ZT_CLUSTER_QUEUE_CHUNK_SIZE>());
-			e = &(_chunks.back().data[0]);
-			for(unsigned int i=1;i<ZT_CLUSTER_QUEUE_CHUNK_SIZE;++i)
-				_pool[_poolCount++] = &(_chunks.back().data[i]);
-		}
-
-		e->timestamp = now;
-		e->fromPeerAddress = from;
-		e->toPeerAddress = to;
-		memcpy(e->data,data,len);
-		e->len = len;
-		e->unite = unite;
-
-		_bySrc.insert(std::pair<Address,_ClusterSendQueueEntry *>(from,e));
-		_byDest.insert(std::pair<Address,_ClusterSendQueueEntry *>(to,e));
-	}
-
-	inline void expire(uint64_t now)
-	{
-		Mutex::Lock _l(_lock);
-		for(std::set< std::pair<Address,_ClusterSendQueueEntry *> >::iterator qi(_bySrc.begin());qi!=_bySrc.end();) {
-			if ((now - qi->second->timestamp) > ZT_CLUSTER_QUEUE_EXPIRATION) {
-				_byDest.erase(std::pair<Address,_ClusterSendQueueEntry *>(qi->second->toPeerAddress,qi->second));
-				_pool[_poolCount++] = qi->second;
-				_bySrc.erase(qi++);
-			} else ++qi;
-		}
-	}
-
-	/**
-	 * Get and dequeue entries for a given destination address
-	 *
-	 * After use these entries must be returned with returnToPool()!
-	 *
-	 * @param dest Destination address
-	 * @param results Array to fill with results
-	 * @param maxResults Size of results[] in pointers
-	 * @return Number of actual results returned
-	 */
-	inline unsigned int getByDest(const Address &dest,_ClusterSendQueueEntry **results,unsigned int maxResults)
-	{
-		unsigned int count = 0;
-		Mutex::Lock _l(_lock);
-		std::set< std::pair<Address,_ClusterSendQueueEntry *> >::iterator qi(_byDest.lower_bound(std::pair<Address,_ClusterSendQueueEntry *>(dest,(_ClusterSendQueueEntry *)0)));
-		while ((qi != _byDest.end())&&(qi->first == dest)) {
-			_bySrc.erase(std::pair<Address,_ClusterSendQueueEntry *>(qi->second->fromPeerAddress,qi->second));
-			results[count++] = qi->second;
-			if (count == maxResults)
-				break;
-			_byDest.erase(qi++);
-		}
-		return count;
-	}
-
-	/**
-	 * Return entries to pool after use
-	 *
-	 * @param entries Array of entries
-	 * @param count Number of entries
-	 */
-	inline void returnToPool(_ClusterSendQueueEntry **entries,unsigned int count)
-	{
-		Mutex::Lock _l(_lock);
-		for(unsigned int i=0;i<count;++i)
-			_pool[_poolCount++] = entries[i];
-	}
-
-private:
-	std::list< Array<_ClusterSendQueueEntry,ZT_CLUSTER_QUEUE_CHUNK_SIZE> > _chunks;
-	_ClusterSendQueueEntry *_pool[ZT_CLUSTER_QUEUE_CHUNK_SIZE * ZT_CLUSTER_MAX_QUEUE_CHUNKS];
-	unsigned long _poolCount;
-	std::set< std::pair<Address,_ClusterSendQueueEntry *> > _bySrc;
-	std::set< std::pair<Address,_ClusterSendQueueEntry *> > _byDest;
-	Mutex _lock;
-};
-
-Cluster::Cluster(
-	const RuntimeEnvironment *renv,
-	uint16_t id,
-	const std::vector<InetAddress> &zeroTierPhysicalEndpoints,
-	int32_t x,
-	int32_t y,
-	int32_t z,
-	void (*sendFunction)(void *,unsigned int,const void *,unsigned int),
-	void *sendFunctionArg,
-	int (*addressToLocationFunction)(void *,const struct sockaddr_storage *,int *,int *,int *),
-	void *addressToLocationFunctionArg) :
-	RR(renv),
-	_sendQueue(new _ClusterSendQueue()),
-	_sendFunction(sendFunction),
-	_sendFunctionArg(sendFunctionArg),
-	_addressToLocationFunction(addressToLocationFunction),
-	_addressToLocationFunctionArg(addressToLocationFunctionArg),
-	_x(x),
-	_y(y),
-	_z(z),
-	_id(id),
-	_zeroTierPhysicalEndpoints(zeroTierPhysicalEndpoints),
-	_members(new _Member[ZT_CLUSTER_MAX_MEMBERS]),
-	_lastFlushed(0),
-	_lastCleanedRemotePeers(0),
-	_lastCleanedQueue(0)
-{
-	uint16_t stmp[ZT_SHA512_DIGEST_LEN / sizeof(uint16_t)];
-
-	// Generate master secret by hashing the secret from our Identity key pair
-	RR->identity.sha512PrivateKey(_masterSecret);
-
-	// Generate our inbound message key, which is the master secret XORed with our ID and hashed twice
-	memcpy(stmp,_masterSecret,sizeof(stmp));
-	stmp[0] ^= Utils::hton(id);
-	SHA512::hash(stmp,stmp,sizeof(stmp));
-	SHA512::hash(stmp,stmp,sizeof(stmp));
-	memcpy(_key,stmp,sizeof(_key));
-	Utils::burn(stmp,sizeof(stmp));
-}
-
-Cluster::~Cluster()
-{
-	Utils::burn(_masterSecret,sizeof(_masterSecret));
-	Utils::burn(_key,sizeof(_key));
-	delete [] _members;
-	delete _sendQueue;
-}
-
-void Cluster::handleIncomingStateMessage(const void *msg,unsigned int len)
-{
-	Buffer<ZT_CLUSTER_MAX_MESSAGE_LENGTH> dmsg;
-	{
-		// FORMAT: <[16] iv><[8] MAC><... data>
-		if ((len < 24)||(len > ZT_CLUSTER_MAX_MESSAGE_LENGTH))
-			return;
-
-		// 16-byte IV: first 8 bytes XORed with key, last 8 bytes used as Salsa20 64-bit IV
-		char keytmp[32];
-		memcpy(keytmp,_key,32);
-		for(int i=0;i<8;++i)
-			keytmp[i] ^= reinterpret_cast<const char *>(msg)[i];
-		Salsa20 s20(keytmp,reinterpret_cast<const char *>(msg) + 8);
-		Utils::burn(keytmp,sizeof(keytmp));
-
-		// One-time-use Poly1305 key from first 32 bytes of Salsa20 keystream (as per DJB/NaCl "standard")
-		char polykey[ZT_POLY1305_KEY_LEN];
-		memset(polykey,0,sizeof(polykey));
-		s20.crypt12(polykey,polykey,sizeof(polykey));
-
-		// Compute 16-byte MAC
-		char mac[ZT_POLY1305_MAC_LEN];
-		Poly1305::compute(mac,reinterpret_cast<const char *>(msg) + 24,len - 24,polykey);
-
-		// Check first 8 bytes of MAC against 64-bit MAC in stream
-		if (!Utils::secureEq(mac,reinterpret_cast<const char *>(msg) + 16,8))
-			return;
-
-		// Decrypt!
-		dmsg.setSize(len - 24);
-		s20.crypt12(reinterpret_cast<const char *>(msg) + 24,const_cast<void *>(dmsg.data()),dmsg.size());
-	}
-
-	if (dmsg.size() < 4)
-		return;
-	const uint16_t fromMemberId = dmsg.at<uint16_t>(0);
-	unsigned int ptr = 2;
-	if (fromMemberId == _id) // sanity check: we don't talk to ourselves
-		return;
-	const uint16_t toMemberId = dmsg.at<uint16_t>(ptr);
-	ptr += 2;
-	if (toMemberId != _id) // sanity check: message not for us?
-		return;
-
-	{	// make sure sender is actually considered a member
-		Mutex::Lock _l3(_memberIds_m);
-		if (std::find(_memberIds.begin(),_memberIds.end(),fromMemberId) == _memberIds.end())
-			return;
-	}
-
-	try {
-		while (ptr < dmsg.size()) {
-			const unsigned int mlen = dmsg.at<uint16_t>(ptr); ptr += 2;
-			const unsigned int nextPtr = ptr + mlen;
-			if (nextPtr > dmsg.size())
-				break;
-
-			int mtype = -1;
-			try {
-				switch((StateMessageType)(mtype = (int)dmsg[ptr++])) {
-					default:
-						break;
-
-					case CLUSTER_MESSAGE_ALIVE: {
-						_Member &m = _members[fromMemberId];
-						Mutex::Lock mlck(m.lock);
-						ptr += 7; // skip version stuff, not used yet
-						m.x = dmsg.at<int32_t>(ptr); ptr += 4;
-						m.y = dmsg.at<int32_t>(ptr); ptr += 4;
-						m.z = dmsg.at<int32_t>(ptr); ptr += 4;
-						ptr += 8; // skip local clock, not used
-						m.load = dmsg.at<uint64_t>(ptr); ptr += 8;
-						m.peers = dmsg.at<uint64_t>(ptr); ptr += 8;
-						ptr += 8; // skip flags, unused
-#ifdef ZT_TRACE
-						std::string addrs;
-#endif
-						unsigned int physicalAddressCount = dmsg[ptr++];
-						m.zeroTierPhysicalEndpoints.clear();
-						for(unsigned int i=0;i<physicalAddressCount;++i) {
-							m.zeroTierPhysicalEndpoints.push_back(InetAddress());
-							ptr += m.zeroTierPhysicalEndpoints.back().deserialize(dmsg,ptr);
-							if (!(m.zeroTierPhysicalEndpoints.back())) {
-								m.zeroTierPhysicalEndpoints.pop_back();
-							}
-#ifdef ZT_TRACE
-							else {
-								if (addrs.length() > 0)
-									addrs.push_back(',');
-								addrs.append(m.zeroTierPhysicalEndpoints.back().toString());
-							}
-#endif
-						}
-#ifdef ZT_TRACE
-						if ((RR->node->now() - m.lastReceivedAliveAnnouncement) >= ZT_CLUSTER_TIMEOUT) {
-							TRACE("[%u] I'm alive! peers close to %d,%d,%d can be redirected to: %s",(unsigned int)fromMemberId,m.x,m.y,m.z,addrs.c_str());
-						}
-#endif
-						m.lastReceivedAliveAnnouncement = RR->node->now();
-					}	break;
-
-					case CLUSTER_MESSAGE_HAVE_PEER: {
-						Identity id;
-						ptr += id.deserialize(dmsg,ptr);
-						if (id) {
-							{
-								Mutex::Lock _l(_remotePeers_m);
-								_RemotePeer &rp = _remotePeers[std::pair<Address,unsigned int>(id.address(),(unsigned int)fromMemberId)];
-								if (!rp.lastHavePeerReceived) {
-									RR->topology->saveIdentity((void *)0,id);
-									RR->identity.agree(id,rp.key,ZT_PEER_SECRET_KEY_LENGTH);
-								}
-								rp.lastHavePeerReceived = RR->node->now();
-							}
-
-							_ClusterSendQueueEntry *q[16384]; // 16384 is "tons"
-							unsigned int qc = _sendQueue->getByDest(id.address(),q,16384);
-							for(unsigned int i=0;i<qc;++i)
-								this->relayViaCluster(q[i]->fromPeerAddress,q[i]->toPeerAddress,q[i]->data,q[i]->len,q[i]->unite);
-							_sendQueue->returnToPool(q,qc);
-
-							TRACE("[%u] has %s (retried %u queued sends)",(unsigned int)fromMemberId,id.address().toString().c_str(),qc);
-						}
-					}	break;
-
-					case CLUSTER_MESSAGE_WANT_PEER: {
-						const Address zeroTierAddress(dmsg.field(ptr,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); ptr += ZT_ADDRESS_LENGTH;
-						SharedPtr<Peer> peer(RR->topology->getPeerNoCache(zeroTierAddress));
-						if ( (peer) && (peer->hasLocalClusterOptimalPath(RR->node->now())) ) {
-							Buffer<1024> buf;
-							peer->identity().serialize(buf);
-							Mutex::Lock _l2(_members[fromMemberId].lock);
-							_send(fromMemberId,CLUSTER_MESSAGE_HAVE_PEER,buf.data(),buf.size());
-						}
-					}	break;
-
-					case CLUSTER_MESSAGE_REMOTE_PACKET: {
-						const unsigned int plen = dmsg.at<uint16_t>(ptr); ptr += 2;
-						if (plen) {
-							Packet remotep(dmsg.field(ptr,plen),plen); ptr += plen;
-							//TRACE("remote %s from %s via %u (%u bytes)",Packet::verbString(remotep.verb()),remotep.source().toString().c_str(),fromMemberId,plen);
-							switch(remotep.verb()) {
-								case Packet::VERB_WHOIS:            _doREMOTE_WHOIS(fromMemberId,remotep); break;
-								case Packet::VERB_MULTICAST_GATHER: _doREMOTE_MULTICAST_GATHER(fromMemberId,remotep); break;
-								default: break; // ignore things we don't care about across cluster
-							}
-						}
-					}	break;
-
-					case CLUSTER_MESSAGE_PROXY_UNITE: {
-						const Address localPeerAddress(dmsg.field(ptr,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); ptr += ZT_ADDRESS_LENGTH;
-						const Address remotePeerAddress(dmsg.field(ptr,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); ptr += ZT_ADDRESS_LENGTH;
-						const unsigned int numRemotePeerPaths = dmsg[ptr++];
-						InetAddress remotePeerPaths[256]; // size is 8-bit, so 256 is max
-						for(unsigned int i=0;i<numRemotePeerPaths;++i)
-							ptr += remotePeerPaths[i].deserialize(dmsg,ptr);
-
-						TRACE("[%u] requested that we unite local %s with remote %s",(unsigned int)fromMemberId,localPeerAddress.toString().c_str(),remotePeerAddress.toString().c_str());
-
-						const uint64_t now = RR->node->now();
-						SharedPtr<Peer> localPeer(RR->topology->getPeerNoCache(localPeerAddress));
-						if ((localPeer)&&(numRemotePeerPaths > 0)) {
-							InetAddress bestLocalV4,bestLocalV6;
-							localPeer->getRendezvousAddresses(now,bestLocalV4,bestLocalV6);
-
-							InetAddress bestRemoteV4,bestRemoteV6;
-							for(unsigned int i=0;i<numRemotePeerPaths;++i) {
-								if ((bestRemoteV4)&&(bestRemoteV6))
-									break;
-								switch(remotePeerPaths[i].ss_family) {
-									case AF_INET:
-										if (!bestRemoteV4)
-											bestRemoteV4 = remotePeerPaths[i];
-										break;
-									case AF_INET6:
-										if (!bestRemoteV6)
-											bestRemoteV6 = remotePeerPaths[i];
-										break;
-								}
-							}
-
-							Packet rendezvousForLocal(localPeerAddress,RR->identity.address(),Packet::VERB_RENDEZVOUS);
-							rendezvousForLocal.append((uint8_t)0);
-							remotePeerAddress.appendTo(rendezvousForLocal);
-
-							Buffer<2048> rendezvousForRemote;
-							remotePeerAddress.appendTo(rendezvousForRemote);
-							rendezvousForRemote.append((uint8_t)Packet::VERB_RENDEZVOUS);
-							rendezvousForRemote.addSize(2); // space for actual packet payload length
-							rendezvousForRemote.append((uint8_t)0); // flags == 0
-							localPeerAddress.appendTo(rendezvousForRemote);
-
-							bool haveMatch = false;
-							if ((bestLocalV6)&&(bestRemoteV6)) {
-								haveMatch = true;
-
-								rendezvousForLocal.append((uint16_t)bestRemoteV6.port());
-								rendezvousForLocal.append((uint8_t)16);
-								rendezvousForLocal.append(bestRemoteV6.rawIpData(),16);
-
-								rendezvousForRemote.append((uint16_t)bestLocalV6.port());
-								rendezvousForRemote.append((uint8_t)16);
-								rendezvousForRemote.append(bestLocalV6.rawIpData(),16);
-								rendezvousForRemote.setAt<uint16_t>(ZT_ADDRESS_LENGTH + 1,(uint16_t)(9 + 16));
-							} else if ((bestLocalV4)&&(bestRemoteV4)) {
-								haveMatch = true;
-
-								rendezvousForLocal.append((uint16_t)bestRemoteV4.port());
-								rendezvousForLocal.append((uint8_t)4);
-								rendezvousForLocal.append(bestRemoteV4.rawIpData(),4);
-
-								rendezvousForRemote.append((uint16_t)bestLocalV4.port());
-								rendezvousForRemote.append((uint8_t)4);
-								rendezvousForRemote.append(bestLocalV4.rawIpData(),4);
-								rendezvousForRemote.setAt<uint16_t>(ZT_ADDRESS_LENGTH + 1,(uint16_t)(9 + 4));
-							}
-
-							if (haveMatch) {
-								{
-									Mutex::Lock _l2(_members[fromMemberId].lock);
-									_send(fromMemberId,CLUSTER_MESSAGE_PROXY_SEND,rendezvousForRemote.data(),rendezvousForRemote.size());
-								}
-								RR->sw->send((void *)0,rendezvousForLocal,true);
-							}
-						}
-					}	break;
-
-					case CLUSTER_MESSAGE_PROXY_SEND: {
-						const Address rcpt(dmsg.field(ptr,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); ptr += ZT_ADDRESS_LENGTH;
-						const Packet::Verb verb = (Packet::Verb)dmsg[ptr++];
-						const unsigned int len = dmsg.at<uint16_t>(ptr); ptr += 2;
-						Packet outp(rcpt,RR->identity.address(),verb);
-						outp.append(dmsg.field(ptr,len),len); ptr += len;
-						RR->sw->send((void *)0,outp,true);
-						//TRACE("[%u] proxy send %s to %s length %u",(unsigned int)fromMemberId,Packet::verbString(verb),rcpt.toString().c_str(),len);
-					}	break;
-
-					case CLUSTER_MESSAGE_NETWORK_CONFIG: {
-						const SharedPtr<Network> network(RR->node->network(dmsg.at<uint64_t>(ptr)));
-						if (network) {
-							// Copy into a Packet just to conform to Network API. Eventually
-							// will want to refactor.
-							network->handleConfigChunk((void *)0,0,Address(),Buffer<ZT_PROTO_MAX_PACKET_LENGTH>(dmsg),ptr);
-						}
-					}	break;
-				}
-			} catch ( ... ) {
-				TRACE("invalid message of size %u type %d (inner decode), discarding",mlen,mtype);
-				// drop invalids
-			}
-
-			ptr = nextPtr;
-		}
-	} catch ( ... ) {
-		TRACE("invalid message (outer loop), discarding");
-		// drop invalids
-	}
-}
-
-void Cluster::broadcastHavePeer(const Identity &id)
-{
-	Buffer<1024> buf;
-	id.serialize(buf);
-	Mutex::Lock _l(_memberIds_m);
-	for(std::vector<uint16_t>::const_iterator mid(_memberIds.begin());mid!=_memberIds.end();++mid) {
-		Mutex::Lock _l2(_members[*mid].lock);
-		_send(*mid,CLUSTER_MESSAGE_HAVE_PEER,buf.data(),buf.size());
-	}
-}
-
-void Cluster::broadcastNetworkConfigChunk(const void *chunk,unsigned int len)
-{
-	Mutex::Lock _l(_memberIds_m);
-	for(std::vector<uint16_t>::const_iterator mid(_memberIds.begin());mid!=_memberIds.end();++mid) {
-		Mutex::Lock _l2(_members[*mid].lock);
-		_send(*mid,CLUSTER_MESSAGE_NETWORK_CONFIG,chunk,len);
-	}
-}
-
-int Cluster::checkSendViaCluster(const Address &toPeerAddress,uint64_t &mostRecentTs,void *peerSecret)
-{
-	const uint64_t now = RR->node->now();
-	mostRecentTs = 0;
-	int mostRecentMemberId = -1;
-	{
-		Mutex::Lock _l2(_remotePeers_m);
-		std::map< std::pair<Address,unsigned int>,_RemotePeer >::const_iterator rpe(_remotePeers.lower_bound(std::pair<Address,unsigned int>(toPeerAddress,0)));
-		for(;;) {
-			if ((rpe == _remotePeers.end())||(rpe->first.first != toPeerAddress))
-				break;
-			else if (rpe->second.lastHavePeerReceived > mostRecentTs) {
-				mostRecentTs = rpe->second.lastHavePeerReceived;
-				memcpy(peerSecret,rpe->second.key,ZT_PEER_SECRET_KEY_LENGTH);
-				mostRecentMemberId = (int)rpe->first.second;
-			}
-			++rpe;
-		}
-	}
-
-	const uint64_t ageOfMostRecentHavePeerAnnouncement = now - mostRecentTs;
-	if (ageOfMostRecentHavePeerAnnouncement >= (ZT_PEER_ACTIVITY_TIMEOUT / 3)) {
-		if (ageOfMostRecentHavePeerAnnouncement >= ZT_PEER_ACTIVITY_TIMEOUT)
-			mostRecentMemberId = -1;
-
-		bool sendWantPeer = true;
-		{
-			Mutex::Lock _l(_remotePeers_m);
-			_RemotePeer &rp = _remotePeers[std::pair<Address,unsigned int>(toPeerAddress,(unsigned int)_id)];
-			if ((now - rp.lastSentWantPeer) >= ZT_CLUSTER_WANT_PEER_EVERY) {
-				rp.lastSentWantPeer = now;
-			} else {
-				sendWantPeer = false; // don't flood WANT_PEER
-			}
-		}
-		if (sendWantPeer) {
-			char tmp[ZT_ADDRESS_LENGTH];
-			toPeerAddress.copyTo(tmp,ZT_ADDRESS_LENGTH);
-			{
-				Mutex::Lock _l(_memberIds_m);
-				for(std::vector<uint16_t>::const_iterator mid(_memberIds.begin());mid!=_memberIds.end();++mid) {
-					Mutex::Lock _l2(_members[*mid].lock);
-					_send(*mid,CLUSTER_MESSAGE_WANT_PEER,tmp,ZT_ADDRESS_LENGTH);
-				}
-			}
-		}
-	}
-
-	return mostRecentMemberId;
-}
-
-bool Cluster::sendViaCluster(int mostRecentMemberId,const Address &toPeerAddress,const void *data,unsigned int len)
-{
-	if ((mostRecentMemberId < 0)||(mostRecentMemberId >= ZT_CLUSTER_MAX_MEMBERS)) // sanity check
-		return false;
-	Mutex::Lock _l2(_members[mostRecentMemberId].lock);
-	for(std::vector<InetAddress>::const_iterator i1(_zeroTierPhysicalEndpoints.begin());i1!=_zeroTierPhysicalEndpoints.end();++i1) {
-		for(std::vector<InetAddress>::const_iterator i2(_members[mostRecentMemberId].zeroTierPhysicalEndpoints.begin());i2!=_members[mostRecentMemberId].zeroTierPhysicalEndpoints.end();++i2) {
-			if (i1->ss_family == i2->ss_family) {
-				TRACE("sendViaCluster sending %u bytes to %s by way of %u (%s->%s)",len,toPeerAddress.toString().c_str(),(unsigned int)mostRecentMemberId,i1->toString().c_str(),i2->toString().c_str());
-				RR->node->putPacket((void *)0,*i1,*i2,data,len);
-				return true;
-			}
-		}
-	}
-	return false;
-}
-
-void Cluster::relayViaCluster(const Address &fromPeerAddress,const Address &toPeerAddress,const void *data,unsigned int len,bool unite)
-{
-	if (len > ZT_PROTO_MAX_PACKET_LENGTH) // sanity check
-		return;
-
-	const uint64_t now = RR->node->now();
-
-	uint64_t mostRecentTs = 0;
-	int mostRecentMemberId = -1;
-	{
-		Mutex::Lock _l2(_remotePeers_m);
-		std::map< std::pair<Address,unsigned int>,_RemotePeer >::const_iterator rpe(_remotePeers.lower_bound(std::pair<Address,unsigned int>(toPeerAddress,0)));
-		for(;;) {
-			if ((rpe == _remotePeers.end())||(rpe->first.first != toPeerAddress))
-				break;
-			else if (rpe->second.lastHavePeerReceived > mostRecentTs) {
-				mostRecentTs = rpe->second.lastHavePeerReceived;
-				mostRecentMemberId = (int)rpe->first.second;
-			}
-			++rpe;
-		}
-	}
-
-	const uint64_t ageOfMostRecentHavePeerAnnouncement = now - mostRecentTs;
-	if (ageOfMostRecentHavePeerAnnouncement >= (ZT_PEER_ACTIVITY_TIMEOUT / 3)) {
-		// Enqueue and wait if peer seems alive, but do WANT_PEER to refresh homing
-		const bool enqueueAndWait = ((ageOfMostRecentHavePeerAnnouncement >= ZT_PEER_ACTIVITY_TIMEOUT)||(mostRecentMemberId < 0));
-
-		// Poll everyone with WANT_PEER if the age of our most recent entry is
-		// approaching expiration (or has expired, or does not exist).
-		bool sendWantPeer = true;
-		{
-			Mutex::Lock _l(_remotePeers_m);
-			_RemotePeer &rp = _remotePeers[std::pair<Address,unsigned int>(toPeerAddress,(unsigned int)_id)];
-			if ((now - rp.lastSentWantPeer) >= ZT_CLUSTER_WANT_PEER_EVERY) {
-				rp.lastSentWantPeer = now;
-			} else {
-				sendWantPeer = false; // don't flood WANT_PEER
-			}
-		}
-		if (sendWantPeer) {
-			char tmp[ZT_ADDRESS_LENGTH];
-			toPeerAddress.copyTo(tmp,ZT_ADDRESS_LENGTH);
-			{
-				Mutex::Lock _l(_memberIds_m);
-				for(std::vector<uint16_t>::const_iterator mid(_memberIds.begin());mid!=_memberIds.end();++mid) {
-					Mutex::Lock _l2(_members[*mid].lock);
-					_send(*mid,CLUSTER_MESSAGE_WANT_PEER,tmp,ZT_ADDRESS_LENGTH);
-				}
-			}
-		}
-
-		// If there isn't a good place to send via, then enqueue this for retrying
-		// later and return after having broadcasted a WANT_PEER.
-		if (enqueueAndWait) {
-			TRACE("relayViaCluster %s -> %s enqueueing to wait for HAVE_PEER",fromPeerAddress.toString().c_str(),toPeerAddress.toString().c_str());
-			_sendQueue->enqueue(now,fromPeerAddress,toPeerAddress,data,len,unite);
-			return;
-		}
-	}
-
-	if (mostRecentMemberId >= 0) {
-		Buffer<1024> buf;
-		if (unite) {
-			InetAddress v4,v6;
-			if (fromPeerAddress) {
-				SharedPtr<Peer> fromPeer(RR->topology->getPeerNoCache(fromPeerAddress));
-				if (fromPeer)
-					fromPeer->getRendezvousAddresses(now,v4,v6);
-			}
-			uint8_t addrCount = 0;
-			if (v4)
-				++addrCount;
-			if (v6)
-				++addrCount;
-			if (addrCount) {
-				toPeerAddress.appendTo(buf);
-				fromPeerAddress.appendTo(buf);
-				buf.append(addrCount);
-				if (v4)
-					v4.serialize(buf);
-				if (v6)
-					v6.serialize(buf);
-			}
-		}
-
-		{
-			Mutex::Lock _l2(_members[mostRecentMemberId].lock);
-			if (buf.size() > 0)
-				_send(mostRecentMemberId,CLUSTER_MESSAGE_PROXY_UNITE,buf.data(),buf.size());
-
-			for(std::vector<InetAddress>::const_iterator i1(_zeroTierPhysicalEndpoints.begin());i1!=_zeroTierPhysicalEndpoints.end();++i1) {
-				for(std::vector<InetAddress>::const_iterator i2(_members[mostRecentMemberId].zeroTierPhysicalEndpoints.begin());i2!=_members[mostRecentMemberId].zeroTierPhysicalEndpoints.end();++i2) {
-					if (i1->ss_family == i2->ss_family) {
-						TRACE("relayViaCluster relaying %u bytes from %s to %s by way of %u (%s->%s)",len,fromPeerAddress.toString().c_str(),toPeerAddress.toString().c_str(),(unsigned int)mostRecentMemberId,i1->toString().c_str(),i2->toString().c_str());
-						RR->node->putPacket((void *)0,*i1,*i2,data,len);
-						return;
-					}
-				}
-			}
-
-			TRACE("relayViaCluster relaying %u bytes from %s to %s by way of %u failed: no common endpoints with the same address family!",len,fromPeerAddress.toString().c_str(),toPeerAddress.toString().c_str(),(unsigned int)mostRecentMemberId);
-		}
-	}
-}
-
-void Cluster::sendDistributedQuery(const Packet &pkt)
-{
-	Buffer<4096> buf;
-	buf.append((uint16_t)pkt.size());
-	buf.append(pkt.data(),pkt.size());
-	Mutex::Lock _l(_memberIds_m);
-	for(std::vector<uint16_t>::const_iterator mid(_memberIds.begin());mid!=_memberIds.end();++mid) {
-		Mutex::Lock _l2(_members[*mid].lock);
-		_send(*mid,CLUSTER_MESSAGE_REMOTE_PACKET,buf.data(),buf.size());
-	}
-}
-
-void Cluster::doPeriodicTasks()
-{
-	const uint64_t now = RR->node->now();
-
-	if ((now - _lastFlushed) >= ZT_CLUSTER_FLUSH_PERIOD) {
-		_lastFlushed = now;
-
-		Mutex::Lock _l(_memberIds_m);
-		for(std::vector<uint16_t>::const_iterator mid(_memberIds.begin());mid!=_memberIds.end();++mid) {
-			Mutex::Lock _l2(_members[*mid].lock);
-
-			if ((now - _members[*mid].lastAnnouncedAliveTo) >= ((ZT_CLUSTER_TIMEOUT / 2) - 1000)) {
-				_members[*mid].lastAnnouncedAliveTo = now;
-
-				Buffer<2048> alive;
-				alive.append((uint16_t)ZEROTIER_ONE_VERSION_MAJOR);
-				alive.append((uint16_t)ZEROTIER_ONE_VERSION_MINOR);
-				alive.append((uint16_t)ZEROTIER_ONE_VERSION_REVISION);
-				alive.append((uint8_t)ZT_PROTO_VERSION);
-				if (_addressToLocationFunction) {
-					alive.append((int32_t)_x);
-					alive.append((int32_t)_y);
-					alive.append((int32_t)_z);
-				} else {
-					alive.append((int32_t)0);
-					alive.append((int32_t)0);
-					alive.append((int32_t)0);
-				}
-				alive.append((uint64_t)now);
-				alive.append((uint64_t)0); // TODO: compute and send load average
-				alive.append((uint64_t)RR->topology->countActive(now));
-				alive.append((uint64_t)0); // unused/reserved flags
-				alive.append((uint8_t)_zeroTierPhysicalEndpoints.size());
-				for(std::vector<InetAddress>::const_iterator pe(_zeroTierPhysicalEndpoints.begin());pe!=_zeroTierPhysicalEndpoints.end();++pe)
-					pe->serialize(alive);
-				_send(*mid,CLUSTER_MESSAGE_ALIVE,alive.data(),alive.size());
-			}
-
-			_flush(*mid);
-		}
-	}
-
-	if ((now - _lastCleanedRemotePeers) >= (ZT_PEER_ACTIVITY_TIMEOUT * 2)) {
-		_lastCleanedRemotePeers = now;
-
-		Mutex::Lock _l(_remotePeers_m);
-		for(std::map< std::pair<Address,unsigned int>,_RemotePeer >::iterator rp(_remotePeers.begin());rp!=_remotePeers.end();) {
-			if ((now - rp->second.lastHavePeerReceived) >= ZT_PEER_ACTIVITY_TIMEOUT)
-				_remotePeers.erase(rp++);
-			else ++rp;
-		}
-	}
-
-	if ((now - _lastCleanedQueue) >= ZT_CLUSTER_QUEUE_EXPIRATION) {
-		_lastCleanedQueue = now;
-		_sendQueue->expire(now);
-	}
-}
-
-void Cluster::addMember(uint16_t memberId)
-{
-	if ((memberId >= ZT_CLUSTER_MAX_MEMBERS)||(memberId == _id))
-		return;
-
-	Mutex::Lock _l2(_members[memberId].lock);
-
-	{
-		Mutex::Lock _l(_memberIds_m);
-		if (std::find(_memberIds.begin(),_memberIds.end(),memberId) != _memberIds.end())
-			return;
-		_memberIds.push_back(memberId);
-		std::sort(_memberIds.begin(),_memberIds.end());
-	}
-
-	_members[memberId].clear();
-
-	// Generate this member's message key from the master and its ID
-	uint16_t stmp[ZT_SHA512_DIGEST_LEN / sizeof(uint16_t)];
-	memcpy(stmp,_masterSecret,sizeof(stmp));
-	stmp[0] ^= Utils::hton(memberId);
-	SHA512::hash(stmp,stmp,sizeof(stmp));
-	SHA512::hash(stmp,stmp,sizeof(stmp));
-	memcpy(_members[memberId].key,stmp,sizeof(_members[memberId].key));
-	Utils::burn(stmp,sizeof(stmp));
-
-	// Prepare q
-	_members[memberId].q.clear();
-	char iv[16];
-	Utils::getSecureRandom(iv,16);
-	_members[memberId].q.append(iv,16);
-	_members[memberId].q.addSize(8); // room for MAC
-	_members[memberId].q.append((uint16_t)_id);
-	_members[memberId].q.append((uint16_t)memberId);
-}
-
-void Cluster::removeMember(uint16_t memberId)
-{
-	Mutex::Lock _l(_memberIds_m);
-	std::vector<uint16_t> newMemberIds;
-	for(std::vector<uint16_t>::const_iterator mid(_memberIds.begin());mid!=_memberIds.end();++mid) {
-		if (*mid != memberId)
-			newMemberIds.push_back(*mid);
-	}
-	_memberIds = newMemberIds;
-}
-
-bool Cluster::findBetterEndpoint(InetAddress &redirectTo,const Address &peerAddress,const InetAddress &peerPhysicalAddress,bool offload)
-{
-	if (_addressToLocationFunction) {
-		// Pick based on location if it can be determined
-		int px = 0,py = 0,pz = 0;
-		if (_addressToLocationFunction(_addressToLocationFunctionArg,reinterpret_cast<const struct sockaddr_storage *>(&peerPhysicalAddress),&px,&py,&pz) == 0) {
-			TRACE("no geolocation data for %s",peerPhysicalAddress.toIpString().c_str());
-			return false;
-		}
-
-		// Find member closest to this peer
-		const uint64_t now = RR->node->now();
-		std::vector<InetAddress> best;
-		const double currentDistance = _dist3d(_x,_y,_z,px,py,pz);
-		double bestDistance = (offload ? 2147483648.0 : currentDistance);
-#ifdef ZT_TRACE
-		unsigned int bestMember = _id;
-#endif
-		{
-			Mutex::Lock _l(_memberIds_m);
-			for(std::vector<uint16_t>::const_iterator mid(_memberIds.begin());mid!=_memberIds.end();++mid) {
-				_Member &m = _members[*mid];
-				Mutex::Lock _ml(m.lock);
-
-				// Consider member if it's alive and has sent us a location and one or more physical endpoints to send peers to
-				if ( ((now - m.lastReceivedAliveAnnouncement) < ZT_CLUSTER_TIMEOUT) && ((m.x != 0)||(m.y != 0)||(m.z != 0)) && (m.zeroTierPhysicalEndpoints.size() > 0) ) {
-					const double mdist = _dist3d(m.x,m.y,m.z,px,py,pz);
-					if (mdist < bestDistance) {
-						bestDistance = mdist;
-#ifdef ZT_TRACE
-						bestMember = *mid;
-#endif
-						best = m.zeroTierPhysicalEndpoints;
-					}
-				}
-			}
-		}
-
-		// Redirect to a closer member if it has a ZeroTier endpoint address in the same ss_family
-		for(std::vector<InetAddress>::const_iterator a(best.begin());a!=best.end();++a) {
-			if (a->ss_family == peerPhysicalAddress.ss_family) {
-				TRACE("%s at [%d,%d,%d] is %f from us but %f from %u, can redirect to %s",peerAddress.toString().c_str(),px,py,pz,currentDistance,bestDistance,bestMember,a->toString().c_str());
-				redirectTo = *a;
-				return true;
-			}
-		}
-		TRACE("%s at [%d,%d,%d] is %f from us, no better endpoints found",peerAddress.toString().c_str(),px,py,pz,currentDistance);
-		return false;
-	} else {
-		// TODO: pick based on load if no location info?
-		return false;
-	}
-}
-
-bool Cluster::isClusterPeerFrontplane(const InetAddress &ip) const
-{
-	Mutex::Lock _l(_memberIds_m);
-	for(std::vector<uint16_t>::const_iterator mid(_memberIds.begin());mid!=_memberIds.end();++mid) {
-		Mutex::Lock _l2(_members[*mid].lock);
-		for(std::vector<InetAddress>::const_iterator i2(_members[*mid].zeroTierPhysicalEndpoints.begin());i2!=_members[*mid].zeroTierPhysicalEndpoints.end();++i2) {
-			if (ip == *i2)
-				return true;
-		}
-	}
-	return false;
-}
-
-void Cluster::status(ZT_ClusterStatus &status) const
-{
-	const uint64_t now = RR->node->now();
-	memset(&status,0,sizeof(ZT_ClusterStatus));
-
-	status.myId = _id;
-
-	{
-		ZT_ClusterMemberStatus *const s = &(status.members[status.clusterSize++]);
-		s->id = _id;
-		s->alive = 1;
-		s->x = _x;
-		s->y = _y;
-		s->z = _z;
-		s->load = 0; // TODO
-		s->peers = RR->topology->countActive(now);
-		for(std::vector<InetAddress>::const_iterator ep(_zeroTierPhysicalEndpoints.begin());ep!=_zeroTierPhysicalEndpoints.end();++ep) {
-			if (s->numZeroTierPhysicalEndpoints >= ZT_CLUSTER_MAX_ZT_PHYSICAL_ADDRESSES) // sanity check
-				break;
-			memcpy(&(s->zeroTierPhysicalEndpoints[s->numZeroTierPhysicalEndpoints++]),&(*ep),sizeof(struct sockaddr_storage));
-		}
-	}
-
-	{
-		Mutex::Lock _l1(_memberIds_m);
-		for(std::vector<uint16_t>::const_iterator mid(_memberIds.begin());mid!=_memberIds.end();++mid) {
-			if (status.clusterSize >= ZT_CLUSTER_MAX_MEMBERS) // sanity check
-				break;
-
-			_Member &m = _members[*mid];
-			Mutex::Lock ml(m.lock);
-
-			ZT_ClusterMemberStatus *const s = &(status.members[status.clusterSize++]);
-			s->id = *mid;
-			s->msSinceLastHeartbeat = (unsigned int)std::min((uint64_t)(~((unsigned int)0)),(now - m.lastReceivedAliveAnnouncement));
-			s->alive = (s->msSinceLastHeartbeat < ZT_CLUSTER_TIMEOUT) ? 1 : 0;
-			s->x = m.x;
-			s->y = m.y;
-			s->z = m.z;
-			s->load = m.load;
-			s->peers = m.peers;
-			for(std::vector<InetAddress>::const_iterator ep(m.zeroTierPhysicalEndpoints.begin());ep!=m.zeroTierPhysicalEndpoints.end();++ep) {
-				if (s->numZeroTierPhysicalEndpoints >= ZT_CLUSTER_MAX_ZT_PHYSICAL_ADDRESSES) // sanity check
-					break;
-				memcpy(&(s->zeroTierPhysicalEndpoints[s->numZeroTierPhysicalEndpoints++]),&(*ep),sizeof(struct sockaddr_storage));
-			}
-		}
-	}
-}
-
-void Cluster::_send(uint16_t memberId,StateMessageType type,const void *msg,unsigned int len)
-{
-	if ((len + 3) > (ZT_CLUSTER_MAX_MESSAGE_LENGTH - (24 + 2 + 2))) // sanity check
-		return;
-	_Member &m = _members[memberId];
-	// assumes m.lock is locked!
-	if ((m.q.size() + len + 3) > ZT_CLUSTER_MAX_MESSAGE_LENGTH)
-		_flush(memberId);
-	m.q.append((uint16_t)(len + 1));
-	m.q.append((uint8_t)type);
-	m.q.append(msg,len);
-}
-
-void Cluster::_flush(uint16_t memberId)
-{
-	_Member &m = _members[memberId];
-	// assumes m.lock is locked!
-	if (m.q.size() > (24 + 2 + 2)) { // 16-byte IV + 8-byte MAC + 2 byte from-member-ID + 2 byte to-member-ID
-		// Create key from member's key and IV
-		char keytmp[32];
-		memcpy(keytmp,m.key,32);
-		for(int i=0;i<8;++i)
-			keytmp[i] ^= m.q[i];
-		Salsa20 s20(keytmp,m.q.field(8,8));
-		Utils::burn(keytmp,sizeof(keytmp));
-
-		// One-time-use Poly1305 key from first 32 bytes of Salsa20 keystream (as per DJB/NaCl "standard")
-		char polykey[ZT_POLY1305_KEY_LEN];
-		memset(polykey,0,sizeof(polykey));
-		s20.crypt12(polykey,polykey,sizeof(polykey));
-
-		// Encrypt m.q in place
-		s20.crypt12(reinterpret_cast<const char *>(m.q.data()) + 24,const_cast<char *>(reinterpret_cast<const char *>(m.q.data())) + 24,m.q.size() - 24);
-
-		// Add MAC for authentication (encrypt-then-MAC)
-		char mac[ZT_POLY1305_MAC_LEN];
-		Poly1305::compute(mac,reinterpret_cast<const char *>(m.q.data()) + 24,m.q.size() - 24,polykey);
-		memcpy(m.q.field(16,8),mac,8);
-
-		// Send!
-		_sendFunction(_sendFunctionArg,memberId,m.q.data(),m.q.size());
-
-		// Prepare for more
-		m.q.clear();
-		char iv[16];
-		Utils::getSecureRandom(iv,16);
-		m.q.append(iv,16);
-		m.q.addSize(8); // room for MAC
-		m.q.append((uint16_t)_id); // from member ID
-		m.q.append((uint16_t)memberId); // to member ID
-	}
-}
-
-void Cluster::_doREMOTE_WHOIS(uint64_t fromMemberId,const Packet &remotep)
-{
-	if (remotep.payloadLength() >= ZT_ADDRESS_LENGTH) {
-		Identity queried(RR->topology->getIdentity((void *)0,Address(remotep.payload(),ZT_ADDRESS_LENGTH)));
-		if (queried) {
-			Buffer<1024> routp;
-			remotep.source().appendTo(routp);
-			routp.append((uint8_t)Packet::VERB_OK);
-			routp.addSize(2); // space for length
-			routp.append((uint8_t)Packet::VERB_WHOIS);
-			routp.append(remotep.packetId());
-			queried.serialize(routp);
-			routp.setAt<uint16_t>(ZT_ADDRESS_LENGTH + 1,(uint16_t)(routp.size() - ZT_ADDRESS_LENGTH - 3));
-
-			TRACE("responding to remote WHOIS from %s @ %u with identity of %s",remotep.source().toString().c_str(),(unsigned int)fromMemberId,queried.address().toString().c_str());
-			Mutex::Lock _l2(_members[fromMemberId].lock);
-			_send(fromMemberId,CLUSTER_MESSAGE_PROXY_SEND,routp.data(),routp.size());
-		}
-	}
-}
-
-void Cluster::_doREMOTE_MULTICAST_GATHER(uint64_t fromMemberId,const Packet &remotep)
-{
-	const uint64_t nwid = remotep.at<uint64_t>(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_NETWORK_ID);
-	const MulticastGroup mg(MAC(remotep.field(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_MAC,6),6),remotep.at<uint32_t>(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_ADI));
-	unsigned int gatherLimit = remotep.at<uint32_t>(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_GATHER_LIMIT);
-	const Address remotePeerAddress(remotep.source());
-
-	if (gatherLimit) {
-		Buffer<ZT_PROTO_MAX_PACKET_LENGTH> routp;
-		remotePeerAddress.appendTo(routp);
-		routp.append((uint8_t)Packet::VERB_OK);
-		routp.addSize(2); // space for length
-		routp.append((uint8_t)Packet::VERB_MULTICAST_GATHER);
-		routp.append(remotep.packetId());
-		routp.append(nwid);
-		mg.mac().appendTo(routp);
-		routp.append((uint32_t)mg.adi());
-
-		if (gatherLimit > ((ZT_CLUSTER_MAX_MESSAGE_LENGTH - 80) / 5))
-			gatherLimit = ((ZT_CLUSTER_MAX_MESSAGE_LENGTH - 80) / 5);
-		if (RR->mc->gather(remotePeerAddress,nwid,mg,routp,gatherLimit)) {
-			routp.setAt<uint16_t>(ZT_ADDRESS_LENGTH + 1,(uint16_t)(routp.size() - ZT_ADDRESS_LENGTH - 3));
-
-			TRACE("responding to remote MULTICAST_GATHER from %s @ %u with %u bytes",remotePeerAddress.toString().c_str(),(unsigned int)fromMemberId,routp.size());
-			Mutex::Lock _l2(_members[fromMemberId].lock);
-			_send(fromMemberId,CLUSTER_MESSAGE_PROXY_SEND,routp.data(),routp.size());
-		}
-	}
-}
-
-} // namespace ZeroTier
-
-#endif // ZT_ENABLE_CLUSTER

+ 0 - 463
attic/Cluster.hpp

@@ -1,463 +0,0 @@
-/*
- * ZeroTier One - Network Virtualization Everywhere
- * Copyright (C) 2011-2017  ZeroTier, Inc.  https://www.zerotier.com/
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * --
- *
- * You can be released from the requirements of the license by purchasing
- * a commercial license. Buying such a license is mandatory as soon as you
- * develop commercial closed-source software that incorporates or links
- * directly against ZeroTier software without disclosing the source code
- * of your own application.
- */
-
-#ifndef ZT_CLUSTER_HPP
-#define ZT_CLUSTER_HPP
-
-#ifdef ZT_ENABLE_CLUSTER
-
-#include <map>
-
-#include "Constants.hpp"
-#include "../include/ZeroTierOne.h"
-#include "Address.hpp"
-#include "InetAddress.hpp"
-#include "SHA512.hpp"
-#include "Utils.hpp"
-#include "Buffer.hpp"
-#include "Mutex.hpp"
-#include "SharedPtr.hpp"
-#include "Hashtable.hpp"
-#include "Packet.hpp"
-#include "SharedPtr.hpp"
-
-/**
- * Timeout for cluster members being considered "alive"
- *
- * A cluster member is considered dead and will no longer have peers
- * redirected to it if we have not heard a heartbeat in this long.
- */
-#define ZT_CLUSTER_TIMEOUT 5000
-
-/**
- * Desired period between doPeriodicTasks() in milliseconds
- */
-#define ZT_CLUSTER_PERIODIC_TASK_PERIOD 20
-
-/**
- * How often to flush outgoing message queues (maximum interval)
- */
-#define ZT_CLUSTER_FLUSH_PERIOD ZT_CLUSTER_PERIODIC_TASK_PERIOD
-
-/**
- * Maximum number of queued outgoing packets per sender address
- */
-#define ZT_CLUSTER_MAX_QUEUE_PER_SENDER 16
-
-/**
- * Expiration time for send queue entries
- */
-#define ZT_CLUSTER_QUEUE_EXPIRATION 3000
-
-/**
- * Chunk size for allocating queue entries
- *
- * Queue entries are allocated in chunks of this many and are added to a pool.
- * ZT_CLUSTER_MAX_QUEUE_GLOBAL must be evenly divisible by this.
- */
-#define ZT_CLUSTER_QUEUE_CHUNK_SIZE 32
-
-/**
- * Maximum number of chunks to ever allocate
- *
- * This is a global sanity limit to prevent resource exhaustion attacks. It
- * works out to about 600mb of RAM. You'll never see this on a normal edge
- * node. We're unlikely to see this on a root server unless someone is DOSing
- * us. In that case cluster relaying will be affected but other functions
- * should continue to operate normally.
- */
-#define ZT_CLUSTER_MAX_QUEUE_CHUNKS 8194
-
-/**
- * Max data per queue entry
- */
-#define ZT_CLUSTER_SEND_QUEUE_DATA_MAX 1500
-
-/**
- * We won't send WANT_PEER to other members more than every (ms) per recipient
- */
-#define ZT_CLUSTER_WANT_PEER_EVERY 1000
-
-namespace ZeroTier {
-
-class RuntimeEnvironment;
-class MulticastGroup;
-class Peer;
-class Identity;
-
-// Internal class implemented inside Cluster.cpp
-class _ClusterSendQueue;
-
-/**
- * Multi-homing cluster state replication and packet relaying
- *
- * Multi-homing means more than one node sharing the same ZeroTier identity.
- * There is nothing in the protocol to prevent this, but to make it work well
- * requires the devices sharing an identity to cooperate and share some
- * information.
- *
- * There are three use cases we want to fulfill:
- *
- * (1) Multi-homing of root servers with handoff for efficient routing,
- *     HA, and load balancing across many commodity nodes.
- * (2) Multi-homing of network controllers for the same reason.
- * (3) Multi-homing of nodes on virtual networks, such as domain servers
- *     and other important endpoints.
- *
- * These use cases are in order of escalating difficulty. The initial
- * version of Cluster is aimed at satisfying the first, though you are
- * free to try #2 and #3.
- */
-class Cluster
-{
-public:
-	/**
-	 * State message types
-	 */
-	enum StateMessageType
-	{
-		CLUSTER_MESSAGE_NOP = 0,
-
-		/**
-		 * This cluster member is alive:
-		 *   <[2] version minor>
-		 *   <[2] version major>
-		 *   <[2] version revision>
-		 *   <[1] protocol version>
-		 *   <[4] X location (signed 32-bit)>
-		 *   <[4] Y location (signed 32-bit)>
-		 *   <[4] Z location (signed 32-bit)>
-		 *   <[8] local clock at this member>
-		 *   <[8] load average>
-		 *   <[8] number of peers>
-		 *   <[8] flags (currently unused, must be zero)>
-		 *   <[1] number of preferred ZeroTier endpoints>
-		 *   <[...] InetAddress(es) of preferred ZeroTier endpoint(s)>
-		 *
-		 * Cluster members constantly broadcast an alive heartbeat and will only
-		 * receive peer redirects if they've done so within the timeout.
-		 */
-		CLUSTER_MESSAGE_ALIVE = 1,
-
-		/**
-		 * Cluster member has this peer:
-		 *   <[...] serialized identity of peer>
-		 *
-		 * This is typically sent in response to WANT_PEER but can also be pushed
-		 * to prepopulate if this makes sense.
-		 */
-		CLUSTER_MESSAGE_HAVE_PEER = 2,
-
-		/**
-		 * Cluster member wants this peer:
-		 *   <[5] ZeroTier address of peer>
-		 *
-		 * Members that have a direct link to this peer will respond with
-		 * HAVE_PEER.
-		 */
-		CLUSTER_MESSAGE_WANT_PEER = 3,
-
-		/**
-		 * A remote packet that we should also possibly respond to:
-		 *   <[2] 16-bit length of remote packet>
-		 *   <[...] remote packet payload>
-		 *
-		 * Cluster members may relay requests by relaying the request packet.
-		 * These may include requests such as WHOIS and MULTICAST_GATHER. The
-		 * packet must be already decrypted, decompressed, and authenticated.
-		 *
-		 * This can only be used for small request packets as per the cluster
-		 * message size limit, but since these are the only ones in question
-		 * this is fine.
-		 *
-		 * If a response is generated it is sent via PROXY_SEND.
-		 */
-		CLUSTER_MESSAGE_REMOTE_PACKET = 4,
-
-		/**
-		 * Request that VERB_RENDEZVOUS be sent to a peer that we have:
-		 *   <[5] ZeroTier address of peer on recipient's side>
-		 *   <[5] ZeroTier address of peer on sender's side>
-		 *   <[1] 8-bit number of sender's peer's active path addresses>
-		 *   <[...] series of serialized InetAddresses of sender's peer's paths>
-		 *
-		 * This requests that we perform NAT-t introduction between a peer that
-		 * we have and one on the sender's side. The sender furnishes contact
-		 * info for its peer, and we send VERB_RENDEZVOUS to both sides: to ours
-		 * directly and with PROXY_SEND to theirs.
-		 */
-		CLUSTER_MESSAGE_PROXY_UNITE = 5,
-
-		/**
-		 * Request that a cluster member send a packet to a locally-known peer:
-		 *   <[5] ZeroTier address of recipient>
-		 *   <[1] packet verb>
-		 *   <[2] length of packet payload>
-		 *   <[...] packet payload>
-		 *
-		 * This differs from RELAY in that it requests the receiving cluster
-		 * member to actually compose a ZeroTier Packet from itself to the
-		 * provided recipient. RELAY simply says "please forward this blob."
-		 * RELAY is used to implement peer-to-peer relaying with RENDEZVOUS,
-		 * while PROXY_SEND is used to implement proxy sending (which right
-		 * now is only used to send RENDEZVOUS).
-		 */
-		CLUSTER_MESSAGE_PROXY_SEND = 6,
-
-		/**
-		 * Replicate a network config for a network we belong to:
-		 *   <[...] network config chunk>
-		 *
-		 * This is used by clusters to avoid every member having to query
-		 * for the same netconf for networks all members belong to.
-		 *
-		 * The first field of a network config chunk is the network ID,
-		 * so this can be checked to look up the network on receipt.
-		 */
-		CLUSTER_MESSAGE_NETWORK_CONFIG = 7
-	};
-
-	/**
-	 * Construct a new cluster
-	 */
-	Cluster(
-		const RuntimeEnvironment *renv,
-		uint16_t id,
-		const std::vector<InetAddress> &zeroTierPhysicalEndpoints,
-		int32_t x,
-		int32_t y,
-		int32_t z,
-		void (*sendFunction)(void *,unsigned int,const void *,unsigned int),
-		void *sendFunctionArg,
-		int (*addressToLocationFunction)(void *,const struct sockaddr_storage *,int *,int *,int *),
-		void *addressToLocationFunctionArg);
-
-	~Cluster();
-
-	/**
-	 * @return This cluster member's ID
-	 */
-	inline uint16_t id() const throw() { return _id; }
-
-	/**
-	 * Handle an incoming intra-cluster message
-	 *
-	 * @param data Message data
-	 * @param len Message length (max: ZT_CLUSTER_MAX_MESSAGE_LENGTH)
-	 */
-	void handleIncomingStateMessage(const void *msg,unsigned int len);
-
-	/**
-	 * Broadcast that we have a given peer
-	 *
-	 * This should be done when new peers are first contacted.
-	 *
-	 * @param id Identity of peer
-	 */
-	void broadcastHavePeer(const Identity &id);
-
-	/**
-	 * Broadcast a network config chunk to other members of cluster
-	 *
-	 * @param chunk Chunk data
-	 * @param len Length of chunk
-	 */
-	void broadcastNetworkConfigChunk(const void *chunk,unsigned int len);
-
-	/**
-	 * If the cluster has this peer, prepare the packet to send via cluster
-	 *
-	 * Note that outp is only armored (or modified at all) if the return value is a member ID.
-	 *
-	 * @param toPeerAddress Value of outp.destination(), simply to save additional lookup
-	 * @param ts Result: set to time of last HAVE_PEER from the cluster
-	 * @param peerSecret Result: Buffer to fill with peer secret on valid return value, must be at least ZT_PEER_SECRET_KEY_LENGTH bytes
-	 * @return -1 if cluster does not know this peer, or a member ID to pass to sendViaCluster()
-	 */
-	int checkSendViaCluster(const Address &toPeerAddress,uint64_t &mostRecentTs,void *peerSecret);
-
-	/**
-	 * Send data via cluster front plane (packet head or fragment)
-	 *
-	 * @param haveMemberId Member ID that has this peer as returned by prepSendviaCluster()
-	 * @param toPeerAddress Destination peer address
-	 * @param data Packet or packet fragment data
-	 * @param len Length of packet or fragment
-	 * @return True if packet was sent (and outp was modified via armoring)
-	 */
-	bool sendViaCluster(int haveMemberId,const Address &toPeerAddress,const void *data,unsigned int len);
-
-	/**
-	 * Relay a packet via the cluster
-	 *
-	 * This is used in the outgoing packet and relaying logic in Switch to
-	 * relay packets to other cluster members. It isn't PROXY_SEND-- that is
-	 * used internally in Cluster to send responses to peer queries.
-	 *
-	 * @param fromPeerAddress Source peer address (if known, should be NULL for fragments)
-	 * @param toPeerAddress Destination peer address
-	 * @param data Packet or packet fragment data
-	 * @param len Length of packet or fragment
-	 * @param unite If true, also request proxy unite across cluster
-	 */
-	void relayViaCluster(const Address &fromPeerAddress,const Address &toPeerAddress,const void *data,unsigned int len,bool unite);
-
-	/**
-	 * Send a distributed query to other cluster members
-	 *
-	 * Some queries such as WHOIS or MULTICAST_GATHER need a response from other
-	 * cluster members. Replies (if any) will be sent back to the peer via
-	 * PROXY_SEND across the cluster.
-	 *
-	 * @param pkt Packet to distribute
-	 */
-	void sendDistributedQuery(const Packet &pkt);
-
-	/**
-	 * Call every ~ZT_CLUSTER_PERIODIC_TASK_PERIOD milliseconds.
-	 */
-	void doPeriodicTasks();
-
-	/**
-	 * Add a member ID to this cluster
-	 *
-	 * @param memberId Member ID
-	 */
-	void addMember(uint16_t memberId);
-
-	/**
-	 * Remove a member ID from this cluster
-	 *
-	 * @param memberId Member ID to remove
-	 */
-	void removeMember(uint16_t memberId);
-
-	/**
-	 * Find a better cluster endpoint for this peer (if any)
-	 *
-	 * @param redirectTo InetAddress to be set to a better endpoint (if there is one)
-	 * @param peerAddress Address of peer to (possibly) redirect
-	 * @param peerPhysicalAddress Physical address of peer's current best path (where packet was most recently received or getBestPath()->address())
-	 * @param offload Always redirect if possible -- can be used to offload peers during shutdown
-	 * @return True if redirectTo was set to a new address, false if redirectTo was not modified
-	 */
-	bool findBetterEndpoint(InetAddress &redirectTo,const Address &peerAddress,const InetAddress &peerPhysicalAddress,bool offload);
-
-	/**
-	 * @param ip Address to check
-	 * @return True if this is a cluster frontplane address (excluding our addresses)
-	 */
-	bool isClusterPeerFrontplane(const InetAddress &ip) const;
-
-	/**
-	 * Fill out ZT_ClusterStatus structure (from core API)
-	 *
-	 * @param status Reference to structure to hold result (anything there is replaced)
-	 */
-	void status(ZT_ClusterStatus &status) const;
-
-private:
-	void _send(uint16_t memberId,StateMessageType type,const void *msg,unsigned int len);
-	void _flush(uint16_t memberId);
-
-	void _doREMOTE_WHOIS(uint64_t fromMemberId,const Packet &remotep);
-	void _doREMOTE_MULTICAST_GATHER(uint64_t fromMemberId,const Packet &remotep);
-
-	// These are initialized in the constructor and remain immutable ------------
-	uint16_t _masterSecret[ZT_SHA512_DIGEST_LEN / sizeof(uint16_t)];
-	unsigned char _key[ZT_PEER_SECRET_KEY_LENGTH];
-	const RuntimeEnvironment *RR;
-	_ClusterSendQueue *const _sendQueue;
-	void (*_sendFunction)(void *,unsigned int,const void *,unsigned int);
-	void *_sendFunctionArg;
-	int (*_addressToLocationFunction)(void *,const struct sockaddr_storage *,int *,int *,int *);
-	void *_addressToLocationFunctionArg;
-	const int32_t _x;
-	const int32_t _y;
-	const int32_t _z;
-	const uint16_t _id;
-	const std::vector<InetAddress> _zeroTierPhysicalEndpoints;
-	// end immutable fields -----------------------------------------------------
-
-	struct _Member
-	{
-		unsigned char key[ZT_PEER_SECRET_KEY_LENGTH];
-
-		uint64_t lastReceivedAliveAnnouncement;
-		uint64_t lastAnnouncedAliveTo;
-
-		uint64_t load;
-		uint64_t peers;
-		int32_t x,y,z;
-
-		std::vector<InetAddress> zeroTierPhysicalEndpoints;
-
-		Buffer<ZT_CLUSTER_MAX_MESSAGE_LENGTH> q;
-
-		Mutex lock;
-
-		inline void clear()
-		{
-			lastReceivedAliveAnnouncement = 0;
-			lastAnnouncedAliveTo = 0;
-			load = 0;
-			peers = 0;
-			x = 0;
-			y = 0;
-			z = 0;
-			zeroTierPhysicalEndpoints.clear();
-			q.clear();
-		}
-
-		_Member() { this->clear(); }
-		~_Member() { Utils::burn(key,sizeof(key)); }
-	};
-	_Member *const _members;
-
-	std::vector<uint16_t> _memberIds;
-	Mutex _memberIds_m;
-
-	struct _RemotePeer
-	{
-		_RemotePeer() : lastHavePeerReceived(0),lastSentWantPeer(0) {}
-		~_RemotePeer() { Utils::burn(key,ZT_PEER_SECRET_KEY_LENGTH); }
-		uint64_t lastHavePeerReceived;
-		uint64_t lastSentWantPeer;
-		uint8_t key[ZT_PEER_SECRET_KEY_LENGTH]; // secret key from identity agreement
-	};
-	std::map< std::pair<Address,unsigned int>,_RemotePeer > _remotePeers; // we need ordered behavior and lower_bound here
-	Mutex _remotePeers_m;
-
-	uint64_t _lastFlushed;
-	uint64_t _lastCleanedRemotePeers;
-	uint64_t _lastCleanedQueue;
-};
-
-} // namespace ZeroTier
-
-#endif // ZT_ENABLE_CLUSTER
-
-#endif

+ 0 - 168
attic/ClusterDefinition.hpp

@@ -1,168 +0,0 @@
-/*
- * ZeroTier One - Network Virtualization Everywhere
- * Copyright (C) 2011-2017  ZeroTier, Inc.  https://www.zerotier.com/
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * --
- *
- * You can be released from the requirements of the license by purchasing
- * a commercial license. Buying such a license is mandatory as soon as you
- * develop commercial closed-source software that incorporates or links
- * directly against ZeroTier software without disclosing the source code
- * of your own application.
- */
-
-#ifndef ZT_CLUSTERDEFINITION_HPP
-#define ZT_CLUSTERDEFINITION_HPP
-
-#ifdef ZT_ENABLE_CLUSTER
-
-#include <vector>
-#include <algorithm>
-
-#include "../node/Constants.hpp"
-#include "../node/Utils.hpp"
-#include "../node/NonCopyable.hpp"
-#include "../osdep/OSUtils.hpp"
-
-#include "ClusterGeoIpService.hpp"
-
-namespace ZeroTier {
-
-/**
- * Parser for cluster definition file
- */
-class ClusterDefinition : NonCopyable
-{
-public:
-	struct MemberDefinition
-	{
-		MemberDefinition() : id(0),x(0),y(0),z(0) { name[0] = (char)0; }
-
-		unsigned int id;
-		int x,y,z;
-		char name[256];
-		InetAddress clusterEndpoint;
-		std::vector<InetAddress> zeroTierEndpoints;
-	};
-
-	/**
-	 * Load and initialize cluster definition and GeoIP data if any
-	 *
-	 * @param myAddress My ZeroTier address
-	 * @param pathToClusterFile Path to cluster definition file
-	 * @throws std::runtime_error Invalid cluster definition or unable to load data
-	 */
-	ClusterDefinition(uint64_t myAddress,const char *pathToClusterFile)
-	{
-		std::string cf;
-		if (!OSUtils::readFile(pathToClusterFile,cf))
-			return;
-
-		char myAddressStr[64];
-		Utils::ztsnprintf(myAddressStr,sizeof(myAddressStr),"%.10llx",myAddress);
-
-		std::vector<std::string> lines(OSUtils::split(cf.c_str(),"\r\n","",""));
-		for(std::vector<std::string>::iterator l(lines.begin());l!=lines.end();++l) {
-			std::vector<std::string> fields(OSUtils::split(l->c_str()," \t","",""));
-			if ((fields.size() < 5)||(fields[0][0] == '#')||(fields[0] != myAddressStr))
-				continue;
-
-			// <address> geo <CSV path> <ip start column> <ip end column> <latitutde column> <longitude column>
-			if (fields[1] == "geo") {
-				if ((fields.size() >= 7)&&(OSUtils::fileExists(fields[2].c_str()))) {
-					int ipStartColumn = Utils::strToInt(fields[3].c_str());
-					int ipEndColumn = Utils::strToInt(fields[4].c_str());
-					int latitudeColumn = Utils::strToInt(fields[5].c_str());
-					int longitudeColumn = Utils::strToInt(fields[6].c_str());
-					if (_geo.load(fields[2].c_str(),ipStartColumn,ipEndColumn,latitudeColumn,longitudeColumn) <= 0)
-						throw std::runtime_error(std::string("failed to load geo-ip data from ")+fields[2]);
-				}
-				continue;
-			}
-
-			// <address> <ID> <name> <backplane IP/port(s)> <ZT frontplane IP/port(s)> <x,y,z>
-			int id = Utils::strToUInt(fields[1].c_str());
-			if ((id < 0)||(id > ZT_CLUSTER_MAX_MEMBERS))
-				throw std::runtime_error(std::string("invalid cluster member ID: ")+fields[1]);
-			MemberDefinition &md = _md[id];
-
-			md.id = (unsigned int)id;
-			if (fields.size() >= 6) {
-				std::vector<std::string> xyz(OSUtils::split(fields[5].c_str(),",","",""));
-				md.x = (xyz.size() > 0) ? Utils::strToInt(xyz[0].c_str()) : 0;
-				md.y = (xyz.size() > 1) ? Utils::strToInt(xyz[1].c_str()) : 0;
-				md.z = (xyz.size() > 2) ? Utils::strToInt(xyz[2].c_str()) : 0;
-			}
-			Utils::scopy(md.name,sizeof(md.name),fields[2].c_str());
-			md.clusterEndpoint.fromString(fields[3]);
-			if (!md.clusterEndpoint)
-				continue;
-			std::vector<std::string> zips(OSUtils::split(fields[4].c_str(),",","",""));
-			for(std::vector<std::string>::iterator zip(zips.begin());zip!=zips.end();++zip) {
-				InetAddress i;
-				i.fromString(*zip);
-				if (i)
-					md.zeroTierEndpoints.push_back(i);
-			}
-
-			_ids.push_back((unsigned int)id);
-		}
-
-		std::sort(_ids.begin(),_ids.end());
-	}
-
-	/**
-	 * @return All member definitions in this cluster by ID (ID is array index)
-	 */
-	inline const MemberDefinition &operator[](unsigned int id) const throw() { return _md[id]; }
-
-	/**
-	 * @return Number of members in this cluster
-	 */
-	inline unsigned int size() const throw() { return (unsigned int)_ids.size(); }
-
-	/**
-	 * @return IDs of members in this cluster sorted by ID
-	 */
-	inline const std::vector<unsigned int> &ids() const throw() { return _ids; }
-
-	/**
-	 * @return GeoIP service for this cluster
-	 */
-	inline ClusterGeoIpService &geo() throw() { return _geo; }
-
-	/**
-	 * @return A vector (new copy) containing all cluster members
-	 */
-	inline std::vector<MemberDefinition> members() const
-	{
-		std::vector<MemberDefinition> m;
-		for(std::vector<unsigned int>::const_iterator i(_ids.begin());i!=_ids.end();++i)
-			m.push_back(_md[*i]);
-		return m;
-	}
-
-private:
-	MemberDefinition _md[ZT_CLUSTER_MAX_MEMBERS];
-	std::vector<unsigned int> _ids;
-	ClusterGeoIpService _geo;
-};
-
-} // namespace ZeroTier
-
-#endif // ZT_ENABLE_CLUSTER
-
-#endif

+ 0 - 243
attic/ClusterGeoIpService.cpp

@@ -1,243 +0,0 @@
-/*
- * ZeroTier One - Network Virtualization Everywhere
- * Copyright (C) 2011-2017  ZeroTier, Inc.  https://www.zerotier.com/
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * --
- *
- * You can be released from the requirements of the license by purchasing
- * a commercial license. Buying such a license is mandatory as soon as you
- * develop commercial closed-source software that incorporates or links
- * directly against ZeroTier software without disclosing the source code
- * of your own application.
- */
-
-#ifdef ZT_ENABLE_CLUSTER
-
-#include <math.h>
-
-#include <cmath>
-
-#include "ClusterGeoIpService.hpp"
-
-#include "../node/Utils.hpp"
-#include "../osdep/OSUtils.hpp"
-
-#define ZT_CLUSTERGEOIPSERVICE_FILE_MODIFICATION_CHECK_EVERY 10000
-
-namespace ZeroTier {
-
-ClusterGeoIpService::ClusterGeoIpService() :
-	_pathToCsv(),
-	_ipStartColumn(-1),
-	_ipEndColumn(-1),
-	_latitudeColumn(-1),
-	_longitudeColumn(-1),
-	_lastFileCheckTime(0),
-	_csvModificationTime(0),
-	_csvFileSize(0)
-{
-}
-
-ClusterGeoIpService::~ClusterGeoIpService()
-{
-}
-
-bool ClusterGeoIpService::locate(const InetAddress &ip,int &x,int &y,int &z)
-{
-	Mutex::Lock _l(_lock);
-
-	if ((_pathToCsv.length() > 0)&&((OSUtils::now() - _lastFileCheckTime) > ZT_CLUSTERGEOIPSERVICE_FILE_MODIFICATION_CHECK_EVERY)) {
-		_lastFileCheckTime = OSUtils::now();
-		if ((_csvFileSize != OSUtils::getFileSize(_pathToCsv.c_str()))||(_csvModificationTime != OSUtils::getLastModified(_pathToCsv.c_str())))
-			_load(_pathToCsv.c_str(),_ipStartColumn,_ipEndColumn,_latitudeColumn,_longitudeColumn);
-	}
-
-	/* We search by looking up the upper bound of the sorted vXdb vectors
-	 * and then iterating down for a matching IP range. We stop when we hit
-	 * the beginning or an entry whose start and end are before the IP we
-	 * are searching. */
-
-	if ((ip.ss_family == AF_INET)&&(_v4db.size() > 0)) {
-		_V4E key;
-		key.start = Utils::ntoh((uint32_t)(reinterpret_cast<const struct sockaddr_in *>(&ip)->sin_addr.s_addr));
-		std::vector<_V4E>::const_iterator i(std::upper_bound(_v4db.begin(),_v4db.end(),key));
-		while (i != _v4db.begin()) {
-			--i;
-			if ((key.start >= i->start)&&(key.start <= i->end)) {
-				x = i->x;
-				y = i->y;
-				z = i->z;
-				//printf("%s : %f,%f %d,%d,%d\n",ip.toIpString().c_str(),i->lat,i->lon,x,y,z);
-				return true;
-			} else if ((key.start > i->start)&&(key.start > i->end))
-				break;
-		}
-	} else if ((ip.ss_family == AF_INET6)&&(_v6db.size() > 0)) {
-		_V6E key;
-		memcpy(key.start,reinterpret_cast<const struct sockaddr_in6 *>(&ip)->sin6_addr.s6_addr,16);
-		std::vector<_V6E>::const_iterator i(std::upper_bound(_v6db.begin(),_v6db.end(),key));
-		while (i != _v6db.begin()) {
-			--i;
-			const int s_vs_s = memcmp(key.start,i->start,16);
-			const int s_vs_e = memcmp(key.start,i->end,16);
-			if ((s_vs_s >= 0)&&(s_vs_e <= 0)) {
-				x = i->x;
-				y = i->y;
-				z = i->z;
-				//printf("%s : %f,%f %d,%d,%d\n",ip.toIpString().c_str(),i->lat,i->lon,x,y,z);
-				return true;
-			} else if ((s_vs_s > 0)&&(s_vs_e > 0))
-				break;
-		}
-	}
-
-	return false;
-}
-
-void ClusterGeoIpService::_parseLine(const char *line,std::vector<_V4E> &v4db,std::vector<_V6E> &v6db,int ipStartColumn,int ipEndColumn,int latitudeColumn,int longitudeColumn)
-{
-	std::vector<std::string> ls(OSUtils::split(line,",\t","\\","\"'"));
-	if ( ((ipStartColumn >= 0)&&(ipStartColumn < (int)ls.size()))&&
-	     ((ipEndColumn >= 0)&&(ipEndColumn < (int)ls.size()))&&
-	     ((latitudeColumn >= 0)&&(latitudeColumn < (int)ls.size()))&&
-	     ((longitudeColumn >= 0)&&(longitudeColumn < (int)ls.size())) ) {
-		InetAddress ipStart(ls[ipStartColumn].c_str(),0);
-		InetAddress ipEnd(ls[ipEndColumn].c_str(),0);
-		const double lat = strtod(ls[latitudeColumn].c_str(),(char **)0);
-		const double lon = strtod(ls[longitudeColumn].c_str(),(char **)0);
-
-		if ((ipStart.ss_family == ipEnd.ss_family)&&(ipStart)&&(ipEnd)&&(std::isfinite(lat))&&(std::isfinite(lon))) {
-			const double latRadians = lat * 0.01745329251994; // PI / 180
-			const double lonRadians = lon * 0.01745329251994; // PI / 180
-			const double cosLat = cos(latRadians);
-			const int x = (int)round((-6371.0) * cosLat * cos(lonRadians)); // 6371 == Earth's approximate radius in kilometers
-			const int y = (int)round(6371.0 * sin(latRadians));
-			const int z = (int)round(6371.0 * cosLat * sin(lonRadians));
-
-			if (ipStart.ss_family == AF_INET) {
-				v4db.push_back(_V4E());
-				v4db.back().start = Utils::ntoh((uint32_t)(reinterpret_cast<const struct sockaddr_in *>(&ipStart)->sin_addr.s_addr));
-				v4db.back().end = Utils::ntoh((uint32_t)(reinterpret_cast<const struct sockaddr_in *>(&ipEnd)->sin_addr.s_addr));
-				v4db.back().lat = (float)lat;
-				v4db.back().lon = (float)lon;
-				v4db.back().x = x;
-				v4db.back().y = y;
-				v4db.back().z = z;
-				//printf("%s - %s : %d,%d,%d\n",ipStart.toIpString().c_str(),ipEnd.toIpString().c_str(),x,y,z);
-			} else if (ipStart.ss_family == AF_INET6) {
-				v6db.push_back(_V6E());
-				memcpy(v6db.back().start,reinterpret_cast<const struct sockaddr_in6 *>(&ipStart)->sin6_addr.s6_addr,16);
-				memcpy(v6db.back().end,reinterpret_cast<const struct sockaddr_in6 *>(&ipEnd)->sin6_addr.s6_addr,16);
-				v6db.back().lat = (float)lat;
-				v6db.back().lon = (float)lon;
-				v6db.back().x = x;
-				v6db.back().y = y;
-				v6db.back().z = z;
-				//printf("%s - %s : %d,%d,%d\n",ipStart.toIpString().c_str(),ipEnd.toIpString().c_str(),x,y,z);
-			}
-		}
-	}
-}
-
-long ClusterGeoIpService::_load(const char *pathToCsv,int ipStartColumn,int ipEndColumn,int latitudeColumn,int longitudeColumn)
-{
-	// assumes _lock is locked
-
-	FILE *f = fopen(pathToCsv,"rb");
-	if (!f)
-		return -1;
-
-	std::vector<_V4E> v4db;
-	std::vector<_V6E> v6db;
-	v4db.reserve(16777216);
-	v6db.reserve(16777216);
-
-	char buf[4096];
-	char linebuf[1024];
-	unsigned int lineptr = 0;
-	for(;;) {
-		int n = (int)fread(buf,1,sizeof(buf),f);
-		if (n <= 0)
-			break;
-		for(int i=0;i<n;++i) {
-			if ((buf[i] == '\r')||(buf[i] == '\n')||(buf[i] == (char)0)) {
-				if (lineptr) {
-					linebuf[lineptr] = (char)0;
-					_parseLine(linebuf,v4db,v6db,ipStartColumn,ipEndColumn,latitudeColumn,longitudeColumn);
-				}
-				lineptr = 0;
-			} else if (lineptr < (unsigned int)sizeof(linebuf))
-				linebuf[lineptr++] = buf[i];
-		}
-	}
-	if (lineptr) {
-		linebuf[lineptr] = (char)0;
-		_parseLine(linebuf,v4db,v6db,ipStartColumn,ipEndColumn,latitudeColumn,longitudeColumn);
-	}
-
-	fclose(f);
-
-	if ((v4db.size() > 0)||(v6db.size() > 0)) {
-		std::sort(v4db.begin(),v4db.end());
-		std::sort(v6db.begin(),v6db.end());
-
-		_pathToCsv = pathToCsv;
-		_ipStartColumn = ipStartColumn;
-		_ipEndColumn = ipEndColumn;
-		_latitudeColumn = latitudeColumn;
-		_longitudeColumn = longitudeColumn;
-
-		_lastFileCheckTime = OSUtils::now();
-		_csvModificationTime = OSUtils::getLastModified(pathToCsv);
-		_csvFileSize = OSUtils::getFileSize(pathToCsv);
-
-		_v4db.swap(v4db);
-		_v6db.swap(v6db);
-
-		return (long)(_v4db.size() + _v6db.size());
-	} else {
-		return 0;
-	}
-}
-
-} // namespace ZeroTier
-
-#endif // ZT_ENABLE_CLUSTER
-
-/*
-int main(int argc,char **argv)
-{
-	char buf[1024];
-
-	ZeroTier::ClusterGeoIpService gip;
-	printf("loading...\n");
-	gip.load("/Users/api/Code/ZeroTier/Infrastructure/root-servers/zerotier-one/cluster-geoip.csv",0,1,5,6);
-	printf("... done!\n"); fflush(stdout);
-
-	while (gets(buf)) { // unsafe, testing only
-		ZeroTier::InetAddress addr(buf,0);
-		printf("looking up: %s\n",addr.toString().c_str()); fflush(stdout);
-		int x = 0,y = 0,z = 0;
-		if (gip.locate(addr,x,y,z)) {
-			//printf("%s: %d,%d,%d\n",addr.toString().c_str(),x,y,z); fflush(stdout);
-		} else {
-			printf("%s: not found!\n",addr.toString().c_str()); fflush(stdout);
-		}
-	}
-
-	return 0;
-}
-*/

+ 0 - 151
attic/ClusterGeoIpService.hpp

@@ -1,151 +0,0 @@
-/*
- * ZeroTier One - Network Virtualization Everywhere
- * Copyright (C) 2011-2017  ZeroTier, Inc.  https://www.zerotier.com/
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * --
- *
- * You can be released from the requirements of the license by purchasing
- * a commercial license. Buying such a license is mandatory as soon as you
- * develop commercial closed-source software that incorporates or links
- * directly against ZeroTier software without disclosing the source code
- * of your own application.
- */
-
-#ifndef ZT_CLUSTERGEOIPSERVICE_HPP
-#define ZT_CLUSTERGEOIPSERVICE_HPP
-
-#ifdef ZT_ENABLE_CLUSTER
-
-#include <stdint.h>
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-
-#include <vector>
-#include <string>
-#include <algorithm>
-
-#include "../node/Constants.hpp"
-#include "../node/Mutex.hpp"
-#include "../node/NonCopyable.hpp"
-#include "../node/InetAddress.hpp"
-
-namespace ZeroTier {
-
-/**
- * Loads a GeoIP CSV into memory for fast lookup, reloading as needed
- *
- * This was designed around the CSV from https://db-ip.com but can be used
- * with any similar GeoIP CSV database that is presented in the form of an
- * IP range and lat/long coordinates.
- *
- * It loads the whole database into memory, which can be kind of large. If
- * the CSV file changes, the changes are loaded automatically.
- */
-class ClusterGeoIpService : NonCopyable
-{
-public:
-	ClusterGeoIpService();
-	~ClusterGeoIpService();
-
-	/**
-	 * Load or reload CSV file
-	 *
-	 * CSV column indexes start at zero. CSVs can be quoted with single or
-	 * double quotes. Whitespace before or after commas is ignored. Backslash
-	 * may be used for escaping whitespace as well.
-	 *
-	 * @param pathToCsv Path to (uncompressed) CSV file
-	 * @param ipStartColumn Column with IP range start
-	 * @param ipEndColumn Column with IP range end (inclusive)
-	 * @param latitudeColumn Column with latitude
-	 * @param longitudeColumn Column with longitude
-	 * @return Number of valid records loaded or -1 on error (invalid file, not found, etc.)
-	 */
-	inline long load(const char *pathToCsv,int ipStartColumn,int ipEndColumn,int latitudeColumn,int longitudeColumn)
-	{
-		Mutex::Lock _l(_lock);
-		return _load(pathToCsv,ipStartColumn,ipEndColumn,latitudeColumn,longitudeColumn);
-	}
-
-	/**
-	 * Attempt to locate an IP
-	 *
-	 * This returns true if x, y, and z are set. If the return value is false
-	 * the values of x, y, and z are undefined.
-	 *
-	 * @param ip IPv4 or IPv6 address
-	 * @param x Reference to variable to receive X
-	 * @param y Reference to variable to receive Y
-	 * @param z Reference to variable to receive Z
-	 * @return True if coordinates were set
-	 */
-	bool locate(const InetAddress &ip,int &x,int &y,int &z);
-
-	/**
-	 * @return True if IP database/service is available for queries (otherwise locate() will always be false)
-	 */
-	inline bool available() const
-	{
-		Mutex::Lock _l(_lock);
-		return ((_v4db.size() + _v6db.size()) > 0);
-	}
-
-private:
-	struct _V4E
-	{
-		uint32_t start;
-		uint32_t end;
-		float lat,lon;
-		int16_t x,y,z;
-
-		inline bool operator<(const _V4E &e) const { return (start < e.start); }
-	};
-
-	struct _V6E
-	{
-		uint8_t start[16];
-		uint8_t end[16];
-		float lat,lon;
-		int16_t x,y,z;
-
-		inline bool operator<(const _V6E &e) const { return (memcmp(start,e.start,16) < 0); }
-	};
-
-	static void _parseLine(const char *line,std::vector<_V4E> &v4db,std::vector<_V6E> &v6db,int ipStartColumn,int ipEndColumn,int latitudeColumn,int longitudeColumn);
-	long _load(const char *pathToCsv,int ipStartColumn,int ipEndColumn,int latitudeColumn,int longitudeColumn);
-
-	std::string _pathToCsv;
-	int _ipStartColumn;
-	int _ipEndColumn;
-	int _latitudeColumn;
-	int _longitudeColumn;
-
-	uint64_t _lastFileCheckTime;
-	uint64_t _csvModificationTime;
-	int64_t _csvFileSize;
-
-	std::vector<_V4E> _v4db;
-	std::vector<_V6E> _v6db;
-
-	Mutex _lock;
-};
-
-} // namespace ZeroTier
-
-#endif // ZT_ENABLE_CLUSTER
-
-#endif

+ 0 - 101
attic/FCV.hpp

@@ -1,101 +0,0 @@
-/*
- * ZeroTier One - Network Virtualization Everywhere
- * Copyright (C) 2011-2018  ZeroTier, Inc.  https://www.zerotier.com/
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * --
- *
- * You can be released from the requirements of the license by purchasing
- * a commercial license. Buying such a license is mandatory as soon as you
- * develop commercial closed-source software that incorporates or links
- * directly against ZeroTier software without disclosing the source code
- * of your own application.
- */
-
-#include "Constants.hpp"
-
-namespace ZeroTier {
-
-/**
- * A really simple fixed capacity vector
- *
- * This class does no bounds checking, so the user must ensure that
- * no more than C elements are ever added and that accesses are in
- * bounds.
- *
- * @tparam T Type to contain
- * @tparam C Capacity of vector
- */
-template<typename T,unsigned long C>
-class FCV
-{
-public:
-	FCV() : _s(0) {}
-	~FCV() { clear(); }
-
-	FCV(const FCV &v) :
-		_s(v._s)
-	{
-		for(unsigned long i=0;i<_s;++i) {
-			new (reinterpret_cast<T *>(_mem + (sizeof(T) * i))) T(reinterpret_cast<const T *>(v._mem)[i]);
-		}
-	}
-
-	inline FCV &operator=(const FCV &v)
-	{
-		clear();
-		_s = v._s;
-		for(unsigned long i=0;i<_s;++i) {
-			new (reinterpret_cast<T *>(_mem + (sizeof(T) * i))) T(reinterpret_cast<const T *>(v._mem)[i]);
-		}
-		return *this;
-	}
-
-	typedef T * iterator;
-	typedef const T * const_iterator;
-	typedef unsigned long size_type;
-
-	inline iterator begin() { return (T *)_mem; }
-	inline iterator end() { return (T *)(_mem + (sizeof(T) * _s)); }
-	inline iterator begin() const { return (const T *)_mem; }
-	inline iterator end() const { return (const T *)(_mem + (sizeof(T) * _s)); }
-
-	inline T &operator[](const size_type i) { return reinterpret_cast<T *>(_mem)[i]; }
-	inline const T &operator[](const size_type i) const { return reinterpret_cast<const T *>(_mem)[i]; }
-
-	inline T &front() { return reinterpret_cast<T *>(_mem)[0]; }
-	inline const T &front() const { return reinterpret_cast<const T *>(_mem)[0]; }
-	inline T &back() { return reinterpret_cast<T *>(_mem)[_s - 1]; }
-	inline const T &back() const { return reinterpret_cast<const T *>(_mem)[_s - 1]; }
-
-	inline void push_back(const T &v) { new (reinterpret_cast<T *>(_mem + (sizeof(T) * _s++))) T(v); }
-	inline void pop_back() { reinterpret_cast<T *>(_mem + (sizeof(T) * --_s))->~T(); }
-
-	inline size_type size() const { return _s; }
-	inline size_type capacity() const { return C; }
-
-	inline void clear()
-	{
-		for(unsigned long i=0;i<_s;++i)
-			reinterpret_cast<T *>(_mem + (sizeof(T) * i))->~T();
-		_s = 0;
-	}
-
-private:
-	char _mem[sizeof(T) * C];
-	unsigned long _s;
-};
-
-} // namespace ZeroTier

+ 0 - 650
attic/OSXEthernetTap.cpp.pcap-with-bridge-test

@@ -1,650 +0,0 @@
-/*
- * ZeroTier One - Network Virtualization Everywhere
- * Copyright (C) 2011-2015  ZeroTier, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * --
- *
- * ZeroTier may be used and distributed under the terms of the GPLv3, which
- * are available at: http://www.gnu.org/licenses/gpl-3.0.html
- *
- * If you would like to embed ZeroTier into a commercial application or
- * redistribute it in a modified binary form, please contact ZeroTier Networks
- * LLC. Start here: http://www.zerotier.com/
- */
-
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-
-#include <unistd.h>
-#include <signal.h>
-
-#include <fcntl.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/ioctl.h>
-#include <sys/wait.h>
-#include <sys/select.h>
-#include <sys/cdefs.h>
-#include <sys/uio.h>
-#include <sys/param.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <net/route.h>
-#include <net/if.h>
-#include <net/if_arp.h>
-#include <net/if_dl.h>
-#include <net/if_media.h>
-#include <netinet6/in6_var.h>
-#include <netinet/in_var.h>
-#include <netinet/icmp6.h>
-
-#include <pcap/pcap.h>
-
-// OSX compile fix... in6_var defines this in a struct which namespaces it for C++ ... why?!?
-struct prf_ra {
-	u_char onlink : 1;
-	u_char autonomous : 1;
-	u_char reserved : 6;
-} prf_ra;
-
-#include <netinet6/nd6.h>
-#include <ifaddrs.h>
-
-// These are KERNEL_PRIVATE... why?
-#ifndef SIOCAUTOCONF_START
-#define SIOCAUTOCONF_START _IOWR('i', 132, struct in6_ifreq)    /* accept rtadvd on this interface */
-#endif
-#ifndef SIOCAUTOCONF_STOP
-#define SIOCAUTOCONF_STOP _IOWR('i', 133, struct in6_ifreq)    /* stop accepting rtadv for this interface */
-#endif
-
-#ifndef ETH_ALEN
-#define ETH_ALEN 6
-#endif
-
-// --------------------------------------------------------------------------
-// --------------------------------------------------------------------------
-// This source is from:
-// http://www.opensource.apple.com/source/Libinfo/Libinfo-406.17/gen.subproj/getifmaddrs.c?txt
-// It's here because OSX 10.6 does not have this convenience function.
-
-#define	SALIGN	(sizeof(uint32_t) - 1)
-#define	SA_RLEN(sa)	((sa)->sa_len ? (((sa)->sa_len + SALIGN) & ~SALIGN) : \
-(SALIGN + 1))
-#define	MAX_SYSCTL_TRY	5
-#define	RTA_MASKS	(RTA_GATEWAY | RTA_IFP | RTA_IFA)
-
-/* FreeBSD uses NET_RT_IFMALIST and RTM_NEWMADDR from <sys/socket.h> */
-/* We can use NET_RT_IFLIST2 and RTM_NEWMADDR2 on Darwin */
-//#define DARWIN_COMPAT
-
-//#ifdef DARWIN_COMPAT
-#define GIM_SYSCTL_MIB NET_RT_IFLIST2
-#define GIM_RTM_ADDR RTM_NEWMADDR2
-//#else
-//#define GIM_SYSCTL_MIB NET_RT_IFMALIST
-//#define GIM_RTM_ADDR RTM_NEWMADDR
-//#endif
-
-// Not in 10.6 includes so use our own
-struct _intl_ifmaddrs {
-	struct _intl_ifmaddrs *ifma_next;
-	struct sockaddr *ifma_name;
-	struct sockaddr *ifma_addr;
-	struct sockaddr *ifma_lladdr;
-};
-
-static inline int _intl_getifmaddrs(struct _intl_ifmaddrs **pif)
-{
-	int icnt = 1;
-	int dcnt = 0;
-	int ntry = 0;
-	size_t len;
-	size_t needed;
-	int mib[6];
-	int i;
-	char *buf;
-	char *data;
-	char *next;
-	char *p;
-	struct ifma_msghdr2 *ifmam;
-	struct _intl_ifmaddrs *ifa, *ift;
-	struct rt_msghdr *rtm;
-	struct sockaddr *sa;
-
-	mib[0] = CTL_NET;
-	mib[1] = PF_ROUTE;
-	mib[2] = 0;             /* protocol */
-	mib[3] = 0;             /* wildcard address family */
-	mib[4] = GIM_SYSCTL_MIB;
-	mib[5] = 0;             /* no flags */
-	do {
-		if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0)
-			return (-1);
-		if ((buf = (char *)malloc(needed)) == NULL)
-			return (-1);
-		if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) {
-			if (errno != ENOMEM || ++ntry >= MAX_SYSCTL_TRY) {
-				free(buf);
-				return (-1);
-			}
-			free(buf);
-			buf = NULL;
-		}
-	} while (buf == NULL);
-
-	for (next = buf; next < buf + needed; next += rtm->rtm_msglen) {
-		rtm = (struct rt_msghdr *)(void *)next;
-		if (rtm->rtm_version != RTM_VERSION)
-			continue;
-		switch (rtm->rtm_type) {
-			case GIM_RTM_ADDR:
-				ifmam = (struct ifma_msghdr2 *)(void *)rtm;
-				if ((ifmam->ifmam_addrs & RTA_IFA) == 0)
-					break;
-				icnt++;
-				p = (char *)(ifmam + 1);
-				for (i = 0; i < RTAX_MAX; i++) {
-					if ((RTA_MASKS & ifmam->ifmam_addrs &
-						 (1 << i)) == 0)
-						continue;
-					sa = (struct sockaddr *)(void *)p;
-					len = SA_RLEN(sa);
-					dcnt += len;
-					p += len;
-				}
-				break;
-		}
-	}
-
-	data = (char *)malloc(sizeof(struct _intl_ifmaddrs) * icnt + dcnt);
-	if (data == NULL) {
-		free(buf);
-		return (-1);
-	}
-
-	ifa = (struct _intl_ifmaddrs *)(void *)data;
-	data += sizeof(struct _intl_ifmaddrs) * icnt;
-
-	memset(ifa, 0, sizeof(struct _intl_ifmaddrs) * icnt);
-	ift = ifa;
-
-	for (next = buf; next < buf + needed; next += rtm->rtm_msglen) {
-		rtm = (struct rt_msghdr *)(void *)next;
-		if (rtm->rtm_version != RTM_VERSION)
-			continue;
-
-		switch (rtm->rtm_type) {
-			case GIM_RTM_ADDR:
-				ifmam = (struct ifma_msghdr2 *)(void *)rtm;
-				if ((ifmam->ifmam_addrs & RTA_IFA) == 0)
-					break;
-
-				p = (char *)(ifmam + 1);
-				for (i = 0; i < RTAX_MAX; i++) {
-					if ((RTA_MASKS & ifmam->ifmam_addrs &
-						 (1 << i)) == 0)
-						continue;
-					sa = (struct sockaddr *)(void *)p;
-					len = SA_RLEN(sa);
-					switch (i) {
-						case RTAX_GATEWAY:
-							ift->ifma_lladdr =
-							(struct sockaddr *)(void *)data;
-							memcpy(data, p, len);
-							data += len;
-							break;
-
-						case RTAX_IFP:
-							ift->ifma_name =
-							(struct sockaddr *)(void *)data;
-							memcpy(data, p, len);
-							data += len;
-							break;
-
-						case RTAX_IFA:
-							ift->ifma_addr =
-							(struct sockaddr *)(void *)data;
-							memcpy(data, p, len);
-							data += len;
-							break;
-
-						default:
-							data += len;
-							break;
-					}
-					p += len;
-				}
-				ift->ifma_next = ift + 1;
-				ift = ift->ifma_next;
-				break;
-		}
-	}
-
-	free(buf);
-
-	if (ift > ifa) {
-		ift--;
-		ift->ifma_next = NULL;
-		*pif = ifa;
-	} else {
-		*pif = NULL;
-		free(ifa);
-	}
-	return (0);
-}
-
-static inline void _intl_freeifmaddrs(struct _intl_ifmaddrs *ifmp)
-{
-	free(ifmp);
-}
-
-// --------------------------------------------------------------------------
-// --------------------------------------------------------------------------
-
-#include <string>
-#include <map>
-#include <set>
-#include <algorithm>
-
-#include "../node/Constants.hpp"
-#include "../node/Utils.hpp"
-#include "../node/Mutex.hpp"
-#include "../node/Dictionary.hpp"
-#include "OSUtils.hpp"
-#include "OSXEthernetTap.hpp"
-
-// ff:ff:ff:ff:ff:ff with no ADI
-static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0);
-
-static inline bool _setIpv6Stuff(const char *ifname,bool performNUD,bool acceptRouterAdverts)
-{
-	struct in6_ndireq nd;
-	struct in6_ifreq ifr;
-
-	int s = socket(AF_INET6,SOCK_DGRAM,0);
-	if (s <= 0)
-		return false;
-
-	memset(&nd,0,sizeof(nd));
-	strncpy(nd.ifname,ifname,sizeof(nd.ifname));
-
-	if (ioctl(s,SIOCGIFINFO_IN6,&nd)) {
-		close(s);
-		return false;
-	}
-
-	unsigned long oldFlags = (unsigned long)nd.ndi.flags;
-
-	if (performNUD)
-		nd.ndi.flags |= ND6_IFF_PERFORMNUD;
-	else nd.ndi.flags &= ~ND6_IFF_PERFORMNUD;
-
-	if (oldFlags != (unsigned long)nd.ndi.flags) {
-		if (ioctl(s,SIOCSIFINFO_FLAGS,&nd)) {
-			close(s);
-			return false;
-		}
-	}
-
-	memset(&ifr,0,sizeof(ifr));
-	strncpy(ifr.ifr_name,ifname,sizeof(ifr.ifr_name));
-	if (ioctl(s,acceptRouterAdverts ? SIOCAUTOCONF_START : SIOCAUTOCONF_STOP,&ifr)) {
-		close(s);
-		return false;
-	}
-
-	close(s);
-	return true;
-}
-
-namespace ZeroTier {
-
-static std::set<std::string> globalDeviceNames;
-static Mutex globalTapCreateLock;
-
-OSXEthernetTap::OSXEthernetTap(
-	const char *homePath,
-	const MAC &mac,
-	unsigned int mtu,
-	unsigned int metric,
-	uint64_t nwid,
-	const char *friendlyName,
-	void (*handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *data,unsigned int len),
-	void *arg) :
-	_handler(handler),
-	_arg(arg),
-	_pcap((void *)0),
-	_nwid(nwid),
-	_mac(mac),
-	_homePath(homePath),
-	_mtu(mtu),
-	_metric(metric),
-	_enabled(true)
-{
-	char errbuf[PCAP_ERRBUF_SIZE];
-	char devname[64],ethaddr[64],mtustr[32],metstr[32],nwids[32];
-
-	Utils::snprintf(nwids,sizeof(nwids),"%.16llx",nwid);
-
-	if (mtu > 2800)
-		throw std::runtime_error("max tap MTU is 2800");
-
-	Mutex::Lock _gl(globalTapCreateLock);
-
-	std::string desiredDevice;
-	Dictionary devmap;
-	{
-		std::string devmapbuf;
-		if (OSUtils::readFile((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),devmapbuf)) {
-			devmap.fromString(devmapbuf);
-			desiredDevice = devmap.get(nwids,"");
-		}
-	}
-
-	if ((desiredDevice.length() >= 9)&&(desiredDevice.substr(0,6) == "bridge")) {
-		// length() >= 9 matches bridge### or bridge####
-		_dev = desiredDevice;
-	} else {
-		if (globalDeviceNames.size() >= (10000 - 128)) // sanity check... this would be nuts
-			throw std::runtime_error("too many devices!");
-		unsigned int pseudoBridgeNo = (unsigned int)((nwid ^ (nwid >> 32)) % (10000 - 128)) + 128; // range: bridge128 to bridge9999
-		sprintf(devname,"bridge%u",pseudoBridgeNo);
-		while (globalDeviceNames.count(std::string(devname)) > 0) {
-			++pseudoBridgeNo;
-			if (pseudoBridgeNo > 9999)
-				pseudoBridgeNo = 64;
-			sprintf(devname,"bridge%u",pseudoBridgeNo);
-		}
-		_dev = devname;
-	}
-
-	// Configure MAC address and MTU, bring interface up
-	long cpid = (long)vfork();
-	if (cpid == 0) {
-		::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"create",(const char *)0);
-		::_exit(-1);
-	} else if (cpid > 0) {
-		int exitcode = -1;
-		::waitpid(cpid,&exitcode,0);
-		if (exitcode != 0)
-			throw std::runtime_error("ifconfig failure setting link-layer address and activating tap interface");
-	} else throw std::runtime_error("unable to fork()");
-	Utils::snprintf(ethaddr,sizeof(ethaddr),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(int)mac[0],(int)mac[1],(int)mac[2],(int)mac[3],(int)mac[4],(int)mac[5]);
-	Utils::snprintf(mtustr,sizeof(mtustr),"%u",_mtu);
-	Utils::snprintf(metstr,sizeof(metstr),"%u",_metric);
-	cpid = (long)vfork();
-	if (cpid == 0) {
-		::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"lladdr",ethaddr,"mtu",mtustr,"metric",metstr,"up",(const char *)0);
-		::_exit(-1);
-	} else if (cpid > 0) {
-		int exitcode = -1;
-		::waitpid(cpid,&exitcode,0);
-		if (exitcode != 0)
-			throw std::runtime_error("ifconfig failure setting link-layer address and activating tap interface");
-	} else throw std::runtime_error("unable to fork()");
-
-	_setIpv6Stuff(_dev.c_str(),true,false);
-
-	_pcap = (void *)pcap_create(_dev.c_str(),errbuf);
-	if (!_pcap) {
-		cpid = (long)vfork();
-		if (cpid == 0) {
-			::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"destroy",(const char *)0);
-			::_exit(-1);
-		} else if (cpid > 0) {
-			int exitcode = -1;
-			::waitpid(cpid,&exitcode,0);
-		}
-		throw std::runtime_error((std::string("pcap_create() on new bridge device failed: ") + errbuf).c_str());
-	}
-	pcap_set_promisc(reinterpret_cast<pcap_t *>(_pcap),1);
-	pcap_set_timeout(reinterpret_cast<pcap_t *>(_pcap),120000);
-	pcap_set_immediate_mode(reinterpret_cast<pcap_t *>(_pcap),1);
-	if (pcap_set_buffer_size(reinterpret_cast<pcap_t *>(_pcap),1024 * 1024 * 16) != 0) // 16MB
-		fprintf(stderr,"WARNING: pcap_set_buffer_size() failed!\n");
-	if (pcap_set_snaplen(reinterpret_cast<pcap_t *>(_pcap),4096) != 0)
-		fprintf(stderr,"WARNING: pcap_set_snaplen() failed!\n");
-	if (pcap_activate(reinterpret_cast<pcap_t *>(_pcap)) != 0) {
-		pcap_close(reinterpret_cast<pcap_t *>(_pcap));
-		cpid = (long)vfork();
-		if (cpid == 0) {
-			::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"destroy",(const char *)0);
-			::_exit(-1);
-		} else if (cpid > 0) {
-			int exitcode = -1;
-			::waitpid(cpid,&exitcode,0);
-		}
-		throw std::runtime_error("pcap_activate() on new bridge device failed.");
-	}
-
-	globalDeviceNames.insert(_dev);
-
-	devmap[nwids] = _dev;
-	OSUtils::writeFile((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),devmap.toString());
-
-	_thread = Thread::start(this);
-}
-
-OSXEthernetTap::~OSXEthernetTap()
-{
-	_enabled = false;
-
-	Mutex::Lock _gl(globalTapCreateLock);
-	globalDeviceNames.erase(_dev);
-
-	long cpid = (long)vfork();
-	if (cpid == 0) {
-		::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"destroy",(const char *)0);
-		::_exit(-1);
-	} else if (cpid > 0) {
-		int exitcode = -1;
-		::waitpid(cpid,&exitcode,0);
-		if (exitcode == 0) {
-			// Destroying the interface nukes pcap and terminates the thread.
-			Thread::join(_thread);
-		}
-	}
-
-	pcap_close(reinterpret_cast<pcap_t *>(_pcap));
-}
-
-static bool ___removeIp(const std::string &_dev,const InetAddress &ip)
-{
-	long cpid = (long)vfork();
-	if (cpid == 0) {
-		execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"inet",ip.toIpString().c_str(),"-alias",(const char *)0);
-		_exit(-1);
-	} else if (cpid > 0) {
-		int exitcode = -1;
-		waitpid(cpid,&exitcode,0);
-		return (exitcode == 0);
-	}
-	return false; // never reached, make compiler shut up about return value
-}
-
-bool OSXEthernetTap::addIp(const InetAddress &ip)
-{
-	if (!ip)
-		return false;
-
-	std::vector<InetAddress> allIps(ips());
-	if (std::binary_search(allIps.begin(),allIps.end(),ip))
-		return true;
-
-	// Remove and reconfigure if address is the same but netmask is different
-	for(std::vector<InetAddress>::iterator i(allIps.begin());i!=allIps.end();++i) {
-		if ((i->ipsEqual(ip))&&(i->netmaskBits() != ip.netmaskBits())) {
-			if (___removeIp(_dev,*i))
-				break;
-		}
-	}
-
-	long cpid = (long)vfork();
-	if (cpid == 0) {
-		::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),ip.isV4() ? "inet" : "inet6",ip.toString().c_str(),"alias",(const char *)0);
-		::_exit(-1);
-	} else if (cpid > 0) {
-		int exitcode = -1;
-		::waitpid(cpid,&exitcode,0);
-		return (exitcode == 0);
-	} // else return false...
-
-	return false;
-}
-
-bool OSXEthernetTap::removeIp(const InetAddress &ip)
-{
-	if (!ip)
-		return true;
-	std::vector<InetAddress> allIps(ips());
-	if (!std::binary_search(allIps.begin(),allIps.end(),ip)) {
-		if (___removeIp(_dev,ip))
-			return true;
-	}
-	return false;
-}
-
-std::vector<InetAddress> OSXEthernetTap::ips() const
-{
-	struct ifaddrs *ifa = (struct ifaddrs *)0;
-	if (getifaddrs(&ifa))
-		return std::vector<InetAddress>();
-
-	std::vector<InetAddress> r;
-
-	struct ifaddrs *p = ifa;
-	while (p) {
-		if ((!strcmp(p->ifa_name,_dev.c_str()))&&(p->ifa_addr)&&(p->ifa_netmask)&&(p->ifa_addr->sa_family == p->ifa_netmask->sa_family)) {
-			switch(p->ifa_addr->sa_family) {
-				case AF_INET: {
-					struct sockaddr_in *sin = (struct sockaddr_in *)p->ifa_addr;
-					struct sockaddr_in *nm = (struct sockaddr_in *)p->ifa_netmask;
-					r.push_back(InetAddress(&(sin->sin_addr.s_addr),4,Utils::countBits((uint32_t)nm->sin_addr.s_addr)));
-				}	break;
-				case AF_INET6: {
-					struct sockaddr_in6 *sin = (struct sockaddr_in6 *)p->ifa_addr;
-					struct sockaddr_in6 *nm = (struct sockaddr_in6 *)p->ifa_netmask;
-					uint32_t b[4];
-					memcpy(b,nm->sin6_addr.s6_addr,sizeof(b));
-					r.push_back(InetAddress(sin->sin6_addr.s6_addr,16,Utils::countBits(b[0]) + Utils::countBits(b[1]) + Utils::countBits(b[2]) + Utils::countBits(b[3])));
-				}	break;
-			}
-		}
-		p = p->ifa_next;
-	}
-
-	if (ifa)
-		freeifaddrs(ifa);
-
-	std::sort(r.begin(),r.end());
-	std::unique(r.begin(),r.end());
-
-	return r;
-}
-
-void OSXEthernetTap::put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len)
-{
-	char putBuf[4096];
-	if ((len <= _mtu)&&(_enabled)) {
-		to.copyTo(putBuf,6);
-		from.copyTo(putBuf + 6,6);
-		*((uint16_t *)(putBuf + 12)) = htons((uint16_t)etherType);
-		memcpy(putBuf + 14,data,len);
-		len += 14;
-		int r = pcap_inject(reinterpret_cast<pcap_t *>(_pcap),putBuf,len);
-		if (r <= 0) {
-			printf("%s: pcap_inject() failed\n",_dev.c_str());
-			return;
-		}
-		printf("%s: inject %s -> %s etherType==%u len=%u r==%d\n",_dev.c_str(),from.toString().c_str(),to.toString().c_str(),etherType,len,r);
-	}
-}
-
-std::string OSXEthernetTap::deviceName() const
-{
-	return _dev;
-}
-
-void OSXEthernetTap::setFriendlyName(const char *friendlyName)
-{
-}
-
-void OSXEthernetTap::scanMulticastGroups(std::vector<MulticastGroup> &added,std::vector<MulticastGroup> &removed)
-{
-	std::vector<MulticastGroup> newGroups;
-
-	struct _intl_ifmaddrs *ifmap = (struct _intl_ifmaddrs *)0;
-	if (!_intl_getifmaddrs(&ifmap)) {
-		struct _intl_ifmaddrs *p = ifmap;
-		while (p) {
-			if (p->ifma_addr->sa_family == AF_LINK) {
-				struct sockaddr_dl *in = (struct sockaddr_dl *)p->ifma_name;
-				struct sockaddr_dl *la = (struct sockaddr_dl *)p->ifma_addr;
-				if ((la->sdl_alen == 6)&&(in->sdl_nlen <= _dev.length())&&(!memcmp(_dev.data(),in->sdl_data,in->sdl_nlen)))
-					newGroups.push_back(MulticastGroup(MAC(la->sdl_data + la->sdl_nlen,6),0));
-			}
-			p = p->ifma_next;
-		}
-		_intl_freeifmaddrs(ifmap);
-	}
-
-	std::vector<InetAddress> allIps(ips());
-	for(std::vector<InetAddress>::iterator ip(allIps.begin());ip!=allIps.end();++ip)
-		newGroups.push_back(MulticastGroup::deriveMulticastGroupForAddressResolution(*ip));
-
-	std::sort(newGroups.begin(),newGroups.end());
-	std::unique(newGroups.begin(),newGroups.end());
-
-	for(std::vector<MulticastGroup>::iterator m(newGroups.begin());m!=newGroups.end();++m) {
-		if (!std::binary_search(_multicastGroups.begin(),_multicastGroups.end(),*m))
-			added.push_back(*m);
-	}
-	for(std::vector<MulticastGroup>::iterator m(_multicastGroups.begin());m!=_multicastGroups.end();++m) {
-		if (!std::binary_search(newGroups.begin(),newGroups.end(),*m))
-			removed.push_back(*m);
-	}
-
-	_multicastGroups.swap(newGroups);
-}
-
-static void _pcapHandler(u_char *ptr,const struct pcap_pkthdr *hdr,const u_char *data)
-{
-	OSXEthernetTap *tap = reinterpret_cast<OSXEthernetTap *>(ptr);
-	if (hdr->caplen > 14) {
-		MAC to(data,6);
-		MAC from(data + 6,6);
-		if (from == tap->_mac) {
-			unsigned int etherType = ntohs(((const uint16_t *)data)[6]);
-			printf("%s: %s -> %s etherType==%u len==%u\n",tap->_dev.c_str(),from.toString().c_str(),to.toString().c_str(),etherType,(unsigned int)hdr->caplen);
-			// TODO: VLAN support
-			tap->_handler(tap->_arg,tap->_nwid,from,to,etherType,0,(const void *)(data + 14),hdr->len - 14);
-		}
-	}
-}
-
-void OSXEthernetTap::threadMain()
-	throw()
-{
-	pcap_loop(reinterpret_cast<pcap_t *>(_pcap),-1,&_pcapHandler,reinterpret_cast<u_char *>(this));
-}
-
-} // namespace ZeroTier

+ 0 - 831
attic/OSXEthernetTap.cpp.utun-work-in-progress

@@ -1,831 +0,0 @@
-/*
- * ZeroTier One - Network Virtualization Everywhere
- * Copyright (C) 2011-2015  ZeroTier, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * --
- *
- * ZeroTier may be used and distributed under the terms of the GPLv3, which
- * are available at: http://www.gnu.org/licenses/gpl-3.0.html
- *
- * If you would like to embed ZeroTier into a commercial application or
- * redistribute it in a modified binary form, please contact ZeroTier Networks
- * LLC. Start here: http://www.zerotier.com/
- */
-
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-
-#include <unistd.h>
-#include <signal.h>
-
-#include <fcntl.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/ioctl.h>
-#include <sys/wait.h>
-#include <sys/select.h>
-#include <sys/cdefs.h>
-#include <sys/uio.h>
-#include <sys/param.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <sys/sys_domain.h>
-#include <sys/kern_control.h>
-#include <net/if_utun.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <net/route.h>
-#include <net/if.h>
-#include <net/if_arp.h>
-#include <net/if_dl.h>
-#include <net/if_media.h>
-#include <netinet6/in6_var.h>
-#include <netinet/in_var.h>
-#include <netinet/icmp6.h>
-
-// OSX compile fix... in6_var defines this in a struct which namespaces it for C++ ... why?!?
-struct prf_ra {
-	u_char onlink : 1;
-	u_char autonomous : 1;
-	u_char reserved : 6;
-} prf_ra;
-
-#include <netinet6/nd6.h>
-#include <ifaddrs.h>
-
-// These are KERNEL_PRIVATE... why?
-#ifndef SIOCAUTOCONF_START
-#define SIOCAUTOCONF_START _IOWR('i', 132, struct in6_ifreq)    /* accept rtadvd on this interface */
-#endif
-#ifndef SIOCAUTOCONF_STOP
-#define SIOCAUTOCONF_STOP _IOWR('i', 133, struct in6_ifreq)    /* stop accepting rtadv for this interface */
-#endif
-
-// --------------------------------------------------------------------------
-// --------------------------------------------------------------------------
-// This source is from:
-// http://www.opensource.apple.com/source/Libinfo/Libinfo-406.17/gen.subproj/getifmaddrs.c?txt
-// It's here because OSX 10.6 does not have this convenience function.
-
-#define	SALIGN	(sizeof(uint32_t) - 1)
-#define	SA_RLEN(sa)	((sa)->sa_len ? (((sa)->sa_len + SALIGN) & ~SALIGN) : \
-(SALIGN + 1))
-#define	MAX_SYSCTL_TRY	5
-#define	RTA_MASKS	(RTA_GATEWAY | RTA_IFP | RTA_IFA)
-
-/* FreeBSD uses NET_RT_IFMALIST and RTM_NEWMADDR from <sys/socket.h> */
-/* We can use NET_RT_IFLIST2 and RTM_NEWMADDR2 on Darwin */
-//#define DARWIN_COMPAT
-
-//#ifdef DARWIN_COMPAT
-#define GIM_SYSCTL_MIB NET_RT_IFLIST2
-#define GIM_RTM_ADDR RTM_NEWMADDR2
-//#else
-//#define GIM_SYSCTL_MIB NET_RT_IFMALIST
-//#define GIM_RTM_ADDR RTM_NEWMADDR
-//#endif
-
-// Not in 10.6 includes so use our own
-struct _intl_ifmaddrs {
-	struct _intl_ifmaddrs *ifma_next;
-	struct sockaddr *ifma_name;
-	struct sockaddr *ifma_addr;
-	struct sockaddr *ifma_lladdr;
-};
-
-static inline int _intl_getifmaddrs(struct _intl_ifmaddrs **pif)
-{
-	int icnt = 1;
-	int dcnt = 0;
-	int ntry = 0;
-	size_t len;
-	size_t needed;
-	int mib[6];
-	int i;
-	char *buf;
-	char *data;
-	char *next;
-	char *p;
-	struct ifma_msghdr2 *ifmam;
-	struct _intl_ifmaddrs *ifa, *ift;
-	struct rt_msghdr *rtm;
-	struct sockaddr *sa;
-
-	mib[0] = CTL_NET;
-	mib[1] = PF_ROUTE;
-	mib[2] = 0;             /* protocol */
-	mib[3] = 0;             /* wildcard address family */
-	mib[4] = GIM_SYSCTL_MIB;
-	mib[5] = 0;             /* no flags */
-	do {
-		if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0)
-			return (-1);
-		if ((buf = (char *)malloc(needed)) == NULL)
-			return (-1);
-		if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) {
-			if (errno != ENOMEM || ++ntry >= MAX_SYSCTL_TRY) {
-				free(buf);
-				return (-1);
-			}
-			free(buf);
-			buf = NULL;
-		}
-	} while (buf == NULL);
-
-	for (next = buf; next < buf + needed; next += rtm->rtm_msglen) {
-		rtm = (struct rt_msghdr *)(void *)next;
-		if (rtm->rtm_version != RTM_VERSION)
-			continue;
-		switch (rtm->rtm_type) {
-			case GIM_RTM_ADDR:
-				ifmam = (struct ifma_msghdr2 *)(void *)rtm;
-				if ((ifmam->ifmam_addrs & RTA_IFA) == 0)
-					break;
-				icnt++;
-				p = (char *)(ifmam + 1);
-				for (i = 0; i < RTAX_MAX; i++) {
-					if ((RTA_MASKS & ifmam->ifmam_addrs &
-						 (1 << i)) == 0)
-						continue;
-					sa = (struct sockaddr *)(void *)p;
-					len = SA_RLEN(sa);
-					dcnt += len;
-					p += len;
-				}
-				break;
-		}
-	}
-
-	data = (char *)malloc(sizeof(struct _intl_ifmaddrs) * icnt + dcnt);
-	if (data == NULL) {
-		free(buf);
-		return (-1);
-	}
-
-	ifa = (struct _intl_ifmaddrs *)(void *)data;
-	data += sizeof(struct _intl_ifmaddrs) * icnt;
-
-	memset(ifa, 0, sizeof(struct _intl_ifmaddrs) * icnt);
-	ift = ifa;
-
-	for (next = buf; next < buf + needed; next += rtm->rtm_msglen) {
-		rtm = (struct rt_msghdr *)(void *)next;
-		if (rtm->rtm_version != RTM_VERSION)
-			continue;
-
-		switch (rtm->rtm_type) {
-			case GIM_RTM_ADDR:
-				ifmam = (struct ifma_msghdr2 *)(void *)rtm;
-				if ((ifmam->ifmam_addrs & RTA_IFA) == 0)
-					break;
-
-				p = (char *)(ifmam + 1);
-				for (i = 0; i < RTAX_MAX; i++) {
-					if ((RTA_MASKS & ifmam->ifmam_addrs &
-						 (1 << i)) == 0)
-						continue;
-					sa = (struct sockaddr *)(void *)p;
-					len = SA_RLEN(sa);
-					switch (i) {
-						case RTAX_GATEWAY:
-							ift->ifma_lladdr =
-							(struct sockaddr *)(void *)data;
-							memcpy(data, p, len);
-							data += len;
-							break;
-
-						case RTAX_IFP:
-							ift->ifma_name =
-							(struct sockaddr *)(void *)data;
-							memcpy(data, p, len);
-							data += len;
-							break;
-
-						case RTAX_IFA:
-							ift->ifma_addr =
-							(struct sockaddr *)(void *)data;
-							memcpy(data, p, len);
-							data += len;
-							break;
-
-						default:
-							data += len;
-							break;
-					}
-					p += len;
-				}
-				ift->ifma_next = ift + 1;
-				ift = ift->ifma_next;
-				break;
-		}
-	}
-
-	free(buf);
-
-	if (ift > ifa) {
-		ift--;
-		ift->ifma_next = NULL;
-		*pif = ifa;
-	} else {
-		*pif = NULL;
-		free(ifa);
-	}
-	return (0);
-}
-
-static inline void _intl_freeifmaddrs(struct _intl_ifmaddrs *ifmp)
-{
-	free(ifmp);
-}
-
-// --------------------------------------------------------------------------
-// --------------------------------------------------------------------------
-
-#include <string>
-#include <map>
-#include <set>
-#include <algorithm>
-
-#include "../node/Constants.hpp"
-#include "../node/Utils.hpp"
-#include "../node/Mutex.hpp"
-#include "../node/Dictionary.hpp"
-#include "Arp.hpp"
-#include "OSUtils.hpp"
-#include "OSXEthernetTap.hpp"
-
-// ff:ff:ff:ff:ff:ff with no ADI
-static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0);
-
-static inline bool _setIpv6Stuff(const char *ifname,bool performNUD,bool acceptRouterAdverts)
-{
-	struct in6_ndireq nd;
-	struct in6_ifreq ifr;
-
-	int s = socket(AF_INET6,SOCK_DGRAM,0);
-	if (s <= 0)
-		return false;
-
-	memset(&nd,0,sizeof(nd));
-	strncpy(nd.ifname,ifname,sizeof(nd.ifname));
-
-	if (ioctl(s,SIOCGIFINFO_IN6,&nd)) {
-		close(s);
-		return false;
-	}
-
-	unsigned long oldFlags = (unsigned long)nd.ndi.flags;
-
-	if (performNUD)
-		nd.ndi.flags |= ND6_IFF_PERFORMNUD;
-	else nd.ndi.flags &= ~ND6_IFF_PERFORMNUD;
-
-	if (oldFlags != (unsigned long)nd.ndi.flags) {
-		if (ioctl(s,SIOCSIFINFO_FLAGS,&nd)) {
-			close(s);
-			return false;
-		}
-	}
-
-	memset(&ifr,0,sizeof(ifr));
-	strncpy(ifr.ifr_name,ifname,sizeof(ifr.ifr_name));
-	if (ioctl(s,acceptRouterAdverts ? SIOCAUTOCONF_START : SIOCAUTOCONF_STOP,&ifr)) {
-		close(s);
-		return false;
-	}
-
-	close(s);
-	return true;
-}
-
-// Create an OSX-native utun device (utun# where # is desiredNumber)
-// Adapted from public domain utun example code by Jonathan Levin
-static int _make_utun(int desiredNumber)
-{
-	struct sockaddr_ctl sc;
-	struct ctl_info ctlInfo;
-	struct ifreq ifr;
-
-	memset(&ctlInfo, 0, sizeof(ctlInfo));
-	if (strlcpy(ctlInfo.ctl_name, UTUN_CONTROL_NAME, sizeof(ctlInfo.ctl_name)) >= sizeof(ctlInfo.ctl_name)) {
-		return -1;
-	}
-
-	int fd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL);
-	if (fd == -1)
-		return -1;
-	if (ioctl(fd, CTLIOCGINFO, &ctlInfo) == -1) {
-		close(fd);
-		return -1;
-	}
-
-	sc.sc_id = ctlInfo.ctl_id;
-	sc.sc_len = sizeof(sc);
-	sc.sc_family = AF_SYSTEM;
-	sc.ss_sysaddr = AF_SYS_CONTROL;
-	sc.sc_unit = desiredNumber + 1;
-
-	if (connect(fd, (struct sockaddr *)&sc, sizeof(sc)) == -1) {
-		close(fd);
-		return -1;
-	}
-
-	memset(&ifr,0,sizeof(ifr));
-	sprintf(ifr.ifr_name,"utun%d",desiredNumber);
-	if (ioctl(fd,SIOCGIFFLAGS,(void *)&ifr) < 0) {
-		printf("SIOCGIFFLAGS failed\n");
-	}
-	ifr.ifr_flags &= ~IFF_POINTOPOINT;
-	if (ioctl(fd,SIOCSIFFLAGS,(void *)&ifr) < 0) {
-		printf("clear IFF_POINTOPOINT failed\n");
-	}
-
-	return fd;
-}
-
-namespace ZeroTier {
-
-static long globalTapsRunning = 0;
-static Mutex globalTapCreateLock;
-
-OSXEthernetTap::OSXEthernetTap(
-	const char *homePath,
-	const MAC &mac,
-	unsigned int mtu,
-	unsigned int metric,
-	uint64_t nwid,
-	const char *friendlyName,
-	void (*handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *data,unsigned int len),
-	void *arg) :
-	_handler(handler),
-	_arg(arg),
-	_arp((Arp *)0),
-	_nwid(nwid),
-	_homePath(homePath),
-	_mtu(mtu),
-	_metric(metric),
-	_fd(0),
-	_utun(false),
-	_enabled(true)
-{
-	char devpath[64],ethaddr[64],mtustr[32],metstr[32],nwids[32];
-	struct stat stattmp;
-
-	Utils::snprintf(nwids,sizeof(nwids),"%.16llx",nwid);
-
-	if (mtu > 2800)
-		throw std::runtime_error("max tap MTU is 2800");
-
-	Mutex::Lock _gl(globalTapCreateLock);
-
-	// Read remembered previous device name, if any -- we'll try to reuse
-	Dictionary devmap;
-	std::string desiredDevice;
-	{
-		std::string devmapbuf;
-		if (OSUtils::readFile((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),devmapbuf)) {
-			devmap.fromString(devmapbuf);
-			desiredDevice = devmap.get(nwids,"");
-		}
-	}
-
-	if (::stat((_homePath + ZT_PATH_SEPARATOR_S + "tap.kext").c_str(),&stattmp) == 0) {
-		// Try to init kext if it's there, otherwise revert to utun mode
-
-		if (::stat("/dev/zt0",&stattmp)) {
-			long kextpid = (long)vfork();
-			if (kextpid == 0) {
-				::chdir(homePath);
-				OSUtils::redirectUnixOutputs("/dev/null",(const char *)0);
-				::execl("/sbin/kextload","/sbin/kextload","-q","-repository",homePath,"tap.kext",(const char *)0);
-				::_exit(-1);
-			} else if (kextpid > 0) {
-				int exitcode = -1;
-				::waitpid(kextpid,&exitcode,0);
-			}
-			::usleep(500); // give tap device driver time to start up and try again
-			if (::stat("/dev/zt0",&stattmp))
-				_utun = true;
-		}
-
-		if (!_utun) {
-			// See if we can re-use the last device we had.
-			bool recalledDevice = false;
-			if (desiredDevice.length() > 2) {
-				Utils::snprintf(devpath,sizeof(devpath),"/dev/%s",desiredDevice.c_str());
-				if (stat(devpath,&stattmp) == 0) {
-					_fd = ::open(devpath,O_RDWR);
-					if (_fd > 0) {
-						_dev = desiredDevice;
-						recalledDevice = true;
-					}
-				}
-			}
-
-			// Open the first unused tap device if we didn't recall a previous one.
-			if (!recalledDevice) {
-				for(int i=0;i<64;++i) {
-					Utils::snprintf(devpath,sizeof(devpath),"/dev/zt%d",i);
-					if (stat(devpath,&stattmp)) {
-						_utun = true;
-						break;
-					}
-					_fd = ::open(devpath,O_RDWR);
-					if (_fd > 0) {
-						char foo[16];
-						Utils::snprintf(foo,sizeof(foo),"zt%d",i);
-						_dev = foo;
-						break;
-					}
-				}
-			}
-			if (_fd <= 0)
-				_utun = true;
-		}
-	} else {
-		_utun = true;
-	}
-
-	if (_utun) {
-		// Use OSX built-in utun device if kext is not available or doesn't work
-
-		int utunNo = 0;
-
-		if ((desiredDevice.length() > 4)&&(desiredDevice.substr(0,4) == "utun")) {
-			utunNo = Utils::strToInt(desiredDevice.substr(4).c_str());
-			if (utunNo >= 0)
-				_fd = _make_utun(utunNo);
-		}
-
-		if (_fd <= 0) {
-			// Start at utun8 to leave lower utuns unused since other stuff might
-			// want them -- OpenVPN, cjdns, etc. I'm not sure if those are smart
-			// enough to scan upward like this.
-			for(utunNo=8;utunNo<=256;++utunNo) {
-				if ((_fd = _make_utun(utunNo)) > 0)
-					break;
-			}
-		}
-
-		if (_fd <= 0)
-			throw std::runtime_error("unable to find/load ZeroTier tap driver OR use built-in utun driver in OSX; permission or system problem or too many open devices?");
-
-		Utils::snprintf(devpath,sizeof(devpath),"utun%d",utunNo);
-		_dev = devpath;
-
-		// Configure address and bring it up
-		Utils::snprintf(mtustr,sizeof(mtustr),"%u",_mtu);
-		Utils::snprintf(metstr,sizeof(metstr),"%u",_metric);
-		long cpid = (long)vfork();
-		if (cpid == 0) {
-			::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"mtu",mtustr,"metric",metstr,"up",(const char *)0);
-			::_exit(-1);
-		} else if (cpid > 0) {
-			int exitcode = -1;
-			::waitpid(cpid,&exitcode,0);
-			if (exitcode) {
-				::close(_fd);
-				throw std::runtime_error("ifconfig failure activating utun interface");
-			}
-		}
-
-	} else {
-		// Use our ZeroTier OSX tun/tap driver for zt# Ethernet tap device
-
-		if (fcntl(_fd,F_SETFL,fcntl(_fd,F_GETFL) & ~O_NONBLOCK) == -1) {
-			::close(_fd);
-			throw std::runtime_error("unable to set flags on file descriptor for TAP device");
-		}
-
-		// Configure MAC address and MTU, bring interface up
-		Utils::snprintf(ethaddr,sizeof(ethaddr),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(int)mac[0],(int)mac[1],(int)mac[2],(int)mac[3],(int)mac[4],(int)mac[5]);
-		Utils::snprintf(mtustr,sizeof(mtustr),"%u",_mtu);
-		Utils::snprintf(metstr,sizeof(metstr),"%u",_metric);
-		long cpid = (long)vfork();
-		if (cpid == 0) {
-			::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"lladdr",ethaddr,"mtu",mtustr,"metric",metstr,"up",(const char *)0);
-			::_exit(-1);
-		} else if (cpid > 0) {
-			int exitcode = -1;
-			::waitpid(cpid,&exitcode,0);
-			if (exitcode) {
-				::close(_fd);
-				throw std::runtime_error("ifconfig failure setting link-layer address and activating tap interface");
-			}
-		}
-
-		_setIpv6Stuff(_dev.c_str(),true,false);
-	}
-
-	// Set close-on-exec so that devices cannot persist if we fork/exec for update
-	fcntl(_fd,F_SETFD,fcntl(_fd,F_GETFD) | FD_CLOEXEC);
-
-	::pipe(_shutdownSignalPipe);
-
-	++globalTapsRunning;
-
-	devmap[nwids] = _dev;
-	OSUtils::writeFile((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),devmap.toString());
-
-	_thread = Thread::start(this);
-}
-
-OSXEthernetTap::~OSXEthernetTap()
-{
-	Mutex::Lock _gl(globalTapCreateLock);
-
-	::write(_shutdownSignalPipe[1],(const void *)this,1); // writing a byte causes thread to exit
-	Thread::join(_thread);
-
-	::close(_fd);
-	::close(_shutdownSignalPipe[0]);
-	::close(_shutdownSignalPipe[1]);
-
-	if (_utun) {
-		delete _arp;
-	} else {
-		if (--globalTapsRunning <= 0) {
-			globalTapsRunning = 0; // sanity check -- should not be possible
-
-			char tmp[16384];
-			sprintf(tmp,"%s/%s",_homePath.c_str(),"tap.kext");
-			long kextpid = (long)vfork();
-			if (kextpid == 0) {
-				OSUtils::redirectUnixOutputs("/dev/null",(const char *)0);
-				::execl("/sbin/kextunload","/sbin/kextunload",tmp,(const char *)0);
-				::_exit(-1);
-			} else if (kextpid > 0) {
-				int exitcode = -1;
-				::waitpid(kextpid,&exitcode,0);
-			}
-		}
-	}
-}
-
-void OSXEthernetTap::setEnabled(bool en)
-{
-	_enabled = en;
-	// TODO: interface status change
-}
-
-bool OSXEthernetTap::enabled() const
-{
-	return _enabled;
-}
-
-static bool ___removeIp(const std::string &_dev,const InetAddress &ip)
-{
-	long cpid = (long)vfork();
-	if (cpid == 0) {
-		execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"inet",ip.toIpString().c_str(),"-alias",(const char *)0);
-		_exit(-1);
-	} else if (cpid > 0) {
-		int exitcode = -1;
-		waitpid(cpid,&exitcode,0);
-		return (exitcode == 0);
-	}
-	return false; // never reached, make compiler shut up about return value
-}
-
-bool OSXEthernetTap::addIp(const InetAddress &ip)
-{
-	if (!ip)
-		return false;
-
-	std::vector<InetAddress> allIps(ips());
-	if (std::binary_search(allIps.begin(),allIps.end(),ip))
-		return true;
-
-	// Remove and reconfigure if address is the same but netmask is different
-	for(std::vector<InetAddress>::iterator i(allIps.begin());i!=allIps.end();++i) {
-		if ((i->ipsEqual(ip))&&(i->netmaskBits() != ip.netmaskBits())) {
-			if (___removeIp(_dev,*i))
-				break;
-		}
-	}
-
-	if (_utun) {
-		long cpid = (long)vfork();
-		if (cpid == 0) {
-			if (ip.ss_family == AF_INET6) {
-				::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"inet6",ip.toString().c_str(),"alias",(const char *)0);
-			} else {
-				::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),ip.toString().c_str(),ip.toIpString().c_str(),"alias",(const char *)0);
-			}
-			::_exit(-1);
-		} else if (cpid > 0) {
-			int exitcode = -1;
-			::waitpid(cpid,&exitcode,0);
-
-			if (exitcode == 0) {
-				if (ip.ss_family == AF_INET) {
-					// Add route to network over tun for IPv4 -- otherwise it behaves
-					// as a simple point to point tunnel instead of a true route.
-					cpid = (long)vfork();
-					if (cpid == 0) {
-						::close(STDERR_FILENO);
-						::close(STDOUT_FILENO);
-						::execl("/sbin/route","/sbin/route","add",ip.network().toString().c_str(),ip.toIpString().c_str(),(const char *)0);
-						::exit(-1);
-					} else if (cpid > 0) {
-						int exitcode = -1;
-						::waitpid(cpid,&exitcode,0);
-						return (exitcode == 0);
-					}
-				} else return true;
-			}
-		}
-	} else {
-		long cpid = (long)vfork();
-		if (cpid == 0) {
-			::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),ip.isV4() ? "inet" : "inet6",ip.toString().c_str(),"alias",(const char *)0);
-			::_exit(-1);
-		} else if (cpid > 0) {
-			int exitcode = -1;
-			::waitpid(cpid,&exitcode,0);
-			return (exitcode == 0);
-		}
-	}
-
-	return false;
-}
-
-bool OSXEthernetTap::removeIp(const InetAddress &ip)
-{
-	if (!ip)
-		return true;
-	std::vector<InetAddress> allIps(ips());
-	if (!std::binary_search(allIps.begin(),allIps.end(),ip)) {
-		if (___removeIp(_dev,ip))
-			return true;
-	}
-	return false;
-}
-
-std::vector<InetAddress> OSXEthernetTap::ips() const
-{
-	struct ifaddrs *ifa = (struct ifaddrs *)0;
-	if (getifaddrs(&ifa))
-		return std::vector<InetAddress>();
-
-	std::vector<InetAddress> r;
-
-	struct ifaddrs *p = ifa;
-	while (p) {
-		if ((!strcmp(p->ifa_name,_dev.c_str()))&&(p->ifa_addr)&&(p->ifa_netmask)&&(p->ifa_addr->sa_family == p->ifa_netmask->sa_family)) {
-			switch(p->ifa_addr->sa_family) {
-				case AF_INET: {
-					struct sockaddr_in *sin = (struct sockaddr_in *)p->ifa_addr;
-					struct sockaddr_in *nm = (struct sockaddr_in *)p->ifa_netmask;
-					r.push_back(InetAddress(&(sin->sin_addr.s_addr),4,Utils::countBits((uint32_t)nm->sin_addr.s_addr)));
-				}	break;
-				case AF_INET6: {
-					struct sockaddr_in6 *sin = (struct sockaddr_in6 *)p->ifa_addr;
-					struct sockaddr_in6 *nm = (struct sockaddr_in6 *)p->ifa_netmask;
-					uint32_t b[4];
-					memcpy(b,nm->sin6_addr.s6_addr,sizeof(b));
-					r.push_back(InetAddress(sin->sin6_addr.s6_addr,16,Utils::countBits(b[0]) + Utils::countBits(b[1]) + Utils::countBits(b[2]) + Utils::countBits(b[3])));
-				}	break;
-			}
-		}
-		p = p->ifa_next;
-	}
-
-	if (ifa)
-		freeifaddrs(ifa);
-
-	std::sort(r.begin(),r.end());
-	std::unique(r.begin(),r.end());
-
-	return r;
-}
-
-void OSXEthernetTap::put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len)
-{
-	char putBuf[4096];
-	if ((_fd > 0)&&(len <= _mtu)&&(_enabled)) {
-		to.copyTo(putBuf,6);
-		from.copyTo(putBuf + 6,6);
-		*((uint16_t *)(putBuf + 12)) = htons((uint16_t)etherType);
-		memcpy(putBuf + 14,data,len);
-		len += 14;
-		::write(_fd,putBuf,len);
-	}
-}
-
-std::string OSXEthernetTap::deviceName() const
-{
-	return _dev;
-}
-
-void OSXEthernetTap::setFriendlyName(const char *friendlyName)
-{
-}
-
-void OSXEthernetTap::scanMulticastGroups(std::vector<MulticastGroup> &added,std::vector<MulticastGroup> &removed)
-{
-	std::vector<MulticastGroup> newGroups;
-
-	struct _intl_ifmaddrs *ifmap = (struct _intl_ifmaddrs *)0;
-	if (!_intl_getifmaddrs(&ifmap)) {
-		struct _intl_ifmaddrs *p = ifmap;
-		while (p) {
-			if (p->ifma_addr->sa_family == AF_LINK) {
-				struct sockaddr_dl *in = (struct sockaddr_dl *)p->ifma_name;
-				struct sockaddr_dl *la = (struct sockaddr_dl *)p->ifma_addr;
-				if ((la->sdl_alen == 6)&&(in->sdl_nlen <= _dev.length())&&(!memcmp(_dev.data(),in->sdl_data,in->sdl_nlen)))
-					newGroups.push_back(MulticastGroup(MAC(la->sdl_data + la->sdl_nlen,6),0));
-			}
-			p = p->ifma_next;
-		}
-		_intl_freeifmaddrs(ifmap);
-	}
-
-	std::vector<InetAddress> allIps(ips());
-	for(std::vector<InetAddress>::iterator ip(allIps.begin());ip!=allIps.end();++ip)
-		newGroups.push_back(MulticastGroup::deriveMulticastGroupForAddressResolution(*ip));
-
-	std::sort(newGroups.begin(),newGroups.end());
-	std::unique(newGroups.begin(),newGroups.end());
-
-	for(std::vector<MulticastGroup>::iterator m(newGroups.begin());m!=newGroups.end();++m) {
-		if (!std::binary_search(_multicastGroups.begin(),_multicastGroups.end(),*m))
-			added.push_back(*m);
-	}
-	for(std::vector<MulticastGroup>::iterator m(_multicastGroups.begin());m!=_multicastGroups.end();++m) {
-		if (!std::binary_search(newGroups.begin(),newGroups.end(),*m))
-			removed.push_back(*m);
-	}
-
-	_multicastGroups.swap(newGroups);
-}
-
-void OSXEthernetTap::threadMain()
-	throw()
-{
-	fd_set readfds,nullfds;
-	MAC to,from;
-	int n,nfds,r;
-	char getBuf[8194];
-
-	Thread::sleep(500);
-
-	FD_ZERO(&readfds);
-	FD_ZERO(&nullfds);
-	nfds = (int)std::max(_shutdownSignalPipe[0],_fd) + 1;
-
-	r = 0;
-	for(;;) {
-		FD_SET(_shutdownSignalPipe[0],&readfds);
-		FD_SET(_fd,&readfds);
-		select(nfds,&readfds,&nullfds,&nullfds,(struct timeval *)0);
-
-		if (FD_ISSET(_shutdownSignalPipe[0],&readfds)) // writes to shutdown pipe terminate thread
-			break;
-
-		if (FD_ISSET(_fd,&readfds)) {
-			n = (int)::read(_fd,getBuf + r,sizeof(getBuf) - r);
-			if (n < 0) {
-				if ((errno != EINTR)&&(errno != ETIMEDOUT))
-					break;
-			} else {
-				// Some tap drivers like to send the ethernet frame and the
-				// payload in two chunks, so handle that by accumulating
-				// data until we have at least a frame.
-				r += n;
-				if (r > 14) {
-					if (r > ((int)_mtu + 14)) // sanity check for weird TAP behavior on some platforms
-						r = _mtu + 14;
-
-					if (_enabled) {
-						to.setTo(getBuf,6);
-						from.setTo(getBuf + 6,6);
-						unsigned int etherType = ntohs(((const uint16_t *)getBuf)[6]);
-						// TODO: VLAN support
-						_handler(_arg,_nwid,from,to,etherType,0,(const void *)(getBuf + 14),r - 14);
-					}
-
-					r = 0;
-				}
-			}
-		}
-	}
-}
-
-} // namespace ZeroTier

+ 0 - 96
attic/OSXEthernetTap.hpp.pcap-with-bridge-test

@@ -1,96 +0,0 @@
-/*
- * ZeroTier One - Network Virtualization Everywhere
- * Copyright (C) 2011-2015  ZeroTier, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * --
- *
- * ZeroTier may be used and distributed under the terms of the GPLv3, which
- * are available at: http://www.gnu.org/licenses/gpl-3.0.html
- *
- * If you would like to embed ZeroTier into a commercial application or
- * redistribute it in a modified binary form, please contact ZeroTier Networks
- * LLC. Start here: http://www.zerotier.com/
- */
-
-#ifndef ZT_OSXETHERNETTAP_HPP
-#define ZT_OSXETHERNETTAP_HPP
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <stdexcept>
-#include <string>
-#include <vector>
-
-#include "../node/Constants.hpp"
-#include "../node/MAC.hpp"
-#include "../node/InetAddress.hpp"
-#include "../node/MulticastGroup.hpp"
-
-#include "Thread.hpp"
-
-namespace ZeroTier {
-
-/**
- * OSX Ethernet tap using ZeroTier kernel extension zt# devices
- */
-class OSXEthernetTap
-{
-public:
-	OSXEthernetTap(
-		const char *homePath,
-		const MAC &mac,
-		unsigned int mtu,
-		unsigned int metric,
-		uint64_t nwid,
-		const char *friendlyName,
-		void (*handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int),
-		void *arg);
-
-	~OSXEthernetTap();
-
-	inline void setEnabled(bool en) { _enabled = en; }
-	inline bool enabled() const { return _enabled; }
-	bool addIp(const InetAddress &ip);
-	bool removeIp(const InetAddress &ip);
-	std::vector<InetAddress> ips() const;
-	void put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len);
-	std::string deviceName() const;
-	void setFriendlyName(const char *friendlyName);
-	void scanMulticastGroups(std::vector<MulticastGroup> &added,std::vector<MulticastGroup> &removed);
-
-	void threadMain()
-		throw();
-
-	// Private members of OSXEthernetTap have public visibility to be accessable
-	// from an internal bounce function; don't modify directly.
-	void (*_handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int);
-	void *_arg;
-	void *_pcap; // pcap_t *
-	uint64_t _nwid;
-	MAC _mac;
-	Thread _thread;
-	std::string _homePath;
-	std::string _dev;
-	std::vector<MulticastGroup> _multicastGroups;
-	unsigned int _mtu;
-	unsigned int _metric;
-	volatile bool _enabled;
-};
-
-} // namespace ZeroTier
-
-#endif

+ 0 - 101
attic/OSXEthernetTap.hpp.utun-work-in-progress

@@ -1,101 +0,0 @@
-/*
- * ZeroTier One - Network Virtualization Everywhere
- * Copyright (C) 2011-2015  ZeroTier, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * --
- *
- * ZeroTier may be used and distributed under the terms of the GPLv3, which
- * are available at: http://www.gnu.org/licenses/gpl-3.0.html
- *
- * If you would like to embed ZeroTier into a commercial application or
- * redistribute it in a modified binary form, please contact ZeroTier Networks
- * LLC. Start here: http://www.zerotier.com/
- */
-
-#ifndef ZT_OSXETHERNETTAP_HPP
-#define ZT_OSXETHERNETTAP_HPP
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <stdexcept>
-#include <string>
-#include <vector>
-
-#include "../node/Constants.hpp"
-#include "../node/MAC.hpp"
-#include "../node/InetAddress.hpp"
-#include "../node/MulticastGroup.hpp"
-
-#include "Thread.hpp"
-
-namespace ZeroTier {
-
-class Arp;
-
-/**
- * OSX Ethernet tap supporting either ZeroTier tun/tap kext or OSX-native utun
- */
-class OSXEthernetTap
-{
-public:
-	OSXEthernetTap(
-		const char *homePath,
-		const MAC &mac,
-		unsigned int mtu,
-		unsigned int metric,
-		uint64_t nwid,
-		const char *friendlyName,
-		void (*handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int),
-		void *arg);
-
-	~OSXEthernetTap();
-
-	void setEnabled(bool en);
-	bool enabled() const;
-	bool addIp(const InetAddress &ip);
-	bool removeIp(const InetAddress &ip);
-	std::vector<InetAddress> ips() const;
-	void put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len);
-	std::string deviceName() const;
-	void setFriendlyName(const char *friendlyName);
-	void scanMulticastGroups(std::vector<MulticastGroup> &added,std::vector<MulticastGroup> &removed);
-
-	inline bool isNativeUtun() const { return _utun; }
-
-	void threadMain()
-		throw();
-
-private:
-	void (*_handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int);
-	void *_arg;
-	Arp *_arp; // created and used if utun is enabled
-	uint64_t _nwid;
-	Thread _thread;
-	std::string _homePath;
-	std::string _dev;
-	std::vector<MulticastGroup> _multicastGroups;
-	unsigned int _mtu;
-	unsigned int _metric;
-	int _fd;
-	int _shutdownSignalPipe[2];
-	bool _utun;
-	volatile bool _enabled;
-};
-
-} // namespace ZeroTier
-
-#endif

+ 0 - 4
attic/README.md

@@ -1,4 +0,0 @@
-Retired Code and Miscellaneous Junk
-======
-
-This directory is for old code that isn't used but we don't want to lose track of, and for anything else random like debug scripts.

+ 0 - 18
attic/kubernetes_docs/.zerotierCliSettings

@@ -1,18 +0,0 @@
-{
-  "configVersion": 1,
-  "defaultCentral": "@my.zerotier.com",
-  "defaultController": "@my.zerotier.com",
-  "defaultOne": "@local",
-  "things": {
-    "local": {
-      "auth": "local_service_auth_token_replaced_automatically",
-      "type": "one",
-      "url": "http://127.0.0.1:9993/"
-    },
-    "my.zerotier.com": {
-      "auth": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
-      "type": "central",
-      "url": "https://my.zerotier.com/"
-    }
-  }
-}

+ 0 - 19
attic/kubernetes_docs/Dockerfile

@@ -1,19 +0,0 @@
-FROM node:4.4
-EXPOSE 8080/tcp 9993/udp
-
-# Install ZT network conf files
-RUN mkdir -p /var/lib/zerotier-one/networks.d
-ADD *.conf /var/lib/zerotier-one/networks.d/
-ADD *.conf /
-ADD zerotier-one /
-ADD zerotier-cli /
-ADD .zerotierCliSettings /
-
-# Install App
-ADD server.js /
-
-# script which will start/auth VM on ZT network
-ADD entrypoint.sh /
-RUN chmod -v +x /entrypoint.sh 
-
-CMD ["./entrypoint.sh"]

+ 0 - 150
attic/kubernetes_docs/README.md

@@ -1,150 +0,0 @@
-Kubernetes + ZeroTier
-====
-
-A self-authorizing Kubernetes cluster deployment over a private ZeroTier network.
-
-This is a quick tutorial for setting up a Kubernetes deployment which can self-authorize each new replica onto your private ZeroTier network with no additional configuration needed when you scale. The Kubernetes-specific instructions and content is based on the [hellonode](http://kubernetes.io/docs/hellonode/) tutorial. All of the files discussed below can be found [here]();
-
-
-
-## Preliminary tasks
-
-**Step 1: Go to [my.zerotier.com](https://my.zerotier.com) and generate a network controller API key. This key will be used by ZeroTier to automatically authorize new instances of your VMs to join your secure deployment network during replication.**
-
-**Step 2: Create a new `private` network. Take note of the network ID, henceforth: `nwid`**
-
-**Step 3: Follow the instructions from the [hellonode](ttp://kubernetes.io/docs/hellonode/) tutorial to set up your development system.**
-
-***
-## Construct docker image
-
-**Step 4: Create necessary files for inclusion into image, your resultant directory should contain:**
-
- - `ztkube/<nwid>.conf`
- - `ztkube/Dockerfile`
- - `ztkube/entrypoint.sh`
- - `ztkube/server.js`
- - `ztkube/zerotier-cli`
- - `ztkube/zerotier-one`
-
-Start by creating a build directory to copy all required files into `mkdir ztkube`. Then build the following:
- - `make one`
- - `make cli`
-
-Add the following files to the `ztkube` directory. These files will be compiled into the Docker image.
- 
- - Create an empty `<nwid>.conf` file to specify the private deployment network you created in *Step 2*:
-
- - Create a CLI tool config file `.zerotierCliSettings` which should only contain your network controller API key to authorize new devices on your network (the local service API key will be filled in automatically). In this example the default controller is hosted by us at [my.zerotier.com](https://my.zerotier.com). Alternatively, you can host your own network controller but you'll need to modify the CLI config file accordingly.
-
-```
-{
-  "configVersion": 1,
-  "defaultCentral": "@my.zerotier.com",
-  "defaultController": "@my.zerotier.com",
-  "defaultOne": "@local",
-  "things": {
-    "local": {
-      "auth": "local_service_auth_token_replaced_automatically",
-      "type": "one",
-      "url": "http://127.0.0.1:9993/"
-    },
-    "my.zerotier.com": {
-      "auth": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
-      "type": "central",
-      "url": "https://my.zerotier.com/"
-    }
-  }
-}
-```
-
-
- - Create a `Dockerfile` which will copy the ZeroTier service as well as the ZeroTier CLI to the image: 
-
-```
-FROM node:4.4
-EXPOSE 8080/tcp 9993/udp
-
-# Install ZT network conf files
-RUN mkdir -p /var/lib/zerotier-one/networks.d
-ADD *.conf /var/lib/zerotier-one/networks.d/
-ADD *.conf /
-ADD zerotier-one /
-ADD zerotier-cli /
-ADD .zerotierCliSettings /
-
-# Install App
-ADD server.js /
-
-# script which will start/auth VM on ZT network
-ADD entrypoint.sh /
-RUN chmod -v +x /entrypoint.sh 
-
-CMD ["./entrypoint.sh"]
-```
-
- - Create the `entrypoint.sh` script which will start the ZeroTier service in the VM, attempt to join your deployment network and automatically authorize the new VM if your network is set to private:
-
-```
-#!/bin/bash
-
-echo '*** ZeroTier-Kubernetes self-auth test script'
-chown -R daemon /var/lib/zerotier-one
-chgrp -R daemon /var/lib/zerotier-one
-su daemon -s /bin/bash -c '/zerotier-one -d -U -p9993 >>/tmp/zerotier-one.out 2>&1'
-dev=""
-nwconf=$(ls *.conf)
-nwid="${nwconf%.*}"
-
-sleep 10
-dev=$(cat /var/lib/zerotier-one/identity.public| cut -d ':' -f 1)
-
-echo '*** Joining'
-./zerotier-cli join "$nwid".conf
-# Fill out local service auth token
-AUTHTOKEN=$(cat /var/lib/zerotier-one/authtoken.secret)
-sed "s|\local_service_auth_token_replaced_automatically|${AUTHTOKEN}|" .zerotierCliSettings > /root/.zerotierCliSettings
-echo '*** Authorizing'
-./zerotier-cli net-auth @my.zerotier.com "$nwid" "$dev"
-echo '*** Cleaning up' # Remove controller auth token
-rm -rf .zerotierCliSettings /root/.zerotierCliSettings
-node server.js
-```
-
-**Step 5: Build the image:**
-
- - `docker build -t gcr.io/$PROJECT_ID/hello-node .`
-
-
-
-**Step 6: Push the docker image to your *Container Registry***
-
- - `gcloud docker push gcr.io/$PROJECT_ID/hello-node:v1`
-
-***
-## Deploy!
-
-**Step 7: Create Kubernetes Cluster**
-
- - `gcloud config set compute/zone us-central1-a`
-
- - `gcloud container clusters create hello-world`
-
- - `gcloud container clusters get-credentials hello-world`
-
-
-
-**Step 8: Create your pod**
-
- - `kubectl run hello-node --image=gcr.io/$PROJECT_ID/hello-node:v1 --port=8080`
-
-
-
-**Step 9: Scale**
-
- - `kubectl scale deployment hello-node --replicas=4`
-
-***
-## Verify
-
-Now, after a minute or so you can use `zerotier-cli net-members <nwid>` to show all of your VM instances on your ZeroTier deployment network. If you haven't [configured your local CLI](https://github.com/zerotier/ZeroTierOne/tree/dev/cli), you can simply log into [my.zerotier.com](https://my.zerotier.com), go to *Networks -> nwid* to check that your VMs are indeed members of your private network. You should also note that the `entrypoint.sh` script will automatically delete your network controller API key once it has authorized your VM. This is merely a security measure and can be removed if needed.

+ 0 - 23
attic/kubernetes_docs/entrypoint.sh

@@ -1,23 +0,0 @@
-#!/bin/bash
-
-echo '*** ZeroTier-Kubernetes self-auth test script'
-chown -R daemon /var/lib/zerotier-one
-chgrp -R daemon /var/lib/zerotier-one
-su daemon -s /bin/bash -c '/zerotier-one -d -U -p9993 >>/tmp/zerotier-one.out 2>&1'
-dev=""
-nwconf=$(ls *.conf)
-nwid="${nwconf%.*}"
-
-sleep 10
-dev=$(cat /var/lib/zerotier-one/identity.public| cut -d ':' -f 1)
-
-echo '*** Joining'
-./zerotier-cli join "$nwid".conf
-# Fill out local service auth token
-AUTHTOKEN=$(cat /var/lib/zerotier-one/authtoken.secret)
-sed "s|\local_service_auth_token_replaced_automatically|${AUTHTOKEN}|" .zerotierCliSettings > /root/.zerotierCliSettings
-echo '*** Authorizing'
-./zerotier-cli net-auth @my.zerotier.com "$nwid" "$dev"
-echo '*** Cleaning up' # Remove controller auth token
-rm -rf .zerotierCliSettings /root/.zerotierCliSettings
-node server.js

+ 0 - 8
attic/kubernetes_docs/server.js

@@ -1,8 +0,0 @@
-var http = require('http');
-var handleRequest = function(request, response) {
-  console.log('Received request for URL: ' + request.url);
-  response.writeHead(200);
-  response.end('Hello World!');
-};
-var www = http.createServer(handleRequest);
-www.listen(8080);

+ 0 - 25
attic/lat_lon_to_xyz.js

@@ -1,25 +0,0 @@
-'use strict'
-
-/* This is a utility to convert latitude/longitude into X,Y,Z coordinates as used by clustering. */
-
-if (process.argv.length !== 4) {
-  console.log('Usage: node lat_lon_to_xyz.js <latitude> <longitude');
-  process.exit(1);
-}
-
-var lat = parseFloat(process.argv[2])||0.0;
-var lon = parseFloat(process.argv[3])||0.0;
-
-var latRadians = lat * 0.01745329251994; // PI / 180
-var lonRadians = lon * 0.01745329251994; // PI / 180
-var cosLat = Math.cos(latRadians);
-
-console.log({
-  lat: lat,
-  lon: lon,
-  x: Math.round((-6371.0) * cosLat * Math.cos(lonRadians)),
-  y: Math.round(6371.0 * Math.sin(latRadians)),
-  z: Math.round(6371.0 * cosLat * Math.sin(lonRadians))
-});
-
-process.exit(0);

+ 3 - 1
attic/world/build.sh

@@ -1 +1,3 @@
-c++ -I.. -o mkworld ../node/C25519.cpp ../node/Salsa20.cpp ../node/SHA512.cpp ../node/Identity.cpp ../node/Utils.cpp ../node/InetAddress.cpp ../osdep/OSUtils.cpp mkworld.cpp
+#!/bin/bash
+
+c++ -std=c++11 -I../.. -I.. -O -o mkworld ../../node/C25519.cpp ../../node/Salsa20.cpp ../../node/SHA512.cpp ../../node/Identity.cpp ../../node/Utils.cpp ../../node/InetAddress.cpp ../../osdep/OSUtils.cpp mkworld.cpp -lm

+ 13 - 8
attic/world/mkworld.cpp

@@ -61,11 +61,11 @@ int main(int argc,char **argv)
 		current = previous;
 		OSUtils::writeFile("previous.c25519",previous);
 		OSUtils::writeFile("current.c25519",current);
-		fprintf(stderr,"INFO: created initial world keys: previous.c25519 and current.c25519 (both initially the same)"ZT_EOL_S);
+		fprintf(stderr,"INFO: created initial world keys: previous.c25519 and current.c25519 (both initially the same)" ZT_EOL_S);
 	}
 
 	if ((previous.length() != (ZT_C25519_PUBLIC_KEY_LEN + ZT_C25519_PRIVATE_KEY_LEN))||(current.length() != (ZT_C25519_PUBLIC_KEY_LEN + ZT_C25519_PRIVATE_KEY_LEN))) {
-		fprintf(stderr,"FATAL: previous.c25519 or current.c25519 empty or invalid"ZT_EOL_S);
+		fprintf(stderr,"FATAL: previous.c25519 or current.c25519 empty or invalid" ZT_EOL_S);
 		return 1;
 	}
 	C25519::Pair previousKP;
@@ -81,7 +81,12 @@ int main(int argc,char **argv)
 	std::vector<World::Root> roots;
 
 	const uint64_t id = ZT_WORLD_ID_EARTH;
-	const uint64_t ts = 1532555817048ULL; // July 25th, 2018
+	const uint64_t ts = 1562631342273ULL; // July 8th, 2019
+
+	roots.push_back(World::Root());
+	roots.back().identity = Identity("3a46f1bf30:0:76e66fab33e28549a62ee2064d1843273c2c300ba45c3f20bef02dbad225723bb59a9bb4b13535730961aeecf5a163ace477cceb0727025b99ac14a5166a09a3");
+	roots.back().stableEndpoints.push_back(InetAddress("185.180.13.82/9993"));
+	roots.back().stableEndpoints.push_back(InetAddress("2a02:6ea0:c815::/9993"));
 
 	// Alice
 	roots.push_back(World::Root());
@@ -118,7 +123,7 @@ int main(int argc,char **argv)
 	// END WORLD DEFINITION
 	// =========================================================================
 
-	fprintf(stderr,"INFO: generating and signing id==%llu ts==%llu"ZT_EOL_S,(unsigned long long)id,(unsigned long long)ts);
+	fprintf(stderr,"INFO: generating and signing id==%llu ts==%llu" ZT_EOL_S,(unsigned long long)id,(unsigned long long)ts);
 
 	World nw = World::make(World::TYPE_PLANET,id,ts,currentKP.pub,roots,previousKP);
 
@@ -127,15 +132,15 @@ int main(int argc,char **argv)
 	World testw;
 	testw.deserialize(outtmp,0);
 	if (testw != nw) {
-		fprintf(stderr,"FATAL: serialization test failed!"ZT_EOL_S);
+		fprintf(stderr,"FATAL: serialization test failed!" ZT_EOL_S);
 		return 1;
 	}
 
 	OSUtils::writeFile("world.bin",std::string((const char *)outtmp.data(),outtmp.size()));
-	fprintf(stderr,"INFO: world.bin written with %u bytes of binary world data."ZT_EOL_S,outtmp.size());
+	fprintf(stderr,"INFO: world.bin written with %u bytes of binary world data." ZT_EOL_S,outtmp.size());
 
 	fprintf(stdout,ZT_EOL_S);
-	fprintf(stdout,"#define ZT_DEFAULT_WORLD_LENGTH %u"ZT_EOL_S,outtmp.size());
+	fprintf(stdout,"#define ZT_DEFAULT_WORLD_LENGTH %u" ZT_EOL_S,outtmp.size());
 	fprintf(stdout,"static const unsigned char ZT_DEFAULT_WORLD[ZT_DEFAULT_WORLD_LENGTH] = {");
 	for(unsigned int i=0;i<outtmp.size();++i) {
 		const unsigned char *d = (const unsigned char *)outtmp.data();
@@ -143,7 +148,7 @@ int main(int argc,char **argv)
 			fprintf(stdout,",");
 		fprintf(stdout,"0x%.2x",(unsigned int)d[i]);
 	}
-	fprintf(stdout,"};"ZT_EOL_S);
+	fprintf(stdout,"};" ZT_EOL_S);
 
 	return 0;
 }

TEMPAT SAMPAH
attic/world/old/earth-2015-11-16.bin


TEMPAT SAMPAH
attic/world/old/earth-2015-11-20.bin


TEMPAT SAMPAH
attic/world/old/earth-2015-12-17.bin


TEMPAT SAMPAH
attic/world/old/earth-2016-01-13.bin


TEMPAT SAMPAH
attic/world/world.bin


File diff ditekan karena terlalu besar
+ 0 - 1
attic/world/world.c


+ 38 - 21
controller/DB.cpp

@@ -1,6 +1,6 @@
 /*
  * ZeroTier One - Network Virtualization Everywhere
- * Copyright (C) 2011-2018  ZeroTier, Inc.
+ * Copyright (C) 2011-2019  ZeroTier, Inc.  https://www.zerotier.com/
  *
  * 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
@@ -13,7 +13,15 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * --
+ *
+ * You can be released from the requirements of the license by purchasing
+ * a commercial license. Buying such a license is mandatory as soon as you
+ * develop commercial closed-source software that incorporates or links
+ * directly against ZeroTier software without disclosing the source code
+ * of your own application.
  */
 
 #include "DB.hpp"
@@ -96,8 +104,7 @@ void DB::cleanMember(nlohmann::json &member)
 	member.erase("lastRequestMetaData");
 }
 
-DB::DB(EmbeddedNetworkController *const nc,const Identity &myId,const char *path) :
-	_controller(nc),
+DB::DB(const Identity &myId,const char *path) :
 	_myId(myId),
 	_myAddress(myId.address()),
 	_path((path) ? path : "")
@@ -107,9 +114,7 @@ DB::DB(EmbeddedNetworkController *const nc,const Identity &myId,const char *path
 	_myAddressStr = tmp;
 }
 
-DB::~DB()
-{
-}
+DB::~DB() {}
 
 bool DB::get(const uint64_t networkId,nlohmann::json &network)
 {
@@ -221,7 +226,7 @@ void DB::networks(std::vector<uint64_t> &networks)
 		networks.push_back(n->first);
 }
 
-void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool push)
+void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool initialized)
 {
 	uint64_t memberId = 0;
 	uint64_t networkId = 0;
@@ -305,8 +310,12 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool pu
 			}
 		}
 
-		if (push)
-			_controller->onNetworkMemberUpdate(networkId,memberId);
+		if (initialized) {
+			std::lock_guard<std::mutex> ll(_changeListeners_l);
+			for(auto i=_changeListeners.begin();i!=_changeListeners.end();++i) {
+				(*i)->onNetworkMemberUpdate(networkId,memberId,memberConfig);
+			}
+		}
 	} else if (memberId) {
 		if (nw) {
 			std::lock_guard<std::mutex> l(nw->lock);
@@ -324,20 +333,24 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool pu
 		}
 	}
 
-	if ((push)&&((wasAuth)&&(!isAuth)&&(networkId)&&(memberId)))
-		_controller->onNetworkMemberDeauthorize(networkId,memberId);
+	if ((initialized)&&((wasAuth)&&(!isAuth)&&(networkId)&&(memberId))) {
+		std::lock_guard<std::mutex> ll(_changeListeners_l);
+		for(auto i=_changeListeners.begin();i!=_changeListeners.end();++i) {
+			(*i)->onNetworkMemberDeauthorize(networkId,memberId);
+		}
+	}
 }
 
-void DB::_networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool push)
+void DB::_networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool initialized)
 {
 	if (networkConfig.is_object()) {
 		const std::string ids = networkConfig["id"];
-		const uint64_t id = Utils::hexStrToU64(ids.c_str());
-		if (id) {
+		const uint64_t networkId = Utils::hexStrToU64(ids.c_str());
+		if (networkId) {
 			std::shared_ptr<_Network> nw;
 			{
 				std::lock_guard<std::mutex> l(_networks_l);
-				std::shared_ptr<_Network> &nw2 = _networks[id];
+				std::shared_ptr<_Network> &nw2 = _networks[networkId];
 				if (!nw2)
 					nw2.reset(new _Network);
 				nw = nw2;
@@ -346,15 +359,19 @@ void DB::_networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool
 				std::lock_guard<std::mutex> l2(nw->lock);
 				nw->config = networkConfig;
 			}
-			if (push)
-				_controller->onNetworkUpdate(id);
+			if (initialized) {
+				std::lock_guard<std::mutex> ll(_changeListeners_l);
+				for(auto i=_changeListeners.begin();i!=_changeListeners.end();++i) {
+					(*i)->onNetworkUpdate(networkId,networkConfig);
+				}
+			}
 		}
 	} else if (old.is_object()) {
 		const std::string ids = old["id"];
-		const uint64_t id = Utils::hexStrToU64(ids.c_str());
-		if (id) {
+		const uint64_t networkId = Utils::hexStrToU64(ids.c_str());
+		if (networkId) {
 			std::lock_guard<std::mutex> l(_networks_l);
-			_networks.erase(id);
+			_networks.erase(networkId);
 		}
 	}
 }

+ 32 - 30
controller/DB.hpp

@@ -1,6 +1,6 @@
 /*
  * ZeroTier One - Network Virtualization Everywhere
- * Copyright (C) 2011-2018  ZeroTier, Inc.
+ * Copyright (C) 2011-2019  ZeroTier, Inc.  https://www.zerotier.com/
  *
  * 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
@@ -13,7 +13,15 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * --
+ *
+ * You can be released from the requirements of the license by purchasing
+ * a commercial license. Buying such a license is mandatory as soon as you
+ * develop commercial closed-source software that incorporates or links
+ * directly against ZeroTier software without disclosing the source code
+ * of your own application.
  */
 
 #ifndef ZT_CONTROLLER_DB_HPP
@@ -32,22 +40,29 @@
 #include <unordered_set>
 #include <vector>
 #include <atomic>
+#include <mutex>
 
 #include "../ext/json/json.hpp"
 
-#define ZT_CONTROLLER_RETHINKDB_COMMIT_THREADS 2
-
 namespace ZeroTier
 {
 
-class EmbeddedNetworkController;
-
 /**
  * Base class with common infrastructure for all controller DB implementations
  */
 class DB
 {
 public:
+	class ChangeListener
+	{
+	public:
+		ChangeListener() {}
+		virtual ~ChangeListener() {}
+		virtual void onNetworkUpdate(uint64_t networkId,const nlohmann::json &network) {}
+		virtual void onNetworkMemberUpdate(uint64_t networkId,uint64_t memberId,const nlohmann::json &member) {}
+		virtual void onNetworkMemberDeauthorize(uint64_t networkId,uint64_t memberId) {}
+	};
+
 	struct NetworkSummaryInfo
 	{
 		NetworkSummaryInfo() : authorizedMemberCount(0),totalMemberCount(0),mostRecentDeauthTime(0) {}
@@ -58,27 +73,12 @@ public:
 		int64_t mostRecentDeauthTime;
 	};
 
-	/**
-	 * Ensure that all network fields are present
-	 */
 	static void initNetwork(nlohmann::json &network);
-
-	/**
-	 * Ensure that all member fields are present
-	 */
 	static void initMember(nlohmann::json &member);
-
-	/**
-	 * Remove old and temporary network fields
-	 */
 	static void cleanNetwork(nlohmann::json &network);
-
-	/**
-	 * Remove old and temporary member fields
-	 */
 	static void cleanMember(nlohmann::json &member);
 
-	DB(EmbeddedNetworkController *const nc,const Identity &myId,const char *path);
+	DB(const Identity &myId,const char *path);
 	virtual ~DB();
 
 	virtual bool waitForReady() = 0;
@@ -94,19 +94,20 @@ public:
 	bool get(const uint64_t networkId,nlohmann::json &network,const uint64_t memberId,nlohmann::json &member);
 	bool get(const uint64_t networkId,nlohmann::json &network,const uint64_t memberId,nlohmann::json &member,NetworkSummaryInfo &info);
 	bool get(const uint64_t networkId,nlohmann::json &network,std::vector<nlohmann::json> &members);
-
 	bool summary(const uint64_t networkId,NetworkSummaryInfo &info);
-
 	void networks(std::vector<uint64_t> &networks);
 
 	virtual void save(nlohmann::json *orig,nlohmann::json &record) = 0;
-
 	virtual void eraseNetwork(const uint64_t networkId) = 0;
-
 	virtual void eraseMember(const uint64_t networkId,const uint64_t memberId) = 0;
-
 	virtual void nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress) = 0;
 
+	inline void addListener(DB::ChangeListener *const listener)
+	{
+		std::lock_guard<std::mutex> l(_changeListeners_l);
+		_changeListeners.push_back(listener);
+	}
+
 protected:
 	struct _Network
 	{
@@ -120,18 +121,19 @@ protected:
 		std::mutex lock;
 	};
 
-	void _memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool push);
-	void _networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool push);
+	void _memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool initialized);
+	void _networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool initialized);
 	void _fillSummaryInfo(const std::shared_ptr<_Network> &nw,NetworkSummaryInfo &info);
 
-	EmbeddedNetworkController *const _controller;
 	const Identity _myId;
 	const Address _myAddress;
 	const std::string _path;
 	std::string _myAddressStr;
 
+	std::vector<DB::ChangeListener *> _changeListeners;
 	std::unordered_map< uint64_t,std::shared_ptr<_Network> > _networks;
 	std::unordered_multimap< uint64_t,uint64_t > _networkByMember;
+	mutable std::mutex _changeListeners_l;
 	mutable std::mutex _networks_l;
 };
 

+ 79 - 22
controller/EmbeddedNetworkController.cpp

@@ -1,6 +1,6 @@
 /*
  * ZeroTier One - Network Virtualization Everywhere
- * Copyright (C) 2011-2018  ZeroTier, Inc
+ * Copyright (C) 2011-2019  ZeroTier, Inc.  https://www.zerotier.com/
  *
  * 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
@@ -13,7 +13,15 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * --
+ *
+ * You can be released from the requirements of the license by purchasing
+ * a commercial license. Buying such a license is mandatory as soon as you
+ * develop commercial closed-source software that incorporates or links
+ * directly against ZeroTier software without disclosing the source code
+ * of your own application.
  */
 
 #include <stdint.h>
@@ -38,6 +46,11 @@
 #include "../version.h"
 
 #include "EmbeddedNetworkController.hpp"
+#include "LFDB.hpp"
+#include "FileDB.hpp"
+#ifdef ZT_CONTROLLER_USE_LIBPQ
+#include "PostgreSQL.hpp"
+#endif
 
 #include "../node/Node.hpp"
 #include "../node/CertificateOfMembership.hpp"
@@ -336,14 +349,14 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule)
 	} else if (t == "MATCH_IPV6_SOURCE") {
 		rule.t |= ZT_NETWORK_RULE_MATCH_IPV6_SOURCE;
 		InetAddress ip(OSUtils::jsonString(r["ip"],"::0").c_str());
-		ZT_FAST_MEMCPY(rule.v.ipv6.ip,reinterpret_cast<struct sockaddr_in6 *>(&ip)->sin6_addr.s6_addr,16);
+		memcpy(rule.v.ipv6.ip,reinterpret_cast<struct sockaddr_in6 *>(&ip)->sin6_addr.s6_addr,16);
 		rule.v.ipv6.mask = Utils::ntoh(reinterpret_cast<struct sockaddr_in6 *>(&ip)->sin6_port) & 0xff;
 		if (rule.v.ipv6.mask > 128) rule.v.ipv6.mask = 128;
 		return true;
 	} else if (t == "MATCH_IPV6_DEST") {
 		rule.t |= ZT_NETWORK_RULE_MATCH_IPV6_DEST;
 		InetAddress ip(OSUtils::jsonString(r["ip"],"::0").c_str());
-		ZT_FAST_MEMCPY(rule.v.ipv6.ip,reinterpret_cast<struct sockaddr_in6 *>(&ip)->sin6_addr.s6_addr,16);
+		memcpy(rule.v.ipv6.ip,reinterpret_cast<struct sockaddr_in6 *>(&ip)->sin6_addr.s6_addr,16);
 		rule.v.ipv6.mask = Utils::ntoh(reinterpret_cast<struct sockaddr_in6 *>(&ip)->sin6_port) & 0xff;
 		if (rule.v.ipv6.mask > 128) rule.v.ipv6.mask = 128;
 		return true;
@@ -456,11 +469,13 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule)
 
 } // anonymous namespace
 
-EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *dbPath) :
+EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *dbPath, int listenPort, MQConfig *mqc) :
 	_startTime(OSUtils::now()),
+	_listenPort(listenPort),
 	_node(node),
 	_path(dbPath),
-	_sender((NetworkController::Sender *)0)
+	_sender((NetworkController::Sender *)0),
+	_mqc(mqc)
 {
 }
 
@@ -478,12 +493,51 @@ void EmbeddedNetworkController::init(const Identity &signingId,Sender *sender)
 	_signingId = signingId;
 	_sender = sender;
 	_signingIdAddressString = signingId.address().toString(tmp);
-#ifdef ZT_CONTROLLER_USE_RETHINKDB
-	if ((_path.length() > 10)&&(_path.substr(0,10) == "rethinkdb:"))
-		_db.reset(new RethinkDB(this,_signingId,_path.c_str()));
-	else // else use FileDB after endif
+
+#ifdef ZT_CONTROLLER_USE_LIBPQ
+	if ((_path.length() > 9)&&(_path.substr(0,9) == "postgres:")) {
+		_db.reset(new PostgreSQL(_signingId,_path.substr(9).c_str(), _listenPort, _mqc));
+	} else {
 #endif
-		_db.reset(new FileDB(this,_signingId,_path.c_str()));
+
+	std::string lfJSON;
+	OSUtils::readFile((_path + ZT_PATH_SEPARATOR_S ".." ZT_PATH_SEPARATOR_S "local.conf").c_str(),lfJSON);
+	if (lfJSON.length() > 0) {
+		nlohmann::json lfConfig(OSUtils::jsonParse(lfJSON));
+		nlohmann::json &settings = lfConfig["settings"];
+		if (settings.is_object()) {
+			nlohmann::json &controllerDb = settings["controllerDb"];
+			if (controllerDb.is_object()) {
+				std::string type = controllerDb["type"];
+				if (type == "lf") {
+					std::string lfOwner = controllerDb["owner"];
+					std::string lfHost = controllerDb["host"];
+					int lfPort = controllerDb["port"];
+					bool storeOnlineState = controllerDb["storeOnlineState"];
+					if ((lfOwner.length())&&(lfHost.length())&&(lfPort > 0)&&(lfPort < 65536)) {
+						std::size_t pubHdrLoc = lfOwner.find("Public: ");
+						if ((pubHdrLoc > 0)&&((pubHdrLoc + 8) < lfOwner.length())) {
+							std::string lfOwnerPublic = lfOwner.substr(pubHdrLoc + 8);
+							std::size_t pubHdrEnd = lfOwnerPublic.find_first_of("\n\r\t ");
+							if (pubHdrEnd != std::string::npos) {
+								lfOwnerPublic = lfOwnerPublic.substr(0,pubHdrEnd);
+								_db.reset(new LFDB(_signingId,_path.c_str(),lfOwner.c_str(),lfOwnerPublic.c_str(),lfHost.c_str(),lfPort,storeOnlineState));
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+	if (!_db)
+		_db.reset(new FileDB(_signingId,_path.c_str()));
+
+	_db->addListener(this);
+
+#ifdef ZT_CONTROLLER_USE_LIBPQ
+	}
+#endif
+
 	_db->waitForReady();
 }
 
@@ -1043,6 +1097,7 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpDELETE(
 
 					json network,member;
 					_db->get(nwid,network,address,member);
+					_db->eraseMember(nwid, address);
 
 					{
 						std::lock_guard<std::mutex> l(_memberStatus_l);
@@ -1135,7 +1190,7 @@ void EmbeddedNetworkController::handleRemoteTrace(const ZT_RemoteTrace &rt)
 	}
 }
 
-void EmbeddedNetworkController::onNetworkUpdate(const uint64_t networkId)
+void EmbeddedNetworkController::onNetworkUpdate(const uint64_t networkId,const nlohmann::json &network)
 {
 	// Send an update to all members of the network that are online
 	const int64_t now = OSUtils::now();
@@ -1146,7 +1201,7 @@ void EmbeddedNetworkController::onNetworkUpdate(const uint64_t networkId)
 	}
 }
 
-void EmbeddedNetworkController::onNetworkMemberUpdate(const uint64_t networkId,const uint64_t memberId)
+void EmbeddedNetworkController::onNetworkMemberUpdate(const uint64_t networkId,const uint64_t memberId,const nlohmann::json &member)
 {
 	// Push update to member if online
 	try {
@@ -1511,13 +1566,13 @@ void EmbeddedNetworkController::_request(
 				const std::string ips = ipAssignments[i];
 				InetAddress ip(ips.c_str());
 
-				// IP assignments are only pushed if there is a corresponding local route. We also now get the netmask bits from
-				// this route, ignoring the netmask bits field of the assigned IP itself. Using that was worthless and a source
-				// of user error / poor UX.
 				int routedNetmaskBits = -1;
 				for(unsigned int rk=0;rk<nc->routeCount;++rk) {
-					if ( (!nc->routes[rk].via.ss_family) && (reinterpret_cast<const InetAddress *>(&(nc->routes[rk].target))->containsAddress(ip)) )
-						routedNetmaskBits = reinterpret_cast<const InetAddress *>(&(nc->routes[rk].target))->netmaskBits();
+					if (reinterpret_cast<const InetAddress *>(&(nc->routes[rk].target))->containsAddress(ip)) {
+						const int nb = (int)(reinterpret_cast<const InetAddress *>(&(nc->routes[rk].target))->netmaskBits());
+						if (nb > routedNetmaskBits)
+							routedNetmaskBits = nb;
+					}
 				}
 
 				if (routedNetmaskBits >= 0) {
@@ -1544,8 +1599,8 @@ void EmbeddedNetworkController::_request(
 				InetAddress ipRangeEnd(OSUtils::jsonString(pool["ipRangeEnd"],"").c_str());
 				if ( (ipRangeStart.ss_family == AF_INET6) && (ipRangeEnd.ss_family == AF_INET6) ) {
 					uint64_t s[2],e[2],x[2],xx[2];
-					ZT_FAST_MEMCPY(s,ipRangeStart.rawIpData(),16);
-					ZT_FAST_MEMCPY(e,ipRangeEnd.rawIpData(),16);
+					memcpy(s,ipRangeStart.rawIpData(),16);
+					memcpy(e,ipRangeEnd.rawIpData(),16);
 					s[0] = Utils::ntoh(s[0]);
 					s[1] = Utils::ntoh(s[1]);
 					e[0] = Utils::ntoh(e[0]);
@@ -1609,18 +1664,20 @@ void EmbeddedNetworkController::_request(
 				if ( (ipRangeStartIA.ss_family == AF_INET) && (ipRangeEndIA.ss_family == AF_INET) ) {
 					uint32_t ipRangeStart = Utils::ntoh((uint32_t)(reinterpret_cast<struct sockaddr_in *>(&ipRangeStartIA)->sin_addr.s_addr));
 					uint32_t ipRangeEnd = Utils::ntoh((uint32_t)(reinterpret_cast<struct sockaddr_in *>(&ipRangeEndIA)->sin_addr.s_addr));
+
 					if ((ipRangeEnd < ipRangeStart)||(ipRangeStart == 0))
 						continue;
 					uint32_t ipRangeLen = ipRangeEnd - ipRangeStart;
-
+					
 					// Start with the LSB of the member's address
 					uint32_t ipTrialCounter = (uint32_t)(identity.address().toInt() & 0xffffffff);
 
 					for(uint32_t k=ipRangeStart,trialCount=0;((k<=ipRangeEnd)&&(trialCount < 1000));++k,++trialCount) {
 						uint32_t ip = (ipRangeLen > 0) ? (ipRangeStart + (ipTrialCounter % ipRangeLen)) : ipRangeStart;
 						++ipTrialCounter;
-						if ((ip & 0x000000ff) == 0x000000ff)
+						if ((ip & 0x000000ff) == 0x000000ff) {
 							continue; // don't allow addresses that end in .255
+						}
 
 						// Check if this IP is within a local-to-Ethernet routed network
 						int routedNetmaskBits = -1;

+ 23 - 12
controller/EmbeddedNetworkController.hpp

@@ -1,6 +1,6 @@
 /*
  * ZeroTier One - Network Virtualization Everywhere
- * Copyright (C) 2011-2018  ZeroTier, Inc.
+ * Copyright (C) 2011-2019  ZeroTier, Inc.  https://www.zerotier.com/
  *
  * 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
@@ -13,7 +13,15 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * --
+ *
+ * You can be released from the requirements of the license by purchasing
+ * a commercial license. Buying such a license is mandatory as soon as you
+ * develop commercial closed-source software that incorporates or links
+ * directly against ZeroTier software without disclosing the source code
+ * of your own application.
  */
 
 #ifndef ZT_SQLITENETWORKCONTROLLER_HPP
@@ -43,23 +51,21 @@
 #include "../ext/json/json.hpp"
 
 #include "DB.hpp"
-#include "FileDB.hpp"
-#ifdef ZT_CONTROLLER_USE_RETHINKDB
-#include "RethinkDB.hpp"
-#endif
 
 namespace ZeroTier {
 
 class Node;
 
-class EmbeddedNetworkController : public NetworkController
+struct MQConfig;
+
+class EmbeddedNetworkController : public NetworkController,public DB::ChangeListener
 {
 public:
 	/**
 	 * @param node Parent node
 	 * @param dbPath Database path (file path or database credentials)
 	 */
-	EmbeddedNetworkController(Node *node,const char *dbPath);
+	EmbeddedNetworkController(Node *node,const char *dbPath, int listenPort, MQConfig *mqc = NULL);
 	virtual ~EmbeddedNetworkController();
 
 	virtual void init(const Identity &signingId,Sender *sender);
@@ -95,10 +101,9 @@ public:
 
 	void handleRemoteTrace(const ZT_RemoteTrace &rt);
 
-	// Called on update via POST or by JSONDB on external update of network or network member records
-	void onNetworkUpdate(const uint64_t networkId);
-	void onNetworkMemberUpdate(const uint64_t networkId,const uint64_t memberId);
-	void onNetworkMemberDeauthorize(const uint64_t networkId,const uint64_t memberId);
+	virtual void onNetworkUpdate(const uint64_t networkId,const nlohmann::json &network);
+	virtual void onNetworkMemberUpdate(const uint64_t networkId,const uint64_t memberId,const nlohmann::json &member);
+	virtual void onNetworkMemberDeauthorize(const uint64_t networkId,const uint64_t memberId);
 
 private:
 	void _request(uint64_t nwid,const InetAddress &fromAddr,uint64_t requestPacketId,const Identity &identity,const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData);
@@ -141,17 +146,23 @@ private:
 	};
 
 	const int64_t _startTime;
+	int _listenPort;
 	Node *const _node;
 	std::string _path;
 	Identity _signingId;
 	std::string _signingIdAddressString;
 	NetworkController::Sender *_sender;
+
 	std::unique_ptr<DB> _db;
 	BlockingQueue< _RQEntry * > _queue;
+
 	std::vector<std::thread> _threads;
 	std::mutex _threads_l;
+
 	std::unordered_map< _MemberStatusKey,_MemberStatus,_MemberStatusHash > _memberStatus;
 	std::mutex _memberStatus_l;
+
+	MQConfig *_mqc;
 };
 
 } // namespace ZeroTier

+ 97 - 17
controller/FileDB.cpp

@@ -1,6 +1,6 @@
 /*
  * ZeroTier One - Network Virtualization Everywhere
- * Copyright (C) 2011-2018  ZeroTier, Inc.
+ * Copyright (C) 2011-2019  ZeroTier, Inc.  https://www.zerotier.com/
  *
  * 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
@@ -13,7 +13,15 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * --
+ *
+ * You can be released from the requirements of the license by purchasing
+ * a commercial license. Buying such a license is mandatory as soon as you
+ * develop commercial closed-source software that incorporates or links
+ * directly against ZeroTier software without disclosing the source code
+ * of your own application.
  */
 
 #include "FileDB.hpp"
@@ -21,10 +29,12 @@
 namespace ZeroTier
 {
 
-FileDB::FileDB(EmbeddedNetworkController *const nc,const Identity &myId,const char *path) :
-	DB(nc,myId,path),
+FileDB::FileDB(const Identity &myId,const char *path) :
+	DB(myId,path),
 	_networksPath(_path + ZT_PATH_SEPARATOR_S + "network"),
-	_tracePath(_path + ZT_PATH_SEPARATOR_S + "trace")
+	_tracePath(_path + ZT_PATH_SEPARATOR_S + "trace"),
+	_onlineChanged(false),
+	_running(true)
 {
 	OSUtils::mkdir(_path.c_str());
 	OSUtils::lockDownFile(_path.c_str(),true);
@@ -61,9 +71,65 @@ FileDB::FileDB(EmbeddedNetworkController *const nc,const Identity &myId,const ch
 			} catch ( ... ) {}
 		}
 	}
+
+	_onlineUpdateThread = std::thread([this]() {
+		unsigned int cnt = 0;
+		while (this->_running) {
+			std::this_thread::sleep_for(std::chrono::microseconds(100));
+			if ((++cnt % 20) == 0) { // 5 seconds
+				std::lock_guard<std::mutex> l(this->_online_l);
+				if (!this->_running) return;
+				if (this->_onlineChanged) {
+					char p[4096],atmp[64];
+					for(auto nw=this->_online.begin();nw!=this->_online.end();++nw) {
+						OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "%.16llx-online.json",_networksPath.c_str(),(unsigned long long)nw->first);
+						FILE *f = fopen(p,"wb");
+						if (f) {
+							fprintf(f,"{");
+							const char *memberPrefix = "";
+							for(auto m=nw->second.begin();m!=nw->second.end();++m) {
+								fprintf(f,"%s\"%.10llx\":{" ZT_EOL_S,memberPrefix,(unsigned long long)m->first);
+								memberPrefix = ",";
+								InetAddress lastAddr;
+								const char *timestampPrefix = " ";
+								int cnt = 0;
+								for(auto ts=m->second.rbegin();ts!=m->second.rend();) {
+									if (cnt < 25) {
+										if (lastAddr != ts->second) {
+											lastAddr = ts->second;
+											fprintf(f,"%s\"%lld\":\"%s\"" ZT_EOL_S,timestampPrefix,(long long)ts->first,ts->second.toString(atmp));
+											timestampPrefix = ",";
+											++cnt;
+											++ts;
+										} else {
+											ts = std::map<int64_t,InetAddress>::reverse_iterator(m->second.erase(std::next(ts).base()));
+										}
+									} else {
+										ts = std::map<int64_t,InetAddress>::reverse_iterator(m->second.erase(std::next(ts).base()));
+									}
+								}
+								fprintf(f,"}");
+							}
+							fprintf(f,"}" ZT_EOL_S);
+							fclose(f);
+						}
+					}
+					this->_onlineChanged = false;
+				}
+			}
+		}
+	});
 }
 
-FileDB::~FileDB() {}
+FileDB::~FileDB()
+{
+	try {
+		_online_l.lock();
+		_running = false;
+		_online_l.unlock();
+		_onlineUpdateThread.join();
+	} catch ( ... ) {}
+}
 
 bool FileDB::waitForReady() { return true; }
 bool FileDB::isReady() { return true; }
@@ -86,14 +152,10 @@ void FileDB::save(nlohmann::json *orig,nlohmann::json &record)
 			if (nwid) {
 				nlohmann::json old;
 				get(nwid,old);
-
 				if ((!old.is_object())||(old != record)) {
-					OSUtils::ztsnprintf(p1,sizeof(p1),"%s" ZT_PATH_SEPARATOR_S "%.16llx.json.new",_networksPath.c_str(),nwid);
-					OSUtils::ztsnprintf(p2,sizeof(p2),"%s" ZT_PATH_SEPARATOR_S "%.16llx.json",_networksPath.c_str(),nwid);
+					OSUtils::ztsnprintf(p1,sizeof(p1),"%s" ZT_PATH_SEPARATOR_S "%.16llx.json",_networksPath.c_str(),nwid);
 					if (!OSUtils::writeFile(p1,OSUtils::jsonDump(record,-1)))
 						fprintf(stderr,"WARNING: controller unable to write to path: %s" ZT_EOL_S,p1);
-					OSUtils::rename(p1,p2);
-
 					_networkChanged(old,record,true);
 				}
 			}
@@ -103,10 +165,9 @@ void FileDB::save(nlohmann::json *orig,nlohmann::json &record)
 			if ((id)&&(nwid)) {
 				nlohmann::json network,old;
 				get(nwid,network,id,old);
-
 				if ((!old.is_object())||(old != record)) {
 					OSUtils::ztsnprintf(pb,sizeof(pb),"%s" ZT_PATH_SEPARATOR_S "%.16llx" ZT_PATH_SEPARATOR_S "member",_networksPath.c_str(),(unsigned long long)nwid);
-					OSUtils::ztsnprintf(p1,sizeof(p1),"%s" ZT_PATH_SEPARATOR_S "%.10llx.json.new",pb,(unsigned long long)id);
+					OSUtils::ztsnprintf(p1,sizeof(p1),"%s" ZT_PATH_SEPARATOR_S "%.10llx.json",pb,(unsigned long long)id);
 					if (!OSUtils::writeFile(p1,OSUtils::jsonDump(record,-1))) {
 						OSUtils::ztsnprintf(p2,sizeof(p2),"%s" ZT_PATH_SEPARATOR_S "%.16llx",_networksPath.c_str(),(unsigned long long)nwid);
 						OSUtils::mkdir(p2);
@@ -114,9 +175,6 @@ void FileDB::save(nlohmann::json *orig,nlohmann::json &record)
 						if (!OSUtils::writeFile(p1,OSUtils::jsonDump(record,-1)))
 							fprintf(stderr,"WARNING: controller unable to write to path: %s" ZT_EOL_S,p1);
 					}
-					OSUtils::ztsnprintf(p2,sizeof(p2),"%s" ZT_PATH_SEPARATOR_S "%.10llx.json",pb,(unsigned long long)id);
-					OSUtils::rename(p1,p2);
-
 					_memberChanged(old,record,true);
 				}
 			}
@@ -137,16 +195,38 @@ void FileDB::eraseNetwork(const uint64_t networkId)
 	char p[16384];
 	OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "%.16llx.json",_networksPath.c_str(),networkId);
 	OSUtils::rm(p);
+	OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "%.16llx-online.json",_networksPath.c_str(),networkId);
+	OSUtils::rm(p);
+	OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "%.16llx" ZT_PATH_SEPARATOR_S "member",_networksPath.c_str(),(unsigned long long)networkId);
+	OSUtils::rmDashRf(p);
 	_networkChanged(network,nullJson,true);
+	std::lock_guard<std::mutex> l(this->_online_l);
+	this->_online.erase(networkId);
+	this->_onlineChanged = true;
 }
 
 void FileDB::eraseMember(const uint64_t networkId,const uint64_t memberId)
 {
+	nlohmann::json network,member,nullJson;
+	get(networkId,network);
+	get(memberId,member);
+	char p[4096];
+	OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "%.16llx" ZT_PATH_SEPARATOR_S "member" ZT_PATH_SEPARATOR_S "%.10llx.json",_networksPath.c_str(),networkId,memberId);
+	OSUtils::rm(p);
+	_memberChanged(member,nullJson,true);
+	std::lock_guard<std::mutex> l(this->_online_l);
+	this->_online[networkId].erase(memberId);
+	this->_onlineChanged = true;
 }
 
 void FileDB::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress)
 {
-	// Nothing to do here right now in the filesystem store mode since we can just get this from the peer list
+	char mid[32],atmp[64];
+	OSUtils::ztsnprintf(mid,sizeof(mid),"%.10llx",(unsigned long long)memberId);
+	physicalAddress.toString(atmp);
+	std::lock_guard<std::mutex> l(this->_online_l);
+	this->_online[networkId][memberId][OSUtils::now()] = physicalAddress;
+	this->_onlineChanged = true;
 }
 
 } // namespace ZeroTier

+ 16 - 3
controller/FileDB.hpp

@@ -1,6 +1,6 @@
 /*
  * ZeroTier One - Network Virtualization Everywhere
- * Copyright (C) 2011-2018  ZeroTier, Inc.
+ * Copyright (C) 2011-2019  ZeroTier, Inc.  https://www.zerotier.com/
  *
  * 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
@@ -13,7 +13,15 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * --
+ *
+ * You can be released from the requirements of the license by purchasing
+ * a commercial license. Buying such a license is mandatory as soon as you
+ * develop commercial closed-source software that incorporates or links
+ * directly against ZeroTier software without disclosing the source code
+ * of your own application.
  */
 
 #ifndef ZT_CONTROLLER_FILEDB_HPP
@@ -27,7 +35,7 @@ namespace ZeroTier
 class FileDB : public DB
 {
 public:
-	FileDB(EmbeddedNetworkController *const nc,const Identity &myId,const char *path);
+	FileDB(const Identity &myId,const char *path);
 	virtual ~FileDB();
 
 	virtual bool waitForReady();
@@ -40,6 +48,11 @@ public:
 protected:
 	std::string _networksPath;
 	std::string _tracePath;
+	std::thread _onlineUpdateThread;
+	std::map< uint64_t,std::map<uint64_t,std::map<int64_t,InetAddress> > > _online;
+	std::mutex _online_l;
+	bool _onlineChanged;
+	bool _running;
 };
 
 } // namespace ZeroTier

+ 400 - 0
controller/LFDB.cpp

@@ -0,0 +1,400 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2019  ZeroTier, Inc.  https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * --
+ *
+ * You can be released from the requirements of the license by purchasing
+ * a commercial license. Buying such a license is mandatory as soon as you
+ * develop commercial closed-source software that incorporates or links
+ * directly against ZeroTier software without disclosing the source code
+ * of your own application.
+ */
+
+#include "LFDB.hpp"
+
+#include <thread>
+#include <chrono>
+#include <iostream>
+#include <sstream>
+
+#include "../osdep/OSUtils.hpp"
+#include "../ext/cpp-httplib/httplib.h"
+
+namespace ZeroTier
+{
+
+LFDB::LFDB(const Identity &myId,const char *path,const char *lfOwnerPrivate,const char *lfOwnerPublic,const char *lfNodeHost,int lfNodePort,bool storeOnlineState) :
+	DB(myId,path),
+	_myId(myId),
+	_lfOwnerPrivate((lfOwnerPrivate) ? lfOwnerPrivate : ""),
+	_lfOwnerPublic((lfOwnerPublic) ? lfOwnerPublic : ""),
+	_lfNodeHost((lfNodeHost) ? lfNodeHost : "127.0.0.1"),
+	_lfNodePort(((lfNodePort > 0)&&(lfNodePort < 65536)) ? lfNodePort : 9980),
+	_running(true),
+	_ready(false),
+	_storeOnlineState(storeOnlineState)
+{
+	_syncThread = std::thread([this]() {
+		char controllerAddress[24];
+		const uint64_t controllerAddressInt = _myId.address().toInt();
+		_myId.address().toString(controllerAddress);
+		std::string networksSelectorName("com.zerotier.controller.lfdb:"); networksSelectorName.append(controllerAddress); networksSelectorName.append("/network");
+		std::string membersSelectorName("com.zerotier.controller.lfdb:"); membersSelectorName.append(controllerAddress); membersSelectorName.append("/member");
+
+		// LF record masking key is the first 32 bytes of SHA512(controller private key) in hex,
+		// hiding record values from anything but the controller or someone who has its key.
+		uint8_t sha512pk[64];
+		_myId.sha512PrivateKey(sha512pk);
+		char maskingKey [128];
+		Utils::hex(sha512pk,32,maskingKey);
+
+		httplib::Client htcli(_lfNodeHost.c_str(),_lfNodePort,600);
+		int64_t timeRangeStart = 0;
+		while (_running) {
+			{
+				std::lock_guard<std::mutex> sl(_state_l);
+				for(auto ns=_state.begin();ns!=_state.end();++ns) {
+					if (ns->second.dirty) {
+						nlohmann::json network;
+						if (get(ns->first,network)) {
+							nlohmann::json newrec,selector0;
+							selector0["Name"] = networksSelectorName;
+							selector0["Ordinal"] = ns->first;
+							newrec["Selectors"].push_back(selector0);
+							newrec["Value"] = network.dump();
+							newrec["OwnerPrivate"] = _lfOwnerPrivate;
+							newrec["MaskingKey"] = maskingKey;
+							newrec["PulseIfUnchanged"] = true;
+							auto resp = htcli.Post("/makerecord",newrec.dump(),"application/json");
+							if (resp) {
+								if (resp->status == 200) {
+									ns->second.dirty = false;
+									printf("SET network %.16llx %s\n",ns->first,resp->body.c_str());
+								} else {
+									fprintf(stderr,"ERROR: LFDB: %d from node (create/update network): %s" ZT_EOL_S,resp->status,resp->body.c_str());
+								}
+							} else {
+								fprintf(stderr,"ERROR: LFDB: node is offline" ZT_EOL_S);
+							}
+						}
+					}
+
+					for(auto ms=ns->second.members.begin();ms!=ns->second.members.end();++ms) {
+						if ((_storeOnlineState)&&(ms->second.lastOnlineDirty)&&(ms->second.lastOnlineAddress)) {
+							nlohmann::json newrec,selector0,selector1,selectors,ip;
+							char tmp[1024],tmp2[128];
+							OSUtils::ztsnprintf(tmp,sizeof(tmp),"com.zerotier.controller.lfdb:%s/network/%.16llx/online",controllerAddress,(unsigned long long)ns->first);
+							ms->second.lastOnlineAddress.toIpString(tmp2);
+							selector0["Name"] = tmp;
+							selector0["Ordinal"] = ms->first;
+							selector1["Name"] = tmp2;
+							selector1["Ordinal"] = 0;
+							selectors.push_back(selector0);
+							selectors.push_back(selector1);
+							newrec["Selectors"] = selectors;
+							const uint8_t *const rawip = (const uint8_t *)ms->second.lastOnlineAddress.rawIpData();
+							switch(ms->second.lastOnlineAddress.ss_family) {
+								case AF_INET:
+									for(int j=0;j<4;++j)
+										ip.push_back((unsigned int)rawip[j]);
+									break;
+								case AF_INET6:
+									for(int j=0;j<16;++j)
+										ip.push_back((unsigned int)rawip[j]);
+									break;
+								default:
+									ip = tmp2; // should never happen since only IP transport is currently supported
+									break;
+							}
+							newrec["Value"] = ip;
+							newrec["OwnerPrivate"] = _lfOwnerPrivate;
+							newrec["MaskingKey"] = maskingKey;
+							newrec["Timestamp"] = ms->second.lastOnlineTime;
+							newrec["PulseIfUnchanged"] = true;
+							auto resp = htcli.Post("/makerecord",newrec.dump(),"application/json");
+							if (resp) {
+								if (resp->status == 200) {
+									ms->second.lastOnlineDirty = false;
+									printf("SET member online %.16llx %.10llx %s\n",ns->first,ms->first,resp->body.c_str());
+								} else {
+									fprintf(stderr,"ERROR: LFDB: %d from node (create/update member online status): %s" ZT_EOL_S,resp->status,resp->body.c_str());
+								}
+							} else {
+								fprintf(stderr,"ERROR: LFDB: node is offline" ZT_EOL_S);
+							}
+						}
+
+						if (ms->second.dirty) {
+							nlohmann::json network,member;
+							if (get(ns->first,network,ms->first,member)) {
+								nlohmann::json newrec,selector0,selector1,selectors;
+								selector0["Name"] = networksSelectorName;
+								selector0["Ordinal"] = ns->first;
+								selector1["Name"] = membersSelectorName;
+								selector1["Ordinal"] = ms->first;
+								selectors.push_back(selector0);
+								selectors.push_back(selector1);
+								newrec["Selectors"] = selectors;
+								newrec["Value"] = member.dump();
+								newrec["OwnerPrivate"] = _lfOwnerPrivate;
+								newrec["MaskingKey"] = maskingKey;
+								newrec["PulseIfUnchanged"] = true;
+								auto resp = htcli.Post("/makerecord",newrec.dump(),"application/json");
+								if (resp) {
+									if (resp->status == 200) {
+										ms->second.dirty = false;
+										printf("SET member %.16llx %.10llx %s\n",ns->first,ms->first,resp->body.c_str());
+									} else {
+										fprintf(stderr,"ERROR: LFDB: %d from node (create/update member): %s" ZT_EOL_S,resp->status,resp->body.c_str());
+									}
+								} else {
+									fprintf(stderr,"ERROR: LFDB: node is offline" ZT_EOL_S);
+								}
+							}
+						}
+					}
+				}
+			}
+
+			{
+				std::ostringstream query;
+				query
+					<< '{'
+						<< "\"Ranges\":[{"
+							<< "\"Name\":\"" << networksSelectorName << "\","
+							<< "\"Range\":[0,18446744073709551615]"
+						<< "}],"
+						<< "\"TimeRange\":[" << timeRangeStart << ",18446744073709551615],"
+						<< "\"MaskingKey\":\"" << maskingKey << "\","
+						<< "\"Owners\":[\"" << _lfOwnerPublic << "\"]"
+					<< '}';
+				auto resp = htcli.Post("/query",query.str(),"application/json");
+				if (resp) {
+					if (resp->status == 200) {
+						nlohmann::json results(OSUtils::jsonParse(resp->body));
+						if ((results.is_array())&&(results.size() > 0)) {
+							for(std::size_t ri=0;ri<results.size();++ri) {
+								nlohmann::json &rset = results[ri];
+								if ((rset.is_array())&&(rset.size() > 0)) {
+									nlohmann::json &result = rset[0];
+									if (result.is_object()) {
+										nlohmann::json &record = result["Record"];
+										if (record.is_object()) {
+											const std::string recordValue = result["Value"];
+											printf("GET network %s\n",recordValue.c_str());
+											nlohmann::json network(OSUtils::jsonParse(recordValue));
+											if (network.is_object()) {
+												const std::string idstr = network["id"];
+												const uint64_t id = Utils::hexStrToU64(idstr.c_str());
+												if ((id >> 24) == controllerAddressInt) { // sanity check
+
+													std::lock_guard<std::mutex> sl(_state_l);
+													_NetworkState &ns = _state[id];
+													if (!ns.dirty) {
+														nlohmann::json oldNetwork;
+														if (get(id,oldNetwork)) {
+															const uint64_t revision = network["revision"];
+															const uint64_t prevRevision = oldNetwork["revision"];
+															if (prevRevision < revision) {
+																_networkChanged(oldNetwork,network,timeRangeStart > 0);
+															}
+														} else {
+															nlohmann::json nullJson;
+															_networkChanged(nullJson,network,timeRangeStart > 0);
+														}
+													}
+
+												}
+											}
+										}
+									}
+								}
+							}
+						}
+					} else {
+						fprintf(stderr,"ERROR: LFDB: %d from node: %s" ZT_EOL_S,resp->status,resp->body.c_str());
+					}
+				} else {
+					fprintf(stderr,"ERROR: LFDB: node is offline" ZT_EOL_S);
+				}
+			}
+
+			{
+				std::ostringstream query;
+				query
+					<< '{'
+						<< "\"Ranges\":[{"
+							<< "\"Name\":\"" << networksSelectorName << "\","
+							<< "\"Range\":[0,18446744073709551615]"
+						<< "},{"
+							<< "\"Name\":\"" << membersSelectorName << "\","
+							<< "\"Range\":[0,18446744073709551615]"
+						<< "}],"
+						<< "\"TimeRange\":[" << timeRangeStart << ",18446744073709551615],"
+						<< "\"MaskingKey\":\"" << maskingKey << "\","
+						<< "\"Owners\":[\"" << _lfOwnerPublic << "\"]"
+					<< '}';
+				auto resp = htcli.Post("/query",query.str(),"application/json");
+				if (resp) {
+					if (resp->status == 200) {
+						nlohmann::json results(OSUtils::jsonParse(resp->body));
+						if ((results.is_array())&&(results.size() > 0)) {
+							for(std::size_t ri=0;ri<results.size();++ri) {
+								nlohmann::json &rset = results[ri];
+								if ((rset.is_array())&&(rset.size() > 0)) {
+									nlohmann::json &result = rset[0];
+									if (result.is_object()) {
+										nlohmann::json &record = result["Record"];
+										if (record.is_object()) {
+											const std::string recordValue = result["Value"];
+											printf("GET member %s\n",recordValue.c_str());
+											nlohmann::json member(OSUtils::jsonParse(recordValue));
+											if (member.is_object()) {
+												const std::string nwidstr = member["nwid"];
+												const std::string idstr = member["id"];
+												const uint64_t nwid = Utils::hexStrToU64(nwidstr.c_str());
+												const uint64_t id = Utils::hexStrToU64(idstr.c_str());
+												if ((id)&&((nwid >> 24) == controllerAddressInt)) { // sanity check
+
+													std::lock_guard<std::mutex> sl(_state_l);
+													auto ns = _state.find(nwid);
+													if ((ns == _state.end())||(!ns->second.members[id].dirty)) {
+														nlohmann::json network,oldMember;
+														if (get(nwid,network,id,oldMember)) {
+															const uint64_t revision = member["revision"];
+															const uint64_t prevRevision = oldMember["revision"];
+															if (prevRevision < revision)
+																_memberChanged(oldMember,member,timeRangeStart > 0);
+														}
+													} else {
+														nlohmann::json nullJson;
+														_memberChanged(nullJson,member,timeRangeStart > 0);
+													}
+
+												}
+											}
+										}
+									}
+								}
+							}
+						}
+					} else {
+						fprintf(stderr,"ERROR: LFDB: %d from node: %s" ZT_EOL_S,resp->status,resp->body.c_str());
+					}
+				} else {
+					fprintf(stderr,"ERROR: LFDB: node is offline" ZT_EOL_S);
+				}
+			}
+
+			timeRangeStart = time(nullptr) - 120; // start next query 2m before now to avoid losing updates
+			_ready = true;
+
+			for(int k=0;k<20;++k) { // 2s delay between queries for remotely modified networks or members
+				if (!_running)
+					return;
+				std::this_thread::sleep_for(std::chrono::milliseconds(100));
+			}
+		}
+	});
+}
+
+LFDB::~LFDB()
+{
+	_running = false;
+	_syncThread.join();
+}
+
+bool LFDB::waitForReady()
+{
+	while (!_ready) {
+		std::this_thread::sleep_for(std::chrono::milliseconds(100));
+	}
+	return true;
+}
+
+bool LFDB::isReady()
+{
+	return (_ready);
+}
+
+void LFDB::save(nlohmann::json *orig,nlohmann::json &record)
+{
+	if (orig) {
+		if (*orig != record) {
+			record["revision"] = OSUtils::jsonInt(record["revision"],0ULL) + 1;
+		}
+	} else {
+		record["revision"] = 1;
+	}
+
+	const std::string objtype = record["objtype"];
+	if (objtype == "network") {
+		const uint64_t nwid = OSUtils::jsonIntHex(record["id"],0ULL);
+		if (nwid) {
+			nlohmann::json old;
+			get(nwid,old);
+			if ((!old.is_object())||(old != record)) {
+				_networkChanged(old,record,true);
+				{
+					std::lock_guard<std::mutex> l(_state_l);
+					_state[nwid].dirty = true;
+				}
+			}
+		}
+	} else if (objtype == "member") {
+		const uint64_t nwid = OSUtils::jsonIntHex(record["nwid"],0ULL);
+		const uint64_t id = OSUtils::jsonIntHex(record["id"],0ULL);
+		if ((id)&&(nwid)) {
+			nlohmann::json network,old;
+			get(nwid,network,id,old);
+			if ((!old.is_object())||(old != record)) {
+				_memberChanged(old,record,true);
+				{
+					std::lock_guard<std::mutex> l(_state_l);
+					_state[nwid].members[id].dirty = true;
+				}
+			}
+		}
+	}
+}
+
+void LFDB::eraseNetwork(const uint64_t networkId)
+{
+	// TODO
+}
+
+void LFDB::eraseMember(const uint64_t networkId,const uint64_t memberId)
+{
+	// TODO
+}
+
+void LFDB::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress)
+{
+	std::lock_guard<std::mutex> l(_state_l);
+	auto nw = _state.find(networkId);
+	if (nw != _state.end()) {
+		auto m = nw->second.members.find(memberId);
+		if (m != nw->second.members.end()) {
+			m->second.lastOnlineTime = OSUtils::now();
+			if (physicalAddress)
+				m->second.lastOnlineAddress = physicalAddress;
+			m->second.lastOnlineDirty = true;
+		}
+	}
+}
+
+} // namespace ZeroTier

+ 102 - 0
controller/LFDB.hpp

@@ -0,0 +1,102 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2019  ZeroTier, Inc.  https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * --
+ *
+ * You can be released from the requirements of the license by purchasing
+ * a commercial license. Buying such a license is mandatory as soon as you
+ * develop commercial closed-source software that incorporates or links
+ * directly against ZeroTier software without disclosing the source code
+ * of your own application.
+ */
+
+#ifndef ZT_CONTROLLER_LFDB_HPP
+#define ZT_CONTROLLER_LFDB_HPP
+
+#include "DB.hpp"
+
+#include <mutex>
+#include <string>
+#include <unordered_map>
+#include <atomic>
+
+namespace ZeroTier {
+
+/**
+ * DB implementation for controller that stores data in LF
+ */
+class LFDB : public DB
+{
+public:
+	/**
+	 * @param myId Identity of controller node (with secret)
+	 * @param path Base path for ZeroTier node itself
+	 * @param lfOwnerPrivate LF owner private in PEM format
+	 * @param lfOwnerPublic LF owner public in @base62 format
+	 * @param lfNodeHost LF node host
+	 * @param lfNodePort LF node http (not https) port
+	 * @param storeOnlineState If true, store online/offline state and IP info in LF (a lot of data, only for private networks!)
+	 */
+	LFDB(const Identity &myId,const char *path,const char *lfOwnerPrivate,const char *lfOwnerPublic,const char *lfNodeHost,int lfNodePort,bool storeOnlineState);
+	virtual ~LFDB();
+
+	virtual bool waitForReady();
+	virtual bool isReady();
+	virtual void save(nlohmann::json *orig,nlohmann::json &record);
+	virtual void eraseNetwork(const uint64_t networkId);
+	virtual void eraseMember(const uint64_t networkId,const uint64_t memberId);
+	virtual void nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress);
+
+protected:
+	const Identity _myId;
+
+	std::string _lfOwnerPrivate,_lfOwnerPublic;
+	std::string _lfNodeHost;
+	int _lfNodePort;
+
+	struct _MemberState
+	{
+		_MemberState() :
+			lastOnlineAddress(),
+			lastOnlineTime(0),
+			dirty(false),
+			lastOnlineDirty(false) {}
+		InetAddress lastOnlineAddress;
+		int64_t lastOnlineTime;
+		bool dirty;
+		bool lastOnlineDirty;
+	};
+	struct _NetworkState
+	{
+		_NetworkState() :
+			members(),
+			dirty(false) {}
+		std::unordered_map<uint64_t,_MemberState> members;
+		bool dirty;
+	};
+	std::unordered_map<uint64_t,_NetworkState> _state;
+	std::mutex _state_l;
+
+	std::atomic_bool _running;
+	std::atomic_bool _ready;
+	std::thread _syncThread;
+	bool _storeOnlineState;
+};
+	
+} // namespace ZeroTier
+
+#endif

+ 1571 - 0
controller/PostgreSQL.cpp

@@ -0,0 +1,1571 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2019  ZeroTier, Inc.  https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * --
+ *
+ * You can be released from the requirements of the license by purchasing
+ * a commercial license. Buying such a license is mandatory as soon as you
+ * develop commercial closed-source software that incorporates or links
+ * directly against ZeroTier software without disclosing the source code
+ * of your own application.
+ */
+
+#ifdef ZT_CONTROLLER_USE_LIBPQ
+
+#include "PostgreSQL.hpp"
+#include "EmbeddedNetworkController.hpp"
+#include "RabbitMQ.hpp"
+#include "../version.h"
+
+#include <libpq-fe.h>
+#include <sstream>
+#include <amqp.h>
+#include <amqp_tcp_socket.h>
+
+using json = nlohmann::json;
+namespace {
+
+static const int DB_MINIMUM_VERSION = 5;
+
+static const char *_timestr()
+{
+	time_t t = time(0);
+	char *ts = ctime(&t);
+	char *p = ts;
+	if (!p)
+		return "";
+	while (*p) {
+		if (*p == '\n') {
+			*p = (char)0;
+			break;
+		}
+		++p;
+	}
+	return ts;
+}
+
+std::string join(const std::vector<std::string> &elements, const char * const separator)
+{
+	switch(elements.size()) {
+	case 0:
+		return "";
+	case 1:
+		return elements[0];
+	default:
+		std::ostringstream os;
+		std::copy(elements.begin(), elements.end()-1, std::ostream_iterator<std::string>(os, separator));
+		os << *elements.rbegin();
+		return os.str();
+	}
+}
+
+}
+
+using namespace ZeroTier;
+
+PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort, MQConfig *mqc)
+    : DB(myId, path)
+    , _ready(0)
+	, _connected(1)
+    , _run(1)
+    , _waitNoticePrinted(false)
+	, _listenPort(listenPort)
+	, _mqc(mqc)
+{
+	_connString = std::string(path) + " application_name=controller_" +_myAddressStr;
+
+	// Database Schema Version Check
+	PGconn *conn = getPgConn();
+	if (PQstatus(conn) != CONNECTION_OK) {
+		fprintf(stderr, "Bad Database Connection: %s", PQerrorMessage(conn));
+		exit(1);
+	}
+
+	PGresult *res = PQexec(conn, "SELECT version FROM ztc_database");
+	if (PQresultStatus(res) != PGRES_TUPLES_OK) {
+		fprintf(stderr, "Error determining database version");
+		exit(1);
+	}
+
+	if (PQntuples(res) != 1) {
+		fprintf(stderr, "Invalid number of db version tuples returned.");
+		exit(1);
+	}
+
+	int dbVersion = std::stoi(PQgetvalue(res, 0, 0));
+
+	if (dbVersion < DB_MINIMUM_VERSION) {
+		fprintf(stderr, "Central database schema version too low.  This controller version requires a minimum schema version of %d. Please upgrade your Central instance", DB_MINIMUM_VERSION);
+		exit(1);
+	}
+
+	PQclear(res);
+	res = NULL;
+	PQfinish(conn);
+	conn = NULL;
+
+	_readyLock.lock();
+	_heartbeatThread = std::thread(&PostgreSQL::heartbeat, this);
+	_membersDbWatcher = std::thread(&PostgreSQL::membersDbWatcher, this);
+	_networksDbWatcher = std::thread(&PostgreSQL::networksDbWatcher, this);
+	for (int i = 0; i < ZT_CENTRAL_CONTROLLER_COMMIT_THREADS; ++i) {
+		_commitThread[i] = std::thread(&PostgreSQL::commitThread, this);
+	}
+	_onlineNotificationThread = std::thread(&PostgreSQL::onlineNotificationThread, this);
+}
+
+PostgreSQL::~PostgreSQL()
+{
+	_run = 0;
+	std::this_thread::sleep_for(std::chrono::milliseconds(100));
+	
+	_heartbeatThread.join();
+	_membersDbWatcher.join();
+	_networksDbWatcher.join();
+	for (int i = 0; i < ZT_CENTRAL_CONTROLLER_COMMIT_THREADS; ++i) {
+		_commitThread[i].join();
+	}
+	_onlineNotificationThread.join();
+
+}
+
+
+bool PostgreSQL::waitForReady()
+{
+	while (_ready < 2) {
+		if (!_waitNoticePrinted) {
+			_waitNoticePrinted = true;
+			fprintf(stderr, "[%s] NOTICE: %.10llx controller PostgreSQL waiting for initial data download..." ZT_EOL_S, ::_timestr(), (unsigned long long)_myAddress.toInt());
+		}
+		_readyLock.lock();
+		_readyLock.unlock();
+	}
+	return true;
+}
+
+bool PostgreSQL::isReady()
+{
+	return ((_ready == 2)&&(_connected));
+}
+
+void PostgreSQL::save(nlohmann::json *orig, nlohmann::json &record)
+{
+	try {
+		if (!record.is_object()) {
+			return;
+		}
+		waitForReady();
+		if (orig) {
+			if (*orig != record) {
+				record["revision"] = OSUtils::jsonInt(record["revision"],0ULL) + 1;
+				_commitQueue.post(new nlohmann::json(record));
+			}
+		} else {
+			record["revision"] = 1;
+			_commitQueue.post(new nlohmann::json(record));
+		}
+	} catch (std::exception &e) {
+		fprintf(stderr, "Error on PostgreSQL::save: %s\n", e.what());
+	} catch (...) {
+		fprintf(stderr, "Unknown error on PostgreSQL::save\n");
+	}
+}
+
+void PostgreSQL::eraseNetwork(const uint64_t networkId)
+{
+	char tmp2[24];
+	waitForReady();
+	Utils::hex(networkId, tmp2);
+	json *tmp = new json();
+	(*tmp)["id"] = tmp2;
+	(*tmp)["objtype"] = "_delete_network";
+	_commitQueue.post(tmp);
+}
+
+void PostgreSQL::eraseMember(const uint64_t networkId, const uint64_t memberId) 
+{
+	char tmp2[24];
+	json *tmp = new json();
+	Utils::hex(networkId, tmp2);
+	(*tmp)["nwid"] = tmp2;
+	Utils::hex(memberId, tmp2);
+	(*tmp)["id"] = tmp2;
+	(*tmp)["objtype"] = "_delete_member";
+	_commitQueue.post(tmp);
+}
+
+void PostgreSQL::nodeIsOnline(const uint64_t networkId, const uint64_t memberId, const InetAddress &physicalAddress)
+{
+	std::lock_guard<std::mutex> l(_lastOnline_l);
+	std::pair<int64_t, InetAddress> &i = _lastOnline[std::pair<uint64_t,uint64_t>(networkId, memberId)];
+	i.first = OSUtils::now();
+	if (physicalAddress) {
+		i.second = physicalAddress;
+	}
+}
+
+void PostgreSQL::initializeNetworks(PGconn *conn)
+{
+	try {
+		if (PQstatus(conn) != CONNECTION_OK) {
+			fprintf(stderr, "Bad Database Connection: %s", PQerrorMessage(conn));
+			exit(1);
+		}
+
+		const char *params[1] = {
+			_myAddressStr.c_str()
+		};
+
+		PGresult *res = PQexecParams(conn, "SELECT id, EXTRACT(EPOCH FROM creation_time AT TIME ZONE 'UTC')*1000, capabilities, "
+			"enable_broadcast, EXTRACT(EPOCH FROM last_modified AT TIME ZONE 'UTC')*1000, mtu, multicast_limit, name, private, remote_trace_level, "
+			"remote_trace_target, revision, rules, tags, v4_assign_mode, v6_assign_mode FROM ztc_network "
+			"WHERE deleted = false AND controller_id = $1",
+			1,
+			NULL,
+			params,
+			NULL,
+			NULL,
+			0);
+		
+		if (PQresultStatus(res) != PGRES_TUPLES_OK) {
+			fprintf(stderr, "Networks Initialization Failed: %s", PQerrorMessage(conn));
+			PQclear(res);
+			exit(1);
+		}
+
+		int numRows = PQntuples(res);
+		for (int i = 0; i < numRows; ++i) {
+			json empty;
+			json config;
+
+			const char *nwidparam[1] = {
+				PQgetvalue(res, i, 0)
+			};
+
+			config["id"] = PQgetvalue(res, i, 0);
+			config["nwid"] = PQgetvalue(res, i, 0);
+			try {
+				config["creationTime"] = std::stoull(PQgetvalue(res, i, 1));
+			} catch (std::exception &e) {
+				config["creationTime"] = 0ULL;
+				//fprintf(stderr, "Error converting creation time: %s\n", PQgetvalue(res, i, 1));
+			}
+			config["capabilities"] = json::parse(PQgetvalue(res, i, 2));
+			config["enableBroadcast"] = (strcmp(PQgetvalue(res, i, 3),"t")==0);
+			try {
+				config["lastModified"] = std::stoull(PQgetvalue(res, i, 4));
+			} catch (std::exception &e) {
+				config["lastModified"] = 0ULL;
+				//fprintf(stderr, "Error converting last modified: %s\n", PQgetvalue(res, i, 4));
+			}
+			try {
+				config["mtu"] = std::stoi(PQgetvalue(res, i, 5));
+			} catch (std::exception &e) {
+				config["mtu"] = 2800;
+			}
+			try {
+				config["multicastLimit"] = std::stoi(PQgetvalue(res, i, 6));
+			} catch (std::exception &e) {
+				config["multicastLimit"] = 64;
+			}
+			config["name"] = PQgetvalue(res, i, 7);
+			config["private"] = (strcmp(PQgetvalue(res, i, 8),"t")==0);
+			try {
+				config["remoteTraceLevel"] = std::stoi(PQgetvalue(res, i, 9));
+			} catch (std::exception &e) {
+				config["remoteTraceLevel"] = 0;
+			}
+			config["remoteTraceTarget"] = PQgetvalue(res, i, 10);
+			try {
+				config["revision"] = std::stoull(PQgetvalue(res, i, 11));
+			} catch (std::exception &e) {
+				config["revision"] = 0ULL;
+				//fprintf(stderr, "Error converting revision: %s\n", PQgetvalue(res, i, 11));
+			}
+			config["rules"] = json::parse(PQgetvalue(res, i, 12));
+			config["tags"] = json::parse(PQgetvalue(res, i, 13));
+			config["v4AssignMode"] = json::parse(PQgetvalue(res, i, 14));
+			config["v6AssignMode"] = json::parse(PQgetvalue(res, i, 15));
+			config["objtype"] = "network";
+			config["ipAssignmentPools"] = json::array();
+			config["routes"] = json::array();
+
+			PGresult *r2 = PQexecParams(conn,
+				"SELECT host(ip_range_start), host(ip_range_end) FROM ztc_network_assignment_pool WHERE network_id = $1",
+				1,
+				NULL,
+				nwidparam,
+				NULL,
+				NULL,
+				0);
+			
+			if (PQresultStatus(r2) != PGRES_TUPLES_OK) {
+				fprintf(stderr, "ERROR: Error retreiving IP pools for network: %s\n", PQresultErrorMessage(r2));
+				PQclear(r2);
+				PQclear(res);
+				exit(1);
+			}
+
+			int n = PQntuples(r2);
+			for (int j = 0; j < n; ++j) {
+				json ip;
+				ip["ipRangeStart"] = PQgetvalue(r2, j, 0);
+				ip["ipRangeEnd"] = PQgetvalue(r2, j, 1);
+
+				config["ipAssignmentPools"].push_back(ip);
+			}
+
+			PQclear(r2);
+
+			r2 = PQexecParams(conn,
+				"SELECT host(address), bits, host(via) FROM ztc_network_route WHERE network_id = $1",
+				1,
+				NULL,
+				nwidparam,
+				NULL,
+				NULL,
+				0);
+
+			if (PQresultStatus(r2) != PGRES_TUPLES_OK) {
+				fprintf(stderr, "ERROR: Error retreiving routes for network: %s\n", PQresultErrorMessage(r2));
+				PQclear(r2);
+				PQclear(res);
+				exit(1);
+			}
+
+			n = PQntuples(r2);
+			for (int j = 0; j < n; ++j) {
+				std::string addr = PQgetvalue(r2, j, 0);
+				std::string bits = PQgetvalue(r2, j, 1);
+				std::string via = PQgetvalue(r2, j, 2);
+				json route;
+				route["target"] = addr + "/" + bits;
+
+				if (via == "NULL") {
+					route["via"] = nullptr;
+				} else {
+					route["via"] = via;
+				}
+				config["routes"].push_back(route);
+			}
+
+			PQclear(r2);
+			
+			_networkChanged(empty, config, false);
+		}
+
+		PQclear(res);
+
+		if (++this->_ready == 2) {
+			if (_waitNoticePrinted) {
+				fprintf(stderr,"[%s] NOTICE: %.10llx controller PostgreSQL data download complete." ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt());
+			}
+			_readyLock.unlock();
+		}
+	} catch (std::exception &e) {
+		fprintf(stderr, "ERROR: Error initializing networks: %s", e.what());
+		exit(-1);
+	}
+}
+
+void PostgreSQL::initializeMembers(PGconn *conn)
+{
+	try {
+		if (PQstatus(conn) != CONNECTION_OK) {
+			fprintf(stderr, "Bad Database Connection: %s", PQerrorMessage(conn));
+			exit(1);
+		}
+
+		const char *params[1] = {
+			_myAddressStr.c_str()
+		};
+
+		PGresult *res = PQexecParams(conn,
+			"SELECT m.id, m.network_id, m.active_bridge, m.authorized, m.capabilities, EXTRACT(EPOCH FROM m.creation_time AT TIME ZONE 'UTC')*1000, m.identity, "
+			"	EXTRACT(EPOCH FROM m.last_authorized_time AT TIME ZONE 'UTC')*1000, "
+			"	EXTRACT(EPOCH FROM m.last_deauthorized_time AT TIME ZONE 'UTC')*1000, "
+			"	m.remote_trace_level, m.remote_trace_target, m.tags, m.v_major, m.v_minor, m.v_rev, m.v_proto, "
+			"	m.no_auto_assign_ips, m.revision "
+			"FROM ztc_member m "
+			"INNER JOIN ztc_network n "
+			"	ON n.id = m.network_id "
+			"WHERE n.controller_id = $1 AND m.deleted = false",
+			1,
+			NULL,
+			params,
+			NULL,
+			NULL,
+			0);
+
+		if (PQresultStatus(res) != PGRES_TUPLES_OK) {
+			fprintf(stderr, "Member Initialization Failed: %s", PQerrorMessage(conn));
+			PQclear(res);
+			exit(1);
+		}
+
+		int numRows = PQntuples(res);
+		for (int i = 0; i < numRows; ++i) {
+			json empty;
+			json config;
+
+			std::string memberId(PQgetvalue(res, i, 0));
+			std::string networkId(PQgetvalue(res, i, 1));
+			std::string ctime = PQgetvalue(res, i, 5);
+			config["id"] = memberId;
+			config["nwid"] = networkId;
+			config["activeBridge"] = (strcmp(PQgetvalue(res, i, 2), "t") == 0);
+			config["authorized"] = (strcmp(PQgetvalue(res, i, 3), "t") == 0);
+			try {
+				config["capabilities"] = json::parse(PQgetvalue(res, i, 4));
+			} catch (std::exception &e) {
+				config["capabilities"] = json::array();
+			}
+			try {
+				config["creationTime"] = std::stoull(PQgetvalue(res, i, 5));
+			} catch (std::exception &e) {
+				config["creationTime"] = 0ULL;
+				//fprintf(stderr, "Error upding creation time (member): %s\n", PQgetvalue(res, i, 5));
+			}
+			config["identity"] = PQgetvalue(res, i, 6);
+			try {
+				config["lastAuthorizedTime"] = std::stoull(PQgetvalue(res, i, 7));
+			} catch(std::exception &e) {
+				config["lastAuthorizedTime"] = 0ULL;
+				//fprintf(stderr, "Error updating last auth time (member): %s\n", PQgetvalue(res, i, 7));
+			}
+			try {
+				config["lastDeauthorizedTime"] = std::stoull(PQgetvalue(res, i, 8));
+			} catch( std::exception &e) {
+				config["lastDeauthorizedTime"] = 0ULL;
+				//fprintf(stderr, "Error updating last deauth time (member): %s\n", PQgetvalue(res, i, 8));
+			}
+			try {
+				config["remoteTraceLevel"] = std::stoi(PQgetvalue(res, i, 9));
+			} catch (std::exception &e) {
+				config["remoteTraceLevel"] = 0;
+			}
+			config["remoteTraceTarget"] = PQgetvalue(res, i, 10);
+			try {
+				config["tags"] = json::parse(PQgetvalue(res, i, 11));
+			} catch (std::exception &e) {
+				config["tags"] = json::array();
+			}
+			try {
+				config["vMajor"] = std::stoi(PQgetvalue(res, i, 12));
+			} catch(std::exception &e) {
+				config["vMajor"] = -1;
+			}
+			try {
+				config["vMinor"] = std::stoi(PQgetvalue(res, i, 13));
+			} catch (std::exception &e) {
+				config["vMinor"] = -1;
+			}
+			try {
+				config["vRev"] = std::stoi(PQgetvalue(res, i, 14));
+			} catch (std::exception &e) {
+				config["vRev"] = -1;
+			}
+			try {
+				config["vProto"] = std::stoi(PQgetvalue(res, i, 15));
+			} catch (std::exception &e) {
+				config["vProto"] = -1;
+			}
+			config["noAutoAssignIps"] = (strcmp(PQgetvalue(res, i, 16), "t") == 0);
+			try {
+				config["revision"] = std::stoull(PQgetvalue(res, i, 17));
+			} catch (std::exception &e) {
+				config["revision"] = 0ULL;
+				//fprintf(stderr, "Error updating revision (member): %s\n", PQgetvalue(res, i, 17));
+			}
+			config["objtype"] = "member";
+			config["ipAssignments"] = json::array();
+			const char *p2[2] = {
+				memberId.c_str(),
+				networkId.c_str()
+			};
+
+			PGresult *r2 = PQexecParams(conn,
+				"SELECT DISTINCT address FROM ztc_member_ip_assignment WHERE member_id = $1 AND network_id = $2",
+				2,
+				NULL,
+				p2,
+				NULL,
+				NULL,
+				0);
+
+			if (PQresultStatus(r2) != PGRES_TUPLES_OK) {
+				fprintf(stderr, "Member Initialization Failed: %s", PQerrorMessage(conn));
+				PQclear(r2);
+				PQclear(res);
+				exit(1);
+			}
+
+			int n = PQntuples(r2);
+			for (int j = 0; j < n; ++j) {
+				config["ipAssignments"].push_back(PQgetvalue(r2, j, 0));
+			}
+
+			_memberChanged(empty, config, false);
+		}
+
+		PQclear(res);
+
+		if (++this->_ready == 2) {
+			if (_waitNoticePrinted) {
+				fprintf(stderr,"[%s] NOTICE: %.10llx controller PostgreSQL data download complete." ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt());
+			}
+			_readyLock.unlock();
+		}
+	} catch (std::exception &e) {
+		fprintf(stderr, "ERROR: Error initializing members: %s\n", e.what());
+		exit(-1);
+	}
+}
+
+void PostgreSQL::heartbeat()
+{
+	char publicId[1024];
+	char hostnameTmp[1024];
+	_myId.toString(false,publicId);
+	if (gethostname(hostnameTmp, sizeof(hostnameTmp))!= 0) {
+		hostnameTmp[0] = (char)0;
+	} else {
+		for (int i = 0; i < sizeof(hostnameTmp); ++i) {
+			if ((hostnameTmp[i] == '.')||(hostnameTmp[i] == 0)) {
+				hostnameTmp[i] = (char)0;
+				break;
+			}
+		}
+	}
+	const char *controllerId = _myAddressStr.c_str();
+	const char *publicIdentity = publicId;
+	const char *hostname = hostnameTmp;
+
+	PGconn *conn = getPgConn();
+	if (PQstatus(conn) == CONNECTION_BAD) {
+		fprintf(stderr, "Connection to database failed: %s\n", PQerrorMessage(conn));
+		PQfinish(conn);
+		exit(1);
+	}
+	while (_run == 1) {
+		if(PQstatus(conn) != CONNECTION_OK) {
+			fprintf(stderr, "%s heartbeat thread lost connection to Database\n", _myAddressStr.c_str());
+			PQfinish(conn);
+			exit(6);
+		}
+		if (conn) {
+			std::string major = std::to_string(ZEROTIER_ONE_VERSION_MAJOR);
+			std::string minor = std::to_string(ZEROTIER_ONE_VERSION_MINOR);
+			std::string rev = std::to_string(ZEROTIER_ONE_VERSION_REVISION);
+			std::string build = std::to_string(ZEROTIER_ONE_VERSION_BUILD);
+			std::string now = std::to_string(OSUtils::now());
+			std::string host_port = std::to_string(_listenPort);
+			std::string use_rabbitmq = (_mqc != NULL) ? "true" : "false";
+			const char *values[10] = {
+				controllerId,
+				hostname,
+				now.c_str(),
+				publicIdentity,
+				major.c_str(),
+				minor.c_str(),
+				rev.c_str(),
+				build.c_str(),
+				host_port.c_str(),
+				use_rabbitmq.c_str()
+			};
+
+			PGresult *res = PQexecParams(conn,
+				"INSERT INTO ztc_controller (id, cluster_host, last_alive, public_identity, v_major, v_minor, v_rev, v_build, host_port, use_rabbitmq) " 
+				"VALUES ($1, $2, TO_TIMESTAMP($3::double precision/1000), $4, $5, $6, $7, $8, $9, $10) "
+				"ON CONFLICT (id) DO UPDATE SET cluster_host = EXCLUDED.cluster_host, last_alive = EXCLUDED.last_alive, "
+				"public_identity = EXCLUDED.public_identity, v_major = EXCLUDED.v_major, v_minor = EXCLUDED.v_minor, "
+				"v_rev = EXCLUDED.v_rev, v_build = EXCLUDED.v_rev, host_port = EXCLUDED.host_port, "
+				"use_rabbitmq = EXCLUDED.use_rabbitmq",
+				10,       // number of parameters
+				NULL,    // oid field.   ignore
+				values,  // values for substitution
+				NULL, // lengths in bytes of each value
+				NULL,  // binary?
+				0);
+
+			if (PQresultStatus(res) != PGRES_COMMAND_OK) {
+				fprintf(stderr, "Heartbeat Update Failed: %s\n", PQresultErrorMessage(res));
+			}
+			PQclear(res);
+		}
+
+		std::this_thread::sleep_for(std::chrono::milliseconds(1000));
+	}
+
+	PQfinish(conn);
+	conn = NULL;
+}
+
+void PostgreSQL::membersDbWatcher()
+{
+	PGconn *conn = getPgConn(NO_OVERRIDE);
+	if (PQstatus(conn) == CONNECTION_BAD) {
+		fprintf(stderr, "Connection to database failed: %s\n", PQerrorMessage(conn));
+		PQfinish(conn);
+		exit(1);
+	}
+
+	initializeMembers(conn);
+
+	if (this->_mqc != NULL) {
+		PQfinish(conn);
+		conn = NULL;
+		_membersWatcher_RabbitMQ();
+	} else {
+		_membersWatcher_Postgres(conn);
+		PQfinish(conn);
+		conn = NULL;
+	}
+
+	if (_run == 1) {
+		fprintf(stderr, "ERROR: %s membersDbWatcher should still be running! Exiting Controller.\n", _myAddressStr.c_str());
+		exit(9);
+	}
+	fprintf(stderr, "Exited membersDbWatcher\n");
+}
+
+void PostgreSQL::_membersWatcher_Postgres(PGconn *conn) {
+	char buf[11] = {0};
+	std::string cmd = "LISTEN member_" + std::string(_myAddress.toString(buf));
+	PGresult *res = PQexec(conn, cmd.c_str());
+	if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) {
+		fprintf(stderr, "LISTEN command failed: %s\n", PQresultErrorMessage(res));
+		PQclear(res);
+		PQfinish(conn);
+		exit(1);
+	}
+
+	PQclear(res); res = NULL;
+
+	while(_run == 1) {
+		if (PQstatus(conn) != CONNECTION_OK) {
+			fprintf(stderr, "ERROR: Member Watcher lost connection to Postgres.");
+			exit(-1);
+		}
+		PGnotify *notify = NULL;
+		PQconsumeInput(conn);
+		while ((notify = PQnotifies(conn)) != NULL) {
+			//fprintf(stderr, "ASYNC NOTIFY of '%s' id:%s received\n", notify->relname, notify->extra);
+
+			try {
+				json tmp(json::parse(notify->extra));
+				json &ov = tmp["old_val"];
+				json &nv = tmp["new_val"];
+				json oldConfig, newConfig;
+				if (ov.is_object()) oldConfig = ov;
+				if (nv.is_object()) newConfig = nv;
+				if (oldConfig.is_object() || newConfig.is_object()) {
+					_memberChanged(oldConfig,newConfig,(this->_ready>=2));
+				}
+			} catch (...) {} // ignore bad records
+
+			free(notify);
+		}
+		std::this_thread::sleep_for(std::chrono::milliseconds(10));
+	}
+}
+
+void PostgreSQL::_membersWatcher_RabbitMQ() {
+	char buf[11] = {0};
+	std::string qname = "member_"+ std::string(_myAddress.toString(buf));
+	RabbitMQ rmq(_mqc, qname.c_str());
+	try {
+		rmq.init();
+	} catch (std::runtime_error &e) {
+		fprintf(stderr, "RABBITMQ ERROR: %s\n", e.what());
+		exit(11);
+	}
+	while (_run == 1) {
+		try {
+			std::string msg = rmq.consume();
+			// fprintf(stderr, "Got Member Update: %s\n", msg.c_str());
+			if (msg.empty()) {
+				continue;
+			}
+			json tmp(json::parse(msg));
+			json &ov = tmp["old_val"];
+			json &nv = tmp["new_val"];
+			json oldConfig, newConfig;
+			if (ov.is_object()) oldConfig = ov;
+			if (nv.is_object()) newConfig = nv;
+			if (oldConfig.is_object() || newConfig.is_object()) {
+				_memberChanged(oldConfig,newConfig,(this->_ready>=2));
+			}
+		} catch (std::runtime_error &e) {
+			fprintf(stderr, "RABBITMQ ERROR member change: %s\n", e.what());
+			break;
+		} catch(std::exception &e ) {
+			fprintf(stderr, "RABBITMQ ERROR member change: %s\n", e.what());
+		} catch(...) {
+			fprintf(stderr, "RABBITMQ ERROR member change: unknown error\n");
+        }
+	}
+}
+
+void PostgreSQL::networksDbWatcher()
+{
+	PGconn *conn = getPgConn(NO_OVERRIDE);
+	if (PQstatus(conn) == CONNECTION_BAD) {
+		fprintf(stderr, "Connection to database failed: %s\n", PQerrorMessage(conn));
+		PQfinish(conn);
+		exit(1);
+	}
+
+	initializeNetworks(conn);
+
+	if (this->_mqc != NULL) {
+		PQfinish(conn);
+		conn = NULL;
+		_networksWatcher_RabbitMQ();
+	} else {
+		_networksWatcher_Postgres(conn);
+		PQfinish(conn);
+		conn = NULL;
+	}
+	
+	if (_run == 1) {
+		fprintf(stderr, "ERROR: %s networksDbWatcher should still be running! Exiting Controller.\n", _myAddressStr.c_str());
+		exit(8);
+	}
+	fprintf(stderr, "Exited membersDbWatcher\n");
+}
+
+void PostgreSQL::_networksWatcher_Postgres(PGconn *conn) {
+	char buf[11] = {0};
+	std::string cmd = "LISTEN network_" + std::string(_myAddress.toString(buf));
+	PGresult *res = PQexec(conn, cmd.c_str());
+	if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) {
+		fprintf(stderr, "LISTEN command failed: %s\n", PQresultErrorMessage(res));
+		PQclear(res);
+		PQfinish(conn);
+		exit(1);
+	}
+
+	PQclear(res); res = NULL;
+
+	while(_run == 1) {
+		if (PQstatus(conn) != CONNECTION_OK) {
+			fprintf(stderr, "ERROR: Network Watcher lost connection to Postgres.");
+			exit(-1);
+		}
+		PGnotify *notify = NULL;
+		PQconsumeInput(conn);
+		while ((notify = PQnotifies(conn)) != NULL) {
+			//fprintf(stderr, "ASYNC NOTIFY of '%s' id:%s received\n", notify->relname, notify->extra);
+			try {
+				json tmp(json::parse(notify->extra));
+				json &ov = tmp["old_val"];
+				json &nv = tmp["new_val"];
+				json oldConfig, newConfig;
+				if (ov.is_object()) oldConfig = ov;
+				if (nv.is_object()) newConfig = nv;
+				if (oldConfig.is_object()||newConfig.is_object()) {
+					_networkChanged(oldConfig,newConfig,(this->_ready >= 2));
+				}
+			} catch (...) {} // ignore bad records
+			free(notify);
+		}
+		std::this_thread::sleep_for(std::chrono::milliseconds(10));
+	}
+}
+
+void PostgreSQL::_networksWatcher_RabbitMQ() {
+	char buf[11] = {0};
+	std::string qname = "network_"+ std::string(_myAddress.toString(buf));
+	RabbitMQ rmq(_mqc, qname.c_str());
+	try {
+		rmq.init();
+	} catch (std::runtime_error &e) {
+		fprintf(stderr, "RABBITMQ ERROR: %s\n", e.what());
+		exit(11);
+	}
+	while (_run == 1) {
+		try {
+			std::string msg = rmq.consume();
+			if (msg.empty()) {
+				continue;
+			}
+			// fprintf(stderr, "Got network update: %s\n", msg.c_str());
+			json tmp(json::parse(msg));
+			json &ov = tmp["old_val"];
+			json &nv = tmp["new_val"];
+			json oldConfig, newConfig;
+			if (ov.is_object()) oldConfig = ov;
+			if (nv.is_object()) newConfig = nv;
+			if (oldConfig.is_object()||newConfig.is_object()) {
+				_networkChanged(oldConfig,newConfig,(this->_ready >= 2));
+			}
+		} catch (std::runtime_error &e) {
+			fprintf(stderr, "RABBITMQ ERROR: %s\n", e.what());
+			break;
+		} catch (std::exception &e) {
+			fprintf(stderr, "RABBITMQ ERROR network watcher: %s\n", e.what());
+		} catch(...) {
+			fprintf(stderr, "RABBITMQ ERROR network watcher: unknown error\n");
+		}
+	}
+}
+
+void PostgreSQL::commitThread()
+{
+	PGconn *conn = getPgConn();
+	if (PQstatus(conn) == CONNECTION_BAD) {
+		fprintf(stderr, "ERROR: Connection to database failed: %s\n", PQerrorMessage(conn));
+		PQfinish(conn);
+		exit(1);
+	}
+
+	json *config = nullptr;
+	while(_commitQueue.get(config)&(_run == 1)) {
+		if (!config) {
+			continue;
+		}
+		if (PQstatus(conn) == CONNECTION_BAD) {
+			fprintf(stderr, "ERROR: Connection to database failed: %s\n", PQerrorMessage(conn));
+			PQfinish(conn);
+			delete config;
+			exit(1);
+		}
+		try { 
+			const std::string objtype = (*config)["objtype"];
+			if (objtype == "member") {
+				try {
+					std::string memberId = (*config)["id"];
+					std::string networkId = (*config)["nwid"];
+					std::string identity = (*config)["identity"];
+					std::string target = "NULL";
+
+					if (!(*config)["remoteTraceTarget"].is_null()) {
+						target = (*config)["remoteTraceTarget"];
+					}
+
+					std::string caps = OSUtils::jsonDump((*config)["capabilities"], -1);
+					std::string lastAuthTime = std::to_string((long long)(*config)["lastAuthorizedTime"]);
+					std::string lastDeauthTime = std::to_string((long long)(*config)["lastDeauthorizedTime"]);
+					std::string rtraceLevel = std::to_string((int)(*config)["remoteTraceLevel"]);
+					std::string rev = std::to_string((unsigned long long)(*config)["revision"]);
+					std::string tags = OSUtils::jsonDump((*config)["tags"], -1);
+					std::string vmajor = std::to_string((int)(*config)["vMajor"]);
+					std::string vminor = std::to_string((int)(*config)["vMinor"]);
+					std::string vrev = std::to_string((int)(*config)["vRev"]);
+					std::string vproto = std::to_string((int)(*config)["vProto"]);
+					const char *values[19] = {
+						memberId.c_str(),
+						networkId.c_str(),
+						((*config)["activeBridge"] ? "true" : "false"),
+						((*config)["authorized"] ? "true" : "false"),
+						caps.c_str(),
+						identity.c_str(),
+						lastAuthTime.c_str(),
+						lastDeauthTime.c_str(),
+						((*config)["noAutoAssignIps"] ? "true" : "false"),
+						rtraceLevel.c_str(),
+						(target == "NULL") ? NULL : target.c_str(),
+						rev.c_str(),
+						tags.c_str(),
+						vmajor.c_str(),
+						vminor.c_str(),
+						vrev.c_str(),
+						vproto.c_str()
+					};
+
+					PGresult *res = PQexecParams(conn,
+						"INSERT INTO ztc_member (id, network_id, active_bridge, authorized, capabilities, "
+						"identity, last_authorized_time, last_deauthorized_time, no_auto_assign_ips, "
+						"remote_trace_level, remote_trace_target, revision, tags, v_major, v_minor, v_rev, v_proto) "
+						"VALUES ($1, $2, $3, $4, $5, $6, "
+						"TO_TIMESTAMP($7::double precision/1000), TO_TIMESTAMP($8::double precision/1000), "
+						"$9, $10, $11, $12, $13, $14, $15, $16, $17) ON CONFLICT (network_id, id) DO UPDATE SET "
+						"active_bridge = EXCLUDED.active_bridge, authorized = EXCLUDED.authorized, capabilities = EXCLUDED.capabilities, "
+						"identity = EXCLUDED.identity, last_authorized_time = EXCLUDED.last_authorized_time, "
+						"last_deauthorized_time = EXCLUDED.last_deauthorized_time, no_auto_assign_ips = EXCLUDED.no_auto_assign_ips, "
+						"remote_trace_level = EXCLUDED.remote_trace_level, remote_trace_target = EXCLUDED.remote_trace_target, "
+						"revision = EXCLUDED.revision+1, tags = EXCLUDED.tags, v_major = EXCLUDED.v_major, "
+						"v_minor = EXCLUDED.v_minor, v_rev = EXCLUDED.v_rev, v_proto = EXCLUDED.v_proto",
+						17,
+						NULL,
+						values,
+						NULL,
+						NULL,
+						0);
+					
+					if (PQresultStatus(res) != PGRES_COMMAND_OK) {
+						fprintf(stderr, "ERROR: Error updating member: %s\n", PQresultErrorMessage(res));
+						fprintf(stderr, "%s", OSUtils::jsonDump(*config, 2).c_str());
+						PQclear(res);
+						delete config;
+						config = nullptr;
+						continue;
+					}
+
+					PQclear(res);
+
+					res = PQexec(conn, "BEGIN");
+					if (PQresultStatus(res) != PGRES_COMMAND_OK) {
+						fprintf(stderr, "ERROR: Error beginning transaction: %s\n", PQresultErrorMessage(res));
+						PQclear(res);
+						delete config;
+						config = nullptr;
+						continue;
+					}
+
+					PQclear(res);
+
+					const char *v2[2] = {
+						memberId.c_str(),
+						networkId.c_str()
+					};
+
+					res = PQexecParams(conn,
+						"DELETE FROM ztc_member_ip_assignment WHERE member_id = $1 AND network_id = $2",
+						2,
+						NULL,
+						v2,
+						NULL,
+						NULL,
+						0);
+
+					if (PQresultStatus(res) != PGRES_COMMAND_OK) {
+						fprintf(stderr, "ERROR: Error updating IP address assignments: %s\n", PQresultErrorMessage(res));
+						PQclear(res);
+						PQclear(PQexec(conn, "ROLLBACK"));;
+						delete config;
+						config = nullptr;
+						continue;
+					}
+
+					PQclear(res);
+
+					std::vector<std::string> assignments;
+					for (auto i = (*config)["ipAssignments"].begin(); i != (*config)["ipAssignments"].end(); ++i) {
+						std::string addr = *i;
+
+						if (std::find(assignments.begin(), assignments.end(), addr) != assignments.end()) {
+							continue;
+						}
+
+						const char *v3[3] = {
+							memberId.c_str(),
+							networkId.c_str(),
+							addr.c_str()
+						};
+
+						res = PQexecParams(conn,
+							"INSERT INTO ztc_member_ip_assignment (member_id, network_id, address) VALUES ($1, $2, $3)",
+							3,
+							NULL,
+							v3,
+							NULL,
+							NULL,
+							0);
+						
+						if (PQresultStatus(res) != PGRES_COMMAND_OK) {
+							fprintf(stderr, "ERROR: Error setting IP addresses for member: %s\n", PQresultErrorMessage(res));
+							PQclear(res);
+							PQclear(PQexec(conn, "ROLLBACK"));
+							break;;
+						}
+					}
+
+					res = PQexec(conn, "COMMIT");
+					if (PQresultStatus(res) != PGRES_COMMAND_OK) {
+						fprintf(stderr, "ERROR: Error committing ip address data: %s\n", PQresultErrorMessage(res));
+					}
+
+					PQclear(res);
+
+					const uint64_t nwidInt = OSUtils::jsonIntHex((*config)["nwid"], 0ULL);
+					const uint64_t memberidInt = OSUtils::jsonIntHex((*config)["id"], 0ULL);
+					if (nwidInt && memberidInt) {
+						nlohmann::json nwOrig;
+						nlohmann::json memOrig;
+
+						nlohmann::json memNew(*config);
+						
+						get(nwidInt, nwOrig, memberidInt, memOrig);
+				
+						_memberChanged(memOrig, memNew, (this->_ready>=2));
+					} else {
+						fprintf(stderr, "Can't notify of change.  Error parsing nwid or memberid: %lu-%lu\n", nwidInt, memberidInt);
+					}
+
+				} catch (std::exception &e) {
+					fprintf(stderr, "ERROR: Error updating member: %s\n", e.what());
+				}
+			} else if (objtype == "network") {
+				try {
+					std::string id = (*config)["id"];
+					std::string controllerId = _myAddressStr.c_str();
+					std::string name = (*config)["name"];
+					std::string remoteTraceTarget("NULL");
+					if (!(*config)["remoteTraceTarget"].is_null()) {
+						remoteTraceTarget = (*config)["remoteTraceTarget"];
+					}
+					std::string rulesSource = (*config)["rulesSource"];
+					std::string caps = OSUtils::jsonDump((*config)["capabilitles"], -1);
+					std::string now = std::to_string(OSUtils::now());
+					std::string mtu = std::to_string((int)(*config)["mtu"]);
+					std::string mcastLimit = std::to_string((int)(*config)["multicastLimit"]);
+					std::string rtraceLevel = std::to_string((int)(*config)["remoteTraceLevel"]);
+					std::string rules = OSUtils::jsonDump((*config)["rules"], -1);
+					std::string tags = OSUtils::jsonDump((*config)["tags"], -1);
+					std::string v4mode = OSUtils::jsonDump((*config)["v4AssignMode"],-1);
+					std::string v6mode = OSUtils::jsonDump((*config)["v6AssignMode"], -1);
+					bool enableBroadcast = (*config)["enableBroadcast"];
+					bool isPrivate = (*config)["private"];
+
+					const char *values[16] = {
+						id.c_str(),
+						controllerId.c_str(),
+						caps.c_str(),
+						enableBroadcast ? "true" : "false",
+						now.c_str(),
+						mtu.c_str(),
+						mcastLimit.c_str(),
+						name.c_str(),
+						isPrivate ? "true" : "false",
+						rtraceLevel.c_str(),
+						(remoteTraceTarget == "NULL" ? NULL : remoteTraceTarget.c_str()),
+						rules.c_str(),
+						rulesSource.c_str(),
+						tags.c_str(),
+						v4mode.c_str(),
+						v6mode.c_str(),
+					};
+
+					PGresult *res = PQexecParams(conn,
+						"UPDATE ztc_network SET controller_id = $2, capabilities = $3, enable_broadcast = $4, "
+						"last_updated = $5, mtu = $6, multicast_limit = $7, name = $8, private = $9, "
+						"remote_trace_level = $10, remote_trace_target = $11, rules = $12, rules_source = $13, "
+						"tags = $14, v4_assign_mode = $15, v6_assign_mode = $16 "
+						"WHERE id = $1",
+						16,
+						NULL,
+						values,
+						NULL,
+						NULL,
+						0);
+					
+					if (PQresultStatus(res) != PGRES_COMMAND_OK) {
+						fprintf(stderr, "ERROR: Error updating network record: %s\n", PQresultErrorMessage(res));
+						PQclear(res);
+						delete config;
+						config = nullptr;
+						continue;
+					}
+
+					PQclear(res);
+
+					res = PQexec(conn, "BEGIN");
+					if (PQresultStatus(res) != PGRES_COMMAND_OK) {
+						fprintf(stderr, "ERROR: Error beginnning transaction: %s\n", PQresultErrorMessage(res));
+						PQclear(res);
+						delete config;
+						config = nullptr;
+						continue;
+					}
+
+					PQclear(res);
+
+					const char *params[1] = {
+						id.c_str()
+					};
+					res = PQexecParams(conn, 
+						"DELETE FROM ztc_network_assignment_pool WHERE network_id = $1",
+						1,
+						NULL,
+						params,
+						NULL,
+						NULL,
+						0);
+					if (PQresultStatus(res) != PGRES_COMMAND_OK) {
+						fprintf(stderr, "ERROR: Error updating assignment pool: %s\n", PQresultErrorMessage(res));
+						PQclear(res);
+						PQclear(PQexec(conn, "ROLLBACK"));
+						delete config;
+						config = nullptr;
+						continue;
+					}
+
+					PQclear(res);
+
+					auto pool = (*config)["ipAssignmentPools"];
+					bool err = false;
+					for (auto i = pool.begin(); i != pool.end(); ++i) {
+						std::string start = (*i)["ipRangeStart"];
+						std::string end = (*i)["ipRangeEnd"];
+						const char *p[3] = {
+							id.c_str(),
+							start.c_str(),
+							end.c_str()
+						};
+
+						res = PQexecParams(conn,
+							"INSERT INTO ztc_network_assignment_pool (network_id, ip_range_start, ip_range_end) "
+							"VALUES ($1, $2, $3)",
+							3,
+							NULL,
+							p,
+							NULL,
+							NULL,
+							0);
+						if (PQresultStatus(res) != PGRES_COMMAND_OK) {
+							fprintf(stderr, "ERROR: Error updating assignment pool: %s\n", PQresultErrorMessage(res));
+							PQclear(res);
+							err = true;
+							break;
+						}
+						PQclear(res);
+					}
+					if (err) {
+						PQclear(PQexec(conn, "ROLLBACK"));
+						delete config;
+						config = nullptr;
+						continue;
+					}
+
+					res = PQexecParams(conn, 
+						"DELETE FROM ztc_network_route WHERE network_id = $1",
+						1,
+						NULL,
+						params,
+						NULL,
+						NULL,
+						0);
+
+					if (PQresultStatus(res) != PGRES_COMMAND_OK) {
+						fprintf(stderr, "ERROR: Error updating routes: %s\n", PQresultErrorMessage(res));
+						PQclear(res);
+						PQclear(PQexec(conn, "ROLLBACK"));
+						delete config;
+						config = nullptr;
+						continue;
+					}
+
+
+					auto routes = (*config)["routes"];
+					err = false;
+					for (auto i = routes.begin(); i != routes.end(); ++i) {
+						std::string t = (*i)["target"];
+						std::vector<std::string> target;
+						std::istringstream f(t);
+						std::string s;
+						while(std::getline(f, s, '/')) {
+							target.push_back(s);
+						}
+						if (target.empty() || target.size() != 2) {
+							continue;
+						}
+						std::string targetAddr = target[0];
+						std::string targetBits = target[1];
+						std::string via = "NULL";
+						if (!(*i)["via"].is_null()) {
+							via = (*i)["via"];
+						}
+
+						const char *p[4] = {
+							id.c_str(),
+							targetAddr.c_str(),
+							targetBits.c_str(),
+							(via == "NULL" ? NULL : via.c_str()),
+						};
+
+						res = PQexecParams(conn,
+							"INSERT INTO ztc_network_route (network_id, address, bits, via) VALUES ($1, $2, $3, $4)",
+							4,
+							NULL,
+							p,
+							NULL,
+							NULL,
+							0);
+
+						if (PQresultStatus(res) != PGRES_COMMAND_OK) {
+							fprintf(stderr, "ERROR: Error updating routes: %s\n", PQresultErrorMessage(res));
+							PQclear(res);
+							err = true;
+							break;
+						}
+						PQclear(res);
+					}
+					if (err) {
+						PQclear(PQexec(conn, "ROLLBACK"));
+						delete config;
+						config = nullptr;
+						continue;
+					}
+
+					res = PQexec(conn, "COMMIT");
+					if (PQresultStatus(res) != PGRES_COMMAND_OK) {
+						fprintf(stderr, "ERROR: Error committing network update: %s\n", PQresultErrorMessage(res));
+					}
+					PQclear(res);
+
+					const uint64_t nwidInt = OSUtils::jsonIntHex((*config)["nwid"], 0ULL);
+					if (nwidInt) {
+						nlohmann::json nwOrig;
+						nlohmann::json nwNew(*config);
+
+						get(nwidInt, nwOrig);
+
+						_networkChanged(nwOrig, nwNew, true);
+					} else {
+						fprintf(stderr, "Can't notify network changed: %lu\n", nwidInt);
+					}
+
+				} catch (std::exception &e) {
+					fprintf(stderr, "ERROR: Error updating member: %s\n", e.what());
+				}
+			} else if (objtype == "trace") {
+				fprintf(stderr, "ERROR: Trace not yet implemented");
+			} else if (objtype == "_delete_network") {
+				try {
+					std::string networkId = (*config)["nwid"];
+					const char *values[1] = {
+						networkId.c_str()
+					};
+					PGresult * res = PQexecParams(conn,
+						"UPDATE ztc_network SET deleted = true WHERE id = $1",
+						1,
+						NULL,
+						values,
+						NULL,
+						NULL,
+						0);
+					
+					if (PQresultStatus(res) != PGRES_COMMAND_OK) {
+						fprintf(stderr, "ERROR: Error deleting network: %s\n", PQresultErrorMessage(res));
+					}
+
+					PQclear(res);
+				} catch (std::exception &e) {
+					fprintf(stderr, "ERROR: Error deleting network: %s\n", e.what());
+				}
+			} else if (objtype == "_delete_member") {
+				try {
+					std::string memberId = (*config)["id"];
+					std::string networkId = (*config)["nwid"];
+
+					const char *values[2] = {
+						memberId.c_str(),
+						networkId.c_str()
+					};
+
+					PGresult *res = PQexecParams(conn,
+						"UPDATE ztc_member SET hidden = true, deleted = true WHERE id = $1 AND network_id = $2",
+						2,
+						NULL,
+						values,
+						NULL,
+						NULL,
+						0);
+
+					if (PQresultStatus(res) != PGRES_COMMAND_OK) {
+						fprintf(stderr, "ERROR: Error deleting member: %s\n", PQresultErrorMessage(res));
+					}
+
+					PQclear(res);
+				} catch (std::exception &e) {
+					fprintf(stderr, "ERROR: Error deleting member: %s\n", e.what());
+				}
+			} else {
+				fprintf(stderr, "ERROR: unknown objtype");
+			}
+		} catch (std::exception &e) {
+			fprintf(stderr, "ERROR: Error getting objtype: %s\n", e.what());
+		}
+		delete config;
+		config = nullptr;
+
+		std::this_thread::sleep_for(std::chrono::milliseconds(10));
+	}
+
+	PQfinish(conn);
+	if (_run == 1) {
+		fprintf(stderr, "ERROR: %s commitThread should still be running! Exiting Controller.\n", _myAddressStr.c_str());
+		exit(7);
+	}
+}
+
+void PostgreSQL::onlineNotificationThread()
+{
+	PGconn *conn = getPgConn();
+	if (PQstatus(conn) == CONNECTION_BAD) {
+		fprintf(stderr, "Connection to database failed: %s\n", PQerrorMessage(conn));
+		PQfinish(conn);
+		exit(1);
+	}
+	_connected = 1;
+
+	int64_t	lastUpdatedNetworkStatus = 0;
+	std::unordered_map< std::pair<uint64_t,uint64_t>,int64_t,_PairHasher > lastOnlineCumulative;
+	
+	while (_run == 1) {
+		if (PQstatus(conn) != CONNECTION_OK) {
+			fprintf(stderr, "ERROR: Online Notification thread lost connection to Postgres.");
+			PQfinish(conn);
+			exit(5);
+		}
+
+		// map used to send notifications to front end
+		std::unordered_map<std::string, std::vector<std::string>> updateMap;
+
+		std::unordered_map< std::pair<uint64_t,uint64_t>,std::pair<int64_t,InetAddress>,_PairHasher > lastOnline;
+		{
+			std::lock_guard<std::mutex> l(_lastOnline_l);
+			lastOnline.swap(_lastOnline);
+		}
+
+		PGresult *res = NULL;
+
+		std::stringstream memberUpdate;
+		memberUpdate << "INSERT INTO ztc_member_status (network_id, member_id, address, last_updated) VALUES ";
+		bool firstRun = true;
+		bool memberAdded = false;
+		for (auto i=lastOnline.begin(); i != lastOnline.end(); ++i) {
+			uint64_t nwid_i = i->first.first;
+			char nwidTmp[64];
+			char memTmp[64];
+			char ipTmp[64];
+			OSUtils::ztsnprintf(nwidTmp,sizeof(nwidTmp), "%.16llx", nwid_i);
+			OSUtils::ztsnprintf(memTmp,sizeof(memTmp), "%.10llx", i->first.second);
+
+			auto found = _networks.find(nwid_i);
+			if (found == _networks.end()) {
+				continue; // skip members trying to join non-existant networks
+			}
+			
+			std::string networkId(nwidTmp);
+			std::string memberId(memTmp);
+			
+			std::vector<std::string> &members = updateMap[networkId];
+			members.push_back(memberId);
+
+			lastOnlineCumulative[i->first] = i->second.first;
+			
+			
+			const char *qvals[2] = {
+				networkId.c_str(),
+				memberId.c_str()
+			};
+
+			res = PQexecParams(conn,
+				"SELECT id, network_id FROM ztc_member WHERE network_id = $1 AND id = $2",
+				2,
+				NULL,
+				qvals,
+				NULL,
+				NULL,
+				0);
+
+			if (PQresultStatus(res) != PGRES_TUPLES_OK) {
+				fprintf(stderr, "Member count failed: %s", PQerrorMessage(conn));
+				PQclear(res);
+				continue;
+			}
+
+			int nrows = PQntuples(res);
+			PQclear(res);
+
+			if (nrows == 1) {
+				int64_t ts = i->second.first;
+				std::string ipAddr = i->second.second.toIpString(ipTmp);
+				std::string timestamp = std::to_string(ts);
+				
+				if (firstRun) {
+					firstRun = false;
+				} else {
+					memberUpdate << ", ";
+				}
+
+				memberUpdate << "('" << networkId << "', '" << memberId << "', ";
+				if (ipAddr.empty()) {
+					memberUpdate << "NULL, ";
+				} else {
+					memberUpdate << "'" << ipAddr << "', ";
+				}
+				memberUpdate << "TO_TIMESTAMP(" << timestamp << "::double precision/1000))";
+				memberAdded = true;
+			} else if (nrows > 1) {
+				fprintf(stderr, "nrows > 1?!?");
+				continue;
+			} else {
+				continue;
+			}
+		}
+		memberUpdate << " ON CONFLICT (network_id, member_id) DO UPDATE SET address = EXCLUDED.address, last_updated = EXCLUDED.last_updated;";
+
+		if (memberAdded) {
+			res = PQexec(conn, memberUpdate.str().c_str());
+			if (PQresultStatus(res) != PGRES_COMMAND_OK) {
+				fprintf(stderr, "Multiple insert failed: %s", PQerrorMessage(conn));
+			}
+			PQclear(res);
+		}
+
+		const int64_t now = OSUtils::now();
+		if ((now - lastUpdatedNetworkStatus) > 10000) {
+			lastUpdatedNetworkStatus = now;
+
+			std::vector<std::pair<uint64_t, std::shared_ptr<_Network>>> networks;
+			{
+				std::lock_guard<std::mutex> l(_networks_l);
+				for (auto i = _networks.begin(); i != _networks.end(); ++i) {
+					networks.push_back(*i);
+				}
+			}
+
+			std::stringstream networkUpdate;
+			networkUpdate << "INSERT INTO ztc_network_status (network_id, bridge_count, authorized_member_count, online_member_count, total_member_count, last_modified) VALUES ";
+			bool nwFirstRun = true;
+			bool networkAdded = false;
+			for (auto i = networks.begin(); i != networks.end(); ++i) {
+				char tmp[64];
+				Utils::hex(i->first, tmp);
+
+				std::string networkId(tmp);
+
+				std::vector<std::string> &_notUsed = updateMap[networkId];
+				(void)_notUsed;
+
+				uint64_t authMemberCount = 0;
+				uint64_t totalMemberCount = 0;
+				uint64_t onlineMemberCount = 0;
+				uint64_t bridgeCount = 0;
+				uint64_t ts = now;
+				{
+					std::lock_guard<std::mutex> l2(i->second->lock);
+					authMemberCount = i->second->authorizedMembers.size();
+					totalMemberCount = i->second->members.size();
+					bridgeCount = i->second->activeBridgeMembers.size();
+					for (auto m=i->second->members.begin(); m != i->second->members.end(); ++m) {
+						auto lo = lastOnlineCumulative.find(std::pair<uint64_t,uint64_t>(i->first, m->first));
+						if (lo != lastOnlineCumulative.end()) {
+							if ((now - lo->second) <= (ZT_NETWORK_AUTOCONF_DELAY * 2)) {
+								++onlineMemberCount;
+							} else {
+								lastOnlineCumulative.erase(lo);
+							}
+						}
+					}
+				}
+
+				const char *nvals[1] = {
+					networkId.c_str()
+				};
+
+				res = PQexecParams(conn,
+					"SELECT id FROM ztc_network WHERE id = $1",
+					1,
+					NULL,
+					nvals,
+					NULL,
+					NULL,
+					0);
+
+				if (PQresultStatus(res) != PGRES_TUPLES_OK) {
+					fprintf(stderr, "Network lookup failed: %s", PQerrorMessage(conn));
+					PQclear(res);
+					continue;
+				}
+
+				int nrows = PQntuples(res);
+				PQclear(res);
+
+				if (nrows == 1) {
+					std::string bc = std::to_string(bridgeCount);
+					std::string amc = std::to_string(authMemberCount);
+					std::string omc = std::to_string(onlineMemberCount);
+					std::string tmc = std::to_string(totalMemberCount);
+					std::string timestamp = std::to_string(ts);
+
+					if (nwFirstRun) {
+						nwFirstRun = false;
+					} else {
+						networkUpdate << ", ";
+					}
+
+					networkUpdate << "('" << networkId << "', " << bc << ", " << amc << ", " << omc << ", " << tmc << ", "
+							<< "TO_TIMESTAMP(" << timestamp << "::double precision/1000))";
+
+					networkAdded = true;
+
+				} else if (nrows > 1) {
+					fprintf(stderr, "Number of networks > 1?!?!?");
+					continue;
+				} else {
+					continue;
+				}
+			}
+			networkUpdate << " ON CONFLICT (network_id) DO UPDATE SET bridge_count = EXCLUDED.bridge_count, "
+					<< "authorized_member_count = EXCLUDED.authorized_member_count, online_member_count = EXCLUDED.online_member_count, "
+					<< "total_member_count = EXCLUDED.total_member_count, last_modified = EXCLUDED.last_modified";
+			if (networkAdded) {
+				res = PQexec(conn, networkUpdate.str().c_str());
+				if (PQresultStatus(res) != PGRES_COMMAND_OK) {
+					fprintf(stderr, "Error during multiple network upsert: %s", PQresultErrorMessage(res));
+				}
+				PQclear(res);
+			}
+		}
+
+		// for (auto it = updateMap.begin(); it != updateMap.end(); ++it) {
+		// 	std::string networkId = it->first;
+		// 	std::vector<std::string> members = it->second;
+		// 	std::stringstream queryBuilder;
+
+		// 	std::string membersStr = ::join(members, ",");
+
+		// 	queryBuilder << "NOTIFY controller, '" << networkId << ":" << membersStr << "'";
+		// 	std::string query = queryBuilder.str();
+
+		// 	PGresult *res = PQexec(conn,query.c_str());
+		// 	if (PQresultStatus(res) != PGRES_COMMAND_OK) {
+		// 		fprintf(stderr, "ERROR: Error sending NOTIFY: %s\n", PQresultErrorMessage(res));
+		// 	}
+		// 	PQclear(res);
+		// }
+
+		std::this_thread::sleep_for(std::chrono::milliseconds(10));
+	}
+	fprintf(stderr, "%s: Fell out of run loop in onlineNotificationThread\n", _myAddressStr.c_str());
+	PQfinish(conn);
+	if (_run == 1) {
+		fprintf(stderr, "ERROR: %s onlineNotificationThread should still be running! Exiting Controller.\n", _myAddressStr.c_str());
+		exit(6);
+	}
+}
+
+PGconn *PostgreSQL::getPgConn(OverrideMode m) {
+	if (m == ALLOW_PGBOUNCER_OVERRIDE) {
+		char *connStr = getenv("PGBOUNCER_CONNSTR");
+		if (connStr != NULL) {
+			fprintf(stderr, "PGBouncer Override\n");
+			std::string conn(connStr);
+			conn += " application_name=controller-"; 
+			conn += _myAddressStr.c_str();
+			return PQconnectdb(conn.c_str());
+		}
+	}
+
+	return PQconnectdb(_connString.c_str());
+}
+#endif //ZT_CONTROLLER_USE_LIBPQ

+ 117 - 0
controller/PostgreSQL.hpp

@@ -0,0 +1,117 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2019  ZeroTier, Inc.  https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * --
+ *
+ * You can be released from the requirements of the license by purchasing
+ * a commercial license. Buying such a license is mandatory as soon as you
+ * develop commercial closed-source software that incorporates or links
+ * directly against ZeroTier software without disclosing the source code
+ * of your own application.
+ */
+ 
+#ifdef ZT_CONTROLLER_USE_LIBPQ
+
+#ifndef ZT_CONTROLLER_LIBPQ_HPP
+#define ZT_CONTROLLER_LIBPQ_HPP
+
+#include "DB.hpp"
+
+#define ZT_CENTRAL_CONTROLLER_COMMIT_THREADS 4
+
+extern "C" {
+    typedef struct pg_conn PGconn;
+}
+
+namespace ZeroTier
+{
+
+struct MQConfig;
+
+/**
+ * A controller database driver that talks to PostgreSQL
+ *
+ * This is for use with ZeroTier Central.  Others are free to build and use it
+ * but be aware taht we might change it at any time.
+ */
+class PostgreSQL : public DB
+{
+public:
+    PostgreSQL(const Identity &myId, const char *path, int listenPort, MQConfig *mqc = NULL);
+    virtual ~PostgreSQL();
+
+    virtual bool waitForReady();
+    virtual bool isReady();
+    virtual void save(nlohmann::json *orig, nlohmann::json &record);
+    virtual void eraseNetwork(const uint64_t networkId);
+    virtual void eraseMember(const uint64_t networkId, const uint64_t memberId);
+    virtual void nodeIsOnline(const uint64_t networkId, const uint64_t memberId, const InetAddress &physicalAddress);
+
+protected:
+    struct _PairHasher
+	{
+		inline std::size_t operator()(const std::pair<uint64_t,uint64_t> &p) const { return (std::size_t)(p.first ^ p.second); }
+	};
+
+private:
+    void initializeNetworks(PGconn *conn);
+    void initializeMembers(PGconn *conn);
+    void heartbeat();
+    void membersDbWatcher();
+    void _membersWatcher_Postgres(PGconn *conn);
+    void _membersWatcher_RabbitMQ();
+    void networksDbWatcher();
+    void _networksWatcher_Postgres(PGconn *conn);
+    void _networksWatcher_RabbitMQ();
+
+    void commitThread();
+    void onlineNotificationThread();
+
+    enum OverrideMode {
+        ALLOW_PGBOUNCER_OVERRIDE = 0,
+        NO_OVERRIDE = 1
+    };
+
+    PGconn * getPgConn( OverrideMode m = ALLOW_PGBOUNCER_OVERRIDE );
+
+    std::string _connString;
+
+    BlockingQueue<nlohmann::json *> _commitQueue;
+
+    std::thread _heartbeatThread;
+    std::thread _membersDbWatcher;
+    std::thread _networksDbWatcher;
+    std::thread _commitThread[ZT_CENTRAL_CONTROLLER_COMMIT_THREADS];
+    std::thread _onlineNotificationThread;
+
+	std::unordered_map< std::pair<uint64_t,uint64_t>,std::pair<int64_t,InetAddress>,_PairHasher > _lastOnline;
+
+    mutable std::mutex _lastOnline_l;
+    mutable std::mutex _readyLock;
+    std::atomic<int> _ready, _connected, _run;
+    mutable volatile bool _waitNoticePrinted;
+
+    int _listenPort;
+
+    MQConfig *_mqc;
+};
+
+}
+
+#endif // ZT_CONTROLLER_LIBPQ_HPP
+
+#endif // ZT_CONTROLLER_USE_LIBPQ

+ 2 - 10
controller/README.md

@@ -19,9 +19,9 @@ Since ZeroTier nodes are mobile and do not need static IPs, implementing high av
 
 ZeroTier network controllers can easily be run in Docker or other container systems. Since containers do not need to actually join networks, extra privilege options like "--device=/dev/net/tun --privileged" are not needed. You'll just need to map the local JSON API port of the running controller and allow it to access the Internet (over UDP/9993 at a minimum) so things can reach and query it.
 
-### RethinkDB Database Implementation
+### PostgreSQL Database Implementation
 
-The default controller stores its data in the filesystem in `controller.d` under ZeroTier's home folder. There's an alternative implementation that stores data in RethinkDB that can be built with `make central-controller`. Right now this is only guaranteed to build and run on Linux and is designed for use with [ZeroTier Central](https://my.zerotier.com/). You're welcome to use it but we don't "officially" support it for end-user use and it could change at any time.
+The default controller stores its data in the filesystem in `controller.d` under ZeroTier's home folder. There's an alternative implementation that stores data in PostgreSQL that can be built with `make central-controller`. Right now this is only guaranteed to build and run on Centos 7 Linux with PostgreSQL 10 installed via the [PostgreSQL Yum Repository](https://www.postgresql.org/download/linux/redhat/) and is designed for use with [ZeroTier Central](https://my.zerotier.com/). You're welcome to use it but we don't "officially" support it for end-user use and it could change at any time.
 
 ### Upgrading from Older (1.1.14 or earlier) Versions
 
@@ -208,14 +208,6 @@ Important notes about rules engine behavior:
 
 This returns a JSON object containing all member IDs as keys and their `memberRevisionCounter` values as values.
 
-#### `/controller/network/<network ID>/active`
-
- * Purpose: Get a set of all active members on this network
- * Methods: GET
- * Returns: { object }
-
-This returns an object containing all currently online members and the most recent `recentLog` entries for their last request.
-
 #### `/controller/network/<network ID>/member/<address>`
 
  * Purpose: Create, authorize, or remove a network member

+ 107 - 0
controller/RabbitMQ.cpp

@@ -0,0 +1,107 @@
+#include "RabbitMQ.hpp"
+
+#ifdef ZT_CONTROLLER_USE_LIBPQ
+
+#include <amqp.h>
+#include <amqp_tcp_socket.h>
+#include <stdexcept>
+#include <cstring>
+
+namespace ZeroTier
+{
+
+RabbitMQ::RabbitMQ(MQConfig *cfg, const char *queueName)
+    : _mqc(cfg)
+    , _qName(queueName)
+    , _socket(NULL)
+    , _status(0)
+{   
+}
+
+RabbitMQ::~RabbitMQ()
+{
+    amqp_channel_close(_conn, _channel, AMQP_REPLY_SUCCESS);
+    amqp_connection_close(_conn, AMQP_REPLY_SUCCESS);
+    amqp_destroy_connection(_conn);
+}
+
+void RabbitMQ::init()
+{
+    struct timeval tval;
+    memset(&tval, 0, sizeof(struct timeval));
+    tval.tv_sec = 5;
+
+    fprintf(stderr, "Initializing RabbitMQ %s\n", _qName);
+    _conn = amqp_new_connection();
+    _socket = amqp_tcp_socket_new(_conn);
+    if (!_socket) {
+        throw std::runtime_error("Can't create socket for RabbitMQ");
+    }
+    
+    _status = amqp_socket_open_noblock(_socket, _mqc->host, _mqc->port, &tval);
+    if (_status) {
+        throw std::runtime_error("Can't connect to RabbitMQ");
+    }
+    
+    amqp_rpc_reply_t r = amqp_login(_conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN,
+        _mqc->username, _mqc->password);
+    if (r.reply_type != AMQP_RESPONSE_NORMAL) {
+        throw std::runtime_error("RabbitMQ Login Error");
+    }
+
+    static int chan = 0;
+	{
+		Mutex::Lock l(_chan_m);
+    	_channel = ++chan;
+	}
+    amqp_channel_open(_conn, _channel);
+    r = amqp_get_rpc_reply(_conn);
+    if(r.reply_type != AMQP_RESPONSE_NORMAL) {
+        throw std::runtime_error("Error opening communication channel");
+    }
+    
+    _q = amqp_queue_declare(_conn, _channel, amqp_cstring_bytes(_qName), 0, 0, 0, 0, amqp_empty_table);
+    r = amqp_get_rpc_reply(_conn);
+    if (r.reply_type != AMQP_RESPONSE_NORMAL) {
+        throw std::runtime_error("Error declaring queue " + std::string(_qName));
+    }
+
+    amqp_basic_consume(_conn, _channel, amqp_cstring_bytes(_qName), amqp_empty_bytes, 0, 1, 0, amqp_empty_table);
+    r = amqp_get_rpc_reply(_conn);
+    if (r.reply_type != AMQP_RESPONSE_NORMAL) {
+        throw std::runtime_error("Error consuming queue " + std::string(_qName));
+    }
+    fprintf(stderr, "RabbitMQ Init OK %s\n", _qName);
+}
+
+std::string RabbitMQ::consume()
+{
+    amqp_rpc_reply_t res;
+    amqp_envelope_t envelope;
+    amqp_maybe_release_buffers(_conn);
+
+    struct timeval timeout;
+    timeout.tv_sec = 1;
+    timeout.tv_usec = 0;
+
+    res = amqp_consume_message(_conn, &envelope, &timeout, 0);
+    if (res.reply_type != AMQP_RESPONSE_NORMAL) {
+        if (res.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION && res.library_error == AMQP_STATUS_TIMEOUT) {
+            // timeout waiting for message.  Return empty string
+            return "";
+        } else {
+            throw std::runtime_error("Error getting message");
+        }
+    }
+
+    std::string msg(
+        (const char*)envelope.message.body.bytes,
+        envelope.message.body.len
+    );
+    amqp_destroy_envelope(&envelope);
+    return msg;
+}
+
+}
+
+#endif // ZT_CONTROLLER_USE_LIBPQ

+ 79 - 0
controller/RabbitMQ.hpp

@@ -0,0 +1,79 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2019  ZeroTier, Inc.  https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * --
+ *
+ * You can be released from the requirements of the license by purchasing
+ * a commercial license. Buying such a license is mandatory as soon as you
+ * develop commercial closed-source software that incorporates or links
+ * directly against ZeroTier software without disclosing the source code
+ * of your own application.
+ */
+#ifndef ZT_CONTROLLER_RABBITMQ_HPP
+#define ZT_CONTROLLER_RABBITMQ_HPP
+
+namespace ZeroTier
+{
+struct MQConfig {
+    const char *host;
+    int port;
+    const char *username;
+    const char *password;
+};
+}
+
+#ifdef ZT_CONTROLLER_USE_LIBPQ
+
+#include "../node/Mutex.hpp"
+
+#include <amqp.h>
+#include <amqp_tcp_socket.h>
+#include <string>
+
+namespace ZeroTier
+{
+
+class RabbitMQ {
+public:
+    RabbitMQ(MQConfig *cfg, const char *queueName);
+    ~RabbitMQ();
+
+    void init();
+
+    std::string consume();
+
+private:
+    MQConfig *_mqc;
+    const char *_qName;
+
+    amqp_socket_t *_socket;
+    amqp_connection_state_t _conn;
+    amqp_queue_declare_ok_t *_q;
+    int _status;
+
+    int _channel;
+
+	Mutex _chan_m;
+
+};
+
+}
+
+#endif // ZT_CONTROLLER_USE_LIBPQ
+
+#endif // ZT_CONTROLLER_RABBITMQ_HPP
+

+ 0 - 497
controller/RethinkDB.cpp

@@ -1,497 +0,0 @@
-/*
- * ZeroTier One - Network Virtualization Everywhere
- * Copyright (C) 2011-2018  ZeroTier, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-//#define ZT_CONTROLLER_USE_RETHINKDB
-
-#ifdef ZT_CONTROLLER_USE_RETHINKDB
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <time.h>
-
-#include "RethinkDB.hpp"
-#include "EmbeddedNetworkController.hpp"
-
-#include "../version.h"
-
-#include <chrono>
-#include <algorithm>
-#include <stdexcept>
-
-#include "../ext/librethinkdbxx/build/include/rethinkdb.h"
-
-namespace R = RethinkDB;
-using json = nlohmann::json;
-
-namespace ZeroTier {
-
-static const char *_timestr()
-{
-	time_t t = time(0);
-	char *ts = ctime(&t);
-	char *p = ts;
-	if (!p)
-		return "";
-	while (*p) {
-		if (*p == '\n') {
-			*p = (char)0;
-			break;
-		}
-		++p;
-	}
-	return ts;
-}
-
-RethinkDB::RethinkDB(EmbeddedNetworkController *const nc,const Identity &myId,const char *path) :
-	DB(nc,myId,path),
-	_ready(2), // two tables need to be synchronized before we're ready, so this is ready when it reaches 0
-	_run(1),
-	_waitNoticePrinted(false)
-{
-	// rethinkdb:host:port:db[:auth]
-	std::vector<std::string> ps(OSUtils::split(path,":","",""));
-	if ((ps.size() < 4)||(ps[0] != "rethinkdb"))
-		throw std::runtime_error("invalid rethinkdb database url");
-	_host = ps[1];
-	_port = Utils::strToInt(ps[2].c_str());
-	_db = ps[3];
-	if (ps.size() > 4)
-		_auth = ps[4];
-
-	_readyLock.lock();
-
-	_membersDbWatcher = std::thread([this]() {
-		try {
-			while (_run == 1) {
-				try {
-					std::unique_ptr<R::Connection> rdb(R::connect(this->_host,this->_port,this->_auth));
-					if (rdb) {
-						_membersDbWatcherConnection = (void *)rdb.get();
-						auto cur = R::db(this->_db).table("Member",R::optargs("read_mode","outdated")).get_all(this->_myAddressStr,R::optargs("index","controllerId")).changes(R::optargs("squash",0.05,"include_initial",true,"include_types",true,"include_states",true)).run(*rdb);
-						while (cur.has_next()) {
-							if (_run != 1) break;
-							json tmp(json::parse(cur.next().as_json()));
-							if ((tmp["type"] == "state")&&(tmp["state"] == "ready")) {
-								if (--this->_ready == 0) {
-									if (_waitNoticePrinted)
-										fprintf(stderr,"[%s] NOTICE: %.10llx controller RethinkDB data download complete." ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt());
-									this->_readyLock.unlock();
-								}
-							} else {
-								try {
-									json &ov = tmp["old_val"];
-									json &nv = tmp["new_val"];
-									json oldConfig,newConfig;
-									if (ov.is_object()) oldConfig = ov["config"];
-									if (nv.is_object()) newConfig = nv["config"];
-									if (oldConfig.is_object()||newConfig.is_object())
-										this->_memberChanged(oldConfig,newConfig,(this->_ready <= 0));
-								} catch ( ... ) {} // ignore bad records
-							}
-						}
-					}
-				} catch (std::exception &e) {
-					fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (member change stream): %s" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),e.what());
-				} catch (R::Error &e) {
-					fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (member change stream): %s" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),e.message.c_str());
-				} catch ( ... ) {
-					fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (member change stream): unknown exception" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt());
-				}
-				std::this_thread::sleep_for(std::chrono::milliseconds(250));
-			}
-		} catch ( ... ) {}
-	});
-
-	_networksDbWatcher = std::thread([this]() {
-		try {
-			while (_run == 1) {
-				try {
-					std::unique_ptr<R::Connection> rdb(R::connect(this->_host,this->_port,this->_auth));
-					if (rdb) {
-						_networksDbWatcherConnection = (void *)rdb.get();
-						auto cur = R::db(this->_db).table("Network",R::optargs("read_mode","outdated")).get_all(this->_myAddressStr,R::optargs("index","controllerId")).changes(R::optargs("squash",0.05,"include_initial",true,"include_types",true,"include_states",true)).run(*rdb);
-						while (cur.has_next()) {
-							if (_run != 1) break;
-							json tmp(json::parse(cur.next().as_json()));
-							if ((tmp["type"] == "state")&&(tmp["state"] == "ready")) {
-								if (--this->_ready == 0) {
-									if (_waitNoticePrinted)
-										fprintf(stderr,"[%s] NOTICE: %.10llx controller RethinkDB data download complete." ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt());
-									this->_readyLock.unlock();
-								}
-							} else {
-								try {
-									json &ov = tmp["old_val"];
-									json &nv = tmp["new_val"];
-									json oldConfig,newConfig;
-									if (ov.is_object()) oldConfig = ov["config"];
-									if (nv.is_object()) newConfig = nv["config"];
-									if (oldConfig.is_object()||newConfig.is_object())
-										this->_networkChanged(oldConfig,newConfig,(this->_ready <= 0));
-								} catch ( ... ) {} // ignore bad records
-							}
-						}
-					}
-				} catch (std::exception &e) {
-					fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (network change stream): %s" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),e.what());
-				} catch (R::Error &e) {
-					fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (network change stream): %s" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),e.message.c_str());
-				} catch ( ... ) {
-					fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (network change stream): unknown exception" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt());
-				}
-				std::this_thread::sleep_for(std::chrono::milliseconds(250));
-			}
-		} catch ( ... ) {}
-	});
-
-	for(int t=0;t<ZT_CONTROLLER_RETHINKDB_COMMIT_THREADS;++t) {
-		_commitThread[t] = std::thread([this]() {
-			try {
-				std::unique_ptr<R::Connection> rdb;
-				nlohmann::json *config = (nlohmann::json *)0;
-				while ((this->_commitQueue.get(config))&&(_run == 1)) {
-					if (!config)
-						continue;
-					nlohmann::json record;
-					const char *table = (const char *)0;
-					std::string deleteId;
-					try {
-						const std::string objtype = (*config)["objtype"];
-						if (objtype == "member") {
-							const std::string nwid = (*config)["nwid"];
-							const std::string id = (*config)["id"];
-							record["id"] = nwid + "-" + id;
-							record["controllerId"] = this->_myAddressStr;
-							record["networkId"] = nwid;
-							record["nodeId"] = id;
-							record["config"] = *config;
-							table = "Member";
-						} else if (objtype == "network") {
-							const std::string id = (*config)["id"];
-							record["id"] = id;
-							record["controllerId"] = this->_myAddressStr;
- 							record["config"] = *config;
-							table = "Network";
-						} else if (objtype == "trace") {
-							record = *config;
-							table = "RemoteTrace";
-						} else if (objtype == "_delete_network") {
-							deleteId = (*config)["id"];
-							table = "Network";
-						} else if (objtype == "_delete_member") {
-							deleteId = (*config)["nwid"];
-							deleteId.push_back('-');
-							const std::string tmp = (*config)["id"];
-							deleteId.append(tmp);
-							table = "Member";
-						}
-					} catch (std::exception &e) {
-						fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (insert/update record creation): %s" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),e.what());
-						table = (const char *)0;
-					} catch (R::Error &e) {
-						fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (insert/update record creation): %s" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),e.message.c_str());
-						table = (const char *)0;
-					} catch ( ... ) {
-						fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (insert/update record creation): unknown exception" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt());
-						table = (const char *)0;
-					}
-					delete config;
-					if (!table)
-						continue;
-					const std::string jdump(OSUtils::jsonDump(record,-1));
-
-					while (_run == 1) {
-						try {
-							if (!rdb)
-								rdb = R::connect(this->_host,this->_port,this->_auth);
-							if (rdb) {
-								if (deleteId.length() > 0) {
-									//printf("DELETE: %s" ZT_EOL_S,deleteId.c_str());
-									R::db(this->_db).table(table).get(deleteId).delete_().run(*rdb);
-								} else {
-									//printf("UPSERT: %s" ZT_EOL_S,record.dump().c_str());
-									R::db(this->_db).table(table).insert(R::Datum::from_json(jdump),R::optargs("conflict","update","return_changes",false)).run(*rdb);
-								}
-								break;
-							} else {
-								fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (insert/update): connect failed (will retry)" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt());
-								rdb.reset();
-							}
-						} catch (std::exception &e) {
-							fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (insert/update): %s [%s]" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),e.what(),jdump.c_str());
-							rdb.reset();
-						} catch (R::Error &e) {
-							fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (insert/update): %s [%s]" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),e.message.c_str(),jdump.c_str());
-							rdb.reset();
-						} catch ( ... ) {
-							fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (insert/update): unknown exception [%s]" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),jdump.c_str());
-							rdb.reset();
-						}
-						std::this_thread::sleep_for(std::chrono::milliseconds(250));
-					}
-				}
-			} catch (std::exception &e) {
-				fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (insert/update outer loop): %s" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),e.what());
-			} catch (R::Error &e) {
-				fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (insert/update outer loop): %s" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),e.message.c_str());
-			} catch ( ... ) {
-				fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (insert/update outer loop): unknown exception" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt());
-			}
-		});
-	}
-
-	_onlineNotificationThread = std::thread([this]() {
-		int64_t lastUpdatedNetworkStatus = 0;
-		std::unordered_map< std::pair<uint64_t,uint64_t>,int64_t,_PairHasher > lastOnlineCumulative;
-		try {
-			std::unique_ptr<R::Connection> rdb;
-			while (_run == 1) {
-				try {
-					if (!rdb) {
-						_connected = 0;
-						rdb = R::connect(this->_host,this->_port,this->_auth);
-					}
-
-					if (rdb) {
-						_connected = 1;
-						R::Array batch;
-						R::Object tmpobj;
-
-						std::unordered_map< std::pair<uint64_t,uint64_t>,std::pair<int64_t,InetAddress>,_PairHasher > lastOnline;
-						{
-							std::lock_guard<std::mutex> l(_lastOnline_l);
-							lastOnline.swap(_lastOnline);
-						}
-
-						for(auto i=lastOnline.begin();i!=lastOnline.end();++i) {
-							lastOnlineCumulative[i->first] = i->second.first;
-							char tmp[64],tmp2[64];
-							OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.16llx-%.10llx",i->first.first,i->first.second);
-							tmpobj["id"] = tmp;
-							tmpobj["ts"] = i->second.first;
-							tmpobj["phy"] = i->second.second.toIpString(tmp2);
-							batch.emplace_back(tmpobj);
-							if (batch.size() >= 1024) {
-								R::db(this->_db).table("MemberStatus",R::optargs("read_mode","outdated")).insert(batch,R::optargs("conflict","update")).run(*rdb);
-								batch.clear();
-							}
-						}
-						if (batch.size() > 0) {
-							R::db(this->_db).table("MemberStatus",R::optargs("read_mode","outdated")).insert(batch,R::optargs("conflict","update")).run(*rdb);
-							batch.clear();
-						}
-						tmpobj.clear();
-
-						const int64_t now = OSUtils::now();
-						if ((now - lastUpdatedNetworkStatus) > 10000) {
-							lastUpdatedNetworkStatus = now;
-
-							std::vector< std::pair< uint64_t,std::shared_ptr<_Network> > > networks;
-							{
-								std::lock_guard<std::mutex> l(_networks_l);
-								networks.reserve(_networks.size() + 1);
-								for(auto i=_networks.begin();i!=_networks.end();++i)
-									networks.push_back(*i);
-							}
-
-							for(auto i=networks.begin();i!=networks.end();++i) {
-								char tmp[64];
-								Utils::hex(i->first,tmp);
-								tmpobj["id"] = tmp;
-								{
-									std::lock_guard<std::mutex> l2(i->second->lock);
-									tmpobj["authorizedMemberCount"] = i->second->authorizedMembers.size();
-									tmpobj["totalMemberCount"] = i->second->members.size();
-									unsigned long onlineMemberCount = 0;
-									for(auto m=i->second->members.begin();m!=i->second->members.end();++m) {
-										auto lo = lastOnlineCumulative.find(std::pair<uint64_t,uint64_t>(i->first,m->first));
-										if (lo != lastOnlineCumulative.end()) {
-											if ((now - lo->second) <= (ZT_NETWORK_AUTOCONF_DELAY * 2))
-												++onlineMemberCount;
-											else lastOnlineCumulative.erase(lo);
-										}
-									}
-									tmpobj["onlineMemberCount"] = onlineMemberCount;
-									tmpobj["bridgeCount"] = i->second->activeBridgeMembers.size();
-									tmpobj["ts"] = now;
-								}
-								batch.emplace_back(tmpobj);
-								if (batch.size() >= 1024) {
-									R::db(this->_db).table("NetworkStatus",R::optargs("read_mode","outdated")).insert(batch,R::optargs("conflict","update")).run(*rdb);
-									batch.clear();
-								}
-							}
-							if (batch.size() > 0) {
-								R::db(this->_db).table("NetworkStatus",R::optargs("read_mode","outdated")).insert(batch,R::optargs("conflict","update")).run(*rdb);
-								batch.clear();
-							}
-						}
-					}
-				} catch (std::exception &e) {
-					fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (node status update): %s" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),e.what());
-					rdb.reset();
-				} catch (R::Error &e) {
-					fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (node status update): %s" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt(),e.message.c_str());
-					rdb.reset();
-				} catch ( ... ) {
-					fprintf(stderr,"[%s] ERROR: %.10llx controller RethinkDB (node status update): unknown exception" ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt());
-					rdb.reset();
-				}
-				std::this_thread::sleep_for(std::chrono::milliseconds(250));
-			}
-		} catch ( ... ) {}
-	});
-
-	_heartbeatThread = std::thread([this]() {
-		try {
-			R::Object controllerRecord;
-			std::unique_ptr<R::Connection> rdb;
-
-			{
-				char publicId[1024];
-				//char secretId[1024];
-				char hostname[1024];
-				this->_myId.toString(false,publicId);
-				//this->_myId.toString(true,secretId);
-				if (gethostname(hostname,sizeof(hostname)) != 0) {
-					hostname[0] = (char)0;
-				} else {
-					for(int i=0;i<sizeof(hostname);++i) {
-						if ((hostname[i] == '.')||(hostname[i] == 0)) {
-							hostname[i] = (char)0;
-							break;
-						}
-					}
-				}
-				controllerRecord["id"] = this->_myAddressStr.c_str();
-				controllerRecord["publicIdentity"] = publicId;
-				//controllerRecord["secretIdentity"] = secretId;
-				if (hostname[0])
-					controllerRecord["clusterHost"] = hostname;
-				controllerRecord["vMajor"] = ZEROTIER_ONE_VERSION_MAJOR;
-				controllerRecord["vMinor"] = ZEROTIER_ONE_VERSION_MINOR;
-				controllerRecord["vRev"] = ZEROTIER_ONE_VERSION_REVISION;
-				controllerRecord["vBuild"] = ZEROTIER_ONE_VERSION_BUILD;
-			}
-
-			while (_run == 1) {
-				try {
-					if (!rdb)
-						rdb = R::connect(this->_host,this->_port,this->_auth);
-					if (rdb) {
-						controllerRecord["lastAlive"] = OSUtils::now();
-						//printf("HEARTBEAT: %s" ZT_EOL_S,tmp);
-						R::db(this->_db).table("Controller",R::optargs("read_mode","outdated")).insert(controllerRecord,R::optargs("conflict","update")).run(*rdb);
-					}
-				} catch ( ... ) {
-					rdb.reset();
-				}
-				std::this_thread::sleep_for(std::chrono::milliseconds(1000));
-			}
-		} catch ( ... ) {}
-	});
-}
-
-RethinkDB::~RethinkDB()
-{
-	_run = 0;
-	std::this_thread::sleep_for(std::chrono::milliseconds(100));
-	_commitQueue.stop();
-	for(int t=0;t<ZT_CONTROLLER_RETHINKDB_COMMIT_THREADS;++t)
-		_commitThread[t].join();
-	if (_membersDbWatcherConnection)
-		((R::Connection *)_membersDbWatcherConnection)->close();
-	if (_networksDbWatcherConnection)
-		((R::Connection *)_networksDbWatcherConnection)->close();
-	_membersDbWatcher.join();
-	_networksDbWatcher.join();
-	_heartbeatThread.join();
-	_onlineNotificationThread.join();
-}
-
-bool RethinkDB::waitForReady()
-{
-	while (_ready > 0) {
-		if (!_waitNoticePrinted) {
-			_waitNoticePrinted = true;
-			fprintf(stderr,"[%s] NOTICE: %.10llx controller RethinkDB waiting for initial data download..." ZT_EOL_S,_timestr(),(unsigned long long)_myAddress.toInt());
-		}
-		_readyLock.lock();
-		_readyLock.unlock();
-	}
-	return true;
-}
-
-bool RethinkDB::isReady()
-{
-	return ((_ready)&&(_connected));
-}
-
-void RethinkDB::save(nlohmann::json *orig,nlohmann::json &record)
-{
-	if (!record.is_object()) // sanity check
-		return;
-	waitForReady();
-	if (orig) {
-		if (*orig != record) {
-			record["revision"] = OSUtils::jsonInt(record["revision"],0ULL) + 1;
-			_commitQueue.post(new nlohmann::json(record));
-		}
-	} else {
-		record["revision"] = 1;
-		_commitQueue.post(new nlohmann::json(record));
-	}
-}
-
-void RethinkDB::eraseNetwork(const uint64_t networkId)
-{
-	char tmp2[24];
-	waitForReady();
-	Utils::hex(networkId,tmp2);
-	json *tmp = new json();
-	(*tmp)["id"] = tmp2;
-	(*tmp)["objtype"] = "_delete_network"; // pseudo-type, tells thread to delete network
-	_commitQueue.post(tmp);
-}
-
-void RethinkDB::eraseMember(const uint64_t networkId,const uint64_t memberId)
-{
-	char tmp2[24];
-	json *tmp = new json();
-	waitForReady();
-	Utils::hex(networkId,tmp2);
-	(*tmp)["nwid"] = tmp2;
-	Utils::hex10(memberId,tmp2);
-	(*tmp)["id"] = tmp2;
-	(*tmp)["objtype"] = "_delete_member"; // pseudo-type, tells thread to delete network
-	_commitQueue.post(tmp);
-}
-
-void RethinkDB::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress)
-{
-	std::lock_guard<std::mutex> l(_lastOnline_l);
-	std::pair<int64_t,InetAddress> &i = _lastOnline[std::pair<uint64_t,uint64_t>(networkId,memberId)];
-	i.first = OSUtils::now();
-	if (physicalAddress)
-		i.second = physicalAddress;
-}
-
-} // namespace ZeroTier
-
-#endif // ZT_CONTROLLER_USE_RETHINKDB

+ 0 - 84
controller/RethinkDB.hpp

@@ -1,84 +0,0 @@
-/*
- * ZeroTier One - Network Virtualization Everywhere
- * Copyright (C) 2011-2018  ZeroTier, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifdef ZT_CONTROLLER_USE_RETHINKDB
-
-#ifndef ZT_CONTROLLER_RETHINKDB_HPP
-#define ZT_CONTROLLER_RETHINKDB_HPP
-
-#include "DB.hpp"
-
-#define ZT_CONTROLLER_RETHINKDB_COMMIT_THREADS 4
-
-namespace ZeroTier
-{
-
-/**
- * A controller database driver that talks to RethinkDB
- *
- * This is for use with ZeroTier Central. Others are free to build and use it
- * but be aware that we might change it at any time.
- */
-class RethinkDB : public DB
-{
-public:
-	RethinkDB(EmbeddedNetworkController *const nc,const Identity &myId,const char *path);
-	virtual ~RethinkDB();
-
-	virtual bool waitForReady();
-	virtual bool isReady();
-	virtual void save(nlohmann::json *orig,nlohmann::json &record);
-	virtual void eraseNetwork(const uint64_t networkId);
-	virtual void eraseMember(const uint64_t networkId,const uint64_t memberId);
-	virtual void nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress);
-
-protected:
-	struct _PairHasher
-	{
-		inline std::size_t operator()(const std::pair<uint64_t,uint64_t> &p) const { return (std::size_t)(p.first ^ p.second); }
-	};
-
-	std::string _host;
-	std::string _db;
-	std::string _auth;
-	int _port;
-
-	void *_networksDbWatcherConnection;
-	void *_membersDbWatcherConnection;
-	std::thread _networksDbWatcher;
-	std::thread _membersDbWatcher;
-
-	BlockingQueue< nlohmann::json * > _commitQueue;
-	std::thread _commitThread[ZT_CONTROLLER_RETHINKDB_COMMIT_THREADS];
-
-	std::unordered_map< std::pair<uint64_t,uint64_t>,std::pair<int64_t,InetAddress>,_PairHasher > _lastOnline;
-	mutable std::mutex _lastOnline_l;
-	std::thread _onlineNotificationThread;
-
-	std::thread _heartbeatThread;
-
-	mutable std::mutex _readyLock; // locked until ready
-	std::atomic<int> _ready,_connected,_run;
-	mutable volatile bool _waitNoticePrinted;
-};
-
-} // namespace ZeroTier
-
-#endif
-
-#endif // ZT_CONTROLLER_USE_RETHINKDB

+ 9 - 0
cycle_controllers.sh

@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+CONTROLLERS=`kubectl get pods -o=name | grep controller | sed "s/^.\{4\}//"`
+
+for c in ${CONTROLLERS[@]}
+do
+    kubectl delete pod ${c}
+    sleep 30
+done

+ 6 - 0
debian/changelog

@@ -1,3 +1,9 @@
+zerotier-one (1.4.0) unstable; urgency=medium
+
+  * See https://github.com/zerotier/ZeroTierOne for release notes.
+
+ -- Adam Ierymenko <[email protected]>  Thu, 29 Jul 2019 01:00:00 -0700
+
 zerotier-one (1.2.12) unstable; urgency=medium
 
   * See https://github.com/zerotier/ZeroTierOne for release notes.

+ 19 - 0
docker/Dockerfile

@@ -0,0 +1,19 @@
+# Dockerfile for ZeroTier Central Controllers
+FROM centos:7
+MAINTAINER Adam Ierymekno <[email protected]>, Grant Limberg <[email protected]>
+
+RUN yum update -y
+RUN yum install -y https://download.postgresql.org/pub/repos/yum/10/redhat/rhel-7-x86_64/pgdg-centos10-10-2.noarch.rpm
+RUN yum install -y bash postgresql10 libpqxx-devel
+
+RUN yum -y install epel-release && yum -y update && yum clean all
+RUN yum -y install clang jemalloc jemalloc-devel
+
+
+ADD zerotier-one /usr/local/bin/zerotier-one
+RUN chmod a+x /usr/local/bin/zerotier-one
+
+ADD docker/main.sh /
+RUN chmod a+x /main.sh
+
+ENTRYPOINT /main.sh 

+ 80 - 0
docker/main.sh

@@ -0,0 +1,80 @@
+#!/bin/bash
+
+if [ -z "$ZT_IDENTITY_PATH" ]; then
+    echo '*** FAILED: ZT_IDENTITY_PATH environment variable is not defined'
+    exit 1
+fi
+if [ -z "$ZT_DB_HOST" ]; then
+    echo '*** FAILED: ZT_DB_HOST environment variable not defined'
+    exit 1
+fi
+if [ -z "$ZT_DB_PORT" ]; then
+    echo '*** FAILED: ZT_DB_PORT environment variable not defined'
+    exit 1
+fi
+if [ -z "$ZT_DB_NAME" ]; then
+    echo '*** FAILED: ZT_DB_NAME environment variable not defined'
+    exit 1
+fi
+if [ -z "$ZT_DB_USER" ]; then
+    echo '*** FAILED: ZT_DB_USER environment variable not defined'
+    exit 1
+fi
+if [ -z "$ZT_DB_PASSWORD" ]; then
+    echo '*** FAILED: ZT_DB_PASSWORD environment variable not defined'
+    exit 1
+fi
+
+RMQ=""
+if [ "$ZT_USE_RABBITMQ" == "true" ]; then
+    if [ -z "$RABBITMQ_HOST" ]; then
+        echo '*** FAILED: RABBITMQ_HOST environment variable not defined'
+        exit 1
+    fi
+    if [ -z "$RABBITMQ_PORT" ]; then
+        echo '*** FAILED: RABBITMQ_PORT environment variable not defined'
+        exit 1
+    fi
+    if [ -z "$RABBITMQ_USERNAME" ]; then
+        echo '*** FAILED: RABBITMQ_USERNAME environment variable not defined'
+        exit 1
+    fi
+    if [ -z "$RABBITMQ_PASSWORD" ]; then
+        echo '*** FAILED: RABBITMQ_PASSWORD environment variable not defined'
+        exit 1
+    fi
+    RMQ=", \"rabbitmq\": {
+        \"host\": \"${RABBITMQ_HOST}\",
+        \"port\": ${RABBITMQ_PORT},
+        \"username\": \"${RABBITMQ_USERNAME}\",
+        \"password\": \"${RABBITMQ_PASSWORD}\"
+    }"
+fi
+
+mkdir -p /var/lib/zerotier-one
+
+pushd /var/lib/zerotier-one
+ln -s $ZT_IDENTITY_PATH/identity.public identity.public
+ln -s $ZT_IDENTITY_PATH/identity.secret identity.secret
+popd
+
+DEFAULT_PORT=9993
+
+echo "{
+    \"settings\": {
+        \"portMappingEnabled\": true,
+        \"softwareUpdate\": \"disable\",
+        \"interfacePrefixBlacklist\": [
+            \"inot\",
+            \"nat64\"
+        ],
+        \"controllerDbPath\": \"postgres:host=${ZT_DB_HOST} port=${ZT_DB_PORT} dbname=${ZT_DB_NAME} user=${ZT_DB_USER} password=${ZT_DB_PASSWORD} sslmode=prefer sslcert=${DB_CLIENT_CERT} sslkey=${DB_CLIENT_KEY} sslrootcert=${DB_SERVER_CA}\"
+        ${RMQ}
+    }
+}    
+" > /var/lib/zerotier-one/local.conf
+
+export GLIBCXX_FORCE_NEW=1
+export GLIBCPP_FORCE_NEW=1
+export LD_PRELOAD="/usr/lib64/libjemalloc.so"
+exec /usr/local/bin/zerotier-one -p${ZT_CONTROLLER_PORT:-$DEFAULT_PORT} /var/lib/zerotier-one

+ 3 - 1
ext/arm32-neon-salsa2012-asm/salsa2012.h

@@ -5,8 +5,10 @@
 #include <sys/auxv.h>
 #include <asm/hwcap.h>
 #define zt_arm_has_neon() ((getauxval(AT_HWCAP) & HWCAP_NEON) != 0)
-#else
+#elif defined(__ARM_NEON__) || defined(__ARM_NEON)
 #define zt_arm_has_neon() (true)
+#else
+#define zt_arm_has_neon() (false)
 #endif
 
 #ifdef __cplusplus

+ 0 - 36
ext/bin/tap-mac/tap.kext/Contents/Info.plist

@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-	<key>CFBundleDevelopmentRegion</key>
-	<string>English</string>
-	<key>CFBundleExecutable</key>
-	<string>tap</string>
-	<key>CFBundleIdentifier</key>
-	<string>com.zerotier.tap</string>
-	<key>CFBundleInfoDictionaryVersion</key>
-	<string>6.0</string>
-	<key>CFBundleName</key>
-	<string>tap</string>
-	<key>CFBundlePackageType</key>
-	<string>KEXT</string>
-	<key>CFBundleShortVersionString</key>
-	<string>20150118</string>
-	<key>CFBundleSignature</key>
-	<string>????</string>
-	<key>CFBundleVersion</key>
-	<string>1.0</string>
-	<key>OSBundleLibraries</key>
-	<dict>
-		<key>com.apple.kpi.mach</key>
-		<string>8.0</string>
-		<key>com.apple.kpi.bsd</key>
-		<string>8.0</string>
-		<key>com.apple.kpi.libkern</key>
-		<string>8.0</string>
-		<key>com.apple.kpi.unsupported</key>
-		<string>8.0</string>
-	</dict>
-</dict>
-</plist>
-

TEMPAT SAMPAH
ext/bin/tap-mac/tap.kext/Contents/MacOS/tap


+ 0 - 105
ext/bin/tap-mac/tap.kext/Contents/_CodeSignature/CodeResources

@@ -1,105 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-	<key>files</key>
-	<dict/>
-	<key>files2</key>
-	<dict/>
-	<key>rules</key>
-	<dict>
-		<key>^Resources/</key>
-		<true/>
-		<key>^Resources/.*\.lproj/</key>
-		<dict>
-			<key>optional</key>
-			<true/>
-			<key>weight</key>
-			<real>1000</real>
-		</dict>
-		<key>^Resources/.*\.lproj/locversion.plist$</key>
-		<dict>
-			<key>omit</key>
-			<true/>
-			<key>weight</key>
-			<real>1100</real>
-		</dict>
-		<key>^version.plist$</key>
-		<true/>
-	</dict>
-	<key>rules2</key>
-	<dict>
-		<key>.*\.dSYM($|/)</key>
-		<dict>
-			<key>weight</key>
-			<real>11</real>
-		</dict>
-		<key>^(.*/)?\.DS_Store$</key>
-		<dict>
-			<key>omit</key>
-			<true/>
-			<key>weight</key>
-			<real>2000</real>
-		</dict>
-		<key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>
-		<dict>
-			<key>nested</key>
-			<true/>
-			<key>weight</key>
-			<real>10</real>
-		</dict>
-		<key>^.*</key>
-		<true/>
-		<key>^Info\.plist$</key>
-		<dict>
-			<key>omit</key>
-			<true/>
-			<key>weight</key>
-			<real>20</real>
-		</dict>
-		<key>^PkgInfo$</key>
-		<dict>
-			<key>omit</key>
-			<true/>
-			<key>weight</key>
-			<real>20</real>
-		</dict>
-		<key>^Resources/</key>
-		<dict>
-			<key>weight</key>
-			<real>20</real>
-		</dict>
-		<key>^Resources/.*\.lproj/</key>
-		<dict>
-			<key>optional</key>
-			<true/>
-			<key>weight</key>
-			<real>1000</real>
-		</dict>
-		<key>^Resources/.*\.lproj/locversion.plist$</key>
-		<dict>
-			<key>omit</key>
-			<true/>
-			<key>weight</key>
-			<real>1100</real>
-		</dict>
-		<key>^[^/]+$</key>
-		<dict>
-			<key>nested</key>
-			<true/>
-			<key>weight</key>
-			<real>10</real>
-		</dict>
-		<key>^embedded\.provisionprofile$</key>
-		<dict>
-			<key>weight</key>
-			<real>20</real>
-		</dict>
-		<key>^version\.plist$</key>
-		<dict>
-			<key>weight</key>
-			<real>20</real>
-		</dict>
-	</dict>
-</dict>
-</plist>

+ 22 - 0
ext/cpp-httplib/LICENSE

@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2017 yhirose
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+

+ 259 - 0
ext/cpp-httplib/README.md

@@ -0,0 +1,259 @@
+cpp-httplib
+===========
+
+[![Build Status](https://travis-ci.org/yhirose/cpp-httplib.svg?branch=master)](https://travis-ci.org/yhirose/cpp-httplib)
+[![Bulid Status](https://ci.appveyor.com/api/projects/status/github/yhirose/cpp-httplib?branch=master&svg=true)](https://ci.appveyor.com/project/yhirose/cpp-httplib)
+
+A C++ header-only cross platform HTTP/HTTPS library.
+
+It's extremely easy to setup. Just include **httplib.h** file in your code!
+
+Inspired by [Sinatra](http://www.sinatrarb.com/) and [express](https://github.com/visionmedia/express).
+
+Server Example
+--------------
+
+```c++
+#include <httplib.h>
+
+int main(void)
+{
+    using namespace httplib;
+
+    Server svr;
+
+    svr.Get("/hi", [](const Request& req, Response& res) {
+        res.set_content("Hello World!", "text/plain");
+    });
+
+    svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
+        auto numbers = req.matches[1];
+        res.set_content(numbers, "text/plain");
+    });
+
+    svr.listen("localhost", 1234);
+}
+```
+
+`Post`, `Put`, `Delete` and `Options` methods are also supported.
+
+### Bind a socket to multiple interfaces and any available port
+
+```cpp
+int port = svr.bind_to_any_port("0.0.0.0");
+svr.listen_after_bind();
+```
+
+### Method Chain
+
+```cpp
+svr.Get("/get", [](const auto& req, auto& res) {
+        res.set_content("get", "text/plain");
+    })
+    .Post("/post", [](const auto& req, auto& res) {
+        res.set_content(req.body(), "text/plain");
+    })
+    .listen("localhost", 1234);
+```
+
+### Static File Server
+
+```cpp
+svr.set_base_dir("./www");
+```
+
+### Logging
+
+```cpp
+svr.set_logger([](const auto& req, const auto& res) {
+    your_logger(req, res);
+});
+```
+
+### Error Handler
+
+```cpp
+svr.set_error_handler([](const auto& req, auto& res) {
+    const char* fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
+    char buf[BUFSIZ];
+    snprintf(buf, sizeof(buf), fmt, res.status);
+    res.set_content(buf, "text/html");
+});
+```
+
+### 'multipart/form-data' POST data
+
+```cpp
+svr.Post("/multipart", [&](const auto& req, auto& res) {
+    auto size = req.files.size();
+    auto ret = req.has_file("name1"));
+    const auto& file = req.get_file_value("name1");
+    // file.filename;
+    // file.content_type;
+    auto body = req.body.substr(file.offset, file.length));
+})
+```
+
+Client Example
+--------------
+
+### GET
+
+```c++
+#include <httplib.h>
+#include <iostream>
+
+int main(void)
+{
+    httplib::Client cli("localhost", 1234);
+
+    auto res = cli.Get("/hi");
+    if (res && res->status == 200) {
+        std::cout << res->body << std::endl;
+    }
+}
+```
+
+### GET with Content Receiver
+
+```c++
+  std::string body;
+  auto res = cli.Get("/large-data", [&](const char *data, size_t len) {
+    body.append(data, len);
+  });
+  assert(res->body.empty());
+```
+
+### POST
+
+```c++
+res = cli.Post("/post", "text", "text/plain");
+res = cli.Post("/person", "name=john1&note=coder", "application/x-www-form-urlencoded");
+```
+
+### POST with parameters
+
+```c++
+httplib::Params params;
+params.emplace("name", "john");
+params.emplace("note", "coder");
+
+auto res = cli.Post("/post", params);
+```
+ or
+
+```c++
+httplib::Params params{
+  { "name", "john" },
+  { "note", "coder" }
+};
+
+auto res = cli.Post("/post", params);
+```
+
+### PUT
+
+```c++
+res = cli.Put("/resource/foo", "text", "text/plain");
+```
+
+### DELETE
+
+```c++
+res = cli.Delete("/resource/foo");
+```
+
+### OPTIONS
+
+```c++
+res = cli.Options("*");
+res = cli.Options("/resource/foo");
+```
+
+### Connection Timeout
+
+```c++
+httplib::Client cli("localhost", 8080, 5); // timeouts in 5 seconds
+```
+### With Progress Callback
+
+```cpp
+httplib::Client client(url, port);
+
+// prints: 0 / 000 bytes => 50% complete
+std::shared_ptr<httplib::Response> res =
+    cli.Get("/", [](uint64_t len, uint64_t total) {
+        printf("%lld / %lld bytes => %d%% complete\n",
+            len, total,
+            (int)((len/total)*100));
+        return true; // return 'false' if you want to cancel the request.
+    }
+);
+```
+
+![progress](https://user-images.githubusercontent.com/236374/33138910-495c4ecc-cf86-11e7-8693-2fc6d09615c4.gif)
+
+This feature was contributed by [underscorediscovery](https://github.com/yhirose/cpp-httplib/pull/23).
+
+### Basic Authentication
+
+```cpp
+httplib::Client cli("httplib.org");
+
+auto res = cli.Get("/basic-auth/hello/world", {
+  httplib::make_basic_authentication_header("hello", "world")
+});
+// res->status should be 200
+// res->body should be "{\n  \"authenticated\": true, \n  \"user\": \"hello\"\n}\n".
+```
+
+### Range
+
+```cpp
+httplib::Client cli("httpbin.org");
+
+auto res = cli.Get("/range/32", {
+  httplib::make_range_header(1, 10) // 'Range: bytes=1-10'
+});
+// res->status should be 206.
+// res->body should be "bcdefghijk".
+```
+
+OpenSSL Support
+---------------
+
+SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked.
+
+```c++
+#define CPPHTTPLIB_OPENSSL_SUPPORT
+
+SSLServer svr("./cert.pem", "./key.pem");
+
+SSLClient cli("localhost", 8080);
+cli.set_ca_cert_path("./ca-bundle.crt");
+cli.enable_server_certificate_verification(true);
+```
+
+Zlib Support
+------------
+
+'gzip' compression is available with `CPPHTTPLIB_ZLIB_SUPPORT`.
+
+The server applies gzip compression to the following MIME type contents:
+
+  * all text types
+  * image/svg+xml
+  * application/javascript
+  * application/json
+  * application/xml
+  * application/xhtml+xml
+
+NOTE
+----
+
+g++ 4.8 cannot build this library since `<regex>` in g++4.8 is [broken](https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions).
+
+License
+-------
+
+MIT license (© 2019 Yuji Hirose)

+ 2779 - 0
ext/cpp-httplib/httplib.h

@@ -0,0 +1,2779 @@
+//
+//  httplib.h
+//
+//  Copyright (c) 2019 Yuji Hirose. All rights reserved.
+//  MIT License
+//
+
+#ifndef CPPHTTPLIB_HTTPLIB_H
+#define CPPHTTPLIB_HTTPLIB_H
+
+#ifdef _WIN32
+#ifndef _CRT_SECURE_NO_WARNINGS
+#define _CRT_SECURE_NO_WARNINGS
+#endif //_CRT_SECURE_NO_WARNINGS
+
+#ifndef _CRT_NONSTDC_NO_DEPRECATE
+#define _CRT_NONSTDC_NO_DEPRECATE
+#endif //_CRT_NONSTDC_NO_DEPRECATE
+
+#if defined(_MSC_VER) && _MSC_VER < 1900
+#define snprintf _snprintf_s
+#endif // _MSC_VER
+
+#ifndef S_ISREG
+#define S_ISREG(m) (((m)&S_IFREG) == S_IFREG)
+#endif // S_ISREG
+
+#ifndef S_ISDIR
+#define S_ISDIR(m) (((m)&S_IFDIR) == S_IFDIR)
+#endif // S_ISDIR
+
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif // NOMINMAX
+
+#include <io.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
+
+#pragma comment(lib, "ws2_32.lib")
+
+#ifndef strcasecmp
+#define strcasecmp _stricmp
+#endif // strcasecmp
+
+typedef SOCKET socket_t;
+#else
+#include <arpa/inet.h>
+#include <cstring>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <pthread.h>
+#include <signal.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+typedef int socket_t;
+#define INVALID_SOCKET (-1)
+#endif //_WIN32
+
+#include <assert.h>
+#include <atomic>
+#include <fcntl.h>
+#include <fstream>
+#include <functional>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <regex>
+#include <string>
+#include <sys/stat.h>
+#include <thread>
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+#include <openssl/x509v3.h>
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) {
+  return M_ASN1_STRING_data(asn1);
+}
+#endif
+#endif
+
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+#include <zlib.h>
+#endif
+
+/*
+ * Configuration
+ */
+#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5
+#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND 0
+#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5
+#define CPPHTTPLIB_READ_TIMEOUT_SECOND 5
+#define CPPHTTPLIB_READ_TIMEOUT_USECOND 0
+#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192
+#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH (std::numeric_limits<size_t>::max)()
+#define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u)
+
+namespace httplib {
+
+namespace detail {
+
+struct ci {
+  bool operator()(const std::string &s1, const std::string &s2) const {
+    return std::lexicographical_compare(
+        s1.begin(), s1.end(), s2.begin(), s2.end(),
+        [](char c1, char c2) { return ::tolower(c1) < ::tolower(c2); });
+  }
+};
+
+} // namespace detail
+
+enum class HttpVersion { v1_0 = 0, v1_1 };
+
+typedef std::multimap<std::string, std::string, detail::ci> Headers;
+
+template <typename uint64_t, typename... Args>
+std::pair<std::string, std::string> make_range_header(uint64_t value,
+                                                      Args... args);
+
+typedef std::multimap<std::string, std::string> Params;
+typedef std::smatch Match;
+
+typedef std::function<std::string(uint64_t offset)> ContentProducer;
+typedef std::function<void(const char *data, size_t len)> ContentReceiver;
+typedef std::function<bool(uint64_t current, uint64_t total)> Progress;
+
+struct MultipartFile {
+  std::string filename;
+  std::string content_type;
+  size_t offset = 0;
+  size_t length = 0;
+};
+typedef std::multimap<std::string, MultipartFile> MultipartFiles;
+
+struct Request {
+  std::string version;
+  std::string method;
+  std::string target;
+  std::string path;
+  Headers headers;
+  std::string body;
+  Params params;
+  MultipartFiles files;
+  Match matches;
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+  const SSL *ssl;
+#endif
+
+  bool has_header(const char *key) const;
+  std::string get_header_value(const char *key, size_t id = 0) const;
+  size_t get_header_value_count(const char *key) const;
+  void set_header(const char *key, const char *val);
+
+  bool has_param(const char *key) const;
+  std::string get_param_value(const char *key, size_t id = 0) const;
+  size_t get_param_value_count(const char *key) const;
+
+  bool has_file(const char *key) const;
+  MultipartFile get_file_value(const char *key) const;
+};
+
+struct Response {
+  std::string version;
+  int status;
+  Headers headers;
+  std::string body;
+
+  ContentProducer content_producer;
+  ContentReceiver content_receiver;
+  Progress progress;
+
+  bool has_header(const char *key) const;
+  std::string get_header_value(const char *key, size_t id = 0) const;
+  size_t get_header_value_count(const char *key) const;
+  void set_header(const char *key, const char *val);
+
+  void set_redirect(const char *uri);
+  void set_content(const char *s, size_t n, const char *content_type);
+  void set_content(const std::string &s, const char *content_type);
+
+  Response() : status(-1) {}
+};
+
+class Stream {
+public:
+  virtual ~Stream() {}
+  virtual int read(char *ptr, size_t size) = 0;
+  virtual int write(const char *ptr, size_t size1) = 0;
+  virtual int write(const char *ptr) = 0;
+  virtual std::string get_remote_addr() const = 0;
+
+  template <typename... Args>
+  void write_format(const char *fmt, const Args &... args);
+};
+
+class SocketStream : public Stream {
+public:
+  SocketStream(socket_t sock);
+  virtual ~SocketStream();
+
+  virtual int read(char *ptr, size_t size);
+  virtual int write(const char *ptr, size_t size);
+  virtual int write(const char *ptr);
+  virtual std::string get_remote_addr() const;
+
+private:
+  socket_t sock_;
+};
+
+class BufferStream : public Stream {
+public:
+  BufferStream() {}
+  virtual ~BufferStream() {}
+
+  virtual int read(char *ptr, size_t size);
+  virtual int write(const char *ptr, size_t size);
+  virtual int write(const char *ptr);
+  virtual std::string get_remote_addr() const;
+
+  const std::string &get_buffer() const;
+
+private:
+  std::string buffer;
+};
+
+class Server {
+public:
+  typedef std::function<void(const Request &, Response &)> Handler;
+  typedef std::function<void(const Request &, const Response &)> Logger;
+
+  Server();
+
+  virtual ~Server();
+
+  virtual bool is_valid() const;
+
+  Server &Get(const char *pattern, Handler handler);
+  Server &Post(const char *pattern, Handler handler);
+
+  Server &Put(const char *pattern, Handler handler);
+  Server &Patch(const char *pattern, Handler handler);
+  Server &Delete(const char *pattern, Handler handler);
+  Server &Options(const char *pattern, Handler handler);
+
+  bool set_base_dir(const char *path);
+
+  void set_error_handler(Handler handler);
+  void set_logger(Logger logger);
+
+  void set_keep_alive_max_count(size_t count);
+  void set_payload_max_length(uint64_t length);
+
+  int bind_to_any_port(const char *host, int socket_flags = 0);
+  bool listen_after_bind();
+
+  bool listen(const char *host, int port, int socket_flags = 0);
+
+  bool is_running() const;
+  void stop();
+
+protected:
+  bool process_request(Stream &strm, bool last_connection,
+                       bool &connection_close,
+                       std::function<void(Request &)> setup_request = nullptr);
+
+  size_t keep_alive_max_count_;
+  size_t payload_max_length_;
+
+private:
+  typedef std::vector<std::pair<std::regex, Handler>> Handlers;
+
+  socket_t create_server_socket(const char *host, int port,
+                                int socket_flags) const;
+  int bind_internal(const char *host, int port, int socket_flags);
+  bool listen_internal();
+
+  bool routing(Request &req, Response &res);
+  bool handle_file_request(Request &req, Response &res);
+  bool dispatch_request(Request &req, Response &res, Handlers &handlers);
+
+  bool parse_request_line(const char *s, Request &req);
+  void write_response(Stream &strm, bool last_connection, const Request &req,
+                      Response &res);
+
+  virtual bool read_and_close_socket(socket_t sock);
+
+  std::atomic<bool> is_running_;
+  std::atomic<socket_t> svr_sock_;
+  std::string base_dir_;
+  Handlers get_handlers_;
+  Handlers post_handlers_;
+  Handlers put_handlers_;
+  Handlers patch_handlers_;
+  Handlers delete_handlers_;
+  Handlers options_handlers_;
+  Handler error_handler_;
+  Logger logger_;
+
+  // TODO: Use thread pool...
+  std::mutex running_threads_mutex_;
+  int running_threads_;
+};
+
+class Client {
+public:
+  Client(const char *host, int port = 80, time_t timeout_sec = 300);
+
+  virtual ~Client();
+
+  virtual bool is_valid() const;
+
+  std::shared_ptr<Response> Get(const char *path, Progress progress = nullptr);
+  std::shared_ptr<Response> Get(const char *path, const Headers &headers,
+                                Progress progress = nullptr);
+
+  std::shared_ptr<Response> Get(const char *path,
+                                ContentReceiver content_receiver,
+                                Progress progress = nullptr);
+  std::shared_ptr<Response> Get(const char *path, const Headers &headers,
+                                ContentReceiver content_receiver,
+                                Progress progress = nullptr);
+
+  std::shared_ptr<Response> Head(const char *path);
+  std::shared_ptr<Response> Head(const char *path, const Headers &headers);
+
+  std::shared_ptr<Response> Post(const char *path, const std::string &body,
+                                 const char *content_type);
+  std::shared_ptr<Response> Post(const char *path, const Headers &headers,
+                                 const std::string &body,
+                                 const char *content_type);
+
+  std::shared_ptr<Response> Post(const char *path, const Params &params);
+  std::shared_ptr<Response> Post(const char *path, const Headers &headers,
+                                 const Params &params);
+
+  std::shared_ptr<Response> Put(const char *path, const std::string &body,
+                                const char *content_type);
+  std::shared_ptr<Response> Put(const char *path, const Headers &headers,
+                                const std::string &body,
+                                const char *content_type);
+
+  std::shared_ptr<Response> Patch(const char *path, const std::string &body,
+                                  const char *content_type);
+  std::shared_ptr<Response> Patch(const char *path, const Headers &headers,
+                                  const std::string &body,
+                                  const char *content_type);
+
+  std::shared_ptr<Response> Delete(const char *path,
+                                   const std::string &body = std::string(),
+                                   const char *content_type = nullptr);
+  std::shared_ptr<Response> Delete(const char *path, const Headers &headers,
+                                   const std::string &body = std::string(),
+                                   const char *content_type = nullptr);
+
+  std::shared_ptr<Response> Options(const char *path);
+  std::shared_ptr<Response> Options(const char *path, const Headers &headers);
+
+  bool send(Request &req, Response &res);
+
+protected:
+  bool process_request(Stream &strm, Request &req, Response &res,
+                       bool &connection_close);
+
+  const std::string host_;
+  const int port_;
+  time_t timeout_sec_;
+  const std::string host_and_port_;
+
+private:
+  socket_t create_client_socket() const;
+  bool read_response_line(Stream &strm, Response &res);
+  void write_request(Stream &strm, Request &req);
+
+  virtual bool read_and_close_socket(socket_t sock, Request &req,
+                                     Response &res);
+  virtual bool is_ssl() const;
+};
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+class SSLSocketStream : public Stream {
+public:
+  SSLSocketStream(socket_t sock, SSL *ssl);
+  virtual ~SSLSocketStream();
+
+  virtual int read(char *ptr, size_t size);
+  virtual int write(const char *ptr, size_t size);
+  virtual int write(const char *ptr);
+  virtual std::string get_remote_addr() const;
+
+private:
+  socket_t sock_;
+  SSL *ssl_;
+};
+
+class SSLServer : public Server {
+public:
+  SSLServer(const char *cert_path, const char *private_key_path,
+            const char *client_ca_cert_file_path = nullptr,
+            const char *client_ca_cert_dir_path = nullptr);
+
+  virtual ~SSLServer();
+
+  virtual bool is_valid() const;
+
+private:
+  virtual bool read_and_close_socket(socket_t sock);
+
+  SSL_CTX *ctx_;
+  std::mutex ctx_mutex_;
+};
+
+class SSLClient : public Client {
+public:
+  SSLClient(const char *host, int port = 443, time_t timeout_sec = 300,
+            const char *client_cert_path = nullptr,
+            const char *client_key_path = nullptr);
+
+  virtual ~SSLClient();
+
+  virtual bool is_valid() const;
+
+  void set_ca_cert_path(const char *ca_ceert_file_path,
+                        const char *ca_cert_dir_path = nullptr);
+  void enable_server_certificate_verification(bool enabled);
+
+  long get_openssl_verify_result() const;
+
+private:
+  virtual bool read_and_close_socket(socket_t sock, Request &req,
+                                     Response &res);
+  virtual bool is_ssl() const;
+
+  bool verify_host(X509 *server_cert) const;
+  bool verify_host_with_subject_alt_name(X509 *server_cert) const;
+  bool verify_host_with_common_name(X509 *server_cert) const;
+  bool check_host_name(const char *pattern, size_t pattern_len) const;
+
+  SSL_CTX *ctx_;
+  std::mutex ctx_mutex_;
+  std::vector<std::string> host_components_;
+  std::string ca_cert_file_path_;
+  std::string ca_cert_dir_path_;
+  bool server_certificate_verification_ = false;
+  long verify_result_ = 0;
+};
+#endif
+
+/*
+ * Implementation
+ */
+namespace detail {
+
+inline bool is_hex(char c, int &v) {
+  if (0x20 <= c && isdigit(c)) {
+    v = c - '0';
+    return true;
+  } else if ('A' <= c && c <= 'F') {
+    v = c - 'A' + 10;
+    return true;
+  } else if ('a' <= c && c <= 'f') {
+    v = c - 'a' + 10;
+    return true;
+  }
+  return false;
+}
+
+inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt,
+                          int &val) {
+  if (i >= s.size()) { return false; }
+
+  val = 0;
+  for (; cnt; i++, cnt--) {
+    if (!s[i]) { return false; }
+    int v = 0;
+    if (is_hex(s[i], v)) {
+      val = val * 16 + v;
+    } else {
+      return false;
+    }
+  }
+  return true;
+}
+
+inline std::string from_i_to_hex(uint64_t n) {
+  const char *charset = "0123456789abcdef";
+  std::string ret;
+  do {
+    ret = charset[n & 15] + ret;
+    n >>= 4;
+  } while (n > 0);
+  return ret;
+}
+
+inline size_t to_utf8(int code, char *buff) {
+  if (code < 0x0080) {
+    buff[0] = (code & 0x7F);
+    return 1;
+  } else if (code < 0x0800) {
+    buff[0] = (0xC0 | ((code >> 6) & 0x1F));
+    buff[1] = (0x80 | (code & 0x3F));
+    return 2;
+  } else if (code < 0xD800) {
+    buff[0] = (0xE0 | ((code >> 12) & 0xF));
+    buff[1] = (0x80 | ((code >> 6) & 0x3F));
+    buff[2] = (0x80 | (code & 0x3F));
+    return 3;
+  } else if (code < 0xE000) { // D800 - DFFF is invalid...
+    return 0;
+  } else if (code < 0x10000) {
+    buff[0] = (0xE0 | ((code >> 12) & 0xF));
+    buff[1] = (0x80 | ((code >> 6) & 0x3F));
+    buff[2] = (0x80 | (code & 0x3F));
+    return 3;
+  } else if (code < 0x110000) {
+    buff[0] = (0xF0 | ((code >> 18) & 0x7));
+    buff[1] = (0x80 | ((code >> 12) & 0x3F));
+    buff[2] = (0x80 | ((code >> 6) & 0x3F));
+    buff[3] = (0x80 | (code & 0x3F));
+    return 4;
+  }
+
+  // NOTREACHED
+  return 0;
+}
+
+// NOTE: This code came up with the following stackoverflow post:
+// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c
+inline std::string base64_encode(const std::string &in) {
+  static const auto lookup =
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+  std::string out;
+  out.reserve(in.size());
+
+  int val = 0;
+  int valb = -6;
+
+  for (uint8_t c : in) {
+    val = (val << 8) + c;
+    valb += 8;
+    while (valb >= 0) {
+      out.push_back(lookup[(val >> valb) & 0x3F]);
+      valb -= 6;
+    }
+  }
+
+  if (valb > -6) {
+    out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]);
+  }
+
+  while (out.size() % 4) {
+    out.push_back('=');
+  }
+
+  return out;
+}
+
+inline bool is_file(const std::string &path) {
+  struct stat st;
+  return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode);
+}
+
+inline bool is_dir(const std::string &path) {
+  struct stat st;
+  return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode);
+}
+
+inline bool is_valid_path(const std::string &path) {
+  size_t level = 0;
+  size_t i = 0;
+
+  // Skip slash
+  while (i < path.size() && path[i] == '/') {
+    i++;
+  }
+
+  while (i < path.size()) {
+    // Read component
+    auto beg = i;
+    while (i < path.size() && path[i] != '/') {
+      i++;
+    }
+
+    auto len = i - beg;
+    assert(len > 0);
+
+    if (!path.compare(beg, len, ".")) {
+      ;
+    } else if (!path.compare(beg, len, "..")) {
+      if (level == 0) { return false; }
+      level--;
+    } else {
+      level++;
+    }
+
+    // Skip slash
+    while (i < path.size() && path[i] == '/') {
+      i++;
+    }
+  }
+
+  return true;
+}
+
+inline void read_file(const std::string &path, std::string &out) {
+  std::ifstream fs(path, std::ios_base::binary);
+  fs.seekg(0, std::ios_base::end);
+  auto size = fs.tellg();
+  fs.seekg(0);
+  out.resize(static_cast<size_t>(size));
+  fs.read(&out[0], size);
+}
+
+inline std::string file_extension(const std::string &path) {
+  std::smatch m;
+  auto pat = std::regex("\\.([a-zA-Z0-9]+)$");
+  if (std::regex_search(path, m, pat)) { return m[1].str(); }
+  return std::string();
+}
+
+template <class Fn> void split(const char *b, const char *e, char d, Fn fn) {
+  int i = 0;
+  int beg = 0;
+
+  while (e ? (b + i != e) : (b[i] != '\0')) {
+    if (b[i] == d) {
+      fn(&b[beg], &b[i]);
+      beg = i + 1;
+    }
+    i++;
+  }
+
+  if (i) { fn(&b[beg], &b[i]); }
+}
+
+// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer`
+// to store data. The call can set memory on stack for performance.
+class stream_line_reader {
+public:
+  stream_line_reader(Stream &strm, char *fixed_buffer, size_t fixed_buffer_size)
+      : strm_(strm), fixed_buffer_(fixed_buffer),
+        fixed_buffer_size_(fixed_buffer_size) {}
+
+  const char *ptr() const {
+    if (glowable_buffer_.empty()) {
+      return fixed_buffer_;
+    } else {
+      return glowable_buffer_.data();
+    }
+  }
+
+  size_t size() const {
+    if (glowable_buffer_.empty()) {
+      return fixed_buffer_used_size_;
+    } else {
+      return glowable_buffer_.size();
+    }
+  }
+
+  bool getline() {
+    fixed_buffer_used_size_ = 0;
+    glowable_buffer_.clear();
+
+    for (size_t i = 0;; i++) {
+      char byte;
+      auto n = strm_.read(&byte, 1);
+
+      if (n < 0) {
+        return false;
+      } else if (n == 0) {
+        if (i == 0) {
+          return false;
+        } else {
+          break;
+        }
+      }
+
+      append(byte);
+
+      if (byte == '\n') { break; }
+    }
+
+    return true;
+  }
+
+private:
+  void append(char c) {
+    if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) {
+      fixed_buffer_[fixed_buffer_used_size_++] = c;
+      fixed_buffer_[fixed_buffer_used_size_] = '\0';
+    } else {
+      if (glowable_buffer_.empty()) {
+        assert(fixed_buffer_[fixed_buffer_used_size_] == '\0');
+        glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_);
+      }
+      glowable_buffer_ += c;
+    }
+  }
+
+  Stream &strm_;
+  char *fixed_buffer_;
+  const size_t fixed_buffer_size_;
+  size_t fixed_buffer_used_size_;
+  std::string glowable_buffer_;
+};
+
+inline int close_socket(socket_t sock) {
+#ifdef _WIN32
+  return closesocket(sock);
+#else
+  return close(sock);
+#endif
+}
+
+inline int select_read(socket_t sock, time_t sec, time_t usec) {
+  fd_set fds;
+  FD_ZERO(&fds);
+  FD_SET(sock, &fds);
+
+  timeval tv;
+  tv.tv_sec = static_cast<long>(sec);
+  tv.tv_usec = static_cast<long>(usec);
+
+  return select(static_cast<int>(sock + 1), &fds, nullptr, nullptr, &tv);
+}
+
+inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
+  fd_set fdsr;
+  FD_ZERO(&fdsr);
+  FD_SET(sock, &fdsr);
+
+  auto fdsw = fdsr;
+  auto fdse = fdsr;
+
+  timeval tv;
+  tv.tv_sec = static_cast<long>(sec);
+  tv.tv_usec = static_cast<long>(usec);
+
+  if (select(static_cast<int>(sock + 1), &fdsr, &fdsw, &fdse, &tv) < 0) {
+    return false;
+  } else if (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw)) {
+    int error = 0;
+    socklen_t len = sizeof(error);
+    if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&error, &len) < 0 ||
+        error) {
+      return false;
+    }
+  } else {
+    return false;
+  }
+
+  return true;
+}
+
+template <typename T>
+inline bool read_and_close_socket(socket_t sock, size_t keep_alive_max_count,
+                                  T callback) {
+  bool ret = false;
+
+  if (keep_alive_max_count > 0) {
+    auto count = keep_alive_max_count;
+    while (count > 0 &&
+           detail::select_read(sock, CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND,
+                               CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0) {
+      SocketStream strm(sock);
+      auto last_connection = count == 1;
+      auto connection_close = false;
+
+      ret = callback(strm, last_connection, connection_close);
+      if (!ret || connection_close) { break; }
+
+      count--;
+    }
+  } else {
+    SocketStream strm(sock);
+    auto dummy_connection_close = false;
+    ret = callback(strm, true, dummy_connection_close);
+  }
+
+  close_socket(sock);
+  return ret;
+}
+
+inline int shutdown_socket(socket_t sock) {
+#ifdef _WIN32
+  return shutdown(sock, SD_BOTH);
+#else
+  return shutdown(sock, SHUT_RDWR);
+#endif
+}
+
+template <typename Fn>
+socket_t create_socket(const char *host, int port, Fn fn,
+                       int socket_flags = 0) {
+#ifdef _WIN32
+#define SO_SYNCHRONOUS_NONALERT 0x20
+#define SO_OPENTYPE 0x7008
+
+  int opt = SO_SYNCHRONOUS_NONALERT;
+  setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, (char *)&opt,
+             sizeof(opt));
+#endif
+
+  // Get address info
+  struct addrinfo hints;
+  struct addrinfo *result;
+
+  memset(&hints, 0, sizeof(struct addrinfo));
+  hints.ai_family = AF_UNSPEC;
+  hints.ai_socktype = SOCK_STREAM;
+  hints.ai_flags = socket_flags;
+  hints.ai_protocol = 0;
+
+  auto service = std::to_string(port);
+
+  if (getaddrinfo(host, service.c_str(), &hints, &result)) {
+    return INVALID_SOCKET;
+  }
+
+  for (auto rp = result; rp; rp = rp->ai_next) {
+    // Create a socket
+#ifdef _WIN32
+    auto sock = WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol,
+                           nullptr, 0, WSA_FLAG_NO_HANDLE_INHERIT);
+#else
+    auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+#endif
+    if (sock == INVALID_SOCKET) { continue; }
+
+#ifndef _WIN32
+    if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { continue; }
+#endif
+
+    // Make 'reuse address' option available
+    int yes = 1;
+    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&yes, sizeof(yes));
+#ifdef SO_REUSEPORT
+    setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (char *)&yes, sizeof(yes));
+#endif
+
+    // bind or connect
+    if (fn(sock, *rp)) {
+      freeaddrinfo(result);
+      return sock;
+    }
+
+    close_socket(sock);
+  }
+
+  freeaddrinfo(result);
+  return INVALID_SOCKET;
+}
+
+inline void set_nonblocking(socket_t sock, bool nonblocking) {
+#ifdef _WIN32
+  auto flags = nonblocking ? 1UL : 0UL;
+  ioctlsocket(sock, FIONBIO, &flags);
+#else
+  auto flags = fcntl(sock, F_GETFL, 0);
+  fcntl(sock, F_SETFL,
+        nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK)));
+#endif
+}
+
+inline bool is_connection_error() {
+#ifdef _WIN32
+  return WSAGetLastError() != WSAEWOULDBLOCK;
+#else
+  return errno != EINPROGRESS;
+#endif
+}
+
+inline std::string get_remote_addr(socket_t sock) {
+  struct sockaddr_storage addr;
+  socklen_t len = sizeof(addr);
+
+  if (!getpeername(sock, (struct sockaddr *)&addr, &len)) {
+    char ipstr[NI_MAXHOST];
+
+    if (!getnameinfo((struct sockaddr *)&addr, len, ipstr, sizeof(ipstr),
+                     nullptr, 0, NI_NUMERICHOST)) {
+      return ipstr;
+    }
+  }
+
+  return std::string();
+}
+
+inline const char *find_content_type(const std::string &path) {
+  auto ext = file_extension(path);
+  if (ext == "txt") {
+    return "text/plain";
+  } else if (ext == "html") {
+    return "text/html";
+  } else if (ext == "css") {
+    return "text/css";
+  } else if (ext == "jpeg" || ext == "jpg") {
+    return "image/jpg";
+  } else if (ext == "png") {
+    return "image/png";
+  } else if (ext == "gif") {
+    return "image/gif";
+  } else if (ext == "svg") {
+    return "image/svg+xml";
+  } else if (ext == "ico") {
+    return "image/x-icon";
+  } else if (ext == "json") {
+    return "application/json";
+  } else if (ext == "pdf") {
+    return "application/pdf";
+  } else if (ext == "js") {
+    return "application/javascript";
+  } else if (ext == "xml") {
+    return "application/xml";
+  } else if (ext == "xhtml") {
+    return "application/xhtml+xml";
+  }
+  return nullptr;
+}
+
+inline const char *status_message(int status) {
+  switch (status) {
+  case 200: return "OK";
+  case 301: return "Moved Permanently";
+  case 302: return "Found";
+  case 303: return "See Other";
+  case 304: return "Not Modified";
+  case 400: return "Bad Request";
+  case 403: return "Forbidden";
+  case 404: return "Not Found";
+  case 413: return "Payload Too Large";
+  case 414: return "Request-URI Too Long";
+  case 415: return "Unsupported Media Type";
+  default:
+  case 500: return "Internal Server Error";
+  }
+}
+
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+inline bool can_compress(const std::string &content_type) {
+  return !content_type.find("text/") || content_type == "image/svg+xml" ||
+         content_type == "application/javascript" ||
+         content_type == "application/json" ||
+         content_type == "application/xml" ||
+         content_type == "application/xhtml+xml";
+}
+
+inline bool compress(std::string &content) {
+  z_stream strm;
+  strm.zalloc = Z_NULL;
+  strm.zfree = Z_NULL;
+  strm.opaque = Z_NULL;
+
+  auto ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8,
+                          Z_DEFAULT_STRATEGY);
+  if (ret != Z_OK) { return false; }
+
+  strm.avail_in = content.size();
+  strm.next_in = (Bytef *)content.data();
+
+  std::string compressed;
+
+  const auto bufsiz = 16384;
+  char buff[bufsiz];
+  do {
+    strm.avail_out = bufsiz;
+    strm.next_out = (Bytef *)buff;
+    ret = deflate(&strm, Z_FINISH);
+    assert(ret != Z_STREAM_ERROR);
+    compressed.append(buff, bufsiz - strm.avail_out);
+  } while (strm.avail_out == 0);
+
+  assert(ret == Z_STREAM_END);
+  assert(strm.avail_in == 0);
+
+  content.swap(compressed);
+
+  deflateEnd(&strm);
+  return true;
+}
+
+class decompressor {
+public:
+  decompressor() {
+    strm.zalloc = Z_NULL;
+    strm.zfree = Z_NULL;
+    strm.opaque = Z_NULL;
+
+    // 15 is the value of wbits, which should be at the maximum possible value
+    // to ensure that any gzip stream can be decoded. The offset of 16 specifies
+    // that the stream to decompress will be formatted with a gzip wrapper.
+    is_valid_ = inflateInit2(&strm, 16 + 15) == Z_OK;
+  }
+
+  ~decompressor() { inflateEnd(&strm); }
+
+  bool is_valid() const { return is_valid_; }
+
+  template <typename T>
+  bool decompress(const char *data, size_t data_len, T callback) {
+    int ret = Z_OK;
+    std::string decompressed;
+
+    // strm.avail_in = content.size();
+    // strm.next_in = (Bytef *)content.data();
+    strm.avail_in = data_len;
+    strm.next_in = (Bytef *)data;
+
+    const auto bufsiz = 16384;
+    char buff[bufsiz];
+    do {
+      strm.avail_out = bufsiz;
+      strm.next_out = (Bytef *)buff;
+
+      ret = inflate(&strm, Z_NO_FLUSH);
+      assert(ret != Z_STREAM_ERROR);
+      switch (ret) {
+      case Z_NEED_DICT:
+      case Z_DATA_ERROR:
+      case Z_MEM_ERROR: inflateEnd(&strm); return false;
+      }
+
+      decompressed.append(buff, bufsiz - strm.avail_out);
+    } while (strm.avail_out == 0);
+
+    if (ret == Z_STREAM_END) {
+      callback(decompressed.data(), decompressed.size());
+      return true;
+    }
+
+    return false;
+  }
+
+private:
+  bool is_valid_;
+  z_stream strm;
+};
+#endif
+
+inline bool has_header(const Headers &headers, const char *key) {
+  return headers.find(key) != headers.end();
+}
+
+inline const char *get_header_value(const Headers &headers, const char *key,
+                                    size_t id = 0, const char *def = nullptr) {
+  auto it = headers.find(key);
+  std::advance(it, id);
+  if (it != headers.end()) { return it->second.c_str(); }
+  return def;
+}
+
+inline uint64_t get_header_value_uint64(const Headers &headers, const char *key,
+                                        int def = 0) {
+  auto it = headers.find(key);
+  if (it != headers.end()) {
+    return std::strtoull(it->second.data(), nullptr, 10);
+  }
+  return def;
+}
+
+inline bool read_headers(Stream &strm, Headers &headers) {
+  static std::regex re(R"((.+?):\s*(.+?)\s*\r\n)");
+
+  const auto bufsiz = 2048;
+  char buf[bufsiz];
+
+  stream_line_reader reader(strm, buf, bufsiz);
+
+  for (;;) {
+    if (!reader.getline()) { return false; }
+    if (!strcmp(reader.ptr(), "\r\n")) { break; }
+    std::cmatch m;
+    if (std::regex_match(reader.ptr(), m, re)) {
+      auto key = std::string(m[1]);
+      auto val = std::string(m[2]);
+      headers.emplace(key, val);
+    }
+  }
+
+  return true;
+}
+
+template <typename T>
+inline bool read_content_with_length(Stream &strm, size_t len,
+                                     Progress progress, T callback) {
+  char buf[CPPHTTPLIB_RECV_BUFSIZ];
+
+  size_t r = 0;
+  while (r < len) {
+    auto n = strm.read(buf, std::min((len - r), CPPHTTPLIB_RECV_BUFSIZ));
+    if (n <= 0) { return false; }
+
+    callback(buf, n);
+
+    r += n;
+
+    if (progress) {
+      if (!progress(r, len)) { return false; }
+    }
+  }
+
+  return true;
+}
+
+inline void skip_content_with_length(Stream &strm, size_t len) {
+  char buf[CPPHTTPLIB_RECV_BUFSIZ];
+  size_t r = 0;
+  while (r < len) {
+    auto n = strm.read(buf, std::min((len - r), CPPHTTPLIB_RECV_BUFSIZ));
+    if (n <= 0) { return; }
+    r += n;
+  }
+}
+
+template <typename T>
+inline bool read_content_without_length(Stream &strm, T callback) {
+  char buf[CPPHTTPLIB_RECV_BUFSIZ];
+  for (;;) {
+    auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ);
+    if (n < 0) {
+      return false;
+    } else if (n == 0) {
+      return true;
+    }
+    callback(buf, n);
+  }
+
+  return true;
+}
+
+template <typename T>
+inline bool read_content_chunked(Stream &strm, T callback) {
+  const auto bufsiz = 16;
+  char buf[bufsiz];
+
+  stream_line_reader reader(strm, buf, bufsiz);
+
+  if (!reader.getline()) { return false; }
+
+  auto chunk_len = std::stoi(reader.ptr(), 0, 16);
+
+  while (chunk_len > 0) {
+    if (!read_content_with_length(strm, chunk_len, nullptr, callback)) {
+      return false;
+    }
+
+    if (!reader.getline()) { return false; }
+
+    if (strcmp(reader.ptr(), "\r\n")) { break; }
+
+    if (!reader.getline()) { return false; }
+
+    chunk_len = std::stoi(reader.ptr(), 0, 16);
+  }
+
+  if (chunk_len == 0) {
+    // Reader terminator after chunks
+    if (!reader.getline() || strcmp(reader.ptr(), "\r\n")) return false;
+  }
+
+  return true;
+}
+
+inline bool is_chunked_transfer_encoding(const Headers &headers) {
+  return !strcasecmp(get_header_value(headers, "Transfer-Encoding", 0, ""),
+                     "chunked");
+}
+
+template <typename T, typename U>
+bool read_content(Stream &strm, T &x, uint64_t payload_max_length, int &status,
+                  Progress progress, U callback) {
+
+  ContentReceiver out = [&](const char *buf, size_t n) { callback(buf, n); };
+
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+  detail::decompressor decompressor;
+
+  if (!decompressor.is_valid()) {
+    status = 500;
+    return false;
+  }
+
+  if (x.get_header_value("Content-Encoding") == "gzip") {
+    out = [&](const char *buf, size_t n) {
+      decompressor.decompress(
+          buf, n, [&](const char *buf, size_t n) { callback(buf, n); });
+    };
+  }
+#else
+  if (x.get_header_value("Content-Encoding") == "gzip") {
+    status = 415;
+    return false;
+  }
+#endif
+
+  auto ret = true;
+  auto exceed_payload_max_length = false;
+
+  if (is_chunked_transfer_encoding(x.headers)) {
+    ret = read_content_chunked(strm, out);
+  } else if (!has_header(x.headers, "Content-Length")) {
+    ret = read_content_without_length(strm, out);
+  } else {
+    auto len = get_header_value_uint64(x.headers, "Content-Length", 0);
+    if (len > 0) {
+      if ((len > payload_max_length) ||
+          // For 32-bit platform
+          (sizeof(size_t) < sizeof(uint64_t) &&
+           len > std::numeric_limits<size_t>::max())) {
+        exceed_payload_max_length = true;
+        skip_content_with_length(strm, len);
+        ret = false;
+      } else {
+        ret = read_content_with_length(strm, len, progress, out);
+      }
+    }
+  }
+
+  if (!ret) { status = exceed_payload_max_length ? 413 : 400; }
+
+  return ret;
+}
+
+template <typename T> inline void write_headers(Stream &strm, const T &info) {
+  for (const auto &x : info.headers) {
+    strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str());
+  }
+  strm.write("\r\n");
+}
+
+template <typename T>
+inline void write_content_chunked(Stream &strm, const T &x) {
+  auto chunked_response = !x.has_header("Content-Length");
+  uint64_t offset = 0;
+  auto data_available = true;
+  while (data_available) {
+    auto chunk = x.content_producer(offset);
+    offset += chunk.size();
+    data_available = !chunk.empty();
+
+    // Emit chunked response header and footer for each chunk
+    if (chunked_response) {
+      chunk = from_i_to_hex(chunk.size()) + "\r\n" + chunk + "\r\n";
+    }
+
+    if (strm.write(chunk.c_str(), chunk.size()) < 0) {
+      break; // Stop on error
+    }
+  }
+}
+
+inline std::string encode_url(const std::string &s) {
+  std::string result;
+
+  for (auto i = 0; s[i]; i++) {
+    switch (s[i]) {
+    case ' ': result += "%20"; break;
+    case '+': result += "%2B"; break;
+    case '\r': result += "%0D"; break;
+    case '\n': result += "%0A"; break;
+    case '\'': result += "%27"; break;
+    case ',': result += "%2C"; break;
+    case ':': result += "%3A"; break;
+    case ';': result += "%3B"; break;
+    default:
+      auto c = static_cast<uint8_t>(s[i]);
+      if (c >= 0x80) {
+        result += '%';
+        char hex[4];
+        size_t len = snprintf(hex, sizeof(hex) - 1, "%02X", c);
+        assert(len == 2);
+        result.append(hex, len);
+      } else {
+        result += s[i];
+      }
+      break;
+    }
+  }
+
+  return result;
+}
+
+inline std::string decode_url(const std::string &s) {
+  std::string result;
+
+  for (size_t i = 0; i < s.size(); i++) {
+    if (s[i] == '%' && i + 1 < s.size()) {
+      if (s[i + 1] == 'u') {
+        int val = 0;
+        if (from_hex_to_i(s, i + 2, 4, val)) {
+          // 4 digits Unicode codes
+          char buff[4];
+          size_t len = to_utf8(val, buff);
+          if (len > 0) { result.append(buff, len); }
+          i += 5; // 'u0000'
+        } else {
+          result += s[i];
+        }
+      } else {
+        int val = 0;
+        if (from_hex_to_i(s, i + 1, 2, val)) {
+          // 2 digits hex codes
+          result += val;
+          i += 2; // '00'
+        } else {
+          result += s[i];
+        }
+      }
+    } else if (s[i] == '+') {
+      result += ' ';
+    } else {
+      result += s[i];
+    }
+  }
+
+  return result;
+}
+
+inline void parse_query_text(const std::string &s, Params &params) {
+  split(&s[0], &s[s.size()], '&', [&](const char *b, const char *e) {
+    std::string key;
+    std::string val;
+    split(b, e, '=', [&](const char *b, const char *e) {
+      if (key.empty()) {
+        key.assign(b, e);
+      } else {
+        val.assign(b, e);
+      }
+    });
+    params.emplace(key, decode_url(val));
+  });
+}
+
+inline bool parse_multipart_boundary(const std::string &content_type,
+                                     std::string &boundary) {
+  auto pos = content_type.find("boundary=");
+  if (pos == std::string::npos) { return false; }
+
+  boundary = content_type.substr(pos + 9);
+  return true;
+}
+
+inline bool parse_multipart_formdata(const std::string &boundary,
+                                     const std::string &body,
+                                     MultipartFiles &files) {
+  static std::string dash = "--";
+  static std::string crlf = "\r\n";
+
+  static std::regex re_content_type("Content-Type: (.*?)",
+                                    std::regex_constants::icase);
+
+  static std::regex re_content_disposition(
+      "Content-Disposition: form-data; name=\"(.*?)\"(?:; filename=\"(.*?)\")?",
+      std::regex_constants::icase);
+
+  auto dash_boundary = dash + boundary;
+
+  auto pos = body.find(dash_boundary);
+  if (pos != 0) { return false; }
+
+  pos += dash_boundary.size();
+
+  auto next_pos = body.find(crlf, pos);
+  if (next_pos == std::string::npos) { return false; }
+
+  pos = next_pos + crlf.size();
+
+  while (pos < body.size()) {
+    next_pos = body.find(crlf, pos);
+    if (next_pos == std::string::npos) { return false; }
+
+    std::string name;
+    MultipartFile file;
+
+    auto header = body.substr(pos, (next_pos - pos));
+
+    while (pos != next_pos) {
+      std::smatch m;
+      if (std::regex_match(header, m, re_content_type)) {
+        file.content_type = m[1];
+      } else if (std::regex_match(header, m, re_content_disposition)) {
+        name = m[1];
+        file.filename = m[2];
+      }
+
+      pos = next_pos + crlf.size();
+
+      next_pos = body.find(crlf, pos);
+      if (next_pos == std::string::npos) { return false; }
+
+      header = body.substr(pos, (next_pos - pos));
+    }
+
+    pos = next_pos + crlf.size();
+
+    next_pos = body.find(crlf + dash_boundary, pos);
+
+    if (next_pos == std::string::npos) { return false; }
+
+    file.offset = pos;
+    file.length = next_pos - pos;
+
+    pos = next_pos + crlf.size() + dash_boundary.size();
+
+    next_pos = body.find(crlf, pos);
+    if (next_pos == std::string::npos) { return false; }
+
+    files.emplace(name, file);
+
+    pos = next_pos + crlf.size();
+  }
+
+  return true;
+}
+
+inline std::string to_lower(const char *beg, const char *end) {
+  std::string out;
+  auto it = beg;
+  while (it != end) {
+    out += ::tolower(*it);
+    it++;
+  }
+  return out;
+}
+
+inline void make_range_header_core(std::string &) {}
+
+template <typename uint64_t>
+inline void make_range_header_core(std::string &field, uint64_t value) {
+  if (!field.empty()) { field += ", "; }
+  field += std::to_string(value) + "-";
+}
+
+template <typename uint64_t, typename... Args>
+inline void make_range_header_core(std::string &field, uint64_t value1,
+                                   uint64_t value2, Args... args) {
+  if (!field.empty()) { field += ", "; }
+  field += std::to_string(value1) + "-" + std::to_string(value2);
+  make_range_header_core(field, args...);
+}
+
+#ifdef _WIN32
+class WSInit {
+public:
+  WSInit() {
+    WSADATA wsaData;
+    WSAStartup(0x0002, &wsaData);
+  }
+
+  ~WSInit() { WSACleanup(); }
+};
+
+static WSInit wsinit_;
+#endif
+
+} // namespace detail
+
+// Header utilities
+template <typename uint64_t, typename... Args>
+inline std::pair<std::string, std::string> make_range_header(uint64_t value,
+                                                             Args... args) {
+  std::string field;
+  detail::make_range_header_core(field, value, args...);
+  field.insert(0, "bytes=");
+  return std::make_pair("Range", field);
+}
+
+
+inline std::pair<std::string, std::string> 
+make_basic_authentication_header(const std::string& username, const std::string& password) {
+  auto field = "Basic " + detail::base64_encode(username + ":" + password);
+  return std::make_pair("Authorization", field);
+}
+// Request implementation
+inline bool Request::has_header(const char *key) const {
+  return detail::has_header(headers, key);
+}
+
+inline std::string Request::get_header_value(const char *key, size_t id) const {
+  return detail::get_header_value(headers, key, id, "");
+}
+
+inline size_t Request::get_header_value_count(const char *key) const {
+  auto r = headers.equal_range(key);
+  return std::distance(r.first, r.second);
+}
+
+inline void Request::set_header(const char *key, const char *val) {
+  headers.emplace(key, val);
+}
+
+inline bool Request::has_param(const char *key) const {
+  return params.find(key) != params.end();
+}
+
+inline std::string Request::get_param_value(const char *key, size_t id) const {
+  auto it = params.find(key);
+  std::advance(it, id);
+  if (it != params.end()) { return it->second; }
+  return std::string();
+}
+
+inline size_t Request::get_param_value_count(const char *key) const {
+  auto r = params.equal_range(key);
+  return std::distance(r.first, r.second);
+}
+
+inline bool Request::has_file(const char *key) const {
+  return files.find(key) != files.end();
+}
+
+inline MultipartFile Request::get_file_value(const char *key) const {
+  auto it = files.find(key);
+  if (it != files.end()) { return it->second; }
+  return MultipartFile();
+}
+
+// Response implementation
+inline bool Response::has_header(const char *key) const {
+  return headers.find(key) != headers.end();
+}
+
+inline std::string Response::get_header_value(const char *key,
+                                              size_t id) const {
+  return detail::get_header_value(headers, key, id, "");
+}
+
+inline size_t Response::get_header_value_count(const char *key) const {
+  auto r = headers.equal_range(key);
+  return std::distance(r.first, r.second);
+}
+
+inline void Response::set_header(const char *key, const char *val) {
+  headers.emplace(key, val);
+}
+
+inline void Response::set_redirect(const char *url) {
+  set_header("Location", url);
+  status = 302;
+}
+
+inline void Response::set_content(const char *s, size_t n,
+                                  const char *content_type) {
+  body.assign(s, n);
+  set_header("Content-Type", content_type);
+}
+
+inline void Response::set_content(const std::string &s,
+                                  const char *content_type) {
+  body = s;
+  set_header("Content-Type", content_type);
+}
+
+// Rstream implementation
+template <typename... Args>
+inline void Stream::write_format(const char *fmt, const Args &... args) {
+  const auto bufsiz = 2048;
+  char buf[bufsiz];
+
+#if defined(_MSC_VER) && _MSC_VER < 1900
+  auto n = _snprintf_s(buf, bufsiz, bufsiz - 1, fmt, args...);
+#else
+  auto n = snprintf(buf, bufsiz - 1, fmt, args...);
+#endif
+  if (n > 0) {
+    if (n >= bufsiz - 1) {
+      std::vector<char> glowable_buf(bufsiz);
+
+      while (n >= static_cast<int>(glowable_buf.size() - 1)) {
+        glowable_buf.resize(glowable_buf.size() * 2);
+#if defined(_MSC_VER) && _MSC_VER < 1900
+        n = _snprintf_s(&glowable_buf[0], glowable_buf.size(),
+                        glowable_buf.size() - 1, fmt, args...);
+#else
+        n = snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...);
+#endif
+      }
+      write(&glowable_buf[0], n);
+    } else {
+      write(buf, n);
+    }
+  }
+}
+
+// Socket stream implementation
+inline SocketStream::SocketStream(socket_t sock) : sock_(sock) {}
+
+inline SocketStream::~SocketStream() {}
+
+inline int SocketStream::read(char *ptr, size_t size) {
+  if (detail::select_read(sock_, CPPHTTPLIB_READ_TIMEOUT_SECOND,
+                          CPPHTTPLIB_READ_TIMEOUT_USECOND) > 0) {
+    return recv(sock_, ptr, static_cast<int>(size), 0);
+  }
+  return -1;
+}
+
+inline int SocketStream::write(const char *ptr, size_t size) {
+  return send(sock_, ptr, static_cast<int>(size), 0);
+}
+
+inline int SocketStream::write(const char *ptr) {
+  return write(ptr, strlen(ptr));
+}
+
+inline std::string SocketStream::get_remote_addr() const {
+  return detail::get_remote_addr(sock_);
+}
+
+// Buffer stream implementation
+inline int BufferStream::read(char *ptr, size_t size) {
+#if defined(_MSC_VER) && _MSC_VER < 1900
+  return static_cast<int>(buffer._Copy_s(ptr, size, size));
+#else
+  return static_cast<int>(buffer.copy(ptr, size));
+#endif
+}
+
+inline int BufferStream::write(const char *ptr, size_t size) {
+  buffer.append(ptr, size);
+  return static_cast<int>(size);
+}
+
+inline int BufferStream::write(const char *ptr) {
+  size_t size = strlen(ptr);
+  buffer.append(ptr, size);
+  return static_cast<int>(size);
+}
+
+inline std::string BufferStream::get_remote_addr() const { return ""; }
+
+inline const std::string &BufferStream::get_buffer() const { return buffer; }
+
+// HTTP server implementation
+inline Server::Server()
+    : keep_alive_max_count_(CPPHTTPLIB_KEEPALIVE_MAX_COUNT),
+      payload_max_length_(CPPHTTPLIB_PAYLOAD_MAX_LENGTH), is_running_(false),
+      svr_sock_(INVALID_SOCKET), running_threads_(0) {
+#ifndef _WIN32
+  signal(SIGPIPE, SIG_IGN);
+#endif
+}
+
+inline Server::~Server() {}
+
+inline Server &Server::Get(const char *pattern, Handler handler) {
+  get_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
+  return *this;
+}
+
+inline Server &Server::Post(const char *pattern, Handler handler) {
+  post_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
+  return *this;
+}
+
+inline Server &Server::Put(const char *pattern, Handler handler) {
+  put_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
+  return *this;
+}
+
+inline Server &Server::Patch(const char *pattern, Handler handler) {
+  patch_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
+  return *this;
+}
+
+inline Server &Server::Delete(const char *pattern, Handler handler) {
+  delete_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
+  return *this;
+}
+
+inline Server &Server::Options(const char *pattern, Handler handler) {
+  options_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
+  return *this;
+}
+
+inline bool Server::set_base_dir(const char *path) {
+  if (detail::is_dir(path)) {
+    base_dir_ = path;
+    return true;
+  }
+  return false;
+}
+
+inline void Server::set_error_handler(Handler handler) {
+  error_handler_ = handler;
+}
+
+inline void Server::set_logger(Logger logger) { logger_ = logger; }
+
+inline void Server::set_keep_alive_max_count(size_t count) {
+  keep_alive_max_count_ = count;
+}
+
+inline void Server::set_payload_max_length(uint64_t length) {
+  payload_max_length_ = length;
+}
+
+inline int Server::bind_to_any_port(const char *host, int socket_flags) {
+  return bind_internal(host, 0, socket_flags);
+}
+
+inline bool Server::listen_after_bind() { return listen_internal(); }
+
+inline bool Server::listen(const char *host, int port, int socket_flags) {
+  if (bind_internal(host, port, socket_flags) < 0) return false;
+  return listen_internal();
+}
+
+inline bool Server::is_running() const { return is_running_; }
+
+inline void Server::stop() {
+  if (is_running_) {
+    assert(svr_sock_ != INVALID_SOCKET);
+    std::atomic<socket_t> sock(svr_sock_.exchange(INVALID_SOCKET));
+    detail::shutdown_socket(sock);
+    detail::close_socket(sock);
+  }
+}
+
+inline bool Server::parse_request_line(const char *s, Request &req) {
+  static std::regex re("(GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS) "
+                       "(([^?]+)(?:\\?(.+?))?) (HTTP/1\\.[01])\r\n");
+
+  std::cmatch m;
+  if (std::regex_match(s, m, re)) {
+    req.version = std::string(m[5]);
+    req.method = std::string(m[1]);
+    req.target = std::string(m[2]);
+    req.path = detail::decode_url(m[3]);
+
+    // Parse query text
+    auto len = std::distance(m[4].first, m[4].second);
+    if (len > 0) { detail::parse_query_text(m[4], req.params); }
+
+    return true;
+  }
+
+  return false;
+}
+
+inline void Server::write_response(Stream &strm, bool last_connection,
+                                   const Request &req, Response &res) {
+  assert(res.status != -1);
+
+  if (400 <= res.status && error_handler_) { error_handler_(req, res); }
+
+  // Response line
+  strm.write_format("HTTP/1.1 %d %s\r\n", res.status,
+                    detail::status_message(res.status));
+
+  // Headers
+  if (last_connection || req.get_header_value("Connection") == "close") {
+    res.set_header("Connection", "close");
+  }
+
+  if (!last_connection && req.get_header_value("Connection") == "Keep-Alive") {
+    res.set_header("Connection", "Keep-Alive");
+  }
+
+  if (res.body.empty()) {
+    if (!res.has_header("Content-Length")) {
+      if (res.content_producer) {
+        // Streamed response
+        res.set_header("Transfer-Encoding", "chunked");
+      } else {
+        res.set_header("Content-Length", "0");
+      }
+    }
+  } else {
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+    // TODO: 'Accpet-Encoding' has gzip, not gzip;q=0
+    const auto &encodings = req.get_header_value("Accept-Encoding");
+    if (encodings.find("gzip") != std::string::npos &&
+        detail::can_compress(res.get_header_value("Content-Type"))) {
+      if (detail::compress(res.body)) {
+        res.set_header("Content-Encoding", "gzip");
+      }
+    }
+#endif
+
+    if (!res.has_header("Content-Type")) {
+      res.set_header("Content-Type", "text/plain");
+    }
+
+    auto length = std::to_string(res.body.size());
+    res.set_header("Content-Length", length.c_str());
+  }
+
+  detail::write_headers(strm, res);
+
+  // Body
+  if (req.method != "HEAD") {
+    if (!res.body.empty()) {
+      strm.write(res.body.c_str(), res.body.size());
+    } else if (res.content_producer) {
+      detail::write_content_chunked(strm, res);
+    }
+  }
+
+  // Log
+  if (logger_) { logger_(req, res); }
+}
+
+inline bool Server::handle_file_request(Request &req, Response &res) {
+  if (!base_dir_.empty() && detail::is_valid_path(req.path)) {
+    std::string path = base_dir_ + req.path;
+
+    if (!path.empty() && path.back() == '/') { path += "index.html"; }
+
+    if (detail::is_file(path)) {
+      detail::read_file(path, res.body);
+      auto type = detail::find_content_type(path);
+      if (type) { res.set_header("Content-Type", type); }
+      res.status = 200;
+      return true;
+    }
+  }
+
+  return false;
+}
+
+inline socket_t Server::create_server_socket(const char *host, int port,
+                                             int socket_flags) const {
+  return detail::create_socket(
+      host, port,
+      [](socket_t sock, struct addrinfo &ai) -> bool {
+        if (::bind(sock, ai.ai_addr, static_cast<int>(ai.ai_addrlen))) {
+          return false;
+        }
+        if (::listen(sock, 5)) { // Listen through 5 channels
+          return false;
+        }
+        return true;
+      },
+      socket_flags);
+}
+
+inline int Server::bind_internal(const char *host, int port, int socket_flags) {
+  if (!is_valid()) { return -1; }
+
+  svr_sock_ = create_server_socket(host, port, socket_flags);
+  if (svr_sock_ == INVALID_SOCKET) { return -1; }
+
+  if (port == 0) {
+    struct sockaddr_storage address;
+    socklen_t len = sizeof(address);
+    if (getsockname(svr_sock_, reinterpret_cast<struct sockaddr *>(&address),
+                    &len) == -1) {
+      return -1;
+    }
+    if (address.ss_family == AF_INET) {
+      return ntohs(reinterpret_cast<struct sockaddr_in *>(&address)->sin_port);
+    } else if (address.ss_family == AF_INET6) {
+      return ntohs(
+          reinterpret_cast<struct sockaddr_in6 *>(&address)->sin6_port);
+    } else {
+      return -1;
+    }
+  } else {
+    return port;
+  }
+}
+
+inline bool Server::listen_internal() {
+  auto ret = true;
+
+  is_running_ = true;
+
+  for (;;) {
+    if (svr_sock_ == INVALID_SOCKET) {
+      // The server socket was closed by 'stop' method.
+      break;
+    }
+
+    auto val = detail::select_read(svr_sock_, 0, 100000);
+
+    if (val == 0) { // Timeout
+      continue;
+    }
+
+    socket_t sock = accept(svr_sock_, nullptr, nullptr);
+
+    if (sock == INVALID_SOCKET) {
+      if (svr_sock_ != INVALID_SOCKET) {
+        detail::close_socket(svr_sock_);
+        ret = false;
+      } else {
+        ; // The server socket was closed by user.
+      }
+      break;
+    }
+
+    // TODO: Use thread pool...
+    std::thread([=]() {
+      {
+        std::lock_guard<std::mutex> guard(running_threads_mutex_);
+        running_threads_++;
+      }
+
+      read_and_close_socket(sock);
+
+      {
+        std::lock_guard<std::mutex> guard(running_threads_mutex_);
+        running_threads_--;
+      }
+    }).detach();
+  }
+
+  // TODO: Use thread pool...
+  for (;;) {
+    std::this_thread::sleep_for(std::chrono::milliseconds(10));
+    std::lock_guard<std::mutex> guard(running_threads_mutex_);
+    if (!running_threads_) { break; }
+  }
+
+  is_running_ = false;
+
+  return ret;
+}
+
+inline bool Server::routing(Request &req, Response &res) {
+  if (req.method == "GET" && handle_file_request(req, res)) { return true; }
+
+  if (req.method == "GET" || req.method == "HEAD") {
+    return dispatch_request(req, res, get_handlers_);
+  } else if (req.method == "POST") {
+    return dispatch_request(req, res, post_handlers_);
+  } else if (req.method == "PUT") {
+    return dispatch_request(req, res, put_handlers_);
+  } else if (req.method == "PATCH") {
+    return dispatch_request(req, res, patch_handlers_);
+  } else if (req.method == "DELETE") {
+    return dispatch_request(req, res, delete_handlers_);
+  } else if (req.method == "OPTIONS") {
+    return dispatch_request(req, res, options_handlers_);
+  }
+  return false;
+}
+
+inline bool Server::dispatch_request(Request &req, Response &res,
+                                     Handlers &handlers) {
+  for (const auto &x : handlers) {
+    const auto &pattern = x.first;
+    const auto &handler = x.second;
+
+    if (std::regex_match(req.path, req.matches, pattern)) {
+      handler(req, res);
+      return true;
+    }
+  }
+  return false;
+}
+
+inline bool
+Server::process_request(Stream &strm, bool last_connection,
+                        bool &connection_close,
+                        std::function<void(Request &)> setup_request) {
+  const auto bufsiz = 2048;
+  char buf[bufsiz];
+
+  detail::stream_line_reader reader(strm, buf, bufsiz);
+
+  // Connection has been closed on client
+  if (!reader.getline()) { return false; }
+
+  Request req;
+  Response res;
+
+  res.version = "HTTP/1.1";
+
+  // Check if the request URI doesn't exceed the limit
+  if (reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
+    res.status = 414;
+    write_response(strm, last_connection, req, res);
+    return true;
+  }
+
+  // Request line and headers
+  if (!parse_request_line(reader.ptr(), req) ||
+      !detail::read_headers(strm, req.headers)) {
+    res.status = 400;
+    write_response(strm, last_connection, req, res);
+    return true;
+  }
+
+  if (req.get_header_value("Connection") == "close") {
+    connection_close = true;
+  }
+
+  req.set_header("REMOTE_ADDR", strm.get_remote_addr().c_str());
+
+  // Body
+  if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH") {
+    if (!detail::read_content(
+            strm, req, payload_max_length_, res.status, Progress(),
+            [&](const char *buf, size_t n) { req.body.append(buf, n); })) {
+      write_response(strm, last_connection, req, res);
+      return true;
+    }
+
+    const auto &content_type = req.get_header_value("Content-Type");
+
+    if (!content_type.find("application/x-www-form-urlencoded")) {
+      detail::parse_query_text(req.body, req.params);
+    } else if (!content_type.find("multipart/form-data")) {
+      std::string boundary;
+      if (!detail::parse_multipart_boundary(content_type, boundary) ||
+          !detail::parse_multipart_formdata(boundary, req.body, req.files)) {
+        res.status = 400;
+        write_response(strm, last_connection, req, res);
+        return true;
+      }
+    }
+  }
+
+  // TODO: Add additional request info
+  if (setup_request) { setup_request(req); }
+
+  if (routing(req, res)) {
+    if (res.status == -1) { res.status = 200; }
+  } else {
+    res.status = 404;
+  }
+
+  write_response(strm, last_connection, req, res);
+  return true;
+}
+
+inline bool Server::is_valid() const { return true; }
+
+inline bool Server::read_and_close_socket(socket_t sock) {
+  return detail::read_and_close_socket(
+      sock, keep_alive_max_count_,
+      [this](Stream &strm, bool last_connection, bool &connection_close) {
+        return process_request(strm, last_connection, connection_close);
+      });
+}
+
+// HTTP client implementation
+inline Client::Client(const char *host, int port, time_t timeout_sec)
+    : host_(host), port_(port), timeout_sec_(timeout_sec),
+      host_and_port_(host_ + ":" + std::to_string(port_)) {}
+
+inline Client::~Client() {}
+
+inline bool Client::is_valid() const { return true; }
+
+inline socket_t Client::create_client_socket() const {
+  return detail::create_socket(
+      host_.c_str(), port_, [=](socket_t sock, struct addrinfo &ai) -> bool {
+        detail::set_nonblocking(sock, true);
+
+        auto ret = connect(sock, ai.ai_addr, static_cast<int>(ai.ai_addrlen));
+        if (ret < 0) {
+          if (detail::is_connection_error() ||
+              !detail::wait_until_socket_is_ready(sock, timeout_sec_, 0)) {
+            detail::close_socket(sock);
+            return false;
+          }
+        }
+
+        detail::set_nonblocking(sock, false);
+        return true;
+      });
+}
+
+inline bool Client::read_response_line(Stream &strm, Response &res) {
+  const auto bufsiz = 2048;
+  char buf[bufsiz];
+
+  detail::stream_line_reader reader(strm, buf, bufsiz);
+
+  if (!reader.getline()) { return false; }
+
+  const static std::regex re("(HTTP/1\\.[01]) (\\d+?) .*\r\n");
+
+  std::cmatch m;
+  if (std::regex_match(reader.ptr(), m, re)) {
+    res.version = std::string(m[1]);
+    res.status = std::stoi(std::string(m[2]));
+  }
+
+  return true;
+}
+
+inline bool Client::send(Request &req, Response &res) {
+  if (req.path.empty()) { return false; }
+
+  auto sock = create_client_socket();
+  if (sock == INVALID_SOCKET) { return false; }
+
+  return read_and_close_socket(sock, req, res);
+}
+
+inline void Client::write_request(Stream &strm, Request &req) {
+  BufferStream bstrm;
+
+  // Request line
+  auto path = detail::encode_url(req.path);
+
+  bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str());
+
+  // Headers
+  if (!req.has_header("Host")) {
+    if (is_ssl()) {
+      if (port_ == 443) {
+        req.set_header("Host", host_.c_str());
+      } else {
+        req.set_header("Host", host_and_port_.c_str());
+      }
+    } else {
+      if (port_ == 80) {
+        req.set_header("Host", host_.c_str());
+      } else {
+        req.set_header("Host", host_and_port_.c_str());
+      }
+    }
+  }
+
+  if (!req.has_header("Accept")) { req.set_header("Accept", "*/*"); }
+
+  if (!req.has_header("User-Agent")) {
+    req.set_header("User-Agent", "cpp-httplib/0.2");
+  }
+
+  // TODO: Support KeepAlive connection
+  // if (!req.has_header("Connection")) {
+  req.set_header("Connection", "close");
+  // }
+
+  if (req.body.empty()) {
+    if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH") {
+      req.set_header("Content-Length", "0");
+    }
+  } else {
+    if (!req.has_header("Content-Type")) {
+      req.set_header("Content-Type", "text/plain");
+    }
+
+    if (!req.has_header("Content-Length")) {
+      auto length = std::to_string(req.body.size());
+      req.set_header("Content-Length", length.c_str());
+    }
+  }
+
+  detail::write_headers(bstrm, req);
+
+  // Body
+  if (!req.body.empty()) { bstrm.write(req.body.c_str(), req.body.size()); }
+
+  // Flush buffer
+  auto &data = bstrm.get_buffer();
+  strm.write(data.data(), data.size());
+}
+
+inline bool Client::process_request(Stream &strm, Request &req, Response &res,
+                                    bool &connection_close) {
+  // Send request
+  write_request(strm, req);
+
+  // Receive response and headers
+  if (!read_response_line(strm, res) ||
+      !detail::read_headers(strm, res.headers)) {
+    return false;
+  }
+
+  if (res.get_header_value("Connection") == "close" ||
+      res.version == "HTTP/1.0") {
+    connection_close = true;
+  }
+
+  // Body
+  if (req.method != "HEAD") {
+    ContentReceiver out = [&](const char *buf, size_t n) {
+      res.body.append(buf, n);
+    };
+
+    if (res.content_receiver) {
+      out = [&](const char *buf, size_t n) { res.content_receiver(buf, n); };
+    }
+
+    int dummy_status;
+    if (!detail::read_content(strm, res, std::numeric_limits<uint64_t>::max(),
+                              dummy_status, res.progress, out)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+inline bool Client::read_and_close_socket(socket_t sock, Request &req,
+                                          Response &res) {
+  return detail::read_and_close_socket(
+      sock, 0,
+      [&](Stream &strm, bool /*last_connection*/, bool &connection_close) {
+        return process_request(strm, req, res, connection_close);
+      });
+}
+
+inline bool Client::is_ssl() const { return false; }
+
+inline std::shared_ptr<Response> Client::Get(const char *path,
+                                             Progress progress) {
+  return Get(path, Headers(), progress);
+}
+
+inline std::shared_ptr<Response>
+Client::Get(const char *path, const Headers &headers, Progress progress) {
+  Request req;
+  req.method = "GET";
+  req.path = path;
+  req.headers = headers;
+
+  auto res = std::make_shared<Response>();
+  res->progress = progress;
+
+  return send(req, *res) ? res : nullptr;
+}
+
+inline std::shared_ptr<Response> Client::Get(const char *path,
+                                             ContentReceiver content_receiver,
+                                             Progress progress) {
+  return Get(path, Headers(), content_receiver, progress);
+}
+
+inline std::shared_ptr<Response> Client::Get(const char *path,
+                                             const Headers &headers,
+                                             ContentReceiver content_receiver,
+                                             Progress progress) {
+  Request req;
+  req.method = "GET";
+  req.path = path;
+  req.headers = headers;
+
+  auto res = std::make_shared<Response>();
+  res->content_receiver = content_receiver;
+  res->progress = progress;
+
+  return send(req, *res) ? res : nullptr;
+}
+
+inline std::shared_ptr<Response> Client::Head(const char *path) {
+  return Head(path, Headers());
+}
+
+inline std::shared_ptr<Response> Client::Head(const char *path,
+                                              const Headers &headers) {
+  Request req;
+  req.method = "HEAD";
+  req.headers = headers;
+  req.path = path;
+
+  auto res = std::make_shared<Response>();
+
+  return send(req, *res) ? res : nullptr;
+}
+
+inline std::shared_ptr<Response> Client::Post(const char *path,
+                                              const std::string &body,
+                                              const char *content_type) {
+  return Post(path, Headers(), body, content_type);
+}
+
+inline std::shared_ptr<Response> Client::Post(const char *path,
+                                              const Headers &headers,
+                                              const std::string &body,
+                                              const char *content_type) {
+  Request req;
+  req.method = "POST";
+  req.headers = headers;
+  req.path = path;
+
+  req.headers.emplace("Content-Type", content_type);
+  req.body = body;
+
+  auto res = std::make_shared<Response>();
+
+  return send(req, *res) ? res : nullptr;
+}
+
+inline std::shared_ptr<Response> Client::Post(const char *path,
+                                              const Params &params) {
+  return Post(path, Headers(), params);
+}
+
+inline std::shared_ptr<Response>
+Client::Post(const char *path, const Headers &headers, const Params &params) {
+  std::string query;
+  for (auto it = params.begin(); it != params.end(); ++it) {
+    if (it != params.begin()) { query += "&"; }
+    query += it->first;
+    query += "=";
+    query += detail::encode_url(it->second);
+  }
+
+  return Post(path, headers, query, "application/x-www-form-urlencoded");
+}
+
+inline std::shared_ptr<Response> Client::Put(const char *path,
+                                             const std::string &body,
+                                             const char *content_type) {
+  return Put(path, Headers(), body, content_type);
+}
+
+inline std::shared_ptr<Response> Client::Put(const char *path,
+                                             const Headers &headers,
+                                             const std::string &body,
+                                             const char *content_type) {
+  Request req;
+  req.method = "PUT";
+  req.headers = headers;
+  req.path = path;
+
+  req.headers.emplace("Content-Type", content_type);
+  req.body = body;
+
+  auto res = std::make_shared<Response>();
+
+  return send(req, *res) ? res : nullptr;
+}
+
+inline std::shared_ptr<Response> Client::Patch(const char *path,
+                                               const std::string &body,
+                                               const char *content_type) {
+  return Patch(path, Headers(), body, content_type);
+}
+
+inline std::shared_ptr<Response> Client::Patch(const char *path,
+                                               const Headers &headers,
+                                               const std::string &body,
+                                               const char *content_type) {
+  Request req;
+  req.method = "PATCH";
+  req.headers = headers;
+  req.path = path;
+
+  req.headers.emplace("Content-Type", content_type);
+  req.body = body;
+
+  auto res = std::make_shared<Response>();
+
+  return send(req, *res) ? res : nullptr;
+}
+
+inline std::shared_ptr<Response> Client::Delete(const char *path,
+                                                const std::string &body,
+                                                const char *content_type) {
+  return Delete(path, Headers(), body, content_type);
+}
+
+inline std::shared_ptr<Response> Client::Delete(const char *path,
+                                                const Headers &headers,
+                                                const std::string &body,
+                                                const char *content_type) {
+  Request req;
+  req.method = "DELETE";
+  req.headers = headers;
+  req.path = path;
+
+  if (content_type) { req.headers.emplace("Content-Type", content_type); }
+  req.body = body;
+
+  auto res = std::make_shared<Response>();
+
+  return send(req, *res) ? res : nullptr;
+}
+
+inline std::shared_ptr<Response> Client::Options(const char *path) {
+  return Options(path, Headers());
+}
+
+inline std::shared_ptr<Response> Client::Options(const char *path,
+                                                 const Headers &headers) {
+  Request req;
+  req.method = "OPTIONS";
+  req.path = path;
+  req.headers = headers;
+
+  auto res = std::make_shared<Response>();
+
+  return send(req, *res) ? res : nullptr;
+}
+
+/*
+ * SSL Implementation
+ */
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+namespace detail {
+
+template <typename U, typename V, typename T>
+inline bool
+read_and_close_socket_ssl(socket_t sock, size_t keep_alive_max_count,
+                          // TODO: OpenSSL 1.0.2 occasionally crashes...
+                          // The upcoming 1.1.0 is going to be thread safe.
+                          SSL_CTX *ctx, std::mutex &ctx_mutex,
+                          U SSL_connect_or_accept, V setup, T callback) {
+  SSL *ssl = nullptr;
+  {
+    std::lock_guard<std::mutex> guard(ctx_mutex);
+    ssl = SSL_new(ctx);
+  }
+
+  if (!ssl) {
+    close_socket(sock);
+    return false;
+  }
+
+  auto bio = BIO_new_socket(sock, BIO_NOCLOSE);
+  SSL_set_bio(ssl, bio, bio);
+
+  if (!setup(ssl)) {
+    SSL_shutdown(ssl);
+    {
+      std::lock_guard<std::mutex> guard(ctx_mutex);
+      SSL_free(ssl);
+    }
+
+    close_socket(sock);
+    return false;
+  }
+
+  bool ret = false;
+
+  if (SSL_connect_or_accept(ssl) == 1) {
+    if (keep_alive_max_count > 0) {
+      auto count = keep_alive_max_count;
+      while (count > 0 &&
+             detail::select_read(sock, CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND,
+                                 CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0) {
+        SSLSocketStream strm(sock, ssl);
+        auto last_connection = count == 1;
+        auto connection_close = false;
+
+        ret = callback(ssl, strm, last_connection, connection_close);
+        if (!ret || connection_close) { break; }
+
+        count--;
+      }
+    } else {
+      SSLSocketStream strm(sock, ssl);
+      auto dummy_connection_close = false;
+      ret = callback(ssl, strm, true, dummy_connection_close);
+    }
+  }
+
+  SSL_shutdown(ssl);
+  {
+    std::lock_guard<std::mutex> guard(ctx_mutex);
+    SSL_free(ssl);
+  }
+
+  close_socket(sock);
+
+  return ret;
+}
+
+class SSLInit {
+public:
+  SSLInit() {
+    SSL_load_error_strings();
+    SSL_library_init();
+  }
+
+  ~SSLInit() { ERR_free_strings(); }
+};
+
+static SSLInit sslinit_;
+
+} // namespace detail
+
+// SSL socket stream implementation
+inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl)
+    : sock_(sock), ssl_(ssl) {}
+
+inline SSLSocketStream::~SSLSocketStream() {}
+
+inline int SSLSocketStream::read(char *ptr, size_t size) {
+  if (SSL_pending(ssl_) > 0 ||
+      detail::select_read(sock_, CPPHTTPLIB_READ_TIMEOUT_SECOND,
+                          CPPHTTPLIB_READ_TIMEOUT_USECOND) > 0) {
+    return SSL_read(ssl_, ptr, size);
+  }
+  return -1;
+}
+
+inline int SSLSocketStream::write(const char *ptr, size_t size) {
+  return SSL_write(ssl_, ptr, size);
+}
+
+inline int SSLSocketStream::write(const char *ptr) {
+  return write(ptr, strlen(ptr));
+}
+
+inline std::string SSLSocketStream::get_remote_addr() const {
+  return detail::get_remote_addr(sock_);
+}
+
+// SSL HTTP server implementation
+inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
+                            const char *client_ca_cert_file_path,
+                            const char *client_ca_cert_dir_path) {
+  ctx_ = SSL_CTX_new(SSLv23_server_method());
+
+  if (ctx_) {
+    SSL_CTX_set_options(ctx_,
+                        SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
+                            SSL_OP_NO_COMPRESSION |
+                            SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+
+    // auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
+    // SSL_CTX_set_tmp_ecdh(ctx_, ecdh);
+    // EC_KEY_free(ecdh);
+
+    if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 ||
+        SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) !=
+            1) {
+      SSL_CTX_free(ctx_);
+      ctx_ = nullptr;
+    } else if (client_ca_cert_file_path || client_ca_cert_dir_path) {
+      // if (client_ca_cert_file_path) {
+      //   auto list = SSL_load_client_CA_file(client_ca_cert_file_path);
+      //   SSL_CTX_set_client_CA_list(ctx_, list);
+      // }
+
+      SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path,
+                                    client_ca_cert_dir_path);
+
+      SSL_CTX_set_verify(
+          ctx_,
+          SSL_VERIFY_PEER |
+              SSL_VERIFY_FAIL_IF_NO_PEER_CERT, // SSL_VERIFY_CLIENT_ONCE,
+          nullptr);
+    }
+  }
+}
+
+inline SSLServer::~SSLServer() {
+  if (ctx_) { SSL_CTX_free(ctx_); }
+}
+
+inline bool SSLServer::is_valid() const { return ctx_; }
+
+inline bool SSLServer::read_and_close_socket(socket_t sock) {
+  return detail::read_and_close_socket_ssl(
+      sock, keep_alive_max_count_, ctx_, ctx_mutex_, SSL_accept,
+      [](SSL * /*ssl*/) { return true; },
+      [this](SSL *ssl, Stream &strm, bool last_connection,
+             bool &connection_close) {
+        return process_request(strm, last_connection, connection_close,
+                               [&](Request &req) { req.ssl = ssl; });
+      });
+}
+
+// SSL HTTP client implementation
+inline SSLClient::SSLClient(const char *host, int port, time_t timeout_sec,
+                            const char *client_cert_path,
+                            const char *client_key_path)
+    : Client(host, port, timeout_sec) {
+  ctx_ = SSL_CTX_new(SSLv23_client_method());
+
+  detail::split(&host_[0], &host_[host_.size()], '.',
+                [&](const char *b, const char *e) {
+                  host_components_.emplace_back(std::string(b, e));
+                });
+  if (client_cert_path && client_key_path) {
+    if (SSL_CTX_use_certificate_file(ctx_, client_cert_path,
+                                     SSL_FILETYPE_PEM) != 1 ||
+        SSL_CTX_use_PrivateKey_file(ctx_, client_key_path, SSL_FILETYPE_PEM) !=
+            1) {
+      SSL_CTX_free(ctx_);
+      ctx_ = nullptr;
+    }
+  }
+}
+
+inline SSLClient::~SSLClient() {
+  if (ctx_) { SSL_CTX_free(ctx_); }
+}
+
+inline bool SSLClient::is_valid() const { return ctx_; }
+
+inline void SSLClient::set_ca_cert_path(const char *ca_cert_file_path,
+                                        const char *ca_cert_dir_path) {
+  if (ca_cert_file_path) { ca_cert_file_path_ = ca_cert_file_path; }
+  if (ca_cert_dir_path) { ca_cert_dir_path_ = ca_cert_dir_path; }
+}
+
+inline void SSLClient::enable_server_certificate_verification(bool enabled) {
+  server_certificate_verification_ = enabled;
+}
+
+inline long SSLClient::get_openssl_verify_result() const {
+  return verify_result_;
+}
+
+inline bool SSLClient::read_and_close_socket(socket_t sock, Request &req,
+                                             Response &res) {
+
+  return is_valid() &&
+         detail::read_and_close_socket_ssl(
+             sock, 0, ctx_, ctx_mutex_,
+             [&](SSL *ssl) {
+               if (ca_cert_file_path_.empty()) {
+                 SSL_CTX_set_verify(ctx_, SSL_VERIFY_NONE, nullptr);
+               } else {
+                 if (!SSL_CTX_load_verify_locations(
+                         ctx_, ca_cert_file_path_.c_str(), nullptr)) {
+                   return false;
+                 }
+                 SSL_CTX_set_verify(ctx_, SSL_VERIFY_PEER, nullptr);
+               }
+
+               if (SSL_connect(ssl) != 1) { return false; }
+
+               if (server_certificate_verification_) {
+                 verify_result_ = SSL_get_verify_result(ssl);
+
+                 if (verify_result_ != X509_V_OK) { return false; }
+
+                 auto server_cert = SSL_get_peer_certificate(ssl);
+
+                 if (server_cert == nullptr) { return false; }
+
+                 if (!verify_host(server_cert)) {
+                   X509_free(server_cert);
+                   return false;
+                 }
+                 X509_free(server_cert);
+               }
+
+               return true;
+             },
+             [&](SSL *ssl) {
+               SSL_set_tlsext_host_name(ssl, host_.c_str());
+               return true;
+             },
+             [&](SSL * /*ssl*/, Stream &strm, bool /*last_connection*/,
+                 bool &connection_close) {
+               return process_request(strm, req, res, connection_close);
+             });
+}
+
+inline bool SSLClient::is_ssl() const { return true; }
+
+inline bool SSLClient::verify_host(X509 *server_cert) const {
+  /* Quote from RFC2818 section 3.1 "Server Identity"
+
+     If a subjectAltName extension of type dNSName is present, that MUST
+     be used as the identity. Otherwise, the (most specific) Common Name
+     field in the Subject field of the certificate MUST be used. Although
+     the use of the Common Name is existing practice, it is deprecated and
+     Certification Authorities are encouraged to use the dNSName instead.
+
+     Matching is performed using the matching rules specified by
+     [RFC2459].  If more than one identity of a given type is present in
+     the certificate (e.g., more than one dNSName name, a match in any one
+     of the set is considered acceptable.) Names may contain the wildcard
+     character * which is considered to match any single domain name
+     component or component fragment. E.g., *.a.com matches foo.a.com but
+     not bar.foo.a.com. f*.com matches foo.com but not bar.com.
+
+     In some cases, the URI is specified as an IP address rather than a
+     hostname. In this case, the iPAddress subjectAltName must be present
+     in the certificate and must exactly match the IP in the URI.
+
+  */
+  return verify_host_with_subject_alt_name(server_cert) ||
+         verify_host_with_common_name(server_cert);
+}
+
+inline bool
+SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
+  auto ret = false;
+
+  auto type = GEN_DNS;
+
+  struct in6_addr addr6;
+  struct in_addr addr;
+  size_t addr_len = 0;
+
+  if (inet_pton(AF_INET6, host_.c_str(), &addr6)) {
+    type = GEN_IPADD;
+    addr_len = sizeof(struct in6_addr);
+  } else if (inet_pton(AF_INET, host_.c_str(), &addr)) {
+    type = GEN_IPADD;
+    addr_len = sizeof(struct in_addr);
+  }
+
+  auto alt_names = static_cast<const struct stack_st_GENERAL_NAME *>(
+      X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr));
+
+  if (alt_names) {
+    auto dsn_matched = false;
+    auto ip_mached = false;
+
+    auto count = sk_GENERAL_NAME_num(alt_names);
+
+    for (auto i = 0; i < count && !dsn_matched; i++) {
+      auto val = sk_GENERAL_NAME_value(alt_names, i);
+      if (val->type == type) {
+        auto name = (const char *)ASN1_STRING_get0_data(val->d.ia5);
+        auto name_len = (size_t)ASN1_STRING_length(val->d.ia5);
+
+        if (strlen(name) == name_len) {
+          switch (type) {
+          case GEN_DNS: dsn_matched = check_host_name(name, name_len); break;
+
+          case GEN_IPADD:
+            if (!memcmp(&addr6, name, addr_len) ||
+                !memcmp(&addr, name, addr_len)) {
+              ip_mached = true;
+            }
+            break;
+          }
+        }
+      }
+    }
+
+    if (dsn_matched || ip_mached) { ret = true; }
+  }
+
+  GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names);
+
+  return ret;
+}
+
+inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const {
+  const auto subject_name = X509_get_subject_name(server_cert);
+
+  if (subject_name != nullptr) {
+    char name[BUFSIZ];
+    auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName,
+                                              name, sizeof(name));
+
+    if (name_len != -1) { return check_host_name(name, name_len); }
+  }
+
+  return false;
+}
+
+inline bool SSLClient::check_host_name(const char *pattern,
+                                       size_t pattern_len) const {
+  if (host_.size() == pattern_len && host_ == pattern) { return true; }
+
+  // Wildcard match
+  // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484
+  std::vector<std::string> pattern_components;
+  detail::split(&pattern[0], &pattern[pattern_len], '.',
+                [&](const char *b, const char *e) {
+                  pattern_components.emplace_back(std::string(b, e));
+                });
+
+  if (host_components_.size() != pattern_components.size()) { return false; }
+
+  auto itr = pattern_components.begin();
+  for (const auto &h : host_components_) {
+    auto &p = *itr;
+    if (p != h && p != "*") {
+      auto partial_match = (p.size() > 0 && p[p.size() - 1] == '*' &&
+                            !p.compare(0, p.size() - 1, h));
+      if (!partial_match) { return false; }
+    }
+    ++itr;
+  }
+
+  return true;
+}
+#endif
+
+} // namespace httplib
+
+#endif // CPPHTTPLIB_HTTPLIB_H

+ 1 - 4
ext/ed25519-amd64-asm/sign.c

@@ -124,18 +124,15 @@ void get_hram(unsigned char *hram, const unsigned char *sm, const unsigned char
 
 extern void ZT_sha512internal(void *digest,const void *data,unsigned int len);
 
-extern void ed25519_amd64_asm_sign(const unsigned char *sk,const unsigned char *pk,const unsigned char *m,const unsigned int mlen,unsigned char *sig)
+extern void ed25519_amd64_asm_sign(const unsigned char *sk,const unsigned char *pk,const unsigned char *digest,unsigned char *sig)
 {
   unsigned char az[64];
   unsigned char nonce[64];
   unsigned char hram[64];
   sc25519 sck, scs, scsk;
   ge25519 ger;
-  unsigned char digest[64];
   unsigned int i;
 
-  ZT_sha512internal(digest,m,mlen);
-
   ZT_sha512internal(az,sk,32);
   az[0] &= 248;
   az[31] &= 127;

+ 23 - 11
ext/installfiles/linux/zerotier-containerized/Dockerfile

@@ -1,20 +1,32 @@
-FROM alpine:latest
-MAINTAINER Adam Ierymenko <[email protected]>
+## NOTE: to retain configuration; mount a Docker volume, or use a bind-mount, on /var/lib/zerotier-one
+
+FROM debian:buster-slim as builder
+
+## Supports x86_64, x86, arm, and arm64
+
+RUN apt-get update && apt-get install -y curl gnupg
+RUN apt-key adv --keyserver ha.pool.sks-keyservers.net --recv-keys 0x1657198823e52a61  && \
+    echo "deb http://download.zerotier.com/debian/buster buster main" > /etc/apt/sources.list.d/zerotier.list
+RUN apt-get update && apt-get install -y zerotier-one=1.2.12
+RUN curl https://raw.githubusercontent.com/zerotier/ZeroTierOne/master/ext/installfiles/linux/zerotier-containerized/main.sh > /var/lib/zerotier-one/main.sh
 
-LABEL version="1.2.4"
+FROM alpine:latest
+LABEL version="1.2.12"
 LABEL description="Containerized ZeroTier One for use on CoreOS or other Docker-only Linux hosts."
 
 # Uncomment to build in container
-#RUN apk add --update alpine-sdk linux-headers
-
+# RUN apk add --update alpine-sdk linux-headers
 RUN apk add --update libgcc libstdc++
 
-ADD zerotier-one /
-RUN chmod 0755 /zerotier-one
-RUN ln -sf /zerotier-one /zerotier-cli
+# ZeroTier relies on UDP port 9993
+EXPOSE 9993/udp
+
 RUN mkdir -p /var/lib/zerotier-one
+COPY --from=builder /usr/sbin/zerotier-cli /usr/sbin/zerotier-cli
+COPY --from=builder /usr/sbin/zerotier-idtool /usr/sbin/zerotier-idtool
+COPY --from=builder /usr/sbin/zerotier-one /usr/sbin/zerotier-one
+COPY --from=builder /var/lib/zerotier-one/main.sh /main.sh
 
-ADD main.sh /
 RUN chmod 0755 /main.sh
-
-ENTRYPOINT /main.sh
+ENTRYPOINT ["/main.sh"]
+CMD ["zerotier-one"]

+ 1 - 1
ext/installfiles/linux/zerotier-containerized/main.sh

@@ -7,4 +7,4 @@ if [ ! -e /dev/net/tun ]; then
 	exit 1
 fi
 
-exec /zerotier-one
+exec "$@"

+ 3 - 3
ext/installfiles/mac/ZeroTier One.pkgproj

@@ -109,9 +109,9 @@
 														<key>CHILDREN</key>
 														<array/>
 														<key>GID</key>
-														<integer>0</integer>
+														<integer>80</integer>
 														<key>PATH</key>
-														<string>../../bin/tap-mac/tap.kext</string>
+														<string>../../../MacEthernetTapAgent</string>
 														<key>PATH_TYPE</key>
 														<integer>1</integer>
 														<key>PERMISSIONS</key>
@@ -664,7 +664,7 @@
 			<key>USE_HFS+_COMPRESSION</key>
 			<false/>
 			<key>VERSION</key>
-			<string>1.2.12</string>
+			<string>1.4.0.1</string>
 		</dict>
 		<key>PROJECT_COMMENTS</key>
 		<dict>

+ 1 - 1
ext/installfiles/mac/uninstall.sh

@@ -27,7 +27,7 @@ kextunload '/Library/Application Support/ZeroTier/One/tap.kext' >>/dev/null 2>&1
 echo "Removing ZeroTier One files..."
 
 rm -rf '/Applications/ZeroTier One.app'
-rm -f '/usr/bin/zerotier-one' '/usr/bin/zerotier-idtool' '/usr/bin/zerotier-cli' '/Library/LaunchDaemons/com.zerotier.one.plist'
+rm -f '/usr/local/bin/zerotier-one' '/usr/local/bin/zerotier-idtool' '/usr/local/bin/zerotier-cli' '/Library/LaunchDaemons/com.zerotier.one.plist'
 
 cd '/Library/Application Support/ZeroTier/One'
 if [ "`pwd`" = '/Library/Application Support/ZeroTier/One' ]; then

+ 5 - 5
ext/installfiles/windows/ZeroTier One.aip

@@ -27,10 +27,10 @@
     <ROW Property="CTRLS" Value="2"/>
     <ROW Property="MSIFASTINSTALL" MultiBuildValue="DefaultBuild:2"/>
     <ROW Property="Manufacturer" Value="ZeroTier, Inc."/>
-    <ROW Property="ProductCode" Value="1033:{855E8629-580C-4BDF-8B59-B9290C7E7BA5} " Type="16"/>
+    <ROW Property="ProductCode" Value="1033:{FF7D9C9B-E9F3-460A-8EA9-9074AF186E1E} " Type="16"/>
     <ROW Property="ProductLanguage" Value="1033"/>
     <ROW Property="ProductName" Value="ZeroTier One"/>
-    <ROW Property="ProductVersion" Value="1.2.12" Type="32"/>
+    <ROW Property="ProductVersion" Value="1.4.0" Type="32"/>
     <ROW Property="REBOOT" MultiBuildValue="DefaultBuild:ReallySuppress"/>
     <ROW Property="RUNAPPLICATION" Value="1" Type="4"/>
     <ROW Property="SecureCustomProperties" Value="OLDPRODUCTS;AI_NEWERPRODUCTFOUND;AI_SETUPEXEPATH;SETUPEXEDIR"/>
@@ -64,7 +64,7 @@
     <ROW Directory="x86_Dir" Directory_Parent="tapwindows_Dir" DefaultDir="x86"/>
   </COMPONENT>
   <COMPONENT cid="caphyon.advinst.msicomp.MsiCompsComponent">
-    <ROW Component="AI_CustomARPName" ComponentId="{92D9A995-E340-41B2-98F5-F2DB3F6E8AD8}" Directory_="APPDIR" Attributes="4" KeyPath="DisplayName" Options="1"/>
+    <ROW Component="AI_CustomARPName" ComponentId="{3771DD83-BFE1-467A-88C3-DE78086A2B44}" Directory_="APPDIR" Attributes="4" KeyPath="DisplayName" Options="1"/>
     <ROW Component="AI_DisableModify" ComponentId="{020DCABD-5D56-49B9-AF48-F07F0B55E590}" Directory_="APPDIR" Attributes="4" KeyPath="NoModify" Options="1"/>
     <ROW Component="AI_ExePath" ComponentId="{8E02B36C-7A19-429B-A93E-77A9261AC918}" Directory_="APPDIR" Attributes="4" KeyPath="AI_ExePath"/>
     <ROW Component="Hardcodet.Wpf.TaskbarNotification.dll" ComponentId="{BEA825AF-2555-44AF-BE40-47FFC16DCBA6}" Directory_="APPDIR" Attributes="0" KeyPath="Hardcodet.Wpf.TaskbarNotification.dll"/>
@@ -454,10 +454,10 @@
     <ROW XmlAttribute="xsischemaLocation" XmlElement="swidsoftware_identification_tag" Name="xsi:schemaLocation" Flags="14" Order="3" Value="http://standards.iso.org/iso/19770/-2/2008/schema.xsd software_identification_tag.xsd"/>
   </COMPONENT>
   <COMPONENT cid="caphyon.advinst.msicomp.XmlElementComponent">
-    <ROW XmlElement="swidbuild" ParentElement="swidnumeric" Name="swid:build" Condition="1" Order="2" Flags="14" Text="12"/>
+    <ROW XmlElement="swidbuild" ParentElement="swidnumeric" Name="swid:build" Condition="1" Order="2" Flags="14" Text="0"/>
     <ROW XmlElement="swidentitlement_required_indicator" ParentElement="swidsoftware_identification_tag" Name="swid:entitlement_required_indicator" Condition="1" Order="0" Flags="14" Text="false"/>
     <ROW XmlElement="swidmajor" ParentElement="swidnumeric" Name="swid:major" Condition="1" Order="0" Flags="14" Text="1"/>
-    <ROW XmlElement="swidminor" ParentElement="swidnumeric" Name="swid:minor" Condition="1" Order="1" Flags="14" Text="2"/>
+    <ROW XmlElement="swidminor" ParentElement="swidnumeric" Name="swid:minor" Condition="1" Order="1" Flags="14" Text="4"/>
     <ROW XmlElement="swidname" ParentElement="swidproduct_version" Name="swid:name" Condition="1" Order="0" Flags="14" Text="[ProductVersion]"/>
     <ROW XmlElement="swidname_1" ParentElement="swidsoftware_creator" Name="swid:name" Condition="1" Order="0" Flags="14" Text="ZeroTier, Inc."/>
     <ROW XmlElement="swidname_2" ParentElement="swidsoftware_licensor" Name="swid:name" Condition="1" Order="0" Flags="14" Text="ZeroTier, Inc."/>

+ 0 - 11
ext/installfiles/windows/chocolatey/zerotier-one/tools/LICENSE.txt

@@ -1,11 +0,0 @@
-From: https://raw.githubusercontent.com/zerotier/ZeroTierOne/master/COPYING
-
-LICENSE
-
-ZeroTier One is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 3 of the License, or (at
-your option) any later version.
-
-See the file ‘LICENSE.GPL-3’ for the text of the GNU GPL version 3.
-If that file is not present, see <http://www.gnu.org/licenses/>.

+ 0 - 5
ext/installfiles/windows/chocolatey/zerotier-one/tools/VERIFICATION.txt

@@ -1,5 +0,0 @@
-VERIFICATION
-Verification is intended to assist the Chocolatey moderators and community
-in verifying that this package's contents are trustworthy.
- 
-Our MSI installer should be signed by ZeroTier, Inc. using a certificate from DigiCert.

+ 0 - 8
ext/installfiles/windows/chocolatey/zerotier-one/tools/chocolateyinstall.ps1

@@ -1,8 +0,0 @@
-$packageName = 'zerotier-one'
-$installerType = 'msi'
-$url = 'https://download.zerotier.com/RELEASES/1.2.4/dist/ZeroTier%20One.msi'
-$url64 = 'https://download.zerotier.com/RELEASES/1.2.4/dist/ZeroTier%20One.msi'
-$silentArgs = '/quiet'
-$validExitCodes = @(0,3010)
-
-Install-ChocolateyPackage $packageName $installerType $silentArgs $url $url64  -validExitCodes $validExitCodes

+ 0 - 30
ext/installfiles/windows/chocolatey/zerotier-one/tools/chocolateyuninstall.ps1

@@ -1,30 +0,0 @@
-$ErrorActionPreference = 'Stop';
-
-$packageName = 'zerotier-one'
-$softwareName = 'ZeroTier One*'
-$installerType = 'MSI'
-
-$silentArgs = '/qn /norestart'
-$validExitCodes = @(0, 3010, 1605, 1614, 1641)
-$uninstalled = $false
-
-[array]$key = Get-UninstallRegistryKey -SoftwareName $softwareName
-
-if ($key.Count -eq 1) {
-  $key | % { 
-    $silentArgs = "$($_.PSChildName) $silentArgs"
-    $file = ''
-    Uninstall-ChocolateyPackage -PackageName $packageName `
-                                -FileType $installerType `
-                                -SilentArgs "$silentArgs" `
-                                -ValidExitCodes $validExitCodes `
-                                -File "$file"
-  }
-} elseif ($key.Count -eq 0) {
-  Write-Warning "$packageName has already been uninstalled by other means."
-} elseif ($key.Count -gt 1) {
-  Write-Warning "$key.Count matches found!"
-  Write-Warning "To prevent accidental data loss, no programs will be uninstalled."
-  Write-Warning "Please alert package maintainer the following keys were matched:"
-  $key | % {Write-Warning "- $_.DisplayName"}
-}

+ 0 - 76
ext/installfiles/windows/chocolatey/zerotier-one/zerotier-one.nuspec

@@ -1,76 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Read this before creating packages: https://github.com/chocolatey/chocolatey/wiki/CreatePackages -->
-<!-- It is especially important to read the above link to understand additional requirements when publishing packages to the community feed aka dot org (https://chocolatey.org/packages). -->
-
-<!-- Test your packages in a test environment: https://github.com/chocolatey/chocolatey-test-environment -->
-
-<!--
-This is a nuspec. It mostly adheres to https://docs.nuget.org/create/Nuspec-Reference. Chocolatey uses a special version of NuGet.Core that allows us to do more than was initially possible. As such there are certain things to be aware of:
-
-* the package xmlns schema url may cause issues with nuget.exe
-* Any of the following elements can ONLY be used by choco tools - projectSourceUrl, docsUrl, mailingListUrl, bugTrackerUrl, packageSourceUrl, provides, conflicts, replaces
-* nuget.exe can still install packages with those elements but they are ignored. Any authoring tools or commands will error on those elements
--->
-
-<!-- You can embed software files directly into packages, as long as you are not bound by distribution rights. -->
-<!-- * If you are an organization making private packages, you probably have no issues here -->
-<!-- * If you are releasing to the community feed, you need to consider distribution rights. -->
-<!-- Do not remove this test for UTF-8: if “Ω” doesn’t appear as greek uppercase omega letter enclosed in quotation marks, you should use an editor that supports UTF-8, not this one. -->
-<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
-  <metadata>
-    <!-- == PACKAGE SPECIFIC SECTION == -->
-    <!-- This section is about this package, although id and version have ties back to the software -->
-    <!-- id is lowercase and if you want a good separator for words, use '-', not '.'. Dots are only acceptable as suffixes for certain types of packages, e.g. .install, .portable, .extension, .template -->
-    <!-- If the software is cross-platform, attempt to use the same id as the debian/rpm package(s) if possible. -->
-    <id>zerotier-one</id>
-    <!-- version should MATCH as closely as possible with the underlying software -->
-    <!-- Is the version a prerelease of a version? https://docs.nuget.org/create/versioning#creating-prerelease-packages -->
-    <!-- Note that unstable versions like 0.0.1 can be considered a released version, but it's possible that one can release a 0.0.1-beta before you release a 0.0.1 version. If the version number is final, that is considered a released version and not a prerelease. -->
-    <version>1.2.12</version>
-    <!-- <packageSourceUrl>Where is this Chocolatey package located (think GitHub)? packageSourceUrl is highly recommended for the community feed</packageSourceUrl>-->
-    <!-- owners is a poor name for maintainers of the package. It sticks around by this name for compatibility reasons. It basically means you. -->
-    <!--<owners>ZeroTier, Inc.</owners>-->
-    <!-- ============================== -->
-
-    <!-- == SOFTWARE SPECIFIC SECTION == -->
-    <!-- This section is about the software itself -->
-    <title>zerotier-one (Install)</title>
-    <authors>ZeroTier, Inc.</authors>
-    <!-- projectUrl is required for the community feed -->
-    <projectUrl>https://www.zerotier.com/</projectUrl>
-    <!--<iconUrl>https://www.zerotier.com/img/ZeroTierIcon.png</iconUrl>-->
-    <!-- <copyright>2011-2016 ZeroTier, Inc.</copyright> -->
-    <!-- If there is a license Url available, it is is required for the community feed -->
-    <!-- <licenseUrl>https://raw.githubusercontent.com/zerotier/ZeroTierOne/master/COPYING</licenseUrl>
-    <requireLicenseAcceptance>true</requireLicenseAcceptance>-->
-    <!--<projectSourceUrl>https://github.com/zerotier/ZeroTierOne</projectSourceUrl>-->
-    <!--<docsUrl>https://www.zerotier.com/</docsUrl>-->
-    <!--<mailingListUrl></mailingListUrl>-->
-    <!--<bugTrackerUrl>https://github.com/zerotier/ZeroTierOne/issues</bugTrackerUrl>-->
-    <tags>zerotier-one admin</tags>
-    <summary>ZeroTier One Virtual Network Endpoint for Windows</summary>
-    <description>ZeroTier is a smart switch for Earth with VLAN capability. See https://www.zerotier.com/ for more information.</description>
-    <!-- <releaseNotes>__REPLACE_OR_REMOVE__MarkDown_Okay</releaseNotes> -->
-    <!-- =============================== -->
-
-    <!-- Specifying dependencies and version ranges? https://docs.nuget.org/create/versioning#specifying-version-ranges-in-.nuspec-files -->
-    <!--<dependencies>
-      <dependency id="" version="__MINIMUM_VERSION__" />
-      <dependency id="" version="[__EXACT_VERSION__]" />
-      <dependency id="" version="[_MIN_VERSION_INCLUSIVE, MAX_VERSION_INCLUSIVE]" />
-      <dependency id="" version="[_MIN_VERSION_INCLUSIVE, MAX_VERSION_EXCLUSIVE)" />
-      <dependency id="" />
-      <dependency id="chocolatey-uninstall.extension" />
-    </dependencies>-->
-    <!-- chocolatey-uninstall.extension - If supporting 0.9.9.x (or below) and including a chocolateyUninstall.ps1 file to uninstall an EXE/MSI, you probably want to include chocolatey-uninstall.extension as a dependency. Please verify whether you are using a helper function from that package. -->
-
-    <!--<provides>NOT YET IMPLEMENTED</provides>-->
-    <!--<conflicts>NOT YET IMPLEMENTED</conflicts>-->
-    <!--<replaces>NOT YET IMPLEMENTED</replaces>-->
-  </metadata>
-  <files>
-    <!-- this section controls what actually gets packaged into the Chocolatey package -->
-    <file src="tools\**" target="tools" />
-    <!--Building from Linux? You may need this instead: <file src="tools/**" target="tools" />-->
-  </files>
-</package>

+ 220 - 81
ext/json/README.md

@@ -5,11 +5,11 @@
 [![Coverage Status](https://img.shields.io/coveralls/nlohmann/json.svg)](https://coveralls.io/r/nlohmann/json)
 [![Coverity Scan Build Status](https://scan.coverity.com/projects/5550/badge.svg)](https://scan.coverity.com/projects/nlohmann-json)
 [![Codacy Badge](https://api.codacy.com/project/badge/Grade/f3732b3327e34358a0e9d1fe9f661f08)](https://www.codacy.com/app/nlohmann/json?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=nlohmann/json&amp;utm_campaign=Badge_Grade)
-[![Try online](https://img.shields.io/badge/try-online-blue.svg)](https://wandbox.org/permlink/Op57X0V7fTf2tdwl)
+[![Try online](https://img.shields.io/badge/try-online-blue.svg)](https://wandbox.org/permlink/TarF5pPn9NtHQjhf)
 [![Documentation](https://img.shields.io/badge/docs-doxygen-blue.svg)](http://nlohmann.github.io/json)
 [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/nlohmann/json/master/LICENSE.MIT)
-[![Github Releases](https://img.shields.io/github/release/nlohmann/json.svg)](https://github.com/nlohmann/json/releases)
-[![Github Issues](https://img.shields.io/github/issues/nlohmann/json.svg)](http://github.com/nlohmann/json/issues)
+[![GitHub Releases](https://img.shields.io/github/release/nlohmann/json.svg)](https://github.com/nlohmann/json/releases)
+[![GitHub Issues](https://img.shields.io/github/issues/nlohmann/json.svg)](http://github.com/nlohmann/json/issues)
 [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/nlohmann/json.svg)](http://isitmaintained.com/project/nlohmann/json "Average time to resolve an issue")
 [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/289/badge)](https://bestpractices.coreinfrastructure.org/projects/289)
 
@@ -21,9 +21,10 @@
   - [STL-like access](#stl-like-access)
   - [Conversion from STL containers](#conversion-from-stl-containers)
   - [JSON Pointer and JSON Patch](#json-pointer-and-json-patch)
+  - [JSON Merge Patch](#json-merge-patch)
   - [Implicit conversions](#implicit-conversions)
   - [Conversions to/from arbitrary types](#arbitrary-types-conversions)
-  - [Binary formats (CBOR and MessagePack)](#binary-formats-cbor-and-messagepack)
+  - [Binary formats (CBOR, MessagePack, and UBJSON)](#binary-formats-cbor-messagepack-and-ubjson)
 - [Supported compilers](#supported-compilers)
 - [License](#license)
 - [Contact](#contact)
@@ -39,9 +40,9 @@ There are myriads of [JSON](http://json.org) libraries out there, and each may e
 
 - **Intuitive syntax**. In languages such as Python, JSON feels like a first class data type. We used all the operator magic of modern C++ to achieve the same feeling in your code. Check out the [examples below](#examples) and you'll know what I mean.
 
-- **Trivial integration**. Our whole code consists of a single header file [`json.hpp`](https://github.com/nlohmann/json/blob/develop/src/json.hpp). That's it. No library, no subproject, no dependencies, no complex build system. The class is written in vanilla C++11. All in all, everything should require no adjustment of your compiler flags or project settings.
+- **Trivial integration**. Our whole code consists of a single header file [`json.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json.hpp). That's it. No library, no subproject, no dependencies, no complex build system. The class is written in vanilla C++11. All in all, everything should require no adjustment of your compiler flags or project settings.
 
-- **Serious testing**. Our class is heavily [unit-tested](https://github.com/nlohmann/json/blob/master/test/src/unit.cpp) and covers [100%](https://coveralls.io/r/nlohmann/json) of the code, including all exceptional behavior. Furthermore, we checked with [Valgrind](http://valgrind.org) that there are no memory leaks. To maintain high quality, the project is following the [Core Infrastructure Initiative (CII) best practices](https://bestpractices.coreinfrastructure.org/projects/289).
+- **Serious testing**. Our class is heavily [unit-tested](https://github.com/nlohmann/json/tree/develop/test/src) and covers [100%](https://coveralls.io/r/nlohmann/json) of the code, including all exceptional behavior. Furthermore, we checked with [Valgrind](http://valgrind.org) and the [Clang Sanitizers](https://clang.llvm.org/docs/index.html) that there are no memory leaks. [Google OSS-Fuzz](https://github.com/google/oss-fuzz/tree/master/projects/json) additionally runs fuzz tests agains all parsers 24/7, effectively executing billions of tests so far. To maintain high quality, the project is following the [Core Infrastructure Initiative (CII) best practices](https://bestpractices.coreinfrastructure.org/projects/289).
 
 Other aspects were not so important to us:
 
@@ -54,31 +55,42 @@ See the [contribution guidelines](https://github.com/nlohmann/json/blob/master/.
 
 ## Integration
 
-The single required source, file `json.hpp` is in the `src` directory or [released here](https://github.com/nlohmann/json/releases). All you need to do is add
+[`json.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json.hpp) is the single required file in `single_include/nlohmann` or [released here](https://github.com/nlohmann/json/releases). You need to add
 
 ```cpp
-#include "json.hpp"
+#include <nlohmann/json.hpp>
 
 // for convenience
 using json = nlohmann::json;
 ```
 
-to the files you want to use JSON objects. That's it. Do not forget to set the necessary switches to enable C++11 (e.g., `-std=c++11` for GCC and Clang).
+to the files you want to process JSON and set the necessary switches to enable C++11 (e.g., `-std=c++11` for GCC and Clang).
+
+You can further use file [`include/nlohmann/json_fwd.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/json_fwd.hpp) for forward-declarations. The installation of json_fwd.hpp (as part of cmake's install step), can be achieved by setting `-DJSON_MultipleHeaders=ON`.
+
+### Package Managers
 
 :beer: If you are using OS X and [Homebrew](http://brew.sh), just type `brew tap nlohmann/json` and `brew install nlohmann_json` and you're set. If you want the bleeding edge rather than the latest release, use `brew install nlohmann_json --HEAD`.
 
-If you are using the [Meson Build System](http://mesonbuild.com), then you can wrap this repo as a subproject.
+If you are using the [Meson Build System](http://mesonbuild.com), then you can wrap this repository as a subproject.
 
 If you are using [Conan](https://www.conan.io/) to manage your dependencies, merely add `jsonformoderncpp/x.y.z@vthiery/stable` to your `conanfile.py`'s requires, where `x.y.z` is the release version you want to use. Please file issues [here](https://github.com/vthiery/conan-jsonformoderncpp/issues) if you experience problems with the packages.
 
+If you are using [Spack](https://www.spack.io/) to manage your dependencies, you can use the `nlohmann_json` package. Please see the [spack project](https://github.com/spack/spack) for any issues regarding the packaging.
+
 If you are using [hunter](https://github.com/ruslo/hunter/) on your project for external dependencies, then you can use the [nlohmann_json package](https://docs.hunter.sh/en/latest/packages/pkg/nlohmann_json.html). Please see the hunter project for any issues regarding the packaging.
 
+If you are using [Buckaroo](https://buckaroo.pm), you can install this library's module with `buckaroo install nlohmann/json`. Please file issues [here](https://github.com/LoopPerfect/buckaroo-recipes/issues/new?title=nlohmann/nlohmann/json).
+
 If you are using [vcpkg](https://github.com/Microsoft/vcpkg/) on your project for external dependencies, then you can use the [nlohmann-json package](https://github.com/Microsoft/vcpkg/tree/master/ports/nlohmann-json). Please see the vcpkg project for any issues regarding the packaging.
 
+If you are using [cget](http://cget.readthedocs.io/en/latest/), you can install the latest development version with `cget install nlohmann/json`. A specific version can be installed with `cget install nlohmann/[email protected]`. Also, the multiple header version can be installed by adding the `-DJSON_MultipleHeaders=ON` flag (i.e., `cget install nlohmann/json -DJSON_MultipleHeaders=ON`).
+
+If you are using [CocoaPods](https://cocoapods.org), you can use the library by adding pod `"nlohmann_json", '~>3.1.2'` to your podfile (see [an example](https://bitbucket.org/benman/nlohmann_json-cocoapod/src/master/)). Please file issues [here](https://bitbucket.org/benman/nlohmann_json-cocoapod/issues?status=new&status=open).
 
 ## Examples
 
-Beside the examples below, you may want to check the [documentation](https://nlohmann.github.io/json/) where each function contains a separate code example (e.g., check out [`emplace()`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_a602f275f0359ab181221384989810604.html#a602f275f0359ab181221384989810604)). All [example files](https://github.com/nlohmann/json/tree/develop/doc/examples) can be compiled and executed on their own (e.g., file [emplace.cpp](https://github.com/nlohmann/json/blob/develop/doc/examples/emplace.cpp)).
+Beside the examples below, you may want to check the [documentation](https://nlohmann.github.io/json/) where each function contains a separate code example (e.g., check out [`emplace()`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_a5338e282d1d02bed389d852dd670d98d.html#a5338e282d1d02bed389d852dd670d98d)). All [example files](https://github.com/nlohmann/json/tree/develop/doc/examples) can be compiled and executed on their own (e.g., file [emplace.cpp](https://github.com/nlohmann/json/blob/develop/doc/examples/emplace.cpp)).
 
 ### JSON as first-class data type
 
@@ -103,7 +115,7 @@ Assume you want to create the JSON object
 }
 ```
 
-With the JSON class, you could write:
+With this library, you could write:
 
 ```cpp
 // create an empty structure (null)
@@ -147,7 +159,7 @@ json j2 = {
 };
 ```
 
-Note that in all these cases, you never need to "tell" the compiler which JSON value you want to use. If you want to be explicit or express some edge cases, the functions `json::array` and `json::object` will help:
+Note that in all these cases, you never need to "tell" the compiler which JSON value type you want to use. If you want to be explicit or express some edge cases, the functions [`json::array`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_aa80485befaffcadaa39965494e0b4d2e.html#aa80485befaffcadaa39965494e0b4d2e) and [`json::object`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_aa13f7c0615867542ce80337cbcf13ada.html#aa13f7c0615867542ce80337cbcf13ada) will help:
 
 ```cpp
 // a way to express the empty array []
@@ -161,12 +173,11 @@ json empty_object_explicit = json::object();
 json array_not_object = json::array({ {"currency", "USD"}, {"value", 42.99} });
 ```
 
-
 ### Serialization / Deserialization
 
 #### To/from strings
 
-You can create an object (deserialization) by appending `_json` to a string literal:
+You can create a JSON value (deserialization) by appending `_json` to a string literal:
 
 ```cpp
 // create object from string literal
@@ -183,14 +194,14 @@ auto j2 = R"(
 
 Note that without appending the `_json` suffix, the passed string literal is not parsed, but just used as JSON string value. That is, `json j = "{ \"happy\": true, \"pi\": 3.141 }"` would just store the string `"{ "happy": true, "pi": 3.141 }"` rather than parsing the actual object.
 
-The above example can also be expressed explicitly using `json::parse()`:
+The above example can also be expressed explicitly using [`json::parse()`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_aa9676414f2e36383c4b181fe856aa3c0.html#aa9676414f2e36383c4b181fe856aa3c0):
 
 ```cpp
 // parse explicitly
 auto j3 = json::parse("{ \"happy\": true, \"pi\": 3.141 }");
 ```
 
-You can also get a string representation (serialize):
+You can also get a string representation of a JSON value (serialize):
 
 ```cpp
 // explicit conversion to string
@@ -225,8 +236,9 @@ std::cout << cpp_string << " == " << cpp_string2 << " == " << j_string.get<std::
 std::cout << j_string << " == " << serialized_string << std::endl;
 ```
 
-`.dump()` always returns the serialized value, and `.get<std::string>()` returns the originally stored string value.
+[`.dump()`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_a5adea76fedba9898d404fef8598aa663.html#a5adea76fedba9898d404fef8598aa663) always returns the serialized value, and [`.get<std::string>()`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_a16f9445f7629f634221a42b967cdcd43.html#a16f9445f7629f634221a42b967cdcd43) returns the originally stored string value.
 
+Note the library only supports UTF-8. When you store strings with different encodings in the library, calling [`dump()`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_a5adea76fedba9898d404fef8598aa663.html#a5adea76fedba9898d404fef8598aa663) may throw an exception.
 
 #### To/from streams (e.g. files, string streams)
 
@@ -261,7 +273,7 @@ Please note that setting the exception bit for `failbit` is inappropriate for th
 
 #### Read from iterator range
 
-You can also read JSON from an iterator range; that is, from any container accessible by iterators whose content is stored as contiguous byte sequence, for instance a `std::vector<std::uint8_t>`:
+You can also parse JSON from an iterator range; that is, from any container accessible by iterators whose content is stored as contiguous byte sequence, for instance a `std::vector<std::uint8_t>`:
 
 ```cpp
 std::vector<std::uint8_t> v = {'t', 'r', 'u', 'e'};
@@ -275,10 +287,53 @@ std::vector<std::uint8_t> v = {'t', 'r', 'u', 'e'};
 json j = json::parse(v);
 ```
 
+#### SAX interface
+
+The library uses a SAX-like interface with the following functions:
+
+```cpp
+// called when null is parsed
+bool null();
+
+// called when a boolean is parsed; value is passed
+bool boolean(bool val);
+
+// called when a signed or unsigned integer number is parsed; value is passed
+bool number_integer(number_integer_t val);
+bool number_unsigned(number_unsigned_t val);
+
+// called when a floating-point number is parsed; value and original string is passed
+bool number_float(number_float_t val, const string_t& s);
+
+// called when a string is parsed; value is passed and can be safely moved away
+bool string(string_t& val);
+
+// called when an object or array begins or ends, resp. The number of elements is passed (or -1 if not known)
+bool start_object(std::size_t elements);
+bool end_object();
+bool start_array(std::size_t elements);
+bool end_array();
+// called when an object key is parsed; value is passed and can be safely moved away
+bool key(string_t& val);
+
+// called when a parse error occurs; byte position, the last token, and an exception is passed
+bool parse_error(std::size_t position, const std::string& last_token, const detail::exception& ex);
+```
+
+The return value of each function determines whether parsing should proceed.
+
+To implement your own SAX handler, proceed as follows:
+
+1. Implement the SAX interface in a class. You can use class `nlohmann::json_sax<json>` as base class, but you can also use any class where the functions described above are implemented and public.
+2. Create an object of your SAX interface class, e.g. `my_sax`.
+3. Call `bool json::sax_parse(input, &my_sax)`; where the first parameter can be any input like a string or an input stream and the second parameter is a pointer to your SAX interface.
+
+Note the `sax_parse` function only returns a `bool` indicating the result of the last executed SAX event. It does not return a  `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error - it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file [`json_sax.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/json_sax.hpp).
+
 
 ### STL-like access
 
-We designed the JSON class to behave just like an STL container. In fact, it satisfies the [**ReversibleContainer**](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) requirement.
+We designed the JSON class to behave just like an STL container. In fact, it satisfies the [**ReversibleContainer**](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) requirement.
 
 ```cpp
 // create an array using push_back
@@ -352,7 +407,7 @@ o.erase("foo");
 
 ### Conversion from STL containers
 
-Any sequence container (`std::array`, `std::vector`, `std::deque`, `std::forward_list`, `std::list`) whose values can be used to construct JSON types (e.g., integers, floating point numbers, Booleans, string types, or again STL containers described in this section) can be used to create a JSON array. The same holds for similar associative containers (`std::set`, `std::multiset`, `std::unordered_set`, `std::unordered_multiset`), but in these cases the order of the elements of the array depends on how the elements are ordered in the respective STL container.
+Any sequence container (`std::array`, `std::vector`, `std::deque`, `std::forward_list`, `std::list`) whose values can be used to construct JSON values (e.g., integers, floating point numbers, Booleans, string types, or again STL containers described in this section) can be used to create a JSON array. The same holds for similar associative containers (`std::set`, `std::multiset`, `std::unordered_set`, `std::unordered_multiset`), but in these cases the order of the elements of the array depends on how the elements are ordered in the respective STL container.
 
 ```cpp
 std::vector<int> c_vector {1, 2, 3, 4};
@@ -392,7 +447,7 @@ json j_umset(c_umset); // both entries for "one" are used
 // maybe ["one", "two", "one", "four"]
 ```
 
-Likewise, any associative key-value containers (`std::map`, `std::multimap`, `std::unordered_map`, `std::unordered_multimap`) whose keys can construct an `std::string` and whose values can be used to construct JSON types (see examples above) can be used to create a JSON object. Note that in case of multimaps only one key is used in the JSON object and the value depends on the internal order of the STL container.
+Likewise, any associative key-value containers (`std::map`, `std::multimap`, `std::unordered_map`, `std::unordered_multimap`) whose keys can construct an `std::string` and whose values can be used to construct JSON values (see examples above) can be used to create a JSON object. Note that in case of multimaps only one key is used in the JSON object and the value depends on the internal order of the STL container.
 
 ```cpp
 std::map<std::string, int> c_map { {"one", 1}, {"two", 2}, {"three", 3} };
@@ -450,6 +505,37 @@ json::diff(j_result, j_original);
 // ]
 ```
 
+### JSON Merge Patch
+
+The library supports **JSON Merge Patch** ([RFC 7386](https://tools.ietf.org/html/rfc7386)) as a patch format. Instead of using JSON Pointer (see above) to specify values to be manipulated, it describes the changes using a syntax that closely mimics the document being modified.
+
+```cpp
+// a JSON value
+json j_document = R"({
+  "a": "b",
+  "c": {
+    "d": "e",
+    "f": "g"
+  }
+})"_json;
+
+// a patch
+json j_patch = R"({
+  "a":"z",
+  "c": {
+    "f": null
+  }
+})"_json;
+
+// apply the patch
+j_original.merge_patch(j_patch);
+// {
+//  "a": "z",
+//  "c": {
+//    "d": "e"
+//  }
+// }
+```
 
 ### Implicit conversions
 
@@ -484,9 +570,17 @@ int vi = jn.get<int>();
 // etc.
 ```
 
+Note that `char` types are not automatically converted to JSON strings, but to integer numbers. A conversion to a string must be specified explicitly:
+
+```cpp
+char ch = 'A';                       // ASCII value 65
+json j_default = ch;                 // stores integer number 65
+json j_string = std::string(1, ch);  // stores string "A"
+```
+
 ### Arbitrary types conversions
 
-Every type can be serialized in JSON, not just STL-containers and scalar types. Usually, you would do something along those lines:
+Every type can be serialized in JSON, not just STL containers and scalar types. Usually, you would do something along those lines:
 
 ```cpp
 namespace ns {
@@ -561,7 +655,8 @@ Likewise, when calling `get<your_type>()`, the `from_json` method will be called
 Some important things:
 
 * Those methods **MUST** be in your type's namespace (which can be the global namespace), or the library will not be able to locate them (in this example, they are in namespace `ns`, where `person` is defined).
-* When using `get<your_type>()`, `your_type` **MUST** be [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible). (There is a way to bypass this requirement described later.)
+* Those methods **MUST** be available (e.g., properly headers must be included) everywhere you use the implicit conversions. Look at [issue 1108](https://github.com/nlohmann/json/issues/1108) for errors that may occur otherwise.
+* When using `get<your_type>()`, `your_type` **MUST** be [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). (There is a way to bypass this requirement described later.)
 * In function `from_json`, use function [`at()`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_a93403e803947b86f4da2d1fb3345cf2c.html#a93403e803947b86f4da2d1fb3345cf2c) to access the object values rather than `operator[]`. In case a key does not exist, `at` throws an exception that you can handle, whereas `operator[]` exhibits undefined behavior.
 * In case your type contains several `operator=` definitions, code like `your_variable = your_json;` [may not compile](https://github.com/nlohmann/json/issues/667). You need to write `your_variable = your_json.get<decltype your_variable>();` instead.
 * You do not need to add serializers or deserializers for STL types like `std::vector`: the library already implements these.
@@ -573,7 +668,7 @@ Some important things:
 This requires a bit more advanced technique. But first, let's see how this conversion mechanism works:
 
 The library uses **JSON Serializers** to convert types to json.
-The default serializer for `nlohmann::json` is `nlohmann::adl_serializer` (ADL means [Argument-Dependent Lookup](http://en.cppreference.com/w/cpp/language/adl)).
+The default serializer for `nlohmann::json` is `nlohmann::adl_serializer` (ADL means [Argument-Dependent Lookup](https://en.cppreference.com/w/cpp/language/adl)).
 
 It is implemented like this (simplified):
 
@@ -590,7 +685,7 @@ struct adl_serializer {
 };
 ```
 
-This serializer works fine when you have control over the type's namespace. However, what about `boost::optional`, or `std::filesystem::path` (C++17)? Hijacking the `boost` namespace is pretty bad, and it's illegal to add something other than template specializations to `std`...
+This serializer works fine when you have control over the type's namespace. However, what about `boost::optional` or `std::filesystem::path` (C++17)? Hijacking the `boost` namespace is pretty bad, and it's illegal to add something other than template specializations to `std`...
 
 To solve this, you need to add a specialization of `adl_serializer` to the `nlohmann` namespace, here's an example:
 
@@ -612,7 +707,7 @@ namespace nlohmann {
             if (j.is_null()) {
                 opt = boost::none;
             } else {
-                opt = j.get<T>(); // same as above, but with 
+                opt = j.get<T>(); // same as above, but with
                                   // adl_serializer<T>::from_json
             }
         }
@@ -622,7 +717,7 @@ namespace nlohmann {
 
 #### How can I use `get()` for non-default constructible/non-copyable types?
 
-There is a way, if your type is [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible). You will need to specialize the `adl_serializer` as well, but with a special `from_json` overload:
+There is a way, if your type is [MoveConstructible](https://en.cppreference.com/w/cpp/named_req/MoveConstructible). You will need to specialize the `adl_serializer` as well, but with a special `from_json` overload:
 
 ```cpp
 struct move_only_type {
@@ -630,7 +725,7 @@ struct move_only_type {
     move_only_type(int ii): i(ii) {}
     move_only_type(const move_only_type&) = delete;
     move_only_type(move_only_type&&) = default;
-    
+
     int i;
 };
 
@@ -642,7 +737,7 @@ namespace nlohmann {
         static move_only_type from_json(const json& j) {
             return {j.get<int>()};
         }
-        
+
         // Here's the catch! You must provide a to_json method! Otherwise you
         // will not be able to convert move_only_type to json, since you fully
         // specialized adl_serializer on that type
@@ -659,9 +754,9 @@ Yes. You might want to take a look at [`unit-udt.cpp`](https://github.com/nlohma
 
 If you write your own serializer, you'll need to do a few things:
 
-* use a different `basic_json` alias than `nlohmann::json` (the last template parameter of `basic_json` is the `JSONSerializer`)
-* use your `basic_json` alias (or a template parameter) in all your `to_json`/`from_json` methods
-* use `nlohmann::to_json` and `nlohmann::from_json` when you need ADL
+- use a different `basic_json` alias than `nlohmann::json` (the last template parameter of `basic_json` is the `JSONSerializer`)
+- use your `basic_json` alias (or a template parameter) in all your `to_json`/`from_json` methods
+- use `nlohmann::to_json` and `nlohmann::from_json` when you need ADL
 
 Here is an example, without simplifications, that only accepts types with a size <= 32, and uses ADL.
 
@@ -677,7 +772,7 @@ struct less_than_32_serializer {
                                  // this is where the magic happens
         to_json(j, value);
     }
-    
+
     template <typename BasicJsonType>
     static void from_json(const BasicJsonType& j, T& value) {
         // same thing here
@@ -699,7 +794,7 @@ struct bad_serializer
       // if BasicJsonType::json_serializer == bad_serializer ... oops!
       j = value;
     }
-    
+
     template <typename BasicJsonType>
     static void to_json(const BasicJsonType& j, T& value) {
       // this calls BasicJsonType::json_serializer<T>::from_json(j, value);
@@ -709,9 +804,9 @@ struct bad_serializer
 };
 ```
 
-### Binary formats (CBOR and MessagePack)
+### Binary formats (CBOR, MessagePack, and UBJSON)
 
-Though JSON is a ubiquitous data format, it is not a very compact format suitable for data exchange, for instance over a network. Hence, the library supports [CBOR](http://cbor.io) (Concise Binary Object Representation) and [MessagePack](http://msgpack.org) to efficiently encode JSON values to byte vectors and to decode such vectors.
+Though JSON is a ubiquitous data format, it is not a very compact format suitable for data exchange, for instance over a network. Hence, the library supports [CBOR](http://cbor.io) (Concise Binary Object Representation), [MessagePack](http://msgpack.org), and [UBJSON](http://ubjson.org) (Universal Binary JSON Specification) to efficiently encode JSON values to byte vectors and to decode such vectors.
 
 ```cpp
 // create a JSON value
@@ -720,7 +815,7 @@ json j = R"({"compact": true, "schema": 0})"_json;
 // serialize to CBOR
 std::vector<std::uint8_t> v_cbor = json::to_cbor(j);
 
-// 0xa2, 0x67, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0xf5, 0x66, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x00
+// 0xA2, 0x67, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0xF5, 0x66, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x00
 
 // roundtrip
 json j_from_cbor = json::from_cbor(v_cbor);
@@ -728,19 +823,27 @@ json j_from_cbor = json::from_cbor(v_cbor);
 // serialize to MessagePack
 std::vector<std::uint8_t> v_msgpack = json::to_msgpack(j);
 
-// 0x82, 0xa7, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0xc3, 0xa6, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x00
+// 0x82, 0xA7, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0xC3, 0xA6, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x00
 
 // roundtrip
 json j_from_msgpack = json::from_msgpack(v_msgpack);
+
+// serialize to UBJSON
+std::vector<std::uint8_t> v_ubjson = json::to_ubjson(j);
+
+// 0x7B, 0x69, 0x07, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0x54, 0x69, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x69, 0x00, 0x7D
+
+// roundtrip
+json j_from_ubjson = json::from_ubjson(v_ubjson);
 ```
 
 
 ## Supported compilers
 
-Though it's 2017 already, the support for C++11 is still a bit sparse. Currently, the following compilers are known to work:
+Though it's 2018 already, the support for C++11 is still a bit sparse. Currently, the following compilers are known to work:
 
-- GCC 4.9 - 7.2 (and possibly later)
-- Clang 3.4 - 5.0 (and possibly later)
+- GCC 4.9 - 8.2 (and possibly later)
+- Clang 3.4 - 6.1 (and possibly later)
 - Intel C++ Compiler 17.0.2 (and possibly later)
 - Microsoft Visual C++ 2015 / Build Tools 14.0.25123.0 (and possibly later)
 - Microsoft Visual C++ 2017 / Build Tools 15.5.180.51428 (and possibly later)
@@ -751,43 +854,49 @@ Please note:
 
 - GCC 4.8 does not work because of two bugs ([55817](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55817) and [57824](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57824)) in the C++11 support. Note there is a [pull request](https://github.com/nlohmann/json/pull/212) to fix some of the issues.
 - Android defaults to using very old compilers and C++ libraries. To fix this, add the following to your `Application.mk`. This will switch to the LLVM C++ library, the Clang compiler, and enable C++11 and other features disabled by default.
- 
+
     ```
     APP_STL := c++_shared
     NDK_TOOLCHAIN_VERSION := clang3.6
     APP_CPPFLAGS += -frtti -fexceptions
     ```
- 
+
     The code compiles successfully with [Android NDK](https://developer.android.com/ndk/index.html?hl=ml), Revision 9 - 11 (and possibly later) and [CrystaX's Android NDK](https://www.crystax.net/en/android/ndk) version 10.
 
 - For GCC running on MinGW or Android SDK, the error `'to_string' is not a member of 'std'` (or similarly, for `strtod`) may occur. Note this is not an issue with the code,  but rather with the compiler itself. On Android, see above to build with a newer environment.  For MinGW, please refer to [this site](http://tehsausage.com/mingw-to-string) and [this discussion](https://github.com/nlohmann/json/issues/136) for information on how to fix this bug. For Android NDK using `APP_STL := gnustl_static`, please refer to [this discussion](https://github.com/nlohmann/json/issues/219).
 
+- Unsupported versions of GCC and Clang are rejected by `#error` directives. This can be switched off by defining `JSON_SKIP_UNSUPPORTED_COMPILER_CHECK`. Note that you can expect no support in this case.
+
 The following compilers are currently used in continuous integration at [Travis](https://travis-ci.org/nlohmann/json) and [AppVeyor](https://ci.appveyor.com/project/nlohmann/json):
 
 | Compiler        | Operating System             | Version String |
 |-----------------|------------------------------|----------------|
-| GCC 4.9.4       | Ubuntu 14.04.5 LTS           | g++-4.9 (Ubuntu 4.9.4-2ubuntu1~14.04.1) 4.9.4 |
-| GCC 5.4.1       | Ubuntu 14.04.5 LTS           | g++-5 (Ubuntu 5.4.1-2ubuntu1~14.04) 5.4.1 20160904 |
-| GCC 6.3.0       | Ubuntu 14.04.5 LTS           | g++-6 (Ubuntu/Linaro 6.3.0-18ubuntu2~14.04) 6.3.0 20170519 |
-| GCC 7.1.0       | Ubuntu 14.04.5 LTS           | g++-7 (Ubuntu 7.1.0-5ubuntu2~14.04) 7.1.0
-| Clang 3.5.0     | Ubuntu 14.04.5 LTS           | clang version 3.5.0-4ubuntu2~trusty2 (tags/RELEASE_350/final) |
-| Clang 3.6.2     | Ubuntu 14.04.5 LTS           | clang version 3.6.2-svn240577-1~exp1 (branches/release_36) |
-| Clang 3.7.1     | Ubuntu 14.04.5 LTS           | clang version 3.7.1-svn253571-1~exp1 (branches/release_37) |
-| Clang 3.8.0     | Ubuntu 14.04.5 LTS           | clang version 3.8.0-2ubuntu3~trusty5 (tags/RELEASE_380/final) |
-| Clang 3.9.1     | Ubuntu 14.04.5 LTS           | clang version 3.9.1-4ubuntu3~14.04.2 (tags/RELEASE_391/rc2) |
-| Clang 4.0.1     | Ubuntu 14.04.5 LTS           | clang version 4.0.1-svn305264-1~exp1 (branches/release_40) |
-| Clang 5.0.0     | Ubuntu 14.04.5 LTS           | clang version 5.0.0-svn310902-1~exp1 (branches/release_50) |
-| Clang Xcode 6.4 | Darwin Kernel Version 14.3.0 (OSX 10.10.3) | Apple LLVM version 6.1.0 (clang-602.0.53) (based on LLVM 3.6.0svn) |
-| Clang Xcode 7.3 | Darwin Kernel Version 15.0.0 (OSX 10.10.5) | Apple LLVM version 7.3.0 (clang-703.0.29) |
-| Clang Xcode 8.0 | Darwin Kernel Version 15.6.0 | Apple LLVM version 8.0.0 (clang-800.0.38) |
-| Clang Xcode 8.1 | Darwin Kernel Version 16.1.0 (macOS 10.12.1) | Apple LLVM version 8.0.0 (clang-800.0.42.1) |
-| Clang Xcode 8.2 | Darwin Kernel Version 16.1.0 (macOS 10.12.1) | Apple LLVM version 8.0.0 (clang-800.0.42.1) |
-| Clang Xcode 8.3 | Darwin Kernel Version 16.5.0 (macOS 10.12.4) | Apple LLVM version 8.1.0 (clang-802.0.38) |
-| Clang Xcode 9.0 | Darwin Kernel Version 16.7.0 (macOS 10.12.6) | Apple LLVM version 9.0.0 (clang-900.0.37) |
-| Clang Xcode 9.1 | Darwin Kernel Version 16.7.0 (macOS 10.12.6) | Apple LLVM version 9.0.0 (clang-900.0.38) |
-| Clang Xcode 9.2 | Darwin Kernel Version 16.7.0 (macOS 10.12.6) | Apple LLVM version 8.1.0 (clang-900.0.39.2) |
-| Visual Studio 14 2015 | Windows Server 2012 R2 (x64) | Microsoft (R) Build Engine version 14.0.25420.1, MSVC 19.0.24215.1 | 
-| Visual Studio 2017 | Windows Server 2016 | Microsoft (R) Build Engine version 15.5.180.51428, MSVC 19.12.25830.2 |
+| GCC 4.9.4       | Ubuntu 14.04.1 LTS           | g++-4.9 (Ubuntu 4.9.4-2ubuntu1~14.04.1) 4.9.4 |
+| GCC 5.5.0       | Ubuntu 14.04.1 LTS           | g++-5 (Ubuntu 5.5.0-12ubuntu1~14.04) 5.5.0 20171010 |
+| GCC 6.4.0       | Ubuntu 14.04.1 LTS           | g++-6 (Ubuntu 6.4.0-17ubuntu1~14.04) 6.4.0 20180424 |
+| GCC 7.3.0       | Ubuntu 14.04.1 LTS           | g++-7 (Ubuntu 7.3.0-21ubuntu1~14.04) 7.3.0 |
+| GCC 7.3.0       | Windows Server 2012 R2 (x64) | g++ (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 7.3.0 |
+| GCC 8.1.0       | Ubuntu 14.04.1 LTS           | g++-8 (Ubuntu 8.1.0-5ubuntu1~14.04) 8.1.0 |
+| Clang 3.5.0     | Ubuntu 14.04.1 LTS           | clang version 3.5.0-4ubuntu2~trusty2 (tags/RELEASE_350/final) (based on LLVM 3.5.0) |
+| Clang 3.6.2     | Ubuntu 14.04.1 LTS           | clang version 3.6.2-svn240577-1~exp1 (branches/release_36) (based on LLVM 3.6.2) |
+| Clang 3.7.1     | Ubuntu 14.04.1 LTS           | clang version 3.7.1-svn253571-1~exp1 (branches/release_37) (based on LLVM 3.7.1) |
+| Clang 3.8.0     | Ubuntu 14.04.1 LTS           | clang version 3.8.0-2ubuntu3~trusty5 (tags/RELEASE_380/final) |
+| Clang 3.9.1     | Ubuntu 14.04.1 LTS           | clang version 3.9.1-4ubuntu3~14.04.3 (tags/RELEASE_391/rc2) |
+| Clang 4.0.1     | Ubuntu 14.04.1 LTS           | clang version 4.0.1-svn305264-1~exp1 (branches/release_40) |
+| Clang 5.0.2     | Ubuntu 14.04.1 LTS           | clang version 5.0.2-svn328729-1~exp1~20180509123505.100 (branches/release_50) |
+| Clang 6.0.1     | Ubuntu 14.04.1 LTS           | clang version 6.0.1-svn334776-1~exp1~20180726133705.85 (branches/release_60) |
+| Clang Xcode 6.4 | OSX 10.10.5 | Apple LLVM version 6.1.0 (clang-602.0.53) (based on LLVM 3.6.0svn) |
+| Clang Xcode 7.3 | OSX 10.11.6 | Apple LLVM version 7.3.0 (clang-703.0.31) |
+| Clang Xcode 8.0 | OSX 10.11.6 | Apple LLVM version 8.0.0 (clang-800.0.38) |
+| Clang Xcode 8.1 | OSX 10.12.6 | Apple LLVM version 8.0.0 (clang-800.0.42.1) |
+| Clang Xcode 8.2 | OSX 10.12.6 | Apple LLVM version 8.0.0 (clang-800.0.42.1) |
+| Clang Xcode 8.3 | OSX 10.11.6 | Apple LLVM version 8.1.0 (clang-802.0.38) |
+| Clang Xcode 9.0 | OSX 10.12.6 | Apple LLVM version 9.0.0 (clang-900.0.37) |
+| Clang Xcode 9.1 | OSX 10.12.6 | Apple LLVM version 9.0.0 (clang-900.0.38) |
+| Clang Xcode 9.2 | OSX 10.13.3 | Apple LLVM version 9.1.0 (clang-902.0.39.1) |
+| Clang Xcode 9.3 | OSX 10.13.3 | Apple LLVM version 9.1.0 (clang-902.0.39.2) |
+| Visual Studio 14 2015 | Windows Server 2012 R2 (x64) | Microsoft (R) Build Engine version 14.0.25420.1, MSVC 19.0.24215.1 |
+| Visual Studio 2017 | Windows Server 2016 | Microsoft (R) Build Engine version 15.7.180.61344, MSVC 19.14.26433.0 |
 
 ## License
 
@@ -795,7 +904,7 @@ The following compilers are currently used in continuous integration at [Travis]
 
 The class is licensed under the [MIT License](http://opensource.org/licenses/MIT):
 
-Copyright &copy; 2013-2017 [Niels Lohmann](http://nlohmann.me)
+Copyright &copy; 2013-2018 [Niels Lohmann](http://nlohmann.me)
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 
@@ -807,12 +916,17 @@ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR I
 
 The class contains the UTF-8 Decoder from Bjoern Hoehrmann which is licensed under the [MIT License](http://opensource.org/licenses/MIT) (see above). Copyright &copy; 2008-2009 [Björn Hoehrmann](http://bjoern.hoehrmann.de/) <[email protected]>
 
+The class contains a slightly modified version of the Grisu2 algorithm from Florian Loitsch which is licensed under the [MIT License](http://opensource.org/licenses/MIT) (see above). Copyright &copy; 2009 [Florian Loitsch](http://florian.loitsch.com/)
+
 ## Contact
 
-If you have questions regarding the library, I would like to invite you to [open an issue at Github](https://github.com/nlohmann/json/issues/new). Please describe your request, problem, or question as detailed as possible, and also mention the version of the library you are using as well as the version of your compiler and operating system. Opening an issue at Github allows other users and contributors to this library to collaborate. For instance, I have little experience with MSVC, and most issues in this regard have been solved by a growing community. If you have a look at the [closed issues](https://github.com/nlohmann/json/issues?q=is%3Aissue+is%3Aclosed), you will see that we react quite timely in most cases.
+If you have questions regarding the library, I would like to invite you to [open an issue at GitHub](https://github.com/nlohmann/json/issues/new). Please describe your request, problem, or question as detailed as possible, and also mention the version of the library you are using as well as the version of your compiler and operating system. Opening an issue at GitHub allows other users and contributors to this library to collaborate. For instance, I have little experience with MSVC, and most issues in this regard have been solved by a growing community. If you have a look at the [closed issues](https://github.com/nlohmann/json/issues?q=is%3Aissue+is%3Aclosed), you will see that we react quite timely in most cases.
 
 Only if your request would contain confidential information, please [send me an email](mailto:[email protected]). For encrypted messages, please use [this key](https://keybase.io/nlohmann/pgp_keys.asc).
 
+## Security
+
+[Commits by Niels Lohmann](https://github.com/nlohmann/json/commits) and [releases](https://github.com/nlohmann/json/releases) are signed with this [PGP Key](https://keybase.io/nlohmann/pgp_keys.asc?fingerprint=797167ae41c0a6d9232e48457f3cea63ae251b69).
 
 ## Thanks
 
@@ -847,7 +961,7 @@ I deeply appreciate the help of the following people.
 - [Corbin Hughes](https://github.com/nibroc) fixed some typos in the contribution guidelines.
 - [twelsby](https://github.com/twelsby) fixed the array subscript operator, an issue that failed the MSVC build, and floating-point parsing/dumping. He further added support for unsigned integer numbers and implemented better roundtrip support for parsed numbers.
 - [Volker Diels-Grabsch](https://github.com/vog) fixed a link in the README file.
-- [msm-](https://github.com/msm-) added support for american fuzzy lop. 
+- [msm-](https://github.com/msm-) added support for American Fuzzy Lop.
 - [Annihil](https://github.com/Annihil) fixed an example in the README file.
 - [Themercee](https://github.com/Themercee) noted a wrong URL in the README file.
 - [Lv Zheng](https://github.com/lv-zheng) fixed a namespace issue with `int64_t` and `uint64_t`.
@@ -860,14 +974,14 @@ I deeply appreciate the help of the following people.
 - [duncanwerner](https://github.com/duncanwerner) found a really embarrassing performance regression in the 2.0.0 release.
 - [Damien](https://github.com/dtoma) fixed one of the last conversion warnings.
 - [Thomas Braun](https://github.com/t-b) fixed a warning in a test case.
-- [Théo DELRIEU](https://github.com/theodelrieu) patiently and constructively oversaw the long way toward [iterator-range parsing](https://github.com/nlohmann/json/issues/290). He also implemented the magic behind the serialization/deserialization of user-defined types.
+- [Théo DELRIEU](https://github.com/theodelrieu) patiently and constructively oversaw the long way toward [iterator-range parsing](https://github.com/nlohmann/json/issues/290). He also implemented the magic behind the serialization/deserialization of user-defined types and split the single header file into smaller chunks.
 - [Stefan](https://github.com/5tefan) fixed a minor issue in the documentation.
 - [Vasil Dimov](https://github.com/vasild) fixed the documentation regarding conversions from `std::multiset`.
 - [ChristophJud](https://github.com/ChristophJud) overworked the CMake files to ease project inclusion.
 - [Vladimir Petrigo](https://github.com/vpetrigo) made a SFINAE hack more readable and added Visual Studio 17 to the build matrix.
 - [Denis Andrejew](https://github.com/seeekr) fixed a grammar issue in the README file.
 - [Pierre-Antoine Lacaze](https://github.com/palacaze) found a subtle bug in the `dump()` function.
-- [TurpentineDistillery](https://github.com/TurpentineDistillery) pointed to [`std::locale::classic()`](http://en.cppreference.com/w/cpp/locale/locale/classic) to avoid too much locale joggling, found some nice performance improvements in the parser, improved the benchmarking code, and realized locale-independent number parsing and printing.
+- [TurpentineDistillery](https://github.com/TurpentineDistillery) pointed to [`std::locale::classic()`](https://en.cppreference.com/w/cpp/locale/locale/classic) to avoid too much locale joggling, found some nice performance improvements in the parser, improved the benchmarking code, and realized locale-independent number parsing and printing.
 - [cgzones](https://github.com/cgzones) had an idea how to fix the Coverity scan.
 - [Jared Grubb](https://github.com/jaredgrubb) silenced a nasty documentation warning.
 - [Yixin Zhang](https://github.com/qwename) fixed an integer overflow check.
@@ -893,7 +1007,7 @@ I deeply appreciate the help of the following people.
 - [Vincent Thiery](https://github.com/vthiery) maintains a package for the Conan package manager.
 - [Steffen](https://github.com/koemeet) fixed a potential issue with MSVC and `std::min`.
 - [Mike Tzou](https://github.com/Chocobo1) fixed some typos.
-- [amrcode](https://github.com/amrcode) noted a missleading documentation about comparison of floats.
+- [amrcode](https://github.com/amrcode) noted a misleading documentation about comparison of floats.
 - [Oleg Endo](https://github.com/olegendo) reduced the memory consumption by replacing `<iostream>` with `<iosfwd>`.
 - [dan-42](https://github.com/dan-42) cleaned up the CMake files to simplify including/reusing of the library.
 - [Nikita Ofitserov](https://github.com/himikof) allowed for moving values from initializer lists.
@@ -910,25 +1024,50 @@ I deeply appreciate the help of the following people.
 - [Nate Vargas](https://github.com/eld00d) added a Doxygen tag file.
 - [pvleuven](https://github.com/pvleuven) helped fixing a warning in ICC.
 - [Pavel](https://github.com/crea7or) helped fixing some warnings in MSVC.
-- [Jamie Seward](https://github.com/jseward) avoided unneccessary string copies in `find()` and `count()`.
+- [Jamie Seward](https://github.com/jseward) avoided unnecessary string copies in `find()` and `count()`.
 - [Mitja](https://github.com/Itja) fixed some typos.
 - [Jorrit Wronski](https://github.com/jowr) updated the Hunter package links.
 - [Matthias Möller](https://github.com/TinyTinni) added a `.natvis` for the MSVC debug view.
 - [bogemic](https://github.com/bogemic) fixed some C++17 deprecation warnings.
 - [Eren Okka](https://github.com/erengy) fixed some MSVC warnings.
-
+- [abolz](https://github.com/abolz) integrated the Grisu2 algorithm for proper floating-point formatting, allowing more roundtrip checks to succeed.
+- [Vadim Evard](https://github.com/Pipeliner) fixed a Markdown issue in the README.
+- [zerodefect](https://github.com/zerodefect) fixed a compiler warning.
+- [Kert](https://github.com/kaidokert) allowed to template the string type in the serialization and added the possibility to override the exceptional behavior.
+- [mark-99](https://github.com/mark-99) helped fixing an ICC error.
+- [Patrik Huber](https://github.com/patrikhuber) fixed links in the README file.
+- [johnfb](https://github.com/johnfb) found a bug in the implementation of CBOR's indefinite length strings.
+- [Paul Fultz II](https://github.com/pfultz2) added a note on the cget package manager.
+- [Wilson Lin](https://github.com/wla80) made the integration section of the README more concise.
+- [RalfBielig](https://github.com/ralfbielig) detected and fixed a memory leak in the parser callback.
+- [agrianius](https://github.com/agrianius) allowed to dump JSON to an alternative string type.
+- [Kevin Tonon](https://github.com/ktonon) overworked the C++11 compiler checks in CMake.
+- [Axel Huebl](https://github.com/ax3l) simplified a CMake check and added support for the [Spack package manager](https://spack.io).
+- [Carlos O'Ryan](https://github.com/coryan) fixed a typo.
+- [James Upjohn](https://github.com/jammehcow) fixed a version number in the compilers section.
+- [Chuck Atkins](https://github.com/chuckatkins) adjusted the CMake files to the CMake packaging guidelines
+- [Jan Schöppach](https://github.com/dns13) fixed a typo.
+- [martin-mfg](https://github.com/martin-mfg) fixed a typo.
+- [Matthias Möller](https://github.com/TinyTinni) removed the dependency from `std::stringstream`.
+- [agrianius](https://github.com/agrianius) added code to use alternative string implementations.
+- [Daniel599](https://github.com/Daniel599) allowed to use more algorithms with the `items()` function.
+- [Julius Rakow](https://github.com/jrakow) fixed the Meson include directory and fixed the links to [cppreference.com](cppreference.com).
+- [Sonu Lohani](https://github.com/sonulohani) fixed the compilation with MSVC 2015 in debug mode.
+- [grembo](https://github.com/grembo) fixed the test suite and re-enabled several test cases.
+- [Hyeon Kim](https://github.com/simnalamburt) introduced the macro `JSON_INTERNAL_CATCH` to control the exception handling inside the library.
+- [thyu](https://github.com/thyu) fixed a compiler warning.
 
 Thanks a lot for helping out! Please [let me know](mailto:[email protected]) if I forgot someone.
 
 
 ## Used third-party tools
 
-The library itself contains of a single header file licensed under the MIT license. However, it is built, tested, documented, and whatnot using a lot of third-party tools and services. Thanks a lot!
+The library itself consists of a single header file licensed under the MIT license. However, it is built, tested, documented, and whatnot using a lot of third-party tools and services. Thanks a lot!
 
+- [**amalgamate.py - Amalgamate C source and header files**](https://github.com/edlund/amalgamate) to create a single header file
 - [**American fuzzy lop**](http://lcamtuf.coredump.cx/afl/) for fuzz testing
 - [**AppVeyor**](https://www.appveyor.com) for [continuous integration](https://ci.appveyor.com/project/nlohmann/json) on Windows
 - [**Artistic Style**](http://astyle.sourceforge.net) for automatic source code identation
-- [**benchpress**](https://github.com/sbs-ableton/benchpress) to benchmark the code
 - [**Catch**](https://github.com/philsquared/Catch) for the unit tests
 - [**Clang**](http://clang.llvm.org) for compilation with code sanitizers
 - [**Cmake**](https://cmake.org) for build automation
@@ -936,17 +1075,17 @@ The library itself contains of a single header file licensed under the MIT licen
 - [**Coveralls**](https://coveralls.io) to measure [code coverage](https://coveralls.io/github/nlohmann/json)
 - [**Coverity Scan**](https://scan.coverity.com) for [static analysis](https://scan.coverity.com/projects/nlohmann-json)
 - [**cppcheck**](http://cppcheck.sourceforge.net) for static analysis
-- [**cxxopts**](https://github.com/jarro2783/cxxopts) to let benchpress parse command-line parameters
 - [**Doxygen**](http://www.stack.nl/~dimitri/doxygen/) to generate [documentation](https://nlohmann.github.io/json/)
 - [**git-update-ghpages**](https://github.com/rstacruz/git-update-ghpages) to upload the documentation to gh-pages
-- [**Github Changelog Generator**](https://github.com/skywinder/github-changelog-generator) to generate the [ChangeLog](https://github.com/nlohmann/json/blob/develop/ChangeLog.md)
+- [**GitHub Changelog Generator**](https://github.com/skywinder/github-changelog-generator) to generate the [ChangeLog](https://github.com/nlohmann/json/blob/develop/ChangeLog.md)
+- [**Google Benchmark**](https://github.com/google/benchmark) to implement the benchmarks
 - [**libFuzzer**](http://llvm.org/docs/LibFuzzer.html) to implement fuzz testing for OSS-Fuzz
-- [**OSS-Fuzz**](https://github.com/google/oss-fuzz) for continuous fuzz testing of the library
+- [**OSS-Fuzz**](https://github.com/google/oss-fuzz) for continuous fuzz testing of the library ([project repository](https://github.com/google/oss-fuzz/tree/master/projects/json))
 - [**Probot**](https://probot.github.io) for automating maintainer tasks such as closing stale issues, requesting missing information, or detecting toxic comments.
 - [**send_to_wandbox**](https://github.com/nlohmann/json/blob/develop/doc/scripts/send_to_wandbox.py) to send code examples to [Wandbox](http://melpon.org/wandbox)
 - [**Travis**](https://travis-ci.org) for [continuous integration](https://travis-ci.org/nlohmann/json) on Linux and macOS
 - [**Valgrind**](http://valgrind.org) to check for correct memory management
-- [**Wandbox**](http://melpon.org/wandbox) for [online examples](https://wandbox.org/permlink/Op57X0V7fTf2tdwl)
+- [**Wandbox**](http://melpon.org/wandbox) for [online examples](https://wandbox.org/permlink/TarF5pPn9NtHQjhf)
 
 
 ## Projects using JSON for Modern C++
@@ -956,7 +1095,7 @@ The library is currently used in Apple macOS Sierra and iOS 10. I am not sure wh
 
 ## Notes
 
-- The code contains numerous debug **assertions** which can be switched off by defining the preprocessor macro `NDEBUG`, see the [documentation of `assert`](http://en.cppreference.com/w/cpp/error/assert). In particular, note [`operator[]`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_a2e26bd0b0168abb61f67ad5bcd5b9fa1.html#a2e26bd0b0168abb61f67ad5bcd5b9fa1) implements **unchecked access** for const objects: If the given key is not present, the behavior is undefined (think of a dereferenced null pointer) and yields an [assertion failure](https://github.com/nlohmann/json/issues/289) if assertions are switched on. If you are not sure whether an element in an object exists, use checked access with the [`at()` function](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_a674de1ee73e6bf4843fc5dc1351fb726.html#a674de1ee73e6bf4843fc5dc1351fb726).
+- The code contains numerous debug **assertions** which can be switched off by defining the preprocessor macro `NDEBUG`, see the [documentation of `assert`](https://en.cppreference.com/w/cpp/error/assert). In particular, note [`operator[]`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_a2e26bd0b0168abb61f67ad5bcd5b9fa1.html#a2e26bd0b0168abb61f67ad5bcd5b9fa1) implements **unchecked access** for const objects: If the given key is not present, the behavior is undefined (think of a dereferenced null pointer) and yields an [assertion failure](https://github.com/nlohmann/json/issues/289) if assertions are switched on. If you are not sure whether an element in an object exists, use checked access with the [`at()` function](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_a674de1ee73e6bf4843fc5dc1351fb726.html#a674de1ee73e6bf4843fc5dc1351fb726).
 - As the exact type of a number is not defined in the [JSON specification](http://rfc7159.net/rfc7159), this library tries to choose the best fitting C++ number type automatically. As a result, the type `double` may be used to store numbers which may yield [**floating-point exceptions**](https://github.com/nlohmann/json/issues/181) in certain rare situations if floating-point exceptions have been unmasked in the calling code. These exceptions are not caused by the library and need to be fixed in the calling code, such as by re-masking the exceptions prior to calling library functions.
 - The library supports **Unicode input** as follows:
   - Only **UTF-8** encoded input is supported which is the default encoding for JSON according to [RFC 7159](http://rfc7159.net/rfc7159#rfc.section.8.1).

File diff ditekan karena terlalu besar
+ 766 - 823
ext/json/json.hpp


+ 2538 - 0
ext/librabbitmq/centos_x64/include/amqp.h

@@ -0,0 +1,2538 @@
+/** \file */
+/*
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MIT
+ *
+ * Portions created by Alan Antonuk are Copyright (c) 2012-2014
+ * Alan Antonuk. All Rights Reserved.
+ *
+ * Portions created by VMware are Copyright (c) 2007-2012 VMware, Inc.
+ * All Rights Reserved.
+ *
+ * Portions created by Tony Garnock-Jones are Copyright (c) 2009-2010
+ * VMware, Inc. and Tony Garnock-Jones. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ * ***** END LICENSE BLOCK *****
+ */
+
+#ifndef AMQP_H
+#define AMQP_H
+
+/** \cond HIDE_FROM_DOXYGEN */
+
+#ifdef __cplusplus
+#define AMQP_BEGIN_DECLS extern "C" {
+#define AMQP_END_DECLS }
+#else
+#define AMQP_BEGIN_DECLS
+#define AMQP_END_DECLS
+#endif
+
+/*
+ * \internal
+ * Important API decorators:
+ *  AMQP_PUBLIC_FUNCTION - a public API function
+ *  AMQP_PUBLIC_VARIABLE - a public API external variable
+ *  AMQP_CALL - calling convension (used on Win32)
+ */
+
+#if defined(_WIN32) && defined(_MSC_VER)
+#if defined(AMQP_BUILD) && !defined(AMQP_STATIC)
+#define AMQP_PUBLIC_FUNCTION __declspec(dllexport)
+#define AMQP_PUBLIC_VARIABLE __declspec(dllexport) extern
+#else
+#define AMQP_PUBLIC_FUNCTION
+#if !defined(AMQP_STATIC)
+#define AMQP_PUBLIC_VARIABLE __declspec(dllimport) extern
+#else
+#define AMQP_PUBLIC_VARIABLE extern
+#endif
+#endif
+#define AMQP_CALL __cdecl
+
+#elif defined(_WIN32) && defined(__BORLANDC__)
+#if defined(AMQP_BUILD) && !defined(AMQP_STATIC)
+#define AMQP_PUBLIC_FUNCTION __declspec(dllexport)
+#define AMQP_PUBLIC_VARIABLE __declspec(dllexport) extern
+#else
+#define AMQP_PUBLIC_FUNCTION
+#if !defined(AMQP_STATIC)
+#define AMQP_PUBLIC_VARIABLE __declspec(dllimport) extern
+#else
+#define AMQP_PUBLIC_VARIABLE extern
+#endif
+#endif
+#define AMQP_CALL __cdecl
+
+#elif defined(_WIN32) && defined(__MINGW32__)
+#if defined(AMQP_BUILD) && !defined(AMQP_STATIC)
+#define AMQP_PUBLIC_FUNCTION __declspec(dllexport)
+#define AMQP_PUBLIC_VARIABLE __declspec(dllexport) extern
+#else
+#define AMQP_PUBLIC_FUNCTION
+#if !defined(AMQP_STATIC)
+#define AMQP_PUBLIC_VARIABLE __declspec(dllimport) extern
+#else
+#define AMQP_PUBLIC_VARIABLE extern
+#endif
+#endif
+#define AMQP_CALL __cdecl
+
+#elif defined(_WIN32) && defined(__CYGWIN__)
+#if defined(AMQP_BUILD) && !defined(AMQP_STATIC)
+#define AMQP_PUBLIC_FUNCTION __declspec(dllexport)
+#define AMQP_PUBLIC_VARIABLE __declspec(dllexport)
+#else
+#define AMQP_PUBLIC_FUNCTION
+#if !defined(AMQP_STATIC)
+#define AMQP_PUBLIC_VARIABLE __declspec(dllimport) extern
+#else
+#define AMQP_PUBLIC_VARIABLE extern
+#endif
+#endif
+#define AMQP_CALL __cdecl
+
+#elif defined(__GNUC__) && __GNUC__ >= 4
+#define AMQP_PUBLIC_FUNCTION __attribute__((visibility("default")))
+#define AMQP_PUBLIC_VARIABLE __attribute__((visibility("default"))) extern
+#define AMQP_CALL
+#else
+#define AMQP_PUBLIC_FUNCTION
+#define AMQP_PUBLIC_VARIABLE extern
+#define AMQP_CALL
+#endif
+
+#if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)
+#define AMQP_DEPRECATED(function) function __attribute__((__deprecated__))
+#elif defined(_MSC_VER)
+#define AMQP_DEPRECATED(function) __declspec(deprecated) function
+#else
+#define AMQP_DEPRECATED(function)
+#endif
+
+/* Define ssize_t on Win32/64 platforms
+   See: http://lists.cs.uiuc.edu/pipermail/llvmdev/2010-April/030649.html for
+   details
+   */
+#if !defined(_W64)
+#if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300
+#define _W64 __w64
+#else
+#define _W64
+#endif
+#endif
+
+#ifdef _MSC_VER
+#ifdef _WIN64
+typedef __int64 ssize_t;
+#else
+typedef _W64 int ssize_t;
+#endif
+#endif
+
+#if defined(_WIN32) && defined(__MINGW32__)
+#include <sys/types.h>
+#endif
+
+/** \endcond */
+
+#include <stddef.h>
+#include <stdint.h>
+
+struct timeval;
+
+AMQP_BEGIN_DECLS
+
+/**
+ * \def AMQP_VERSION_MAJOR
+ *
+ * Major library version number compile-time constant
+ *
+ * The major version is incremented when backwards incompatible API changes
+ * are made.
+ *
+ * \sa AMQP_VERSION, AMQP_VERSION_STRING
+ *
+ * \since v0.4.0
+ */
+
+/**
+ * \def AMQP_VERSION_MINOR
+ *
+ * Minor library version number compile-time constant
+ *
+ * The minor version is incremented when new APIs are added. Existing APIs
+ * are left alone.
+ *
+ * \sa AMQP_VERSION, AMQP_VERSION_STRING
+ *
+ * \since v0.4.0
+ */
+
+/**
+ * \def AMQP_VERSION_PATCH
+ *
+ * Patch library version number compile-time constant
+ *
+ * The patch version is incremented when library code changes, but the API
+ * is not changed.
+ *
+ * \sa AMQP_VERSION, AMQP_VERSION_STRING
+ *
+ * \since v0.4.0
+ */
+
+/**
+ * \def AMQP_VERSION_IS_RELEASE
+ *
+ * Version constant set to 1 for tagged release, 0 otherwise
+ *
+ * NOTE: versions that are not tagged releases are not guaranteed to be API/ABI
+ * compatible with older releases, and may change commit-to-commit.
+ *
+ * \sa AMQP_VERSION, AMQP_VERSION_STRING
+ *
+ * \since v0.4.0
+ */
+/*
+ * Developer note: when changing these, be sure to update SOVERSION constants
+ *  in CMakeLists.txt and configure.ac
+ */
+
+#define AMQP_VERSION_MAJOR 0
+#define AMQP_VERSION_MINOR 10
+#define AMQP_VERSION_PATCH 0
+#define AMQP_VERSION_IS_RELEASE 0
+
+/**
+ * \def AMQP_VERSION_CODE
+ *
+ * Helper macro to geneate a packed version code suitable for
+ * comparison with AMQP_VERSION.
+ *
+ * \sa amqp_version_number() AMQP_VERSION_MAJOR, AMQP_VERSION_MINOR,
+ *     AMQP_VERSION_PATCH, AMQP_VERSION_IS_RELEASE, AMQP_VERSION
+ *
+ * \since v0.6.1
+ */
+#define AMQP_VERSION_CODE(major, minor, patch, release) \
+  ((major << 24) | (minor << 16) | (patch << 8) | (release))
+
+/**
+ * \def AMQP_VERSION
+ *
+ * Packed version number
+ *
+ * AMQP_VERSION is a 4-byte unsigned integer with the most significant byte
+ * set to AMQP_VERSION_MAJOR, the second most significant byte set to
+ * AMQP_VERSION_MINOR, third most significant byte set to AMQP_VERSION_PATCH,
+ * and the lowest byte set to AMQP_VERSION_IS_RELEASE.
+ *
+ * For example version 2.3.4 which is released version would be encoded as
+ * 0x02030401
+ *
+ * \sa amqp_version_number() AMQP_VERSION_MAJOR, AMQP_VERSION_MINOR,
+ *     AMQP_VERSION_PATCH, AMQP_VERSION_IS_RELEASE, AMQP_VERSION_CODE
+ *
+ * \since v0.4.0
+ */
+#define AMQP_VERSION                                        \
+  AMQP_VERSION_CODE(AMQP_VERSION_MAJOR, AMQP_VERSION_MINOR, \
+                    AMQP_VERSION_PATCH, AMQP_VERSION_IS_RELEASE)
+
+/** \cond HIDE_FROM_DOXYGEN */
+#define AMQ_STRINGIFY(s) AMQ_STRINGIFY_HELPER(s)
+#define AMQ_STRINGIFY_HELPER(s) #s
+
+#define AMQ_VERSION_STRING          \
+  AMQ_STRINGIFY(AMQP_VERSION_MAJOR) \
+  "." AMQ_STRINGIFY(AMQP_VERSION_MINOR) "." AMQ_STRINGIFY(AMQP_VERSION_PATCH)
+/** \endcond */
+
+/**
+ * \def AMQP_VERSION_STRING
+ *
+ * Version string compile-time constant
+ *
+ * Non-released versions of the library will have "-pre" appended to the
+ * version string
+ *
+ * \sa amqp_version()
+ *
+ * \since v0.4.0
+ */
+#if AMQP_VERSION_IS_RELEASE
+#define AMQP_VERSION_STRING AMQ_VERSION_STRING
+#else
+#define AMQP_VERSION_STRING AMQ_VERSION_STRING "-pre"
+#endif
+
+/**
+ * Returns the rabbitmq-c version as a packed integer.
+ *
+ * See \ref AMQP_VERSION
+ *
+ * \return packed 32-bit integer representing version of library at runtime
+ *
+ * \sa AMQP_VERSION, amqp_version()
+ *
+ * \since v0.4.0
+ */
+AMQP_PUBLIC_FUNCTION
+uint32_t AMQP_CALL amqp_version_number(void);
+
+/**
+ * Returns the rabbitmq-c version as a string.
+ *
+ * See \ref AMQP_VERSION_STRING
+ *
+ * \return a statically allocated string describing the version of rabbitmq-c.
+ *
+ * \sa amqp_version_number(), AMQP_VERSION_STRING, AMQP_VERSION
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+char const *AMQP_CALL amqp_version(void);
+
+/**
+ * \def AMQP_DEFAULT_FRAME_SIZE
+ *
+ * Default frame size (128Kb)
+ *
+ * \sa amqp_login(), amqp_login_with_properties()
+ *
+ * \since v0.4.0
+ */
+#define AMQP_DEFAULT_FRAME_SIZE 131072
+
+/**
+ * \def AMQP_DEFAULT_MAX_CHANNELS
+ *
+ * Default maximum number of channels (2047, RabbitMQ default limit of 2048,
+ * minus 1 for channel 0). RabbitMQ set a default limit of 2048 channels per
+ * connection in v3.7.5 to prevent broken clients from leaking too many
+ * channels.
+ *
+ * \sa amqp_login(), amqp_login_with_properties()
+ *
+ * \since v0.4.0
+ */
+#define AMQP_DEFAULT_MAX_CHANNELS 2047
+
+/**
+ * \def AMQP_DEFAULT_HEARTBEAT
+ *
+ * Default heartbeat interval (0, heartbeat disabled)
+ *
+ * \sa amqp_login(), amqp_login_with_properties()
+ *
+ * \since v0.4.0
+ */
+#define AMQP_DEFAULT_HEARTBEAT 0
+
+/**
+ * \def AMQP_DEFAULT_VHOST
+ *
+ * Default RabbitMQ vhost: "/"
+ *
+ * \sa amqp_login(), amqp_login_with_properties()
+ *
+ * \since v0.9.0
+ */
+#define AMQP_DEFAULT_VHOST "/"
+
+/**
+ * boolean type 0 = false, true otherwise
+ *
+ * \since v0.1
+ */
+typedef int amqp_boolean_t;
+
+/**
+ * Method number
+ *
+ * \since v0.1
+ */
+typedef uint32_t amqp_method_number_t;
+
+/**
+ * Bitmask for flags
+ *
+ * \since v0.1
+ */
+typedef uint32_t amqp_flags_t;
+
+/**
+ * Channel type
+ *
+ * \since v0.1
+ */
+typedef uint16_t amqp_channel_t;
+
+/**
+ * Buffer descriptor
+ *
+ * \since v0.1
+ */
+typedef struct amqp_bytes_t_ {
+  size_t len;  /**< length of the buffer in bytes */
+  void *bytes; /**< pointer to the beginning of the buffer */
+} amqp_bytes_t;
+
+/**
+ * Decimal data type
+ *
+ * \since v0.1
+ */
+typedef struct amqp_decimal_t_ {
+  uint8_t decimals; /**< the location of the decimal point */
+  uint32_t value;   /**< the value before the decimal point is applied */
+} amqp_decimal_t;
+
+/**
+ * AMQP field table
+ *
+ * An AMQP field table is a set of key-value pairs.
+ * A key is a UTF-8 encoded string up to 128 bytes long, and are not null
+ * terminated.
+ * A value can be one of several different datatypes. \sa
+ * amqp_field_value_kind_t
+ *
+ * \sa amqp_table_entry_t
+ *
+ * \since v0.1
+ */
+typedef struct amqp_table_t_ {
+  int num_entries;                     /**< length of entries array */
+  struct amqp_table_entry_t_ *entries; /**< an array of table entries */
+} amqp_table_t;
+
+/**
+ * An AMQP Field Array
+ *
+ * A repeated set of field values, all must be of the same type
+ *
+ * \since v0.1
+ */
+typedef struct amqp_array_t_ {
+  int num_entries;                     /**< Number of entries in the table */
+  struct amqp_field_value_t_ *entries; /**< linked list of field values */
+} amqp_array_t;
+
+/*
+  0-9   0-9-1   Qpid/Rabbit  Type               Remarks
+---------------------------------------------------------------------------
+        t       t            Boolean
+        b       b            Signed 8-bit
+        B                    Unsigned 8-bit
+        U       s            Signed 16-bit      (A1)
+        u                    Unsigned 16-bit
+  I     I       I            Signed 32-bit
+        i                    Unsigned 32-bit
+        L       l            Signed 64-bit      (B)
+        l                    Unsigned 64-bit
+        f       f            32-bit float
+        d       d            64-bit float
+  D     D       D            Decimal
+        s                    Short string       (A2)
+  S     S       S            Long string
+        A                    Nested Array
+  T     T       T            Timestamp (u64)
+  F     F       F            Nested Table
+  V     V       V            Void
+                x            Byte array
+
+Remarks:
+
+ A1, A2: Notice how the types **CONFLICT** here. In Qpid and Rabbit,
+         's' means a signed 16-bit integer; in 0-9-1, it means a
+          short string.
+
+ B: Notice how the signednesses **CONFLICT** here. In Qpid and Rabbit,
+    'l' means a signed 64-bit integer; in 0-9-1, it means an unsigned
+    64-bit integer.
+
+I'm going with the Qpid/Rabbit types, where there's a conflict, and
+the 0-9-1 types otherwise. 0-8 is a subset of 0-9, which is a subset
+of the other two, so this will work for both 0-8 and 0-9-1 branches of
+the code.
+*/
+
+/**
+ * A field table value
+ *
+ * \since v0.1
+ */
+typedef struct amqp_field_value_t_ {
+  uint8_t kind; /**< the type of the entry /sa amqp_field_value_kind_t */
+  union {
+    amqp_boolean_t boolean; /**< boolean type AMQP_FIELD_KIND_BOOLEAN */
+    int8_t i8;              /**< int8_t type AMQP_FIELD_KIND_I8 */
+    uint8_t u8;             /**< uint8_t type AMQP_FIELD_KIND_U8 */
+    int16_t i16;            /**< int16_t type AMQP_FIELD_KIND_I16 */
+    uint16_t u16;           /**< uint16_t type AMQP_FIELD_KIND_U16 */
+    int32_t i32;            /**< int32_t type AMQP_FIELD_KIND_I32 */
+    uint32_t u32;           /**< uint32_t type AMQP_FIELD_KIND_U32 */
+    int64_t i64;            /**< int64_t type AMQP_FIELD_KIND_I64 */
+    uint64_t u64;           /**< uint64_t type AMQP_FIELD_KIND_U64,
+                               AMQP_FIELD_KIND_TIMESTAMP */
+    float f32;              /**< float type AMQP_FIELD_KIND_F32 */
+    double f64;             /**< double type AMQP_FIELD_KIND_F64 */
+    amqp_decimal_t decimal; /**< amqp_decimal_t AMQP_FIELD_KIND_DECIMAL */
+    amqp_bytes_t bytes;     /**< amqp_bytes_t type AMQP_FIELD_KIND_UTF8,
+                               AMQP_FIELD_KIND_BYTES */
+    amqp_table_t table;     /**< amqp_table_t type AMQP_FIELD_KIND_TABLE */
+    amqp_array_t array;     /**< amqp_array_t type AMQP_FIELD_KIND_ARRAY */
+  } value;                  /**< a union of the value */
+} amqp_field_value_t;
+
+/**
+ * An entry in a field-table
+ *
+ * \sa amqp_table_encode(), amqp_table_decode(), amqp_table_clone()
+ *
+ * \since v0.1
+ */
+typedef struct amqp_table_entry_t_ {
+  amqp_bytes_t key; /**< the table entry key. Its a null-terminated UTF-8
+                     * string, with a maximum size of 128 bytes */
+  amqp_field_value_t value; /**< the table entry values */
+} amqp_table_entry_t;
+
+/**
+ * Field value types
+ *
+ * \since v0.1
+ */
+typedef enum {
+  AMQP_FIELD_KIND_BOOLEAN =
+      't', /**< boolean type. 0 = false, 1 = true @see amqp_boolean_t */
+  AMQP_FIELD_KIND_I8 = 'b',  /**< 8-bit signed integer, datatype: int8_t */
+  AMQP_FIELD_KIND_U8 = 'B',  /**< 8-bit unsigned integer, datatype: uint8_t */
+  AMQP_FIELD_KIND_I16 = 's', /**< 16-bit signed integer, datatype: int16_t */
+  AMQP_FIELD_KIND_U16 = 'u', /**< 16-bit unsigned integer, datatype: uint16_t */
+  AMQP_FIELD_KIND_I32 = 'I', /**< 32-bit signed integer, datatype: int32_t */
+  AMQP_FIELD_KIND_U32 = 'i', /**< 32-bit unsigned integer, datatype: uint32_t */
+  AMQP_FIELD_KIND_I64 = 'l', /**< 64-bit signed integer, datatype: int64_t */
+  AMQP_FIELD_KIND_U64 = 'L', /**< 64-bit unsigned integer, datatype: uint64_t */
+  AMQP_FIELD_KIND_F32 =
+      'f', /**< single-precision floating point value, datatype: float */
+  AMQP_FIELD_KIND_F64 =
+      'd', /**< double-precision floating point value, datatype: double */
+  AMQP_FIELD_KIND_DECIMAL =
+      'D', /**< amqp-decimal value, datatype: amqp_decimal_t */
+  AMQP_FIELD_KIND_UTF8 = 'S',      /**< UTF-8 null-terminated character string,
+                                      datatype: amqp_bytes_t */
+  AMQP_FIELD_KIND_ARRAY = 'A',     /**< field array (repeated values of another
+                                      datatype. datatype: amqp_array_t */
+  AMQP_FIELD_KIND_TIMESTAMP = 'T', /**< 64-bit timestamp. datatype uint64_t */
+  AMQP_FIELD_KIND_TABLE = 'F', /**< field table. encapsulates a table inside a
+                                  table entry. datatype: amqp_table_t */
+  AMQP_FIELD_KIND_VOID = 'V',  /**< empty entry */
+  AMQP_FIELD_KIND_BYTES =
+      'x' /**< unformatted byte string, datatype: amqp_bytes_t */
+} amqp_field_value_kind_t;
+
+/**
+ * A list of allocation blocks
+ *
+ * \since v0.1
+ */
+typedef struct amqp_pool_blocklist_t_ {
+  int num_blocks;   /**< Number of blocks in the block list */
+  void **blocklist; /**< Array of memory blocks */
+} amqp_pool_blocklist_t;
+
+/**
+ * A memory pool
+ *
+ * \since v0.1
+ */
+typedef struct amqp_pool_t_ {
+  size_t pagesize; /**< the size of the page in bytes. Allocations less than or
+                    * equal to this size are allocated in the pages block list.
+                    * Allocations greater than this are allocated in their own
+                    * own block in the large_blocks block list */
+
+  amqp_pool_blocklist_t pages; /**< blocks that are the size of pagesize */
+  amqp_pool_blocklist_t
+      large_blocks; /**< allocations larger than the pagesize */
+
+  int next_page;     /**< an index to the next unused page block */
+  char *alloc_block; /**< pointer to the current allocation block */
+  size_t alloc_used; /**< number of bytes in the current allocation block that
+                        has been used */
+} amqp_pool_t;
+
+/**
+ * An amqp method
+ *
+ * \since v0.1
+ */
+typedef struct amqp_method_t_ {
+  amqp_method_number_t id; /**< the method id number */
+  void *decoded;           /**< pointer to the decoded method,
+                            *    cast to the appropriate type to use */
+} amqp_method_t;
+
+/**
+ * An AMQP frame
+ *
+ * \since v0.1
+ */
+typedef struct amqp_frame_t_ {
+  uint8_t frame_type;     /**< frame type. The types:
+                           * - AMQP_FRAME_METHOD - use the method union member
+                           * - AMQP_FRAME_HEADER - use the properties union member
+                           * - AMQP_FRAME_BODY - use the body_fragment union member
+                           */
+  amqp_channel_t channel; /**< the channel the frame was received on */
+  union {
+    amqp_method_t
+        method; /**< a method, use if frame_type == AMQP_FRAME_METHOD */
+    struct {
+      uint16_t class_id;        /**< the class for the properties */
+      uint64_t body_size;       /**< size of the body in bytes */
+      void *decoded;            /**< the decoded properties */
+      amqp_bytes_t raw;         /**< amqp-encoded properties structure */
+    } properties;               /**< message header, a.k.a., properties,
+                                      use if frame_type == AMQP_FRAME_HEADER */
+    amqp_bytes_t body_fragment; /**< a body fragment, use if frame_type ==
+                                   AMQP_FRAME_BODY */
+    struct {
+      uint8_t transport_high;         /**< @internal first byte of handshake */
+      uint8_t transport_low;          /**< @internal second byte of handshake */
+      uint8_t protocol_version_major; /**< @internal third byte of handshake */
+      uint8_t protocol_version_minor; /**< @internal fourth byte of handshake */
+    } protocol_header; /**< Used only when doing the initial handshake with the
+                          broker, don't use otherwise */
+  } payload;           /**< the payload of the frame */
+} amqp_frame_t;
+
+/**
+ * Response type
+ *
+ * \since v0.1
+ */
+typedef enum amqp_response_type_enum_ {
+  AMQP_RESPONSE_NONE = 0, /**< the library got an EOF from the socket */
+  AMQP_RESPONSE_NORMAL, /**< response normal, the RPC completed successfully */
+  AMQP_RESPONSE_LIBRARY_EXCEPTION, /**< library error, an error occurred in the
+                                      library, examine the library_error */
+  AMQP_RESPONSE_SERVER_EXCEPTION   /**< server exception, the broker returned an
+                                      error, check replay */
+} amqp_response_type_enum;
+
+/**
+ * Reply from a RPC method on the broker
+ *
+ * \since v0.1
+ */
+typedef struct amqp_rpc_reply_t_ {
+  amqp_response_type_enum reply_type; /**< the reply type:
+                                       * - AMQP_RESPONSE_NORMAL - the RPC
+                                       * completed successfully
+                                       * - AMQP_RESPONSE_SERVER_EXCEPTION - the
+                                       * broker returned
+                                       *     an exception, check the reply field
+                                       * - AMQP_RESPONSE_LIBRARY_EXCEPTION - the
+                                       * library
+                                       *    encountered an error, check the
+                                       * library_error field
+                                       */
+  amqp_method_t reply; /**< in case of AMQP_RESPONSE_SERVER_EXCEPTION this
+                        * field will be set to the method returned from the
+                        * broker */
+  int library_error;   /**< in case of AMQP_RESPONSE_LIBRARY_EXCEPTION this
+                        *    field will be set to an error code. An error
+                        *     string can be retrieved using amqp_error_string */
+} amqp_rpc_reply_t;
+
+/**
+ * SASL method type
+ *
+ * \since v0.1
+ */
+typedef enum amqp_sasl_method_enum_ {
+  AMQP_SASL_METHOD_UNDEFINED = -1, /**< Invalid SASL method */
+  AMQP_SASL_METHOD_PLAIN =
+      0, /**< the PLAIN SASL method for authentication to the broker */
+  AMQP_SASL_METHOD_EXTERNAL =
+      1 /**< the EXTERNAL SASL method for authentication to the broker */
+} amqp_sasl_method_enum;
+
+/**
+ * connection state object
+ *
+ * \since v0.1
+ */
+typedef struct amqp_connection_state_t_ *amqp_connection_state_t;
+
+/**
+ * Socket object
+ *
+ * \since v0.4.0
+ */
+typedef struct amqp_socket_t_ amqp_socket_t;
+
+/**
+ * Status codes
+ *
+ * \since v0.4.0
+ */
+/* NOTE: When updating this enum, update the strings in librabbitmq/amqp_api.c
+ */
+typedef enum amqp_status_enum_ {
+  AMQP_STATUS_OK = 0x0,                             /**< Operation successful */
+  AMQP_STATUS_NO_MEMORY = -0x0001,                  /**< Memory allocation
+                                                         failed */
+  AMQP_STATUS_BAD_AMQP_DATA = -0x0002,              /**< Incorrect or corrupt
+                                                         data was received from
+                                                         the broker. This is a
+                                                         protocol error. */
+  AMQP_STATUS_UNKNOWN_CLASS = -0x0003,              /**< An unknown AMQP class
+                                                         was received. This is
+                                                         a protocol error. */
+  AMQP_STATUS_UNKNOWN_METHOD = -0x0004,             /**< An unknown AMQP method
+                                                         was received. This is
+                                                         a protocol error. */
+  AMQP_STATUS_HOSTNAME_RESOLUTION_FAILED = -0x0005, /**< Unable to resolve the
+                                                     * hostname */
+  AMQP_STATUS_INCOMPATIBLE_AMQP_VERSION = -0x0006,  /**< The broker advertised
+                                                         an incompaible AMQP
+                                                         version */
+  AMQP_STATUS_CONNECTION_CLOSED = -0x0007,          /**< The connection to the
+                                                         broker has been closed
+                                                         */
+  AMQP_STATUS_BAD_URL = -0x0008,                    /**< malformed AMQP URL */
+  AMQP_STATUS_SOCKET_ERROR = -0x0009,               /**< A socket error
+                                                         occurred */
+  AMQP_STATUS_INVALID_PARAMETER = -0x000A,          /**< An invalid parameter
+                                                         was passed into the
+                                                         function */
+  AMQP_STATUS_TABLE_TOO_BIG = -0x000B,              /**< The amqp_table_t object
+                                                         cannot be serialized
+                                                         because the output
+                                                         buffer is too small */
+  AMQP_STATUS_WRONG_METHOD = -0x000C,               /**< The wrong method was
+                                                         received */
+  AMQP_STATUS_TIMEOUT = -0x000D,                    /**< Operation timed out */
+  AMQP_STATUS_TIMER_FAILURE = -0x000E,              /**< The underlying system
+                                                         timer facility failed */
+  AMQP_STATUS_HEARTBEAT_TIMEOUT = -0x000F,          /**< Timed out waiting for
+                                                         heartbeat */
+  AMQP_STATUS_UNEXPECTED_STATE = -0x0010,           /**< Unexpected protocol
+                                                         state */
+  AMQP_STATUS_SOCKET_CLOSED = -0x0011,              /**< Underlying socket is
+                                                         closed */
+  AMQP_STATUS_SOCKET_INUSE = -0x0012,               /**< Underlying socket is
+                                                         already open */
+  AMQP_STATUS_BROKER_UNSUPPORTED_SASL_METHOD = -0x0013, /**< Broker does not
+                                                          support the requested
+                                                          SASL mechanism */
+  AMQP_STATUS_UNSUPPORTED = -0x0014, /**< Parameter is unsupported
+                                       in this version */
+  _AMQP_STATUS_NEXT_VALUE = -0x0015, /**< Internal value */
+
+  AMQP_STATUS_TCP_ERROR = -0x0100,                /**< A generic TCP error
+                                                       occurred */
+  AMQP_STATUS_TCP_SOCKETLIB_INIT_ERROR = -0x0101, /**< An error occurred trying
+                                                       to initialize the
+                                                       socket library*/
+  _AMQP_STATUS_TCP_NEXT_VALUE = -0x0102,          /**< Internal value */
+
+  AMQP_STATUS_SSL_ERROR = -0x0200,                  /**< A generic SSL error
+                                                         occurred. */
+  AMQP_STATUS_SSL_HOSTNAME_VERIFY_FAILED = -0x0201, /**< SSL validation of
+                                                         hostname against
+                                                         peer certificate
+                                                         failed */
+  AMQP_STATUS_SSL_PEER_VERIFY_FAILED = -0x0202,     /**< SSL validation of peer
+                                                         certificate failed. */
+  AMQP_STATUS_SSL_CONNECTION_FAILED = -0x0203, /**< SSL handshake failed. */
+  _AMQP_STATUS_SSL_NEXT_VALUE = -0x0204        /**< Internal value */
+} amqp_status_enum;
+
+/**
+ * AMQP delivery modes.
+ * Use these values for the #amqp_basic_properties_t::delivery_mode field.
+ *
+ * \since v0.5
+ */
+typedef enum {
+  AMQP_DELIVERY_NONPERSISTENT = 1, /**< Non-persistent message */
+  AMQP_DELIVERY_PERSISTENT = 2     /**< Persistent message */
+} amqp_delivery_mode_enum;
+
+AMQP_END_DECLS
+
+#include <amqp_framing.h>
+
+AMQP_BEGIN_DECLS
+
+/**
+ * Empty bytes structure
+ *
+ * \since v0.2
+ */
+AMQP_PUBLIC_VARIABLE const amqp_bytes_t amqp_empty_bytes;
+
+/**
+ * Empty table structure
+ *
+ * \since v0.2
+ */
+AMQP_PUBLIC_VARIABLE const amqp_table_t amqp_empty_table;
+
+/**
+ * Empty table array structure
+ *
+ * \since v0.2
+ */
+AMQP_PUBLIC_VARIABLE const amqp_array_t amqp_empty_array;
+
+/* Compatibility macros for the above, to avoid the need to update
+   code written against earlier versions of librabbitmq. */
+
+/**
+ * \def AMQP_EMPTY_BYTES
+ *
+ * Deprecated, use \ref amqp_empty_bytes instead
+ *
+ * \deprecated use \ref amqp_empty_bytes instead
+ *
+ * \since v0.1
+ */
+#define AMQP_EMPTY_BYTES amqp_empty_bytes
+
+/**
+ * \def AMQP_EMPTY_TABLE
+ *
+ * Deprecated, use \ref amqp_empty_table instead
+ *
+ * \deprecated use \ref amqp_empty_table instead
+ *
+ * \since v0.1
+ */
+#define AMQP_EMPTY_TABLE amqp_empty_table
+
+/**
+ * \def AMQP_EMPTY_ARRAY
+ *
+ * Deprecated, use \ref amqp_empty_array instead
+ *
+ * \deprecated use \ref amqp_empty_array instead
+ *
+ * \since v0.1
+ */
+#define AMQP_EMPTY_ARRAY amqp_empty_array
+
+/**
+ * Initializes an amqp_pool_t memory allocation pool for use
+ *
+ * Readies an allocation pool for use. An amqp_pool_t
+ * must be initialized before use
+ *
+ * \param [in] pool the amqp_pool_t structure to initialize.
+ *              Calling this function on a pool a pool that has
+ *              already been initialized will result in undefined
+ *              behavior
+ * \param [in] pagesize the unit size that the pool will allocate
+ *              memory chunks in. Anything allocated against the pool
+ *              with a requested size will be carved out of a block
+ *              this size. Allocations larger than this will be
+ *              allocated individually
+ *
+ * \sa recycle_amqp_pool(), empty_amqp_pool(), amqp_pool_alloc(),
+ *     amqp_pool_alloc_bytes(), amqp_pool_t
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+void AMQP_CALL init_amqp_pool(amqp_pool_t *pool, size_t pagesize);
+
+/**
+ * Recycles an amqp_pool_t memory allocation pool
+ *
+ * Recycles the space allocate by the pool
+ *
+ * This invalidates all allocations made against the pool before this call is
+ * made, any use of any allocations made before recycle_amqp_pool() is called
+ * will result in undefined behavior.
+ *
+ * Note: this may or may not release memory, to force memory to be released
+ * call empty_amqp_pool().
+ *
+ * \param [in] pool the amqp_pool_t to recycle
+ *
+ * \sa recycle_amqp_pool(), empty_amqp_pool(), amqp_pool_alloc(),
+ *      amqp_pool_alloc_bytes()
+ *
+ * \since v0.1
+ *
+ */
+AMQP_PUBLIC_FUNCTION
+void AMQP_CALL recycle_amqp_pool(amqp_pool_t *pool);
+
+/**
+ * Empties an amqp memory pool
+ *
+ * Releases all memory associated with an allocation pool
+ *
+ * \param [in] pool the amqp_pool_t to empty
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+void AMQP_CALL empty_amqp_pool(amqp_pool_t *pool);
+
+/**
+ * Allocates a block of memory from an amqp_pool_t memory pool
+ *
+ * Memory will be aligned on a 8-byte boundary. If a 0-length allocation is
+ * requested, a NULL pointer will be returned.
+ *
+ * \param [in] pool the allocation pool to allocate the memory from
+ * \param [in] amount the size of the allocation in bytes.
+ * \return a pointer to the memory block, or NULL if the allocation cannot
+ *          be satisfied.
+ *
+ * \sa init_amqp_pool(), recycle_amqp_pool(), empty_amqp_pool(),
+ *     amqp_pool_alloc_bytes()
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+void *AMQP_CALL amqp_pool_alloc(amqp_pool_t *pool, size_t amount);
+
+/**
+ * Allocates a block of memory from an amqp_pool_t to an amqp_bytes_t
+ *
+ * Memory will be aligned on a 8-byte boundary. If a 0-length allocation is
+ * requested, output.bytes = NULL.
+ *
+ * \param [in] pool the allocation pool to allocate the memory from
+ * \param [in] amount the size of the allocation in bytes
+ * \param [in] output the location to store the pointer. On success
+ *              output.bytes will be set to the beginning of the buffer
+ *              output.len will be set to amount
+ *              On error output.bytes will be set to NULL and output.len
+ *              set to 0
+ *
+ * \sa init_amqp_pool(), recycle_amqp_pool(), empty_amqp_pool(),
+ *     amqp_pool_alloc()
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+void AMQP_CALL amqp_pool_alloc_bytes(amqp_pool_t *pool, size_t amount,
+                                     amqp_bytes_t *output);
+
+/**
+ * Wraps a c string in an amqp_bytes_t
+ *
+ * Takes a string, calculates its length and creates an
+ * amqp_bytes_t that points to it. The string is not duplicated.
+ *
+ * For a given input cstr, The amqp_bytes_t output.bytes is the
+ * same as cstr, output.len is the length of the string not including
+ * the \0 terminator
+ *
+ * This function uses strlen() internally so cstr must be properly
+ * terminated
+ *
+ * \param [in] cstr the c string to wrap
+ * \return an amqp_bytes_t that describes the string
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_bytes_t AMQP_CALL amqp_cstring_bytes(char const *cstr);
+
+/**
+ * Duplicates an amqp_bytes_t buffer.
+ *
+ * The buffer is cloned and the contents copied.
+ *
+ * The memory associated with the output is allocated
+ * with amqp_bytes_malloc() and should be freed with
+ * amqp_bytes_free()
+ *
+ * \param [in] src
+ * \return a clone of the src
+ *
+ * \sa amqp_bytes_free(), amqp_bytes_malloc()
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_bytes_t AMQP_CALL amqp_bytes_malloc_dup(amqp_bytes_t src);
+
+/**
+ * Allocates a amqp_bytes_t buffer
+ *
+ * Creates an amqp_bytes_t buffer of the specified amount, the buffer should be
+ * freed using amqp_bytes_free()
+ *
+ * \param [in] amount the size of the buffer in bytes
+ * \returns an amqp_bytes_t with amount bytes allocated.
+ *           output.bytes will be set to NULL on error
+ *
+ * \sa amqp_bytes_free(), amqp_bytes_malloc_dup()
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_bytes_t AMQP_CALL amqp_bytes_malloc(size_t amount);
+
+/**
+ * Frees an amqp_bytes_t buffer
+ *
+ * Frees a buffer allocated with amqp_bytes_malloc() or amqp_bytes_malloc_dup()
+ *
+ * Calling amqp_bytes_free on buffers not allocated with one
+ * of those two functions will result in undefined behavior
+ *
+ * \param [in] bytes the buffer to free
+ *
+ * \sa amqp_bytes_malloc(), amqp_bytes_malloc_dup()
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+void AMQP_CALL amqp_bytes_free(amqp_bytes_t bytes);
+
+/**
+ * Allocate and initialize a new amqp_connection_state_t object
+ *
+ * amqp_connection_state_t objects created with this function
+ * should be freed with amqp_destroy_connection()
+ *
+ * \returns an opaque pointer on success, NULL or 0 on failure.
+ *
+ * \sa amqp_destroy_connection()
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_connection_state_t AMQP_CALL amqp_new_connection(void);
+
+/**
+ * Get the underlying socket descriptor for the connection
+ *
+ * \warning Use the socket returned from this function carefully, incorrect use
+ * of the socket outside of the library will lead to undefined behavior.
+ * Additionally rabbitmq-c may use the socket differently version-to-version,
+ * what may work in one version, may break in the next version. Be sure to
+ * throughly test any applications that use the socket returned by this
+ * function especially when using a newer version of rabbitmq-c
+ *
+ * \param [in] state the connection object
+ * \returns the socket descriptor if one has been set, -1 otherwise
+ *
+ * \sa amqp_tcp_socket_new(), amqp_ssl_socket_new(), amqp_socket_open()
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_get_sockfd(amqp_connection_state_t state);
+
+/**
+ * Deprecated, use amqp_tcp_socket_new() or amqp_ssl_socket_new()
+ *
+ * \deprecated Use amqp_tcp_socket_new() or amqp_ssl_socket_new()
+ *
+ * Sets the socket descriptor associated with the connection. The socket
+ * should be connected to a broker, and should not be read to or written from
+ * before calling this function.  A socket descriptor can be created and opened
+ * using amqp_open_socket()
+ *
+ * \param [in] state the connection object
+ * \param [in] sockfd the socket
+ *
+ * \sa amqp_open_socket(), amqp_tcp_socket_new(), amqp_ssl_socket_new()
+ *
+ * \since v0.1
+ */
+AMQP_DEPRECATED(AMQP_PUBLIC_FUNCTION void AMQP_CALL
+                    amqp_set_sockfd(amqp_connection_state_t state, int sockfd));
+
+/**
+ * Tune client side parameters
+ *
+ * \warning This function may call abort() if the connection is in a certain
+ *  state. As such it should probably not be called code outside the library.
+ *  connection parameters should be specified when calling amqp_login() or
+ *  amqp_login_with_properties()
+ *
+ * This function changes channel_max, frame_max, and heartbeat parameters, on
+ * the client side only. It does not try to renegotiate these parameters with
+ * the broker. Using this function will lead to unexpected results.
+ *
+ * \param [in] state the connection object
+ * \param [in] channel_max the maximum number of channels.
+ *              The largest this can be is 65535
+ * \param [in] frame_max the maximum size of an frame.
+ *              The smallest this can be is 4096
+ *              The largest this can be is 2147483647
+ *              Unless you know what you're doing the recommended
+ *              size is 131072 or 128KB
+ * \param [in] heartbeat the number of seconds between heartbeats
+ *
+ * \return AMQP_STATUS_OK on success, an amqp_status_enum value otherwise.
+ *  Possible error codes include:
+ *  - AMQP_STATUS_NO_MEMORY memory allocation failed.
+ *  - AMQP_STATUS_TIMER_FAILURE the underlying system timer indicated it
+ *    failed.
+ *
+ * \sa amqp_login(), amqp_login_with_properties()
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_tune_connection(amqp_connection_state_t state,
+                                   int channel_max, int frame_max,
+                                   int heartbeat);
+
+/**
+ * Get the maximum number of channels the connection can handle
+ *
+ * The maximum number of channels is set when connection negotiation takes
+ * place in amqp_login() or amqp_login_with_properties().
+ *
+ * \param [in] state the connection object
+ * \return the maximum number of channels. 0 if there is no limit
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_get_channel_max(amqp_connection_state_t state);
+
+/**
+ * Get the maximum size of an frame the connection can handle
+ *
+ * The maximum size of an frame is set when connection negotiation takes
+ * place in amqp_login() or amqp_login_with_properties().
+ *
+ * \param [in] state the connection object
+ * \return the maximum size of an frame.
+ *
+ * \since v0.6
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_get_frame_max(amqp_connection_state_t state);
+
+/**
+ * Get the number of seconds between heartbeats of the connection
+ *
+ * The number of seconds between heartbeats is set when connection
+ * negotiation takes place in amqp_login() or amqp_login_with_properties().
+ *
+ * \param [in] state the connection object
+ * \return the number of seconds between heartbeats.
+ *
+ * \since v0.6
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_get_heartbeat(amqp_connection_state_t state);
+
+/**
+ * Destroys an amqp_connection_state_t object
+ *
+ * Destroys a amqp_connection_state_t object that was created with
+ * amqp_new_connection(). If the connection with the broker is open, it will be
+ * implicitly closed with a reply code of 200 (success). Any memory that
+ * would be freed with amqp_maybe_release_buffers() or
+ * amqp_maybe_release_buffers_on_channel() will be freed, and use of that
+ * memory will caused undefined behavior.
+ *
+ * \param [in] state the connection object
+ * \return AMQP_STATUS_OK on success. amqp_status_enum value failure
+ *
+ * \sa amqp_new_connection()
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_destroy_connection(amqp_connection_state_t state);
+
+/**
+ * Process incoming data
+ *
+ * \warning This is a low-level function intended for those who want to
+ *  have greater control over input and output over the socket from the
+ *  broker. Correctly using this function requires in-depth knowledge of AMQP
+ *  and rabbitmq-c.
+ *
+ * For a given buffer of data received from the broker, decode the first
+ * frame in the buffer. If more than one frame is contained in the input buffer
+ * the return value will be less than the received_data size, the caller should
+ * adjust received_data buffer descriptor to point to the beginning of the
+ * buffer + the return value.
+ *
+ * \param [in] state the connection object
+ * \param [in] received_data a buffer of data received from the broker. The
+ *  function will return the number of bytes of the buffer it used. The
+ *  function copies these bytes to an internal buffer: this part of the buffer
+ *  may be reused after this function successfully completes.
+ * \param [in,out] decoded_frame caller should pass in a pointer to an
+ *  amqp_frame_t struct. If there is enough data in received_data for a
+ *  complete frame, decoded_frame->frame_type will be set to something OTHER
+ *  than 0. decoded_frame may contain members pointing to memory owned by
+ *  the state object. This memory can be recycled with
+ *  amqp_maybe_release_buffers() or amqp_maybe_release_buffers_on_channel().
+ * \return number of bytes consumed from received_data or 0 if a 0-length
+ *  buffer was passed. A negative return value indicates failure. Possible
+ * errors:
+ *  - AMQP_STATUS_NO_MEMORY failure in allocating memory. The library is likely
+ *    in an indeterminate state making recovery unlikely. Client should note the
+ *    error and terminate the application
+ *  - AMQP_STATUS_BAD_AMQP_DATA bad AMQP data was received. The connection
+ *    should be shutdown immediately
+ *  - AMQP_STATUS_UNKNOWN_METHOD: an unknown method was received from the
+ *    broker. This is likely a protocol error and the connection should be
+ *    shutdown immediately
+ *  - AMQP_STATUS_UNKNOWN_CLASS: a properties frame with an unknown class
+ *    was received from the broker. This is likely a protocol error and the
+ *    connection should be shutdown immediately
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_handle_input(amqp_connection_state_t state,
+                                amqp_bytes_t received_data,
+                                amqp_frame_t *decoded_frame);
+
+/**
+ * Check to see if connection memory can be released
+ *
+ * \deprecated This function is deprecated in favor of
+ *  amqp_maybe_release_buffers() or amqp_maybe_release_buffers_on_channel()
+ *
+ * Checks the state of an amqp_connection_state_t object to see if
+ * amqp_release_buffers() can be called successfully.
+ *
+ * \param [in] state the connection object
+ * \returns TRUE if the buffers can be released FALSE otherwise
+ *
+ * \sa amqp_release_buffers() amqp_maybe_release_buffers()
+ *  amqp_maybe_release_buffers_on_channel()
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_boolean_t AMQP_CALL amqp_release_buffers_ok(amqp_connection_state_t state);
+
+/**
+ * Release amqp_connection_state_t owned memory
+ *
+ * \deprecated This function is deprecated in favor of
+ *  amqp_maybe_release_buffers() or amqp_maybe_release_buffers_on_channel()
+ *
+ * \warning caller should ensure amqp_release_buffers_ok() returns true before
+ *  calling this function. Failure to do so may result in abort() being called.
+ *
+ * Release memory owned by the amqp_connection_state_t for reuse by the
+ * library. Use of any memory returned by the library before this function is
+ * called will result in undefined behavior.
+ *
+ * \note internally rabbitmq-c tries to reuse memory when possible. As a result
+ * its possible calling this function may not have a noticeable effect on
+ * memory usage.
+ *
+ * \param [in] state the connection object
+ *
+ * \sa amqp_release_buffers_ok() amqp_maybe_release_buffers()
+ *  amqp_maybe_release_buffers_on_channel()
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+void AMQP_CALL amqp_release_buffers(amqp_connection_state_t state);
+
+/**
+ * Release amqp_connection_state_t owned memory
+ *
+ * Release memory owned by the amqp_connection_state_t object related to any
+ * channel, allowing reuse by the library. Use of any memory returned by the
+ * library before this function is called with result in undefined behavior.
+ *
+ * \note internally rabbitmq-c tries to reuse memory when possible. As a result
+ * its possible calling this function may not have a noticeable effect on
+ * memory usage.
+ *
+ * \param [in] state the connection object
+ *
+ * \sa amqp_maybe_release_buffers_on_channel()
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+void AMQP_CALL amqp_maybe_release_buffers(amqp_connection_state_t state);
+
+/**
+ * Release amqp_connection_state_t owned memory related to a channel
+ *
+ * Release memory owned by the amqp_connection_state_t object related to the
+ * specified channel, allowing reuse by the library. Use of any memory returned
+ * the library for a specific channel will result in undefined behavior.
+ *
+ * \note internally rabbitmq-c tries to reuse memory when possible. As a result
+ * its possible calling this function may not have a noticeable effect on
+ * memory usage.
+ *
+ * \param [in] state the connection object
+ * \param [in] channel the channel specifier for which memory should be
+ *  released. Note that the library does not care about the state of the
+ *  channel when calling this function
+ *
+ * \sa amqp_maybe_release_buffers()
+ *
+ * \since v0.4.0
+ */
+AMQP_PUBLIC_FUNCTION
+void AMQP_CALL amqp_maybe_release_buffers_on_channel(
+    amqp_connection_state_t state, amqp_channel_t channel);
+
+/**
+ * Send a frame to the broker
+ *
+ * \param [in] state the connection object
+ * \param [in] frame the frame to send to the broker
+ * \return AMQP_STATUS_OK on success, an amqp_status_enum value on error.
+ *  Possible error codes:
+ *  - AMQP_STATUS_BAD_AMQP_DATA the serialized form of the method or
+ *    properties was too large to fit in a single AMQP frame, or the
+ *    method contains an invalid value. The frame was not sent.
+ *  - AMQP_STATUS_TABLE_TOO_BIG the serialized form of an amqp_table_t is
+ *    too large to fit in a single AMQP frame. Frame was not sent.
+ *  - AMQP_STATUS_UNKNOWN_METHOD an invalid method type was passed in
+ *  - AMQP_STATUS_UNKNOWN_CLASS an invalid properties type was passed in
+ *  - AMQP_STATUS_TIMER_FAILURE system timer indicated failure. The frame
+ *    was sent
+ *  - AMQP_STATUS_SOCKET_ERROR
+ *  - AMQP_STATUS_SSL_ERROR
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_send_frame(amqp_connection_state_t state,
+                              amqp_frame_t const *frame);
+
+/**
+ * Compare two table entries
+ *
+ * Works just like strcmp(), comparing two the table keys, datatype, then values
+ *
+ * \param [in] entry1 the entry on the left
+ * \param [in] entry2 the entry on the right
+ * \return 0 if entries are equal, 0 < if left is greater, 0 > if right is
+ * greater
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_table_entry_cmp(void const *entry1, void const *entry2);
+
+/**
+ * Open a socket to a remote host
+ *
+ * \deprecated This function is deprecated in favor of amqp_socket_open()
+ *
+ * Looks up the hostname, then attempts to open a socket to the host using
+ * the specified portnumber. It also sets various options on the socket to
+ * improve performance and correctness.
+ *
+ * \param [in] hostname this can be a hostname or IP address.
+ *              Both IPv4 and IPv6 are acceptable
+ * \param [in] portnumber the port to connect on. RabbitMQ brokers
+ *              listen on port 5672, and 5671 for SSL
+ * \return a positive value indicates success and is the sockfd. A negative
+ *  value (see amqp_status_enum)is returned on failure. Possible error codes:
+ *  - AMQP_STATUS_TCP_SOCKETLIB_INIT_ERROR Initialization of underlying socket
+ *    library failed.
+ *  - AMQP_STATUS_HOSTNAME_RESOLUTION_FAILED hostname lookup failed.
+ *  - AMQP_STATUS_SOCKET_ERROR a socket error occurred. errno or
+ *    WSAGetLastError() may return more useful information.
+ *
+ * \note IPv6 support was added in v0.3
+ *
+ * \sa amqp_socket_open() amqp_set_sockfd()
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_open_socket(char const *hostname, int portnumber);
+
+/**
+ * Send initial AMQP header to the broker
+ *
+ * \warning this is a low level function intended for those who want to
+ * interact with the broker at a very low level. Use of this function without
+ * understanding what it does will result in AMQP protocol errors.
+ *
+ * This function sends the AMQP protocol header to the broker.
+ *
+ * \param [in] state the connection object
+ * \return AMQP_STATUS_OK on success, a negative value on failure. Possible
+ *  error codes:
+ * - AMQP_STATUS_CONNECTION_CLOSED the connection to the broker was closed.
+ * - AMQP_STATUS_SOCKET_ERROR a socket error occurred. It is likely the
+ *   underlying socket has been closed. errno or WSAGetLastError() may provide
+ *   further information.
+ * - AMQP_STATUS_SSL_ERROR a SSL error occurred. The connection to the broker
+ *   was closed.
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_send_header(amqp_connection_state_t state);
+
+/**
+ * Checks to see if there are any incoming frames ready to be read
+ *
+ * Checks to see if there are any amqp_frame_t objects buffered by the
+ * amqp_connection_state_t object. Having one or more frames buffered means
+ * that amqp_simple_wait_frame() or amqp_simple_wait_frame_noblock() will
+ * return a frame without potentially blocking on a read() call.
+ *
+ * \param [in] state the connection object
+ * \return TRUE if there are frames enqueued, FALSE otherwise
+ *
+ * \sa amqp_simple_wait_frame() amqp_simple_wait_frame_noblock()
+ *  amqp_data_in_buffer()
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_boolean_t AMQP_CALL amqp_frames_enqueued(amqp_connection_state_t state);
+
+/**
+ * Read a single amqp_frame_t
+ *
+ * Waits for the next amqp_frame_t frame to be read from the broker.
+ * This function has the potential to block for a long time in the case of
+ * waiting for a basic.deliver method frame from the broker.
+ *
+ * The library may buffer frames. When an amqp_connection_state_t object
+ * has frames buffered calling amqp_simple_wait_frame() will return an
+ * amqp_frame_t without entering a blocking read(). You can test to see if
+ * an amqp_connection_state_t object has frames buffered by calling the
+ * amqp_frames_enqueued() function.
+ *
+ * The library has a socket read buffer. When there is data in an
+ * amqp_connection_state_t read buffer, amqp_simple_wait_frame() may return an
+ * amqp_frame_t without entering a blocking read(). You can test to see if an
+ * amqp_connection_state_t object has data in its read buffer by calling the
+ * amqp_data_in_buffer() function.
+ *
+ * \param [in] state the connection object
+ * \param [out] decoded_frame the frame
+ * \return AMQP_STATUS_OK on success, an amqp_status_enum value
+ *  is returned otherwise. Possible errors include:
+ *  - AMQP_STATUS_NO_MEMORY failure in allocating memory. The library is likely
+ *    in an indeterminate state making recovery unlikely. Client should note the
+ *    error and terminate the application
+ *  - AMQP_STATUS_BAD_AMQP_DATA bad AMQP data was received. The connection
+ *    should be shutdown immediately
+ *  - AMQP_STATUS_UNKNOWN_METHOD: an unknown method was received from the
+ *    broker. This is likely a protocol error and the connection should be
+ *    shutdown immediately
+ *  - AMQP_STATUS_UNKNOWN_CLASS: a properties frame with an unknown class
+ *    was received from the broker. This is likely a protocol error and the
+ *    connection should be shutdown immediately
+ *  - AMQP_STATUS_HEARTBEAT_TIMEOUT timed out while waiting for heartbeat
+ *    from the broker. The connection has been closed.
+ *  - AMQP_STATUS_TIMER_FAILURE system timer indicated failure.
+ *  - AMQP_STATUS_SOCKET_ERROR a socket error occurred. The connection has
+ *    been closed
+ *  - AMQP_STATUS_SSL_ERROR a SSL socket error occurred. The connection has
+ *    been closed.
+ *
+ * \sa amqp_simple_wait_frame_noblock() amqp_frames_enqueued()
+ *  amqp_data_in_buffer()
+ *
+ * \note as of v0.4.0 this function will no longer return heartbeat frames
+ *  when enabled by specifying a non-zero heartbeat value in amqp_login().
+ *  Heartbeating is handled internally by the library.
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_simple_wait_frame(amqp_connection_state_t state,
+                                     amqp_frame_t *decoded_frame);
+
+/**
+ * Read a single amqp_frame_t with a timeout.
+ *
+ * Waits for the next amqp_frame_t frame to be read from the broker, up to
+ * a timespan specified by tv. The function will return AMQP_STATUS_TIMEOUT
+ * if the timeout is reached. The tv value is not modified by the function.
+ *
+ * If a 0 timeval is specified, the function behaves as if its non-blocking: it
+ * will test to see if a frame can be read from the broker, and return
+ * immediately.
+ *
+ * If NULL is passed in for tv, the function will behave like
+ * amqp_simple_wait_frame() and block until a frame is received from the broker
+ *
+ * The library may buffer frames.  When an amqp_connection_state_t object
+ * has frames buffered calling amqp_simple_wait_frame_noblock() will return an
+ * amqp_frame_t without entering a blocking read(). You can test to see if an
+ * amqp_connection_state_t object has frames buffered by calling the
+ * amqp_frames_enqueued() function.
+ *
+ * The library has a socket read buffer. When there is data in an
+ * amqp_connection_state_t read buffer, amqp_simple_wait_frame_noblock() may
+ * return
+ * an amqp_frame_t without entering a blocking read(). You can test to see if an
+ * amqp_connection_state_t object has data in its read buffer by calling the
+ * amqp_data_in_buffer() function.
+ *
+ * \note This function does not return heartbeat frames. When enabled,
+ *  heartbeating is handed internally internally by the library.
+ *
+ * \param [in,out] state the connection object
+ * \param [out] decoded_frame the frame
+ * \param [in] tv the maximum time to wait for a frame to be read. Setting
+ * tv->tv_sec = 0 and tv->tv_usec = 0 will do a non-blocking read. Specifying
+ * NULL for tv will make the function block until a frame is read.
+ * \return AMQP_STATUS_OK on success. An amqp_status_enum value is returned
+ *  otherwise. Possible errors include:
+ *  - AMQP_STATUS_TIMEOUT the timeout was reached while waiting for a frame
+ *    from the broker.
+ *  - AMQP_STATUS_INVALID_PARAMETER the tv parameter contains an invalid value.
+ *  - AMQP_STATUS_NO_MEMORY failure in allocating memory. The library is likely
+ *    in an indeterminate state making recovery unlikely. Client should note the
+ *    error and terminate the application
+ *  - AMQP_STATUS_BAD_AMQP_DATA bad AMQP data was received. The connection
+ *    should be shutdown immediately
+ *  - AMQP_STATUS_UNKNOWN_METHOD: an unknown method was received from the
+ *    broker. This is likely a protocol error and the connection should be
+ *    shutdown immediately
+ *  - AMQP_STATUS_UNKNOWN_CLASS: a properties frame with an unknown class
+ *    was received from the broker. This is likely a protocol error and the
+ *    connection should be shutdown immediately
+ *  - AMQP_STATUS_HEARTBEAT_TIMEOUT timed out while waiting for heartbeat
+ *    from the broker. The connection has been closed.
+ *  - AMQP_STATUS_TIMER_FAILURE system timer indicated failure.
+ *  - AMQP_STATUS_SOCKET_ERROR a socket error occurred. The connection has
+ *    been closed
+ *  - AMQP_STATUS_SSL_ERROR a SSL socket error occurred. The connection has
+ *    been closed.
+ *
+ * \sa amqp_simple_wait_frame() amqp_frames_enqueued() amqp_data_in_buffer()
+ *
+ * \since v0.4.0
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_simple_wait_frame_noblock(amqp_connection_state_t state,
+                                             amqp_frame_t *decoded_frame,
+                                             struct timeval *tv);
+
+/**
+ * Waits for a specific method from the broker
+ *
+ * \warning You probably don't want to use this function. If this function
+ *  doesn't receive exactly the frame requested it closes the whole connection.
+ *
+ * Waits for a single method on a channel from the broker.
+ * If a frame is received that does not match expected_channel
+ * or expected_method the program will abort
+ *
+ * \param [in] state the connection object
+ * \param [in] expected_channel the channel that the method should be delivered
+ *  on
+ * \param [in] expected_method the method to wait for
+ * \param [out] output the method
+ * \returns AMQP_STATUS_OK on success. An amqp_status_enum value is returned
+ *  otherwise. Possible errors include:
+ *  - AMQP_STATUS_WRONG_METHOD a frame containing the wrong method, wrong frame
+ *    type or wrong channel was received. The connection is closed.
+ *  - AMQP_STATUS_NO_MEMORY failure in allocating memory. The library is likely
+ *    in an indeterminate state making recovery unlikely. Client should note the
+ *    error and terminate the application
+ *  - AMQP_STATUS_BAD_AMQP_DATA bad AMQP data was received. The connection
+ *    should be shutdown immediately
+ *  - AMQP_STATUS_UNKNOWN_METHOD: an unknown method was received from the
+ *    broker. This is likely a protocol error and the connection should be
+ *    shutdown immediately
+ *  - AMQP_STATUS_UNKNOWN_CLASS: a properties frame with an unknown class
+ *    was received from the broker. This is likely a protocol error and the
+ *    connection should be shutdown immediately
+ *  - AMQP_STATUS_HEARTBEAT_TIMEOUT timed out while waiting for heartbeat
+ *    from the broker. The connection has been closed.
+ *  - AMQP_STATUS_TIMER_FAILURE system timer indicated failure.
+ *  - AMQP_STATUS_SOCKET_ERROR a socket error occurred. The connection has
+ *    been closed
+ *  - AMQP_STATUS_SSL_ERROR a SSL socket error occurred. The connection has
+ *    been closed.
+ *
+ * \since v0.1
+ */
+
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_simple_wait_method(amqp_connection_state_t state,
+                                      amqp_channel_t expected_channel,
+                                      amqp_method_number_t expected_method,
+                                      amqp_method_t *output);
+
+/**
+ * Sends a method to the broker
+ *
+ * This is a thin wrapper around amqp_send_frame(), providing a way to send
+ * a method to the broker on a specified channel.
+ *
+ * \param [in] state the connection object
+ * \param [in] channel the channel object
+ * \param [in] id the method number
+ * \param [in] decoded the method object
+ * \returns AMQP_STATUS_OK on success, an amqp_status_enum value otherwise.
+ *  Possible errors include:
+ *  - AMQP_STATUS_BAD_AMQP_DATA the serialized form of the method or
+ *    properties was too large to fit in a single AMQP frame, or the
+ *    method contains an invalid value. The frame was not sent.
+ *  - AMQP_STATUS_TABLE_TOO_BIG the serialized form of an amqp_table_t is
+ *    too large to fit in a single AMQP frame. Frame was not sent.
+ *  - AMQP_STATUS_UNKNOWN_METHOD an invalid method type was passed in
+ *  - AMQP_STATUS_UNKNOWN_CLASS an invalid properties type was passed in
+ *  - AMQP_STATUS_TIMER_FAILURE system timer indicated failure. The frame
+ *    was sent
+ *  - AMQP_STATUS_SOCKET_ERROR
+ *  - AMQP_STATUS_SSL_ERROR
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_send_method(amqp_connection_state_t state,
+                               amqp_channel_t channel, amqp_method_number_t id,
+                               void *decoded);
+
+/**
+ * Sends a method to the broker and waits for a method response
+ *
+ * \param [in] state the connection object
+ * \param [in] channel the channel object
+ * \param [in] request_id the method number of the request
+ * \param [in] expected_reply_ids a 0 terminated array of expected response
+ *             method numbers
+ * \param [in] decoded_request_method the method to be sent to the broker
+ * \return a amqp_rpc_reply_t:
+ *  - r.reply_type == AMQP_RESPONSE_NORMAL. RPC completed successfully
+ *  - r.reply_type == AMQP_RESPONSE_SERVER_EXCEPTION. The broker returned an
+ *    exception:
+ *    - If r.reply.id == AMQP_CHANNEL_CLOSE_METHOD a channel exception
+ *      occurred, cast r.reply.decoded to amqp_channel_close_t* to see details
+ *      of the exception. The client should amqp_send_method() a
+ *      amqp_channel_close_ok_t. The channel must be re-opened before it
+ *      can be used again. Any resources associated with the channel
+ *      (auto-delete exchanges, auto-delete queues, consumers) are invalid
+ *      and must be recreated before attempting to use them again.
+ *    - If r.reply.id == AMQP_CONNECTION_CLOSE_METHOD a connection exception
+ *      occurred, cast r.reply.decoded to amqp_connection_close_t* to see
+ *      details of the exception. The client amqp_send_method() a
+ *      amqp_connection_close_ok_t and disconnect from the broker.
+ *  - r.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION. An exception occurred
+ *    within the library. Examine r.library_error and compare it against
+ *    amqp_status_enum values to determine the error.
+ *
+ * \sa amqp_simple_rpc_decoded()
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_rpc_reply_t AMQP_CALL amqp_simple_rpc(
+    amqp_connection_state_t state, amqp_channel_t channel,
+    amqp_method_number_t request_id, amqp_method_number_t *expected_reply_ids,
+    void *decoded_request_method);
+
+/**
+ * Sends a method to the broker and waits for a method response
+ *
+ * \param [in] state the connection object
+ * \param [in] channel the channel object
+ * \param [in] request_id the method number of the request
+ * \param [in] reply_id the method number expected in response
+ * \param [in] decoded_request_method the request method
+ * \return a pointer to the method returned from the broker, or NULL on error.
+ *  On error amqp_get_rpc_reply() will return an amqp_rpc_reply_t with
+ *  details on the error that occurred.
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+void *AMQP_CALL amqp_simple_rpc_decoded(amqp_connection_state_t state,
+                                        amqp_channel_t channel,
+                                        amqp_method_number_t request_id,
+                                        amqp_method_number_t reply_id,
+                                        void *decoded_request_method);
+
+/**
+ * Get the last global amqp_rpc_reply
+ *
+ * The API methods corresponding to most synchronous AMQP methods
+ * return a pointer to the decoded method result.  Upon error, they
+ * return NULL, and we need some way of discovering what, if anything,
+ * went wrong. amqp_get_rpc_reply() returns the most recent
+ * amqp_rpc_reply_t instance corresponding to such an API operation
+ * for the given connection.
+ *
+ * Only use it for operations that do not themselves return
+ * amqp_rpc_reply_t; operations that do return amqp_rpc_reply_t
+ * generally do NOT update this per-connection-global amqp_rpc_reply_t
+ * instance.
+ *
+ * \param [in] state the connection object
+ * \return the most recent amqp_rpc_reply_t:
+ *  - r.reply_type == AMQP_RESPONSE_NORMAL. RPC completed successfully
+ *  - r.reply_type == AMQP_RESPONSE_SERVER_EXCEPTION. The broker returned an
+ *    exception:
+ *    - If r.reply.id == AMQP_CHANNEL_CLOSE_METHOD a channel exception
+ *      occurred, cast r.reply.decoded to amqp_channel_close_t* to see details
+ *      of the exception. The client should amqp_send_method() a
+ *      amqp_channel_close_ok_t. The channel must be re-opened before it
+ *      can be used again. Any resources associated with the channel
+ *      (auto-delete exchanges, auto-delete queues, consumers) are invalid
+ *      and must be recreated before attempting to use them again.
+ *    - If r.reply.id == AMQP_CONNECTION_CLOSE_METHOD a connection exception
+ *      occurred, cast r.reply.decoded to amqp_connection_close_t* to see
+ *      details of the exception. The client amqp_send_method() a
+ *      amqp_connection_close_ok_t and disconnect from the broker.
+ *  - r.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION. An exception occurred
+ *    within the library. Examine r.library_error and compare it against
+ *    amqp_status_enum values to determine the error.
+ *
+ * \sa amqp_simple_rpc_decoded()
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_rpc_reply_t AMQP_CALL amqp_get_rpc_reply(amqp_connection_state_t state);
+
+/**
+ * Login to the broker
+ *
+ * After using amqp_open_socket and amqp_set_sockfd, call
+ * amqp_login to complete connecting to the broker
+ *
+ * \param [in] state the connection object
+ * \param [in] vhost the virtual host to connect to on the broker. The default
+ *              on most brokers is "/"
+ * \param [in] channel_max the limit for number of channels for the connection.
+ *              0 means no limit, and is a good default
+ *              (AMQP_DEFAULT_MAX_CHANNELS)
+ *              Note that the maximum number of channels the protocol supports
+ *              is 65535 (2^16, with the 0-channel reserved). The server can
+ *              set a lower channel_max and then the client will use the lowest
+ *              of the two
+ * \param [in] frame_max the maximum size of an AMQP frame on the wire to
+ *              request of the broker for this connection. 4096 is the minimum
+ *              size, 2^31-1 is the maximum, a good default is 131072 (128KB),
+ *              or AMQP_DEFAULT_FRAME_SIZE
+ * \param [in] heartbeat the number of seconds between heartbeat frames to
+ *              request of the broker. A value of 0 disables heartbeats.
+ *              Note rabbitmq-c only has partial support for heartbeats, as of
+ *              v0.4.0 they are only serviced during amqp_basic_publish() and
+ *              amqp_simple_wait_frame()/amqp_simple_wait_frame_noblock()
+ * \param [in] sasl_method the SASL method to authenticate with the broker.
+ *              followed by the authentication information. The following SASL
+ *              methods are implemented:
+ *              -  AMQP_SASL_METHOD_PLAIN, the AMQP_SASL_METHOD_PLAIN argument
+ *                 should be followed by two arguments in this order:
+ *                 const char* username, and const char* password.
+ *              -  AMQP_SASL_METHOD_EXTERNAL, the AMQP_SASL_METHOD_EXTERNAL
+ *                 argument should be followed one argument:
+ *                 const char* identity.
+ * \return amqp_rpc_reply_t indicating success or failure.
+ *  - r.reply_type == AMQP_RESPONSE_NORMAL. Login completed successfully
+ *  - r.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION. In most cases errors
+ *    from the broker when logging in will be represented by the broker closing
+ *    the socket. In this case r.library_error will be set to
+ *    AMQP_STATUS_CONNECTION_CLOSED. This error can represent a number of
+ *    error conditions including: invalid vhost, authentication failure.
+ *  - r.reply_type == AMQP_RESPONSE_SERVER_EXCEPTION. The broker returned an
+ *    exception:
+ *    - If r.reply.id == AMQP_CHANNEL_CLOSE_METHOD a channel exception
+ *      occurred, cast r.reply.decoded to amqp_channel_close_t* to see details
+ *      of the exception. The client should amqp_send_method() a
+ *      amqp_channel_close_ok_t. The channel must be re-opened before it
+ *      can be used again. Any resources associated with the channel
+ *      (auto-delete exchanges, auto-delete queues, consumers) are invalid
+ *      and must be recreated before attempting to use them again.
+ *    - If r.reply.id == AMQP_CONNECTION_CLOSE_METHOD a connection exception
+ *      occurred, cast r.reply.decoded to amqp_connection_close_t* to see
+ *      details of the exception. The client amqp_send_method() a
+ *      amqp_connection_close_ok_t and disconnect from the broker.
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_rpc_reply_t AMQP_CALL amqp_login(amqp_connection_state_t state,
+                                      char const *vhost, int channel_max,
+                                      int frame_max, int heartbeat,
+                                      amqp_sasl_method_enum sasl_method, ...);
+
+/**
+ * Login to the broker passing a properties table
+ *
+ * This function is similar to amqp_login() and differs in that it provides a
+ * way to pass client properties to the broker. This is commonly used to
+ * negotiate newer protocol features as they are supported by the broker.
+ *
+ * \param [in] state the connection object
+ * \param [in] vhost the virtual host to connect to on the broker. The default
+ *              on most brokers is "/"
+ * \param [in] channel_max the limit for the number of channels for the
+ *             connection.
+ *             0 means no limit, and is a good default
+ *             (AMQP_DEFAULT_MAX_CHANNELS)
+ *             Note that the maximum number of channels the protocol supports
+ *             is 65535 (2^16, with the 0-channel reserved). The server can
+ *             set a lower channel_max and then the client will use the lowest
+ *             of the two
+ * \param [in] frame_max the maximum size of an AMQP frame ont he wire to
+ *              request of the broker for this connection. 4096 is the minimum
+ *              size, 2^31-1 is the maximum, a good default is 131072 (128KB),
+ *              or AMQP_DEFAULT_FRAME_SIZE
+ * \param [in] heartbeat the number of seconds between heartbeat frame to
+ *             request of the broker. A value of 0 disables heartbeats.
+ *             Note rabbitmq-c only has partial support for hearts, as of
+ *             v0.4.0 heartbeats are only serviced during amqp_basic_publish(),
+ *             and amqp_simple_wait_frame()/amqp_simple_wait_frame_noblock()
+ * \param [in] properties a table of properties to send the broker.
+ * \param [in] sasl_method the SASL method to authenticate with the broker
+ *             followed by the authentication information. The following SASL
+ *             methods are implemented:
+ *             -  AMQP_SASL_METHOD_PLAIN, the AMQP_SASL_METHOD_PLAIN argument
+ *                should be followed by two arguments in this order:
+ *                const char* username, and const char* password.
+ *             -  AMQP_SASL_METHOD_EXTERNAL, the AMQP_SASL_METHOD_EXTERNAL
+ *                argument should be followed one argument:
+ *                const char* identity.
+ * \return amqp_rpc_reply_t indicating success or failure.
+ *  - r.reply_type == AMQP_RESPONSE_NORMAL. Login completed successfully
+ *  - r.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION. In most cases errors
+ *    from the broker when logging in will be represented by the broker closing
+ *    the socket. In this case r.library_error will be set to
+ *    AMQP_STATUS_CONNECTION_CLOSED. This error can represent a number of
+ *    error conditions including: invalid vhost, authentication failure.
+ *  - r.reply_type == AMQP_RESPONSE_SERVER_EXCEPTION. The broker returned an
+ *    exception:
+ *    - If r.reply.id == AMQP_CHANNEL_CLOSE_METHOD a channel exception
+ *      occurred, cast r.reply.decoded to amqp_channel_close_t* to see details
+ *      of the exception. The client should amqp_send_method() a
+ *      amqp_channel_close_ok_t. The channel must be re-opened before it
+ *      can be used again. Any resources associated with the channel
+ *      (auto-delete exchanges, auto-delete queues, consumers) are invalid
+ *      and must be recreated before attempting to use them again.
+ *    - If r.reply.id == AMQP_CONNECTION_CLOSE_METHOD a connection exception
+ *      occurred, cast r.reply.decoded to amqp_connection_close_t* to see
+ *      details of the exception. The client amqp_send_method() a
+ *      amqp_connection_close_ok_t and disconnect from the broker.
+ *
+ * \since v0.4.0
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_rpc_reply_t AMQP_CALL amqp_login_with_properties(
+    amqp_connection_state_t state, char const *vhost, int channel_max,
+    int frame_max, int heartbeat, const amqp_table_t *properties,
+    amqp_sasl_method_enum sasl_method, ...);
+
+struct amqp_basic_properties_t_;
+
+/**
+ * Publish a message to the broker
+ *
+ * Publish a message on an exchange with a routing key.
+ *
+ * Note that at the AMQ protocol level basic.publish is an async method:
+ * this means error conditions that occur on the broker (such as publishing to
+ * a non-existent exchange) will not be reflected in the return value of this
+ * function.
+ *
+ * \param [in] state the connection object
+ * \param [in] channel the channel identifier
+ * \param [in] exchange the exchange on the broker to publish to
+ * \param [in] routing_key the routing key to use when publishing the message
+ * \param [in] mandatory indicate to the broker that the message MUST be routed
+ *              to a queue. If the broker cannot do this it should respond with
+ *              a basic.return method.
+ * \param [in] immediate indicate to the broker that the message MUST be
+ *             delivered to a consumer immediately. If the broker cannot do this
+ *             it should respond with a basic.return method.
+ * \param [in] properties the properties associated with the message
+ * \param [in] body the message body
+ * \return AMQP_STATUS_OK on success, amqp_status_enum value on failure. Note
+ *         that basic.publish is an async method, the return value from this
+ *         function only indicates that the message data was successfully
+ *         transmitted to the broker. It does not indicate failures that occur
+ *         on the broker, such as publishing to a non-existent exchange.
+ *         Possible error values:
+ *         - AMQP_STATUS_TIMER_FAILURE: system timer facility returned an error
+ *           the message was not sent.
+ *         - AMQP_STATUS_HEARTBEAT_TIMEOUT: connection timed out waiting for a
+ *           heartbeat from the broker. The message was not sent.
+ *         - AMQP_STATUS_NO_MEMORY: memory allocation failed. The message was
+ *           not sent.
+ *         - AMQP_STATUS_TABLE_TOO_BIG: a table in the properties was too large
+ *           to fit in a single frame. Message was not sent.
+ *         - AMQP_STATUS_CONNECTION_CLOSED: the connection was closed.
+ *         - AMQP_STATUS_SSL_ERROR: a SSL error occurred.
+ *         - AMQP_STATUS_TCP_ERROR: a TCP error occurred. errno or
+ *           WSAGetLastError() may provide more information
+ *
+ * Note: this function does heartbeat processing as of v0.4.0
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_basic_publish(
+    amqp_connection_state_t state, amqp_channel_t channel,
+    amqp_bytes_t exchange, amqp_bytes_t routing_key, amqp_boolean_t mandatory,
+    amqp_boolean_t immediate, struct amqp_basic_properties_t_ const *properties,
+    amqp_bytes_t body);
+
+/**
+ * Closes an channel
+ *
+ * \param [in] state the connection object
+ * \param [in] channel the channel identifier
+ * \param [in] code the reason for closing the channel, AMQP_REPLY_SUCCESS is a
+ *             good default
+ * \return amqp_rpc_reply_t indicating success or failure
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_rpc_reply_t AMQP_CALL amqp_channel_close(amqp_connection_state_t state,
+                                              amqp_channel_t channel, int code);
+
+/**
+ * Closes the entire connection
+ *
+ * Implicitly closes all channels and informs the broker the connection
+ * is being closed, after receiving acknowledgment from the broker it closes
+ * the socket.
+ *
+ * \param [in] state the connection object
+ * \param [in] code the reason code for closing the connection.
+ *             AMQP_REPLY_SUCCESS is a good default.
+ * \return amqp_rpc_reply_t indicating the result
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_rpc_reply_t AMQP_CALL amqp_connection_close(amqp_connection_state_t state,
+                                                 int code);
+
+/**
+ * Acknowledges a message
+ *
+ * Does a basic.ack on a received message
+ *
+ * \param [in] state the connection object
+ * \param [in] channel the channel identifier
+ * \param [in] delivery_tag the delivery tag of the message to be ack'd
+ * \param [in] multiple if true, ack all messages up to this delivery tag, if
+ *              false ack only this delivery tag
+ * \return 0 on success,  0 > on failing to send the ack to the broker.
+ *            this will not indicate failure if something goes wrong on the
+ *            broker
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_basic_ack(amqp_connection_state_t state,
+                             amqp_channel_t channel, uint64_t delivery_tag,
+                             amqp_boolean_t multiple);
+
+/**
+ * Do a basic.get
+ *
+ * Synchonously polls the broker for a message in a queue, and
+ * retrieves the message if a message is in the queue.
+ *
+ * \param [in] state the connection object
+ * \param [in] channel the channel identifier to use
+ * \param [in] queue the queue name to retrieve from
+ * \param [in] no_ack if true the message is automatically ack'ed
+ *              if false amqp_basic_ack should be called once the message
+ *              retrieved has been processed
+ * \return amqp_rpc_reply indicating success or failure
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_rpc_reply_t AMQP_CALL amqp_basic_get(amqp_connection_state_t state,
+                                          amqp_channel_t channel,
+                                          amqp_bytes_t queue,
+                                          amqp_boolean_t no_ack);
+
+/**
+ * Do a basic.reject
+ *
+ * Actively reject a message that has been delivered
+ *
+ * \param [in] state the connection object
+ * \param [in] channel the channel identifier
+ * \param [in] delivery_tag the delivery tag of the message to reject
+ * \param [in] requeue indicate to the broker whether it should requeue the
+ *              message or just discard it.
+ * \return 0 on success, 0 > on failing to send the reject method to the broker.
+ *          This will not indicate failure if something goes wrong on the
+ * broker.
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_basic_reject(amqp_connection_state_t state,
+                                amqp_channel_t channel, uint64_t delivery_tag,
+                                amqp_boolean_t requeue);
+
+/**
+ * Do a basic.nack
+ *
+ * Actively reject a message, this has the same effect as amqp_basic_reject()
+ * however, amqp_basic_nack() can negatively acknowledge multiple messages with
+ * one call much like amqp_basic_ack() can acknowledge mutliple messages with
+ * one call.
+ *
+ * \param [in] state the connection object
+ * \param [in] channel the channel identifier
+ * \param [in] delivery_tag the delivery tag of the message to reject
+ * \param [in] multiple if set to 1 negatively acknowledge all unacknowledged
+ *              messages on this channel.
+ * \param [in] requeue indicate to the broker whether it should requeue the
+ *              message or dead-letter it.
+ * \return AMQP_STATUS_OK on success, an amqp_status_enum value otherwise.
+ *
+ * \since v0.5.0
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_basic_nack(amqp_connection_state_t state,
+                              amqp_channel_t channel, uint64_t delivery_tag,
+                              amqp_boolean_t multiple, amqp_boolean_t requeue);
+/**
+ * Check to see if there is data left in the receive buffer
+ *
+ * Can be used to see if there is data still in the buffer, if so
+ * calling amqp_simple_wait_frame will not immediately enter a
+ * blocking read.
+ *
+ * \param [in] state the connection object
+ * \return true if there is data in the recieve buffer, false otherwise
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_boolean_t AMQP_CALL amqp_data_in_buffer(amqp_connection_state_t state);
+
+/**
+ * Get the error string for the given error code.
+ *
+ * \deprecated This function has been deprecated in favor of
+ *  \ref amqp_error_string2() which returns statically allocated
+ *  string which do not need to be freed by the caller.
+ *
+ * The returned string resides on the heap; the caller is responsible
+ * for freeing it.
+ *
+ * \param [in] err return error code
+ * \return the error string
+ *
+ * \since v0.1
+ */
+AMQP_DEPRECATED(
+    AMQP_PUBLIC_FUNCTION char *AMQP_CALL amqp_error_string(int err));
+
+/**
+ * Get the error string for the given error code.
+ *
+ * Get an error string associated with an error code. The string is statically
+ * allocated and does not need to be freed
+ *
+ * \param [in] err the error code
+ * \return the error string
+ *
+ * \since v0.4.0
+ */
+AMQP_PUBLIC_FUNCTION
+const char *AMQP_CALL amqp_error_string2(int err);
+
+/**
+ * Deserialize an amqp_table_t from AMQP wireformat
+ *
+ * This is an internal function and is not typically used by
+ * client applications
+ *
+ * \param [in] encoded the buffer containing the serialized data
+ * \param [in] pool memory pool used to allocate the table entries from
+ * \param [in] output the amqp_table_t structure to fill in. Any existing
+ *             entries will be erased
+ * \param [in,out] offset The offset into the encoded buffer to start
+ *                 reading the serialized table. It will be updated
+ *                 by this function to end of the table
+ * \return AMQP_STATUS_OK on success, an amqp_status_enum value on failure
+ *  Possible error codes:
+ *  - AMQP_STATUS_NO_MEMORY out of memory
+ *  - AMQP_STATUS_BAD_AMQP_DATA invalid wireformat
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_decode_table(amqp_bytes_t encoded, amqp_pool_t *pool,
+                                amqp_table_t *output, size_t *offset);
+
+/**
+ * Serializes an amqp_table_t to the AMQP wireformat
+ *
+ * This is an internal function and is not typically used by
+ * client applications
+ *
+ * \param [in] encoded the buffer where to serialize the table to
+ * \param [in] input the amqp_table_t to serialize
+ * \param [in,out] offset The offset into the encoded buffer to start
+ *                 writing the serialized table. It will be updated
+ *                 by this function to where writing left off
+ * \return AMQP_STATUS_OK on success, an amqp_status_enum value on failure
+ *  Possible error codes:
+ *  - AMQP_STATUS_TABLE_TOO_BIG the serialized form is too large for the
+ *    buffer
+ *  - AMQP_STATUS_BAD_AMQP_DATA invalid table
+ *
+ * \since v0.1
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_encode_table(amqp_bytes_t encoded, amqp_table_t *input,
+                                size_t *offset);
+
+/**
+ * Create a deep-copy of an amqp_table_t object
+ *
+ * Creates a deep-copy of an amqp_table_t object, using the provided pool
+ * object to allocate the necessary memory. This memory can be freed later by
+ * call recycle_amqp_pool(), or empty_amqp_pool()
+ *
+ * \param [in] original the table to copy
+ * \param [in,out] clone the table to copy to
+ * \param [in] pool the initialized memory pool to do allocations for the table
+ *             from
+ * \return AMQP_STATUS_OK on success, amqp_status_enum value on failure.
+ *  Possible error values:
+ *  - AMQP_STATUS_NO_MEMORY - memory allocation failure.
+ *  - AMQP_STATUS_INVALID_PARAMETER - invalid table (e.g., no key name)
+ *
+ * \since v0.4.0
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_table_clone(const amqp_table_t *original,
+                               amqp_table_t *clone, amqp_pool_t *pool);
+
+/**
+ * A message object
+ *
+ * \since v0.4.0
+ */
+typedef struct amqp_message_t_ {
+  amqp_basic_properties_t properties; /**< message properties */
+  amqp_bytes_t body;                  /**< message body */
+  amqp_pool_t pool;                   /**< pool used to allocate properties */
+} amqp_message_t;
+
+/**
+ * Reads the next message on a channel
+ *
+ * Reads a complete message (header + body) on a specified channel. This
+ * function is intended to be used with amqp_basic_get() or when an
+ * AMQP_BASIC_DELIVERY_METHOD method is received.
+ *
+ * \param [in,out] state the connection object
+ * \param [in] channel the channel on which to read the message from
+ * \param [in,out] message a pointer to a amqp_message_t object. Caller should
+ *                 call amqp_message_destroy() when it is done using the
+ *                 fields in the message object.  The caller is responsible for
+ *                 allocating/destroying the amqp_message_t object itself.
+ * \param [in] flags pass in 0. Currently unused.
+ * \returns a amqp_rpc_reply_t object. ret.reply_type == AMQP_RESPONSE_NORMAL on
+ * success.
+ *
+ * \since v0.4.0
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_rpc_reply_t AMQP_CALL amqp_read_message(amqp_connection_state_t state,
+                                             amqp_channel_t channel,
+                                             amqp_message_t *message,
+                                             int flags);
+
+/**
+ * Frees memory associated with a amqp_message_t allocated in amqp_read_message
+ *
+ * \param [in] message
+ *
+ * \since v0.4.0
+ */
+AMQP_PUBLIC_FUNCTION
+void AMQP_CALL amqp_destroy_message(amqp_message_t *message);
+
+/**
+ * Envelope object
+ *
+ * \since v0.4.0
+ */
+typedef struct amqp_envelope_t_ {
+  amqp_channel_t channel; /**< channel message was delivered on */
+  amqp_bytes_t
+      consumer_tag;      /**< the consumer tag the message was delivered to */
+  uint64_t delivery_tag; /**< the messages delivery tag */
+  amqp_boolean_t redelivered; /**< flag indicating whether this message is being
+                                 redelivered */
+  amqp_bytes_t exchange;      /**< exchange this message was published to */
+  amqp_bytes_t
+      routing_key; /**< the routing key this message was published with */
+  amqp_message_t message; /**< the message */
+} amqp_envelope_t;
+
+/**
+ * Wait for and consume a message
+ *
+ * Waits for a basic.deliver method on any channel, upon receipt of
+ * basic.deliver it reads that message, and returns. If any other method is
+ * received before basic.deliver, this function will return an amqp_rpc_reply_t
+ * with ret.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION, and
+ * ret.library_error == AMQP_STATUS_UNEXPECTED_STATE. The caller should then
+ * call amqp_simple_wait_frame() to read this frame and take appropriate action.
+ *
+ * This function should be used after starting a consumer with the
+ * amqp_basic_consume() function
+ *
+ * \param [in,out] state the connection object
+ * \param [in,out] envelope a pointer to a amqp_envelope_t object. Caller
+ *                 should call #amqp_destroy_envelope() when it is done using
+ *                 the fields in the envelope object. The caller is responsible
+ *                 for allocating/destroying the amqp_envelope_t object itself.
+ * \param [in] timeout a timeout to wait for a message delivery. Passing in
+ *             NULL will result in blocking behavior.
+ * \param [in] flags pass in 0. Currently unused.
+ * \returns a amqp_rpc_reply_t object.  ret.reply_type == AMQP_RESPONSE_NORMAL
+ *          on success. If ret.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION,
+ *          and ret.library_error == AMQP_STATUS_UNEXPECTED_STATE, a frame other
+ *          than AMQP_BASIC_DELIVER_METHOD was received, the caller should call
+ *          amqp_simple_wait_frame() to read this frame and take appropriate
+ *          action.
+ *
+ * \since v0.4.0
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_rpc_reply_t AMQP_CALL amqp_consume_message(amqp_connection_state_t state,
+                                                amqp_envelope_t *envelope,
+                                                struct timeval *timeout,
+                                                int flags);
+
+/**
+ * Frees memory associated with a amqp_envelope_t allocated in
+ * amqp_consume_message()
+ *
+ * \param [in] envelope
+ *
+ * \since v0.4.0
+ */
+AMQP_PUBLIC_FUNCTION
+void AMQP_CALL amqp_destroy_envelope(amqp_envelope_t *envelope);
+
+/**
+ * Parameters used to connect to the RabbitMQ broker
+ *
+ * \since v0.2
+ */
+struct amqp_connection_info {
+  char *user; /**< the username to authenticate with the broker, default on most
+                 broker is 'guest' */
+  char *password; /**< the password to authenticate with the broker, default on
+                     most brokers is 'guest' */
+  char *host;     /**< the hostname of the broker */
+  char *vhost; /**< the virtual host on the broker to connect to, a good default
+                  is "/" */
+  int port;    /**< the port that the broker is listening on, default on most
+                  brokers is 5672 */
+  amqp_boolean_t ssl;
+};
+
+/**
+ * Initialze an amqp_connection_info to default values
+ *
+ * The default values are:
+ * - user: "guest"
+ * - password: "guest"
+ * - host: "localhost"
+ * - vhost: "/"
+ * - port: 5672
+ *
+ * \param [out] parsed the connection info to set defaults on
+ *
+ * \since v0.2
+ */
+AMQP_PUBLIC_FUNCTION
+void AMQP_CALL
+    amqp_default_connection_info(struct amqp_connection_info *parsed);
+
+/**
+ * Parse a connection URL
+ *
+ * An amqp connection url takes the form:
+ *
+ * amqp://[$USERNAME[:$PASSWORD]\@]$HOST[:$PORT]/[$VHOST]
+ *
+ * Examples:
+ *  amqp://guest:guest\@localhost:5672//
+ *  amqp://guest:guest\@localhost/myvhost
+ *
+ *  Any missing parts of the URL will be set to the defaults specified in
+ *  amqp_default_connection_info. For amqps: URLs the default port will be set
+ *  to 5671 instead of 5672 for non-SSL URLs.
+ *
+ * \note This function modifies url parameter.
+ *
+ * \param [in] url URI to parse, note that this parameter is modified by the
+ *             function.
+ * \param [out] parsed the connection info gleaned from the URI. The char*
+ *              members will point to parts of the url input parameter.
+ *              Memory management will depend on how the url is allocated.
+ * \returns AMQP_STATUS_OK on success, AMQP_STATUS_BAD_URL on failure
+ *
+ * \since v0.2
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_parse_url(char *url, struct amqp_connection_info *parsed);
+
+/* socket API */
+
+/**
+ * Open a socket connection.
+ *
+ * This function opens a socket connection returned from amqp_tcp_socket_new()
+ * or amqp_ssl_socket_new(). This function should be called after setting
+ * socket options and prior to assigning the socket to an AMQP connection with
+ * amqp_set_socket().
+ *
+ * \param [in,out] self A socket object.
+ * \param [in] host Connect to this host.
+ * \param [in] port Connect on this remote port.
+ *
+ * \return AMQP_STATUS_OK on success, an amqp_status_enum on failure
+ *
+ * \since v0.4.0
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_socket_open(amqp_socket_t *self, const char *host, int port);
+
+/**
+ * Open a socket connection.
+ *
+ * This function opens a socket connection returned from amqp_tcp_socket_new()
+ * or amqp_ssl_socket_new(). This function should be called after setting
+ * socket options and prior to assigning the socket to an AMQP connection with
+ * amqp_set_socket().
+ *
+ * \param [in,out] self A socket object.
+ * \param [in] host Connect to this host.
+ * \param [in] port Connect on this remote port.
+ * \param [in] timeout Max allowed time to spent on opening. If NULL - run in
+ *             blocking mode
+ *
+ * \return AMQP_STATUS_OK on success, an amqp_status_enum on failure.
+ *
+ * \since v0.4.0
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_socket_open_noblock(amqp_socket_t *self, const char *host,
+                                       int port, struct timeval *timeout);
+
+/**
+ * Get the socket descriptor in use by a socket object.
+ *
+ * Retrieve the underlying socket descriptor. This function can be used to
+ * perform low-level socket operations that aren't supported by the socket
+ * interface. Use with caution!
+ *
+ * \param [in,out] self A socket object.
+ *
+ * \return The underlying socket descriptor, or -1 if there is no socket
+ *  descriptor associated with
+ *
+ * \since v0.4.0
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_socket_get_sockfd(amqp_socket_t *self);
+
+/**
+ * Get the socket object associated with a amqp_connection_state_t
+ *
+ * \param [in] state the connection object to get the socket from
+ * \return a pointer to the socket object, or NULL if one has not been assigned
+ *
+ * \since v0.4.0
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_socket_t *AMQP_CALL amqp_get_socket(amqp_connection_state_t state);
+
+/**
+ * Get the broker properties table
+ *
+ * \param [in] state the connection object
+ * \return a pointer to an amqp_table_t containing the properties advertised
+ *  by the broker on connection. The connection object owns the table, it
+ *  should not be modified.
+ *
+ * \since v0.5.0
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_table_t *AMQP_CALL
+    amqp_get_server_properties(amqp_connection_state_t state);
+
+/**
+ * Get the client properties table
+ *
+ * Get the properties that were passed to the broker on connection.
+ *
+ * \param [in] state the connection object
+ * \return a pointer to an amqp_table_t containing the properties advertised
+ *  by the client on connection. The connection object owns the table, it
+ *  should not be modified.
+ *
+ * \since v0.7.0
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_table_t *AMQP_CALL
+    amqp_get_client_properties(amqp_connection_state_t state);
+
+/**
+ * Get the login handshake timeout.
+ *
+ * amqp_login and amqp_login_with_properties perform the login handshake with
+ * the broker.  This function returns the timeout associated with completing
+ * this operation from the client side. This value can be set by using the
+ * amqp_set_handshake_timeout.
+ *
+ * Note that the RabbitMQ broker has configurable timeout for completing the
+ * login handshake, the default is 10 seconds.  rabbitmq-c has a default of 12
+ * seconds.
+ *
+ * \param [in] state the connection object
+ * \return a struct timeval representing the current login timeout for the state
+ *  object. A NULL value represents an infinite timeout. The memory returned is
+ *  owned by the connection object.
+ *
+ * \since v0.9.0
+ */
+AMQP_PUBLIC_FUNCTION
+struct timeval *AMQP_CALL
+    amqp_get_handshake_timeout(amqp_connection_state_t state);
+
+/**
+ * Set the login handshake timeout.
+ *
+ * amqp_login and amqp_login_with_properties perform the login handshake with
+ * the broker. This function sets the timeout associated with completing this
+ * operation from the client side.
+ *
+ * The timeout must be set before amqp_login or amqp_login_with_properties is
+ * called to change from the default timeout.
+ *
+ * Note that the RabbitMQ broker has a configurable timeout for completing the
+ * login handshake, the default is 10 seconds. rabbitmq-c has a default of 12
+ * seconds.
+ *
+ * \param [in] state the connection object
+ * \param [in] timeout a struct timeval* representing new login timeout for the
+ *  state object. NULL represents an infinite timeout. The value of timeout is
+ *  copied internally, the caller is responsible for ownership of the passed in
+ *  pointer, it does not need to remain valid after this function is called.
+ * \return AMQP_STATUS_OK on success.
+ *
+ * \since v0.9.0
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_set_handshake_timeout(amqp_connection_state_t state,
+                                         struct timeval *timeout);
+
+/**
+ * Get the RPC timeout
+ *
+ * Gets the timeout for any RPC-style AMQP command (e.g., amqp_queue_declare).
+ * This timeout may be changed at any time by calling \amqp_set_rpc_timeout
+ * function with a new timeout. The timeout applies individually to each RPC
+ * that is made.
+ *
+ * The default value is NULL, or an infinite timeout.
+ *
+ * When an RPC times out, the function will return an error AMQP_STATUS_TIMEOUT,
+ * and the connection will be closed.
+ *
+ *\warning RPC-timeouts are an advanced feature intended to be used to detect
+ * dead connections quickly when the rabbitmq-c implementation of heartbeats
+ * does not work. Do not use RPC timeouts unless you understand the implications
+ * of doing so.
+ *
+ * \param [in] state the connection object
+ * \return a struct timeval representing the current RPC timeout for the state
+ * object. A NULL value represents an infinite timeout. The memory returned is
+ * owned by the connection object.
+ *
+ * \since v0.9.0
+ */
+AMQP_PUBLIC_FUNCTION
+struct timeval *AMQP_CALL amqp_get_rpc_timeout(amqp_connection_state_t state);
+
+/**
+ * Set the RPC timeout
+ *
+ * Sets the timeout for any RPC-style AMQP command (e.g., amqp_queue_declare).
+ * This timeout may be changed at any time by calling this function with a new
+ * timeout. The timeout applies individually to each RPC that is made.
+ *
+ * The default value is NULL, or an infinite timeout.
+ *
+ * When an RPC times out, the function will return an error AMQP_STATUS_TIMEOUT,
+ * and the connection will be closed.
+ *
+ *\warning RPC-timeouts are an advanced feature intended to be used to detect
+ * dead connections quickly when the rabbitmq-c implementation of heartbeats
+ * does not work. Do not use RPC timeouts unless you understand the implications
+ * of doing so.
+ *
+ * \param [in] state the connection object
+ * \param [in] timeout a struct timeval* representing new RPC timeout for the
+ * state object. NULL represents an infinite timeout. The value of timeout is
+ * copied internally, the caller is responsible for ownership of the passed
+ * pointer, it does not need to remain valid after this function is called.
+ * \return AMQP_STATUS_SUCCESS on success.
+ *
+ * \since v0.9.0
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_set_rpc_timeout(amqp_connection_state_t state,
+                                   struct timeval *timeout);
+
+AMQP_END_DECLS
+
+#endif /* AMQP_H */

+ 1144 - 0
ext/librabbitmq/centos_x64/include/amqp_framing.h

@@ -0,0 +1,1144 @@
+/* Generated code. Do not edit. Edit and re-run codegen.py instead.
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MIT
+ *
+ * Portions created by Alan Antonuk are Copyright (c) 2012-2013
+ * Alan Antonuk. All Rights Reserved.
+ *
+ * Portions created by VMware are Copyright (c) 2007-2012 VMware, Inc.
+ * All Rights Reserved.
+ *
+ * Portions created by Tony Garnock-Jones are Copyright (c) 2009-2010
+ * VMware, Inc. and Tony Garnock-Jones. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ * ***** END LICENSE BLOCK *****
+ */
+
+/** @file amqp_framing.h */
+#ifndef AMQP_FRAMING_H
+#define AMQP_FRAMING_H
+
+#include <amqp.h>
+
+AMQP_BEGIN_DECLS
+
+#define AMQP_PROTOCOL_VERSION_MAJOR 0 /**< AMQP protocol version major */
+#define AMQP_PROTOCOL_VERSION_MINOR 9 /**< AMQP protocol version minor */
+#define AMQP_PROTOCOL_VERSION_REVISION                                   \
+  1                                  /**< AMQP protocol version revision \
+                                        */
+#define AMQP_PROTOCOL_PORT 5672      /**< Default AMQP Port */
+#define AMQP_FRAME_METHOD 1          /**< Constant: FRAME-METHOD */
+#define AMQP_FRAME_HEADER 2          /**< Constant: FRAME-HEADER */
+#define AMQP_FRAME_BODY 3            /**< Constant: FRAME-BODY */
+#define AMQP_FRAME_HEARTBEAT 8       /**< Constant: FRAME-HEARTBEAT */
+#define AMQP_FRAME_MIN_SIZE 4096     /**< Constant: FRAME-MIN-SIZE */
+#define AMQP_FRAME_END 206           /**< Constant: FRAME-END */
+#define AMQP_REPLY_SUCCESS 200       /**< Constant: REPLY-SUCCESS */
+#define AMQP_CONTENT_TOO_LARGE 311   /**< Constant: CONTENT-TOO-LARGE */
+#define AMQP_NO_ROUTE 312            /**< Constant: NO-ROUTE */
+#define AMQP_NO_CONSUMERS 313        /**< Constant: NO-CONSUMERS */
+#define AMQP_ACCESS_REFUSED 403      /**< Constant: ACCESS-REFUSED */
+#define AMQP_NOT_FOUND 404           /**< Constant: NOT-FOUND */
+#define AMQP_RESOURCE_LOCKED 405     /**< Constant: RESOURCE-LOCKED */
+#define AMQP_PRECONDITION_FAILED 406 /**< Constant: PRECONDITION-FAILED */
+#define AMQP_CONNECTION_FORCED 320   /**< Constant: CONNECTION-FORCED */
+#define AMQP_INVALID_PATH 402        /**< Constant: INVALID-PATH */
+#define AMQP_FRAME_ERROR 501         /**< Constant: FRAME-ERROR */
+#define AMQP_SYNTAX_ERROR 502        /**< Constant: SYNTAX-ERROR */
+#define AMQP_COMMAND_INVALID 503     /**< Constant: COMMAND-INVALID */
+#define AMQP_CHANNEL_ERROR 504       /**< Constant: CHANNEL-ERROR */
+#define AMQP_UNEXPECTED_FRAME 505    /**< Constant: UNEXPECTED-FRAME */
+#define AMQP_RESOURCE_ERROR 506      /**< Constant: RESOURCE-ERROR */
+#define AMQP_NOT_ALLOWED 530         /**< Constant: NOT-ALLOWED */
+#define AMQP_NOT_IMPLEMENTED 540     /**< Constant: NOT-IMPLEMENTED */
+#define AMQP_INTERNAL_ERROR 541      /**< Constant: INTERNAL-ERROR */
+
+/* Function prototypes. */
+
+/**
+ * Get constant name string from constant
+ *
+ * @param [in] constantNumber constant to get the name of
+ * @returns string describing the constant. String is managed by
+ *           the library and should not be free()'d by the program
+ */
+AMQP_PUBLIC_FUNCTION
+char const *AMQP_CALL amqp_constant_name(int constantNumber);
+
+/**
+ * Checks to see if a constant is a hard error
+ *
+ * A hard error occurs when something severe enough
+ * happens that the connection must be closed.
+ *
+ * @param [in] constantNumber the error constant
+ * @returns true if its a hard error, false otherwise
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_boolean_t AMQP_CALL amqp_constant_is_hard_error(int constantNumber);
+
+/**
+ * Get method name string from method number
+ *
+ * @param [in] methodNumber the method number
+ * @returns method name string. String is managed by the library
+ *           and should not be freed()'d by the program
+ */
+AMQP_PUBLIC_FUNCTION
+char const *AMQP_CALL amqp_method_name(amqp_method_number_t methodNumber);
+
+/**
+ * Check whether a method has content
+ *
+ * A method that has content will receive the method frame
+ * a properties frame, then 1 to N body frames
+ *
+ * @param [in] methodNumber the method number
+ * @returns true if method has content, false otherwise
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_boolean_t AMQP_CALL
+    amqp_method_has_content(amqp_method_number_t methodNumber);
+
+/**
+ * Decodes a method from AMQP wireformat
+ *
+ * @param [in] methodNumber the method number for the decoded parameter
+ * @param [in] pool the memory pool to allocate the decoded method from
+ * @param [in] encoded the encoded byte string buffer
+ * @param [out] decoded pointer to the decoded method struct
+ * @returns 0 on success, an error code otherwise
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_decode_method(amqp_method_number_t methodNumber,
+                                 amqp_pool_t *pool, amqp_bytes_t encoded,
+                                 void **decoded);
+
+/**
+ * Decodes a header frame properties structure from AMQP wireformat
+ *
+ * @param [in] class_id the class id for the decoded parameter
+ * @param [in] pool the memory pool to allocate the decoded properties from
+ * @param [in] encoded the encoded byte string buffer
+ * @param [out] decoded pointer to the decoded properties struct
+ * @returns 0 on success, an error code otherwise
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_decode_properties(uint16_t class_id, amqp_pool_t *pool,
+                                     amqp_bytes_t encoded, void **decoded);
+
+/**
+ * Encodes a method structure in AMQP wireformat
+ *
+ * @param [in] methodNumber the method number for the decoded parameter
+ * @param [in] decoded the method structure (e.g., amqp_connection_start_t)
+ * @param [in] encoded an allocated byte buffer for the encoded method
+ *              structure to be written to. If the buffer isn't large enough
+ *              to hold the encoded method, an error code will be returned.
+ * @returns 0 on success, an error code otherwise.
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_encode_method(amqp_method_number_t methodNumber,
+                                 void *decoded, amqp_bytes_t encoded);
+
+/**
+ * Encodes a properties structure in AMQP wireformat
+ *
+ * @param [in] class_id the class id for the decoded parameter
+ * @param [in] decoded the properties structure (e.g., amqp_basic_properties_t)
+ * @param [in] encoded an allocated byte buffer for the encoded properties to
+ * written to.
+ *              If the buffer isn't large enough to hold the encoded method, an
+ *              an error code will be returned
+ * @returns 0 on success, an error code otherwise.
+ */
+AMQP_PUBLIC_FUNCTION
+int AMQP_CALL amqp_encode_properties(uint16_t class_id, void *decoded,
+                                     amqp_bytes_t encoded);
+
+/* Method field records. */
+
+#define AMQP_CONNECTION_START_METHOD                                 \
+  ((amqp_method_number_t)0x000A000A) /**< connection.start method id \
+                                        @internal 10, 10; 655370 */
+/** connection.start method fields */
+typedef struct amqp_connection_start_t_ {
+  uint8_t version_major;          /**< version-major */
+  uint8_t version_minor;          /**< version-minor */
+  amqp_table_t server_properties; /**< server-properties */
+  amqp_bytes_t mechanisms;        /**< mechanisms */
+  amqp_bytes_t locales;           /**< locales */
+} amqp_connection_start_t;
+
+#define AMQP_CONNECTION_START_OK_METHOD                                 \
+  ((amqp_method_number_t)0x000A000B) /**< connection.start-ok method id \
+                                        @internal 10, 11; 655371 */
+/** connection.start-ok method fields */
+typedef struct amqp_connection_start_ok_t_ {
+  amqp_table_t client_properties; /**< client-properties */
+  amqp_bytes_t mechanism;         /**< mechanism */
+  amqp_bytes_t response;          /**< response */
+  amqp_bytes_t locale;            /**< locale */
+} amqp_connection_start_ok_t;
+
+#define AMQP_CONNECTION_SECURE_METHOD                                 \
+  ((amqp_method_number_t)0x000A0014) /**< connection.secure method id \
+                                        @internal 10, 20; 655380 */
+/** connection.secure method fields */
+typedef struct amqp_connection_secure_t_ {
+  amqp_bytes_t challenge; /**< challenge */
+} amqp_connection_secure_t;
+
+#define AMQP_CONNECTION_SECURE_OK_METHOD                                 \
+  ((amqp_method_number_t)0x000A0015) /**< connection.secure-ok method id \
+                                        @internal 10, 21; 655381 */
+/** connection.secure-ok method fields */
+typedef struct amqp_connection_secure_ok_t_ {
+  amqp_bytes_t response; /**< response */
+} amqp_connection_secure_ok_t;
+
+#define AMQP_CONNECTION_TUNE_METHOD                                 \
+  ((amqp_method_number_t)0x000A001E) /**< connection.tune method id \
+                                        @internal 10, 30; 655390 */
+/** connection.tune method fields */
+typedef struct amqp_connection_tune_t_ {
+  uint16_t channel_max; /**< channel-max */
+  uint32_t frame_max;   /**< frame-max */
+  uint16_t heartbeat;   /**< heartbeat */
+} amqp_connection_tune_t;
+
+#define AMQP_CONNECTION_TUNE_OK_METHOD                                 \
+  ((amqp_method_number_t)0x000A001F) /**< connection.tune-ok method id \
+                                        @internal 10, 31; 655391 */
+/** connection.tune-ok method fields */
+typedef struct amqp_connection_tune_ok_t_ {
+  uint16_t channel_max; /**< channel-max */
+  uint32_t frame_max;   /**< frame-max */
+  uint16_t heartbeat;   /**< heartbeat */
+} amqp_connection_tune_ok_t;
+
+#define AMQP_CONNECTION_OPEN_METHOD                                 \
+  ((amqp_method_number_t)0x000A0028) /**< connection.open method id \
+                                        @internal 10, 40; 655400 */
+/** connection.open method fields */
+typedef struct amqp_connection_open_t_ {
+  amqp_bytes_t virtual_host; /**< virtual-host */
+  amqp_bytes_t capabilities; /**< capabilities */
+  amqp_boolean_t insist;     /**< insist */
+} amqp_connection_open_t;
+
+#define AMQP_CONNECTION_OPEN_OK_METHOD                                 \
+  ((amqp_method_number_t)0x000A0029) /**< connection.open-ok method id \
+                                        @internal 10, 41; 655401 */
+/** connection.open-ok method fields */
+typedef struct amqp_connection_open_ok_t_ {
+  amqp_bytes_t known_hosts; /**< known-hosts */
+} amqp_connection_open_ok_t;
+
+#define AMQP_CONNECTION_CLOSE_METHOD                                 \
+  ((amqp_method_number_t)0x000A0032) /**< connection.close method id \
+                                        @internal 10, 50; 655410 */
+/** connection.close method fields */
+typedef struct amqp_connection_close_t_ {
+  uint16_t reply_code;     /**< reply-code */
+  amqp_bytes_t reply_text; /**< reply-text */
+  uint16_t class_id;       /**< class-id */
+  uint16_t method_id;      /**< method-id */
+} amqp_connection_close_t;
+
+#define AMQP_CONNECTION_CLOSE_OK_METHOD                                 \
+  ((amqp_method_number_t)0x000A0033) /**< connection.close-ok method id \
+                                        @internal 10, 51; 655411 */
+/** connection.close-ok method fields */
+typedef struct amqp_connection_close_ok_t_ {
+  char dummy; /**< Dummy field to avoid empty struct */
+} amqp_connection_close_ok_t;
+
+#define AMQP_CONNECTION_BLOCKED_METHOD                                 \
+  ((amqp_method_number_t)0x000A003C) /**< connection.blocked method id \
+                                        @internal 10, 60; 655420 */
+/** connection.blocked method fields */
+typedef struct amqp_connection_blocked_t_ {
+  amqp_bytes_t reason; /**< reason */
+} amqp_connection_blocked_t;
+
+#define AMQP_CONNECTION_UNBLOCKED_METHOD                                 \
+  ((amqp_method_number_t)0x000A003D) /**< connection.unblocked method id \
+                                        @internal 10, 61; 655421 */
+/** connection.unblocked method fields */
+typedef struct amqp_connection_unblocked_t_ {
+  char dummy; /**< Dummy field to avoid empty struct */
+} amqp_connection_unblocked_t;
+
+#define AMQP_CHANNEL_OPEN_METHOD                                           \
+  ((amqp_method_number_t)0x0014000A) /**< channel.open method id @internal \
+                                        20, 10; 1310730 */
+/** channel.open method fields */
+typedef struct amqp_channel_open_t_ {
+  amqp_bytes_t out_of_band; /**< out-of-band */
+} amqp_channel_open_t;
+
+#define AMQP_CHANNEL_OPEN_OK_METHOD                                 \
+  ((amqp_method_number_t)0x0014000B) /**< channel.open-ok method id \
+                                        @internal 20, 11; 1310731 */
+/** channel.open-ok method fields */
+typedef struct amqp_channel_open_ok_t_ {
+  amqp_bytes_t channel_id; /**< channel-id */
+} amqp_channel_open_ok_t;
+
+#define AMQP_CHANNEL_FLOW_METHOD                                           \
+  ((amqp_method_number_t)0x00140014) /**< channel.flow method id @internal \
+                                        20, 20; 1310740 */
+/** channel.flow method fields */
+typedef struct amqp_channel_flow_t_ {
+  amqp_boolean_t active; /**< active */
+} amqp_channel_flow_t;
+
+#define AMQP_CHANNEL_FLOW_OK_METHOD                                 \
+  ((amqp_method_number_t)0x00140015) /**< channel.flow-ok method id \
+                                        @internal 20, 21; 1310741 */
+/** channel.flow-ok method fields */
+typedef struct amqp_channel_flow_ok_t_ {
+  amqp_boolean_t active; /**< active */
+} amqp_channel_flow_ok_t;
+
+#define AMQP_CHANNEL_CLOSE_METHOD                                           \
+  ((amqp_method_number_t)0x00140028) /**< channel.close method id @internal \
+                                        20, 40; 1310760 */
+/** channel.close method fields */
+typedef struct amqp_channel_close_t_ {
+  uint16_t reply_code;     /**< reply-code */
+  amqp_bytes_t reply_text; /**< reply-text */
+  uint16_t class_id;       /**< class-id */
+  uint16_t method_id;      /**< method-id */
+} amqp_channel_close_t;
+
+#define AMQP_CHANNEL_CLOSE_OK_METHOD                                 \
+  ((amqp_method_number_t)0x00140029) /**< channel.close-ok method id \
+                                        @internal 20, 41; 1310761 */
+/** channel.close-ok method fields */
+typedef struct amqp_channel_close_ok_t_ {
+  char dummy; /**< Dummy field to avoid empty struct */
+} amqp_channel_close_ok_t;
+
+#define AMQP_ACCESS_REQUEST_METHOD                                           \
+  ((amqp_method_number_t)0x001E000A) /**< access.request method id @internal \
+                                        30, 10; 1966090 */
+/** access.request method fields */
+typedef struct amqp_access_request_t_ {
+  amqp_bytes_t realm;       /**< realm */
+  amqp_boolean_t exclusive; /**< exclusive */
+  amqp_boolean_t passive;   /**< passive */
+  amqp_boolean_t active;    /**< active */
+  amqp_boolean_t write;     /**< write */
+  amqp_boolean_t read;      /**< read */
+} amqp_access_request_t;
+
+#define AMQP_ACCESS_REQUEST_OK_METHOD                                 \
+  ((amqp_method_number_t)0x001E000B) /**< access.request-ok method id \
+                                        @internal 30, 11; 1966091 */
+/** access.request-ok method fields */
+typedef struct amqp_access_request_ok_t_ {
+  uint16_t ticket; /**< ticket */
+} amqp_access_request_ok_t;
+
+#define AMQP_EXCHANGE_DECLARE_METHOD                                 \
+  ((amqp_method_number_t)0x0028000A) /**< exchange.declare method id \
+                                        @internal 40, 10; 2621450 */
+/** exchange.declare method fields */
+typedef struct amqp_exchange_declare_t_ {
+  uint16_t ticket;            /**< ticket */
+  amqp_bytes_t exchange;      /**< exchange */
+  amqp_bytes_t type;          /**< type */
+  amqp_boolean_t passive;     /**< passive */
+  amqp_boolean_t durable;     /**< durable */
+  amqp_boolean_t auto_delete; /**< auto-delete */
+  amqp_boolean_t internal;    /**< internal */
+  amqp_boolean_t nowait;      /**< nowait */
+  amqp_table_t arguments;     /**< arguments */
+} amqp_exchange_declare_t;
+
+#define AMQP_EXCHANGE_DECLARE_OK_METHOD                                 \
+  ((amqp_method_number_t)0x0028000B) /**< exchange.declare-ok method id \
+                                        @internal 40, 11; 2621451 */
+/** exchange.declare-ok method fields */
+typedef struct amqp_exchange_declare_ok_t_ {
+  char dummy; /**< Dummy field to avoid empty struct */
+} amqp_exchange_declare_ok_t;
+
+#define AMQP_EXCHANGE_DELETE_METHOD                                 \
+  ((amqp_method_number_t)0x00280014) /**< exchange.delete method id \
+                                        @internal 40, 20; 2621460 */
+/** exchange.delete method fields */
+typedef struct amqp_exchange_delete_t_ {
+  uint16_t ticket;          /**< ticket */
+  amqp_bytes_t exchange;    /**< exchange */
+  amqp_boolean_t if_unused; /**< if-unused */
+  amqp_boolean_t nowait;    /**< nowait */
+} amqp_exchange_delete_t;
+
+#define AMQP_EXCHANGE_DELETE_OK_METHOD                                 \
+  ((amqp_method_number_t)0x00280015) /**< exchange.delete-ok method id \
+                                        @internal 40, 21; 2621461 */
+/** exchange.delete-ok method fields */
+typedef struct amqp_exchange_delete_ok_t_ {
+  char dummy; /**< Dummy field to avoid empty struct */
+} amqp_exchange_delete_ok_t;
+
+#define AMQP_EXCHANGE_BIND_METHOD                                           \
+  ((amqp_method_number_t)0x0028001E) /**< exchange.bind method id @internal \
+                                        40, 30; 2621470 */
+/** exchange.bind method fields */
+typedef struct amqp_exchange_bind_t_ {
+  uint16_t ticket;          /**< ticket */
+  amqp_bytes_t destination; /**< destination */
+  amqp_bytes_t source;      /**< source */
+  amqp_bytes_t routing_key; /**< routing-key */
+  amqp_boolean_t nowait;    /**< nowait */
+  amqp_table_t arguments;   /**< arguments */
+} amqp_exchange_bind_t;
+
+#define AMQP_EXCHANGE_BIND_OK_METHOD                                 \
+  ((amqp_method_number_t)0x0028001F) /**< exchange.bind-ok method id \
+                                        @internal 40, 31; 2621471 */
+/** exchange.bind-ok method fields */
+typedef struct amqp_exchange_bind_ok_t_ {
+  char dummy; /**< Dummy field to avoid empty struct */
+} amqp_exchange_bind_ok_t;
+
+#define AMQP_EXCHANGE_UNBIND_METHOD                                 \
+  ((amqp_method_number_t)0x00280028) /**< exchange.unbind method id \
+                                        @internal 40, 40; 2621480 */
+/** exchange.unbind method fields */
+typedef struct amqp_exchange_unbind_t_ {
+  uint16_t ticket;          /**< ticket */
+  amqp_bytes_t destination; /**< destination */
+  amqp_bytes_t source;      /**< source */
+  amqp_bytes_t routing_key; /**< routing-key */
+  amqp_boolean_t nowait;    /**< nowait */
+  amqp_table_t arguments;   /**< arguments */
+} amqp_exchange_unbind_t;
+
+#define AMQP_EXCHANGE_UNBIND_OK_METHOD                                 \
+  ((amqp_method_number_t)0x00280033) /**< exchange.unbind-ok method id \
+                                        @internal 40, 51; 2621491 */
+/** exchange.unbind-ok method fields */
+typedef struct amqp_exchange_unbind_ok_t_ {
+  char dummy; /**< Dummy field to avoid empty struct */
+} amqp_exchange_unbind_ok_t;
+
+#define AMQP_QUEUE_DECLARE_METHOD                                           \
+  ((amqp_method_number_t)0x0032000A) /**< queue.declare method id @internal \
+                                        50, 10; 3276810 */
+/** queue.declare method fields */
+typedef struct amqp_queue_declare_t_ {
+  uint16_t ticket;            /**< ticket */
+  amqp_bytes_t queue;         /**< queue */
+  amqp_boolean_t passive;     /**< passive */
+  amqp_boolean_t durable;     /**< durable */
+  amqp_boolean_t exclusive;   /**< exclusive */
+  amqp_boolean_t auto_delete; /**< auto-delete */
+  amqp_boolean_t nowait;      /**< nowait */
+  amqp_table_t arguments;     /**< arguments */
+} amqp_queue_declare_t;
+
+#define AMQP_QUEUE_DECLARE_OK_METHOD                                 \
+  ((amqp_method_number_t)0x0032000B) /**< queue.declare-ok method id \
+                                        @internal 50, 11; 3276811 */
+/** queue.declare-ok method fields */
+typedef struct amqp_queue_declare_ok_t_ {
+  amqp_bytes_t queue;      /**< queue */
+  uint32_t message_count;  /**< message-count */
+  uint32_t consumer_count; /**< consumer-count */
+} amqp_queue_declare_ok_t;
+
+#define AMQP_QUEUE_BIND_METHOD                                               \
+  ((amqp_method_number_t)0x00320014) /**< queue.bind method id @internal 50, \
+                                        20; 3276820 */
+/** queue.bind method fields */
+typedef struct amqp_queue_bind_t_ {
+  uint16_t ticket;          /**< ticket */
+  amqp_bytes_t queue;       /**< queue */
+  amqp_bytes_t exchange;    /**< exchange */
+  amqp_bytes_t routing_key; /**< routing-key */
+  amqp_boolean_t nowait;    /**< nowait */
+  amqp_table_t arguments;   /**< arguments */
+} amqp_queue_bind_t;
+
+#define AMQP_QUEUE_BIND_OK_METHOD                                           \
+  ((amqp_method_number_t)0x00320015) /**< queue.bind-ok method id @internal \
+                                        50, 21; 3276821 */
+/** queue.bind-ok method fields */
+typedef struct amqp_queue_bind_ok_t_ {
+  char dummy; /**< Dummy field to avoid empty struct */
+} amqp_queue_bind_ok_t;
+
+#define AMQP_QUEUE_PURGE_METHOD                                           \
+  ((amqp_method_number_t)0x0032001E) /**< queue.purge method id @internal \
+                                        50, 30; 3276830 */
+/** queue.purge method fields */
+typedef struct amqp_queue_purge_t_ {
+  uint16_t ticket;       /**< ticket */
+  amqp_bytes_t queue;    /**< queue */
+  amqp_boolean_t nowait; /**< nowait */
+} amqp_queue_purge_t;
+
+#define AMQP_QUEUE_PURGE_OK_METHOD                                           \
+  ((amqp_method_number_t)0x0032001F) /**< queue.purge-ok method id @internal \
+                                        50, 31; 3276831 */
+/** queue.purge-ok method fields */
+typedef struct amqp_queue_purge_ok_t_ {
+  uint32_t message_count; /**< message-count */
+} amqp_queue_purge_ok_t;
+
+#define AMQP_QUEUE_DELETE_METHOD                                           \
+  ((amqp_method_number_t)0x00320028) /**< queue.delete method id @internal \
+                                        50, 40; 3276840 */
+/** queue.delete method fields */
+typedef struct amqp_queue_delete_t_ {
+  uint16_t ticket;          /**< ticket */
+  amqp_bytes_t queue;       /**< queue */
+  amqp_boolean_t if_unused; /**< if-unused */
+  amqp_boolean_t if_empty;  /**< if-empty */
+  amqp_boolean_t nowait;    /**< nowait */
+} amqp_queue_delete_t;
+
+#define AMQP_QUEUE_DELETE_OK_METHOD                                 \
+  ((amqp_method_number_t)0x00320029) /**< queue.delete-ok method id \
+                                        @internal 50, 41; 3276841 */
+/** queue.delete-ok method fields */
+typedef struct amqp_queue_delete_ok_t_ {
+  uint32_t message_count; /**< message-count */
+} amqp_queue_delete_ok_t;
+
+#define AMQP_QUEUE_UNBIND_METHOD                                           \
+  ((amqp_method_number_t)0x00320032) /**< queue.unbind method id @internal \
+                                        50, 50; 3276850 */
+/** queue.unbind method fields */
+typedef struct amqp_queue_unbind_t_ {
+  uint16_t ticket;          /**< ticket */
+  amqp_bytes_t queue;       /**< queue */
+  amqp_bytes_t exchange;    /**< exchange */
+  amqp_bytes_t routing_key; /**< routing-key */
+  amqp_table_t arguments;   /**< arguments */
+} amqp_queue_unbind_t;
+
+#define AMQP_QUEUE_UNBIND_OK_METHOD                                 \
+  ((amqp_method_number_t)0x00320033) /**< queue.unbind-ok method id \
+                                        @internal 50, 51; 3276851 */
+/** queue.unbind-ok method fields */
+typedef struct amqp_queue_unbind_ok_t_ {
+  char dummy; /**< Dummy field to avoid empty struct */
+} amqp_queue_unbind_ok_t;
+
+#define AMQP_BASIC_QOS_METHOD                                               \
+  ((amqp_method_number_t)0x003C000A) /**< basic.qos method id @internal 60, \
+                                        10; 3932170 */
+/** basic.qos method fields */
+typedef struct amqp_basic_qos_t_ {
+  uint32_t prefetch_size;  /**< prefetch-size */
+  uint16_t prefetch_count; /**< prefetch-count */
+  amqp_boolean_t global;   /**< global */
+} amqp_basic_qos_t;
+
+#define AMQP_BASIC_QOS_OK_METHOD                                           \
+  ((amqp_method_number_t)0x003C000B) /**< basic.qos-ok method id @internal \
+                                        60, 11; 3932171 */
+/** basic.qos-ok method fields */
+typedef struct amqp_basic_qos_ok_t_ {
+  char dummy; /**< Dummy field to avoid empty struct */
+} amqp_basic_qos_ok_t;
+
+#define AMQP_BASIC_CONSUME_METHOD                                           \
+  ((amqp_method_number_t)0x003C0014) /**< basic.consume method id @internal \
+                                        60, 20; 3932180 */
+/** basic.consume method fields */
+typedef struct amqp_basic_consume_t_ {
+  uint16_t ticket;           /**< ticket */
+  amqp_bytes_t queue;        /**< queue */
+  amqp_bytes_t consumer_tag; /**< consumer-tag */
+  amqp_boolean_t no_local;   /**< no-local */
+  amqp_boolean_t no_ack;     /**< no-ack */
+  amqp_boolean_t exclusive;  /**< exclusive */
+  amqp_boolean_t nowait;     /**< nowait */
+  amqp_table_t arguments;    /**< arguments */
+} amqp_basic_consume_t;
+
+#define AMQP_BASIC_CONSUME_OK_METHOD                                 \
+  ((amqp_method_number_t)0x003C0015) /**< basic.consume-ok method id \
+                                        @internal 60, 21; 3932181 */
+/** basic.consume-ok method fields */
+typedef struct amqp_basic_consume_ok_t_ {
+  amqp_bytes_t consumer_tag; /**< consumer-tag */
+} amqp_basic_consume_ok_t;
+
+#define AMQP_BASIC_CANCEL_METHOD                                           \
+  ((amqp_method_number_t)0x003C001E) /**< basic.cancel method id @internal \
+                                        60, 30; 3932190 */
+/** basic.cancel method fields */
+typedef struct amqp_basic_cancel_t_ {
+  amqp_bytes_t consumer_tag; /**< consumer-tag */
+  amqp_boolean_t nowait;     /**< nowait */
+} amqp_basic_cancel_t;
+
+#define AMQP_BASIC_CANCEL_OK_METHOD                                 \
+  ((amqp_method_number_t)0x003C001F) /**< basic.cancel-ok method id \
+                                        @internal 60, 31; 3932191 */
+/** basic.cancel-ok method fields */
+typedef struct amqp_basic_cancel_ok_t_ {
+  amqp_bytes_t consumer_tag; /**< consumer-tag */
+} amqp_basic_cancel_ok_t;
+
+#define AMQP_BASIC_PUBLISH_METHOD                                           \
+  ((amqp_method_number_t)0x003C0028) /**< basic.publish method id @internal \
+                                        60, 40; 3932200 */
+/** basic.publish method fields */
+typedef struct amqp_basic_publish_t_ {
+  uint16_t ticket;          /**< ticket */
+  amqp_bytes_t exchange;    /**< exchange */
+  amqp_bytes_t routing_key; /**< routing-key */
+  amqp_boolean_t mandatory; /**< mandatory */
+  amqp_boolean_t immediate; /**< immediate */
+} amqp_basic_publish_t;
+
+#define AMQP_BASIC_RETURN_METHOD                                           \
+  ((amqp_method_number_t)0x003C0032) /**< basic.return method id @internal \
+                                        60, 50; 3932210 */
+/** basic.return method fields */
+typedef struct amqp_basic_return_t_ {
+  uint16_t reply_code;      /**< reply-code */
+  amqp_bytes_t reply_text;  /**< reply-text */
+  amqp_bytes_t exchange;    /**< exchange */
+  amqp_bytes_t routing_key; /**< routing-key */
+} amqp_basic_return_t;
+
+#define AMQP_BASIC_DELIVER_METHOD                                           \
+  ((amqp_method_number_t)0x003C003C) /**< basic.deliver method id @internal \
+                                        60, 60; 3932220 */
+/** basic.deliver method fields */
+typedef struct amqp_basic_deliver_t_ {
+  amqp_bytes_t consumer_tag;  /**< consumer-tag */
+  uint64_t delivery_tag;      /**< delivery-tag */
+  amqp_boolean_t redelivered; /**< redelivered */
+  amqp_bytes_t exchange;      /**< exchange */
+  amqp_bytes_t routing_key;   /**< routing-key */
+} amqp_basic_deliver_t;
+
+#define AMQP_BASIC_GET_METHOD                                               \
+  ((amqp_method_number_t)0x003C0046) /**< basic.get method id @internal 60, \
+                                        70; 3932230 */
+/** basic.get method fields */
+typedef struct amqp_basic_get_t_ {
+  uint16_t ticket;       /**< ticket */
+  amqp_bytes_t queue;    /**< queue */
+  amqp_boolean_t no_ack; /**< no-ack */
+} amqp_basic_get_t;
+
+#define AMQP_BASIC_GET_OK_METHOD                                           \
+  ((amqp_method_number_t)0x003C0047) /**< basic.get-ok method id @internal \
+                                        60, 71; 3932231 */
+/** basic.get-ok method fields */
+typedef struct amqp_basic_get_ok_t_ {
+  uint64_t delivery_tag;      /**< delivery-tag */
+  amqp_boolean_t redelivered; /**< redelivered */
+  amqp_bytes_t exchange;      /**< exchange */
+  amqp_bytes_t routing_key;   /**< routing-key */
+  uint32_t message_count;     /**< message-count */
+} amqp_basic_get_ok_t;
+
+#define AMQP_BASIC_GET_EMPTY_METHOD                                 \
+  ((amqp_method_number_t)0x003C0048) /**< basic.get-empty method id \
+                                        @internal 60, 72; 3932232 */
+/** basic.get-empty method fields */
+typedef struct amqp_basic_get_empty_t_ {
+  amqp_bytes_t cluster_id; /**< cluster-id */
+} amqp_basic_get_empty_t;
+
+#define AMQP_BASIC_ACK_METHOD                                               \
+  ((amqp_method_number_t)0x003C0050) /**< basic.ack method id @internal 60, \
+                                        80; 3932240 */
+/** basic.ack method fields */
+typedef struct amqp_basic_ack_t_ {
+  uint64_t delivery_tag;   /**< delivery-tag */
+  amqp_boolean_t multiple; /**< multiple */
+} amqp_basic_ack_t;
+
+#define AMQP_BASIC_REJECT_METHOD                                           \
+  ((amqp_method_number_t)0x003C005A) /**< basic.reject method id @internal \
+                                        60, 90; 3932250 */
+/** basic.reject method fields */
+typedef struct amqp_basic_reject_t_ {
+  uint64_t delivery_tag;  /**< delivery-tag */
+  amqp_boolean_t requeue; /**< requeue */
+} amqp_basic_reject_t;
+
+#define AMQP_BASIC_RECOVER_ASYNC_METHOD                                 \
+  ((amqp_method_number_t)0x003C0064) /**< basic.recover-async method id \
+                                        @internal 60, 100; 3932260 */
+/** basic.recover-async method fields */
+typedef struct amqp_basic_recover_async_t_ {
+  amqp_boolean_t requeue; /**< requeue */
+} amqp_basic_recover_async_t;
+
+#define AMQP_BASIC_RECOVER_METHOD                                           \
+  ((amqp_method_number_t)0x003C006E) /**< basic.recover method id @internal \
+                                        60, 110; 3932270 */
+/** basic.recover method fields */
+typedef struct amqp_basic_recover_t_ {
+  amqp_boolean_t requeue; /**< requeue */
+} amqp_basic_recover_t;
+
+#define AMQP_BASIC_RECOVER_OK_METHOD                                 \
+  ((amqp_method_number_t)0x003C006F) /**< basic.recover-ok method id \
+                                        @internal 60, 111; 3932271 */
+/** basic.recover-ok method fields */
+typedef struct amqp_basic_recover_ok_t_ {
+  char dummy; /**< Dummy field to avoid empty struct */
+} amqp_basic_recover_ok_t;
+
+#define AMQP_BASIC_NACK_METHOD                                               \
+  ((amqp_method_number_t)0x003C0078) /**< basic.nack method id @internal 60, \
+                                        120; 3932280 */
+/** basic.nack method fields */
+typedef struct amqp_basic_nack_t_ {
+  uint64_t delivery_tag;   /**< delivery-tag */
+  amqp_boolean_t multiple; /**< multiple */
+  amqp_boolean_t requeue;  /**< requeue */
+} amqp_basic_nack_t;
+
+#define AMQP_TX_SELECT_METHOD                                               \
+  ((amqp_method_number_t)0x005A000A) /**< tx.select method id @internal 90, \
+                                        10; 5898250 */
+/** tx.select method fields */
+typedef struct amqp_tx_select_t_ {
+  char dummy; /**< Dummy field to avoid empty struct */
+} amqp_tx_select_t;
+
+#define AMQP_TX_SELECT_OK_METHOD                                           \
+  ((amqp_method_number_t)0x005A000B) /**< tx.select-ok method id @internal \
+                                        90, 11; 5898251 */
+/** tx.select-ok method fields */
+typedef struct amqp_tx_select_ok_t_ {
+  char dummy; /**< Dummy field to avoid empty struct */
+} amqp_tx_select_ok_t;
+
+#define AMQP_TX_COMMIT_METHOD                                               \
+  ((amqp_method_number_t)0x005A0014) /**< tx.commit method id @internal 90, \
+                                        20; 5898260 */
+/** tx.commit method fields */
+typedef struct amqp_tx_commit_t_ {
+  char dummy; /**< Dummy field to avoid empty struct */
+} amqp_tx_commit_t;
+
+#define AMQP_TX_COMMIT_OK_METHOD                                           \
+  ((amqp_method_number_t)0x005A0015) /**< tx.commit-ok method id @internal \
+                                        90, 21; 5898261 */
+/** tx.commit-ok method fields */
+typedef struct amqp_tx_commit_ok_t_ {
+  char dummy; /**< Dummy field to avoid empty struct */
+} amqp_tx_commit_ok_t;
+
+#define AMQP_TX_ROLLBACK_METHOD                                           \
+  ((amqp_method_number_t)0x005A001E) /**< tx.rollback method id @internal \
+                                        90, 30; 5898270 */
+/** tx.rollback method fields */
+typedef struct amqp_tx_rollback_t_ {
+  char dummy; /**< Dummy field to avoid empty struct */
+} amqp_tx_rollback_t;
+
+#define AMQP_TX_ROLLBACK_OK_METHOD                                           \
+  ((amqp_method_number_t)0x005A001F) /**< tx.rollback-ok method id @internal \
+                                        90, 31; 5898271 */
+/** tx.rollback-ok method fields */
+typedef struct amqp_tx_rollback_ok_t_ {
+  char dummy; /**< Dummy field to avoid empty struct */
+} amqp_tx_rollback_ok_t;
+
+#define AMQP_CONFIRM_SELECT_METHOD                                           \
+  ((amqp_method_number_t)0x0055000A) /**< confirm.select method id @internal \
+                                        85, 10; 5570570 */
+/** confirm.select method fields */
+typedef struct amqp_confirm_select_t_ {
+  amqp_boolean_t nowait; /**< nowait */
+} amqp_confirm_select_t;
+
+#define AMQP_CONFIRM_SELECT_OK_METHOD                                 \
+  ((amqp_method_number_t)0x0055000B) /**< confirm.select-ok method id \
+                                        @internal 85, 11; 5570571 */
+/** confirm.select-ok method fields */
+typedef struct amqp_confirm_select_ok_t_ {
+  char dummy; /**< Dummy field to avoid empty struct */
+} amqp_confirm_select_ok_t;
+
+/* Class property records. */
+#define AMQP_CONNECTION_CLASS                    \
+  (0x000A) /**< connection class id @internal 10 \
+              */
+/** connection class properties */
+typedef struct amqp_connection_properties_t_ {
+  amqp_flags_t _flags; /**< bit-mask of set fields */
+  char dummy;          /**< Dummy field to avoid empty struct */
+} amqp_connection_properties_t;
+
+#define AMQP_CHANNEL_CLASS (0x0014) /**< channel class id @internal 20 */
+/** channel class properties */
+typedef struct amqp_channel_properties_t_ {
+  amqp_flags_t _flags; /**< bit-mask of set fields */
+  char dummy;          /**< Dummy field to avoid empty struct */
+} amqp_channel_properties_t;
+
+#define AMQP_ACCESS_CLASS (0x001E) /**< access class id @internal 30 */
+/** access class properties */
+typedef struct amqp_access_properties_t_ {
+  amqp_flags_t _flags; /**< bit-mask of set fields */
+  char dummy;          /**< Dummy field to avoid empty struct */
+} amqp_access_properties_t;
+
+#define AMQP_EXCHANGE_CLASS (0x0028) /**< exchange class id @internal 40 */
+/** exchange class properties */
+typedef struct amqp_exchange_properties_t_ {
+  amqp_flags_t _flags; /**< bit-mask of set fields */
+  char dummy;          /**< Dummy field to avoid empty struct */
+} amqp_exchange_properties_t;
+
+#define AMQP_QUEUE_CLASS (0x0032) /**< queue class id @internal 50 */
+/** queue class properties */
+typedef struct amqp_queue_properties_t_ {
+  amqp_flags_t _flags; /**< bit-mask of set fields */
+  char dummy;          /**< Dummy field to avoid empty struct */
+} amqp_queue_properties_t;
+
+#define AMQP_BASIC_CLASS (0x003C) /**< basic class id @internal 60 */
+#define AMQP_BASIC_CONTENT_TYPE_FLAG (1 << 15)
+#define AMQP_BASIC_CONTENT_ENCODING_FLAG (1 << 14)
+#define AMQP_BASIC_HEADERS_FLAG (1 << 13)
+#define AMQP_BASIC_DELIVERY_MODE_FLAG (1 << 12)
+#define AMQP_BASIC_PRIORITY_FLAG (1 << 11)
+#define AMQP_BASIC_CORRELATION_ID_FLAG (1 << 10)
+#define AMQP_BASIC_REPLY_TO_FLAG (1 << 9)
+#define AMQP_BASIC_EXPIRATION_FLAG (1 << 8)
+#define AMQP_BASIC_MESSAGE_ID_FLAG (1 << 7)
+#define AMQP_BASIC_TIMESTAMP_FLAG (1 << 6)
+#define AMQP_BASIC_TYPE_FLAG (1 << 5)
+#define AMQP_BASIC_USER_ID_FLAG (1 << 4)
+#define AMQP_BASIC_APP_ID_FLAG (1 << 3)
+#define AMQP_BASIC_CLUSTER_ID_FLAG (1 << 2)
+/** basic class properties */
+typedef struct amqp_basic_properties_t_ {
+  amqp_flags_t _flags;           /**< bit-mask of set fields */
+  amqp_bytes_t content_type;     /**< content-type */
+  amqp_bytes_t content_encoding; /**< content-encoding */
+  amqp_table_t headers;          /**< headers */
+  uint8_t delivery_mode;         /**< delivery-mode */
+  uint8_t priority;              /**< priority */
+  amqp_bytes_t correlation_id;   /**< correlation-id */
+  amqp_bytes_t reply_to;         /**< reply-to */
+  amqp_bytes_t expiration;       /**< expiration */
+  amqp_bytes_t message_id;       /**< message-id */
+  uint64_t timestamp;            /**< timestamp */
+  amqp_bytes_t type;             /**< type */
+  amqp_bytes_t user_id;          /**< user-id */
+  amqp_bytes_t app_id;           /**< app-id */
+  amqp_bytes_t cluster_id;       /**< cluster-id */
+} amqp_basic_properties_t;
+
+#define AMQP_TX_CLASS (0x005A) /**< tx class id @internal 90 */
+/** tx class properties */
+typedef struct amqp_tx_properties_t_ {
+  amqp_flags_t _flags; /**< bit-mask of set fields */
+  char dummy;          /**< Dummy field to avoid empty struct */
+} amqp_tx_properties_t;
+
+#define AMQP_CONFIRM_CLASS (0x0055) /**< confirm class id @internal 85 */
+/** confirm class properties */
+typedef struct amqp_confirm_properties_t_ {
+  amqp_flags_t _flags; /**< bit-mask of set fields */
+  char dummy;          /**< Dummy field to avoid empty struct */
+} amqp_confirm_properties_t;
+
+/* API functions for methods */
+
+/**
+ * amqp_channel_open
+ *
+ * @param [in] state connection state
+ * @param [in] channel the channel to do the RPC on
+ * @returns amqp_channel_open_ok_t
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_channel_open_ok_t *AMQP_CALL
+    amqp_channel_open(amqp_connection_state_t state, amqp_channel_t channel);
+/**
+ * amqp_channel_flow
+ *
+ * @param [in] state connection state
+ * @param [in] channel the channel to do the RPC on
+ * @param [in] active active
+ * @returns amqp_channel_flow_ok_t
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_channel_flow_ok_t *AMQP_CALL
+    amqp_channel_flow(amqp_connection_state_t state, amqp_channel_t channel,
+                      amqp_boolean_t active);
+/**
+ * amqp_exchange_declare
+ *
+ * @param [in] state connection state
+ * @param [in] channel the channel to do the RPC on
+ * @param [in] exchange exchange
+ * @param [in] type type
+ * @param [in] passive passive
+ * @param [in] durable durable
+ * @param [in] auto_delete auto_delete
+ * @param [in] internal internal
+ * @param [in] arguments arguments
+ * @returns amqp_exchange_declare_ok_t
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_exchange_declare_ok_t *AMQP_CALL amqp_exchange_declare(
+    amqp_connection_state_t state, amqp_channel_t channel,
+    amqp_bytes_t exchange, amqp_bytes_t type, amqp_boolean_t passive,
+    amqp_boolean_t durable, amqp_boolean_t auto_delete, amqp_boolean_t internal,
+    amqp_table_t arguments);
+/**
+ * amqp_exchange_delete
+ *
+ * @param [in] state connection state
+ * @param [in] channel the channel to do the RPC on
+ * @param [in] exchange exchange
+ * @param [in] if_unused if_unused
+ * @returns amqp_exchange_delete_ok_t
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_exchange_delete_ok_t *AMQP_CALL
+    amqp_exchange_delete(amqp_connection_state_t state, amqp_channel_t channel,
+                         amqp_bytes_t exchange, amqp_boolean_t if_unused);
+/**
+ * amqp_exchange_bind
+ *
+ * @param [in] state connection state
+ * @param [in] channel the channel to do the RPC on
+ * @param [in] destination destination
+ * @param [in] source source
+ * @param [in] routing_key routing_key
+ * @param [in] arguments arguments
+ * @returns amqp_exchange_bind_ok_t
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_exchange_bind_ok_t *AMQP_CALL
+    amqp_exchange_bind(amqp_connection_state_t state, amqp_channel_t channel,
+                       amqp_bytes_t destination, amqp_bytes_t source,
+                       amqp_bytes_t routing_key, amqp_table_t arguments);
+/**
+ * amqp_exchange_unbind
+ *
+ * @param [in] state connection state
+ * @param [in] channel the channel to do the RPC on
+ * @param [in] destination destination
+ * @param [in] source source
+ * @param [in] routing_key routing_key
+ * @param [in] arguments arguments
+ * @returns amqp_exchange_unbind_ok_t
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_exchange_unbind_ok_t *AMQP_CALL
+    amqp_exchange_unbind(amqp_connection_state_t state, amqp_channel_t channel,
+                         amqp_bytes_t destination, amqp_bytes_t source,
+                         amqp_bytes_t routing_key, amqp_table_t arguments);
+/**
+ * amqp_queue_declare
+ *
+ * @param [in] state connection state
+ * @param [in] channel the channel to do the RPC on
+ * @param [in] queue queue
+ * @param [in] passive passive
+ * @param [in] durable durable
+ * @param [in] exclusive exclusive
+ * @param [in] auto_delete auto_delete
+ * @param [in] arguments arguments
+ * @returns amqp_queue_declare_ok_t
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_queue_declare_ok_t *AMQP_CALL amqp_queue_declare(
+    amqp_connection_state_t state, amqp_channel_t channel, amqp_bytes_t queue,
+    amqp_boolean_t passive, amqp_boolean_t durable, amqp_boolean_t exclusive,
+    amqp_boolean_t auto_delete, amqp_table_t arguments);
+/**
+ * amqp_queue_bind
+ *
+ * @param [in] state connection state
+ * @param [in] channel the channel to do the RPC on
+ * @param [in] queue queue
+ * @param [in] exchange exchange
+ * @param [in] routing_key routing_key
+ * @param [in] arguments arguments
+ * @returns amqp_queue_bind_ok_t
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_queue_bind_ok_t *AMQP_CALL amqp_queue_bind(
+    amqp_connection_state_t state, amqp_channel_t channel, amqp_bytes_t queue,
+    amqp_bytes_t exchange, amqp_bytes_t routing_key, amqp_table_t arguments);
+/**
+ * amqp_queue_purge
+ *
+ * @param [in] state connection state
+ * @param [in] channel the channel to do the RPC on
+ * @param [in] queue queue
+ * @returns amqp_queue_purge_ok_t
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_queue_purge_ok_t *AMQP_CALL amqp_queue_purge(amqp_connection_state_t state,
+                                                  amqp_channel_t channel,
+                                                  amqp_bytes_t queue);
+/**
+ * amqp_queue_delete
+ *
+ * @param [in] state connection state
+ * @param [in] channel the channel to do the RPC on
+ * @param [in] queue queue
+ * @param [in] if_unused if_unused
+ * @param [in] if_empty if_empty
+ * @returns amqp_queue_delete_ok_t
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_queue_delete_ok_t *AMQP_CALL amqp_queue_delete(
+    amqp_connection_state_t state, amqp_channel_t channel, amqp_bytes_t queue,
+    amqp_boolean_t if_unused, amqp_boolean_t if_empty);
+/**
+ * amqp_queue_unbind
+ *
+ * @param [in] state connection state
+ * @param [in] channel the channel to do the RPC on
+ * @param [in] queue queue
+ * @param [in] exchange exchange
+ * @param [in] routing_key routing_key
+ * @param [in] arguments arguments
+ * @returns amqp_queue_unbind_ok_t
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_queue_unbind_ok_t *AMQP_CALL amqp_queue_unbind(
+    amqp_connection_state_t state, amqp_channel_t channel, amqp_bytes_t queue,
+    amqp_bytes_t exchange, amqp_bytes_t routing_key, amqp_table_t arguments);
+/**
+ * amqp_basic_qos
+ *
+ * @param [in] state connection state
+ * @param [in] channel the channel to do the RPC on
+ * @param [in] prefetch_size prefetch_size
+ * @param [in] prefetch_count prefetch_count
+ * @param [in] global global
+ * @returns amqp_basic_qos_ok_t
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_basic_qos_ok_t *AMQP_CALL amqp_basic_qos(amqp_connection_state_t state,
+                                              amqp_channel_t channel,
+                                              uint32_t prefetch_size,
+                                              uint16_t prefetch_count,
+                                              amqp_boolean_t global);
+/**
+ * amqp_basic_consume
+ *
+ * @param [in] state connection state
+ * @param [in] channel the channel to do the RPC on
+ * @param [in] queue queue
+ * @param [in] consumer_tag consumer_tag
+ * @param [in] no_local no_local
+ * @param [in] no_ack no_ack
+ * @param [in] exclusive exclusive
+ * @param [in] arguments arguments
+ * @returns amqp_basic_consume_ok_t
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_basic_consume_ok_t *AMQP_CALL amqp_basic_consume(
+    amqp_connection_state_t state, amqp_channel_t channel, amqp_bytes_t queue,
+    amqp_bytes_t consumer_tag, amqp_boolean_t no_local, amqp_boolean_t no_ack,
+    amqp_boolean_t exclusive, amqp_table_t arguments);
+/**
+ * amqp_basic_cancel
+ *
+ * @param [in] state connection state
+ * @param [in] channel the channel to do the RPC on
+ * @param [in] consumer_tag consumer_tag
+ * @returns amqp_basic_cancel_ok_t
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_basic_cancel_ok_t *AMQP_CALL
+    amqp_basic_cancel(amqp_connection_state_t state, amqp_channel_t channel,
+                      amqp_bytes_t consumer_tag);
+/**
+ * amqp_basic_recover
+ *
+ * @param [in] state connection state
+ * @param [in] channel the channel to do the RPC on
+ * @param [in] requeue requeue
+ * @returns amqp_basic_recover_ok_t
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_basic_recover_ok_t *AMQP_CALL
+    amqp_basic_recover(amqp_connection_state_t state, amqp_channel_t channel,
+                       amqp_boolean_t requeue);
+/**
+ * amqp_tx_select
+ *
+ * @param [in] state connection state
+ * @param [in] channel the channel to do the RPC on
+ * @returns amqp_tx_select_ok_t
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_tx_select_ok_t *AMQP_CALL amqp_tx_select(amqp_connection_state_t state,
+                                              amqp_channel_t channel);
+/**
+ * amqp_tx_commit
+ *
+ * @param [in] state connection state
+ * @param [in] channel the channel to do the RPC on
+ * @returns amqp_tx_commit_ok_t
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_tx_commit_ok_t *AMQP_CALL amqp_tx_commit(amqp_connection_state_t state,
+                                              amqp_channel_t channel);
+/**
+ * amqp_tx_rollback
+ *
+ * @param [in] state connection state
+ * @param [in] channel the channel to do the RPC on
+ * @returns amqp_tx_rollback_ok_t
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_tx_rollback_ok_t *AMQP_CALL amqp_tx_rollback(amqp_connection_state_t state,
+                                                  amqp_channel_t channel);
+/**
+ * amqp_confirm_select
+ *
+ * @param [in] state connection state
+ * @param [in] channel the channel to do the RPC on
+ * @returns amqp_confirm_select_ok_t
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_confirm_select_ok_t *AMQP_CALL
+    amqp_confirm_select(amqp_connection_state_t state, amqp_channel_t channel);
+
+AMQP_END_DECLS
+
+#endif /* AMQP_FRAMING_H */

+ 68 - 0
ext/librabbitmq/centos_x64/include/amqp_tcp_socket.h

@@ -0,0 +1,68 @@
+/** \file */
+/*
+ * Portions created by Alan Antonuk are Copyright (c) 2013-2014 Alan Antonuk.
+ * All Rights Reserved.
+ *
+ * Portions created by Michael Steinert are Copyright (c) 2012-2013 Michael
+ * Steinert. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * A TCP socket connection.
+ */
+
+#ifndef AMQP_TCP_SOCKET_H
+#define AMQP_TCP_SOCKET_H
+
+#include <amqp.h>
+
+AMQP_BEGIN_DECLS
+
+/**
+ * Create a new TCP socket.
+ *
+ * Call amqp_connection_close() to release socket resources.
+ *
+ * \return A new socket object or NULL if an error occurred.
+ *
+ * \since v0.4.0
+ */
+AMQP_PUBLIC_FUNCTION
+amqp_socket_t *AMQP_CALL amqp_tcp_socket_new(amqp_connection_state_t state);
+
+/**
+ * Assign an open file descriptor to a socket object.
+ *
+ * This function must not be used in conjunction with amqp_socket_open(), i.e.
+ * the socket connection should already be open(2) when this function is
+ * called.
+ *
+ * \param [in,out] self A TCP socket object.
+ * \param [in] sockfd An open socket descriptor.
+ *
+ * \since v0.4.0
+ */
+AMQP_PUBLIC_FUNCTION
+void AMQP_CALL amqp_tcp_socket_set_sockfd(amqp_socket_t *self, int sockfd);
+
+AMQP_END_DECLS
+
+#endif /* AMQP_TCP_SOCKET_H */

TEMPAT SAMPAH
ext/librabbitmq/centos_x64/lib/librabbitmq.a


+ 0 - 11
ext/librethinkdbxx/.travis.yml

@@ -1,11 +0,0 @@
-sudo: required
-dist: trusty
-
-python:
-  - "3.4.3"
-
-addons:
-  rethinkdb: "2.3"
-
-script:
-  - make test

+ 0 - 16
ext/librethinkdbxx/COPYRIGHT

@@ -1,16 +0,0 @@
-RethinkDB Language Drivers
-
-Copyright 2010-2012 RethinkDB
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this product except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-

+ 0 - 126
ext/librethinkdbxx/Makefile

@@ -1,126 +0,0 @@
-# Customisable build settings
-
-CXX ?= clang++
-CXXFLAGS ?=
-INCLUDE_PYTHON_DOCS ?= no
-DEBUG ?= no
-PYTHON ?= python3
-
-# Required build settings
-
-ifneq (no,$(DEBUG))
-  CXXFLAGS += -ggdb
-else
-  CXXFLAGS += -O3 # -flto
-endif
-
-CXXFLAGS += -std=c++11 -I'build/gen' -Wall -pthread -fPIC
-
-prefix ?= /usr
-DESTDIR ?=
-
-.DELETE_ON_ERROR:
-SHELL := /bin/bash
-
-modules := connection datum json term cursor types utils
-headers := utils error exceptions types datum connection cursor term
-
-o_files := $(patsubst %, build/obj/%.o, $(modules))
-d_files := $(patsubst %, build/dep/%.d, $(modules))
-
-skip_tests := regression/1133 regression/767 regression/1005 # python-only
-skip_tests += arity # arity errors are compile-time
-skip_tests += geo # geo types not implemented yet
-skip_tests += limits # possibly broken tests: https://github.com/rethinkdb/rethinkdb/issues/5940
-
-upstream_tests := \
-  $(filter-out %.rb.%, \
-    $(filter-out $(patsubst %,test/upstream/%%, $(skip_tests)), \
-      $(filter test/upstream/$(test_filter)%, \
-        $(shell find test/upstream -name '*.yaml' | egrep -v '.(rb|js).yaml$$'))))
-upstream_tests_cc := $(patsubst %.yaml, build/tests/%.cc, $(upstream_tests))
-upstream_tests_o := $(patsubst %.cc, %.o, $(upstream_tests_cc))
-
-.PRECIOUS: $(upstream_tests_cc) $(upstream_tests_o)
-
-default: build/librethinkdb++.a build/include/rethinkdb.h build/librethinkdb++.so
-
-all: default build/test
-
-build/librethinkdb++.a: $(o_files)
-	ar rcs $@ $^
-
-build/librethinkdb++.so: $(o_files)
-	$(CXX) -o $@ $(CXXFLAGS) -shared $^
-
-build/obj/%.o: src/%.cc build/gen/protocol_defs.h
-	@mkdir -p $(dir $@)
-	@mkdir -p $(dir build/dep/$*.d)
-	$(CXX) -o $@ $(CXXFLAGS) -c $< -MP -MQ $@ -MD -MF build/dep/$*.d
-
-build/gen/protocol_defs.h: reql/ql2.proto reql/gen.py | build/gen/.
-	$(PYTHON) reql/gen.py $< > $@
-
-clean:
-	rm -rf build
-
-ifneq (no,$(INCLUDE_PYTHON_DOCS))
-build/include/rethinkdb.h: build/rethinkdb.nodocs.h reql/add_docs.py reql/python_docs.txt | build/include/.
-	$(PYTHON) reql/add_docs.py reql/python_docs.txt < $< > $@
-else
-build/include/rethinkdb.h: build/rethinkdb.nodocs.h | build/include/.
-	cp $< $@
-endif
-
-build/rethinkdb.nodocs.h: build/gen/protocol_defs.h $(patsubst %, src/%.h, $(headers))
-	( echo "// Auto-generated file, built from $^"; \
-	  echo '#pragma once'; \
-	  cat $^ | \
-	    grep -v '^#pragma once' | \
-	    grep -v '^#include "'; \
-	) > $@
-
-build/tests/%.cc: %.yaml test/yaml_to_cxx.py
-	@mkdir -p $(dir $@)
-	$(PYTHON) test/yaml_to_cxx.py $< > $@
-
-build/tests/upstream_tests.cc: $(upstream_tests) test/gen_index_cxx.py FORCE | build/tests/.
-	@echo '$(PYTHON) test/gen_index_cxx.py $(wordlist 1,5,$(upstream_tests)) ... > $@'
-	@$(PYTHON) test/gen_index_cxx.py $(upstream_tests) > $@
-
-build/tests/%.o: build/tests/%.cc build/include/rethinkdb.h test/testlib.h | build/tests/.
-	$(CXX) -o $@ $(CXXFLAGS) -isystem build/include -I test -c $< -Wno-unused-variable
-
-build/tests/%.o: test/%.cc test/testlib.h build/include/rethinkdb.h | build/tests/.
-	$(CXX) -o $@ $(CXXFLAGS) -isystem build/include -I test -c $<
-
-build/test: build/tests/testlib.o build/tests/test.o build/tests/upstream_tests.o $(upstream_tests_o) build/librethinkdb++.a
-	@echo $(CXX) -o $@ $(CXXFLAGS) $(wordlist 1,5,$^) ...
-	@$(CXX) -o $@ $(CXXFLAGS) build/librethinkdb++.a $^
-
-.PHONY: test
-test: build/test
-	build/test
-
-build/bench: build/tests/bench.o build/librethinkdb++.a
-	@$(CXX) -o $@ $(CXXFLAGS) -isystem build/include build/librethinkdb++.a $^
-
-.PHONY: bench
-bench: build/bench
-	build/bench
-
-.PHONY: install
-install: build/librethinkdb++.a build/include/rethinkdb.h build/librethinkdb++.so
-	install -m755 -d $(DESTDIR)$(prefix)/lib
-	install -m755 -d $(DESTDIR)$(prefix)/include
-	install -m644 build/librethinkdb++.a $(DESTDIR)$(prefix)/lib/librethinkdb++.a
-	install -m644 build/librethinkdb++.so $(DESTDIR)$(prefix)/lib/librethinkdb++.so
-	install -m644 build/include/rethinkdb.h $(DESTDIR)$(prefix)/include/rethinkdb.h
-
-%/.:
-	mkdir -p $*
-
-.PHONY: FORCE
-FORCE:
-
--include $(d_files)

+ 0 - 72
ext/librethinkdbxx/README.md

@@ -1,72 +0,0 @@
-# RethinkDB driver for C++
-
-This driver is compatible with RethinkDB 2.0. It is based on the
-official RethinkDB Python driver.
-
-* [RethinkDB server](http://rethinkdb.com/)
-* [RethinkDB API docs](http://rethinkdb.com/api/python/)
-
-## Example
-
-```
-#include <memory>
-#include <cstdio>
-#include <rethinkdb.h>
-
-namespace R = RethinkDB;
-
-int main() {
-  std::unique_ptr<R::Connection> conn = R::connect("localhost", 28015);
-  R::Cursor cursor = R::table("users").filter(R::row["age"] > 14).run(*conn);
-  for (R::Datum& user : cursor) {
-      printf("%s\n", user.as_json().c_str());
-  }
-}
-```
-
-## Build
-
-Requires a modern C++ compiler. to build and install, run:
-
-```
-make
-make install
-```
-
-Will build `include/rethinkdb.h`, `librethinkdb++.a` and
-`librethinkdb++.so` into the `build/` directory.
-
-To include documentation from the Python driver in the header file,
-pass the following argument to make.
-
-```
-make INCLUDE_PYTHON_DOCS=yes
-```
-
-To build in debug mode:
-
-```
-make DEBUG=yes
-```
-
-To install to a specific location:
-
-```
-make install prefix=/usr/local DESTDIR=
-```
-
-## Status
-
-Still in early stages of development.
-
-## Tests
-
-This driver is tested against the upstream ReQL tests from the
-RethinkDB repo, which are programmatically translated from Python to
-C++. As of 34dc13c, all tests pass:
-
-```
-$ make test
-...
-SUCCESS: 2053 tests passed
-```

+ 0 - 80
ext/librethinkdbxx/reql/add_docs.py

@@ -1,80 +0,0 @@
-from sys import stdin, stderr, stdout, argv
-from re import match, sub
-
-docs = {}
-
-for line in open(argv[1]):
-    res = match('^\t\(([^,]*), (.*)\),$', line)
-    if res:
-        fullname = res.group(1)
-        docs[fullname.split('.')[-1]] = eval(res.group(2)).decode('utf-8')
-
-translate_name = {
-    'name': None,
-    'delete_': 'delete',
-    'union_': 'union',
-    'operator[]': '__getitem__',
-    'operator+': '__add__',
-    'operator-': '__sub__',
-    'operator*': '__mul__',
-    'operator/': '__div__',
-    'operator%': '__mod__',
-    'operator&&': 'and_',
-    'operator||': 'or_',
-    'operator==': '__eq__',
-    'operator!=': '__ne__',
-    'operator>': '__gt__',
-    'operator>=': '__ge__',
-    'operator<': '__lt__',
-    'operator<=': '__le__',
-    'operator!': 'not_',
-    'default_': 'default',
-    'array': None,
-    'desc': None,
-    'asc': None,
-    'maxval': None,
-    'minval': None,
-    'january': None,
-    'february': None,
-    'march': None,
-    'april': None,
-    'may': None,
-    'june': None,
-    'july': None,
-    'august': None,
-    'september': None,
-    'october': None,
-    'november': None,
-    'december': None,
-    'monday': None,
-    'tuesday': None,
-    'wednesday': None,
-    'thursday': None,
-    'friday': None,
-    'saturday': None,
-    'sunday': None,
-}
-
-def print_docs(name, line):
-    py_name = translate_name.get(name, name)
-    if py_name in docs:
-        indent = match("^( *)", line).group(1)
-        stdout.write('\n')
-        # TODO: convert the examples to C++
-        for line in docs[py_name].split('\n'):
-            stdout.write(indent + "// " + line + '\n')
-    elif py_name:
-        stderr.write('Warning: no docs for ' + py_name + ': ' + line)
-
-stdout.write('// Contains documentation copied as-is from the Python driver')
-
-for line in stdin:
-    res = match("^ *CO?[0-9_]+\(([^,)]+)|extern Query (\w+)|^ *// *(\$)doc\((\w+)\) *$", line)
-    if res:
-        name = res.group(1) or res.group(2) or res.group(4)
-        print_docs(name, line)
-        if not res.group(3):
-            stdout.write(line)
-    else:
-        stdout.write(line)
-

+ 0 - 33
ext/librethinkdbxx/reql/gen.py

@@ -1,33 +0,0 @@
-from sys import argv
-from re import sub, finditer, VERBOSE
-
-def gen(defs):
-    indent = 0
-    enum = False
-    def p(s): print(" " * (indent * 4) + s)
-    for item in finditer("""
-        (?P<type> message|enum) \\s+ (?P<name> \\w+) \\s* \\{ |
-        (?P<var> \\w+) \\s* = \\s* (?P<val> \\w+) \\s* ; |
-        \\}
-        """, defs, flags=VERBOSE):
-        if item.group(0) == "}":
-            indent = indent - 1
-            p("};" if enum else "}")
-            enum = False;
-        elif item.group('type') == 'enum':
-            p("enum class %s {" % item.group('name'))
-            indent = indent + 1
-            enum = True
-        elif item.group('type') == 'message':
-            p("namespace %s {" % item.group('name'))
-            indent = indent + 1
-            enum = False
-        else:
-            if enum:
-                p("%s = %s," % (item.group('var'), item.group('val')))
-
-print("// Auto-generated by reql/gen.py")
-print("#pragma once")
-print("namespace RethinkDB { namespace Protocol {")
-gen(sub("//.*", "", open(argv[1]).read()))
-print("} }")

File diff ditekan karena terlalu besar
+ 0 - 13
ext/librethinkdbxx/reql/python_docs.txt


+ 0 - 843
ext/librethinkdbxx/reql/ql2.proto

@@ -1,843 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-//                            THE HIGH-LEVEL VIEW                             //
-////////////////////////////////////////////////////////////////////////////////
-
-// Process: When you first open a connection, send the magic number
-// for the version of the protobuf you're targeting (in the [Version]
-// enum).  This should **NOT** be sent as a protobuf; just send the
-// little-endian 32-bit integer over the wire raw.  This number should
-// only be sent once per connection.
-
-// The magic number shall be followed by an authorization key.  The
-// first 4 bytes are the length of the key to be sent as a little-endian
-// 32-bit integer, followed by the key string.  Even if there is no key,
-// an empty string should be sent (length 0 and no data).
-
-// Following the authorization key, the client shall send a magic number
-// for the communication protocol they want to use (in the [Protocol]
-// enum).  This shall be a little-endian 32-bit integer.
-
-// The server will then respond with a NULL-terminated string response.
-// "SUCCESS" indicates that the connection has been accepted. Any other
-// response indicates an error, and the response string should describe
-// the error.
-
-// Next, for each query you want to send, construct a [Query] protobuf
-// and serialize it to a binary blob.  Send the blob's size to the
-// server encoded as a little-endian 32-bit integer, followed by the
-// blob itself.  You will recieve a [Response] protobuf back preceded
-// by its own size, once again encoded as a little-endian 32-bit
-// integer.  You can see an example exchange below in **EXAMPLE**.
-
-// A query consists of a [Term] to evaluate and a unique-per-connection
-// [token].
-
-// Tokens are used for two things:
-// * Keeping track of which responses correspond to which queries.
-// * Batched queries.  Some queries return lots of results, so we send back
-//   batches of <1000, and you need to send a [CONTINUE] query with the same
-//   token to get more results from the original query.
-////////////////////////////////////////////////////////////////////////////////
-
-message VersionDummy { // We need to wrap it like this for some
-                       // non-conforming protobuf libraries
-    // This enum contains the magic numbers for your version.  See **THE HIGH-LEVEL
-    // VIEW** for what to do with it.
-    enum Version {
-        V0_1      = 0x3f61ba36;
-        V0_2      = 0x723081e1; // Authorization key during handshake
-        V0_3      = 0x5f75e83e; // Authorization key and protocol during handshake
-        V0_4      = 0x400c2d20; // Queries execute in parallel
-        V1_0      = 0x34c2bdc3; // Users and permissions
-    }
-
-    // The protocol to use after the handshake, specified in V0_3
-    enum Protocol {
-        PROTOBUF  = 0x271ffc41;
-        JSON      = 0x7e6970c7;
-    }
-}
-
-// You send one of:
-// * A [START] query with a [Term] to evaluate and a unique-per-connection token.
-// * A [CONTINUE] query with the same token as a [START] query that returned
-//   [SUCCESS_PARTIAL] in its [Response].
-// * A [STOP] query with the same token as a [START] query that you want to stop.
-// * A [NOREPLY_WAIT] query with a unique per-connection token. The server answers
-//   with a [WAIT_COMPLETE] [Response].
-// * A [SERVER_INFO] query. The server answers with a [SERVER_INFO] [Response].
-message Query {
-    enum QueryType {
-        START        = 1; // Start a new query.
-        CONTINUE     = 2; // Continue a query that returned [SUCCESS_PARTIAL]
-                          // (see [Response]).
-        STOP         = 3; // Stop a query partway through executing.
-        NOREPLY_WAIT = 4; // Wait for noreply operations to finish.
-        SERVER_INFO  = 5; // Get server information.
-    }
-    optional QueryType type = 1;
-    // A [Term] is how we represent the operations we want a query to perform.
-    optional Term query = 2; // only present when [type] = [START]
-    optional int64 token = 3;
-    // This flag is ignored on the server.  `noreply` should be added
-    // to `global_optargs` instead (the key "noreply" should map to
-    // either true or false).
-    optional bool OBSOLETE_noreply = 4 [default = false];
-
-    // If this is set to [true], then [Datum] values will sometimes be
-    // of [DatumType] [R_JSON] (see below).  This can provide enormous
-    // speedups in languages with poor protobuf libraries.
-    optional bool accepts_r_json = 5 [default = false];
-
-    message AssocPair {
-        optional string key = 1;
-        optional Term val = 2;
-    }
-    repeated AssocPair global_optargs = 6;
-}
-
-// A backtrace frame (see `backtrace` in Response below)
-message Frame {
-    enum FrameType {
-        POS = 1; // Error occurred in a positional argument.
-        OPT = 2; // Error occurred in an optional argument.
-    }
-    optional FrameType type = 1;
-    optional int64 pos = 2; // The index of the positional argument.
-    optional string opt = 3; // The name of the optional argument.
-}
-message Backtrace {
-    repeated Frame frames = 1;
-}
-
-// You get back a response with the same [token] as your query.
-message Response {
-    enum ResponseType {
-        // These response types indicate success.
-        SUCCESS_ATOM      = 1; // Query returned a single RQL datatype.
-        SUCCESS_SEQUENCE  = 2; // Query returned a sequence of RQL datatypes.
-        SUCCESS_PARTIAL   = 3; // Query returned a partial sequence of RQL
-                               // datatypes.  If you send a [CONTINUE] query with
-                               // the same token as this response, you will get
-                               // more of the sequence.  Keep sending [CONTINUE]
-                               // queries until you get back [SUCCESS_SEQUENCE].
-        WAIT_COMPLETE     = 4; // A [NOREPLY_WAIT] query completed.
-        SERVER_INFO       = 5; // The data for a [SERVER_INFO] request.  This is
-                               // the same as `SUCCESS_ATOM` except that there will
-                               // never be profiling data.
-
-        // These response types indicate failure.
-        CLIENT_ERROR  = 16; // Means the client is buggy.  An example is if the
-                            // client sends a malformed protobuf, or tries to
-                            // send [CONTINUE] for an unknown token.
-        COMPILE_ERROR = 17; // Means the query failed during parsing or type
-                            // checking.  For example, if you pass too many
-                            // arguments to a function.
-        RUNTIME_ERROR = 18; // Means the query failed at runtime.  An example is
-                            // if you add together two values from a table, but
-                            // they turn out at runtime to be booleans rather
-                            // than numbers.
-    }
-    optional ResponseType type = 1;
-
-    // If `ResponseType` is `RUNTIME_ERROR`, this may be filled in with more
-    // information about the error.
-    enum ErrorType {
-        INTERNAL = 1000000;
-        RESOURCE_LIMIT = 2000000;
-        QUERY_LOGIC = 3000000;
-        NON_EXISTENCE = 3100000;
-        OP_FAILED = 4100000;
-        OP_INDETERMINATE = 4200000;
-        USER = 5000000;
-        PERMISSION_ERROR = 6000000;
-    }
-    optional ErrorType error_type = 7;
-
-    // ResponseNotes are used to provide information about the query
-    // response that may be useful for people writing drivers or ORMs.
-    // Currently all the notes we send indicate that a stream has certain
-    // special properties.
-    enum ResponseNote {
-        // The stream is a changefeed stream (e.g. `r.table('test').changes()`).
-        SEQUENCE_FEED = 1;
-        // The stream is a point changefeed stream
-        // (e.g. `r.table('test').get(0).changes()`).
-        ATOM_FEED = 2;
-        // The stream is an order_by_limit changefeed stream
-        // (e.g. `r.table('test').order_by(index: 'id').limit(5).changes()`).
-        ORDER_BY_LIMIT_FEED = 3;
-        // The stream is a union of multiple changefeed types that can't be
-        // collapsed to a single type
-        // (e.g. `r.table('test').changes().union(r.table('test').get(0).changes())`).
-        UNIONED_FEED = 4;
-        // The stream is a changefeed stream and includes notes on what state
-        // the changefeed stream is in (e.g. objects of the form `{state:
-        // 'initializing'}`).
-        INCLUDES_STATES = 5;
-    }
-    repeated ResponseNote notes = 6;
-
-    optional int64 token = 2; // Indicates what [Query] this response corresponds to.
-
-    // [response] contains 1 RQL datum if [type] is [SUCCESS_ATOM] or
-    // [SERVER_INFO].  [response] contains many RQL data if [type] is
-    // [SUCCESS_SEQUENCE] or [SUCCESS_PARTIAL].  [response] contains 1
-    // error message (of type [R_STR]) in all other cases.
-    repeated Datum response = 3;
-
-    // If [type] is [CLIENT_ERROR], [TYPE_ERROR], or [RUNTIME_ERROR], then a
-    // backtrace will be provided.  The backtrace says where in the query the
-    // error occurred.  Ideally this information will be presented to the user as
-    // a pretty-printed version of their query with the erroneous section
-    // underlined.  A backtrace is a series of 0 or more [Frame]s, each of which
-    // specifies either the index of a positional argument or the name of an
-    // optional argument.  (Those words will make more sense if you look at the
-    // [Term] message below.)
-    optional Backtrace backtrace = 4; // Contains n [Frame]s when you get back an error.
-
-    // If the [global_optargs] in the [Query] that this [Response] is a
-    // response to contains a key "profile" which maps to a static value of
-    // true then [profile] will contain a [Datum] which provides profiling
-    // information about the execution of the query. This field should be
-    // returned to the user along with the result that would normally be
-    // returned (a datum or a cursor). In official drivers this is accomplished
-    // by putting them inside of an object with "value" mapping to the return
-    // value and "profile" mapping to the profile object.
-    optional Datum profile = 5;
-}
-
-// A [Datum] is a chunk of data that can be serialized to disk or returned to
-// the user in a Response.  Currently we only support JSON types, but we may
-// support other types in the future (e.g., a date type or an integer type).
-message Datum {
-    enum DatumType {
-        R_NULL   = 1;
-        R_BOOL   = 2;
-        R_NUM    = 3; // a double
-        R_STR    = 4;
-        R_ARRAY  = 5;
-        R_OBJECT = 6;
-        // This [DatumType] will only be used if [accepts_r_json] is
-        // set to [true] in [Query].  [r_str] will be filled with a
-        // JSON encoding of the [Datum].
-        R_JSON   = 7; // uses r_str
-    }
-    optional DatumType type = 1;
-    optional bool r_bool = 2;
-    optional double r_num = 3;
-    optional string r_str = 4;
-
-    repeated Datum r_array = 5;
-    message AssocPair {
-        optional string key = 1;
-        optional Datum val = 2;
-    }
-    repeated AssocPair r_object = 6;
-}
-
-// A [Term] is either a piece of data (see **Datum** above), or an operator and
-// its operands.  If you have a [Datum], it's stored in the member [datum].  If
-// you have an operator, its positional arguments are stored in [args] and its
-// optional arguments are stored in [optargs].
-//
-// A note about type signatures:
-// We use the following notation to denote types:
-//   arg1_type, arg2_type, argrest_type... -> result_type
-// So, for example, if we have a function `avg` that takes any number of
-// arguments and averages them, we might write:
-//   NUMBER... -> NUMBER
-// Or if we had a function that took one number modulo another:
-//   NUMBER, NUMBER -> NUMBER
-// Or a function that takes a table and a primary key of any Datum type, then
-// retrieves the entry with that primary key:
-//   Table, DATUM -> OBJECT
-// Some arguments must be provided as literal values (and not the results of sub
-// terms).  These are marked with a `!`.
-// Optional arguments are specified within curly braces as argname `:` value
-// type (e.x `{noreply:BOOL}`)
-// Many RQL operations are polymorphic. For these, alterantive type signatures
-// are separated by `|`.
-//
-// The RQL type hierarchy is as follows:
-//   Top
-//     DATUM
-//       NULL
-//       BOOL
-//       NUMBER
-//       STRING
-//       OBJECT
-//         SingleSelection
-//       ARRAY
-//     Sequence
-//       ARRAY
-//       Stream
-//         StreamSelection
-//           Table
-//     Database
-//     Function
-//     Ordering - used only by ORDER_BY
-//     Pathspec -- an object, string, or array that specifies a path
-//   Error
-message Term {
-    enum TermType {
-        // A RQL datum, stored in `datum` below.
-        DATUM = 1;
-
-        MAKE_ARRAY = 2; // DATUM... -> ARRAY
-        // Evaluate the terms in [optargs] and make an object
-        MAKE_OBJ   = 3; // {...} -> OBJECT
-
-        // * Compound types
-
-        // Takes an integer representing a variable and returns the value stored
-        // in that variable.  It's the responsibility of the client to translate
-        // from their local representation of a variable to a unique _non-negative_
-        // integer for that variable.  (We do it this way instead of letting
-        // clients provide variable names as strings to discourage
-        // variable-capturing client libraries, and because it's more efficient
-        // on the wire.)
-        VAR          = 10; // !NUMBER -> DATUM
-        // Takes some javascript code and executes it.
-        JAVASCRIPT   = 11; // STRING {timeout: !NUMBER} -> DATUM |
-                           // STRING {timeout: !NUMBER} -> Function(*)
-        UUID = 169; // () -> DATUM
-
-        // Takes an HTTP URL and gets it.  If the get succeeds and
-        //  returns valid JSON, it is converted into a DATUM
-        HTTP = 153; // STRING {data: OBJECT | STRING,
-                    //         timeout: !NUMBER,
-                    //         method: STRING,
-                    //         params: OBJECT,
-                    //         header: OBJECT | ARRAY,
-                    //         attempts: NUMBER,
-                    //         redirects: NUMBER,
-                    //         verify: BOOL,
-                    //         page: FUNC | STRING,
-                    //         page_limit: NUMBER,
-                    //         auth: OBJECT,
-                    //         result_format: STRING,
-                    //         } -> STRING | STREAM
-
-        // Takes a string and throws an error with that message.
-        // Inside of a `default` block, you can omit the first
-        // argument to rethrow whatever error you catch (this is most
-        // useful as an argument to the `default` filter optarg).
-        ERROR        = 12; // STRING -> Error | -> Error
-        // Takes nothing and returns a reference to the implicit variable.
-        IMPLICIT_VAR = 13; // -> DATUM
-
-        // * Data Operators
-        // Returns a reference to a database.
-        DB    = 14; // STRING -> Database
-        // Returns a reference to a table.
-        TABLE = 15; // Database, STRING, {read_mode:STRING, identifier_format:STRING} -> Table
-                    // STRING, {read_mode:STRING, identifier_format:STRING} -> Table
-        // Gets a single element from a table by its primary or a secondary key.
-        GET   = 16; // Table, STRING -> SingleSelection | Table, NUMBER -> SingleSelection |
-                    // Table, STRING -> NULL            | Table, NUMBER -> NULL |
-        GET_ALL = 78; // Table, DATUM..., {index:!STRING} => ARRAY
-
-        // Simple DATUM Ops
-        EQ  = 17; // DATUM... -> BOOL
-        NE  = 18; // DATUM... -> BOOL
-        LT  = 19; // DATUM... -> BOOL
-        LE  = 20; // DATUM... -> BOOL
-        GT  = 21; // DATUM... -> BOOL
-        GE  = 22; // DATUM... -> BOOL
-        NOT = 23; // BOOL -> BOOL
-        // ADD can either add two numbers or concatenate two arrays.
-        ADD = 24; // NUMBER... -> NUMBER | STRING... -> STRING
-        SUB = 25; // NUMBER... -> NUMBER
-        MUL = 26; // NUMBER... -> NUMBER
-        DIV = 27; // NUMBER... -> NUMBER
-        MOD = 28; // NUMBER, NUMBER -> NUMBER
-
-        FLOOR = 183;    // NUMBER -> NUMBER
-        CEIL = 184;     // NUMBER -> NUMBER
-        ROUND = 185;    // NUMBER -> NUMBER
-
-        // DATUM Array Ops
-        // Append a single element to the end of an array (like `snoc`).
-        APPEND = 29; // ARRAY, DATUM -> ARRAY
-        // Prepend a single element to the end of an array (like `cons`).
-        PREPEND = 80; // ARRAY, DATUM -> ARRAY
-        //Remove the elements of one array from another array.
-        DIFFERENCE = 95; // ARRAY, ARRAY -> ARRAY
-
-        // DATUM Set Ops
-        // Set ops work on arrays. They don't use actual sets and thus have
-        // performance characteristics you would expect from arrays rather than
-        // from sets. All set operations have the post condition that they
-        // array they return contains no duplicate values.
-        SET_INSERT = 88; // ARRAY, DATUM -> ARRAY
-        SET_INTERSECTION = 89; // ARRAY, ARRAY -> ARRAY
-        SET_UNION = 90; // ARRAY, ARRAY -> ARRAY
-        SET_DIFFERENCE = 91; // ARRAY, ARRAY -> ARRAY
-
-        SLICE  = 30; // Sequence, NUMBER, NUMBER -> Sequence
-        SKIP  = 70; // Sequence, NUMBER -> Sequence
-        LIMIT = 71; // Sequence, NUMBER -> Sequence
-        OFFSETS_OF = 87; // Sequence, DATUM -> Sequence | Sequence, Function(1) -> Sequence
-        CONTAINS = 93; // Sequence, (DATUM | Function(1))... -> BOOL
-
-        // Stream/Object Ops
-        // Get a particular field from an object, or map that over a
-        // sequence.
-        GET_FIELD  = 31; // OBJECT, STRING -> DATUM
-                         // | Sequence, STRING -> Sequence
-        // Return an array containing the keys of the object.
-        KEYS = 94; // OBJECT -> ARRAY
-        // Return an array containing the values of the object.
-        VALUES = 186; // OBJECT -> ARRAY
-        // Creates an object
-        OBJECT = 143; // STRING, DATUM, ... -> OBJECT
-        // Check whether an object contains all the specified fields,
-        // or filters a sequence so that all objects inside of it
-        // contain all the specified fields.
-        HAS_FIELDS = 32; // OBJECT, Pathspec... -> BOOL
-        // x.with_fields(...) <=> x.has_fields(...).pluck(...)
-        WITH_FIELDS = 96; // Sequence, Pathspec... -> Sequence
-        // Get a subset of an object by selecting some attributes to preserve,
-        // or map that over a sequence.  (Both pick and pluck, polymorphic.)
-        PLUCK    = 33; // Sequence, Pathspec... -> Sequence | OBJECT, Pathspec... -> OBJECT
-        // Get a subset of an object by selecting some attributes to discard, or
-        // map that over a sequence.  (Both unpick and without, polymorphic.)
-        WITHOUT  = 34; // Sequence, Pathspec... -> Sequence | OBJECT, Pathspec... -> OBJECT
-        // Merge objects (right-preferential)
-        MERGE    = 35; // OBJECT... -> OBJECT | Sequence -> Sequence
-
-        // Sequence Ops
-        // Get all elements of a sequence between two values.
-        // Half-open by default, but the openness of either side can be
-        // changed by passing 'closed' or 'open for `right_bound` or
-        // `left_bound`.
-        BETWEEN_DEPRECATED = 36; // Deprecated version of between, which allows `null` to specify unboundedness
-                                 // With the newer version, clients should use `r.minval` and `r.maxval` for unboundedness
-        BETWEEN   = 182; // StreamSelection, DATUM, DATUM, {index:!STRING, right_bound:STRING, left_bound:STRING} -> StreamSelection
-        REDUCE    = 37; // Sequence, Function(2) -> DATUM
-        MAP       = 38; // Sequence, Function(1) -> Sequence
-                        // The arity of the function should be
-                        // Sequence..., Function(sizeof...(Sequence)) -> Sequence
-
-        FOLD      = 187; // Sequence, Datum, Function(2), {Function(3), Function(1)
-
-        // Filter a sequence with either a function or a shortcut
-        // object (see API docs for details).  The body of FILTER is
-        // wrapped in an implicit `.default(false)`, and you can
-        // change the default value by specifying the `default`
-        // optarg.  If you make the default `r.error`, all errors
-        // caught by `default` will be rethrown as if the `default`
-        // did not exist.
-        FILTER    = 39; // Sequence, Function(1), {default:DATUM} -> Sequence |
-                        // Sequence, OBJECT, {default:DATUM} -> Sequence
-        // Map a function over a sequence and then concatenate the results together.
-        CONCAT_MAP = 40; // Sequence, Function(1) -> Sequence
-        // Order a sequence based on one or more attributes.
-        ORDER_BY   = 41; // Sequence, (!STRING | Ordering)..., {index: (!STRING | Ordering)} -> Sequence
-        // Get all distinct elements of a sequence (like `uniq`).
-        DISTINCT  = 42; // Sequence -> Sequence
-        // Count the number of elements in a sequence, or only the elements that match
-        // a given filter.
-        COUNT     = 43; // Sequence -> NUMBER | Sequence, DATUM -> NUMBER | Sequence, Function(1) -> NUMBER
-        IS_EMPTY = 86; // Sequence -> BOOL
-        // Take the union of multiple sequences (preserves duplicate elements! (use distinct)).
-        UNION     = 44; // Sequence... -> Sequence
-        // Get the Nth element of a sequence.
-        NTH       = 45; // Sequence, NUMBER -> DATUM
-        // do NTH or GET_FIELD depending on target object
-        BRACKET            = 170; // Sequence | OBJECT, NUMBER | STRING -> DATUM
-        // OBSOLETE_GROUPED_MAPREDUCE = 46;
-        // OBSOLETE_GROUPBY = 47;
-
-        INNER_JOIN         = 48; // Sequence, Sequence, Function(2) -> Sequence
-        OUTER_JOIN         = 49; // Sequence, Sequence, Function(2) -> Sequence
-        // An inner-join that does an equality comparison on two attributes.
-        EQ_JOIN            = 50; // Sequence, !STRING, Sequence, {index:!STRING} -> Sequence
-        ZIP                = 72; // Sequence -> Sequence
-        RANGE              = 173; // -> Sequence                        [0, +inf)
-                                  // NUMBER -> Sequence                 [0, a)
-                                  // NUMBER, NUMBER -> Sequence         [a, b)
-
-        // Array Ops
-        // Insert an element in to an array at a given index.
-        INSERT_AT          = 82; // ARRAY, NUMBER, DATUM -> ARRAY
-        // Remove an element at a given index from an array.
-        DELETE_AT          = 83; // ARRAY, NUMBER -> ARRAY |
-                                 // ARRAY, NUMBER, NUMBER -> ARRAY
-        // Change the element at a given index of an array.
-        CHANGE_AT          = 84; // ARRAY, NUMBER, DATUM -> ARRAY
-        // Splice one array in to another array.
-        SPLICE_AT          = 85; // ARRAY, NUMBER, ARRAY -> ARRAY
-
-        // * Type Ops
-        // Coerces a datum to a named type (e.g. "bool").
-        // If you previously used `stream_to_array`, you should use this instead
-        // with the type "array".
-        COERCE_TO = 51; // Top, STRING -> Top
-        // Returns the named type of a datum (e.g. TYPE_OF(true) = "BOOL")
-        TYPE_OF = 52; // Top -> STRING
-
-        // * Write Ops (the OBJECTs contain data about number of errors etc.)
-        // Updates all the rows in a selection.  Calls its Function with the row
-        // to be updated, and then merges the result of that call.
-        UPDATE   = 53; // StreamSelection, Function(1), {non_atomic:BOOL, durability:STRING, return_changes:BOOL} -> OBJECT |
-                       // SingleSelection, Function(1), {non_atomic:BOOL, durability:STRING, return_changes:BOOL} -> OBJECT |
-                       // StreamSelection, OBJECT,      {non_atomic:BOOL, durability:STRING, return_changes:BOOL} -> OBJECT |
-                       // SingleSelection, OBJECT,      {non_atomic:BOOL, durability:STRING, return_changes:BOOL} -> OBJECT
-        // Deletes all the rows in a selection.
-        DELETE   = 54; // StreamSelection, {durability:STRING, return_changes:BOOL} -> OBJECT | SingleSelection -> OBJECT
-        // Replaces all the rows in a selection.  Calls its Function with the row
-        // to be replaced, and then discards it and stores the result of that
-        // call.
-        REPLACE  = 55; // StreamSelection, Function(1), {non_atomic:BOOL, durability:STRING, return_changes:BOOL} -> OBJECT | SingleSelection, Function(1), {non_atomic:BOOL, durability:STRING, return_changes:BOOL} -> OBJECT
-        // Inserts into a table.  If `conflict` is replace, overwrites
-        // entries with the same primary key.  If `conflict` is
-        // update, does an update on the entry.  If `conflict` is
-        // error, or is omitted, conflicts will trigger an error.
-        INSERT   = 56; // Table, OBJECT, {conflict:STRING, durability:STRING, return_changes:BOOL} -> OBJECT | Table, Sequence, {conflict:STRING, durability:STRING, return_changes:BOOL} -> OBJECT
-
-        // * Administrative OPs
-        // Creates a database with a particular name.
-        DB_CREATE     = 57; // STRING -> OBJECT
-        // Drops a database with a particular name.
-        DB_DROP       = 58; // STRING -> OBJECT
-        // Lists all the databases by name.  (Takes no arguments)
-        DB_LIST       = 59; // -> ARRAY
-        // Creates a table with a particular name in a particular
-        // database.  (You may omit the first argument to use the
-        // default database.)
-        TABLE_CREATE  = 60; // Database, STRING, {primary_key:STRING, shards:NUMBER, replicas:NUMBER, primary_replica_tag:STRING} -> OBJECT
-                            // Database, STRING, {primary_key:STRING, shards:NUMBER, replicas:OBJECT, primary_replica_tag:STRING} -> OBJECT
-                            // STRING, {primary_key:STRING, shards:NUMBER, replicas:NUMBER, primary_replica_tag:STRING} -> OBJECT
-                            // STRING, {primary_key:STRING, shards:NUMBER, replicas:OBJECT, primary_replica_tag:STRING} -> OBJECT
-        // Drops a table with a particular name from a particular
-        // database.  (You may omit the first argument to use the
-        // default database.)
-        TABLE_DROP    = 61; // Database, STRING -> OBJECT
-                                // STRING -> OBJECT
-        // Lists all the tables in a particular database.  (You may
-        // omit the first argument to use the default database.)
-        TABLE_LIST    = 62; // Database -> ARRAY
-                            //  -> ARRAY
-        // Returns the row in the `rethinkdb.table_config` or `rethinkdb.db_config` table
-        // that corresponds to the given database or table.
-        CONFIG  = 174; // Database -> SingleSelection
-                       // Table -> SingleSelection
-        // Returns the row in the `rethinkdb.table_status` table that corresponds to the
-        // given table.
-        STATUS  = 175; // Table -> SingleSelection
-        // Called on a table, waits for that table to be ready for read/write operations.
-        // Called on a database, waits for all of the tables in the database to be ready.
-        // Returns the corresponding row or rows from the `rethinkdb.table_status` table.
-        WAIT    = 177; // Table -> OBJECT
-                       // Database -> OBJECT
-        // Generates a new config for the given table, or all tables in the given database
-        // The `shards` and `replicas` arguments are required. If `emergency_repair` is
-        // specified, it will enter a completely different mode of repairing a table
-        // which has lost half or more of its replicas.
-        RECONFIGURE   = 176; // Database|Table, {shards:NUMBER, replicas:NUMBER [,
-                             //                  dry_run:BOOLEAN]
-                             //                 } -> OBJECT
-                             // Database|Table, {shards:NUMBER, replicas:OBJECT [,
-                             //                  primary_replica_tag:STRING,
-                             //                  nonvoting_replica_tags:ARRAY,
-                             //                  dry_run:BOOLEAN]
-                             //                 } -> OBJECT
-                             // Table, {emergency_repair:STRING, dry_run:BOOLEAN} -> OBJECT
-        // Balances the table's shards but leaves everything else the same. Can also be
-        // applied to an entire database at once.
-        REBALANCE     = 179; // Table -> OBJECT
-                             // Database -> OBJECT
-
-        // Ensures that previously issued soft-durability writes are complete and
-        // written to disk.
-        SYNC          = 138; // Table -> OBJECT
-
-        // Set global, database, or table-specific permissions
-        GRANT         = 188; //          -> OBJECT
-                             // Database -> OBJECT
-                             // Table    -> OBJECT
-
-        // * Secondary indexes OPs
-        // Creates a new secondary index with a particular name and definition.
-        INDEX_CREATE = 75; // Table, STRING, Function(1), {multi:BOOL} -> OBJECT
-        // Drops a secondary index with a particular name from the specified table.
-        INDEX_DROP   = 76; // Table, STRING -> OBJECT
-        // Lists all secondary indexes on a particular table.
-        INDEX_LIST   = 77; // Table -> ARRAY
-        // Gets information about whether or not a set of indexes are ready to
-        // be accessed. Returns a list of objects that look like this:
-        // {index:STRING, ready:BOOL[, progress:NUMBER]}
-        INDEX_STATUS = 139; // Table, STRING... -> ARRAY
-        // Blocks until a set of indexes are ready to be accessed. Returns the
-        // same values INDEX_STATUS.
-        INDEX_WAIT = 140; // Table, STRING... -> ARRAY
-        // Renames the given index to a new name
-        INDEX_RENAME = 156; // Table, STRING, STRING, {overwrite:BOOL} -> OBJECT
-
-        // * Control Operators
-        // Calls a function on data
-        FUNCALL  = 64; // Function(*), DATUM... -> DATUM
-        // Executes its first argument, and returns its second argument if it
-        // got [true] or its third argument if it got [false] (like an `if`
-        // statement).
-        BRANCH  = 65; // BOOL, Top, Top -> Top
-        // Returns true if any of its arguments returns true (short-circuits).
-        OR      = 66; // BOOL... -> BOOL
-        // Returns true if all of its arguments return true (short-circuits).
-        AND     = 67; // BOOL... -> BOOL
-        // Calls its Function with each entry in the sequence
-        // and executes the array of terms that Function returns.
-        FOR_EACH = 68; // Sequence, Function(1) -> OBJECT
-
-////////////////////////////////////////////////////////////////////////////////
-////////// Special Terms
-////////////////////////////////////////////////////////////////////////////////
-
-        // An anonymous function.  Takes an array of numbers representing
-        // variables (see [VAR] above), and a [Term] to execute with those in
-        // scope.  Returns a function that may be passed an array of arguments,
-        // then executes the Term with those bound to the variable names.  The
-        // user will never construct this directly.  We use it internally for
-        // things like `map` which take a function.  The "arity" of a [Function] is
-        // the number of arguments it takes.
-        // For example, here's what `_X_.map{|x| x+2}` turns into:
-        // Term {
-        //   type = MAP;
-        //   args = [_X_,
-        //           Term {
-        //             type = Function;
-        //             args = [Term {
-        //                       type = DATUM;
-        //                       datum = Datum {
-        //                         type = R_ARRAY;
-        //                         r_array = [Datum { type = R_NUM; r_num = 1; }];
-        //                       };
-        //                     },
-        //                     Term {
-        //                       type = ADD;
-        //                       args = [Term {
-        //                                 type = VAR;
-        //                                 args = [Term {
-        //                                           type = DATUM;
-        //                                           datum = Datum { type = R_NUM;
-        //                                                           r_num = 1};
-        //                                         }];
-        //                               },
-        //                               Term {
-        //                                 type = DATUM;
-        //                                 datum = Datum { type = R_NUM; r_num = 2; };
-        //                               }];
-        //                     }];
-        //           }];
-        FUNC = 69; // ARRAY, Top -> ARRAY -> Top
-
-        // Indicates to ORDER_BY that this attribute is to be sorted in ascending order.
-        ASC = 73; // !STRING -> Ordering
-        // Indicates to ORDER_BY that this attribute is to be sorted in descending order.
-        DESC = 74; // !STRING -> Ordering
-
-        // Gets info about anything.  INFO is most commonly called on tables.
-        INFO = 79; // Top -> OBJECT
-
-        // `a.match(b)` returns a match object if the string `a`
-        // matches the regular expression `b`.
-        MATCH = 97; // STRING, STRING -> DATUM
-
-        // Change the case of a string.
-        UPCASE   = 141; // STRING -> STRING
-        DOWNCASE = 142; // STRING -> STRING
-
-        // Select a number of elements from sequence with uniform distribution.
-        SAMPLE = 81; // Sequence, NUMBER -> Sequence
-
-        // Evaluates its first argument.  If that argument returns
-        // NULL or throws an error related to the absence of an
-        // expected value (for instance, accessing a non-existent
-        // field or adding NULL to an integer), DEFAULT will either
-        // return its second argument or execute it if it's a
-        // function.  If the second argument is a function, it will be
-        // passed either the text of the error or NULL as its
-        // argument.
-        DEFAULT = 92; // Top, Top -> Top
-
-        // Parses its first argument as a json string and returns it as a
-        // datum.
-        JSON = 98; // STRING -> DATUM
-        // Returns the datum as a JSON string.
-        // N.B.: we would really prefer this be named TO_JSON and that exists as
-        // an alias in Python and JavaScript drivers; however it conflicts with the
-        // standard `to_json` method defined by Ruby's standard json library.
-        TO_JSON_STRING = 172; // DATUM -> STRING
-
-        // Parses its first arguments as an ISO 8601 time and returns it as a
-        // datum.
-        ISO8601 = 99; // STRING -> PSEUDOTYPE(TIME)
-        // Prints a time as an ISO 8601 time.
-        TO_ISO8601 = 100; // PSEUDOTYPE(TIME) -> STRING
-
-        // Returns a time given seconds since epoch in UTC.
-        EPOCH_TIME = 101; // NUMBER -> PSEUDOTYPE(TIME)
-        // Returns seconds since epoch in UTC given a time.
-        TO_EPOCH_TIME = 102; // PSEUDOTYPE(TIME) -> NUMBER
-
-        // The time the query was received by the server.
-        NOW = 103; // -> PSEUDOTYPE(TIME)
-        // Puts a time into an ISO 8601 timezone.
-        IN_TIMEZONE = 104; // PSEUDOTYPE(TIME), STRING -> PSEUDOTYPE(TIME)
-        // a.during(b, c) returns whether a is in the range [b, c)
-        DURING = 105; // PSEUDOTYPE(TIME), PSEUDOTYPE(TIME), PSEUDOTYPE(TIME) -> BOOL
-        // Retrieves the date portion of a time.
-        DATE = 106; // PSEUDOTYPE(TIME) -> PSEUDOTYPE(TIME)
-        // x.time_of_day == x.date - x
-        TIME_OF_DAY = 126; // PSEUDOTYPE(TIME) -> NUMBER
-        // Returns the timezone of a time.
-        TIMEZONE = 127; // PSEUDOTYPE(TIME) -> STRING
-
-        // These access the various components of a time.
-        YEAR = 128; // PSEUDOTYPE(TIME) -> NUMBER
-        MONTH = 129; // PSEUDOTYPE(TIME) -> NUMBER
-        DAY = 130; // PSEUDOTYPE(TIME) -> NUMBER
-        DAY_OF_WEEK = 131; // PSEUDOTYPE(TIME) -> NUMBER
-        DAY_OF_YEAR = 132; // PSEUDOTYPE(TIME) -> NUMBER
-        HOURS = 133; // PSEUDOTYPE(TIME) -> NUMBER
-        MINUTES = 134; // PSEUDOTYPE(TIME) -> NUMBER
-        SECONDS = 135; // PSEUDOTYPE(TIME) -> NUMBER
-
-        // Construct a time from a date and optional timezone or a
-        // date+time and optional timezone.
-        TIME = 136; // NUMBER, NUMBER, NUMBER, STRING -> PSEUDOTYPE(TIME) |
-                    // NUMBER, NUMBER, NUMBER, NUMBER, NUMBER, NUMBER, STRING -> PSEUDOTYPE(TIME) |
-
-        // Constants for ISO 8601 days of the week.
-        MONDAY = 107;    // -> 1
-        TUESDAY = 108;   // -> 2
-        WEDNESDAY = 109; // -> 3
-        THURSDAY = 110;  // -> 4
-        FRIDAY = 111;    // -> 5
-        SATURDAY = 112;  // -> 6
-        SUNDAY = 113;    // -> 7
-
-        // Constants for ISO 8601 months.
-        JANUARY = 114;   // -> 1
-        FEBRUARY = 115;  // -> 2
-        MARCH = 116;     // -> 3
-        APRIL = 117;     // -> 4
-        MAY = 118;       // -> 5
-        JUNE = 119;      // -> 6
-        JULY = 120;      // -> 7
-        AUGUST = 121;    // -> 8
-        SEPTEMBER = 122; // -> 9
-        OCTOBER = 123;   // -> 10
-        NOVEMBER = 124;  // -> 11
-        DECEMBER = 125;  // -> 12
-
-        // Indicates to MERGE to replace, or remove in case of an empty literal, the
-        // other object rather than merge it.
-        LITERAL = 137; // -> Merging
-                       // JSON -> Merging
-
-        // SEQUENCE, STRING -> GROUPED_SEQUENCE | SEQUENCE, FUNCTION -> GROUPED_SEQUENCE
-        GROUP = 144;
-        SUM = 145;
-        AVG = 146;
-        MIN = 147;
-        MAX = 148;
-
-        // `str.split()` splits on whitespace
-        // `str.split(" ")` splits on spaces only
-        // `str.split(" ", 5)` splits on spaces with at most 5 results
-        // `str.split(nil, 5)` splits on whitespace with at most 5 results
-        SPLIT = 149; // STRING -> ARRAY | STRING, STRING -> ARRAY | STRING, STRING, NUMBER -> ARRAY | STRING, NULL, NUMBER -> ARRAY
-
-        UNGROUP = 150; // GROUPED_DATA -> ARRAY
-
-        // Takes a range of numbers and returns a random number within the range
-        RANDOM = 151; // NUMBER, NUMBER {float:BOOL} -> DATUM
-
-        CHANGES = 152; // TABLE -> STREAM
-        ARGS = 154; // ARRAY -> SPECIAL (used to splice arguments)
-
-        // BINARY is client-only at the moment, it is not supported on the server
-        BINARY = 155; // STRING -> PSEUDOTYPE(BINARY)
-
-        GEOJSON = 157;           // OBJECT -> PSEUDOTYPE(GEOMETRY)
-        TO_GEOJSON = 158;        // PSEUDOTYPE(GEOMETRY) -> OBJECT
-        POINT = 159;             // NUMBER, NUMBER -> PSEUDOTYPE(GEOMETRY)
-        LINE = 160;              // (ARRAY | PSEUDOTYPE(GEOMETRY))... -> PSEUDOTYPE(GEOMETRY)
-        POLYGON = 161;           // (ARRAY | PSEUDOTYPE(GEOMETRY))... -> PSEUDOTYPE(GEOMETRY)
-        DISTANCE = 162;          // PSEUDOTYPE(GEOMETRY), PSEUDOTYPE(GEOMETRY) {geo_system:STRING, unit:STRING} -> NUMBER
-        INTERSECTS = 163;        // PSEUDOTYPE(GEOMETRY), PSEUDOTYPE(GEOMETRY) -> BOOL
-        INCLUDES = 164;          // PSEUDOTYPE(GEOMETRY), PSEUDOTYPE(GEOMETRY) -> BOOL
-        CIRCLE = 165;            // PSEUDOTYPE(GEOMETRY), NUMBER {num_vertices:NUMBER, geo_system:STRING, unit:STRING, fill:BOOL} -> PSEUDOTYPE(GEOMETRY)
-        GET_INTERSECTING = 166;  // TABLE, PSEUDOTYPE(GEOMETRY) {index:!STRING} -> StreamSelection
-        FILL = 167;              // PSEUDOTYPE(GEOMETRY) -> PSEUDOTYPE(GEOMETRY)
-        GET_NEAREST = 168;       // TABLE, PSEUDOTYPE(GEOMETRY) {index:!STRING, max_results:NUM, max_dist:NUM, geo_system:STRING, unit:STRING} -> ARRAY
-        POLYGON_SUB = 171;       // PSEUDOTYPE(GEOMETRY), PSEUDOTYPE(GEOMETRY) -> PSEUDOTYPE(GEOMETRY)
-
-        // Constants for specifying key ranges
-        MINVAL = 180;
-        MAXVAL = 181;
-    }
-    optional TermType type = 1;
-
-    // This is only used when type is DATUM.
-    optional Datum datum = 2;
-
-    repeated Term args = 3; // Holds the positional arguments of the query.
-    message AssocPair {
-        optional string key = 1;
-        optional Term val = 2;
-    }
-    repeated AssocPair optargs = 4; // Holds the optional arguments of the query.
-    // (Note that the order of the optional arguments doesn't matter; think of a
-    // Hash.)
-}
-
-////////////////////////////////////////////////////////////////////////////////
-//                                  EXAMPLE                                   //
-////////////////////////////////////////////////////////////////////////////////
-//   ```ruby
-//   r.table('tbl', {:read_mode => 'outdated'}).insert([{:id => 0}, {:id => 1}])
-//   ```
-// Would turn into:
-//   Term {
-//     type = INSERT;
-//     args = [Term {
-//               type = TABLE;
-//               args = [Term {
-//                         type = DATUM;
-//                         datum = Datum { type = R_STR; r_str = "tbl"; };
-//                       }];
-//               optargs = [["read_mode",
-//                           Term {
-//                             type = DATUM;
-//                             datum = Datum { type = R_STR; r_bool = "outdated"; };
-//                           }]];
-//             },
-//             Term {
-//               type = MAKE_ARRAY;
-//               args = [Term {
-//                         type = DATUM;
-//                         datum = Datum { type = R_OBJECT; r_object = [["id", 0]]; };
-//                       },
-//                       Term {
-//                         type = DATUM;
-//                         datum = Datum { type = R_OBJECT; r_object = [["id", 1]]; };
-//                       }];
-//             }]
-//   }
-// And the server would reply:
-//   Response {
-//     type = SUCCESS_ATOM;
-//     token = 1;
-//     response = [Datum { type = R_OBJECT; r_object = [["inserted", 2]]; }];
-//   }
-// Or, if there were an error:
-//   Response {
-//     type = RUNTIME_ERROR;
-//     token = 1;
-//     response = [Datum { type = R_STR; r_str = "The table `tbl` doesn't exist!"; }];
-//     backtrace = [Frame { type = POS; pos = 0; }, Frame { type = POS; pos = 0; }];
-//   }

+ 0 - 434
ext/librethinkdbxx/src/connection.cc

@@ -1,434 +0,0 @@
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/select.h>
-
-#include <netdb.h>
-#include <unistd.h>
-
-#include <algorithm>
-#include <cstring>
-#include <cinttypes>
-#include <memory>
-
-#include "connection.h"
-#include "connection_p.h"
-#include "json_p.h"
-#include "exceptions.h"
-#include "term.h"
-#include "cursor_p.h"
-
-#include "rapidjson-config.h"
-#include "rapidjson/rapidjson.h"
-#include "rapidjson/encodedstream.h"
-#include "rapidjson/document.h"
-
-namespace RethinkDB {
-
-using QueryType = Protocol::Query::QueryType;
-
-// constants
-const int debug_net = 0;
-const uint32_t version_magic =
-    static_cast<uint32_t>(Protocol::VersionDummy::Version::V0_4);
-const uint32_t json_magic =
-    static_cast<uint32_t>(Protocol::VersionDummy::Protocol::JSON);
-
-std::unique_ptr<Connection> connect(std::string host, int port, std::string auth_key) {
-    struct addrinfo hints;
-    memset(&hints, 0, sizeof hints);
-    hints.ai_family = AF_UNSPEC;
-    hints.ai_socktype = SOCK_STREAM;
-
-    char port_str[16];
-    snprintf(port_str, 16, "%d", port);
-    struct addrinfo *servinfo;
-    int ret = getaddrinfo(host.c_str(), port_str, &hints, &servinfo);
-    if (ret) throw Error("getaddrinfo: %s\n", gai_strerror(ret));
-
-    struct addrinfo *p;
-    Error error;
-    int sockfd;
-    for (p = servinfo; p != NULL; p = p->ai_next) {
-        sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
-        if (sockfd == -1) {
-            error = Error::from_errno("socket");
-            continue;
-        }
-
-        if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
-            ::close(sockfd);
-            error = Error::from_errno("connect");
-            continue;
-        }
-
-        break;
-    }
-
-    if (p == NULL) {
-        throw error;
-    }
-
-    freeaddrinfo(servinfo);
-
-    std::unique_ptr<ConnectionPrivate> conn_private(new ConnectionPrivate(sockfd));
-    WriteLock writer(conn_private.get());
-    {
-        size_t size = auth_key.size();
-        char buf[12 + size];
-        memcpy(buf, &version_magic, 4);
-        uint32_t n = size;
-        memcpy(buf + 4, &n, 4);
-        memcpy(buf + 8, auth_key.data(), size);
-        memcpy(buf + 8 + size, &json_magic, 4);
-        writer.send(buf, sizeof buf);
-    }
-
-    ReadLock reader(conn_private.get());
-    {
-        const size_t max_response_length = 1024;
-        char buf[max_response_length + 1];
-        size_t len = reader.recv_cstring(buf, max_response_length);
-        if (len == max_response_length || strcmp(buf, "SUCCESS")) {
-            buf[len] = 0;
-            ::close(sockfd);
-            throw Error("Server rejected connection with message: %s", buf);
-        }
-    }
-
-    return std::unique_ptr<Connection>(new Connection(conn_private.release()));
-}
-
-Connection::Connection(ConnectionPrivate *dd) : d(dd) { }
-Connection::~Connection() {
-    // close();
-    if (d->guarded_sockfd >= 0)
-        ::close(d->guarded_sockfd);
-}
-
-size_t ReadLock::recv_some(char* buf, size_t size, double wait) {
-    if (wait != FOREVER) {
-        while (true) {
-            fd_set readfds;
-            struct timeval tv;
-
-            FD_ZERO(&readfds);
-            FD_SET(conn->guarded_sockfd, &readfds);
-
-            tv.tv_sec = (int)wait;
-            tv.tv_usec = (int)((wait - (int)wait) / MICROSECOND);
-            int rv = select(conn->guarded_sockfd + 1, &readfds, NULL, NULL, &tv);
-            if (rv == -1) {
-                throw Error::from_errno("select");
-            } else if (rv == 0) {
-                throw TimeoutException();
-            }
-
-            if (FD_ISSET(conn->guarded_sockfd, &readfds)) {
-                break;
-            }
-        }
-    }
-
-    ssize_t numbytes = ::recv(conn->guarded_sockfd, buf, size, 0);
-    if (numbytes <= 0) throw Error::from_errno("recv");
-    if (debug_net > 1) {
-        fprintf(stderr, "<< %s\n", write_datum(std::string(buf, numbytes)).c_str());
-    }
-
-    return numbytes;
-}
-
-void ReadLock::recv(char* buf, size_t size, double wait) {
-    while (size) {
-        size_t numbytes = recv_some(buf, size, wait);
-
-        buf += numbytes;
-        size -= numbytes;
-    }
-}
-
-size_t ReadLock::recv_cstring(char* buf, size_t max_size){
-    size_t size = 0;
-    for (; size < max_size; size++) {
-        recv(buf, 1, FOREVER);
-        if (*buf == 0) {
-            break;
-        }
-        buf++;
-    }
-    return size;
-}
-
-void WriteLock::send(const char* buf, size_t size) {
-    while (size) {
-        ssize_t numbytes = ::write(conn->guarded_sockfd, buf, size);
-        if (numbytes == -1) throw Error::from_errno("write");
-        if (debug_net > 1) {
-            fprintf(stderr, ">> %s\n", write_datum(std::string(buf, numbytes)).c_str());
-        }
-
-        buf += numbytes;
-        size -= numbytes;
-    }
-}
-
-void WriteLock::send(const std::string data) {
-    send(data.data(), data.size());
-}
-
-std::string ReadLock::recv(size_t size) {
-    char buf[size];
-    recv(buf, size, FOREVER);
-    return buf;
-}
-
-void Connection::close() {
-    CacheLock guard(d.get());
-    for (auto& it : d->guarded_cache) {
-        stop_query(it.first);
-    }
-
-    int ret = ::close(d->guarded_sockfd);
-    if (ret == -1) {
-        throw Error::from_errno("close");
-    }
-    d->guarded_sockfd = -1;
-}
-
-Response ConnectionPrivate::wait_for_response(uint64_t token_want, double wait) {
-    CacheLock guard(this);
-    ConnectionPrivate::TokenCache& cache = guarded_cache[token_want];
-
-    while (true) {
-        if (!cache.responses.empty()) {
-            Response response(std::move(cache.responses.front()));
-            cache.responses.pop();
-            if (cache.closed && cache.responses.empty()) {
-                guarded_cache.erase(token_want);
-            }
-
-            return response;
-        }
-
-        if (cache.closed) {
-            throw Error("Trying to read from a closed token");
-        }
-
-        if (guarded_loop_active) {
-            cache.cond.wait(guard.inner_lock);
-        } else {
-            break;
-        }
-    }
-
-    ReadLock reader(this);
-    return reader.read_loop(token_want, std::move(guard), wait);
-}
-
-Response ReadLock::read_loop(uint64_t token_want, CacheLock&& guard, double wait) {
-    if (!guard.inner_lock) {
-        guard.lock();
-    }
-    if (conn->guarded_loop_active) {
-        throw Error("Cannot run more than one read loop on the same connection");
-    }
-    conn->guarded_loop_active = true;
-    guard.unlock();
-
-    try {
-        while (true) {
-            char buf[12];
-            bzero(buf, sizeof(buf));
-            recv(buf, 12, wait);
-            uint64_t token_got;
-            memcpy(&token_got, buf, 8);
-            uint32_t length;
-            memcpy(&length, buf + 8, 4);
-
-            std::unique_ptr<char[]> bufmem(new char[length + 1]);
-            char *buffer = bufmem.get();
-            bzero(buffer, length + 1);
-            recv(buffer, length, wait);
-            buffer[length] = '\0';
-
-            rapidjson::Document json;
-            json.ParseInsitu(buffer);
-            if (json.HasParseError()) {
-                fprintf(stderr, "json parse error, code: %d, position: %d\n",
-                    (int)json.GetParseError(), (int)json.GetErrorOffset());
-            } else if (json.IsNull()) {
-                fprintf(stderr, "null value, read: %s\n", buffer);
-            }
-
-            Datum datum = read_datum(json);
-            if (debug_net > 0) {
-                fprintf(stderr, "[%" PRIu64 "] << %s\n", token_got, write_datum(datum).c_str());
-            }
-
-            Response response(std::move(datum));
-
-            if (token_got == token_want) {
-                guard.lock();
-                if (response.type != Protocol::Response::ResponseType::SUCCESS_PARTIAL) {
-                    auto it = conn->guarded_cache.find(token_got);
-                    if (it != conn->guarded_cache.end()) {
-                        it->second.closed = true;
-                        it->second.cond.notify_all();
-                    }
-                    conn->guarded_cache.erase(it);
-                }
-                conn->guarded_loop_active = false;
-                for (auto& it : conn->guarded_cache) {
-                    it.second.cond.notify_all();
-                }
-                return response;
-            } else {
-                guard.lock();
-                auto it = conn->guarded_cache.find(token_got);
-                if (it == conn->guarded_cache.end()) {
-                    // drop the response
-                } else if (!it->second.closed) {
-                    it->second.responses.emplace(std::move(response));
-                    if (response.type != Protocol::Response::ResponseType::SUCCESS_PARTIAL) {
-                        it->second.closed = true;
-                    }
-                }
-                it->second.cond.notify_all();
-                guard.unlock();
-            }
-        }
-    } catch (const TimeoutException &e) {
-        if (!guard.inner_lock){
-            guard.lock();
-        }
-        conn->guarded_loop_active = false;
-        throw e;
-    }
-}
-
-void ConnectionPrivate::run_query(Query query, bool no_reply) {
-    WriteLock writer(this);
-    writer.send(query.serialize());
-}
-
-Cursor Connection::start_query(Term *term, OptArgs&& opts) {
-    bool no_reply = false;
-    auto it = opts.find("noreply");
-    if (it != opts.end()) {
-        no_reply = *(it->second.datum.get_boolean());
-    }
-
-    uint64_t token = d->new_token();
-    {
-        CacheLock guard(d.get());
-        d->guarded_cache[token];
-    }
-
-    d->run_query(Query{QueryType::START, token, term->datum, std::move(opts)});
-    if (no_reply) {
-        return Cursor(new CursorPrivate(token, this, Nil()));
-    }
-
-    Cursor cursor(new CursorPrivate(token, this));
-    Response response = d->wait_for_response(token, FOREVER);
-    cursor.d->add_response(std::move(response));
-    return cursor;
-}
-
-void Connection::stop_query(uint64_t token) {
-    const auto& it = d->guarded_cache.find(token);
-    if (it != d->guarded_cache.end() && !it->second.closed) {
-        d->run_query(Query{QueryType::STOP, token}, true);
-    }
-}
-
-void Connection::continue_query(uint64_t token) {
-    d->run_query(Query{QueryType::CONTINUE, token}, true);
-}
-
-Error Response::as_error() {
-    std::string repr;
-    if (result.size() == 1) {
-        std::string* string = result[0].get_string();
-        if (string) {
-            repr = *string;
-        } else {
-            repr = write_datum(result[0]);
-        }
-    } else {
-        repr = write_datum(Datum(result));
-    }
-    std::string err;
-    using RT = Protocol::Response::ResponseType;
-    using ET = Protocol::Response::ErrorType;
-    switch (type) {
-    case RT::SUCCESS_SEQUENCE: err = "unexpected response: SUCCESS_SEQUENCE"; break;
-    case RT::SUCCESS_PARTIAL:  err = "unexpected response: SUCCESS_PARTIAL"; break;
-    case RT::SUCCESS_ATOM: err = "unexpected response: SUCCESS_ATOM"; break;
-    case RT::WAIT_COMPLETE: err = "unexpected response: WAIT_COMPLETE"; break;
-    case RT::SERVER_INFO: err = "unexpected response: SERVER_INFO"; break;
-    case RT::CLIENT_ERROR: err = "ReqlDriverError"; break;
-    case RT::COMPILE_ERROR: err = "ReqlCompileError"; break;
-    case RT::RUNTIME_ERROR:
-        switch (error_type) {
-        case ET::INTERNAL: err = "ReqlInternalError"; break;
-        case ET::RESOURCE_LIMIT: err = "ReqlResourceLimitError"; break;
-        case ET::QUERY_LOGIC: err = "ReqlQueryLogicError"; break;
-        case ET::NON_EXISTENCE: err = "ReqlNonExistenceError"; break;
-        case ET::OP_FAILED: err = "ReqlOpFailedError"; break;
-        case ET::OP_INDETERMINATE: err = "ReqlOpIndeterminateError"; break;
-        case ET::USER: err = "ReqlUserError"; break;
-        case ET::PERMISSION_ERROR: err = "ReqlPermissionError"; break;
-        default: err = "ReqlRuntimeError"; break;
-        }
-    }
-    throw Error("%s: %s", err.c_str(), repr.c_str());
-}
-
-Protocol::Response::ResponseType response_type(double t) {
-    int n = static_cast<int>(t);
-    using RT = Protocol::Response::ResponseType;
-    switch (n) {
-    case static_cast<int>(RT::SUCCESS_ATOM):
-        return RT::SUCCESS_ATOM;
-    case static_cast<int>(RT::SUCCESS_SEQUENCE):
-        return RT::SUCCESS_SEQUENCE;
-    case static_cast<int>(RT::SUCCESS_PARTIAL):
-        return RT::SUCCESS_PARTIAL;
-    case static_cast<int>(RT::WAIT_COMPLETE):
-        return RT::WAIT_COMPLETE;
-    case static_cast<int>(RT::CLIENT_ERROR):
-        return RT::CLIENT_ERROR;
-    case static_cast<int>(RT::COMPILE_ERROR):
-        return RT::COMPILE_ERROR;
-    case static_cast<int>(RT::RUNTIME_ERROR):
-        return RT::RUNTIME_ERROR;
-    default:
-        throw Error("Unknown response type");
-    }
-}
-
-Protocol::Response::ErrorType runtime_error_type(double t) {
-    int n = static_cast<int>(t);
-    using ET = Protocol::Response::ErrorType;
-    switch (n) {
-    case static_cast<int>(ET::INTERNAL):
-        return ET::INTERNAL;
-    case static_cast<int>(ET::RESOURCE_LIMIT):
-        return ET::RESOURCE_LIMIT;
-    case static_cast<int>(ET::QUERY_LOGIC):
-        return ET::QUERY_LOGIC;
-    case static_cast<int>(ET::NON_EXISTENCE):
-        return ET::NON_EXISTENCE;
-    case static_cast<int>(ET::OP_FAILED):
-        return ET::OP_FAILED;
-    case static_cast<int>(ET::OP_INDETERMINATE):
-        return ET::OP_INDETERMINATE;
-    case static_cast<int>(ET::USER):
-        return ET::USER;
-    default:
-        throw Error("Unknown error type");
-    }
-}
-
-}

+ 0 - 59
ext/librethinkdbxx/src/connection.h

@@ -1,59 +0,0 @@
-#pragma once
-
-#include <string>
-#include <queue>
-#include <mutex>
-#include <memory>
-#include <condition_variable>
-
-#include "protocol_defs.h"
-#include "datum.h"
-#include "error.h"
-
-#define FOREVER (-1)
-#define SECOND 1
-#define MICROSECOND 0.000001
-
-namespace RethinkDB {
-
-class Term;
-using OptArgs = std::map<std::string, Term>;
-
-// A connection to a RethinkDB server
-// It contains:
-//  * A socket
-//  * Read and write locks
-//  * A cache of responses that have not been read by the corresponding Cursor
-class ConnectionPrivate;
-class Connection {
-public:
-    Connection() = delete;
-    Connection(const Connection&) noexcept = delete;
-    Connection(Connection&&) noexcept = delete;
-    Connection& operator=(Connection&&) noexcept = delete;
-    Connection& operator=(const Connection&) noexcept = delete;
-    ~Connection();
-
-    void close();
-
-private:
-    explicit Connection(ConnectionPrivate *dd);
-    std::unique_ptr<ConnectionPrivate> d;
-
-    Cursor start_query(Term *term, OptArgs&& args);
-    void stop_query(uint64_t);
-    void continue_query(uint64_t);
-
-    friend class Cursor;
-    friend class CursorPrivate;
-    friend class Token;
-    friend class Term;
-    friend std::unique_ptr<Connection>
-        connect(std::string host, int port, std::string auth_key);
-
-};
-
-// $doc(connect)
-std::unique_ptr<Connection> connect(std::string host = "localhost", int port = 28015, std::string auth_key = "");
-
-}

+ 0 - 133
ext/librethinkdbxx/src/connection_p.h

@@ -1,133 +0,0 @@
-#ifndef CONNECTION_P_H
-#define CONNECTION_P_H
-
-#include <inttypes.h>
-
-#include "connection.h"
-#include "term.h"
-#include "json_p.h"
-
-namespace RethinkDB {
-
-extern const int debug_net;
-
-struct Query {
-    Protocol::Query::QueryType type;
-    uint64_t token;
-    Datum term;
-    OptArgs optArgs;
-
-    std::string serialize() {
-        Array query_arr{static_cast<double>(type)};
-        if (term.is_valid()) query_arr.emplace_back(term);
-        if (!optArgs.empty())
-            query_arr.emplace_back(Term(std::move(optArgs)).datum);
-
-        std::string query_str = write_datum(query_arr);
-        if (debug_net > 0) {
-            fprintf(stderr, "[%" PRIu64 "] >> %s\n", token, query_str.c_str());
-        }
-
-        char header[12];
-        memcpy(header, &token, 8);
-        uint32_t size = query_str.size();
-        memcpy(header + 8, &size, 4);
-        query_str.insert(0, header, 12);
-        return query_str;
-    }
-};
-
-// Used internally to convert a raw response type into an enum
-Protocol::Response::ResponseType response_type(double t);
-Protocol::Response::ErrorType runtime_error_type(double t);
-
-// Contains a response from the server. Use the Cursor class to interact with these responses
-class Response {
-public:
-    Response() = delete;
-    explicit Response(Datum&& datum) :
-        type(response_type(std::move(datum).extract_field("t").extract_number())),
-        error_type(datum.get_field("e") ?
-                   runtime_error_type(std::move(datum).extract_field("e").extract_number()) :
-                   Protocol::Response::ErrorType(0)),
-        result(std::move(datum).extract_field("r").extract_array()) { }
-    Error as_error();
-    Protocol::Response::ResponseType type;
-    Protocol::Response::ErrorType error_type;
-    Array result;
-};
-
-class Token;
-class ConnectionPrivate {
-public:
-    ConnectionPrivate(int sockfd)
-        : guarded_next_token(1), guarded_sockfd(sockfd), guarded_loop_active(false)
-    { }
-
-    void run_query(Query query, bool no_reply = false);
-
-    Response wait_for_response(uint64_t, double);
-    uint64_t new_token() {
-        return guarded_next_token++;
-    }
-
-    std::mutex read_lock;
-    std::mutex write_lock;
-    std::mutex cache_lock;
-
-    struct TokenCache {
-        bool closed = false;
-        std::condition_variable cond;
-        std::queue<Response> responses;
-    };
-
-    std::map<uint64_t, TokenCache> guarded_cache;
-    uint64_t guarded_next_token;
-    int guarded_sockfd;
-    bool guarded_loop_active;
-};
-
-class CacheLock {
-public:
-    CacheLock(ConnectionPrivate* conn) : inner_lock(conn->cache_lock) { }
-
-    void lock() {
-        inner_lock.lock();
-    }
-
-    void unlock() {
-        inner_lock.unlock();
-    }
-
-    std::unique_lock<std::mutex> inner_lock;
-};
-
-class ReadLock {
-public:
-    ReadLock(ConnectionPrivate* conn_) : lock(conn_->read_lock), conn(conn_) { }
-
-    size_t recv_some(char*, size_t, double wait);
-    void recv(char*, size_t, double wait);
-    std::string recv(size_t);
-    size_t recv_cstring(char*, size_t);
-
-    Response read_loop(uint64_t, CacheLock&&, double);
-
-    std::lock_guard<std::mutex> lock;
-    ConnectionPrivate* conn;
-};
-
-class WriteLock {
-public:
-    WriteLock(ConnectionPrivate* conn_) : lock(conn_->write_lock), conn(conn_) { }
-
-    void send(const char*, size_t);
-    void send(std::string);
-
-    std::lock_guard<std::mutex> lock;
-    ConnectionPrivate* conn;
-};
-
-}   // namespace RethinkDB
-
-#endif  // CONNECTION_P_H

+ 0 - 223
ext/librethinkdbxx/src/cursor.cc

@@ -1,223 +0,0 @@
-#include "cursor.h"
-#include "cursor_p.h"
-#include "exceptions.h"
-
-namespace RethinkDB {
-
-// for type completion, in order to forward declare with unique_ptr
-Cursor::Cursor(Cursor&&) = default;
-Cursor& Cursor::operator=(Cursor&&) = default;
-
-CursorPrivate::CursorPrivate(uint64_t token_, Connection *conn_)
-    : single(false), no_more(false), index(0),
-      token(token_), conn(conn_)
-{ }
-
-CursorPrivate::CursorPrivate(uint64_t token_, Connection *conn_, Datum&& datum)
-    : single(true), no_more(true), index(0), buffer(Array{std::move(datum)}),
-      token(token_), conn(conn_)
-{ }
-
-Cursor::Cursor(CursorPrivate *dd) : d(dd) {}
-
-Cursor::~Cursor() {
-    try {
-        if (d && d->conn) {
-            close();
-        }
-    } catch ( ... ) {}
-}
-
-Datum& Cursor::next(double wait) const {
-    if (!has_next(wait)) {
-        throw Error("next: No more data");
-    }
-
-    return d->buffer[d->index++];
-}
-
-Datum& Cursor::peek(double wait) const {
-    if (!has_next(wait)) {
-        throw Error("next: No more data");
-    }
-
-    return d->buffer[d->index];
-}
-
-void Cursor::each(std::function<void(Datum&&)> f, double wait) const {
-    while (has_next(wait)) {
-        f(std::move(d->buffer[d->index++]));
-    }
-}
-
-void CursorPrivate::convert_single() const {
-    if (index != 0) {
-        throw Error("Cursor: already consumed");
-    }
-
-    if (buffer.size() != 1) {
-        throw Error("Cursor: invalid response from server");
-    }
-
-    if (!buffer[0].is_array()) {
-        throw Error("Cursor: not an array");
-    }
-
-    buffer.swap(buffer[0].extract_array());
-    single = false;
-}
-
-void CursorPrivate::clear_and_read_all() const {
-    if (single) {
-        convert_single();
-    }
-    if (index != 0) {
-        buffer.erase(buffer.begin(), buffer.begin() + index);
-        index = 0;
-    }
-    while (!no_more) {
-        add_response(conn->d->wait_for_response(token, FOREVER));
-    }
-}
-
-Array&& Cursor::to_array() && {
-    d->clear_and_read_all();
-    return std::move(d->buffer);
-}
-
-Array Cursor::to_array() const & {
-    d->clear_and_read_all();
-    return d->buffer;
-}
-
-Datum Cursor::to_datum() const & {
-    if (d->single) {
-        if (d->index != 0) {
-            throw Error("to_datum: already consumed");
-        }
-        return d->buffer[0];
-    }
-
-    d->clear_and_read_all();
-    return d->buffer;
-}
-
-Datum Cursor::to_datum() && {
-    Datum ret((Nil()));
-    if (d->single) {
-        if (d->index != 0) {
-            throw Error("to_datum: already consumed");
-        }
-        ret = std::move(d->buffer[0]);
-    } else {
-        d->clear_and_read_all();
-        ret = std::move(d->buffer);
-    }
-
-    return ret;
-}
-
-void Cursor::close() const {
-    d->conn->stop_query(d->token);
-    d->no_more = true;
-}
-
-bool Cursor::has_next(double wait) const {
-    if (d->single) {
-        d->convert_single();
-    }
-
-    while (true) {
-        if (d->index >= d->buffer.size()) {
-            if (d->no_more) {
-                return false;
-            }
-            d->add_response(d->conn->d->wait_for_response(d->token, wait));
-        } else {
-            return true;
-        }
-    }
-}
-
-bool Cursor::is_single() const {
-    return d->single;
-}
-
-void CursorPrivate::add_results(Array&& results) const {
-    if (index >= buffer.size()) {
-        buffer = std::move(results);
-        index = 0;
-    } else {
-        for (auto& it : results) {
-            buffer.emplace_back(std::move(it));
-        }
-    }
-}
-
-void CursorPrivate::add_response(Response&& response) const {
-    using RT = Protocol::Response::ResponseType;
-    switch (response.type) {
-    case RT::SUCCESS_SEQUENCE:
-        add_results(std::move(response.result));
-        no_more = true;
-        break;
-    case RT::SUCCESS_PARTIAL:
-        conn->continue_query(token);
-        add_results(std::move(response.result));
-        break;
-    case RT::SUCCESS_ATOM:
-        add_results(std::move(response.result));
-        single = true;
-        no_more = true;
-        break;
-    case RT::SERVER_INFO:
-        add_results(std::move(response.result));
-        single = true;
-        no_more = true;
-        break;
-    case RT::WAIT_COMPLETE:
-    case RT::CLIENT_ERROR:
-    case RT::COMPILE_ERROR:
-    case RT::RUNTIME_ERROR:
-        no_more = true;
-        throw response.as_error();
-    }
-}
-
-Cursor::iterator Cursor::begin() {
-    return iterator(this);
-}
-
-Cursor::iterator Cursor::end() {
-    return iterator(nullptr);
-}
-
-Cursor::iterator::iterator(Cursor* cursor_) : cursor(cursor_) {}
-
-Cursor::iterator& Cursor::iterator::operator++ () {
-    if (cursor == nullptr) {
-        throw Error("incrementing an exhausted Cursor iterator");
-    }
-
-    cursor->next();
-    return *this;
-}
-
-Datum& Cursor::iterator::operator* () {
-    if (cursor == nullptr) {
-        throw Error("reading from empty Cursor iterator");
-    }
-
-    return cursor->peek();
-}
-
-bool Cursor::iterator::operator!= (const Cursor::iterator& other) const {
-    if (cursor == other.cursor) {
-        return false;
-    }
-
-    return !((cursor == nullptr && !other.cursor->has_next()) ||
-             (other.cursor == nullptr && !cursor->has_next()));
-}
-
-}

+ 0 - 76
ext/librethinkdbxx/src/cursor.h

@@ -1,76 +0,0 @@
-#pragma once
-
-#include "connection.h"
-
-namespace RethinkDB {
-
-// The response from the server, as returned by run.
-// The response is either a single datum or a stream:
-//  * If it is a stream, the cursor represents each element of the stream.
-//    - Batches are fetched from the server as needed.
-//  * If it is a single datum, is_single() returns true.
-//    - If it is an array, the cursor represents each element of that array
-//    - Otherwise, to_datum() returns the datum and iteration throws an exception.
-// The cursor can only be iterated over once, it discards data that has already been read.
-class CursorPrivate;
-class Cursor {
-public:
-    Cursor() = delete;
-    ~Cursor();
-
-    Cursor(Cursor&&);                   // movable
-    Cursor& operator=(Cursor&&);
-    Cursor(const Cursor&) = delete;     // not copyable
-    Cursor& operator=(const Cursor&) = delete;
-
-    // Returned by begin() and end()
-    class iterator {
-    public:
-        iterator(Cursor*);
-        iterator& operator++ ();
-        Datum& operator* ();
-        bool operator!= (const iterator&) const;
-
-    private:
-        Cursor *cursor;
-    };
-
-    // Consume the next element
-    Datum& next(double wait = FOREVER) const;
-
-    // Peek at the next element
-    Datum& peek(double wait = FOREVER) const;
-
-    // Call f on every element of the Cursor
-    void each(std::function<void(Datum&&)> f, double wait = FOREVER) const;
-
-    // Consume and return all elements
-    Array&& to_array() &&;
-
-    // If is_single(), returns the single datum. Otherwise returns to_array().
-    Datum to_datum() &&;
-    Datum to_datum() const &;
-
-    // Efficiently consume and return all elements
-    Array to_array() const &;
-
-    // Close the cursor
-    void close() const;
-
-    // Returns false if there are no more elements
-    bool has_next(double wait = FOREVER) const;
-
-    // Returns false if the cursor is a stream
-    bool is_single() const;
-
-    iterator begin();
-    iterator end();
-
-private:
-    explicit Cursor(CursorPrivate *dd);
-    std::unique_ptr<CursorPrivate> d;
-
-    friend class Connection;
-};
-
-}

+ 0 - 29
ext/librethinkdbxx/src/cursor_p.h

@@ -1,29 +0,0 @@
-#ifndef CURSOR_P_H
-#define CURSOR_P_H
-
-#include "connection_p.h"
-
-namespace RethinkDB {
-
-class CursorPrivate {
-public:
-    CursorPrivate(uint64_t token, Connection *conn);
-    CursorPrivate(uint64_t token, Connection *conn, Datum&&);
-
-    void add_response(Response&&) const;
-    void add_results(Array&&) const;
-    void clear_and_read_all() const;
-    void convert_single() const;
-
-    mutable bool single = false;
-    mutable bool no_more = false;
-    mutable size_t index = 0;
-    mutable Array buffer;
-
-    uint64_t token;
-    Connection *conn;
-};
-
-}   // namespace RethinkDB
-
-#endif  // CURSOR_P_H

+ 0 - 449
ext/librethinkdbxx/src/datum.cc

@@ -1,449 +0,0 @@
-#include <float.h>
-#include <cmath>
-
-#include "datum.h"
-#include "json_p.h"
-#include "utils.h"
-#include "cursor.h"
-
-#include "rapidjson-config.h"
-#include "rapidjson/prettywriter.h"
-#include "rapidjson/stringbuffer.h"
-
-namespace RethinkDB {
-
-using TT = Protocol::Term::TermType;
-
-bool Datum::is_nil() const {
-    return type == Type::NIL;
-}
-
-bool Datum::is_boolean() const {
-    return type == Type::BOOLEAN;
-}
-
-bool Datum::is_number() const {
-    return type == Type::NUMBER;
-}
-
-bool Datum::is_string() const {
-    return type == Type::STRING;
-}
-
-bool Datum::is_object() const {
-    return type == Type::OBJECT;
-}
-
-bool Datum::is_array() const {
-    return type == Type::ARRAY;
-}
-
-bool Datum::is_binary() const {
-    return type == Type::BINARY;
-}
-
-bool Datum::is_time() const {
-    return type == Type::TIME;
-}
-
-bool* Datum::get_boolean() {
-    if (type == Type::BOOLEAN) {
-        return &value.boolean;
-    } else {
-        return NULL;
-    }
-}
-
-const bool* Datum::get_boolean() const {
-    if (type == Type::BOOLEAN) {
-        return &value.boolean;
-    } else {
-        return NULL;
-    }
-}
-
-double* Datum::get_number() {
-    if (type == Type::NUMBER) {
-        return &value.number;
-    } else {
-        return NULL;
-    }
-}
-
-const double* Datum::get_number() const {
-    if (type == Type::NUMBER) {
-        return &value.number;
-    } else {
-        return NULL;
-    }
-}
-
-std::string* Datum::get_string() {
-    if (type == Type::STRING) {
-        return &value.string;
-    } else {
-        return NULL;
-    }
-}
-
-const std::string* Datum::get_string() const {
-    if (type == Type::STRING) {
-        return &value.string;
-    } else {
-        return NULL;
-    }
-}
-
-Datum* Datum::get_field(std::string key) {
-    if (type != Type::OBJECT) {
-        return NULL;
-    }
-    auto it = value.object.find(key);
-    if (it == value.object.end()) {
-        return NULL;
-    }
-    return &it->second;
-}
-
-const Datum* Datum::get_field(std::string key) const {
-    if (type != Type::OBJECT) {
-        return NULL;
-    }
-    auto it = value.object.find(key);
-    if (it == value.object.end()) {
-        return NULL;
-    }
-    return &it->second;
-}
-
-Datum* Datum::get_nth(size_t i) {
-    if (type != Type::ARRAY) {
-        return NULL;
-    }
-    if (i >= value.array.size()) {
-        return NULL;
-    }
-    return &value.array[i];
-}
-
-const Datum* Datum::get_nth(size_t i) const {
-    if (type != Type::ARRAY) {
-        return NULL;
-    }
-    if (i >= value.array.size()) {
-        return NULL;
-    }
-    return &value.array[i];
-}
-
-Object* Datum::get_object() {
-    if (type == Type::OBJECT) {
-        return &value.object;
-    } else {
-        return NULL;
-    }
-}
-
-const Object* Datum::get_object() const {
-    if (type == Type::OBJECT) {
-        return &value.object;
-    } else {
-        return NULL;
-    }
-}
-
-Array* Datum::get_array() {
-    if (type == Type::ARRAY) {
-        return &value.array;
-    } else {
-        return NULL;
-    }
-}
-
-const Array* Datum::get_array() const {
-    if (type == Type::ARRAY) {
-        return &value.array;
-    } else {
-        return NULL;
-    }
-}
-
-Binary* Datum::get_binary() {
-    if (type == Type::BINARY) {
-        return &value.binary;
-    } else {
-        return NULL;
-    }
-}
-
-const Binary* Datum::get_binary() const {
-    if (type == Type::BINARY) {
-        return &value.binary;
-    } else {
-        return NULL;
-    }
-}
-
-Time* Datum::get_time() {
-    if (type == Type::TIME) {
-        return &value.time;
-    } else {
-        return NULL;
-    }
-}
-
-const Time* Datum::get_time() const {
-    if (type == Type::TIME) {
-        return &value.time;
-    } else {
-        return NULL;
-    }
-}
-
-bool& Datum::extract_boolean() {
-    if (type != Type::BOOLEAN) {
-        throw Error("extract_bool: Not a boolean");
-    }
-    return value.boolean;
-}
-
-double& Datum::extract_number() {
-    if (type != Type::NUMBER) {
-        throw Error("extract_number: Not a number: %s", write_datum(*this).c_str());
-    }
-    return value.number;
-}
-
-std::string& Datum::extract_string() {
-    if (type != Type::STRING) {
-        throw Error("extract_string: Not a string");
-    }
-    return value.string;
-}
-
-Object& Datum::extract_object() {
-    if (type != Type::OBJECT) {
-        throw Error("extract_object: Not an object");
-    }
-    return value.object;
-}
-
-Datum& Datum::extract_field(std::string key) {
-    if (type != Type::OBJECT) {
-        throw Error("extract_field: Not an object");
-    }
-    auto it = value.object.find(key);
-    if (it == value.object.end()) {
-        throw Error("extract_field: No such key in object");
-    }
-    return it->second;
-}
-
-Datum& Datum::extract_nth(size_t i) {
-    if (type != Type::ARRAY) {
-        throw Error("extract_nth: Not an array");
-    }
-    if (i >= value.array.size()) {
-        throw Error("extract_nth: index too large");
-    }
-    return value.array[i];
-}
-
-Array& Datum::extract_array() {
-    if (type != Type::ARRAY) {
-        throw Error("get_array: Not an array");
-    }
-    return value.array;
-}
-
-Binary& Datum::extract_binary() {
-    if (type != Type::BINARY) {
-        throw Error("get_binary: Not a binary");
-    }
-    return value.binary;
-}
-
-Time& Datum::extract_time() {
-    if (type != Type::TIME) {
-        throw Error("get_time: Not a time");
-    }
-    return value.time;
-}
-
-int Datum::compare(const Datum& other) const {
-#define COMPARE(a, b) do {          \
-    if (a < b) { return -1; }       \
-    if (a > b) { return 1; } } while(0)
-#define COMPARE_OTHER(x) COMPARE(x, other.x)
-
-    COMPARE_OTHER(type);
-    int c;
-    switch (type) {
-    case Type::NIL: case Type::INVALID: break;
-    case Type::BOOLEAN: COMPARE_OTHER(value.boolean); break;
-    case Type::NUMBER: COMPARE_OTHER(value.number); break;
-    case Type::STRING:
-        c = value.string.compare(other.value.string);
-        COMPARE(c, 0);
-        break;
-    case Type::BINARY:
-        c = value.binary.data.compare(other.value.binary.data);
-        COMPARE(c, 0);
-        break;
-    case Type::TIME:
-        COMPARE(value.time.epoch_time, other.value.time.epoch_time);
-        COMPARE(value.time.utc_offset, other.value.time.utc_offset);
-        break;
-    case Type::ARRAY:
-        COMPARE_OTHER(value.array.size());
-        for (size_t i = 0; i < value.array.size(); i++) {
-            c = value.array[i].compare(other.value.array[i]);
-            COMPARE(c, 0);
-        }
-        break;
-    case Type::OBJECT:
-        COMPARE_OTHER(value.object.size());
-        for (Object::const_iterator l = value.object.begin(),
-                 r = other.value.object.begin();
-             l != value.object.end();
-             ++l, ++r) {
-            COMPARE(l->first, r->first);
-            c = l->second.compare(r->second);
-            COMPARE(c, 0);
-        }
-        break;
-    default:
-        throw Error("cannot compare invalid datum");
-    }
-    return 0;
-#undef COMPARE_OTHER
-#undef COMPARE
-}
-
-bool Datum::operator== (const Datum& other) const {
-    return compare(other) == 0;
-}
-
-Datum Datum::from_raw() const {
-    do {
-        const Datum* type_field = get_field("$reql_type$");
-        if (!type_field) break;
-        const std::string* type = type_field->get_string();
-        if (!type) break;;
-        if (!strcmp(type->c_str(), "BINARY")) {
-            const Datum* data_field = get_field("data");
-            if (!data_field) break;
-            const std::string* encoded_data = data_field->get_string();
-            if (!encoded_data) break;
-            Binary binary("");
-            if (base64_decode(*encoded_data, binary.data)) {
-                return binary;
-            }
-        } else if (!strcmp(type->c_str(), "TIME")) {
-            const Datum* epoch_field = get_field("epoch_time");
-            if (!epoch_field) break;
-            const Datum* tz_field = get_field("timezone");
-            if (!tz_field) break;
-            const double* epoch_time = epoch_field->get_number();
-            if (!epoch_time) break;
-            const std::string* tz  = tz_field->get_string();
-            if (!tz) break;
-            double offset;
-            if (!Time::parse_utc_offset(*tz, &offset)) break;
-            return Time(*epoch_time, offset);
-        }
-    } while (0);
-    return *this;
-}
-
-Datum Datum::to_raw() const {
-    if (type == Type::BINARY) {
-        return Object{
-            {"$reql_type$", "BINARY"},
-            {"data", base64_encode(value.binary.data)}};
-    } else if (type == Type::TIME) {
-        return Object{
-            {"$reql_type$", "TIME"},
-            {"epoch_time", value.time.epoch_time},
-            {"timezone", Time::utc_offset_string(value.time.utc_offset)}};
-    }
-    return *this;
-}
-
-Datum::Datum(Cursor&& cursor) : Datum(cursor.to_datum()) { }
-Datum::Datum(const Cursor& cursor) : Datum(cursor.to_datum()) { }
-
-static const double max_dbl_int = 0x1LL << DBL_MANT_DIG;
-static const double min_dbl_int = max_dbl_int * -1;
-bool number_as_integer(double d, int64_t *i_out) {
-    static_assert(DBL_MANT_DIG == 53, "Doubles are wrong size.");
-
-    if (min_dbl_int <= d && d <= max_dbl_int) {
-        int64_t i = d;
-        if (static_cast<double>(i) == d) {
-            *i_out = i;
-            return true;
-        }
-    }
-    return false;
-}
-
-template void Datum::write_json(
-    rapidjson::Writer<rapidjson::StringBuffer> *writer) const;
-template void Datum::write_json(
-    rapidjson::PrettyWriter<rapidjson::StringBuffer> *writer) const;
-
-template <class json_writer_t>
-void Datum::write_json(json_writer_t *writer) const {
-    switch (type) {
-    case Type::NIL: writer->Null(); break;
-    case Type::BOOLEAN: writer->Bool(value.boolean); break;
-    case Type::NUMBER: {
-        const double d = value.number;
-        // Always print -0.0 as a double since integers cannot represent -0.
-        // Otherwise check if the number is an integer and print it as such.
-        int64_t i;
-        if (!(d == 0.0 && std::signbit(d)) && number_as_integer(d, &i)) {
-            writer->Int64(i);
-        } else {
-            writer->Double(d);
-        }
-    } break;
-    case Type::STRING: writer->String(value.string.data(), value.string.size()); break;
-    case Type::ARRAY: {
-        writer->StartArray();
-        for (auto it : value.array) {
-            it.write_json(writer);
-        }
-        writer->EndArray();
-    } break;
-    case Type::OBJECT: {
-        writer->StartObject();
-        for (auto it : value.object) {
-            writer->Key(it.first.data(), it.first.size());
-            it.second.write_json(writer);
-        }
-        writer->EndObject();
-    } break;
-
-    case Type::BINARY:
-    case Type::TIME:
-        to_raw().write_json(writer);
-        break;
-    default:
-        throw Error("cannot write invalid datum");
-    }
-}
-
-std::string Datum::as_json() const {
-    rapidjson::StringBuffer buffer;
-    rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
-    write_json(&writer);
-    return std::string(buffer.GetString(), buffer.GetSize());
-}
-
-Datum Datum::from_json(const std::string& json) {
-    return read_datum(json);
-}
-
-}   // namespace RethinkDB

+ 0 - 287
ext/librethinkdbxx/src/datum.h

@@ -1,287 +0,0 @@
-#pragma once
-
-#include <string>
-#include <vector>
-#include <map>
-#include <functional>
-
-#include "protocol_defs.h"
-#include "error.h"
-#include "types.h"
-
-namespace RethinkDB {
-
-class Cursor;
-
-// The type of data stored in a RethinkDB database.
-// The following JSON types are represented in a Datum as
-//  * null -> Nil
-//  * boolean -> bool
-//  * number -> double
-//  * unicode strings -> std::string
-//  * array -> Array (aka std::vector<Datum>
-//  * object -> Object (aka std::map<std::string, Datum>>
-// Datums can also contain one of the following extra types
-//  * binary strings -> Binary
-//  * timestamps -> Time
-//  * points. lines and polygons -> not implemented
-class Datum {
-public:
-    Datum() : type(Type::INVALID), value() {}
-    Datum(Nil) : type(Type::NIL), value() { }
-    Datum(bool boolean_) : type(Type::BOOLEAN), value(boolean_) { }
-    Datum(double number_) : type(Type::NUMBER), value(number_) { }
-    Datum(const std::string& string_) : type(Type::STRING), value(string_) { }
-    Datum(std::string&& string_) : type(Type::STRING), value(std::move(string_)) { }
-    Datum(const Array& array_) : type(Type::ARRAY), value(array_) { }
-    Datum(Array&& array_) : type(Type::ARRAY), value(std::move(array_)) { }
-    Datum(const Binary& binary) : type(Type::BINARY), value(binary) { }
-    Datum(Binary&& binary) : type(Type::BINARY), value(std::move(binary)) { }
-    Datum(const Time time) : type(Type::TIME), value(time) { }
-    Datum(const Object& object_) : type(Type::OBJECT), value(object_) { }
-    Datum(Object&& object_) : type(Type::OBJECT), value(std::move(object_)) { }
-    Datum(const Datum& other) : type(other.type), value(other.type, other.value) { }
-    Datum(Datum&& other) : type(other.type), value(other.type, std::move(other.value)) { }
-
-    Datum& operator=(const Datum& other) {
-        value.destroy(type);
-        type = other.type;
-        value.set(type, other.value);
-        return *this;
-    }
-
-    Datum& operator=(Datum&& other) {
-        value.destroy(type);
-        type = other.type;
-        value.set(type, std::move(other.value));
-        return *this;
-    }
-
-    Datum(unsigned short number_) : Datum(static_cast<double>(number_)) { }
-    Datum(signed short number_) : Datum(static_cast<double>(number_)) { }
-    Datum(unsigned int number_) : Datum(static_cast<double>(number_)) { }
-    Datum(signed int number_) : Datum(static_cast<double>(number_)) { }
-    Datum(unsigned long number_) : Datum(static_cast<double>(number_)) { }
-    Datum(signed long number_) : Datum(static_cast<double>(number_)) { }
-    Datum(unsigned long long number_) : Datum(static_cast<double>(number_)) { }
-    Datum(signed long long number_) : Datum(static_cast<double>(number_)) { }
-
-    Datum(Protocol::Term::TermType type) : Datum(static_cast<double>(type)) { }
-    Datum(const char* string) : Datum(static_cast<std::string>(string)) { }
-
-    // Cursors are implicitly converted into datums
-    Datum(Cursor&&);
-    Datum(const Cursor&);
-
-    template <class T>
-    Datum(const std::map<std::string, T>& map) : type(Type::OBJECT), value(Object()) {
-        for (const auto& it : map) {
-            value.object.emplace(it.left, Datum(it.right));
-        }
-    }
-
-    template <class T>
-    Datum(std::map<std::string, T>&& map) : type(Type::OBJECT), value(Object()) {
-        for (auto& it : map) {
-            value.object.emplace(it.first, Datum(std::move(it.second)));
-        }
-    }
-
-    template <class T>
-    Datum(const std::vector<T>& vec) : type(Type::ARRAY), value(Array()) {
-        for (const auto& it : vec) {
-            value.array.emplace_back(it);
-        }
-    }
-
-    template <class T>
-    Datum(std::vector<T>&& vec) : type(Type::ARRAY), value(Array()) {
-        for (auto& it : vec) {
-            value.array.emplace_back(std::move(it));
-        }
-    }
-
-    ~Datum() {
-        value.destroy(type);
-    }
-
-    // Apply a visitor
-    template <class R, class F, class ...A>
-    R apply(F f, A&& ...args) const & {
-        switch (type) {
-        case Type::NIL: return f(Nil(), std::forward<A>(args)...); break;
-        case Type::BOOLEAN: return f(value.boolean, std::forward<A>(args)...); break;
-        case Type::NUMBER: return f(value.number, std::forward<A>(args)...); break;
-        case Type::STRING: return f(value.string, std::forward<A>(args)...); break;
-        case Type::OBJECT: return f(value.object, std::forward<A>(args)...); break;
-        case Type::ARRAY: return f(value.array, std::forward<A>(args)...); break;
-        case Type::BINARY: return f(value.binary, std::forward<A>(args)...); break;
-        case Type::TIME: return f(value.time, std::forward<A>(args)...); break;
-        default:
-            throw Error("internal error: no such datum type %d", static_cast<int>(type));
-        }
-    }
-
-    template <class R, class F, class ...A>
-    R apply(F f, A&& ...args) && {
-        switch (type) {
-        case Type::NIL: return f(Nil(), std::forward<A>(args)...); break;
-        case Type::BOOLEAN: return f(std::move(value.boolean), std::forward<A>(args)...); break;
-        case Type::NUMBER: return f(std::move(value.number), std::forward<A>(args)...); break;
-        case Type::STRING: return f(std::move(value.string), std::forward<A>(args)...); break;
-        case Type::OBJECT: return f(std::move(value.object), std::forward<A>(args)...); break;
-        case Type::ARRAY: return f(std::move(value.array), std::forward<A>(args)...); break;
-        case Type::BINARY: return f(std::move(value.binary), std::forward<A>(args)...); break;
-        case Type::TIME: return f(std::move(value.time), std::forward<A>(args)...); break;
-        default:
-            throw Error("internal error: no such datum type %d", static_cast<int>(type));
-        }
-    }
-
-    bool is_nil() const;
-    bool is_boolean() const;
-    bool is_number() const;
-    bool is_string() const;
-    bool is_object() const;
-    bool is_array() const;
-    bool is_binary() const;
-    bool is_time() const;
-
-    // get_* returns nullptr if the datum has a different type
-
-    bool* get_boolean();
-    const bool* get_boolean() const;
-    double* get_number();
-    const double* get_number() const;
-    std::string* get_string();
-    const std::string* get_string() const;
-    Object* get_object();
-    const Object* get_object() const;
-    Datum* get_field(std::string);
-    const Datum* get_field(std::string) const;
-    Array* get_array();
-    const Array* get_array() const;
-    Datum* get_nth(size_t);
-    const Datum* get_nth(size_t) const;
-    Binary* get_binary();
-    const Binary* get_binary() const;
-    Time* get_time();
-    const Time* get_time() const;
-
-    // extract_* throws an exception if the types don't match
-
-    bool& extract_boolean();
-    double& extract_number();
-    std::string& extract_string();
-    Object& extract_object();
-    Datum& extract_field(std::string);
-    Array& extract_array();
-    Datum& extract_nth(size_t);
-    Binary& extract_binary();
-    Time& extract_time();
-
-    // negative, zero or positive if this datum is smaller, identical or larger than the other one, respectively
-    // This is meant to match the results of RethinkDB's comparison operators
-    int compare(const Datum&) const;
-
-    // Deep equality
-    bool operator== (const Datum&) const;
-
-    // Recusively replace non-JSON types into objects that represent them
-    Datum to_raw() const;
-
-    // Recursively replace objects with a $reql_type$ field into the datum they represent
-    Datum from_raw() const;
-
-    template <class json_writer_t> void write_json(json_writer_t *writer) const;
-
-    std::string as_json() const;
-    static Datum from_json(const std::string&);
-
-    bool is_valid() const { return type != Type::INVALID; }
-
-private:
-    enum class Type {
-        INVALID,    // default constructed
-        ARRAY, BOOLEAN, NIL, NUMBER, OBJECT, BINARY, STRING, TIME
-        // POINT, LINE, POLYGON
-    };
-    Type type;
-
-    union datum_value {
-        bool boolean;
-        double number;
-        std::string string;
-        Object object;
-        Array array;
-        Binary binary;
-        Time time;
-
-        datum_value() { }
-        datum_value(bool boolean_) : boolean(boolean_) { }
-        datum_value(double number_) : number(number_) { }
-        datum_value(const std::string& string_) : string(string_) { }
-        datum_value(std::string&& string_) : string(std::move(string_)) { }
-        datum_value(const Object& object_) : object(object_) { }
-        datum_value(Object&& object_) : object(std::move(object_)) { }
-        datum_value(const Array& array_) : array(array_) { }
-        datum_value(Array&& array_) : array(std::move(array_)) { }
-        datum_value(const Binary& binary_) : binary(binary_) { }
-        datum_value(Binary&& binary_) : binary(std::move(binary_)) { }
-        datum_value(Time time) : time(std::move(time)) { }
-
-        datum_value(Type type, const datum_value& other){
-            set(type, other);
-        }
-
-        datum_value(Type type, datum_value&& other){
-            set(type, std::move(other));
-        }
-
-        void set(Type type, datum_value&& other) {
-            switch(type){
-            case Type::NIL: case Type::INVALID: break;
-            case Type::BOOLEAN: new (this) bool(other.boolean); break;
-            case Type::NUMBER: new (this) double(other.number); break;
-            case Type::STRING: new (this) std::string(std::move(other.string)); break;
-            case Type::OBJECT: new (this) Object(std::move(other.object)); break;
-            case Type::ARRAY: new (this) Array(std::move(other.array)); break;
-            case Type::BINARY: new (this) Binary(std::move(other.binary)); break;
-            case Type::TIME: new (this) Time(std::move(other.time)); break;
-            }
-        }
-
-        void set(Type type, const datum_value& other) {
-            switch(type){
-            case Type::NIL: case Type::INVALID: break;
-            case Type::BOOLEAN: new (this) bool(other.boolean); break;
-            case Type::NUMBER: new (this) double(other.number); break;
-            case Type::STRING: new (this) std::string(other.string); break;
-            case Type::OBJECT: new (this) Object(other.object); break;
-            case Type::ARRAY: new (this) Array(other.array); break;
-            case Type::BINARY: new (this) Binary(other.binary); break;
-            case Type::TIME: new (this) Time(other.time); break;
-            }
-        }
-
-        void destroy(Type type) {
-            switch(type){
-            case Type::INVALID: break;
-            case Type::NIL: break;
-            case Type::BOOLEAN: break;
-            case Type::NUMBER: break;
-            case Type::STRING: { typedef std::string str; string.~str(); } break;
-            case Type::OBJECT: object.~Object(); break;
-            case Type::ARRAY: array.~Array(); break;
-            case Type::BINARY: binary.~Binary(); break;
-            case Type::TIME: time.~Time(); break;
-            }
-        }
-
-        ~datum_value() { }
-    };
-
-    datum_value value;
-};
-
-}

+ 0 - 46
ext/librethinkdbxx/src/error.h

@@ -1,46 +0,0 @@
-#pragma once
-
-#include <cstdarg>
-#include <cstring>
-#include <string>
-#include <cerrno>
-
-namespace RethinkDB {
-
-// All errors thrown by the server have this type
-struct Error {
-    template <class ...T>
-    explicit Error(const char* format_, T... A) {
-        format(format_, A...);
-    }
-
-    Error() = default;
-    Error(Error&&) = default;
-    Error(const Error&) = default;
-
-    Error& operator= (Error&& other) {
-        message = std::move(other.message);
-        return *this;
-    }
-
-    static Error from_errno(const char* str){
-        return Error("%s: %s", str, strerror(errno));
-    }
-
-    // The error message
-    std::string message;
-
-private:
-    const size_t max_message_size = 2048;
-
-    void format(const char* format_, ...) {
-        va_list args;
-        va_start(args, format_);
-        char message_[max_message_size];
-        vsnprintf(message_, max_message_size, format_, args);
-        va_end(args);
-        message = message_;
-    }
-};
-
-}

+ 0 - 13
ext/librethinkdbxx/src/exceptions.h

@@ -1,13 +0,0 @@
-#ifndef EXCEPTIONS_H
-#define EXCEPTIONS_H
-
-namespace RethinkDB {
-
-class TimeoutException : public std::exception {
-public:
-    const char *what() const throw () { return "operation timed out"; }
-};
-
-}
-
-#endif  // EXCEPTIONS_H

+ 0 - 62
ext/librethinkdbxx/src/json.cc

@@ -1,62 +0,0 @@
-#include "json_p.h"
-#include "error.h"
-#include "utils.h"
-
-#include "rapidjson-config.h"
-#include "rapidjson/document.h"
-#include "rapidjson/stringbuffer.h"
-#include "rapidjson/writer.h"
-#include "rapidjson/prettywriter.h"
-
-namespace RethinkDB {
-
-Datum read_datum(const std::string& json) {
-    rapidjson::Document document;
-    document.Parse(json);
-    return read_datum(document);
-}
-
-Datum read_datum(const rapidjson::Value &json) {
-    switch(json.GetType()) {
-    case rapidjson::kNullType: return Nil();
-    case rapidjson::kFalseType: return false;
-    case rapidjson::kTrueType: return true;
-    case rapidjson::kNumberType: return json.GetDouble();
-    case rapidjson::kStringType:
-        return std::string(json.GetString(), json.GetStringLength());
-
-    case rapidjson::kObjectType: {
-        Object result;
-        for (rapidjson::Value::ConstMemberIterator it = json.MemberBegin();
-             it != json.MemberEnd(); ++it) {
-            result.insert(std::make_pair(std::string(it->name.GetString(),
-                                         it->name.GetStringLength()),
-                                         read_datum(it->value)));
-        }
-
-        if (result.count("$reql_type$"))
-            return Datum(std::move(result)).from_raw();
-        return std::move(result);
-    } break;
-    case rapidjson::kArrayType: {
-        Array result;
-        result.reserve(json.Size());
-        for (rapidjson::Value::ConstValueIterator it = json.Begin();
-             it != json.End(); ++it) {
-            result.push_back(read_datum(*it));
-        }
-        return std::move(result);
-    } break;
-    default:
-        throw Error("invalid rapidjson value");
-    }
-}
-
-std::string write_datum(const Datum& datum) {
-    rapidjson::StringBuffer buffer;
-    rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
-    datum.write_json(&writer);
-    return std::string(buffer.GetString(), buffer.GetSize());
-}
-
-}

+ 0 - 19
ext/librethinkdbxx/src/json_p.h

@@ -1,19 +0,0 @@
-#pragma once
-
-#include "datum.h"
-
-namespace rapidjson {
-  class CrtAllocator;
-  template<typename> struct UTF8;
-  template <typename, typename> class GenericValue;
-  template <typename> class MemoryPoolAllocator;
-  typedef GenericValue<UTF8<char>, MemoryPoolAllocator<CrtAllocator> > Value;
-}
-
-namespace RethinkDB {
-
-Datum read_datum(const std::string&);
-Datum read_datum(const rapidjson::Value &json);
-std::string write_datum(const Datum&);
-
-}

+ 0 - 8
ext/librethinkdbxx/src/rapidjson-config.h

@@ -1,8 +0,0 @@
-#pragma once
-
-#define RAPIDJSON_HAS_STDSTRING 1
-#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 1
-#define RAPIDJSON_HAS_CXX11_NOEXCEPT 1
-#define RAPIDJSON_HAS_CXX11_TYPETRAITS 1
-#define RAPIDJSON_HAS_CXX11_RANGE_FOR 1
-#define RAPIDJSON_PARSE_DEFAULT_FLAGS kParseFullPrecisionFlag

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini