Browse Source

clang-format this branch

Adam Ierymenko 1 month ago
parent
commit
342fa9d33f
100 changed files with 30773 additions and 30550 deletions
  1. 1 1
      .clang-format
  2. 132 132
      controller/ConnectionPool.hpp
  3. 165 116
      controller/DB.cpp
  4. 78 79
      controller/DB.hpp
  5. 66 67
      controller/DBMirrorSet.cpp
  6. 22 23
      controller/DBMirrorSet.hpp
  7. 328 252
      controller/EmbeddedNetworkController.cpp
  8. 77 80
      controller/EmbeddedNetworkController.hpp
  9. 71 66
      controller/FileDB.cpp
  10. 10 12
      controller/FileDB.hpp
  11. 192 160
      controller/LFDB.cpp
  12. 20 25
      controller/LFDB.hpp
  13. 335 258
      controller/PostgreSQL.cpp
  14. 58 55
      controller/PostgreSQL.hpp
  15. 5 5
      controller/Redis.hpp
  16. 633 633
      node/AES.cpp
  17. 503 503
      node/AES.hpp
  18. 511 511
      node/AES_aesni.cpp
  19. 341 341
      node/AES_armcrypto.cpp
  20. 192 192
      node/Address.hpp
  21. 29 29
      node/AtomicCounter.hpp
  22. 1804 1804
      node/Bond.cpp
  23. 1470 1470
      node/Bond.hpp
  24. 454 454
      node/Buffer.hpp
  25. 40 40
      node/Capability.cpp
  26. 460 460
      node/Capability.hpp
  27. 101 101
      node/CertificateOfMembership.cpp
  28. 264 264
      node/CertificateOfMembership.hpp
  29. 32 32
      node/CertificateOfOwnership.cpp
  30. 226 226
      node/CertificateOfOwnership.hpp
  31. 34 34
      node/Constants.hpp
  32. 12 12
      node/Credential.hpp
  33. 21 21
      node/DNS.hpp
  34. 451 451
      node/Dictionary.hpp
  35. 536 536
      node/ECC.cpp
  36. 169 169
      node/ECC.hpp
  37. 399 399
      node/Hashtable.hpp
  38. 141 141
      node/Identity.cpp
  39. 307 307
      node/Identity.hpp
  40. 1352 1352
      node/IncomingPacket.cpp
  41. 85 85
      node/IncomingPacket.hpp
  42. 435 435
      node/InetAddress.cpp
  43. 708 708
      node/InetAddress.hpp
  44. 238 238
      node/MAC.hpp
  45. 162 162
      node/Membership.cpp
  46. 243 243
      node/Membership.hpp
  47. 4 4
      node/Metrics.cpp
  48. 4 4
      node/Metrics.hpp
  49. 82 82
      node/MulticastGroup.hpp
  50. 386 386
      node/Multicaster.cpp
  51. 170 170
      node/Multicaster.hpp
  52. 111 111
      node/Mutex.hpp
  53. 1678 1678
      node/Network.cpp
  54. 463 463
      node/Network.hpp
  55. 561 561
      node/NetworkConfig.cpp
  56. 497 497
      node/NetworkConfig.hpp
  57. 63 63
      node/NetworkController.hpp
  58. 572 572
      node/Node.cpp
  59. 304 304
      node/Node.hpp
  60. 57 57
      node/OutboundMulticast.cpp
  61. 124 124
      node/OutboundMulticast.hpp
  62. 702 702
      node/Packet.cpp
  63. 1169 1169
      node/Packet.hpp
  64. 81 81
      node/PacketMultiplexer.cpp
  65. 25 25
      node/PacketMultiplexer.hpp
  66. 6 6
      node/Path.cpp
  67. 443 443
      node/Path.hpp
  68. 619 620
      node/Peer.cpp
  69. 673 673
      node/Peer.hpp
  70. 457 457
      node/Poly1305.cpp
  71. 10 10
      node/Poly1305.hpp
  72. 17 17
      node/Revocation.cpp
  73. 177 177
      node/Revocation.hpp
  74. 281 281
      node/RingBuffer.hpp
  75. 32 32
      node/RuntimeEnvironment.hpp
  76. 191 191
      node/SHA512.cpp
  77. 14 14
      node/SHA512.hpp
  78. 1252 1252
      node/Salsa20.cpp
  79. 115 115
      node/Salsa20.hpp
  80. 73 73
      node/SelfAwareness.cpp
  81. 67 67
      node/SelfAwareness.hpp
  82. 139 139
      node/SharedPtr.hpp
  83. 1120 1124
      node/Switch.cpp
  84. 266 266
      node/Switch.hpp
  85. 17 17
      node/Tag.cpp
  86. 182 182
      node/Tag.hpp
  87. 374 374
      node/Topology.cpp
  88. 417 417
      node/Topology.hpp
  89. 520 520
      node/Trace.cpp
  90. 120 120
      node/Trace.hpp
  91. 153 153
      node/Utils.cpp
  92. 739 739
      node/Utils.hpp
  93. 238 238
      node/World.hpp
  94. 80 80
      osdep/Arp.cpp
  95. 66 66
      osdep/Arp.hpp
  96. 320 321
      osdep/BSDEthernetTap.cpp
  97. 43 43
      osdep/BSDEthernetTap.hpp
  98. 428 428
      osdep/Binder.hpp
  99. 82 82
      osdep/BlockingQueue.hpp
  100. 76 76
      osdep/EthernetTap.cpp

+ 1 - 1
.clang-format

@@ -57,7 +57,7 @@ SpacesInCStyleCastParentheses: 'false'
 SpacesInContainerLiterals: 'true'
 SpacesInParentheses: 'false'
 SpacesInSquareBrackets: 'false'
-UseTab: 'Never'
+UseTab: 'Always'
 
 ---
 Language: Cpp

+ 132 - 132
controller/ConnectionPool.hpp

@@ -14,162 +14,162 @@
 #ifndef ZT_CONNECTION_POOL_H_
 #define ZT_CONNECTION_POOL_H_
 
-
 #ifndef _DEBUG
-	#define _DEBUG(x)
+#define _DEBUG(x)
 #endif
 
 #include "../node/Metrics.hpp"
 
 #include <deque>
-#include <set>
+#include <exception>
 #include <memory>
 #include <mutex>
-#include <exception>
+#include <set>
 #include <string>
 
 namespace ZeroTier {
 
-struct ConnectionUnavailable : std::exception { 
-    char const* what() const throw() {
-        return "Unable to allocate connection";
-    }; 
+struct ConnectionUnavailable : std::exception {
+	char const* what() const throw()
+	{
+		return "Unable to allocate connection";
+	};
 };
 
-
 class Connection {
-public:
-    virtual ~Connection() {};
+  public:
+	virtual ~Connection() {};
 };
 
 class ConnectionFactory {
-public:
-    virtual ~ConnectionFactory() {};
-    virtual std::shared_ptr<Connection> create()=0;
+  public:
+	virtual ~ConnectionFactory() {};
+	virtual std::shared_ptr<Connection> create() = 0;
 };
 
 struct ConnectionPoolStats {
-    size_t pool_size;
-    size_t borrowed_size;
+	size_t pool_size;
+	size_t borrowed_size;
 };
 
-template<class T>
-class ConnectionPool {
-public:
-    ConnectionPool(size_t max_pool_size, size_t min_pool_size, std::shared_ptr<ConnectionFactory> factory)
-        : m_maxPoolSize(max_pool_size)
-        , m_minPoolSize(min_pool_size)
-        , m_factory(factory)
-    {
-        Metrics::max_pool_size += max_pool_size;
-        Metrics::min_pool_size += min_pool_size;
-        while(m_pool.size() < m_minPoolSize){
-            m_pool.push_back(m_factory->create());
-            Metrics::pool_avail++;
-        }
-    };
-
-    ConnectionPoolStats get_stats() {
-        std::unique_lock<std::mutex> lock(m_poolMutex);
-
-        ConnectionPoolStats stats;
-        stats.pool_size = m_pool.size();
-        stats.borrowed_size = m_borrowed.size();			
-
-        return stats;
-    };
-
-    ~ConnectionPool() {
-    };
-
-    /**
-     * Borrow
-     *
-     * Borrow a connection for temporary use
-     *
-     * When done, either (a) call unborrow() to return it, or (b) (if it's bad) just let it go out of scope.  This will cause it to automatically be replaced.
-     * @retval a shared_ptr to the connection object
-     */
-    std::shared_ptr<T> borrow() {
-        std::unique_lock<std::mutex> l(m_poolMutex);
-        
-        while((m_pool.size() + m_borrowed.size()) < m_minPoolSize) {
-            std::shared_ptr<Connection> conn = m_factory->create();
-            m_pool.push_back(conn);
-            Metrics::pool_avail++;
-        }
-
-        if(m_pool.size()==0){
-            
-            if ((m_pool.size() + m_borrowed.size()) < m_maxPoolSize) {
-                try {
-                    std::shared_ptr<Connection> conn = m_factory->create();
-                    m_borrowed.insert(conn);
-                    Metrics::pool_in_use++;
-                    return std::static_pointer_cast<T>(conn);
-                } catch (std::exception &e) {
-                    Metrics::pool_errors++;
-                    throw ConnectionUnavailable();
-                }
-            } else {
-                for(auto it = m_borrowed.begin(); it != m_borrowed.end(); ++it){
-                    if((*it).unique()) {
-                        // This connection has been abandoned! Destroy it and create a new connection
-                        try {
-                            // If we are able to create a new connection, return it
-                            _DEBUG("Creating new connection to replace discarded connection");
-                            std::shared_ptr<Connection> conn = m_factory->create();
-                            m_borrowed.erase(it);
-                            m_borrowed.insert(conn);
-                            return std::static_pointer_cast<T>(conn);
-                        } catch(std::exception& e) {
-                            // Error creating a replacement connection
-                            Metrics::pool_errors++;
-                            throw ConnectionUnavailable();
-                        }
-                    }
-                }
-                // Nothing available
-                Metrics::pool_errors++;
-                throw ConnectionUnavailable();
-            }
-        }
-
-        // Take one off the front
-        std::shared_ptr<Connection> conn = m_pool.front();
-        m_pool.pop_front();
-        Metrics::pool_avail--;
-        // Add it to the borrowed list
-        m_borrowed.insert(conn);
-        Metrics::pool_in_use++;
-        return std::static_pointer_cast<T>(conn);
-    };
-
-    /**
-     * Unborrow a connection
-     *
-     * Only call this if you are returning a working connection.  If the connection was bad, just let it go out of scope (so the connection manager can replace it).
-     * @param the connection
-     */
-    void unborrow(std::shared_ptr<T> conn) {
-        // Lock
-        std::unique_lock<std::mutex> lock(m_poolMutex);
-        m_borrowed.erase(conn);
-        Metrics::pool_in_use--;
-        if ((m_pool.size() + m_borrowed.size()) < m_maxPoolSize) {
-            Metrics::pool_avail++;
-            m_pool.push_back(conn);
-        }
-    };
-protected:
-    size_t m_maxPoolSize;
-    size_t m_minPoolSize;
-    std::shared_ptr<ConnectionFactory> m_factory;
-    std::deque<std::shared_ptr<Connection> > m_pool;
-    std::set<std::shared_ptr<Connection> > m_borrowed;
-    std::mutex m_poolMutex;
+template <class T> class ConnectionPool {
+  public:
+	ConnectionPool(size_t max_pool_size, size_t min_pool_size, std::shared_ptr<ConnectionFactory> factory) : m_maxPoolSize(max_pool_size), m_minPoolSize(min_pool_size), m_factory(factory)
+	{
+		Metrics::max_pool_size += max_pool_size;
+		Metrics::min_pool_size += min_pool_size;
+		while (m_pool.size() < m_minPoolSize) {
+			m_pool.push_back(m_factory->create());
+			Metrics::pool_avail++;
+		}
+	};
+
+	ConnectionPoolStats get_stats()
+	{
+		std::unique_lock<std::mutex> lock(m_poolMutex);
+
+		ConnectionPoolStats stats;
+		stats.pool_size = m_pool.size();
+		stats.borrowed_size = m_borrowed.size();
+
+		return stats;
+	};
+
+	~ConnectionPool() {};
+
+	/**
+	 * Borrow
+	 *
+	 * Borrow a connection for temporary use
+	 *
+	 * When done, either (a) call unborrow() to return it, or (b) (if it's bad) just let it go out of scope.  This will cause it to automatically be replaced.
+	 * @retval a shared_ptr to the connection object
+	 */
+	std::shared_ptr<T> borrow()
+	{
+		std::unique_lock<std::mutex> l(m_poolMutex);
+
+		while ((m_pool.size() + m_borrowed.size()) < m_minPoolSize) {
+			std::shared_ptr<Connection> conn = m_factory->create();
+			m_pool.push_back(conn);
+			Metrics::pool_avail++;
+		}
+
+		if (m_pool.size() == 0) {
+			if ((m_pool.size() + m_borrowed.size()) < m_maxPoolSize) {
+				try {
+					std::shared_ptr<Connection> conn = m_factory->create();
+					m_borrowed.insert(conn);
+					Metrics::pool_in_use++;
+					return std::static_pointer_cast<T>(conn);
+				}
+				catch (std::exception& e) {
+					Metrics::pool_errors++;
+					throw ConnectionUnavailable();
+				}
+			}
+			else {
+				for (auto it = m_borrowed.begin(); it != m_borrowed.end(); ++it) {
+					if ((*it).unique()) {
+						// This connection has been abandoned! Destroy it and create a new connection
+						try {
+							// If we are able to create a new connection, return it
+							_DEBUG("Creating new connection to replace discarded connection");
+							std::shared_ptr<Connection> conn = m_factory->create();
+							m_borrowed.erase(it);
+							m_borrowed.insert(conn);
+							return std::static_pointer_cast<T>(conn);
+						}
+						catch (std::exception& e) {
+							// Error creating a replacement connection
+							Metrics::pool_errors++;
+							throw ConnectionUnavailable();
+						}
+					}
+				}
+				// Nothing available
+				Metrics::pool_errors++;
+				throw ConnectionUnavailable();
+			}
+		}
+
+		// Take one off the front
+		std::shared_ptr<Connection> conn = m_pool.front();
+		m_pool.pop_front();
+		Metrics::pool_avail--;
+		// Add it to the borrowed list
+		m_borrowed.insert(conn);
+		Metrics::pool_in_use++;
+		return std::static_pointer_cast<T>(conn);
+	};
+
+	/**
+	 * Unborrow a connection
+	 *
+	 * Only call this if you are returning a working connection.  If the connection was bad, just let it go out of scope (so the connection manager can replace it).
+	 * @param the connection
+	 */
+	void unborrow(std::shared_ptr<T> conn)
+	{
+		// Lock
+		std::unique_lock<std::mutex> lock(m_poolMutex);
+		m_borrowed.erase(conn);
+		Metrics::pool_in_use--;
+		if ((m_pool.size() + m_borrowed.size()) < m_maxPoolSize) {
+			Metrics::pool_avail++;
+			m_pool.push_back(conn);
+		}
+	};
+
+  protected:
+	size_t m_maxPoolSize;
+	size_t m_minPoolSize;
+	std::shared_ptr<ConnectionFactory> m_factory;
+	std::deque<std::shared_ptr<Connection> > m_pool;
+	std::set<std::shared_ptr<Connection> > m_borrowed;
+	std::mutex m_poolMutex;
 };
 
-}
+}	// namespace ZeroTier
 
 #endif

+ 165 - 116
controller/DB.cpp

@@ -12,77 +12,114 @@
 /****/
 
 #include "DB.hpp"
-#include "EmbeddedNetworkController.hpp"
+
 #include "../node/Metrics.hpp"
+#include "EmbeddedNetworkController.hpp"
 
-#include <chrono>
 #include <algorithm>
+#include <chrono>
 #include <stdexcept>
 
 using json = nlohmann::json;
 
 namespace ZeroTier {
 
-void DB::initNetwork(nlohmann::json &network)
+void DB::initNetwork(nlohmann::json& network)
 {
-	if (!network.count("private")) network["private"] = true;
-	if (!network.count("creationTime")) network["creationTime"] = OSUtils::now();
-	if (!network.count("name")) network["name"] = "";
-	if (!network.count("multicastLimit")) network["multicastLimit"] = (uint64_t)32;
-	if (!network.count("enableBroadcast")) network["enableBroadcast"] = true;
-	if (!network.count("v4AssignMode")) network["v4AssignMode"] = {{"zt",false}};
-	if (!network.count("v6AssignMode")) network["v6AssignMode"] = {{"rfc4193",false},{"zt",false},{"6plane",false}};
-	if (!network.count("authTokens")) network["authTokens"] = {{}};
-	if (!network.count("capabilities")) network["capabilities"] = nlohmann::json::array();
-	if (!network.count("tags")) network["tags"] = nlohmann::json::array();
-	if (!network.count("routes")) network["routes"] = nlohmann::json::array();
-	if (!network.count("ipAssignmentPools")) network["ipAssignmentPools"] = nlohmann::json::array();
-	if (!network.count("mtu")) network["mtu"] = ZT_DEFAULT_MTU;
-	if (!network.count("remoteTraceTarget")) network["remoteTraceTarget"] = nlohmann::json();
-	if (!network.count("removeTraceLevel")) network["remoteTraceLevel"] = 0;
-	if (!network.count("rulesSource")) network["rulesSource"] = "";
-	if (!network.count("rules")) {
+	if (! network.count("private"))
+		network["private"] = true;
+	if (! network.count("creationTime"))
+		network["creationTime"] = OSUtils::now();
+	if (! network.count("name"))
+		network["name"] = "";
+	if (! network.count("multicastLimit"))
+		network["multicastLimit"] = (uint64_t)32;
+	if (! network.count("enableBroadcast"))
+		network["enableBroadcast"] = true;
+	if (! network.count("v4AssignMode"))
+		network["v4AssignMode"] = { { "zt", false } };
+	if (! network.count("v6AssignMode"))
+		network["v6AssignMode"] = { { "rfc4193", false }, { "zt", false }, { "6plane", false } };
+	if (! network.count("authTokens"))
+		network["authTokens"] = { {} };
+	if (! network.count("capabilities"))
+		network["capabilities"] = nlohmann::json::array();
+	if (! network.count("tags"))
+		network["tags"] = nlohmann::json::array();
+	if (! network.count("routes"))
+		network["routes"] = nlohmann::json::array();
+	if (! network.count("ipAssignmentPools"))
+		network["ipAssignmentPools"] = nlohmann::json::array();
+	if (! network.count("mtu"))
+		network["mtu"] = ZT_DEFAULT_MTU;
+	if (! network.count("remoteTraceTarget"))
+		network["remoteTraceTarget"] = nlohmann::json();
+	if (! network.count("removeTraceLevel"))
+		network["remoteTraceLevel"] = 0;
+	if (! network.count("rulesSource"))
+		network["rulesSource"] = "";
+	if (! network.count("rules")) {
 		// If unspecified, rules are set to allow anything and behave like a flat L2 segment
-		network["rules"] = {{
-			{ "not",false },
-			{ "or", false },
-			{ "type","ACTION_ACCEPT" }
-		}};
+		network["rules"] = { { { "not", false }, { "or", false }, { "type", "ACTION_ACCEPT" } } };
 	}
-	if (!network.count("dns")) network["dns"] = nlohmann::json::array();
-	if (!network.count("ssoEnabled")) network["ssoEnabled"] = false;
-	if (!network.count("clientId")) network["clientId"] = "";
-	if (!network.count("authorizationEndpoint")) network["authorizationEndpoint"] = "";
+	if (! network.count("dns"))
+		network["dns"] = nlohmann::json::array();
+	if (! network.count("ssoEnabled"))
+		network["ssoEnabled"] = false;
+	if (! network.count("clientId"))
+		network["clientId"] = "";
+	if (! network.count("authorizationEndpoint"))
+		network["authorizationEndpoint"] = "";
 
 	network["objtype"] = "network";
 }
 
-void DB::initMember(nlohmann::json &member)
+void DB::initMember(nlohmann::json& member)
 {
-	if (!member.count("authorized")) member["authorized"] = false;
-	if (!member.count("ssoExempt")) member["ssoExempt"] = false;
-	if (!member.count("ipAssignments")) member["ipAssignments"] = nlohmann::json::array();
-	if (!member.count("activeBridge")) member["activeBridge"] = false;
-	if (!member.count("tags")) member["tags"] = nlohmann::json::array();
-	if (!member.count("capabilities")) member["capabilities"] = nlohmann::json::array();
-	if (!member.count("creationTime")) member["creationTime"] = OSUtils::now();
-	if (!member.count("noAutoAssignIps")) member["noAutoAssignIps"] = false;
-	if (!member.count("revision")) member["revision"] = 0ULL;
-	if (!member.count("lastDeauthorizedTime")) member["lastDeauthorizedTime"] = 0ULL;
-	if (!member.count("lastAuthorizedTime")) member["lastAuthorizedTime"] = 0ULL;
-	if (!member.count("lastAuthorizedCredentialType")) member["lastAuthorizedCredentialType"] = nlohmann::json();
-	if (!member.count("lastAuthorizedCredential")) member["lastAuthorizedCredential"] = nlohmann::json();
-	if (!member.count("authenticationExpiryTime")) member["authenticationExpiryTime"] = 0LL;
-	if (!member.count("vMajor")) member["vMajor"] = -1;
-	if (!member.count("vMinor")) member["vMinor"] = -1;
-	if (!member.count("vRev")) member["vRev"] = -1;
-	if (!member.count("vProto")) member["vProto"] = -1;
-	if (!member.count("remoteTraceTarget")) member["remoteTraceTarget"] = nlohmann::json();
-	if (!member.count("removeTraceLevel")) member["remoteTraceLevel"] = 0;
+	if (! member.count("authorized"))
+		member["authorized"] = false;
+	if (! member.count("ssoExempt"))
+		member["ssoExempt"] = false;
+	if (! member.count("ipAssignments"))
+		member["ipAssignments"] = nlohmann::json::array();
+	if (! member.count("activeBridge"))
+		member["activeBridge"] = false;
+	if (! member.count("tags"))
+		member["tags"] = nlohmann::json::array();
+	if (! member.count("capabilities"))
+		member["capabilities"] = nlohmann::json::array();
+	if (! member.count("creationTime"))
+		member["creationTime"] = OSUtils::now();
+	if (! member.count("noAutoAssignIps"))
+		member["noAutoAssignIps"] = false;
+	if (! member.count("revision"))
+		member["revision"] = 0ULL;
+	if (! member.count("lastDeauthorizedTime"))
+		member["lastDeauthorizedTime"] = 0ULL;
+	if (! member.count("lastAuthorizedTime"))
+		member["lastAuthorizedTime"] = 0ULL;
+	if (! member.count("lastAuthorizedCredentialType"))
+		member["lastAuthorizedCredentialType"] = nlohmann::json();
+	if (! member.count("lastAuthorizedCredential"))
+		member["lastAuthorizedCredential"] = nlohmann::json();
+	if (! member.count("authenticationExpiryTime"))
+		member["authenticationExpiryTime"] = 0LL;
+	if (! member.count("vMajor"))
+		member["vMajor"] = -1;
+	if (! member.count("vMinor"))
+		member["vMinor"] = -1;
+	if (! member.count("vRev"))
+		member["vRev"] = -1;
+	if (! member.count("vProto"))
+		member["vProto"] = -1;
+	if (! member.count("remoteTraceTarget"))
+		member["remoteTraceTarget"] = nlohmann::json();
+	if (! member.count("removeTraceLevel"))
+		member["remoteTraceLevel"] = 0;
 	member["objtype"] = "member";
 }
 
-void DB::cleanNetwork(nlohmann::json &network)
+void DB::cleanNetwork(nlohmann::json& network)
 {
 	network.erase("clock");
 	network.erase("authorizedMemberCount");
@@ -91,21 +128,25 @@ void DB::cleanNetwork(nlohmann::json &network)
 	network.erase("lastModified");
 }
 
-void DB::cleanMember(nlohmann::json &member)
+void DB::cleanMember(nlohmann::json& member)
 {
 	member.erase("clock");
 	member.erase("physicalAddr");
 	member.erase("recentLog");
 	member.erase("lastModified");
 	member.erase("lastRequestMetaData");
-	member.erase("authenticationURL"); // computed
-	member.erase("authenticationClientID"); // computed
+	member.erase("authenticationURL");		  // computed
+	member.erase("authenticationClientID");	  // computed
 }
 
-DB::DB() {}
-DB::~DB() {}
+DB::DB()
+{
+}
+DB::~DB()
+{
+}
 
-bool DB::get(const uint64_t networkId,nlohmann::json &network)
+bool DB::get(const uint64_t networkId, nlohmann::json& network)
 {
 	waitForReady();
 	Metrics::db_get_network++;
@@ -124,7 +165,7 @@ bool DB::get(const uint64_t networkId,nlohmann::json &network)
 	return true;
 }
 
-bool DB::get(const uint64_t networkId,nlohmann::json &network,const uint64_t memberId,nlohmann::json &member)
+bool DB::get(const uint64_t networkId, nlohmann::json& network, const uint64_t memberId, nlohmann::json& member)
 {
 	waitForReady();
 	Metrics::db_get_network_and_member++;
@@ -147,7 +188,7 @@ bool DB::get(const uint64_t networkId,nlohmann::json &network,const uint64_t mem
 	return true;
 }
 
-bool DB::get(const uint64_t networkId,nlohmann::json &network,const uint64_t memberId,nlohmann::json &member,NetworkSummaryInfo &info)
+bool DB::get(const uint64_t networkId, nlohmann::json& network, const uint64_t memberId, nlohmann::json& member, NetworkSummaryInfo& info)
 {
 	waitForReady();
 	Metrics::db_get_network_and_member_and_summary++;
@@ -162,7 +203,7 @@ bool DB::get(const uint64_t networkId,nlohmann::json &network,const uint64_t mem
 	{
 		std::shared_lock<std::shared_mutex> l2(nw->lock);
 		network = nw->config;
-		_fillSummaryInfo(nw,info);
+		_fillSummaryInfo(nw, info);
 		auto m = nw->members.find(memberId);
 		if (m == nw->members.end())
 			return false;
@@ -171,7 +212,7 @@ bool DB::get(const uint64_t networkId,nlohmann::json &network,const uint64_t mem
 	return true;
 }
 
-bool DB::get(const uint64_t networkId,nlohmann::json &network,std::vector<nlohmann::json> &members)
+bool DB::get(const uint64_t networkId, nlohmann::json& network, std::vector<nlohmann::json>& members)
 {
 	waitForReady();
 	Metrics::db_get_member_list++;
@@ -186,23 +227,23 @@ bool DB::get(const uint64_t networkId,nlohmann::json &network,std::vector<nlohma
 	{
 		std::shared_lock<std::shared_mutex> l2(nw->lock);
 		network = nw->config;
-		for(auto m=nw->members.begin();m!=nw->members.end();++m) {
+		for (auto m = nw->members.begin(); m != nw->members.end(); ++m) {
 			members.push_back(m->second);
 		}
 	}
 	return true;
 }
 
-void DB::networks(std::set<uint64_t> &networks)
+void DB::networks(std::set<uint64_t>& networks)
 {
 	waitForReady();
 	Metrics::db_get_network_list++;
 	std::shared_lock<std::shared_mutex> l(_networks_l);
-	for(auto n=_networks.begin();n!=_networks.end();++n)
+	for (auto n = _networks.begin(); n != _networks.end(); ++n)
 		networks.insert(n->first);
 }
 
-void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool notifyListeners)
+void DB::_memberChanged(nlohmann::json& old, nlohmann::json& memberConfig, bool notifyListeners)
 {
 	Metrics::db_member_change++;
 	uint64_t memberId = 0;
@@ -212,9 +253,9 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool no
 	std::shared_ptr<_Network> nw;
 
 	if (old.is_object()) {
-		memberId = OSUtils::jsonIntHex(old["id"],0ULL);
-		networkId = OSUtils::jsonIntHex(old["nwid"],0ULL);
-		if ((memberId)&&(networkId)) {
+		memberId = OSUtils::jsonIntHex(old["id"], 0ULL);
+		networkId = OSUtils::jsonIntHex(old["nwid"], 0ULL);
+		if ((memberId) && (networkId)) {
 			{
 				std::unique_lock<std::shared_mutex> l(_networks_l);
 				auto nw2 = _networks.find(networkId);
@@ -224,17 +265,17 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool no
 			}
 			if (nw) {
 				std::unique_lock<std::shared_mutex> l(nw->lock);
-				if (OSUtils::jsonBool(old["activeBridge"],false)) {
+				if (OSUtils::jsonBool(old["activeBridge"], false)) {
 					nw->activeBridgeMembers.erase(memberId);
 				}
-				wasAuth = OSUtils::jsonBool(old["authorized"],false);
+				wasAuth = OSUtils::jsonBool(old["authorized"], false);
 				if (wasAuth) {
 					nw->authorizedMembers.erase(memberId);
 				}
-				json &ips = old["ipAssignments"];
+				json& ips = old["ipAssignments"];
 				if (ips.is_array()) {
-					for(unsigned long i=0;i<ips.size();++i) {
-						json &ipj = ips[i];
+					for (unsigned long i = 0; i < ips.size(); ++i) {
+						json& ipj = ips[i];
 						if (ipj.is_string()) {
 							const std::string ips = ipj;
 							InetAddress ipa(ips.c_str());
@@ -248,14 +289,14 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool no
 	}
 
 	if (memberConfig.is_object()) {
-		if (!nw) {
-			memberId = OSUtils::jsonIntHex(memberConfig["id"],0ULL);
-			networkId = OSUtils::jsonIntHex(memberConfig["nwid"],0ULL);
-			if ((!memberId)||(!networkId))
+		if (! nw) {
+			memberId = OSUtils::jsonIntHex(memberConfig["id"], 0ULL);
+			networkId = OSUtils::jsonIntHex(memberConfig["nwid"], 0ULL);
+			if ((! memberId) || (! networkId))
 				return;
 			std::unique_lock<std::shared_mutex> l(_networks_l);
-			std::shared_ptr<_Network> &nw2 = _networks[networkId];
-			if (!nw2)
+			std::shared_ptr<_Network>& nw2 = _networks[networkId];
+			if (! nw2)
 				nw2.reset(new _Network);
 			nw = nw2;
 		}
@@ -265,18 +306,18 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool no
 
 			nw->members[memberId] = memberConfig;
 
-			if (OSUtils::jsonBool(memberConfig["activeBridge"],false)) {
+			if (OSUtils::jsonBool(memberConfig["activeBridge"], false)) {
 				nw->activeBridgeMembers.insert(memberId);
 			}
-			isAuth = OSUtils::jsonBool(memberConfig["authorized"],false);
+			isAuth = OSUtils::jsonBool(memberConfig["authorized"], false);
 			if (isAuth) {
 				Metrics::member_auths++;
 				nw->authorizedMembers.insert(memberId);
 			}
-			json &ips = memberConfig["ipAssignments"];
+			json& ips = memberConfig["ipAssignments"];
 			if (ips.is_array()) {
-				for(unsigned long i=0;i<ips.size();++i) {
-					json &ipj = ips[i];
+				for (unsigned long i = 0; i < ips.size(); ++i) {
+					json& ipj = ips[i];
 					if (ipj.is_string()) {
 						const std::string ips = ipj;
 						InetAddress ipa(ips.c_str());
@@ -286,8 +327,8 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool no
 				}
 			}
 
-			if (!isAuth) {
-				const int64_t ldt = (int64_t)OSUtils::jsonInt(memberConfig["lastDeauthorizedTime"],0ULL);
+			if (! isAuth) {
+				const int64_t ldt = (int64_t)OSUtils::jsonInt(memberConfig["lastDeauthorizedTime"], 0ULL);
 				if (ldt > nw->mostRecentDeauthTime)
 					nw->mostRecentDeauthTime = ldt;
 			}
@@ -295,11 +336,12 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool no
 
 		if (notifyListeners) {
 			std::unique_lock<std::shared_mutex> ll(_changeListeners_l);
-			for(auto i=_changeListeners.begin();i!=_changeListeners.end();++i) {
-				(*i)->onNetworkMemberUpdate(this,networkId,memberId,memberConfig);
+			for (auto i = _changeListeners.begin(); i != _changeListeners.end(); ++i) {
+				(*i)->onNetworkMemberUpdate(this, networkId, memberId, memberConfig);
 			}
 		}
-	} else if (memberId) {
+	}
+	else if (memberId) {
 		if (nw) {
 			std::unique_lock<std::shared_mutex> l(nw->lock);
 			nw->members.erase(memberId);
@@ -307,7 +349,7 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool no
 		if (networkId) {
 			std::unique_lock<std::shared_mutex> l(_networks_l);
 			auto er = _networkByMember.equal_range(memberId);
-			for(auto i=er.first;i!=er.second;++i) {
+			for (auto i = er.first; i != er.second; ++i) {
 				if (i->second == networkId) {
 					_networkByMember.erase(i);
 					break;
@@ -317,40 +359,45 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool no
 	}
 
 	if (notifyListeners) {
-		if(networkId != 0 && memberId != 0 && old.is_object() && !memberConfig.is_object()) {
+		if (networkId != 0 && memberId != 0 && old.is_object() && ! memberConfig.is_object()) {
 			// member delete
 			Metrics::member_count--;
-		} else if (networkId != 0 && memberId != 0 && !old.is_object() && memberConfig.is_object()) {
+		}
+		else if (networkId != 0 && memberId != 0 && ! old.is_object() && memberConfig.is_object()) {
 			// new member
 			Metrics::member_count++;
 		}
 
-		if (!wasAuth && isAuth) {
+		if (! wasAuth && isAuth) {
 			Metrics::member_auths++;
-		} else if (wasAuth && !isAuth) {
+		}
+		else if (wasAuth && ! isAuth) {
 			Metrics::member_deauths++;
-		} else {
+		}
+		else {
 			Metrics::member_changes++;
 		}
 	}
 
-	if ((notifyListeners)&&((wasAuth)&&(!isAuth)&&(networkId)&&(memberId))) {
+	if ((notifyListeners) && ((wasAuth) && (! isAuth) && (networkId) && (memberId))) {
 		std::unique_lock<std::shared_mutex> ll(_changeListeners_l);
-		for(auto i=_changeListeners.begin();i!=_changeListeners.end();++i) {
-			(*i)->onNetworkMemberDeauthorize(this,networkId,memberId);
+		for (auto i = _changeListeners.begin(); i != _changeListeners.end(); ++i) {
+			(*i)->onNetworkMemberDeauthorize(this, networkId, memberId);
 		}
 	}
 }
 
-void DB::_networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool notifyListeners)
+void DB::_networkChanged(nlohmann::json& old, nlohmann::json& networkConfig, bool notifyListeners)
 {
 	Metrics::db_network_change++;
 	if (notifyListeners) {
 		if (old.is_object() && old.contains("id") && networkConfig.is_object() && networkConfig.contains("id")) {
 			Metrics::network_changes++;
-		} else if (!old.is_object() && networkConfig.is_object() && networkConfig.contains("id")) {
+		}
+		else if (! old.is_object() && networkConfig.is_object() && networkConfig.contains("id")) {
 			Metrics::network_count++;
-		} else if (old.is_object() && old.contains("id") && !networkConfig.is_object()) {
+		}
+		else if (old.is_object() && old.contains("id") && ! networkConfig.is_object()) {
 			Metrics::network_count--;
 		}
 	}
@@ -362,8 +409,8 @@ void DB::_networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool
 			std::shared_ptr<_Network> nw;
 			{
 				std::unique_lock<std::shared_mutex> l(_networks_l);
-				std::shared_ptr<_Network> &nw2 = _networks[networkId];
-				if (!nw2)
+				std::shared_ptr<_Network>& nw2 = _networks[networkId];
+				if (! nw2)
 					nw2.reset(new _Network);
 				nw = nw2;
 			}
@@ -373,12 +420,13 @@ void DB::_networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool
 			}
 			if (notifyListeners) {
 				std::unique_lock<std::shared_mutex> ll(_changeListeners_l);
-				for(auto i=_changeListeners.begin();i!=_changeListeners.end();++i) {
-					(*i)->onNetworkUpdate(this,networkId,networkConfig);
+				for (auto i = _changeListeners.begin(); i != _changeListeners.end(); ++i) {
+					(*i)->onNetworkUpdate(this, networkId, networkConfig);
 				}
 			}
 		}
-	} else if (old.is_object()) {
+	}
+	else if (old.is_object()) {
 		const std::string ids = old["id"];
 		const uint64_t networkId = Utils::hexStrToU64(ids.c_str());
 		if (networkId) {
@@ -387,15 +435,16 @@ void DB::_networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool
 				nlohmann::json network;
 				std::vector<nlohmann::json> members;
 				this->get(networkId, network, members);
-				for(auto i=members.begin();i!=members.end();++i) {
+				for (auto i = members.begin(); i != members.end(); ++i) {
 					const std::string nodeID = (*i)["id"];
 					const uint64_t memberId = Utils::hexStrToU64(nodeID.c_str());
 					std::unique_lock<std::shared_mutex> ll(_changeListeners_l);
-					for(auto j=_changeListeners.begin();j!=_changeListeners.end();++j) {
-						(*j)->onNetworkMemberDeauthorize(this,networkId,memberId);
+					for (auto j = _changeListeners.begin(); j != _changeListeners.end(); ++j) {
+						(*j)->onNetworkMemberDeauthorize(this, networkId, memberId);
 					}
 				}
-			} catch (std::exception &e) {
+			}
+			catch (std::exception& e) {
 				std::cerr << "Error deauthorizing members on network delete: " << e.what() << std::endl;
 			}
 
@@ -406,17 +455,17 @@ void DB::_networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool
 	}
 }
 
-void DB::_fillSummaryInfo(const std::shared_ptr<_Network> &nw,NetworkSummaryInfo &info)
+void DB::_fillSummaryInfo(const std::shared_ptr<_Network>& nw, NetworkSummaryInfo& info)
 {
-	for(auto ab=nw->activeBridgeMembers.begin();ab!=nw->activeBridgeMembers.end();++ab)
+	for (auto ab = nw->activeBridgeMembers.begin(); ab != nw->activeBridgeMembers.end(); ++ab)
 		info.activeBridges.push_back(Address(*ab));
-	std::sort(info.activeBridges.begin(),info.activeBridges.end());
-	for(auto ip=nw->allocatedIps.begin();ip!=nw->allocatedIps.end();++ip)
+	std::sort(info.activeBridges.begin(), info.activeBridges.end());
+	for (auto ip = nw->allocatedIps.begin(); ip != nw->allocatedIps.end(); ++ip)
 		info.allocatedIps.push_back(*ip);
-	std::sort(info.allocatedIps.begin(),info.allocatedIps.end());
+	std::sort(info.allocatedIps.begin(), info.allocatedIps.end());
 	info.authorizedMemberCount = (unsigned long)nw->authorizedMembers.size();
 	info.totalMemberCount = (unsigned long)nw->members.size();
 	info.mostRecentDeauthTime = nw->mostRecentDeauthTime;
 }
 
-} // namespace ZeroTier
+}	// namespace ZeroTier

+ 78 - 79
controller/DB.hpp

@@ -14,49 +14,36 @@
 #ifndef ZT_CONTROLLER_DB_HPP
 #define ZT_CONTROLLER_DB_HPP
 
-//#define ZT_CONTROLLER_USE_LIBPQ
+// #define ZT_CONTROLLER_USE_LIBPQ
 
 #include "../node/Constants.hpp"
 #include "../node/Identity.hpp"
 #include "../node/InetAddress.hpp"
-#include "../osdep/OSUtils.hpp"
 #include "../osdep/BlockingQueue.hpp"
+#include "../osdep/OSUtils.hpp"
 
+#include <atomic>
+#include <map>
 #include <memory>
+#include <nlohmann/json.hpp>
+#include <prometheus/simpleapi.h>
+#include <set>
+#include <shared_mutex>
 #include <string>
 #include <thread>
 #include <unordered_map>
 #include <unordered_set>
 #include <vector>
-#include <atomic>
-#include <shared_mutex>
-#include <set>
-#include <map>
-
-#include <nlohmann/json.hpp>
-
-#include <prometheus/simpleapi.h>
 
 #define ZT_MEMBER_AUTH_TIMEOUT_NOTIFY_BEFORE 25000
 
-namespace ZeroTier
-{
-
-struct AuthInfo
-{
-public:
-	AuthInfo() 
-	: enabled(false)
-	, version(0)
-	, authenticationURL()
-	, authenticationExpiryTime(0)
-	, issuerURL()
-	, centralAuthURL()
-	, ssoNonce()
-	, ssoState()
-	, ssoClientID()
-	, ssoProvider("default")
-	{}
+namespace ZeroTier {
+
+struct AuthInfo {
+  public:
+	AuthInfo() : enabled(false), version(0), authenticationURL(), authenticationExpiryTime(0), issuerURL(), centralAuthURL(), ssoNonce(), ssoState(), ssoClientID(), ssoProvider("default")
+	{
+	}
 
 	bool enabled;
 	uint64_t version;
@@ -73,22 +60,31 @@ public:
 /**
  * Base class with common infrastructure for all controller DB implementations
  */
-class DB
-{
-public:
-	class ChangeListener
-	{
-	public:
-		ChangeListener() {}
-		virtual ~ChangeListener() {}
-		virtual void onNetworkUpdate(const void *db,uint64_t networkId,const nlohmann::json &network) {}
-		virtual void onNetworkMemberUpdate(const void *db,uint64_t networkId,uint64_t memberId,const nlohmann::json &member) {}
-		virtual void onNetworkMemberDeauthorize(const void *db,uint64_t networkId,uint64_t memberId) {}
+class DB {
+  public:
+	class ChangeListener {
+	  public:
+		ChangeListener()
+		{
+		}
+		virtual ~ChangeListener()
+		{
+		}
+		virtual void onNetworkUpdate(const void* db, uint64_t networkId, const nlohmann::json& network)
+		{
+		}
+		virtual void onNetworkMemberUpdate(const void* db, uint64_t networkId, uint64_t memberId, const nlohmann::json& member)
+		{
+		}
+		virtual void onNetworkMemberDeauthorize(const void* db, uint64_t networkId, uint64_t memberId)
+		{
+		}
 	};
 
-	struct NetworkSummaryInfo
-	{
-		NetworkSummaryInfo() : authorizedMemberCount(0),totalMemberCount(0),mostRecentDeauthTime(0) {}
+	struct NetworkSummaryInfo {
+		NetworkSummaryInfo() : authorizedMemberCount(0), totalMemberCount(0), mostRecentDeauthTime(0)
+		{
+		}
 		std::vector<Address> activeBridges;
 		std::vector<InetAddress> allocatedIps;
 		unsigned long authorizedMemberCount;
@@ -96,10 +92,10 @@ public:
 		int64_t mostRecentDeauthTime;
 	};
 
-	static void initNetwork(nlohmann::json &network);
-	static void initMember(nlohmann::json &member);
-	static void cleanNetwork(nlohmann::json &network);
-	static void cleanMember(nlohmann::json &member);
+	static void initNetwork(nlohmann::json& network);
+	static void initMember(nlohmann::json& member);
+	static void cleanNetwork(nlohmann::json& network);
+	static void cleanMember(nlohmann::json& member);
 
 	DB();
 	virtual ~DB();
@@ -113,41 +109,43 @@ public:
 		return (_networks.find(networkId) != _networks.end());
 	}
 
-	bool get(const uint64_t networkId,nlohmann::json &network);
-	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 get(const uint64_t networkId, nlohmann::json& network);
+	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);
 
-	void networks(std::set<uint64_t> &networks);
+	void networks(std::set<uint64_t>& networks);
 
-	template<typename F>
-	inline void each(F f)
+	template <typename F> inline void each(F f)
 	{
 		nlohmann::json nullJson;
 		std::unique_lock<std::shared_mutex> lck(_networks_l);
-		for(auto nw=_networks.begin();nw!=_networks.end();++nw) {
-			f(nw->first,nw->second->config,0,nullJson); // first provide network with 0 for member ID
-			for(auto m=nw->second->members.begin();m!=nw->second->members.end();++m) {
-				f(nw->first,nw->second->config,m->first,m->second);
+		for (auto nw = _networks.begin(); nw != _networks.end(); ++nw) {
+			f(nw->first, nw->second->config, 0, nullJson);	 // first provide network with 0 for member ID
+			for (auto m = nw->second->members.begin(); m != nw->second->members.end(); ++m) {
+				f(nw->first, nw->second->config, m->first, m->second);
 			}
 		}
 	}
 
-	virtual bool save(nlohmann::json &record,bool notifyListeners) = 0;
+	virtual bool save(nlohmann::json& record, bool notifyListeners) = 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;
+	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;
 
-	virtual AuthInfo getSSOAuthInfo(const nlohmann::json &member, const std::string &redirectURL) { return AuthInfo(); }
+	virtual AuthInfo getSSOAuthInfo(const nlohmann::json& member, const std::string& redirectURL)
+	{
+		return AuthInfo();
+	}
 
-	inline void addListener(DB::ChangeListener *const listener)
+	inline void addListener(DB::ChangeListener* const listener)
 	{
 		std::unique_lock<std::shared_mutex> l(_changeListeners_l);
 		_changeListeners.push_back(listener);
 	}
 
-protected:
-	static inline bool _compareRecords(const nlohmann::json &a,const nlohmann::json &b)
+  protected:
+	static inline bool _compareRecords(const nlohmann::json& a, const nlohmann::json& b)
 	{
 		if (a.is_object() == b.is_object()) {
 			if (a.is_object()) {
@@ -155,10 +153,10 @@ protected:
 					return false;
 				auto amap = a.get<nlohmann::json::object_t>();
 				auto bmap = b.get<nlohmann::json::object_t>();
-				for(auto ai=amap.begin();ai!=amap.end();++ai) {
-					if (ai->first != "revision") { // ignore revision, compare only non-revision-counter fields
+				for (auto ai = amap.begin(); ai != amap.end(); ++ai) {
+					if (ai->first != "revision") {	 // ignore revision, compare only non-revision-counter fields
 						auto bi = bmap.find(ai->first);
-						if ((bi == bmap.end())||(bi->second != ai->second))
+						if ((bi == bmap.end()) || (bi->second != ai->second))
 							return false;
 					}
 				}
@@ -169,29 +167,30 @@ protected:
 		return false;
 	}
 
-	struct _Network
-	{
-		_Network() : mostRecentDeauthTime(0) {}
+	struct _Network {
+		_Network() : mostRecentDeauthTime(0)
+		{
+		}
 		nlohmann::json config;
-		std::unordered_map<uint64_t,nlohmann::json> members;
+		std::unordered_map<uint64_t, nlohmann::json> members;
 		std::unordered_set<uint64_t> activeBridgeMembers;
 		std::unordered_set<uint64_t> authorizedMembers;
-		std::unordered_set<InetAddress,InetAddress::Hasher> allocatedIps;
+		std::unordered_set<InetAddress, InetAddress::Hasher> allocatedIps;
 		int64_t mostRecentDeauthTime;
 		std::shared_mutex lock;
 	};
 
-	virtual void _memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool notifyListeners);
-	virtual void _networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool notifyListeners);
-	void _fillSummaryInfo(const std::shared_ptr<_Network> &nw,NetworkSummaryInfo &info);
+	virtual void _memberChanged(nlohmann::json& old, nlohmann::json& memberConfig, bool notifyListeners);
+	virtual void _networkChanged(nlohmann::json& old, nlohmann::json& networkConfig, bool notifyListeners);
+	void _fillSummaryInfo(const std::shared_ptr<_Network>& nw, NetworkSummaryInfo& info);
 
-	std::vector<DB::ChangeListener *> _changeListeners;
-	std::unordered_map< uint64_t,std::shared_ptr<_Network> > _networks;
-	std::unordered_multimap< uint64_t,uint64_t > _networkByMember;
+	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::shared_mutex _changeListeners_l;
 	mutable std::shared_mutex _networks_l;
 };
 
-} // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 66 - 67
controller/DBMirrorSet.cpp

@@ -15,56 +15,54 @@
 
 namespace ZeroTier {
 
-DBMirrorSet::DBMirrorSet(DB::ChangeListener *listener)
-	: _listener(listener)
-	, _running(true)
-	, _syncCheckerThread()
-	, _dbs()
-	, _dbs_l()
+DBMirrorSet::DBMirrorSet(DB::ChangeListener* listener) : _listener(listener), _running(true), _syncCheckerThread(), _dbs(), _dbs_l()
 {
 	_syncCheckerThread = std::thread([this]() {
-		for(;;) {
-			for(int i=0;i<120;++i) { // 1 minute delay between checks
-				if (!_running)
+		for (;;) {
+			for (int i = 0; i < 120; ++i) {	  // 1 minute delay between checks
+				if (! _running)
 					return;
 				std::this_thread::sleep_for(std::chrono::milliseconds(500));
 			}
 
-			std::vector< std::shared_ptr<DB> > dbs;
+			std::vector<std::shared_ptr<DB> > dbs;
 			{
 				std::unique_lock<std::shared_mutex> l(_dbs_l);
 				if (_dbs.size() <= 1)
-					continue; // no need to do this if there's only one DB, so skip the iteration
+					continue;	// no need to do this if there's only one DB, so skip the iteration
 				dbs = _dbs;
 			}
 
-			for(auto db=dbs.begin();db!=dbs.end();++db) {
-				(*db)->each([&dbs,&db](uint64_t networkId,const nlohmann::json &network,uint64_t memberId,const nlohmann::json &member) {
+			for (auto db = dbs.begin(); db != dbs.end(); ++db) {
+				(*db)->each([&dbs, &db](uint64_t networkId, const nlohmann::json& network, uint64_t memberId, const nlohmann::json& member) {
 					try {
 						if (network.is_object()) {
 							if (memberId == 0) {
-								for(auto db2=dbs.begin();db2!=dbs.end();++db2) {
+								for (auto db2 = dbs.begin(); db2 != dbs.end(); ++db2) {
 									if (db->get() != db2->get()) {
 										nlohmann::json nw2;
-										if ((!(*db2)->get(networkId,nw2))||((nw2.is_object())&&(OSUtils::jsonInt(nw2["revision"],0) < OSUtils::jsonInt(network["revision"],0)))) {
+										if ((! (*db2)->get(networkId, nw2)) || ((nw2.is_object()) && (OSUtils::jsonInt(nw2["revision"], 0) < OSUtils::jsonInt(network["revision"], 0)))) {
 											nw2 = network;
-											(*db2)->save(nw2,false);
+											(*db2)->save(nw2, false);
 										}
 									}
 								}
-							} else if (member.is_object()) {
-								for(auto db2=dbs.begin();db2!=dbs.end();++db2) {
+							}
+							else if (member.is_object()) {
+								for (auto db2 = dbs.begin(); db2 != dbs.end(); ++db2) {
 									if (db->get() != db2->get()) {
-										nlohmann::json nw2,m2;
-										if ((!(*db2)->get(networkId,nw2,memberId,m2))||((m2.is_object())&&(OSUtils::jsonInt(m2["revision"],0) < OSUtils::jsonInt(member["revision"],0)))) {
+										nlohmann::json nw2, m2;
+										if ((! (*db2)->get(networkId, nw2, memberId, m2)) || ((m2.is_object()) && (OSUtils::jsonInt(m2["revision"], 0) < OSUtils::jsonInt(member["revision"], 0)))) {
 											m2 = member;
-											(*db2)->save(m2,false);
+											(*db2)->save(m2, false);
 										}
 									}
 								}
 							}
 						}
-					} catch ( ... ) {} // skip entries that generate JSON errors
+					}
+					catch (...) {
+					}	// skip entries that generate JSON errors
 				});
 			}
 		}
@@ -80,58 +78,58 @@ DBMirrorSet::~DBMirrorSet()
 bool DBMirrorSet::hasNetwork(const uint64_t networkId) const
 {
 	std::shared_lock<std::shared_mutex> l(_dbs_l);
-	for(auto d=_dbs.begin();d!=_dbs.end();++d) {
+	for (auto d = _dbs.begin(); d != _dbs.end(); ++d) {
 		if ((*d)->hasNetwork(networkId))
 			return true;
 	}
 	return false;
 }
 
-bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network)
+bool DBMirrorSet::get(const uint64_t networkId, nlohmann::json& network)
 {
 	std::shared_lock<std::shared_mutex> l(_dbs_l);
-	for(auto d=_dbs.begin();d!=_dbs.end();++d) {
-		if ((*d)->get(networkId,network)) {
+	for (auto d = _dbs.begin(); d != _dbs.end(); ++d) {
+		if ((*d)->get(networkId, network)) {
 			return true;
 		}
 	}
 	return false;
 }
 
-bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network,const uint64_t memberId,nlohmann::json &member)
+bool DBMirrorSet::get(const uint64_t networkId, nlohmann::json& network, const uint64_t memberId, nlohmann::json& member)
 {
 	std::shared_lock<std::shared_mutex> l(_dbs_l);
-	for(auto d=_dbs.begin();d!=_dbs.end();++d) {
-		if ((*d)->get(networkId,network,memberId,member))
+	for (auto d = _dbs.begin(); d != _dbs.end(); ++d) {
+		if ((*d)->get(networkId, network, memberId, member))
 			return true;
 	}
 	return false;
 }
 
-bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network,const uint64_t memberId,nlohmann::json &member,DB::NetworkSummaryInfo &info)
+bool DBMirrorSet::get(const uint64_t networkId, nlohmann::json& network, const uint64_t memberId, nlohmann::json& member, DB::NetworkSummaryInfo& info)
 {
 	std::shared_lock<std::shared_mutex> l(_dbs_l);
-	for(auto d=_dbs.begin();d!=_dbs.end();++d) {
-		if ((*d)->get(networkId,network,memberId,member,info))
+	for (auto d = _dbs.begin(); d != _dbs.end(); ++d) {
+		if ((*d)->get(networkId, network, memberId, member, info))
 			return true;
 	}
 	return false;
 }
 
-bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network,std::vector<nlohmann::json> &members)
+bool DBMirrorSet::get(const uint64_t networkId, nlohmann::json& network, std::vector<nlohmann::json>& members)
 {
 	std::shared_lock<std::shared_mutex> l(_dbs_l);
-	for(auto d=_dbs.begin();d!=_dbs.end();++d) {
-		if ((*d)->get(networkId,network,members))
+	for (auto d = _dbs.begin(); d != _dbs.end(); ++d) {
+		if ((*d)->get(networkId, network, members))
 			return true;
 	}
 	return false;
 }
 
-AuthInfo DBMirrorSet::getSSOAuthInfo(const nlohmann::json &member, const std::string &redirectURL) 
+AuthInfo DBMirrorSet::getSSOAuthInfo(const nlohmann::json& member, const std::string& redirectURL)
 {
 	std::shared_lock<std::shared_mutex> l(_dbs_l);
-	for(auto d=_dbs.begin();d!=_dbs.end();++d) { 
+	for (auto d = _dbs.begin(); d != _dbs.end(); ++d) {
 		AuthInfo info = (*d)->getSSOAuthInfo(member, redirectURL);
 		if (info.enabled) {
 			return info;
@@ -140,10 +138,10 @@ AuthInfo DBMirrorSet::getSSOAuthInfo(const nlohmann::json &member, const std::st
 	return AuthInfo();
 }
 
-void DBMirrorSet::networks(std::set<uint64_t> &networks)
+void DBMirrorSet::networks(std::set<uint64_t>& networks)
 {
 	std::shared_lock<std::shared_mutex> l(_dbs_l);
-	for(auto d=_dbs.begin();d!=_dbs.end();++d) {
+	for (auto d = _dbs.begin(); d != _dbs.end(); ++d) {
 		(*d)->networks(networks);
 	}
 }
@@ -152,7 +150,7 @@ bool DBMirrorSet::waitForReady()
 {
 	bool r = false;
 	std::shared_lock<std::shared_mutex> l(_dbs_l);
-	for(auto d=_dbs.begin();d!=_dbs.end();++d) {
+	for (auto d = _dbs.begin(); d != _dbs.end(); ++d) {
 		r |= (*d)->waitForReady();
 	}
 	return r;
@@ -161,30 +159,31 @@ bool DBMirrorSet::waitForReady()
 bool DBMirrorSet::isReady()
 {
 	std::shared_lock<std::shared_mutex> l(_dbs_l);
-	for(auto d=_dbs.begin();d!=_dbs.end();++d) {
-		if (!(*d)->isReady())
+	for (auto d = _dbs.begin(); d != _dbs.end(); ++d) {
+		if (! (*d)->isReady())
 			return false;
 	}
 	return true;
 }
 
-bool DBMirrorSet::save(nlohmann::json &record,bool notifyListeners)
+bool DBMirrorSet::save(nlohmann::json& record, bool notifyListeners)
 {
-	std::vector< std::shared_ptr<DB> > dbs;
+	std::vector<std::shared_ptr<DB> > dbs;
 	{
 		std::unique_lock<std::shared_mutex> l(_dbs_l);
 		dbs = _dbs;
 	}
 	if (notifyListeners) {
-		for(auto d=dbs.begin();d!=dbs.end();++d) {
-			if ((*d)->save(record,true))
+		for (auto d = dbs.begin(); d != dbs.end(); ++d) {
+			if ((*d)->save(record, true))
 				return true;
 		}
 		return false;
-	} else {
+	}
+	else {
 		bool modified = false;
-		for(auto d=dbs.begin();d!=dbs.end();++d) {
-			modified |= (*d)->save(record,false);
+		for (auto d = dbs.begin(); d != dbs.end(); ++d) {
+			modified |= (*d)->save(record, false);
 		}
 		return modified;
 	}
@@ -193,54 +192,54 @@ bool DBMirrorSet::save(nlohmann::json &record,bool notifyListeners)
 void DBMirrorSet::eraseNetwork(const uint64_t networkId)
 {
 	std::unique_lock<std::shared_mutex> l(_dbs_l);
-	for(auto d=_dbs.begin();d!=_dbs.end();++d) {
+	for (auto d = _dbs.begin(); d != _dbs.end(); ++d) {
 		(*d)->eraseNetwork(networkId);
 	}
 }
 
-void DBMirrorSet::eraseMember(const uint64_t networkId,const uint64_t memberId)
+void DBMirrorSet::eraseMember(const uint64_t networkId, const uint64_t memberId)
 {
 	std::unique_lock<std::shared_mutex> l(_dbs_l);
-	for(auto d=_dbs.begin();d!=_dbs.end();++d) {
-		(*d)->eraseMember(networkId,memberId);
+	for (auto d = _dbs.begin(); d != _dbs.end(); ++d) {
+		(*d)->eraseMember(networkId, memberId);
 	}
 }
 
-void DBMirrorSet::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress)
+void DBMirrorSet::nodeIsOnline(const uint64_t networkId, const uint64_t memberId, const InetAddress& physicalAddress)
 {
 	std::shared_lock<std::shared_mutex> l(_dbs_l);
-	for(auto d=_dbs.begin();d!=_dbs.end();++d) {
-		(*d)->nodeIsOnline(networkId,memberId,physicalAddress);
+	for (auto d = _dbs.begin(); d != _dbs.end(); ++d) {
+		(*d)->nodeIsOnline(networkId, memberId, physicalAddress);
 	}
 }
 
-void DBMirrorSet::onNetworkUpdate(const void *db,uint64_t networkId,const nlohmann::json &network)
+void DBMirrorSet::onNetworkUpdate(const void* db, uint64_t networkId, const nlohmann::json& network)
 {
 	nlohmann::json record(network);
 	std::unique_lock<std::shared_mutex> l(_dbs_l);
-	for(auto d=_dbs.begin();d!=_dbs.end();++d) {
+	for (auto d = _dbs.begin(); d != _dbs.end(); ++d) {
 		if (d->get() != db) {
-			(*d)->save(record,false);
+			(*d)->save(record, false);
 		}
 	}
-	_listener->onNetworkUpdate(this,networkId,network);
+	_listener->onNetworkUpdate(this, networkId, network);
 }
 
-void DBMirrorSet::onNetworkMemberUpdate(const void *db,uint64_t networkId,uint64_t memberId,const nlohmann::json &member)
+void DBMirrorSet::onNetworkMemberUpdate(const void* db, uint64_t networkId, uint64_t memberId, const nlohmann::json& member)
 {
 	nlohmann::json record(member);
 	std::unique_lock<std::shared_mutex> l(_dbs_l);
-	for(auto d=_dbs.begin();d!=_dbs.end();++d) {
+	for (auto d = _dbs.begin(); d != _dbs.end(); ++d) {
 		if (d->get() != db) {
-			(*d)->save(record,false);
+			(*d)->save(record, false);
 		}
 	}
-	_listener->onNetworkMemberUpdate(this,networkId,memberId,member);
+	_listener->onNetworkMemberUpdate(this, networkId, memberId, member);
 }
 
-void DBMirrorSet::onNetworkMemberDeauthorize(const void *db,uint64_t networkId,uint64_t memberId)
+void DBMirrorSet::onNetworkMemberDeauthorize(const void* db, uint64_t networkId, uint64_t memberId)
 {
-	_listener->onNetworkMemberDeauthorize(this,networkId,memberId);
+	_listener->onNetworkMemberDeauthorize(this, networkId, memberId);
 }
 
-} // namespace ZeroTier
+}	// namespace ZeroTier

+ 22 - 23
controller/DBMirrorSet.hpp

@@ -16,58 +16,57 @@
 
 #include "DB.hpp"
 
-#include <vector>
 #include <memory>
-#include <shared_mutex>
 #include <set>
+#include <shared_mutex>
 #include <thread>
+#include <vector>
 
 namespace ZeroTier {
 
-class DBMirrorSet : public DB::ChangeListener
-{
-public:
-	DBMirrorSet(DB::ChangeListener *listener);
+class DBMirrorSet : public DB::ChangeListener {
+  public:
+	DBMirrorSet(DB::ChangeListener* listener);
 	virtual ~DBMirrorSet();
 
 	bool hasNetwork(const uint64_t networkId) const;
 
-	bool get(const uint64_t networkId,nlohmann::json &network);
-	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,DB::NetworkSummaryInfo &info);
-	bool get(const uint64_t networkId,nlohmann::json &network,std::vector<nlohmann::json> &members);
+	bool get(const uint64_t networkId, nlohmann::json& network);
+	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, DB::NetworkSummaryInfo& info);
+	bool get(const uint64_t networkId, nlohmann::json& network, std::vector<nlohmann::json>& members);
 
-	void networks(std::set<uint64_t> &networks);
+	void networks(std::set<uint64_t>& networks);
 
 	bool waitForReady();
 	bool isReady();
-	bool save(nlohmann::json &record,bool notifyListeners);
+	bool save(nlohmann::json& record, bool notifyListeners);
 	void eraseNetwork(const uint64_t networkId);
-	void eraseMember(const uint64_t networkId,const uint64_t memberId);
-	void nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress);
+	void eraseMember(const uint64_t networkId, const uint64_t memberId);
+	void nodeIsOnline(const uint64_t networkId, const uint64_t memberId, const InetAddress& physicalAddress);
 
 	// These are called by various DB instances when changes occur.
-	virtual void onNetworkUpdate(const void *db,uint64_t networkId,const nlohmann::json &network);
-	virtual void onNetworkMemberUpdate(const void *db,uint64_t networkId,uint64_t memberId,const nlohmann::json &member);
-	virtual void onNetworkMemberDeauthorize(const void *db,uint64_t networkId,uint64_t memberId);
+	virtual void onNetworkUpdate(const void* db, uint64_t networkId, const nlohmann::json& network);
+	virtual void onNetworkMemberUpdate(const void* db, uint64_t networkId, uint64_t memberId, const nlohmann::json& member);
+	virtual void onNetworkMemberDeauthorize(const void* db, uint64_t networkId, uint64_t memberId);
 
-	AuthInfo getSSOAuthInfo(const nlohmann::json &member, const std::string &redirectURL);
+	AuthInfo getSSOAuthInfo(const nlohmann::json& member, const std::string& redirectURL);
 
-	inline void addDB(const std::shared_ptr<DB> &db)
+	inline void addDB(const std::shared_ptr<DB>& db)
 	{
 		db->addListener(this);
 		std::unique_lock<std::shared_mutex> l(_dbs_l);
 		_dbs.push_back(db);
 	}
 
-private:
-	DB::ChangeListener *const _listener;
+  private:
+	DB::ChangeListener* const _listener;
 	std::atomic_bool _running;
 	std::thread _syncCheckerThread;
-	std::vector< std::shared_ptr< DB > > _dbs;
+	std::vector<std::shared_ptr<DB> > _dbs;
 	mutable std::shared_mutex _dbs_l;
 };
 
-} // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

File diff suppressed because it is too large
+ 328 - 252
controller/EmbeddedNetworkController.cpp


+ 77 - 80
controller/EmbeddedNetworkController.hpp

@@ -14,112 +14,109 @@
 #ifndef ZT_SQLITENETWORKCONTROLLER_HPP
 #define ZT_SQLITENETWORKCONTROLLER_HPP
 
-#include <stdint.h>
-
-#include <string>
-#include <map>
-#include <vector>
-#include <set>
-#include <list>
-#include <thread>
-#include <unordered_map>
-#include <atomic>
-
+#include "../node/Address.hpp"
 #include "../node/Constants.hpp"
+#include "../node/InetAddress.hpp"
 #include "../node/NetworkController.hpp"
 #include "../node/Utils.hpp"
-#include "../node/Address.hpp"
-#include "../node/InetAddress.hpp"
-
+#include "../osdep/BlockingQueue.hpp"
 #include "../osdep/OSUtils.hpp"
 #include "../osdep/Thread.hpp"
-#include "../osdep/BlockingQueue.hpp"
-
-#include <nlohmann/json.hpp>
-
-#include <cpp-httplib/httplib.h>
-
 #include "DB.hpp"
 #include "DBMirrorSet.hpp"
 
+#include <atomic>
+#include <cpp-httplib/httplib.h>
+#include <list>
+#include <map>
+#include <nlohmann/json.hpp>
+#include <set>
+#include <stdint.h>
+#include <string>
+#include <thread>
+#include <unordered_map>
+#include <vector>
+
 namespace ZeroTier {
 
 class Node;
 struct RedisConfig;
 
-class EmbeddedNetworkController : public NetworkController,public DB::ChangeListener
-{
-public:
+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 *ztPath,const char *dbPath, int listenPort, RedisConfig *rc);
+	EmbeddedNetworkController(Node* node, const char* ztPath, const char* dbPath, int listenPort, RedisConfig* rc);
 	virtual ~EmbeddedNetworkController();
 
-	virtual void init(const Identity &signingId,Sender *sender);
+	virtual void init(const Identity& signingId, Sender* sender);
 
-	void setSSORedirectURL(const std::string &url);
+	void setSSORedirectURL(const std::string& url);
 
-	virtual void request(
-		uint64_t nwid,
-		const InetAddress &fromAddr,
-		uint64_t requestPacketId,
-		const Identity &identity,
-		const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData);
+	virtual void request(uint64_t nwid, const InetAddress& fromAddr, uint64_t requestPacketId, const Identity& identity, const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY>& metaData);
 
-	void configureHTTPControlPlane(
-		httplib::Server &s,
-		httplib::Server &sV6,
-		const std::function<void(const httplib::Request&, httplib::Response&, std::string)>);
+	void configureHTTPControlPlane(httplib::Server& s, httplib::Server& sV6, const std::function<void(const httplib::Request&, httplib::Response&, std::string)>);
 
-	void handleRemoteTrace(const ZT_RemoteTrace &rt);
+	void handleRemoteTrace(const ZT_RemoteTrace& rt);
 
-	virtual void onNetworkUpdate(const void *db,uint64_t networkId,const nlohmann::json &network);
-	virtual void onNetworkMemberUpdate(const void *db,uint64_t networkId,uint64_t memberId,const nlohmann::json &member);
-	virtual void onNetworkMemberDeauthorize(const void *db,uint64_t networkId,uint64_t memberId);
+	virtual void onNetworkUpdate(const void* db, uint64_t networkId, const nlohmann::json& network);
+	virtual void onNetworkMemberUpdate(const void* db, uint64_t networkId, uint64_t memberId, const nlohmann::json& member);
+	virtual void onNetworkMemberDeauthorize(const void* db, uint64_t networkId, 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);
+  private:
+	void _request(uint64_t nwid, const InetAddress& fromAddr, uint64_t requestPacketId, const Identity& identity, const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY>& metaData);
 	void _startThreads();
 	void _ssoExpiryThread();
 
-	std::string networkUpdateFromPostData(uint64_t networkID, const std::string &body);
+	std::string networkUpdateFromPostData(uint64_t networkID, const std::string& body);
 
-	struct _RQEntry
-	{
+	struct _RQEntry {
 		uint64_t nwid;
 		uint64_t requestPacketId;
 		InetAddress fromAddr;
 		Identity identity;
 		Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> metaData;
-		enum {
-			RQENTRY_TYPE_REQUEST = 0
-		} type;
+		enum { RQENTRY_TYPE_REQUEST = 0 } type;
 	};
 
-	struct _MemberStatusKey
-	{
-		_MemberStatusKey() : networkId(0),nodeId(0) {}
-		_MemberStatusKey(const uint64_t nwid,const uint64_t nid) : networkId(nwid),nodeId(nid) {}
+	struct _MemberStatusKey {
+		_MemberStatusKey() : networkId(0), nodeId(0)
+		{
+		}
+		_MemberStatusKey(const uint64_t nwid, const uint64_t nid) : networkId(nwid), nodeId(nid)
+		{
+		}
 		uint64_t networkId;
 		uint64_t nodeId;
-		inline bool operator==(const _MemberStatusKey &k) const { return ((k.networkId == networkId)&&(k.nodeId == nodeId)); }
-		inline bool operator<(const _MemberStatusKey &k) const { return (k.networkId < networkId) || ((k.networkId == networkId)&&(k.nodeId < nodeId)); }
+		inline bool operator==(const _MemberStatusKey& k) const
+		{
+			return ((k.networkId == networkId) && (k.nodeId == nodeId));
+		}
+		inline bool operator<(const _MemberStatusKey& k) const
+		{
+			return (k.networkId < networkId) || ((k.networkId == networkId) && (k.nodeId < nodeId));
+		}
 	};
-	struct _MemberStatus
-	{
-		_MemberStatus() : lastRequestTime(0),authenticationExpiryTime(-1),vMajor(-1),vMinor(-1),vRev(-1),vProto(-1) {}
+	struct _MemberStatus {
+		_MemberStatus() : lastRequestTime(0), authenticationExpiryTime(-1), vMajor(-1), vMinor(-1), vRev(-1), vProto(-1)
+		{
+		}
 		int64_t lastRequestTime;
 		int64_t authenticationExpiryTime;
-		int vMajor,vMinor,vRev,vProto;
+		int vMajor, vMinor, vRev, vProto;
 		Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> lastRequestMetaData;
 		Identity identity;
-		inline bool online(const int64_t now) const { return ((now - lastRequestTime) < (ZT_NETWORK_AUTOCONF_DELAY * 2)); }
+		inline bool online(const int64_t now) const
+		{
+			return ((now - lastRequestTime) < (ZT_NETWORK_AUTOCONF_DELAY * 2));
+		}
 	};
-	struct _MemberStatusHash
-	{
-		inline std::size_t operator()(const _MemberStatusKey &networkIdNodeId) const
+	struct _MemberStatusHash {
+		inline std::size_t operator()(const _MemberStatusKey& networkIdNodeId) const
 		{
 			return (std::size_t)(networkIdNodeId.networkId + networkIdNodeId.nodeId);
 		}
@@ -127,26 +124,26 @@ private:
 
 	const int64_t _startTime;
 	int _listenPort;
-	Node *const _node;
+	Node* const _node;
 	std::string _ztPath;
 	std::string _path;
 	Identity _signingId;
 	std::string _signingIdAddressString;
-	NetworkController::Sender *_sender;
+	NetworkController::Sender* _sender;
 
 	DBMirrorSet _db;
-	BlockingQueue< _RQEntry * > _queue;
+	BlockingQueue<_RQEntry*> _queue;
 
 	std::vector<std::thread> _threads;
 	std::mutex _threads_l;
 
-	std::unordered_map< _MemberStatusKey,_MemberStatus,_MemberStatusHash > _memberStatus;
+	std::unordered_map<_MemberStatusKey, _MemberStatus, _MemberStatusHash> _memberStatus;
 	std::mutex _memberStatus_l;
 
-	std::set< std::pair<int64_t, _MemberStatusKey> > _expiringSoon;
+	std::set<std::pair<int64_t, _MemberStatusKey> > _expiringSoon;
 	std::mutex _expiringSoon_l;
 
-	RedisConfig *_rc;
+	RedisConfig* _rc;
 	std::string _ssoRedirectURL;
 
 	bool _ssoExpiryRunning;
@@ -154,30 +151,30 @@ private:
 
 #ifdef CENTRAL_CONTROLLER_REQUEST_BENCHMARK
 	prometheus::simpleapi::benchmark_family_t _member_status_lookup;
-	prometheus::simpleapi::counter_family_t   _member_status_lookup_count;
+	prometheus::simpleapi::counter_family_t _member_status_lookup_count;
 	prometheus::simpleapi::benchmark_family_t _node_is_online;
-	prometheus::simpleapi::counter_family_t   _node_is_online_count;
+	prometheus::simpleapi::counter_family_t _node_is_online_count;
 	prometheus::simpleapi::benchmark_family_t _get_and_init_member;
-	prometheus::simpleapi::counter_family_t   _get_and_init_member_count;
+	prometheus::simpleapi::counter_family_t _get_and_init_member_count;
 	prometheus::simpleapi::benchmark_family_t _have_identity;
-	prometheus::simpleapi::counter_family_t   _have_identity_count;
+	prometheus::simpleapi::counter_family_t _have_identity_count;
 	prometheus::simpleapi::benchmark_family_t _determine_auth;
-	prometheus::simpleapi::counter_family_t   _determine_auth_count;
+	prometheus::simpleapi::counter_family_t _determine_auth_count;
 	prometheus::simpleapi::benchmark_family_t _sso_check;
-	prometheus::simpleapi::counter_family_t   _sso_check_count;
+	prometheus::simpleapi::counter_family_t _sso_check_count;
 	prometheus::simpleapi::benchmark_family_t _auth_check;
-	prometheus::simpleapi::counter_family_t   _auth_check_count;
+	prometheus::simpleapi::counter_family_t _auth_check_count;
 	prometheus::simpleapi::benchmark_family_t _json_schlep;
-	prometheus::simpleapi::counter_family_t   _json_schlep_count;
+	prometheus::simpleapi::counter_family_t _json_schlep_count;
 	prometheus::simpleapi::benchmark_family_t _issue_certificate;
-	prometheus::simpleapi::counter_family_t   _issue_certificate_count;
+	prometheus::simpleapi::counter_family_t _issue_certificate_count;
 	prometheus::simpleapi::benchmark_family_t _save_member;
-	prometheus::simpleapi::counter_family_t   _save_member_count;
+	prometheus::simpleapi::counter_family_t _save_member_count;
 	prometheus::simpleapi::benchmark_family_t _send_netconf;
-	prometheus::simpleapi::counter_family_t   _send_netconf_count;
+	prometheus::simpleapi::counter_family_t _send_netconf_count;
 #endif
 };
 
-} // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 71 - 66
controller/FileDB.cpp

@@ -15,51 +15,49 @@
 
 #include "../node/Metrics.hpp"
 
-namespace ZeroTier
-{
+namespace ZeroTier {
 
-FileDB::FileDB(const char *path) :
-	DB(),
-	_path(path),
-	_networksPath(_path + ZT_PATH_SEPARATOR_S + "network"),
-	_tracePath(_path + ZT_PATH_SEPARATOR_S + "trace"),
-	_running(true)
+FileDB::FileDB(const char* path) : DB(), _path(path), _networksPath(_path + ZT_PATH_SEPARATOR_S + "network"), _tracePath(_path + ZT_PATH_SEPARATOR_S + "trace"), _running(true)
 {
 	OSUtils::mkdir(_path.c_str());
-	OSUtils::lockDownFile(_path.c_str(),true);
+	OSUtils::lockDownFile(_path.c_str(), true);
 	OSUtils::mkdir(_networksPath.c_str());
 	OSUtils::mkdir(_tracePath.c_str());
 
-	std::vector<std::string> networks(OSUtils::listDirectory(_networksPath.c_str(),false));
+	std::vector<std::string> networks(OSUtils::listDirectory(_networksPath.c_str(), false));
 	std::string buf;
-	for(auto n=networks.begin();n!=networks.end();++n) {
+	for (auto n = networks.begin(); n != networks.end(); ++n) {
 		buf.clear();
-		if ((n->length() == 21)&&(OSUtils::readFile((_networksPath + ZT_PATH_SEPARATOR_S + *n).c_str(),buf))) {
+		if ((n->length() == 21) && (OSUtils::readFile((_networksPath + ZT_PATH_SEPARATOR_S + *n).c_str(), buf))) {
 			try {
 				nlohmann::json network(OSUtils::jsonParse(buf));
 				const std::string nwids = network["id"];
 				if (nwids.length() == 16) {
 					nlohmann::json nullJson;
-					_networkChanged(nullJson,network,false);
+					_networkChanged(nullJson, network, false);
 					Metrics::network_count++;
 					std::string membersPath(_networksPath + ZT_PATH_SEPARATOR_S + nwids + ZT_PATH_SEPARATOR_S "member");
-					std::vector<std::string> members(OSUtils::listDirectory(membersPath.c_str(),false));
-					for(auto m=members.begin();m!=members.end();++m) {
+					std::vector<std::string> members(OSUtils::listDirectory(membersPath.c_str(), false));
+					for (auto m = members.begin(); m != members.end(); ++m) {
 						buf.clear();
-						if ((m->length() == 15)&&(OSUtils::readFile((membersPath + ZT_PATH_SEPARATOR_S + *m).c_str(),buf))) {
+						if ((m->length() == 15) && (OSUtils::readFile((membersPath + ZT_PATH_SEPARATOR_S + *m).c_str(), buf))) {
 							try {
 								nlohmann::json member(OSUtils::jsonParse(buf));
 								const std::string addrs = member["id"];
 								if (addrs.length() == 10) {
 									nlohmann::json nullJson2;
-									_memberChanged(nullJson2,member,false);
+									_memberChanged(nullJson2, member, false);
 									Metrics::member_count++;
 								}
-							} catch ( ... ) {}
+							}
+							catch (...) {
+							}
 						}
 					}
 				}
-			} catch ( ... ) {}
+			}
+			catch (...) {
+			}
 		}
 	}
 }
@@ -71,97 +69,104 @@ FileDB::~FileDB()
 		_running = false;
 		_online_l.unlock();
 		_onlineUpdateThread.join();
-	} catch ( ... ) {}
+	}
+	catch (...) {
+	}
 }
 
-bool FileDB::waitForReady() { return true; }
-bool FileDB::isReady() { return true; }
+bool FileDB::waitForReady()
+{
+	return true;
+}
+bool FileDB::isReady()
+{
+	return true;
+}
 
-bool FileDB::save(nlohmann::json &record,bool notifyListeners)
+bool FileDB::save(nlohmann::json& record, bool notifyListeners)
 {
-	char p1[4096],p2[4096],pb[4096];
+	char p1[4096], p2[4096], pb[4096];
 	bool modified = false;
 	try {
 		const std::string objtype = record["objtype"];
 		if (objtype == "network") {
-
-			const uint64_t nwid = OSUtils::jsonIntHex(record["id"],0ULL);
+			const uint64_t nwid = OSUtils::jsonIntHex(record["id"], 0ULL);
 			if (nwid) {
 				nlohmann::json old;
-				get(nwid,old);
-				if ((!old.is_object())||(!_compareRecords(old,record))) {
-					record["revision"] = OSUtils::jsonInt(record["revision"],0ULL) + 1ULL;
-					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);
+				get(nwid, old);
+				if ((! old.is_object()) || (! _compareRecords(old, record))) {
+					record["revision"] = OSUtils::jsonInt(record["revision"], 0ULL) + 1ULL;
+					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);
 					}
-					_networkChanged(old,record,notifyListeners);
+					_networkChanged(old, record, notifyListeners);
 					modified = true;
 				}
 			}
-
-		} else if (objtype == "member") {
-
-			const uint64_t id = OSUtils::jsonIntHex(record["id"],0ULL);
-			const uint64_t nwid = OSUtils::jsonIntHex(record["nwid"],0ULL);
-			if ((id)&&(nwid)) {
-				nlohmann::json network,old;
-				get(nwid,network,id,old);
-				if ((!old.is_object())||(!_compareRecords(old,record))) {
-					record["revision"] = OSUtils::jsonInt(record["revision"],0ULL) + 1ULL;
-					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",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);
+		}
+		else if (objtype == "member") {
+			const uint64_t id = OSUtils::jsonIntHex(record["id"], 0ULL);
+			const uint64_t nwid = OSUtils::jsonIntHex(record["nwid"], 0ULL);
+			if ((id) && (nwid)) {
+				nlohmann::json network, old;
+				get(nwid, network, id, old);
+				if ((! old.is_object()) || (! _compareRecords(old, record))) {
+					record["revision"] = OSUtils::jsonInt(record["revision"], 0ULL) + 1ULL;
+					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", 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);
 						OSUtils::mkdir(pb);
-						if (!OSUtils::writeFile(p1,OSUtils::jsonDump(record,-1))) {
-							fprintf(stderr,"WARNING: controller unable to write to path: %s" ZT_EOL_S,p1);
+						if (! OSUtils::writeFile(p1, OSUtils::jsonDump(record, -1))) {
+							fprintf(stderr, "WARNING: controller unable to write to path: %s" ZT_EOL_S, p1);
 						}
 					}
-					_memberChanged(old,record,notifyListeners);
+					_memberChanged(old, record, notifyListeners);
 					modified = true;
 				}
 			}
-
 		}
-	} catch ( ... ) {} // drop invalid records missing fields
+	}
+	catch (...) {
+	}	// drop invalid records missing fields
 	return modified;
 }
 
 void FileDB::eraseNetwork(const uint64_t networkId)
 {
-	nlohmann::json network,nullJson;
-	get(networkId,network);
+	nlohmann::json network, nullJson;
+	get(networkId, network);
 	char p[16384];
-	OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "%.16llx.json",_networksPath.c_str(),networkId);
+	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",_networksPath.c_str(),(unsigned long long)networkId);
+	OSUtils::ztsnprintf(p, sizeof(p), "%s" ZT_PATH_SEPARATOR_S "%.16llx", _networksPath.c_str(), (unsigned long long)networkId);
 	OSUtils::rmDashRf(p);
-	_networkChanged(network,nullJson,true);
+	_networkChanged(network, nullJson, true);
 	std::lock_guard<std::mutex> l(this->_online_l);
 	this->_online.erase(networkId);
 }
 
-void FileDB::eraseMember(const uint64_t networkId,const uint64_t memberId)
+void FileDB::eraseMember(const uint64_t networkId, const uint64_t memberId)
 {
-	nlohmann::json network,member,nullJson;
-	get(networkId,network,memberId,member);
+	nlohmann::json network, member, nullJson;
+	get(networkId, network, 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::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);
+	_memberChanged(member, nullJson, true);
 	std::lock_guard<std::mutex> l(this->_online_l);
 	this->_online[networkId].erase(memberId);
 }
 
-void FileDB::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress)
+void FileDB::nodeIsOnline(const uint64_t networkId, const uint64_t memberId, const InetAddress& physicalAddress)
 {
-	char mid[32],atmp[64];
-	OSUtils::ztsnprintf(mid,sizeof(mid),"%.10llx",(unsigned long long)memberId);
+	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;
 }
 
-} // namespace ZeroTier
+}	// namespace ZeroTier

+ 10 - 12
controller/FileDB.hpp

@@ -16,32 +16,30 @@
 
 #include "DB.hpp"
 
-namespace ZeroTier
-{
+namespace ZeroTier {
 
-class FileDB : public DB
-{
-public:
-	FileDB(const char *path);
+class FileDB : public DB {
+  public:
+	FileDB(const char* path);
 	virtual ~FileDB();
 
 	virtual bool waitForReady();
 	virtual bool isReady();
-	virtual bool save(nlohmann::json &record,bool notifyListeners);
+	virtual bool save(nlohmann::json& record, bool notifyListeners);
 	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);
+	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:
+  protected:
 	std::string _path;
 	std::string _networksPath;
 	std::string _tracePath;
 	std::thread _onlineUpdateThread;
-	std::map< uint64_t,std::map<uint64_t,std::map<int64_t,InetAddress> > > _online;
+	std::map<uint64_t, std::map<uint64_t, std::map<int64_t, InetAddress> > > _online;
 	std::mutex _online_l;
 	bool _running;
 };
 
-} // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 192 - 160
controller/LFDB.cpp

@@ -13,51 +13,52 @@
 
 #include "LFDB.hpp"
 
-#include <thread>
+#include "../ext/cpp-httplib/httplib.h"
+#include "../osdep/OSUtils.hpp"
+
 #include <chrono>
 #include <iostream>
 #include <sstream>
+#include <thread>
 
-#include "../osdep/OSUtils.hpp"
-#include "../ext/cpp-httplib/httplib.h"
-
-namespace ZeroTier
-{
+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(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)
+LFDB::LFDB(const Identity& myId, const char* path, const char* lfOwnerPrivate, const char* lfOwnerPublic, const char* lfNodeHost, int lfNodePort, bool storeOnlineState)
+	: DB()
+	, _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 networksSelectorName("com.zerotier.controller.lfdb:");
+		networksSelectorName.append(controllerAddress);
+		networksSelectorName.append("/network");
 
 		// 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);
+		char maskingKey[128];
+		Utils::hex(sha512pk, 32, maskingKey);
 
-		httplib::Client htcli(_lfNodeHost.c_str(),_lfNodePort);
+		httplib::Client htcli(_lfNodeHost.c_str(), _lfNodePort);
 		int64_t timeRangeStart = 0;
 		while (_running.load()) {
 			{
 				std::lock_guard<std::mutex> sl(_state_l);
-				for(auto ns=_state.begin();ns!=_state.end();++ns) {
+				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;
+						if (get(ns->first, network)) {
+							nlohmann::json newrec, selector0;
 							selector0["Name"] = networksSelectorName;
 							selector0["Ordinal"] = ns->first;
 							newrec["Selectors"].push_back(selector0);
@@ -66,30 +67,34 @@ LFDB::LFDB(const Identity &myId,const char *path,const char *lfOwnerPrivate,cons
 							newrec["MaskingKey"] = maskingKey;
 							newrec["PulseIfUnchanged"] = true;
 							try {
-								auto resp = htcli.Post("/makerecord",newrec.dump(),"application/json");
+								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());
+										// 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);
 								}
-							} catch (std::exception &e) {
-								fprintf(stderr,"ERROR: LFDB: unexpected exception querying node (create/update network): %s" ZT_EOL_S,e.what());
-							} catch ( ... ) {
-								fprintf(stderr,"ERROR: LFDB: unexpected exception querying node (create/update network): unknown exception" ZT_EOL_S);
+								else {
+									fprintf(stderr, "ERROR: LFDB: node is offline" ZT_EOL_S);
+								}
+							}
+							catch (std::exception& e) {
+								fprintf(stderr, "ERROR: LFDB: unexpected exception querying node (create/update network): %s" ZT_EOL_S, e.what());
+							}
+							catch (...) {
+								fprintf(stderr, "ERROR: LFDB: unexpected exception querying node (create/update network): unknown exception" 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);
+					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;
@@ -98,18 +103,18 @@ LFDB::LFDB(const Identity &myId,const char *path,const char *lfOwnerPrivate,cons
 							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) {
+							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)
+									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)
+									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
+									ip = tmp2;	 // should never happen since only IP transport is currently supported
 									break;
 							}
 							newrec["Value"] = ip;
@@ -118,28 +123,32 @@ LFDB::LFDB(const Identity &myId,const char *path,const char *lfOwnerPrivate,cons
 							newrec["Timestamp"] = ms->second.lastOnlineTime;
 							newrec["PulseIfUnchanged"] = true;
 							try {
-								auto resp = htcli.Post("/makerecord",newrec.dump(),"application/json");
+								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());
+										// printf("SET member online %.16llx %.10llx %s\n",ns->first,ms->first,resp->body.c_str());
 									}
-								} else {
-									fprintf(stderr,"ERROR: LFDB: node is offline" ZT_EOL_S);
+									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);
 								}
-							} catch (std::exception &e) {
-								fprintf(stderr,"ERROR: LFDB: unexpected exception querying node (create/update member online status): %s" ZT_EOL_S,e.what());
-							} catch ( ... ) {
-								fprintf(stderr,"ERROR: LFDB: unexpected exception querying node (create/update member online status): unknown exception" ZT_EOL_S);
+							}
+							catch (std::exception& e) {
+								fprintf(stderr, "ERROR: LFDB: unexpected exception querying node (create/update member online status): %s" ZT_EOL_S, e.what());
+							}
+							catch (...) {
+								fprintf(stderr, "ERROR: LFDB: unexpected exception querying node (create/update member online status): unknown exception" 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;
+							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"] = "member";
@@ -152,21 +161,25 @@ LFDB::LFDB(const Identity &myId,const char *path,const char *lfOwnerPrivate,cons
 								newrec["MaskingKey"] = maskingKey;
 								newrec["PulseIfUnchanged"] = true;
 								try {
-									auto resp = htcli.Post("/makerecord",newrec.dump(),"application/json");
+									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());
+											// printf("SET member %.16llx %.10llx %s\n",ns->first,ms->first,resp->body.c_str());
 										}
-									} else {
-										fprintf(stderr,"ERROR: LFDB: node is offline" ZT_EOL_S);
+										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);
 									}
-								} catch (std::exception &e) {
-									fprintf(stderr,"ERROR: LFDB: unexpected exception querying node (create/update member): %s" ZT_EOL_S,e.what());
-								} catch ( ... ) {
-									fprintf(stderr,"ERROR: LFDB: unexpected exception querying node (create/update member): unknown exception" ZT_EOL_S);
+								}
+								catch (std::exception& e) {
+									fprintf(stderr, "ERROR: LFDB: unexpected exception querying node (create/update member): %s" ZT_EOL_S, e.what());
+								}
+								catch (...) {
+									fprintf(stderr, "ERROR: LFDB: unexpected exception querying node (create/update member): unknown exception" ZT_EOL_S);
 								}
 							}
 						}
@@ -176,143 +189,161 @@ LFDB::LFDB(const Identity &myId,const char *path,const char *lfOwnerPrivate,cons
 
 			try {
 				std::ostringstream query;
-				query <<
-					"{"
-						"\"Ranges\":[{"
-							"\"Name\":\"" << networksSelectorName << "\","
-							"\"Range\":[0,18446744073709551615]"
-						"}],"
-						"\"TimeRange\":[" << timeRangeStart << ",9223372036854775807],"
-						"\"MaskingKey\":\"" << maskingKey << "\","
-						"\"Owners\":[\"" << _lfOwnerPublic << "\"]"
-					"}";
-				auto resp = htcli.Post("/query",query.str(),"application/json");
+				query << "{"
+						 "\"Ranges\":[{"
+						 "\"Name\":\""
+					  << networksSelectorName
+					  << "\","
+						 "\"Range\":[0,18446744073709551615]"
+						 "}],"
+						 "\"TimeRange\":["
+					  << timeRangeStart
+					  << ",9223372036854775807],"
+						 "\"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.empty())) {
-							for(std::size_t ri=0;ri<results.size();++ri) {
-								nlohmann::json &rset = results[ri];
-								if ((rset.is_array())&&(!rset.empty())) {
-
-									nlohmann::json &result = rset[0];
+						if ((results.is_array()) && (! results.empty())) {
+							for (std::size_t ri = 0; ri < results.size(); ++ri) {
+								nlohmann::json& rset = results[ri];
+								if ((rset.is_array()) && (! rset.empty())) {
+									nlohmann::json& result = rset[0];
 									if (result.is_object()) {
-										nlohmann::json &record = result["Record"];
+										nlohmann::json& record = result["Record"];
 										if (record.is_object()) {
 											const std::string recordValue = result["Value"];
-											//printf("GET network %s\n",recordValue.c_str());
+											// 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
+												if ((id >> 24) == controllerAddressInt) {	// sanity check
 
 													nlohmann::json oldNetwork;
-													if ((timeRangeStart > 0)&&(get(id,oldNetwork))) {
+													if ((timeRangeStart > 0) && (get(id, oldNetwork))) {
 														const uint64_t revision = network["revision"];
 														const uint64_t prevRevision = oldNetwork["revision"];
 														if (prevRevision < revision) {
-															_networkChanged(oldNetwork,network,timeRangeStart > 0);
+															_networkChanged(oldNetwork, network, timeRangeStart > 0);
 														}
-													} else {
+													}
+													else {
 														nlohmann::json nullJson;
-														_networkChanged(nullJson,network,timeRangeStart > 0);
+														_networkChanged(nullJson, network, timeRangeStart > 0);
 													}
-
 												}
 											}
 										}
 									}
-
 								}
 							}
 						}
-					} else {
-						fprintf(stderr,"ERROR: LFDB: %d from node (check for network updates): %s" ZT_EOL_S,resp->status,resp->body.c_str());
 					}
-				} else {
-					fprintf(stderr,"ERROR: LFDB: node is offline" ZT_EOL_S);
+					else {
+						fprintf(stderr, "ERROR: LFDB: %d from node (check for network updates): %s" ZT_EOL_S, resp->status, resp->body.c_str());
+					}
+				}
+				else {
+					fprintf(stderr, "ERROR: LFDB: node is offline" ZT_EOL_S);
 				}
-			} catch (std::exception &e) {
-				fprintf(stderr,"ERROR: LFDB: unexpected exception querying node (check for network updates): %s" ZT_EOL_S,e.what());
-			} catch ( ... ) {
-				fprintf(stderr,"ERROR: LFDB: unexpected exception querying node (check for network updates): unknown exception" ZT_EOL_S);
+			}
+			catch (std::exception& e) {
+				fprintf(stderr, "ERROR: LFDB: unexpected exception querying node (check for network updates): %s" ZT_EOL_S, e.what());
+			}
+			catch (...) {
+				fprintf(stderr, "ERROR: LFDB: unexpected exception querying node (check for network updates): unknown exception" ZT_EOL_S);
 			}
 
 			try {
 				std::ostringstream query;
-				query <<
-					"{"
-						"\"Ranges\":[{"
-							"\"Name\":\"" << networksSelectorName << "\","
-							"\"Range\":[0,18446744073709551615]"
-						"},{"
-							"\"Name\":\"member\","
-							"\"Range\":[0,18446744073709551615]"
-						"}],"
-						"\"TimeRange\":[" << timeRangeStart << ",9223372036854775807],"
-						"\"MaskingKey\":\"" << maskingKey << "\","
-						"\"Owners\":[\"" << _lfOwnerPublic << "\"]"
-					"}";
-				auto resp = htcli.Post("/query",query.str(),"application/json");
+				query << "{"
+						 "\"Ranges\":[{"
+						 "\"Name\":\""
+					  << networksSelectorName
+					  << "\","
+						 "\"Range\":[0,18446744073709551615]"
+						 "},{"
+						 "\"Name\":\"member\","
+						 "\"Range\":[0,18446744073709551615]"
+						 "}],"
+						 "\"TimeRange\":["
+					  << timeRangeStart
+					  << ",9223372036854775807],"
+						 "\"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.empty())) {
-							for(std::size_t ri=0;ri<results.size();++ri) {
-								nlohmann::json &rset = results[ri];
-								if ((rset.is_array())&&(!rset.empty())) {
-
-									nlohmann::json &result = rset[0];
+						if ((results.is_array()) && (! results.empty())) {
+							for (std::size_t ri = 0; ri < results.size(); ++ri) {
+								nlohmann::json& rset = results[ri];
+								if ((rset.is_array()) && (! rset.empty())) {
+									nlohmann::json& result = rset[0];
 									if (result.is_object()) {
-										nlohmann::json &record = result["Record"];
+										nlohmann::json& record = result["Record"];
 										if (record.is_object()) {
 											const std::string recordValue = result["Value"];
-											//printf("GET member %s\n",recordValue.c_str());
+											// 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
+												if ((id) && ((nwid >> 24) == controllerAddressInt)) {	// sanity check
 
-													nlohmann::json network,oldMember;
-													if ((timeRangeStart > 0)&&(get(nwid,network,id,oldMember))) {
+													nlohmann::json network, oldMember;
+													if ((timeRangeStart > 0) && (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 if (hasNetwork(nwid)) {
+															_memberChanged(oldMember, member, timeRangeStart > 0);
+													}
+													else if (hasNetwork(nwid)) {
 														nlohmann::json nullJson;
-														_memberChanged(nullJson,member,timeRangeStart > 0);
+														_memberChanged(nullJson, member, timeRangeStart > 0);
 													}
-
 												}
 											}
 										}
 									}
-
 								}
 							}
 						}
-					} else {
-						fprintf(stderr,"ERROR: LFDB: %d from node (check for member updates): %s" ZT_EOL_S,resp->status,resp->body.c_str());
 					}
-				} else {
-					fprintf(stderr,"ERROR: LFDB: node is offline" ZT_EOL_S);
+					else {
+						fprintf(stderr, "ERROR: LFDB: %d from node (check for member updates): %s" ZT_EOL_S, resp->status, resp->body.c_str());
+					}
 				}
-			} catch (std::exception &e) {
-				fprintf(stderr,"ERROR: LFDB: unexpected exception querying node (check for member updates): %s" ZT_EOL_S,e.what());
-			} catch ( ... ) {
-				fprintf(stderr,"ERROR: LFDB: unexpected exception querying node (check for member updates): unknown exception" ZT_EOL_S);
+				else {
+					fprintf(stderr, "ERROR: LFDB: node is offline" ZT_EOL_S);
+				}
+			}
+			catch (std::exception& e) {
+				fprintf(stderr, "ERROR: LFDB: unexpected exception querying node (check for member updates): %s" ZT_EOL_S, e.what());
+			}
+			catch (...) {
+				fprintf(stderr, "ERROR: LFDB: unexpected exception querying node (check for member updates): unknown exception" ZT_EOL_S);
 			}
 
-			timeRangeStart = time(nullptr) - 120; // start next query 2m before now to avoid losing updates
+			timeRangeStart = time(nullptr) - 120;	// start next query 2m before now to avoid losing updates
 			_ready.store(true);
 
-			for(int k=0;k<4;++k) { // 2s delay between queries for remotely modified networks or members
-				if (!_running.load())
+			for (int k = 0; k < 4; ++k) {	// 2s delay between queries for remotely modified networks or members
+				if (! _running.load())
 					return;
 				std::this_thread::sleep_for(std::chrono::milliseconds(500));
 			}
@@ -328,7 +359,7 @@ LFDB::~LFDB()
 
 bool LFDB::waitForReady()
 {
-	while (!_ready.load()) {
+	while (! _ready.load()) {
 		std::this_thread::sleep_for(std::chrono::milliseconds(500));
 	}
 	return true;
@@ -339,18 +370,18 @@ bool LFDB::isReady()
 	return (_ready.load());
 }
 
-bool LFDB::save(nlohmann::json &record,bool notifyListeners)
+bool LFDB::save(nlohmann::json& record, bool notifyListeners)
 {
 	bool modified = false;
 	const std::string objtype = record["objtype"];
 	if (objtype == "network") {
-		const uint64_t nwid = OSUtils::jsonIntHex(record["id"],0ULL);
+		const uint64_t nwid = OSUtils::jsonIntHex(record["id"], 0ULL);
 		if (nwid) {
 			nlohmann::json old;
-			get(nwid,old);
-			if ((!old.is_object())||(!_compareRecords(old,record))) {
-				record["revision"] = OSUtils::jsonInt(record["revision"],0ULL) + 1ULL;
-				_networkChanged(old,record,notifyListeners);
+			get(nwid, old);
+			if ((! old.is_object()) || (! _compareRecords(old, record))) {
+				record["revision"] = OSUtils::jsonInt(record["revision"], 0ULL) + 1ULL;
+				_networkChanged(old, record, notifyListeners);
 				{
 					std::lock_guard<std::mutex> l(_state_l);
 					_state[nwid].dirty = true;
@@ -358,15 +389,16 @@ bool LFDB::save(nlohmann::json &record,bool notifyListeners)
 				modified = 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())||(!_compareRecords(old,record))) {
-				record["revision"] = OSUtils::jsonInt(record["revision"],0ULL) + 1ULL;
-				_memberChanged(old,record,notifyListeners);
+	}
+	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()) || (! _compareRecords(old, record))) {
+				record["revision"] = OSUtils::jsonInt(record["revision"], 0ULL) + 1ULL;
+				_memberChanged(old, record, notifyListeners);
 				{
 					std::lock_guard<std::mutex> l(_state_l);
 					_state[nwid].members[id].dirty = true;
@@ -383,12 +415,12 @@ void LFDB::eraseNetwork(const uint64_t networkId)
 	// TODO
 }
 
-void LFDB::eraseMember(const uint64_t networkId,const uint64_t memberId)
+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)
+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);
@@ -403,4 +435,4 @@ void LFDB::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const I
 	}
 }
 
-} // namespace ZeroTier
+}	// namespace ZeroTier

+ 20 - 25
controller/LFDB.hpp

@@ -16,19 +16,18 @@
 
 #include "DB.hpp"
 
+#include <atomic>
 #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:
+class LFDB : public DB {
+  public:
 	/**
 	 * @param myId This controller's identity
 	 * @param path Base path for ZeroTier node itself
@@ -38,44 +37,40 @@ public:
 	 * @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);
+	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 bool save(nlohmann::json &record,bool notifyListeners);
+	virtual bool save(nlohmann::json& record, bool notifyListeners);
 	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);
+	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:
+  protected:
 	const Identity _myId;
 
-	std::string _lfOwnerPrivate,_lfOwnerPublic;
+	std::string _lfOwnerPrivate, _lfOwnerPublic;
 	std::string _lfNodeHost;
 	int _lfNodePort;
 
-	struct _MemberState
-	{
-		_MemberState() :
-			lastOnlineAddress(),
-			lastOnlineTime(0),
-			dirty(false),
-			lastOnlineDirty(false) {}
+	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;
+	struct _NetworkState {
+		_NetworkState() : members(), dirty(false)
+		{
+		}
+		std::unordered_map<uint64_t, _MemberState> members;
 		bool dirty;
 	};
-	std::unordered_map<uint64_t,_NetworkState> _state;
+	std::unordered_map<uint64_t, _NetworkState> _state;
 	std::mutex _state_l;
 
 	std::atomic_bool _running;
@@ -84,6 +79,6 @@ protected:
 	bool _storeOnlineState;
 };
 
-} // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

File diff suppressed because it is too large
+ 335 - 258
controller/PostgreSQL.cpp


+ 58 - 55
controller/PostgreSQL.hpp

@@ -20,78 +20,81 @@
 
 #define ZT_CENTRAL_CONTROLLER_COMMIT_THREADS 4
 
+#include "../node/Metrics.hpp"
 #include "ConnectionPool.hpp"
-#include <pqxx/pqxx>
 
 #include <memory>
+#include <pqxx/pqxx>
 #include <redis++/redis++.h>
 
-#include "../node/Metrics.hpp"
-
 extern "C" {
 typedef struct pg_conn PGconn;
 }
 
 namespace smeeclient {
-	struct SmeeClient;
+struct SmeeClient;
 }
 
 namespace ZeroTier {
 
 struct RedisConfig;
 
-
 class PostgresConnection : public Connection {
-public:
-	virtual ~PostgresConnection() {
+  public:
+	virtual ~PostgresConnection()
+	{
 	}
 
 	std::shared_ptr<pqxx::connection> c;
 	int a;
 };
 
-
 class PostgresConnFactory : public ConnectionFactory {
-public:
-	PostgresConnFactory(std::string &connString) 
-		: m_connString(connString)
+  public:
+	PostgresConnFactory(std::string& connString) : m_connString(connString)
 	{
 	}
 
-	virtual std::shared_ptr<Connection> create() {
+	virtual std::shared_ptr<Connection> create()
+	{
 		Metrics::conn_counter++;
 		auto c = std::shared_ptr<PostgresConnection>(new PostgresConnection());
 		c->c = std::make_shared<pqxx::connection>(m_connString);
 		return std::static_pointer_cast<Connection>(c);
 	}
-private:
+
+  private:
 	std::string m_connString;
 };
 
 class PostgreSQL;
 
 class MemberNotificationReceiver : public pqxx::notification_receiver {
-public: 
-	MemberNotificationReceiver(PostgreSQL *p, pqxx::connection &c, const std::string &channel);
-	virtual ~MemberNotificationReceiver() {
+  public:
+	MemberNotificationReceiver(PostgreSQL* p, pqxx::connection& c, const std::string& channel);
+	virtual ~MemberNotificationReceiver()
+	{
 		fprintf(stderr, "MemberNotificationReceiver destroyed\n");
 	}
 
-	virtual void operator() (const std::string &payload, int backendPid);
-private:
-	PostgreSQL *_psql;
+	virtual void operator()(const std::string& payload, int backendPid);
+
+  private:
+	PostgreSQL* _psql;
 };
 
 class NetworkNotificationReceiver : public pqxx::notification_receiver {
-public:
-	NetworkNotificationReceiver(PostgreSQL *p, pqxx::connection &c, const std::string &channel);
-	virtual ~NetworkNotificationReceiver() {
+  public:
+	NetworkNotificationReceiver(PostgreSQL* p, pqxx::connection& c, const std::string& channel);
+	virtual ~NetworkNotificationReceiver()
+	{
 		fprintf(stderr, "NetworkNotificationReceiver destroyed\n");
 	};
 
-	virtual void operator() (const std::string &payload, int packend_pid);
-private:
-	PostgreSQL *_psql;
+	virtual void operator()(const std::string& payload, int packend_pid);
+
+  private:
+	PostgreSQL* _psql;
 };
 
 /**
@@ -100,36 +103,40 @@ private:
  * 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 PostgreSQL : public DB
-{
+class PostgreSQL : public DB {
 	friend class MemberNotificationReceiver;
 	friend class NetworkNotificationReceiver;
-public:
-	PostgreSQL(const Identity &myId, const char *path, int listenPort, RedisConfig *rc);
+
+  public:
+	PostgreSQL(const Identity& myId, const char* path, int listenPort, RedisConfig* rc);
 	virtual ~PostgreSQL();
 
 	virtual bool waitForReady();
 	virtual bool isReady();
-	virtual bool save(nlohmann::json &record,bool notifyListeners);
+	virtual bool save(nlohmann::json& record, bool notifyListeners);
 	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);
-	virtual AuthInfo getSSOAuthInfo(const nlohmann::json &member, const std::string &redirectURL);
-
-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); }
+	virtual void nodeIsOnline(const uint64_t networkId, const uint64_t memberId, const InetAddress& physicalAddress);
+	virtual AuthInfo getSSOAuthInfo(const nlohmann::json& member, const std::string& redirectURL);
+
+  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);
+		}
 	};
-	virtual void _memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool notifyListeners) {
+	virtual void _memberChanged(nlohmann::json& old, nlohmann::json& memberConfig, bool notifyListeners)
+	{
 		DB::_memberChanged(old, memberConfig, notifyListeners);
 	}
 
-	virtual void _networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool notifyListeners) {
+	virtual void _networkChanged(nlohmann::json& old, nlohmann::json& networkConfig, bool notifyListeners)
+	{
 		DB::_networkChanged(old, networkConfig, notifyListeners);
 	}
 
-private:
+  private:
 	void initializeNetworks();
 	void initializeMembers();
 	void heartbeat();
@@ -145,16 +152,12 @@ private:
 	void onlineNotificationThread();
 	void onlineNotification_Postgres();
 	void onlineNotification_Redis();
-	uint64_t _doRedisUpdate(sw::redis::Transaction &tx, std::string &controllerId,
-		std::unordered_map< std::pair<uint64_t,uint64_t>,std::pair<int64_t,InetAddress>,_PairHasher > &lastOnline);
+	uint64_t _doRedisUpdate(sw::redis::Transaction& tx, std::string& controllerId, std::unordered_map<std::pair<uint64_t, uint64_t>, std::pair<int64_t, InetAddress>, _PairHasher>& lastOnline);
 
 	void configureSmee();
-	void notifyNewMember(const std::string &networkID, const std::string &memberID);
+	void notifyNewMember(const std::string& networkID, const std::string& memberID);
 
-	enum OverrideMode {
-		ALLOW_PGBOUNCER_OVERRIDE = 0,
-		NO_OVERRIDE = 1
-	};
+	enum OverrideMode { ALLOW_PGBOUNCER_OVERRIDE = 0, NO_OVERRIDE = 1 };
 
 	std::shared_ptr<ConnectionPool<PostgresConnection> > _pool;
 
@@ -163,7 +166,7 @@ private:
 	std::string _myAddressStr;
 	std::string _connString;
 
-	BlockingQueue< std::pair<nlohmann::json,bool> > _commitQueue;
+	BlockingQueue<std::pair<nlohmann::json, bool> > _commitQueue;
 
 	std::thread _heartbeatThread;
 	std::thread _membersDbWatcher;
@@ -171,7 +174,7 @@ private:
 	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;
+	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;
@@ -181,16 +184,16 @@ private:
 	int _listenPort;
 	uint8_t _ssoPsk[48];
 
-	RedisConfig *_rc;
+	RedisConfig* _rc;
 	std::shared_ptr<sw::redis::Redis> _redis;
 	std::shared_ptr<sw::redis::RedisCluster> _cluster;
-    bool _redisMemberStatus;
+	bool _redisMemberStatus;
 
-	smeeclient::SmeeClient *_smee;
+	smeeclient::SmeeClient* _smee;
 };
 
-} // namespace ZeroTier
+}	// namespace ZeroTier
 
-#endif // ZT_CONTROLLER_LIBPQ_HPP
+#endif	 // ZT_CONTROLLER_LIBPQ_HPP
 
-#endif // ZT_CONTROLLER_USE_LIBPQ
+#endif	 // ZT_CONTROLLER_USE_LIBPQ

+ 5 - 5
controller/Redis.hpp

@@ -5,11 +5,11 @@
 
 namespace ZeroTier {
 struct RedisConfig {
-    std::string hostname;
-    int port;
-    std::string password;
-    bool clusterMode;
+	std::string hostname;
+	int port;
+	std::string password;
+	bool clusterMode;
 };
-}
+}	// namespace ZeroTier
 
 #endif

+ 633 - 633
node/AES.cpp

@@ -33,695 +33,695 @@ namespace ZeroTier {
 namespace {
 
 #define s_bmul32(N, x, y, rh, rl)                                                                                                                                                                                                              \
-    uint32_t x0t_##N = (x) & 0x11111111U;                                                                                                                                                                                                      \
-    uint32_t x1t_##N = (x) & 0x22222222U;                                                                                                                                                                                                      \
-    uint32_t x2t_##N = (x) & 0x44444444U;                                                                                                                                                                                                      \
-    uint32_t x3t_##N = (x) & 0x88888888U;                                                                                                                                                                                                      \
-    uint32_t y0t_##N = (y) & 0x11111111U;                                                                                                                                                                                                      \
-    uint32_t y1t_##N = (y) & 0x22222222U;                                                                                                                                                                                                      \
-    uint32_t y2t_##N = (y) & 0x44444444U;                                                                                                                                                                                                      \
-    uint32_t y3t_##N = (y) & 0x88888888U;                                                                                                                                                                                                      \
-    uint64_t z0t_##N = (((uint64_t)x0t_##N * y0t_##N) ^ ((uint64_t)x1t_##N * y3t_##N) ^ ((uint64_t)x2t_##N * y2t_##N) ^ ((uint64_t)x3t_##N * y1t_##N)) & 0x1111111111111111ULL;                                                                \
-    uint64_t z1t_##N = (((uint64_t)x0t_##N * y1t_##N) ^ ((uint64_t)x1t_##N * y0t_##N) ^ ((uint64_t)x2t_##N * y3t_##N) ^ ((uint64_t)x3t_##N * y2t_##N)) & 0x2222222222222222ULL;                                                                \
-    uint64_t z2t_##N = (((uint64_t)x0t_##N * y2t_##N) ^ ((uint64_t)x1t_##N * y1t_##N) ^ ((uint64_t)x2t_##N * y0t_##N) ^ ((uint64_t)x3t_##N * y3t_##N)) & 0x4444444444444444ULL;                                                                \
-    z0t_##N |= z1t_##N;                                                                                                                                                                                                                        \
-    z2t_##N |= z0t_##N;                                                                                                                                                                                                                        \
-    uint64_t zt_##N = z2t_##N | ((((uint64_t)x0t_##N * y3t_##N) ^ ((uint64_t)x1t_##N * y2t_##N) ^ ((uint64_t)x2t_##N * y1t_##N) ^ ((uint64_t)x3t_##N * y0t_##N)) & 0x8888888888888888ULL);                                                     \
-    (rh) = (uint32_t)(zt_##N >> 32U);                                                                                                                                                                                                          \
-    (rl) = (uint32_t)zt_##N;
+	uint32_t x0t_##N = (x) & 0x11111111U;                                                                                                                                                                                                      \
+	uint32_t x1t_##N = (x) & 0x22222222U;                                                                                                                                                                                                      \
+	uint32_t x2t_##N = (x) & 0x44444444U;                                                                                                                                                                                                      \
+	uint32_t x3t_##N = (x) & 0x88888888U;                                                                                                                                                                                                      \
+	uint32_t y0t_##N = (y) & 0x11111111U;                                                                                                                                                                                                      \
+	uint32_t y1t_##N = (y) & 0x22222222U;                                                                                                                                                                                                      \
+	uint32_t y2t_##N = (y) & 0x44444444U;                                                                                                                                                                                                      \
+	uint32_t y3t_##N = (y) & 0x88888888U;                                                                                                                                                                                                      \
+	uint64_t z0t_##N = (((uint64_t)x0t_##N * y0t_##N) ^ ((uint64_t)x1t_##N * y3t_##N) ^ ((uint64_t)x2t_##N * y2t_##N) ^ ((uint64_t)x3t_##N * y1t_##N)) & 0x1111111111111111ULL;                                                                \
+	uint64_t z1t_##N = (((uint64_t)x0t_##N * y1t_##N) ^ ((uint64_t)x1t_##N * y0t_##N) ^ ((uint64_t)x2t_##N * y3t_##N) ^ ((uint64_t)x3t_##N * y2t_##N)) & 0x2222222222222222ULL;                                                                \
+	uint64_t z2t_##N = (((uint64_t)x0t_##N * y2t_##N) ^ ((uint64_t)x1t_##N * y1t_##N) ^ ((uint64_t)x2t_##N * y0t_##N) ^ ((uint64_t)x3t_##N * y3t_##N)) & 0x4444444444444444ULL;                                                                \
+	z0t_##N |= z1t_##N;                                                                                                                                                                                                                        \
+	z2t_##N |= z0t_##N;                                                                                                                                                                                                                        \
+	uint64_t zt_##N = z2t_##N | ((((uint64_t)x0t_##N * y3t_##N) ^ ((uint64_t)x1t_##N * y2t_##N) ^ ((uint64_t)x2t_##N * y1t_##N) ^ ((uint64_t)x3t_##N * y0t_##N)) & 0x8888888888888888ULL);                                                     \
+	(rh) = (uint32_t)(zt_##N >> 32U);                                                                                                                                                                                                          \
+	(rl) = (uint32_t)zt_##N;
 
 void s_gfmul(const uint64_t hh, const uint64_t hl, uint64_t& y0, uint64_t& y1) noexcept
 {
-    uint32_t hhh = (uint32_t)(hh >> 32U);
-    uint32_t hhl = (uint32_t)hh;
-    uint32_t hlh = (uint32_t)(hl >> 32U);
-    uint32_t hll = (uint32_t)hl;
-    uint32_t hhXlh = hhh ^ hlh;
-    uint32_t hhXll = hhl ^ hll;
-    uint64_t yl = Utils::ntoh(y0);
-    uint64_t yh = Utils::ntoh(y1);
-    uint32_t cilh = (uint32_t)(yh >> 32U);
-    uint32_t cill = (uint32_t)yh;
-    uint32_t cihh = (uint32_t)(yl >> 32U);
-    uint32_t cihl = (uint32_t)yl;
-    uint32_t cihXlh = cihh ^ cilh;
-    uint32_t cihXll = cihl ^ cill;
-    uint32_t aah, aal, abh, abl, ach, acl;
-    s_bmul32(M0, cihh, hhh, aah, aal);
-    s_bmul32(M1, cihl, hhl, abh, abl);
-    s_bmul32(M2, cihh ^ cihl, hhh ^ hhl, ach, acl);
-    ach ^= aah ^ abh;
-    acl ^= aal ^ abl;
-    aal ^= ach;
-    abh ^= acl;
-    uint32_t bah, bal, bbh, bbl, bch, bcl;
-    s_bmul32(M3, cilh, hlh, bah, bal);
-    s_bmul32(M4, cill, hll, bbh, bbl);
-    s_bmul32(M5, cilh ^ cill, hlh ^ hll, bch, bcl);
-    bch ^= bah ^ bbh;
-    bcl ^= bal ^ bbl;
-    bal ^= bch;
-    bbh ^= bcl;
-    uint32_t cah, cal, cbh, cbl, cch, ccl;
-    s_bmul32(M6, cihXlh, hhXlh, cah, cal);
-    s_bmul32(M7, cihXll, hhXll, cbh, cbl);
-    s_bmul32(M8, cihXlh ^ cihXll, hhXlh ^ hhXll, cch, ccl);
-    cch ^= cah ^ cbh;
-    ccl ^= cal ^ cbl;
-    cal ^= cch;
-    cbh ^= ccl;
-    cah ^= bah ^ aah;
-    cal ^= bal ^ aal;
-    cbh ^= bbh ^ abh;
-    cbl ^= bbl ^ abl;
-    uint64_t zhh = ((uint64_t)aah << 32U) | aal;
-    uint64_t zhl = (((uint64_t)abh << 32U) | abl) ^ (((uint64_t)cah << 32U) | cal);
-    uint64_t zlh = (((uint64_t)bah << 32U) | bal) ^ (((uint64_t)cbh << 32U) | cbl);
-    uint64_t zll = ((uint64_t)bbh << 32U) | bbl;
-    zhh = zhh << 1U | zhl >> 63U;
-    zhl = zhl << 1U | zlh >> 63U;
-    zlh = zlh << 1U | zll >> 63U;
-    zll <<= 1U;
-    zlh ^= (zll << 63U) ^ (zll << 62U) ^ (zll << 57U);
-    zhh ^= zlh ^ (zlh >> 1U) ^ (zlh >> 2U) ^ (zlh >> 7U);
-    zhl ^= zll ^ (zll >> 1U) ^ (zll >> 2U) ^ (zll >> 7U) ^ (zlh << 63U) ^ (zlh << 62U) ^ (zlh << 57U);
-    y0 = Utils::hton(zhh);
-    y1 = Utils::hton(zhl);
+	uint32_t hhh = (uint32_t)(hh >> 32U);
+	uint32_t hhl = (uint32_t)hh;
+	uint32_t hlh = (uint32_t)(hl >> 32U);
+	uint32_t hll = (uint32_t)hl;
+	uint32_t hhXlh = hhh ^ hlh;
+	uint32_t hhXll = hhl ^ hll;
+	uint64_t yl = Utils::ntoh(y0);
+	uint64_t yh = Utils::ntoh(y1);
+	uint32_t cilh = (uint32_t)(yh >> 32U);
+	uint32_t cill = (uint32_t)yh;
+	uint32_t cihh = (uint32_t)(yl >> 32U);
+	uint32_t cihl = (uint32_t)yl;
+	uint32_t cihXlh = cihh ^ cilh;
+	uint32_t cihXll = cihl ^ cill;
+	uint32_t aah, aal, abh, abl, ach, acl;
+	s_bmul32(M0, cihh, hhh, aah, aal);
+	s_bmul32(M1, cihl, hhl, abh, abl);
+	s_bmul32(M2, cihh ^ cihl, hhh ^ hhl, ach, acl);
+	ach ^= aah ^ abh;
+	acl ^= aal ^ abl;
+	aal ^= ach;
+	abh ^= acl;
+	uint32_t bah, bal, bbh, bbl, bch, bcl;
+	s_bmul32(M3, cilh, hlh, bah, bal);
+	s_bmul32(M4, cill, hll, bbh, bbl);
+	s_bmul32(M5, cilh ^ cill, hlh ^ hll, bch, bcl);
+	bch ^= bah ^ bbh;
+	bcl ^= bal ^ bbl;
+	bal ^= bch;
+	bbh ^= bcl;
+	uint32_t cah, cal, cbh, cbl, cch, ccl;
+	s_bmul32(M6, cihXlh, hhXlh, cah, cal);
+	s_bmul32(M7, cihXll, hhXll, cbh, cbl);
+	s_bmul32(M8, cihXlh ^ cihXll, hhXlh ^ hhXll, cch, ccl);
+	cch ^= cah ^ cbh;
+	ccl ^= cal ^ cbl;
+	cal ^= cch;
+	cbh ^= ccl;
+	cah ^= bah ^ aah;
+	cal ^= bal ^ aal;
+	cbh ^= bbh ^ abh;
+	cbl ^= bbl ^ abl;
+	uint64_t zhh = ((uint64_t)aah << 32U) | aal;
+	uint64_t zhl = (((uint64_t)abh << 32U) | abl) ^ (((uint64_t)cah << 32U) | cal);
+	uint64_t zlh = (((uint64_t)bah << 32U) | bal) ^ (((uint64_t)cbh << 32U) | cbl);
+	uint64_t zll = ((uint64_t)bbh << 32U) | bbl;
+	zhh = zhh << 1U | zhl >> 63U;
+	zhl = zhl << 1U | zlh >> 63U;
+	zlh = zlh << 1U | zll >> 63U;
+	zll <<= 1U;
+	zlh ^= (zll << 63U) ^ (zll << 62U) ^ (zll << 57U);
+	zhh ^= zlh ^ (zlh >> 1U) ^ (zlh >> 2U) ^ (zlh >> 7U);
+	zhl ^= zll ^ (zll >> 1U) ^ (zll >> 2U) ^ (zll >> 7U) ^ (zlh << 63U) ^ (zlh << 62U) ^ (zlh << 57U);
+	y0 = Utils::hton(zhh);
+	y1 = Utils::hton(zhl);
 }
 
-}   // anonymous namespace
+}	// anonymous namespace
 
 void AES::GMAC::update(const void* const data, unsigned int len) noexcept
 {
-    const uint8_t* in = reinterpret_cast<const uint8_t*>(data);
-    _len += len;
+	const uint8_t* in = reinterpret_cast<const uint8_t*>(data);
+	_len += len;
 
 #ifdef ZT_AES_AESNI
-    if (likely(Utils::CPUID.aes)) {
-        p_aesNIUpdate(in, len);
-        return;
-    }
-#endif   // ZT_AES_AESNI
+	if (likely(Utils::CPUID.aes)) {
+		p_aesNIUpdate(in, len);
+		return;
+	}
+#endif	 // ZT_AES_AESNI
 
 #ifdef ZT_AES_NEON
-    if (Utils::ARMCAP.pmull) {
-        p_armUpdate(in, len);
-        return;
-    }
-#endif   // ZT_AES_NEON
-
-    const uint64_t h0 = _aes.p_k.sw.h[0];
-    const uint64_t h1 = _aes.p_k.sw.h[1];
-    uint64_t y0 = _y[0];
-    uint64_t y1 = _y[1];
-
-    if (_rp) {
-        for (;;) {
-            if (! len) {
-                return;
-            }
-            --len;
-            _r[_rp++] = *(in++);
-            if (_rp == 16) {
-                y0 ^= Utils::loadMachineEndian<uint64_t>(_r);
-                y1 ^= Utils::loadMachineEndian<uint64_t>(_r + 8);
-                s_gfmul(h0, h1, y0, y1);
-                break;
-            }
-        }
-    }
-
-    while (len >= 16) {
-        y0 ^= Utils::loadMachineEndian<uint64_t>(in);
-        y1 ^= Utils::loadMachineEndian<uint64_t>(in + 8);
-        in += 16;
-        s_gfmul(h0, h1, y0, y1);
-        len -= 16;
-    }
-
-    _y[0] = y0;
-    _y[1] = y1;
-
-    for (unsigned int i = 0; i < len; ++i) {
-        _r[i] = in[i];
-    }
-    _rp = len;   // len is always less than 16 here
+	if (Utils::ARMCAP.pmull) {
+		p_armUpdate(in, len);
+		return;
+	}
+#endif	 // ZT_AES_NEON
+
+	const uint64_t h0 = _aes.p_k.sw.h[0];
+	const uint64_t h1 = _aes.p_k.sw.h[1];
+	uint64_t y0 = _y[0];
+	uint64_t y1 = _y[1];
+
+	if (_rp) {
+		for (;;) {
+			if (! len) {
+				return;
+			}
+			--len;
+			_r[_rp++] = *(in++);
+			if (_rp == 16) {
+				y0 ^= Utils::loadMachineEndian<uint64_t>(_r);
+				y1 ^= Utils::loadMachineEndian<uint64_t>(_r + 8);
+				s_gfmul(h0, h1, y0, y1);
+				break;
+			}
+		}
+	}
+
+	while (len >= 16) {
+		y0 ^= Utils::loadMachineEndian<uint64_t>(in);
+		y1 ^= Utils::loadMachineEndian<uint64_t>(in + 8);
+		in += 16;
+		s_gfmul(h0, h1, y0, y1);
+		len -= 16;
+	}
+
+	_y[0] = y0;
+	_y[1] = y1;
+
+	for (unsigned int i = 0; i < len; ++i) {
+		_r[i] = in[i];
+	}
+	_rp = len;	 // len is always less than 16 here
 }
 
 void AES::GMAC::finish(uint8_t tag[16]) noexcept
 {
 #ifdef ZT_AES_AESNI
-    if (likely(Utils::CPUID.aes)) {
-        p_aesNIFinish(tag);
-        return;
-    }
-#endif   // ZT_AES_AESNI
+	if (likely(Utils::CPUID.aes)) {
+		p_aesNIFinish(tag);
+		return;
+	}
+#endif	 // ZT_AES_AESNI
 
 #ifdef ZT_AES_NEON
-    if (Utils::ARMCAP.pmull) {
-        p_armFinish(tag);
-        return;
-    }
-#endif   // ZT_AES_NEON
-
-    const uint64_t h0 = _aes.p_k.sw.h[0];
-    const uint64_t h1 = _aes.p_k.sw.h[1];
-    uint64_t y0 = _y[0];
-    uint64_t y1 = _y[1];
-
-    if (_rp) {
-        while (_rp < 16) {
-            _r[_rp++] = 0;
-        }
-        y0 ^= Utils::loadMachineEndian<uint64_t>(_r);
-        y1 ^= Utils::loadMachineEndian<uint64_t>(_r + 8);
-        s_gfmul(h0, h1, y0, y1);
-    }
-
-    y0 ^= Utils::hton((uint64_t)_len << 3U);
-    s_gfmul(h0, h1, y0, y1);
-
-    uint64_t iv2[2];
-    Utils::copy<12>(iv2, _iv);
+	if (Utils::ARMCAP.pmull) {
+		p_armFinish(tag);
+		return;
+	}
+#endif	 // ZT_AES_NEON
+
+	const uint64_t h0 = _aes.p_k.sw.h[0];
+	const uint64_t h1 = _aes.p_k.sw.h[1];
+	uint64_t y0 = _y[0];
+	uint64_t y1 = _y[1];
+
+	if (_rp) {
+		while (_rp < 16) {
+			_r[_rp++] = 0;
+		}
+		y0 ^= Utils::loadMachineEndian<uint64_t>(_r);
+		y1 ^= Utils::loadMachineEndian<uint64_t>(_r + 8);
+		s_gfmul(h0, h1, y0, y1);
+	}
+
+	y0 ^= Utils::hton((uint64_t)_len << 3U);
+	s_gfmul(h0, h1, y0, y1);
+
+	uint64_t iv2[2];
+	Utils::copy<12>(iv2, _iv);
 #if __BYTE_ORDER == __BIG_ENDIAN
-    reinterpret_cast<uint32_t*>(iv2)[3] = 0x00000001;
+	reinterpret_cast<uint32_t*>(iv2)[3] = 0x00000001;
 #else
-    reinterpret_cast<uint32_t*>(iv2)[3] = 0x01000000;
+	reinterpret_cast<uint32_t*>(iv2)[3] = 0x01000000;
 #endif
-    _aes.encrypt(iv2, iv2);
+	_aes.encrypt(iv2, iv2);
 
-    Utils::storeMachineEndian<uint64_t>(tag, iv2[0] ^ y0);
-    Utils::storeMachineEndian<uint64_t>(tag + 8, iv2[1] ^ y1);
+	Utils::storeMachineEndian<uint64_t>(tag, iv2[0] ^ y0);
+	Utils::storeMachineEndian<uint64_t>(tag + 8, iv2[1] ^ y1);
 }
 
 // AES-CTR ------------------------------------------------------------------------------------------------------------
 
 void AES::CTR::crypt(const void* const input, unsigned int len) noexcept
 {
-    const uint8_t* in = reinterpret_cast<const uint8_t*>(input);
-    uint8_t* out = _out;
+	const uint8_t* in = reinterpret_cast<const uint8_t*>(input);
+	uint8_t* out = _out;
 
 #ifdef ZT_AES_AESNI
-    if (likely(Utils::CPUID.aes)) {
-        p_aesNICrypt(in, out, len);
-        return;
-    }
-#endif   // ZT_AES_AESNI
+	if (likely(Utils::CPUID.aes)) {
+		p_aesNICrypt(in, out, len);
+		return;
+	}
+#endif	 // ZT_AES_AESNI
 
 #ifdef ZT_AES_NEON
-    if (Utils::ARMCAP.aes) {
-        p_armCrypt(in, out, len);
-        return;
-    }
-#endif   // ZT_AES_NEON
-
-    uint64_t keyStream[2];
-    uint32_t ctr = Utils::ntoh(reinterpret_cast<uint32_t*>(_ctr)[3]);
-
-    unsigned int totalLen = _len;
-    if ((totalLen & 15U)) {
-        for (;;) {
-            if (! len) {
-                _len = (totalLen + len);
-                return;
-            }
-            --len;
-            out[totalLen++] = *(in++);
-            if (! (totalLen & 15U)) {
-                _aes.p_encryptSW(reinterpret_cast<const uint8_t*>(_ctr), reinterpret_cast<uint8_t*>(keyStream));
-                reinterpret_cast<uint32_t*>(_ctr)[3] = Utils::hton(++ctr);
-                uint8_t* outblk = out + (totalLen - 16);
-                for (int i = 0; i < 16; ++i) {
-                    outblk[i] ^= reinterpret_cast<uint8_t*>(keyStream)[i];
-                }
-                break;
-            }
-        }
-    }
-
-    out += totalLen;
-    _len = (totalLen + len);
-
-    if (likely(len >= 16)) {
-        const uint32_t* const restrict rk = _aes.p_k.sw.ek;
-        const uint32_t ctr0rk0 = Utils::ntoh(reinterpret_cast<const uint32_t*>(_ctr)[0]) ^ rk[0];
-        const uint32_t ctr1rk1 = Utils::ntoh(reinterpret_cast<const uint32_t*>(_ctr)[1]) ^ rk[1];
-        const uint32_t ctr2rk2 = Utils::ntoh(reinterpret_cast<const uint32_t*>(_ctr)[2]) ^ rk[2];
-        const uint32_t m8 = 0x000000ff;
-        const uint32_t m8_8 = 0x0000ff00;
-        const uint32_t m8_16 = 0x00ff0000;
-        const uint32_t m8_24 = 0xff000000;
-        if (likely((((uintptr_t)out & 7U) == 0U) && (((uintptr_t)in & 7U) == 0U))) {
-            do {
-                uint32_t s0, s1, s2, s3, t0, t1, t2, t3;
-                s0 = ctr0rk0;
-                s1 = ctr1rk1;
-                s2 = ctr2rk2;
-                s3 = ctr++ ^ rk[3];
-
-                const uint64_t in0 = *reinterpret_cast<const uint64_t*>(in);
-                const uint64_t in1 = *reinterpret_cast<const uint64_t*>(in + 8);
-                in += 16;
-
-                t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[4];
-                t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[5];
-                t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[6];
-                t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[7];
-                s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[8];
-                s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[9];
-                s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[10];
-                s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[11];
-                t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[12];
-                t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[13];
-                t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[14];
-                t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[15];
-                s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[16];
-                s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[17];
-                s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[18];
-                s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[19];
-                t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[20];
-                t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[21];
-                t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[22];
-                t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[23];
-                s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[24];
-                s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[25];
-                s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[26];
-                s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[27];
-                t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[28];
-                t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[29];
-                t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[30];
-                t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[31];
-                s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[32];
-                s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[33];
-                s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[34];
-                s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[35];
-                t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[36];
-                t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[37];
-                t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[38];
-                t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[39];
-                s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[40];
-                s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[41];
-                s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[42];
-                s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[43];
-                t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[44];
-                t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[45];
-                t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[46];
-                t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[47];
-                s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[48];
-                s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[49];
-                s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[50];
-                s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[51];
-                t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[52];
-                t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[53];
-                t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[54];
-                t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[55];
-                s0 = (Te2_r(t0 >> 24U) & m8_24) ^ (Te3_r((t1 >> 16U) & m8) & m8_16) ^ (Te0[(t2 >> 8U) & m8] & m8_8) ^ (Te1_r(t3 & m8) & m8) ^ rk[56];
-                s1 = (Te2_r(t1 >> 24U) & m8_24) ^ (Te3_r((t2 >> 16U) & m8) & m8_16) ^ (Te0[(t3 >> 8U) & m8] & m8_8) ^ (Te1_r(t0 & m8) & m8) ^ rk[57];
-                s2 = (Te2_r(t2 >> 24U) & m8_24) ^ (Te3_r((t3 >> 16U) & m8) & m8_16) ^ (Te0[(t0 >> 8U) & m8] & m8_8) ^ (Te1_r(t1 & m8) & m8) ^ rk[58];
-                s3 = (Te2_r(t3 >> 24U) & m8_24) ^ (Te3_r((t0 >> 16U) & m8) & m8_16) ^ (Te0[(t1 >> 8U) & m8] & m8_8) ^ (Te1_r(t2 & m8) & m8) ^ rk[59];
-
-                *reinterpret_cast<uint64_t*>(out) = in0 ^ Utils::hton(((uint64_t)s0 << 32U) | (uint64_t)s1);
-                *reinterpret_cast<uint64_t*>(out + 8) = in1 ^ Utils::hton(((uint64_t)s2 << 32U) | (uint64_t)s3);
-                out += 16;
-            } while ((len -= 16) >= 16);
-        }
-        else {
-            do {
-                uint32_t s0, s1, s2, s3, t0, t1, t2, t3;
-                s0 = ctr0rk0;
-                s1 = ctr1rk1;
-                s2 = ctr2rk2;
-                s3 = ctr++ ^ rk[3];
-
-                t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[4];
-                t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[5];
-                t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[6];
-                t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[7];
-                s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[8];
-                s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[9];
-                s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[10];
-                s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[11];
-                t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[12];
-                t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[13];
-                t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[14];
-                t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[15];
-                s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[16];
-                s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[17];
-                s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[18];
-                s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[19];
-                t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[20];
-                t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[21];
-                t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[22];
-                t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[23];
-                s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[24];
-                s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[25];
-                s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[26];
-                s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[27];
-                t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[28];
-                t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[29];
-                t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[30];
-                t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[31];
-                s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[32];
-                s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[33];
-                s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[34];
-                s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[35];
-                t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[36];
-                t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[37];
-                t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[38];
-                t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[39];
-                s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[40];
-                s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[41];
-                s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[42];
-                s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[43];
-                t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[44];
-                t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[45];
-                t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[46];
-                t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[47];
-                s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[48];
-                s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[49];
-                s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[50];
-                s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[51];
-                t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[52];
-                t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[53];
-                t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[54];
-                t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[55];
-                s0 = (Te2_r(t0 >> 24U) & m8_24) ^ (Te3_r((t1 >> 16U) & m8) & m8_16) ^ (Te0[(t2 >> 8U) & m8] & m8_8) ^ (Te1_r(t3 & m8) & m8) ^ rk[56];
-                s1 = (Te2_r(t1 >> 24U) & m8_24) ^ (Te3_r((t2 >> 16U) & m8) & m8_16) ^ (Te0[(t3 >> 8U) & m8] & m8_8) ^ (Te1_r(t0 & m8) & m8) ^ rk[57];
-                s2 = (Te2_r(t2 >> 24U) & m8_24) ^ (Te3_r((t3 >> 16U) & m8) & m8_16) ^ (Te0[(t0 >> 8U) & m8] & m8_8) ^ (Te1_r(t1 & m8) & m8) ^ rk[58];
-                s3 = (Te2_r(t3 >> 24U) & m8_24) ^ (Te3_r((t0 >> 16U) & m8) & m8_16) ^ (Te0[(t1 >> 8U) & m8] & m8_8) ^ (Te1_r(t2 & m8) & m8) ^ rk[59];
-
-                out[0] = in[0] ^ (uint8_t)(s0 >> 24U);
-                out[1] = in[1] ^ (uint8_t)(s0 >> 16U);
-                out[2] = in[2] ^ (uint8_t)(s0 >> 8U);
-                out[3] = in[3] ^ (uint8_t)s0;
-                out[4] = in[4] ^ (uint8_t)(s1 >> 24U);
-                out[5] = in[5] ^ (uint8_t)(s1 >> 16U);
-                out[6] = in[6] ^ (uint8_t)(s1 >> 8U);
-                out[7] = in[7] ^ (uint8_t)s1;
-                out[8] = in[8] ^ (uint8_t)(s2 >> 24U);
-                out[9] = in[9] ^ (uint8_t)(s2 >> 16U);
-                out[10] = in[10] ^ (uint8_t)(s2 >> 8U);
-                out[11] = in[11] ^ (uint8_t)s2;
-                out[12] = in[12] ^ (uint8_t)(s3 >> 24U);
-                out[13] = in[13] ^ (uint8_t)(s3 >> 16U);
-                out[14] = in[14] ^ (uint8_t)(s3 >> 8U);
-                out[15] = in[15] ^ (uint8_t)s3;
-                out += 16;
-                in += 16;
-            } while ((len -= 16) >= 16);
-        }
-        reinterpret_cast<uint32_t*>(_ctr)[3] = Utils::hton(ctr);
-    }
-
-    // Any remaining input is placed in _out. This will be picked up and crypted
-    // on subsequent calls to crypt() or finish() as it'll mean _len will not be
-    // an even multiple of 16.
-    while (len) {
-        --len;
-        *(out++) = *(in++);
-    }
+	if (Utils::ARMCAP.aes) {
+		p_armCrypt(in, out, len);
+		return;
+	}
+#endif	 // ZT_AES_NEON
+
+	uint64_t keyStream[2];
+	uint32_t ctr = Utils::ntoh(reinterpret_cast<uint32_t*>(_ctr)[3]);
+
+	unsigned int totalLen = _len;
+	if ((totalLen & 15U)) {
+		for (;;) {
+			if (! len) {
+				_len = (totalLen + len);
+				return;
+			}
+			--len;
+			out[totalLen++] = *(in++);
+			if (! (totalLen & 15U)) {
+				_aes.p_encryptSW(reinterpret_cast<const uint8_t*>(_ctr), reinterpret_cast<uint8_t*>(keyStream));
+				reinterpret_cast<uint32_t*>(_ctr)[3] = Utils::hton(++ctr);
+				uint8_t* outblk = out + (totalLen - 16);
+				for (int i = 0; i < 16; ++i) {
+					outblk[i] ^= reinterpret_cast<uint8_t*>(keyStream)[i];
+				}
+				break;
+			}
+		}
+	}
+
+	out += totalLen;
+	_len = (totalLen + len);
+
+	if (likely(len >= 16)) {
+		const uint32_t* const restrict rk = _aes.p_k.sw.ek;
+		const uint32_t ctr0rk0 = Utils::ntoh(reinterpret_cast<const uint32_t*>(_ctr)[0]) ^ rk[0];
+		const uint32_t ctr1rk1 = Utils::ntoh(reinterpret_cast<const uint32_t*>(_ctr)[1]) ^ rk[1];
+		const uint32_t ctr2rk2 = Utils::ntoh(reinterpret_cast<const uint32_t*>(_ctr)[2]) ^ rk[2];
+		const uint32_t m8 = 0x000000ff;
+		const uint32_t m8_8 = 0x0000ff00;
+		const uint32_t m8_16 = 0x00ff0000;
+		const uint32_t m8_24 = 0xff000000;
+		if (likely((((uintptr_t)out & 7U) == 0U) && (((uintptr_t)in & 7U) == 0U))) {
+			do {
+				uint32_t s0, s1, s2, s3, t0, t1, t2, t3;
+				s0 = ctr0rk0;
+				s1 = ctr1rk1;
+				s2 = ctr2rk2;
+				s3 = ctr++ ^ rk[3];
+
+				const uint64_t in0 = *reinterpret_cast<const uint64_t*>(in);
+				const uint64_t in1 = *reinterpret_cast<const uint64_t*>(in + 8);
+				in += 16;
+
+				t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[4];
+				t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[5];
+				t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[6];
+				t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[7];
+				s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[8];
+				s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[9];
+				s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[10];
+				s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[11];
+				t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[12];
+				t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[13];
+				t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[14];
+				t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[15];
+				s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[16];
+				s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[17];
+				s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[18];
+				s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[19];
+				t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[20];
+				t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[21];
+				t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[22];
+				t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[23];
+				s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[24];
+				s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[25];
+				s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[26];
+				s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[27];
+				t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[28];
+				t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[29];
+				t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[30];
+				t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[31];
+				s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[32];
+				s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[33];
+				s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[34];
+				s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[35];
+				t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[36];
+				t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[37];
+				t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[38];
+				t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[39];
+				s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[40];
+				s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[41];
+				s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[42];
+				s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[43];
+				t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[44];
+				t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[45];
+				t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[46];
+				t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[47];
+				s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[48];
+				s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[49];
+				s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[50];
+				s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[51];
+				t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[52];
+				t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[53];
+				t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[54];
+				t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[55];
+				s0 = (Te2_r(t0 >> 24U) & m8_24) ^ (Te3_r((t1 >> 16U) & m8) & m8_16) ^ (Te0[(t2 >> 8U) & m8] & m8_8) ^ (Te1_r(t3 & m8) & m8) ^ rk[56];
+				s1 = (Te2_r(t1 >> 24U) & m8_24) ^ (Te3_r((t2 >> 16U) & m8) & m8_16) ^ (Te0[(t3 >> 8U) & m8] & m8_8) ^ (Te1_r(t0 & m8) & m8) ^ rk[57];
+				s2 = (Te2_r(t2 >> 24U) & m8_24) ^ (Te3_r((t3 >> 16U) & m8) & m8_16) ^ (Te0[(t0 >> 8U) & m8] & m8_8) ^ (Te1_r(t1 & m8) & m8) ^ rk[58];
+				s3 = (Te2_r(t3 >> 24U) & m8_24) ^ (Te3_r((t0 >> 16U) & m8) & m8_16) ^ (Te0[(t1 >> 8U) & m8] & m8_8) ^ (Te1_r(t2 & m8) & m8) ^ rk[59];
+
+				*reinterpret_cast<uint64_t*>(out) = in0 ^ Utils::hton(((uint64_t)s0 << 32U) | (uint64_t)s1);
+				*reinterpret_cast<uint64_t*>(out + 8) = in1 ^ Utils::hton(((uint64_t)s2 << 32U) | (uint64_t)s3);
+				out += 16;
+			} while ((len -= 16) >= 16);
+		}
+		else {
+			do {
+				uint32_t s0, s1, s2, s3, t0, t1, t2, t3;
+				s0 = ctr0rk0;
+				s1 = ctr1rk1;
+				s2 = ctr2rk2;
+				s3 = ctr++ ^ rk[3];
+
+				t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[4];
+				t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[5];
+				t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[6];
+				t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[7];
+				s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[8];
+				s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[9];
+				s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[10];
+				s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[11];
+				t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[12];
+				t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[13];
+				t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[14];
+				t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[15];
+				s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[16];
+				s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[17];
+				s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[18];
+				s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[19];
+				t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[20];
+				t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[21];
+				t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[22];
+				t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[23];
+				s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[24];
+				s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[25];
+				s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[26];
+				s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[27];
+				t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[28];
+				t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[29];
+				t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[30];
+				t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[31];
+				s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[32];
+				s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[33];
+				s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[34];
+				s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[35];
+				t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[36];
+				t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[37];
+				t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[38];
+				t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[39];
+				s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[40];
+				s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[41];
+				s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[42];
+				s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[43];
+				t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[44];
+				t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[45];
+				t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[46];
+				t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[47];
+				s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[48];
+				s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[49];
+				s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[50];
+				s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[51];
+				t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[52];
+				t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[53];
+				t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[54];
+				t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[55];
+				s0 = (Te2_r(t0 >> 24U) & m8_24) ^ (Te3_r((t1 >> 16U) & m8) & m8_16) ^ (Te0[(t2 >> 8U) & m8] & m8_8) ^ (Te1_r(t3 & m8) & m8) ^ rk[56];
+				s1 = (Te2_r(t1 >> 24U) & m8_24) ^ (Te3_r((t2 >> 16U) & m8) & m8_16) ^ (Te0[(t3 >> 8U) & m8] & m8_8) ^ (Te1_r(t0 & m8) & m8) ^ rk[57];
+				s2 = (Te2_r(t2 >> 24U) & m8_24) ^ (Te3_r((t3 >> 16U) & m8) & m8_16) ^ (Te0[(t0 >> 8U) & m8] & m8_8) ^ (Te1_r(t1 & m8) & m8) ^ rk[58];
+				s3 = (Te2_r(t3 >> 24U) & m8_24) ^ (Te3_r((t0 >> 16U) & m8) & m8_16) ^ (Te0[(t1 >> 8U) & m8] & m8_8) ^ (Te1_r(t2 & m8) & m8) ^ rk[59];
+
+				out[0] = in[0] ^ (uint8_t)(s0 >> 24U);
+				out[1] = in[1] ^ (uint8_t)(s0 >> 16U);
+				out[2] = in[2] ^ (uint8_t)(s0 >> 8U);
+				out[3] = in[3] ^ (uint8_t)s0;
+				out[4] = in[4] ^ (uint8_t)(s1 >> 24U);
+				out[5] = in[5] ^ (uint8_t)(s1 >> 16U);
+				out[6] = in[6] ^ (uint8_t)(s1 >> 8U);
+				out[7] = in[7] ^ (uint8_t)s1;
+				out[8] = in[8] ^ (uint8_t)(s2 >> 24U);
+				out[9] = in[9] ^ (uint8_t)(s2 >> 16U);
+				out[10] = in[10] ^ (uint8_t)(s2 >> 8U);
+				out[11] = in[11] ^ (uint8_t)s2;
+				out[12] = in[12] ^ (uint8_t)(s3 >> 24U);
+				out[13] = in[13] ^ (uint8_t)(s3 >> 16U);
+				out[14] = in[14] ^ (uint8_t)(s3 >> 8U);
+				out[15] = in[15] ^ (uint8_t)s3;
+				out += 16;
+				in += 16;
+			} while ((len -= 16) >= 16);
+		}
+		reinterpret_cast<uint32_t*>(_ctr)[3] = Utils::hton(ctr);
+	}
+
+	// Any remaining input is placed in _out. This will be picked up and crypted
+	// on subsequent calls to crypt() or finish() as it'll mean _len will not be
+	// an even multiple of 16.
+	while (len) {
+		--len;
+		*(out++) = *(in++);
+	}
 }
 
 void AES::CTR::finish() noexcept
 {
-    uint8_t tmp[16];
-    const unsigned int rem = _len & 15U;
-    if (rem) {
-        _aes.encrypt(_ctr, tmp);
-        for (unsigned int i = 0, j = _len - rem; i < rem; ++i) {
-            _out[j + i] ^= tmp[i];
-        }
-    }
+	uint8_t tmp[16];
+	const unsigned int rem = _len & 15U;
+	if (rem) {
+		_aes.encrypt(_ctr, tmp);
+		for (unsigned int i = 0, j = _len - rem; i < rem; ++i) {
+			_out[j + i] ^= tmp[i];
+		}
+	}
 }
 
 // Software AES and AES key expansion ---------------------------------------------------------------------------------
 
 const uint32_t AES::Te0[256] = { 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a,
-                                 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b,
-                                 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f,
-                                 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f,
-                                 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497,
-                                 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a,
-                                 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3,
-                                 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d,
-                                 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395,
-                                 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883, 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76,
-                                 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b,
-                                 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818,
-                                 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85,
-                                 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9,
-                                 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a,
-                                 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a };
+								 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b,
+								 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f,
+								 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f,
+								 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497,
+								 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a,
+								 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3,
+								 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d,
+								 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395,
+								 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883, 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76,
+								 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b,
+								 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818,
+								 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85,
+								 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9,
+								 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a,
+								 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a };
 const uint32_t AES::Te4[256] = { 0x63636363, 0x7c7c7c7c, 0x77777777, 0x7b7b7b7b, 0xf2f2f2f2, 0x6b6b6b6b, 0x6f6f6f6f, 0xc5c5c5c5, 0x30303030, 0x01010101, 0x67676767, 0x2b2b2b2b, 0xfefefefe, 0xd7d7d7d7, 0xabababab, 0x76767676,
-                                 0xcacacaca, 0x82828282, 0xc9c9c9c9, 0x7d7d7d7d, 0xfafafafa, 0x59595959, 0x47474747, 0xf0f0f0f0, 0xadadadad, 0xd4d4d4d4, 0xa2a2a2a2, 0xafafafaf, 0x9c9c9c9c, 0xa4a4a4a4, 0x72727272, 0xc0c0c0c0,
-                                 0xb7b7b7b7, 0xfdfdfdfd, 0x93939393, 0x26262626, 0x36363636, 0x3f3f3f3f, 0xf7f7f7f7, 0xcccccccc, 0x34343434, 0xa5a5a5a5, 0xe5e5e5e5, 0xf1f1f1f1, 0x71717171, 0xd8d8d8d8, 0x31313131, 0x15151515,
-                                 0x04040404, 0xc7c7c7c7, 0x23232323, 0xc3c3c3c3, 0x18181818, 0x96969696, 0x05050505, 0x9a9a9a9a, 0x07070707, 0x12121212, 0x80808080, 0xe2e2e2e2, 0xebebebeb, 0x27272727, 0xb2b2b2b2, 0x75757575,
-                                 0x09090909, 0x83838383, 0x2c2c2c2c, 0x1a1a1a1a, 0x1b1b1b1b, 0x6e6e6e6e, 0x5a5a5a5a, 0xa0a0a0a0, 0x52525252, 0x3b3b3b3b, 0xd6d6d6d6, 0xb3b3b3b3, 0x29292929, 0xe3e3e3e3, 0x2f2f2f2f, 0x84848484,
-                                 0x53535353, 0xd1d1d1d1, 0x00000000, 0xedededed, 0x20202020, 0xfcfcfcfc, 0xb1b1b1b1, 0x5b5b5b5b, 0x6a6a6a6a, 0xcbcbcbcb, 0xbebebebe, 0x39393939, 0x4a4a4a4a, 0x4c4c4c4c, 0x58585858, 0xcfcfcfcf,
-                                 0xd0d0d0d0, 0xefefefef, 0xaaaaaaaa, 0xfbfbfbfb, 0x43434343, 0x4d4d4d4d, 0x33333333, 0x85858585, 0x45454545, 0xf9f9f9f9, 0x02020202, 0x7f7f7f7f, 0x50505050, 0x3c3c3c3c, 0x9f9f9f9f, 0xa8a8a8a8,
-                                 0x51515151, 0xa3a3a3a3, 0x40404040, 0x8f8f8f8f, 0x92929292, 0x9d9d9d9d, 0x38383838, 0xf5f5f5f5, 0xbcbcbcbc, 0xb6b6b6b6, 0xdadadada, 0x21212121, 0x10101010, 0xffffffff, 0xf3f3f3f3, 0xd2d2d2d2,
-                                 0xcdcdcdcd, 0x0c0c0c0c, 0x13131313, 0xecececec, 0x5f5f5f5f, 0x97979797, 0x44444444, 0x17171717, 0xc4c4c4c4, 0xa7a7a7a7, 0x7e7e7e7e, 0x3d3d3d3d, 0x64646464, 0x5d5d5d5d, 0x19191919, 0x73737373,
-                                 0x60606060, 0x81818181, 0x4f4f4f4f, 0xdcdcdcdc, 0x22222222, 0x2a2a2a2a, 0x90909090, 0x88888888, 0x46464646, 0xeeeeeeee, 0xb8b8b8b8, 0x14141414, 0xdededede, 0x5e5e5e5e, 0x0b0b0b0b, 0xdbdbdbdb,
-                                 0xe0e0e0e0, 0x32323232, 0x3a3a3a3a, 0x0a0a0a0a, 0x49494949, 0x06060606, 0x24242424, 0x5c5c5c5c, 0xc2c2c2c2, 0xd3d3d3d3, 0xacacacac, 0x62626262, 0x91919191, 0x95959595, 0xe4e4e4e4, 0x79797979,
-                                 0xe7e7e7e7, 0xc8c8c8c8, 0x37373737, 0x6d6d6d6d, 0x8d8d8d8d, 0xd5d5d5d5, 0x4e4e4e4e, 0xa9a9a9a9, 0x6c6c6c6c, 0x56565656, 0xf4f4f4f4, 0xeaeaeaea, 0x65656565, 0x7a7a7a7a, 0xaeaeaeae, 0x08080808,
-                                 0xbabababa, 0x78787878, 0x25252525, 0x2e2e2e2e, 0x1c1c1c1c, 0xa6a6a6a6, 0xb4b4b4b4, 0xc6c6c6c6, 0xe8e8e8e8, 0xdddddddd, 0x74747474, 0x1f1f1f1f, 0x4b4b4b4b, 0xbdbdbdbd, 0x8b8b8b8b, 0x8a8a8a8a,
-                                 0x70707070, 0x3e3e3e3e, 0xb5b5b5b5, 0x66666666, 0x48484848, 0x03030303, 0xf6f6f6f6, 0x0e0e0e0e, 0x61616161, 0x35353535, 0x57575757, 0xb9b9b9b9, 0x86868686, 0xc1c1c1c1, 0x1d1d1d1d, 0x9e9e9e9e,
-                                 0xe1e1e1e1, 0xf8f8f8f8, 0x98989898, 0x11111111, 0x69696969, 0xd9d9d9d9, 0x8e8e8e8e, 0x94949494, 0x9b9b9b9b, 0x1e1e1e1e, 0x87878787, 0xe9e9e9e9, 0xcececece, 0x55555555, 0x28282828, 0xdfdfdfdf,
-                                 0x8c8c8c8c, 0xa1a1a1a1, 0x89898989, 0x0d0d0d0d, 0xbfbfbfbf, 0xe6e6e6e6, 0x42424242, 0x68686868, 0x41414141, 0x99999999, 0x2d2d2d2d, 0x0f0f0f0f, 0xb0b0b0b0, 0x54545454, 0xbbbbbbbb, 0x16161616 };
+								 0xcacacaca, 0x82828282, 0xc9c9c9c9, 0x7d7d7d7d, 0xfafafafa, 0x59595959, 0x47474747, 0xf0f0f0f0, 0xadadadad, 0xd4d4d4d4, 0xa2a2a2a2, 0xafafafaf, 0x9c9c9c9c, 0xa4a4a4a4, 0x72727272, 0xc0c0c0c0,
+								 0xb7b7b7b7, 0xfdfdfdfd, 0x93939393, 0x26262626, 0x36363636, 0x3f3f3f3f, 0xf7f7f7f7, 0xcccccccc, 0x34343434, 0xa5a5a5a5, 0xe5e5e5e5, 0xf1f1f1f1, 0x71717171, 0xd8d8d8d8, 0x31313131, 0x15151515,
+								 0x04040404, 0xc7c7c7c7, 0x23232323, 0xc3c3c3c3, 0x18181818, 0x96969696, 0x05050505, 0x9a9a9a9a, 0x07070707, 0x12121212, 0x80808080, 0xe2e2e2e2, 0xebebebeb, 0x27272727, 0xb2b2b2b2, 0x75757575,
+								 0x09090909, 0x83838383, 0x2c2c2c2c, 0x1a1a1a1a, 0x1b1b1b1b, 0x6e6e6e6e, 0x5a5a5a5a, 0xa0a0a0a0, 0x52525252, 0x3b3b3b3b, 0xd6d6d6d6, 0xb3b3b3b3, 0x29292929, 0xe3e3e3e3, 0x2f2f2f2f, 0x84848484,
+								 0x53535353, 0xd1d1d1d1, 0x00000000, 0xedededed, 0x20202020, 0xfcfcfcfc, 0xb1b1b1b1, 0x5b5b5b5b, 0x6a6a6a6a, 0xcbcbcbcb, 0xbebebebe, 0x39393939, 0x4a4a4a4a, 0x4c4c4c4c, 0x58585858, 0xcfcfcfcf,
+								 0xd0d0d0d0, 0xefefefef, 0xaaaaaaaa, 0xfbfbfbfb, 0x43434343, 0x4d4d4d4d, 0x33333333, 0x85858585, 0x45454545, 0xf9f9f9f9, 0x02020202, 0x7f7f7f7f, 0x50505050, 0x3c3c3c3c, 0x9f9f9f9f, 0xa8a8a8a8,
+								 0x51515151, 0xa3a3a3a3, 0x40404040, 0x8f8f8f8f, 0x92929292, 0x9d9d9d9d, 0x38383838, 0xf5f5f5f5, 0xbcbcbcbc, 0xb6b6b6b6, 0xdadadada, 0x21212121, 0x10101010, 0xffffffff, 0xf3f3f3f3, 0xd2d2d2d2,
+								 0xcdcdcdcd, 0x0c0c0c0c, 0x13131313, 0xecececec, 0x5f5f5f5f, 0x97979797, 0x44444444, 0x17171717, 0xc4c4c4c4, 0xa7a7a7a7, 0x7e7e7e7e, 0x3d3d3d3d, 0x64646464, 0x5d5d5d5d, 0x19191919, 0x73737373,
+								 0x60606060, 0x81818181, 0x4f4f4f4f, 0xdcdcdcdc, 0x22222222, 0x2a2a2a2a, 0x90909090, 0x88888888, 0x46464646, 0xeeeeeeee, 0xb8b8b8b8, 0x14141414, 0xdededede, 0x5e5e5e5e, 0x0b0b0b0b, 0xdbdbdbdb,
+								 0xe0e0e0e0, 0x32323232, 0x3a3a3a3a, 0x0a0a0a0a, 0x49494949, 0x06060606, 0x24242424, 0x5c5c5c5c, 0xc2c2c2c2, 0xd3d3d3d3, 0xacacacac, 0x62626262, 0x91919191, 0x95959595, 0xe4e4e4e4, 0x79797979,
+								 0xe7e7e7e7, 0xc8c8c8c8, 0x37373737, 0x6d6d6d6d, 0x8d8d8d8d, 0xd5d5d5d5, 0x4e4e4e4e, 0xa9a9a9a9, 0x6c6c6c6c, 0x56565656, 0xf4f4f4f4, 0xeaeaeaea, 0x65656565, 0x7a7a7a7a, 0xaeaeaeae, 0x08080808,
+								 0xbabababa, 0x78787878, 0x25252525, 0x2e2e2e2e, 0x1c1c1c1c, 0xa6a6a6a6, 0xb4b4b4b4, 0xc6c6c6c6, 0xe8e8e8e8, 0xdddddddd, 0x74747474, 0x1f1f1f1f, 0x4b4b4b4b, 0xbdbdbdbd, 0x8b8b8b8b, 0x8a8a8a8a,
+								 0x70707070, 0x3e3e3e3e, 0xb5b5b5b5, 0x66666666, 0x48484848, 0x03030303, 0xf6f6f6f6, 0x0e0e0e0e, 0x61616161, 0x35353535, 0x57575757, 0xb9b9b9b9, 0x86868686, 0xc1c1c1c1, 0x1d1d1d1d, 0x9e9e9e9e,
+								 0xe1e1e1e1, 0xf8f8f8f8, 0x98989898, 0x11111111, 0x69696969, 0xd9d9d9d9, 0x8e8e8e8e, 0x94949494, 0x9b9b9b9b, 0x1e1e1e1e, 0x87878787, 0xe9e9e9e9, 0xcececece, 0x55555555, 0x28282828, 0xdfdfdfdf,
+								 0x8c8c8c8c, 0xa1a1a1a1, 0x89898989, 0x0d0d0d0d, 0xbfbfbfbf, 0xe6e6e6e6, 0x42424242, 0x68686868, 0x41414141, 0x99999999, 0x2d2d2d2d, 0x0f0f0f0f, 0xb0b0b0b0, 0x54545454, 0xbbbbbbbb, 0x16161616 };
 const uint32_t AES::Td0[256] = { 0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96, 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393, 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f,
-                                 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1, 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844,
-                                 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd, 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45, 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94,
-                                 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7, 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a, 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c,
-                                 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1, 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a, 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75, 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051,
-                                 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff, 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77, 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb,
-                                 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000, 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e, 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927, 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a,
-                                 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e, 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d, 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8,
-                                 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163, 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120,
-                                 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0, 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef,
-                                 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36, 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5,
-                                 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3, 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8, 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6,
-                                 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6, 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0, 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f,
-                                 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df, 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f, 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e, 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713,
-                                 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c, 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf, 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86,
-                                 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541, 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190, 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742 };
+								 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1, 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844,
+								 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd, 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45, 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94,
+								 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7, 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a, 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c,
+								 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1, 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a, 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75, 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051,
+								 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff, 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77, 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb,
+								 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000, 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e, 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927, 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a,
+								 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e, 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d, 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8,
+								 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163, 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120,
+								 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0, 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef,
+								 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36, 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5,
+								 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3, 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8, 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6,
+								 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6, 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0, 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f,
+								 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df, 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f, 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e, 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713,
+								 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c, 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf, 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86,
+								 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541, 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190, 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742 };
 const uint8_t AES::Td4[256] = { 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
-                                0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
-                                0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
-                                0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
-                                0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
-                                0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
-                                0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
-                                0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d };
+								0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
+								0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
+								0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
+								0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
+								0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
+								0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
+								0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d };
 const uint32_t AES::rcon[15] = { 0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, 0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000, 0x6c000000, 0xd8000000, 0xab000000, 0x4d000000, 0x9a000000 };
 
 void AES::p_initSW(const uint8_t* key) noexcept
 {
-    uint32_t* rk = p_k.sw.ek;
-
-    rk[0] = Utils::loadBigEndian<uint32_t>(key);
-    rk[1] = Utils::loadBigEndian<uint32_t>(key + 4);
-    rk[2] = Utils::loadBigEndian<uint32_t>(key + 8);
-    rk[3] = Utils::loadBigEndian<uint32_t>(key + 12);
-    rk[4] = Utils::loadBigEndian<uint32_t>(key + 16);
-    rk[5] = Utils::loadBigEndian<uint32_t>(key + 20);
-    rk[6] = Utils::loadBigEndian<uint32_t>(key + 24);
-    rk[7] = Utils::loadBigEndian<uint32_t>(key + 28);
-    for (int i = 0;;) {
-        uint32_t temp = rk[7];
-        rk[8] = rk[0] ^ (Te2_r((temp >> 16U) & 0xffU) & 0xff000000U) ^ (Te3_r((temp >> 8U) & 0xffU) & 0x00ff0000U) ^ (Te0[(temp) & 0xffU] & 0x0000ff00U) ^ (Te1_r(temp >> 24U) & 0x000000ffU) ^ rcon[i];
-        rk[9] = rk[1] ^ rk[8];
-        rk[10] = rk[2] ^ rk[9];
-        rk[11] = rk[3] ^ rk[10];
-        if (++i == 7) {
-            break;
-        }
-        temp = rk[11];
-        rk[12] = rk[4] ^ (Te2_r(temp >> 24U) & 0xff000000U) ^ (Te3_r((temp >> 16U) & 0xffU) & 0x00ff0000U) ^ (Te0[(temp >> 8U) & 0xffU] & 0x0000ff00U) ^ (Te1_r((temp) & 0xffU) & 0x000000ffU);
-        rk[13] = rk[5] ^ rk[12];
-        rk[14] = rk[6] ^ rk[13];
-        rk[15] = rk[7] ^ rk[14];
-        rk += 8;
-    }
-
-    p_encryptSW((const uint8_t*)Utils::ZERO256, (uint8_t*)p_k.sw.h);
-    p_k.sw.h[0] = Utils::ntoh(p_k.sw.h[0]);
-    p_k.sw.h[1] = Utils::ntoh(p_k.sw.h[1]);
-
-    for (int i = 0; i < 60; ++i) {
-        p_k.sw.dk[i] = p_k.sw.ek[i];
-    }
-    rk = p_k.sw.dk;
-
-    for (int i = 0, j = 56; i < j; i += 4, j -= 4) {
-        uint32_t temp = rk[i];
-        rk[i] = rk[j];
-        rk[j] = temp;
-        temp = rk[i + 1];
-        rk[i + 1] = rk[j + 1];
-        rk[j + 1] = temp;
-        temp = rk[i + 2];
-        rk[i + 2] = rk[j + 2];
-        rk[j + 2] = temp;
-        temp = rk[i + 3];
-        rk[i + 3] = rk[j + 3];
-        rk[j + 3] = temp;
-    }
-    for (int i = 1; i < 14; ++i) {
-        rk += 4;
-        rk[0] = Td0[Te4[(rk[0] >> 24U)] & 0xffU] ^ Td1_r(Te4[(rk[0] >> 16U) & 0xffU] & 0xffU) ^ Td2_r(Te4[(rk[0] >> 8U) & 0xffU] & 0xffU) ^ Td3_r(Te4[(rk[0]) & 0xffU] & 0xffU);
-        rk[1] = Td0[Te4[(rk[1] >> 24U)] & 0xffU] ^ Td1_r(Te4[(rk[1] >> 16U) & 0xffU] & 0xffU) ^ Td2_r(Te4[(rk[1] >> 8U) & 0xffU] & 0xffU) ^ Td3_r(Te4[(rk[1]) & 0xffU] & 0xffU);
-        rk[2] = Td0[Te4[(rk[2] >> 24U)] & 0xffU] ^ Td1_r(Te4[(rk[2] >> 16U) & 0xffU] & 0xffU) ^ Td2_r(Te4[(rk[2] >> 8U) & 0xffU] & 0xffU) ^ Td3_r(Te4[(rk[2]) & 0xffU] & 0xffU);
-        rk[3] = Td0[Te4[(rk[3] >> 24U)] & 0xffU] ^ Td1_r(Te4[(rk[3] >> 16U) & 0xffU] & 0xffU) ^ Td2_r(Te4[(rk[3] >> 8U) & 0xffU] & 0xffU) ^ Td3_r(Te4[(rk[3]) & 0xffU] & 0xffU);
-    }
+	uint32_t* rk = p_k.sw.ek;
+
+	rk[0] = Utils::loadBigEndian<uint32_t>(key);
+	rk[1] = Utils::loadBigEndian<uint32_t>(key + 4);
+	rk[2] = Utils::loadBigEndian<uint32_t>(key + 8);
+	rk[3] = Utils::loadBigEndian<uint32_t>(key + 12);
+	rk[4] = Utils::loadBigEndian<uint32_t>(key + 16);
+	rk[5] = Utils::loadBigEndian<uint32_t>(key + 20);
+	rk[6] = Utils::loadBigEndian<uint32_t>(key + 24);
+	rk[7] = Utils::loadBigEndian<uint32_t>(key + 28);
+	for (int i = 0;;) {
+		uint32_t temp = rk[7];
+		rk[8] = rk[0] ^ (Te2_r((temp >> 16U) & 0xffU) & 0xff000000U) ^ (Te3_r((temp >> 8U) & 0xffU) & 0x00ff0000U) ^ (Te0[(temp) & 0xffU] & 0x0000ff00U) ^ (Te1_r(temp >> 24U) & 0x000000ffU) ^ rcon[i];
+		rk[9] = rk[1] ^ rk[8];
+		rk[10] = rk[2] ^ rk[9];
+		rk[11] = rk[3] ^ rk[10];
+		if (++i == 7) {
+			break;
+		}
+		temp = rk[11];
+		rk[12] = rk[4] ^ (Te2_r(temp >> 24U) & 0xff000000U) ^ (Te3_r((temp >> 16U) & 0xffU) & 0x00ff0000U) ^ (Te0[(temp >> 8U) & 0xffU] & 0x0000ff00U) ^ (Te1_r((temp) & 0xffU) & 0x000000ffU);
+		rk[13] = rk[5] ^ rk[12];
+		rk[14] = rk[6] ^ rk[13];
+		rk[15] = rk[7] ^ rk[14];
+		rk += 8;
+	}
+
+	p_encryptSW((const uint8_t*)Utils::ZERO256, (uint8_t*)p_k.sw.h);
+	p_k.sw.h[0] = Utils::ntoh(p_k.sw.h[0]);
+	p_k.sw.h[1] = Utils::ntoh(p_k.sw.h[1]);
+
+	for (int i = 0; i < 60; ++i) {
+		p_k.sw.dk[i] = p_k.sw.ek[i];
+	}
+	rk = p_k.sw.dk;
+
+	for (int i = 0, j = 56; i < j; i += 4, j -= 4) {
+		uint32_t temp = rk[i];
+		rk[i] = rk[j];
+		rk[j] = temp;
+		temp = rk[i + 1];
+		rk[i + 1] = rk[j + 1];
+		rk[j + 1] = temp;
+		temp = rk[i + 2];
+		rk[i + 2] = rk[j + 2];
+		rk[j + 2] = temp;
+		temp = rk[i + 3];
+		rk[i + 3] = rk[j + 3];
+		rk[j + 3] = temp;
+	}
+	for (int i = 1; i < 14; ++i) {
+		rk += 4;
+		rk[0] = Td0[Te4[(rk[0] >> 24U)] & 0xffU] ^ Td1_r(Te4[(rk[0] >> 16U) & 0xffU] & 0xffU) ^ Td2_r(Te4[(rk[0] >> 8U) & 0xffU] & 0xffU) ^ Td3_r(Te4[(rk[0]) & 0xffU] & 0xffU);
+		rk[1] = Td0[Te4[(rk[1] >> 24U)] & 0xffU] ^ Td1_r(Te4[(rk[1] >> 16U) & 0xffU] & 0xffU) ^ Td2_r(Te4[(rk[1] >> 8U) & 0xffU] & 0xffU) ^ Td3_r(Te4[(rk[1]) & 0xffU] & 0xffU);
+		rk[2] = Td0[Te4[(rk[2] >> 24U)] & 0xffU] ^ Td1_r(Te4[(rk[2] >> 16U) & 0xffU] & 0xffU) ^ Td2_r(Te4[(rk[2] >> 8U) & 0xffU] & 0xffU) ^ Td3_r(Te4[(rk[2]) & 0xffU] & 0xffU);
+		rk[3] = Td0[Te4[(rk[3] >> 24U)] & 0xffU] ^ Td1_r(Te4[(rk[3] >> 16U) & 0xffU] & 0xffU) ^ Td2_r(Te4[(rk[3] >> 8U) & 0xffU] & 0xffU) ^ Td3_r(Te4[(rk[3]) & 0xffU] & 0xffU);
+	}
 }
 
 void AES::p_encryptSW(const uint8_t* in, uint8_t* out) const noexcept
 {
-    const uint32_t* const restrict rk = p_k.sw.ek;
-    const uint32_t m8 = 0x000000ff;
-    const uint32_t m8_8 = 0x0000ff00;
-    const uint32_t m8_16 = 0x00ff0000;
-    const uint32_t m8_24 = 0xff000000;
-    uint32_t s0 = Utils::loadBigEndian<uint32_t>(in) ^ rk[0];
-    uint32_t s1 = Utils::loadBigEndian<uint32_t>(in + 4) ^ rk[1];
-    uint32_t s2 = Utils::loadBigEndian<uint32_t>(in + 8) ^ rk[2];
-    uint32_t s3 = Utils::loadBigEndian<uint32_t>(in + 12) ^ rk[3];
-
-    uint32_t t0, t1, t2, t3;
-    t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[4];
-    t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[5];
-    t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[6];
-    t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[7];
-    s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[8];
-    s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[9];
-    s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[10];
-    s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[11];
-    t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[12];
-    t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[13];
-    t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[14];
-    t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[15];
-    s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[16];
-    s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[17];
-    s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[18];
-    s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[19];
-    t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[20];
-    t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[21];
-    t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[22];
-    t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[23];
-    s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[24];
-    s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[25];
-    s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[26];
-    s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[27];
-    t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[28];
-    t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[29];
-    t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[30];
-    t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[31];
-    s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[32];
-    s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[33];
-    s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[34];
-    s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[35];
-    t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[36];
-    t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[37];
-    t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[38];
-    t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[39];
-    s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[40];
-    s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[41];
-    s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[42];
-    s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[43];
-    t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[44];
-    t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[45];
-    t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[46];
-    t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[47];
-    s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[48];
-    s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[49];
-    s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[50];
-    s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[51];
-    t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[52];
-    t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[53];
-    t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[54];
-    t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[55];
-    s0 = (Te2_r(t0 >> 24U) & m8_24) ^ (Te3_r((t1 >> 16U) & m8) & m8_16) ^ (Te0[(t2 >> 8U) & m8] & m8_8) ^ (Te1_r(t3 & m8) & m8) ^ rk[56];
-    s1 = (Te2_r(t1 >> 24U) & m8_24) ^ (Te3_r((t2 >> 16U) & m8) & m8_16) ^ (Te0[(t3 >> 8U) & m8] & m8_8) ^ (Te1_r(t0 & m8) & m8) ^ rk[57];
-    s2 = (Te2_r(t2 >> 24U) & m8_24) ^ (Te3_r((t3 >> 16U) & m8) & m8_16) ^ (Te0[(t0 >> 8U) & m8] & m8_8) ^ (Te1_r(t1 & m8) & m8) ^ rk[58];
-    s3 = (Te2_r(t3 >> 24U) & m8_24) ^ (Te3_r((t0 >> 16U) & m8) & m8_16) ^ (Te0[(t1 >> 8U) & m8] & m8_8) ^ (Te1_r(t2 & m8) & m8) ^ rk[59];
-
-    Utils::storeBigEndian<uint32_t>(out, s0);
-    Utils::storeBigEndian<uint32_t>(out + 4, s1);
-    Utils::storeBigEndian<uint32_t>(out + 8, s2);
-    Utils::storeBigEndian<uint32_t>(out + 12, s3);
+	const uint32_t* const restrict rk = p_k.sw.ek;
+	const uint32_t m8 = 0x000000ff;
+	const uint32_t m8_8 = 0x0000ff00;
+	const uint32_t m8_16 = 0x00ff0000;
+	const uint32_t m8_24 = 0xff000000;
+	uint32_t s0 = Utils::loadBigEndian<uint32_t>(in) ^ rk[0];
+	uint32_t s1 = Utils::loadBigEndian<uint32_t>(in + 4) ^ rk[1];
+	uint32_t s2 = Utils::loadBigEndian<uint32_t>(in + 8) ^ rk[2];
+	uint32_t s3 = Utils::loadBigEndian<uint32_t>(in + 12) ^ rk[3];
+
+	uint32_t t0, t1, t2, t3;
+	t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[4];
+	t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[5];
+	t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[6];
+	t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[7];
+	s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[8];
+	s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[9];
+	s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[10];
+	s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[11];
+	t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[12];
+	t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[13];
+	t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[14];
+	t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[15];
+	s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[16];
+	s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[17];
+	s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[18];
+	s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[19];
+	t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[20];
+	t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[21];
+	t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[22];
+	t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[23];
+	s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[24];
+	s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[25];
+	s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[26];
+	s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[27];
+	t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[28];
+	t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[29];
+	t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[30];
+	t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[31];
+	s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[32];
+	s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[33];
+	s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[34];
+	s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[35];
+	t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[36];
+	t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[37];
+	t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[38];
+	t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[39];
+	s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[40];
+	s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[41];
+	s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[42];
+	s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[43];
+	t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[44];
+	t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[45];
+	t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[46];
+	t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[47];
+	s0 = Te0[t0 >> 24U] ^ Te1_r((t1 >> 16U) & m8) ^ Te2_r((t2 >> 8U) & m8) ^ Te3_r(t3 & m8) ^ rk[48];
+	s1 = Te0[t1 >> 24U] ^ Te1_r((t2 >> 16U) & m8) ^ Te2_r((t3 >> 8U) & m8) ^ Te3_r(t0 & m8) ^ rk[49];
+	s2 = Te0[t2 >> 24U] ^ Te1_r((t3 >> 16U) & m8) ^ Te2_r((t0 >> 8U) & m8) ^ Te3_r(t1 & m8) ^ rk[50];
+	s3 = Te0[t3 >> 24U] ^ Te1_r((t0 >> 16U) & m8) ^ Te2_r((t1 >> 8U) & m8) ^ Te3_r(t2 & m8) ^ rk[51];
+	t0 = Te0[s0 >> 24U] ^ Te1_r((s1 >> 16U) & m8) ^ Te2_r((s2 >> 8U) & m8) ^ Te3_r(s3 & m8) ^ rk[52];
+	t1 = Te0[s1 >> 24U] ^ Te1_r((s2 >> 16U) & m8) ^ Te2_r((s3 >> 8U) & m8) ^ Te3_r(s0 & m8) ^ rk[53];
+	t2 = Te0[s2 >> 24U] ^ Te1_r((s3 >> 16U) & m8) ^ Te2_r((s0 >> 8U) & m8) ^ Te3_r(s1 & m8) ^ rk[54];
+	t3 = Te0[s3 >> 24U] ^ Te1_r((s0 >> 16U) & m8) ^ Te2_r((s1 >> 8U) & m8) ^ Te3_r(s2 & m8) ^ rk[55];
+	s0 = (Te2_r(t0 >> 24U) & m8_24) ^ (Te3_r((t1 >> 16U) & m8) & m8_16) ^ (Te0[(t2 >> 8U) & m8] & m8_8) ^ (Te1_r(t3 & m8) & m8) ^ rk[56];
+	s1 = (Te2_r(t1 >> 24U) & m8_24) ^ (Te3_r((t2 >> 16U) & m8) & m8_16) ^ (Te0[(t3 >> 8U) & m8] & m8_8) ^ (Te1_r(t0 & m8) & m8) ^ rk[57];
+	s2 = (Te2_r(t2 >> 24U) & m8_24) ^ (Te3_r((t3 >> 16U) & m8) & m8_16) ^ (Te0[(t0 >> 8U) & m8] & m8_8) ^ (Te1_r(t1 & m8) & m8) ^ rk[58];
+	s3 = (Te2_r(t3 >> 24U) & m8_24) ^ (Te3_r((t0 >> 16U) & m8) & m8_16) ^ (Te0[(t1 >> 8U) & m8] & m8_8) ^ (Te1_r(t2 & m8) & m8) ^ rk[59];
+
+	Utils::storeBigEndian<uint32_t>(out, s0);
+	Utils::storeBigEndian<uint32_t>(out + 4, s1);
+	Utils::storeBigEndian<uint32_t>(out + 8, s2);
+	Utils::storeBigEndian<uint32_t>(out + 12, s3);
 }
 
 void AES::p_decryptSW(const uint8_t* in, uint8_t* out) const noexcept
 {
-    const uint32_t* restrict rk = p_k.sw.dk;
-    const uint32_t m8 = 0x000000ff;
-    uint32_t s0 = Utils::loadBigEndian<uint32_t>(in) ^ rk[0];
-    uint32_t s1 = Utils::loadBigEndian<uint32_t>(in + 4) ^ rk[1];
-    uint32_t s2 = Utils::loadBigEndian<uint32_t>(in + 8) ^ rk[2];
-    uint32_t s3 = Utils::loadBigEndian<uint32_t>(in + 12) ^ rk[3];
-
-    uint32_t t0, t1, t2, t3;
-    t0 = Td0[s0 >> 24U] ^ Td1_r((s3 >> 16U) & m8) ^ Td2_r((s2 >> 8U) & m8) ^ Td3_r(s1 & m8) ^ rk[4];
-    t1 = Td0[s1 >> 24U] ^ Td1_r((s0 >> 16U) & m8) ^ Td2_r((s3 >> 8U) & m8) ^ Td3_r(s2 & m8) ^ rk[5];
-    t2 = Td0[s2 >> 24U] ^ Td1_r((s1 >> 16U) & m8) ^ Td2_r((s0 >> 8U) & m8) ^ Td3_r(s3 & m8) ^ rk[6];
-    t3 = Td0[s3 >> 24U] ^ Td1_r((s2 >> 16U) & m8) ^ Td2_r((s1 >> 8U) & m8) ^ Td3_r(s0 & m8) ^ rk[7];
-    s0 = Td0[t0 >> 24U] ^ Td1_r((t3 >> 16U) & m8) ^ Td2_r((t2 >> 8U) & m8) ^ Td3_r(t1 & m8) ^ rk[8];
-    s1 = Td0[t1 >> 24U] ^ Td1_r((t0 >> 16U) & m8) ^ Td2_r((t3 >> 8U) & m8) ^ Td3_r(t2 & m8) ^ rk[9];
-    s2 = Td0[t2 >> 24U] ^ Td1_r((t1 >> 16U) & m8) ^ Td2_r((t0 >> 8U) & m8) ^ Td3_r(t3 & m8) ^ rk[10];
-    s3 = Td0[t3 >> 24U] ^ Td1_r((t2 >> 16U) & m8) ^ Td2_r((t1 >> 8U) & m8) ^ Td3_r(t0 & m8) ^ rk[11];
-    t0 = Td0[s0 >> 24U] ^ Td1_r((s3 >> 16U) & m8) ^ Td2_r((s2 >> 8U) & m8) ^ Td3_r(s1 & m8) ^ rk[12];
-    t1 = Td0[s1 >> 24U] ^ Td1_r((s0 >> 16U) & m8) ^ Td2_r((s3 >> 8U) & m8) ^ Td3_r(s2 & m8) ^ rk[13];
-    t2 = Td0[s2 >> 24U] ^ Td1_r((s1 >> 16U) & m8) ^ Td2_r((s0 >> 8U) & m8) ^ Td3_r(s3 & m8) ^ rk[14];
-    t3 = Td0[s3 >> 24U] ^ Td1_r((s2 >> 16U) & m8) ^ Td2_r((s1 >> 8U) & m8) ^ Td3_r(s0 & m8) ^ rk[15];
-    s0 = Td0[t0 >> 24U] ^ Td1_r((t3 >> 16U) & m8) ^ Td2_r((t2 >> 8U) & m8) ^ Td3_r(t1 & m8) ^ rk[16];
-    s1 = Td0[t1 >> 24U] ^ Td1_r((t0 >> 16U) & m8) ^ Td2_r((t3 >> 8U) & m8) ^ Td3_r(t2 & m8) ^ rk[17];
-    s2 = Td0[t2 >> 24U] ^ Td1_r((t1 >> 16U) & m8) ^ Td2_r((t0 >> 8U) & m8) ^ Td3_r(t3 & m8) ^ rk[18];
-    s3 = Td0[t3 >> 24U] ^ Td1_r((t2 >> 16U) & m8) ^ Td2_r((t1 >> 8U) & m8) ^ Td3_r(t0 & m8) ^ rk[19];
-    t0 = Td0[s0 >> 24U] ^ Td1_r((s3 >> 16U) & m8) ^ Td2_r((s2 >> 8U) & m8) ^ Td3_r(s1 & m8) ^ rk[20];
-    t1 = Td0[s1 >> 24U] ^ Td1_r((s0 >> 16U) & m8) ^ Td2_r((s3 >> 8U) & m8) ^ Td3_r(s2 & m8) ^ rk[21];
-    t2 = Td0[s2 >> 24U] ^ Td1_r((s1 >> 16U) & m8) ^ Td2_r((s0 >> 8U) & m8) ^ Td3_r(s3 & m8) ^ rk[22];
-    t3 = Td0[s3 >> 24U] ^ Td1_r((s2 >> 16U) & m8) ^ Td2_r((s1 >> 8U) & m8) ^ Td3_r(s0 & m8) ^ rk[23];
-    s0 = Td0[t0 >> 24U] ^ Td1_r((t3 >> 16U) & m8) ^ Td2_r((t2 >> 8U) & m8) ^ Td3_r(t1 & m8) ^ rk[24];
-    s1 = Td0[t1 >> 24U] ^ Td1_r((t0 >> 16U) & m8) ^ Td2_r((t3 >> 8U) & m8) ^ Td3_r(t2 & m8) ^ rk[25];
-    s2 = Td0[t2 >> 24U] ^ Td1_r((t1 >> 16U) & m8) ^ Td2_r((t0 >> 8U) & m8) ^ Td3_r(t3 & m8) ^ rk[26];
-    s3 = Td0[t3 >> 24U] ^ Td1_r((t2 >> 16U) & m8) ^ Td2_r((t1 >> 8U) & m8) ^ Td3_r(t0 & m8) ^ rk[27];
-    t0 = Td0[s0 >> 24U] ^ Td1_r((s3 >> 16U) & m8) ^ Td2_r((s2 >> 8U) & m8) ^ Td3_r(s1 & m8) ^ rk[28];
-    t1 = Td0[s1 >> 24U] ^ Td1_r((s0 >> 16U) & m8) ^ Td2_r((s3 >> 8U) & m8) ^ Td3_r(s2 & m8) ^ rk[29];
-    t2 = Td0[s2 >> 24U] ^ Td1_r((s1 >> 16U) & m8) ^ Td2_r((s0 >> 8U) & m8) ^ Td3_r(s3 & m8) ^ rk[30];
-    t3 = Td0[s3 >> 24U] ^ Td1_r((s2 >> 16U) & m8) ^ Td2_r((s1 >> 8U) & m8) ^ Td3_r(s0 & m8) ^ rk[31];
-    s0 = Td0[t0 >> 24U] ^ Td1_r((t3 >> 16U) & m8) ^ Td2_r((t2 >> 8U) & m8) ^ Td3_r(t1 & m8) ^ rk[32];
-    s1 = Td0[t1 >> 24U] ^ Td1_r((t0 >> 16U) & m8) ^ Td2_r((t3 >> 8U) & m8) ^ Td3_r(t2 & m8) ^ rk[33];
-    s2 = Td0[t2 >> 24U] ^ Td1_r((t1 >> 16U) & m8) ^ Td2_r((t0 >> 8U) & m8) ^ Td3_r(t3 & m8) ^ rk[34];
-    s3 = Td0[t3 >> 24U] ^ Td1_r((t2 >> 16U) & m8) ^ Td2_r((t1 >> 8U) & m8) ^ Td3_r(t0 & m8) ^ rk[35];
-    t0 = Td0[s0 >> 24U] ^ Td1_r((s3 >> 16U) & m8) ^ Td2_r((s2 >> 8U) & m8) ^ Td3_r(s1 & m8) ^ rk[36];
-    t1 = Td0[s1 >> 24U] ^ Td1_r((s0 >> 16U) & m8) ^ Td2_r((s3 >> 8U) & m8) ^ Td3_r(s2 & m8) ^ rk[37];
-    t2 = Td0[s2 >> 24U] ^ Td1_r((s1 >> 16U) & m8) ^ Td2_r((s0 >> 8U) & m8) ^ Td3_r(s3 & m8) ^ rk[38];
-    t3 = Td0[s3 >> 24U] ^ Td1_r((s2 >> 16U) & m8) ^ Td2_r((s1 >> 8U) & m8) ^ Td3_r(s0 & m8) ^ rk[39];
-    s0 = Td0[t0 >> 24U] ^ Td1_r((t3 >> 16U) & m8) ^ Td2_r((t2 >> 8U) & m8) ^ Td3_r(t1 & m8) ^ rk[40];
-    s1 = Td0[t1 >> 24U] ^ Td1_r((t0 >> 16U) & m8) ^ Td2_r((t3 >> 8U) & m8) ^ Td3_r(t2 & m8) ^ rk[41];
-    s2 = Td0[t2 >> 24U] ^ Td1_r((t1 >> 16U) & m8) ^ Td2_r((t0 >> 8U) & m8) ^ Td3_r(t3 & m8) ^ rk[42];
-    s3 = Td0[t3 >> 24U] ^ Td1_r((t2 >> 16U) & m8) ^ Td2_r((t1 >> 8U) & m8) ^ Td3_r(t0 & m8) ^ rk[43];
-    t0 = Td0[s0 >> 24U] ^ Td1_r((s3 >> 16U) & m8) ^ Td2_r((s2 >> 8U) & m8) ^ Td3_r(s1 & m8) ^ rk[44];
-    t1 = Td0[s1 >> 24U] ^ Td1_r((s0 >> 16U) & m8) ^ Td2_r((s3 >> 8U) & m8) ^ Td3_r(s2 & m8) ^ rk[45];
-    t2 = Td0[s2 >> 24U] ^ Td1_r((s1 >> 16U) & m8) ^ Td2_r((s0 >> 8U) & m8) ^ Td3_r(s3 & m8) ^ rk[46];
-    t3 = Td0[s3 >> 24U] ^ Td1_r((s2 >> 16U) & m8) ^ Td2_r((s1 >> 8U) & m8) ^ Td3_r(s0 & m8) ^ rk[47];
-    s0 = Td0[t0 >> 24U] ^ Td1_r((t3 >> 16U) & m8) ^ Td2_r((t2 >> 8U) & m8) ^ Td3_r(t1 & m8) ^ rk[48];
-    s1 = Td0[t1 >> 24U] ^ Td1_r((t0 >> 16U) & m8) ^ Td2_r((t3 >> 8U) & m8) ^ Td3_r(t2 & m8) ^ rk[49];
-    s2 = Td0[t2 >> 24U] ^ Td1_r((t1 >> 16U) & m8) ^ Td2_r((t0 >> 8U) & m8) ^ Td3_r(t3 & m8) ^ rk[50];
-    s3 = Td0[t3 >> 24U] ^ Td1_r((t2 >> 16U) & m8) ^ Td2_r((t1 >> 8U) & m8) ^ Td3_r(t0 & m8) ^ rk[51];
-    t0 = Td0[s0 >> 24U] ^ Td1_r((s3 >> 16U) & m8) ^ Td2_r((s2 >> 8U) & m8) ^ Td3_r(s1 & m8) ^ rk[52];
-    t1 = Td0[s1 >> 24U] ^ Td1_r((s0 >> 16U) & m8) ^ Td2_r((s3 >> 8U) & m8) ^ Td3_r(s2 & m8) ^ rk[53];
-    t2 = Td0[s2 >> 24U] ^ Td1_r((s1 >> 16U) & m8) ^ Td2_r((s0 >> 8U) & m8) ^ Td3_r(s3 & m8) ^ rk[54];
-    t3 = Td0[s3 >> 24U] ^ Td1_r((s2 >> 16U) & m8) ^ Td2_r((s1 >> 8U) & m8) ^ Td3_r(s0 & m8) ^ rk[55];
-    s0 = (Td4[t0 >> 24U] << 24U) ^ (Td4[(t3 >> 16U) & m8] << 16U) ^ (Td4[(t2 >> 8U) & m8] << 8U) ^ (Td4[(t1)&m8]) ^ rk[56];
-    s1 = (Td4[t1 >> 24U] << 24U) ^ (Td4[(t0 >> 16U) & m8] << 16U) ^ (Td4[(t3 >> 8U) & m8] << 8U) ^ (Td4[(t2)&m8]) ^ rk[57];
-    s2 = (Td4[t2 >> 24U] << 24U) ^ (Td4[(t1 >> 16U) & m8] << 16U) ^ (Td4[(t0 >> 8U) & m8] << 8U) ^ (Td4[(t3)&m8]) ^ rk[58];
-    s3 = (Td4[t3 >> 24U] << 24U) ^ (Td4[(t2 >> 16U) & m8] << 16U) ^ (Td4[(t1 >> 8U) & m8] << 8U) ^ (Td4[(t0)&m8]) ^ rk[59];
-
-    Utils::storeBigEndian<uint32_t>(out, s0);
-    Utils::storeBigEndian<uint32_t>(out + 4, s1);
-    Utils::storeBigEndian<uint32_t>(out + 8, s2);
-    Utils::storeBigEndian<uint32_t>(out + 12, s3);
+	const uint32_t* restrict rk = p_k.sw.dk;
+	const uint32_t m8 = 0x000000ff;
+	uint32_t s0 = Utils::loadBigEndian<uint32_t>(in) ^ rk[0];
+	uint32_t s1 = Utils::loadBigEndian<uint32_t>(in + 4) ^ rk[1];
+	uint32_t s2 = Utils::loadBigEndian<uint32_t>(in + 8) ^ rk[2];
+	uint32_t s3 = Utils::loadBigEndian<uint32_t>(in + 12) ^ rk[3];
+
+	uint32_t t0, t1, t2, t3;
+	t0 = Td0[s0 >> 24U] ^ Td1_r((s3 >> 16U) & m8) ^ Td2_r((s2 >> 8U) & m8) ^ Td3_r(s1 & m8) ^ rk[4];
+	t1 = Td0[s1 >> 24U] ^ Td1_r((s0 >> 16U) & m8) ^ Td2_r((s3 >> 8U) & m8) ^ Td3_r(s2 & m8) ^ rk[5];
+	t2 = Td0[s2 >> 24U] ^ Td1_r((s1 >> 16U) & m8) ^ Td2_r((s0 >> 8U) & m8) ^ Td3_r(s3 & m8) ^ rk[6];
+	t3 = Td0[s3 >> 24U] ^ Td1_r((s2 >> 16U) & m8) ^ Td2_r((s1 >> 8U) & m8) ^ Td3_r(s0 & m8) ^ rk[7];
+	s0 = Td0[t0 >> 24U] ^ Td1_r((t3 >> 16U) & m8) ^ Td2_r((t2 >> 8U) & m8) ^ Td3_r(t1 & m8) ^ rk[8];
+	s1 = Td0[t1 >> 24U] ^ Td1_r((t0 >> 16U) & m8) ^ Td2_r((t3 >> 8U) & m8) ^ Td3_r(t2 & m8) ^ rk[9];
+	s2 = Td0[t2 >> 24U] ^ Td1_r((t1 >> 16U) & m8) ^ Td2_r((t0 >> 8U) & m8) ^ Td3_r(t3 & m8) ^ rk[10];
+	s3 = Td0[t3 >> 24U] ^ Td1_r((t2 >> 16U) & m8) ^ Td2_r((t1 >> 8U) & m8) ^ Td3_r(t0 & m8) ^ rk[11];
+	t0 = Td0[s0 >> 24U] ^ Td1_r((s3 >> 16U) & m8) ^ Td2_r((s2 >> 8U) & m8) ^ Td3_r(s1 & m8) ^ rk[12];
+	t1 = Td0[s1 >> 24U] ^ Td1_r((s0 >> 16U) & m8) ^ Td2_r((s3 >> 8U) & m8) ^ Td3_r(s2 & m8) ^ rk[13];
+	t2 = Td0[s2 >> 24U] ^ Td1_r((s1 >> 16U) & m8) ^ Td2_r((s0 >> 8U) & m8) ^ Td3_r(s3 & m8) ^ rk[14];
+	t3 = Td0[s3 >> 24U] ^ Td1_r((s2 >> 16U) & m8) ^ Td2_r((s1 >> 8U) & m8) ^ Td3_r(s0 & m8) ^ rk[15];
+	s0 = Td0[t0 >> 24U] ^ Td1_r((t3 >> 16U) & m8) ^ Td2_r((t2 >> 8U) & m8) ^ Td3_r(t1 & m8) ^ rk[16];
+	s1 = Td0[t1 >> 24U] ^ Td1_r((t0 >> 16U) & m8) ^ Td2_r((t3 >> 8U) & m8) ^ Td3_r(t2 & m8) ^ rk[17];
+	s2 = Td0[t2 >> 24U] ^ Td1_r((t1 >> 16U) & m8) ^ Td2_r((t0 >> 8U) & m8) ^ Td3_r(t3 & m8) ^ rk[18];
+	s3 = Td0[t3 >> 24U] ^ Td1_r((t2 >> 16U) & m8) ^ Td2_r((t1 >> 8U) & m8) ^ Td3_r(t0 & m8) ^ rk[19];
+	t0 = Td0[s0 >> 24U] ^ Td1_r((s3 >> 16U) & m8) ^ Td2_r((s2 >> 8U) & m8) ^ Td3_r(s1 & m8) ^ rk[20];
+	t1 = Td0[s1 >> 24U] ^ Td1_r((s0 >> 16U) & m8) ^ Td2_r((s3 >> 8U) & m8) ^ Td3_r(s2 & m8) ^ rk[21];
+	t2 = Td0[s2 >> 24U] ^ Td1_r((s1 >> 16U) & m8) ^ Td2_r((s0 >> 8U) & m8) ^ Td3_r(s3 & m8) ^ rk[22];
+	t3 = Td0[s3 >> 24U] ^ Td1_r((s2 >> 16U) & m8) ^ Td2_r((s1 >> 8U) & m8) ^ Td3_r(s0 & m8) ^ rk[23];
+	s0 = Td0[t0 >> 24U] ^ Td1_r((t3 >> 16U) & m8) ^ Td2_r((t2 >> 8U) & m8) ^ Td3_r(t1 & m8) ^ rk[24];
+	s1 = Td0[t1 >> 24U] ^ Td1_r((t0 >> 16U) & m8) ^ Td2_r((t3 >> 8U) & m8) ^ Td3_r(t2 & m8) ^ rk[25];
+	s2 = Td0[t2 >> 24U] ^ Td1_r((t1 >> 16U) & m8) ^ Td2_r((t0 >> 8U) & m8) ^ Td3_r(t3 & m8) ^ rk[26];
+	s3 = Td0[t3 >> 24U] ^ Td1_r((t2 >> 16U) & m8) ^ Td2_r((t1 >> 8U) & m8) ^ Td3_r(t0 & m8) ^ rk[27];
+	t0 = Td0[s0 >> 24U] ^ Td1_r((s3 >> 16U) & m8) ^ Td2_r((s2 >> 8U) & m8) ^ Td3_r(s1 & m8) ^ rk[28];
+	t1 = Td0[s1 >> 24U] ^ Td1_r((s0 >> 16U) & m8) ^ Td2_r((s3 >> 8U) & m8) ^ Td3_r(s2 & m8) ^ rk[29];
+	t2 = Td0[s2 >> 24U] ^ Td1_r((s1 >> 16U) & m8) ^ Td2_r((s0 >> 8U) & m8) ^ Td3_r(s3 & m8) ^ rk[30];
+	t3 = Td0[s3 >> 24U] ^ Td1_r((s2 >> 16U) & m8) ^ Td2_r((s1 >> 8U) & m8) ^ Td3_r(s0 & m8) ^ rk[31];
+	s0 = Td0[t0 >> 24U] ^ Td1_r((t3 >> 16U) & m8) ^ Td2_r((t2 >> 8U) & m8) ^ Td3_r(t1 & m8) ^ rk[32];
+	s1 = Td0[t1 >> 24U] ^ Td1_r((t0 >> 16U) & m8) ^ Td2_r((t3 >> 8U) & m8) ^ Td3_r(t2 & m8) ^ rk[33];
+	s2 = Td0[t2 >> 24U] ^ Td1_r((t1 >> 16U) & m8) ^ Td2_r((t0 >> 8U) & m8) ^ Td3_r(t3 & m8) ^ rk[34];
+	s3 = Td0[t3 >> 24U] ^ Td1_r((t2 >> 16U) & m8) ^ Td2_r((t1 >> 8U) & m8) ^ Td3_r(t0 & m8) ^ rk[35];
+	t0 = Td0[s0 >> 24U] ^ Td1_r((s3 >> 16U) & m8) ^ Td2_r((s2 >> 8U) & m8) ^ Td3_r(s1 & m8) ^ rk[36];
+	t1 = Td0[s1 >> 24U] ^ Td1_r((s0 >> 16U) & m8) ^ Td2_r((s3 >> 8U) & m8) ^ Td3_r(s2 & m8) ^ rk[37];
+	t2 = Td0[s2 >> 24U] ^ Td1_r((s1 >> 16U) & m8) ^ Td2_r((s0 >> 8U) & m8) ^ Td3_r(s3 & m8) ^ rk[38];
+	t3 = Td0[s3 >> 24U] ^ Td1_r((s2 >> 16U) & m8) ^ Td2_r((s1 >> 8U) & m8) ^ Td3_r(s0 & m8) ^ rk[39];
+	s0 = Td0[t0 >> 24U] ^ Td1_r((t3 >> 16U) & m8) ^ Td2_r((t2 >> 8U) & m8) ^ Td3_r(t1 & m8) ^ rk[40];
+	s1 = Td0[t1 >> 24U] ^ Td1_r((t0 >> 16U) & m8) ^ Td2_r((t3 >> 8U) & m8) ^ Td3_r(t2 & m8) ^ rk[41];
+	s2 = Td0[t2 >> 24U] ^ Td1_r((t1 >> 16U) & m8) ^ Td2_r((t0 >> 8U) & m8) ^ Td3_r(t3 & m8) ^ rk[42];
+	s3 = Td0[t3 >> 24U] ^ Td1_r((t2 >> 16U) & m8) ^ Td2_r((t1 >> 8U) & m8) ^ Td3_r(t0 & m8) ^ rk[43];
+	t0 = Td0[s0 >> 24U] ^ Td1_r((s3 >> 16U) & m8) ^ Td2_r((s2 >> 8U) & m8) ^ Td3_r(s1 & m8) ^ rk[44];
+	t1 = Td0[s1 >> 24U] ^ Td1_r((s0 >> 16U) & m8) ^ Td2_r((s3 >> 8U) & m8) ^ Td3_r(s2 & m8) ^ rk[45];
+	t2 = Td0[s2 >> 24U] ^ Td1_r((s1 >> 16U) & m8) ^ Td2_r((s0 >> 8U) & m8) ^ Td3_r(s3 & m8) ^ rk[46];
+	t3 = Td0[s3 >> 24U] ^ Td1_r((s2 >> 16U) & m8) ^ Td2_r((s1 >> 8U) & m8) ^ Td3_r(s0 & m8) ^ rk[47];
+	s0 = Td0[t0 >> 24U] ^ Td1_r((t3 >> 16U) & m8) ^ Td2_r((t2 >> 8U) & m8) ^ Td3_r(t1 & m8) ^ rk[48];
+	s1 = Td0[t1 >> 24U] ^ Td1_r((t0 >> 16U) & m8) ^ Td2_r((t3 >> 8U) & m8) ^ Td3_r(t2 & m8) ^ rk[49];
+	s2 = Td0[t2 >> 24U] ^ Td1_r((t1 >> 16U) & m8) ^ Td2_r((t0 >> 8U) & m8) ^ Td3_r(t3 & m8) ^ rk[50];
+	s3 = Td0[t3 >> 24U] ^ Td1_r((t2 >> 16U) & m8) ^ Td2_r((t1 >> 8U) & m8) ^ Td3_r(t0 & m8) ^ rk[51];
+	t0 = Td0[s0 >> 24U] ^ Td1_r((s3 >> 16U) & m8) ^ Td2_r((s2 >> 8U) & m8) ^ Td3_r(s1 & m8) ^ rk[52];
+	t1 = Td0[s1 >> 24U] ^ Td1_r((s0 >> 16U) & m8) ^ Td2_r((s3 >> 8U) & m8) ^ Td3_r(s2 & m8) ^ rk[53];
+	t2 = Td0[s2 >> 24U] ^ Td1_r((s1 >> 16U) & m8) ^ Td2_r((s0 >> 8U) & m8) ^ Td3_r(s3 & m8) ^ rk[54];
+	t3 = Td0[s3 >> 24U] ^ Td1_r((s2 >> 16U) & m8) ^ Td2_r((s1 >> 8U) & m8) ^ Td3_r(s0 & m8) ^ rk[55];
+	s0 = (Td4[t0 >> 24U] << 24U) ^ (Td4[(t3 >> 16U) & m8] << 16U) ^ (Td4[(t2 >> 8U) & m8] << 8U) ^ (Td4[(t1)&m8]) ^ rk[56];
+	s1 = (Td4[t1 >> 24U] << 24U) ^ (Td4[(t0 >> 16U) & m8] << 16U) ^ (Td4[(t3 >> 8U) & m8] << 8U) ^ (Td4[(t2)&m8]) ^ rk[57];
+	s2 = (Td4[t2 >> 24U] << 24U) ^ (Td4[(t1 >> 16U) & m8] << 16U) ^ (Td4[(t0 >> 8U) & m8] << 8U) ^ (Td4[(t3)&m8]) ^ rk[58];
+	s3 = (Td4[t3 >> 24U] << 24U) ^ (Td4[(t2 >> 16U) & m8] << 16U) ^ (Td4[(t1 >> 8U) & m8] << 8U) ^ (Td4[(t0)&m8]) ^ rk[59];
+
+	Utils::storeBigEndian<uint32_t>(out, s0);
+	Utils::storeBigEndian<uint32_t>(out + 4, s1);
+	Utils::storeBigEndian<uint32_t>(out + 8, s2);
+	Utils::storeBigEndian<uint32_t>(out + 12, s3);
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 503 - 503
node/AES.hpp

@@ -42,560 +42,560 @@ namespace ZeroTier {
  */
 class AES {
   public:
-    /**
-     * @return True if this system has hardware AES acceleration
-     */
-    static ZT_INLINE bool accelerated()
-    {
+	/**
+	 * @return True if this system has hardware AES acceleration
+	 */
+	static ZT_INLINE bool accelerated()
+	{
 #ifdef ZT_AES_AESNI
-        return Utils::CPUID.aes;
+		return Utils::CPUID.aes;
 #else
 #ifdef ZT_AES_NEON
-        return Utils::ARMCAP.aes;
+		return Utils::ARMCAP.aes;
 #else
-        return false;
+		return false;
 #endif
 #endif
-    }
-
-    /**
-     * Create an un-initialized AES instance (must call init() before use)
-     */
-    ZT_INLINE AES() noexcept
-    {
-    }
-
-    /**
-     * Create an AES instance with the given key
-     *
-     * @param key 256-bit key
-     */
-    explicit ZT_INLINE AES(const void* const key) noexcept
-    {
-        this->init(key);
-    }
-
-    ZT_INLINE ~AES()
-    {
-        Utils::burn(&p_k, sizeof(p_k));
-    }
-
-    /**
-     * Set (or re-set) this AES256 cipher's key
-     *
-     * @param key 256-bit / 32-byte key
-     */
-    ZT_INLINE void init(const void* const key) noexcept
-    {
+	}
+
+	/**
+	 * Create an un-initialized AES instance (must call init() before use)
+	 */
+	ZT_INLINE AES() noexcept
+	{
+	}
+
+	/**
+	 * Create an AES instance with the given key
+	 *
+	 * @param key 256-bit key
+	 */
+	explicit ZT_INLINE AES(const void* const key) noexcept
+	{
+		this->init(key);
+	}
+
+	ZT_INLINE ~AES()
+	{
+		Utils::burn(&p_k, sizeof(p_k));
+	}
+
+	/**
+	 * Set (or re-set) this AES256 cipher's key
+	 *
+	 * @param key 256-bit / 32-byte key
+	 */
+	ZT_INLINE void init(const void* const key) noexcept
+	{
 #ifdef ZT_AES_AESNI
-        if (likely(Utils::CPUID.aes)) {
-            p_init_aesni(reinterpret_cast<const uint8_t*>(key));
-            return;
-        }
+		if (likely(Utils::CPUID.aes)) {
+			p_init_aesni(reinterpret_cast<const uint8_t*>(key));
+			return;
+		}
 #endif
 #ifdef ZT_AES_NEON
-        if (Utils::ARMCAP.aes) {
-            p_init_armneon_crypto(reinterpret_cast<const uint8_t*>(key));
-            return;
-        }
+		if (Utils::ARMCAP.aes) {
+			p_init_armneon_crypto(reinterpret_cast<const uint8_t*>(key));
+			return;
+		}
 #endif
-        p_initSW(reinterpret_cast<const uint8_t*>(key));
-    }
-
-    /**
-     * Encrypt a single AES block
-     *
-     * @param in Input block
-     * @param out Output block (can be same as input)
-     */
-    ZT_INLINE void encrypt(const void* const in, void* const out) const noexcept
-    {
+		p_initSW(reinterpret_cast<const uint8_t*>(key));
+	}
+
+	/**
+	 * Encrypt a single AES block
+	 *
+	 * @param in Input block
+	 * @param out Output block (can be same as input)
+	 */
+	ZT_INLINE void encrypt(const void* const in, void* const out) const noexcept
+	{
 #ifdef ZT_AES_AESNI
-        if (likely(Utils::CPUID.aes)) {
-            p_encrypt_aesni(in, out);
-            return;
-        }
+		if (likely(Utils::CPUID.aes)) {
+			p_encrypt_aesni(in, out);
+			return;
+		}
 #endif
 #ifdef ZT_AES_NEON
-        if (Utils::ARMCAP.aes) {
-            p_encrypt_armneon_crypto(in, out);
-            return;
-        }
+		if (Utils::ARMCAP.aes) {
+			p_encrypt_armneon_crypto(in, out);
+			return;
+		}
 #endif
-        p_encryptSW(reinterpret_cast<const uint8_t*>(in), reinterpret_cast<uint8_t*>(out));
-    }
-
-    /**
-     * Decrypt a single AES block
-     *
-     * @param in Input block
-     * @param out Output block (can be same as input)
-     */
-    ZT_INLINE void decrypt(const void* const in, void* const out) const noexcept
-    {
+		p_encryptSW(reinterpret_cast<const uint8_t*>(in), reinterpret_cast<uint8_t*>(out));
+	}
+
+	/**
+	 * Decrypt a single AES block
+	 *
+	 * @param in Input block
+	 * @param out Output block (can be same as input)
+	 */
+	ZT_INLINE void decrypt(const void* const in, void* const out) const noexcept
+	{
 #ifdef ZT_AES_AESNI
-        if (likely(Utils::CPUID.aes)) {
-            p_decrypt_aesni(in, out);
-            return;
-        }
+		if (likely(Utils::CPUID.aes)) {
+			p_decrypt_aesni(in, out);
+			return;
+		}
 #endif
 #ifdef ZT_AES_NEON
-        if (Utils::ARMCAP.aes) {
-            p_decrypt_armneon_crypto(in, out);
-            return;
-        }
+		if (Utils::ARMCAP.aes) {
+			p_decrypt_armneon_crypto(in, out);
+			return;
+		}
 #endif
-        p_decryptSW(reinterpret_cast<const uint8_t*>(in), reinterpret_cast<uint8_t*>(out));
-    }
-
-    class GMACSIVEncryptor;
-    class GMACSIVDecryptor;
-
-    /**
-     * Streaming GMAC calculator
-     */
-    class GMAC {
-        friend class GMACSIVEncryptor;
-        friend class GMACSIVDecryptor;
-
-      public:
-        /**
-         * @return True if this system has hardware GMAC acceleration
-         */
-        static ZT_INLINE bool accelerated()
-        {
+		p_decryptSW(reinterpret_cast<const uint8_t*>(in), reinterpret_cast<uint8_t*>(out));
+	}
+
+	class GMACSIVEncryptor;
+	class GMACSIVDecryptor;
+
+	/**
+	 * Streaming GMAC calculator
+	 */
+	class GMAC {
+		friend class GMACSIVEncryptor;
+		friend class GMACSIVDecryptor;
+
+	  public:
+		/**
+		 * @return True if this system has hardware GMAC acceleration
+		 */
+		static ZT_INLINE bool accelerated()
+		{
 #ifdef ZT_AES_AESNI
-            return Utils::CPUID.aes;
+			return Utils::CPUID.aes;
 #else
 #ifdef ZT_AES_NEON
-            return Utils::ARMCAP.pmull;
+			return Utils::ARMCAP.pmull;
 #else
-            return false;
+			return false;
 #endif
 #endif
-        }
-
-        /**
-         * Create a new instance of GMAC (must be initialized with init() before use)
-         *
-         * @param aes Keyed AES instance to use
-         */
-        ZT_INLINE GMAC(const AES& aes) : _aes(aes)
-        {
-        }
-
-        /**
-         * Reset and initialize for a new GMAC calculation
-         *
-         * @param iv 96-bit initialization vector (pad with zeroes if actual IV is shorter)
-         */
-        ZT_INLINE void init(const uint8_t iv[12]) noexcept
-        {
-            _rp = 0;
-            _len = 0;
-            // We fill the least significant 32 bits in the _iv field with 1 since in GCM mode
-            // this would hold the counter, but we're not doing GCM. The counter is therefore
-            // always 1.
-#ifdef ZT_AES_AESNI   // also implies an x64 processor
-            *reinterpret_cast<uint64_t*>(_iv) = *reinterpret_cast<const uint64_t*>(iv);
-            *reinterpret_cast<uint32_t*>(_iv + 8) = *reinterpret_cast<const uint64_t*>(iv + 8);
-            *reinterpret_cast<uint32_t*>(_iv + 12) = 0x01000000;   // 0x00000001 in big-endian byte order
+		}
+
+		/**
+		 * Create a new instance of GMAC (must be initialized with init() before use)
+		 *
+		 * @param aes Keyed AES instance to use
+		 */
+		ZT_INLINE GMAC(const AES& aes) : _aes(aes)
+		{
+		}
+
+		/**
+		 * Reset and initialize for a new GMAC calculation
+		 *
+		 * @param iv 96-bit initialization vector (pad with zeroes if actual IV is shorter)
+		 */
+		ZT_INLINE void init(const uint8_t iv[12]) noexcept
+		{
+			_rp = 0;
+			_len = 0;
+			// We fill the least significant 32 bits in the _iv field with 1 since in GCM mode
+			// this would hold the counter, but we're not doing GCM. The counter is therefore
+			// always 1.
+#ifdef ZT_AES_AESNI	  // also implies an x64 processor
+			*reinterpret_cast<uint64_t*>(_iv) = *reinterpret_cast<const uint64_t*>(iv);
+			*reinterpret_cast<uint32_t*>(_iv + 8) = *reinterpret_cast<const uint64_t*>(iv + 8);
+			*reinterpret_cast<uint32_t*>(_iv + 12) = 0x01000000;   // 0x00000001 in big-endian byte order
 #else
-            for (int i = 0; i < 12; ++i) {
-                _iv[i] = iv[i];
-            }
-            _iv[12] = 0;
-            _iv[13] = 0;
-            _iv[14] = 0;
-            _iv[15] = 1;
+			for (int i = 0; i < 12; ++i) {
+				_iv[i] = iv[i];
+			}
+			_iv[12] = 0;
+			_iv[13] = 0;
+			_iv[14] = 0;
+			_iv[15] = 1;
 #endif
-            _y[0] = 0;
-            _y[1] = 0;
-        }
-
-        /**
-         * Process data through GMAC
-         *
-         * @param data Bytes to process
-         * @param len Length of input
-         */
-        void update(const void* data, unsigned int len) noexcept;
-
-        /**
-         * Process any remaining cached bytes and generate tag
-         *
-         * Don't call finish() more than once or you'll get an invalid result.
-         *
-         * @param tag 128-bit GMAC tag (can be truncated)
-         */
-        void finish(uint8_t tag[16]) noexcept;
-
-      private:
+			_y[0] = 0;
+			_y[1] = 0;
+		}
+
+		/**
+		 * Process data through GMAC
+		 *
+		 * @param data Bytes to process
+		 * @param len Length of input
+		 */
+		void update(const void* data, unsigned int len) noexcept;
+
+		/**
+		 * Process any remaining cached bytes and generate tag
+		 *
+		 * Don't call finish() more than once or you'll get an invalid result.
+		 *
+		 * @param tag 128-bit GMAC tag (can be truncated)
+		 */
+		void finish(uint8_t tag[16]) noexcept;
+
+	  private:
 #ifdef ZT_AES_AESNI
-        void p_aesNIUpdate(const uint8_t* in, unsigned int len) noexcept;
-        void p_aesNIFinish(uint8_t tag[16]) noexcept;
+		void p_aesNIUpdate(const uint8_t* in, unsigned int len) noexcept;
+		void p_aesNIFinish(uint8_t tag[16]) noexcept;
 #endif
 #ifdef ZT_AES_NEON
-        void p_armUpdate(const uint8_t* in, unsigned int len) noexcept;
-        void p_armFinish(uint8_t tag[16]) noexcept;
+		void p_armUpdate(const uint8_t* in, unsigned int len) noexcept;
+		void p_armFinish(uint8_t tag[16]) noexcept;
 #endif
-        const AES& _aes;
-        unsigned int _rp;
-        unsigned int _len;
-        uint8_t _r[16];   // remainder
-        uint8_t _iv[16];
-        uint64_t _y[2];
-    };
-
-    /**
-     * Streaming AES-CTR encrypt/decrypt
-     *
-     * NOTE: this doesn't support overflow of the counter in the least significant 32 bits.
-     * AES-GMAC-CTR doesn't need this, so we don't support it as an optimization.
-     */
-    class CTR {
-        friend class GMACSIVEncryptor;
-        friend class GMACSIVDecryptor;
-
-      public:
-        ZT_INLINE CTR(const AES& aes) noexcept : _aes(aes)
-        {
-        }
-
-        /**
-         * Initialize this CTR instance to encrypt a new stream
-         *
-         * @param iv Unique initialization vector and initial 32-bit counter (least significant 32 bits, big-endian)
-         * @param output Buffer to which to store output (MUST be large enough for total bytes processed!)
-         */
-        ZT_INLINE void init(const uint8_t iv[16], void* const output) noexcept
-        {
-            Utils::copy<16>(_ctr, iv);
-            _out = reinterpret_cast<uint8_t*>(output);
-            _len = 0;
-        }
-
-        /**
-         * Initialize this CTR instance to encrypt a new stream
-         *
-         * @param iv Unique initialization vector
-         * @param ic Initial counter (must be in big-endian byte order!)
-         * @param output Buffer to which to store output (MUST be large enough for total bytes processed!)
-         */
-        ZT_INLINE void init(const uint8_t iv[12], const uint32_t ic, void* const output) noexcept
-        {
-            Utils::copy<12>(_ctr, iv);
-            reinterpret_cast<uint32_t*>(_ctr)[3] = ic;
-            _out = reinterpret_cast<uint8_t*>(output);
-            _len = 0;
-        }
-
-        /**
-         * Encrypt or decrypt data, writing result to the output provided to init()
-         *
-         * @param input Input data
-         * @param len Length of input
-         */
-        void crypt(const void* input, unsigned int len) noexcept;
-
-        /**
-         * Finish any remaining bytes if total bytes processed wasn't a multiple of 16
-         *
-         * Don't call more than once for a given stream or data may be corrupted.
-         */
-        void finish() noexcept;
-
-      private:
+		const AES& _aes;
+		unsigned int _rp;
+		unsigned int _len;
+		uint8_t _r[16];	  // remainder
+		uint8_t _iv[16];
+		uint64_t _y[2];
+	};
+
+	/**
+	 * Streaming AES-CTR encrypt/decrypt
+	 *
+	 * NOTE: this doesn't support overflow of the counter in the least significant 32 bits.
+	 * AES-GMAC-CTR doesn't need this, so we don't support it as an optimization.
+	 */
+	class CTR {
+		friend class GMACSIVEncryptor;
+		friend class GMACSIVDecryptor;
+
+	  public:
+		ZT_INLINE CTR(const AES& aes) noexcept : _aes(aes)
+		{
+		}
+
+		/**
+		 * Initialize this CTR instance to encrypt a new stream
+		 *
+		 * @param iv Unique initialization vector and initial 32-bit counter (least significant 32 bits, big-endian)
+		 * @param output Buffer to which to store output (MUST be large enough for total bytes processed!)
+		 */
+		ZT_INLINE void init(const uint8_t iv[16], void* const output) noexcept
+		{
+			Utils::copy<16>(_ctr, iv);
+			_out = reinterpret_cast<uint8_t*>(output);
+			_len = 0;
+		}
+
+		/**
+		 * Initialize this CTR instance to encrypt a new stream
+		 *
+		 * @param iv Unique initialization vector
+		 * @param ic Initial counter (must be in big-endian byte order!)
+		 * @param output Buffer to which to store output (MUST be large enough for total bytes processed!)
+		 */
+		ZT_INLINE void init(const uint8_t iv[12], const uint32_t ic, void* const output) noexcept
+		{
+			Utils::copy<12>(_ctr, iv);
+			reinterpret_cast<uint32_t*>(_ctr)[3] = ic;
+			_out = reinterpret_cast<uint8_t*>(output);
+			_len = 0;
+		}
+
+		/**
+		 * Encrypt or decrypt data, writing result to the output provided to init()
+		 *
+		 * @param input Input data
+		 * @param len Length of input
+		 */
+		void crypt(const void* input, unsigned int len) noexcept;
+
+		/**
+		 * Finish any remaining bytes if total bytes processed wasn't a multiple of 16
+		 *
+		 * Don't call more than once for a given stream or data may be corrupted.
+		 */
+		void finish() noexcept;
+
+	  private:
 #ifdef ZT_AES_AESNI
-        void p_aesNICrypt(const uint8_t* in, uint8_t* out, unsigned int len) noexcept;
+		void p_aesNICrypt(const uint8_t* in, uint8_t* out, unsigned int len) noexcept;
 #endif
 #ifdef ZT_AES_NEON
-        void p_armCrypt(const uint8_t* in, uint8_t* out, unsigned int len) noexcept;
+		void p_armCrypt(const uint8_t* in, uint8_t* out, unsigned int len) noexcept;
 #endif
-        const AES& _aes;
-        uint64_t _ctr[2];
-        uint8_t* _out;
-        unsigned int _len;
-    };
-
-    /**
-     * Encryptor for AES-GMAC-SIV.
-     *
-     * Encryption requires two passes. The first pass starts after init
-     * with aad (if any) followed by update1() and finish1(). Then the
-     * update2() and finish2() methods must be used over the same data
-     * (but NOT AAD) again.
-     *
-     * This supports encryption of a maximum of 2^31 bytes of data per
-     * call to init().
-     */
-    class GMACSIVEncryptor {
-      public:
-        /**
-         * Create a new AES-GMAC-SIV encryptor keyed with the provided AES instances
-         *
-         * @param k0 First of two AES instances keyed with K0
-         * @param k1 Second of two AES instances keyed with K1
-         */
-        ZT_INLINE GMACSIVEncryptor(const AES& k0, const AES& k1) noexcept
-            : _gmac(k0)
-            , _ctr(k1)
-        {
-        }
-
-        /**
-         * Initialize AES-GMAC-SIV
-         *
-         * @param iv IV in network byte order (byte order in which it will appear on the wire)
-         * @param output Pointer to buffer to receive ciphertext, must be large enough for all to-be-processed data!
-         */
-        ZT_INLINE void init(const uint64_t iv, void* const output) noexcept
-        {
-            // Output buffer to receive the result of AES-CTR encryption.
-            _output = output;
-
-            // Initialize GMAC with 64-bit IV (and remaining 32 bits padded to zero).
-            _tag[0] = iv;
-            _tag[1] = 0;
-            _gmac.init(reinterpret_cast<const uint8_t*>(_tag));
-        }
-
-        /**
-         * Process AAD (additional authenticated data) that is not being encrypted.
-         *
-         * If such data exists this must be called before update1() and finish1().
-         *
-         * Note: current code only supports one single chunk of AAD. Don't call this
-         * multiple times per message.
-         *
-         * @param aad Additional authenticated data
-         * @param len Length of AAD in bytes
-         */
-        ZT_INLINE void aad(const void* const aad, unsigned int len) noexcept
-        {
-            // Feed ADD into GMAC first
-            _gmac.update(aad, len);
-
-            // End of AAD is padded to a multiple of 16 bytes to ensure unique encoding.
-            len &= 0xfU;
-            if (len != 0) {
-                _gmac.update(Utils::ZERO256, 16 - len);
-            }
-        }
-
-        /**
-         * First pass plaintext input function
-         *
-         * @param input Plaintext chunk
-         * @param len Length of plaintext chunk
-         */
-        ZT_INLINE void update1(const void* const input, const unsigned int len) noexcept
-        {
-            _gmac.update(input, len);
-        }
-
-        /**
-         * Finish first pass, compute CTR IV, initialize second pass.
-         */
-        ZT_INLINE void finish1() noexcept
-        {
-            // Compute 128-bit GMAC tag.
-            uint64_t tmp[2];
-            _gmac.finish(reinterpret_cast<uint8_t*>(tmp));
-
-            // Shorten to 64 bits, concatenate with message IV, and encrypt with AES to
-            // yield the CTR IV and opaque IV/MAC blob. In ZeroTier's use of GMAC-SIV
-            // this get split into the packet ID (64 bits) and the MAC (64 bits) in each
-            // packet and then recombined on receipt for legacy reasons (but with no
-            // cryptographic or performance impact).
-            _tag[1] = tmp[0] ^ tmp[1];
-            _ctr._aes.encrypt(_tag, _tag);
-
-            // Initialize CTR with 96-bit CTR nonce and 32-bit counter. The counter
-            // incorporates 31 more bits of entropy which should raise our security margin
-            // a bit, but this is not included in the worst case analysis of GMAC-SIV.
-            // The most significant bit of the counter is masked to zero to allow up to
-            // 2^31 bytes to be encrypted before the counter loops. Some CTR implementations
-            // increment the whole big-endian 128-bit integer in which case this could be
-            // used for more than 2^31 bytes, but ours does not for performance reasons
-            // and so 2^31 should be considered the input limit.
-            tmp[0] = _tag[0];
-            tmp[1] = _tag[1] & ZT_CONST_TO_BE_UINT64(0xffffffff7fffffffULL);
-            _ctr.init(reinterpret_cast<const uint8_t*>(tmp), _output);
-        }
-
-        /**
-         * Second pass plaintext input function
-         *
-         * The same plaintext must be fed in the second time in the same order,
-         * though chunk boundaries do not have to be the same.
-         *
-         * @param input Plaintext chunk
-         * @param len Length of plaintext chunk
-         */
-        ZT_INLINE void update2(const void* const input, const unsigned int len) noexcept
-        {
-            _ctr.crypt(input, len);
-        }
-
-        /**
-         * Finish second pass and return a pointer to the opaque 128-bit IV+MAC block
-         *
-         * The returned pointer remains valid as long as this object exists and init()
-         * is not called again.
-         *
-         * @return Pointer to 128-bit opaque IV+MAC (packed into two 64-bit integers)
-         */
-        ZT_INLINE const uint64_t* finish2()
-        {
-            _ctr.finish();
-            return _tag;
-        }
-
-      private:
-        void* _output;
-        uint64_t _tag[2];
-        AES::GMAC _gmac;
-        AES::CTR _ctr;
-    };
-
-    /**
-     * Decryptor for AES-GMAC-SIV.
-     *
-     * GMAC-SIV decryption is single-pass. AAD (if any) must be processed first.
-     */
-    class GMACSIVDecryptor {
-      public:
-        ZT_INLINE GMACSIVDecryptor(const AES& k0, const AES& k1) noexcept
-            : _ctr(k1)
-            , _gmac(k0)
-        {
-        }
-
-        /**
-         * Initialize decryptor for a new message
-         *
-         * @param tag 128-bit combined IV/MAC originally created by GMAC-SIV encryption
-         * @param output Buffer in which to write output plaintext (must be large enough!)
-         */
-        ZT_INLINE void init(const uint64_t tag[2], void* const output) noexcept
-        {
-            uint64_t tmp[2];
-            tmp[0] = tag[0];
-            tmp[1] = tag[1] & ZT_CONST_TO_BE_UINT64(0xffffffff7fffffffULL);
-            _ctr.init(reinterpret_cast<const uint8_t*>(tmp), output);
-
-            _ctr._aes.decrypt(tag, _ivMac);
-
-            tmp[0] = _ivMac[0];
-            tmp[1] = 0;
-            _gmac.init(reinterpret_cast<const uint8_t*>(tmp));
-
-            _output = output;
-            _decryptedLen = 0;
-        }
-
-        /**
-         * Process AAD (additional authenticated data) that wasn't encrypted
-         *
-         * @param aad Additional authenticated data
-         * @param len Length of AAD in bytes
-         */
-        ZT_INLINE void aad(const void* const aad, unsigned int len) noexcept
-        {
-            _gmac.update(aad, len);
-            len &= 0xfU;
-            if (len != 0) {
-                _gmac.update(Utils::ZERO256, 16 - len);
-            }
-        }
-
-        /**
-         * Feed ciphertext into the decryptor
-         *
-         * Unlike encryption, GMAC-SIV decryption requires only one pass.
-         *
-         * @param input Input ciphertext
-         * @param len Length of ciphertext
-         */
-        ZT_INLINE void update(const void* const input, const unsigned int len) noexcept
-        {
-            _ctr.crypt(input, len);
-            _decryptedLen += len;
-        }
-
-        /**
-         * Flush decryption, compute MAC, and verify
-         *
-         * @return True if resulting plaintext (and AAD) pass message authentication check
-         */
-        ZT_INLINE bool finish() noexcept
-        {
-            _ctr.finish();
-
-            uint64_t gmacTag[2];
-            _gmac.update(_output, _decryptedLen);
-            _gmac.finish(reinterpret_cast<uint8_t*>(gmacTag));
-            return (gmacTag[0] ^ gmacTag[1]) == _ivMac[1];
-        }
-
-      private:
-        uint64_t _ivMac[2];
-        AES::CTR _ctr;
-        AES::GMAC _gmac;
-        void* _output;
-        unsigned int _decryptedLen;
-    };
+		const AES& _aes;
+		uint64_t _ctr[2];
+		uint8_t* _out;
+		unsigned int _len;
+	};
+
+	/**
+	 * Encryptor for AES-GMAC-SIV.
+	 *
+	 * Encryption requires two passes. The first pass starts after init
+	 * with aad (if any) followed by update1() and finish1(). Then the
+	 * update2() and finish2() methods must be used over the same data
+	 * (but NOT AAD) again.
+	 *
+	 * This supports encryption of a maximum of 2^31 bytes of data per
+	 * call to init().
+	 */
+	class GMACSIVEncryptor {
+	  public:
+		/**
+		 * Create a new AES-GMAC-SIV encryptor keyed with the provided AES instances
+		 *
+		 * @param k0 First of two AES instances keyed with K0
+		 * @param k1 Second of two AES instances keyed with K1
+		 */
+		ZT_INLINE GMACSIVEncryptor(const AES& k0, const AES& k1) noexcept
+			: _gmac(k0)
+			, _ctr(k1)
+		{
+		}
+
+		/**
+		 * Initialize AES-GMAC-SIV
+		 *
+		 * @param iv IV in network byte order (byte order in which it will appear on the wire)
+		 * @param output Pointer to buffer to receive ciphertext, must be large enough for all to-be-processed data!
+		 */
+		ZT_INLINE void init(const uint64_t iv, void* const output) noexcept
+		{
+			// Output buffer to receive the result of AES-CTR encryption.
+			_output = output;
+
+			// Initialize GMAC with 64-bit IV (and remaining 32 bits padded to zero).
+			_tag[0] = iv;
+			_tag[1] = 0;
+			_gmac.init(reinterpret_cast<const uint8_t*>(_tag));
+		}
+
+		/**
+		 * Process AAD (additional authenticated data) that is not being encrypted.
+		 *
+		 * If such data exists this must be called before update1() and finish1().
+		 *
+		 * Note: current code only supports one single chunk of AAD. Don't call this
+		 * multiple times per message.
+		 *
+		 * @param aad Additional authenticated data
+		 * @param len Length of AAD in bytes
+		 */
+		ZT_INLINE void aad(const void* const aad, unsigned int len) noexcept
+		{
+			// Feed ADD into GMAC first
+			_gmac.update(aad, len);
+
+			// End of AAD is padded to a multiple of 16 bytes to ensure unique encoding.
+			len &= 0xfU;
+			if (len != 0) {
+				_gmac.update(Utils::ZERO256, 16 - len);
+			}
+		}
+
+		/**
+		 * First pass plaintext input function
+		 *
+		 * @param input Plaintext chunk
+		 * @param len Length of plaintext chunk
+		 */
+		ZT_INLINE void update1(const void* const input, const unsigned int len) noexcept
+		{
+			_gmac.update(input, len);
+		}
+
+		/**
+		 * Finish first pass, compute CTR IV, initialize second pass.
+		 */
+		ZT_INLINE void finish1() noexcept
+		{
+			// Compute 128-bit GMAC tag.
+			uint64_t tmp[2];
+			_gmac.finish(reinterpret_cast<uint8_t*>(tmp));
+
+			// Shorten to 64 bits, concatenate with message IV, and encrypt with AES to
+			// yield the CTR IV and opaque IV/MAC blob. In ZeroTier's use of GMAC-SIV
+			// this get split into the packet ID (64 bits) and the MAC (64 bits) in each
+			// packet and then recombined on receipt for legacy reasons (but with no
+			// cryptographic or performance impact).
+			_tag[1] = tmp[0] ^ tmp[1];
+			_ctr._aes.encrypt(_tag, _tag);
+
+			// Initialize CTR with 96-bit CTR nonce and 32-bit counter. The counter
+			// incorporates 31 more bits of entropy which should raise our security margin
+			// a bit, but this is not included in the worst case analysis of GMAC-SIV.
+			// The most significant bit of the counter is masked to zero to allow up to
+			// 2^31 bytes to be encrypted before the counter loops. Some CTR implementations
+			// increment the whole big-endian 128-bit integer in which case this could be
+			// used for more than 2^31 bytes, but ours does not for performance reasons
+			// and so 2^31 should be considered the input limit.
+			tmp[0] = _tag[0];
+			tmp[1] = _tag[1] & ZT_CONST_TO_BE_UINT64(0xffffffff7fffffffULL);
+			_ctr.init(reinterpret_cast<const uint8_t*>(tmp), _output);
+		}
+
+		/**
+		 * Second pass plaintext input function
+		 *
+		 * The same plaintext must be fed in the second time in the same order,
+		 * though chunk boundaries do not have to be the same.
+		 *
+		 * @param input Plaintext chunk
+		 * @param len Length of plaintext chunk
+		 */
+		ZT_INLINE void update2(const void* const input, const unsigned int len) noexcept
+		{
+			_ctr.crypt(input, len);
+		}
+
+		/**
+		 * Finish second pass and return a pointer to the opaque 128-bit IV+MAC block
+		 *
+		 * The returned pointer remains valid as long as this object exists and init()
+		 * is not called again.
+		 *
+		 * @return Pointer to 128-bit opaque IV+MAC (packed into two 64-bit integers)
+		 */
+		ZT_INLINE const uint64_t* finish2()
+		{
+			_ctr.finish();
+			return _tag;
+		}
+
+	  private:
+		void* _output;
+		uint64_t _tag[2];
+		AES::GMAC _gmac;
+		AES::CTR _ctr;
+	};
+
+	/**
+	 * Decryptor for AES-GMAC-SIV.
+	 *
+	 * GMAC-SIV decryption is single-pass. AAD (if any) must be processed first.
+	 */
+	class GMACSIVDecryptor {
+	  public:
+		ZT_INLINE GMACSIVDecryptor(const AES& k0, const AES& k1) noexcept
+			: _ctr(k1)
+			, _gmac(k0)
+		{
+		}
+
+		/**
+		 * Initialize decryptor for a new message
+		 *
+		 * @param tag 128-bit combined IV/MAC originally created by GMAC-SIV encryption
+		 * @param output Buffer in which to write output plaintext (must be large enough!)
+		 */
+		ZT_INLINE void init(const uint64_t tag[2], void* const output) noexcept
+		{
+			uint64_t tmp[2];
+			tmp[0] = tag[0];
+			tmp[1] = tag[1] & ZT_CONST_TO_BE_UINT64(0xffffffff7fffffffULL);
+			_ctr.init(reinterpret_cast<const uint8_t*>(tmp), output);
+
+			_ctr._aes.decrypt(tag, _ivMac);
+
+			tmp[0] = _ivMac[0];
+			tmp[1] = 0;
+			_gmac.init(reinterpret_cast<const uint8_t*>(tmp));
+
+			_output = output;
+			_decryptedLen = 0;
+		}
+
+		/**
+		 * Process AAD (additional authenticated data) that wasn't encrypted
+		 *
+		 * @param aad Additional authenticated data
+		 * @param len Length of AAD in bytes
+		 */
+		ZT_INLINE void aad(const void* const aad, unsigned int len) noexcept
+		{
+			_gmac.update(aad, len);
+			len &= 0xfU;
+			if (len != 0) {
+				_gmac.update(Utils::ZERO256, 16 - len);
+			}
+		}
+
+		/**
+		 * Feed ciphertext into the decryptor
+		 *
+		 * Unlike encryption, GMAC-SIV decryption requires only one pass.
+		 *
+		 * @param input Input ciphertext
+		 * @param len Length of ciphertext
+		 */
+		ZT_INLINE void update(const void* const input, const unsigned int len) noexcept
+		{
+			_ctr.crypt(input, len);
+			_decryptedLen += len;
+		}
+
+		/**
+		 * Flush decryption, compute MAC, and verify
+		 *
+		 * @return True if resulting plaintext (and AAD) pass message authentication check
+		 */
+		ZT_INLINE bool finish() noexcept
+		{
+			_ctr.finish();
+
+			uint64_t gmacTag[2];
+			_gmac.update(_output, _decryptedLen);
+			_gmac.finish(reinterpret_cast<uint8_t*>(gmacTag));
+			return (gmacTag[0] ^ gmacTag[1]) == _ivMac[1];
+		}
+
+	  private:
+		uint64_t _ivMac[2];
+		AES::CTR _ctr;
+		AES::GMAC _gmac;
+		void* _output;
+		unsigned int _decryptedLen;
+	};
 
   private:
-    static const uint32_t Te0[256];
-    static const uint32_t Te4[256];
-    static const uint32_t Td0[256];
-    static const uint8_t Td4[256];
-    static const uint32_t rcon[15];
+	static const uint32_t Te0[256];
+	static const uint32_t Te4[256];
+	static const uint32_t Td0[256];
+	static const uint8_t Td4[256];
+	static const uint32_t rcon[15];
 
-    void p_initSW(const uint8_t* key) noexcept;
-    void p_encryptSW(const uint8_t* in, uint8_t* out) const noexcept;
-    void p_decryptSW(const uint8_t* in, uint8_t* out) const noexcept;
+	void p_initSW(const uint8_t* key) noexcept;
+	void p_encryptSW(const uint8_t* in, uint8_t* out) const noexcept;
+	void p_decryptSW(const uint8_t* in, uint8_t* out) const noexcept;
 
-    union {
+	union {
 #ifdef ZT_AES_AESNI
-        struct {
-            __m128i k[28];
-            __m128i h[4];    // h, hh, hhh, hhhh
-            __m128i h2[4];   // _mm_xor_si128(_mm_shuffle_epi32(h, 78), h), etc.
-        } ni;
+		struct {
+			__m128i k[28];
+			__m128i h[4];	 // h, hh, hhh, hhhh
+			__m128i h2[4];	 // _mm_xor_si128(_mm_shuffle_epi32(h, 78), h), etc.
+		} ni;
 #endif
 
 #ifdef ZT_AES_NEON
-        struct {
-            uint64_t hsw[2];   // in case it has AES but not PMULL, not sure if that ever happens
-            uint8x16_t ek[15];
-            uint8x16_t dk[15];
-            uint8x16_t h;
-        } neon;
+		struct {
+			uint64_t hsw[2];   // in case it has AES but not PMULL, not sure if that ever happens
+			uint8x16_t ek[15];
+			uint8x16_t dk[15];
+			uint8x16_t h;
+		} neon;
 #endif
 
-        struct {
-            uint64_t h[2];
-            uint32_t ek[60];
-            uint32_t dk[60];
-        } sw;
-    } p_k;
+		struct {
+			uint64_t h[2];
+			uint32_t ek[60];
+			uint32_t dk[60];
+		} sw;
+	} p_k;
 
 #ifdef ZT_AES_AESNI
-    void p_init_aesni(const uint8_t* key) noexcept;
-    void p_encrypt_aesni(const void* in, void* out) const noexcept;
-    void p_decrypt_aesni(const void* in, void* out) const noexcept;
+	void p_init_aesni(const uint8_t* key) noexcept;
+	void p_encrypt_aesni(const void* in, void* out) const noexcept;
+	void p_decrypt_aesni(const void* in, void* out) const noexcept;
 #endif
 
 #ifdef ZT_AES_NEON
-    void p_init_armneon_crypto(const uint8_t* key) noexcept;
-    void p_encrypt_armneon_crypto(const void* in, void* out) const noexcept;
-    void p_decrypt_armneon_crypto(const void* in, void* out) const noexcept;
+	void p_init_armneon_crypto(const uint8_t* key) noexcept;
+	void p_encrypt_armneon_crypto(const void* in, void* out) const noexcept;
+	void p_decrypt_armneon_crypto(const void* in, void* out) const noexcept;
 #endif
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

File diff suppressed because it is too large
+ 511 - 511
node/AES_aesni.cpp


+ 341 - 341
node/AES_armcrypto.cpp

@@ -22,379 +22,379 @@ namespace {
 
 ZT_INLINE uint8x16_t s_clmul_armneon_crypto(uint8x16_t h, uint8x16_t y, const uint8_t b[16]) noexcept
 {
-    uint8x16_t r0, r1, t0, t1;
-    r0 = vld1q_u8(b);
-    const uint8x16_t z = veorq_u8(h, h);
-    y = veorq_u8(r0, y);
-    y = vrbitq_u8(y);
-    const uint8x16_t p = vreinterpretq_u8_u64(vdupq_n_u64(0x0000000000000087));
-    t0 = vextq_u8(y, y, 8);
-    __asm__ __volatile__("pmull     %0.1q, %1.1d, %2.1d \n\t" : "=w"(r0) : "w"(h), "w"(y));
-    __asm__ __volatile__("pmull2   %0.1q, %1.2d, %2.2d \n\t" : "=w"(r1) : "w"(h), "w"(y));
-    __asm__ __volatile__("pmull     %0.1q, %1.1d, %2.1d \n\t" : "=w"(t1) : "w"(h), "w"(t0));
-    __asm__ __volatile__("pmull2   %0.1q, %1.2d, %2.2d \n\t" : "=w"(t0) : "w"(h), "w"(t0));
-    t0 = veorq_u8(t0, t1);
-    t1 = vextq_u8(z, t0, 8);
-    r0 = veorq_u8(r0, t1);
-    t1 = vextq_u8(t0, z, 8);
-    r1 = veorq_u8(r1, t1);
-    __asm__ __volatile__("pmull2   %0.1q, %1.2d, %2.2d \n\t" : "=w"(t0) : "w"(r1), "w"(p));
-    t1 = vextq_u8(t0, z, 8);
-    r1 = veorq_u8(r1, t1);
-    t1 = vextq_u8(z, t0, 8);
-    r0 = veorq_u8(r0, t1);
-    __asm__ __volatile__("pmull     %0.1q, %1.1d, %2.1d \n\t" : "=w"(t0) : "w"(r1), "w"(p));
-    return vrbitq_u8(veorq_u8(r0, t0));
+	uint8x16_t r0, r1, t0, t1;
+	r0 = vld1q_u8(b);
+	const uint8x16_t z = veorq_u8(h, h);
+	y = veorq_u8(r0, y);
+	y = vrbitq_u8(y);
+	const uint8x16_t p = vreinterpretq_u8_u64(vdupq_n_u64(0x0000000000000087));
+	t0 = vextq_u8(y, y, 8);
+	__asm__ __volatile__("pmull     %0.1q, %1.1d, %2.1d \n\t" : "=w"(r0) : "w"(h), "w"(y));
+	__asm__ __volatile__("pmull2   %0.1q, %1.2d, %2.2d \n\t" : "=w"(r1) : "w"(h), "w"(y));
+	__asm__ __volatile__("pmull     %0.1q, %1.1d, %2.1d \n\t" : "=w"(t1) : "w"(h), "w"(t0));
+	__asm__ __volatile__("pmull2   %0.1q, %1.2d, %2.2d \n\t" : "=w"(t0) : "w"(h), "w"(t0));
+	t0 = veorq_u8(t0, t1);
+	t1 = vextq_u8(z, t0, 8);
+	r0 = veorq_u8(r0, t1);
+	t1 = vextq_u8(t0, z, 8);
+	r1 = veorq_u8(r1, t1);
+	__asm__ __volatile__("pmull2   %0.1q, %1.2d, %2.2d \n\t" : "=w"(t0) : "w"(r1), "w"(p));
+	t1 = vextq_u8(t0, z, 8);
+	r1 = veorq_u8(r1, t1);
+	t1 = vextq_u8(z, t0, 8);
+	r0 = veorq_u8(r0, t1);
+	__asm__ __volatile__("pmull     %0.1q, %1.1d, %2.1d \n\t" : "=w"(t0) : "w"(r1), "w"(p));
+	return vrbitq_u8(veorq_u8(r0, t0));
 }
 
-}   // anonymous namespace
+}	// anonymous namespace
 
 void AES::GMAC::p_armUpdate(const uint8_t* in, unsigned int len) noexcept
 {
-    uint8x16_t y = vld1q_u8(reinterpret_cast<const uint8_t*>(_y));
-    const uint8x16_t h = _aes.p_k.neon.h;
-
-    if (_rp) {
-        for (;;) {
-            if (! len) {
-                return;
-            }
-            --len;
-            _r[_rp++] = *(in++);
-            if (_rp == 16) {
-                y = s_clmul_armneon_crypto(h, y, _r);
-                break;
-            }
-        }
-    }
-
-    while (len >= 16) {
-        y = s_clmul_armneon_crypto(h, y, in);
-        in += 16;
-        len -= 16;
-    }
-
-    vst1q_u8(reinterpret_cast<uint8_t*>(_y), y);
-
-    for (unsigned int i = 0; i < len; ++i) {
-        _r[i] = in[i];
-    }
-    _rp = len;   // len is always less than 16 here
+	uint8x16_t y = vld1q_u8(reinterpret_cast<const uint8_t*>(_y));
+	const uint8x16_t h = _aes.p_k.neon.h;
+
+	if (_rp) {
+		for (;;) {
+			if (! len) {
+				return;
+			}
+			--len;
+			_r[_rp++] = *(in++);
+			if (_rp == 16) {
+				y = s_clmul_armneon_crypto(h, y, _r);
+				break;
+			}
+		}
+	}
+
+	while (len >= 16) {
+		y = s_clmul_armneon_crypto(h, y, in);
+		in += 16;
+		len -= 16;
+	}
+
+	vst1q_u8(reinterpret_cast<uint8_t*>(_y), y);
+
+	for (unsigned int i = 0; i < len; ++i) {
+		_r[i] = in[i];
+	}
+	_rp = len;	 // len is always less than 16 here
 }
 
 void AES::GMAC::p_armFinish(uint8_t tag[16]) noexcept
 {
-    uint64_t tmp[2];
-    uint8x16_t y = vld1q_u8(reinterpret_cast<const uint8_t*>(_y));
-    const uint8x16_t h = _aes.p_k.neon.h;
-
-    if (_rp) {
-        while (_rp < 16) {
-            _r[_rp++] = 0;
-        }
-        y = s_clmul_armneon_crypto(h, y, _r);
-    }
-
-    tmp[0] = Utils::hton((uint64_t)_len << 3U);
-    tmp[1] = 0;
-    y = s_clmul_armneon_crypto(h, y, reinterpret_cast<const uint8_t*>(tmp));
-
-    Utils::copy<12>(tmp, _iv);
+	uint64_t tmp[2];
+	uint8x16_t y = vld1q_u8(reinterpret_cast<const uint8_t*>(_y));
+	const uint8x16_t h = _aes.p_k.neon.h;
+
+	if (_rp) {
+		while (_rp < 16) {
+			_r[_rp++] = 0;
+		}
+		y = s_clmul_armneon_crypto(h, y, _r);
+	}
+
+	tmp[0] = Utils::hton((uint64_t)_len << 3U);
+	tmp[1] = 0;
+	y = s_clmul_armneon_crypto(h, y, reinterpret_cast<const uint8_t*>(tmp));
+
+	Utils::copy<12>(tmp, _iv);
 #if __BYTE_ORDER == __BIG_ENDIAN
-    reinterpret_cast<uint32_t*>(tmp)[3] = 0x00000001;
+	reinterpret_cast<uint32_t*>(tmp)[3] = 0x00000001;
 #else
-    reinterpret_cast<uint32_t*>(tmp)[3] = 0x01000000;
+	reinterpret_cast<uint32_t*>(tmp)[3] = 0x01000000;
 #endif
-    _aes.encrypt(tmp, tmp);
+	_aes.encrypt(tmp, tmp);
 
-    uint8x16_t yy = y;
-    Utils::storeMachineEndian<uint64_t>(tag, tmp[0] ^ reinterpret_cast<const uint64_t*>(&yy)[0]);
-    Utils::storeMachineEndian<uint64_t>(tag + 8, tmp[1] ^ reinterpret_cast<const uint64_t*>(&yy)[1]);
+	uint8x16_t yy = y;
+	Utils::storeMachineEndian<uint64_t>(tag, tmp[0] ^ reinterpret_cast<const uint64_t*>(&yy)[0]);
+	Utils::storeMachineEndian<uint64_t>(tag + 8, tmp[1] ^ reinterpret_cast<const uint64_t*>(&yy)[1]);
 }
 
 void AES::CTR::p_armCrypt(const uint8_t* in, uint8_t* out, unsigned int len) noexcept
 {
-    uint8x16_t dd = vrev32q_u8(vld1q_u8(reinterpret_cast<uint8_t*>(_ctr)));
-    const uint32x4_t one = { 0, 0, 0, 1 };
-
-    uint8x16_t k0 = _aes.p_k.neon.ek[0];
-    uint8x16_t k1 = _aes.p_k.neon.ek[1];
-    uint8x16_t k2 = _aes.p_k.neon.ek[2];
-    uint8x16_t k3 = _aes.p_k.neon.ek[3];
-    uint8x16_t k4 = _aes.p_k.neon.ek[4];
-    uint8x16_t k5 = _aes.p_k.neon.ek[5];
-    uint8x16_t k6 = _aes.p_k.neon.ek[6];
-    uint8x16_t k7 = _aes.p_k.neon.ek[7];
-    uint8x16_t k8 = _aes.p_k.neon.ek[8];
-    uint8x16_t k9 = _aes.p_k.neon.ek[9];
-    uint8x16_t k10 = _aes.p_k.neon.ek[10];
-    uint8x16_t k11 = _aes.p_k.neon.ek[11];
-    uint8x16_t k12 = _aes.p_k.neon.ek[12];
-    uint8x16_t k13 = _aes.p_k.neon.ek[13];
-    uint8x16_t k14 = _aes.p_k.neon.ek[14];
-
-    unsigned int totalLen = _len;
-    if ((totalLen & 15U) != 0) {
-        for (;;) {
-            if (unlikely(! len)) {
-                vst1q_u8(reinterpret_cast<uint8_t*>(_ctr), vrev32q_u8(dd));
-                _len = totalLen;
-                return;
-            }
-            --len;
-            out[totalLen++] = *(in++);
-            if ((totalLen & 15U) == 0) {
-                uint8_t* const otmp = out + (totalLen - 16);
-                uint8x16_t d0 = vrev32q_u8(dd);
-                uint8x16_t pt = vld1q_u8(otmp);
-                d0 = vaesmcq_u8(vaeseq_u8(d0, k0));
-                d0 = vaesmcq_u8(vaeseq_u8(d0, k1));
-                d0 = vaesmcq_u8(vaeseq_u8(d0, k2));
-                d0 = vaesmcq_u8(vaeseq_u8(d0, k3));
-                d0 = vaesmcq_u8(vaeseq_u8(d0, k4));
-                d0 = vaesmcq_u8(vaeseq_u8(d0, k5));
-                d0 = vaesmcq_u8(vaeseq_u8(d0, k6));
-                d0 = vaesmcq_u8(vaeseq_u8(d0, k7));
-                d0 = vaesmcq_u8(vaeseq_u8(d0, k8));
-                d0 = vaesmcq_u8(vaeseq_u8(d0, k9));
-                d0 = vaesmcq_u8(vaeseq_u8(d0, k10));
-                d0 = vaesmcq_u8(vaeseq_u8(d0, k11));
-                d0 = vaesmcq_u8(vaeseq_u8(d0, k12));
-                d0 = veorq_u8(vaeseq_u8(d0, k13), k14);
-                vst1q_u8(otmp, veorq_u8(pt, d0));
-                dd = (uint8x16_t)vaddq_u32((uint32x4_t)dd, one);
-                break;
-            }
-        }
-    }
-
-    out += totalLen;
-    _len = totalLen + len;
-
-    if (likely(len >= 64)) {
-        const uint32x4_t four = vshlq_n_u32(one, 2);
-        uint8x16_t dd1 = (uint8x16_t)vaddq_u32((uint32x4_t)dd, one);
-        uint8x16_t dd2 = (uint8x16_t)vaddq_u32((uint32x4_t)dd1, one);
-        uint8x16_t dd3 = (uint8x16_t)vaddq_u32((uint32x4_t)dd2, one);
-        for (;;) {
-            len -= 64;
-            uint8x16_t d0 = vrev32q_u8(dd);
-            uint8x16_t d1 = vrev32q_u8(dd1);
-            uint8x16_t d2 = vrev32q_u8(dd2);
-            uint8x16_t d3 = vrev32q_u8(dd3);
-            uint8x16_t pt0 = vld1q_u8(in);
-            uint8x16_t pt1 = vld1q_u8(in + 16);
-            uint8x16_t pt2 = vld1q_u8(in + 32);
-            uint8x16_t pt3 = vld1q_u8(in + 48);
-
-            d0 = vaesmcq_u8(vaeseq_u8(d0, k0));
-            d1 = vaesmcq_u8(vaeseq_u8(d1, k0));
-            d2 = vaesmcq_u8(vaeseq_u8(d2, k0));
-            d3 = vaesmcq_u8(vaeseq_u8(d3, k0));
-            d0 = vaesmcq_u8(vaeseq_u8(d0, k1));
-            d1 = vaesmcq_u8(vaeseq_u8(d1, k1));
-            d2 = vaesmcq_u8(vaeseq_u8(d2, k1));
-            d3 = vaesmcq_u8(vaeseq_u8(d3, k1));
-            d0 = vaesmcq_u8(vaeseq_u8(d0, k2));
-            d1 = vaesmcq_u8(vaeseq_u8(d1, k2));
-            d2 = vaesmcq_u8(vaeseq_u8(d2, k2));
-            d3 = vaesmcq_u8(vaeseq_u8(d3, k2));
-            d0 = vaesmcq_u8(vaeseq_u8(d0, k3));
-            d1 = vaesmcq_u8(vaeseq_u8(d1, k3));
-            d2 = vaesmcq_u8(vaeseq_u8(d2, k3));
-            d3 = vaesmcq_u8(vaeseq_u8(d3, k3));
-            d0 = vaesmcq_u8(vaeseq_u8(d0, k4));
-            d1 = vaesmcq_u8(vaeseq_u8(d1, k4));
-            d2 = vaesmcq_u8(vaeseq_u8(d2, k4));
-            d3 = vaesmcq_u8(vaeseq_u8(d3, k4));
-            d0 = vaesmcq_u8(vaeseq_u8(d0, k5));
-            d1 = vaesmcq_u8(vaeseq_u8(d1, k5));
-            d2 = vaesmcq_u8(vaeseq_u8(d2, k5));
-            d3 = vaesmcq_u8(vaeseq_u8(d3, k5));
-            d0 = vaesmcq_u8(vaeseq_u8(d0, k6));
-            d1 = vaesmcq_u8(vaeseq_u8(d1, k6));
-            d2 = vaesmcq_u8(vaeseq_u8(d2, k6));
-            d3 = vaesmcq_u8(vaeseq_u8(d3, k6));
-            d0 = vaesmcq_u8(vaeseq_u8(d0, k7));
-            d1 = vaesmcq_u8(vaeseq_u8(d1, k7));
-            d2 = vaesmcq_u8(vaeseq_u8(d2, k7));
-            d3 = vaesmcq_u8(vaeseq_u8(d3, k7));
-            d0 = vaesmcq_u8(vaeseq_u8(d0, k8));
-            d1 = vaesmcq_u8(vaeseq_u8(d1, k8));
-            d2 = vaesmcq_u8(vaeseq_u8(d2, k8));
-            d3 = vaesmcq_u8(vaeseq_u8(d3, k8));
-            d0 = vaesmcq_u8(vaeseq_u8(d0, k9));
-            d1 = vaesmcq_u8(vaeseq_u8(d1, k9));
-            d2 = vaesmcq_u8(vaeseq_u8(d2, k9));
-            d3 = vaesmcq_u8(vaeseq_u8(d3, k9));
-            d0 = vaesmcq_u8(vaeseq_u8(d0, k10));
-            d1 = vaesmcq_u8(vaeseq_u8(d1, k10));
-            d2 = vaesmcq_u8(vaeseq_u8(d2, k10));
-            d3 = vaesmcq_u8(vaeseq_u8(d3, k10));
-            d0 = vaesmcq_u8(vaeseq_u8(d0, k11));
-            d1 = vaesmcq_u8(vaeseq_u8(d1, k11));
-            d2 = vaesmcq_u8(vaeseq_u8(d2, k11));
-            d3 = vaesmcq_u8(vaeseq_u8(d3, k11));
-            d0 = vaesmcq_u8(vaeseq_u8(d0, k12));
-            d1 = vaesmcq_u8(vaeseq_u8(d1, k12));
-            d2 = vaesmcq_u8(vaeseq_u8(d2, k12));
-            d3 = vaesmcq_u8(vaeseq_u8(d3, k12));
-            d0 = veorq_u8(vaeseq_u8(d0, k13), k14);
-            d1 = veorq_u8(vaeseq_u8(d1, k13), k14);
-            d2 = veorq_u8(vaeseq_u8(d2, k13), k14);
-            d3 = veorq_u8(vaeseq_u8(d3, k13), k14);
-
-            d0 = veorq_u8(pt0, d0);
-            d1 = veorq_u8(pt1, d1);
-            d2 = veorq_u8(pt2, d2);
-            d3 = veorq_u8(pt3, d3);
-
-            vst1q_u8(out, d0);
-            vst1q_u8(out + 16, d1);
-            vst1q_u8(out + 32, d2);
-            vst1q_u8(out + 48, d3);
-
-            out += 64;
-            in += 64;
-
-            dd = (uint8x16_t)vaddq_u32((uint32x4_t)dd, four);
-            if (unlikely(len < 64)) {
-                break;
-            }
-            dd1 = (uint8x16_t)vaddq_u32((uint32x4_t)dd1, four);
-            dd2 = (uint8x16_t)vaddq_u32((uint32x4_t)dd2, four);
-            dd3 = (uint8x16_t)vaddq_u32((uint32x4_t)dd3, four);
-        }
-    }
-
-    while (len >= 16) {
-        len -= 16;
-        uint8x16_t d0 = vrev32q_u8(dd);
-        uint8x16_t pt = vld1q_u8(in);
-        in += 16;
-        dd = (uint8x16_t)vaddq_u32((uint32x4_t)dd, one);
-        d0 = vaesmcq_u8(vaeseq_u8(d0, k0));
-        d0 = vaesmcq_u8(vaeseq_u8(d0, k1));
-        d0 = vaesmcq_u8(vaeseq_u8(d0, k2));
-        d0 = vaesmcq_u8(vaeseq_u8(d0, k3));
-        d0 = vaesmcq_u8(vaeseq_u8(d0, k4));
-        d0 = vaesmcq_u8(vaeseq_u8(d0, k5));
-        d0 = vaesmcq_u8(vaeseq_u8(d0, k6));
-        d0 = vaesmcq_u8(vaeseq_u8(d0, k7));
-        d0 = vaesmcq_u8(vaeseq_u8(d0, k8));
-        d0 = vaesmcq_u8(vaeseq_u8(d0, k9));
-        d0 = vaesmcq_u8(vaeseq_u8(d0, k10));
-        d0 = vaesmcq_u8(vaeseq_u8(d0, k11));
-        d0 = vaesmcq_u8(vaeseq_u8(d0, k12));
-        d0 = veorq_u8(vaeseq_u8(d0, k13), k14);
-        vst1q_u8(out, veorq_u8(pt, d0));
-        out += 16;
-    }
-
-    // Any remaining input is placed in _out. This will be picked up and crypted
-    // on subsequent calls to crypt() or finish() as it'll mean _len will not be
-    // an even multiple of 16.
-    for (unsigned int i = 0; i < len; ++i) {
-        out[i] = in[i];
-    }
-
-    vst1q_u8(reinterpret_cast<uint8_t*>(_ctr), vrev32q_u8(dd));
+	uint8x16_t dd = vrev32q_u8(vld1q_u8(reinterpret_cast<uint8_t*>(_ctr)));
+	const uint32x4_t one = { 0, 0, 0, 1 };
+
+	uint8x16_t k0 = _aes.p_k.neon.ek[0];
+	uint8x16_t k1 = _aes.p_k.neon.ek[1];
+	uint8x16_t k2 = _aes.p_k.neon.ek[2];
+	uint8x16_t k3 = _aes.p_k.neon.ek[3];
+	uint8x16_t k4 = _aes.p_k.neon.ek[4];
+	uint8x16_t k5 = _aes.p_k.neon.ek[5];
+	uint8x16_t k6 = _aes.p_k.neon.ek[6];
+	uint8x16_t k7 = _aes.p_k.neon.ek[7];
+	uint8x16_t k8 = _aes.p_k.neon.ek[8];
+	uint8x16_t k9 = _aes.p_k.neon.ek[9];
+	uint8x16_t k10 = _aes.p_k.neon.ek[10];
+	uint8x16_t k11 = _aes.p_k.neon.ek[11];
+	uint8x16_t k12 = _aes.p_k.neon.ek[12];
+	uint8x16_t k13 = _aes.p_k.neon.ek[13];
+	uint8x16_t k14 = _aes.p_k.neon.ek[14];
+
+	unsigned int totalLen = _len;
+	if ((totalLen & 15U) != 0) {
+		for (;;) {
+			if (unlikely(! len)) {
+				vst1q_u8(reinterpret_cast<uint8_t*>(_ctr), vrev32q_u8(dd));
+				_len = totalLen;
+				return;
+			}
+			--len;
+			out[totalLen++] = *(in++);
+			if ((totalLen & 15U) == 0) {
+				uint8_t* const otmp = out + (totalLen - 16);
+				uint8x16_t d0 = vrev32q_u8(dd);
+				uint8x16_t pt = vld1q_u8(otmp);
+				d0 = vaesmcq_u8(vaeseq_u8(d0, k0));
+				d0 = vaesmcq_u8(vaeseq_u8(d0, k1));
+				d0 = vaesmcq_u8(vaeseq_u8(d0, k2));
+				d0 = vaesmcq_u8(vaeseq_u8(d0, k3));
+				d0 = vaesmcq_u8(vaeseq_u8(d0, k4));
+				d0 = vaesmcq_u8(vaeseq_u8(d0, k5));
+				d0 = vaesmcq_u8(vaeseq_u8(d0, k6));
+				d0 = vaesmcq_u8(vaeseq_u8(d0, k7));
+				d0 = vaesmcq_u8(vaeseq_u8(d0, k8));
+				d0 = vaesmcq_u8(vaeseq_u8(d0, k9));
+				d0 = vaesmcq_u8(vaeseq_u8(d0, k10));
+				d0 = vaesmcq_u8(vaeseq_u8(d0, k11));
+				d0 = vaesmcq_u8(vaeseq_u8(d0, k12));
+				d0 = veorq_u8(vaeseq_u8(d0, k13), k14);
+				vst1q_u8(otmp, veorq_u8(pt, d0));
+				dd = (uint8x16_t)vaddq_u32((uint32x4_t)dd, one);
+				break;
+			}
+		}
+	}
+
+	out += totalLen;
+	_len = totalLen + len;
+
+	if (likely(len >= 64)) {
+		const uint32x4_t four = vshlq_n_u32(one, 2);
+		uint8x16_t dd1 = (uint8x16_t)vaddq_u32((uint32x4_t)dd, one);
+		uint8x16_t dd2 = (uint8x16_t)vaddq_u32((uint32x4_t)dd1, one);
+		uint8x16_t dd3 = (uint8x16_t)vaddq_u32((uint32x4_t)dd2, one);
+		for (;;) {
+			len -= 64;
+			uint8x16_t d0 = vrev32q_u8(dd);
+			uint8x16_t d1 = vrev32q_u8(dd1);
+			uint8x16_t d2 = vrev32q_u8(dd2);
+			uint8x16_t d3 = vrev32q_u8(dd3);
+			uint8x16_t pt0 = vld1q_u8(in);
+			uint8x16_t pt1 = vld1q_u8(in + 16);
+			uint8x16_t pt2 = vld1q_u8(in + 32);
+			uint8x16_t pt3 = vld1q_u8(in + 48);
+
+			d0 = vaesmcq_u8(vaeseq_u8(d0, k0));
+			d1 = vaesmcq_u8(vaeseq_u8(d1, k0));
+			d2 = vaesmcq_u8(vaeseq_u8(d2, k0));
+			d3 = vaesmcq_u8(vaeseq_u8(d3, k0));
+			d0 = vaesmcq_u8(vaeseq_u8(d0, k1));
+			d1 = vaesmcq_u8(vaeseq_u8(d1, k1));
+			d2 = vaesmcq_u8(vaeseq_u8(d2, k1));
+			d3 = vaesmcq_u8(vaeseq_u8(d3, k1));
+			d0 = vaesmcq_u8(vaeseq_u8(d0, k2));
+			d1 = vaesmcq_u8(vaeseq_u8(d1, k2));
+			d2 = vaesmcq_u8(vaeseq_u8(d2, k2));
+			d3 = vaesmcq_u8(vaeseq_u8(d3, k2));
+			d0 = vaesmcq_u8(vaeseq_u8(d0, k3));
+			d1 = vaesmcq_u8(vaeseq_u8(d1, k3));
+			d2 = vaesmcq_u8(vaeseq_u8(d2, k3));
+			d3 = vaesmcq_u8(vaeseq_u8(d3, k3));
+			d0 = vaesmcq_u8(vaeseq_u8(d0, k4));
+			d1 = vaesmcq_u8(vaeseq_u8(d1, k4));
+			d2 = vaesmcq_u8(vaeseq_u8(d2, k4));
+			d3 = vaesmcq_u8(vaeseq_u8(d3, k4));
+			d0 = vaesmcq_u8(vaeseq_u8(d0, k5));
+			d1 = vaesmcq_u8(vaeseq_u8(d1, k5));
+			d2 = vaesmcq_u8(vaeseq_u8(d2, k5));
+			d3 = vaesmcq_u8(vaeseq_u8(d3, k5));
+			d0 = vaesmcq_u8(vaeseq_u8(d0, k6));
+			d1 = vaesmcq_u8(vaeseq_u8(d1, k6));
+			d2 = vaesmcq_u8(vaeseq_u8(d2, k6));
+			d3 = vaesmcq_u8(vaeseq_u8(d3, k6));
+			d0 = vaesmcq_u8(vaeseq_u8(d0, k7));
+			d1 = vaesmcq_u8(vaeseq_u8(d1, k7));
+			d2 = vaesmcq_u8(vaeseq_u8(d2, k7));
+			d3 = vaesmcq_u8(vaeseq_u8(d3, k7));
+			d0 = vaesmcq_u8(vaeseq_u8(d0, k8));
+			d1 = vaesmcq_u8(vaeseq_u8(d1, k8));
+			d2 = vaesmcq_u8(vaeseq_u8(d2, k8));
+			d3 = vaesmcq_u8(vaeseq_u8(d3, k8));
+			d0 = vaesmcq_u8(vaeseq_u8(d0, k9));
+			d1 = vaesmcq_u8(vaeseq_u8(d1, k9));
+			d2 = vaesmcq_u8(vaeseq_u8(d2, k9));
+			d3 = vaesmcq_u8(vaeseq_u8(d3, k9));
+			d0 = vaesmcq_u8(vaeseq_u8(d0, k10));
+			d1 = vaesmcq_u8(vaeseq_u8(d1, k10));
+			d2 = vaesmcq_u8(vaeseq_u8(d2, k10));
+			d3 = vaesmcq_u8(vaeseq_u8(d3, k10));
+			d0 = vaesmcq_u8(vaeseq_u8(d0, k11));
+			d1 = vaesmcq_u8(vaeseq_u8(d1, k11));
+			d2 = vaesmcq_u8(vaeseq_u8(d2, k11));
+			d3 = vaesmcq_u8(vaeseq_u8(d3, k11));
+			d0 = vaesmcq_u8(vaeseq_u8(d0, k12));
+			d1 = vaesmcq_u8(vaeseq_u8(d1, k12));
+			d2 = vaesmcq_u8(vaeseq_u8(d2, k12));
+			d3 = vaesmcq_u8(vaeseq_u8(d3, k12));
+			d0 = veorq_u8(vaeseq_u8(d0, k13), k14);
+			d1 = veorq_u8(vaeseq_u8(d1, k13), k14);
+			d2 = veorq_u8(vaeseq_u8(d2, k13), k14);
+			d3 = veorq_u8(vaeseq_u8(d3, k13), k14);
+
+			d0 = veorq_u8(pt0, d0);
+			d1 = veorq_u8(pt1, d1);
+			d2 = veorq_u8(pt2, d2);
+			d3 = veorq_u8(pt3, d3);
+
+			vst1q_u8(out, d0);
+			vst1q_u8(out + 16, d1);
+			vst1q_u8(out + 32, d2);
+			vst1q_u8(out + 48, d3);
+
+			out += 64;
+			in += 64;
+
+			dd = (uint8x16_t)vaddq_u32((uint32x4_t)dd, four);
+			if (unlikely(len < 64)) {
+				break;
+			}
+			dd1 = (uint8x16_t)vaddq_u32((uint32x4_t)dd1, four);
+			dd2 = (uint8x16_t)vaddq_u32((uint32x4_t)dd2, four);
+			dd3 = (uint8x16_t)vaddq_u32((uint32x4_t)dd3, four);
+		}
+	}
+
+	while (len >= 16) {
+		len -= 16;
+		uint8x16_t d0 = vrev32q_u8(dd);
+		uint8x16_t pt = vld1q_u8(in);
+		in += 16;
+		dd = (uint8x16_t)vaddq_u32((uint32x4_t)dd, one);
+		d0 = vaesmcq_u8(vaeseq_u8(d0, k0));
+		d0 = vaesmcq_u8(vaeseq_u8(d0, k1));
+		d0 = vaesmcq_u8(vaeseq_u8(d0, k2));
+		d0 = vaesmcq_u8(vaeseq_u8(d0, k3));
+		d0 = vaesmcq_u8(vaeseq_u8(d0, k4));
+		d0 = vaesmcq_u8(vaeseq_u8(d0, k5));
+		d0 = vaesmcq_u8(vaeseq_u8(d0, k6));
+		d0 = vaesmcq_u8(vaeseq_u8(d0, k7));
+		d0 = vaesmcq_u8(vaeseq_u8(d0, k8));
+		d0 = vaesmcq_u8(vaeseq_u8(d0, k9));
+		d0 = vaesmcq_u8(vaeseq_u8(d0, k10));
+		d0 = vaesmcq_u8(vaeseq_u8(d0, k11));
+		d0 = vaesmcq_u8(vaeseq_u8(d0, k12));
+		d0 = veorq_u8(vaeseq_u8(d0, k13), k14);
+		vst1q_u8(out, veorq_u8(pt, d0));
+		out += 16;
+	}
+
+	// Any remaining input is placed in _out. This will be picked up and crypted
+	// on subsequent calls to crypt() or finish() as it'll mean _len will not be
+	// an even multiple of 16.
+	for (unsigned int i = 0; i < len; ++i) {
+		out[i] = in[i];
+	}
+
+	vst1q_u8(reinterpret_cast<uint8_t*>(_ctr), vrev32q_u8(dd));
 }
 
 #define ZT_INIT_ARMNEON_CRYPTO_SUBWORD(w) ((uint32_t)s_sbox[w & 0xffU] + ((uint32_t)s_sbox[(w >> 8U) & 0xffU] << 8U) + ((uint32_t)s_sbox[(w >> 16U) & 0xffU] << 16U) + ((uint32_t)s_sbox[(w >> 24U) & 0xffU] << 24U))
 #define ZT_INIT_ARMNEON_CRYPTO_ROTWORD(w) (((w) << 8U) | ((w) >> 24U))
-#define ZT_INIT_ARMNEON_CRYPTO_NK         8
-#define ZT_INIT_ARMNEON_CRYPTO_NB         4
-#define ZT_INIT_ARMNEON_CRYPTO_NR         14
+#define ZT_INIT_ARMNEON_CRYPTO_NK		  8
+#define ZT_INIT_ARMNEON_CRYPTO_NB		  4
+#define ZT_INIT_ARMNEON_CRYPTO_NR		  14
 
 void AES::p_init_armneon_crypto(const uint8_t* key) noexcept
 {
-    static const uint8_t s_sbox[256] = { 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
-                                         0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
-                                         0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
-                                         0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
-                                         0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
-                                         0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
-                                         0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
-                                         0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 };
-
-    uint64_t h[2];
-    uint32_t* const w = reinterpret_cast<uint32_t*>(p_k.neon.ek);
-
-    for (unsigned int i = 0; i < ZT_INIT_ARMNEON_CRYPTO_NK; ++i) {
-        const unsigned int j = i * 4;
-        w[i] = ((uint32_t)key[j] << 24U) | ((uint32_t)key[j + 1] << 16U) | ((uint32_t)key[j + 2] << 8U) | (uint32_t)key[j + 3];
-    }
-
-    for (unsigned int i = ZT_INIT_ARMNEON_CRYPTO_NK; i < (ZT_INIT_ARMNEON_CRYPTO_NB * (ZT_INIT_ARMNEON_CRYPTO_NR + 1)); ++i) {
-        uint32_t t = w[i - 1];
-        const unsigned int imod = i & (ZT_INIT_ARMNEON_CRYPTO_NK - 1);
-        if (imod == 0) {
-            t = ZT_INIT_ARMNEON_CRYPTO_SUBWORD(ZT_INIT_ARMNEON_CRYPTO_ROTWORD(t)) ^ rcon[(i - 1) / ZT_INIT_ARMNEON_CRYPTO_NK];
-        }
-        else if (imod == 4) {
-            t = ZT_INIT_ARMNEON_CRYPTO_SUBWORD(t);
-        }
-        w[i] = w[i - ZT_INIT_ARMNEON_CRYPTO_NK] ^ t;
-    }
-
-    for (unsigned int i = 0; i < (ZT_INIT_ARMNEON_CRYPTO_NB * (ZT_INIT_ARMNEON_CRYPTO_NR + 1)); ++i) {
-        w[i] = Utils::hton(w[i]);
-    }
-
-    p_k.neon.dk[0] = p_k.neon.ek[14];
-    for (int i = 1; i < 14; ++i) {
-        p_k.neon.dk[i] = vaesimcq_u8(p_k.neon.ek[14 - i]);
-    }
-    p_k.neon.dk[14] = p_k.neon.ek[0];
-
-    p_encrypt_armneon_crypto(Utils::ZERO256, h);
-    Utils::copy<16>(&(p_k.neon.h), h);
-    p_k.neon.h = vrbitq_u8(p_k.neon.h);
-    p_k.sw.h[0] = Utils::ntoh(h[0]);
-    p_k.sw.h[1] = Utils::ntoh(h[1]);
+	static const uint8_t s_sbox[256] = { 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
+										 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
+										 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
+										 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
+										 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
+										 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
+										 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
+										 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 };
+
+	uint64_t h[2];
+	uint32_t* const w = reinterpret_cast<uint32_t*>(p_k.neon.ek);
+
+	for (unsigned int i = 0; i < ZT_INIT_ARMNEON_CRYPTO_NK; ++i) {
+		const unsigned int j = i * 4;
+		w[i] = ((uint32_t)key[j] << 24U) | ((uint32_t)key[j + 1] << 16U) | ((uint32_t)key[j + 2] << 8U) | (uint32_t)key[j + 3];
+	}
+
+	for (unsigned int i = ZT_INIT_ARMNEON_CRYPTO_NK; i < (ZT_INIT_ARMNEON_CRYPTO_NB * (ZT_INIT_ARMNEON_CRYPTO_NR + 1)); ++i) {
+		uint32_t t = w[i - 1];
+		const unsigned int imod = i & (ZT_INIT_ARMNEON_CRYPTO_NK - 1);
+		if (imod == 0) {
+			t = ZT_INIT_ARMNEON_CRYPTO_SUBWORD(ZT_INIT_ARMNEON_CRYPTO_ROTWORD(t)) ^ rcon[(i - 1) / ZT_INIT_ARMNEON_CRYPTO_NK];
+		}
+		else if (imod == 4) {
+			t = ZT_INIT_ARMNEON_CRYPTO_SUBWORD(t);
+		}
+		w[i] = w[i - ZT_INIT_ARMNEON_CRYPTO_NK] ^ t;
+	}
+
+	for (unsigned int i = 0; i < (ZT_INIT_ARMNEON_CRYPTO_NB * (ZT_INIT_ARMNEON_CRYPTO_NR + 1)); ++i) {
+		w[i] = Utils::hton(w[i]);
+	}
+
+	p_k.neon.dk[0] = p_k.neon.ek[14];
+	for (int i = 1; i < 14; ++i) {
+		p_k.neon.dk[i] = vaesimcq_u8(p_k.neon.ek[14 - i]);
+	}
+	p_k.neon.dk[14] = p_k.neon.ek[0];
+
+	p_encrypt_armneon_crypto(Utils::ZERO256, h);
+	Utils::copy<16>(&(p_k.neon.h), h);
+	p_k.neon.h = vrbitq_u8(p_k.neon.h);
+	p_k.sw.h[0] = Utils::ntoh(h[0]);
+	p_k.sw.h[1] = Utils::ntoh(h[1]);
 }
 
 void AES::p_encrypt_armneon_crypto(const void* const in, void* const out) const noexcept
 {
-    uint8x16_t tmp = vld1q_u8(reinterpret_cast<const uint8_t*>(in));
-    tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[0]));
-    tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[1]));
-    tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[2]));
-    tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[3]));
-    tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[4]));
-    tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[5]));
-    tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[6]));
-    tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[7]));
-    tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[8]));
-    tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[9]));
-    tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[10]));
-    tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[11]));
-    tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[12]));
-    tmp = veorq_u8(vaeseq_u8(tmp, p_k.neon.ek[13]), p_k.neon.ek[14]);
-    vst1q_u8(reinterpret_cast<uint8_t*>(out), tmp);
+	uint8x16_t tmp = vld1q_u8(reinterpret_cast<const uint8_t*>(in));
+	tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[0]));
+	tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[1]));
+	tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[2]));
+	tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[3]));
+	tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[4]));
+	tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[5]));
+	tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[6]));
+	tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[7]));
+	tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[8]));
+	tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[9]));
+	tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[10]));
+	tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[11]));
+	tmp = vaesmcq_u8(vaeseq_u8(tmp, p_k.neon.ek[12]));
+	tmp = veorq_u8(vaeseq_u8(tmp, p_k.neon.ek[13]), p_k.neon.ek[14]);
+	vst1q_u8(reinterpret_cast<uint8_t*>(out), tmp);
 }
 
 void AES::p_decrypt_armneon_crypto(const void* const in, void* const out) const noexcept
 {
-    uint8x16_t tmp = vld1q_u8(reinterpret_cast<const uint8_t*>(in));
-    tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[0]));
-    tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[1]));
-    tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[2]));
-    tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[3]));
-    tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[4]));
-    tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[5]));
-    tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[6]));
-    tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[7]));
-    tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[8]));
-    tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[9]));
-    tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[10]));
-    tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[11]));
-    tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[12]));
-    tmp = veorq_u8(vaesdq_u8(tmp, p_k.neon.dk[13]), p_k.neon.dk[14]);
-    vst1q_u8(reinterpret_cast<uint8_t*>(out), tmp);
+	uint8x16_t tmp = vld1q_u8(reinterpret_cast<const uint8_t*>(in));
+	tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[0]));
+	tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[1]));
+	tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[2]));
+	tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[3]));
+	tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[4]));
+	tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[5]));
+	tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[6]));
+	tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[7]));
+	tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[8]));
+	tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[9]));
+	tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[10]));
+	tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[11]));
+	tmp = vaesimcq_u8(vaesdq_u8(tmp, p_k.neon.dk[12]));
+	tmp = veorq_u8(vaesdq_u8(tmp, p_k.neon.dk[13]), p_k.neon.dk[14]);
+	vst1q_u8(reinterpret_cast<uint8_t*>(out), tmp);
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
-#endif   // ZT_AES_NEON
+#endif	 // ZT_AES_NEON

+ 192 - 192
node/Address.hpp

@@ -31,201 +31,201 @@ namespace ZeroTier {
  */
 class Address {
   public:
-    Address() : _a(0)
-    {
-    }
-    Address(const Address& a) : _a(a._a)
-    {
-    }
-    Address(uint64_t a) : _a(a & 0xffffffffffULL)
-    {
-    }
-
-    /**
-     * @param bits Raw address -- 5 bytes, big-endian byte order
-     * @param len Length of array
-     */
-    Address(const void* bits, unsigned int len)
-    {
-        setTo(bits, len);
-    }
-
-    inline Address& operator=(const Address& a)
-    {
-        _a = a._a;
-        return *this;
-    }
-    inline Address& operator=(const uint64_t a)
-    {
-        _a = (a & 0xffffffffffULL);
-        return *this;
-    }
-
-    /**
-     * @param bits Raw address -- 5 bytes, big-endian byte order
-     * @param len Length of array
-     */
-    inline void setTo(const void* bits, const unsigned int len)
-    {
-        if (len < ZT_ADDRESS_LENGTH) {
-            _a = 0;
-            return;
-        }
-        const unsigned char* b = (const unsigned char*)bits;
-        uint64_t a = ((uint64_t)*b++) << 32;
-        a |= ((uint64_t)*b++) << 24;
-        a |= ((uint64_t)*b++) << 16;
-        a |= ((uint64_t)*b++) << 8;
-        a |= ((uint64_t)*b);
-        _a = a;
-    }
-
-    /**
-     * @param bits Buffer to hold 5-byte address in big-endian byte order
-     * @param len Length of array
-     */
-    inline void copyTo(void* const bits, const unsigned int len) const
-    {
-        if (len < ZT_ADDRESS_LENGTH) {
-            return;
-        }
-        unsigned char* b = (unsigned char*)bits;
-        *(b++) = (unsigned char)((_a >> 32) & 0xff);
-        *(b++) = (unsigned char)((_a >> 24) & 0xff);
-        *(b++) = (unsigned char)((_a >> 16) & 0xff);
-        *(b++) = (unsigned char)((_a >> 8) & 0xff);
-        *b = (unsigned char)(_a & 0xff);
-    }
-
-    /**
-     * Append to a buffer in big-endian byte order
-     *
-     * @param b Buffer to append to
-     */
-    template <unsigned int C> inline void appendTo(Buffer<C>& b) const
-    {
-        unsigned char* p = (unsigned char*)b.appendField(ZT_ADDRESS_LENGTH);
-        *(p++) = (unsigned char)((_a >> 32) & 0xff);
-        *(p++) = (unsigned char)((_a >> 24) & 0xff);
-        *(p++) = (unsigned char)((_a >> 16) & 0xff);
-        *(p++) = (unsigned char)((_a >> 8) & 0xff);
-        *p = (unsigned char)(_a & 0xff);
-    }
-
-    /**
-     * @return Integer containing address (0 to 2^40)
-     */
-    inline uint64_t toInt() const
-    {
-        return _a;
-    }
-
-    /**
-     * @return Hash code for use with Hashtable
-     */
-    inline unsigned long hashCode() const
-    {
-        return (unsigned long)_a;
-    }
-
-    /**
-     * @return Hexadecimal string
-     */
-    inline char* toString(char buf[11]) const
-    {
-        return Utils::hex10(_a, buf);
-    }
-
-    /**
-     * @return True if this address is not zero
-     */
-    inline operator bool() const
-    {
-        return (_a != 0);
-    }
-
-    /**
-     * Check if this address is reserved
-     *
-     * The all-zero null address and any address beginning with 0xff are
-     * reserved. (0xff is reserved for future use to designate possibly
-     * longer addresses, addresses based on IPv6 innards, etc.)
-     *
-     * @return True if address is reserved and may not be used
-     */
-    inline bool isReserved() const
-    {
-        return ((! _a) || ((_a >> 32) == ZT_ADDRESS_RESERVED_PREFIX));
-    }
-
-    /**
-     * @param i Value from 0 to 4 (inclusive)
-     * @return Byte at said position (address interpreted in big-endian order)
-     */
-    inline uint8_t operator[](unsigned int i) const
-    {
-        return (uint8_t)(_a >> (32 - (i * 8)));
-    }
-
-    inline void zero()
-    {
-        _a = 0;
-    }
-
-    inline bool operator==(const uint64_t& a) const
-    {
-        return (_a == (a & 0xffffffffffULL));
-    }
-    inline bool operator!=(const uint64_t& a) const
-    {
-        return (_a != (a & 0xffffffffffULL));
-    }
-    inline bool operator>(const uint64_t& a) const
-    {
-        return (_a > (a & 0xffffffffffULL));
-    }
-    inline bool operator<(const uint64_t& a) const
-    {
-        return (_a < (a & 0xffffffffffULL));
-    }
-    inline bool operator>=(const uint64_t& a) const
-    {
-        return (_a >= (a & 0xffffffffffULL));
-    }
-    inline bool operator<=(const uint64_t& a) const
-    {
-        return (_a <= (a & 0xffffffffffULL));
-    }
-
-    inline bool operator==(const Address& a) const
-    {
-        return (_a == a._a);
-    }
-    inline bool operator!=(const Address& a) const
-    {
-        return (_a != a._a);
-    }
-    inline bool operator>(const Address& a) const
-    {
-        return (_a > a._a);
-    }
-    inline bool operator<(const Address& a) const
-    {
-        return (_a < a._a);
-    }
-    inline bool operator>=(const Address& a) const
-    {
-        return (_a >= a._a);
-    }
-    inline bool operator<=(const Address& a) const
-    {
-        return (_a <= a._a);
-    }
+	Address() : _a(0)
+	{
+	}
+	Address(const Address& a) : _a(a._a)
+	{
+	}
+	Address(uint64_t a) : _a(a & 0xffffffffffULL)
+	{
+	}
+
+	/**
+	 * @param bits Raw address -- 5 bytes, big-endian byte order
+	 * @param len Length of array
+	 */
+	Address(const void* bits, unsigned int len)
+	{
+		setTo(bits, len);
+	}
+
+	inline Address& operator=(const Address& a)
+	{
+		_a = a._a;
+		return *this;
+	}
+	inline Address& operator=(const uint64_t a)
+	{
+		_a = (a & 0xffffffffffULL);
+		return *this;
+	}
+
+	/**
+	 * @param bits Raw address -- 5 bytes, big-endian byte order
+	 * @param len Length of array
+	 */
+	inline void setTo(const void* bits, const unsigned int len)
+	{
+		if (len < ZT_ADDRESS_LENGTH) {
+			_a = 0;
+			return;
+		}
+		const unsigned char* b = (const unsigned char*)bits;
+		uint64_t a = ((uint64_t)*b++) << 32;
+		a |= ((uint64_t)*b++) << 24;
+		a |= ((uint64_t)*b++) << 16;
+		a |= ((uint64_t)*b++) << 8;
+		a |= ((uint64_t)*b);
+		_a = a;
+	}
+
+	/**
+	 * @param bits Buffer to hold 5-byte address in big-endian byte order
+	 * @param len Length of array
+	 */
+	inline void copyTo(void* const bits, const unsigned int len) const
+	{
+		if (len < ZT_ADDRESS_LENGTH) {
+			return;
+		}
+		unsigned char* b = (unsigned char*)bits;
+		*(b++) = (unsigned char)((_a >> 32) & 0xff);
+		*(b++) = (unsigned char)((_a >> 24) & 0xff);
+		*(b++) = (unsigned char)((_a >> 16) & 0xff);
+		*(b++) = (unsigned char)((_a >> 8) & 0xff);
+		*b = (unsigned char)(_a & 0xff);
+	}
+
+	/**
+	 * Append to a buffer in big-endian byte order
+	 *
+	 * @param b Buffer to append to
+	 */
+	template <unsigned int C> inline void appendTo(Buffer<C>& b) const
+	{
+		unsigned char* p = (unsigned char*)b.appendField(ZT_ADDRESS_LENGTH);
+		*(p++) = (unsigned char)((_a >> 32) & 0xff);
+		*(p++) = (unsigned char)((_a >> 24) & 0xff);
+		*(p++) = (unsigned char)((_a >> 16) & 0xff);
+		*(p++) = (unsigned char)((_a >> 8) & 0xff);
+		*p = (unsigned char)(_a & 0xff);
+	}
+
+	/**
+	 * @return Integer containing address (0 to 2^40)
+	 */
+	inline uint64_t toInt() const
+	{
+		return _a;
+	}
+
+	/**
+	 * @return Hash code for use with Hashtable
+	 */
+	inline unsigned long hashCode() const
+	{
+		return (unsigned long)_a;
+	}
+
+	/**
+	 * @return Hexadecimal string
+	 */
+	inline char* toString(char buf[11]) const
+	{
+		return Utils::hex10(_a, buf);
+	}
+
+	/**
+	 * @return True if this address is not zero
+	 */
+	inline operator bool() const
+	{
+		return (_a != 0);
+	}
+
+	/**
+	 * Check if this address is reserved
+	 *
+	 * The all-zero null address and any address beginning with 0xff are
+	 * reserved. (0xff is reserved for future use to designate possibly
+	 * longer addresses, addresses based on IPv6 innards, etc.)
+	 *
+	 * @return True if address is reserved and may not be used
+	 */
+	inline bool isReserved() const
+	{
+		return ((! _a) || ((_a >> 32) == ZT_ADDRESS_RESERVED_PREFIX));
+	}
+
+	/**
+	 * @param i Value from 0 to 4 (inclusive)
+	 * @return Byte at said position (address interpreted in big-endian order)
+	 */
+	inline uint8_t operator[](unsigned int i) const
+	{
+		return (uint8_t)(_a >> (32 - (i * 8)));
+	}
+
+	inline void zero()
+	{
+		_a = 0;
+	}
+
+	inline bool operator==(const uint64_t& a) const
+	{
+		return (_a == (a & 0xffffffffffULL));
+	}
+	inline bool operator!=(const uint64_t& a) const
+	{
+		return (_a != (a & 0xffffffffffULL));
+	}
+	inline bool operator>(const uint64_t& a) const
+	{
+		return (_a > (a & 0xffffffffffULL));
+	}
+	inline bool operator<(const uint64_t& a) const
+	{
+		return (_a < (a & 0xffffffffffULL));
+	}
+	inline bool operator>=(const uint64_t& a) const
+	{
+		return (_a >= (a & 0xffffffffffULL));
+	}
+	inline bool operator<=(const uint64_t& a) const
+	{
+		return (_a <= (a & 0xffffffffffULL));
+	}
+
+	inline bool operator==(const Address& a) const
+	{
+		return (_a == a._a);
+	}
+	inline bool operator!=(const Address& a) const
+	{
+		return (_a != a._a);
+	}
+	inline bool operator>(const Address& a) const
+	{
+		return (_a > a._a);
+	}
+	inline bool operator<(const Address& a) const
+	{
+		return (_a < a._a);
+	}
+	inline bool operator>=(const Address& a) const
+	{
+		return (_a >= a._a);
+	}
+	inline bool operator<=(const Address& a) const
+	{
+		return (_a <= a._a);
+	}
 
   private:
-    uint64_t _a;
+	uint64_t _a;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 29 - 29
node/AtomicCounter.hpp

@@ -27,54 +27,54 @@ namespace ZeroTier {
  */
 class AtomicCounter {
   public:
-    AtomicCounter()
-    {
-        _v = 0;
-    }
+	AtomicCounter()
+	{
+		_v = 0;
+	}
 
-    inline int load() const
-    {
+	inline int load() const
+	{
 #ifdef __GNUC__
-        return __sync_or_and_fetch(const_cast<int*>(&_v), 0);
+		return __sync_or_and_fetch(const_cast<int*>(&_v), 0);
 #else
-        return _v.load();
+		return _v.load();
 #endif
-    }
+	}
 
-    inline int operator++()
-    {
+	inline int operator++()
+	{
 #ifdef __GNUC__
-        return __sync_add_and_fetch(&_v, 1);
+		return __sync_add_and_fetch(&_v, 1);
 #else
-        return ++_v;
+		return ++_v;
 #endif
-    }
+	}
 
-    inline int operator--()
-    {
+	inline int operator--()
+	{
 #ifdef __GNUC__
-        return __sync_sub_and_fetch(&_v, 1);
+		return __sync_sub_and_fetch(&_v, 1);
 #else
-        return --_v;
+		return --_v;
 #endif
-    }
+	}
 
   private:
-    AtomicCounter(const AtomicCounter&)
-    {
-    }
-    const AtomicCounter& operator=(const AtomicCounter&)
-    {
-        return *this;
-    }
+	AtomicCounter(const AtomicCounter&)
+	{
+	}
+	const AtomicCounter& operator=(const AtomicCounter&)
+	{
+		return *this;
+	}
 
 #ifdef __GNUC__
-    int _v;
+	int _v;
 #else
-    std::atomic_int _v;
+	std::atomic_int _v;
 #endif
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 1804 - 1804
node/Bond.cpp

@@ -48,2008 +48,2008 @@ std::map<std::string, std::map<std::string, SharedPtr<Link> > > Bond::_interface
 
 bool Bond::linkAllowed(std::string& policyAlias, SharedPtr<Link> link)
 {
-    if (! link) {
-        return false;
-    }
-    bool foundInDefinitions = false;
-    if (_linkDefinitions.count(policyAlias)) {
-        auto it = _linkDefinitions[policyAlias].begin();
-        while (it != _linkDefinitions[policyAlias].end()) {
-            if (link->ifname() == (*it)->ifname()) {
-                foundInDefinitions = true;
-                break;
-            }
-            ++it;
-        }
-    }
-    return _linkDefinitions[policyAlias].empty() || foundInDefinitions;
+	if (! link) {
+		return false;
+	}
+	bool foundInDefinitions = false;
+	if (_linkDefinitions.count(policyAlias)) {
+		auto it = _linkDefinitions[policyAlias].begin();
+		while (it != _linkDefinitions[policyAlias].end()) {
+			if (link->ifname() == (*it)->ifname()) {
+				foundInDefinitions = true;
+				break;
+			}
+			++it;
+		}
+	}
+	return _linkDefinitions[policyAlias].empty() || foundInDefinitions;
 }
 
 void Bond::addCustomLink(std::string& policyAlias, SharedPtr<Link> link)
 {
-    Mutex::Lock _l(_links_m);
-    _linkDefinitions[policyAlias].push_back(link);
-    auto search = _interfaceToLinkMap[policyAlias].find(link->ifname());
-    if (search == _interfaceToLinkMap[policyAlias].end()) {
-        link->setAsUserSpecified(true);
-        _interfaceToLinkMap[policyAlias].insert(std::pair<std::string, SharedPtr<Link> >(link->ifname(), link));
-    }
+	Mutex::Lock _l(_links_m);
+	_linkDefinitions[policyAlias].push_back(link);
+	auto search = _interfaceToLinkMap[policyAlias].find(link->ifname());
+	if (search == _interfaceToLinkMap[policyAlias].end()) {
+		link->setAsUserSpecified(true);
+		_interfaceToLinkMap[policyAlias].insert(std::pair<std::string, SharedPtr<Link> >(link->ifname(), link));
+	}
 }
 
 bool Bond::addCustomPolicy(const SharedPtr<Bond>& newBond)
 {
-    Mutex::Lock _l(_bonds_m);
-    if (! _bondPolicyTemplates.count(newBond->policyAlias())) {
-        _bondPolicyTemplates[newBond->policyAlias()] = newBond;
-        return true;
-    }
-    return false;
+	Mutex::Lock _l(_bonds_m);
+	if (! _bondPolicyTemplates.count(newBond->policyAlias())) {
+		_bondPolicyTemplates[newBond->policyAlias()] = newBond;
+		return true;
+	}
+	return false;
 }
 
 bool Bond::assignBondingPolicyToPeer(int64_t identity, const std::string& policyAlias)
 {
-    Mutex::Lock _l(_bonds_m);
-    if (! _policyTemplateAssignments.count(identity)) {
-        _policyTemplateAssignments[identity] = policyAlias;
-        return true;
-    }
-    return false;
+	Mutex::Lock _l(_bonds_m);
+	if (! _policyTemplateAssignments.count(identity)) {
+		_policyTemplateAssignments[identity] = policyAlias;
+		return true;
+	}
+	return false;
 }
 
 SharedPtr<Bond> Bond::getBondByPeerId(int64_t identity)
 {
-    Mutex::Lock _l(_bonds_m);
-    return _bonds.count(identity) ? _bonds[identity] : SharedPtr<Bond>();
+	Mutex::Lock _l(_bonds_m);
+	return _bonds.count(identity) ? _bonds[identity] : SharedPtr<Bond>();
 }
 
 bool Bond::setAllMtuByTuple(uint16_t mtu, const std::string& ifStr, const std::string& ipStr)
 {
-    Mutex::Lock _l(_bonds_m);
-    std::map<int64_t, SharedPtr<Bond> >::iterator bondItr = _bonds.begin();
-    bool found = false;
-    while (bondItr != _bonds.end()) {
-        if (bondItr->second->setMtuByTuple(mtu, ifStr, ipStr)) {
-            found = true;
-        }
-        ++bondItr;
-    }
-    return found;
+	Mutex::Lock _l(_bonds_m);
+	std::map<int64_t, SharedPtr<Bond> >::iterator bondItr = _bonds.begin();
+	bool found = false;
+	while (bondItr != _bonds.end()) {
+		if (bondItr->second->setMtuByTuple(mtu, ifStr, ipStr)) {
+			found = true;
+		}
+		++bondItr;
+	}
+	return found;
 }
 
 bool Bond::setMtuByTuple(uint16_t mtu, const std::string& ifStr, const std::string& ipStr)
 {
-    Mutex::Lock _lp(_paths_m);
-    bool found = false;
-    for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-        if (_paths[i].p) {
-            SharedPtr<Link> sl = getLink(_paths[i].p);
-            if (sl) {
-                if (sl->ifname() == ifStr) {
-                    char ipBuf[64] = { 0 };
-                    _paths[i].p->address().toIpString(ipBuf);
-                    std::string newString = std::string(ipBuf);
-                    if (newString == ipStr) {
-                        _paths[i].p->_mtu = mtu;
-                        found = true;
-                    }
-                }
-            }
-        }
-    }
-    return found;
+	Mutex::Lock _lp(_paths_m);
+	bool found = false;
+	for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+		if (_paths[i].p) {
+			SharedPtr<Link> sl = getLink(_paths[i].p);
+			if (sl) {
+				if (sl->ifname() == ifStr) {
+					char ipBuf[64] = { 0 };
+					_paths[i].p->address().toIpString(ipBuf);
+					std::string newString = std::string(ipBuf);
+					if (newString == ipStr) {
+						_paths[i].p->_mtu = mtu;
+						found = true;
+					}
+				}
+			}
+		}
+	}
+	return found;
 }
 
 SharedPtr<Bond> Bond::createBond(const RuntimeEnvironment* renv, const SharedPtr<Peer>& peer)
 {
-    Mutex::Lock _l(_bonds_m);
-    int64_t identity = peer->identity().address().toInt();
-    Bond* bond = nullptr;
-    if (! _bonds.count(identity)) {
-        if (! _policyTemplateAssignments.count(identity)) {
-            if (_defaultPolicy) {
-                bond = new Bond(renv, _defaultPolicy, peer);
-                bond->debug("new default bond");
-            }
-            if (! _defaultPolicy && _defaultPolicyStr.length()) {
-                bond = new Bond(renv, _bondPolicyTemplates[_defaultPolicyStr].ptr(), peer);
-                bond->debug("new default custom bond (based on %s)", bond->getPolicyStrByCode(bond->policy()).c_str());
-            }
-        }
-        else {
-            if (! _bondPolicyTemplates[_policyTemplateAssignments[identity]]) {
-                bond = new Bond(renv, _defaultPolicy, peer);
-                bond->debug("peer-specific bond, was specified as %s but the bond definition was not found, using default %s", _policyTemplateAssignments[identity].c_str(), getPolicyStrByCode(_defaultPolicy).c_str());
-            }
-            else {
-                bond = new Bond(renv, _bondPolicyTemplates[_policyTemplateAssignments[identity]].ptr(), peer);
-                bond->debug("new default bond");
-            }
-        }
-    }
-    if (bond) {
-        _bonds[identity] = bond;
-        /**
-         * Determine if user has specified anything that could affect the bonding policy's decisions
-         */
-        if (_interfaceToLinkMap.count(bond->policyAlias())) {
-            std::map<std::string, SharedPtr<Link> >::iterator it = _interfaceToLinkMap[bond->policyAlias()].begin();
-            while (it != _interfaceToLinkMap[bond->policyAlias()].end()) {
-                if (it->second->isUserSpecified()) {
-                    bond->_userHasSpecifiedLinks = true;
-                }
-                if (it->second->isUserSpecified() && it->second->primary()) {
-                    bond->_userHasSpecifiedPrimaryLink = true;
-                }
-                if (it->second->isUserSpecified() && it->second->userHasSpecifiedFailoverInstructions()) {
-                    bond->_userHasSpecifiedFailoverInstructions = true;
-                }
-                if (it->second->isUserSpecified() && (it->second->capacity() > 0)) {
-                    bond->_userHasSpecifiedLinkCapacities = true;
-                }
-                ++it;
-            }
-        }
-        bond->startBond();
-        return bond;
-    }
-    return SharedPtr<Bond>();
+	Mutex::Lock _l(_bonds_m);
+	int64_t identity = peer->identity().address().toInt();
+	Bond* bond = nullptr;
+	if (! _bonds.count(identity)) {
+		if (! _policyTemplateAssignments.count(identity)) {
+			if (_defaultPolicy) {
+				bond = new Bond(renv, _defaultPolicy, peer);
+				bond->debug("new default bond");
+			}
+			if (! _defaultPolicy && _defaultPolicyStr.length()) {
+				bond = new Bond(renv, _bondPolicyTemplates[_defaultPolicyStr].ptr(), peer);
+				bond->debug("new default custom bond (based on %s)", bond->getPolicyStrByCode(bond->policy()).c_str());
+			}
+		}
+		else {
+			if (! _bondPolicyTemplates[_policyTemplateAssignments[identity]]) {
+				bond = new Bond(renv, _defaultPolicy, peer);
+				bond->debug("peer-specific bond, was specified as %s but the bond definition was not found, using default %s", _policyTemplateAssignments[identity].c_str(), getPolicyStrByCode(_defaultPolicy).c_str());
+			}
+			else {
+				bond = new Bond(renv, _bondPolicyTemplates[_policyTemplateAssignments[identity]].ptr(), peer);
+				bond->debug("new default bond");
+			}
+		}
+	}
+	if (bond) {
+		_bonds[identity] = bond;
+		/**
+		 * Determine if user has specified anything that could affect the bonding policy's decisions
+		 */
+		if (_interfaceToLinkMap.count(bond->policyAlias())) {
+			std::map<std::string, SharedPtr<Link> >::iterator it = _interfaceToLinkMap[bond->policyAlias()].begin();
+			while (it != _interfaceToLinkMap[bond->policyAlias()].end()) {
+				if (it->second->isUserSpecified()) {
+					bond->_userHasSpecifiedLinks = true;
+				}
+				if (it->second->isUserSpecified() && it->second->primary()) {
+					bond->_userHasSpecifiedPrimaryLink = true;
+				}
+				if (it->second->isUserSpecified() && it->second->userHasSpecifiedFailoverInstructions()) {
+					bond->_userHasSpecifiedFailoverInstructions = true;
+				}
+				if (it->second->isUserSpecified() && (it->second->capacity() > 0)) {
+					bond->_userHasSpecifiedLinkCapacities = true;
+				}
+				++it;
+			}
+		}
+		bond->startBond();
+		return bond;
+	}
+	return SharedPtr<Bond>();
 }
 
 void Bond::destroyBond(uint64_t peerId)
 {
-    Mutex::Lock _l(_bonds_m);
-    auto iter = _bonds.find(peerId);
-    if (iter != _bonds.end()) {
-        iter->second->stopBond();
-        _bonds.erase(iter);
-    }
+	Mutex::Lock _l(_bonds_m);
+	auto iter = _bonds.find(peerId);
+	if (iter != _bonds.end()) {
+		iter->second->stopBond();
+		_bonds.erase(iter);
+	}
 }
 
 void Bond::stopBond()
 {
-    debug("stopping bond");
-    _run = false;
+	debug("stopping bond");
+	_run = false;
 }
 
 void Bond::startBond()
 {
-    debug("starting bond");
-    _run = true;
+	debug("starting bond");
+	_run = true;
 }
 
 SharedPtr<Link> Bond::getLinkBySocket(const std::string& policyAlias, uint64_t localSocket, bool createIfNeeded = false)
 {
-    Mutex::Lock _l(_links_m);
-    char ifname[ZT_MAX_PHYSIFNAME] = {};
-    _binder->getIfName((PhySocket*)((uintptr_t)localSocket), ifname, sizeof(ifname) - 1);
-    std::string ifnameStr(ifname);
-    auto search = _interfaceToLinkMap[policyAlias].find(ifnameStr);
-    if (search == _interfaceToLinkMap[policyAlias].end()) {
-        if (createIfNeeded) {
-            SharedPtr<Link> s = new Link(ifnameStr, 0, 0, 0, true, ZT_BOND_SLAVE_MODE_PRIMARY, "");
-            _interfaceToLinkMap[policyAlias].insert(std::pair<std::string, SharedPtr<Link> >(ifnameStr, s));
-            return s;
-        }
-        else {
-            return SharedPtr<Link>();
-        }
-    }
-    else {
-        return search->second;
-    }
+	Mutex::Lock _l(_links_m);
+	char ifname[ZT_MAX_PHYSIFNAME] = {};
+	_binder->getIfName((PhySocket*)((uintptr_t)localSocket), ifname, sizeof(ifname) - 1);
+	std::string ifnameStr(ifname);
+	auto search = _interfaceToLinkMap[policyAlias].find(ifnameStr);
+	if (search == _interfaceToLinkMap[policyAlias].end()) {
+		if (createIfNeeded) {
+			SharedPtr<Link> s = new Link(ifnameStr, 0, 0, 0, true, ZT_BOND_SLAVE_MODE_PRIMARY, "");
+			_interfaceToLinkMap[policyAlias].insert(std::pair<std::string, SharedPtr<Link> >(ifnameStr, s));
+			return s;
+		}
+		else {
+			return SharedPtr<Link>();
+		}
+	}
+	else {
+		return search->second;
+	}
 }
 
 SharedPtr<Link> Bond::getLinkByName(const std::string& policyAlias, const std::string& ifname)
 {
-    Mutex::Lock _l(_links_m);
-    auto search = _interfaceToLinkMap[policyAlias].find(ifname);
-    if (search != _interfaceToLinkMap[policyAlias].end()) {
-        return search->second;
-    }
-    return SharedPtr<Link>();
+	Mutex::Lock _l(_links_m);
+	auto search = _interfaceToLinkMap[policyAlias].find(ifname);
+	if (search != _interfaceToLinkMap[policyAlias].end()) {
+		return search->second;
+	}
+	return SharedPtr<Link>();
 }
 
 void Bond::processBackgroundTasks(void* tPtr, const int64_t now)
 {
-    unsigned long _currMinReqMonitorInterval = ZT_BOND_FAILOVER_DEFAULT_INTERVAL;
-    Mutex::Lock _l(_bonds_m);
-    std::map<int64_t, SharedPtr<Bond> >::iterator bondItr = _bonds.begin();
-    while (bondItr != _bonds.end()) {
-        // Update Bond Controller's background processing timer
-        _currMinReqMonitorInterval = std::min(_currMinReqMonitorInterval, (unsigned long)(bondItr->second->monitorInterval()));
-        bondItr->second->processBackgroundBondTasks(tPtr, now);
-        ++bondItr;
-    }
-    _minReqMonitorInterval = std::min(_currMinReqMonitorInterval, (unsigned long)ZT_BOND_FAILOVER_DEFAULT_INTERVAL);
+	unsigned long _currMinReqMonitorInterval = ZT_BOND_FAILOVER_DEFAULT_INTERVAL;
+	Mutex::Lock _l(_bonds_m);
+	std::map<int64_t, SharedPtr<Bond> >::iterator bondItr = _bonds.begin();
+	while (bondItr != _bonds.end()) {
+		// Update Bond Controller's background processing timer
+		_currMinReqMonitorInterval = std::min(_currMinReqMonitorInterval, (unsigned long)(bondItr->second->monitorInterval()));
+		bondItr->second->processBackgroundBondTasks(tPtr, now);
+		++bondItr;
+	}
+	_minReqMonitorInterval = std::min(_currMinReqMonitorInterval, (unsigned long)ZT_BOND_FAILOVER_DEFAULT_INTERVAL);
 }
 
 Bond::Bond(const RuntimeEnvironment* renv) : RR(renv)
 {
-    initTimers();
+	initTimers();
 }
 
 Bond::Bond(const RuntimeEnvironment* renv, int policy, const SharedPtr<Peer>& peer) : RR(renv), _freeRandomByte((unsigned char)((uintptr_t)this >> 4) ^ ++s_freeRandomByteCounter), _peer(peer), _peerId(_peer->_id.address().toInt())
 {
-    initTimers();
-    setBondParameters(policy, SharedPtr<Bond>(), false);
-    _policyAlias = getPolicyStrByCode(policy);
+	initTimers();
+	setBondParameters(policy, SharedPtr<Bond>(), false);
+	_policyAlias = getPolicyStrByCode(policy);
 }
 
 Bond::Bond(const RuntimeEnvironment* renv, std::string& basePolicy, std::string& policyAlias, const SharedPtr<Peer>& peer) : RR(renv), _policyAlias(policyAlias), _peer(peer)
 {
-    initTimers();
-    setBondParameters(getPolicyCodeByStr(basePolicy), SharedPtr<Bond>(), false);
+	initTimers();
+	setBondParameters(getPolicyCodeByStr(basePolicy), SharedPtr<Bond>(), false);
 }
 
 Bond::Bond(const RuntimeEnvironment* renv, SharedPtr<Bond> originalBond, const SharedPtr<Peer>& peer)
-    : RR(renv)
-    , _freeRandomByte((unsigned char)((uintptr_t)this >> 4) ^ ++s_freeRandomByteCounter)
-    , _peer(peer)
-    , _peerId(_peer->_id.address().toInt())
+	: RR(renv)
+	, _freeRandomByte((unsigned char)((uintptr_t)this >> 4) ^ ++s_freeRandomByteCounter)
+	, _peer(peer)
+	, _peerId(_peer->_id.address().toInt())
 {
-    initTimers();
-    setBondParameters(originalBond->_policy, originalBond, true);
+	initTimers();
+	setBondParameters(originalBond->_policy, originalBond, true);
 }
 
 void Bond::nominatePathToBond(const SharedPtr<Path>& path, int64_t now)
 {
-    Mutex::Lock _l(_paths_m);
-    debug("attempting to nominate link %s", pathToStr(path).c_str());
-    /**
-     * Ensure the link is allowed and the path is not already present
-     */
-    if (! RR->bc->linkAllowed(_policyAlias, getLinkBySocket(_policyAlias, path->localSocket(), true))) {
-        debug("link %s is not allowed according to user-specified rules", pathToStr(path).c_str());
-        return;
-    }
-    bool alreadyPresent = false;
-    for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-        // Sanity check
-        if (path.ptr() == _paths[i].p.ptr()) {
-            alreadyPresent = true;
-            debug("link %s already exists", pathToStr(path).c_str());
-            break;
-        }
-    }
-    if (! alreadyPresent) {
-        SharedPtr<Link> link = getLink(path);
-        if (link) {
-            std::string ifnameStr = std::string(link->ifname());
-            memset(path->_ifname, 0x0, ZT_MAX_PHYSIFNAME);
-            memcpy(path->_ifname, ifnameStr.c_str(), std::min((int)ifnameStr.length(), ZT_MAX_PHYSIFNAME));
-        }
-        /**
-         * Find somewhere to stick it
-         */
-        for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-            if (! _paths[i].p) {
-                _paths[i].set(now, path);
-                /**
-                 * Set user preferences and update state variables of other paths on the same link
-                 */
-                SharedPtr<Link> sl = getLink(_paths[i].p);
-                if (sl) {
-                    // Determine if there are any other paths on this link
-                    bool bFoundCommonLink = false;
-                    SharedPtr<Link> commonLink = RR->bc->getLinkBySocket(_policyAlias, _paths[i].p->localSocket());
-                    if (commonLink) {
-                        for (unsigned int j = 0; j < ZT_MAX_PEER_NETWORK_PATHS; ++j) {
-                            if (_paths[j].p && _paths[j].p.ptr() != _paths[i].p.ptr()) {
-                                if (RR->bc->getLinkBySocket(_policyAlias, _paths[j].p->localSocket(), true) == commonLink) {
-                                    bFoundCommonLink = true;
-                                    _paths[j].onlyPathOnLink = false;
-                                }
-                            }
-                        }
-                        _paths[i].ipvPref = sl->ipvPref();
-                        _paths[i].mode = sl->mode();
-                        _paths[i].enabled = sl->enabled();
-                        _paths[i].localPort = _phy->getLocalPort((PhySocket*)((uintptr_t)path->localSocket()));
-                        _paths[i].onlyPathOnLink = ! bFoundCommonLink;
-                    }
-                }
-                log("nominated link %s", pathToStr(path).c_str());
-                break;
-            }
-        }
-    }
-    curateBond(now, true);
-    estimatePathQuality(now);
+	Mutex::Lock _l(_paths_m);
+	debug("attempting to nominate link %s", pathToStr(path).c_str());
+	/**
+	 * Ensure the link is allowed and the path is not already present
+	 */
+	if (! RR->bc->linkAllowed(_policyAlias, getLinkBySocket(_policyAlias, path->localSocket(), true))) {
+		debug("link %s is not allowed according to user-specified rules", pathToStr(path).c_str());
+		return;
+	}
+	bool alreadyPresent = false;
+	for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+		// Sanity check
+		if (path.ptr() == _paths[i].p.ptr()) {
+			alreadyPresent = true;
+			debug("link %s already exists", pathToStr(path).c_str());
+			break;
+		}
+	}
+	if (! alreadyPresent) {
+		SharedPtr<Link> link = getLink(path);
+		if (link) {
+			std::string ifnameStr = std::string(link->ifname());
+			memset(path->_ifname, 0x0, ZT_MAX_PHYSIFNAME);
+			memcpy(path->_ifname, ifnameStr.c_str(), std::min((int)ifnameStr.length(), ZT_MAX_PHYSIFNAME));
+		}
+		/**
+		 * Find somewhere to stick it
+		 */
+		for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+			if (! _paths[i].p) {
+				_paths[i].set(now, path);
+				/**
+				 * Set user preferences and update state variables of other paths on the same link
+				 */
+				SharedPtr<Link> sl = getLink(_paths[i].p);
+				if (sl) {
+					// Determine if there are any other paths on this link
+					bool bFoundCommonLink = false;
+					SharedPtr<Link> commonLink = RR->bc->getLinkBySocket(_policyAlias, _paths[i].p->localSocket());
+					if (commonLink) {
+						for (unsigned int j = 0; j < ZT_MAX_PEER_NETWORK_PATHS; ++j) {
+							if (_paths[j].p && _paths[j].p.ptr() != _paths[i].p.ptr()) {
+								if (RR->bc->getLinkBySocket(_policyAlias, _paths[j].p->localSocket(), true) == commonLink) {
+									bFoundCommonLink = true;
+									_paths[j].onlyPathOnLink = false;
+								}
+							}
+						}
+						_paths[i].ipvPref = sl->ipvPref();
+						_paths[i].mode = sl->mode();
+						_paths[i].enabled = sl->enabled();
+						_paths[i].localPort = _phy->getLocalPort((PhySocket*)((uintptr_t)path->localSocket()));
+						_paths[i].onlyPathOnLink = ! bFoundCommonLink;
+					}
+				}
+				log("nominated link %s", pathToStr(path).c_str());
+				break;
+			}
+		}
+	}
+	curateBond(now, true);
+	estimatePathQuality(now);
 }
 
 void Bond::addPathToBond(int nominatedIdx, int bondedIdx)
 {
-    // Map bonded set to nominated set
-    _realIdxMap[bondedIdx] = nominatedIdx;
-    // Tell the bonding layer that we can now use this path for traffic
-    _paths[nominatedIdx].bonded = true;
+	// Map bonded set to nominated set
+	_realIdxMap[bondedIdx] = nominatedIdx;
+	// Tell the bonding layer that we can now use this path for traffic
+	_paths[nominatedIdx].bonded = true;
 }
 
 SharedPtr<Path> Bond::getAppropriatePath(int64_t now, int32_t flowId)
 {
-    Mutex::Lock _l(_paths_m);
-    /**
-     * active-backup
-     */
-    if (_policy == ZT_BOND_POLICY_ACTIVE_BACKUP) {
-        if (_abPathIdx != ZT_MAX_PEER_NETWORK_PATHS && _paths[_abPathIdx].p) {
-            // fprintf(stderr, "trying to send via (_abPathIdx=%d) %s\n", _abPathIdx, pathToStr(_paths[_abPathIdx].p).c_str());
-            return _paths[_abPathIdx].p;
-        }
-    }
-    /**
-     * broadcast
-     */
-    if (_policy == ZT_BOND_POLICY_BROADCAST) {
-        return SharedPtr<Path>();   // Handled in Switch::_trySend()
-    }
-    if (! _numBondedPaths) {
-        return SharedPtr<Path>();   // No paths assigned to bond yet, cannot balance traffic
-    }
-    /**
-     * balance-rr
-     */
-    if (_policy == ZT_BOND_POLICY_BALANCE_RR) {
-        if (_packetsPerLink == 0) {
-            // Randomly select a path
-            return _paths[_realIdxMap[_freeRandomByte % _numBondedPaths]].p;
-        }
-        if (_rrPacketsSentOnCurrLink < _packetsPerLink) {
-            // Continue to use this link
-            ++_rrPacketsSentOnCurrLink;
-            return _paths[_realIdxMap[_rrIdx]].p;
-        }
-        // Reset striping counter
-        _rrPacketsSentOnCurrLink = 0;
-        if (_numBondedPaths == 1 || _rrIdx >= (ZT_MAX_PEER_NETWORK_PATHS - 1)) {
-            _rrIdx = 0;
-        }
-        else {
-            int _tempIdx = _rrIdx;
-            for (int searchCount = 0; searchCount < (_numBondedPaths - 1); searchCount++) {
-                _tempIdx = (_tempIdx == (_numBondedPaths - 1)) ? 0 : _tempIdx + 1;
-                if (_realIdxMap[_tempIdx] != ZT_MAX_PEER_NETWORK_PATHS) {
-                    if (_paths[_realIdxMap[_tempIdx]].p && _paths[_realIdxMap[_tempIdx]].eligible) {
-                        _rrIdx = _tempIdx;
-                        break;
-                    }
-                }
-            }
-        }
-        if (_paths[_realIdxMap[_rrIdx]].p) {
-            return _paths[_realIdxMap[_rrIdx]].p;
-        }
-    }
-    /**
-     * balance-xor/aware
-     */
-    if (_policy == ZT_BOND_POLICY_BALANCE_XOR || _policy == ZT_BOND_POLICY_BALANCE_AWARE) {
-        if (flowId == -1) {
-            // No specific path required for unclassified traffic, send on anything
-            int m_idx = _realIdxMap[_freeRandomByte % _numBondedPaths];
-            return _paths[m_idx].p;
-        }
-        Mutex::Lock _l(_flows_m);
-        std::map<int16_t, SharedPtr<Flow> >::iterator it = _flows.find(flowId);
-        if (likely(it != _flows.end())) {
-            it->second->lastActivity = now;
-            return _paths[it->second->assignedPath].p;
-        }
-        else {
-            unsigned char entropy;
-            Utils::getSecureRandom(&entropy, 1);
-            SharedPtr<Flow> flow = createFlow(ZT_MAX_PEER_NETWORK_PATHS, flowId, entropy, now);
-            _flows[flowId] = flow;
-            return _paths[flow->assignedPath].p;
-        }
-    }
-    return SharedPtr<Path>();
+	Mutex::Lock _l(_paths_m);
+	/**
+	 * active-backup
+	 */
+	if (_policy == ZT_BOND_POLICY_ACTIVE_BACKUP) {
+		if (_abPathIdx != ZT_MAX_PEER_NETWORK_PATHS && _paths[_abPathIdx].p) {
+			// fprintf(stderr, "trying to send via (_abPathIdx=%d) %s\n", _abPathIdx, pathToStr(_paths[_abPathIdx].p).c_str());
+			return _paths[_abPathIdx].p;
+		}
+	}
+	/**
+	 * broadcast
+	 */
+	if (_policy == ZT_BOND_POLICY_BROADCAST) {
+		return SharedPtr<Path>();	// Handled in Switch::_trySend()
+	}
+	if (! _numBondedPaths) {
+		return SharedPtr<Path>();	// No paths assigned to bond yet, cannot balance traffic
+	}
+	/**
+	 * balance-rr
+	 */
+	if (_policy == ZT_BOND_POLICY_BALANCE_RR) {
+		if (_packetsPerLink == 0) {
+			// Randomly select a path
+			return _paths[_realIdxMap[_freeRandomByte % _numBondedPaths]].p;
+		}
+		if (_rrPacketsSentOnCurrLink < _packetsPerLink) {
+			// Continue to use this link
+			++_rrPacketsSentOnCurrLink;
+			return _paths[_realIdxMap[_rrIdx]].p;
+		}
+		// Reset striping counter
+		_rrPacketsSentOnCurrLink = 0;
+		if (_numBondedPaths == 1 || _rrIdx >= (ZT_MAX_PEER_NETWORK_PATHS - 1)) {
+			_rrIdx = 0;
+		}
+		else {
+			int _tempIdx = _rrIdx;
+			for (int searchCount = 0; searchCount < (_numBondedPaths - 1); searchCount++) {
+				_tempIdx = (_tempIdx == (_numBondedPaths - 1)) ? 0 : _tempIdx + 1;
+				if (_realIdxMap[_tempIdx] != ZT_MAX_PEER_NETWORK_PATHS) {
+					if (_paths[_realIdxMap[_tempIdx]].p && _paths[_realIdxMap[_tempIdx]].eligible) {
+						_rrIdx = _tempIdx;
+						break;
+					}
+				}
+			}
+		}
+		if (_paths[_realIdxMap[_rrIdx]].p) {
+			return _paths[_realIdxMap[_rrIdx]].p;
+		}
+	}
+	/**
+	 * balance-xor/aware
+	 */
+	if (_policy == ZT_BOND_POLICY_BALANCE_XOR || _policy == ZT_BOND_POLICY_BALANCE_AWARE) {
+		if (flowId == -1) {
+			// No specific path required for unclassified traffic, send on anything
+			int m_idx = _realIdxMap[_freeRandomByte % _numBondedPaths];
+			return _paths[m_idx].p;
+		}
+		Mutex::Lock _l(_flows_m);
+		std::map<int16_t, SharedPtr<Flow> >::iterator it = _flows.find(flowId);
+		if (likely(it != _flows.end())) {
+			it->second->lastActivity = now;
+			return _paths[it->second->assignedPath].p;
+		}
+		else {
+			unsigned char entropy;
+			Utils::getSecureRandom(&entropy, 1);
+			SharedPtr<Flow> flow = createFlow(ZT_MAX_PEER_NETWORK_PATHS, flowId, entropy, now);
+			_flows[flowId] = flow;
+			return _paths[flow->assignedPath].p;
+		}
+	}
+	return SharedPtr<Path>();
 }
 
 void Bond::recordIncomingInvalidPacket(const SharedPtr<Path>& path)
 {
-    Mutex::Lock _l(_paths_m);
-    for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-        if (_paths[i].p == path) {
-            //_paths[i].packetValiditySamples.push(false);
-        }
-    }
+	Mutex::Lock _l(_paths_m);
+	for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+		if (_paths[i].p == path) {
+			//_paths[i].packetValiditySamples.push(false);
+		}
+	}
 }
 
 void Bond::recordOutgoingPacket(const SharedPtr<Path>& path, uint64_t packetId, uint16_t payloadLength, const Packet::Verb verb, const int32_t flowId, int64_t now)
 {
-    _freeRandomByte += (unsigned char)(packetId >> 8);   // Grab entropy to use in path selection logic
-    bool isFrame = (verb == Packet::Packet::VERB_ECHO || verb == Packet::VERB_FRAME || verb == Packet::VERB_EXT_FRAME);
-    bool shouldRecord = (packetId & (ZT_QOS_ACK_DIVISOR - 1) && (verb != Packet::VERB_ACK) && (verb != Packet::VERB_QOS_MEASUREMENT));
-    if (isFrame || shouldRecord) {
-        Mutex::Lock _l(_paths_m);
-        int pathIdx = getNominatedPathIdx(path);
-        if (pathIdx == ZT_MAX_PEER_NETWORK_PATHS) {
-            return;
-        }
-        if (isFrame) {
-            ++(_paths[pathIdx].packetsOut);
-            _lastFrame = now;
-        }
-        if (shouldRecord) {
-            //_paths[pathIdx].expectingAckAsOf = now;
-            //_paths[pathIdx].totalBytesSentSinceLastAckReceived += payloadLength;
-            //_paths[pathIdx].unackedBytes += payloadLength;
-            if (_paths[pathIdx].qosStatsOut.size() < ZT_QOS_MAX_PENDING_RECORDS) {
-                _paths[pathIdx].qosStatsOut[packetId] = now;
-            }
-        }
-    }
-    if (flowId != ZT_QOS_NO_FLOW) {
-        Mutex::Lock _l(_flows_m);
-        if (_flows.count(flowId)) {
-            _flows[flowId]->bytesOut += payloadLength;
-        }
-    }
+	_freeRandomByte += (unsigned char)(packetId >> 8);	 // Grab entropy to use in path selection logic
+	bool isFrame = (verb == Packet::Packet::VERB_ECHO || verb == Packet::VERB_FRAME || verb == Packet::VERB_EXT_FRAME);
+	bool shouldRecord = (packetId & (ZT_QOS_ACK_DIVISOR - 1) && (verb != Packet::VERB_ACK) && (verb != Packet::VERB_QOS_MEASUREMENT));
+	if (isFrame || shouldRecord) {
+		Mutex::Lock _l(_paths_m);
+		int pathIdx = getNominatedPathIdx(path);
+		if (pathIdx == ZT_MAX_PEER_NETWORK_PATHS) {
+			return;
+		}
+		if (isFrame) {
+			++(_paths[pathIdx].packetsOut);
+			_lastFrame = now;
+		}
+		if (shouldRecord) {
+			//_paths[pathIdx].expectingAckAsOf = now;
+			//_paths[pathIdx].totalBytesSentSinceLastAckReceived += payloadLength;
+			//_paths[pathIdx].unackedBytes += payloadLength;
+			if (_paths[pathIdx].qosStatsOut.size() < ZT_QOS_MAX_PENDING_RECORDS) {
+				_paths[pathIdx].qosStatsOut[packetId] = now;
+			}
+		}
+	}
+	if (flowId != ZT_QOS_NO_FLOW) {
+		Mutex::Lock _l(_flows_m);
+		if (_flows.count(flowId)) {
+			_flows[flowId]->bytesOut += payloadLength;
+		}
+	}
 }
 
 void Bond::recordIncomingPacket(const SharedPtr<Path>& path, uint64_t packetId, uint16_t payloadLength, Packet::Verb verb, int32_t flowId, int64_t now)
 {
-    bool isFrame = (verb == Packet::Packet::VERB_ECHO || verb == Packet::VERB_FRAME || verb == Packet::VERB_EXT_FRAME);
-    bool shouldRecord = (packetId & (ZT_QOS_ACK_DIVISOR - 1) && (verb != Packet::VERB_ACK) && (verb != Packet::VERB_QOS_MEASUREMENT));
-    Mutex::Lock _l(_paths_m);
-    int pathIdx = getNominatedPathIdx(path);
-    if (pathIdx == ZT_MAX_PEER_NETWORK_PATHS) {
-        return;
-    }
-    // Take note of the time that this previously-dead path received a packet
-    if (! _paths[pathIdx].alive) {
-        _paths[pathIdx].lastAliveToggle = now;
-    }
-    if (isFrame || shouldRecord) {
-        if (_paths[pathIdx].allowed()) {
-            if (isFrame) {
-                ++(_paths[pathIdx].packetsIn);
-                _lastFrame = now;
-            }
-            if (shouldRecord) {
-                if (_paths[pathIdx].qosStatsIn.size() < ZT_QOS_MAX_PENDING_RECORDS) {
-                    // debug("Recording QoS information (table size = %d)", _paths[pathIdx].qosStatsIn.size());
-                    _paths[pathIdx].qosStatsIn[packetId] = now;
-                    ++(_paths[pathIdx].packetsReceivedSinceLastQoS);
-                    //_paths[pathIdx].packetValiditySamples.push(true);
-                }
-                else {
-                    // debug("QoS buffer full, will not record information");
-                }
-                /*
-                if (_paths[pathIdx].ackStatsIn.size() < ZT_ACK_MAX_PENDING_RECORDS) {
-                    //debug("Recording ACK information (table size = %d)", _paths[pathIdx].ackStatsIn.size());
-                    _paths[pathIdx].ackStatsIn[packetId] = payloadLength;
-                    ++(_paths[pathIdx].packetsReceivedSinceLastAck);
-                }
-                else {
-                    debug("ACK buffer full, will not record information");
-                }
-                */
-            }
-        }
-    }
-
-    /**
-     * Learn new flows and pro-actively create entries for them in the bond so
-     * that the next time we send a packet out that is part of a flow we know
-     * which path to use.
-     */
-    if ((flowId != ZT_QOS_NO_FLOW) && (_policy == ZT_BOND_POLICY_BALANCE_RR || _policy == ZT_BOND_POLICY_BALANCE_XOR || _policy == ZT_BOND_POLICY_BALANCE_AWARE)) {
-        Mutex::Lock _l(_flows_m);
-        SharedPtr<Flow> flow;
-        if (! _flows.count(flowId)) {
-            flow = createFlow(pathIdx, flowId, 0, now);
-        }
-        else {
-            flow = _flows[flowId];
-        }
-        if (flow) {
-            flow->bytesIn += payloadLength;
-        }
-    }
+	bool isFrame = (verb == Packet::Packet::VERB_ECHO || verb == Packet::VERB_FRAME || verb == Packet::VERB_EXT_FRAME);
+	bool shouldRecord = (packetId & (ZT_QOS_ACK_DIVISOR - 1) && (verb != Packet::VERB_ACK) && (verb != Packet::VERB_QOS_MEASUREMENT));
+	Mutex::Lock _l(_paths_m);
+	int pathIdx = getNominatedPathIdx(path);
+	if (pathIdx == ZT_MAX_PEER_NETWORK_PATHS) {
+		return;
+	}
+	// Take note of the time that this previously-dead path received a packet
+	if (! _paths[pathIdx].alive) {
+		_paths[pathIdx].lastAliveToggle = now;
+	}
+	if (isFrame || shouldRecord) {
+		if (_paths[pathIdx].allowed()) {
+			if (isFrame) {
+				++(_paths[pathIdx].packetsIn);
+				_lastFrame = now;
+			}
+			if (shouldRecord) {
+				if (_paths[pathIdx].qosStatsIn.size() < ZT_QOS_MAX_PENDING_RECORDS) {
+					// debug("Recording QoS information (table size = %d)", _paths[pathIdx].qosStatsIn.size());
+					_paths[pathIdx].qosStatsIn[packetId] = now;
+					++(_paths[pathIdx].packetsReceivedSinceLastQoS);
+					//_paths[pathIdx].packetValiditySamples.push(true);
+				}
+				else {
+					// debug("QoS buffer full, will not record information");
+				}
+				/*
+				if (_paths[pathIdx].ackStatsIn.size() < ZT_ACK_MAX_PENDING_RECORDS) {
+					//debug("Recording ACK information (table size = %d)", _paths[pathIdx].ackStatsIn.size());
+					_paths[pathIdx].ackStatsIn[packetId] = payloadLength;
+					++(_paths[pathIdx].packetsReceivedSinceLastAck);
+				}
+				else {
+					debug("ACK buffer full, will not record information");
+				}
+				*/
+			}
+		}
+	}
+
+	/**
+	 * Learn new flows and pro-actively create entries for them in the bond so
+	 * that the next time we send a packet out that is part of a flow we know
+	 * which path to use.
+	 */
+	if ((flowId != ZT_QOS_NO_FLOW) && (_policy == ZT_BOND_POLICY_BALANCE_RR || _policy == ZT_BOND_POLICY_BALANCE_XOR || _policy == ZT_BOND_POLICY_BALANCE_AWARE)) {
+		Mutex::Lock _l(_flows_m);
+		SharedPtr<Flow> flow;
+		if (! _flows.count(flowId)) {
+			flow = createFlow(pathIdx, flowId, 0, now);
+		}
+		else {
+			flow = _flows[flowId];
+		}
+		if (flow) {
+			flow->bytesIn += payloadLength;
+		}
+	}
 }
 
 void Bond::receivedQoS(const SharedPtr<Path>& path, int64_t now, int count, uint64_t* rx_id, uint16_t* rx_ts)
 {
-    Mutex::Lock _l(_paths_m);
-    int pathIdx = getNominatedPathIdx(path);
-    if (pathIdx == ZT_MAX_PEER_NETWORK_PATHS) {
-        return;
-    }
-    _paths[pathIdx].lastQoSReceived = now;
-    // debug("received QoS packet (sampling %d frames) via %s", count, pathToStr(path).c_str());
-    //  Look up egress times and compute latency values for each record
-    std::map<uint64_t, uint64_t>::iterator it;
-    for (int j = 0; j < count; j++) {
-        it = _paths[pathIdx].qosStatsOut.find(rx_id[j]);
-        if (it != _paths[pathIdx].qosStatsOut.end()) {
-            _paths[pathIdx].latencySamples.push(((uint16_t)(now - it->second) - rx_ts[j]) / 2);
-            // if (_paths[pathIdx].shouldAvoid) {
-            //	debug("RX sample on avoided path %d", pathIdx);
-            // }
-            _paths[pathIdx].qosStatsOut.erase(it);
-        }
-    }
-    _paths[pathIdx].qosRecordSize.push(count);
+	Mutex::Lock _l(_paths_m);
+	int pathIdx = getNominatedPathIdx(path);
+	if (pathIdx == ZT_MAX_PEER_NETWORK_PATHS) {
+		return;
+	}
+	_paths[pathIdx].lastQoSReceived = now;
+	// debug("received QoS packet (sampling %d frames) via %s", count, pathToStr(path).c_str());
+	//  Look up egress times and compute latency values for each record
+	std::map<uint64_t, uint64_t>::iterator it;
+	for (int j = 0; j < count; j++) {
+		it = _paths[pathIdx].qosStatsOut.find(rx_id[j]);
+		if (it != _paths[pathIdx].qosStatsOut.end()) {
+			_paths[pathIdx].latencySamples.push(((uint16_t)(now - it->second) - rx_ts[j]) / 2);
+			// if (_paths[pathIdx].shouldAvoid) {
+			//	debug("RX sample on avoided path %d", pathIdx);
+			// }
+			_paths[pathIdx].qosStatsOut.erase(it);
+		}
+	}
+	_paths[pathIdx].qosRecordSize.push(count);
 }
 
 void Bond::receivedAck(int pathIdx, int64_t now, int32_t ackedBytes)
 {
-    /*
-    Mutex::Lock _l(_paths_m);
-    debug("received ACK of %d bytes on path %s, there are still %d un-acked bytes", ackedBytes, pathToStr(_paths[pathIdx].p).c_str(), _paths[pathIdx].unackedBytes);
-    _paths[pathIdx].lastAckReceived = now;
-    _paths[pathIdx].unackedBytes = (ackedBytes > _paths[pathIdx].unackedBytes) ? 0 : _paths[pathIdx].unackedBytes - ackedBytes;
-    */
+	/*
+	Mutex::Lock _l(_paths_m);
+	debug("received ACK of %d bytes on path %s, there are still %d un-acked bytes", ackedBytes, pathToStr(_paths[pathIdx].p).c_str(), _paths[pathIdx].unackedBytes);
+	_paths[pathIdx].lastAckReceived = now;
+	_paths[pathIdx].unackedBytes = (ackedBytes > _paths[pathIdx].unackedBytes) ? 0 : _paths[pathIdx].unackedBytes - ackedBytes;
+	*/
 }
 
 int32_t Bond::generateQoSPacket(int pathIdx, int64_t now, char* qosBuffer)
 {
-    int32_t len = 0;
-    std::map<uint64_t, uint64_t>::iterator it = _paths[pathIdx].qosStatsIn.begin();
-    int i = 0;
-    int numRecords = std::min(_paths[pathIdx].packetsReceivedSinceLastQoS, ZT_QOS_TABLE_SIZE);
-    // debug("numRecords=%3d, packetsReceivedSinceLastQoS=%3d, _paths[pathIdx].qosStatsIn.size()=%3zu", numRecords, _paths[pathIdx].packetsReceivedSinceLastQoS, _paths[pathIdx].qosStatsIn.size());
-    while (i < numRecords && it != _paths[pathIdx].qosStatsIn.end()) {
-        uint64_t id = it->first;
-        memcpy(qosBuffer, &id, sizeof(uint64_t));
-        qosBuffer += sizeof(uint64_t);
-        uint16_t holdingTime = (uint16_t)(now - it->second);
-        memcpy(qosBuffer, &holdingTime, sizeof(uint16_t));
-        qosBuffer += sizeof(uint16_t);
-        len += sizeof(uint64_t) + sizeof(uint16_t);
-        _paths[pathIdx].qosStatsIn.erase(it++);
-        ++i;
-    }
-    return len;
+	int32_t len = 0;
+	std::map<uint64_t, uint64_t>::iterator it = _paths[pathIdx].qosStatsIn.begin();
+	int i = 0;
+	int numRecords = std::min(_paths[pathIdx].packetsReceivedSinceLastQoS, ZT_QOS_TABLE_SIZE);
+	// debug("numRecords=%3d, packetsReceivedSinceLastQoS=%3d, _paths[pathIdx].qosStatsIn.size()=%3zu", numRecords, _paths[pathIdx].packetsReceivedSinceLastQoS, _paths[pathIdx].qosStatsIn.size());
+	while (i < numRecords && it != _paths[pathIdx].qosStatsIn.end()) {
+		uint64_t id = it->first;
+		memcpy(qosBuffer, &id, sizeof(uint64_t));
+		qosBuffer += sizeof(uint64_t);
+		uint16_t holdingTime = (uint16_t)(now - it->second);
+		memcpy(qosBuffer, &holdingTime, sizeof(uint16_t));
+		qosBuffer += sizeof(uint16_t);
+		len += sizeof(uint64_t) + sizeof(uint16_t);
+		_paths[pathIdx].qosStatsIn.erase(it++);
+		++i;
+	}
+	return len;
 }
 
 bool Bond::assignFlowToBondedPath(SharedPtr<Flow>& flow, int64_t now, bool reassign = false)
 {
-    if (! _numBondedPaths) {
-        debug("unable to assign flow %x (bond has no links)", flow->id);
-        return false;
-    }
-    unsigned int bondedIdx = ZT_MAX_PEER_NETWORK_PATHS;
-    if (_policy == ZT_BOND_POLICY_BALANCE_XOR) {
-        bondedIdx = abs((int)(flow->id % _numBondedPaths));
-        flow->assignPath(_realIdxMap[bondedIdx], now);
-        ++(_paths[_realIdxMap[bondedIdx]].assignedFlowCount);
-    }
-    if (_policy == ZT_BOND_POLICY_BALANCE_AWARE) {
-        /** balance-aware generally works like balance-xor except that it will try to
-         * take into account user preferences (or default sane limits) that will discourage
-         * allocating traffic to links with a lesser perceived "quality" */
-        int offset = 0;
-        float bestQuality = 0.0;
-        int nextBestQualIdx = ZT_MAX_PEER_NETWORK_PATHS;
-
-        if (reassign) {
-            log("attempting to re-assign out-flow %04x previously on idx %d (%u / %zu flows)", flow->id, flow->assignedPath, _paths[_realIdxMap[flow->assignedPath]].assignedFlowCount, _flows.size());
-        }
-        else {
-            debug("attempting to assign flow for the first time");
-        }
-
-        unsigned char entropy;
-        Utils::getSecureRandom(&entropy, 1);
-        float randomLinkCapacity = ((float)entropy / 255.0);   // Used to random but proportional choices
-
-        while (offset < _numBondedPaths) {
-            unsigned char entropy;
-            Utils::getSecureRandom(&entropy, 1);
-
-            if (reassign) {
-                bondedIdx = (flow->assignedPath + offset) % (_numBondedPaths);
-            }
-            else {
-                bondedIdx = abs((int)((entropy + offset) % (_numBondedPaths)));
-            }
-            // debug("idx=%d, offset=%d, randomCap=%f, actualCap=%f", bondedIdx, offset, randomLinkCapacity, _paths[_realIdxMap[bondedIdx]].relativeLinkCapacity);
-            if (! _paths[_realIdxMap[bondedIdx]].p) {
-                continue;
-            }
-            if (! _paths[_realIdxMap[bondedIdx]].shouldAvoid && randomLinkCapacity <= _paths[_realIdxMap[bondedIdx]].relativeLinkCapacity) {
-                // debug("  assign out-flow %04x to link %s (%u / %zu flows)", flow->id, pathToStr(_paths[_realIdxMap[bondedIdx]].p).c_str(), _paths[_realIdxMap[bondedIdx]].assignedFlowCount, _flows.size());
-                break;   // Acceptable -- No violation of quality spec
-            }
-            if (_paths[_realIdxMap[bondedIdx]].relativeQuality > bestQuality) {
-                bestQuality = _paths[_realIdxMap[bondedIdx]].relativeQuality;
-                nextBestQualIdx = bondedIdx;
-                // debug("    recording next-best link %f idx %d", _paths[_realIdxMap[bondedIdx]].relativeQuality, bondedIdx);
-            }
-            ++offset;
-        }
-        if (offset < _numBondedPaths) {
-            // We were (able) to find a path that didn't violate any of the user's quality requirements
-            flow->assignPath(_realIdxMap[bondedIdx], now);
-            ++(_paths[_realIdxMap[bondedIdx]].assignedFlowCount);
-            // debug("       ABLE to find optimal link %f idx %d", _paths[_realIdxMap[bondedIdx]].relativeQuality, bondedIdx);
-        }
-        else {
-            // We were (unable) to find a path that didn't violate at least one quality requirement, will choose next best option
-            flow->assignPath(_realIdxMap[nextBestQualIdx], now);
-            ++(_paths[_realIdxMap[nextBestQualIdx]].assignedFlowCount);
-            // debug("       UNABLE to find, will use link %f idx %d", _paths[_realIdxMap[nextBestQualIdx]].relativeQuality, nextBestQualIdx);
-        }
-    }
-    if (_policy == ZT_BOND_POLICY_ACTIVE_BACKUP) {
-        if (_abPathIdx == ZT_MAX_PEER_NETWORK_PATHS) {
-            log("unable to assign out-flow %x (no active backup link)", flow->id);
-        }
-        flow->assignPath(_abPathIdx, now);
-    }
-    log("assign out-flow %04x to link %s (%u / %zu flows)", flow->id, pathToStr(_paths[flow->assignedPath].p).c_str(), _paths[flow->assignedPath].assignedFlowCount, _flows.size());
-    return true;
+	if (! _numBondedPaths) {
+		debug("unable to assign flow %x (bond has no links)", flow->id);
+		return false;
+	}
+	unsigned int bondedIdx = ZT_MAX_PEER_NETWORK_PATHS;
+	if (_policy == ZT_BOND_POLICY_BALANCE_XOR) {
+		bondedIdx = abs((int)(flow->id % _numBondedPaths));
+		flow->assignPath(_realIdxMap[bondedIdx], now);
+		++(_paths[_realIdxMap[bondedIdx]].assignedFlowCount);
+	}
+	if (_policy == ZT_BOND_POLICY_BALANCE_AWARE) {
+		/** balance-aware generally works like balance-xor except that it will try to
+		 * take into account user preferences (or default sane limits) that will discourage
+		 * allocating traffic to links with a lesser perceived "quality" */
+		int offset = 0;
+		float bestQuality = 0.0;
+		int nextBestQualIdx = ZT_MAX_PEER_NETWORK_PATHS;
+
+		if (reassign) {
+			log("attempting to re-assign out-flow %04x previously on idx %d (%u / %zu flows)", flow->id, flow->assignedPath, _paths[_realIdxMap[flow->assignedPath]].assignedFlowCount, _flows.size());
+		}
+		else {
+			debug("attempting to assign flow for the first time");
+		}
+
+		unsigned char entropy;
+		Utils::getSecureRandom(&entropy, 1);
+		float randomLinkCapacity = ((float)entropy / 255.0);   // Used to random but proportional choices
+
+		while (offset < _numBondedPaths) {
+			unsigned char entropy;
+			Utils::getSecureRandom(&entropy, 1);
+
+			if (reassign) {
+				bondedIdx = (flow->assignedPath + offset) % (_numBondedPaths);
+			}
+			else {
+				bondedIdx = abs((int)((entropy + offset) % (_numBondedPaths)));
+			}
+			// debug("idx=%d, offset=%d, randomCap=%f, actualCap=%f", bondedIdx, offset, randomLinkCapacity, _paths[_realIdxMap[bondedIdx]].relativeLinkCapacity);
+			if (! _paths[_realIdxMap[bondedIdx]].p) {
+				continue;
+			}
+			if (! _paths[_realIdxMap[bondedIdx]].shouldAvoid && randomLinkCapacity <= _paths[_realIdxMap[bondedIdx]].relativeLinkCapacity) {
+				// debug("  assign out-flow %04x to link %s (%u / %zu flows)", flow->id, pathToStr(_paths[_realIdxMap[bondedIdx]].p).c_str(), _paths[_realIdxMap[bondedIdx]].assignedFlowCount, _flows.size());
+				break;	 // Acceptable -- No violation of quality spec
+			}
+			if (_paths[_realIdxMap[bondedIdx]].relativeQuality > bestQuality) {
+				bestQuality = _paths[_realIdxMap[bondedIdx]].relativeQuality;
+				nextBestQualIdx = bondedIdx;
+				// debug("    recording next-best link %f idx %d", _paths[_realIdxMap[bondedIdx]].relativeQuality, bondedIdx);
+			}
+			++offset;
+		}
+		if (offset < _numBondedPaths) {
+			// We were (able) to find a path that didn't violate any of the user's quality requirements
+			flow->assignPath(_realIdxMap[bondedIdx], now);
+			++(_paths[_realIdxMap[bondedIdx]].assignedFlowCount);
+			// debug("       ABLE to find optimal link %f idx %d", _paths[_realIdxMap[bondedIdx]].relativeQuality, bondedIdx);
+		}
+		else {
+			// We were (unable) to find a path that didn't violate at least one quality requirement, will choose next best option
+			flow->assignPath(_realIdxMap[nextBestQualIdx], now);
+			++(_paths[_realIdxMap[nextBestQualIdx]].assignedFlowCount);
+			// debug("       UNABLE to find, will use link %f idx %d", _paths[_realIdxMap[nextBestQualIdx]].relativeQuality, nextBestQualIdx);
+		}
+	}
+	if (_policy == ZT_BOND_POLICY_ACTIVE_BACKUP) {
+		if (_abPathIdx == ZT_MAX_PEER_NETWORK_PATHS) {
+			log("unable to assign out-flow %x (no active backup link)", flow->id);
+		}
+		flow->assignPath(_abPathIdx, now);
+	}
+	log("assign out-flow %04x to link %s (%u / %zu flows)", flow->id, pathToStr(_paths[flow->assignedPath].p).c_str(), _paths[flow->assignedPath].assignedFlowCount, _flows.size());
+	return true;
 }
 
 SharedPtr<Bond::Flow> Bond::createFlow(int pathIdx, int32_t flowId, unsigned char entropy, int64_t now)
 {
-    if (! _numBondedPaths) {
-        debug("unable to assign flow %04x (bond has no links)", flowId);
-        return SharedPtr<Flow>();
-    }
-    if (_flows.size() >= ZT_FLOW_MAX_COUNT) {
-        debug("forget oldest flow (max flows reached: %d)", ZT_FLOW_MAX_COUNT);
-        forgetFlowsWhenNecessary(0, true, now);
-    }
-    SharedPtr<Flow> flow = new Flow(flowId, now);
-    _flows[flowId] = flow;
-    /**
-     * Add a flow with a given Path already provided. This is the case when a packet
-     * is received on a path but no flow exists, in this case we simply assign the path
-     * that the remote peer chose for us.
-     */
-    if (pathIdx != ZT_MAX_PEER_NETWORK_PATHS) {
-        flow->assignPath(pathIdx, now);
-        _paths[pathIdx].assignedFlowCount++;
-        debug("assign in-flow %04x to link %s (%u / %zu)", flow->id, pathToStr(_paths[pathIdx].p).c_str(), _paths[pathIdx].assignedFlowCount, _flows.size());
-    }
-    /**
-     * Add a flow when no path was provided. This means that it is an outgoing packet
-     * and that it is up to the local peer to decide how to load-balance its transmission.
-     */
-    else {
-        assignFlowToBondedPath(flow, now);
-    }
-    return flow;
+	if (! _numBondedPaths) {
+		debug("unable to assign flow %04x (bond has no links)", flowId);
+		return SharedPtr<Flow>();
+	}
+	if (_flows.size() >= ZT_FLOW_MAX_COUNT) {
+		debug("forget oldest flow (max flows reached: %d)", ZT_FLOW_MAX_COUNT);
+		forgetFlowsWhenNecessary(0, true, now);
+	}
+	SharedPtr<Flow> flow = new Flow(flowId, now);
+	_flows[flowId] = flow;
+	/**
+	 * Add a flow with a given Path already provided. This is the case when a packet
+	 * is received on a path but no flow exists, in this case we simply assign the path
+	 * that the remote peer chose for us.
+	 */
+	if (pathIdx != ZT_MAX_PEER_NETWORK_PATHS) {
+		flow->assignPath(pathIdx, now);
+		_paths[pathIdx].assignedFlowCount++;
+		debug("assign in-flow %04x to link %s (%u / %zu)", flow->id, pathToStr(_paths[pathIdx].p).c_str(), _paths[pathIdx].assignedFlowCount, _flows.size());
+	}
+	/**
+	 * Add a flow when no path was provided. This means that it is an outgoing packet
+	 * and that it is up to the local peer to decide how to load-balance its transmission.
+	 */
+	else {
+		assignFlowToBondedPath(flow, now);
+	}
+	return flow;
 }
 
 void Bond::forgetFlowsWhenNecessary(uint64_t age, bool oldest, int64_t now)
 {
-    std::map<int16_t, SharedPtr<Flow> >::iterator it = _flows.begin();
-    std::map<int16_t, SharedPtr<Flow> >::iterator oldestFlow = _flows.end();
-    SharedPtr<Flow> expiredFlow;
-    if (age) {   // Remove by specific age
-        while (it != _flows.end()) {
-            if (it->second->age(now) > age) {
-                debug("forget flow %04x (age %" PRId64 ") (%u / %zu)", it->first, it->second->age(now), _paths[it->second->assignedPath].assignedFlowCount, (_flows.size() - 1));
-                _paths[it->second->assignedPath].assignedFlowCount--;
-                it = _flows.erase(it);
-            }
-            else {
-                ++it;
-            }
-        }
-    }
-    else if (oldest) {   // Remove single oldest by natural expiration
-        uint64_t maxAge = 0;
-        while (it != _flows.end()) {
-            if (it->second->age(now) > maxAge) {
-                maxAge = (now - it->second->age(now));
-                oldestFlow = it;
-            }
-            ++it;
-        }
-        if (oldestFlow != _flows.end()) {
-            debug("forget oldest flow %04x (age %" PRId64 ") (total flows: %zu)", oldestFlow->first, oldestFlow->second->age(now), _flows.size() - 1);
-            _paths[oldestFlow->second->assignedPath].assignedFlowCount--;
-            _flows.erase(oldestFlow);
-        }
-    }
+	std::map<int16_t, SharedPtr<Flow> >::iterator it = _flows.begin();
+	std::map<int16_t, SharedPtr<Flow> >::iterator oldestFlow = _flows.end();
+	SharedPtr<Flow> expiredFlow;
+	if (age) {	 // Remove by specific age
+		while (it != _flows.end()) {
+			if (it->second->age(now) > age) {
+				debug("forget flow %04x (age %" PRId64 ") (%u / %zu)", it->first, it->second->age(now), _paths[it->second->assignedPath].assignedFlowCount, (_flows.size() - 1));
+				_paths[it->second->assignedPath].assignedFlowCount--;
+				it = _flows.erase(it);
+			}
+			else {
+				++it;
+			}
+		}
+	}
+	else if (oldest) {	 // Remove single oldest by natural expiration
+		uint64_t maxAge = 0;
+		while (it != _flows.end()) {
+			if (it->second->age(now) > maxAge) {
+				maxAge = (now - it->second->age(now));
+				oldestFlow = it;
+			}
+			++it;
+		}
+		if (oldestFlow != _flows.end()) {
+			debug("forget oldest flow %04x (age %" PRId64 ") (total flows: %zu)", oldestFlow->first, oldestFlow->second->age(now), _flows.size() - 1);
+			_paths[oldestFlow->second->assignedPath].assignedFlowCount--;
+			_flows.erase(oldestFlow);
+		}
+	}
 }
 
 void Bond::processIncomingPathNegotiationRequest(uint64_t now, SharedPtr<Path>& path, int16_t remoteUtility)
 {
-    char pathStr[64] = { 0 };
-    if (_abLinkSelectMethod != ZT_BOND_RESELECTION_POLICY_OPTIMIZE) {
-        return;
-    }
-    Mutex::Lock _l(_paths_m);
-    int pathIdx = getNominatedPathIdx(path);
-    if (pathIdx == ZT_MAX_PEER_NETWORK_PATHS) {
-        return;
-    }
-    _paths[pathIdx].p->address().toString(pathStr);
-    if (! _lastPathNegotiationCheck) {
-        return;
-    }
-    SharedPtr<Link> link = RR->bc->getLinkBySocket(_policyAlias, _paths[pathIdx].p->localSocket());
-    if (link) {
-        if (remoteUtility > _localUtility) {
-            _paths[pathIdx].p->address().toString(pathStr);
-            debug("peer suggests alternate link %s/%s, remote utility (%d) greater than local utility (%d), switching to suggested link\n", link->ifname().c_str(), pathStr, remoteUtility, _localUtility);
-            _negotiatedPathIdx = pathIdx;
-        }
-        if (remoteUtility < _localUtility) {
-            debug("peer suggests alternate link %s/%s, remote utility (%d) less than local utility (%d), not switching\n", link->ifname().c_str(), pathStr, remoteUtility, _localUtility);
-        }
-        if (remoteUtility == _localUtility) {
-            debug("peer suggests alternate link %s/%s, remote utility (%d) equal to local utility (%d)\n", link->ifname().c_str(), pathStr, remoteUtility, _localUtility);
-            if (_peer->_id.address().toInt() > RR->node->identity().address().toInt()) {
-                debug("agree with peer to use alternate link %s/%s\n", link->ifname().c_str(), pathStr);
-                _negotiatedPathIdx = pathIdx;
-            }
-            else {
-                debug("ignore petition from peer to use alternate link %s/%s\n", link->ifname().c_str(), pathStr);
-            }
-        }
-    }
+	char pathStr[64] = { 0 };
+	if (_abLinkSelectMethod != ZT_BOND_RESELECTION_POLICY_OPTIMIZE) {
+		return;
+	}
+	Mutex::Lock _l(_paths_m);
+	int pathIdx = getNominatedPathIdx(path);
+	if (pathIdx == ZT_MAX_PEER_NETWORK_PATHS) {
+		return;
+	}
+	_paths[pathIdx].p->address().toString(pathStr);
+	if (! _lastPathNegotiationCheck) {
+		return;
+	}
+	SharedPtr<Link> link = RR->bc->getLinkBySocket(_policyAlias, _paths[pathIdx].p->localSocket());
+	if (link) {
+		if (remoteUtility > _localUtility) {
+			_paths[pathIdx].p->address().toString(pathStr);
+			debug("peer suggests alternate link %s/%s, remote utility (%d) greater than local utility (%d), switching to suggested link\n", link->ifname().c_str(), pathStr, remoteUtility, _localUtility);
+			_negotiatedPathIdx = pathIdx;
+		}
+		if (remoteUtility < _localUtility) {
+			debug("peer suggests alternate link %s/%s, remote utility (%d) less than local utility (%d), not switching\n", link->ifname().c_str(), pathStr, remoteUtility, _localUtility);
+		}
+		if (remoteUtility == _localUtility) {
+			debug("peer suggests alternate link %s/%s, remote utility (%d) equal to local utility (%d)\n", link->ifname().c_str(), pathStr, remoteUtility, _localUtility);
+			if (_peer->_id.address().toInt() > RR->node->identity().address().toInt()) {
+				debug("agree with peer to use alternate link %s/%s\n", link->ifname().c_str(), pathStr);
+				_negotiatedPathIdx = pathIdx;
+			}
+			else {
+				debug("ignore petition from peer to use alternate link %s/%s\n", link->ifname().c_str(), pathStr);
+			}
+		}
+	}
 }
 
 void Bond::pathNegotiationCheck(void* tPtr, int64_t now)
 {
-    int maxInPathIdx = ZT_MAX_PEER_NETWORK_PATHS;
-    int maxOutPathIdx = ZT_MAX_PEER_NETWORK_PATHS;
-    uint64_t maxInCount = 0;
-    uint64_t maxOutCount = 0;
-    for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-        if (! _paths[i].p) {
-            continue;
-        }
-        if (_paths[i].packetsIn > maxInCount) {
-            maxInCount = _paths[i].packetsIn;
-            maxInPathIdx = i;
-        }
-        if (_paths[i].packetsOut > maxOutCount) {
-            maxOutCount = _paths[i].packetsOut;
-            maxOutPathIdx = i;
-        }
-        _paths[i].resetPacketCounts();
-    }
-    bool _peerLinksSynchronized = ((maxInPathIdx != ZT_MAX_PEER_NETWORK_PATHS) && (maxOutPathIdx != ZT_MAX_PEER_NETWORK_PATHS) && (maxInPathIdx != maxOutPathIdx)) ? false : true;
-    /**
-     * Determine utility and attempt to petition remote peer to switch to our chosen path
-     */
-    if (! _peerLinksSynchronized) {
-        _localUtility = _paths[maxOutPathIdx].failoverScore - _paths[maxInPathIdx].failoverScore;
-        if (_paths[maxOutPathIdx].negotiated) {
-            _localUtility -= ZT_BOND_FAILOVER_HANDICAP_NEGOTIATED;
-        }
-        if ((now - _lastSentPathNegotiationRequest) > ZT_PATH_NEGOTIATION_CUTOFF_TIME) {
-            // fprintf(stderr, "BT: (sync) it's been long enough, sending more requests.\n");
-            _numSentPathNegotiationRequests = 0;
-        }
-        if (_numSentPathNegotiationRequests < ZT_PATH_NEGOTIATION_TRY_COUNT) {
-            if (_localUtility >= 0) {
-                // fprintf(stderr, "BT: (sync) paths appear to be out of sync (utility=%d)\n", _localUtility);
-                sendPATH_NEGOTIATION_REQUEST(tPtr, _paths[maxOutPathIdx].p);
-                ++_numSentPathNegotiationRequests;
-                _lastSentPathNegotiationRequest = now;
-                // fprintf(stderr, "sending request to use %s on %s, ls=%llx, utility=%d\n", pathStr, link->ifname().c_str(), _paths[maxOutPathIdx].p->localSocket(), _localUtility);
-            }
-        }
-        /**
-         * Give up negotiating and consider switching
-         */
-        else if ((now - _lastSentPathNegotiationRequest) > (2 * ZT_BOND_OPTIMIZE_INTERVAL)) {
-            if (_localUtility == 0) {
-                // There's no loss to us, just switch without sending a another request
-                // fprintf(stderr, "BT: (sync) giving up, switching to remote peer's path.\n");
-                _negotiatedPathIdx = maxInPathIdx;
-            }
-        }
-    }
+	int maxInPathIdx = ZT_MAX_PEER_NETWORK_PATHS;
+	int maxOutPathIdx = ZT_MAX_PEER_NETWORK_PATHS;
+	uint64_t maxInCount = 0;
+	uint64_t maxOutCount = 0;
+	for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+		if (! _paths[i].p) {
+			continue;
+		}
+		if (_paths[i].packetsIn > maxInCount) {
+			maxInCount = _paths[i].packetsIn;
+			maxInPathIdx = i;
+		}
+		if (_paths[i].packetsOut > maxOutCount) {
+			maxOutCount = _paths[i].packetsOut;
+			maxOutPathIdx = i;
+		}
+		_paths[i].resetPacketCounts();
+	}
+	bool _peerLinksSynchronized = ((maxInPathIdx != ZT_MAX_PEER_NETWORK_PATHS) && (maxOutPathIdx != ZT_MAX_PEER_NETWORK_PATHS) && (maxInPathIdx != maxOutPathIdx)) ? false : true;
+	/**
+	 * Determine utility and attempt to petition remote peer to switch to our chosen path
+	 */
+	if (! _peerLinksSynchronized) {
+		_localUtility = _paths[maxOutPathIdx].failoverScore - _paths[maxInPathIdx].failoverScore;
+		if (_paths[maxOutPathIdx].negotiated) {
+			_localUtility -= ZT_BOND_FAILOVER_HANDICAP_NEGOTIATED;
+		}
+		if ((now - _lastSentPathNegotiationRequest) > ZT_PATH_NEGOTIATION_CUTOFF_TIME) {
+			// fprintf(stderr, "BT: (sync) it's been long enough, sending more requests.\n");
+			_numSentPathNegotiationRequests = 0;
+		}
+		if (_numSentPathNegotiationRequests < ZT_PATH_NEGOTIATION_TRY_COUNT) {
+			if (_localUtility >= 0) {
+				// fprintf(stderr, "BT: (sync) paths appear to be out of sync (utility=%d)\n", _localUtility);
+				sendPATH_NEGOTIATION_REQUEST(tPtr, _paths[maxOutPathIdx].p);
+				++_numSentPathNegotiationRequests;
+				_lastSentPathNegotiationRequest = now;
+				// fprintf(stderr, "sending request to use %s on %s, ls=%llx, utility=%d\n", pathStr, link->ifname().c_str(), _paths[maxOutPathIdx].p->localSocket(), _localUtility);
+			}
+		}
+		/**
+		 * Give up negotiating and consider switching
+		 */
+		else if ((now - _lastSentPathNegotiationRequest) > (2 * ZT_BOND_OPTIMIZE_INTERVAL)) {
+			if (_localUtility == 0) {
+				// There's no loss to us, just switch without sending a another request
+				// fprintf(stderr, "BT: (sync) giving up, switching to remote peer's path.\n");
+				_negotiatedPathIdx = maxInPathIdx;
+			}
+		}
+	}
 }
 
 void Bond::sendPATH_NEGOTIATION_REQUEST(void* tPtr, int pathIdx)
 {
-    debug("send link negotiation request to peer via link %s, local utility is %d", pathToStr(_paths[pathIdx].p).c_str(), _localUtility);
-    if (_abLinkSelectMethod != ZT_BOND_RESELECTION_POLICY_OPTIMIZE) {
-        return;
-    }
-    Packet outp(_peer->_id.address(), RR->identity.address(), Packet::VERB_PATH_NEGOTIATION_REQUEST);
-    outp.append<int16_t>(_localUtility);
-    if (_paths[pathIdx].p->address()) {
-        Metrics::pkt_path_negotiation_request_out++;
-        outp.armor(_peer->key(), true, false, _peer->aesKeysIfSupported(), _peer->identity());
-        RR->node->putPacket(tPtr, _paths[pathIdx].p->localSocket(), _paths[pathIdx].p->address(), outp.data(), outp.size());
-        _overheadBytes += outp.size();
-    }
+	debug("send link negotiation request to peer via link %s, local utility is %d", pathToStr(_paths[pathIdx].p).c_str(), _localUtility);
+	if (_abLinkSelectMethod != ZT_BOND_RESELECTION_POLICY_OPTIMIZE) {
+		return;
+	}
+	Packet outp(_peer->_id.address(), RR->identity.address(), Packet::VERB_PATH_NEGOTIATION_REQUEST);
+	outp.append<int16_t>(_localUtility);
+	if (_paths[pathIdx].p->address()) {
+		Metrics::pkt_path_negotiation_request_out++;
+		outp.armor(_peer->key(), true, false, _peer->aesKeysIfSupported(), _peer->identity());
+		RR->node->putPacket(tPtr, _paths[pathIdx].p->localSocket(), _paths[pathIdx].p->address(), outp.data(), outp.size());
+		_overheadBytes += outp.size();
+	}
 }
 
 void Bond::sendACK(void* tPtr, int pathIdx, int64_t localSocket, const InetAddress& atAddress, int64_t now)
 {
-    /*
-    Packet outp(_peer->_id.address(), RR->identity.address(), Packet::VERB_ACK);
-    int32_t bytesToAck = 0;
-    std::map<uint64_t, uint64_t>::iterator it = _paths[pathIdx].ackStatsIn.begin();
-    while (it != _paths[pathIdx].ackStatsIn.end()) {
-        bytesToAck += it->second;
-        ++it;
-    }
-    debug("sending ACK of %d bytes on path %s (table size = %zu)", bytesToAck, pathToStr(_paths[pathIdx].p).c_str(), _paths[pathIdx].ackStatsIn.size());
-    outp.append<uint32_t>(bytesToAck);
-    if (atAddress) {
-        outp.armor(_peer->key(), false, _peer->aesKeysIfSupported());
-        RR->node->putPacket(tPtr, localSocket, atAddress, outp.data(), outp.size());
-    }
-    else {
-        RR->sw->send(tPtr, outp, false);
-    }
-    _paths[pathIdx].ackStatsIn.clear();
-    _paths[pathIdx].packetsReceivedSinceLastAck = 0;
-    _paths[pathIdx].lastAckSent = now;
-    */
+	/*
+	Packet outp(_peer->_id.address(), RR->identity.address(), Packet::VERB_ACK);
+	int32_t bytesToAck = 0;
+	std::map<uint64_t, uint64_t>::iterator it = _paths[pathIdx].ackStatsIn.begin();
+	while (it != _paths[pathIdx].ackStatsIn.end()) {
+		bytesToAck += it->second;
+		++it;
+	}
+	debug("sending ACK of %d bytes on path %s (table size = %zu)", bytesToAck, pathToStr(_paths[pathIdx].p).c_str(), _paths[pathIdx].ackStatsIn.size());
+	outp.append<uint32_t>(bytesToAck);
+	if (atAddress) {
+		outp.armor(_peer->key(), false, _peer->aesKeysIfSupported());
+		RR->node->putPacket(tPtr, localSocket, atAddress, outp.data(), outp.size());
+	}
+	else {
+		RR->sw->send(tPtr, outp, false);
+	}
+	_paths[pathIdx].ackStatsIn.clear();
+	_paths[pathIdx].packetsReceivedSinceLastAck = 0;
+	_paths[pathIdx].lastAckSent = now;
+	*/
 }
 
 void Bond::sendQOS_MEASUREMENT(void* tPtr, int pathIdx, int64_t localSocket, const InetAddress& atAddress, int64_t now)
 {
-    int64_t _now = RR->node->now();
-    Packet outp(_peer->_id.address(), RR->identity.address(), Packet::VERB_QOS_MEASUREMENT);
-    char qosData[ZT_QOS_MAX_PACKET_SIZE];
-    int16_t len = generateQoSPacket(pathIdx, _now, qosData);
-    if (len) {
-        // debug("sending QOS via link %s (len=%d)", pathToStr(_paths[pathIdx].p).c_str(), len);
-        outp.append(qosData, len);
-        if (atAddress) {
-            outp.armor(_peer->key(), true, false, _peer->aesKeysIfSupported(), _peer->identity());
-            RR->node->putPacket(tPtr, localSocket, atAddress, outp.data(), outp.size());
-        }
-        else {
-            RR->sw->send(tPtr, outp, false);
-        }
-        Metrics::pkt_qos_out++;
-        _paths[pathIdx].packetsReceivedSinceLastQoS = 0;
-        _paths[pathIdx].lastQoSMeasurement = now;
-        _overheadBytes += outp.size();
-    }
+	int64_t _now = RR->node->now();
+	Packet outp(_peer->_id.address(), RR->identity.address(), Packet::VERB_QOS_MEASUREMENT);
+	char qosData[ZT_QOS_MAX_PACKET_SIZE];
+	int16_t len = generateQoSPacket(pathIdx, _now, qosData);
+	if (len) {
+		// debug("sending QOS via link %s (len=%d)", pathToStr(_paths[pathIdx].p).c_str(), len);
+		outp.append(qosData, len);
+		if (atAddress) {
+			outp.armor(_peer->key(), true, false, _peer->aesKeysIfSupported(), _peer->identity());
+			RR->node->putPacket(tPtr, localSocket, atAddress, outp.data(), outp.size());
+		}
+		else {
+			RR->sw->send(tPtr, outp, false);
+		}
+		Metrics::pkt_qos_out++;
+		_paths[pathIdx].packetsReceivedSinceLastQoS = 0;
+		_paths[pathIdx].lastQoSMeasurement = now;
+		_overheadBytes += outp.size();
+	}
 }
 
 void Bond::processBackgroundBondTasks(void* tPtr, int64_t now)
 {
-    if (! _run) {
-        return;
-    }
-    if (! _peer->_localMultipathSupported || (now - _lastBackgroundTaskCheck) < ZT_BOND_BACKGROUND_TASK_MIN_INTERVAL) {
-        return;
-    }
-    _lastBackgroundTaskCheck = now;
-    Mutex::Lock _l(_paths_m);
-
-    curateBond(now, false);
-    if ((now - _lastQualityEstimation) > _qualityEstimationInterval) {
-        _lastQualityEstimation = now;
-        estimatePathQuality(now);
-    }
-    dumpInfo(now, false);
-
-    // Send ambient monitoring traffic
-    for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-        if (_paths[i].p && _paths[i].allowed()) {
-            if (_isLeaf) {
-                if ((_monitorInterval > 0) && (((now - _paths[i].p->_lastIn) >= (_paths[i].alive ? _monitorInterval : _failoverInterval)))) {
-                    if ((_peer->remoteVersionProtocol() >= 5) && (! ((_peer->remoteVersionMajor() == 1) && (_peer->remoteVersionMinor() == 1) && (_peer->remoteVersionRevision() == 0)))) {
-                        Packet outp(_peer->address(), RR->identity.address(), Packet::VERB_ECHO);   // ECHO (this is our bond's heartbeat)
-                        outp.armor(_peer->key(), true, false, _peer->aesKeysIfSupported(), _peer->identity());
-                        RR->node->expectReplyTo(outp.packetId());
-                        RR->node->putPacket(tPtr, _paths[i].p->localSocket(), _paths[i].p->address(), outp.data(), outp.size());
-                        _paths[i].p->_lastOut = now;
-                        _overheadBytes += outp.size();
-                        Metrics::pkt_echo_out++;
-                        // debug("tx: verb 0x%-2x of len %4d via %s (ECHO)", Packet::VERB_ECHO, outp.size(), pathToStr(_paths[i].p).c_str());
-                    }
-                }
-                // QOS
-                if (_paths[i].needsToSendQoS(now, _qosSendInterval)) {
-                    sendQOS_MEASUREMENT(tPtr, i, _paths[i].p->localSocket(), _paths[i].p->address(), now);
-                }
-                // ACK
-                /*
-                if (_paths[i].needsToSendAck(now, _ackSendInterval)) {
-                    sendACK(tPtr, i, _paths[i].p->localSocket(), _paths[i].p->address(), now);
-                }
-                */
-            }
-        }
-    }
-    // Perform periodic background tasks unique to each bonding policy
-    switch (_policy) {
-        case ZT_BOND_POLICY_ACTIVE_BACKUP:
-            processActiveBackupTasks(tPtr, now);
-            break;
-        case ZT_BOND_POLICY_BROADCAST:
-            break;
-        case ZT_BOND_POLICY_BALANCE_RR:
-        case ZT_BOND_POLICY_BALANCE_XOR:
-        case ZT_BOND_POLICY_BALANCE_AWARE:
-            processBalanceTasks(now);
-            break;
-        default:
-            break;
-    }
-    // Check whether or not a path negotiation needs to be performed
-    if (((now - _lastPathNegotiationCheck) > ZT_BOND_OPTIMIZE_INTERVAL) && _allowPathNegotiation) {
-        _lastPathNegotiationCheck = now;
-        pathNegotiationCheck(tPtr, now);
-    }
+	if (! _run) {
+		return;
+	}
+	if (! _peer->_localMultipathSupported || (now - _lastBackgroundTaskCheck) < ZT_BOND_BACKGROUND_TASK_MIN_INTERVAL) {
+		return;
+	}
+	_lastBackgroundTaskCheck = now;
+	Mutex::Lock _l(_paths_m);
+
+	curateBond(now, false);
+	if ((now - _lastQualityEstimation) > _qualityEstimationInterval) {
+		_lastQualityEstimation = now;
+		estimatePathQuality(now);
+	}
+	dumpInfo(now, false);
+
+	// Send ambient monitoring traffic
+	for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+		if (_paths[i].p && _paths[i].allowed()) {
+			if (_isLeaf) {
+				if ((_monitorInterval > 0) && (((now - _paths[i].p->_lastIn) >= (_paths[i].alive ? _monitorInterval : _failoverInterval)))) {
+					if ((_peer->remoteVersionProtocol() >= 5) && (! ((_peer->remoteVersionMajor() == 1) && (_peer->remoteVersionMinor() == 1) && (_peer->remoteVersionRevision() == 0)))) {
+						Packet outp(_peer->address(), RR->identity.address(), Packet::VERB_ECHO);	// ECHO (this is our bond's heartbeat)
+						outp.armor(_peer->key(), true, false, _peer->aesKeysIfSupported(), _peer->identity());
+						RR->node->expectReplyTo(outp.packetId());
+						RR->node->putPacket(tPtr, _paths[i].p->localSocket(), _paths[i].p->address(), outp.data(), outp.size());
+						_paths[i].p->_lastOut = now;
+						_overheadBytes += outp.size();
+						Metrics::pkt_echo_out++;
+						// debug("tx: verb 0x%-2x of len %4d via %s (ECHO)", Packet::VERB_ECHO, outp.size(), pathToStr(_paths[i].p).c_str());
+					}
+				}
+				// QOS
+				if (_paths[i].needsToSendQoS(now, _qosSendInterval)) {
+					sendQOS_MEASUREMENT(tPtr, i, _paths[i].p->localSocket(), _paths[i].p->address(), now);
+				}
+				// ACK
+				/*
+				if (_paths[i].needsToSendAck(now, _ackSendInterval)) {
+					sendACK(tPtr, i, _paths[i].p->localSocket(), _paths[i].p->address(), now);
+				}
+				*/
+			}
+		}
+	}
+	// Perform periodic background tasks unique to each bonding policy
+	switch (_policy) {
+		case ZT_BOND_POLICY_ACTIVE_BACKUP:
+			processActiveBackupTasks(tPtr, now);
+			break;
+		case ZT_BOND_POLICY_BROADCAST:
+			break;
+		case ZT_BOND_POLICY_BALANCE_RR:
+		case ZT_BOND_POLICY_BALANCE_XOR:
+		case ZT_BOND_POLICY_BALANCE_AWARE:
+			processBalanceTasks(now);
+			break;
+		default:
+			break;
+	}
+	// Check whether or not a path negotiation needs to be performed
+	if (((now - _lastPathNegotiationCheck) > ZT_BOND_OPTIMIZE_INTERVAL) && _allowPathNegotiation) {
+		_lastPathNegotiationCheck = now;
+		pathNegotiationCheck(tPtr, now);
+	}
 }
 
 void Bond::curateBond(int64_t now, bool rebuildBond)
 {
-    uint8_t tmpNumAliveLinks = 0;
-    uint8_t tmpNumTotalLinks = 0;
-
-    /**
-     * Update path state variables. State variables are used so that critical
-     * blocks that perform fast packet processing won't need to make as many
-     * function calls or computations.
-     */
-    for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-        if (! _paths[i].p) {
-            continue;
-        }
-
-        // Whether this path is still in its trial period
-        bool inTrial = (now - _paths[i].whenNominated) < ZT_BOND_OPTIMIZE_INTERVAL;
-
-        /**
-         * Remove expired or invalid links from bond
-         */
-        SharedPtr<Link> link = getLink(_paths[i].p);
-        if (! link) {
-            log("link is no longer valid, removing from bond");
-            _paths[i].p->_valid = false;
-            _paths[i] = NominatedPath();
-            _paths[i].p = SharedPtr<Path>();
-            continue;
-        }
-        if ((now - _paths[i].lastEligibility) > (ZT_PEER_EXPIRED_PATH_TRIAL_PERIOD) && ! inTrial) {
-            log("link (%s) has expired or is invalid, removing from bond", pathToStr(_paths[i].p).c_str());
-            _paths[i] = NominatedPath();
-            _paths[i].p = SharedPtr<Path>();
-            continue;
-        }
-
-        tmpNumTotalLinks++;
-        if (_paths[i].eligible) {
-            tmpNumAliveLinks++;
-        }
-
-        /**
-         * Determine aliveness
-         */
-        _paths[i].alive = _isLeaf ? (now - _paths[i].p->_lastIn) < _failoverInterval : (now - _paths[i].p->_lastIn) < ZT_PEER_PATH_EXPIRATION;
-
-        /**
-         * Determine current eligibility
-         */
-        bool currEligibility = false;
-        // Simple RX age (driven by packets of any type and gratuitous VERB_HELLOs)
-        bool acceptableAge = _isLeaf ? (_paths[i].p->age(now) < (_failoverInterval + _downDelay)) : _paths[i].alive;
-        // Whether we've waited long enough since the link last came online
-        bool satisfiedUpDelay = (now - _paths[i].lastAliveToggle) >= _upDelay;
-        // How long since the last QoS was received (Must be less than ZT_PEER_PATH_EXPIRATION since the remote peer's _qosSendInterval isn't known)
-        bool acceptableQoSAge = (_paths[i].lastQoSReceived == 0 && inTrial) || ((now - _paths[i].lastQoSReceived) < ZT_PEER_EXPIRED_PATH_TRIAL_PERIOD);
-
-        // Allow active-backup to operate without the receipt of QoS records
-        // This may be expanded to the other modes as an option
-        if (_policy == ZT_BOND_POLICY_ACTIVE_BACKUP) {
-            acceptableQoSAge = true;
-        }
-
-        currEligibility = _paths[i].allowed() && ((acceptableAge && satisfiedUpDelay && acceptableQoSAge) || inTrial);
-
-        if (currEligibility) {
-            _paths[i].lastEligibility = now;
-        }
-
-        /**
-         * Note eligibility state change (if any) and take appropriate action
-         */
-        if (currEligibility != _paths[i].eligible) {
-            if (currEligibility == 0) {
-                log("link %s is no longer eligible (reason: allowed=%d, age=%d, ud=%d, qos=%d, trial=%d)", pathToStr(_paths[i].p).c_str(), _paths[i].allowed(), acceptableAge, satisfiedUpDelay, acceptableQoSAge, inTrial);
-            }
-            if (currEligibility == 1) {
-                log("link %s is eligible", pathToStr(_paths[i].p).c_str());
-            }
-            dumpPathStatus(now, i);
-            if (currEligibility) {
-                rebuildBond = true;
-            }
-            if (! currEligibility) {
-                _paths[i].adjustRefractoryPeriod(now, _defaultPathRefractoryPeriod, ! currEligibility);
-                if (_paths[i].bonded) {
-                    debug("link %s was bonded, flow reallocation will occur soon", pathToStr(_paths[i].p).c_str());
-                    rebuildBond = true;
-                    _paths[i].shouldAvoid = true;
-                    _paths[i].bonded = false;
-                }
-            }
-        }
-        if (currEligibility) {
-            _paths[i].adjustRefractoryPeriod(now, _defaultPathRefractoryPeriod, false);
-        }
-        _paths[i].eligible = currEligibility;
-    }
-
-    /**
-     * Trigger status report if number of links change
-     */
-    _numAliveLinks = tmpNumAliveLinks;
-    _numTotalLinks = tmpNumTotalLinks;
-    if ((_numAliveLinks != tmpNumAliveLinks) || (_numTotalLinks != tmpNumTotalLinks)) {
-        dumpInfo(now, true);
-    }
-
-    /**
-     * Check for failure of (all) primary links and inform bond to use spares if present
-     */
-    bool foundUsablePrimaryPath = false;
-    for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-        // debug("[%d], bonded=%d, alive=%d", i, _paths[i].bonded , _paths[i].alive);
-        if (_paths[i].p && _paths[i].bonded && _paths[i].alive) {
-            foundUsablePrimaryPath = true;
-        }
-    }
-    rebuildBond = rebuildBond ? true : ! foundUsablePrimaryPath;
-
-    /**
-     * Curate the set of paths that are part of the bond proper. Select a set of paths
-     * per logical link according to eligibility and user-specified constraints.
-     */
-    int updatedBondedPathCount = 0;
-    if ((_policy == ZT_BOND_POLICY_BALANCE_RR) || (_policy == ZT_BOND_POLICY_BALANCE_XOR) || (_policy == ZT_BOND_POLICY_BALANCE_AWARE)) {
-        if (! _numBondedPaths) {
-            rebuildBond = true;
-        }
-        if (rebuildBond) {
-            // Clear previous bonded index mapping
-            for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-                _realIdxMap[i] = ZT_MAX_PEER_NETWORK_PATHS;
-                _paths[i].bonded = false;
-            }
-
-            // Build map associating paths with local physical links. Will be selected from in next step
-            std::map<SharedPtr<Link>, std::vector<int> > linkMap;
-            for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-                if (_paths[i].p) {
-                    SharedPtr<Link> link = RR->bc->getLinkBySocket(_policyAlias, _paths[i].p->localSocket());
-                    if (link) {
-                        linkMap[link].push_back(i);
-                    }
-                }
-            }
-            // Re-form bond from link<->path map
-            std::map<SharedPtr<Link>, std::vector<int> >::iterator it = linkMap.begin();
-            while (it != linkMap.end()) {
-                SharedPtr<Link> link = it->first;
-
-                // Bond a spare link if required (no viable primary links left)
-                if (! foundUsablePrimaryPath) {
-                    // debug("no usable primary links remain, will attempt to use spare if available");
-                    for (int j = 0; j < it->second.size(); j++) {
-                        int idx = it->second.at(j);
-                        if (! _paths[idx].p || ! _paths[idx].eligible || ! _paths[idx].allowed() || ! _paths[idx].isSpare()) {
-                            continue;
-                        }
-                        addPathToBond(idx, updatedBondedPathCount);
-                        ++updatedBondedPathCount;
-                        debug("add %s (spare)", pathToStr(_paths[idx].p).c_str());
-                    }
-                }
-
-                int ipvPref = link->ipvPref();
-
-                // If user has no address type preference, then use every path we find on a link
-                if (ipvPref == 0) {
-                    for (int j = 0; j < it->second.size(); j++) {
-                        int idx = it->second.at(j);
-                        if (! _paths[idx].p || ! _paths[idx].eligible || ! _paths[idx].allowed() || _paths[idx].isSpare()) {
-                            continue;
-                        }
-                        addPathToBond(idx, updatedBondedPathCount);
-                        ++updatedBondedPathCount;
-                        debug("add %s (no user addr preference)", pathToStr(_paths[idx].p).c_str());
-                    }
-                }
-                // If the user prefers to only use one address type (IPv4 or IPv6)
-                if (ipvPref == 4 || ipvPref == 6) {
-                    for (int j = 0; j < it->second.size(); j++) {
-                        int idx = it->second.at(j);
-                        if (! _paths[idx].p || ! _paths[idx].eligible || _paths[idx].isSpare()) {
-                            continue;
-                        }
-                        if (! _paths[idx].allowed()) {
-                            debug("did not add %s (user addr preference %d)", pathToStr(_paths[idx].p).c_str(), ipvPref);
-                            continue;
-                        }
-                        addPathToBond(idx, updatedBondedPathCount);
-                        ++updatedBondedPathCount;
-                        debug("add path %s (user addr preference %d)", pathToStr(_paths[idx].p).c_str(), ipvPref);
-                    }
-                }
-                // If the users prefers one address type to another, try to find at least
-                // one path of that type before considering others.
-                if (ipvPref == 46 || ipvPref == 64) {
-                    bool foundPreferredPath = false;
-                    // Search for preferred paths
-                    for (int j = 0; j < it->second.size(); j++) {
-                        int idx = it->second.at(j);
-                        if (! _paths[idx].p || ! _paths[idx].eligible || ! _paths[idx].allowed() || _paths[idx].isSpare()) {
-                            continue;
-                        }
-                        if (_paths[idx].preferred()) {
-                            addPathToBond(idx, updatedBondedPathCount);
-                            ++updatedBondedPathCount;
-                            debug("add %s (user addr preference %d)", pathToStr(_paths[idx].p).c_str(), ipvPref);
-                            foundPreferredPath = true;
-                        }
-                    }
-                    // Unable to find a path that matches user preference, settle for another address type
-                    if (! foundPreferredPath) {
-                        debug("did not find first-choice path type on link %s (user preference %d)", link->ifname().c_str(), ipvPref);
-                        for (int j = 0; j < it->second.size(); j++) {
-                            int idx = it->second.at(j);
-                            if (! _paths[idx].p || ! _paths[idx].eligible || _paths[idx].isSpare()) {
-                                continue;
-                            }
-                            addPathToBond(idx, updatedBondedPathCount);
-                            ++updatedBondedPathCount;
-                            debug("add %s (user addr preference %d)", pathToStr(_paths[idx].p).c_str(), ipvPref);
-                            foundPreferredPath = true;
-                        }
-                    }
-                }
-                ++it;   // Next link
-            }
-            _numBondedPaths = updatedBondedPathCount;
-            if (_policy == ZT_BOND_POLICY_BALANCE_RR) {
-                // Cause a RR reset since the current index might no longer be valid
-                _rrPacketsSentOnCurrLink = _packetsPerLink;
-                _rrIdx = 0;
-            }
-        }
-    }
-    if (_policy == ZT_BOND_POLICY_ACTIVE_BACKUP) {
-        for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-            if (_paths[i].p && _paths[i].bonded) {
-                updatedBondedPathCount++;
-            }
-        }
-        _numBondedPaths = updatedBondedPathCount;
-    }
+	uint8_t tmpNumAliveLinks = 0;
+	uint8_t tmpNumTotalLinks = 0;
+
+	/**
+	 * Update path state variables. State variables are used so that critical
+	 * blocks that perform fast packet processing won't need to make as many
+	 * function calls or computations.
+	 */
+	for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+		if (! _paths[i].p) {
+			continue;
+		}
+
+		// Whether this path is still in its trial period
+		bool inTrial = (now - _paths[i].whenNominated) < ZT_BOND_OPTIMIZE_INTERVAL;
+
+		/**
+		 * Remove expired or invalid links from bond
+		 */
+		SharedPtr<Link> link = getLink(_paths[i].p);
+		if (! link) {
+			log("link is no longer valid, removing from bond");
+			_paths[i].p->_valid = false;
+			_paths[i] = NominatedPath();
+			_paths[i].p = SharedPtr<Path>();
+			continue;
+		}
+		if ((now - _paths[i].lastEligibility) > (ZT_PEER_EXPIRED_PATH_TRIAL_PERIOD) && ! inTrial) {
+			log("link (%s) has expired or is invalid, removing from bond", pathToStr(_paths[i].p).c_str());
+			_paths[i] = NominatedPath();
+			_paths[i].p = SharedPtr<Path>();
+			continue;
+		}
+
+		tmpNumTotalLinks++;
+		if (_paths[i].eligible) {
+			tmpNumAliveLinks++;
+		}
+
+		/**
+		 * Determine aliveness
+		 */
+		_paths[i].alive = _isLeaf ? (now - _paths[i].p->_lastIn) < _failoverInterval : (now - _paths[i].p->_lastIn) < ZT_PEER_PATH_EXPIRATION;
+
+		/**
+		 * Determine current eligibility
+		 */
+		bool currEligibility = false;
+		// Simple RX age (driven by packets of any type and gratuitous VERB_HELLOs)
+		bool acceptableAge = _isLeaf ? (_paths[i].p->age(now) < (_failoverInterval + _downDelay)) : _paths[i].alive;
+		// Whether we've waited long enough since the link last came online
+		bool satisfiedUpDelay = (now - _paths[i].lastAliveToggle) >= _upDelay;
+		// How long since the last QoS was received (Must be less than ZT_PEER_PATH_EXPIRATION since the remote peer's _qosSendInterval isn't known)
+		bool acceptableQoSAge = (_paths[i].lastQoSReceived == 0 && inTrial) || ((now - _paths[i].lastQoSReceived) < ZT_PEER_EXPIRED_PATH_TRIAL_PERIOD);
+
+		// Allow active-backup to operate without the receipt of QoS records
+		// This may be expanded to the other modes as an option
+		if (_policy == ZT_BOND_POLICY_ACTIVE_BACKUP) {
+			acceptableQoSAge = true;
+		}
+
+		currEligibility = _paths[i].allowed() && ((acceptableAge && satisfiedUpDelay && acceptableQoSAge) || inTrial);
+
+		if (currEligibility) {
+			_paths[i].lastEligibility = now;
+		}
+
+		/**
+		 * Note eligibility state change (if any) and take appropriate action
+		 */
+		if (currEligibility != _paths[i].eligible) {
+			if (currEligibility == 0) {
+				log("link %s is no longer eligible (reason: allowed=%d, age=%d, ud=%d, qos=%d, trial=%d)", pathToStr(_paths[i].p).c_str(), _paths[i].allowed(), acceptableAge, satisfiedUpDelay, acceptableQoSAge, inTrial);
+			}
+			if (currEligibility == 1) {
+				log("link %s is eligible", pathToStr(_paths[i].p).c_str());
+			}
+			dumpPathStatus(now, i);
+			if (currEligibility) {
+				rebuildBond = true;
+			}
+			if (! currEligibility) {
+				_paths[i].adjustRefractoryPeriod(now, _defaultPathRefractoryPeriod, ! currEligibility);
+				if (_paths[i].bonded) {
+					debug("link %s was bonded, flow reallocation will occur soon", pathToStr(_paths[i].p).c_str());
+					rebuildBond = true;
+					_paths[i].shouldAvoid = true;
+					_paths[i].bonded = false;
+				}
+			}
+		}
+		if (currEligibility) {
+			_paths[i].adjustRefractoryPeriod(now, _defaultPathRefractoryPeriod, false);
+		}
+		_paths[i].eligible = currEligibility;
+	}
+
+	/**
+	 * Trigger status report if number of links change
+	 */
+	_numAliveLinks = tmpNumAliveLinks;
+	_numTotalLinks = tmpNumTotalLinks;
+	if ((_numAliveLinks != tmpNumAliveLinks) || (_numTotalLinks != tmpNumTotalLinks)) {
+		dumpInfo(now, true);
+	}
+
+	/**
+	 * Check for failure of (all) primary links and inform bond to use spares if present
+	 */
+	bool foundUsablePrimaryPath = false;
+	for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+		// debug("[%d], bonded=%d, alive=%d", i, _paths[i].bonded , _paths[i].alive);
+		if (_paths[i].p && _paths[i].bonded && _paths[i].alive) {
+			foundUsablePrimaryPath = true;
+		}
+	}
+	rebuildBond = rebuildBond ? true : ! foundUsablePrimaryPath;
+
+	/**
+	 * Curate the set of paths that are part of the bond proper. Select a set of paths
+	 * per logical link according to eligibility and user-specified constraints.
+	 */
+	int updatedBondedPathCount = 0;
+	if ((_policy == ZT_BOND_POLICY_BALANCE_RR) || (_policy == ZT_BOND_POLICY_BALANCE_XOR) || (_policy == ZT_BOND_POLICY_BALANCE_AWARE)) {
+		if (! _numBondedPaths) {
+			rebuildBond = true;
+		}
+		if (rebuildBond) {
+			// Clear previous bonded index mapping
+			for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+				_realIdxMap[i] = ZT_MAX_PEER_NETWORK_PATHS;
+				_paths[i].bonded = false;
+			}
+
+			// Build map associating paths with local physical links. Will be selected from in next step
+			std::map<SharedPtr<Link>, std::vector<int> > linkMap;
+			for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+				if (_paths[i].p) {
+					SharedPtr<Link> link = RR->bc->getLinkBySocket(_policyAlias, _paths[i].p->localSocket());
+					if (link) {
+						linkMap[link].push_back(i);
+					}
+				}
+			}
+			// Re-form bond from link<->path map
+			std::map<SharedPtr<Link>, std::vector<int> >::iterator it = linkMap.begin();
+			while (it != linkMap.end()) {
+				SharedPtr<Link> link = it->first;
+
+				// Bond a spare link if required (no viable primary links left)
+				if (! foundUsablePrimaryPath) {
+					// debug("no usable primary links remain, will attempt to use spare if available");
+					for (int j = 0; j < it->second.size(); j++) {
+						int idx = it->second.at(j);
+						if (! _paths[idx].p || ! _paths[idx].eligible || ! _paths[idx].allowed() || ! _paths[idx].isSpare()) {
+							continue;
+						}
+						addPathToBond(idx, updatedBondedPathCount);
+						++updatedBondedPathCount;
+						debug("add %s (spare)", pathToStr(_paths[idx].p).c_str());
+					}
+				}
+
+				int ipvPref = link->ipvPref();
+
+				// If user has no address type preference, then use every path we find on a link
+				if (ipvPref == 0) {
+					for (int j = 0; j < it->second.size(); j++) {
+						int idx = it->second.at(j);
+						if (! _paths[idx].p || ! _paths[idx].eligible || ! _paths[idx].allowed() || _paths[idx].isSpare()) {
+							continue;
+						}
+						addPathToBond(idx, updatedBondedPathCount);
+						++updatedBondedPathCount;
+						debug("add %s (no user addr preference)", pathToStr(_paths[idx].p).c_str());
+					}
+				}
+				// If the user prefers to only use one address type (IPv4 or IPv6)
+				if (ipvPref == 4 || ipvPref == 6) {
+					for (int j = 0; j < it->second.size(); j++) {
+						int idx = it->second.at(j);
+						if (! _paths[idx].p || ! _paths[idx].eligible || _paths[idx].isSpare()) {
+							continue;
+						}
+						if (! _paths[idx].allowed()) {
+							debug("did not add %s (user addr preference %d)", pathToStr(_paths[idx].p).c_str(), ipvPref);
+							continue;
+						}
+						addPathToBond(idx, updatedBondedPathCount);
+						++updatedBondedPathCount;
+						debug("add path %s (user addr preference %d)", pathToStr(_paths[idx].p).c_str(), ipvPref);
+					}
+				}
+				// If the users prefers one address type to another, try to find at least
+				// one path of that type before considering others.
+				if (ipvPref == 46 || ipvPref == 64) {
+					bool foundPreferredPath = false;
+					// Search for preferred paths
+					for (int j = 0; j < it->second.size(); j++) {
+						int idx = it->second.at(j);
+						if (! _paths[idx].p || ! _paths[idx].eligible || ! _paths[idx].allowed() || _paths[idx].isSpare()) {
+							continue;
+						}
+						if (_paths[idx].preferred()) {
+							addPathToBond(idx, updatedBondedPathCount);
+							++updatedBondedPathCount;
+							debug("add %s (user addr preference %d)", pathToStr(_paths[idx].p).c_str(), ipvPref);
+							foundPreferredPath = true;
+						}
+					}
+					// Unable to find a path that matches user preference, settle for another address type
+					if (! foundPreferredPath) {
+						debug("did not find first-choice path type on link %s (user preference %d)", link->ifname().c_str(), ipvPref);
+						for (int j = 0; j < it->second.size(); j++) {
+							int idx = it->second.at(j);
+							if (! _paths[idx].p || ! _paths[idx].eligible || _paths[idx].isSpare()) {
+								continue;
+							}
+							addPathToBond(idx, updatedBondedPathCount);
+							++updatedBondedPathCount;
+							debug("add %s (user addr preference %d)", pathToStr(_paths[idx].p).c_str(), ipvPref);
+							foundPreferredPath = true;
+						}
+					}
+				}
+				++it;	// Next link
+			}
+			_numBondedPaths = updatedBondedPathCount;
+			if (_policy == ZT_BOND_POLICY_BALANCE_RR) {
+				// Cause a RR reset since the current index might no longer be valid
+				_rrPacketsSentOnCurrLink = _packetsPerLink;
+				_rrIdx = 0;
+			}
+		}
+	}
+	if (_policy == ZT_BOND_POLICY_ACTIVE_BACKUP) {
+		for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+			if (_paths[i].p && _paths[i].bonded) {
+				updatedBondedPathCount++;
+			}
+		}
+		_numBondedPaths = updatedBondedPathCount;
+	}
 }
 
 void Bond::estimatePathQuality(int64_t now)
 {
-    float lat[ZT_MAX_PEER_NETWORK_PATHS] = { 0 };
-    float pdv[ZT_MAX_PEER_NETWORK_PATHS] = { 0 };
-    float plr[ZT_MAX_PEER_NETWORK_PATHS] = { 0 };
-    float per[ZT_MAX_PEER_NETWORK_PATHS] = { 0 };
-
-    float maxLAT = 0;
-    float maxPDV = 0;
-    float maxPLR = 0;
-    float maxPER = 0;
-
-    float absoluteQuality[ZT_MAX_PEER_NETWORK_PATHS] = { 0 };
-
-    float totQuality = 0.0f;
-
-    // Process observation samples, compute summary statistics, and compute relative link qualities
-    for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-        if (! _paths[i].p || ! _paths[i].allowed()) {
-            continue;
-        }
-        // Drain unacknowledged QoS records
-        int qosRecordTimeout = (_qosSendInterval * 3);
-        std::map<uint64_t, uint64_t>::iterator it = _paths[i].qosStatsOut.begin();
-        int numDroppedQosOutRecords = 0;
-        while (it != _paths[i].qosStatsOut.end()) {
-            if ((now - it->second) >= qosRecordTimeout) {
-                it = _paths[i].qosStatsOut.erase(it);
-                ++numDroppedQosOutRecords;
-            }
-            else {
-                ++it;
-            }
-        }
-        if (numDroppedQosOutRecords) {
-            // debug("dropped %d QOS out-records", numDroppedQosOutRecords);
-        }
-
-        /*
-        for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-            if (! _paths[i].p) {
-                continue;
-            }
-            // if ((now - _paths[i].lastAckReceived) > ackSendInterval) {
-            //	debug("been a while since ACK");
-            //	if (_paths[i].unackedBytes > 0) {
-            //		_paths[i].unackedBytes / _paths[i].bytesSen
-            //	}
-            // }
-        }
-        */
-
-        it = _paths[i].qosStatsIn.begin();
-        int numDroppedQosInRecords = 0;
-        while (it != _paths[i].qosStatsIn.end()) {
-            if ((now - it->second) >= qosRecordTimeout) {
-                it = _paths[i].qosStatsIn.erase(it);
-                ++numDroppedQosInRecords;
-            }
-            else {
-                ++it;
-            }
-        }
-        if (numDroppedQosInRecords) {
-            // debug("dropped %d QOS in-records", numDroppedQosInRecords);
-        }
-
-        absoluteQuality[i] = 0;
-        totQuality = 0;
-        // Normalize raw observations according to sane limits and/or user specified values
-        lat[i] = 1.0 / expf(4 * Utils::normalize(_paths[i].latency, 0, _qw[ZT_QOS_LAT_MAX_IDX], 0, 1));
-        pdv[i] = 1.0 / expf(4 * Utils::normalize(_paths[i].latencyVariance, 0, _qw[ZT_QOS_PDV_MAX_IDX], 0, 1));
-        plr[i] = 1.0 / expf(4 * Utils::normalize(_paths[i].packetLossRatio, 0, _qw[ZT_QOS_PLR_MAX_IDX], 0, 1));
-        per[i] = 1.0 / expf(4 * Utils::normalize(_paths[i].packetErrorRatio, 0, _qw[ZT_QOS_PER_MAX_IDX], 0, 1));
-        // Record bond-wide maximums to determine relative values
-        maxLAT = lat[i] > maxLAT ? lat[i] : maxLAT;
-        maxPDV = pdv[i] > maxPDV ? pdv[i] : maxPDV;
-        maxPLR = plr[i] > maxPLR ? plr[i] : maxPLR;
-        maxPER = per[i] > maxPER ? per[i] : maxPER;
-    }
-
-    // Compute relative user-specified link capacities (may change during life of Bond)
-    int maxObservedLinkCap = 0;
-    // Find current maximum
-    for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-        if (_paths[i].p && _paths[i].allowed()) {
-            SharedPtr<Link> link = RR->bc->getLinkBySocket(_policyAlias, _paths[i].p->localSocket());
-            if (link) {
-                int linkSpeed = link->capacity();
-                _paths[i].p->_givenLinkSpeed = linkSpeed;
-                _paths[i].p->_mtu = link->mtu() ? link->mtu() : _paths[i].p->_mtu;
-                _paths[i].p->_assignedFlowCount = _paths[i].assignedFlowCount;
-                maxObservedLinkCap = linkSpeed > maxObservedLinkCap ? linkSpeed : maxObservedLinkCap;
-            }
-        }
-    }
-    // Compute relative link capacity (Used for weighting traffic allocations)
-    for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-        if (_paths[i].p && _paths[i].allowed()) {
-            SharedPtr<Link> link = RR->bc->getLinkBySocket(_policyAlias, _paths[i].p->localSocket());
-            if (link) {
-                float relativeCapacity = (link->capacity() / (float)maxObservedLinkCap);
-                link->setRelativeCapacity(relativeCapacity);
-                _paths[i].relativeLinkCapacity = relativeCapacity;
-            }
-        }
-    }
-
-    // Convert metrics to relative quantities and apply contribution weights
-    for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-        if (_paths[i].p && _paths[i].bonded) {
-            absoluteQuality[i] += ((maxLAT > 0.0f ? lat[i] / maxLAT : 0.0f) * _qw[ZT_QOS_LAT_WEIGHT_IDX]);
-            absoluteQuality[i] += ((maxPDV > 0.0f ? pdv[i] / maxPDV : 0.0f) * _qw[ZT_QOS_PDV_WEIGHT_IDX]);
-            absoluteQuality[i] += ((maxPLR > 0.0f ? plr[i] / maxPLR : 0.0f) * _qw[ZT_QOS_PLR_WEIGHT_IDX]);
-            absoluteQuality[i] += ((maxPER > 0.0f ? per[i] / maxPER : 0.0f) * _qw[ZT_QOS_PER_WEIGHT_IDX]);
-            absoluteQuality[i] *= _paths[i].relativeLinkCapacity;
-            totQuality += absoluteQuality[i];
-        }
-    }
-
-    // Compute quality of link relative to all others in the bond (also accounting for stated link capacity)
-    if (totQuality > 0.0) {
-        for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-            if (_paths[i].p && _paths[i].bonded) {
-                _paths[i].relativeQuality = absoluteQuality[i] / totQuality;
-                // debug("[%2d], abs=%f, tot=%f, rel=%f, relcap=%f", i, absoluteQuality[i], totQuality, _paths[i].relativeQuality, _paths[i].relativeLinkCapacity);
-            }
-        }
-    }
-
-    // Compute summary statistics
-    for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-        if (! _paths[i].p || ! _paths[i].allowed()) {
-            continue;
-        }
-        // Compute/Smooth average of real-world observations
-        if (_paths[i].latencySamples.count() >= ZT_QOS_SHORTTERM_SAMPLE_WIN_MIN_REQ_SIZE) {
-            _paths[i].latency = _paths[i].latencySamples.mean();
-        }
-        if (_paths[i].latencySamples.count() >= ZT_QOS_SHORTTERM_SAMPLE_WIN_MIN_REQ_SIZE) {
-            _paths[i].latencyVariance = _paths[i].latencySamples.stddev();
-        }
-
-        // Write values to external path object so that it can be propagated to the user
-        _paths[i].p->_latencyMean = _paths[i].latency;
-        _paths[i].p->_latencyVariance = _paths[i].latencyVariance;
-        _paths[i].p->_packetLossRatio = _paths[i].packetLossRatio;
-        _paths[i].p->_packetErrorRatio = _paths[i].packetErrorRatio;
-        _paths[i].p->_bonded = _paths[i].bonded;
-        _paths[i].p->_eligible = _paths[i].eligible;
-        //_paths[i].packetErrorRatio = 1.0 - (_paths[i].packetValiditySamples.count() ? _paths[i].packetValiditySamples.mean() : 1.0);
-        // _valid is written elsewhere
-        _paths[i].p->_relativeQuality = _paths[i].relativeQuality;
-        _paths[i].p->_localPort = _paths[i].localPort;
-    }
-
-    // Flag links for avoidance
-    for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-        if (! _paths[i].p || ! _paths[i].allowed()) {
-            continue;
-        }
-        bool shouldAvoid = false;
-        if (! _paths[i].shouldAvoid) {
-            if (_paths[i].latency > _qw[ZT_QOS_LAT_MAX_IDX]) {
-                log("avoiding link %s because (lat %6.4f > %6.4f)", pathToStr(_paths[i].p).c_str(), _paths[i].latency, _qw[ZT_QOS_LAT_MAX_IDX]);
-                shouldAvoid = true;
-            }
-            if (_paths[i].latencyVariance > _qw[ZT_QOS_PDV_MAX_IDX]) {
-                log("avoiding link %s because (pdv %6.4f > %6.4f)", pathToStr(_paths[i].p).c_str(), _paths[i].latencyVariance, _qw[ZT_QOS_PDV_MAX_IDX]);
-                shouldAvoid = true;
-            }
-            if (_paths[i].packetErrorRatio > _qw[ZT_QOS_PER_MAX_IDX]) {
-                log("avoiding link %s because (per %6.4f > %6.4f)", pathToStr(_paths[i].p).c_str(), _paths[i].packetErrorRatio, _qw[ZT_QOS_PER_MAX_IDX]);
-                shouldAvoid = true;
-            }
-            if (_paths[i].packetLossRatio > _qw[ZT_QOS_PLR_MAX_IDX]) {
-                log("avoiding link %s because (plr %6.4f > %6.4f)", pathToStr(_paths[i].p).c_str(), _paths[i].packetLossRatio, _qw[ZT_QOS_PLR_MAX_IDX]);
-                shouldAvoid = true;
-            }
-            _paths[i].shouldAvoid = shouldAvoid;
-        }
-        else {
-            if (! shouldAvoid) {
-                log("no longer avoiding link %s", pathToStr(_paths[i].p).c_str());
-                _paths[i].shouldAvoid = false;
-            }
-        }
-    }
+	float lat[ZT_MAX_PEER_NETWORK_PATHS] = { 0 };
+	float pdv[ZT_MAX_PEER_NETWORK_PATHS] = { 0 };
+	float plr[ZT_MAX_PEER_NETWORK_PATHS] = { 0 };
+	float per[ZT_MAX_PEER_NETWORK_PATHS] = { 0 };
+
+	float maxLAT = 0;
+	float maxPDV = 0;
+	float maxPLR = 0;
+	float maxPER = 0;
+
+	float absoluteQuality[ZT_MAX_PEER_NETWORK_PATHS] = { 0 };
+
+	float totQuality = 0.0f;
+
+	// Process observation samples, compute summary statistics, and compute relative link qualities
+	for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+		if (! _paths[i].p || ! _paths[i].allowed()) {
+			continue;
+		}
+		// Drain unacknowledged QoS records
+		int qosRecordTimeout = (_qosSendInterval * 3);
+		std::map<uint64_t, uint64_t>::iterator it = _paths[i].qosStatsOut.begin();
+		int numDroppedQosOutRecords = 0;
+		while (it != _paths[i].qosStatsOut.end()) {
+			if ((now - it->second) >= qosRecordTimeout) {
+				it = _paths[i].qosStatsOut.erase(it);
+				++numDroppedQosOutRecords;
+			}
+			else {
+				++it;
+			}
+		}
+		if (numDroppedQosOutRecords) {
+			// debug("dropped %d QOS out-records", numDroppedQosOutRecords);
+		}
+
+		/*
+		for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+			if (! _paths[i].p) {
+				continue;
+			}
+			// if ((now - _paths[i].lastAckReceived) > ackSendInterval) {
+			//	debug("been a while since ACK");
+			//	if (_paths[i].unackedBytes > 0) {
+			//		_paths[i].unackedBytes / _paths[i].bytesSen
+			//	}
+			// }
+		}
+		*/
+
+		it = _paths[i].qosStatsIn.begin();
+		int numDroppedQosInRecords = 0;
+		while (it != _paths[i].qosStatsIn.end()) {
+			if ((now - it->second) >= qosRecordTimeout) {
+				it = _paths[i].qosStatsIn.erase(it);
+				++numDroppedQosInRecords;
+			}
+			else {
+				++it;
+			}
+		}
+		if (numDroppedQosInRecords) {
+			// debug("dropped %d QOS in-records", numDroppedQosInRecords);
+		}
+
+		absoluteQuality[i] = 0;
+		totQuality = 0;
+		// Normalize raw observations according to sane limits and/or user specified values
+		lat[i] = 1.0 / expf(4 * Utils::normalize(_paths[i].latency, 0, _qw[ZT_QOS_LAT_MAX_IDX], 0, 1));
+		pdv[i] = 1.0 / expf(4 * Utils::normalize(_paths[i].latencyVariance, 0, _qw[ZT_QOS_PDV_MAX_IDX], 0, 1));
+		plr[i] = 1.0 / expf(4 * Utils::normalize(_paths[i].packetLossRatio, 0, _qw[ZT_QOS_PLR_MAX_IDX], 0, 1));
+		per[i] = 1.0 / expf(4 * Utils::normalize(_paths[i].packetErrorRatio, 0, _qw[ZT_QOS_PER_MAX_IDX], 0, 1));
+		// Record bond-wide maximums to determine relative values
+		maxLAT = lat[i] > maxLAT ? lat[i] : maxLAT;
+		maxPDV = pdv[i] > maxPDV ? pdv[i] : maxPDV;
+		maxPLR = plr[i] > maxPLR ? plr[i] : maxPLR;
+		maxPER = per[i] > maxPER ? per[i] : maxPER;
+	}
+
+	// Compute relative user-specified link capacities (may change during life of Bond)
+	int maxObservedLinkCap = 0;
+	// Find current maximum
+	for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+		if (_paths[i].p && _paths[i].allowed()) {
+			SharedPtr<Link> link = RR->bc->getLinkBySocket(_policyAlias, _paths[i].p->localSocket());
+			if (link) {
+				int linkSpeed = link->capacity();
+				_paths[i].p->_givenLinkSpeed = linkSpeed;
+				_paths[i].p->_mtu = link->mtu() ? link->mtu() : _paths[i].p->_mtu;
+				_paths[i].p->_assignedFlowCount = _paths[i].assignedFlowCount;
+				maxObservedLinkCap = linkSpeed > maxObservedLinkCap ? linkSpeed : maxObservedLinkCap;
+			}
+		}
+	}
+	// Compute relative link capacity (Used for weighting traffic allocations)
+	for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+		if (_paths[i].p && _paths[i].allowed()) {
+			SharedPtr<Link> link = RR->bc->getLinkBySocket(_policyAlias, _paths[i].p->localSocket());
+			if (link) {
+				float relativeCapacity = (link->capacity() / (float)maxObservedLinkCap);
+				link->setRelativeCapacity(relativeCapacity);
+				_paths[i].relativeLinkCapacity = relativeCapacity;
+			}
+		}
+	}
+
+	// Convert metrics to relative quantities and apply contribution weights
+	for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+		if (_paths[i].p && _paths[i].bonded) {
+			absoluteQuality[i] += ((maxLAT > 0.0f ? lat[i] / maxLAT : 0.0f) * _qw[ZT_QOS_LAT_WEIGHT_IDX]);
+			absoluteQuality[i] += ((maxPDV > 0.0f ? pdv[i] / maxPDV : 0.0f) * _qw[ZT_QOS_PDV_WEIGHT_IDX]);
+			absoluteQuality[i] += ((maxPLR > 0.0f ? plr[i] / maxPLR : 0.0f) * _qw[ZT_QOS_PLR_WEIGHT_IDX]);
+			absoluteQuality[i] += ((maxPER > 0.0f ? per[i] / maxPER : 0.0f) * _qw[ZT_QOS_PER_WEIGHT_IDX]);
+			absoluteQuality[i] *= _paths[i].relativeLinkCapacity;
+			totQuality += absoluteQuality[i];
+		}
+	}
+
+	// Compute quality of link relative to all others in the bond (also accounting for stated link capacity)
+	if (totQuality > 0.0) {
+		for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+			if (_paths[i].p && _paths[i].bonded) {
+				_paths[i].relativeQuality = absoluteQuality[i] / totQuality;
+				// debug("[%2d], abs=%f, tot=%f, rel=%f, relcap=%f", i, absoluteQuality[i], totQuality, _paths[i].relativeQuality, _paths[i].relativeLinkCapacity);
+			}
+		}
+	}
+
+	// Compute summary statistics
+	for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+		if (! _paths[i].p || ! _paths[i].allowed()) {
+			continue;
+		}
+		// Compute/Smooth average of real-world observations
+		if (_paths[i].latencySamples.count() >= ZT_QOS_SHORTTERM_SAMPLE_WIN_MIN_REQ_SIZE) {
+			_paths[i].latency = _paths[i].latencySamples.mean();
+		}
+		if (_paths[i].latencySamples.count() >= ZT_QOS_SHORTTERM_SAMPLE_WIN_MIN_REQ_SIZE) {
+			_paths[i].latencyVariance = _paths[i].latencySamples.stddev();
+		}
+
+		// Write values to external path object so that it can be propagated to the user
+		_paths[i].p->_latencyMean = _paths[i].latency;
+		_paths[i].p->_latencyVariance = _paths[i].latencyVariance;
+		_paths[i].p->_packetLossRatio = _paths[i].packetLossRatio;
+		_paths[i].p->_packetErrorRatio = _paths[i].packetErrorRatio;
+		_paths[i].p->_bonded = _paths[i].bonded;
+		_paths[i].p->_eligible = _paths[i].eligible;
+		//_paths[i].packetErrorRatio = 1.0 - (_paths[i].packetValiditySamples.count() ? _paths[i].packetValiditySamples.mean() : 1.0);
+		// _valid is written elsewhere
+		_paths[i].p->_relativeQuality = _paths[i].relativeQuality;
+		_paths[i].p->_localPort = _paths[i].localPort;
+	}
+
+	// Flag links for avoidance
+	for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+		if (! _paths[i].p || ! _paths[i].allowed()) {
+			continue;
+		}
+		bool shouldAvoid = false;
+		if (! _paths[i].shouldAvoid) {
+			if (_paths[i].latency > _qw[ZT_QOS_LAT_MAX_IDX]) {
+				log("avoiding link %s because (lat %6.4f > %6.4f)", pathToStr(_paths[i].p).c_str(), _paths[i].latency, _qw[ZT_QOS_LAT_MAX_IDX]);
+				shouldAvoid = true;
+			}
+			if (_paths[i].latencyVariance > _qw[ZT_QOS_PDV_MAX_IDX]) {
+				log("avoiding link %s because (pdv %6.4f > %6.4f)", pathToStr(_paths[i].p).c_str(), _paths[i].latencyVariance, _qw[ZT_QOS_PDV_MAX_IDX]);
+				shouldAvoid = true;
+			}
+			if (_paths[i].packetErrorRatio > _qw[ZT_QOS_PER_MAX_IDX]) {
+				log("avoiding link %s because (per %6.4f > %6.4f)", pathToStr(_paths[i].p).c_str(), _paths[i].packetErrorRatio, _qw[ZT_QOS_PER_MAX_IDX]);
+				shouldAvoid = true;
+			}
+			if (_paths[i].packetLossRatio > _qw[ZT_QOS_PLR_MAX_IDX]) {
+				log("avoiding link %s because (plr %6.4f > %6.4f)", pathToStr(_paths[i].p).c_str(), _paths[i].packetLossRatio, _qw[ZT_QOS_PLR_MAX_IDX]);
+				shouldAvoid = true;
+			}
+			_paths[i].shouldAvoid = shouldAvoid;
+		}
+		else {
+			if (! shouldAvoid) {
+				log("no longer avoiding link %s", pathToStr(_paths[i].p).c_str());
+				_paths[i].shouldAvoid = false;
+			}
+		}
+	}
 }
 
 void Bond::processBalanceTasks(int64_t now)
 {
-    if (! _numBondedPaths) {
-        return;
-    }
-    /**
-     * Clean up and reset flows if necessary
-     */
-    if ((now - _lastFlowExpirationCheck) > ZT_PEER_PATH_EXPIRATION) {
-        Mutex::Lock _l(_flows_m);
-        forgetFlowsWhenNecessary(ZT_PEER_PATH_EXPIRATION, false, now);
-        std::map<int16_t, SharedPtr<Flow> >::iterator it = _flows.begin();
-        while (it != _flows.end()) {
-            it->second->resetByteCounts();
-            ++it;
-        }
-        _lastFlowExpirationCheck = now;
-    }
-    /**
-     * Move (all) flows from dead paths
-     */
-    if (_policy == ZT_BOND_POLICY_BALANCE_XOR || _policy == ZT_BOND_POLICY_BALANCE_AWARE) {
-        Mutex::Lock _l(_flows_m);
-        std::map<int16_t, SharedPtr<Flow> >::iterator flow_it = _flows.begin();
-        while (flow_it != _flows.end()) {
-            if (_paths[flow_it->second->assignedPath].p) {
-                int originalPathIdx = flow_it->second->assignedPath;
-                if (! _paths[originalPathIdx].eligible) {
-                    log("moving all flows from dead link %s", pathToStr(_paths[originalPathIdx].p).c_str());
-                    if (assignFlowToBondedPath(flow_it->second, now, true)) {
-                        _paths[originalPathIdx].assignedFlowCount--;
-                    }
-                }
-            }
-            ++flow_it;
-        }
-    }
-    /**
-     * Move (some) flows from low quality paths
-     */
-    if (_policy == ZT_BOND_POLICY_BALANCE_AWARE) {
-        Mutex::Lock _l(_flows_m);
-        std::map<int16_t, SharedPtr<Flow> >::iterator flow_it = _flows.begin();
-        while (flow_it != _flows.end()) {
-            if (_paths[flow_it->second->assignedPath].p) {
-                int originalPathIdx = flow_it->second->assignedPath;
-                if (_paths[originalPathIdx].shouldAvoid) {
-                    if (assignFlowToBondedPath(flow_it->second, now, true)) {
-                        _paths[originalPathIdx].assignedFlowCount--;
-                        return;   // Only move one flow at a time
-                    }
-                }
-            }
-            ++flow_it;
-        }
-    }
+	if (! _numBondedPaths) {
+		return;
+	}
+	/**
+	 * Clean up and reset flows if necessary
+	 */
+	if ((now - _lastFlowExpirationCheck) > ZT_PEER_PATH_EXPIRATION) {
+		Mutex::Lock _l(_flows_m);
+		forgetFlowsWhenNecessary(ZT_PEER_PATH_EXPIRATION, false, now);
+		std::map<int16_t, SharedPtr<Flow> >::iterator it = _flows.begin();
+		while (it != _flows.end()) {
+			it->second->resetByteCounts();
+			++it;
+		}
+		_lastFlowExpirationCheck = now;
+	}
+	/**
+	 * Move (all) flows from dead paths
+	 */
+	if (_policy == ZT_BOND_POLICY_BALANCE_XOR || _policy == ZT_BOND_POLICY_BALANCE_AWARE) {
+		Mutex::Lock _l(_flows_m);
+		std::map<int16_t, SharedPtr<Flow> >::iterator flow_it = _flows.begin();
+		while (flow_it != _flows.end()) {
+			if (_paths[flow_it->second->assignedPath].p) {
+				int originalPathIdx = flow_it->second->assignedPath;
+				if (! _paths[originalPathIdx].eligible) {
+					log("moving all flows from dead link %s", pathToStr(_paths[originalPathIdx].p).c_str());
+					if (assignFlowToBondedPath(flow_it->second, now, true)) {
+						_paths[originalPathIdx].assignedFlowCount--;
+					}
+				}
+			}
+			++flow_it;
+		}
+	}
+	/**
+	 * Move (some) flows from low quality paths
+	 */
+	if (_policy == ZT_BOND_POLICY_BALANCE_AWARE) {
+		Mutex::Lock _l(_flows_m);
+		std::map<int16_t, SharedPtr<Flow> >::iterator flow_it = _flows.begin();
+		while (flow_it != _flows.end()) {
+			if (_paths[flow_it->second->assignedPath].p) {
+				int originalPathIdx = flow_it->second->assignedPath;
+				if (_paths[originalPathIdx].shouldAvoid) {
+					if (assignFlowToBondedPath(flow_it->second, now, true)) {
+						_paths[originalPathIdx].assignedFlowCount--;
+						return;	  // Only move one flow at a time
+					}
+				}
+			}
+			++flow_it;
+		}
+	}
 }
 
 void Bond::dequeueNextActiveBackupPath(uint64_t now)
 {
-    if (_abFailoverQueue.empty()) {
-        return;
-    }
-    _abPathIdx = _abFailoverQueue.front();
-    _abFailoverQueue.pop_front();
-    _lastActiveBackupPathChange = now;
-    for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-        if (_paths[i].p) {
-            _paths[i].resetPacketCounts();
-        }
-    }
+	if (_abFailoverQueue.empty()) {
+		return;
+	}
+	_abPathIdx = _abFailoverQueue.front();
+	_abFailoverQueue.pop_front();
+	_lastActiveBackupPathChange = now;
+	for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+		if (_paths[i].p) {
+			_paths[i].resetPacketCounts();
+		}
+	}
 }
 
 bool Bond::abForciblyRotateLink()
 {
-    Mutex::Lock _l(_paths_m);
-    if (_policy == ZT_BOND_POLICY_ACTIVE_BACKUP) {
-        int prevPathIdx = _abPathIdx;
-        dequeueNextActiveBackupPath(RR->node->now());
-        log("active link rotated from %s to %s", pathToStr(_paths[prevPathIdx].p).c_str(), pathToStr(_paths[_abPathIdx].p).c_str());
-        return true;
-    }
-    return false;
+	Mutex::Lock _l(_paths_m);
+	if (_policy == ZT_BOND_POLICY_ACTIVE_BACKUP) {
+		int prevPathIdx = _abPathIdx;
+		dequeueNextActiveBackupPath(RR->node->now());
+		log("active link rotated from %s to %s", pathToStr(_paths[prevPathIdx].p).c_str(), pathToStr(_paths[_abPathIdx].p).c_str());
+		return true;
+	}
+	return false;
 }
 
 void Bond::processActiveBackupTasks(void* tPtr, int64_t now)
 {
-    int prevActiveBackupPathIdx = _abPathIdx;
-    int nonPreferredPathIdx = ZT_MAX_PEER_NETWORK_PATHS;
-    bool foundPathOnPrimaryLink = false;
-    bool foundPreferredPath = false;
-
-    if (_abPathIdx != ZT_MAX_PEER_NETWORK_PATHS && ! _paths[_abPathIdx].p) {
-        _abPathIdx = ZT_MAX_PEER_NETWORK_PATHS;
-        log("main active-backup path has been removed");
-    }
-
-    /**
-     * Generate periodic status report
-     */
-    if ((now - _lastBondStatusLog) > ZT_BOND_STATUS_INTERVAL) {
-        _lastBondStatusLog = now;
-        if (_abPathIdx == ZT_MAX_PEER_NETWORK_PATHS) {
-            log("no active link");
-        }
-        else if (_paths[_abPathIdx].p) {
-            log("active link is %s, failover queue size is %zu", pathToStr(_paths[_abPathIdx].p).c_str(), _abFailoverQueue.size());
-        }
-        if (_abFailoverQueue.empty()) {
-            log("failover queue is empty, bond is no longer fault-tolerant");
-        }
-    }
-    /**
-     * Select initial "active" active-backup link
-     */
-    if (_abPathIdx == ZT_MAX_PEER_NETWORK_PATHS) {
-        /**
-         * [Automatic mode]
-         * The user has not explicitly specified links or their failover schedule,
-         * the bonding policy will now select the first eligible path and set it as
-         * its active backup path, if a substantially better path is detected the bonding
-         * policy will assign it as the new active backup path. If the path fails it will
-         * simply find the next eligible path.
-         */
-        if (! userHasSpecifiedLinks()) {
-            for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-                if (_paths[i].p && _paths[i].eligible) {
-                    SharedPtr<Link> link = RR->bc->getLinkBySocket(_policyAlias, _paths[i].p->localSocket());
-                    if (link) {
-                        log("found eligible link %s", pathToStr(_paths[i].p).c_str());
-                        _abPathIdx = i;
-                        break;
-                    }
-                }
-            }
-        }
-
-        /**
-         * [Manual mode]
-         * The user has specified links or failover rules that the bonding policy should adhere to.
-         */
-        else if (userHasSpecifiedLinks()) {
-            if (userHasSpecifiedPrimaryLink()) {
-                for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-                    if (! _paths[i].p) {
-                        continue;
-                    }
-                    SharedPtr<Link> link = RR->bc->getLinkBySocket(_policyAlias, _paths[i].p->localSocket());
-                    if (link) {
-                        if (_paths[i].eligible && link->primary()) {
-                            if (! _paths[i].preferred()) {
-                                // Found path on primary link, take note in case we don't find a preferred path
-                                nonPreferredPathIdx = i;
-                                foundPathOnPrimaryLink = true;
-                            }
-                            if (_paths[i].preferred()) {
-                                _abPathIdx = i;
-                                foundPathOnPrimaryLink = true;
-                                if (_paths[_abPathIdx].p) {
-                                    SharedPtr<Link> abLink = RR->bc->getLinkBySocket(_policyAlias, _paths[_abPathIdx].p->localSocket());
-                                    if (abLink) {
-                                        log("found preferred primary link (_abPathIdx=%d), %s", _abPathIdx, pathToStr(_paths[_abPathIdx].p).c_str());
-                                        foundPreferredPath = true;
-                                    }
-                                    break;   // Found preferred path on primary link
-                                }
-                            }
-                        }
-                    }
-                }
-                if (! foundPreferredPath && foundPathOnPrimaryLink && (nonPreferredPathIdx != ZT_MAX_PEER_NETWORK_PATHS)) {
-                    log("found non-preferred primary link (_abPathIdx=%d)", _abPathIdx);
-                    _abPathIdx = nonPreferredPathIdx;
-                }
-            }
-
-            else if (! userHasSpecifiedPrimaryLink()) {
-                for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-                    if (_paths[i].p && _paths[i].eligible) {
-                        _abPathIdx = i;
-                        break;
-                    }
-                }
-                if (_abPathIdx != ZT_MAX_PEER_NETWORK_PATHS) {
-                    if (_paths[_abPathIdx].p) {
-                        SharedPtr<Link> link = RR->bc->getLinkBySocket(_policyAlias, _paths[_abPathIdx].p->localSocket());
-                        if (link) {
-                            log("select non-primary link %s", pathToStr(_paths[_abPathIdx].p).c_str());
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    // Short-circuit if we don't have an active link yet. Everything below is optimization from the base case
-    if (_abPathIdx < 0 || _abPathIdx == ZT_MAX_PEER_NETWORK_PATHS || (! _paths[_abPathIdx].p)) {
-        return;
-    }
-
-    // Remove ineligible paths from the failover link queue
-    for (std::deque<int>::iterator it(_abFailoverQueue.begin()); it != _abFailoverQueue.end();) {
-        if (! _paths[(*it)].p) {
-            log("link is no longer valid, removing from failover queue (%zu links remain in queue)", _abFailoverQueue.size());
-            it = _abFailoverQueue.erase(it);
-            continue;
-        }
-        if (_paths[(*it)].p && ! _paths[(*it)].eligible) {
-            SharedPtr<Link> link = RR->bc->getLinkBySocket(_policyAlias, _paths[(*it)].p->localSocket());
-            if (link) {
-                log("link %s is ineligible, removing from failover queue (%zu links remain in queue)", pathToStr(_paths[(*it)].p).c_str(), _abFailoverQueue.size());
-            }
-            it = _abFailoverQueue.erase(it);
-            continue;
-        }
-        else {
-            ++it;
-        }
-    }
-    /**
-     * Failover instructions were provided by user, build queue according those as well as IPv
-     * preference, disregarding performance.
-     */
-    if (userHasSpecifiedFailoverInstructions()) {
-        /**
-         * Clear failover scores
-         */
-        for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-            if (_paths[i].p) {
-                _paths[i].failoverScore = 0;
-            }
-        }
-        // Follow user-specified failover instructions
-        for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-            if (! _paths[i].p || ! _paths[i].allowed() || ! _paths[i].eligible) {
-                continue;
-            }
-            SharedPtr<Link> link = RR->bc->getLinkBySocket(_policyAlias, _paths[i].p->localSocket());
-            if (! link) {
-                continue;
-            }
-            int failoverScoreHandicap = _paths[i].failoverScore;
-            if (_paths[i].preferred()) {
-                failoverScoreHandicap += ZT_BOND_FAILOVER_HANDICAP_PREFERRED;
-            }
-            if (link->primary()) {
-                // If using "optimize" primary re-select mode, ignore user link designations
-                failoverScoreHandicap += ZT_BOND_FAILOVER_HANDICAP_PRIMARY;
-            }
-            if (! _paths[i].failoverScore) {
-                // If we didn't inherit a failover score from a "parent" that wants to use this path as a failover
-                int newHandicap = failoverScoreHandicap ? failoverScoreHandicap : (_paths[i].relativeQuality * 255.0);
-                _paths[i].failoverScore = newHandicap;
-            }
-            SharedPtr<Link> failoverLink;
-            if (link->failoverToLink().length()) {
-                failoverLink = RR->bc->getLinkByName(_policyAlias, link->failoverToLink());
-            }
-            if (failoverLink) {
-                for (int j = 0; j < ZT_MAX_PEER_NETWORK_PATHS; j++) {
-                    if (_paths[j].p && getLink(_paths[j].p) == failoverLink.ptr()) {
-                        int inheritedHandicap = failoverScoreHandicap - 10;
-                        int newHandicap = _paths[j].failoverScore > inheritedHandicap ? _paths[j].failoverScore : inheritedHandicap;
-                        if (! _paths[j].preferred()) {
-                            newHandicap--;
-                        }
-                        _paths[j].failoverScore = newHandicap;
-                    }
-                }
-            }
-            if (_paths[i].p) {
-                if (_paths[i].p.ptr() != _paths[_abPathIdx].p.ptr()) {
-                    bool bFoundPathInQueue = false;
-                    for (std::deque<int>::iterator it(_abFailoverQueue.begin()); it != _abFailoverQueue.end(); ++it) {
-                        if (_paths[(*it)].p && (_paths[i].p.ptr() == _paths[(*it)].p.ptr())) {
-                            bFoundPathInQueue = true;
-                        }
-                    }
-                    if (! bFoundPathInQueue) {
-                        _abFailoverQueue.push_back(i);
-                        log("add link %s to failover queue (%zu links in queue)", pathToStr(_paths[i].p).c_str(), _abFailoverQueue.size());
-                        addPathToBond(i, 0);
-                    }
-                }
-            }
-        }
-    }
-    /**
-     * No failover instructions provided by user, build queue according to performance
-     * and IPv preference.
-     */
-    else if (! userHasSpecifiedFailoverInstructions()) {
-        for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-            if (! _paths[i].p || ! _paths[i].allowed() || ! _paths[i].eligible) {
-                continue;
-            }
-            int failoverScoreHandicap = 0;
-            if (_paths[i].preferred()) {
-                failoverScoreHandicap = ZT_BOND_FAILOVER_HANDICAP_PREFERRED;
-            }
-            if (! _paths[i].eligible) {
-                failoverScoreHandicap = -10000;
-            }
-            SharedPtr<Link> link = getLink(_paths[i].p);
-            if (! link) {
-                continue;
-            }
-            if (link->primary() && _abLinkSelectMethod != ZT_BOND_RESELECTION_POLICY_OPTIMIZE) {
-                // If using "optimize" primary re-select mode, ignore user link designations
-                failoverScoreHandicap = ZT_BOND_FAILOVER_HANDICAP_PRIMARY;
-            }
-            /*
-            if (_paths[i].p.ptr() == _paths[_negotiatedPathIdx].p.ptr()) {
-                _paths[i].negotiated = true;
-                failoverScoreHandicap = ZT_BOND_FAILOVER_HANDICAP_NEGOTIATED;
-            }
-            else {
-                _paths[i].negotiated = false;
-            }
-            */
-            _paths[i].failoverScore = _paths[i].relativeQuality + failoverScoreHandicap;
-            if (_paths[i].p.ptr() != _paths[_abPathIdx].p.ptr()) {
-                bool bFoundPathInQueue = false;
-                for (std::deque<int>::iterator it(_abFailoverQueue.begin()); it != _abFailoverQueue.end(); ++it) {
-                    if (_paths[i].p.ptr() == _paths[(*it)].p.ptr()) {
-                        bFoundPathInQueue = true;
-                    }
-                }
-                if (! bFoundPathInQueue) {
-                    _abFailoverQueue.push_back(i);
-                    log("add link %s to failover queue (%zu links in queue)", pathToStr(_paths[i].p).c_str(), _abFailoverQueue.size());
-                    addPathToBond(i, 0);
-                }
-            }
-        }
-    }
-    /*
-    // Sort queue based on performance
-    if (! _abFailoverQueue.empty()) {
-        for (int i = 0; i < _abFailoverQueue.size(); i++) {
-            int value_to_insert = _abFailoverQueue[i];
-            int hole_position = i;
-            while (hole_position > 0 && (_abFailoverQueue[hole_position - 1] > value_to_insert)) {
-                _abFailoverQueue[hole_position] = _abFailoverQueue[hole_position - 1];
-                hole_position = hole_position - 1;
-            }
-            _abFailoverQueue[hole_position] = value_to_insert;
-        }
-    }*/
-
-    /**
-     * Short-circuit if we have no queued paths
-     */
-    if (_abFailoverQueue.empty()) {
-        return;
-    }
-
-    /**
-     * Fulfill primary re-select obligations
-     */
-    if (! _paths[_abPathIdx].eligible) {   // Implicit ZT_BOND_RESELECTION_POLICY_FAILURE
-        log("link %s has failed, select link from failover queue (%zu links in queue)", pathToStr(_paths[_abPathIdx].p).c_str(), _abFailoverQueue.size());
-        if (! _abFailoverQueue.empty()) {
-            dequeueNextActiveBackupPath(now);
-            log("active link switched to %s", pathToStr(_paths[_abPathIdx].p).c_str());
-        }
-        else {
-            log("failover queue is empty, no links to choose from");
-        }
-    }
-    /**
-     * Detect change to prevent flopping during later optimization step.
-     */
-    if (prevActiveBackupPathIdx != _abPathIdx) {
-        _lastActiveBackupPathChange = now;
-    }
-    if (_abFailoverQueue.empty()) {
-        return;   // No sense in continuing since there are no links to switch to
-    }
-
-    if (_abLinkSelectMethod == ZT_BOND_RESELECTION_POLICY_ALWAYS) {
-        SharedPtr<Link> abLink = getLink(_paths[_abPathIdx].p);
-        if (! _paths[_abFailoverQueue.front()].p) {
-            log("invalid link. not switching");
-            return;
-        }
-
-        SharedPtr<Link> abFailoverLink = getLink(_paths[_abFailoverQueue.front()].p);
-        if (abLink && ! abLink->primary() && _paths[_abFailoverQueue.front()].p && abFailoverLink && abFailoverLink->primary()) {
-            dequeueNextActiveBackupPath(now);
-            log("switch back to available primary link %s (select mode: always)", pathToStr(_paths[_abPathIdx].p).c_str());
-        }
-    }
-    if (_abLinkSelectMethod == ZT_BOND_RESELECTION_POLICY_BETTER) {
-        SharedPtr<Link> abLink = getLink(_paths[_abPathIdx].p);
-        if (abLink && ! abLink->primary()) {
-            // Active backup has switched to "better" primary link according to re-select policy.
-            SharedPtr<Link> abFailoverLink = getLink(_paths[_abFailoverQueue.front()].p);
-            if (_paths[_abFailoverQueue.front()].p && abFailoverLink && abFailoverLink->primary() && (_paths[_abFailoverQueue.front()].failoverScore > _paths[_abPathIdx].failoverScore)) {
-                dequeueNextActiveBackupPath(now);
-                log("switch back to user-defined primary link %s (select mode: better)", pathToStr(_paths[_abPathIdx].p).c_str());
-            }
-        }
-    }
-    if (_abLinkSelectMethod == ZT_BOND_RESELECTION_POLICY_OPTIMIZE && ! _abFailoverQueue.empty()) {
-        /**
-         * Implement link negotiation that was previously-decided
-         */
-        if (_paths[_abFailoverQueue.front()].negotiated) {
-            dequeueNextActiveBackupPath(now);
-            _lastPathNegotiationCheck = now;
-            log("switch negotiated link %s (select mode: optimize)", pathToStr(_paths[_abPathIdx].p).c_str());
-        }
-        else {
-            // Try to find a better path and automatically switch to it -- not too often, though.
-            if ((now - _lastActiveBackupPathChange) > ZT_BOND_OPTIMIZE_INTERVAL) {
-                if (! _abFailoverQueue.empty()) {
-                    int newFScore = _paths[_abFailoverQueue.front()].failoverScore;
-                    int prevFScore = _paths[_abPathIdx].failoverScore;
-                    // Establish a minimum switch threshold to prevent flapping
-                    int failoverScoreDifference = _paths[_abFailoverQueue.front()].failoverScore - _paths[_abPathIdx].failoverScore;
-                    int thresholdQuantity = (int)(ZT_BOND_ACTIVE_BACKUP_OPTIMIZE_MIN_THRESHOLD * (float)_paths[_abPathIdx].relativeQuality);
-                    if ((failoverScoreDifference > 0) && (failoverScoreDifference > thresholdQuantity)) {
-                        SharedPtr<Path> oldPath = _paths[_abPathIdx].p;
-                        dequeueNextActiveBackupPath(now);
-                        log("switch from %s (score: %d) to better link %s (score: %d) (select mode: optimize)", pathToStr(oldPath).c_str(), prevFScore, pathToStr(_paths[_abPathIdx].p).c_str(), newFScore);
-                    }
-                }
-            }
-        }
-    }
+	int prevActiveBackupPathIdx = _abPathIdx;
+	int nonPreferredPathIdx = ZT_MAX_PEER_NETWORK_PATHS;
+	bool foundPathOnPrimaryLink = false;
+	bool foundPreferredPath = false;
+
+	if (_abPathIdx != ZT_MAX_PEER_NETWORK_PATHS && ! _paths[_abPathIdx].p) {
+		_abPathIdx = ZT_MAX_PEER_NETWORK_PATHS;
+		log("main active-backup path has been removed");
+	}
+
+	/**
+	 * Generate periodic status report
+	 */
+	if ((now - _lastBondStatusLog) > ZT_BOND_STATUS_INTERVAL) {
+		_lastBondStatusLog = now;
+		if (_abPathIdx == ZT_MAX_PEER_NETWORK_PATHS) {
+			log("no active link");
+		}
+		else if (_paths[_abPathIdx].p) {
+			log("active link is %s, failover queue size is %zu", pathToStr(_paths[_abPathIdx].p).c_str(), _abFailoverQueue.size());
+		}
+		if (_abFailoverQueue.empty()) {
+			log("failover queue is empty, bond is no longer fault-tolerant");
+		}
+	}
+	/**
+	 * Select initial "active" active-backup link
+	 */
+	if (_abPathIdx == ZT_MAX_PEER_NETWORK_PATHS) {
+		/**
+		 * [Automatic mode]
+		 * The user has not explicitly specified links or their failover schedule,
+		 * the bonding policy will now select the first eligible path and set it as
+		 * its active backup path, if a substantially better path is detected the bonding
+		 * policy will assign it as the new active backup path. If the path fails it will
+		 * simply find the next eligible path.
+		 */
+		if (! userHasSpecifiedLinks()) {
+			for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+				if (_paths[i].p && _paths[i].eligible) {
+					SharedPtr<Link> link = RR->bc->getLinkBySocket(_policyAlias, _paths[i].p->localSocket());
+					if (link) {
+						log("found eligible link %s", pathToStr(_paths[i].p).c_str());
+						_abPathIdx = i;
+						break;
+					}
+				}
+			}
+		}
+
+		/**
+		 * [Manual mode]
+		 * The user has specified links or failover rules that the bonding policy should adhere to.
+		 */
+		else if (userHasSpecifiedLinks()) {
+			if (userHasSpecifiedPrimaryLink()) {
+				for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+					if (! _paths[i].p) {
+						continue;
+					}
+					SharedPtr<Link> link = RR->bc->getLinkBySocket(_policyAlias, _paths[i].p->localSocket());
+					if (link) {
+						if (_paths[i].eligible && link->primary()) {
+							if (! _paths[i].preferred()) {
+								// Found path on primary link, take note in case we don't find a preferred path
+								nonPreferredPathIdx = i;
+								foundPathOnPrimaryLink = true;
+							}
+							if (_paths[i].preferred()) {
+								_abPathIdx = i;
+								foundPathOnPrimaryLink = true;
+								if (_paths[_abPathIdx].p) {
+									SharedPtr<Link> abLink = RR->bc->getLinkBySocket(_policyAlias, _paths[_abPathIdx].p->localSocket());
+									if (abLink) {
+										log("found preferred primary link (_abPathIdx=%d), %s", _abPathIdx, pathToStr(_paths[_abPathIdx].p).c_str());
+										foundPreferredPath = true;
+									}
+									break;	 // Found preferred path on primary link
+								}
+							}
+						}
+					}
+				}
+				if (! foundPreferredPath && foundPathOnPrimaryLink && (nonPreferredPathIdx != ZT_MAX_PEER_NETWORK_PATHS)) {
+					log("found non-preferred primary link (_abPathIdx=%d)", _abPathIdx);
+					_abPathIdx = nonPreferredPathIdx;
+				}
+			}
+
+			else if (! userHasSpecifiedPrimaryLink()) {
+				for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+					if (_paths[i].p && _paths[i].eligible) {
+						_abPathIdx = i;
+						break;
+					}
+				}
+				if (_abPathIdx != ZT_MAX_PEER_NETWORK_PATHS) {
+					if (_paths[_abPathIdx].p) {
+						SharedPtr<Link> link = RR->bc->getLinkBySocket(_policyAlias, _paths[_abPathIdx].p->localSocket());
+						if (link) {
+							log("select non-primary link %s", pathToStr(_paths[_abPathIdx].p).c_str());
+						}
+					}
+				}
+			}
+		}
+	}
+
+	// Short-circuit if we don't have an active link yet. Everything below is optimization from the base case
+	if (_abPathIdx < 0 || _abPathIdx == ZT_MAX_PEER_NETWORK_PATHS || (! _paths[_abPathIdx].p)) {
+		return;
+	}
+
+	// Remove ineligible paths from the failover link queue
+	for (std::deque<int>::iterator it(_abFailoverQueue.begin()); it != _abFailoverQueue.end();) {
+		if (! _paths[(*it)].p) {
+			log("link is no longer valid, removing from failover queue (%zu links remain in queue)", _abFailoverQueue.size());
+			it = _abFailoverQueue.erase(it);
+			continue;
+		}
+		if (_paths[(*it)].p && ! _paths[(*it)].eligible) {
+			SharedPtr<Link> link = RR->bc->getLinkBySocket(_policyAlias, _paths[(*it)].p->localSocket());
+			if (link) {
+				log("link %s is ineligible, removing from failover queue (%zu links remain in queue)", pathToStr(_paths[(*it)].p).c_str(), _abFailoverQueue.size());
+			}
+			it = _abFailoverQueue.erase(it);
+			continue;
+		}
+		else {
+			++it;
+		}
+	}
+	/**
+	 * Failover instructions were provided by user, build queue according those as well as IPv
+	 * preference, disregarding performance.
+	 */
+	if (userHasSpecifiedFailoverInstructions()) {
+		/**
+		 * Clear failover scores
+		 */
+		for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+			if (_paths[i].p) {
+				_paths[i].failoverScore = 0;
+			}
+		}
+		// Follow user-specified failover instructions
+		for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+			if (! _paths[i].p || ! _paths[i].allowed() || ! _paths[i].eligible) {
+				continue;
+			}
+			SharedPtr<Link> link = RR->bc->getLinkBySocket(_policyAlias, _paths[i].p->localSocket());
+			if (! link) {
+				continue;
+			}
+			int failoverScoreHandicap = _paths[i].failoverScore;
+			if (_paths[i].preferred()) {
+				failoverScoreHandicap += ZT_BOND_FAILOVER_HANDICAP_PREFERRED;
+			}
+			if (link->primary()) {
+				// If using "optimize" primary re-select mode, ignore user link designations
+				failoverScoreHandicap += ZT_BOND_FAILOVER_HANDICAP_PRIMARY;
+			}
+			if (! _paths[i].failoverScore) {
+				// If we didn't inherit a failover score from a "parent" that wants to use this path as a failover
+				int newHandicap = failoverScoreHandicap ? failoverScoreHandicap : (_paths[i].relativeQuality * 255.0);
+				_paths[i].failoverScore = newHandicap;
+			}
+			SharedPtr<Link> failoverLink;
+			if (link->failoverToLink().length()) {
+				failoverLink = RR->bc->getLinkByName(_policyAlias, link->failoverToLink());
+			}
+			if (failoverLink) {
+				for (int j = 0; j < ZT_MAX_PEER_NETWORK_PATHS; j++) {
+					if (_paths[j].p && getLink(_paths[j].p) == failoverLink.ptr()) {
+						int inheritedHandicap = failoverScoreHandicap - 10;
+						int newHandicap = _paths[j].failoverScore > inheritedHandicap ? _paths[j].failoverScore : inheritedHandicap;
+						if (! _paths[j].preferred()) {
+							newHandicap--;
+						}
+						_paths[j].failoverScore = newHandicap;
+					}
+				}
+			}
+			if (_paths[i].p) {
+				if (_paths[i].p.ptr() != _paths[_abPathIdx].p.ptr()) {
+					bool bFoundPathInQueue = false;
+					for (std::deque<int>::iterator it(_abFailoverQueue.begin()); it != _abFailoverQueue.end(); ++it) {
+						if (_paths[(*it)].p && (_paths[i].p.ptr() == _paths[(*it)].p.ptr())) {
+							bFoundPathInQueue = true;
+						}
+					}
+					if (! bFoundPathInQueue) {
+						_abFailoverQueue.push_back(i);
+						log("add link %s to failover queue (%zu links in queue)", pathToStr(_paths[i].p).c_str(), _abFailoverQueue.size());
+						addPathToBond(i, 0);
+					}
+				}
+			}
+		}
+	}
+	/**
+	 * No failover instructions provided by user, build queue according to performance
+	 * and IPv preference.
+	 */
+	else if (! userHasSpecifiedFailoverInstructions()) {
+		for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+			if (! _paths[i].p || ! _paths[i].allowed() || ! _paths[i].eligible) {
+				continue;
+			}
+			int failoverScoreHandicap = 0;
+			if (_paths[i].preferred()) {
+				failoverScoreHandicap = ZT_BOND_FAILOVER_HANDICAP_PREFERRED;
+			}
+			if (! _paths[i].eligible) {
+				failoverScoreHandicap = -10000;
+			}
+			SharedPtr<Link> link = getLink(_paths[i].p);
+			if (! link) {
+				continue;
+			}
+			if (link->primary() && _abLinkSelectMethod != ZT_BOND_RESELECTION_POLICY_OPTIMIZE) {
+				// If using "optimize" primary re-select mode, ignore user link designations
+				failoverScoreHandicap = ZT_BOND_FAILOVER_HANDICAP_PRIMARY;
+			}
+			/*
+			if (_paths[i].p.ptr() == _paths[_negotiatedPathIdx].p.ptr()) {
+				_paths[i].negotiated = true;
+				failoverScoreHandicap = ZT_BOND_FAILOVER_HANDICAP_NEGOTIATED;
+			}
+			else {
+				_paths[i].negotiated = false;
+			}
+			*/
+			_paths[i].failoverScore = _paths[i].relativeQuality + failoverScoreHandicap;
+			if (_paths[i].p.ptr() != _paths[_abPathIdx].p.ptr()) {
+				bool bFoundPathInQueue = false;
+				for (std::deque<int>::iterator it(_abFailoverQueue.begin()); it != _abFailoverQueue.end(); ++it) {
+					if (_paths[i].p.ptr() == _paths[(*it)].p.ptr()) {
+						bFoundPathInQueue = true;
+					}
+				}
+				if (! bFoundPathInQueue) {
+					_abFailoverQueue.push_back(i);
+					log("add link %s to failover queue (%zu links in queue)", pathToStr(_paths[i].p).c_str(), _abFailoverQueue.size());
+					addPathToBond(i, 0);
+				}
+			}
+		}
+	}
+	/*
+	// Sort queue based on performance
+	if (! _abFailoverQueue.empty()) {
+		for (int i = 0; i < _abFailoverQueue.size(); i++) {
+			int value_to_insert = _abFailoverQueue[i];
+			int hole_position = i;
+			while (hole_position > 0 && (_abFailoverQueue[hole_position - 1] > value_to_insert)) {
+				_abFailoverQueue[hole_position] = _abFailoverQueue[hole_position - 1];
+				hole_position = hole_position - 1;
+			}
+			_abFailoverQueue[hole_position] = value_to_insert;
+		}
+	}*/
+
+	/**
+	 * Short-circuit if we have no queued paths
+	 */
+	if (_abFailoverQueue.empty()) {
+		return;
+	}
+
+	/**
+	 * Fulfill primary re-select obligations
+	 */
+	if (! _paths[_abPathIdx].eligible) {   // Implicit ZT_BOND_RESELECTION_POLICY_FAILURE
+		log("link %s has failed, select link from failover queue (%zu links in queue)", pathToStr(_paths[_abPathIdx].p).c_str(), _abFailoverQueue.size());
+		if (! _abFailoverQueue.empty()) {
+			dequeueNextActiveBackupPath(now);
+			log("active link switched to %s", pathToStr(_paths[_abPathIdx].p).c_str());
+		}
+		else {
+			log("failover queue is empty, no links to choose from");
+		}
+	}
+	/**
+	 * Detect change to prevent flopping during later optimization step.
+	 */
+	if (prevActiveBackupPathIdx != _abPathIdx) {
+		_lastActiveBackupPathChange = now;
+	}
+	if (_abFailoverQueue.empty()) {
+		return;	  // No sense in continuing since there are no links to switch to
+	}
+
+	if (_abLinkSelectMethod == ZT_BOND_RESELECTION_POLICY_ALWAYS) {
+		SharedPtr<Link> abLink = getLink(_paths[_abPathIdx].p);
+		if (! _paths[_abFailoverQueue.front()].p) {
+			log("invalid link. not switching");
+			return;
+		}
+
+		SharedPtr<Link> abFailoverLink = getLink(_paths[_abFailoverQueue.front()].p);
+		if (abLink && ! abLink->primary() && _paths[_abFailoverQueue.front()].p && abFailoverLink && abFailoverLink->primary()) {
+			dequeueNextActiveBackupPath(now);
+			log("switch back to available primary link %s (select mode: always)", pathToStr(_paths[_abPathIdx].p).c_str());
+		}
+	}
+	if (_abLinkSelectMethod == ZT_BOND_RESELECTION_POLICY_BETTER) {
+		SharedPtr<Link> abLink = getLink(_paths[_abPathIdx].p);
+		if (abLink && ! abLink->primary()) {
+			// Active backup has switched to "better" primary link according to re-select policy.
+			SharedPtr<Link> abFailoverLink = getLink(_paths[_abFailoverQueue.front()].p);
+			if (_paths[_abFailoverQueue.front()].p && abFailoverLink && abFailoverLink->primary() && (_paths[_abFailoverQueue.front()].failoverScore > _paths[_abPathIdx].failoverScore)) {
+				dequeueNextActiveBackupPath(now);
+				log("switch back to user-defined primary link %s (select mode: better)", pathToStr(_paths[_abPathIdx].p).c_str());
+			}
+		}
+	}
+	if (_abLinkSelectMethod == ZT_BOND_RESELECTION_POLICY_OPTIMIZE && ! _abFailoverQueue.empty()) {
+		/**
+		 * Implement link negotiation that was previously-decided
+		 */
+		if (_paths[_abFailoverQueue.front()].negotiated) {
+			dequeueNextActiveBackupPath(now);
+			_lastPathNegotiationCheck = now;
+			log("switch negotiated link %s (select mode: optimize)", pathToStr(_paths[_abPathIdx].p).c_str());
+		}
+		else {
+			// Try to find a better path and automatically switch to it -- not too often, though.
+			if ((now - _lastActiveBackupPathChange) > ZT_BOND_OPTIMIZE_INTERVAL) {
+				if (! _abFailoverQueue.empty()) {
+					int newFScore = _paths[_abFailoverQueue.front()].failoverScore;
+					int prevFScore = _paths[_abPathIdx].failoverScore;
+					// Establish a minimum switch threshold to prevent flapping
+					int failoverScoreDifference = _paths[_abFailoverQueue.front()].failoverScore - _paths[_abPathIdx].failoverScore;
+					int thresholdQuantity = (int)(ZT_BOND_ACTIVE_BACKUP_OPTIMIZE_MIN_THRESHOLD * (float)_paths[_abPathIdx].relativeQuality);
+					if ((failoverScoreDifference > 0) && (failoverScoreDifference > thresholdQuantity)) {
+						SharedPtr<Path> oldPath = _paths[_abPathIdx].p;
+						dequeueNextActiveBackupPath(now);
+						log("switch from %s (score: %d) to better link %s (score: %d) (select mode: optimize)", pathToStr(oldPath).c_str(), prevFScore, pathToStr(_paths[_abPathIdx].p).c_str(), newFScore);
+					}
+				}
+			}
+		}
+	}
 }
 
 void Bond::initTimers()
 {
-    _lastFlowExpirationCheck = 0;
-    _lastFlowRebalance = 0;
-    _lastSentPathNegotiationRequest = 0;
-    _lastPathNegotiationCheck = 0;
-    _lastPathNegotiationReceived = 0;
-    _lastQoSRateCheck = 0;
-    _lastAckRateCheck = 0;
-    _lastQualityEstimation = 0;
-    _lastBondStatusLog = 0;
-    _lastSummaryDump = 0;
-    _lastActiveBackupPathChange = 0;
-    _lastFrame = 0;
-    _lastBackgroundTaskCheck = 0;
+	_lastFlowExpirationCheck = 0;
+	_lastFlowRebalance = 0;
+	_lastSentPathNegotiationRequest = 0;
+	_lastPathNegotiationCheck = 0;
+	_lastPathNegotiationReceived = 0;
+	_lastQoSRateCheck = 0;
+	_lastAckRateCheck = 0;
+	_lastQualityEstimation = 0;
+	_lastBondStatusLog = 0;
+	_lastSummaryDump = 0;
+	_lastActiveBackupPathChange = 0;
+	_lastFrame = 0;
+	_lastBackgroundTaskCheck = 0;
 }
 
 void Bond::setBondParameters(int policy, SharedPtr<Bond> templateBond, bool useTemplate)
 {
-    // Sanity check for policy
-
-    _defaultPolicy = (_defaultPolicy <= ZT_BOND_POLICY_NONE || _defaultPolicy > ZT_BOND_POLICY_BALANCE_AWARE) ? ZT_BOND_POLICY_NONE : _defaultPolicy;
-    _policy = (policy <= ZT_BOND_POLICY_NONE || policy > ZT_BOND_POLICY_BALANCE_AWARE) ? _defaultPolicy : policy;
-
-    // Check if non-leaf to prevent spamming infrastructure
-    ZT_PeerRole role;
-    if (_peer) {
-        role = RR->topology->role(_peer->address());
-    }
-    _isLeaf = _peer ? (role != ZT_PEER_ROLE_PLANET && role != ZT_PEER_ROLE_MOON) : false;
-
-    // Path negotiation
-
-    _allowPathNegotiation = false;
-    _pathNegotiationCutoffCount = 0;
-    _localUtility = 0;
-    _negotiatedPathIdx = 0;
-
-    // User preferences which may override the default bonding algorithm's behavior
-
-    _userHasSpecifiedPrimaryLink = false;
-    _userHasSpecifiedFailoverInstructions = false;
-    _userHasSpecifiedLinkCapacities = 0;
-
-    // Bond status
-
-    _numAliveLinks = 0;
-    _numTotalLinks = 0;
-    _numBondedPaths = 0;
-
-    // General parameters
-
-    _downDelay = 0;
-    _upDelay = 0;
-    _monitorInterval = 0;
-
-    // balance-aware
-
-    _totalBondUnderload = 0;
-    _overheadBytes = 0;
-
-    /**
-     * Policy defaults
-     */
-    _abPathIdx = ZT_MAX_PEER_NETWORK_PATHS;
-    _abLinkSelectMethod = ZT_BOND_RESELECTION_POLICY_ALWAYS;
-    _rrPacketsSentOnCurrLink = 0;
-    _rrIdx = 0;
-    _packetsPerLink = 64;
-
-    // Sane quality defaults
-
-    _qw[ZT_QOS_LAT_MAX_IDX] = 500.0f;
-    _qw[ZT_QOS_PDV_MAX_IDX] = 100.0f;
-    _qw[ZT_QOS_PLR_MAX_IDX] = 0.001f;
-    _qw[ZT_QOS_PER_MAX_IDX] = 0.0001f;
-    _qw[ZT_QOS_LAT_WEIGHT_IDX] = 0.25f;
-    _qw[ZT_QOS_PDV_WEIGHT_IDX] = 0.25f;
-    _qw[ZT_QOS_PLR_WEIGHT_IDX] = 0.25f;
-    _qw[ZT_QOS_PER_WEIGHT_IDX] = 0.25f;
-
-    _failoverInterval = ZT_BOND_FAILOVER_DEFAULT_INTERVAL;
-
-    /* If a user has specified custom parameters for this bonding policy, overlay them onto the defaults */
-    if (useTemplate) {
-        _policyAlias = templateBond->_policyAlias;
-        _policy = templateBond->policy();
-        _failoverInterval = templateBond->_failoverInterval >= ZT_BOND_FAILOVER_MIN_INTERVAL ? templateBond->_failoverInterval : ZT_BOND_FAILOVER_MIN_INTERVAL;
-        _downDelay = templateBond->_downDelay;
-        _upDelay = templateBond->_upDelay;
-        _abLinkSelectMethod = templateBond->_abLinkSelectMethod;
-        memcpy(_qw, templateBond->_qw, ZT_QOS_PARAMETER_SIZE * sizeof(float));
-        debug("user link quality spec = {%6.3f, %6.3f, %6.3f, %6.3f, %6.3f, %6.3f, %6.3f, %6.3f}", _qw[0], _qw[1], _qw[2], _qw[3], _qw[4], _qw[5], _qw[6], _qw[7]);
-    }
-
-    if (! _isLeaf) {
-        _policy = ZT_BOND_POLICY_NONE;
-    }
-
-    // Timer geometry
-
-    _monitorInterval = _failoverInterval / ZT_BOND_ECHOS_PER_FAILOVER_INTERVAL;
-    _qualityEstimationInterval = _failoverInterval * 2;
-    _qosSendInterval = _failoverInterval * 2;
-    _ackSendInterval = _failoverInterval * 2;
-    _qosCutoffCount = 0;
-    _ackCutoffCount = 0;
-    _defaultPathRefractoryPeriod = 8000;
+	// Sanity check for policy
+
+	_defaultPolicy = (_defaultPolicy <= ZT_BOND_POLICY_NONE || _defaultPolicy > ZT_BOND_POLICY_BALANCE_AWARE) ? ZT_BOND_POLICY_NONE : _defaultPolicy;
+	_policy = (policy <= ZT_BOND_POLICY_NONE || policy > ZT_BOND_POLICY_BALANCE_AWARE) ? _defaultPolicy : policy;
+
+	// Check if non-leaf to prevent spamming infrastructure
+	ZT_PeerRole role;
+	if (_peer) {
+		role = RR->topology->role(_peer->address());
+	}
+	_isLeaf = _peer ? (role != ZT_PEER_ROLE_PLANET && role != ZT_PEER_ROLE_MOON) : false;
+
+	// Path negotiation
+
+	_allowPathNegotiation = false;
+	_pathNegotiationCutoffCount = 0;
+	_localUtility = 0;
+	_negotiatedPathIdx = 0;
+
+	// User preferences which may override the default bonding algorithm's behavior
+
+	_userHasSpecifiedPrimaryLink = false;
+	_userHasSpecifiedFailoverInstructions = false;
+	_userHasSpecifiedLinkCapacities = 0;
+
+	// Bond status
+
+	_numAliveLinks = 0;
+	_numTotalLinks = 0;
+	_numBondedPaths = 0;
+
+	// General parameters
+
+	_downDelay = 0;
+	_upDelay = 0;
+	_monitorInterval = 0;
+
+	// balance-aware
+
+	_totalBondUnderload = 0;
+	_overheadBytes = 0;
+
+	/**
+	 * Policy defaults
+	 */
+	_abPathIdx = ZT_MAX_PEER_NETWORK_PATHS;
+	_abLinkSelectMethod = ZT_BOND_RESELECTION_POLICY_ALWAYS;
+	_rrPacketsSentOnCurrLink = 0;
+	_rrIdx = 0;
+	_packetsPerLink = 64;
+
+	// Sane quality defaults
+
+	_qw[ZT_QOS_LAT_MAX_IDX] = 500.0f;
+	_qw[ZT_QOS_PDV_MAX_IDX] = 100.0f;
+	_qw[ZT_QOS_PLR_MAX_IDX] = 0.001f;
+	_qw[ZT_QOS_PER_MAX_IDX] = 0.0001f;
+	_qw[ZT_QOS_LAT_WEIGHT_IDX] = 0.25f;
+	_qw[ZT_QOS_PDV_WEIGHT_IDX] = 0.25f;
+	_qw[ZT_QOS_PLR_WEIGHT_IDX] = 0.25f;
+	_qw[ZT_QOS_PER_WEIGHT_IDX] = 0.25f;
+
+	_failoverInterval = ZT_BOND_FAILOVER_DEFAULT_INTERVAL;
+
+	/* If a user has specified custom parameters for this bonding policy, overlay them onto the defaults */
+	if (useTemplate) {
+		_policyAlias = templateBond->_policyAlias;
+		_policy = templateBond->policy();
+		_failoverInterval = templateBond->_failoverInterval >= ZT_BOND_FAILOVER_MIN_INTERVAL ? templateBond->_failoverInterval : ZT_BOND_FAILOVER_MIN_INTERVAL;
+		_downDelay = templateBond->_downDelay;
+		_upDelay = templateBond->_upDelay;
+		_abLinkSelectMethod = templateBond->_abLinkSelectMethod;
+		memcpy(_qw, templateBond->_qw, ZT_QOS_PARAMETER_SIZE * sizeof(float));
+		debug("user link quality spec = {%6.3f, %6.3f, %6.3f, %6.3f, %6.3f, %6.3f, %6.3f, %6.3f}", _qw[0], _qw[1], _qw[2], _qw[3], _qw[4], _qw[5], _qw[6], _qw[7]);
+	}
+
+	if (! _isLeaf) {
+		_policy = ZT_BOND_POLICY_NONE;
+	}
+
+	// Timer geometry
+
+	_monitorInterval = _failoverInterval / ZT_BOND_ECHOS_PER_FAILOVER_INTERVAL;
+	_qualityEstimationInterval = _failoverInterval * 2;
+	_qosSendInterval = _failoverInterval * 2;
+	_ackSendInterval = _failoverInterval * 2;
+	_qosCutoffCount = 0;
+	_ackCutoffCount = 0;
+	_defaultPathRefractoryPeriod = 8000;
 }
 
 void Bond::setUserLinkQualitySpec(float weights[], int len)
 {
-    if (len != ZT_QOS_PARAMETER_SIZE) {
-        debug("link quality spec has an invalid number of parameters (%d out of %d), ignoring", len, ZT_QOS_PARAMETER_SIZE);
-        return;
-    }
-    float weightTotal = 0.0;
-    for (unsigned int i = 4; i < ZT_QOS_PARAMETER_SIZE; ++i) {
-        weightTotal += weights[i];
-    }
-    if (weightTotal > 0.99 && weightTotal < 1.01) {
-        memcpy(_qw, weights, len * sizeof(float));
-    }
+	if (len != ZT_QOS_PARAMETER_SIZE) {
+		debug("link quality spec has an invalid number of parameters (%d out of %d), ignoring", len, ZT_QOS_PARAMETER_SIZE);
+		return;
+	}
+	float weightTotal = 0.0;
+	for (unsigned int i = 4; i < ZT_QOS_PARAMETER_SIZE; ++i) {
+		weightTotal += weights[i];
+	}
+	if (weightTotal > 0.99 && weightTotal < 1.01) {
+		memcpy(_qw, weights, len * sizeof(float));
+	}
 }
 
 SharedPtr<Link> Bond::getLink(const SharedPtr<Path>& path)
 {
-    return ! path ? SharedPtr<Link>() : RR->bc->getLinkBySocket(_policyAlias, path->localSocket());
+	return ! path ? SharedPtr<Link>() : RR->bc->getLinkBySocket(_policyAlias, path->localSocket());
 }
 
 std::string Bond::pathToStr(const SharedPtr<Path>& path)
 {
 #ifdef ZT_TRACE
-    if (path) {
-        char pathStr[64] = { 0 };
-        char fullPathStr[384] = { 0 };
-        path->address().toString(pathStr);
-        SharedPtr<Link> link = getLink(path);
-        if (link) {
-            std::string ifnameStr = std::string(link->ifname());
-            snprintf(fullPathStr, 384, "%.16" PRIx64 "-%s/%s", path->localSocket(), ifnameStr.c_str(), pathStr);
-            return std::string(fullPathStr);
-        }
-    }
-    return "";
+	if (path) {
+		char pathStr[64] = { 0 };
+		char fullPathStr[384] = { 0 };
+		path->address().toString(pathStr);
+		SharedPtr<Link> link = getLink(path);
+		if (link) {
+			std::string ifnameStr = std::string(link->ifname());
+			snprintf(fullPathStr, 384, "%.16" PRIx64 "-%s/%s", path->localSocket(), ifnameStr.c_str(), pathStr);
+			return std::string(fullPathStr);
+		}
+	}
+	return "";
 #else
-    return "";
+	return "";
 #endif
 }
 
 void Bond::dumpPathStatus(int64_t now, int pathIdx)
 {
 #ifdef ZT_TRACE
-    std::string aliveOrDead = _paths[pathIdx].alive ? std::string("alive") : std::string("dead");
-    std::string eligibleOrNot = _paths[pathIdx].eligible ? std::string("eligible") : std::string("ineligible");
-    std::string bondedOrNot = _paths[pathIdx].bonded ? std::string("bonded") : std::string("unbonded");
-    log("path[%2u] --- %5s (in %7" PRId64 ", out: %7" PRId64 "), %10s, %8s, flows=%-6u lat=%-8.3f pdv=%-7.3f err=%-6.4f loss=%-6.4f qual=%-6.4f --- (%s) spare=%d",
-        pathIdx,
-        aliveOrDead.c_str(),
-        _paths[pathIdx].p->age(now),
-        _paths[pathIdx].p->_lastOut == 0 ? static_cast<int64_t>(0) : now - _paths[pathIdx].p->_lastOut,
-        eligibleOrNot.c_str(),
-        bondedOrNot.c_str(),
-        _paths[pathIdx].assignedFlowCount,
-        _paths[pathIdx].latency,
-        _paths[pathIdx].latencyVariance,
-        _paths[pathIdx].packetErrorRatio,
-        _paths[pathIdx].packetLossRatio,
-        _paths[pathIdx].relativeQuality,
-        pathToStr(_paths[pathIdx].p).c_str(),
-        _paths[pathIdx].isSpare());
+	std::string aliveOrDead = _paths[pathIdx].alive ? std::string("alive") : std::string("dead");
+	std::string eligibleOrNot = _paths[pathIdx].eligible ? std::string("eligible") : std::string("ineligible");
+	std::string bondedOrNot = _paths[pathIdx].bonded ? std::string("bonded") : std::string("unbonded");
+	log("path[%2u] --- %5s (in %7" PRId64 ", out: %7" PRId64 "), %10s, %8s, flows=%-6u lat=%-8.3f pdv=%-7.3f err=%-6.4f loss=%-6.4f qual=%-6.4f --- (%s) spare=%d",
+		pathIdx,
+		aliveOrDead.c_str(),
+		_paths[pathIdx].p->age(now),
+		_paths[pathIdx].p->_lastOut == 0 ? static_cast<int64_t>(0) : now - _paths[pathIdx].p->_lastOut,
+		eligibleOrNot.c_str(),
+		bondedOrNot.c_str(),
+		_paths[pathIdx].assignedFlowCount,
+		_paths[pathIdx].latency,
+		_paths[pathIdx].latencyVariance,
+		_paths[pathIdx].packetErrorRatio,
+		_paths[pathIdx].packetLossRatio,
+		_paths[pathIdx].relativeQuality,
+		pathToStr(_paths[pathIdx].p).c_str(),
+		_paths[pathIdx].isSpare());
 #endif
 }
 
 void Bond::dumpInfo(int64_t now, bool force)
 {
 #ifdef ZT_TRACE
-    uint64_t timeSinceLastDump = now - _lastSummaryDump;
-    if (! force && timeSinceLastDump < ZT_BOND_STATUS_INTERVAL) {
-        return;
-    }
-    _lastSummaryDump = now;
-    float overhead = (_overheadBytes / (timeSinceLastDump / 1000.0f) / 1000.0f);
-    _overheadBytes = 0;
-    log("bond: ready=%d, bp=%d, fi=%" PRIu64 ", mi=%d, ud=%d, dd=%d, flows=%zu, leaf=%d, overhead=%f KB/s, links=(%d/%d)",
-        isReady(),
-        _policy,
-        _failoverInterval,
-        _monitorInterval,
-        _upDelay,
-        _downDelay,
-        _flows.size(),
-        _isLeaf,
-        overhead,
-        _numAliveLinks,
-        _numTotalLinks);
-    for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-        if (_paths[i].p) {
-            dumpPathStatus(now, i);
-        }
-    }
-    log("");
+	uint64_t timeSinceLastDump = now - _lastSummaryDump;
+	if (! force && timeSinceLastDump < ZT_BOND_STATUS_INTERVAL) {
+		return;
+	}
+	_lastSummaryDump = now;
+	float overhead = (_overheadBytes / (timeSinceLastDump / 1000.0f) / 1000.0f);
+	_overheadBytes = 0;
+	log("bond: ready=%d, bp=%d, fi=%" PRIu64 ", mi=%d, ud=%d, dd=%d, flows=%zu, leaf=%d, overhead=%f KB/s, links=(%d/%d)",
+		isReady(),
+		_policy,
+		_failoverInterval,
+		_monitorInterval,
+		_upDelay,
+		_downDelay,
+		_flows.size(),
+		_isLeaf,
+		overhead,
+		_numAliveLinks,
+		_numTotalLinks);
+	for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+		if (_paths[i].p) {
+			dumpPathStatus(now, i);
+		}
+	}
+	log("");
 #endif
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 1470 - 1470
node/Bond.hpp

@@ -35,66 +35,66 @@ enum ZT_BondQualityWeightIndex { ZT_QOS_LAT_MAX_IDX, ZT_QOS_PDV_MAX_IDX, ZT_QOS_
  *  Multipath bonding policy
  */
 enum ZT_BondBondingPolicy {
-    /**
-     * Normal operation. No fault tolerance, no load balancing
-     */
-    ZT_BOND_POLICY_NONE = 0,
-
-    /**
-     * Sends traffic out on only one path at a time. Configurable immediate
-     * fail-over.
-     */
-    ZT_BOND_POLICY_ACTIVE_BACKUP = 1,
-
-    /**
-     * Sends traffic out on all paths
-     */
-    ZT_BOND_POLICY_BROADCAST = 2,
-
-    /**
-     * Stripes packets across all paths
-     */
-    ZT_BOND_POLICY_BALANCE_RR = 3,
-
-    /**
-     * Packets destined for specific peers will always be sent over the same
-     * path.
-     */
-    ZT_BOND_POLICY_BALANCE_XOR = 4,
-
-    /**
-     * Balances flows among all paths according to path performance
-     */
-    ZT_BOND_POLICY_BALANCE_AWARE = 5
+	/**
+	 * Normal operation. No fault tolerance, no load balancing
+	 */
+	ZT_BOND_POLICY_NONE = 0,
+
+	/**
+	 * Sends traffic out on only one path at a time. Configurable immediate
+	 * fail-over.
+	 */
+	ZT_BOND_POLICY_ACTIVE_BACKUP = 1,
+
+	/**
+	 * Sends traffic out on all paths
+	 */
+	ZT_BOND_POLICY_BROADCAST = 2,
+
+	/**
+	 * Stripes packets across all paths
+	 */
+	ZT_BOND_POLICY_BALANCE_RR = 3,
+
+	/**
+	 * Packets destined for specific peers will always be sent over the same
+	 * path.
+	 */
+	ZT_BOND_POLICY_BALANCE_XOR = 4,
+
+	/**
+	 * Balances flows among all paths according to path performance
+	 */
+	ZT_BOND_POLICY_BALANCE_AWARE = 5
 };
 
 /**
  * Multipath active re-selection policy (linkSelectMethod)
  */
 enum ZT_BondLinkSelectMethod {
-    /**
-     * Primary link regains status as active link whenever it comes back up
-     * (default when links are explicitly specified)
-     */
-    ZT_BOND_RESELECTION_POLICY_ALWAYS = 0,
-
-    /**
-     * Primary link regains status as active link when it comes back up and
-     * (if) it is better than the currently-active link.
-     */
-    ZT_BOND_RESELECTION_POLICY_BETTER = 1,
-
-    /**
-     * Primary link regains status as active link only if the currently-active
-     * link fails.
-     */
-    ZT_BOND_RESELECTION_POLICY_FAILURE = 2,
-
-    /**
-     * The primary link can change if a superior path is detected.
-     * (default if user provides no fail-over guidance)
-     */
-    ZT_BOND_RESELECTION_POLICY_OPTIMIZE = 3
+	/**
+	 * Primary link regains status as active link whenever it comes back up
+	 * (default when links are explicitly specified)
+	 */
+	ZT_BOND_RESELECTION_POLICY_ALWAYS = 0,
+
+	/**
+	 * Primary link regains status as active link when it comes back up and
+	 * (if) it is better than the currently-active link.
+	 */
+	ZT_BOND_RESELECTION_POLICY_BETTER = 1,
+
+	/**
+	 * Primary link regains status as active link only if the currently-active
+	 * link fails.
+	 */
+	ZT_BOND_RESELECTION_POLICY_FAILURE = 2,
+
+	/**
+	 * The primary link can change if a superior path is detected.
+	 * (default if user provides no fail-over guidance)
+	 */
+	ZT_BOND_RESELECTION_POLICY_OPTIMIZE = 3
 };
 
 /**
@@ -110,204 +110,204 @@ enum ZT_BondLinkMode { ZT_BOND_SLAVE_MODE_PRIMARY = 0, ZT_BOND_SLAVE_MODE_SPARE
 namespace ZeroTier {
 
 class Link {
-    friend class SharedPtr<Link>;
+	friend class SharedPtr<Link>;
 
   public:
-    /**
-     *
-     * @param ifnameStr
-     * @param ipvPref
-     * @param capacity
-     * @param enabled
-     * @param mode
-     * @param failoverToLinkStr
-     */
-    Link(std::string ifnameStr, uint8_t ipvPref, uint16_t mtu, uint32_t capacity, bool enabled, uint8_t mode, std::string failoverToLinkStr)
-        : _ifnameStr(ifnameStr)
-        , _ipvPref(ipvPref)
-        , _mtu(mtu)
-        , _capacity(capacity)
-        , _relativeCapacity(0.0)
-        , _enabled(enabled)
-        , _mode(mode)
-        , _failoverToLinkStr(failoverToLinkStr)
-        , _isUserSpecified(false)
-    {
-    }
-
-    /**
-     * @return The string representation of this link's underlying interface's system name.
-     */
-    inline std::string ifname()
-    {
-        return _ifnameStr;
-    }
-
-    /**
-     * @return Whether this link is designated as a primary.
-     */
-    inline bool primary()
-    {
-        return _mode == ZT_BOND_SLAVE_MODE_PRIMARY;
-    }
-
-    /**
-     * @return Whether this link is designated as a spare.
-     */
-    inline bool spare()
-    {
-        return _mode == ZT_BOND_SLAVE_MODE_SPARE;
-    }
-
-    /**
-     * @return The name of the link interface that should be used in the event of a failure.
-     */
-    inline std::string failoverToLink()
-    {
-        return _failoverToLinkStr;
-    }
-
-    /**
-     * @return Whether this link interface was specified by the user or auto-detected.
-     */
-    inline bool isUserSpecified()
-    {
-        return _isUserSpecified;
-    }
-
-    /**
-     * Signify that this link was specified by the user and not the result of auto-detection.
-     *
-     * @param isUserSpecified
-     */
-    inline void setAsUserSpecified(bool isUserSpecified)
-    {
-        _isUserSpecified = isUserSpecified;
-    }
-
-    /**
-     * @return Whether or not the user has specified failover instructions.
-     */
-    inline bool userHasSpecifiedFailoverInstructions()
-    {
-        return _failoverToLinkStr.length();
-    }
-
-    /**
-     * @return The capacity of the link relative to others in the bond.
-     */
-    inline float relativeCapacity()
-    {
-        return _relativeCapacity;
-    }
-
-    /**
-     * Sets the capacity of the link relative to others in the bond.
-     *
-     * @param relativeCapacity The capacity relative to the rest of the link.
-     */
-    inline void setRelativeCapacity(float relativeCapacity)
-    {
-        _relativeCapacity = relativeCapacity;
-    }
-
-    /**
-     * @return The absolute capacity of the link (as specified by the user.)
-     */
-    inline uint32_t capacity()
-    {
-        return _capacity;
-    }
-
-    /**
-     * @return The address preference for this link (as specified by the user.)
-     */
-    inline uint8_t ipvPref()
-    {
-        return _ipvPref;
-    }
-
-    /**
-     * @return The MTU for this link (as specified by the user.)
-     */
-    inline uint16_t mtu()
-    {
-        return _mtu;
-    }
-
-    /**
-     * @return The mode (e.g. primary/spare) for this link (as specified by the user.)
-     */
-    inline uint8_t mode()
-    {
-        return _mode;
-    }
-
-    /**
-     * @return Whether this link is enabled or disabled
-     */
-    inline uint8_t enabled()
-    {
-        return _enabled;
-    }
+	/**
+	 *
+	 * @param ifnameStr
+	 * @param ipvPref
+	 * @param capacity
+	 * @param enabled
+	 * @param mode
+	 * @param failoverToLinkStr
+	 */
+	Link(std::string ifnameStr, uint8_t ipvPref, uint16_t mtu, uint32_t capacity, bool enabled, uint8_t mode, std::string failoverToLinkStr)
+		: _ifnameStr(ifnameStr)
+		, _ipvPref(ipvPref)
+		, _mtu(mtu)
+		, _capacity(capacity)
+		, _relativeCapacity(0.0)
+		, _enabled(enabled)
+		, _mode(mode)
+		, _failoverToLinkStr(failoverToLinkStr)
+		, _isUserSpecified(false)
+	{
+	}
+
+	/**
+	 * @return The string representation of this link's underlying interface's system name.
+	 */
+	inline std::string ifname()
+	{
+		return _ifnameStr;
+	}
+
+	/**
+	 * @return Whether this link is designated as a primary.
+	 */
+	inline bool primary()
+	{
+		return _mode == ZT_BOND_SLAVE_MODE_PRIMARY;
+	}
+
+	/**
+	 * @return Whether this link is designated as a spare.
+	 */
+	inline bool spare()
+	{
+		return _mode == ZT_BOND_SLAVE_MODE_SPARE;
+	}
+
+	/**
+	 * @return The name of the link interface that should be used in the event of a failure.
+	 */
+	inline std::string failoverToLink()
+	{
+		return _failoverToLinkStr;
+	}
+
+	/**
+	 * @return Whether this link interface was specified by the user or auto-detected.
+	 */
+	inline bool isUserSpecified()
+	{
+		return _isUserSpecified;
+	}
+
+	/**
+	 * Signify that this link was specified by the user and not the result of auto-detection.
+	 *
+	 * @param isUserSpecified
+	 */
+	inline void setAsUserSpecified(bool isUserSpecified)
+	{
+		_isUserSpecified = isUserSpecified;
+	}
+
+	/**
+	 * @return Whether or not the user has specified failover instructions.
+	 */
+	inline bool userHasSpecifiedFailoverInstructions()
+	{
+		return _failoverToLinkStr.length();
+	}
+
+	/**
+	 * @return The capacity of the link relative to others in the bond.
+	 */
+	inline float relativeCapacity()
+	{
+		return _relativeCapacity;
+	}
+
+	/**
+	 * Sets the capacity of the link relative to others in the bond.
+	 *
+	 * @param relativeCapacity The capacity relative to the rest of the link.
+	 */
+	inline void setRelativeCapacity(float relativeCapacity)
+	{
+		_relativeCapacity = relativeCapacity;
+	}
+
+	/**
+	 * @return The absolute capacity of the link (as specified by the user.)
+	 */
+	inline uint32_t capacity()
+	{
+		return _capacity;
+	}
+
+	/**
+	 * @return The address preference for this link (as specified by the user.)
+	 */
+	inline uint8_t ipvPref()
+	{
+		return _ipvPref;
+	}
+
+	/**
+	 * @return The MTU for this link (as specified by the user.)
+	 */
+	inline uint16_t mtu()
+	{
+		return _mtu;
+	}
+
+	/**
+	 * @return The mode (e.g. primary/spare) for this link (as specified by the user.)
+	 */
+	inline uint8_t mode()
+	{
+		return _mode;
+	}
+
+	/**
+	 * @return Whether this link is enabled or disabled
+	 */
+	inline uint8_t enabled()
+	{
+		return _enabled;
+	}
 
   private:
-    /**
-     * String representation of underlying interface's system name
-     */
-    std::string _ifnameStr;
-
-    /**
-     * What preference (if any) a user has for IP protocol version used in
-     * path aggregations. Preference is expressed in the order of the digits:
-     *
-     *  0: no preference
-     *  4: IPv4 only
-     *  6: IPv6 only
-     * 46: IPv4 over IPv6
-     * 64: IPv6 over IPv4
-     */
-    uint8_t _ipvPref;
-
-    /**
-     * The physical-layer MTU for this link
-     */
-    uint16_t _mtu;
-
-    /**
-     * User-specified capacity of this link
-     */
-    uint32_t _capacity;
-
-    /**
-     * Speed relative to other specified links (computed by Bond)
-     */
-    float _relativeCapacity;
-
-    /**
-     * Whether this link is enabled, or (disabled (possibly bad config))
-     */
-    uint8_t _enabled;
-
-    /**
-     * Whether this link is designated as a primary, a spare, or no preference.
-     */
-    uint8_t _mode;
-
-    /**
-     * The specific name of the link to be used in the event that this
-     * link fails.
-     */
-    std::string _failoverToLinkStr;
-
-    /**
-     * Whether or not this link was created as a result of manual user specification. This is
-     * important to know because certain policy decisions are dependent on whether the user
-     * intents to use a specific set of interfaces.
-     */
-    bool _isUserSpecified;
-
-    AtomicCounter __refCount;
+	/**
+	 * String representation of underlying interface's system name
+	 */
+	std::string _ifnameStr;
+
+	/**
+	 * What preference (if any) a user has for IP protocol version used in
+	 * path aggregations. Preference is expressed in the order of the digits:
+	 *
+	 *  0: no preference
+	 *  4: IPv4 only
+	 *  6: IPv6 only
+	 * 46: IPv4 over IPv6
+	 * 64: IPv6 over IPv4
+	 */
+	uint8_t _ipvPref;
+
+	/**
+	 * The physical-layer MTU for this link
+	 */
+	uint16_t _mtu;
+
+	/**
+	 * User-specified capacity of this link
+	 */
+	uint32_t _capacity;
+
+	/**
+	 * Speed relative to other specified links (computed by Bond)
+	 */
+	float _relativeCapacity;
+
+	/**
+	 * Whether this link is enabled, or (disabled (possibly bad config))
+	 */
+	uint8_t _enabled;
+
+	/**
+	 * Whether this link is designated as a primary, a spare, or no preference.
+	 */
+	uint8_t _mode;
+
+	/**
+	 * The specific name of the link to be used in the event that this
+	 * link fails.
+	 */
+	std::string _failoverToLinkStr;
+
+	/**
+	 * Whether or not this link was created as a result of manual user specification. This is
+	 * important to know because certain policy decisions are dependent on whether the user
+	 * intents to use a specific set of interfaces.
+	 */
+	bool _isUserSpecified;
+
+	AtomicCounter __refCount;
 };
 
 class Link;
@@ -315,1259 +315,1259 @@ class Peer;
 
 class Bond {
   public:
-    /**
-     * Stop bond's internal functions (can be resumed)
-     */
-    void stopBond();
-
-    /**
-     * Start or resume a bond's internal functions
-     */
-    void startBond();
-
-    /**
-     * @return Whether this link is permitted to become a member of a bond.
-     */
-    static bool linkAllowed(std::string& policyAlias, SharedPtr<Link> link);
-
-    /**
-     * @return The minimum interval required to poll the active bonds to fulfill all active monitoring timing requirements.
-     */
-    static int minReqMonitorInterval()
-    {
-        return _minReqMonitorInterval;
-    }
-
-    /**
-     * @return Whether the bonding layer is currently set up to be used.
-     */
-    static bool inUse()
-    {
-        return ! _bondPolicyTemplates.empty() || _defaultPolicy;
-    }
-
-    /**
-     * Sets a pointer to an instance of _binder used by the Bond to get interface data
-     */
-    static void setBinder(Binder* b)
-    {
-        _binder = b;
-    }
-
-    /**
-     * @param basePolicyName Bonding policy name (See ZeroTierOne.h)
-     * @return The bonding policy code for a given human-readable bonding policy name
-     */
-    static int getPolicyCodeByStr(const std::string& basePolicyName)
-    {
-        if (basePolicyName == "active-backup") {
-            return 1;
-        }
-        if (basePolicyName == "broadcast") {
-            return 2;
-        }
-        if (basePolicyName == "balance-rr") {
-            return 3;
-        }
-        if (basePolicyName == "balance-xor") {
-            return 4;
-        }
-        if (basePolicyName == "balance-aware") {
-            return 5;
-        }
-        return 0;   // "none"
-    }
-
-    /**
-     * @param policy Bonding policy code (See ZeroTierOne.h)
-     * @return The human-readable name for the given bonding policy code
-     */
-    static std::string getPolicyStrByCode(int policy)
-    {
-        if (policy == 1) {
-            return "active-backup";
-        }
-        if (policy == 2) {
-            return "broadcast";
-        }
-        if (policy == 3) {
-            return "balance-rr";
-        }
-        if (policy == 4) {
-            return "balance-xor";
-        }
-        if (policy == 5) {
-            return "balance-aware";
-        }
-        return "none";
-    }
-
-    /**
-     * Sets the default bonding policy for new or undefined bonds.
-     *
-     * @param bp Bonding policy
-     */
-    static void setBondingLayerDefaultPolicy(uint8_t bp)
-    {
-        _defaultPolicy = bp;
-    }
-
-    /**
-     * Sets the default (custom) bonding policy for new or undefined bonds.
-     *
-     * @param alias Human-readable string alias for bonding policy
-     */
-    static void setBondingLayerDefaultPolicyStr(std::string alias)
-    {
-        _defaultPolicyStr = alias;
-    }
-
-    /**
-     * Add a user-defined link to a given bonding policy.
-     *
-     * @param policyAlias User-defined custom name for variant of bonding policy
-     * @param link Pointer to new link definition
-     */
-    static void addCustomLink(std::string& policyAlias, SharedPtr<Link> link);
-
-    /**
-     * Add a user-defined bonding policy that is based on one of the standard types.
-     *
-     * @param newBond Pointer to custom Bond object
-     * @return Whether a uniquely-named custom policy was successfully added
-     */
-    static bool addCustomPolicy(const SharedPtr<Bond>& newBond);
-
-    /**
-     * Assigns a specific bonding policy
-     *
-     * @param identity
-     * @param policyAlias
-     * @return
-     */
-    static bool assignBondingPolicyToPeer(int64_t identity, const std::string& policyAlias);
-
-    /**
-     * Get pointer to bond by a given peer ID
-     *
-     * @param peer Remote peer ID
-     * @return A pointer to the Bond
-     */
-    static SharedPtr<Bond> getBondByPeerId(int64_t identity);
-
-    /**
-     * Set MTU for link by given interface name and IP address (across all bonds)
-     *
-     * @param mtu MTU to be used on this link
-     * @param ifStr interface name to match
-     * @param ipStr IP address to match
-     * @return Whether the MTU was set
-     */
-    static bool setAllMtuByTuple(uint16_t mtu, const std::string& ifStr, const std::string& ipStr);
-
-    /**
-     * Set MTU for link by given interface name and IP address
-     *
-     * @param mtu MTU to be used on this link
-     * @param ifStr interface name to match
-     * @param ipStr IP address to match
-     * @return Whether the MTU was set
-     */
-    bool setMtuByTuple(uint16_t mtu, const std::string& ifStr, const std::string& ipStr);
-
-    /**
-     * Add a new bond to the bond controller.
-     *
-     * @param renv Runtime environment
-     * @param peer Remote peer that this bond services
-     * @return A pointer to the newly created Bond
-     */
-    static SharedPtr<Bond> createBond(const RuntimeEnvironment* renv, const SharedPtr<Peer>& peer);
-
-    /**
-     * Remove a bond from the bond controller.
-     *
-     * @param peerId Remote peer that this bond services
-     */
-    static void destroyBond(uint64_t peerId);
-
-    /**
-     * Periodically perform maintenance tasks for the bonding layer.
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param now Current time
-     */
-    static void processBackgroundTasks(void* tPtr, int64_t now);
-
-    /**
-     * Gets a reference to a physical link definition given a policy alias and a local socket.
-     *
-     * @param policyAlias Policy in use
-     * @param localSocket Local source socket
-     * @param createIfNeeded Whether a Link object is created if the name wasn't previously in the link map
-     * @return Physical link definition
-     */
-    SharedPtr<Link> getLinkBySocket(const std::string& policyAlias, uint64_t localSocket, bool createIfNeeded);
-
-    /**
-     * Gets a reference to a physical link definition given its human-readable system name.
-     *
-     * @param policyAlias Policy in use
-     * @param ifname Alphanumeric human-readable name
-     * @return Physical link definition
-     */
-    static SharedPtr<Link> getLinkByName(const std::string& policyAlias, const std::string& ifname);
+	/**
+	 * Stop bond's internal functions (can be resumed)
+	 */
+	void stopBond();
+
+	/**
+	 * Start or resume a bond's internal functions
+	 */
+	void startBond();
+
+	/**
+	 * @return Whether this link is permitted to become a member of a bond.
+	 */
+	static bool linkAllowed(std::string& policyAlias, SharedPtr<Link> link);
+
+	/**
+	 * @return The minimum interval required to poll the active bonds to fulfill all active monitoring timing requirements.
+	 */
+	static int minReqMonitorInterval()
+	{
+		return _minReqMonitorInterval;
+	}
+
+	/**
+	 * @return Whether the bonding layer is currently set up to be used.
+	 */
+	static bool inUse()
+	{
+		return ! _bondPolicyTemplates.empty() || _defaultPolicy;
+	}
+
+	/**
+	 * Sets a pointer to an instance of _binder used by the Bond to get interface data
+	 */
+	static void setBinder(Binder* b)
+	{
+		_binder = b;
+	}
+
+	/**
+	 * @param basePolicyName Bonding policy name (See ZeroTierOne.h)
+	 * @return The bonding policy code for a given human-readable bonding policy name
+	 */
+	static int getPolicyCodeByStr(const std::string& basePolicyName)
+	{
+		if (basePolicyName == "active-backup") {
+			return 1;
+		}
+		if (basePolicyName == "broadcast") {
+			return 2;
+		}
+		if (basePolicyName == "balance-rr") {
+			return 3;
+		}
+		if (basePolicyName == "balance-xor") {
+			return 4;
+		}
+		if (basePolicyName == "balance-aware") {
+			return 5;
+		}
+		return 0;	// "none"
+	}
+
+	/**
+	 * @param policy Bonding policy code (See ZeroTierOne.h)
+	 * @return The human-readable name for the given bonding policy code
+	 */
+	static std::string getPolicyStrByCode(int policy)
+	{
+		if (policy == 1) {
+			return "active-backup";
+		}
+		if (policy == 2) {
+			return "broadcast";
+		}
+		if (policy == 3) {
+			return "balance-rr";
+		}
+		if (policy == 4) {
+			return "balance-xor";
+		}
+		if (policy == 5) {
+			return "balance-aware";
+		}
+		return "none";
+	}
+
+	/**
+	 * Sets the default bonding policy for new or undefined bonds.
+	 *
+	 * @param bp Bonding policy
+	 */
+	static void setBondingLayerDefaultPolicy(uint8_t bp)
+	{
+		_defaultPolicy = bp;
+	}
+
+	/**
+	 * Sets the default (custom) bonding policy for new or undefined bonds.
+	 *
+	 * @param alias Human-readable string alias for bonding policy
+	 */
+	static void setBondingLayerDefaultPolicyStr(std::string alias)
+	{
+		_defaultPolicyStr = alias;
+	}
+
+	/**
+	 * Add a user-defined link to a given bonding policy.
+	 *
+	 * @param policyAlias User-defined custom name for variant of bonding policy
+	 * @param link Pointer to new link definition
+	 */
+	static void addCustomLink(std::string& policyAlias, SharedPtr<Link> link);
+
+	/**
+	 * Add a user-defined bonding policy that is based on one of the standard types.
+	 *
+	 * @param newBond Pointer to custom Bond object
+	 * @return Whether a uniquely-named custom policy was successfully added
+	 */
+	static bool addCustomPolicy(const SharedPtr<Bond>& newBond);
+
+	/**
+	 * Assigns a specific bonding policy
+	 *
+	 * @param identity
+	 * @param policyAlias
+	 * @return
+	 */
+	static bool assignBondingPolicyToPeer(int64_t identity, const std::string& policyAlias);
+
+	/**
+	 * Get pointer to bond by a given peer ID
+	 *
+	 * @param peer Remote peer ID
+	 * @return A pointer to the Bond
+	 */
+	static SharedPtr<Bond> getBondByPeerId(int64_t identity);
+
+	/**
+	 * Set MTU for link by given interface name and IP address (across all bonds)
+	 *
+	 * @param mtu MTU to be used on this link
+	 * @param ifStr interface name to match
+	 * @param ipStr IP address to match
+	 * @return Whether the MTU was set
+	 */
+	static bool setAllMtuByTuple(uint16_t mtu, const std::string& ifStr, const std::string& ipStr);
+
+	/**
+	 * Set MTU for link by given interface name and IP address
+	 *
+	 * @param mtu MTU to be used on this link
+	 * @param ifStr interface name to match
+	 * @param ipStr IP address to match
+	 * @return Whether the MTU was set
+	 */
+	bool setMtuByTuple(uint16_t mtu, const std::string& ifStr, const std::string& ipStr);
+
+	/**
+	 * Add a new bond to the bond controller.
+	 *
+	 * @param renv Runtime environment
+	 * @param peer Remote peer that this bond services
+	 * @return A pointer to the newly created Bond
+	 */
+	static SharedPtr<Bond> createBond(const RuntimeEnvironment* renv, const SharedPtr<Peer>& peer);
+
+	/**
+	 * Remove a bond from the bond controller.
+	 *
+	 * @param peerId Remote peer that this bond services
+	 */
+	static void destroyBond(uint64_t peerId);
+
+	/**
+	 * Periodically perform maintenance tasks for the bonding layer.
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param now Current time
+	 */
+	static void processBackgroundTasks(void* tPtr, int64_t now);
+
+	/**
+	 * Gets a reference to a physical link definition given a policy alias and a local socket.
+	 *
+	 * @param policyAlias Policy in use
+	 * @param localSocket Local source socket
+	 * @param createIfNeeded Whether a Link object is created if the name wasn't previously in the link map
+	 * @return Physical link definition
+	 */
+	SharedPtr<Link> getLinkBySocket(const std::string& policyAlias, uint64_t localSocket, bool createIfNeeded);
+
+	/**
+	 * Gets a reference to a physical link definition given its human-readable system name.
+	 *
+	 * @param policyAlias Policy in use
+	 * @param ifname Alphanumeric human-readable name
+	 * @return Physical link definition
+	 */
+	static SharedPtr<Link> getLinkByName(const std::string& policyAlias, const std::string& ifname);
 
   private:
-    static Phy<Bond*>* _phy;
+	static Phy<Bond*>* _phy;
 
-    static Mutex _bonds_m;
-    static Mutex _links_m;
+	static Mutex _bonds_m;
+	static Mutex _links_m;
 
-    /**
-     * The minimum required monitoring interval among all bonds
-     */
-    static int _minReqMonitorInterval;
+	/**
+	 * The minimum required monitoring interval among all bonds
+	 */
+	static int _minReqMonitorInterval;
 
-    /**
-     * The default bonding policy used for new bonds unless otherwise specified.
-     */
-    static uint8_t _defaultPolicy;
+	/**
+	 * The default bonding policy used for new bonds unless otherwise specified.
+	 */
+	static uint8_t _defaultPolicy;
 
-    /**
-     * The default bonding policy used for new bonds unless otherwise specified.
-     */
-    static std::string _defaultPolicyStr;
+	/**
+	 * The default bonding policy used for new bonds unless otherwise specified.
+	 */
+	static std::string _defaultPolicyStr;
 
-    /**
-     * All currently active bonds.
-     */
-    static std::map<int64_t, SharedPtr<Bond> > _bonds;
+	/**
+	 * All currently active bonds.
+	 */
+	static std::map<int64_t, SharedPtr<Bond> > _bonds;
 
-    /**
-     * Map of peers to custom bonding policies
-     */
-    static std::map<int64_t, std::string> _policyTemplateAssignments;
+	/**
+	 * Map of peers to custom bonding policies
+	 */
+	static std::map<int64_t, std::string> _policyTemplateAssignments;
 
-    /**
-     * User-defined bonding policies (can be assigned to a peer)
-     */
-    static std::map<std::string, SharedPtr<Bond> > _bondPolicyTemplates;
+	/**
+	 * User-defined bonding policies (can be assigned to a peer)
+	 */
+	static std::map<std::string, SharedPtr<Bond> > _bondPolicyTemplates;
 
-    /**
-     * Set of links defined for a given bonding policy
-     */
-    static std::map<std::string, std::vector<SharedPtr<Link> > > _linkDefinitions;
+	/**
+	 * Set of links defined for a given bonding policy
+	 */
+	static std::map<std::string, std::vector<SharedPtr<Link> > > _linkDefinitions;
 
-    /**
-     * Set of link objects mapped to their physical interfaces
-     */
-    static std::map<std::string, std::map<std::string, SharedPtr<Link> > > _interfaceToLinkMap;
+	/**
+	 * Set of link objects mapped to their physical interfaces
+	 */
+	static std::map<std::string, std::map<std::string, SharedPtr<Link> > > _interfaceToLinkMap;
 
-    struct NominatedPath;
-    struct Flow;
+	struct NominatedPath;
+	struct Flow;
 
-    friend class SharedPtr<Bond>;
-    friend class Peer;
+	friend class SharedPtr<Bond>;
+	friend class Peer;
 
   public:
-    void dumpInfo(int64_t now, bool force);
-    std::string pathToStr(const SharedPtr<Path>& path);
-    void dumpPathStatus(int64_t now, int pathIdx);
-
-    SharedPtr<Link> getLink(const SharedPtr<Path>& path);
-
-    /**
-     * Constructor
-     *
-     *
-     */
-    Bond(const RuntimeEnvironment* renv);
-
-    /**
-     * Constructor. Creates a bond based off of ZT defaults
-     *
-     * @param renv Runtime environment
-     * @param policy Bonding policy
-     * @param peer
-     */
-    Bond(const RuntimeEnvironment* renv, int policy, const SharedPtr<Peer>& peer);
-
-    /**
-     * Constructor. For use when user intends to manually specify parameters
-     *
-     * @param basePolicy
-     * @param policyAlias
-     * @param peer
-     */
-    Bond(const RuntimeEnvironment* renv, std::string& basePolicy, std::string& policyAlias, const SharedPtr<Peer>& peer);
-
-    /**
-     * Constructor. Creates a bond based off of a user-defined bond template
-     *
-     * @param renv Runtime environment
-     * @param original
-     * @param peer
-     */
-    Bond(const RuntimeEnvironment* renv, SharedPtr<Bond> originalBond, const SharedPtr<Peer>& peer);
-
-    /**
-     * @return The human-readable name of the bonding policy
-     */
-    std::string policyAlias()
-    {
-        return _policyAlias;
-    }
-
-    /**
-     * Return whether this bond is able to properly process traffic
-     */
-    bool isReady()
-    {
-        return _numBondedPaths;
-    }
-
-    /**
-     * Inform the bond about the path that its peer (owning object) just learned about.
-     * If the path is allowed to be used, it will be inducted into the bond on a trial
-     * period where link statistics will be collected to judge its quality.
-     *
-     * @param path Newly-learned Path which should now be handled by the Bond
-     * @param now Current time
-     */
-    void nominatePathToBond(const SharedPtr<Path>& path, int64_t now);
-
-    /**
-     * Add a nominated path to the bond. This merely maps the index from the nominated set
-     * to a smaller set and sets the path's bonded flag to true.
-     *
-     * @param nominatedIdx The index in the nominated set
-     * @param bondedIdx The index in the bonded set (subset of nominated)
-     */
-    void addPathToBond(int nominatedIdx, int bondedIdx);
-
-    /**
-     * Check path states and perform bond rebuilds if needed.
-     *
-     * @param now Current time
-     * @param rebuild Whether or not the bond should be reconstructed.
-     */
-    void curateBond(int64_t now, bool rebuild);
-
-    /**
-     * Periodically perform statistical summaries of quality metrics for all paths.
-     *
-     * @param now Current time
-     */
-    void estimatePathQuality(int64_t now);
-
-    /**
-     * Record an invalid incoming packet. This packet failed
-     * MAC/compression/cipher checks and will now contribute to a
-     * Packet Error Ratio (PER).
-     *
-     * @param path Path over which packet was received
-     */
-    void recordIncomingInvalidPacket(const SharedPtr<Path>& path);
-
-    /**
-     * Record statistics on outbound an packet.
-     *
-     * @param path Path over which packet is being sent
-     * @param packetId Packet ID
-     * @param payloadLength Packet data length
-     * @param verb Packet verb
-     * @param flowId Flow ID
-     * @param now Current time
-     */
-    void recordOutgoingPacket(const SharedPtr<Path>& path, uint64_t packetId, uint16_t payloadLength, Packet::Verb verb, int32_t flowId, int64_t now);
-
-    /**
-     * Process the contents of an inbound VERB_QOS_MEASUREMENT to gather path quality observations.
-     *
-     * @param now Current time
-     * @param count Number of records
-     * @param rx_id table of packet IDs
-     * @param rx_ts table of holding times
-     */
-    void receivedQoS(const SharedPtr<Path>& path, int64_t now, int count, uint64_t* rx_id, uint16_t* rx_ts);
-
-    /**
-     * Process the contents of an inbound VERB_ACK to gather path quality observations.
-     *
-     * @param pathIdx Path over which packet was received
-     * @param now Current time
-     * @param ackedBytes Number of bytes ACKed by this VERB_ACK
-     */
-    void receivedAck(int pathIdx, int64_t now, int32_t ackedBytes);
-
-    /**
-     * Generate the contents of a VERB_QOS_MEASUREMENT packet.
-     *
-     * @param now Current time
-     * @param qosBuffer destination buffer
-     * @return Size of payload
-     */
-    int32_t generateQoSPacket(int pathIdx, int64_t now, char* qosBuffer);
-
-    /**
-     * Record statistics for an inbound packet.
-     *
-     * @param path Path over which packet was received
-     * @param packetId Packet ID
-     * @param payloadLength Packet data length
-     * @param verb Packet verb
-     * @param flowId Flow ID
-     * @param now Current time
-     */
-    void recordIncomingPacket(const SharedPtr<Path>& path, uint64_t packetId, uint16_t payloadLength, Packet::Verb verb, int32_t flowId, int64_t now);
-
-    /**
-     * Determines the most appropriate path for packet and flow egress. This decision is made by
-     * the underlying bonding policy as well as QoS-related statistical observations of path quality.
-     *
-     * @param now Current time
-     * @param flowId Flow ID
-     * @return Pointer to suggested Path
-     */
-    SharedPtr<Path> getAppropriatePath(int64_t now, int32_t flowId);
-
-    /**
-     * Creates a new flow record
-     *
-     * @param np Path over which flow shall be handled
-     * @param flowId Flow ID
-     * @param entropy A byte of entropy to be used by the bonding algorithm
-     * @param now Current time
-     * @return Pointer to newly-created Flow
-     */
-    SharedPtr<Flow> createFlow(int pathIdx, int32_t flowId, unsigned char entropy, int64_t now);
-
-    /**
-     * Removes flow records that are past a certain age limit.
-     *
-     * @param age Age threshold to be forgotten
-     * @param oldest Whether only the oldest shall be forgotten
-     * @param now Current time
-     */
-    void forgetFlowsWhenNecessary(uint64_t age, bool oldest, int64_t now);
-
-    /**
-     * Assigns a new flow to a bonded path
-     *
-     * @param flow Flow to be assigned
-     * @param now Current time
-     * @param reassign Whether this flow is being re-assigned to another path
-     */
-    bool assignFlowToBondedPath(SharedPtr<Flow>& flow, int64_t now, bool reassign);
-
-    /**
-     * Determine whether a path change should occur given the remote peer's reported utility and our
-     * local peer's known utility. This has the effect of assigning inbound and outbound traffic to
-     * the same path.
-     *
-     * @param now Current time
-     * @param path Path over which the negotiation request was received
-     * @param remoteUtility How much utility the remote peer claims to gain by using the declared path
-     */
-    void processIncomingPathNegotiationRequest(uint64_t now, SharedPtr<Path>& path, int16_t remoteUtility);
-
-    /**
-     * Determine state of path synchronization and whether a negotiation request
-     * shall be sent to the peer.
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param now Current time
-     */
-    void pathNegotiationCheck(void* tPtr, int64_t now);
-
-    /**
-     * Sends a VERB_ACK to the remote peer.
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param path Path over which packet should be sent
-     * @param localSocket Local source socket
-     * @param atAddress
-     * @param now Current time
-     */
-    void sendACK(void* tPtr, int pathIdx, int64_t localSocket, const InetAddress& atAddress, int64_t now);
-
-    /**
-     * Sends a VERB_QOS_MEASUREMENT to the remote peer.
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param path Path over which packet should be sent
-     * @param localSocket Local source socket
-     * @param atAddress
-     * @param now Current time
-     */
-    void sendQOS_MEASUREMENT(void* tPtr, int pathIdx, int64_t localSocket, const InetAddress& atAddress, int64_t now);
-
-    /**
-     * Sends a VERB_PATH_NEGOTIATION_REQUEST to the remote peer.
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param path Path over which packet should be sent
-     */
-    void sendPATH_NEGOTIATION_REQUEST(void* tPtr, int pathIdx);
-
-    /**
-     *
-     * @param now Current time
-     */
-    void processBalanceTasks(int64_t now);
-
-    /**
-     * Perform periodic tasks unique to active-backup
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param now Current time
-     */
-    void processActiveBackupTasks(void* tPtr, int64_t now);
-
-    /**
-     * Switches the active link in an active-backup scenario to the next best during
-     * a failover event.
-     *
-     * @param now Current time
-     */
-    void dequeueNextActiveBackupPath(uint64_t now);
-
-    /**
-     * Zero all timers
-     */
-    void initTimers();
-
-    /**
-     * Set bond parameters to reasonable defaults, these may later be overwritten by
-     * user-specified parameters.
-     *
-     * @param policy Bonding policy
-     * @param templateBond
-     */
-    void setBondParameters(int policy, SharedPtr<Bond> templateBond, bool useTemplate);
-
-    /**
-     * Check and assign user-specified link quality parameters to this bond.
-     *
-     * @param weights Set of user-specified parameters
-     * @param len Length of parameter vector
-     */
-    void setUserLinkQualitySpec(float weights[], int len);
-
-    /**
-     * @return Whether the user has defined links for use on this bond
-     */
-    inline bool userHasSpecifiedLinks()
-    {
-        return _userHasSpecifiedLinks;
-    }
-
-    /**
-     * @return Whether the user has defined a set of failover link(s) for this bond
-     */
-    inline bool userHasSpecifiedFailoverInstructions()
-    {
-        return _userHasSpecifiedFailoverInstructions;
-    };
-
-    /**
-     * @return Whether the user has specified a primary link
-     */
-    inline bool userHasSpecifiedPrimaryLink()
-    {
-        return _userHasSpecifiedPrimaryLink;
-    }
-
-    /**
-     * @return Whether the user has specified link capacities
-     */
-    inline bool userHasSpecifiedLinkCapacities()
-    {
-        return _userHasSpecifiedLinkCapacities;
-    }
-
-    /**
-     * Periodically perform maintenance tasks for each active bond.
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param now Current time
-     */
-    void processBackgroundBondTasks(void* tPtr, int64_t now);
-
-    /**
-     * Rate limit gate for VERB_ACK
-     *
-     * @param now Current time
-     * @return Whether the incoming packet should be rate-gated
-     */
-    inline bool rateGateACK(const int64_t now)
-    {
-        _ackCutoffCount++;
-        int numToDrain = _lastAckRateCheck ? (now - _lastAckRateCheck) / ZT_ACK_DRAINAGE_DIVISOR : _ackCutoffCount;
-        _lastAckRateCheck = now;
-        if (_ackCutoffCount > numToDrain) {
-            _ackCutoffCount -= numToDrain;
-        }
-        else {
-            _ackCutoffCount = 0;
-        }
-        return (_ackCutoffCount < ZT_ACK_CUTOFF_LIMIT);
-    }
-
-    /**
-     * Rate limit gate for VERB_QOS_MEASUREMENT
-     *
-     * @param now Current time
-     * @return Whether the incoming packet should be rate-gated
-     */
-    inline bool rateGateQoS(int64_t now, SharedPtr<Path>& path)
-    {
-        char pathStr[64] = { 0 };
-        path->address().toString(pathStr);
-        uint64_t diff = now - _lastQoSRateCheck;
-        if ((diff) <= (_qosSendInterval / ZT_MAX_PEER_NETWORK_PATHS)) {
-            ++_qosCutoffCount;
-        }
-        else {
-            _qosCutoffCount = 0;
-        }
-        _lastQoSRateCheck = now;
-        return (_qosCutoffCount < (ZT_MAX_PEER_NETWORK_PATHS * 2));
-    }
-
-    /**
-     * Rate limit gate for VERB_PATH_NEGOTIATION_REQUEST
-     *
-     * @param now Current time
-     * @return Whether the incoming packet should be rate-gated
-     */
-    inline bool rateGatePathNegotiation(int64_t now, SharedPtr<Path>& path)
-    {
-        char pathStr[64] = { 0 };
-        path->address().toString(pathStr);
-        int diff = now - _lastPathNegotiationReceived;
-        if ((diff) <= (ZT_PATH_NEGOTIATION_CUTOFF_TIME / ZT_MAX_PEER_NETWORK_PATHS)) {
-            ++_pathNegotiationCutoffCount;
-        }
-        else {
-            _pathNegotiationCutoffCount = 0;
-        }
-        _lastPathNegotiationReceived = now;
-        return (_pathNegotiationCutoffCount < (ZT_MAX_PEER_NETWORK_PATHS * 2));
-    }
-
-    /**
-     * @param interval Maximum amount of time user expects a failover to take on this bond.
-     */
-    inline void setFailoverInterval(uint32_t interval)
-    {
-        _failoverInterval = interval;
-    }
-
-    /**
-     * @param interval Maximum amount of time user expects a failover to take on this bond.
-     */
-    inline uint32_t getFailoverInterval()
-    {
-        return _failoverInterval;
-    }
-
-    /**
-     * @param strategy Strategy that the bond uses to prob for path aliveness and quality
-     */
-    inline void setLinkMonitorStrategy(uint8_t strategy)
-    {
-        _linkMonitorStrategy = strategy;
-    }
-
-    /**
-     * @return the current up delay parameter
-     */
-    inline uint16_t getUpDelay()
-    {
-        return _upDelay;
-    }
-
-    /**
-     * @param upDelay Length of time before a newly-discovered path is admitted to the bond
-     */
-    inline void setUpDelay(int upDelay)
-    {
-        if (upDelay >= 0) {
-            _upDelay = upDelay;
-        }
-    }
-
-    /**
-     * @return Length of time before a newly-failed path is removed from the bond
-     */
-    inline uint16_t getDownDelay()
-    {
-        return _downDelay;
-    }
-
-    /**
-     * @param downDelay Length of time before a newly-failed path is removed from the bond
-     */
-    inline void setDownDelay(int downDelay)
-    {
-        if (downDelay >= 0) {
-            _downDelay = downDelay;
-        }
-    }
-
-    /**
-     * @return The current monitoring interval for the bond
-     */
-    inline int monitorInterval()
-    {
-        return _monitorInterval;
-    }
-
-    /**
-     * Set the current monitoring interval for the bond (can be overridden with intervals specific to certain links.)
-     *
-     * @param monitorInterval How often gratuitous VERB_HELLO(s) are sent to remote peer.
-     */
-    inline void setBondMonitorInterval(uint16_t interval)
-    {
-        _monitorInterval = interval;
-    }
-
-    /**
-     * @param policy Bonding policy for this bond
-     */
-
-    inline void setPolicy(uint8_t policy)
-    {
-        _policy = policy;
-    }
-
-    /**
-     * @return the current bonding policy
-     */
-    inline uint8_t policy()
-    {
-        return _policy;
-    }
-
-    /**
-     * @return the number of links in this bond which are considered alive
-     */
-    inline uint8_t getNumAliveLinks()
-    {
-        return _numAliveLinks;
-    };
-
-    /**
-     * @return the number of links in this bond
-     */
-    inline uint8_t getNumTotalLinks()
-    {
-        return _numTotalLinks;
-    }
-
-    /**
-     * @return Whether flow-hashing is currently supported for this bond.
-     */
-    bool flowHashingSupported()
-    {
-        return _policy == ZT_BOND_POLICY_BALANCE_XOR || _policy == ZT_BOND_POLICY_BALANCE_AWARE;
-    }
-
-    /**
-     *
-     * @param packetsPerLink
-     */
-    inline void setPacketsPerLink(int packetsPerLink)
-    {
-        _packetsPerLink = packetsPerLink;
-    }
-
-    /**
-     * @return Number of packets to be sent on each interface in a balance-rr bond
-     */
-    inline int getPacketsPerLink()
-    {
-        return _packetsPerLink;
-    }
-
-    /**
-     *
-     * @param linkSelectMethod
-     */
-    inline void setLinkSelectMethod(uint8_t method)
-    {
-        _abLinkSelectMethod = method;
-    }
-
-    /**
-     *
-     * @return
-     */
-    inline uint8_t getLinkSelectMethod()
-    {
-        return _abLinkSelectMethod;
-    }
-
-    /**
-     *
-     * @param allowPathNegotiation
-     */
-    inline void setAllowPathNegotiation(bool allowPathNegotiation)
-    {
-        _allowPathNegotiation = allowPathNegotiation;
-    }
-
-    /**
-     *
-     * @return
-     */
-    inline bool allowPathNegotiation()
-    {
-        return _allowPathNegotiation;
-    }
-
-    /**
-     * Forcibly rotates the currently active link used in an active-backup bond to the next link in the failover queue
-     *
-     * @return True if this operation succeeded, false if otherwise
-     */
-    bool abForciblyRotateLink();
-
-    /**
-     * Emit message to tracing system but with added timestamp and subsystem info
-     */
-    void log(const char* fmt, ...)
+	void dumpInfo(int64_t now, bool force);
+	std::string pathToStr(const SharedPtr<Path>& path);
+	void dumpPathStatus(int64_t now, int pathIdx);
+
+	SharedPtr<Link> getLink(const SharedPtr<Path>& path);
+
+	/**
+	 * Constructor
+	 *
+	 *
+	 */
+	Bond(const RuntimeEnvironment* renv);
+
+	/**
+	 * Constructor. Creates a bond based off of ZT defaults
+	 *
+	 * @param renv Runtime environment
+	 * @param policy Bonding policy
+	 * @param peer
+	 */
+	Bond(const RuntimeEnvironment* renv, int policy, const SharedPtr<Peer>& peer);
+
+	/**
+	 * Constructor. For use when user intends to manually specify parameters
+	 *
+	 * @param basePolicy
+	 * @param policyAlias
+	 * @param peer
+	 */
+	Bond(const RuntimeEnvironment* renv, std::string& basePolicy, std::string& policyAlias, const SharedPtr<Peer>& peer);
+
+	/**
+	 * Constructor. Creates a bond based off of a user-defined bond template
+	 *
+	 * @param renv Runtime environment
+	 * @param original
+	 * @param peer
+	 */
+	Bond(const RuntimeEnvironment* renv, SharedPtr<Bond> originalBond, const SharedPtr<Peer>& peer);
+
+	/**
+	 * @return The human-readable name of the bonding policy
+	 */
+	std::string policyAlias()
+	{
+		return _policyAlias;
+	}
+
+	/**
+	 * Return whether this bond is able to properly process traffic
+	 */
+	bool isReady()
+	{
+		return _numBondedPaths;
+	}
+
+	/**
+	 * Inform the bond about the path that its peer (owning object) just learned about.
+	 * If the path is allowed to be used, it will be inducted into the bond on a trial
+	 * period where link statistics will be collected to judge its quality.
+	 *
+	 * @param path Newly-learned Path which should now be handled by the Bond
+	 * @param now Current time
+	 */
+	void nominatePathToBond(const SharedPtr<Path>& path, int64_t now);
+
+	/**
+	 * Add a nominated path to the bond. This merely maps the index from the nominated set
+	 * to a smaller set and sets the path's bonded flag to true.
+	 *
+	 * @param nominatedIdx The index in the nominated set
+	 * @param bondedIdx The index in the bonded set (subset of nominated)
+	 */
+	void addPathToBond(int nominatedIdx, int bondedIdx);
+
+	/**
+	 * Check path states and perform bond rebuilds if needed.
+	 *
+	 * @param now Current time
+	 * @param rebuild Whether or not the bond should be reconstructed.
+	 */
+	void curateBond(int64_t now, bool rebuild);
+
+	/**
+	 * Periodically perform statistical summaries of quality metrics for all paths.
+	 *
+	 * @param now Current time
+	 */
+	void estimatePathQuality(int64_t now);
+
+	/**
+	 * Record an invalid incoming packet. This packet failed
+	 * MAC/compression/cipher checks and will now contribute to a
+	 * Packet Error Ratio (PER).
+	 *
+	 * @param path Path over which packet was received
+	 */
+	void recordIncomingInvalidPacket(const SharedPtr<Path>& path);
+
+	/**
+	 * Record statistics on outbound an packet.
+	 *
+	 * @param path Path over which packet is being sent
+	 * @param packetId Packet ID
+	 * @param payloadLength Packet data length
+	 * @param verb Packet verb
+	 * @param flowId Flow ID
+	 * @param now Current time
+	 */
+	void recordOutgoingPacket(const SharedPtr<Path>& path, uint64_t packetId, uint16_t payloadLength, Packet::Verb verb, int32_t flowId, int64_t now);
+
+	/**
+	 * Process the contents of an inbound VERB_QOS_MEASUREMENT to gather path quality observations.
+	 *
+	 * @param now Current time
+	 * @param count Number of records
+	 * @param rx_id table of packet IDs
+	 * @param rx_ts table of holding times
+	 */
+	void receivedQoS(const SharedPtr<Path>& path, int64_t now, int count, uint64_t* rx_id, uint16_t* rx_ts);
+
+	/**
+	 * Process the contents of an inbound VERB_ACK to gather path quality observations.
+	 *
+	 * @param pathIdx Path over which packet was received
+	 * @param now Current time
+	 * @param ackedBytes Number of bytes ACKed by this VERB_ACK
+	 */
+	void receivedAck(int pathIdx, int64_t now, int32_t ackedBytes);
+
+	/**
+	 * Generate the contents of a VERB_QOS_MEASUREMENT packet.
+	 *
+	 * @param now Current time
+	 * @param qosBuffer destination buffer
+	 * @return Size of payload
+	 */
+	int32_t generateQoSPacket(int pathIdx, int64_t now, char* qosBuffer);
+
+	/**
+	 * Record statistics for an inbound packet.
+	 *
+	 * @param path Path over which packet was received
+	 * @param packetId Packet ID
+	 * @param payloadLength Packet data length
+	 * @param verb Packet verb
+	 * @param flowId Flow ID
+	 * @param now Current time
+	 */
+	void recordIncomingPacket(const SharedPtr<Path>& path, uint64_t packetId, uint16_t payloadLength, Packet::Verb verb, int32_t flowId, int64_t now);
+
+	/**
+	 * Determines the most appropriate path for packet and flow egress. This decision is made by
+	 * the underlying bonding policy as well as QoS-related statistical observations of path quality.
+	 *
+	 * @param now Current time
+	 * @param flowId Flow ID
+	 * @return Pointer to suggested Path
+	 */
+	SharedPtr<Path> getAppropriatePath(int64_t now, int32_t flowId);
+
+	/**
+	 * Creates a new flow record
+	 *
+	 * @param np Path over which flow shall be handled
+	 * @param flowId Flow ID
+	 * @param entropy A byte of entropy to be used by the bonding algorithm
+	 * @param now Current time
+	 * @return Pointer to newly-created Flow
+	 */
+	SharedPtr<Flow> createFlow(int pathIdx, int32_t flowId, unsigned char entropy, int64_t now);
+
+	/**
+	 * Removes flow records that are past a certain age limit.
+	 *
+	 * @param age Age threshold to be forgotten
+	 * @param oldest Whether only the oldest shall be forgotten
+	 * @param now Current time
+	 */
+	void forgetFlowsWhenNecessary(uint64_t age, bool oldest, int64_t now);
+
+	/**
+	 * Assigns a new flow to a bonded path
+	 *
+	 * @param flow Flow to be assigned
+	 * @param now Current time
+	 * @param reassign Whether this flow is being re-assigned to another path
+	 */
+	bool assignFlowToBondedPath(SharedPtr<Flow>& flow, int64_t now, bool reassign);
+
+	/**
+	 * Determine whether a path change should occur given the remote peer's reported utility and our
+	 * local peer's known utility. This has the effect of assigning inbound and outbound traffic to
+	 * the same path.
+	 *
+	 * @param now Current time
+	 * @param path Path over which the negotiation request was received
+	 * @param remoteUtility How much utility the remote peer claims to gain by using the declared path
+	 */
+	void processIncomingPathNegotiationRequest(uint64_t now, SharedPtr<Path>& path, int16_t remoteUtility);
+
+	/**
+	 * Determine state of path synchronization and whether a negotiation request
+	 * shall be sent to the peer.
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param now Current time
+	 */
+	void pathNegotiationCheck(void* tPtr, int64_t now);
+
+	/**
+	 * Sends a VERB_ACK to the remote peer.
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param path Path over which packet should be sent
+	 * @param localSocket Local source socket
+	 * @param atAddress
+	 * @param now Current time
+	 */
+	void sendACK(void* tPtr, int pathIdx, int64_t localSocket, const InetAddress& atAddress, int64_t now);
+
+	/**
+	 * Sends a VERB_QOS_MEASUREMENT to the remote peer.
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param path Path over which packet should be sent
+	 * @param localSocket Local source socket
+	 * @param atAddress
+	 * @param now Current time
+	 */
+	void sendQOS_MEASUREMENT(void* tPtr, int pathIdx, int64_t localSocket, const InetAddress& atAddress, int64_t now);
+
+	/**
+	 * Sends a VERB_PATH_NEGOTIATION_REQUEST to the remote peer.
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param path Path over which packet should be sent
+	 */
+	void sendPATH_NEGOTIATION_REQUEST(void* tPtr, int pathIdx);
+
+	/**
+	 *
+	 * @param now Current time
+	 */
+	void processBalanceTasks(int64_t now);
+
+	/**
+	 * Perform periodic tasks unique to active-backup
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param now Current time
+	 */
+	void processActiveBackupTasks(void* tPtr, int64_t now);
+
+	/**
+	 * Switches the active link in an active-backup scenario to the next best during
+	 * a failover event.
+	 *
+	 * @param now Current time
+	 */
+	void dequeueNextActiveBackupPath(uint64_t now);
+
+	/**
+	 * Zero all timers
+	 */
+	void initTimers();
+
+	/**
+	 * Set bond parameters to reasonable defaults, these may later be overwritten by
+	 * user-specified parameters.
+	 *
+	 * @param policy Bonding policy
+	 * @param templateBond
+	 */
+	void setBondParameters(int policy, SharedPtr<Bond> templateBond, bool useTemplate);
+
+	/**
+	 * Check and assign user-specified link quality parameters to this bond.
+	 *
+	 * @param weights Set of user-specified parameters
+	 * @param len Length of parameter vector
+	 */
+	void setUserLinkQualitySpec(float weights[], int len);
+
+	/**
+	 * @return Whether the user has defined links for use on this bond
+	 */
+	inline bool userHasSpecifiedLinks()
+	{
+		return _userHasSpecifiedLinks;
+	}
+
+	/**
+	 * @return Whether the user has defined a set of failover link(s) for this bond
+	 */
+	inline bool userHasSpecifiedFailoverInstructions()
+	{
+		return _userHasSpecifiedFailoverInstructions;
+	};
+
+	/**
+	 * @return Whether the user has specified a primary link
+	 */
+	inline bool userHasSpecifiedPrimaryLink()
+	{
+		return _userHasSpecifiedPrimaryLink;
+	}
+
+	/**
+	 * @return Whether the user has specified link capacities
+	 */
+	inline bool userHasSpecifiedLinkCapacities()
+	{
+		return _userHasSpecifiedLinkCapacities;
+	}
+
+	/**
+	 * Periodically perform maintenance tasks for each active bond.
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param now Current time
+	 */
+	void processBackgroundBondTasks(void* tPtr, int64_t now);
+
+	/**
+	 * Rate limit gate for VERB_ACK
+	 *
+	 * @param now Current time
+	 * @return Whether the incoming packet should be rate-gated
+	 */
+	inline bool rateGateACK(const int64_t now)
+	{
+		_ackCutoffCount++;
+		int numToDrain = _lastAckRateCheck ? (now - _lastAckRateCheck) / ZT_ACK_DRAINAGE_DIVISOR : _ackCutoffCount;
+		_lastAckRateCheck = now;
+		if (_ackCutoffCount > numToDrain) {
+			_ackCutoffCount -= numToDrain;
+		}
+		else {
+			_ackCutoffCount = 0;
+		}
+		return (_ackCutoffCount < ZT_ACK_CUTOFF_LIMIT);
+	}
+
+	/**
+	 * Rate limit gate for VERB_QOS_MEASUREMENT
+	 *
+	 * @param now Current time
+	 * @return Whether the incoming packet should be rate-gated
+	 */
+	inline bool rateGateQoS(int64_t now, SharedPtr<Path>& path)
+	{
+		char pathStr[64] = { 0 };
+		path->address().toString(pathStr);
+		uint64_t diff = now - _lastQoSRateCheck;
+		if ((diff) <= (_qosSendInterval / ZT_MAX_PEER_NETWORK_PATHS)) {
+			++_qosCutoffCount;
+		}
+		else {
+			_qosCutoffCount = 0;
+		}
+		_lastQoSRateCheck = now;
+		return (_qosCutoffCount < (ZT_MAX_PEER_NETWORK_PATHS * 2));
+	}
+
+	/**
+	 * Rate limit gate for VERB_PATH_NEGOTIATION_REQUEST
+	 *
+	 * @param now Current time
+	 * @return Whether the incoming packet should be rate-gated
+	 */
+	inline bool rateGatePathNegotiation(int64_t now, SharedPtr<Path>& path)
+	{
+		char pathStr[64] = { 0 };
+		path->address().toString(pathStr);
+		int diff = now - _lastPathNegotiationReceived;
+		if ((diff) <= (ZT_PATH_NEGOTIATION_CUTOFF_TIME / ZT_MAX_PEER_NETWORK_PATHS)) {
+			++_pathNegotiationCutoffCount;
+		}
+		else {
+			_pathNegotiationCutoffCount = 0;
+		}
+		_lastPathNegotiationReceived = now;
+		return (_pathNegotiationCutoffCount < (ZT_MAX_PEER_NETWORK_PATHS * 2));
+	}
+
+	/**
+	 * @param interval Maximum amount of time user expects a failover to take on this bond.
+	 */
+	inline void setFailoverInterval(uint32_t interval)
+	{
+		_failoverInterval = interval;
+	}
+
+	/**
+	 * @param interval Maximum amount of time user expects a failover to take on this bond.
+	 */
+	inline uint32_t getFailoverInterval()
+	{
+		return _failoverInterval;
+	}
+
+	/**
+	 * @param strategy Strategy that the bond uses to prob for path aliveness and quality
+	 */
+	inline void setLinkMonitorStrategy(uint8_t strategy)
+	{
+		_linkMonitorStrategy = strategy;
+	}
+
+	/**
+	 * @return the current up delay parameter
+	 */
+	inline uint16_t getUpDelay()
+	{
+		return _upDelay;
+	}
+
+	/**
+	 * @param upDelay Length of time before a newly-discovered path is admitted to the bond
+	 */
+	inline void setUpDelay(int upDelay)
+	{
+		if (upDelay >= 0) {
+			_upDelay = upDelay;
+		}
+	}
+
+	/**
+	 * @return Length of time before a newly-failed path is removed from the bond
+	 */
+	inline uint16_t getDownDelay()
+	{
+		return _downDelay;
+	}
+
+	/**
+	 * @param downDelay Length of time before a newly-failed path is removed from the bond
+	 */
+	inline void setDownDelay(int downDelay)
+	{
+		if (downDelay >= 0) {
+			_downDelay = downDelay;
+		}
+	}
+
+	/**
+	 * @return The current monitoring interval for the bond
+	 */
+	inline int monitorInterval()
+	{
+		return _monitorInterval;
+	}
+
+	/**
+	 * Set the current monitoring interval for the bond (can be overridden with intervals specific to certain links.)
+	 *
+	 * @param monitorInterval How often gratuitous VERB_HELLO(s) are sent to remote peer.
+	 */
+	inline void setBondMonitorInterval(uint16_t interval)
+	{
+		_monitorInterval = interval;
+	}
+
+	/**
+	 * @param policy Bonding policy for this bond
+	 */
+
+	inline void setPolicy(uint8_t policy)
+	{
+		_policy = policy;
+	}
+
+	/**
+	 * @return the current bonding policy
+	 */
+	inline uint8_t policy()
+	{
+		return _policy;
+	}
+
+	/**
+	 * @return the number of links in this bond which are considered alive
+	 */
+	inline uint8_t getNumAliveLinks()
+	{
+		return _numAliveLinks;
+	};
+
+	/**
+	 * @return the number of links in this bond
+	 */
+	inline uint8_t getNumTotalLinks()
+	{
+		return _numTotalLinks;
+	}
+
+	/**
+	 * @return Whether flow-hashing is currently supported for this bond.
+	 */
+	bool flowHashingSupported()
+	{
+		return _policy == ZT_BOND_POLICY_BALANCE_XOR || _policy == ZT_BOND_POLICY_BALANCE_AWARE;
+	}
+
+	/**
+	 *
+	 * @param packetsPerLink
+	 */
+	inline void setPacketsPerLink(int packetsPerLink)
+	{
+		_packetsPerLink = packetsPerLink;
+	}
+
+	/**
+	 * @return Number of packets to be sent on each interface in a balance-rr bond
+	 */
+	inline int getPacketsPerLink()
+	{
+		return _packetsPerLink;
+	}
+
+	/**
+	 *
+	 * @param linkSelectMethod
+	 */
+	inline void setLinkSelectMethod(uint8_t method)
+	{
+		_abLinkSelectMethod = method;
+	}
+
+	/**
+	 *
+	 * @return
+	 */
+	inline uint8_t getLinkSelectMethod()
+	{
+		return _abLinkSelectMethod;
+	}
+
+	/**
+	 *
+	 * @param allowPathNegotiation
+	 */
+	inline void setAllowPathNegotiation(bool allowPathNegotiation)
+	{
+		_allowPathNegotiation = allowPathNegotiation;
+	}
+
+	/**
+	 *
+	 * @return
+	 */
+	inline bool allowPathNegotiation()
+	{
+		return _allowPathNegotiation;
+	}
+
+	/**
+	 * Forcibly rotates the currently active link used in an active-backup bond to the next link in the failover queue
+	 *
+	 * @return True if this operation succeeded, false if otherwise
+	 */
+	bool abForciblyRotateLink();
+
+	/**
+	 * Emit message to tracing system but with added timestamp and subsystem info
+	 */
+	void log(const char* fmt, ...)
 #ifdef __GNUC__
-        __attribute__((format(printf, 2, 3)))
+		__attribute__((format(printf, 2, 3)))
 #endif
-    {
-        // if (_peerId != 0x0 && _peerId != 0x0) { return; }
+	{
+		// if (_peerId != 0x0 && _peerId != 0x0) { return; }
 #ifdef ZT_TRACE
-        time_t rawtime;
-        struct tm* timeinfo;
-        char timestamp[80];
-        time(&rawtime);
-        timeinfo = localtime(&rawtime);
-        strftime(timestamp, 80, "%F %T", timeinfo);
+		time_t rawtime;
+		struct tm* timeinfo;
+		char timestamp[80];
+		time(&rawtime);
+		timeinfo = localtime(&rawtime);
+		strftime(timestamp, 80, "%F %T", timeinfo);
 #define MAX_BOND_MSG_LEN 1024
-        char traceMsg[MAX_BOND_MSG_LEN];
-        char userMsg[MAX_BOND_MSG_LEN];
-        va_list args;
-        va_start(args, fmt);
-        if (vsnprintf(userMsg, sizeof(userMsg), fmt, args) < 0) {
-            fprintf(stderr, "Encountered format encoding error while writing to trace log\n");
-            return;
-        }
-        snprintf(traceMsg, MAX_BOND_MSG_LEN, "%s (%llx/%s) %s", timestamp, _peerId, _policyAlias.c_str(), userMsg);
-        va_end(args);
-        RR->t->bondStateMessage(NULL, traceMsg);
+		char traceMsg[MAX_BOND_MSG_LEN];
+		char userMsg[MAX_BOND_MSG_LEN];
+		va_list args;
+		va_start(args, fmt);
+		if (vsnprintf(userMsg, sizeof(userMsg), fmt, args) < 0) {
+			fprintf(stderr, "Encountered format encoding error while writing to trace log\n");
+			return;
+		}
+		snprintf(traceMsg, MAX_BOND_MSG_LEN, "%s (%llx/%s) %s", timestamp, _peerId, _policyAlias.c_str(), userMsg);
+		va_end(args);
+		RR->t->bondStateMessage(NULL, traceMsg);
 #undef MAX_MSG_LEN
 #endif
-    }
+	}
 
-    /**
-     * Emit message to tracing system but with added timestamp and subsystem info
-     */
-    void debug(const char* fmt, ...)
+	/**
+	 * Emit message to tracing system but with added timestamp and subsystem info
+	 */
+	void debug(const char* fmt, ...)
 #ifdef __GNUC__
-        __attribute__((format(printf, 2, 3)))
+		__attribute__((format(printf, 2, 3)))
 #endif
-    {
-        // if (_peerId != 0x0 && _peerId != 0x0) { return; }
+	{
+		// if (_peerId != 0x0 && _peerId != 0x0) { return; }
 #ifdef ZT_DEBUG
-        time_t rawtime;
-        struct tm* timeinfo;
-        char timestamp[80];
-        time(&rawtime);
-        timeinfo = localtime(&rawtime);
-        strftime(timestamp, 80, "%F %T", timeinfo);
+		time_t rawtime;
+		struct tm* timeinfo;
+		char timestamp[80];
+		time(&rawtime);
+		timeinfo = localtime(&rawtime);
+		strftime(timestamp, 80, "%F %T", timeinfo);
 #define MAX_BOND_MSG_LEN 1024
-        char traceMsg[MAX_BOND_MSG_LEN];
-        char userMsg[MAX_BOND_MSG_LEN];
-        va_list args;
-        va_start(args, fmt);
-        if (vsnprintf(userMsg, sizeof(userMsg), fmt, args) < 0) {
-            fprintf(stderr, "Encountered format encoding error while writing to trace log\n");
-            return;
-        }
-        snprintf(traceMsg, MAX_BOND_MSG_LEN, "%s (%llx/%s) %s", timestamp, _peerId, _policyAlias.c_str(), userMsg);
-        va_end(args);
-        RR->t->bondStateMessage(NULL, traceMsg);
+		char traceMsg[MAX_BOND_MSG_LEN];
+		char userMsg[MAX_BOND_MSG_LEN];
+		va_list args;
+		va_start(args, fmt);
+		if (vsnprintf(userMsg, sizeof(userMsg), fmt, args) < 0) {
+			fprintf(stderr, "Encountered format encoding error while writing to trace log\n");
+			return;
+		}
+		snprintf(traceMsg, MAX_BOND_MSG_LEN, "%s (%llx/%s) %s", timestamp, _peerId, _policyAlias.c_str(), userMsg);
+		va_end(args);
+		RR->t->bondStateMessage(NULL, traceMsg);
 #undef MAX_MSG_LEN
 #endif
-    }
+	}
 
   private:
-    struct NominatedPath {
-        NominatedPath()
-            : lastAckSent(0)
-            , lastAckReceived(0)
-            , lastQoSReceived(0)
-            , unackedBytes(0)
-            , packetsReceivedSinceLastAck(0)
-            , lastQoSMeasurement(0)
-            , lastThroughputEstimation(0)
-            , lastRefractoryUpdate(0)
-            , lastAliveToggle(0)
-            , alive(false)
-            , eligible(true)
-            , lastEligibility(0)
-            , whenNominated(0)
-            , refractoryPeriod(0)
-            , ipvPref(0)
-            , mode(0)
-            , onlyPathOnLink(false)
-            , bonded(false)
-            , negotiated(false)
-            , shouldAvoid(false)
-            , assignedFlowCount(0)
-            , latency(0)
-            , latencyVariance(0)
-            , packetLossRatio(0)
-            , packetErrorRatio(0)
-            , relativeQuality(0)
-            , relativeLinkCapacity(0)
-            , failoverScore(0)
-            , packetsReceivedSinceLastQoS(0)
-            , packetsIn(0)
-            , packetsOut(0)
-            , localPort(0)
-        {
-        }
-
-        /**
-         * Set or update a refractory period for the path.
-         *
-         * @param punishment How much a path should be punished
-         * @param pathFailure Whether this call is the result of a recent path failure
-         */
-        inline void adjustRefractoryPeriod(int64_t now, uint32_t punishment, bool pathFailure)
-        {
-            if (pathFailure) {
-                unsigned int suggestedRefractoryPeriod = refractoryPeriod ? punishment + (refractoryPeriod * 2) : punishment;
-                refractoryPeriod = std::min(suggestedRefractoryPeriod, (unsigned int)ZT_BOND_MAX_REFRACTORY_PERIOD);
-                lastRefractoryUpdate = 0;
-            }
-            else {
-                uint32_t drainRefractory = 0;
-                if (lastRefractoryUpdate) {
-                    drainRefractory = (now - lastRefractoryUpdate);
-                }
-                else {
-                    drainRefractory = (now - lastAliveToggle);
-                }
-                lastRefractoryUpdate = now;
-                if (refractoryPeriod > drainRefractory) {
-                    refractoryPeriod -= drainRefractory;
-                }
-                else {
-                    refractoryPeriod = 0;
-                    lastRefractoryUpdate = 0;
-                }
-            }
-        }
-
-        /**
-         * @return True if a path is permitted to be used in a bond (according to user pref.)
-         */
-        inline bool allowed()
-        {
-            return (! ipvPref || ((p->_addr.isV4() && (ipvPref == 4 || ipvPref == 46 || ipvPref == 64)) || ((p->_addr.isV6() && (ipvPref == 6 || ipvPref == 46 || ipvPref == 64)))));
-        }
-
-        /**
-         * @return True if a path exists on a link marked as a spare
-         */
-        inline bool isSpare()
-        {
-            return mode == ZT_BOND_SLAVE_MODE_SPARE;
-        }
-
-        /**
-         * @return True if a path is preferred over another on the same physical link (according to user pref.)
-         */
-        inline bool preferred()
-        {
-            return onlyPathOnLink || (p->_addr.isV4() && (ipvPref == 4 || ipvPref == 46)) || (p->_addr.isV6() && (ipvPref == 6 || ipvPref == 64));
-        }
-
-        /**
-         * @param now Current time
-         * @return Whether a QoS (VERB_QOS_MEASUREMENT) packet needs to be emitted at this time
-         */
-        inline bool needsToSendQoS(int64_t now, uint64_t qosSendInterval)
-        {
-            return ((packetsReceivedSinceLastQoS >= ZT_QOS_TABLE_SIZE) || ((now - lastQoSMeasurement) > qosSendInterval)) && packetsReceivedSinceLastQoS;
-        }
-
-        /**
-         * @param now Current time
-         * @return Whether an ACK (VERB_ACK) packet needs to be emitted at this time
-         */
-        inline bool needsToSendAck(int64_t now, uint64_t ackSendInterval)
-        {
-            return ((now - lastAckSent) >= ackSendInterval || (packetsReceivedSinceLastAck == ZT_QOS_TABLE_SIZE)) && packetsReceivedSinceLastAck;
-        }
-
-        /**
-         * Reset packet counters
-         */
-        inline void resetPacketCounts()
-        {
-            packetsIn = 0;
-            packetsOut = 0;
-        }
-
-        std::map<uint64_t, uint64_t> qosStatsOut;   // id:egress_time
-        std::map<uint64_t, uint64_t> qosStatsIn;    // id:now
-        std::map<uint64_t, uint64_t> ackStatsIn;    // id:now
-
-        RingBuffer<int, ZT_QOS_SHORTTERM_SAMPLE_WIN_SIZE> qosRecordSize;
-        RingBuffer<float, ZT_QOS_SHORTTERM_SAMPLE_WIN_SIZE> qosRecordLossSamples;
-        RingBuffer<uint64_t, ZT_QOS_SHORTTERM_SAMPLE_WIN_SIZE> throughputSamples;
-        RingBuffer<bool, ZT_QOS_SHORTTERM_SAMPLE_WIN_SIZE> packetValiditySamples;
-        RingBuffer<float, ZT_QOS_SHORTTERM_SAMPLE_WIN_SIZE> throughputVarianceSamples;
-        RingBuffer<uint16_t, ZT_QOS_SHORTTERM_SAMPLE_WIN_SIZE> latencySamples;
-
-        uint64_t lastAckSent;
-        uint64_t lastAckReceived;
-        uint64_t lastQoSReceived;
-        uint64_t unackedBytes;
-        uint64_t packetsReceivedSinceLastAck;
-
-        uint64_t lastQoSMeasurement;         // Last time that a VERB_QOS_MEASUREMENT was sent out on this path.
-        uint64_t lastThroughputEstimation;   // Last time that the path's throughput was estimated.
-        uint64_t lastRefractoryUpdate;       // The last time that the refractory period was updated.
-        uint64_t lastAliveToggle;            // The last time that the path was marked as "alive".
-        bool alive;
-        bool eligible;                // State of eligibility at last check. Used for determining state changes.
-        uint64_t lastEligibility;     // The last time that this path was eligible
-        uint64_t whenNominated;       // Timestamp indicating when this path's trial period began.
-        uint32_t refractoryPeriod;    // Amount of time that this path will be prevented from becoming a member of a bond.
-        uint8_t ipvPref;              // IP version preference inherited from the physical link.
-        uint8_t mode;                 // Mode inherited from the physical link.
-        bool onlyPathOnLink;          // IP version preference inherited from the physical link.
-        bool enabled;                 // Enabled state inherited from the physical link.
-        bool bonded;                  // Whether this path is currently part of a bond.
-        bool negotiated;              // Whether this path was intentionally negotiated by either peer.
-        bool shouldAvoid;             // Whether flows should be moved from this path. Current traffic flows will be re-allocated immediately.
-        uint16_t assignedFlowCount;   // The number of flows currently assigned to this path.
-        float latency;                // The mean latency (computed from a sliding window.)
-        float latencyVariance;        // Packet delay variance (computed from a sliding window.)
-        float packetLossRatio;        // The ratio of lost packets to received packets.
-        float packetErrorRatio;       // The ratio of packets that failed their MAC/CRC checks to those that did not.
-        float relativeQuality;        // The relative quality of the link.
-        float relativeLinkCapacity;   // The relative capacity of the link.
-
-        uint32_t failoverScore;                // Score that indicates to what degree this path is preferred over others that are available to the bonding policy. (specifically for active-backup)
-        int32_t packetsReceivedSinceLastQoS;   // Number of packets received since the last VERB_QOS_MEASUREMENT was sent to the remote peer.
-
-        /**
-         * Counters used for tracking path load.
-         */
-        int packetsIn;
-        int packetsOut;
-
-        uint16_t localPort;
-
-        // AtomicCounter __refCount;
-
-        SharedPtr<Path> p;
-        void set(uint64_t now, const SharedPtr<Path>& path)
-        {
-            p = path;
-            whenNominated = now;
-        }
-    };
-
-    /**
-     * Paths nominated to the bond (may or may not actually be bonded)
-     */
-    NominatedPath _paths[ZT_MAX_PEER_NETWORK_PATHS];
-
-    inline int getNominatedPathIdx(const SharedPtr<Path>& path)
-    {
-        for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-            if (_paths[i].p == path) {
-                return i;
-            }
-        }
-        return ZT_MAX_PEER_NETWORK_PATHS;
-    }
-
-    /**
-     * A protocol flow that is identified by the origin and destination port.
-     */
-    struct Flow {
-        /**
-         * @param flowId Given flow ID
-         * @param now Current time
-         */
-        Flow(int32_t flowId, int64_t now) : id(flowId), bytesIn(0), bytesOut(0), lastActivity(now), lastPathReassignment(0), assignedPath(ZT_MAX_PEER_NETWORK_PATHS)
-        {
-        }
-
-        /**
-         * Reset flow statistics
-         */
-        inline void resetByteCounts()
-        {
-            bytesIn = 0;
-            bytesOut = 0;
-        }
-
-        /**
-         * How long since a packet was sent or received in this flow
-         *
-         * @param now Current time
-         * @return The age of the flow in terms of last recorded activity
-         */
-        int64_t age(int64_t now)
-        {
-            return now - lastActivity;
-        }
-
-        /**
-         * @param path Assigned path over which this flow should be handled
-         */
-        inline void assignPath(int pathIdx, int64_t now)
-        {
-            assignedPath = pathIdx;
-            lastPathReassignment = now;
-        }
-
-        AtomicCounter __refCount;
-
-        int32_t id;                     // Flow ID used for hashing and path selection
-        uint64_t bytesIn;               // Used for tracking flow size
-        uint64_t bytesOut;              // Used for tracking flow size
-        int64_t lastActivity;           // The last time that this flow handled traffic
-        int64_t lastPathReassignment;   // Time of last path assignment. Used for anti-flapping
-        int assignedPath;               // Index of path to which this flow is assigned
-    };
-
-    const RuntimeEnvironment* RR;
-    AtomicCounter __refCount;
-
-    std::string _policyAlias;   // Custom name given by the user to this bond type.
-
-    static Binder* _binder;
-
-    /**
-     * Set of indices corresponding to paths currently included in the bond proper. This
-     * may only be updated during a call to curateBond(). The reason for this is so that
-     * we can simplify the high frequency packet egress logic.
-     */
-    int _realIdxMap[ZT_MAX_PEER_NETWORK_PATHS] = { ZT_MAX_PEER_NETWORK_PATHS };
-    int _numBondedPaths;                          // Number of paths currently included in the _realIdxMap set.
-    std::map<int16_t, SharedPtr<Flow> > _flows;   // Flows hashed according to port and protocol
-    float _qw[ZT_QOS_PARAMETER_SIZE];             // Link quality specification (can be customized by user)
-
-    bool _run;
-
-    uint8_t _policy;
-    uint32_t _upDelay;
-    uint32_t _downDelay;
-
-    // active-backup
-    int _abPathIdx;   // current active path
-    std::deque<int> _abFailoverQueue;
-    uint8_t _abLinkSelectMethod;   // link re-selection policy for the primary link in active-backup
-
-    // balance-rr
-    uint8_t _rrIdx;                      // index to path currently in use during Round Robin operation
-    uint16_t _rrPacketsSentOnCurrLink;   // number of packets sent on this link since the most recent path switch.
-    /**
-     * How many packets will be sent on a path before moving to the next path
-     * in the round-robin sequence. A value of zero will cause a random path
-     * selection for each outgoing packet.
-     */
-    int _packetsPerLink;
-
-    // balance-aware
-    uint64_t _totalBondUnderload;
-
-    // dynamic link monitoring
-    uint8_t _linkMonitorStrategy;
-
-    // path negotiation
-    int16_t _localUtility;
-    int _negotiatedPathIdx;
-    uint8_t _numSentPathNegotiationRequests;
-    bool _allowPathNegotiation;
-
-    /**
-     * Timers and intervals
-     */
-    uint64_t _failoverInterval;
-    uint64_t _qosSendInterval;
-    uint64_t _ackSendInterval;
-    uint64_t throughputMeasurementInterval;
-    uint64_t _qualityEstimationInterval;
-
-    /**
-     * Link state reporting
-     */
-    uint8_t _numAliveLinks;
-    uint8_t _numTotalLinks;
-
-    /**
-     * Default initial punishment inflicted on misbehaving paths. Punishment slowly
-     * drains linearly. For each eligibility change the remaining punishment is doubled.
-     */
-    uint32_t _defaultPathRefractoryPeriod;
-    unsigned char _freeRandomByte;   // Free byte of entropy that is updated on every packet egress event.
-    SharedPtr<Peer> _peer;           // Remote peer that this bond services
-    unsigned long long _peerId;      // ID of the peer that this bond services
-    bool _isLeaf;
-
-    /**
-     * Rate-limiting
-     */
-    uint16_t _qosCutoffCount;
-    uint16_t _ackCutoffCount;
-    uint64_t _lastQoSRateCheck;
-    uint64_t _lastAckRateCheck;
-    uint16_t _pathNegotiationCutoffCount;
-    uint64_t _lastPathNegotiationReceived;
-
-    /**
-     * Recent event timestamps
-     */
-    uint64_t _lastSummaryDump;
-
-    uint64_t _lastQualityEstimation;
-    uint64_t _lastBackgroundTaskCheck;
-    uint64_t _lastBondStatusLog;
-    uint64_t _lastPathNegotiationCheck;
-    uint64_t _lastSentPathNegotiationRequest;
-    uint64_t _lastFlowExpirationCheck;
-    uint64_t _lastFlowRebalance;
-    uint64_t _lastFrame;
-    uint64_t _lastActiveBackupPathChange;
-
-    Mutex _paths_m;
-
-    Mutex _flows_m;
-
-    bool _userHasSpecifiedLinks;                  // Whether the user has specified links for this bond.
-    bool _userHasSpecifiedPrimaryLink;            // Whether the user has specified a primary link for this bond.
-    bool _userHasSpecifiedFailoverInstructions;   // Whether the user has specified failover instructions for this bond.
-    bool _userHasSpecifiedLinkCapacities;         // Whether the user has specified links capacities for this bond.
-    /**
-     * How frequently (in ms) a VERB_ECHO is sent to a peer to verify that a
-     * path is still active. A value of zero (0) will disable active path
-     * monitoring; as result, all monitoring will be a function of traffic.
-     */
-    int _monitorInterval;
-    bool _allowFlowHashing;   // Whether or not flow hashing is allowed.
-
-    uint64_t _overheadBytes;
+	struct NominatedPath {
+		NominatedPath()
+			: lastAckSent(0)
+			, lastAckReceived(0)
+			, lastQoSReceived(0)
+			, unackedBytes(0)
+			, packetsReceivedSinceLastAck(0)
+			, lastQoSMeasurement(0)
+			, lastThroughputEstimation(0)
+			, lastRefractoryUpdate(0)
+			, lastAliveToggle(0)
+			, alive(false)
+			, eligible(true)
+			, lastEligibility(0)
+			, whenNominated(0)
+			, refractoryPeriod(0)
+			, ipvPref(0)
+			, mode(0)
+			, onlyPathOnLink(false)
+			, bonded(false)
+			, negotiated(false)
+			, shouldAvoid(false)
+			, assignedFlowCount(0)
+			, latency(0)
+			, latencyVariance(0)
+			, packetLossRatio(0)
+			, packetErrorRatio(0)
+			, relativeQuality(0)
+			, relativeLinkCapacity(0)
+			, failoverScore(0)
+			, packetsReceivedSinceLastQoS(0)
+			, packetsIn(0)
+			, packetsOut(0)
+			, localPort(0)
+		{
+		}
+
+		/**
+		 * Set or update a refractory period for the path.
+		 *
+		 * @param punishment How much a path should be punished
+		 * @param pathFailure Whether this call is the result of a recent path failure
+		 */
+		inline void adjustRefractoryPeriod(int64_t now, uint32_t punishment, bool pathFailure)
+		{
+			if (pathFailure) {
+				unsigned int suggestedRefractoryPeriod = refractoryPeriod ? punishment + (refractoryPeriod * 2) : punishment;
+				refractoryPeriod = std::min(suggestedRefractoryPeriod, (unsigned int)ZT_BOND_MAX_REFRACTORY_PERIOD);
+				lastRefractoryUpdate = 0;
+			}
+			else {
+				uint32_t drainRefractory = 0;
+				if (lastRefractoryUpdate) {
+					drainRefractory = (now - lastRefractoryUpdate);
+				}
+				else {
+					drainRefractory = (now - lastAliveToggle);
+				}
+				lastRefractoryUpdate = now;
+				if (refractoryPeriod > drainRefractory) {
+					refractoryPeriod -= drainRefractory;
+				}
+				else {
+					refractoryPeriod = 0;
+					lastRefractoryUpdate = 0;
+				}
+			}
+		}
+
+		/**
+		 * @return True if a path is permitted to be used in a bond (according to user pref.)
+		 */
+		inline bool allowed()
+		{
+			return (! ipvPref || ((p->_addr.isV4() && (ipvPref == 4 || ipvPref == 46 || ipvPref == 64)) || ((p->_addr.isV6() && (ipvPref == 6 || ipvPref == 46 || ipvPref == 64)))));
+		}
+
+		/**
+		 * @return True if a path exists on a link marked as a spare
+		 */
+		inline bool isSpare()
+		{
+			return mode == ZT_BOND_SLAVE_MODE_SPARE;
+		}
+
+		/**
+		 * @return True if a path is preferred over another on the same physical link (according to user pref.)
+		 */
+		inline bool preferred()
+		{
+			return onlyPathOnLink || (p->_addr.isV4() && (ipvPref == 4 || ipvPref == 46)) || (p->_addr.isV6() && (ipvPref == 6 || ipvPref == 64));
+		}
+
+		/**
+		 * @param now Current time
+		 * @return Whether a QoS (VERB_QOS_MEASUREMENT) packet needs to be emitted at this time
+		 */
+		inline bool needsToSendQoS(int64_t now, uint64_t qosSendInterval)
+		{
+			return ((packetsReceivedSinceLastQoS >= ZT_QOS_TABLE_SIZE) || ((now - lastQoSMeasurement) > qosSendInterval)) && packetsReceivedSinceLastQoS;
+		}
+
+		/**
+		 * @param now Current time
+		 * @return Whether an ACK (VERB_ACK) packet needs to be emitted at this time
+		 */
+		inline bool needsToSendAck(int64_t now, uint64_t ackSendInterval)
+		{
+			return ((now - lastAckSent) >= ackSendInterval || (packetsReceivedSinceLastAck == ZT_QOS_TABLE_SIZE)) && packetsReceivedSinceLastAck;
+		}
+
+		/**
+		 * Reset packet counters
+		 */
+		inline void resetPacketCounts()
+		{
+			packetsIn = 0;
+			packetsOut = 0;
+		}
+
+		std::map<uint64_t, uint64_t> qosStatsOut;	// id:egress_time
+		std::map<uint64_t, uint64_t> qosStatsIn;	// id:now
+		std::map<uint64_t, uint64_t> ackStatsIn;	// id:now
+
+		RingBuffer<int, ZT_QOS_SHORTTERM_SAMPLE_WIN_SIZE> qosRecordSize;
+		RingBuffer<float, ZT_QOS_SHORTTERM_SAMPLE_WIN_SIZE> qosRecordLossSamples;
+		RingBuffer<uint64_t, ZT_QOS_SHORTTERM_SAMPLE_WIN_SIZE> throughputSamples;
+		RingBuffer<bool, ZT_QOS_SHORTTERM_SAMPLE_WIN_SIZE> packetValiditySamples;
+		RingBuffer<float, ZT_QOS_SHORTTERM_SAMPLE_WIN_SIZE> throughputVarianceSamples;
+		RingBuffer<uint16_t, ZT_QOS_SHORTTERM_SAMPLE_WIN_SIZE> latencySamples;
+
+		uint64_t lastAckSent;
+		uint64_t lastAckReceived;
+		uint64_t lastQoSReceived;
+		uint64_t unackedBytes;
+		uint64_t packetsReceivedSinceLastAck;
+
+		uint64_t lastQoSMeasurement;		 // Last time that a VERB_QOS_MEASUREMENT was sent out on this path.
+		uint64_t lastThroughputEstimation;	 // Last time that the path's throughput was estimated.
+		uint64_t lastRefractoryUpdate;		 // The last time that the refractory period was updated.
+		uint64_t lastAliveToggle;			 // The last time that the path was marked as "alive".
+		bool alive;
+		bool eligible;				  // State of eligibility at last check. Used for determining state changes.
+		uint64_t lastEligibility;	  // The last time that this path was eligible
+		uint64_t whenNominated;		  // Timestamp indicating when this path's trial period began.
+		uint32_t refractoryPeriod;	  // Amount of time that this path will be prevented from becoming a member of a bond.
+		uint8_t ipvPref;			  // IP version preference inherited from the physical link.
+		uint8_t mode;				  // Mode inherited from the physical link.
+		bool onlyPathOnLink;		  // IP version preference inherited from the physical link.
+		bool enabled;				  // Enabled state inherited from the physical link.
+		bool bonded;				  // Whether this path is currently part of a bond.
+		bool negotiated;			  // Whether this path was intentionally negotiated by either peer.
+		bool shouldAvoid;			  // Whether flows should be moved from this path. Current traffic flows will be re-allocated immediately.
+		uint16_t assignedFlowCount;	  // The number of flows currently assigned to this path.
+		float latency;				  // The mean latency (computed from a sliding window.)
+		float latencyVariance;		  // Packet delay variance (computed from a sliding window.)
+		float packetLossRatio;		  // The ratio of lost packets to received packets.
+		float packetErrorRatio;		  // The ratio of packets that failed their MAC/CRC checks to those that did not.
+		float relativeQuality;		  // The relative quality of the link.
+		float relativeLinkCapacity;	  // The relative capacity of the link.
+
+		uint32_t failoverScore;				   // Score that indicates to what degree this path is preferred over others that are available to the bonding policy. (specifically for active-backup)
+		int32_t packetsReceivedSinceLastQoS;   // Number of packets received since the last VERB_QOS_MEASUREMENT was sent to the remote peer.
+
+		/**
+		 * Counters used for tracking path load.
+		 */
+		int packetsIn;
+		int packetsOut;
+
+		uint16_t localPort;
+
+		// AtomicCounter __refCount;
+
+		SharedPtr<Path> p;
+		void set(uint64_t now, const SharedPtr<Path>& path)
+		{
+			p = path;
+			whenNominated = now;
+		}
+	};
+
+	/**
+	 * Paths nominated to the bond (may or may not actually be bonded)
+	 */
+	NominatedPath _paths[ZT_MAX_PEER_NETWORK_PATHS];
+
+	inline int getNominatedPathIdx(const SharedPtr<Path>& path)
+	{
+		for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+			if (_paths[i].p == path) {
+				return i;
+			}
+		}
+		return ZT_MAX_PEER_NETWORK_PATHS;
+	}
+
+	/**
+	 * A protocol flow that is identified by the origin and destination port.
+	 */
+	struct Flow {
+		/**
+		 * @param flowId Given flow ID
+		 * @param now Current time
+		 */
+		Flow(int32_t flowId, int64_t now) : id(flowId), bytesIn(0), bytesOut(0), lastActivity(now), lastPathReassignment(0), assignedPath(ZT_MAX_PEER_NETWORK_PATHS)
+		{
+		}
+
+		/**
+		 * Reset flow statistics
+		 */
+		inline void resetByteCounts()
+		{
+			bytesIn = 0;
+			bytesOut = 0;
+		}
+
+		/**
+		 * How long since a packet was sent or received in this flow
+		 *
+		 * @param now Current time
+		 * @return The age of the flow in terms of last recorded activity
+		 */
+		int64_t age(int64_t now)
+		{
+			return now - lastActivity;
+		}
+
+		/**
+		 * @param path Assigned path over which this flow should be handled
+		 */
+		inline void assignPath(int pathIdx, int64_t now)
+		{
+			assignedPath = pathIdx;
+			lastPathReassignment = now;
+		}
+
+		AtomicCounter __refCount;
+
+		int32_t id;						// Flow ID used for hashing and path selection
+		uint64_t bytesIn;				// Used for tracking flow size
+		uint64_t bytesOut;				// Used for tracking flow size
+		int64_t lastActivity;			// The last time that this flow handled traffic
+		int64_t lastPathReassignment;	// Time of last path assignment. Used for anti-flapping
+		int assignedPath;				// Index of path to which this flow is assigned
+	};
+
+	const RuntimeEnvironment* RR;
+	AtomicCounter __refCount;
+
+	std::string _policyAlias;	// Custom name given by the user to this bond type.
+
+	static Binder* _binder;
+
+	/**
+	 * Set of indices corresponding to paths currently included in the bond proper. This
+	 * may only be updated during a call to curateBond(). The reason for this is so that
+	 * we can simplify the high frequency packet egress logic.
+	 */
+	int _realIdxMap[ZT_MAX_PEER_NETWORK_PATHS] = { ZT_MAX_PEER_NETWORK_PATHS };
+	int _numBondedPaths;						  // Number of paths currently included in the _realIdxMap set.
+	std::map<int16_t, SharedPtr<Flow> > _flows;	  // Flows hashed according to port and protocol
+	float _qw[ZT_QOS_PARAMETER_SIZE];			  // Link quality specification (can be customized by user)
+
+	bool _run;
+
+	uint8_t _policy;
+	uint32_t _upDelay;
+	uint32_t _downDelay;
+
+	// active-backup
+	int _abPathIdx;	  // current active path
+	std::deque<int> _abFailoverQueue;
+	uint8_t _abLinkSelectMethod;   // link re-selection policy for the primary link in active-backup
+
+	// balance-rr
+	uint8_t _rrIdx;						 // index to path currently in use during Round Robin operation
+	uint16_t _rrPacketsSentOnCurrLink;	 // number of packets sent on this link since the most recent path switch.
+	/**
+	 * How many packets will be sent on a path before moving to the next path
+	 * in the round-robin sequence. A value of zero will cause a random path
+	 * selection for each outgoing packet.
+	 */
+	int _packetsPerLink;
+
+	// balance-aware
+	uint64_t _totalBondUnderload;
+
+	// dynamic link monitoring
+	uint8_t _linkMonitorStrategy;
+
+	// path negotiation
+	int16_t _localUtility;
+	int _negotiatedPathIdx;
+	uint8_t _numSentPathNegotiationRequests;
+	bool _allowPathNegotiation;
+
+	/**
+	 * Timers and intervals
+	 */
+	uint64_t _failoverInterval;
+	uint64_t _qosSendInterval;
+	uint64_t _ackSendInterval;
+	uint64_t throughputMeasurementInterval;
+	uint64_t _qualityEstimationInterval;
+
+	/**
+	 * Link state reporting
+	 */
+	uint8_t _numAliveLinks;
+	uint8_t _numTotalLinks;
+
+	/**
+	 * Default initial punishment inflicted on misbehaving paths. Punishment slowly
+	 * drains linearly. For each eligibility change the remaining punishment is doubled.
+	 */
+	uint32_t _defaultPathRefractoryPeriod;
+	unsigned char _freeRandomByte;	 // Free byte of entropy that is updated on every packet egress event.
+	SharedPtr<Peer> _peer;			 // Remote peer that this bond services
+	unsigned long long _peerId;		 // ID of the peer that this bond services
+	bool _isLeaf;
+
+	/**
+	 * Rate-limiting
+	 */
+	uint16_t _qosCutoffCount;
+	uint16_t _ackCutoffCount;
+	uint64_t _lastQoSRateCheck;
+	uint64_t _lastAckRateCheck;
+	uint16_t _pathNegotiationCutoffCount;
+	uint64_t _lastPathNegotiationReceived;
+
+	/**
+	 * Recent event timestamps
+	 */
+	uint64_t _lastSummaryDump;
+
+	uint64_t _lastQualityEstimation;
+	uint64_t _lastBackgroundTaskCheck;
+	uint64_t _lastBondStatusLog;
+	uint64_t _lastPathNegotiationCheck;
+	uint64_t _lastSentPathNegotiationRequest;
+	uint64_t _lastFlowExpirationCheck;
+	uint64_t _lastFlowRebalance;
+	uint64_t _lastFrame;
+	uint64_t _lastActiveBackupPathChange;
+
+	Mutex _paths_m;
+
+	Mutex _flows_m;
+
+	bool _userHasSpecifiedLinks;				  // Whether the user has specified links for this bond.
+	bool _userHasSpecifiedPrimaryLink;			  // Whether the user has specified a primary link for this bond.
+	bool _userHasSpecifiedFailoverInstructions;	  // Whether the user has specified failover instructions for this bond.
+	bool _userHasSpecifiedLinkCapacities;		  // Whether the user has specified links capacities for this bond.
+	/**
+	 * How frequently (in ms) a VERB_ECHO is sent to a peer to verify that a
+	 * path is still active. A value of zero (0) will disable active path
+	 * monitoring; as result, all monitoring will be a function of traffic.
+	 */
+	int _monitorInterval;
+	bool _allowFlowHashing;	  // Whether or not flow hashing is allowed.
+
+	uint64_t _overheadBytes;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 454 - 454
node/Buffer.hpp

@@ -46,474 +46,474 @@ namespace ZeroTier {
  * @tparam C Total capacity
  */
 template <unsigned int C> class Buffer {
-    // I love me!
-    template <unsigned int C2> friend class Buffer;
+	// I love me!
+	template <unsigned int C2> friend class Buffer;
 
   public:
-    // STL container idioms
-    typedef unsigned char value_type;
-    typedef unsigned char* pointer;
-    typedef const char* const_pointer;
-    typedef char& reference;
-    typedef const char& const_reference;
-    typedef char* iterator;
-    typedef const char* const_iterator;
-    typedef unsigned int size_type;
-    typedef int difference_type;
-    typedef std::reverse_iterator<iterator> reverse_iterator;
-    typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
-    inline iterator begin()
-    {
-        return _b;
-    }
-    inline iterator end()
-    {
-        return (_b + _l);
-    }
-    inline const_iterator begin() const
-    {
-        return _b;
-    }
-    inline const_iterator end() const
-    {
-        return (_b + _l);
-    }
-    inline reverse_iterator rbegin()
-    {
-        return reverse_iterator(begin());
-    }
-    inline reverse_iterator rend()
-    {
-        return reverse_iterator(end());
-    }
-    inline const_reverse_iterator rbegin() const
-    {
-        return const_reverse_iterator(begin());
-    }
-    inline const_reverse_iterator rend() const
-    {
-        return const_reverse_iterator(end());
-    }
-
-    Buffer() : _l(0)
-    {
-    }
-
-    Buffer(unsigned int l)
-    {
-        if (l > C) {
-            throw ZT_EXCEPTION_OUT_OF_BOUNDS;
-        }
-        _l = l;
-    }
-
-    template <unsigned int C2> Buffer(const Buffer<C2>& b)
-    {
-        *this = b;
-    }
-
-    Buffer(const void* b, unsigned int l)
-    {
-        copyFrom(b, l);
-    }
-
-    template <unsigned int C2> inline Buffer& operator=(const Buffer<C2>& b)
-    {
-        if (unlikely(b._l > C)) {
-            throw ZT_EXCEPTION_OUT_OF_BOUNDS;
-        }
-        if (C2 == C) {
-            memcpy(this, &b, sizeof(Buffer<C>));
-        }
-        else {
-            memcpy(_b, b._b, _l = b._l);
-        }
-        return *this;
-    }
-
-    inline void copyFrom(const void* b, unsigned int l)
-    {
-        if (unlikely(l > C)) {
-            throw ZT_EXCEPTION_OUT_OF_BOUNDS;
-        }
-        memcpy(_b, b, l);
-        _l = l;
-    }
-
-    unsigned char operator[](const unsigned int i) const
-    {
-        if (unlikely(i >= _l)) {
-            throw ZT_EXCEPTION_OUT_OF_BOUNDS;
-        }
-        return (unsigned char)_b[i];
-    }
-
-    unsigned char& operator[](const unsigned int i)
-    {
-        if (unlikely(i >= _l)) {
-            throw ZT_EXCEPTION_OUT_OF_BOUNDS;
-        }
-        return ((unsigned char*)_b)[i];
-    }
-
-    /**
-     * Get a raw pointer to a field with bounds checking
-     *
-     * This isn't perfectly safe in that the caller could still overflow
-     * the pointer, but its use provides both a sanity check and
-     * documentation / reminder to the calling code to treat the returned
-     * pointer as being of size [l].
-     *
-     * @param i Index of field in buffer
-     * @param l Length of field in bytes
-     * @return Pointer to field data
-     * @throws std::out_of_range Field extends beyond data size
-     */
-    unsigned char* field(unsigned int i, unsigned int l)
-    {
-        if (unlikely((i + l) > _l)) {
-            throw ZT_EXCEPTION_OUT_OF_BOUNDS;
-        }
-        return (unsigned char*)(_b + i);
-    }
-    const unsigned char* field(unsigned int i, unsigned int l) const
-    {
-        if (unlikely((i + l) > _l)) {
-            throw ZT_EXCEPTION_OUT_OF_BOUNDS;
-        }
-        return (const unsigned char*)(_b + i);
-    }
-
-    /**
-     * Place a primitive integer value at a given position
-     *
-     * @param i Index to place value
-     * @param v Value
-     * @tparam T Integer type (e.g. uint16_t, int64_t)
-     */
-    template <typename T> inline void setAt(unsigned int i, const T v)
-    {
-        if (unlikely((i + sizeof(T)) > _l)) {
-            throw ZT_EXCEPTION_OUT_OF_BOUNDS;
-        }
+	// STL container idioms
+	typedef unsigned char value_type;
+	typedef unsigned char* pointer;
+	typedef const char* const_pointer;
+	typedef char& reference;
+	typedef const char& const_reference;
+	typedef char* iterator;
+	typedef const char* const_iterator;
+	typedef unsigned int size_type;
+	typedef int difference_type;
+	typedef std::reverse_iterator<iterator> reverse_iterator;
+	typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
+	inline iterator begin()
+	{
+		return _b;
+	}
+	inline iterator end()
+	{
+		return (_b + _l);
+	}
+	inline const_iterator begin() const
+	{
+		return _b;
+	}
+	inline const_iterator end() const
+	{
+		return (_b + _l);
+	}
+	inline reverse_iterator rbegin()
+	{
+		return reverse_iterator(begin());
+	}
+	inline reverse_iterator rend()
+	{
+		return reverse_iterator(end());
+	}
+	inline const_reverse_iterator rbegin() const
+	{
+		return const_reverse_iterator(begin());
+	}
+	inline const_reverse_iterator rend() const
+	{
+		return const_reverse_iterator(end());
+	}
+
+	Buffer() : _l(0)
+	{
+	}
+
+	Buffer(unsigned int l)
+	{
+		if (l > C) {
+			throw ZT_EXCEPTION_OUT_OF_BOUNDS;
+		}
+		_l = l;
+	}
+
+	template <unsigned int C2> Buffer(const Buffer<C2>& b)
+	{
+		*this = b;
+	}
+
+	Buffer(const void* b, unsigned int l)
+	{
+		copyFrom(b, l);
+	}
+
+	template <unsigned int C2> inline Buffer& operator=(const Buffer<C2>& b)
+	{
+		if (unlikely(b._l > C)) {
+			throw ZT_EXCEPTION_OUT_OF_BOUNDS;
+		}
+		if (C2 == C) {
+			memcpy(this, &b, sizeof(Buffer<C>));
+		}
+		else {
+			memcpy(_b, b._b, _l = b._l);
+		}
+		return *this;
+	}
+
+	inline void copyFrom(const void* b, unsigned int l)
+	{
+		if (unlikely(l > C)) {
+			throw ZT_EXCEPTION_OUT_OF_BOUNDS;
+		}
+		memcpy(_b, b, l);
+		_l = l;
+	}
+
+	unsigned char operator[](const unsigned int i) const
+	{
+		if (unlikely(i >= _l)) {
+			throw ZT_EXCEPTION_OUT_OF_BOUNDS;
+		}
+		return (unsigned char)_b[i];
+	}
+
+	unsigned char& operator[](const unsigned int i)
+	{
+		if (unlikely(i >= _l)) {
+			throw ZT_EXCEPTION_OUT_OF_BOUNDS;
+		}
+		return ((unsigned char*)_b)[i];
+	}
+
+	/**
+	 * Get a raw pointer to a field with bounds checking
+	 *
+	 * This isn't perfectly safe in that the caller could still overflow
+	 * the pointer, but its use provides both a sanity check and
+	 * documentation / reminder to the calling code to treat the returned
+	 * pointer as being of size [l].
+	 *
+	 * @param i Index of field in buffer
+	 * @param l Length of field in bytes
+	 * @return Pointer to field data
+	 * @throws std::out_of_range Field extends beyond data size
+	 */
+	unsigned char* field(unsigned int i, unsigned int l)
+	{
+		if (unlikely((i + l) > _l)) {
+			throw ZT_EXCEPTION_OUT_OF_BOUNDS;
+		}
+		return (unsigned char*)(_b + i);
+	}
+	const unsigned char* field(unsigned int i, unsigned int l) const
+	{
+		if (unlikely((i + l) > _l)) {
+			throw ZT_EXCEPTION_OUT_OF_BOUNDS;
+		}
+		return (const unsigned char*)(_b + i);
+	}
+
+	/**
+	 * Place a primitive integer value at a given position
+	 *
+	 * @param i Index to place value
+	 * @param v Value
+	 * @tparam T Integer type (e.g. uint16_t, int64_t)
+	 */
+	template <typename T> inline void setAt(unsigned int i, const T v)
+	{
+		if (unlikely((i + sizeof(T)) > _l)) {
+			throw ZT_EXCEPTION_OUT_OF_BOUNDS;
+		}
 #ifdef ZT_NO_TYPE_PUNNING
-        uint8_t* p = reinterpret_cast<uint8_t*>(_b + i);
-        for (unsigned int x = 1; x <= sizeof(T); ++x) {
-            *(p++) = (uint8_t)(v >> (8 * (sizeof(T) - x)));
-        }
+		uint8_t* p = reinterpret_cast<uint8_t*>(_b + i);
+		for (unsigned int x = 1; x <= sizeof(T); ++x) {
+			*(p++) = (uint8_t)(v >> (8 * (sizeof(T) - x)));
+		}
 #else
-        T* const ZT_VAR_MAY_ALIAS p = reinterpret_cast<T*>(_b + i);
-        *p = Utils::hton(v);
+		T* const ZT_VAR_MAY_ALIAS p = reinterpret_cast<T*>(_b + i);
+		*p = Utils::hton(v);
 #endif
-    }
-
-    /**
-     * Get a primitive integer value at a given position
-     *
-     * @param i Index to get integer
-     * @tparam T Integer type (e.g. uint16_t, int64_t)
-     * @return Integer value
-     */
-    template <typename T> inline T at(unsigned int i) const
-    {
-        if (unlikely((i + sizeof(T)) > _l)) {
-            throw ZT_EXCEPTION_OUT_OF_BOUNDS;
-        }
+	}
+
+	/**
+	 * Get a primitive integer value at a given position
+	 *
+	 * @param i Index to get integer
+	 * @tparam T Integer type (e.g. uint16_t, int64_t)
+	 * @return Integer value
+	 */
+	template <typename T> inline T at(unsigned int i) const
+	{
+		if (unlikely((i + sizeof(T)) > _l)) {
+			throw ZT_EXCEPTION_OUT_OF_BOUNDS;
+		}
 #ifdef ZT_NO_TYPE_PUNNING
-        T v = 0;
-        const uint8_t* p = reinterpret_cast<const uint8_t*>(_b + i);
-        for (unsigned int x = 0; x < sizeof(T); ++x) {
-            v <<= 8;
-            v |= (T) * (p++);
-        }
-        return v;
+		T v = 0;
+		const uint8_t* p = reinterpret_cast<const uint8_t*>(_b + i);
+		for (unsigned int x = 0; x < sizeof(T); ++x) {
+			v <<= 8;
+			v |= (T) * (p++);
+		}
+		return v;
 #else
-        const T* const ZT_VAR_MAY_ALIAS p = reinterpret_cast<const T*>(_b + i);
-        return Utils::ntoh(*p);
+		const T* const ZT_VAR_MAY_ALIAS p = reinterpret_cast<const T*>(_b + i);
+		return Utils::ntoh(*p);
 #endif
-    }
-
-    /**
-     * Append an integer type to this buffer
-     *
-     * @param v Value to append
-     * @tparam T Integer type (e.g. uint16_t, int64_t)
-     * @throws std::out_of_range Attempt to append beyond capacity
-     */
-    template <typename T> inline void append(const T v)
-    {
-        if (unlikely((_l + sizeof(T)) > C)) {
-            throw ZT_EXCEPTION_OUT_OF_BOUNDS;
-        }
+	}
+
+	/**
+	 * Append an integer type to this buffer
+	 *
+	 * @param v Value to append
+	 * @tparam T Integer type (e.g. uint16_t, int64_t)
+	 * @throws std::out_of_range Attempt to append beyond capacity
+	 */
+	template <typename T> inline void append(const T v)
+	{
+		if (unlikely((_l + sizeof(T)) > C)) {
+			throw ZT_EXCEPTION_OUT_OF_BOUNDS;
+		}
 #ifdef ZT_NO_TYPE_PUNNING
-        uint8_t* p = reinterpret_cast<uint8_t*>(_b + _l);
-        for (unsigned int x = 1; x <= sizeof(T); ++x) {
-            *(p++) = (uint8_t)(v >> (8 * (sizeof(T) - x)));
-        }
+		uint8_t* p = reinterpret_cast<uint8_t*>(_b + _l);
+		for (unsigned int x = 1; x <= sizeof(T); ++x) {
+			*(p++) = (uint8_t)(v >> (8 * (sizeof(T) - x)));
+		}
 #else
-        T* const ZT_VAR_MAY_ALIAS p = reinterpret_cast<T*>(_b + _l);
-        *p = Utils::hton(v);
+		T* const ZT_VAR_MAY_ALIAS p = reinterpret_cast<T*>(_b + _l);
+		*p = Utils::hton(v);
 #endif
-        _l += sizeof(T);
-    }
-
-    /**
-     * Append a run of bytes
-     *
-     * @param c Character value to append
-     * @param n Number of times to append
-     * @throws std::out_of_range Attempt to append beyond capacity
-     */
-    inline void append(unsigned char c, unsigned int n)
-    {
-        if (unlikely((_l + n) > C)) {
-            throw ZT_EXCEPTION_OUT_OF_BOUNDS;
-        }
-        for (unsigned int i = 0; i < n; ++i) {
-            _b[_l++] = (char)c;
-        }
-    }
-
-    /**
-     * Append secure random bytes
-     *
-     * @param n Number of random bytes to append
-     */
-    inline void appendRandom(unsigned int n)
-    {
-        if (unlikely((_l + n) > C)) {
-            throw ZT_EXCEPTION_OUT_OF_BOUNDS;
-        }
-        Utils::getSecureRandom(_b + _l, n);
-        _l += n;
-    }
-
-    /**
-     * Append a C-array of bytes
-     *
-     * @param b Data
-     * @param l Length
-     * @throws std::out_of_range Attempt to append beyond capacity
-     */
-    inline void append(const void* b, unsigned int l)
-    {
-        if (unlikely((_l + l) > C)) {
-            throw ZT_EXCEPTION_OUT_OF_BOUNDS;
-        }
-        memcpy(_b + _l, b, l);
-        _l += l;
-    }
-
-    /**
-     * Append a C string including null termination byte
-     *
-     * @param s C string
-     * @throws std::out_of_range Attempt to append beyond capacity
-     */
-    inline void appendCString(const char* s)
-    {
-        for (;;) {
-            if (unlikely(_l >= C)) {
-                throw ZT_EXCEPTION_OUT_OF_BOUNDS;
-            }
-            if (! (_b[_l++] = *(s++))) {
-                break;
-            }
-        }
-    }
-
-    /**
-     * Append a buffer
-     *
-     * @param b Buffer to append
-     * @tparam C2 Capacity of second buffer (typically inferred)
-     * @throws std::out_of_range Attempt to append beyond capacity
-     */
-    template <unsigned int C2> inline void append(const Buffer<C2>& b)
-    {
-        append(b._b, b._l);
-    }
-
-    /**
-     * Increment size and return pointer to field of specified size
-     *
-     * Nothing is actually written to the memory. This is a shortcut
-     * for addSize() followed by field() to reference the previous
-     * position and the new size.
-     *
-     * @param l Length of field to append
-     * @return Pointer to beginning of appended field of length 'l'
-     */
-    inline char* appendField(unsigned int l)
-    {
-        if (unlikely((_l + l) > C)) {
-            throw ZT_EXCEPTION_OUT_OF_BOUNDS;
-        }
-        char* r = _b + _l;
-        _l += l;
-        return r;
-    }
-
-    /**
-     * Increment size by a given number of bytes
-     *
-     * The contents of new space are undefined.
-     *
-     * @param i Bytes to increment
-     * @throws std::out_of_range Capacity exceeded
-     */
-    inline void addSize(unsigned int i)
-    {
-        if (unlikely((i + _l) > C)) {
-            throw ZT_EXCEPTION_OUT_OF_BOUNDS;
-        }
-        _l += i;
-    }
-
-    /**
-     * Set size of data in buffer
-     *
-     * The contents of new space are undefined.
-     *
-     * @param i New size
-     * @throws std::out_of_range Size larger than capacity
-     */
-    inline void setSize(const unsigned int i)
-    {
-        if (unlikely(i > C)) {
-            throw ZT_EXCEPTION_OUT_OF_BOUNDS;
-        }
-        _l = i;
-    }
-
-    /**
-     * Move everything after 'at' to the buffer's front and truncate
-     *
-     * @param at Truncate before this position
-     * @throws std::out_of_range Position is beyond size of buffer
-     */
-    inline void behead(const unsigned int at)
-    {
-        if (! at) {
-            return;
-        }
-        if (unlikely(at > _l)) {
-            throw ZT_EXCEPTION_OUT_OF_BOUNDS;
-        }
-        ::memmove(_b, _b + at, _l -= at);
-    }
-
-    /**
-     * Erase something from the middle of the buffer
-     *
-     * @param start Starting position
-     * @param length Length of block to erase
-     * @throws std::out_of_range Position plus length is beyond size of buffer
-     */
-    inline void erase(const unsigned int at, const unsigned int length)
-    {
-        const unsigned int endr = at + length;
-        if (unlikely(endr > _l)) {
-            throw ZT_EXCEPTION_OUT_OF_BOUNDS;
-        }
-        ::memmove(_b + at, _b + endr, _l - endr);
-        _l -= length;
-    }
-
-    /**
-     * Set buffer data length to zero
-     */
-    inline void clear()
-    {
-        _l = 0;
-    }
-
-    /**
-     * Zero buffer up to size()
-     */
-    inline void zero()
-    {
-        memset(_b, 0, _l);
-    }
-
-    /**
-     * Zero unused capacity area
-     */
-    inline void zeroUnused()
-    {
-        memset(_b + _l, 0, C - _l);
-    }
-
-    /**
-     * Unconditionally and securely zero buffer's underlying memory
-     */
-    inline void burn()
-    {
-        Utils::burn(_b, sizeof(_b));
-    }
-
-    /**
-     * @return Constant pointer to data in buffer
-     */
-    inline const void* data() const
-    {
-        return _b;
-    }
-
-    /**
-     * @return Non-constant pointer to data in buffer
-     */
-    inline void* unsafeData()
-    {
-        return _b;
-    }
-
-    /**
-     * @return Size of data in buffer
-     */
-    inline unsigned int size() const
-    {
-        return _l;
-    }
-
-    /**
-     * @return Capacity of buffer
-     */
-    inline unsigned int capacity() const
-    {
-        return C;
-    }
-
-    template <unsigned int C2> inline bool operator==(const Buffer<C2>& b) const
-    {
-        return ((_l == b._l) && (! memcmp(_b, b._b, _l)));
-    }
-    template <unsigned int C2> inline bool operator!=(const Buffer<C2>& b) const
-    {
-        return ((_l != b._l) || (memcmp(_b, b._b, _l)));
-    }
-    template <unsigned int C2> inline bool operator<(const Buffer<C2>& b) const
-    {
-        return (memcmp(_b, b._b, std::min(_l, b._l)) < 0);
-    }
-    template <unsigned int C2> inline bool operator>(const Buffer<C2>& b) const
-    {
-        return (b < *this);
-    }
-    template <unsigned int C2> inline bool operator<=(const Buffer<C2>& b) const
-    {
-        return ! (b < *this);
-    }
-    template <unsigned int C2> inline bool operator>=(const Buffer<C2>& b) const
-    {
-        return ! (*this < b);
-    }
+		_l += sizeof(T);
+	}
+
+	/**
+	 * Append a run of bytes
+	 *
+	 * @param c Character value to append
+	 * @param n Number of times to append
+	 * @throws std::out_of_range Attempt to append beyond capacity
+	 */
+	inline void append(unsigned char c, unsigned int n)
+	{
+		if (unlikely((_l + n) > C)) {
+			throw ZT_EXCEPTION_OUT_OF_BOUNDS;
+		}
+		for (unsigned int i = 0; i < n; ++i) {
+			_b[_l++] = (char)c;
+		}
+	}
+
+	/**
+	 * Append secure random bytes
+	 *
+	 * @param n Number of random bytes to append
+	 */
+	inline void appendRandom(unsigned int n)
+	{
+		if (unlikely((_l + n) > C)) {
+			throw ZT_EXCEPTION_OUT_OF_BOUNDS;
+		}
+		Utils::getSecureRandom(_b + _l, n);
+		_l += n;
+	}
+
+	/**
+	 * Append a C-array of bytes
+	 *
+	 * @param b Data
+	 * @param l Length
+	 * @throws std::out_of_range Attempt to append beyond capacity
+	 */
+	inline void append(const void* b, unsigned int l)
+	{
+		if (unlikely((_l + l) > C)) {
+			throw ZT_EXCEPTION_OUT_OF_BOUNDS;
+		}
+		memcpy(_b + _l, b, l);
+		_l += l;
+	}
+
+	/**
+	 * Append a C string including null termination byte
+	 *
+	 * @param s C string
+	 * @throws std::out_of_range Attempt to append beyond capacity
+	 */
+	inline void appendCString(const char* s)
+	{
+		for (;;) {
+			if (unlikely(_l >= C)) {
+				throw ZT_EXCEPTION_OUT_OF_BOUNDS;
+			}
+			if (! (_b[_l++] = *(s++))) {
+				break;
+			}
+		}
+	}
+
+	/**
+	 * Append a buffer
+	 *
+	 * @param b Buffer to append
+	 * @tparam C2 Capacity of second buffer (typically inferred)
+	 * @throws std::out_of_range Attempt to append beyond capacity
+	 */
+	template <unsigned int C2> inline void append(const Buffer<C2>& b)
+	{
+		append(b._b, b._l);
+	}
+
+	/**
+	 * Increment size and return pointer to field of specified size
+	 *
+	 * Nothing is actually written to the memory. This is a shortcut
+	 * for addSize() followed by field() to reference the previous
+	 * position and the new size.
+	 *
+	 * @param l Length of field to append
+	 * @return Pointer to beginning of appended field of length 'l'
+	 */
+	inline char* appendField(unsigned int l)
+	{
+		if (unlikely((_l + l) > C)) {
+			throw ZT_EXCEPTION_OUT_OF_BOUNDS;
+		}
+		char* r = _b + _l;
+		_l += l;
+		return r;
+	}
+
+	/**
+	 * Increment size by a given number of bytes
+	 *
+	 * The contents of new space are undefined.
+	 *
+	 * @param i Bytes to increment
+	 * @throws std::out_of_range Capacity exceeded
+	 */
+	inline void addSize(unsigned int i)
+	{
+		if (unlikely((i + _l) > C)) {
+			throw ZT_EXCEPTION_OUT_OF_BOUNDS;
+		}
+		_l += i;
+	}
+
+	/**
+	 * Set size of data in buffer
+	 *
+	 * The contents of new space are undefined.
+	 *
+	 * @param i New size
+	 * @throws std::out_of_range Size larger than capacity
+	 */
+	inline void setSize(const unsigned int i)
+	{
+		if (unlikely(i > C)) {
+			throw ZT_EXCEPTION_OUT_OF_BOUNDS;
+		}
+		_l = i;
+	}
+
+	/**
+	 * Move everything after 'at' to the buffer's front and truncate
+	 *
+	 * @param at Truncate before this position
+	 * @throws std::out_of_range Position is beyond size of buffer
+	 */
+	inline void behead(const unsigned int at)
+	{
+		if (! at) {
+			return;
+		}
+		if (unlikely(at > _l)) {
+			throw ZT_EXCEPTION_OUT_OF_BOUNDS;
+		}
+		::memmove(_b, _b + at, _l -= at);
+	}
+
+	/**
+	 * Erase something from the middle of the buffer
+	 *
+	 * @param start Starting position
+	 * @param length Length of block to erase
+	 * @throws std::out_of_range Position plus length is beyond size of buffer
+	 */
+	inline void erase(const unsigned int at, const unsigned int length)
+	{
+		const unsigned int endr = at + length;
+		if (unlikely(endr > _l)) {
+			throw ZT_EXCEPTION_OUT_OF_BOUNDS;
+		}
+		::memmove(_b + at, _b + endr, _l - endr);
+		_l -= length;
+	}
+
+	/**
+	 * Set buffer data length to zero
+	 */
+	inline void clear()
+	{
+		_l = 0;
+	}
+
+	/**
+	 * Zero buffer up to size()
+	 */
+	inline void zero()
+	{
+		memset(_b, 0, _l);
+	}
+
+	/**
+	 * Zero unused capacity area
+	 */
+	inline void zeroUnused()
+	{
+		memset(_b + _l, 0, C - _l);
+	}
+
+	/**
+	 * Unconditionally and securely zero buffer's underlying memory
+	 */
+	inline void burn()
+	{
+		Utils::burn(_b, sizeof(_b));
+	}
+
+	/**
+	 * @return Constant pointer to data in buffer
+	 */
+	inline const void* data() const
+	{
+		return _b;
+	}
+
+	/**
+	 * @return Non-constant pointer to data in buffer
+	 */
+	inline void* unsafeData()
+	{
+		return _b;
+	}
+
+	/**
+	 * @return Size of data in buffer
+	 */
+	inline unsigned int size() const
+	{
+		return _l;
+	}
+
+	/**
+	 * @return Capacity of buffer
+	 */
+	inline unsigned int capacity() const
+	{
+		return C;
+	}
+
+	template <unsigned int C2> inline bool operator==(const Buffer<C2>& b) const
+	{
+		return ((_l == b._l) && (! memcmp(_b, b._b, _l)));
+	}
+	template <unsigned int C2> inline bool operator!=(const Buffer<C2>& b) const
+	{
+		return ((_l != b._l) || (memcmp(_b, b._b, _l)));
+	}
+	template <unsigned int C2> inline bool operator<(const Buffer<C2>& b) const
+	{
+		return (memcmp(_b, b._b, std::min(_l, b._l)) < 0);
+	}
+	template <unsigned int C2> inline bool operator>(const Buffer<C2>& b) const
+	{
+		return (b < *this);
+	}
+	template <unsigned int C2> inline bool operator<=(const Buffer<C2>& b) const
+	{
+		return ! (b < *this);
+	}
+	template <unsigned int C2> inline bool operator>=(const Buffer<C2>& b) const
+	{
+		return ! (*this < b);
+	}
 
   private:
-    char ZT_VAR_MAY_ALIAS _b[C];
-    unsigned int _l;
+	char ZT_VAR_MAY_ALIAS _b[C];
+	unsigned int _l;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 40 - 40
node/Capability.cpp

@@ -24,48 +24,48 @@ namespace ZeroTier {
 
 int Capability::verify(const RuntimeEnvironment* RR, void* tPtr) const
 {
-    try {
-        // There must be at least one entry, and sanity check for bad chain max length
-        if ((_maxCustodyChainLength < 1) || (_maxCustodyChainLength > ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)) {
-            return -1;
-        }
+	try {
+		// There must be at least one entry, and sanity check for bad chain max length
+		if ((_maxCustodyChainLength < 1) || (_maxCustodyChainLength > ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)) {
+			return -1;
+		}
 
-        // Validate all entries in chain of custody
-        Buffer<(sizeof(Capability) * 2)> tmp;
-        this->serialize(tmp, true);
-        for (unsigned int c = 0; c < _maxCustodyChainLength; ++c) {
-            if (c == 0) {
-                if ((! _custody[c].to) || (! _custody[c].from) || (_custody[c].from != Network::controllerFor(_nwid))) {
-                    return -1;   // the first entry must be present and from the network's controller
-                }
-            }
-            else {
-                if (! _custody[c].to) {
-                    return 0;   // all previous entries were valid, so we are valid
-                }
-                else if ((! _custody[c].from) || (_custody[c].from != _custody[c - 1].to)) {
-                    return -1;   // otherwise if we have another entry it must be from the previous holder in the chain
-                }
-            }
+		// Validate all entries in chain of custody
+		Buffer<(sizeof(Capability) * 2)> tmp;
+		this->serialize(tmp, true);
+		for (unsigned int c = 0; c < _maxCustodyChainLength; ++c) {
+			if (c == 0) {
+				if ((! _custody[c].to) || (! _custody[c].from) || (_custody[c].from != Network::controllerFor(_nwid))) {
+					return -1;	 // the first entry must be present and from the network's controller
+				}
+			}
+			else {
+				if (! _custody[c].to) {
+					return 0;	// all previous entries were valid, so we are valid
+				}
+				else if ((! _custody[c].from) || (_custody[c].from != _custody[c - 1].to)) {
+					return -1;	 // otherwise if we have another entry it must be from the previous holder in the chain
+				}
+			}
 
-            const Identity id(RR->topology->getIdentity(tPtr, _custody[c].from));
-            if (id) {
-                if (! id.verify(tmp.data(), tmp.size(), _custody[c].signature)) {
-                    return -1;
-                }
-            }
-            else {
-                RR->sw->requestWhois(tPtr, RR->node->now(), _custody[c].from);
-                return 1;
-            }
-        }
+			const Identity id(RR->topology->getIdentity(tPtr, _custody[c].from));
+			if (id) {
+				if (! id.verify(tmp.data(), tmp.size(), _custody[c].signature)) {
+					return -1;
+				}
+			}
+			else {
+				RR->sw->requestWhois(tPtr, RR->node->now(), _custody[c].from);
+				return 1;
+			}
+		}
 
-        // We reached max custody chain length and everything was valid
-        return 0;
-    }
-    catch (...) {
-    }
-    return -1;
+		// We reached max custody chain length and everything was valid
+		return 0;
+	}
+	catch (...) {
+	}
+	return -1;
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 460 - 460
node/Capability.hpp

@@ -56,472 +56,472 @@ class RuntimeEnvironment;
  */
 class Capability : public Credential {
   public:
-    static inline Credential::Type credentialType()
-    {
-        return Credential::CREDENTIAL_TYPE_CAPABILITY;
-    }
-
-    Capability() : _nwid(0), _ts(0), _id(0), _maxCustodyChainLength(0), _ruleCount(0)
-    {
-        memset(_rules, 0, sizeof(_rules));
-        memset(_custody, 0, sizeof(_custody));
-    }
-
-    /**
-     * @param id Capability ID
-     * @param nwid Network ID
-     * @param ts Timestamp (at controller)
-     * @param mccl Maximum custody chain length (1 to create non-transferable capability)
-     * @param rules Network flow rules for this capability
-     * @param ruleCount Number of flow rules
-     */
-    Capability(uint32_t id, uint64_t nwid, int64_t ts, unsigned int mccl, const ZT_VirtualNetworkRule* rules, unsigned int ruleCount)
-        : _nwid(nwid)
-        , _ts(ts)
-        , _id(id)
-        , _maxCustodyChainLength((mccl > 0) ? ((mccl < ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH) ? mccl : (unsigned int)ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH) : 1)
-        , _ruleCount((ruleCount < ZT_MAX_CAPABILITY_RULES) ? ruleCount : ZT_MAX_CAPABILITY_RULES)
-    {
-        if (_ruleCount > 0) {
-            memcpy(_rules, rules, sizeof(ZT_VirtualNetworkRule) * _ruleCount);
-        }
-    }
-
-    /**
-     * @return Rules -- see ruleCount() for size of array
-     */
-    inline const ZT_VirtualNetworkRule* rules() const
-    {
-        return _rules;
-    }
-
-    /**
-     * @return Number of rules in rules()
-     */
-    inline unsigned int ruleCount() const
-    {
-        return _ruleCount;
-    }
-
-    /**
-     * @return ID and evaluation order of this capability in network
-     */
-    inline uint32_t id() const
-    {
-        return _id;
-    }
-
-    /**
-     * @return Network ID for which this capability was issued
-     */
-    inline uint64_t networkId() const
-    {
-        return _nwid;
-    }
-
-    /**
-     * @return Timestamp
-     */
-    inline int64_t timestamp() const
-    {
-        return _ts;
-    }
-
-    /**
-     * @return Last 'to' address in chain of custody
-     */
-    inline Address issuedTo() const
-    {
-        Address i2;
-        for (unsigned int i = 0; i < ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH; ++i) {
-            if (! _custody[i].to) {
-                return i2;
-            }
-            else {
-                i2 = _custody[i].to;
-            }
-        }
-        return i2;
-    }
-
-    /**
-     * Sign this capability and add signature to its chain of custody
-     *
-     * If this returns false, this object should be considered to be
-     * in an undefined state and should be discarded. False can be returned
-     * if there is no more room for signatures (max chain length reached)
-     * or if the 'from' identity does not include a secret key to allow
-     * it to sign anything.
-     *
-     * @param from Signing identity (must have secret)
-     * @param to Recipient of this signature
-     * @return True if signature successful and chain of custody appended
-     */
-    inline bool sign(const Identity& from, const Address& to)
-    {
-        try {
-            for (unsigned int i = 0; ((i < _maxCustodyChainLength) && (i < ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)); ++i) {
-                if (! (_custody[i].to)) {
-                    Buffer<(sizeof(Capability) * 2)> tmp;
-                    this->serialize(tmp, true);
-                    _custody[i].to = to;
-                    _custody[i].from = from.address();
-                    _custody[i].signature = from.sign(tmp.data(), tmp.size());
-                    return true;
-                }
-            }
-        }
-        catch (...) {
-        }
-        return false;
-    }
-
-    /**
-     * Verify this capability's chain of custody and signatures
-     *
-     * @param RR Runtime environment to provide for peer lookup, etc.
-     * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or chain
-     */
-    int verify(const RuntimeEnvironment* RR, void* tPtr) const;
-
-    template <unsigned int C> static inline void serializeRules(Buffer<C>& b, const ZT_VirtualNetworkRule* rules, unsigned int ruleCount)
-    {
-        for (unsigned int i = 0; i < ruleCount; ++i) {
-            // Each rule consists of its 8-bit type followed by the size of that type's
-            // field followed by field data. The inclusion of the size will allow non-supported
-            // rules to be ignored but still parsed.
-            b.append((uint8_t)rules[i].t);
-            switch ((ZT_VirtualNetworkRuleType)(rules[i].t & 0x3f)) {
-                default:
-                    b.append((uint8_t)0);
-                    break;
-                case ZT_NETWORK_RULE_ACTION_TEE:
-                case ZT_NETWORK_RULE_ACTION_WATCH:
-                case ZT_NETWORK_RULE_ACTION_REDIRECT:
-                    b.append((uint8_t)14);
-                    b.append((uint64_t)rules[i].v.fwd.address);
-                    b.append((uint32_t)rules[i].v.fwd.flags);
-                    b.append((uint16_t)rules[i].v.fwd.length);   // unused for redirect
-                    break;
-                case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS:
-                case ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS:
-                    b.append((uint8_t)5);
-                    Address(rules[i].v.zt).appendTo(b);
-                    break;
-                case ZT_NETWORK_RULE_MATCH_VLAN_ID:
-                    b.append((uint8_t)2);
-                    b.append((uint16_t)rules[i].v.vlanId);
-                    break;
-                case ZT_NETWORK_RULE_MATCH_VLAN_PCP:
-                    b.append((uint8_t)1);
-                    b.append((uint8_t)rules[i].v.vlanPcp);
-                    break;
-                case ZT_NETWORK_RULE_MATCH_VLAN_DEI:
-                    b.append((uint8_t)1);
-                    b.append((uint8_t)rules[i].v.vlanDei);
-                    break;
-                case ZT_NETWORK_RULE_MATCH_MAC_SOURCE:
-                case ZT_NETWORK_RULE_MATCH_MAC_DEST:
-                    b.append((uint8_t)6);
-                    b.append(rules[i].v.mac, 6);
-                    break;
-                case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE:
-                case ZT_NETWORK_RULE_MATCH_IPV4_DEST:
-                    b.append((uint8_t)5);
-                    b.append(&(rules[i].v.ipv4.ip), 4);
-                    b.append((uint8_t)rules[i].v.ipv4.mask);
-                    break;
-                case ZT_NETWORK_RULE_MATCH_IPV6_SOURCE:
-                case ZT_NETWORK_RULE_MATCH_IPV6_DEST:
-                    b.append((uint8_t)17);
-                    b.append(rules[i].v.ipv6.ip, 16);
-                    b.append((uint8_t)rules[i].v.ipv6.mask);
-                    break;
-                case ZT_NETWORK_RULE_MATCH_IP_TOS:
-                    b.append((uint8_t)3);
-                    b.append((uint8_t)rules[i].v.ipTos.mask);
-                    b.append((uint8_t)rules[i].v.ipTos.value[0]);
-                    b.append((uint8_t)rules[i].v.ipTos.value[1]);
-                    break;
-                case ZT_NETWORK_RULE_MATCH_IP_PROTOCOL:
-                    b.append((uint8_t)1);
-                    b.append((uint8_t)rules[i].v.ipProtocol);
-                    break;
-                case ZT_NETWORK_RULE_MATCH_ETHERTYPE:
-                    b.append((uint8_t)2);
-                    b.append((uint16_t)rules[i].v.etherType);
-                    break;
-                case ZT_NETWORK_RULE_MATCH_ICMP:
-                    b.append((uint8_t)3);
-                    b.append((uint8_t)rules[i].v.icmp.type);
-                    b.append((uint8_t)rules[i].v.icmp.code);
-                    b.append((uint8_t)rules[i].v.icmp.flags);
-                    break;
-                case ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE:
-                case ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE:
-                    b.append((uint8_t)4);
-                    b.append((uint16_t)rules[i].v.port[0]);
-                    b.append((uint16_t)rules[i].v.port[1]);
-                    break;
-                case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS:
-                    b.append((uint8_t)8);
-                    b.append((uint64_t)rules[i].v.characteristics);
-                    break;
-                case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE:
-                    b.append((uint8_t)4);
-                    b.append((uint16_t)rules[i].v.frameSize[0]);
-                    b.append((uint16_t)rules[i].v.frameSize[1]);
-                    break;
-                case ZT_NETWORK_RULE_MATCH_RANDOM:
-                    b.append((uint8_t)4);
-                    b.append((uint32_t)rules[i].v.randomProbability);
-                    break;
-                case ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE:
-                case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND:
-                case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR:
-                case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR:
-                case ZT_NETWORK_RULE_MATCH_TAGS_EQUAL:
-                case ZT_NETWORK_RULE_MATCH_TAG_SENDER:
-                case ZT_NETWORK_RULE_MATCH_TAG_RECEIVER:
-                    b.append((uint8_t)8);
-                    b.append((uint32_t)rules[i].v.tag.id);
-                    b.append((uint32_t)rules[i].v.tag.value);
-                    break;
-                case ZT_NETWORK_RULE_MATCH_INTEGER_RANGE:
-                    b.append((uint8_t)19);
-                    b.append((uint64_t)rules[i].v.intRange.start);
-                    b.append((uint64_t)(rules[i].v.intRange.start + (uint64_t)rules[i].v.intRange.end));   // more future-proof
-                    b.append((uint16_t)rules[i].v.intRange.idx);
-                    b.append((uint8_t)rules[i].v.intRange.format);
-                    break;
-            }
-        }
-    }
-
-    template <unsigned int C> static inline void deserializeRules(const Buffer<C>& b, unsigned int& p, ZT_VirtualNetworkRule* rules, unsigned int& ruleCount, const unsigned int maxRuleCount)
-    {
-        while ((ruleCount < maxRuleCount) && (p < b.size())) {
-            rules[ruleCount].t = (uint8_t)b[p++];
-            const unsigned int fieldLen = (unsigned int)b[p++];
-            switch ((ZT_VirtualNetworkRuleType)(rules[ruleCount].t & 0x3f)) {
-                default:
-                    break;
-                case ZT_NETWORK_RULE_ACTION_TEE:
-                case ZT_NETWORK_RULE_ACTION_WATCH:
-                case ZT_NETWORK_RULE_ACTION_REDIRECT:
-                    rules[ruleCount].v.fwd.address = b.template at<uint64_t>(p);
-                    rules[ruleCount].v.fwd.flags = b.template at<uint32_t>(p + 8);
-                    rules[ruleCount].v.fwd.length = b.template at<uint16_t>(p + 12);
-                    break;
-                case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS:
-                case ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS:
-                    rules[ruleCount].v.zt = Address(b.field(p, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH).toInt();
-                    break;
-                case ZT_NETWORK_RULE_MATCH_VLAN_ID:
-                    rules[ruleCount].v.vlanId = b.template at<uint16_t>(p);
-                    break;
-                case ZT_NETWORK_RULE_MATCH_VLAN_PCP:
-                    rules[ruleCount].v.vlanPcp = (uint8_t)b[p];
-                    break;
-                case ZT_NETWORK_RULE_MATCH_VLAN_DEI:
-                    rules[ruleCount].v.vlanDei = (uint8_t)b[p];
-                    break;
-                case ZT_NETWORK_RULE_MATCH_MAC_SOURCE:
-                case ZT_NETWORK_RULE_MATCH_MAC_DEST:
-                    memcpy(rules[ruleCount].v.mac, b.field(p, 6), 6);
-                    break;
-                case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE:
-                case ZT_NETWORK_RULE_MATCH_IPV4_DEST:
-                    memcpy(&(rules[ruleCount].v.ipv4.ip), b.field(p, 4), 4);
-                    rules[ruleCount].v.ipv4.mask = (uint8_t)b[p + 4];
-                    break;
-                case ZT_NETWORK_RULE_MATCH_IPV6_SOURCE:
-                case ZT_NETWORK_RULE_MATCH_IPV6_DEST:
-                    memcpy(rules[ruleCount].v.ipv6.ip, b.field(p, 16), 16);
-                    rules[ruleCount].v.ipv6.mask = (uint8_t)b[p + 16];
-                    break;
-                case ZT_NETWORK_RULE_MATCH_IP_TOS:
-                    rules[ruleCount].v.ipTos.mask = (uint8_t)b[p];
-                    rules[ruleCount].v.ipTos.value[0] = (uint8_t)b[p + 1];
-                    rules[ruleCount].v.ipTos.value[1] = (uint8_t)b[p + 2];
-                    break;
-                case ZT_NETWORK_RULE_MATCH_IP_PROTOCOL:
-                    rules[ruleCount].v.ipProtocol = (uint8_t)b[p];
-                    break;
-                case ZT_NETWORK_RULE_MATCH_ETHERTYPE:
-                    rules[ruleCount].v.etherType = b.template at<uint16_t>(p);
-                    break;
-                case ZT_NETWORK_RULE_MATCH_ICMP:
-                    rules[ruleCount].v.icmp.type = (uint8_t)b[p];
-                    rules[ruleCount].v.icmp.code = (uint8_t)b[p + 1];
-                    rules[ruleCount].v.icmp.flags = (uint8_t)b[p + 2];
-                    break;
-                case ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE:
-                case ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE:
-                    rules[ruleCount].v.port[0] = b.template at<uint16_t>(p);
-                    rules[ruleCount].v.port[1] = b.template at<uint16_t>(p + 2);
-                    break;
-                case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS:
-                    rules[ruleCount].v.characteristics = b.template at<uint64_t>(p);
-                    break;
-                case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE:
-                    rules[ruleCount].v.frameSize[0] = b.template at<uint16_t>(p);
-                    rules[ruleCount].v.frameSize[1] = b.template at<uint16_t>(p + 2);
-                    break;
-                case ZT_NETWORK_RULE_MATCH_RANDOM:
-                    rules[ruleCount].v.randomProbability = b.template at<uint32_t>(p);
-                    break;
-                case ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE:
-                case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND:
-                case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR:
-                case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR:
-                case ZT_NETWORK_RULE_MATCH_TAGS_EQUAL:
-                case ZT_NETWORK_RULE_MATCH_TAG_SENDER:
-                case ZT_NETWORK_RULE_MATCH_TAG_RECEIVER:
-                    rules[ruleCount].v.tag.id = b.template at<uint32_t>(p);
-                    rules[ruleCount].v.tag.value = b.template at<uint32_t>(p + 4);
-                    break;
-                case ZT_NETWORK_RULE_MATCH_INTEGER_RANGE:
-                    rules[ruleCount].v.intRange.start = b.template at<uint64_t>(p);
-                    rules[ruleCount].v.intRange.end = (uint32_t)(b.template at<uint64_t>(p + 8) - rules[ruleCount].v.intRange.start);
-                    rules[ruleCount].v.intRange.idx = b.template at<uint16_t>(p + 16);
-                    rules[ruleCount].v.intRange.format = (uint8_t)b[p + 18];
-                    break;
-            }
-            p += fieldLen;
-            ++ruleCount;
-        }
-    }
-
-    template <unsigned int C> inline void serialize(Buffer<C>& b, const bool forSign = false) const
-    {
-        if (forSign) {
-            b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
-        }
-
-        // These are the same between Tag and Capability
-        b.append(_nwid);
-        b.append(_ts);
-        b.append(_id);
-
-        b.append((uint16_t)_ruleCount);
-        serializeRules(b, _rules, _ruleCount);
-        b.append((uint8_t)_maxCustodyChainLength);
-
-        if (! forSign) {
-            for (unsigned int i = 0;; ++i) {
-                if ((i < _maxCustodyChainLength) && (i < ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH) && (_custody[i].to)) {
-                    _custody[i].to.appendTo(b);
-                    _custody[i].from.appendTo(b);
-                    b.append((uint8_t)1);                       // 1 == Ed25519 signature
-                    b.append((uint16_t)ZT_ECC_SIGNATURE_LEN);   // length of signature
-                    b.append(_custody[i].signature.data, ZT_ECC_SIGNATURE_LEN);
-                }
-                else {
-                    b.append((unsigned char)0, ZT_ADDRESS_LENGTH);   // zero 'to' terminates chain
-                    break;
-                }
-            }
-        }
-
-        // This is the size of any additional fields, currently 0.
-        b.append((uint16_t)0);
-
-        if (forSign) {
-            b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
-        }
-    }
-
-    template <unsigned int C> inline unsigned int deserialize(const Buffer<C>& b, unsigned int startAt = 0)
-    {
-        *this = Capability();
-
-        unsigned int p = startAt;
-
-        _nwid = b.template at<uint64_t>(p);
-        p += 8;
-        _ts = b.template at<uint64_t>(p);
-        p += 8;
-        _id = b.template at<uint32_t>(p);
-        p += 4;
-
-        const unsigned int rc = b.template at<uint16_t>(p);
-        p += 2;
-        if (rc > ZT_MAX_CAPABILITY_RULES) {
-            throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW;
-        }
-        deserializeRules(b, p, _rules, _ruleCount, rc);
-
-        _maxCustodyChainLength = (unsigned int)b[p++];
-        if ((_maxCustodyChainLength < 1) || (_maxCustodyChainLength > ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)) {
-            throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW;
-        }
-
-        for (unsigned int i = 0;; ++i) {
-            const Address to(b.field(p, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
-            p += ZT_ADDRESS_LENGTH;
-            if (! to) {
-                break;
-            }
-            if ((i >= _maxCustodyChainLength) || (i >= ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)) {
-                throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW;
-            }
-            _custody[i].to = to;
-            _custody[i].from.setTo(b.field(p, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
-            p += ZT_ADDRESS_LENGTH;
-            if (b[p++] == 1) {
-                if (b.template at<uint16_t>(p) != ZT_ECC_SIGNATURE_LEN) {
-                    throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_CRYPTOGRAPHIC_TOKEN;
-                }
-                p += 2;
-                memcpy(_custody[i].signature.data, b.field(p, ZT_ECC_SIGNATURE_LEN), ZT_ECC_SIGNATURE_LEN);
-                p += ZT_ECC_SIGNATURE_LEN;
-            }
-            else {
-                p += 2 + b.template at<uint16_t>(p);
-            }
-        }
-
-        p += 2 + b.template at<uint16_t>(p);
-        if (p > b.size()) {
-            throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW;
-        }
-
-        return (p - startAt);
-    }
-
-    // Provides natural sort order by ID
-    inline bool operator<(const Capability& c) const
-    {
-        return (_id < c._id);
-    }
-
-    inline bool operator==(const Capability& c) const
-    {
-        return (memcmp(this, &c, sizeof(Capability)) == 0);
-    }
-    inline bool operator!=(const Capability& c) const
-    {
-        return (memcmp(this, &c, sizeof(Capability)) != 0);
-    }
+	static inline Credential::Type credentialType()
+	{
+		return Credential::CREDENTIAL_TYPE_CAPABILITY;
+	}
+
+	Capability() : _nwid(0), _ts(0), _id(0), _maxCustodyChainLength(0), _ruleCount(0)
+	{
+		memset(_rules, 0, sizeof(_rules));
+		memset(_custody, 0, sizeof(_custody));
+	}
+
+	/**
+	 * @param id Capability ID
+	 * @param nwid Network ID
+	 * @param ts Timestamp (at controller)
+	 * @param mccl Maximum custody chain length (1 to create non-transferable capability)
+	 * @param rules Network flow rules for this capability
+	 * @param ruleCount Number of flow rules
+	 */
+	Capability(uint32_t id, uint64_t nwid, int64_t ts, unsigned int mccl, const ZT_VirtualNetworkRule* rules, unsigned int ruleCount)
+		: _nwid(nwid)
+		, _ts(ts)
+		, _id(id)
+		, _maxCustodyChainLength((mccl > 0) ? ((mccl < ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH) ? mccl : (unsigned int)ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH) : 1)
+		, _ruleCount((ruleCount < ZT_MAX_CAPABILITY_RULES) ? ruleCount : ZT_MAX_CAPABILITY_RULES)
+	{
+		if (_ruleCount > 0) {
+			memcpy(_rules, rules, sizeof(ZT_VirtualNetworkRule) * _ruleCount);
+		}
+	}
+
+	/**
+	 * @return Rules -- see ruleCount() for size of array
+	 */
+	inline const ZT_VirtualNetworkRule* rules() const
+	{
+		return _rules;
+	}
+
+	/**
+	 * @return Number of rules in rules()
+	 */
+	inline unsigned int ruleCount() const
+	{
+		return _ruleCount;
+	}
+
+	/**
+	 * @return ID and evaluation order of this capability in network
+	 */
+	inline uint32_t id() const
+	{
+		return _id;
+	}
+
+	/**
+	 * @return Network ID for which this capability was issued
+	 */
+	inline uint64_t networkId() const
+	{
+		return _nwid;
+	}
+
+	/**
+	 * @return Timestamp
+	 */
+	inline int64_t timestamp() const
+	{
+		return _ts;
+	}
+
+	/**
+	 * @return Last 'to' address in chain of custody
+	 */
+	inline Address issuedTo() const
+	{
+		Address i2;
+		for (unsigned int i = 0; i < ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH; ++i) {
+			if (! _custody[i].to) {
+				return i2;
+			}
+			else {
+				i2 = _custody[i].to;
+			}
+		}
+		return i2;
+	}
+
+	/**
+	 * Sign this capability and add signature to its chain of custody
+	 *
+	 * If this returns false, this object should be considered to be
+	 * in an undefined state and should be discarded. False can be returned
+	 * if there is no more room for signatures (max chain length reached)
+	 * or if the 'from' identity does not include a secret key to allow
+	 * it to sign anything.
+	 *
+	 * @param from Signing identity (must have secret)
+	 * @param to Recipient of this signature
+	 * @return True if signature successful and chain of custody appended
+	 */
+	inline bool sign(const Identity& from, const Address& to)
+	{
+		try {
+			for (unsigned int i = 0; ((i < _maxCustodyChainLength) && (i < ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)); ++i) {
+				if (! (_custody[i].to)) {
+					Buffer<(sizeof(Capability) * 2)> tmp;
+					this->serialize(tmp, true);
+					_custody[i].to = to;
+					_custody[i].from = from.address();
+					_custody[i].signature = from.sign(tmp.data(), tmp.size());
+					return true;
+				}
+			}
+		}
+		catch (...) {
+		}
+		return false;
+	}
+
+	/**
+	 * Verify this capability's chain of custody and signatures
+	 *
+	 * @param RR Runtime environment to provide for peer lookup, etc.
+	 * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or chain
+	 */
+	int verify(const RuntimeEnvironment* RR, void* tPtr) const;
+
+	template <unsigned int C> static inline void serializeRules(Buffer<C>& b, const ZT_VirtualNetworkRule* rules, unsigned int ruleCount)
+	{
+		for (unsigned int i = 0; i < ruleCount; ++i) {
+			// Each rule consists of its 8-bit type followed by the size of that type's
+			// field followed by field data. The inclusion of the size will allow non-supported
+			// rules to be ignored but still parsed.
+			b.append((uint8_t)rules[i].t);
+			switch ((ZT_VirtualNetworkRuleType)(rules[i].t & 0x3f)) {
+				default:
+					b.append((uint8_t)0);
+					break;
+				case ZT_NETWORK_RULE_ACTION_TEE:
+				case ZT_NETWORK_RULE_ACTION_WATCH:
+				case ZT_NETWORK_RULE_ACTION_REDIRECT:
+					b.append((uint8_t)14);
+					b.append((uint64_t)rules[i].v.fwd.address);
+					b.append((uint32_t)rules[i].v.fwd.flags);
+					b.append((uint16_t)rules[i].v.fwd.length);	 // unused for redirect
+					break;
+				case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS:
+				case ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS:
+					b.append((uint8_t)5);
+					Address(rules[i].v.zt).appendTo(b);
+					break;
+				case ZT_NETWORK_RULE_MATCH_VLAN_ID:
+					b.append((uint8_t)2);
+					b.append((uint16_t)rules[i].v.vlanId);
+					break;
+				case ZT_NETWORK_RULE_MATCH_VLAN_PCP:
+					b.append((uint8_t)1);
+					b.append((uint8_t)rules[i].v.vlanPcp);
+					break;
+				case ZT_NETWORK_RULE_MATCH_VLAN_DEI:
+					b.append((uint8_t)1);
+					b.append((uint8_t)rules[i].v.vlanDei);
+					break;
+				case ZT_NETWORK_RULE_MATCH_MAC_SOURCE:
+				case ZT_NETWORK_RULE_MATCH_MAC_DEST:
+					b.append((uint8_t)6);
+					b.append(rules[i].v.mac, 6);
+					break;
+				case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE:
+				case ZT_NETWORK_RULE_MATCH_IPV4_DEST:
+					b.append((uint8_t)5);
+					b.append(&(rules[i].v.ipv4.ip), 4);
+					b.append((uint8_t)rules[i].v.ipv4.mask);
+					break;
+				case ZT_NETWORK_RULE_MATCH_IPV6_SOURCE:
+				case ZT_NETWORK_RULE_MATCH_IPV6_DEST:
+					b.append((uint8_t)17);
+					b.append(rules[i].v.ipv6.ip, 16);
+					b.append((uint8_t)rules[i].v.ipv6.mask);
+					break;
+				case ZT_NETWORK_RULE_MATCH_IP_TOS:
+					b.append((uint8_t)3);
+					b.append((uint8_t)rules[i].v.ipTos.mask);
+					b.append((uint8_t)rules[i].v.ipTos.value[0]);
+					b.append((uint8_t)rules[i].v.ipTos.value[1]);
+					break;
+				case ZT_NETWORK_RULE_MATCH_IP_PROTOCOL:
+					b.append((uint8_t)1);
+					b.append((uint8_t)rules[i].v.ipProtocol);
+					break;
+				case ZT_NETWORK_RULE_MATCH_ETHERTYPE:
+					b.append((uint8_t)2);
+					b.append((uint16_t)rules[i].v.etherType);
+					break;
+				case ZT_NETWORK_RULE_MATCH_ICMP:
+					b.append((uint8_t)3);
+					b.append((uint8_t)rules[i].v.icmp.type);
+					b.append((uint8_t)rules[i].v.icmp.code);
+					b.append((uint8_t)rules[i].v.icmp.flags);
+					break;
+				case ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE:
+				case ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE:
+					b.append((uint8_t)4);
+					b.append((uint16_t)rules[i].v.port[0]);
+					b.append((uint16_t)rules[i].v.port[1]);
+					break;
+				case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS:
+					b.append((uint8_t)8);
+					b.append((uint64_t)rules[i].v.characteristics);
+					break;
+				case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE:
+					b.append((uint8_t)4);
+					b.append((uint16_t)rules[i].v.frameSize[0]);
+					b.append((uint16_t)rules[i].v.frameSize[1]);
+					break;
+				case ZT_NETWORK_RULE_MATCH_RANDOM:
+					b.append((uint8_t)4);
+					b.append((uint32_t)rules[i].v.randomProbability);
+					break;
+				case ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE:
+				case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND:
+				case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR:
+				case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR:
+				case ZT_NETWORK_RULE_MATCH_TAGS_EQUAL:
+				case ZT_NETWORK_RULE_MATCH_TAG_SENDER:
+				case ZT_NETWORK_RULE_MATCH_TAG_RECEIVER:
+					b.append((uint8_t)8);
+					b.append((uint32_t)rules[i].v.tag.id);
+					b.append((uint32_t)rules[i].v.tag.value);
+					break;
+				case ZT_NETWORK_RULE_MATCH_INTEGER_RANGE:
+					b.append((uint8_t)19);
+					b.append((uint64_t)rules[i].v.intRange.start);
+					b.append((uint64_t)(rules[i].v.intRange.start + (uint64_t)rules[i].v.intRange.end));   // more future-proof
+					b.append((uint16_t)rules[i].v.intRange.idx);
+					b.append((uint8_t)rules[i].v.intRange.format);
+					break;
+			}
+		}
+	}
+
+	template <unsigned int C> static inline void deserializeRules(const Buffer<C>& b, unsigned int& p, ZT_VirtualNetworkRule* rules, unsigned int& ruleCount, const unsigned int maxRuleCount)
+	{
+		while ((ruleCount < maxRuleCount) && (p < b.size())) {
+			rules[ruleCount].t = (uint8_t)b[p++];
+			const unsigned int fieldLen = (unsigned int)b[p++];
+			switch ((ZT_VirtualNetworkRuleType)(rules[ruleCount].t & 0x3f)) {
+				default:
+					break;
+				case ZT_NETWORK_RULE_ACTION_TEE:
+				case ZT_NETWORK_RULE_ACTION_WATCH:
+				case ZT_NETWORK_RULE_ACTION_REDIRECT:
+					rules[ruleCount].v.fwd.address = b.template at<uint64_t>(p);
+					rules[ruleCount].v.fwd.flags = b.template at<uint32_t>(p + 8);
+					rules[ruleCount].v.fwd.length = b.template at<uint16_t>(p + 12);
+					break;
+				case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS:
+				case ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS:
+					rules[ruleCount].v.zt = Address(b.field(p, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH).toInt();
+					break;
+				case ZT_NETWORK_RULE_MATCH_VLAN_ID:
+					rules[ruleCount].v.vlanId = b.template at<uint16_t>(p);
+					break;
+				case ZT_NETWORK_RULE_MATCH_VLAN_PCP:
+					rules[ruleCount].v.vlanPcp = (uint8_t)b[p];
+					break;
+				case ZT_NETWORK_RULE_MATCH_VLAN_DEI:
+					rules[ruleCount].v.vlanDei = (uint8_t)b[p];
+					break;
+				case ZT_NETWORK_RULE_MATCH_MAC_SOURCE:
+				case ZT_NETWORK_RULE_MATCH_MAC_DEST:
+					memcpy(rules[ruleCount].v.mac, b.field(p, 6), 6);
+					break;
+				case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE:
+				case ZT_NETWORK_RULE_MATCH_IPV4_DEST:
+					memcpy(&(rules[ruleCount].v.ipv4.ip), b.field(p, 4), 4);
+					rules[ruleCount].v.ipv4.mask = (uint8_t)b[p + 4];
+					break;
+				case ZT_NETWORK_RULE_MATCH_IPV6_SOURCE:
+				case ZT_NETWORK_RULE_MATCH_IPV6_DEST:
+					memcpy(rules[ruleCount].v.ipv6.ip, b.field(p, 16), 16);
+					rules[ruleCount].v.ipv6.mask = (uint8_t)b[p + 16];
+					break;
+				case ZT_NETWORK_RULE_MATCH_IP_TOS:
+					rules[ruleCount].v.ipTos.mask = (uint8_t)b[p];
+					rules[ruleCount].v.ipTos.value[0] = (uint8_t)b[p + 1];
+					rules[ruleCount].v.ipTos.value[1] = (uint8_t)b[p + 2];
+					break;
+				case ZT_NETWORK_RULE_MATCH_IP_PROTOCOL:
+					rules[ruleCount].v.ipProtocol = (uint8_t)b[p];
+					break;
+				case ZT_NETWORK_RULE_MATCH_ETHERTYPE:
+					rules[ruleCount].v.etherType = b.template at<uint16_t>(p);
+					break;
+				case ZT_NETWORK_RULE_MATCH_ICMP:
+					rules[ruleCount].v.icmp.type = (uint8_t)b[p];
+					rules[ruleCount].v.icmp.code = (uint8_t)b[p + 1];
+					rules[ruleCount].v.icmp.flags = (uint8_t)b[p + 2];
+					break;
+				case ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE:
+				case ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE:
+					rules[ruleCount].v.port[0] = b.template at<uint16_t>(p);
+					rules[ruleCount].v.port[1] = b.template at<uint16_t>(p + 2);
+					break;
+				case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS:
+					rules[ruleCount].v.characteristics = b.template at<uint64_t>(p);
+					break;
+				case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE:
+					rules[ruleCount].v.frameSize[0] = b.template at<uint16_t>(p);
+					rules[ruleCount].v.frameSize[1] = b.template at<uint16_t>(p + 2);
+					break;
+				case ZT_NETWORK_RULE_MATCH_RANDOM:
+					rules[ruleCount].v.randomProbability = b.template at<uint32_t>(p);
+					break;
+				case ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE:
+				case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND:
+				case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR:
+				case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR:
+				case ZT_NETWORK_RULE_MATCH_TAGS_EQUAL:
+				case ZT_NETWORK_RULE_MATCH_TAG_SENDER:
+				case ZT_NETWORK_RULE_MATCH_TAG_RECEIVER:
+					rules[ruleCount].v.tag.id = b.template at<uint32_t>(p);
+					rules[ruleCount].v.tag.value = b.template at<uint32_t>(p + 4);
+					break;
+				case ZT_NETWORK_RULE_MATCH_INTEGER_RANGE:
+					rules[ruleCount].v.intRange.start = b.template at<uint64_t>(p);
+					rules[ruleCount].v.intRange.end = (uint32_t)(b.template at<uint64_t>(p + 8) - rules[ruleCount].v.intRange.start);
+					rules[ruleCount].v.intRange.idx = b.template at<uint16_t>(p + 16);
+					rules[ruleCount].v.intRange.format = (uint8_t)b[p + 18];
+					break;
+			}
+			p += fieldLen;
+			++ruleCount;
+		}
+	}
+
+	template <unsigned int C> inline void serialize(Buffer<C>& b, const bool forSign = false) const
+	{
+		if (forSign) {
+			b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
+		}
+
+		// These are the same between Tag and Capability
+		b.append(_nwid);
+		b.append(_ts);
+		b.append(_id);
+
+		b.append((uint16_t)_ruleCount);
+		serializeRules(b, _rules, _ruleCount);
+		b.append((uint8_t)_maxCustodyChainLength);
+
+		if (! forSign) {
+			for (unsigned int i = 0;; ++i) {
+				if ((i < _maxCustodyChainLength) && (i < ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH) && (_custody[i].to)) {
+					_custody[i].to.appendTo(b);
+					_custody[i].from.appendTo(b);
+					b.append((uint8_t)1);						// 1 == Ed25519 signature
+					b.append((uint16_t)ZT_ECC_SIGNATURE_LEN);	// length of signature
+					b.append(_custody[i].signature.data, ZT_ECC_SIGNATURE_LEN);
+				}
+				else {
+					b.append((unsigned char)0, ZT_ADDRESS_LENGTH);	 // zero 'to' terminates chain
+					break;
+				}
+			}
+		}
+
+		// This is the size of any additional fields, currently 0.
+		b.append((uint16_t)0);
+
+		if (forSign) {
+			b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
+		}
+	}
+
+	template <unsigned int C> inline unsigned int deserialize(const Buffer<C>& b, unsigned int startAt = 0)
+	{
+		*this = Capability();
+
+		unsigned int p = startAt;
+
+		_nwid = b.template at<uint64_t>(p);
+		p += 8;
+		_ts = b.template at<uint64_t>(p);
+		p += 8;
+		_id = b.template at<uint32_t>(p);
+		p += 4;
+
+		const unsigned int rc = b.template at<uint16_t>(p);
+		p += 2;
+		if (rc > ZT_MAX_CAPABILITY_RULES) {
+			throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW;
+		}
+		deserializeRules(b, p, _rules, _ruleCount, rc);
+
+		_maxCustodyChainLength = (unsigned int)b[p++];
+		if ((_maxCustodyChainLength < 1) || (_maxCustodyChainLength > ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)) {
+			throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW;
+		}
+
+		for (unsigned int i = 0;; ++i) {
+			const Address to(b.field(p, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
+			p += ZT_ADDRESS_LENGTH;
+			if (! to) {
+				break;
+			}
+			if ((i >= _maxCustodyChainLength) || (i >= ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)) {
+				throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW;
+			}
+			_custody[i].to = to;
+			_custody[i].from.setTo(b.field(p, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
+			p += ZT_ADDRESS_LENGTH;
+			if (b[p++] == 1) {
+				if (b.template at<uint16_t>(p) != ZT_ECC_SIGNATURE_LEN) {
+					throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_CRYPTOGRAPHIC_TOKEN;
+				}
+				p += 2;
+				memcpy(_custody[i].signature.data, b.field(p, ZT_ECC_SIGNATURE_LEN), ZT_ECC_SIGNATURE_LEN);
+				p += ZT_ECC_SIGNATURE_LEN;
+			}
+			else {
+				p += 2 + b.template at<uint16_t>(p);
+			}
+		}
+
+		p += 2 + b.template at<uint16_t>(p);
+		if (p > b.size()) {
+			throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW;
+		}
+
+		return (p - startAt);
+	}
+
+	// Provides natural sort order by ID
+	inline bool operator<(const Capability& c) const
+	{
+		return (_id < c._id);
+	}
+
+	inline bool operator==(const Capability& c) const
+	{
+		return (memcmp(this, &c, sizeof(Capability)) == 0);
+	}
+	inline bool operator!=(const Capability& c) const
+	{
+		return (memcmp(this, &c, sizeof(Capability)) != 0);
+	}
 
   private:
-    uint64_t _nwid;
-    int64_t _ts;
-    uint32_t _id;
+	uint64_t _nwid;
+	int64_t _ts;
+	uint32_t _id;
 
-    unsigned int _maxCustodyChainLength;
+	unsigned int _maxCustodyChainLength;
 
-    unsigned int _ruleCount;
-    ZT_VirtualNetworkRule _rules[ZT_MAX_CAPABILITY_RULES];
+	unsigned int _ruleCount;
+	ZT_VirtualNetworkRule _rules[ZT_MAX_CAPABILITY_RULES];
 
-    struct {
-        Address to;
-        Address from;
-        ECC::Signature signature;
-    } _custody[ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH];
+	struct {
+		Address to;
+		Address from;
+		ECC::Signature signature;
+	} _custody[ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH];
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 101 - 101
node/CertificateOfMembership.cpp

@@ -24,118 +24,118 @@ namespace ZeroTier {
 
 CertificateOfMembership::CertificateOfMembership(uint64_t timestamp, uint64_t timestampMaxDelta, uint64_t nwid, const Identity& issuedTo)
 {
-    _qualifiers[0].id = COM_RESERVED_ID_TIMESTAMP;
-    _qualifiers[0].value = timestamp;
-    _qualifiers[0].maxDelta = timestampMaxDelta;
-    _qualifiers[1].id = COM_RESERVED_ID_NETWORK_ID;
-    _qualifiers[1].value = nwid;
-    _qualifiers[1].maxDelta = 0;
-    _qualifiers[2].id = COM_RESERVED_ID_ISSUED_TO;
-    _qualifiers[2].value = issuedTo.address().toInt();
-    _qualifiers[2].maxDelta = 0xffffffffffffffffULL;
-
-    // Include hash of full identity public key in COM for hardening purposes. Pack it in
-    // using the original COM format. Format may be revised in the future to make this cleaner.
-    uint64_t idHash[6];
-    issuedTo.publicKeyHash(idHash);
-    for (unsigned long i = 0; i < 4; ++i) {
-        _qualifiers[i + 3].id = (uint64_t)(i + 3);
-        _qualifiers[i + 3].value = Utils::ntoh(idHash[i]);
-        _qualifiers[i + 3].maxDelta = 0xffffffffffffffffULL;
-    }
-
-    _qualifierCount = 7;
-    memset(_signature.data, 0, ZT_ECC_SIGNATURE_LEN);
+	_qualifiers[0].id = COM_RESERVED_ID_TIMESTAMP;
+	_qualifiers[0].value = timestamp;
+	_qualifiers[0].maxDelta = timestampMaxDelta;
+	_qualifiers[1].id = COM_RESERVED_ID_NETWORK_ID;
+	_qualifiers[1].value = nwid;
+	_qualifiers[1].maxDelta = 0;
+	_qualifiers[2].id = COM_RESERVED_ID_ISSUED_TO;
+	_qualifiers[2].value = issuedTo.address().toInt();
+	_qualifiers[2].maxDelta = 0xffffffffffffffffULL;
+
+	// Include hash of full identity public key in COM for hardening purposes. Pack it in
+	// using the original COM format. Format may be revised in the future to make this cleaner.
+	uint64_t idHash[6];
+	issuedTo.publicKeyHash(idHash);
+	for (unsigned long i = 0; i < 4; ++i) {
+		_qualifiers[i + 3].id = (uint64_t)(i + 3);
+		_qualifiers[i + 3].value = Utils::ntoh(idHash[i]);
+		_qualifiers[i + 3].maxDelta = 0xffffffffffffffffULL;
+	}
+
+	_qualifierCount = 7;
+	memset(_signature.data, 0, ZT_ECC_SIGNATURE_LEN);
 }
 
 bool CertificateOfMembership::agreesWith(const CertificateOfMembership& other, const Identity& otherIdentity) const
 {
-    if ((_qualifierCount == 0) || (other._qualifierCount == 0)) {
-        return false;
-    }
-
-    std::map<uint64_t, uint64_t> otherFields;
-    for (unsigned int i = 0; i < other._qualifierCount; ++i) {
-        otherFields[other._qualifiers[i].id] = other._qualifiers[i].value;
-    }
-
-    bool fullIdentityVerification = false;
-    for (unsigned int i = 0; i < _qualifierCount; ++i) {
-        const uint64_t qid = _qualifiers[i].id;
-        if ((qid >= 3) && (qid <= 6)) {
-            fullIdentityVerification = true;
-        }
-        std::map<uint64_t, uint64_t>::iterator otherQ(otherFields.find(qid));
-        if (otherQ == otherFields.end()) {
-            return false;
-        }
-        const uint64_t a = _qualifiers[i].value;
-        const uint64_t b = otherQ->second;
-        if (((a >= b) ? (a - b) : (b - a)) > _qualifiers[i].maxDelta) {
-            return false;
-        }
-    }
-
-    // If this COM has a full hash of its identity, assume the other must have this as well.
-    // Otherwise we are on a controller that does not incorporate these.
-    if (fullIdentityVerification) {
-        uint64_t idHash[6];
-        otherIdentity.publicKeyHash(idHash);
-        for (unsigned long i = 0; i < 4; ++i) {
-            std::map<uint64_t, uint64_t>::iterator otherQ(otherFields.find((uint64_t)(i + 3)));
-            if (otherQ == otherFields.end()) {
-                return false;
-            }
-            if (otherQ->second != Utils::ntoh(idHash[i])) {
-                return false;
-            }
-        }
-    }
-
-    return true;
+	if ((_qualifierCount == 0) || (other._qualifierCount == 0)) {
+		return false;
+	}
+
+	std::map<uint64_t, uint64_t> otherFields;
+	for (unsigned int i = 0; i < other._qualifierCount; ++i) {
+		otherFields[other._qualifiers[i].id] = other._qualifiers[i].value;
+	}
+
+	bool fullIdentityVerification = false;
+	for (unsigned int i = 0; i < _qualifierCount; ++i) {
+		const uint64_t qid = _qualifiers[i].id;
+		if ((qid >= 3) && (qid <= 6)) {
+			fullIdentityVerification = true;
+		}
+		std::map<uint64_t, uint64_t>::iterator otherQ(otherFields.find(qid));
+		if (otherQ == otherFields.end()) {
+			return false;
+		}
+		const uint64_t a = _qualifiers[i].value;
+		const uint64_t b = otherQ->second;
+		if (((a >= b) ? (a - b) : (b - a)) > _qualifiers[i].maxDelta) {
+			return false;
+		}
+	}
+
+	// If this COM has a full hash of its identity, assume the other must have this as well.
+	// Otherwise we are on a controller that does not incorporate these.
+	if (fullIdentityVerification) {
+		uint64_t idHash[6];
+		otherIdentity.publicKeyHash(idHash);
+		for (unsigned long i = 0; i < 4; ++i) {
+			std::map<uint64_t, uint64_t>::iterator otherQ(otherFields.find((uint64_t)(i + 3)));
+			if (otherQ == otherFields.end()) {
+				return false;
+			}
+			if (otherQ->second != Utils::ntoh(idHash[i])) {
+				return false;
+			}
+		}
+	}
+
+	return true;
 }
 
 bool CertificateOfMembership::sign(const Identity& with)
 {
-    uint64_t buf[ZT_NETWORK_COM_MAX_QUALIFIERS * 3];
-    unsigned int ptr = 0;
-    for (unsigned int i = 0; i < _qualifierCount; ++i) {
-        buf[ptr++] = Utils::hton(_qualifiers[i].id);
-        buf[ptr++] = Utils::hton(_qualifiers[i].value);
-        buf[ptr++] = Utils::hton(_qualifiers[i].maxDelta);
-    }
-
-    try {
-        _signature = with.sign(buf, ptr * sizeof(uint64_t));
-        _signedBy = with.address();
-        return true;
-    }
-    catch (...) {
-        _signedBy.zero();
-        return false;
-    }
+	uint64_t buf[ZT_NETWORK_COM_MAX_QUALIFIERS * 3];
+	unsigned int ptr = 0;
+	for (unsigned int i = 0; i < _qualifierCount; ++i) {
+		buf[ptr++] = Utils::hton(_qualifiers[i].id);
+		buf[ptr++] = Utils::hton(_qualifiers[i].value);
+		buf[ptr++] = Utils::hton(_qualifiers[i].maxDelta);
+	}
+
+	try {
+		_signature = with.sign(buf, ptr * sizeof(uint64_t));
+		_signedBy = with.address();
+		return true;
+	}
+	catch (...) {
+		_signedBy.zero();
+		return false;
+	}
 }
 
 int CertificateOfMembership::verify(const RuntimeEnvironment* RR, void* tPtr) const
 {
-    if ((! _signedBy) || (_signedBy != Network::controllerFor(networkId())) || (_qualifierCount > ZT_NETWORK_COM_MAX_QUALIFIERS)) {
-        return -1;
-    }
-
-    const Identity id(RR->topology->getIdentity(tPtr, _signedBy));
-    if (! id) {
-        RR->sw->requestWhois(tPtr, RR->node->now(), _signedBy);
-        return 1;
-    }
-
-    uint64_t buf[ZT_NETWORK_COM_MAX_QUALIFIERS * 3];
-    unsigned int ptr = 0;
-    for (unsigned int i = 0; i < _qualifierCount; ++i) {
-        buf[ptr++] = Utils::hton(_qualifiers[i].id);
-        buf[ptr++] = Utils::hton(_qualifiers[i].value);
-        buf[ptr++] = Utils::hton(_qualifiers[i].maxDelta);
-    }
-    return (id.verify(buf, ptr * sizeof(uint64_t), _signature) ? 0 : -1);
+	if ((! _signedBy) || (_signedBy != Network::controllerFor(networkId())) || (_qualifierCount > ZT_NETWORK_COM_MAX_QUALIFIERS)) {
+		return -1;
+	}
+
+	const Identity id(RR->topology->getIdentity(tPtr, _signedBy));
+	if (! id) {
+		RR->sw->requestWhois(tPtr, RR->node->now(), _signedBy);
+		return 1;
+	}
+
+	uint64_t buf[ZT_NETWORK_COM_MAX_QUALIFIERS * 3];
+	unsigned int ptr = 0;
+	for (unsigned int i = 0; i < _qualifierCount; ++i) {
+		buf[ptr++] = Utils::hton(_qualifiers[i].id);
+		buf[ptr++] = Utils::hton(_qualifiers[i].value);
+		buf[ptr++] = Utils::hton(_qualifiers[i].maxDelta);
+	}
+	return (id.verify(buf, ptr * sizeof(uint64_t), _signature) ? 0 : -1);
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 264 - 264
node/CertificateOfMembership.hpp

@@ -65,273 +65,273 @@ class RuntimeEnvironment;
  */
 class CertificateOfMembership : public Credential {
   public:
-    static inline Credential::Type credentialType()
-    {
-        return Credential::CREDENTIAL_TYPE_COM;
-    }
-
-    /**
-     * Reserved qualifier IDs
-     *
-     * IDs below 1024 are reserved for use as standard IDs. Others are available
-     * for user-defined use.
-     *
-     * Addition of new required fields requires that code in hasRequiredFields
-     * be updated as well.
-     */
-    enum ReservedId {
-        /**
-         * Timestamp of certificate
-         */
-        COM_RESERVED_ID_TIMESTAMP = 0,
-
-        /**
-         * Network ID for which certificate was issued
-         */
-        COM_RESERVED_ID_NETWORK_ID = 1,
-
-        /**
-         * ZeroTier address to whom certificate was issued
-         */
-        COM_RESERVED_ID_ISSUED_TO = 2
-
-        // IDs 3-6 reserved for full hash of identity to which this COM was issued.
-    };
-
-    /**
-     * Create an empty certificate of membership
-     */
-    CertificateOfMembership() : _qualifierCount(0)
-    {
-    }
-
-    /**
-     * Create from required fields common to all networks
-     *
-     * @param timestamp Timestamp of certificate
-     * @param timestampMaxDelta Maximum variation between timestamps on this net
-     * @param nwid Network ID
-     * @param issuedTo Certificate recipient
-     */
-    CertificateOfMembership(uint64_t timestamp, uint64_t timestampMaxDelta, uint64_t nwid, const Identity& issuedTo);
-
-    /**
-     * Create from binary-serialized COM in buffer
-     *
-     * @param b Buffer to deserialize from
-     * @param startAt Position to start in buffer
-     */
-    template <unsigned int C> CertificateOfMembership(const Buffer<C>& b, unsigned int startAt = 0)
-    {
-        deserialize(b, startAt);
-    }
-
-    /**
-     * @return True if there's something here
-     */
-    inline operator bool() const
-    {
-        return (_qualifierCount != 0);
-    }
-
-    /**
-     * @return Credential ID, always 0 for COMs
-     */
-    inline uint32_t id() const
-    {
-        return 0;
-    }
-
-    /**
-     * @return Timestamp for this cert and maximum delta for timestamp
-     */
-    inline int64_t timestamp() const
-    {
-        for (unsigned int i = 0; i < _qualifierCount; ++i) {
-            if (_qualifiers[i].id == COM_RESERVED_ID_TIMESTAMP) {
-                return _qualifiers[i].value;
-            }
-        }
-        return 0;
-    }
-
-    /**
-     * @return Address to which this cert was issued
-     */
-    inline Address issuedTo() const
-    {
-        for (unsigned int i = 0; i < _qualifierCount; ++i) {
-            if (_qualifiers[i].id == COM_RESERVED_ID_ISSUED_TO) {
-                return Address(_qualifiers[i].value);
-            }
-        }
-        return Address();
-    }
-
-    /**
-     * @return Network ID for which this cert was issued
-     */
-    inline uint64_t networkId() const
-    {
-        for (unsigned int i = 0; i < _qualifierCount; ++i) {
-            if (_qualifiers[i].id == COM_RESERVED_ID_NETWORK_ID) {
-                return _qualifiers[i].value;
-            }
-        }
-        return 0ULL;
-    }
-
-    /**
-     * Compare two certificates for parameter agreement
-     *
-     * This compares this certificate with the other and returns true if all
-     * parameters in this cert are present in the other and if they agree to
-     * within this cert's max delta value for each given parameter.
-     *
-     * Tuples present in other but not in this cert are ignored, but any
-     * tuples present in this cert but not in other result in 'false'.
-     *
-     * @param other Cert to compare with
-     * @param otherIdentity Identity of other node
-     * @return True if certs agree and 'other' may be communicated with
-     */
-    bool agreesWith(const CertificateOfMembership& other, const Identity& otherIdentity) const;
-
-    /**
-     * Sign this certificate
-     *
-     * @param with Identity to sign with, must include private key
-     * @return True if signature was successful
-     */
-    bool sign(const Identity& with);
-
-    /**
-     * Verify this COM and its signature
-     *
-     * @param RR Runtime environment for looking up peers
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or credential
-     */
-    int verify(const RuntimeEnvironment* RR, void* tPtr) const;
-
-    /**
-     * @return True if signed
-     */
-    inline bool isSigned() const
-    {
-        return (_signedBy);
-    }
-
-    /**
-     * @return Address that signed this certificate or null address if none
-     */
-    inline const Address& signedBy() const
-    {
-        return _signedBy;
-    }
-
-    template <unsigned int C> inline void serialize(Buffer<C>& b) const
-    {
-        b.append((uint8_t)1);
-        b.append((uint16_t)_qualifierCount);
-        for (unsigned int i = 0; i < _qualifierCount; ++i) {
-            b.append(_qualifiers[i].id);
-            b.append(_qualifiers[i].value);
-            b.append(_qualifiers[i].maxDelta);
-        }
-        _signedBy.appendTo(b);
-        if (_signedBy) {
-            b.append(_signature.data, ZT_ECC_SIGNATURE_LEN);
-        }
-    }
-
-    template <unsigned int C> inline unsigned int deserialize(const Buffer<C>& b, unsigned int startAt = 0)
-    {
-        unsigned int p = startAt;
-
-        _qualifierCount = 0;
-        _signedBy.zero();
-
-        if (b[p++] != 1) {
-            throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_TYPE;
-        }
-
-        unsigned int numq = b.template at<uint16_t>(p);
-        p += sizeof(uint16_t);
-        uint64_t lastId = 0;
-        for (unsigned int i = 0; i < numq; ++i) {
-            const uint64_t qid = b.template at<uint64_t>(p);
-            if (qid < lastId) {
-                throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_BAD_ENCODING;
-            }
-            else {
-                lastId = qid;
-            }
-            if (_qualifierCount < ZT_NETWORK_COM_MAX_QUALIFIERS) {
-                _qualifiers[_qualifierCount].id = qid;
-                _qualifiers[_qualifierCount].value = b.template at<uint64_t>(p + 8);
-                _qualifiers[_qualifierCount].maxDelta = b.template at<uint64_t>(p + 16);
-                p += 24;
-                ++_qualifierCount;
-            }
-            else {
-                throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW;
-            }
-        }
-
-        _signedBy.setTo(b.field(p, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
-        p += ZT_ADDRESS_LENGTH;
-
-        if (_signedBy) {
-            memcpy(_signature.data, b.field(p, ZT_ECC_SIGNATURE_LEN), ZT_ECC_SIGNATURE_LEN);
-            p += ZT_ECC_SIGNATURE_LEN;
-        }
-
-        return (p - startAt);
-    }
-
-    inline bool operator==(const CertificateOfMembership& c) const
-    {
-        if (_signedBy != c._signedBy) {
-            return false;
-        }
-        if (_qualifierCount != c._qualifierCount) {
-            return false;
-        }
-        for (unsigned int i = 0; i < _qualifierCount; ++i) {
-            const _Qualifier& a = _qualifiers[i];
-            const _Qualifier& b = c._qualifiers[i];
-            if ((a.id != b.id) || (a.value != b.value) || (a.maxDelta != b.maxDelta)) {
-                return false;
-            }
-        }
-        return (memcmp(_signature.data, c._signature.data, ZT_ECC_SIGNATURE_LEN) == 0);
-    }
-    inline bool operator!=(const CertificateOfMembership& c) const
-    {
-        return (! (*this == c));
-    }
+	static inline Credential::Type credentialType()
+	{
+		return Credential::CREDENTIAL_TYPE_COM;
+	}
+
+	/**
+	 * Reserved qualifier IDs
+	 *
+	 * IDs below 1024 are reserved for use as standard IDs. Others are available
+	 * for user-defined use.
+	 *
+	 * Addition of new required fields requires that code in hasRequiredFields
+	 * be updated as well.
+	 */
+	enum ReservedId {
+		/**
+		 * Timestamp of certificate
+		 */
+		COM_RESERVED_ID_TIMESTAMP = 0,
+
+		/**
+		 * Network ID for which certificate was issued
+		 */
+		COM_RESERVED_ID_NETWORK_ID = 1,
+
+		/**
+		 * ZeroTier address to whom certificate was issued
+		 */
+		COM_RESERVED_ID_ISSUED_TO = 2
+
+		// IDs 3-6 reserved for full hash of identity to which this COM was issued.
+	};
+
+	/**
+	 * Create an empty certificate of membership
+	 */
+	CertificateOfMembership() : _qualifierCount(0)
+	{
+	}
+
+	/**
+	 * Create from required fields common to all networks
+	 *
+	 * @param timestamp Timestamp of certificate
+	 * @param timestampMaxDelta Maximum variation between timestamps on this net
+	 * @param nwid Network ID
+	 * @param issuedTo Certificate recipient
+	 */
+	CertificateOfMembership(uint64_t timestamp, uint64_t timestampMaxDelta, uint64_t nwid, const Identity& issuedTo);
+
+	/**
+	 * Create from binary-serialized COM in buffer
+	 *
+	 * @param b Buffer to deserialize from
+	 * @param startAt Position to start in buffer
+	 */
+	template <unsigned int C> CertificateOfMembership(const Buffer<C>& b, unsigned int startAt = 0)
+	{
+		deserialize(b, startAt);
+	}
+
+	/**
+	 * @return True if there's something here
+	 */
+	inline operator bool() const
+	{
+		return (_qualifierCount != 0);
+	}
+
+	/**
+	 * @return Credential ID, always 0 for COMs
+	 */
+	inline uint32_t id() const
+	{
+		return 0;
+	}
+
+	/**
+	 * @return Timestamp for this cert and maximum delta for timestamp
+	 */
+	inline int64_t timestamp() const
+	{
+		for (unsigned int i = 0; i < _qualifierCount; ++i) {
+			if (_qualifiers[i].id == COM_RESERVED_ID_TIMESTAMP) {
+				return _qualifiers[i].value;
+			}
+		}
+		return 0;
+	}
+
+	/**
+	 * @return Address to which this cert was issued
+	 */
+	inline Address issuedTo() const
+	{
+		for (unsigned int i = 0; i < _qualifierCount; ++i) {
+			if (_qualifiers[i].id == COM_RESERVED_ID_ISSUED_TO) {
+				return Address(_qualifiers[i].value);
+			}
+		}
+		return Address();
+	}
+
+	/**
+	 * @return Network ID for which this cert was issued
+	 */
+	inline uint64_t networkId() const
+	{
+		for (unsigned int i = 0; i < _qualifierCount; ++i) {
+			if (_qualifiers[i].id == COM_RESERVED_ID_NETWORK_ID) {
+				return _qualifiers[i].value;
+			}
+		}
+		return 0ULL;
+	}
+
+	/**
+	 * Compare two certificates for parameter agreement
+	 *
+	 * This compares this certificate with the other and returns true if all
+	 * parameters in this cert are present in the other and if they agree to
+	 * within this cert's max delta value for each given parameter.
+	 *
+	 * Tuples present in other but not in this cert are ignored, but any
+	 * tuples present in this cert but not in other result in 'false'.
+	 *
+	 * @param other Cert to compare with
+	 * @param otherIdentity Identity of other node
+	 * @return True if certs agree and 'other' may be communicated with
+	 */
+	bool agreesWith(const CertificateOfMembership& other, const Identity& otherIdentity) const;
+
+	/**
+	 * Sign this certificate
+	 *
+	 * @param with Identity to sign with, must include private key
+	 * @return True if signature was successful
+	 */
+	bool sign(const Identity& with);
+
+	/**
+	 * Verify this COM and its signature
+	 *
+	 * @param RR Runtime environment for looking up peers
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or credential
+	 */
+	int verify(const RuntimeEnvironment* RR, void* tPtr) const;
+
+	/**
+	 * @return True if signed
+	 */
+	inline bool isSigned() const
+	{
+		return (_signedBy);
+	}
+
+	/**
+	 * @return Address that signed this certificate or null address if none
+	 */
+	inline const Address& signedBy() const
+	{
+		return _signedBy;
+	}
+
+	template <unsigned int C> inline void serialize(Buffer<C>& b) const
+	{
+		b.append((uint8_t)1);
+		b.append((uint16_t)_qualifierCount);
+		for (unsigned int i = 0; i < _qualifierCount; ++i) {
+			b.append(_qualifiers[i].id);
+			b.append(_qualifiers[i].value);
+			b.append(_qualifiers[i].maxDelta);
+		}
+		_signedBy.appendTo(b);
+		if (_signedBy) {
+			b.append(_signature.data, ZT_ECC_SIGNATURE_LEN);
+		}
+	}
+
+	template <unsigned int C> inline unsigned int deserialize(const Buffer<C>& b, unsigned int startAt = 0)
+	{
+		unsigned int p = startAt;
+
+		_qualifierCount = 0;
+		_signedBy.zero();
+
+		if (b[p++] != 1) {
+			throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_TYPE;
+		}
+
+		unsigned int numq = b.template at<uint16_t>(p);
+		p += sizeof(uint16_t);
+		uint64_t lastId = 0;
+		for (unsigned int i = 0; i < numq; ++i) {
+			const uint64_t qid = b.template at<uint64_t>(p);
+			if (qid < lastId) {
+				throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_BAD_ENCODING;
+			}
+			else {
+				lastId = qid;
+			}
+			if (_qualifierCount < ZT_NETWORK_COM_MAX_QUALIFIERS) {
+				_qualifiers[_qualifierCount].id = qid;
+				_qualifiers[_qualifierCount].value = b.template at<uint64_t>(p + 8);
+				_qualifiers[_qualifierCount].maxDelta = b.template at<uint64_t>(p + 16);
+				p += 24;
+				++_qualifierCount;
+			}
+			else {
+				throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW;
+			}
+		}
+
+		_signedBy.setTo(b.field(p, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
+		p += ZT_ADDRESS_LENGTH;
+
+		if (_signedBy) {
+			memcpy(_signature.data, b.field(p, ZT_ECC_SIGNATURE_LEN), ZT_ECC_SIGNATURE_LEN);
+			p += ZT_ECC_SIGNATURE_LEN;
+		}
+
+		return (p - startAt);
+	}
+
+	inline bool operator==(const CertificateOfMembership& c) const
+	{
+		if (_signedBy != c._signedBy) {
+			return false;
+		}
+		if (_qualifierCount != c._qualifierCount) {
+			return false;
+		}
+		for (unsigned int i = 0; i < _qualifierCount; ++i) {
+			const _Qualifier& a = _qualifiers[i];
+			const _Qualifier& b = c._qualifiers[i];
+			if ((a.id != b.id) || (a.value != b.value) || (a.maxDelta != b.maxDelta)) {
+				return false;
+			}
+		}
+		return (memcmp(_signature.data, c._signature.data, ZT_ECC_SIGNATURE_LEN) == 0);
+	}
+	inline bool operator!=(const CertificateOfMembership& c) const
+	{
+		return (! (*this == c));
+	}
 
   private:
-    struct _Qualifier {
-        _Qualifier() : id(0), value(0), maxDelta(0)
-        {
-        }
-        uint64_t id;
-        uint64_t value;
-        uint64_t maxDelta;
-        inline bool operator<(const _Qualifier& q) const
-        {
-            return (id < q.id);
-        }   // sort order
-    };
-
-    Address _signedBy;
-    _Qualifier _qualifiers[ZT_NETWORK_COM_MAX_QUALIFIERS];
-    unsigned int _qualifierCount;
-    ECC::Signature _signature;
+	struct _Qualifier {
+		_Qualifier() : id(0), value(0), maxDelta(0)
+		{
+		}
+		uint64_t id;
+		uint64_t value;
+		uint64_t maxDelta;
+		inline bool operator<(const _Qualifier& q) const
+		{
+			return (id < q.id);
+		}	// sort order
+	};
+
+	Address _signedBy;
+	_Qualifier _qualifiers[ZT_NETWORK_COM_MAX_QUALIFIERS];
+	unsigned int _qualifierCount;
+	ECC::Signature _signature;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 32 - 32
node/CertificateOfOwnership.cpp

@@ -24,41 +24,41 @@ namespace ZeroTier {
 
 int CertificateOfOwnership::verify(const RuntimeEnvironment* RR, void* tPtr) const
 {
-    if ((! _signedBy) || (_signedBy != Network::controllerFor(_networkId))) {
-        return -1;
-    }
-    const Identity id(RR->topology->getIdentity(tPtr, _signedBy));
-    if (! id) {
-        RR->sw->requestWhois(tPtr, RR->node->now(), _signedBy);
-        return 1;
-    }
-    try {
-        Buffer<(sizeof(CertificateOfOwnership) + 64)> tmp;
-        this->serialize(tmp, true);
-        return (id.verify(tmp.data(), tmp.size(), _signature) ? 0 : -1);
-    }
-    catch (...) {
-        return -1;
-    }
+	if ((! _signedBy) || (_signedBy != Network::controllerFor(_networkId))) {
+		return -1;
+	}
+	const Identity id(RR->topology->getIdentity(tPtr, _signedBy));
+	if (! id) {
+		RR->sw->requestWhois(tPtr, RR->node->now(), _signedBy);
+		return 1;
+	}
+	try {
+		Buffer<(sizeof(CertificateOfOwnership) + 64)> tmp;
+		this->serialize(tmp, true);
+		return (id.verify(tmp.data(), tmp.size(), _signature) ? 0 : -1);
+	}
+	catch (...) {
+		return -1;
+	}
 }
 
 bool CertificateOfOwnership::_owns(const CertificateOfOwnership::Thing& t, const void* v, unsigned int l) const
 {
-    for (unsigned int i = 0, j = _thingCount; i < j; ++i) {
-        if (_thingTypes[i] == (uint8_t)t) {
-            unsigned int k = 0;
-            while (k < l) {
-                if (reinterpret_cast<const uint8_t*>(v)[k] != _thingValues[i][k]) {
-                    break;
-                }
-                ++k;
-            }
-            if (k == l) {
-                return true;
-            }
-        }
-    }
-    return false;
+	for (unsigned int i = 0, j = _thingCount; i < j; ++i) {
+		if (_thingTypes[i] == (uint8_t)t) {
+			unsigned int k = 0;
+			while (k < l) {
+				if (reinterpret_cast<const uint8_t*>(v)[k] != _thingValues[i][k]) {
+					break;
+				}
+				++k;
+			}
+			if (k == l) {
+				return true;
+			}
+		}
+	}
+	return false;
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 226 - 226
node/CertificateOfOwnership.hpp

@@ -43,235 +43,235 @@ class RuntimeEnvironment;
  */
 class CertificateOfOwnership : public Credential {
   public:
-    static inline Credential::Type credentialType()
-    {
-        return Credential::CREDENTIAL_TYPE_COO;
-    }
-
-    enum Thing { THING_NULL = 0, THING_MAC_ADDRESS = 1, THING_IPV4_ADDRESS = 2, THING_IPV6_ADDRESS = 3 };
-
-    CertificateOfOwnership()
-    {
-        memset(reinterpret_cast<void*>(this), 0, sizeof(CertificateOfOwnership));
-    }
-
-    CertificateOfOwnership(const uint64_t nwid, const int64_t ts, const Address& issuedTo, const uint32_t id)
-    {
-        memset(reinterpret_cast<void*>(this), 0, sizeof(CertificateOfOwnership));
-        _networkId = nwid;
-        _ts = ts;
-        _id = id;
-        _issuedTo = issuedTo;
-    }
-
-    inline uint64_t networkId() const
-    {
-        return _networkId;
-    }
-    inline int64_t timestamp() const
-    {
-        return _ts;
-    }
-    inline uint32_t id() const
-    {
-        return _id;
-    }
-    inline unsigned int thingCount() const
-    {
-        return (unsigned int)_thingCount;
-    }
-
-    inline Thing thingType(const unsigned int i) const
-    {
-        return (Thing)_thingTypes[i];
-    }
-    inline const uint8_t* thingValue(const unsigned int i) const
-    {
-        return _thingValues[i];
-    }
-
-    inline const Address& issuedTo() const
-    {
-        return _issuedTo;
-    }
-
-    inline bool owns(const InetAddress& ip) const
-    {
-        if (ip.ss_family == AF_INET) {
-            return this->_owns(THING_IPV4_ADDRESS, &(reinterpret_cast<const struct sockaddr_in*>(&ip)->sin_addr.s_addr), 4);
-        }
-        if (ip.ss_family == AF_INET6) {
-            return this->_owns(THING_IPV6_ADDRESS, reinterpret_cast<const struct sockaddr_in6*>(&ip)->sin6_addr.s6_addr, 16);
-        }
-        return false;
-    }
-
-    inline bool owns(const MAC& mac) const
-    {
-        uint8_t tmp[6];
-        mac.copyTo(tmp, 6);
-        return this->_owns(THING_MAC_ADDRESS, tmp, 6);
-    }
-
-    inline void addThing(const InetAddress& ip)
-    {
-        if (_thingCount >= ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS) {
-            return;
-        }
-        if (ip.ss_family == AF_INET) {
-            _thingTypes[_thingCount] = THING_IPV4_ADDRESS;
-            memcpy(_thingValues[_thingCount], &(reinterpret_cast<const struct sockaddr_in*>(&ip)->sin_addr.s_addr), 4);
-            ++_thingCount;
-        }
-        else if (ip.ss_family == AF_INET6) {
-            _thingTypes[_thingCount] = THING_IPV6_ADDRESS;
-            memcpy(_thingValues[_thingCount], reinterpret_cast<const struct sockaddr_in6*>(&ip)->sin6_addr.s6_addr, 16);
-            ++_thingCount;
-        }
-    }
-
-    inline void addThing(const MAC& mac)
-    {
-        if (_thingCount >= ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS) {
-            return;
-        }
-        _thingTypes[_thingCount] = THING_MAC_ADDRESS;
-        mac.copyTo(_thingValues[_thingCount], 6);
-        ++_thingCount;
-    }
-
-    /**
-     * @param signer Signing identity, must have private key
-     * @return True if signature was successful
-     */
-    inline bool sign(const Identity& signer)
-    {
-        if (signer.hasPrivate()) {
-            Buffer<sizeof(CertificateOfOwnership) + 64> tmp;
-            _signedBy = signer.address();
-            this->serialize(tmp, true);
-            _signature = signer.sign(tmp.data(), tmp.size());
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * @param RR Runtime environment to allow identity lookup for signedBy
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature
-     */
-    int verify(const RuntimeEnvironment* RR, void* tPtr) const;
-
-    template <unsigned int C> inline void serialize(Buffer<C>& b, const bool forSign = false) const
-    {
-        if (forSign) {
-            b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
-        }
-
-        b.append(_networkId);
-        b.append(_ts);
-        b.append(_flags);
-        b.append(_id);
-        b.append((uint16_t)_thingCount);
-        for (unsigned int i = 0, j = _thingCount; i < j; ++i) {
-            b.append((uint8_t)_thingTypes[i]);
-            b.append(_thingValues[i], ZT_CERTIFICATEOFOWNERSHIP_MAX_THING_VALUE_SIZE);
-        }
-
-        _issuedTo.appendTo(b);
-        _signedBy.appendTo(b);
-        if (! forSign) {
-            b.append((uint8_t)1);                       // 1 == Ed25519
-            b.append((uint16_t)ZT_ECC_SIGNATURE_LEN);   // length of signature
-            b.append(_signature.data, ZT_ECC_SIGNATURE_LEN);
-        }
-
-        b.append((uint16_t)0);   // length of additional fields, currently 0
-
-        if (forSign) {
-            b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
-        }
-    }
-
-    template <unsigned int C> inline unsigned int deserialize(const Buffer<C>& b, unsigned int startAt = 0)
-    {
-        unsigned int p = startAt;
-
-        *this = CertificateOfOwnership();
-
-        _networkId = b.template at<uint64_t>(p);
-        p += 8;
-        _ts = b.template at<uint64_t>(p);
-        p += 8;
-        _flags = b.template at<uint64_t>(p);
-        p += 8;
-        _id = b.template at<uint32_t>(p);
-        p += 4;
-        _thingCount = b.template at<uint16_t>(p);
-        p += 2;
-        for (unsigned int i = 0, j = _thingCount; i < j; ++i) {
-            if (i < ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS) {
-                _thingTypes[i] = (uint8_t)b[p++];
-                memcpy(_thingValues[i], b.field(p, ZT_CERTIFICATEOFOWNERSHIP_MAX_THING_VALUE_SIZE), ZT_CERTIFICATEOFOWNERSHIP_MAX_THING_VALUE_SIZE);
-                p += ZT_CERTIFICATEOFOWNERSHIP_MAX_THING_VALUE_SIZE;
-            }
-        }
-
-        _issuedTo.setTo(b.field(p, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
-        p += ZT_ADDRESS_LENGTH;
-        _signedBy.setTo(b.field(p, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
-        p += ZT_ADDRESS_LENGTH;
-        if (b[p++] == 1) {
-            if (b.template at<uint16_t>(p) != ZT_ECC_SIGNATURE_LEN) {
-                throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_CRYPTOGRAPHIC_TOKEN;
-            }
-            p += 2;
-            memcpy(_signature.data, b.field(p, ZT_ECC_SIGNATURE_LEN), ZT_ECC_SIGNATURE_LEN);
-            p += ZT_ECC_SIGNATURE_LEN;
-        }
-        else {
-            p += 2 + b.template at<uint16_t>(p);
-        }
-
-        p += 2 + b.template at<uint16_t>(p);
-        if (p > b.size()) {
-            throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW;
-        }
-
-        return (p - startAt);
-    }
-
-    // Provides natural sort order by ID
-    inline bool operator<(const CertificateOfOwnership& coo) const
-    {
-        return (_id < coo._id);
-    }
-
-    inline bool operator==(const CertificateOfOwnership& coo) const
-    {
-        return (memcmp(this, &coo, sizeof(CertificateOfOwnership)) == 0);
-    }
-    inline bool operator!=(const CertificateOfOwnership& coo) const
-    {
-        return (memcmp(this, &coo, sizeof(CertificateOfOwnership)) != 0);
-    }
+	static inline Credential::Type credentialType()
+	{
+		return Credential::CREDENTIAL_TYPE_COO;
+	}
+
+	enum Thing { THING_NULL = 0, THING_MAC_ADDRESS = 1, THING_IPV4_ADDRESS = 2, THING_IPV6_ADDRESS = 3 };
+
+	CertificateOfOwnership()
+	{
+		memset(reinterpret_cast<void*>(this), 0, sizeof(CertificateOfOwnership));
+	}
+
+	CertificateOfOwnership(const uint64_t nwid, const int64_t ts, const Address& issuedTo, const uint32_t id)
+	{
+		memset(reinterpret_cast<void*>(this), 0, sizeof(CertificateOfOwnership));
+		_networkId = nwid;
+		_ts = ts;
+		_id = id;
+		_issuedTo = issuedTo;
+	}
+
+	inline uint64_t networkId() const
+	{
+		return _networkId;
+	}
+	inline int64_t timestamp() const
+	{
+		return _ts;
+	}
+	inline uint32_t id() const
+	{
+		return _id;
+	}
+	inline unsigned int thingCount() const
+	{
+		return (unsigned int)_thingCount;
+	}
+
+	inline Thing thingType(const unsigned int i) const
+	{
+		return (Thing)_thingTypes[i];
+	}
+	inline const uint8_t* thingValue(const unsigned int i) const
+	{
+		return _thingValues[i];
+	}
+
+	inline const Address& issuedTo() const
+	{
+		return _issuedTo;
+	}
+
+	inline bool owns(const InetAddress& ip) const
+	{
+		if (ip.ss_family == AF_INET) {
+			return this->_owns(THING_IPV4_ADDRESS, &(reinterpret_cast<const struct sockaddr_in*>(&ip)->sin_addr.s_addr), 4);
+		}
+		if (ip.ss_family == AF_INET6) {
+			return this->_owns(THING_IPV6_ADDRESS, reinterpret_cast<const struct sockaddr_in6*>(&ip)->sin6_addr.s6_addr, 16);
+		}
+		return false;
+	}
+
+	inline bool owns(const MAC& mac) const
+	{
+		uint8_t tmp[6];
+		mac.copyTo(tmp, 6);
+		return this->_owns(THING_MAC_ADDRESS, tmp, 6);
+	}
+
+	inline void addThing(const InetAddress& ip)
+	{
+		if (_thingCount >= ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS) {
+			return;
+		}
+		if (ip.ss_family == AF_INET) {
+			_thingTypes[_thingCount] = THING_IPV4_ADDRESS;
+			memcpy(_thingValues[_thingCount], &(reinterpret_cast<const struct sockaddr_in*>(&ip)->sin_addr.s_addr), 4);
+			++_thingCount;
+		}
+		else if (ip.ss_family == AF_INET6) {
+			_thingTypes[_thingCount] = THING_IPV6_ADDRESS;
+			memcpy(_thingValues[_thingCount], reinterpret_cast<const struct sockaddr_in6*>(&ip)->sin6_addr.s6_addr, 16);
+			++_thingCount;
+		}
+	}
+
+	inline void addThing(const MAC& mac)
+	{
+		if (_thingCount >= ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS) {
+			return;
+		}
+		_thingTypes[_thingCount] = THING_MAC_ADDRESS;
+		mac.copyTo(_thingValues[_thingCount], 6);
+		++_thingCount;
+	}
+
+	/**
+	 * @param signer Signing identity, must have private key
+	 * @return True if signature was successful
+	 */
+	inline bool sign(const Identity& signer)
+	{
+		if (signer.hasPrivate()) {
+			Buffer<sizeof(CertificateOfOwnership) + 64> tmp;
+			_signedBy = signer.address();
+			this->serialize(tmp, true);
+			_signature = signer.sign(tmp.data(), tmp.size());
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * @param RR Runtime environment to allow identity lookup for signedBy
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature
+	 */
+	int verify(const RuntimeEnvironment* RR, void* tPtr) const;
+
+	template <unsigned int C> inline void serialize(Buffer<C>& b, const bool forSign = false) const
+	{
+		if (forSign) {
+			b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
+		}
+
+		b.append(_networkId);
+		b.append(_ts);
+		b.append(_flags);
+		b.append(_id);
+		b.append((uint16_t)_thingCount);
+		for (unsigned int i = 0, j = _thingCount; i < j; ++i) {
+			b.append((uint8_t)_thingTypes[i]);
+			b.append(_thingValues[i], ZT_CERTIFICATEOFOWNERSHIP_MAX_THING_VALUE_SIZE);
+		}
+
+		_issuedTo.appendTo(b);
+		_signedBy.appendTo(b);
+		if (! forSign) {
+			b.append((uint8_t)1);						// 1 == Ed25519
+			b.append((uint16_t)ZT_ECC_SIGNATURE_LEN);	// length of signature
+			b.append(_signature.data, ZT_ECC_SIGNATURE_LEN);
+		}
+
+		b.append((uint16_t)0);	 // length of additional fields, currently 0
+
+		if (forSign) {
+			b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
+		}
+	}
+
+	template <unsigned int C> inline unsigned int deserialize(const Buffer<C>& b, unsigned int startAt = 0)
+	{
+		unsigned int p = startAt;
+
+		*this = CertificateOfOwnership();
+
+		_networkId = b.template at<uint64_t>(p);
+		p += 8;
+		_ts = b.template at<uint64_t>(p);
+		p += 8;
+		_flags = b.template at<uint64_t>(p);
+		p += 8;
+		_id = b.template at<uint32_t>(p);
+		p += 4;
+		_thingCount = b.template at<uint16_t>(p);
+		p += 2;
+		for (unsigned int i = 0, j = _thingCount; i < j; ++i) {
+			if (i < ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS) {
+				_thingTypes[i] = (uint8_t)b[p++];
+				memcpy(_thingValues[i], b.field(p, ZT_CERTIFICATEOFOWNERSHIP_MAX_THING_VALUE_SIZE), ZT_CERTIFICATEOFOWNERSHIP_MAX_THING_VALUE_SIZE);
+				p += ZT_CERTIFICATEOFOWNERSHIP_MAX_THING_VALUE_SIZE;
+			}
+		}
+
+		_issuedTo.setTo(b.field(p, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
+		p += ZT_ADDRESS_LENGTH;
+		_signedBy.setTo(b.field(p, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
+		p += ZT_ADDRESS_LENGTH;
+		if (b[p++] == 1) {
+			if (b.template at<uint16_t>(p) != ZT_ECC_SIGNATURE_LEN) {
+				throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_CRYPTOGRAPHIC_TOKEN;
+			}
+			p += 2;
+			memcpy(_signature.data, b.field(p, ZT_ECC_SIGNATURE_LEN), ZT_ECC_SIGNATURE_LEN);
+			p += ZT_ECC_SIGNATURE_LEN;
+		}
+		else {
+			p += 2 + b.template at<uint16_t>(p);
+		}
+
+		p += 2 + b.template at<uint16_t>(p);
+		if (p > b.size()) {
+			throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW;
+		}
+
+		return (p - startAt);
+	}
+
+	// Provides natural sort order by ID
+	inline bool operator<(const CertificateOfOwnership& coo) const
+	{
+		return (_id < coo._id);
+	}
+
+	inline bool operator==(const CertificateOfOwnership& coo) const
+	{
+		return (memcmp(this, &coo, sizeof(CertificateOfOwnership)) == 0);
+	}
+	inline bool operator!=(const CertificateOfOwnership& coo) const
+	{
+		return (memcmp(this, &coo, sizeof(CertificateOfOwnership)) != 0);
+	}
 
   private:
-    bool _owns(const Thing& t, const void* v, unsigned int l) const;
-
-    uint64_t _networkId;
-    int64_t _ts;
-    uint64_t _flags;
-    uint32_t _id;
-    uint16_t _thingCount;
-    uint8_t _thingTypes[ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS];
-    uint8_t _thingValues[ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS][ZT_CERTIFICATEOFOWNERSHIP_MAX_THING_VALUE_SIZE];
-    Address _issuedTo;
-    Address _signedBy;
-    ECC::Signature _signature;
+	bool _owns(const Thing& t, const void* v, unsigned int l) const;
+
+	uint64_t _networkId;
+	int64_t _ts;
+	uint64_t _flags;
+	uint32_t _id;
+	uint16_t _thingCount;
+	uint8_t _thingTypes[ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS];
+	uint8_t _thingValues[ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS][ZT_CERTIFICATEOFOWNERSHIP_MAX_THING_VALUE_SIZE];
+	Address _issuedTo;
+	Address _signedBy;
+	ECC::Signature _signature;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 34 - 34
node/Constants.hpp

@@ -61,7 +61,7 @@
 #ifdef ZT_SSO_SUPPORTED
 #define ZT_SSO_ENABLED 1
 #endif
-#define likely(x)   __builtin_expect((x), 1)
+#define likely(x)	__builtin_expect((x), 1)
 #define unlikely(x) __builtin_expect((x), 0)
 #include <TargetConditionals.h>
 #ifndef __UNIX_LIKE__
@@ -85,9 +85,9 @@
 #endif
 #include <machine/endian.h>
 #ifndef __BYTE_ORDER
-#define __BYTE_ORDER    _BYTE_ORDER
+#define __BYTE_ORDER	_BYTE_ORDER
 #define __LITTLE_ENDIAN _LITTLE_ENDIAN
-#define __BIG_ENDIAN    _BIG_ENDIAN
+#define __BIG_ENDIAN	_BIG_ENDIAN
 #endif
 #endif
 
@@ -146,7 +146,7 @@
 
 // Define ZT_NO_TYPE_PUNNING to disable reckless casts on anything other than x86/x64.
 #if (! (defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_AMD64) || defined(_M_X64) || defined(i386) || defined(__i386) || defined(__i386__) || defined(__i486__) || defined(__i586__)          \
-        || defined(__i686__) || defined(_M_IX86) || defined(__X86__) || defined(_X86_) || defined(__I86__) || defined(__INTEL__) || defined(__386)))
+		|| defined(__i686__) || defined(_M_IX86) || defined(__X86__) || defined(_X86_) || defined(__I86__) || defined(__INTEL__) || defined(__386)))
 #ifndef ZT_NO_TYPE_PUNNING
 #define ZT_NO_TYPE_PUNNING 1
 #endif
@@ -162,19 +162,19 @@
 #undef __BYTE_ORDER
 #undef __LITTLE_ENDIAN
 #undef __BIG_ENDIAN
-#define __BIG_ENDIAN    4321
+#define __BIG_ENDIAN	4321
 #define __LITTLE_ENDIAN 1234
-#define __BYTE_ORDER    1234
+#define __BYTE_ORDER	1234
 #endif
 
 #ifdef __WINDOWS__
-#define ZT_PATH_SEPARATOR   '\\'
+#define ZT_PATH_SEPARATOR	'\\'
 #define ZT_PATH_SEPARATOR_S "\\"
-#define ZT_EOL_S            "\r\n"
+#define ZT_EOL_S			"\r\n"
 #else
-#define ZT_PATH_SEPARATOR   '/'
+#define ZT_PATH_SEPARATOR	'/'
 #define ZT_PATH_SEPARATOR_S "/"
-#define ZT_EOL_S            "\n"
+#define ZT_EOL_S			"\n"
 #endif
 
 #ifndef __BYTE_ORDER
@@ -204,37 +204,37 @@
 #endif
 
 #if defined(_WIN32)
-#define ZT_PLATFORM_NAME "windows"   // Windows
+#define ZT_PLATFORM_NAME "windows"	 // Windows
 #elif defined(_WIN64)
-#define ZT_PLATFORM_NAME "windows"   // Windows
+#define ZT_PLATFORM_NAME "windows"	 // Windows
 #elif defined(__CYGWIN__)
-#define ZT_PLATFORM_NAME "windows"   // Windows (Cygwin POSIX under Microsoft Window)
+#define ZT_PLATFORM_NAME "windows"	 // Windows (Cygwin POSIX under Microsoft Window)
 #elif defined(__ANDROID__)
-#define ZT_PLATFORM_NAME "android"   // Android (implies Linux, so it must come first)
+#define ZT_PLATFORM_NAME "android"	 // Android (implies Linux, so it must come first)
 #elif defined(__linux__)
 #define ZT_PLATFORM_NAME "linux"   // Debian, Ubuntu, Gentoo, Fedora, openSUSE, RedHat, Centos and other
 #elif defined(__unix__) || ! defined(__APPLE__) && defined(__MACH__)
 #include <sys/param.h>
 #if defined(BSD)
-#define ZT_PLATFORM_NAME "bsd"   // FreeBSD, NetBSD, OpenBSD, DragonFly BSD
+#define ZT_PLATFORM_NAME "bsd"	 // FreeBSD, NetBSD, OpenBSD, DragonFly BSD
 #endif
 #elif defined(__hpux)
 #define ZT_PLATFORM_NAME "hp-ux"   // HP-UX
 #elif defined(_AIX)
-#define ZT_PLATFORM_NAME "aix"                  // IBM AIX
-#elif defined(__APPLE__) && defined(__MACH__)   // Apple OSX and iOS (Darwin)
+#define ZT_PLATFORM_NAME "aix"					// IBM AIX
+#elif defined(__APPLE__) && defined(__MACH__)	// Apple OSX and iOS (Darwin)
 #include <TargetConditionals.h>
 #if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR == 1
-#define ZT_PLATFORM_NAME "ios_sim"   // Apple iOS
+#define ZT_PLATFORM_NAME "ios_sim"	 // Apple iOS
 #elif defined(TARGET_OS_IPAD) && TARGET_OS_IPAD == 1
 #define ZT_PLATFORM_NAME "ios_ipad"
 #elif defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1
-#define ZT_PLATFORM_NAME "ios_iphone"   // Apple iOS
+#define ZT_PLATFORM_NAME "ios_iphone"	// Apple iOS
 #elif defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1
 #define ZT_PLATFORM_NAME "macos"   // Apple OSX
 #endif
 #elif defined(__sun) && defined(__SVR4)
-#define ZT_PLATFORM_NAME "solaris"   // Oracle Solaris, Open Indiana
+#define ZT_PLATFORM_NAME "solaris"	 // Oracle Solaris, Open Indiana
 #else
 #define ZT_PLATFORM_NAME "unknown"
 #endif
@@ -581,23 +581,23 @@
 /**
  * Drainage constants for VERB_ECHO rate-limiters
  */
-#define ZT_ECHO_CUTOFF_LIMIT     ((1000 / ZT_CORE_TIMER_TASK_GRANULARITY) * ZT_MAX_PEER_NETWORK_PATHS)
+#define ZT_ECHO_CUTOFF_LIMIT	 ((1000 / ZT_CORE_TIMER_TASK_GRANULARITY) * ZT_MAX_PEER_NETWORK_PATHS)
 #define ZT_ECHO_DRAINAGE_DIVISOR (1000 / ZT_ECHO_CUTOFF_LIMIT)
 
 /**
  * Drainage constants for VERB_QOS rate-limiters
  */
-#define ZT_QOS_CUTOFF_LIMIT     ((1000 / ZT_CORE_TIMER_TASK_GRANULARITY) * ZT_MAX_PEER_NETWORK_PATHS)
+#define ZT_QOS_CUTOFF_LIMIT		((1000 / ZT_CORE_TIMER_TASK_GRANULARITY) * ZT_MAX_PEER_NETWORK_PATHS)
 #define ZT_QOS_DRAINAGE_DIVISOR (1000 / ZT_QOS_CUTOFF_LIMIT)
 
 /**
  * Drainage constants for VERB_ACK rate-limiters
  */
-#define ZT_ACK_CUTOFF_LIMIT     128
+#define ZT_ACK_CUTOFF_LIMIT		128
 #define ZT_ACK_DRAINAGE_DIVISOR (1000 / ZT_ACK_CUTOFF_LIMIT)
 
 #define ZT_BOND_DEFAULT_REFRACTORY_PERIOD 8000
-#define ZT_BOND_MAX_REFRACTORY_PERIOD     600000
+#define ZT_BOND_MAX_REFRACTORY_PERIOD	  600000
 
 /**
  * Maximum number of direct path pushes within cutoff time
@@ -709,8 +709,8 @@
  * Artificially inflates the failover score for paths which meet
  * certain non-performance-related policy ranking criteria.
  */
-#define ZT_BOND_FAILOVER_HANDICAP_PREFERRED  500
-#define ZT_BOND_FAILOVER_HANDICAP_PRIMARY    1000
+#define ZT_BOND_FAILOVER_HANDICAP_PREFERRED	 500
+#define ZT_BOND_FAILOVER_HANDICAP_PRIMARY	 1000
 #define ZT_BOND_FAILOVER_HANDICAP_NEGOTIATED 5000
 
 /**
@@ -754,14 +754,14 @@
 #define ZT_THREAD_MIN_STACK_SIZE 1048576
 
 // Exceptions thrown in core ZT code
-#define ZT_EXCEPTION_OUT_OF_BOUNDS                                       100
-#define ZT_EXCEPTION_OUT_OF_MEMORY                                       101
-#define ZT_EXCEPTION_PRIVATE_KEY_REQUIRED                                102
-#define ZT_EXCEPTION_INVALID_ARGUMENT                                    103
-#define ZT_EXCEPTION_INVALID_IDENTITY                                    104
-#define ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_TYPE                200
-#define ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW                    201
+#define ZT_EXCEPTION_OUT_OF_BOUNDS										 100
+#define ZT_EXCEPTION_OUT_OF_MEMORY										 101
+#define ZT_EXCEPTION_PRIVATE_KEY_REQUIRED								 102
+#define ZT_EXCEPTION_INVALID_ARGUMENT									 103
+#define ZT_EXCEPTION_INVALID_IDENTITY									 104
+#define ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_TYPE				 200
+#define ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW					 201
 #define ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_CRYPTOGRAPHIC_TOKEN 202
-#define ZT_EXCEPTION_INVALID_SERIALIZED_DATA_BAD_ENCODING                203
+#define ZT_EXCEPTION_INVALID_SERIALIZED_DATA_BAD_ENCODING				 203
 
 #endif

+ 12 - 12
node/Credential.hpp

@@ -21,19 +21,19 @@ namespace ZeroTier {
  */
 class Credential {
   public:
-    /**
-     * Do not change type code IDs -- these are used in Revocation objects and elsewhere
-     */
-    enum Type {
-        CREDENTIAL_TYPE_NULL = 0,
-        CREDENTIAL_TYPE_COM = 1,   // CertificateOfMembership
-        CREDENTIAL_TYPE_CAPABILITY = 2,
-        CREDENTIAL_TYPE_TAG = 3,
-        CREDENTIAL_TYPE_COO = 4,   // CertificateOfOwnership
-        CREDENTIAL_TYPE_REVOCATION = 6
-    };
+	/**
+	 * Do not change type code IDs -- these are used in Revocation objects and elsewhere
+	 */
+	enum Type {
+		CREDENTIAL_TYPE_NULL = 0,
+		CREDENTIAL_TYPE_COM = 1,   // CertificateOfMembership
+		CREDENTIAL_TYPE_CAPABILITY = 2,
+		CREDENTIAL_TYPE_TAG = 3,
+		CREDENTIAL_TYPE_COO = 4,   // CertificateOfOwnership
+		CREDENTIAL_TYPE_REVOCATION = 6
+	};
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 21 - 21
node/DNS.hpp

@@ -28,28 +28,28 @@ namespace ZeroTier {
  */
 class DNS {
   public:
-    template <unsigned int C> static inline void serializeDNS(Buffer<C>& b, const ZT_VirtualNetworkDNS* dns)
-    {
-        b.append(dns->domain, 128);
-        for (unsigned int j = 0; j < ZT_MAX_DNS_SERVERS; ++j) {
-            InetAddress tmp(dns->server_addr[j]);
-            tmp.serialize(b);
-        }
-    }
+	template <unsigned int C> static inline void serializeDNS(Buffer<C>& b, const ZT_VirtualNetworkDNS* dns)
+	{
+		b.append(dns->domain, 128);
+		for (unsigned int j = 0; j < ZT_MAX_DNS_SERVERS; ++j) {
+			InetAddress tmp(dns->server_addr[j]);
+			tmp.serialize(b);
+		}
+	}
 
-    template <unsigned int C> static inline void deserializeDNS(const Buffer<C>& b, unsigned int& p, ZT_VirtualNetworkDNS* dns)
-    {
-        char* d = (char*)b.data() + p;
-        memset(dns, 0, sizeof(ZT_VirtualNetworkDNS));
-        memcpy(dns->domain, d, 128);
-        dns->domain[127] = 0;
-        p += 128;
-        for (unsigned int j = 0; j < ZT_MAX_DNS_SERVERS; ++j) {
-            p += reinterpret_cast<InetAddress*>(&(dns->server_addr[j]))->deserialize(b, p);
-        }
-    }
+	template <unsigned int C> static inline void deserializeDNS(const Buffer<C>& b, unsigned int& p, ZT_VirtualNetworkDNS* dns)
+	{
+		char* d = (char*)b.data() + p;
+		memset(dns, 0, sizeof(ZT_VirtualNetworkDNS));
+		memcpy(dns->domain, d, 128);
+		dns->domain[127] = 0;
+		p += 128;
+		for (unsigned int j = 0; j < ZT_MAX_DNS_SERVERS; ++j) {
+			p += reinterpret_cast<InetAddress*>(&(dns->server_addr[j]))->deserialize(b, p);
+		}
+	}
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
-#endif   // ZT_DNS_HPP
+#endif	 // ZT_DNS_HPP

+ 451 - 451
node/Dictionary.hpp

@@ -47,460 +47,460 @@ namespace ZeroTier {
  */
 template <unsigned int C> class Dictionary {
   public:
-    Dictionary()
-    {
-        memset(_d, 0, sizeof(_d));
-    }
-    Dictionary(const char* s)
-    {
-        this->load(s);
-    }
-    Dictionary(const char* s, unsigned int len)
-    {
-        for (unsigned int i = 0; i < C; ++i) {
-            if ((s) && (i < len)) {
-                if (! (_d[i] = *s)) {
-                    s = (const char*)0;
-                }
-                else {
-                    ++s;
-                }
-            }
-            else {
-                _d[i] = (char)0;
-            }
-        }
-        _d[C - 1] = (char)0;
-    }
-    Dictionary(const Dictionary& d)
-    {
-        memcpy(_d, d._d, C);
-    }
-
-    inline Dictionary& operator=(const Dictionary& d)
-    {
-        memcpy(_d, d._d, C);
-        return *this;
-    }
-
-    inline operator bool() const
-    {
-        return (_d[0] != 0);
-    }
-
-    /**
-     * Load a dictionary from a C-string
-     *
-     * @param s Dictionary in string form
-     * @return False if 's' was longer than our capacity
-     */
-    inline bool load(const char* s)
-    {
-        for (unsigned int i = 0; i < C; ++i) {
-            if (s) {
-                if (! (_d[i] = *s)) {
-                    s = (const char*)0;
-                }
-                else {
-                    ++s;
-                }
-            }
-            else {
-                _d[i] = (char)0;
-            }
-        }
-        _d[C - 1] = (char)0;
-        return (! s);
-    }
-
-    /**
-     * Delete all entries
-     */
-    inline void clear()
-    {
-        memset(_d, 0, sizeof(_d));
-    }
-
-    /**
-     * @return Size of dictionary in bytes not including terminating NULL
-     */
-    inline unsigned int sizeBytes() const
-    {
-        for (unsigned int i = 0; i < C; ++i) {
-            if (! _d[i]) {
-                return i;
-            }
-        }
-        return C - 1;
-    }
-
-    /**
-     * Get an entry
-     *
-     * Note that to get binary values, dest[] should be at least one more than
-     * the maximum size of the value being retrieved. That's because even if
-     * the data is binary a terminating 0 is still appended to dest[] after it.
-     *
-     * If the key is not found, dest[0] is set to 0 to make dest[] an empty
-     * C string in that case. The dest[] array will *never* be unterminated
-     * after this call.
-     *
-     * Security note: if 'key' is ever directly based on anything that is not
-     * a hard-code or internally-generated name, it must be checked to ensure
-     * that the buffer is NULL-terminated since key[] does not take a secondary
-     * size parameter. In NetworkConfig all keys are hard-coded strings so this
-     * isn't a problem in the core.
-     *
-     * @param key Key to look up
-     * @param dest Destination buffer
-     * @param destlen Size of destination buffer
-     * @return -1 if not found, or actual number of bytes stored in dest[] minus trailing 0
-     */
-    inline int get(const char* key, char* dest, unsigned int destlen) const
-    {
-        const char* p = _d;
-        const char* const eof = p + C;
-        const char* k;
-        bool esc;
-        int j;
-
-        if (! destlen) {   // sanity check
-            return -1;
-        }
-
-        while (*p) {
-            k = key;
-            while ((*k) && (*p)) {
-                if (*p != *k) {
-                    break;
-                }
-                ++k;
-                if (++p == eof) {
-                    dest[0] = (char)0;
-                    return -1;
-                }
-            }
-
-            if ((! *k) && (*p == '=')) {
-                j = 0;
-                esc = false;
-                ++p;
-                while ((*p != 0) && (*p != 13) && (*p != 10)) {
-                    if (esc) {
-                        esc = false;
-                        switch (*p) {
-                            case 'r':
-                                dest[j++] = 13;
-                                break;
-                            case 'n':
-                                dest[j++] = 10;
-                                break;
-                            case '0':
-                                dest[j++] = (char)0;
-                                break;
-                            case 'e':
-                                dest[j++] = '=';
-                                break;
-                            default:
-                                dest[j++] = *p;
-                                break;
-                        }
-                        if (j == (int)destlen) {
-                            dest[j - 1] = (char)0;
-                            return j - 1;
-                        }
-                    }
-                    else if (*p == '\\') {
-                        esc = true;
-                    }
-                    else {
-                        dest[j++] = *p;
-                        if (j == (int)destlen) {
-                            dest[j - 1] = (char)0;
-                            return j - 1;
-                        }
-                    }
-                    if (++p == eof) {
-                        dest[0] = (char)0;
-                        return -1;
-                    }
-                }
-                dest[j] = (char)0;
-                return j;
-            }
-            else {
-                while ((*p) && (*p != 13) && (*p != 10)) {
-                    if (++p == eof) {
-                        dest[0] = (char)0;
-                        return -1;
-                    }
-                }
-                if (*p) {
-                    if (++p == eof) {
-                        dest[0] = (char)0;
-                        return -1;
-                    }
-                }
-                else {
-                    break;
-                }
-            }
-        }
-
-        dest[0] = (char)0;
-        return -1;
-    }
-
-    /**
-     * Get the contents of a key into a buffer
-     *
-     * @param key Key to get
-     * @param dest Destination buffer
-     * @return True if key was found (if false, dest will be empty)
-     * @tparam BC Buffer capacity (usually inferred)
-     */
-    template <unsigned int BC> inline bool get(const char* key, Buffer<BC>& dest) const
-    {
-        const int r = this->get(key, const_cast<char*>(reinterpret_cast<const char*>(dest.data())), BC);
-        if (r >= 0) {
-            dest.setSize((unsigned int)r);
-            return true;
-        }
-        else {
-            dest.clear();
-            return false;
-        }
-    }
-
-    /**
-     * Get a boolean value
-     *
-     * @param key Key to look up
-     * @param dfl Default value if not found in dictionary
-     * @return Boolean value of key or 'dfl' if not found
-     */
-    bool getB(const char* key, bool dfl = false) const
-    {
-        char tmp[4];
-        if (this->get(key, tmp, sizeof(tmp)) >= 0) {
-            return ((*tmp == '1') || (*tmp == 't') || (*tmp == 'T'));
-        }
-        return dfl;
-    }
-
-    /**
-     * Get an unsigned int64 stored as hex in the dictionary
-     *
-     * @param key Key to look up
-     * @param dfl Default value or 0 if unspecified
-     * @return Decoded hex UInt value or 'dfl' if not found
-     */
-    inline uint64_t getUI(const char* key, uint64_t dfl = 0) const
-    {
-        char tmp[128];
-        if (this->get(key, tmp, sizeof(tmp)) >= 1) {
-            return Utils::hexStrToU64(tmp);
-        }
-        return dfl;
-    }
-
-    /**
-     * Get an unsigned int64 stored as hex in the dictionary
-     *
-     * @param key Key to look up
-     * @param dfl Default value or 0 if unspecified
-     * @return Decoded hex UInt value or 'dfl' if not found
-     */
-    inline int64_t getI(const char* key, int64_t dfl = 0) const
-    {
-        char tmp[128];
-        if (this->get(key, tmp, sizeof(tmp)) >= 1) {
-            return Utils::hexStrTo64(tmp);
-        }
-        return dfl;
-    }
-
-    /**
-     * Add a new key=value pair
-     *
-     * If the key is already present this will append another, but the first
-     * will always be returned by get(). This is not checked. If you want to
-     * ensure a key is not present use erase() first.
-     *
-     * Use the vlen parameter to add binary values. Nulls will be escaped.
-     *
-     * @param key Key -- nulls, CR/LF, and equals (=) are illegal characters
-     * @param value Value to set
-     * @param vlen Length of value in bytes or -1 to treat value[] as a C-string and look for terminating 0
-     * @return True if there was enough room to add this key=value pair
-     */
-    inline bool add(const char* key, const char* value, int vlen = -1)
-    {
-        for (unsigned int i = 0; i < C; ++i) {
-            if (! _d[i]) {
-                unsigned int j = i;
-
-                if (j > 0) {
-                    _d[j++] = (char)10;
-                    if (j == C) {
-                        _d[i] = (char)0;
-                        return false;
-                    }
-                }
-
-                const char* p = key;
-                while (*p) {
-                    _d[j++] = *(p++);
-                    if (j == C) {
-                        _d[i] = (char)0;
-                        return false;
-                    }
-                }
-
-                _d[j++] = '=';
-                if (j == C) {
-                    _d[i] = (char)0;
-                    return false;
-                }
-
-                p = value;
-                int k = 0;
-                while (((vlen < 0) && (*p)) || (k < vlen)) {
-                    switch (*p) {
-                        case 0:
-                        case 13:
-                        case 10:
-                        case '\\':
-                        case '=':
-                            _d[j++] = '\\';
-                            if (j == C) {
-                                _d[i] = (char)0;
-                                return false;
-                            }
-                            switch (*p) {
-                                case 0:
-                                    _d[j++] = '0';
-                                    break;
-                                case 13:
-                                    _d[j++] = 'r';
-                                    break;
-                                case 10:
-                                    _d[j++] = 'n';
-                                    break;
-                                case '\\':
-                                    _d[j++] = '\\';
-                                    break;
-                                case '=':
-                                    _d[j++] = 'e';
-                                    break;
-                            }
-                            if (j == C) {
-                                _d[i] = (char)0;
-                                return false;
-                            }
-                            break;
-                        default:
-                            _d[j++] = *p;
-                            if (j == C) {
-                                _d[i] = (char)0;
-                                return false;
-                            }
-                            break;
-                    }
-                    ++p;
-                    ++k;
-                }
-
-                _d[j] = (char)0;
-
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Add a boolean as a '1' or a '0'
-     */
-    inline bool add(const char* key, bool value)
-    {
-        return this->add(key, (value) ? "1" : "0", 1);
-    }
-
-    /**
-     * Add a 64-bit integer (unsigned) as a hex value
-     */
-    inline bool add(const char* key, uint64_t value)
-    {
-        char tmp[32];
-        return this->add(key, Utils::hex(value, tmp), -1);
-    }
-
-    /**
-     * Add a 64-bit integer (unsigned) as a hex value
-     */
-    inline bool add(const char* key, int64_t value)
-    {
-        char tmp[32];
-        if (value >= 0) {
-            return this->add(key, Utils::hex((uint64_t)value, tmp), -1);
-        }
-        else {
-            tmp[0] = '-';
-            return this->add(key, Utils::hex((uint64_t)(value * -1), tmp + 1), -1);
-        }
-    }
-
-    /**
-     * Add a 64-bit integer (unsigned) as a hex value
-     */
-    inline bool add(const char* key, const Address& a)
-    {
-        char tmp[32];
-        return this->add(key, Utils::hex(a.toInt(), tmp), -1);
-    }
-
-    /**
-     * Add a binary buffer's contents as a value
-     *
-     * @tparam BC Buffer capacity (usually inferred)
-     */
-    template <unsigned int BC> inline bool add(const char* key, const Buffer<BC>& value)
-    {
-        return this->add(key, (const char*)value.data(), (int)value.size());
-    }
-
-    /**
-     * @param key Key to check
-     * @return True if key is present
-     */
-    inline bool contains(const char* key) const
-    {
-        char tmp[2];
-        return (this->get(key, tmp, 2) >= 0);
-    }
-
-    /**
-     * @return Value of C template parameter
-     */
-    inline unsigned int capacity() const
-    {
-        return C;
-    }
-
-    inline const char* data() const
-    {
-        return _d;
-    }
-    inline char* unsafeData()
-    {
-        return _d;
-    }
+	Dictionary()
+	{
+		memset(_d, 0, sizeof(_d));
+	}
+	Dictionary(const char* s)
+	{
+		this->load(s);
+	}
+	Dictionary(const char* s, unsigned int len)
+	{
+		for (unsigned int i = 0; i < C; ++i) {
+			if ((s) && (i < len)) {
+				if (! (_d[i] = *s)) {
+					s = (const char*)0;
+				}
+				else {
+					++s;
+				}
+			}
+			else {
+				_d[i] = (char)0;
+			}
+		}
+		_d[C - 1] = (char)0;
+	}
+	Dictionary(const Dictionary& d)
+	{
+		memcpy(_d, d._d, C);
+	}
+
+	inline Dictionary& operator=(const Dictionary& d)
+	{
+		memcpy(_d, d._d, C);
+		return *this;
+	}
+
+	inline operator bool() const
+	{
+		return (_d[0] != 0);
+	}
+
+	/**
+	 * Load a dictionary from a C-string
+	 *
+	 * @param s Dictionary in string form
+	 * @return False if 's' was longer than our capacity
+	 */
+	inline bool load(const char* s)
+	{
+		for (unsigned int i = 0; i < C; ++i) {
+			if (s) {
+				if (! (_d[i] = *s)) {
+					s = (const char*)0;
+				}
+				else {
+					++s;
+				}
+			}
+			else {
+				_d[i] = (char)0;
+			}
+		}
+		_d[C - 1] = (char)0;
+		return (! s);
+	}
+
+	/**
+	 * Delete all entries
+	 */
+	inline void clear()
+	{
+		memset(_d, 0, sizeof(_d));
+	}
+
+	/**
+	 * @return Size of dictionary in bytes not including terminating NULL
+	 */
+	inline unsigned int sizeBytes() const
+	{
+		for (unsigned int i = 0; i < C; ++i) {
+			if (! _d[i]) {
+				return i;
+			}
+		}
+		return C - 1;
+	}
+
+	/**
+	 * Get an entry
+	 *
+	 * Note that to get binary values, dest[] should be at least one more than
+	 * the maximum size of the value being retrieved. That's because even if
+	 * the data is binary a terminating 0 is still appended to dest[] after it.
+	 *
+	 * If the key is not found, dest[0] is set to 0 to make dest[] an empty
+	 * C string in that case. The dest[] array will *never* be unterminated
+	 * after this call.
+	 *
+	 * Security note: if 'key' is ever directly based on anything that is not
+	 * a hard-code or internally-generated name, it must be checked to ensure
+	 * that the buffer is NULL-terminated since key[] does not take a secondary
+	 * size parameter. In NetworkConfig all keys are hard-coded strings so this
+	 * isn't a problem in the core.
+	 *
+	 * @param key Key to look up
+	 * @param dest Destination buffer
+	 * @param destlen Size of destination buffer
+	 * @return -1 if not found, or actual number of bytes stored in dest[] minus trailing 0
+	 */
+	inline int get(const char* key, char* dest, unsigned int destlen) const
+	{
+		const char* p = _d;
+		const char* const eof = p + C;
+		const char* k;
+		bool esc;
+		int j;
+
+		if (! destlen) {   // sanity check
+			return -1;
+		}
+
+		while (*p) {
+			k = key;
+			while ((*k) && (*p)) {
+				if (*p != *k) {
+					break;
+				}
+				++k;
+				if (++p == eof) {
+					dest[0] = (char)0;
+					return -1;
+				}
+			}
+
+			if ((! *k) && (*p == '=')) {
+				j = 0;
+				esc = false;
+				++p;
+				while ((*p != 0) && (*p != 13) && (*p != 10)) {
+					if (esc) {
+						esc = false;
+						switch (*p) {
+							case 'r':
+								dest[j++] = 13;
+								break;
+							case 'n':
+								dest[j++] = 10;
+								break;
+							case '0':
+								dest[j++] = (char)0;
+								break;
+							case 'e':
+								dest[j++] = '=';
+								break;
+							default:
+								dest[j++] = *p;
+								break;
+						}
+						if (j == (int)destlen) {
+							dest[j - 1] = (char)0;
+							return j - 1;
+						}
+					}
+					else if (*p == '\\') {
+						esc = true;
+					}
+					else {
+						dest[j++] = *p;
+						if (j == (int)destlen) {
+							dest[j - 1] = (char)0;
+							return j - 1;
+						}
+					}
+					if (++p == eof) {
+						dest[0] = (char)0;
+						return -1;
+					}
+				}
+				dest[j] = (char)0;
+				return j;
+			}
+			else {
+				while ((*p) && (*p != 13) && (*p != 10)) {
+					if (++p == eof) {
+						dest[0] = (char)0;
+						return -1;
+					}
+				}
+				if (*p) {
+					if (++p == eof) {
+						dest[0] = (char)0;
+						return -1;
+					}
+				}
+				else {
+					break;
+				}
+			}
+		}
+
+		dest[0] = (char)0;
+		return -1;
+	}
+
+	/**
+	 * Get the contents of a key into a buffer
+	 *
+	 * @param key Key to get
+	 * @param dest Destination buffer
+	 * @return True if key was found (if false, dest will be empty)
+	 * @tparam BC Buffer capacity (usually inferred)
+	 */
+	template <unsigned int BC> inline bool get(const char* key, Buffer<BC>& dest) const
+	{
+		const int r = this->get(key, const_cast<char*>(reinterpret_cast<const char*>(dest.data())), BC);
+		if (r >= 0) {
+			dest.setSize((unsigned int)r);
+			return true;
+		}
+		else {
+			dest.clear();
+			return false;
+		}
+	}
+
+	/**
+	 * Get a boolean value
+	 *
+	 * @param key Key to look up
+	 * @param dfl Default value if not found in dictionary
+	 * @return Boolean value of key or 'dfl' if not found
+	 */
+	bool getB(const char* key, bool dfl = false) const
+	{
+		char tmp[4];
+		if (this->get(key, tmp, sizeof(tmp)) >= 0) {
+			return ((*tmp == '1') || (*tmp == 't') || (*tmp == 'T'));
+		}
+		return dfl;
+	}
+
+	/**
+	 * Get an unsigned int64 stored as hex in the dictionary
+	 *
+	 * @param key Key to look up
+	 * @param dfl Default value or 0 if unspecified
+	 * @return Decoded hex UInt value or 'dfl' if not found
+	 */
+	inline uint64_t getUI(const char* key, uint64_t dfl = 0) const
+	{
+		char tmp[128];
+		if (this->get(key, tmp, sizeof(tmp)) >= 1) {
+			return Utils::hexStrToU64(tmp);
+		}
+		return dfl;
+	}
+
+	/**
+	 * Get an unsigned int64 stored as hex in the dictionary
+	 *
+	 * @param key Key to look up
+	 * @param dfl Default value or 0 if unspecified
+	 * @return Decoded hex UInt value or 'dfl' if not found
+	 */
+	inline int64_t getI(const char* key, int64_t dfl = 0) const
+	{
+		char tmp[128];
+		if (this->get(key, tmp, sizeof(tmp)) >= 1) {
+			return Utils::hexStrTo64(tmp);
+		}
+		return dfl;
+	}
+
+	/**
+	 * Add a new key=value pair
+	 *
+	 * If the key is already present this will append another, but the first
+	 * will always be returned by get(). This is not checked. If you want to
+	 * ensure a key is not present use erase() first.
+	 *
+	 * Use the vlen parameter to add binary values. Nulls will be escaped.
+	 *
+	 * @param key Key -- nulls, CR/LF, and equals (=) are illegal characters
+	 * @param value Value to set
+	 * @param vlen Length of value in bytes or -1 to treat value[] as a C-string and look for terminating 0
+	 * @return True if there was enough room to add this key=value pair
+	 */
+	inline bool add(const char* key, const char* value, int vlen = -1)
+	{
+		for (unsigned int i = 0; i < C; ++i) {
+			if (! _d[i]) {
+				unsigned int j = i;
+
+				if (j > 0) {
+					_d[j++] = (char)10;
+					if (j == C) {
+						_d[i] = (char)0;
+						return false;
+					}
+				}
+
+				const char* p = key;
+				while (*p) {
+					_d[j++] = *(p++);
+					if (j == C) {
+						_d[i] = (char)0;
+						return false;
+					}
+				}
+
+				_d[j++] = '=';
+				if (j == C) {
+					_d[i] = (char)0;
+					return false;
+				}
+
+				p = value;
+				int k = 0;
+				while (((vlen < 0) && (*p)) || (k < vlen)) {
+					switch (*p) {
+						case 0:
+						case 13:
+						case 10:
+						case '\\':
+						case '=':
+							_d[j++] = '\\';
+							if (j == C) {
+								_d[i] = (char)0;
+								return false;
+							}
+							switch (*p) {
+								case 0:
+									_d[j++] = '0';
+									break;
+								case 13:
+									_d[j++] = 'r';
+									break;
+								case 10:
+									_d[j++] = 'n';
+									break;
+								case '\\':
+									_d[j++] = '\\';
+									break;
+								case '=':
+									_d[j++] = 'e';
+									break;
+							}
+							if (j == C) {
+								_d[i] = (char)0;
+								return false;
+							}
+							break;
+						default:
+							_d[j++] = *p;
+							if (j == C) {
+								_d[i] = (char)0;
+								return false;
+							}
+							break;
+					}
+					++p;
+					++k;
+				}
+
+				_d[j] = (char)0;
+
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Add a boolean as a '1' or a '0'
+	 */
+	inline bool add(const char* key, bool value)
+	{
+		return this->add(key, (value) ? "1" : "0", 1);
+	}
+
+	/**
+	 * Add a 64-bit integer (unsigned) as a hex value
+	 */
+	inline bool add(const char* key, uint64_t value)
+	{
+		char tmp[32];
+		return this->add(key, Utils::hex(value, tmp), -1);
+	}
+
+	/**
+	 * Add a 64-bit integer (unsigned) as a hex value
+	 */
+	inline bool add(const char* key, int64_t value)
+	{
+		char tmp[32];
+		if (value >= 0) {
+			return this->add(key, Utils::hex((uint64_t)value, tmp), -1);
+		}
+		else {
+			tmp[0] = '-';
+			return this->add(key, Utils::hex((uint64_t)(value * -1), tmp + 1), -1);
+		}
+	}
+
+	/**
+	 * Add a 64-bit integer (unsigned) as a hex value
+	 */
+	inline bool add(const char* key, const Address& a)
+	{
+		char tmp[32];
+		return this->add(key, Utils::hex(a.toInt(), tmp), -1);
+	}
+
+	/**
+	 * Add a binary buffer's contents as a value
+	 *
+	 * @tparam BC Buffer capacity (usually inferred)
+	 */
+	template <unsigned int BC> inline bool add(const char* key, const Buffer<BC>& value)
+	{
+		return this->add(key, (const char*)value.data(), (int)value.size());
+	}
+
+	/**
+	 * @param key Key to check
+	 * @return True if key is present
+	 */
+	inline bool contains(const char* key) const
+	{
+		char tmp[2];
+		return (this->get(key, tmp, 2) >= 0);
+	}
+
+	/**
+	 * @return Value of C template parameter
+	 */
+	inline unsigned int capacity() const
+	{
+		return C;
+	}
+
+	inline const char* data() const
+	{
+		return _d;
+	}
+	inline char* unsafeData()
+	{
+		return _d;
+	}
 
   private:
-    char _d[C];
+	char _d[C];
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

File diff suppressed because it is too large
+ 536 - 536
node/ECC.cpp


+ 169 - 169
node/ECC.hpp

@@ -35,192 +35,192 @@
 #include <openssl/evp.h>
 #include <openssl/pem.h>
 
-#define ZT_ECC_EPHEMERAL_PUBLIC_KEY_LEN 97       /* Single ECC P-384 key */
-#define ZT_ECC_PUBLIC_KEY_SET_LEN       (97 * 2) /* Two ECC P-384 keys */
-#define ZT_ECC_PRIVATE_KEY_SET_LEN      (48 * 2) /* Two ECC P-384 secret keys */
-#define ZT_ECC_SIGNATURE_LEN            96       /* NIST P-384 ECDSA signature */
+#define ZT_ECC_EPHEMERAL_PUBLIC_KEY_LEN 97		 /* Single ECC P-384 key */
+#define ZT_ECC_PUBLIC_KEY_SET_LEN		(97 * 2) /* Two ECC P-384 keys */
+#define ZT_ECC_PRIVATE_KEY_SET_LEN		(48 * 2) /* Two ECC P-384 secret keys */
+#define ZT_ECC_SIGNATURE_LEN			96		 /* NIST P-384 ECDSA signature */
 
 class ECC {
   public:
-    struct Public {
-        uint8_t data[ZT_ECC_PUBLIC_KEY_SET_LEN];
-    };
-    struct Private {
-        uint8_t data[ZT_ECC_PRIVATE_KEY_SET_LEN];
-    };
-    struct Signature {
-        uint8_t data[ZT_ECC_SIGNATURE_LEN];
-    };
-    struct Pair {
-        Public pub;
-        Private priv;
-    };
+	struct Public {
+		uint8_t data[ZT_ECC_PUBLIC_KEY_SET_LEN];
+	};
+	struct Private {
+		uint8_t data[ZT_ECC_PRIVATE_KEY_SET_LEN];
+	};
+	struct Signature {
+		uint8_t data[ZT_ECC_SIGNATURE_LEN];
+	};
+	struct Pair {
+		Public pub;
+		Private priv;
+	};
 };
 
-#else   // Curve25519 / Ed25519
+#else	// Curve25519 / Ed25519
 
 namespace ZeroTier {
 
 #define ZT_ECC_EPHEMERAL_PUBLIC_KEY_LEN 32 /* Single C25519 ECDH key */
-#define ZT_ECC_PUBLIC_KEY_SET_LEN       64 /* C25519 and Ed25519 keys */
-#define ZT_ECC_PRIVATE_KEY_SET_LEN      64 /* C25519 and Ed25519 secret keys */
-#define ZT_ECC_SIGNATURE_LEN            96 /* Ed25519 signature plus (not necessary) hash */
+#define ZT_ECC_PUBLIC_KEY_SET_LEN		64 /* C25519 and Ed25519 keys */
+#define ZT_ECC_PRIVATE_KEY_SET_LEN		64 /* C25519 and Ed25519 secret keys */
+#define ZT_ECC_SIGNATURE_LEN			96 /* Ed25519 signature plus (not necessary) hash */
 
 class ECC {
   public:
-    struct Public {
-        uint8_t data[ZT_ECC_PUBLIC_KEY_SET_LEN];
-    };
-    struct Private {
-        uint8_t data[ZT_ECC_PRIVATE_KEY_SET_LEN];
-    };
-    struct Signature {
-        uint8_t data[ZT_ECC_SIGNATURE_LEN];
-    };
-    struct Pair {
-        Public pub;
-        Private priv;
-    };
-
-    /**
-     * Generate an elliptic curve key pair
-     */
-    static inline Pair generate()
-    {
-        Pair kp;
-        Utils::getSecureRandom(kp.priv.data, ZT_ECC_PRIVATE_KEY_SET_LEN);
-        _calcPubDH(kp);
-        _calcPubED(kp);
-        return kp;
-    }
-
-    /**
-     * Generate a key pair satisfying a condition
-     *
-     * This begins with a random keypair from a random secret key and then
-     * iteratively increments the random secret until cond(kp) returns true.
-     * This is used to compute key pairs in which the public key, its hash
-     * or some other aspect of it satisfies some condition, such as for a
-     * hashcash criteria.
-     *
-     * @param cond Condition function or function object
-     * @return Key pair where cond(kp) returns true
-     * @tparam F Type of 'cond'
-     */
-    template <typename F> static inline Pair generateSatisfying(F cond)
-    {
-        Pair kp;
-        void* const priv = (void*)kp.priv.data;
-        Utils::getSecureRandom(priv, ZT_ECC_PRIVATE_KEY_SET_LEN);
-        _calcPubED(kp);   // do Ed25519 key -- bytes 32-63 of pub and priv
-        do {
-            ++(((uint64_t*)priv)[1]);
-            --(((uint64_t*)priv)[2]);
-            _calcPubDH(kp);   // keep regenerating bytes 0-31 until satisfied
-        } while (! cond(kp));
-        return kp;
-    }
-
-    /**
-     * Perform C25519 ECC key agreement
-     *
-     * Actual key bytes are generated from one or more SHA-512 digests of
-     * the raw result of key agreement.
-     *
-     * @param mine My private key
-     * @param their Their public key
-     * @param keybuf Buffer to fill
-     * @param keylen Number of key bytes to generate
-     */
-    static void agree(const Private& mine, const Public& their, void* keybuf, unsigned int keylen);
-    static inline void agree(const Pair& mine, const Public& their, void* keybuf, unsigned int keylen)
-    {
-        agree(mine.priv, their, keybuf, keylen);
-    }
-
-    /**
-     * Sign a message with a sender's key pair
-     *
-     * This takes the SHA-521 of msg[] and then signs the first 32 bytes of this
-     * digest, returning it and the 64-byte ed25519 signature in signature[].
-     * This results in a signature that verifies both the signer's authenticity
-     * and the integrity of the message.
-     *
-     * This is based on the original ed25519 code from NaCl and the SUPERCOP
-     * cipher benchmark suite, but with the modification that it always
-     * produces a signature of fixed 96-byte length based on the hash of an
-     * arbitrary-length message.
-     *
-     * @param myPrivate My private key
-     * @param myPublic My public key
-     * @param msg Message to sign
-     * @param len Length of message in bytes
-     * @param signature Buffer to fill with signature -- MUST be 96 bytes in length
-     */
-    static void sign(const Private& myPrivate, const Public& myPublic, const void* msg, unsigned int len, void* signature);
-    static inline void sign(const Pair& mine, const void* msg, unsigned int len, void* signature)
-    {
-        sign(mine.priv, mine.pub, msg, len, signature);
-    }
-
-    /**
-     * Sign a message with a sender's key pair
-     *
-     * @param myPrivate My private key
-     * @param myPublic My public key
-     * @param msg Message to sign
-     * @param len Length of message in bytes
-     * @return Signature
-     */
-    static inline Signature sign(const Private& myPrivate, const Public& myPublic, const void* msg, unsigned int len)
-    {
-        Signature sig;
-        sign(myPrivate, myPublic, msg, len, sig.data);
-        return sig;
-    }
-    static inline Signature sign(const Pair& mine, const void* msg, unsigned int len)
-    {
-        Signature sig;
-        sign(mine.priv, mine.pub, msg, len, sig.data);
-        return sig;
-    }
-
-    /**
-     * Verify a message's signature
-     *
-     * @param their Public key to verify against
-     * @param msg Message to verify signature integrity against
-     * @param len Length of message in bytes
-     * @param signature 96-byte signature
-     * @return True if signature is valid and the message is authentic and unmodified
-     */
-    static bool verify(const Public& their, const void* msg, unsigned int len, const void* signature);
-
-    /**
-     * Verify a message's signature
-     *
-     * @param their Public key to verify against
-     * @param msg Message to verify signature integrity against
-     * @param len Length of message in bytes
-     * @param signature 96-byte signature
-     * @return True if signature is valid and the message is authentic and unmodified
-     */
-    static inline bool verify(const Public& their, const void* msg, unsigned int len, const Signature& signature)
-    {
-        return verify(their, msg, len, signature.data);
-    }
+	struct Public {
+		uint8_t data[ZT_ECC_PUBLIC_KEY_SET_LEN];
+	};
+	struct Private {
+		uint8_t data[ZT_ECC_PRIVATE_KEY_SET_LEN];
+	};
+	struct Signature {
+		uint8_t data[ZT_ECC_SIGNATURE_LEN];
+	};
+	struct Pair {
+		Public pub;
+		Private priv;
+	};
+
+	/**
+	 * Generate an elliptic curve key pair
+	 */
+	static inline Pair generate()
+	{
+		Pair kp;
+		Utils::getSecureRandom(kp.priv.data, ZT_ECC_PRIVATE_KEY_SET_LEN);
+		_calcPubDH(kp);
+		_calcPubED(kp);
+		return kp;
+	}
+
+	/**
+	 * Generate a key pair satisfying a condition
+	 *
+	 * This begins with a random keypair from a random secret key and then
+	 * iteratively increments the random secret until cond(kp) returns true.
+	 * This is used to compute key pairs in which the public key, its hash
+	 * or some other aspect of it satisfies some condition, such as for a
+	 * hashcash criteria.
+	 *
+	 * @param cond Condition function or function object
+	 * @return Key pair where cond(kp) returns true
+	 * @tparam F Type of 'cond'
+	 */
+	template <typename F> static inline Pair generateSatisfying(F cond)
+	{
+		Pair kp;
+		void* const priv = (void*)kp.priv.data;
+		Utils::getSecureRandom(priv, ZT_ECC_PRIVATE_KEY_SET_LEN);
+		_calcPubED(kp);	  // do Ed25519 key -- bytes 32-63 of pub and priv
+		do {
+			++(((uint64_t*)priv)[1]);
+			--(((uint64_t*)priv)[2]);
+			_calcPubDH(kp);	  // keep regenerating bytes 0-31 until satisfied
+		} while (! cond(kp));
+		return kp;
+	}
+
+	/**
+	 * Perform C25519 ECC key agreement
+	 *
+	 * Actual key bytes are generated from one or more SHA-512 digests of
+	 * the raw result of key agreement.
+	 *
+	 * @param mine My private key
+	 * @param their Their public key
+	 * @param keybuf Buffer to fill
+	 * @param keylen Number of key bytes to generate
+	 */
+	static void agree(const Private& mine, const Public& their, void* keybuf, unsigned int keylen);
+	static inline void agree(const Pair& mine, const Public& their, void* keybuf, unsigned int keylen)
+	{
+		agree(mine.priv, their, keybuf, keylen);
+	}
+
+	/**
+	 * Sign a message with a sender's key pair
+	 *
+	 * This takes the SHA-521 of msg[] and then signs the first 32 bytes of this
+	 * digest, returning it and the 64-byte ed25519 signature in signature[].
+	 * This results in a signature that verifies both the signer's authenticity
+	 * and the integrity of the message.
+	 *
+	 * This is based on the original ed25519 code from NaCl and the SUPERCOP
+	 * cipher benchmark suite, but with the modification that it always
+	 * produces a signature of fixed 96-byte length based on the hash of an
+	 * arbitrary-length message.
+	 *
+	 * @param myPrivate My private key
+	 * @param myPublic My public key
+	 * @param msg Message to sign
+	 * @param len Length of message in bytes
+	 * @param signature Buffer to fill with signature -- MUST be 96 bytes in length
+	 */
+	static void sign(const Private& myPrivate, const Public& myPublic, const void* msg, unsigned int len, void* signature);
+	static inline void sign(const Pair& mine, const void* msg, unsigned int len, void* signature)
+	{
+		sign(mine.priv, mine.pub, msg, len, signature);
+	}
+
+	/**
+	 * Sign a message with a sender's key pair
+	 *
+	 * @param myPrivate My private key
+	 * @param myPublic My public key
+	 * @param msg Message to sign
+	 * @param len Length of message in bytes
+	 * @return Signature
+	 */
+	static inline Signature sign(const Private& myPrivate, const Public& myPublic, const void* msg, unsigned int len)
+	{
+		Signature sig;
+		sign(myPrivate, myPublic, msg, len, sig.data);
+		return sig;
+	}
+	static inline Signature sign(const Pair& mine, const void* msg, unsigned int len)
+	{
+		Signature sig;
+		sign(mine.priv, mine.pub, msg, len, sig.data);
+		return sig;
+	}
+
+	/**
+	 * Verify a message's signature
+	 *
+	 * @param their Public key to verify against
+	 * @param msg Message to verify signature integrity against
+	 * @param len Length of message in bytes
+	 * @param signature 96-byte signature
+	 * @return True if signature is valid and the message is authentic and unmodified
+	 */
+	static bool verify(const Public& their, const void* msg, unsigned int len, const void* signature);
+
+	/**
+	 * Verify a message's signature
+	 *
+	 * @param their Public key to verify against
+	 * @param msg Message to verify signature integrity against
+	 * @param len Length of message in bytes
+	 * @param signature 96-byte signature
+	 * @return True if signature is valid and the message is authentic and unmodified
+	 */
+	static inline bool verify(const Public& their, const void* msg, unsigned int len, const Signature& signature)
+	{
+		return verify(their, msg, len, signature.data);
+	}
 
   private:
-    // derive first 32 bytes of kp.pub from first 32 bytes of kp.priv
-    // this is the ECDH key
-    static void _calcPubDH(Pair& kp);
+	// derive first 32 bytes of kp.pub from first 32 bytes of kp.priv
+	// this is the ECDH key
+	static void _calcPubDH(Pair& kp);
 
-    // derive 2nd 32 bytes of kp.pub from 2nd 32 bytes of kp.priv
-    // this is the Ed25519 sign/verify key
-    static void _calcPubED(Pair& kp);
+	// derive 2nd 32 bytes of kp.pub from 2nd 32 bytes of kp.priv
+	// this is the Ed25519 sign/verify key
+	static void _calcPubED(Pair& kp);
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif
 

+ 399 - 399
node/Hashtable.hpp

@@ -31,410 +31,410 @@ namespace ZeroTier {
  */
 template <typename K, typename V> class Hashtable {
   private:
-    struct _Bucket {
-        _Bucket(const K& k, const V& v) : k(k), v(v)
-        {
-        }
-        _Bucket(const K& k) : k(k), v()
-        {
-        }
-        _Bucket(const _Bucket& b) : k(b.k), v(b.v)
-        {
-        }
-        inline _Bucket& operator=(const _Bucket& b)
-        {
-            k = b.k;
-            v = b.v;
-            return *this;
-        }
-        K k;
-        V v;
-        _Bucket* next;   // must be set manually for each _Bucket
-    };
+	struct _Bucket {
+		_Bucket(const K& k, const V& v) : k(k), v(v)
+		{
+		}
+		_Bucket(const K& k) : k(k), v()
+		{
+		}
+		_Bucket(const _Bucket& b) : k(b.k), v(b.v)
+		{
+		}
+		inline _Bucket& operator=(const _Bucket& b)
+		{
+			k = b.k;
+			v = b.v;
+			return *this;
+		}
+		K k;
+		V v;
+		_Bucket* next;	 // must be set manually for each _Bucket
+	};
 
   public:
-    /**
-     * A simple forward iterator (different from STL)
-     *
-     * It's safe to erase the last key, but not others. Don't use set() since that
-     * may rehash and invalidate the iterator. Note the erasing the key will destroy
-     * the targets of the pointers returned by next().
-     */
-    class Iterator {
-      public:
-        /**
-         * @param ht Hash table to iterate over
-         */
-        Iterator(Hashtable& ht) : _idx(0), _ht(&ht), _b(ht._t[0])
-        {
-        }
-
-        /**
-         * @param kptr Pointer to set to point to next key
-         * @param vptr Pointer to set to point to next value
-         * @return True if kptr and vptr are set, false if no more entries
-         */
-        inline bool next(K*& kptr, V*& vptr)
-        {
-            for (;;) {
-                if (_b) {
-                    kptr = &(_b->k);
-                    vptr = &(_b->v);
-                    _b = _b->next;
-                    return true;
-                }
-                ++_idx;
-                if (_idx >= _ht->_bc) {
-                    return false;
-                }
-                _b = _ht->_t[_idx];
-            }
-        }
-
-      private:
-        unsigned long _idx;
-        Hashtable* _ht;
-        _Bucket* _b;
-    };
-    // friend class Hashtable<K,V>::Iterator;
-
-    /**
-     * @param bc Initial capacity in buckets (default: 64, must be nonzero)
-     */
-    Hashtable(unsigned long bc = 64) : _t(reinterpret_cast<_Bucket**>(::malloc(sizeof(_Bucket*) * bc))), _bc(bc), _s(0)
-    {
-        if (! _t) {
-            throw ZT_EXCEPTION_OUT_OF_MEMORY;
-        }
-        for (unsigned long i = 0; i < bc; ++i) {
-            _t[i] = (_Bucket*)0;
-        }
-    }
-
-    Hashtable(const Hashtable<K, V>& ht) : _t(reinterpret_cast<_Bucket**>(::malloc(sizeof(_Bucket*) * ht._bc))), _bc(ht._bc), _s(ht._s)
-    {
-        if (! _t) {
-            throw ZT_EXCEPTION_OUT_OF_MEMORY;
-        }
-        for (unsigned long i = 0; i < _bc; ++i) {
-            _t[i] = (_Bucket*)0;
-        }
-        for (unsigned long i = 0; i < _bc; ++i) {
-            const _Bucket* b = ht._t[i];
-            while (b) {
-                _Bucket* nb = new _Bucket(*b);
-                nb->next = _t[i];
-                _t[i] = nb;
-                b = b->next;
-            }
-        }
-    }
-
-    ~Hashtable()
-    {
-        this->clear();
-        ::free(_t);
-    }
-
-    inline Hashtable& operator=(const Hashtable<K, V>& ht)
-    {
-        this->clear();
-        if (ht._s) {
-            for (unsigned long i = 0; i < ht._bc; ++i) {
-                const _Bucket* b = ht._t[i];
-                while (b) {
-                    this->set(b->k, b->v);
-                    b = b->next;
-                }
-            }
-        }
-        return *this;
-    }
-
-    /**
-     * Erase all entries
-     */
-    inline void clear()
-    {
-        if (_s) {
-            for (unsigned long i = 0; i < _bc; ++i) {
-                _Bucket* b = _t[i];
-                while (b) {
-                    _Bucket* const nb = b->next;
-                    delete b;
-                    b = nb;
-                }
-                _t[i] = (_Bucket*)0;
-            }
-            _s = 0;
-        }
-    }
-
-    /**
-     * @return Vector of all keys
-     */
-    inline typename std::vector<K> keys() const
-    {
-        typename std::vector<K> k;
-        if (_s) {
-            k.reserve(_s);
-            for (unsigned long i = 0; i < _bc; ++i) {
-                _Bucket* b = _t[i];
-                while (b) {
-                    k.push_back(b->k);
-                    b = b->next;
-                }
-            }
-        }
-        return k;
-    }
-
-    /**
-     * Append all keys (in unspecified order) to the supplied vector or list
-     *
-     * @param v Vector, list, or other compliant container
-     * @tparam Type of V (generally inferred)
-     */
-    template <typename C> inline void appendKeys(C& v) const
-    {
-        if (_s) {
-            for (unsigned long i = 0; i < _bc; ++i) {
-                _Bucket* b = _t[i];
-                while (b) {
-                    v.push_back(b->k);
-                    b = b->next;
-                }
-            }
-        }
-    }
-
-    /**
-     * @return Vector of all entries (pairs of K,V)
-     */
-    inline typename std::vector<std::pair<K, V> > entries() const
-    {
-        typename std::vector<std::pair<K, V> > k;
-        if (_s) {
-            k.reserve(_s);
-            for (unsigned long i = 0; i < _bc; ++i) {
-                _Bucket* b = _t[i];
-                while (b) {
-                    k.push_back(std::pair<K, V>(b->k, b->v));
-                    b = b->next;
-                }
-            }
-        }
-        return k;
-    }
-
-    /**
-     * @param k Key
-     * @return Pointer to value or NULL if not found
-     */
-    inline V* get(const K& k)
-    {
-        _Bucket* b = _t[_hc(k) % _bc];
-        while (b) {
-            if (b->k == k) {
-                return &(b->v);
-            }
-            b = b->next;
-        }
-        return (V*)0;
-    }
-    inline const V* get(const K& k) const
-    {
-        return const_cast<Hashtable*>(this)->get(k);
-    }
-
-    /**
-     * @param k Key
-     * @param v Value to fill with result
-     * @return True if value was found and set (if false, v is not modified)
-     */
-    inline bool get(const K& k, V& v) const
-    {
-        _Bucket* b = _t[_hc(k) % _bc];
-        while (b) {
-            if (b->k == k) {
-                v = b->v;
-                return true;
-            }
-            b = b->next;
-        }
-        return false;
-    }
-
-    /**
-     * @param k Key to check
-     * @return True if key is present
-     */
-    inline bool contains(const K& k) const
-    {
-        _Bucket* b = _t[_hc(k) % _bc];
-        while (b) {
-            if (b->k == k) {
-                return true;
-            }
-            b = b->next;
-        }
-        return false;
-    }
-
-    /**
-     * @param k Key
-     * @return True if value was present
-     */
-    inline bool erase(const K& k)
-    {
-        const unsigned long bidx = _hc(k) % _bc;
-        _Bucket* lastb = (_Bucket*)0;
-        _Bucket* b = _t[bidx];
-        while (b) {
-            if (b->k == k) {
-                if (lastb) {
-                    lastb->next = b->next;
-                }
-                else {
-                    _t[bidx] = b->next;
-                }
-                delete b;
-                --_s;
-                return true;
-            }
-            lastb = b;
-            b = b->next;
-        }
-        return false;
-    }
-
-    /**
-     * @param k Key
-     * @param v Value
-     * @return Reference to value in table
-     */
-    inline V& set(const K& k, const V& v)
-    {
-        const unsigned long h = _hc(k);
-        unsigned long bidx = h % _bc;
-
-        _Bucket* b = _t[bidx];
-        while (b) {
-            if (b->k == k) {
-                b->v = v;
-                return b->v;
-            }
-            b = b->next;
-        }
-
-        if (_s >= _bc) {
-            _grow();
-            bidx = h % _bc;
-        }
-
-        b = new _Bucket(k, v);
-        b->next = _t[bidx];
-        _t[bidx] = b;
-        ++_s;
-        return b->v;
-    }
-
-    /**
-     * @param k Key
-     * @return Value, possibly newly created
-     */
-    inline V& operator[](const K& k)
-    {
-        const unsigned long h = _hc(k);
-        unsigned long bidx = h % _bc;
-
-        _Bucket* b = _t[bidx];
-        while (b) {
-            if (b->k == k) {
-                return b->v;
-            }
-            b = b->next;
-        }
-
-        if (_s >= _bc) {
-            _grow();
-            bidx = h % _bc;
-        }
-
-        b = new _Bucket(k);
-        b->next = _t[bidx];
-        _t[bidx] = b;
-        ++_s;
-        return b->v;
-    }
-
-    /**
-     * @return Number of entries
-     */
-    inline unsigned long size() const
-    {
-        return _s;
-    }
-
-    /**
-     * @return True if table is empty
-     */
-    inline bool empty() const
-    {
-        return (_s == 0);
-    }
+	/**
+	 * A simple forward iterator (different from STL)
+	 *
+	 * It's safe to erase the last key, but not others. Don't use set() since that
+	 * may rehash and invalidate the iterator. Note the erasing the key will destroy
+	 * the targets of the pointers returned by next().
+	 */
+	class Iterator {
+	  public:
+		/**
+		 * @param ht Hash table to iterate over
+		 */
+		Iterator(Hashtable& ht) : _idx(0), _ht(&ht), _b(ht._t[0])
+		{
+		}
+
+		/**
+		 * @param kptr Pointer to set to point to next key
+		 * @param vptr Pointer to set to point to next value
+		 * @return True if kptr and vptr are set, false if no more entries
+		 */
+		inline bool next(K*& kptr, V*& vptr)
+		{
+			for (;;) {
+				if (_b) {
+					kptr = &(_b->k);
+					vptr = &(_b->v);
+					_b = _b->next;
+					return true;
+				}
+				++_idx;
+				if (_idx >= _ht->_bc) {
+					return false;
+				}
+				_b = _ht->_t[_idx];
+			}
+		}
+
+	  private:
+		unsigned long _idx;
+		Hashtable* _ht;
+		_Bucket* _b;
+	};
+	// friend class Hashtable<K,V>::Iterator;
+
+	/**
+	 * @param bc Initial capacity in buckets (default: 64, must be nonzero)
+	 */
+	Hashtable(unsigned long bc = 64) : _t(reinterpret_cast<_Bucket**>(::malloc(sizeof(_Bucket*) * bc))), _bc(bc), _s(0)
+	{
+		if (! _t) {
+			throw ZT_EXCEPTION_OUT_OF_MEMORY;
+		}
+		for (unsigned long i = 0; i < bc; ++i) {
+			_t[i] = (_Bucket*)0;
+		}
+	}
+
+	Hashtable(const Hashtable<K, V>& ht) : _t(reinterpret_cast<_Bucket**>(::malloc(sizeof(_Bucket*) * ht._bc))), _bc(ht._bc), _s(ht._s)
+	{
+		if (! _t) {
+			throw ZT_EXCEPTION_OUT_OF_MEMORY;
+		}
+		for (unsigned long i = 0; i < _bc; ++i) {
+			_t[i] = (_Bucket*)0;
+		}
+		for (unsigned long i = 0; i < _bc; ++i) {
+			const _Bucket* b = ht._t[i];
+			while (b) {
+				_Bucket* nb = new _Bucket(*b);
+				nb->next = _t[i];
+				_t[i] = nb;
+				b = b->next;
+			}
+		}
+	}
+
+	~Hashtable()
+	{
+		this->clear();
+		::free(_t);
+	}
+
+	inline Hashtable& operator=(const Hashtable<K, V>& ht)
+	{
+		this->clear();
+		if (ht._s) {
+			for (unsigned long i = 0; i < ht._bc; ++i) {
+				const _Bucket* b = ht._t[i];
+				while (b) {
+					this->set(b->k, b->v);
+					b = b->next;
+				}
+			}
+		}
+		return *this;
+	}
+
+	/**
+	 * Erase all entries
+	 */
+	inline void clear()
+	{
+		if (_s) {
+			for (unsigned long i = 0; i < _bc; ++i) {
+				_Bucket* b = _t[i];
+				while (b) {
+					_Bucket* const nb = b->next;
+					delete b;
+					b = nb;
+				}
+				_t[i] = (_Bucket*)0;
+			}
+			_s = 0;
+		}
+	}
+
+	/**
+	 * @return Vector of all keys
+	 */
+	inline typename std::vector<K> keys() const
+	{
+		typename std::vector<K> k;
+		if (_s) {
+			k.reserve(_s);
+			for (unsigned long i = 0; i < _bc; ++i) {
+				_Bucket* b = _t[i];
+				while (b) {
+					k.push_back(b->k);
+					b = b->next;
+				}
+			}
+		}
+		return k;
+	}
+
+	/**
+	 * Append all keys (in unspecified order) to the supplied vector or list
+	 *
+	 * @param v Vector, list, or other compliant container
+	 * @tparam Type of V (generally inferred)
+	 */
+	template <typename C> inline void appendKeys(C& v) const
+	{
+		if (_s) {
+			for (unsigned long i = 0; i < _bc; ++i) {
+				_Bucket* b = _t[i];
+				while (b) {
+					v.push_back(b->k);
+					b = b->next;
+				}
+			}
+		}
+	}
+
+	/**
+	 * @return Vector of all entries (pairs of K,V)
+	 */
+	inline typename std::vector<std::pair<K, V> > entries() const
+	{
+		typename std::vector<std::pair<K, V> > k;
+		if (_s) {
+			k.reserve(_s);
+			for (unsigned long i = 0; i < _bc; ++i) {
+				_Bucket* b = _t[i];
+				while (b) {
+					k.push_back(std::pair<K, V>(b->k, b->v));
+					b = b->next;
+				}
+			}
+		}
+		return k;
+	}
+
+	/**
+	 * @param k Key
+	 * @return Pointer to value or NULL if not found
+	 */
+	inline V* get(const K& k)
+	{
+		_Bucket* b = _t[_hc(k) % _bc];
+		while (b) {
+			if (b->k == k) {
+				return &(b->v);
+			}
+			b = b->next;
+		}
+		return (V*)0;
+	}
+	inline const V* get(const K& k) const
+	{
+		return const_cast<Hashtable*>(this)->get(k);
+	}
+
+	/**
+	 * @param k Key
+	 * @param v Value to fill with result
+	 * @return True if value was found and set (if false, v is not modified)
+	 */
+	inline bool get(const K& k, V& v) const
+	{
+		_Bucket* b = _t[_hc(k) % _bc];
+		while (b) {
+			if (b->k == k) {
+				v = b->v;
+				return true;
+			}
+			b = b->next;
+		}
+		return false;
+	}
+
+	/**
+	 * @param k Key to check
+	 * @return True if key is present
+	 */
+	inline bool contains(const K& k) const
+	{
+		_Bucket* b = _t[_hc(k) % _bc];
+		while (b) {
+			if (b->k == k) {
+				return true;
+			}
+			b = b->next;
+		}
+		return false;
+	}
+
+	/**
+	 * @param k Key
+	 * @return True if value was present
+	 */
+	inline bool erase(const K& k)
+	{
+		const unsigned long bidx = _hc(k) % _bc;
+		_Bucket* lastb = (_Bucket*)0;
+		_Bucket* b = _t[bidx];
+		while (b) {
+			if (b->k == k) {
+				if (lastb) {
+					lastb->next = b->next;
+				}
+				else {
+					_t[bidx] = b->next;
+				}
+				delete b;
+				--_s;
+				return true;
+			}
+			lastb = b;
+			b = b->next;
+		}
+		return false;
+	}
+
+	/**
+	 * @param k Key
+	 * @param v Value
+	 * @return Reference to value in table
+	 */
+	inline V& set(const K& k, const V& v)
+	{
+		const unsigned long h = _hc(k);
+		unsigned long bidx = h % _bc;
+
+		_Bucket* b = _t[bidx];
+		while (b) {
+			if (b->k == k) {
+				b->v = v;
+				return b->v;
+			}
+			b = b->next;
+		}
+
+		if (_s >= _bc) {
+			_grow();
+			bidx = h % _bc;
+		}
+
+		b = new _Bucket(k, v);
+		b->next = _t[bidx];
+		_t[bidx] = b;
+		++_s;
+		return b->v;
+	}
+
+	/**
+	 * @param k Key
+	 * @return Value, possibly newly created
+	 */
+	inline V& operator[](const K& k)
+	{
+		const unsigned long h = _hc(k);
+		unsigned long bidx = h % _bc;
+
+		_Bucket* b = _t[bidx];
+		while (b) {
+			if (b->k == k) {
+				return b->v;
+			}
+			b = b->next;
+		}
+
+		if (_s >= _bc) {
+			_grow();
+			bidx = h % _bc;
+		}
+
+		b = new _Bucket(k);
+		b->next = _t[bidx];
+		_t[bidx] = b;
+		++_s;
+		return b->v;
+	}
+
+	/**
+	 * @return Number of entries
+	 */
+	inline unsigned long size() const
+	{
+		return _s;
+	}
+
+	/**
+	 * @return True if table is empty
+	 */
+	inline bool empty() const
+	{
+		return (_s == 0);
+	}
 
   private:
-    template <typename O> static inline unsigned long _hc(const O& obj)
-    {
-        return (unsigned long)obj.hashCode();
-    }
-    static inline unsigned long _hc(const uint64_t i)
-    {
-        return (unsigned long)(i ^ (i >> 32));   // good for network IDs and addresses
-    }
-    static inline unsigned long _hc(const uint32_t i)
-    {
-        return ((unsigned long)i * (unsigned long)0x9e3779b1);
-    }
-    static inline unsigned long _hc(const uint16_t i)
-    {
-        return ((unsigned long)i * (unsigned long)0x9e3779b1);
-    }
-    static inline unsigned long _hc(const int i)
-    {
-        return ((unsigned long)i * (unsigned long)0x9e3379b1);
-    }
-
-    inline void _grow()
-    {
-        const unsigned long nc = _bc * 2;
-        _Bucket** nt = reinterpret_cast<_Bucket**>(::malloc(sizeof(_Bucket*) * nc));
-        if (nt) {
-            for (unsigned long i = 0; i < nc; ++i) {
-                nt[i] = (_Bucket*)0;
-            }
-            for (unsigned long i = 0; i < _bc; ++i) {
-                _Bucket* b = _t[i];
-                while (b) {
-                    _Bucket* const nb = b->next;
-                    const unsigned long nidx = _hc(b->k) % nc;
-                    b->next = nt[nidx];
-                    nt[nidx] = b;
-                    b = nb;
-                }
-            }
-            ::free(_t);
-            _t = nt;
-            _bc = nc;
-        }
-    }
-
-    _Bucket** _t;
-    unsigned long _bc;
-    unsigned long _s;
+	template <typename O> static inline unsigned long _hc(const O& obj)
+	{
+		return (unsigned long)obj.hashCode();
+	}
+	static inline unsigned long _hc(const uint64_t i)
+	{
+		return (unsigned long)(i ^ (i >> 32));	 // good for network IDs and addresses
+	}
+	static inline unsigned long _hc(const uint32_t i)
+	{
+		return ((unsigned long)i * (unsigned long)0x9e3779b1);
+	}
+	static inline unsigned long _hc(const uint16_t i)
+	{
+		return ((unsigned long)i * (unsigned long)0x9e3779b1);
+	}
+	static inline unsigned long _hc(const int i)
+	{
+		return ((unsigned long)i * (unsigned long)0x9e3379b1);
+	}
+
+	inline void _grow()
+	{
+		const unsigned long nc = _bc * 2;
+		_Bucket** nt = reinterpret_cast<_Bucket**>(::malloc(sizeof(_Bucket*) * nc));
+		if (nt) {
+			for (unsigned long i = 0; i < nc; ++i) {
+				nt[i] = (_Bucket*)0;
+			}
+			for (unsigned long i = 0; i < _bc; ++i) {
+				_Bucket* b = _t[i];
+				while (b) {
+					_Bucket* const nb = b->next;
+					const unsigned long nidx = _hc(b->k) % nc;
+					b->next = nt[nidx];
+					nt[nidx] = b;
+					b = nb;
+				}
+			}
+			::free(_t);
+			_t = nt;
+			_bc = nc;
+		}
+	}
+
+	_Bucket** _t;
+	unsigned long _bc;
+	unsigned long _s;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 141 - 141
node/Identity.cpp

@@ -28,176 +28,176 @@
 // parameters of the hashcash hashing/searching algorithm.
 
 #define ZT_IDENTITY_GEN_HASHCASH_FIRST_BYTE_LESS_THAN 17
-#define ZT_IDENTITY_GEN_MEMORY                        2097152
+#define ZT_IDENTITY_GEN_MEMORY						  2097152
 
 namespace ZeroTier {
 
 // A memory-hard composition of SHA-512 and Salsa20 for hashcash hashing
 static inline void _computeMemoryHardHash(const void* publicKey, unsigned int publicKeyBytes, void* digest, void* genmem)
 {
-    // Digest publicKey[] to obtain initial digest
-    SHA512(digest, publicKey, publicKeyBytes);
-
-    // Initialize genmem[] using Salsa20 in a CBC-like configuration since
-    // ordinary Salsa20 is randomly seek-able. This is good for a cipher
-    // but is not what we want for sequential memory-hardness.
-    memset(genmem, 0, ZT_IDENTITY_GEN_MEMORY);
-    Salsa20 s20(digest, (char*)digest + 32);
-    s20.crypt20((char*)genmem, (char*)genmem, 64);
-    for (unsigned long i = 64; i < ZT_IDENTITY_GEN_MEMORY; i += 64) {
-        unsigned long k = i - 64;
-        *((uint64_t*)((char*)genmem + i)) = *((uint64_t*)((char*)genmem + k));
-        *((uint64_t*)((char*)genmem + i + 8)) = *((uint64_t*)((char*)genmem + k + 8));
-        *((uint64_t*)((char*)genmem + i + 16)) = *((uint64_t*)((char*)genmem + k + 16));
-        *((uint64_t*)((char*)genmem + i + 24)) = *((uint64_t*)((char*)genmem + k + 24));
-        *((uint64_t*)((char*)genmem + i + 32)) = *((uint64_t*)((char*)genmem + k + 32));
-        *((uint64_t*)((char*)genmem + i + 40)) = *((uint64_t*)((char*)genmem + k + 40));
-        *((uint64_t*)((char*)genmem + i + 48)) = *((uint64_t*)((char*)genmem + k + 48));
-        *((uint64_t*)((char*)genmem + i + 56)) = *((uint64_t*)((char*)genmem + k + 56));
-        s20.crypt20((char*)genmem + i, (char*)genmem + i, 64);
-    }
-
-    // Render final digest using genmem as a lookup table
-    for (unsigned long i = 0; i < (ZT_IDENTITY_GEN_MEMORY / sizeof(uint64_t));) {
-        unsigned long idx1 = (unsigned long)(Utils::ntoh(((uint64_t*)genmem)[i++]) % (64 / sizeof(uint64_t)));
-        unsigned long idx2 = (unsigned long)(Utils::ntoh(((uint64_t*)genmem)[i++]) % (ZT_IDENTITY_GEN_MEMORY / sizeof(uint64_t)));
-        uint64_t tmp = ((uint64_t*)genmem)[idx2];
-        ((uint64_t*)genmem)[idx2] = ((uint64_t*)digest)[idx1];
-        ((uint64_t*)digest)[idx1] = tmp;
-        s20.crypt20(digest, digest, 64);
-    }
+	// Digest publicKey[] to obtain initial digest
+	SHA512(digest, publicKey, publicKeyBytes);
+
+	// Initialize genmem[] using Salsa20 in a CBC-like configuration since
+	// ordinary Salsa20 is randomly seek-able. This is good for a cipher
+	// but is not what we want for sequential memory-hardness.
+	memset(genmem, 0, ZT_IDENTITY_GEN_MEMORY);
+	Salsa20 s20(digest, (char*)digest + 32);
+	s20.crypt20((char*)genmem, (char*)genmem, 64);
+	for (unsigned long i = 64; i < ZT_IDENTITY_GEN_MEMORY; i += 64) {
+		unsigned long k = i - 64;
+		*((uint64_t*)((char*)genmem + i)) = *((uint64_t*)((char*)genmem + k));
+		*((uint64_t*)((char*)genmem + i + 8)) = *((uint64_t*)((char*)genmem + k + 8));
+		*((uint64_t*)((char*)genmem + i + 16)) = *((uint64_t*)((char*)genmem + k + 16));
+		*((uint64_t*)((char*)genmem + i + 24)) = *((uint64_t*)((char*)genmem + k + 24));
+		*((uint64_t*)((char*)genmem + i + 32)) = *((uint64_t*)((char*)genmem + k + 32));
+		*((uint64_t*)((char*)genmem + i + 40)) = *((uint64_t*)((char*)genmem + k + 40));
+		*((uint64_t*)((char*)genmem + i + 48)) = *((uint64_t*)((char*)genmem + k + 48));
+		*((uint64_t*)((char*)genmem + i + 56)) = *((uint64_t*)((char*)genmem + k + 56));
+		s20.crypt20((char*)genmem + i, (char*)genmem + i, 64);
+	}
+
+	// Render final digest using genmem as a lookup table
+	for (unsigned long i = 0; i < (ZT_IDENTITY_GEN_MEMORY / sizeof(uint64_t));) {
+		unsigned long idx1 = (unsigned long)(Utils::ntoh(((uint64_t*)genmem)[i++]) % (64 / sizeof(uint64_t)));
+		unsigned long idx2 = (unsigned long)(Utils::ntoh(((uint64_t*)genmem)[i++]) % (ZT_IDENTITY_GEN_MEMORY / sizeof(uint64_t)));
+		uint64_t tmp = ((uint64_t*)genmem)[idx2];
+		((uint64_t*)genmem)[idx2] = ((uint64_t*)digest)[idx1];
+		((uint64_t*)digest)[idx1] = tmp;
+		s20.crypt20(digest, digest, 64);
+	}
 }
 
 // Hashcash generation halting condition -- halt when first byte is less than
 // threshold value.
 struct _Identity_generate_cond {
-    _Identity_generate_cond()
-    {
-    }
-    _Identity_generate_cond(unsigned char* sb, char* gm) : digest(sb), genmem(gm)
-    {
-    }
-    inline bool operator()(const ECC::Pair& kp) const
-    {
-        _computeMemoryHardHash(kp.pub.data, ZT_ECC_PUBLIC_KEY_SET_LEN, digest, genmem);
-        return (digest[0] < ZT_IDENTITY_GEN_HASHCASH_FIRST_BYTE_LESS_THAN);
-    }
-    unsigned char* digest;
-    char* genmem;
+	_Identity_generate_cond()
+	{
+	}
+	_Identity_generate_cond(unsigned char* sb, char* gm) : digest(sb), genmem(gm)
+	{
+	}
+	inline bool operator()(const ECC::Pair& kp) const
+	{
+		_computeMemoryHardHash(kp.pub.data, ZT_ECC_PUBLIC_KEY_SET_LEN, digest, genmem);
+		return (digest[0] < ZT_IDENTITY_GEN_HASHCASH_FIRST_BYTE_LESS_THAN);
+	}
+	unsigned char* digest;
+	char* genmem;
 };
 
 void Identity::generate()
 {
-    unsigned char digest[64];
-    char* genmem = new char[ZT_IDENTITY_GEN_MEMORY];
-
-    ECC::Pair kp;
-    do {
-        kp = ECC::generateSatisfying(_Identity_generate_cond(digest, genmem));
-        _address.setTo(digest + 59, ZT_ADDRESS_LENGTH);   // last 5 bytes are address
-    } while (_address.isReserved());
-
-    _publicKey = kp.pub;
-    if (! _privateKey) {
-        _privateKey = new ECC::Private();
-    }
-    *_privateKey = kp.priv;
-
-    delete[] genmem;
+	unsigned char digest[64];
+	char* genmem = new char[ZT_IDENTITY_GEN_MEMORY];
+
+	ECC::Pair kp;
+	do {
+		kp = ECC::generateSatisfying(_Identity_generate_cond(digest, genmem));
+		_address.setTo(digest + 59, ZT_ADDRESS_LENGTH);	  // last 5 bytes are address
+	} while (_address.isReserved());
+
+	_publicKey = kp.pub;
+	if (! _privateKey) {
+		_privateKey = new ECC::Private();
+	}
+	*_privateKey = kp.priv;
+
+	delete[] genmem;
 }
 
 bool Identity::locallyValidate() const
 {
-    if (_address.isReserved()) {
-        return false;
-    }
+	if (_address.isReserved()) {
+		return false;
+	}
 
-    unsigned char digest[64];
-    char* genmem = new char[ZT_IDENTITY_GEN_MEMORY];
-    _computeMemoryHardHash(_publicKey.data, ZT_ECC_PUBLIC_KEY_SET_LEN, digest, genmem);
-    delete[] genmem;
+	unsigned char digest[64];
+	char* genmem = new char[ZT_IDENTITY_GEN_MEMORY];
+	_computeMemoryHardHash(_publicKey.data, ZT_ECC_PUBLIC_KEY_SET_LEN, digest, genmem);
+	delete[] genmem;
 
-    unsigned char addrb[5];
-    _address.copyTo(addrb, 5);
+	unsigned char addrb[5];
+	_address.copyTo(addrb, 5);
 
-    return ((digest[0] < ZT_IDENTITY_GEN_HASHCASH_FIRST_BYTE_LESS_THAN) && (digest[59] == addrb[0]) && (digest[60] == addrb[1]) && (digest[61] == addrb[2]) && (digest[62] == addrb[3]) && (digest[63] == addrb[4]));
+	return ((digest[0] < ZT_IDENTITY_GEN_HASHCASH_FIRST_BYTE_LESS_THAN) && (digest[59] == addrb[0]) && (digest[60] == addrb[1]) && (digest[61] == addrb[2]) && (digest[62] == addrb[3]) && (digest[63] == addrb[4]));
 }
 
 char* Identity::toString(bool includePrivate, char buf[ZT_IDENTITY_STRING_BUFFER_LENGTH]) const
 {
-    char* p = buf;
-    Utils::hex10(_address.toInt(), p);
-    p += 10;
-    *(p++) = ':';
-    *(p++) = '0';
-    *(p++) = ':';
-    Utils::hex(_publicKey.data, ZT_ECC_PUBLIC_KEY_SET_LEN, p);
-    p += ZT_ECC_PUBLIC_KEY_SET_LEN * 2;
-    if ((_privateKey) && (includePrivate)) {
-        *(p++) = ':';
-        Utils::hex(_privateKey->data, ZT_ECC_PRIVATE_KEY_SET_LEN, p);
-        p += ZT_ECC_PRIVATE_KEY_SET_LEN * 2;
-    }
-    *p = (char)0;
-    return buf;
+	char* p = buf;
+	Utils::hex10(_address.toInt(), p);
+	p += 10;
+	*(p++) = ':';
+	*(p++) = '0';
+	*(p++) = ':';
+	Utils::hex(_publicKey.data, ZT_ECC_PUBLIC_KEY_SET_LEN, p);
+	p += ZT_ECC_PUBLIC_KEY_SET_LEN * 2;
+	if ((_privateKey) && (includePrivate)) {
+		*(p++) = ':';
+		Utils::hex(_privateKey->data, ZT_ECC_PRIVATE_KEY_SET_LEN, p);
+		p += ZT_ECC_PRIVATE_KEY_SET_LEN * 2;
+	}
+	*p = (char)0;
+	return buf;
 }
 
 bool Identity::fromString(const char* str)
 {
-    if (! str) {
-        _address.zero();
-        return false;
-    }
-    char tmp[ZT_IDENTITY_STRING_BUFFER_LENGTH];
-    if (! Utils::scopy(tmp, sizeof(tmp), str)) {
-        _address.zero();
-        return false;
-    }
-
-    delete _privateKey;
-    _privateKey = (ECC::Private*)0;
-
-    int fno = 0;
-    char* saveptr = (char*)0;
-    for (char* f = Utils::stok(tmp, ":", &saveptr); (f); f = Utils::stok((char*)0, ":", &saveptr)) {
-        switch (fno++) {
-            case 0:
-                _address = Address(Utils::hexStrToU64(f));
-                if (_address.isReserved()) {
-                    _address.zero();
-                    return false;
-                }
-                break;
-            case 1:
-                if ((f[0] != '0') || (f[1])) {
-                    _address.zero();
-                    return false;
-                }
-                break;
-            case 2:
-                if (Utils::unhex(f, _publicKey.data, ZT_ECC_PUBLIC_KEY_SET_LEN) != ZT_ECC_PUBLIC_KEY_SET_LEN) {
-                    _address.zero();
-                    return false;
-                }
-                break;
-            case 3:
-                _privateKey = new ECC::Private();
-                if (Utils::unhex(f, _privateKey->data, ZT_ECC_PRIVATE_KEY_SET_LEN) != ZT_ECC_PRIVATE_KEY_SET_LEN) {
-                    _address.zero();
-                    return false;
-                }
-                break;
-            default:
-                _address.zero();
-                return false;
-        }
-    }
-    if (fno < 3) {
-        _address.zero();
-        return false;
-    }
-
-    return true;
+	if (! str) {
+		_address.zero();
+		return false;
+	}
+	char tmp[ZT_IDENTITY_STRING_BUFFER_LENGTH];
+	if (! Utils::scopy(tmp, sizeof(tmp), str)) {
+		_address.zero();
+		return false;
+	}
+
+	delete _privateKey;
+	_privateKey = (ECC::Private*)0;
+
+	int fno = 0;
+	char* saveptr = (char*)0;
+	for (char* f = Utils::stok(tmp, ":", &saveptr); (f); f = Utils::stok((char*)0, ":", &saveptr)) {
+		switch (fno++) {
+			case 0:
+				_address = Address(Utils::hexStrToU64(f));
+				if (_address.isReserved()) {
+					_address.zero();
+					return false;
+				}
+				break;
+			case 1:
+				if ((f[0] != '0') || (f[1])) {
+					_address.zero();
+					return false;
+				}
+				break;
+			case 2:
+				if (Utils::unhex(f, _publicKey.data, ZT_ECC_PUBLIC_KEY_SET_LEN) != ZT_ECC_PUBLIC_KEY_SET_LEN) {
+					_address.zero();
+					return false;
+				}
+				break;
+			case 3:
+				_privateKey = new ECC::Private();
+				if (Utils::unhex(f, _privateKey->data, ZT_ECC_PRIVATE_KEY_SET_LEN) != ZT_ECC_PRIVATE_KEY_SET_LEN) {
+					_address.zero();
+					return false;
+				}
+				break;
+			default:
+				_address.zero();
+				return false;
+		}
+	}
+	if (fno < 3) {
+		_address.zero();
+		return false;
+	}
+
+	return true;
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 307 - 307
node/Identity.hpp

@@ -40,316 +40,316 @@ namespace ZeroTier {
  */
 class Identity {
   public:
-    Identity() : _privateKey((ECC::Private*)0)
-    {
-    }
-
-    Identity(const Identity& id) : _address(id._address), _publicKey(id._publicKey), _privateKey((id._privateKey) ? new ECC::Private(*(id._privateKey)) : (ECC::Private*)0)
-    {
-    }
-
-    Identity(const char* str) : _privateKey((ECC::Private*)0)
-    {
-        if (! fromString(str)) {
-            throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_TYPE;
-        }
-    }
-
-    template <unsigned int C> Identity(const Buffer<C>& b, unsigned int startAt = 0) : _privateKey((ECC::Private*)0)
-    {
-        deserialize(b, startAt);
-    }
-
-    ~Identity()
-    {
-        if (_privateKey) {
-            Utils::burn(_privateKey, sizeof(ECC::Private));
-            delete _privateKey;
-        }
-    }
-
-    inline Identity& operator=(const Identity& id)
-    {
-        _address = id._address;
-        _publicKey = id._publicKey;
-        if (id._privateKey) {
-            if (! _privateKey) {
-                _privateKey = new ECC::Private();
-            }
-            *_privateKey = *(id._privateKey);
-        }
-        else {
-            delete _privateKey;
-            _privateKey = (ECC::Private*)0;
-        }
-        return *this;
-    }
-
-    /**
-     * Generate a new identity (address, key pair)
-     *
-     * This is a time consuming operation.
-     */
-    void generate();
-
-    /**
-     * Check the validity of this identity's pairing of key to address
-     *
-     * @return True if validation check passes
-     */
-    bool locallyValidate() const;
-
-    /**
-     * @return True if this identity contains a private key
-     */
-    inline bool hasPrivate() const
-    {
-        return (_privateKey != (ECC::Private*)0);
-    }
-
-    /**
-     * Compute a SHA384 hash of this identity's address and public key(s).
-     *
-     * @param sha384buf Buffer with 48 bytes of space to receive hash
-     */
-    inline void publicKeyHash(void* sha384buf) const
-    {
-        uint8_t address[ZT_ADDRESS_LENGTH];
-        _address.copyTo(address, ZT_ADDRESS_LENGTH);
-        SHA384(sha384buf, address, ZT_ADDRESS_LENGTH, _publicKey.data, ZT_ECC_PUBLIC_KEY_SET_LEN);
-    }
-
-    /**
-     * Compute the SHA512 hash of our private key (if we have one)
-     *
-     * @param sha Buffer to receive SHA512 (MUST be ZT_SHA512_DIGEST_LEN (64) bytes in length)
-     * @return True on success, false if no private key
-     */
-    inline bool sha512PrivateKey(void* sha) const
-    {
-        if (_privateKey) {
-            SHA512(sha, _privateKey->data, ZT_ECC_PRIVATE_KEY_SET_LEN);
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Sign a message with this identity (private key required)
-     *
-     * @param data Data to sign
-     * @param len Length of data
-     */
-    inline ECC::Signature sign(const void* data, unsigned int len) const
-    {
-        if (_privateKey) {
-            return ECC::sign(*_privateKey, _publicKey, data, len);
-        }
-        throw ZT_EXCEPTION_PRIVATE_KEY_REQUIRED;
-    }
-
-    /**
-     * Verify a message signature against this identity
-     *
-     * @param data Data to check
-     * @param len Length of data
-     * @param signature Signature bytes
-     * @param siglen Length of signature in bytes
-     * @return True if signature validates and data integrity checks
-     */
-    inline bool verify(const void* data, unsigned int len, const void* signature, unsigned int siglen) const
-    {
-        if (siglen != ZT_ECC_SIGNATURE_LEN) {
-            return false;
-        }
-        return ECC::verify(_publicKey, data, len, signature);
-    }
-
-    /**
-     * Verify a message signature against this identity
-     *
-     * @param data Data to check
-     * @param len Length of data
-     * @param signature Signature
-     * @return True if signature validates and data integrity checks
-     */
-    inline bool verify(const void* data, unsigned int len, const ECC::Signature& signature) const
-    {
-        return ECC::verify(_publicKey, data, len, signature);
-    }
-
-    /**
-     * Shortcut method to perform key agreement with another identity
-     *
-     * This identity must have a private key. (Check hasPrivate())
-     *
-     * @param id Identity to agree with
-     * @param key Result parameter to fill with key bytes
-     * @return Was agreement successful?
-     */
-    inline bool agree(const Identity& id, void* const key) const
-    {
-        if (_privateKey) {
-            ECC::agree(*_privateKey, id._publicKey, key, ZT_SYMMETRIC_KEY_SIZE);
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * @return This identity's address
-     */
-    inline const Address& address() const
-    {
-        return _address;
-    }
-
-    /**
-     * Serialize this identity (binary)
-     *
-     * @param b Destination buffer to append to
-     * @param includePrivate If true, include private key component (if present) (default: false)
-     * @throws std::out_of_range Buffer too small
-     */
-    template <unsigned int C> inline void serialize(Buffer<C>& b, bool includePrivate = false) const
-    {
-        _address.appendTo(b);
-        b.append((uint8_t)0);   // C25519/Ed25519 identity type
-        b.append(_publicKey.data, ZT_ECC_PUBLIC_KEY_SET_LEN);
-        if ((_privateKey) && (includePrivate)) {
-            b.append((unsigned char)ZT_ECC_PRIVATE_KEY_SET_LEN);
-            b.append(_privateKey->data, ZT_ECC_PRIVATE_KEY_SET_LEN);
-        }
-        else {
-            b.append((unsigned char)0);
-        }
-    }
-
-    /**
-     * Deserialize a binary serialized identity
-     *
-     * If an exception is thrown, the Identity object is left in an undefined
-     * state and should not be used.
-     *
-     * @param b Buffer containing serialized data
-     * @param startAt Index within buffer of serialized data (default: 0)
-     * @return Length of serialized data read from buffer
-     * @throws std::out_of_range Serialized data invalid
-     * @throws std::invalid_argument Serialized data invalid
-     */
-    template <unsigned int C> inline unsigned int deserialize(const Buffer<C>& b, unsigned int startAt = 0)
-    {
-        delete _privateKey;
-        _privateKey = (ECC::Private*)0;
-
-        unsigned int p = startAt;
-
-        _address.setTo(b.field(p, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
-        p += ZT_ADDRESS_LENGTH;
-
-        if (b[p++] != 0) {
-            throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_TYPE;
-        }
-
-        memcpy(_publicKey.data, b.field(p, ZT_ECC_PUBLIC_KEY_SET_LEN), ZT_ECC_PUBLIC_KEY_SET_LEN);
-        p += ZT_ECC_PUBLIC_KEY_SET_LEN;
-
-        unsigned int privateKeyLength = (unsigned int)b[p++];
-        if (privateKeyLength) {
-            if (privateKeyLength != ZT_ECC_PRIVATE_KEY_SET_LEN) {
-                throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_CRYPTOGRAPHIC_TOKEN;
-            }
-            _privateKey = new ECC::Private();
-            memcpy(_privateKey->data, b.field(p, ZT_ECC_PRIVATE_KEY_SET_LEN), ZT_ECC_PRIVATE_KEY_SET_LEN);
-            p += ZT_ECC_PRIVATE_KEY_SET_LEN;
-        }
-
-        return (p - startAt);
-    }
-
-    /**
-     * Serialize to a more human-friendly string
-     *
-     * @param includePrivate If true, include private key (if it exists)
-     * @param buf Buffer to store string
-     * @return ASCII string representation of identity
-     */
-    char* toString(bool includePrivate, char buf[ZT_IDENTITY_STRING_BUFFER_LENGTH]) const;
-
-    /**
-     * Deserialize a human-friendly string
-     *
-     * Note: validation is for the format only. The locallyValidate() method
-     * must be used to check signature and address/key correspondence.
-     *
-     * @param str String to deserialize
-     * @return True if deserialization appears successful
-     */
-    bool fromString(const char* str);
-
-    /**
-     * @return C25519 public key
-     */
-    inline const ECC::Public& publicKey() const
-    {
-        return _publicKey;
-    }
-
-    /**
-     * @return C25519 key pair (only returns valid pair if private key is present in this Identity object)
-     */
-    inline const ECC::Pair privateKeyPair() const
-    {
-        ECC::Pair pair;
-        pair.pub = _publicKey;
-        if (_privateKey) {
-            pair.priv = *_privateKey;
-        }
-        else {
-            memset(pair.priv.data, 0, ZT_ECC_PRIVATE_KEY_SET_LEN);
-        }
-        return pair;
-    }
-
-    /**
-     * @return True if this identity contains something
-     */
-    inline operator bool() const
-    {
-        return (_address);
-    }
-
-    inline bool operator==(const Identity& id) const
-    {
-        return ((_address == id._address) && (memcmp(_publicKey.data, id._publicKey.data, ZT_ECC_PUBLIC_KEY_SET_LEN) == 0));
-    }
-    inline bool operator<(const Identity& id) const
-    {
-        return ((_address < id._address) || ((_address == id._address) && (memcmp(_publicKey.data, id._publicKey.data, ZT_ECC_PUBLIC_KEY_SET_LEN) < 0)));
-    }
-    inline bool operator!=(const Identity& id) const
-    {
-        return ! (*this == id);
-    }
-    inline bool operator>(const Identity& id) const
-    {
-        return (id < *this);
-    }
-    inline bool operator<=(const Identity& id) const
-    {
-        return ! (id < *this);
-    }
-    inline bool operator>=(const Identity& id) const
-    {
-        return ! (*this < id);
-    }
+	Identity() : _privateKey((ECC::Private*)0)
+	{
+	}
+
+	Identity(const Identity& id) : _address(id._address), _publicKey(id._publicKey), _privateKey((id._privateKey) ? new ECC::Private(*(id._privateKey)) : (ECC::Private*)0)
+	{
+	}
+
+	Identity(const char* str) : _privateKey((ECC::Private*)0)
+	{
+		if (! fromString(str)) {
+			throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_TYPE;
+		}
+	}
+
+	template <unsigned int C> Identity(const Buffer<C>& b, unsigned int startAt = 0) : _privateKey((ECC::Private*)0)
+	{
+		deserialize(b, startAt);
+	}
+
+	~Identity()
+	{
+		if (_privateKey) {
+			Utils::burn(_privateKey, sizeof(ECC::Private));
+			delete _privateKey;
+		}
+	}
+
+	inline Identity& operator=(const Identity& id)
+	{
+		_address = id._address;
+		_publicKey = id._publicKey;
+		if (id._privateKey) {
+			if (! _privateKey) {
+				_privateKey = new ECC::Private();
+			}
+			*_privateKey = *(id._privateKey);
+		}
+		else {
+			delete _privateKey;
+			_privateKey = (ECC::Private*)0;
+		}
+		return *this;
+	}
+
+	/**
+	 * Generate a new identity (address, key pair)
+	 *
+	 * This is a time consuming operation.
+	 */
+	void generate();
+
+	/**
+	 * Check the validity of this identity's pairing of key to address
+	 *
+	 * @return True if validation check passes
+	 */
+	bool locallyValidate() const;
+
+	/**
+	 * @return True if this identity contains a private key
+	 */
+	inline bool hasPrivate() const
+	{
+		return (_privateKey != (ECC::Private*)0);
+	}
+
+	/**
+	 * Compute a SHA384 hash of this identity's address and public key(s).
+	 *
+	 * @param sha384buf Buffer with 48 bytes of space to receive hash
+	 */
+	inline void publicKeyHash(void* sha384buf) const
+	{
+		uint8_t address[ZT_ADDRESS_LENGTH];
+		_address.copyTo(address, ZT_ADDRESS_LENGTH);
+		SHA384(sha384buf, address, ZT_ADDRESS_LENGTH, _publicKey.data, ZT_ECC_PUBLIC_KEY_SET_LEN);
+	}
+
+	/**
+	 * Compute the SHA512 hash of our private key (if we have one)
+	 *
+	 * @param sha Buffer to receive SHA512 (MUST be ZT_SHA512_DIGEST_LEN (64) bytes in length)
+	 * @return True on success, false if no private key
+	 */
+	inline bool sha512PrivateKey(void* sha) const
+	{
+		if (_privateKey) {
+			SHA512(sha, _privateKey->data, ZT_ECC_PRIVATE_KEY_SET_LEN);
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Sign a message with this identity (private key required)
+	 *
+	 * @param data Data to sign
+	 * @param len Length of data
+	 */
+	inline ECC::Signature sign(const void* data, unsigned int len) const
+	{
+		if (_privateKey) {
+			return ECC::sign(*_privateKey, _publicKey, data, len);
+		}
+		throw ZT_EXCEPTION_PRIVATE_KEY_REQUIRED;
+	}
+
+	/**
+	 * Verify a message signature against this identity
+	 *
+	 * @param data Data to check
+	 * @param len Length of data
+	 * @param signature Signature bytes
+	 * @param siglen Length of signature in bytes
+	 * @return True if signature validates and data integrity checks
+	 */
+	inline bool verify(const void* data, unsigned int len, const void* signature, unsigned int siglen) const
+	{
+		if (siglen != ZT_ECC_SIGNATURE_LEN) {
+			return false;
+		}
+		return ECC::verify(_publicKey, data, len, signature);
+	}
+
+	/**
+	 * Verify a message signature against this identity
+	 *
+	 * @param data Data to check
+	 * @param len Length of data
+	 * @param signature Signature
+	 * @return True if signature validates and data integrity checks
+	 */
+	inline bool verify(const void* data, unsigned int len, const ECC::Signature& signature) const
+	{
+		return ECC::verify(_publicKey, data, len, signature);
+	}
+
+	/**
+	 * Shortcut method to perform key agreement with another identity
+	 *
+	 * This identity must have a private key. (Check hasPrivate())
+	 *
+	 * @param id Identity to agree with
+	 * @param key Result parameter to fill with key bytes
+	 * @return Was agreement successful?
+	 */
+	inline bool agree(const Identity& id, void* const key) const
+	{
+		if (_privateKey) {
+			ECC::agree(*_privateKey, id._publicKey, key, ZT_SYMMETRIC_KEY_SIZE);
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * @return This identity's address
+	 */
+	inline const Address& address() const
+	{
+		return _address;
+	}
+
+	/**
+	 * Serialize this identity (binary)
+	 *
+	 * @param b Destination buffer to append to
+	 * @param includePrivate If true, include private key component (if present) (default: false)
+	 * @throws std::out_of_range Buffer too small
+	 */
+	template <unsigned int C> inline void serialize(Buffer<C>& b, bool includePrivate = false) const
+	{
+		_address.appendTo(b);
+		b.append((uint8_t)0);	// C25519/Ed25519 identity type
+		b.append(_publicKey.data, ZT_ECC_PUBLIC_KEY_SET_LEN);
+		if ((_privateKey) && (includePrivate)) {
+			b.append((unsigned char)ZT_ECC_PRIVATE_KEY_SET_LEN);
+			b.append(_privateKey->data, ZT_ECC_PRIVATE_KEY_SET_LEN);
+		}
+		else {
+			b.append((unsigned char)0);
+		}
+	}
+
+	/**
+	 * Deserialize a binary serialized identity
+	 *
+	 * If an exception is thrown, the Identity object is left in an undefined
+	 * state and should not be used.
+	 *
+	 * @param b Buffer containing serialized data
+	 * @param startAt Index within buffer of serialized data (default: 0)
+	 * @return Length of serialized data read from buffer
+	 * @throws std::out_of_range Serialized data invalid
+	 * @throws std::invalid_argument Serialized data invalid
+	 */
+	template <unsigned int C> inline unsigned int deserialize(const Buffer<C>& b, unsigned int startAt = 0)
+	{
+		delete _privateKey;
+		_privateKey = (ECC::Private*)0;
+
+		unsigned int p = startAt;
+
+		_address.setTo(b.field(p, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
+		p += ZT_ADDRESS_LENGTH;
+
+		if (b[p++] != 0) {
+			throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_TYPE;
+		}
+
+		memcpy(_publicKey.data, b.field(p, ZT_ECC_PUBLIC_KEY_SET_LEN), ZT_ECC_PUBLIC_KEY_SET_LEN);
+		p += ZT_ECC_PUBLIC_KEY_SET_LEN;
+
+		unsigned int privateKeyLength = (unsigned int)b[p++];
+		if (privateKeyLength) {
+			if (privateKeyLength != ZT_ECC_PRIVATE_KEY_SET_LEN) {
+				throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_CRYPTOGRAPHIC_TOKEN;
+			}
+			_privateKey = new ECC::Private();
+			memcpy(_privateKey->data, b.field(p, ZT_ECC_PRIVATE_KEY_SET_LEN), ZT_ECC_PRIVATE_KEY_SET_LEN);
+			p += ZT_ECC_PRIVATE_KEY_SET_LEN;
+		}
+
+		return (p - startAt);
+	}
+
+	/**
+	 * Serialize to a more human-friendly string
+	 *
+	 * @param includePrivate If true, include private key (if it exists)
+	 * @param buf Buffer to store string
+	 * @return ASCII string representation of identity
+	 */
+	char* toString(bool includePrivate, char buf[ZT_IDENTITY_STRING_BUFFER_LENGTH]) const;
+
+	/**
+	 * Deserialize a human-friendly string
+	 *
+	 * Note: validation is for the format only. The locallyValidate() method
+	 * must be used to check signature and address/key correspondence.
+	 *
+	 * @param str String to deserialize
+	 * @return True if deserialization appears successful
+	 */
+	bool fromString(const char* str);
+
+	/**
+	 * @return C25519 public key
+	 */
+	inline const ECC::Public& publicKey() const
+	{
+		return _publicKey;
+	}
+
+	/**
+	 * @return C25519 key pair (only returns valid pair if private key is present in this Identity object)
+	 */
+	inline const ECC::Pair privateKeyPair() const
+	{
+		ECC::Pair pair;
+		pair.pub = _publicKey;
+		if (_privateKey) {
+			pair.priv = *_privateKey;
+		}
+		else {
+			memset(pair.priv.data, 0, ZT_ECC_PRIVATE_KEY_SET_LEN);
+		}
+		return pair;
+	}
+
+	/**
+	 * @return True if this identity contains something
+	 */
+	inline operator bool() const
+	{
+		return (_address);
+	}
+
+	inline bool operator==(const Identity& id) const
+	{
+		return ((_address == id._address) && (memcmp(_publicKey.data, id._publicKey.data, ZT_ECC_PUBLIC_KEY_SET_LEN) == 0));
+	}
+	inline bool operator<(const Identity& id) const
+	{
+		return ((_address < id._address) || ((_address == id._address) && (memcmp(_publicKey.data, id._publicKey.data, ZT_ECC_PUBLIC_KEY_SET_LEN) < 0)));
+	}
+	inline bool operator!=(const Identity& id) const
+	{
+		return ! (*this == id);
+	}
+	inline bool operator>(const Identity& id) const
+	{
+		return (id < *this);
+	}
+	inline bool operator<=(const Identity& id) const
+	{
+		return ! (id < *this);
+	}
+	inline bool operator>=(const Identity& id) const
+	{
+		return ! (*this < id);
+	}
 
   private:
-    Address _address;
-    ECC::Public _publicKey;
-    ECC::Private* _privateKey;
+	Address _address;
+	ECC::Public _publicKey;
+	ECC::Private* _privateKey;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 1352 - 1352
node/IncomingPacket.cpp

@@ -44,1446 +44,1446 @@ namespace ZeroTier {
 
 bool IncomingPacket::tryDecode(const RuntimeEnvironment* RR, void* tPtr, int32_t flowId)
 {
-    const Address sourceAddress(source());
-
-    try {
-        // Check for trusted paths or unencrypted HELLOs (HELLO is the only packet sent in the clear)
-        const unsigned int c = cipher();
-        if (c == ZT_PROTO_CIPHER_SUITE__NO_CRYPTO_TRUSTED_PATH) {
-            // If this is marked as a packet via a trusted path, check source address and path ID.
-            // Obviously if no trusted paths are configured this always returns false and such
-            // packets are dropped on the floor.
-            const uint64_t tpid = trustedPathId();
-            if (RR->topology->shouldInboundPathBeTrusted(_path->address(), tpid)) {
-                _authenticated = true;
-            }
-            else {
-                RR->t->incomingPacketMessageAuthenticationFailure(tPtr, _path, packetId(), sourceAddress, hops(), "path not trusted");
-                return true;
-            }
-        }
-        else if ((c == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_NONE) && (verb() == Packet::VERB_HELLO)) {
-            // Only HELLO is allowed in the clear, but will still have a MAC
-            return _doHELLO(RR, tPtr, false);
-        }
-
-        const SharedPtr<Peer> peer(RR->topology->getPeer(tPtr, sourceAddress));
-        if (peer) {
-            if (! _authenticated) {
-                if (! dearmor(peer->key(), peer->aesKeys(), RR->identity)) {
-                    RR->t->incomingPacketMessageAuthenticationFailure(tPtr, _path, packetId(), sourceAddress, hops(), "invalid MAC");
-                    peer->recordIncomingInvalidPacket(_path);
-                    return true;
-                }
-            }
-
-            if (! uncompress()) {
-                RR->t->incomingPacketInvalid(tPtr, _path, packetId(), sourceAddress, hops(), Packet::VERB_NOP, "LZ4 decompression failed");
-                return true;
-            }
-
-            _authenticated = true;
-            const Packet::Verb v = verb();
-
-            bool r = true;
-            switch (v) {
-                // case Packet::VERB_NOP:
-                default:   // ignore unknown verbs, but if they pass auth check they are "received"
-                    Metrics::pkt_nop_in++;
-                    peer->received(tPtr, _path, hops(), packetId(), payloadLength(), v, 0, Packet::VERB_NOP, false, 0, ZT_QOS_NO_FLOW);
-                    break;
-                case Packet::VERB_HELLO:
-                    r = _doHELLO(RR, tPtr, true);
-                    break;
-                case Packet::VERB_ACK:
-                    r = _doACK(RR, tPtr, peer);
-                    break;
-                case Packet::VERB_QOS_MEASUREMENT:
-                    r = _doQOS_MEASUREMENT(RR, tPtr, peer);
-                    break;
-                case Packet::VERB_ERROR:
-                    r = _doERROR(RR, tPtr, peer);
-                    break;
-                case Packet::VERB_OK:
-                    r = _doOK(RR, tPtr, peer);
-                    break;
-                case Packet::VERB_WHOIS:
-                    r = _doWHOIS(RR, tPtr, peer);
-                    break;
-                case Packet::VERB_RENDEZVOUS:
-                    r = _doRENDEZVOUS(RR, tPtr, peer);
-                    break;
-                case Packet::VERB_FRAME:
-                    r = _doFRAME(RR, tPtr, peer, flowId);
-                    break;
-                case Packet::VERB_EXT_FRAME:
-                    r = _doEXT_FRAME(RR, tPtr, peer, flowId);
-                    break;
-                case Packet::VERB_ECHO:
-                    r = _doECHO(RR, tPtr, peer);
-                    break;
-                case Packet::VERB_MULTICAST_LIKE:
-                    r = _doMULTICAST_LIKE(RR, tPtr, peer);
-                    break;
-                case Packet::VERB_NETWORK_CREDENTIALS:
-                    r = _doNETWORK_CREDENTIALS(RR, tPtr, peer);
-                    break;
-                case Packet::VERB_NETWORK_CONFIG_REQUEST:
-                    r = _doNETWORK_CONFIG_REQUEST(RR, tPtr, peer);
-                    break;
-                case Packet::VERB_NETWORK_CONFIG:
-                    r = _doNETWORK_CONFIG(RR, tPtr, peer);
-                    break;
-                case Packet::VERB_MULTICAST_GATHER:
-                    r = _doMULTICAST_GATHER(RR, tPtr, peer);
-                    break;
-                case Packet::VERB_MULTICAST_FRAME:
-                    r = _doMULTICAST_FRAME(RR, tPtr, peer);
-                    break;
-                case Packet::VERB_PUSH_DIRECT_PATHS:
-                    r = _doPUSH_DIRECT_PATHS(RR, tPtr, peer);
-                    break;
-                case Packet::VERB_USER_MESSAGE:
-                    r = _doUSER_MESSAGE(RR, tPtr, peer);
-                    break;
-                case Packet::VERB_REMOTE_TRACE:
-                    r = _doREMOTE_TRACE(RR, tPtr, peer);
-                    break;
-                case Packet::VERB_PATH_NEGOTIATION_REQUEST:
-                    r = _doPATH_NEGOTIATION_REQUEST(RR, tPtr, peer);
-                    break;
-            }
-            if (r) {
-                RR->node->statsLogVerb((unsigned int)v, (unsigned int)size());
-                return true;
-            }
-            return false;
-        }
-        else {
-            RR->sw->requestWhois(tPtr, RR->node->now(), sourceAddress);
-            return false;
-        }
-    }
-    catch (...) {
-        RR->t->incomingPacketInvalid(tPtr, _path, packetId(), sourceAddress, hops(), verb(), "unexpected exception in tryDecode()");
-        return true;
-    }
+	const Address sourceAddress(source());
+
+	try {
+		// Check for trusted paths or unencrypted HELLOs (HELLO is the only packet sent in the clear)
+		const unsigned int c = cipher();
+		if (c == ZT_PROTO_CIPHER_SUITE__NO_CRYPTO_TRUSTED_PATH) {
+			// If this is marked as a packet via a trusted path, check source address and path ID.
+			// Obviously if no trusted paths are configured this always returns false and such
+			// packets are dropped on the floor.
+			const uint64_t tpid = trustedPathId();
+			if (RR->topology->shouldInboundPathBeTrusted(_path->address(), tpid)) {
+				_authenticated = true;
+			}
+			else {
+				RR->t->incomingPacketMessageAuthenticationFailure(tPtr, _path, packetId(), sourceAddress, hops(), "path not trusted");
+				return true;
+			}
+		}
+		else if ((c == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_NONE) && (verb() == Packet::VERB_HELLO)) {
+			// Only HELLO is allowed in the clear, but will still have a MAC
+			return _doHELLO(RR, tPtr, false);
+		}
+
+		const SharedPtr<Peer> peer(RR->topology->getPeer(tPtr, sourceAddress));
+		if (peer) {
+			if (! _authenticated) {
+				if (! dearmor(peer->key(), peer->aesKeys(), RR->identity)) {
+					RR->t->incomingPacketMessageAuthenticationFailure(tPtr, _path, packetId(), sourceAddress, hops(), "invalid MAC");
+					peer->recordIncomingInvalidPacket(_path);
+					return true;
+				}
+			}
+
+			if (! uncompress()) {
+				RR->t->incomingPacketInvalid(tPtr, _path, packetId(), sourceAddress, hops(), Packet::VERB_NOP, "LZ4 decompression failed");
+				return true;
+			}
+
+			_authenticated = true;
+			const Packet::Verb v = verb();
+
+			bool r = true;
+			switch (v) {
+				// case Packet::VERB_NOP:
+				default:   // ignore unknown verbs, but if they pass auth check they are "received"
+					Metrics::pkt_nop_in++;
+					peer->received(tPtr, _path, hops(), packetId(), payloadLength(), v, 0, Packet::VERB_NOP, false, 0, ZT_QOS_NO_FLOW);
+					break;
+				case Packet::VERB_HELLO:
+					r = _doHELLO(RR, tPtr, true);
+					break;
+				case Packet::VERB_ACK:
+					r = _doACK(RR, tPtr, peer);
+					break;
+				case Packet::VERB_QOS_MEASUREMENT:
+					r = _doQOS_MEASUREMENT(RR, tPtr, peer);
+					break;
+				case Packet::VERB_ERROR:
+					r = _doERROR(RR, tPtr, peer);
+					break;
+				case Packet::VERB_OK:
+					r = _doOK(RR, tPtr, peer);
+					break;
+				case Packet::VERB_WHOIS:
+					r = _doWHOIS(RR, tPtr, peer);
+					break;
+				case Packet::VERB_RENDEZVOUS:
+					r = _doRENDEZVOUS(RR, tPtr, peer);
+					break;
+				case Packet::VERB_FRAME:
+					r = _doFRAME(RR, tPtr, peer, flowId);
+					break;
+				case Packet::VERB_EXT_FRAME:
+					r = _doEXT_FRAME(RR, tPtr, peer, flowId);
+					break;
+				case Packet::VERB_ECHO:
+					r = _doECHO(RR, tPtr, peer);
+					break;
+				case Packet::VERB_MULTICAST_LIKE:
+					r = _doMULTICAST_LIKE(RR, tPtr, peer);
+					break;
+				case Packet::VERB_NETWORK_CREDENTIALS:
+					r = _doNETWORK_CREDENTIALS(RR, tPtr, peer);
+					break;
+				case Packet::VERB_NETWORK_CONFIG_REQUEST:
+					r = _doNETWORK_CONFIG_REQUEST(RR, tPtr, peer);
+					break;
+				case Packet::VERB_NETWORK_CONFIG:
+					r = _doNETWORK_CONFIG(RR, tPtr, peer);
+					break;
+				case Packet::VERB_MULTICAST_GATHER:
+					r = _doMULTICAST_GATHER(RR, tPtr, peer);
+					break;
+				case Packet::VERB_MULTICAST_FRAME:
+					r = _doMULTICAST_FRAME(RR, tPtr, peer);
+					break;
+				case Packet::VERB_PUSH_DIRECT_PATHS:
+					r = _doPUSH_DIRECT_PATHS(RR, tPtr, peer);
+					break;
+				case Packet::VERB_USER_MESSAGE:
+					r = _doUSER_MESSAGE(RR, tPtr, peer);
+					break;
+				case Packet::VERB_REMOTE_TRACE:
+					r = _doREMOTE_TRACE(RR, tPtr, peer);
+					break;
+				case Packet::VERB_PATH_NEGOTIATION_REQUEST:
+					r = _doPATH_NEGOTIATION_REQUEST(RR, tPtr, peer);
+					break;
+			}
+			if (r) {
+				RR->node->statsLogVerb((unsigned int)v, (unsigned int)size());
+				return true;
+			}
+			return false;
+		}
+		else {
+			RR->sw->requestWhois(tPtr, RR->node->now(), sourceAddress);
+			return false;
+		}
+	}
+	catch (...) {
+		RR->t->incomingPacketInvalid(tPtr, _path, packetId(), sourceAddress, hops(), verb(), "unexpected exception in tryDecode()");
+		return true;
+	}
 }
 
 bool IncomingPacket::_doERROR(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer)
 {
-    const Packet::Verb inReVerb = (Packet::Verb)(*this)[ZT_PROTO_VERB_ERROR_IDX_IN_RE_VERB];
-    const uint64_t inRePacketId = at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_IN_RE_PACKET_ID);
-    const Packet::ErrorCode errorCode = (Packet::ErrorCode)(*this)[ZT_PROTO_VERB_ERROR_IDX_ERROR_CODE];
-    uint64_t networkId = 0;
-
-    Metrics::pkt_error_in++;
-
-    /* Security note: we do not gate doERROR() with expectingReplyTo() to
-     * avoid having to log every outgoing packet ID. Instead we put the
-     * logic to determine whether we should consider an ERROR in each
-     * error handler. In most cases these are only trusted in specific
-     * circumstances. */
-
-    switch (errorCode) {
-        case Packet::ERROR_OBJ_NOT_FOUND:
-            // Object not found, currently only meaningful from network controllers.
-            if (inReVerb == Packet::VERB_NETWORK_CONFIG_REQUEST) {
-                const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
-                if ((network) && (network->controller() == peer->address())) {
-                    network->setNotFound(tPtr);
-                }
-            }
-            Metrics::pkt_error_obj_not_found_in++;
-            break;
-
-        case Packet::ERROR_UNSUPPORTED_OPERATION:
-            // This can be sent in response to any operation, though right now we only
-            // consider it meaningful from network controllers. This would indicate
-            // that the queried node does not support acting as a controller.
-            if (inReVerb == Packet::VERB_NETWORK_CONFIG_REQUEST) {
-                const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
-                if ((network) && (network->controller() == peer->address())) {
-                    network->setNotFound(tPtr);
-                }
-            }
-            Metrics::pkt_error_unsupported_op_in++;
-            break;
-
-        case Packet::ERROR_IDENTITY_COLLISION:
-            // FIXME: for federation this will need a payload with a signature or something.
-            if (RR->topology->isUpstream(peer->identity())) {
-                RR->node->postEvent(tPtr, ZT_EVENT_FATAL_ERROR_IDENTITY_COLLISION);
-            }
-            Metrics::pkt_error_identity_collision_in++;
-            break;
-
-        case Packet::ERROR_NEED_MEMBERSHIP_CERTIFICATE: {
-            // Peers can send this in response to frames if they do not have a recent enough COM from us
-            networkId = at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD);
-            const SharedPtr<Network> network(RR->node->network(networkId));
-            const int64_t now = RR->node->now();
-            if ((network) && (network->config().com)) {
-                network->peerRequestedCredentials(tPtr, peer->address(), now);
-            }
-            Metrics::pkt_error_need_membership_cert_in++;
-        } break;
-
-        case Packet::ERROR_NETWORK_ACCESS_DENIED_: {
-            // Network controller: network access denied.
-            const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
-            if ((network) && (network->controller() == peer->address())) {
-                network->setAccessDenied(tPtr);
-            }
-            Metrics::pkt_error_network_access_denied_in++;
-        } break;
-
-        case Packet::ERROR_UNWANTED_MULTICAST: {
-            // Members of networks can use this error to indicate that they no longer
-            // want to receive multicasts on a given channel.
-            networkId = at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD);
-            const SharedPtr<Network> network(RR->node->network(networkId));
-            if ((network) && (network->gate(tPtr, peer))) {
-                const MulticastGroup mg(MAC(field(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 8, 6), 6), at<uint32_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 14));
-                RR->mc->remove(network->id(), mg, peer->address());
-            }
-            Metrics::pkt_error_unwanted_multicast_in++;
-        } break;
-
-        case Packet::ERROR_NETWORK_AUTHENTICATION_REQUIRED: {
-            // fprintf(stderr, "\nPacket::ERROR_NETWORK_AUTHENTICATION_REQUIRED\n\n");
-            const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
-            if ((network) && (network->controller() == peer->address())) {
-                int s = (int)size() - (ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 8);
-                if (s > 2) {
-                    const uint16_t errorDataSize = at<uint16_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 8);
-                    s -= 2;
-                    if (s >= (int)errorDataSize) {
-                        Dictionary<8192> authInfo(((const char*)this->data()) + (ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 10), errorDataSize);
-
-                        uint64_t authVer = authInfo.getUI(ZT_AUTHINFO_DICT_KEY_VERSION, 0ULL);
-
-                        if (authVer == 0) {
-                            char authenticationURL[2048];
-
-                            if (authInfo.get(ZT_AUTHINFO_DICT_KEY_AUTHENTICATION_URL, authenticationURL, sizeof(authenticationURL)) > 0) {
-                                authenticationURL[sizeof(authenticationURL) - 1] = 0;   // ensure always zero terminated
-                                network->setAuthenticationRequired(tPtr, authenticationURL);
-                            }
-                        }
-                        else if (authVer == 1) {
-                            char issuerURL[2048] = { 0 };
-                            char centralAuthURL[2048] = { 0 };
-                            char ssoNonce[64] = { 0 };
-                            char ssoState[128] = { 0 };
-                            char ssoClientID[256] = { 0 };
-                            char ssoProvider[64] = { 0 };
-
-                            if (authInfo.get(ZT_AUTHINFO_DICT_KEY_ISSUER_URL, issuerURL, sizeof(issuerURL)) > 0) {
-                                issuerURL[sizeof(issuerURL) - 1] = 0;
-                            }
-                            if (authInfo.get(ZT_AUTHINFO_DICT_KEY_CENTRAL_ENDPOINT_URL, centralAuthURL, sizeof(centralAuthURL)) > 0) {
-                                centralAuthURL[sizeof(centralAuthURL) - 1] = 0;
-                            }
-                            if (authInfo.get(ZT_AUTHINFO_DICT_KEY_NONCE, ssoNonce, sizeof(ssoNonce)) > 0) {
-                                ssoNonce[sizeof(ssoNonce) - 1] = 0;
-                            }
-                            if (authInfo.get(ZT_AUTHINFO_DICT_KEY_STATE, ssoState, sizeof(ssoState)) > 0) {
-                                ssoState[sizeof(ssoState) - 1] = 0;
-                            }
-                            if (authInfo.get(ZT_AUTHINFO_DICT_KEY_CLIENT_ID, ssoClientID, sizeof(ssoClientID)) > 0) {
-                                ssoClientID[sizeof(ssoClientID) - 1] = 0;
-                            }
-                            if (authInfo.get(ZT_AUTHINFO_DICT_KEY_SSO_PROVIDER, ssoProvider, sizeof(ssoProvider)) > 0) {
-                                ssoProvider[sizeof(ssoProvider) - 1] = 0;
-                            }
-                            else {
-                                strncpy(ssoProvider, "default", sizeof(ssoProvider));
-                            }
-
-                            network->setAuthenticationRequired(tPtr, issuerURL, centralAuthURL, ssoClientID, ssoProvider, ssoNonce, ssoState);
-                        }
-                    }
-                }
-                else {
-                    network->setAuthenticationRequired(tPtr, "");
-                }
-            }
-            Metrics::pkt_error_authentication_required_in++;
-        } break;
-
-        default:
-            break;
-    }
-
-    peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_ERROR, inRePacketId, inReVerb, false, networkId, ZT_QOS_NO_FLOW);
-
-    return true;
+	const Packet::Verb inReVerb = (Packet::Verb)(*this)[ZT_PROTO_VERB_ERROR_IDX_IN_RE_VERB];
+	const uint64_t inRePacketId = at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_IN_RE_PACKET_ID);
+	const Packet::ErrorCode errorCode = (Packet::ErrorCode)(*this)[ZT_PROTO_VERB_ERROR_IDX_ERROR_CODE];
+	uint64_t networkId = 0;
+
+	Metrics::pkt_error_in++;
+
+	/* Security note: we do not gate doERROR() with expectingReplyTo() to
+	 * avoid having to log every outgoing packet ID. Instead we put the
+	 * logic to determine whether we should consider an ERROR in each
+	 * error handler. In most cases these are only trusted in specific
+	 * circumstances. */
+
+	switch (errorCode) {
+		case Packet::ERROR_OBJ_NOT_FOUND:
+			// Object not found, currently only meaningful from network controllers.
+			if (inReVerb == Packet::VERB_NETWORK_CONFIG_REQUEST) {
+				const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
+				if ((network) && (network->controller() == peer->address())) {
+					network->setNotFound(tPtr);
+				}
+			}
+			Metrics::pkt_error_obj_not_found_in++;
+			break;
+
+		case Packet::ERROR_UNSUPPORTED_OPERATION:
+			// This can be sent in response to any operation, though right now we only
+			// consider it meaningful from network controllers. This would indicate
+			// that the queried node does not support acting as a controller.
+			if (inReVerb == Packet::VERB_NETWORK_CONFIG_REQUEST) {
+				const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
+				if ((network) && (network->controller() == peer->address())) {
+					network->setNotFound(tPtr);
+				}
+			}
+			Metrics::pkt_error_unsupported_op_in++;
+			break;
+
+		case Packet::ERROR_IDENTITY_COLLISION:
+			// FIXME: for federation this will need a payload with a signature or something.
+			if (RR->topology->isUpstream(peer->identity())) {
+				RR->node->postEvent(tPtr, ZT_EVENT_FATAL_ERROR_IDENTITY_COLLISION);
+			}
+			Metrics::pkt_error_identity_collision_in++;
+			break;
+
+		case Packet::ERROR_NEED_MEMBERSHIP_CERTIFICATE: {
+			// Peers can send this in response to frames if they do not have a recent enough COM from us
+			networkId = at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD);
+			const SharedPtr<Network> network(RR->node->network(networkId));
+			const int64_t now = RR->node->now();
+			if ((network) && (network->config().com)) {
+				network->peerRequestedCredentials(tPtr, peer->address(), now);
+			}
+			Metrics::pkt_error_need_membership_cert_in++;
+		} break;
+
+		case Packet::ERROR_NETWORK_ACCESS_DENIED_: {
+			// Network controller: network access denied.
+			const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
+			if ((network) && (network->controller() == peer->address())) {
+				network->setAccessDenied(tPtr);
+			}
+			Metrics::pkt_error_network_access_denied_in++;
+		} break;
+
+		case Packet::ERROR_UNWANTED_MULTICAST: {
+			// Members of networks can use this error to indicate that they no longer
+			// want to receive multicasts on a given channel.
+			networkId = at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD);
+			const SharedPtr<Network> network(RR->node->network(networkId));
+			if ((network) && (network->gate(tPtr, peer))) {
+				const MulticastGroup mg(MAC(field(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 8, 6), 6), at<uint32_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 14));
+				RR->mc->remove(network->id(), mg, peer->address());
+			}
+			Metrics::pkt_error_unwanted_multicast_in++;
+		} break;
+
+		case Packet::ERROR_NETWORK_AUTHENTICATION_REQUIRED: {
+			// fprintf(stderr, "\nPacket::ERROR_NETWORK_AUTHENTICATION_REQUIRED\n\n");
+			const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
+			if ((network) && (network->controller() == peer->address())) {
+				int s = (int)size() - (ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 8);
+				if (s > 2) {
+					const uint16_t errorDataSize = at<uint16_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 8);
+					s -= 2;
+					if (s >= (int)errorDataSize) {
+						Dictionary<8192> authInfo(((const char*)this->data()) + (ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 10), errorDataSize);
+
+						uint64_t authVer = authInfo.getUI(ZT_AUTHINFO_DICT_KEY_VERSION, 0ULL);
+
+						if (authVer == 0) {
+							char authenticationURL[2048];
+
+							if (authInfo.get(ZT_AUTHINFO_DICT_KEY_AUTHENTICATION_URL, authenticationURL, sizeof(authenticationURL)) > 0) {
+								authenticationURL[sizeof(authenticationURL) - 1] = 0;	// ensure always zero terminated
+								network->setAuthenticationRequired(tPtr, authenticationURL);
+							}
+						}
+						else if (authVer == 1) {
+							char issuerURL[2048] = { 0 };
+							char centralAuthURL[2048] = { 0 };
+							char ssoNonce[64] = { 0 };
+							char ssoState[128] = { 0 };
+							char ssoClientID[256] = { 0 };
+							char ssoProvider[64] = { 0 };
+
+							if (authInfo.get(ZT_AUTHINFO_DICT_KEY_ISSUER_URL, issuerURL, sizeof(issuerURL)) > 0) {
+								issuerURL[sizeof(issuerURL) - 1] = 0;
+							}
+							if (authInfo.get(ZT_AUTHINFO_DICT_KEY_CENTRAL_ENDPOINT_URL, centralAuthURL, sizeof(centralAuthURL)) > 0) {
+								centralAuthURL[sizeof(centralAuthURL) - 1] = 0;
+							}
+							if (authInfo.get(ZT_AUTHINFO_DICT_KEY_NONCE, ssoNonce, sizeof(ssoNonce)) > 0) {
+								ssoNonce[sizeof(ssoNonce) - 1] = 0;
+							}
+							if (authInfo.get(ZT_AUTHINFO_DICT_KEY_STATE, ssoState, sizeof(ssoState)) > 0) {
+								ssoState[sizeof(ssoState) - 1] = 0;
+							}
+							if (authInfo.get(ZT_AUTHINFO_DICT_KEY_CLIENT_ID, ssoClientID, sizeof(ssoClientID)) > 0) {
+								ssoClientID[sizeof(ssoClientID) - 1] = 0;
+							}
+							if (authInfo.get(ZT_AUTHINFO_DICT_KEY_SSO_PROVIDER, ssoProvider, sizeof(ssoProvider)) > 0) {
+								ssoProvider[sizeof(ssoProvider) - 1] = 0;
+							}
+							else {
+								strncpy(ssoProvider, "default", sizeof(ssoProvider));
+							}
+
+							network->setAuthenticationRequired(tPtr, issuerURL, centralAuthURL, ssoClientID, ssoProvider, ssoNonce, ssoState);
+						}
+					}
+				}
+				else {
+					network->setAuthenticationRequired(tPtr, "");
+				}
+			}
+			Metrics::pkt_error_authentication_required_in++;
+		} break;
+
+		default:
+			break;
+	}
+
+	peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_ERROR, inRePacketId, inReVerb, false, networkId, ZT_QOS_NO_FLOW);
+
+	return true;
 }
 
 bool IncomingPacket::_doACK(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer)
 {
-    /*
-    if (! peer->rateGateACK(RR->node->now())) {
-        return true;
-    }
-    int32_t ackedBytes;
-    if (payloadLength() != sizeof(ackedBytes)) {
-        return true;   // ignore
-    }
-    memcpy(&ackedBytes, payload(), sizeof(ackedBytes));
-    peer->receivedAck(_path, RR->node->now(), Utils::ntoh(ackedBytes));
-    */
-    Metrics::pkt_ack_in++;
-    return true;
+	/*
+	if (! peer->rateGateACK(RR->node->now())) {
+		return true;
+	}
+	int32_t ackedBytes;
+	if (payloadLength() != sizeof(ackedBytes)) {
+		return true;   // ignore
+	}
+	memcpy(&ackedBytes, payload(), sizeof(ackedBytes));
+	peer->receivedAck(_path, RR->node->now(), Utils::ntoh(ackedBytes));
+	*/
+	Metrics::pkt_ack_in++;
+	return true;
 }
 
 bool IncomingPacket::_doQOS_MEASUREMENT(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer)
 {
-    Metrics::pkt_qos_in++;
-    if (! peer->rateGateQoS(RR->node->now(), _path)) {
-        return true;
-    }
-    if (payloadLength() > ZT_QOS_MAX_PACKET_SIZE || payloadLength() < ZT_QOS_MIN_PACKET_SIZE) {
-        return true;   // ignore
-    }
-    const int64_t now = RR->node->now();
-    uint64_t rx_id[ZT_QOS_TABLE_SIZE];
-    uint16_t rx_ts[ZT_QOS_TABLE_SIZE];
-    char* begin = (char*)payload();
-    char* ptr = begin;
-    int count = 0;
-    unsigned int len = payloadLength();
-    // Read packet IDs and latency compensation intervals for each packet tracked by this QoS packet
-    while (ptr < (begin + len) && (count < ZT_QOS_TABLE_SIZE)) {
-        memcpy((void*)&rx_id[count], ptr, sizeof(uint64_t));
-        ptr += sizeof(uint64_t);
-        memcpy((void*)&rx_ts[count], ptr, sizeof(uint16_t));
-        ptr += sizeof(uint16_t);
-        count++;
-    }
-    peer->receivedQoS(_path, now, count, rx_id, rx_ts);
-    return true;
+	Metrics::pkt_qos_in++;
+	if (! peer->rateGateQoS(RR->node->now(), _path)) {
+		return true;
+	}
+	if (payloadLength() > ZT_QOS_MAX_PACKET_SIZE || payloadLength() < ZT_QOS_MIN_PACKET_SIZE) {
+		return true;   // ignore
+	}
+	const int64_t now = RR->node->now();
+	uint64_t rx_id[ZT_QOS_TABLE_SIZE];
+	uint16_t rx_ts[ZT_QOS_TABLE_SIZE];
+	char* begin = (char*)payload();
+	char* ptr = begin;
+	int count = 0;
+	unsigned int len = payloadLength();
+	// Read packet IDs and latency compensation intervals for each packet tracked by this QoS packet
+	while (ptr < (begin + len) && (count < ZT_QOS_TABLE_SIZE)) {
+		memcpy((void*)&rx_id[count], ptr, sizeof(uint64_t));
+		ptr += sizeof(uint64_t);
+		memcpy((void*)&rx_ts[count], ptr, sizeof(uint16_t));
+		ptr += sizeof(uint16_t);
+		count++;
+	}
+	peer->receivedQoS(_path, now, count, rx_id, rx_ts);
+	return true;
 }
 
 bool IncomingPacket::_doHELLO(const RuntimeEnvironment* RR, void* tPtr, const bool alreadyAuthenticated)
 {
-    Metrics::pkt_hello_in++;
-    const int64_t now = RR->node->now();
-
-    const uint64_t pid = packetId();
-    const Address fromAddress(source());
-    const unsigned int protoVersion = (*this)[ZT_PROTO_VERB_HELLO_IDX_PROTOCOL_VERSION];
-    const unsigned int vMajor = (*this)[ZT_PROTO_VERB_HELLO_IDX_MAJOR_VERSION];
-    const unsigned int vMinor = (*this)[ZT_PROTO_VERB_HELLO_IDX_MINOR_VERSION];
-    const unsigned int vRevision = at<uint16_t>(ZT_PROTO_VERB_HELLO_IDX_REVISION);
-    const int64_t timestamp = at<int64_t>(ZT_PROTO_VERB_HELLO_IDX_TIMESTAMP);
-    Identity id;
-    unsigned int ptr = ZT_PROTO_VERB_HELLO_IDX_IDENTITY + id.deserialize(*this, ZT_PROTO_VERB_HELLO_IDX_IDENTITY);
-
-    if (protoVersion < ZT_PROTO_VERSION_MIN) {
-        RR->t->incomingPacketDroppedHELLO(tPtr, _path, pid, fromAddress, "protocol version too old");
-        return true;
-    }
-    if (fromAddress != id.address()) {
-        RR->t->incomingPacketDroppedHELLO(tPtr, _path, pid, fromAddress, "identity/address mismatch");
-        return true;
-    }
-
-    SharedPtr<Peer> peer(RR->topology->getPeer(tPtr, id.address()));
-    if (peer) {
-        // We already have an identity with this address -- check for collisions
-        if (! alreadyAuthenticated) {
-            if (peer->identity() != id) {
-                // Identity is different from the one we already have -- address collision
-
-                // Check rate limits
-                if (! RR->node->rateGateIdentityVerification(now, _path->address())) {
-                    return true;
-                }
-
-                uint8_t key[ZT_SYMMETRIC_KEY_SIZE];
-                if (RR->identity.agree(id, key)) {
-                    if (dearmor(key, peer->aesKeysIfSupported(), RR->identity)) {   // ensure packet is authentic, otherwise drop
-                        RR->t->incomingPacketDroppedHELLO(tPtr, _path, pid, fromAddress, "address collision");
-                        Packet outp(id.address(), RR->identity.address(), Packet::VERB_ERROR);
-                        outp.append((uint8_t)Packet::VERB_HELLO);
-                        outp.append((uint64_t)pid);
-                        outp.append((uint8_t)Packet::ERROR_IDENTITY_COLLISION);
-                        outp.armor(key, true, false, peer->aesKeysIfSupported(), peer->identity());
-                        Metrics::pkt_error_out++;
-                        Metrics::pkt_error_identity_collision_out++;
-                        _path->send(RR, tPtr, outp.data(), outp.size(), RR->node->now());
-                    }
-                    else {
-                        RR->t->incomingPacketMessageAuthenticationFailure(tPtr, _path, pid, fromAddress, hops(), "invalid MAC");
-                    }
-                }
-                else {
-                    RR->t->incomingPacketMessageAuthenticationFailure(tPtr, _path, pid, fromAddress, hops(), "invalid identity");
-                }
-
-                return true;
-            }
-            else {
-                // Identity is the same as the one we already have -- check packet integrity
-
-                if (! dearmor(peer->key(), peer->aesKeysIfSupported(), RR->identity)) {
-                    RR->t->incomingPacketMessageAuthenticationFailure(tPtr, _path, pid, fromAddress, hops(), "invalid MAC");
-                    return true;
-                }
-
-                // Continue at // VALID
-            }
-        }   // else if alreadyAuthenticated then continue at // VALID
-    }
-    else {
-        // We don't already have an identity with this address -- validate and learn it
-
-        // Sanity check: this basically can't happen
-        if (alreadyAuthenticated) {
-            RR->t->incomingPacketDroppedHELLO(tPtr, _path, pid, fromAddress, "illegal alreadyAuthenticated state");
-            return true;
-        }
-
-        // Check rate limits
-        if (! RR->node->rateGateIdentityVerification(now, _path->address())) {
-            RR->t->incomingPacketDroppedHELLO(tPtr, _path, pid, fromAddress, "rate limit exceeded");
-            return true;
-        }
-
-        // Check packet integrity and MAC (this is faster than locallyValidate() so do it first to filter out total crap)
-        SharedPtr<Peer> newPeer(new Peer(RR, RR->identity, id));
-        if (! dearmor(newPeer->key(), newPeer->aesKeysIfSupported(), RR->identity)) {
-            RR->t->incomingPacketMessageAuthenticationFailure(tPtr, _path, pid, fromAddress, hops(), "invalid MAC");
-            return true;
-        }
-
-        // Check that identity's address is valid as per the derivation function
-        if (! id.locallyValidate()) {
-            RR->t->incomingPacketDroppedHELLO(tPtr, _path, pid, fromAddress, "invalid identity");
-            return true;
-        }
-
-        peer = RR->topology->addPeer(tPtr, newPeer);
-
-        // Continue at // VALID
-    }
-
-    // VALID -- if we made it here, packet passed identity and authenticity checks!
-
-    // Get external surface address if present (was not in old versions)
-    InetAddress externalSurfaceAddress;
-    if (ptr < size()) {
-        ptr += externalSurfaceAddress.deserialize(*this, ptr);
-        if ((externalSurfaceAddress) && (hops() == 0)) {
-            RR->sa->iam(tPtr, id.address(), _path->localSocket(), _path->address(), externalSurfaceAddress, RR->topology->isUpstream(id), now);
-        }
-    }
-
-    // Get primary planet world ID and world timestamp if present
-    uint64_t planetWorldId = 0;
-    uint64_t planetWorldTimestamp = 0;
-    if ((ptr + 16) <= size()) {
-        planetWorldId = at<uint64_t>(ptr);
-        ptr += 8;
-        planetWorldTimestamp = at<uint64_t>(ptr);
-        ptr += 8;
-    }
-
-    std::vector<std::pair<uint64_t, uint64_t> > moonIdsAndTimestamps;
-    if (ptr < size()) {
-        // Remainder of packet, if present, is encrypted
-        cryptField(peer->key(), ptr, size() - ptr);
-
-        // Get moon IDs and timestamps if present
-        if ((ptr + 2) <= size()) {
-            const unsigned int numMoons = at<uint16_t>(ptr);
-            ptr += 2;
-            for (unsigned int i = 0; i < numMoons; ++i) {
-                if ((World::Type)(*this)[ptr++] == World::TYPE_MOON) {
-                    moonIdsAndTimestamps.push_back(std::pair<uint64_t, uint64_t>(at<uint64_t>(ptr), at<uint64_t>(ptr + 8)));
-                }
-                ptr += 16;
-            }
-        }
-    }
-
-    // Send OK(HELLO) with an echo of the packet's timestamp and some of the same
-    // information about us: version, sent-to address, etc.
-
-    Packet outp(id.address(), RR->identity.address(), Packet::VERB_OK);
-    outp.append((unsigned char)Packet::VERB_HELLO);
-    outp.append((uint64_t)pid);
-    outp.append((uint64_t)timestamp);
-    outp.append((unsigned char)ZT_PROTO_VERSION);
-    outp.append((unsigned char)ZEROTIER_ONE_VERSION_MAJOR);
-    outp.append((unsigned char)ZEROTIER_ONE_VERSION_MINOR);
-    outp.append((uint16_t)ZEROTIER_ONE_VERSION_REVISION);
-    _path->address().serialize(outp);
-    const unsigned int worldUpdateSizeAt = outp.size();
-    outp.addSize(2);   // make room for 16-bit size field
-    if ((planetWorldId) && (RR->topology->planetWorldTimestamp() > planetWorldTimestamp) && (planetWorldId == RR->topology->planetWorldId())) {
-        RR->topology->planet().serialize(outp, false);
-    }
-    if (! moonIdsAndTimestamps.empty()) {
-        std::vector<World> moons(RR->topology->moons());
-        for (std::vector<World>::const_iterator m(moons.begin()); m != moons.end(); ++m) {
-            for (std::vector<std::pair<uint64_t, uint64_t> >::const_iterator i(moonIdsAndTimestamps.begin()); i != moonIdsAndTimestamps.end(); ++i) {
-                if (i->first == m->id()) {
-                    if (m->timestamp() > i->second) {
-                        m->serialize(outp, false);
-                    }
-                    break;
-                }
-            }
-        }
-    }
-    outp.setAt<uint16_t>(worldUpdateSizeAt, (uint16_t)(outp.size() - (worldUpdateSizeAt + 2)));
-
-    outp.armor(peer->key(), true, false, peer->aesKeysIfSupported(), peer->identity());
-    peer->recordOutgoingPacket(_path, outp.packetId(), outp.payloadLength(), outp.verb(), ZT_QOS_NO_FLOW, now);
-    Metrics::pkt_ok_out++;
-    _path->send(RR, tPtr, outp.data(), outp.size(), now);
-
-    peer->setRemoteVersion(protoVersion, vMajor, vMinor, vRevision);   // important for this to go first so received() knows the version
-    peer->received(tPtr, _path, hops(), pid, payloadLength(), Packet::VERB_HELLO, 0, Packet::VERB_NOP, false, 0, ZT_QOS_NO_FLOW);
-
-    return true;
+	Metrics::pkt_hello_in++;
+	const int64_t now = RR->node->now();
+
+	const uint64_t pid = packetId();
+	const Address fromAddress(source());
+	const unsigned int protoVersion = (*this)[ZT_PROTO_VERB_HELLO_IDX_PROTOCOL_VERSION];
+	const unsigned int vMajor = (*this)[ZT_PROTO_VERB_HELLO_IDX_MAJOR_VERSION];
+	const unsigned int vMinor = (*this)[ZT_PROTO_VERB_HELLO_IDX_MINOR_VERSION];
+	const unsigned int vRevision = at<uint16_t>(ZT_PROTO_VERB_HELLO_IDX_REVISION);
+	const int64_t timestamp = at<int64_t>(ZT_PROTO_VERB_HELLO_IDX_TIMESTAMP);
+	Identity id;
+	unsigned int ptr = ZT_PROTO_VERB_HELLO_IDX_IDENTITY + id.deserialize(*this, ZT_PROTO_VERB_HELLO_IDX_IDENTITY);
+
+	if (protoVersion < ZT_PROTO_VERSION_MIN) {
+		RR->t->incomingPacketDroppedHELLO(tPtr, _path, pid, fromAddress, "protocol version too old");
+		return true;
+	}
+	if (fromAddress != id.address()) {
+		RR->t->incomingPacketDroppedHELLO(tPtr, _path, pid, fromAddress, "identity/address mismatch");
+		return true;
+	}
+
+	SharedPtr<Peer> peer(RR->topology->getPeer(tPtr, id.address()));
+	if (peer) {
+		// We already have an identity with this address -- check for collisions
+		if (! alreadyAuthenticated) {
+			if (peer->identity() != id) {
+				// Identity is different from the one we already have -- address collision
+
+				// Check rate limits
+				if (! RR->node->rateGateIdentityVerification(now, _path->address())) {
+					return true;
+				}
+
+				uint8_t key[ZT_SYMMETRIC_KEY_SIZE];
+				if (RR->identity.agree(id, key)) {
+					if (dearmor(key, peer->aesKeysIfSupported(), RR->identity)) {	// ensure packet is authentic, otherwise drop
+						RR->t->incomingPacketDroppedHELLO(tPtr, _path, pid, fromAddress, "address collision");
+						Packet outp(id.address(), RR->identity.address(), Packet::VERB_ERROR);
+						outp.append((uint8_t)Packet::VERB_HELLO);
+						outp.append((uint64_t)pid);
+						outp.append((uint8_t)Packet::ERROR_IDENTITY_COLLISION);
+						outp.armor(key, true, false, peer->aesKeysIfSupported(), peer->identity());
+						Metrics::pkt_error_out++;
+						Metrics::pkt_error_identity_collision_out++;
+						_path->send(RR, tPtr, outp.data(), outp.size(), RR->node->now());
+					}
+					else {
+						RR->t->incomingPacketMessageAuthenticationFailure(tPtr, _path, pid, fromAddress, hops(), "invalid MAC");
+					}
+				}
+				else {
+					RR->t->incomingPacketMessageAuthenticationFailure(tPtr, _path, pid, fromAddress, hops(), "invalid identity");
+				}
+
+				return true;
+			}
+			else {
+				// Identity is the same as the one we already have -- check packet integrity
+
+				if (! dearmor(peer->key(), peer->aesKeysIfSupported(), RR->identity)) {
+					RR->t->incomingPacketMessageAuthenticationFailure(tPtr, _path, pid, fromAddress, hops(), "invalid MAC");
+					return true;
+				}
+
+				// Continue at // VALID
+			}
+		}	// else if alreadyAuthenticated then continue at // VALID
+	}
+	else {
+		// We don't already have an identity with this address -- validate and learn it
+
+		// Sanity check: this basically can't happen
+		if (alreadyAuthenticated) {
+			RR->t->incomingPacketDroppedHELLO(tPtr, _path, pid, fromAddress, "illegal alreadyAuthenticated state");
+			return true;
+		}
+
+		// Check rate limits
+		if (! RR->node->rateGateIdentityVerification(now, _path->address())) {
+			RR->t->incomingPacketDroppedHELLO(tPtr, _path, pid, fromAddress, "rate limit exceeded");
+			return true;
+		}
+
+		// Check packet integrity and MAC (this is faster than locallyValidate() so do it first to filter out total crap)
+		SharedPtr<Peer> newPeer(new Peer(RR, RR->identity, id));
+		if (! dearmor(newPeer->key(), newPeer->aesKeysIfSupported(), RR->identity)) {
+			RR->t->incomingPacketMessageAuthenticationFailure(tPtr, _path, pid, fromAddress, hops(), "invalid MAC");
+			return true;
+		}
+
+		// Check that identity's address is valid as per the derivation function
+		if (! id.locallyValidate()) {
+			RR->t->incomingPacketDroppedHELLO(tPtr, _path, pid, fromAddress, "invalid identity");
+			return true;
+		}
+
+		peer = RR->topology->addPeer(tPtr, newPeer);
+
+		// Continue at // VALID
+	}
+
+	// VALID -- if we made it here, packet passed identity and authenticity checks!
+
+	// Get external surface address if present (was not in old versions)
+	InetAddress externalSurfaceAddress;
+	if (ptr < size()) {
+		ptr += externalSurfaceAddress.deserialize(*this, ptr);
+		if ((externalSurfaceAddress) && (hops() == 0)) {
+			RR->sa->iam(tPtr, id.address(), _path->localSocket(), _path->address(), externalSurfaceAddress, RR->topology->isUpstream(id), now);
+		}
+	}
+
+	// Get primary planet world ID and world timestamp if present
+	uint64_t planetWorldId = 0;
+	uint64_t planetWorldTimestamp = 0;
+	if ((ptr + 16) <= size()) {
+		planetWorldId = at<uint64_t>(ptr);
+		ptr += 8;
+		planetWorldTimestamp = at<uint64_t>(ptr);
+		ptr += 8;
+	}
+
+	std::vector<std::pair<uint64_t, uint64_t> > moonIdsAndTimestamps;
+	if (ptr < size()) {
+		// Remainder of packet, if present, is encrypted
+		cryptField(peer->key(), ptr, size() - ptr);
+
+		// Get moon IDs and timestamps if present
+		if ((ptr + 2) <= size()) {
+			const unsigned int numMoons = at<uint16_t>(ptr);
+			ptr += 2;
+			for (unsigned int i = 0; i < numMoons; ++i) {
+				if ((World::Type)(*this)[ptr++] == World::TYPE_MOON) {
+					moonIdsAndTimestamps.push_back(std::pair<uint64_t, uint64_t>(at<uint64_t>(ptr), at<uint64_t>(ptr + 8)));
+				}
+				ptr += 16;
+			}
+		}
+	}
+
+	// Send OK(HELLO) with an echo of the packet's timestamp and some of the same
+	// information about us: version, sent-to address, etc.
+
+	Packet outp(id.address(), RR->identity.address(), Packet::VERB_OK);
+	outp.append((unsigned char)Packet::VERB_HELLO);
+	outp.append((uint64_t)pid);
+	outp.append((uint64_t)timestamp);
+	outp.append((unsigned char)ZT_PROTO_VERSION);
+	outp.append((unsigned char)ZEROTIER_ONE_VERSION_MAJOR);
+	outp.append((unsigned char)ZEROTIER_ONE_VERSION_MINOR);
+	outp.append((uint16_t)ZEROTIER_ONE_VERSION_REVISION);
+	_path->address().serialize(outp);
+	const unsigned int worldUpdateSizeAt = outp.size();
+	outp.addSize(2);   // make room for 16-bit size field
+	if ((planetWorldId) && (RR->topology->planetWorldTimestamp() > planetWorldTimestamp) && (planetWorldId == RR->topology->planetWorldId())) {
+		RR->topology->planet().serialize(outp, false);
+	}
+	if (! moonIdsAndTimestamps.empty()) {
+		std::vector<World> moons(RR->topology->moons());
+		for (std::vector<World>::const_iterator m(moons.begin()); m != moons.end(); ++m) {
+			for (std::vector<std::pair<uint64_t, uint64_t> >::const_iterator i(moonIdsAndTimestamps.begin()); i != moonIdsAndTimestamps.end(); ++i) {
+				if (i->first == m->id()) {
+					if (m->timestamp() > i->second) {
+						m->serialize(outp, false);
+					}
+					break;
+				}
+			}
+		}
+	}
+	outp.setAt<uint16_t>(worldUpdateSizeAt, (uint16_t)(outp.size() - (worldUpdateSizeAt + 2)));
+
+	outp.armor(peer->key(), true, false, peer->aesKeysIfSupported(), peer->identity());
+	peer->recordOutgoingPacket(_path, outp.packetId(), outp.payloadLength(), outp.verb(), ZT_QOS_NO_FLOW, now);
+	Metrics::pkt_ok_out++;
+	_path->send(RR, tPtr, outp.data(), outp.size(), now);
+
+	peer->setRemoteVersion(protoVersion, vMajor, vMinor, vRevision);   // important for this to go first so received() knows the version
+	peer->received(tPtr, _path, hops(), pid, payloadLength(), Packet::VERB_HELLO, 0, Packet::VERB_NOP, false, 0, ZT_QOS_NO_FLOW);
+
+	return true;
 }
 
 bool IncomingPacket::_doOK(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer)
 {
-    Metrics::pkt_ok_in++;
-    const Packet::Verb inReVerb = (Packet::Verb)(*this)[ZT_PROTO_VERB_OK_IDX_IN_RE_VERB];
-    const uint64_t inRePacketId = at<uint64_t>(ZT_PROTO_VERB_OK_IDX_IN_RE_PACKET_ID);
-    uint64_t networkId = 0;
-
-    if (! RR->node->expectingReplyTo(inRePacketId)) {
-        return true;
-    }
-
-    switch (inReVerb) {
-        case Packet::VERB_HELLO: {
-            const uint64_t latency = RR->node->now() - at<uint64_t>(ZT_PROTO_VERB_HELLO__OK__IDX_TIMESTAMP);
-            const unsigned int vProto = (*this)[ZT_PROTO_VERB_HELLO__OK__IDX_PROTOCOL_VERSION];
-            const unsigned int vMajor = (*this)[ZT_PROTO_VERB_HELLO__OK__IDX_MAJOR_VERSION];
-            const unsigned int vMinor = (*this)[ZT_PROTO_VERB_HELLO__OK__IDX_MINOR_VERSION];
-            const unsigned int vRevision = at<uint16_t>(ZT_PROTO_VERB_HELLO__OK__IDX_REVISION);
-            if (vProto < ZT_PROTO_VERSION_MIN) {
-                return true;
-            }
-
-            InetAddress externalSurfaceAddress;
-            unsigned int ptr = ZT_PROTO_VERB_HELLO__OK__IDX_REVISION + 2;
-
-            // Get reported external surface address if present
-            if (ptr < size()) {
-                ptr += externalSurfaceAddress.deserialize(*this, ptr);
-            }
-
-            // Handle planet or moon updates if present
-            if ((ptr + 2) <= size()) {
-                const unsigned int worldsLen = at<uint16_t>(ptr);
-                ptr += 2;
-                if (RR->topology->shouldAcceptWorldUpdateFrom(peer->address())) {
-                    const unsigned int endOfWorlds = ptr + worldsLen;
-                    while (ptr < endOfWorlds) {
-                        World w;
-                        ptr += w.deserialize(*this, ptr);
-                        RR->topology->addWorld(tPtr, w, false);
-                    }
-                }
-                else {
-                    ptr += worldsLen;
-                }
-            }
-
-            if (! hops()) {
-                _path->updateLatency((unsigned int)latency, RR->node->now());
-            }
-
-            peer->setRemoteVersion(vProto, vMajor, vMinor, vRevision);
-
-            if ((externalSurfaceAddress) && (hops() == 0)) {
-                RR->sa->iam(tPtr, peer->address(), _path->localSocket(), _path->address(), externalSurfaceAddress, RR->topology->isUpstream(peer->identity()), RR->node->now());
-            }
-        } break;
-
-        case Packet::VERB_WHOIS:
-            if (RR->topology->isUpstream(peer->identity())) {
-                const Identity id(*this, ZT_PROTO_VERB_WHOIS__OK__IDX_IDENTITY);
-                RR->sw->doAnythingWaitingForPeer(tPtr, RR->topology->addPeer(tPtr, SharedPtr<Peer>(new Peer(RR, RR->identity, id))));
-            }
-            break;
-
-        case Packet::VERB_NETWORK_CONFIG_REQUEST: {
-            networkId = at<uint64_t>(ZT_PROTO_VERB_OK_IDX_PAYLOAD);
-            const SharedPtr<Network> network(RR->node->network(networkId));
-            if (network) {
-                network->handleConfigChunk(tPtr, packetId(), source(), *this, ZT_PROTO_VERB_OK_IDX_PAYLOAD);
-            }
-        } break;
-
-        case Packet::VERB_MULTICAST_GATHER: {
-            networkId = at<uint64_t>(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_NETWORK_ID);
-            const SharedPtr<Network> network(RR->node->network(networkId));
-            if (network) {
-                const MulticastGroup mg(MAC(field(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_MAC, 6), 6), at<uint32_t>(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_ADI));
-                const unsigned int count = at<uint16_t>(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_GATHER_RESULTS + 4);
-                RR->mc->addMultiple(tPtr, RR->node->now(), networkId, mg, field(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_GATHER_RESULTS + 6, count * 5), count, at<uint32_t>(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_GATHER_RESULTS));
-            }
-        } break;
-
-        case Packet::VERB_MULTICAST_FRAME: {
-            const unsigned int flags = (*this)[ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_FLAGS];
-            networkId = at<uint64_t>(ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_NETWORK_ID);
-            const MulticastGroup mg(MAC(field(ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_MAC, 6), 6), at<uint32_t>(ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_ADI));
-
-            const SharedPtr<Network> network(RR->node->network(networkId));
-            if (network) {
-                unsigned int offset = 0;
-
-                if ((flags & 0x01) != 0) {   // deprecated but still used by older peers
-                    CertificateOfMembership com;
-                    offset += com.deserialize(*this, ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_COM_AND_GATHER_RESULTS);
-                    if (com) {
-                        network->addCredential(tPtr, com);
-                    }
-                }
-
-                if ((flags & 0x02) != 0) {
-                    // OK(MULTICAST_FRAME) includes implicit gather results
-                    offset += ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_COM_AND_GATHER_RESULTS;
-                    unsigned int totalKnown = at<uint32_t>(offset);
-                    offset += 4;
-                    unsigned int count = at<uint16_t>(offset);
-                    offset += 2;
-                    RR->mc->addMultiple(tPtr, RR->node->now(), networkId, mg, field(offset, count * 5), count, totalKnown);
-                }
-            }
-        } break;
-
-        default:
-            break;
-    }
-
-    peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_OK, inRePacketId, inReVerb, false, networkId, ZT_QOS_NO_FLOW);
-
-    return true;
+	Metrics::pkt_ok_in++;
+	const Packet::Verb inReVerb = (Packet::Verb)(*this)[ZT_PROTO_VERB_OK_IDX_IN_RE_VERB];
+	const uint64_t inRePacketId = at<uint64_t>(ZT_PROTO_VERB_OK_IDX_IN_RE_PACKET_ID);
+	uint64_t networkId = 0;
+
+	if (! RR->node->expectingReplyTo(inRePacketId)) {
+		return true;
+	}
+
+	switch (inReVerb) {
+		case Packet::VERB_HELLO: {
+			const uint64_t latency = RR->node->now() - at<uint64_t>(ZT_PROTO_VERB_HELLO__OK__IDX_TIMESTAMP);
+			const unsigned int vProto = (*this)[ZT_PROTO_VERB_HELLO__OK__IDX_PROTOCOL_VERSION];
+			const unsigned int vMajor = (*this)[ZT_PROTO_VERB_HELLO__OK__IDX_MAJOR_VERSION];
+			const unsigned int vMinor = (*this)[ZT_PROTO_VERB_HELLO__OK__IDX_MINOR_VERSION];
+			const unsigned int vRevision = at<uint16_t>(ZT_PROTO_VERB_HELLO__OK__IDX_REVISION);
+			if (vProto < ZT_PROTO_VERSION_MIN) {
+				return true;
+			}
+
+			InetAddress externalSurfaceAddress;
+			unsigned int ptr = ZT_PROTO_VERB_HELLO__OK__IDX_REVISION + 2;
+
+			// Get reported external surface address if present
+			if (ptr < size()) {
+				ptr += externalSurfaceAddress.deserialize(*this, ptr);
+			}
+
+			// Handle planet or moon updates if present
+			if ((ptr + 2) <= size()) {
+				const unsigned int worldsLen = at<uint16_t>(ptr);
+				ptr += 2;
+				if (RR->topology->shouldAcceptWorldUpdateFrom(peer->address())) {
+					const unsigned int endOfWorlds = ptr + worldsLen;
+					while (ptr < endOfWorlds) {
+						World w;
+						ptr += w.deserialize(*this, ptr);
+						RR->topology->addWorld(tPtr, w, false);
+					}
+				}
+				else {
+					ptr += worldsLen;
+				}
+			}
+
+			if (! hops()) {
+				_path->updateLatency((unsigned int)latency, RR->node->now());
+			}
+
+			peer->setRemoteVersion(vProto, vMajor, vMinor, vRevision);
+
+			if ((externalSurfaceAddress) && (hops() == 0)) {
+				RR->sa->iam(tPtr, peer->address(), _path->localSocket(), _path->address(), externalSurfaceAddress, RR->topology->isUpstream(peer->identity()), RR->node->now());
+			}
+		} break;
+
+		case Packet::VERB_WHOIS:
+			if (RR->topology->isUpstream(peer->identity())) {
+				const Identity id(*this, ZT_PROTO_VERB_WHOIS__OK__IDX_IDENTITY);
+				RR->sw->doAnythingWaitingForPeer(tPtr, RR->topology->addPeer(tPtr, SharedPtr<Peer>(new Peer(RR, RR->identity, id))));
+			}
+			break;
+
+		case Packet::VERB_NETWORK_CONFIG_REQUEST: {
+			networkId = at<uint64_t>(ZT_PROTO_VERB_OK_IDX_PAYLOAD);
+			const SharedPtr<Network> network(RR->node->network(networkId));
+			if (network) {
+				network->handleConfigChunk(tPtr, packetId(), source(), *this, ZT_PROTO_VERB_OK_IDX_PAYLOAD);
+			}
+		} break;
+
+		case Packet::VERB_MULTICAST_GATHER: {
+			networkId = at<uint64_t>(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_NETWORK_ID);
+			const SharedPtr<Network> network(RR->node->network(networkId));
+			if (network) {
+				const MulticastGroup mg(MAC(field(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_MAC, 6), 6), at<uint32_t>(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_ADI));
+				const unsigned int count = at<uint16_t>(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_GATHER_RESULTS + 4);
+				RR->mc->addMultiple(tPtr, RR->node->now(), networkId, mg, field(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_GATHER_RESULTS + 6, count * 5), count, at<uint32_t>(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_GATHER_RESULTS));
+			}
+		} break;
+
+		case Packet::VERB_MULTICAST_FRAME: {
+			const unsigned int flags = (*this)[ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_FLAGS];
+			networkId = at<uint64_t>(ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_NETWORK_ID);
+			const MulticastGroup mg(MAC(field(ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_MAC, 6), 6), at<uint32_t>(ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_ADI));
+
+			const SharedPtr<Network> network(RR->node->network(networkId));
+			if (network) {
+				unsigned int offset = 0;
+
+				if ((flags & 0x01) != 0) {	 // deprecated but still used by older peers
+					CertificateOfMembership com;
+					offset += com.deserialize(*this, ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_COM_AND_GATHER_RESULTS);
+					if (com) {
+						network->addCredential(tPtr, com);
+					}
+				}
+
+				if ((flags & 0x02) != 0) {
+					// OK(MULTICAST_FRAME) includes implicit gather results
+					offset += ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_COM_AND_GATHER_RESULTS;
+					unsigned int totalKnown = at<uint32_t>(offset);
+					offset += 4;
+					unsigned int count = at<uint16_t>(offset);
+					offset += 2;
+					RR->mc->addMultiple(tPtr, RR->node->now(), networkId, mg, field(offset, count * 5), count, totalKnown);
+				}
+			}
+		} break;
+
+		default:
+			break;
+	}
+
+	peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_OK, inRePacketId, inReVerb, false, networkId, ZT_QOS_NO_FLOW);
+
+	return true;
 }
 
 bool IncomingPacket::_doWHOIS(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer)
 {
-    if ((! RR->topology->amUpstream()) && (! peer->rateGateInboundWhoisRequest(RR->node->now()))) {
-        return true;
-    }
-
-    Metrics::pkt_whois_in++;
-
-    Packet outp(peer->address(), RR->identity.address(), Packet::VERB_OK);
-    outp.append((unsigned char)Packet::VERB_WHOIS);
-    outp.append(packetId());
-
-    unsigned int count = 0;
-    unsigned int ptr = ZT_PACKET_IDX_PAYLOAD;
-    while ((ptr + ZT_ADDRESS_LENGTH) <= size()) {
-        const Address addr(field(ptr, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
-        ptr += ZT_ADDRESS_LENGTH;
-
-        const Identity id(RR->topology->getIdentity(tPtr, addr));
-        if (id) {
-            id.serialize(outp, false);
-            ++count;
-        }
-        else {
-            // Request unknown WHOIS from upstream from us (if we have one)
-            RR->sw->requestWhois(tPtr, RR->node->now(), addr);
-        }
-    }
-
-    if (count > 0) {
-        Metrics::pkt_ok_out++;
-        outp.armor(peer->key(), true, false, peer->aesKeysIfSupported(), RR->identity);
-        _path->send(RR, tPtr, outp.data(), outp.size(), RR->node->now());
-    }
-
-    peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_WHOIS, 0, Packet::VERB_NOP, false, 0, ZT_QOS_NO_FLOW);
-
-    return true;
+	if ((! RR->topology->amUpstream()) && (! peer->rateGateInboundWhoisRequest(RR->node->now()))) {
+		return true;
+	}
+
+	Metrics::pkt_whois_in++;
+
+	Packet outp(peer->address(), RR->identity.address(), Packet::VERB_OK);
+	outp.append((unsigned char)Packet::VERB_WHOIS);
+	outp.append(packetId());
+
+	unsigned int count = 0;
+	unsigned int ptr = ZT_PACKET_IDX_PAYLOAD;
+	while ((ptr + ZT_ADDRESS_LENGTH) <= size()) {
+		const Address addr(field(ptr, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
+		ptr += ZT_ADDRESS_LENGTH;
+
+		const Identity id(RR->topology->getIdentity(tPtr, addr));
+		if (id) {
+			id.serialize(outp, false);
+			++count;
+		}
+		else {
+			// Request unknown WHOIS from upstream from us (if we have one)
+			RR->sw->requestWhois(tPtr, RR->node->now(), addr);
+		}
+	}
+
+	if (count > 0) {
+		Metrics::pkt_ok_out++;
+		outp.armor(peer->key(), true, false, peer->aesKeysIfSupported(), RR->identity);
+		_path->send(RR, tPtr, outp.data(), outp.size(), RR->node->now());
+	}
+
+	peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_WHOIS, 0, Packet::VERB_NOP, false, 0, ZT_QOS_NO_FLOW);
+
+	return true;
 }
 
 bool IncomingPacket::_doRENDEZVOUS(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer)
 {
-    Metrics::pkt_rendezvous_in++;
-    if (RR->topology->isUpstream(peer->identity())) {
-        const Address with(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
-        const SharedPtr<Peer> rendezvousWith(RR->topology->getPeer(tPtr, with));
-        if (rendezvousWith) {
-            const unsigned int port = at<uint16_t>(ZT_PROTO_VERB_RENDEZVOUS_IDX_PORT);
-            const unsigned int addrlen = (*this)[ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRLEN];
-            if ((port > 0) && ((addrlen == 4) || (addrlen == 16))) {
-                InetAddress atAddr(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRESS, addrlen), addrlen, port);
-                if (RR->node->shouldUsePathForZeroTierTraffic(tPtr, with, _path->localSocket(), atAddr)) {
-                    const uint64_t junk = RR->node->prng();
-                    RR->node->putPacket(tPtr, _path->localSocket(), atAddr, &junk, 4, 2);   // send low-TTL junk packet to 'open' local NAT(s) and stateful firewalls
-                    rendezvousWith->attemptToContactAt(tPtr, _path->localSocket(), atAddr, RR->node->now(), false);
-                }
-            }
-        }
-    }
-
-    peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_RENDEZVOUS, 0, Packet::VERB_NOP, false, 0, ZT_QOS_NO_FLOW);
-
-    return true;
+	Metrics::pkt_rendezvous_in++;
+	if (RR->topology->isUpstream(peer->identity())) {
+		const Address with(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
+		const SharedPtr<Peer> rendezvousWith(RR->topology->getPeer(tPtr, with));
+		if (rendezvousWith) {
+			const unsigned int port = at<uint16_t>(ZT_PROTO_VERB_RENDEZVOUS_IDX_PORT);
+			const unsigned int addrlen = (*this)[ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRLEN];
+			if ((port > 0) && ((addrlen == 4) || (addrlen == 16))) {
+				InetAddress atAddr(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRESS, addrlen), addrlen, port);
+				if (RR->node->shouldUsePathForZeroTierTraffic(tPtr, with, _path->localSocket(), atAddr)) {
+					const uint64_t junk = RR->node->prng();
+					RR->node->putPacket(tPtr, _path->localSocket(), atAddr, &junk, 4, 2);	// send low-TTL junk packet to 'open' local NAT(s) and stateful firewalls
+					rendezvousWith->attemptToContactAt(tPtr, _path->localSocket(), atAddr, RR->node->now(), false);
+				}
+			}
+		}
+	}
+
+	peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_RENDEZVOUS, 0, Packet::VERB_NOP, false, 0, ZT_QOS_NO_FLOW);
+
+	return true;
 }
 
 // Returns true if packet appears valid; pos and proto will be set
 static bool _ipv6GetPayload(const uint8_t* frameData, unsigned int frameLen, unsigned int& pos, unsigned int& proto)
 {
-    if (frameLen < 40) {
-        return false;
-    }
-    pos = 40;
-    proto = frameData[6];
-    while (pos <= frameLen) {
-        switch (proto) {
-            case 0:     // hop-by-hop options
-            case 43:    // routing
-            case 60:    // destination options
-            case 135:   // mobility options
-                if ((pos + 8) > frameLen) {
-                    return false;   // invalid!
-                }
-                proto = frameData[pos];
-                pos += ((unsigned int)frameData[pos + 1] * 8) + 8;
-                break;
-
-            // case 44: // fragment -- we currently can't parse these and they are deprecated in IPv6 anyway
-            // case 50:
-            // case 51: // IPSec ESP and AH -- we have to stop here since this is encrypted stuff
-            default:
-                return true;
-        }
-    }
-    return false;   // overflow == invalid
+	if (frameLen < 40) {
+		return false;
+	}
+	pos = 40;
+	proto = frameData[6];
+	while (pos <= frameLen) {
+		switch (proto) {
+			case 0:		// hop-by-hop options
+			case 43:	// routing
+			case 60:	// destination options
+			case 135:	// mobility options
+				if ((pos + 8) > frameLen) {
+					return false;	// invalid!
+				}
+				proto = frameData[pos];
+				pos += ((unsigned int)frameData[pos + 1] * 8) + 8;
+				break;
+
+			// case 44: // fragment -- we currently can't parse these and they are deprecated in IPv6 anyway
+			// case 50:
+			// case 51: // IPSec ESP and AH -- we have to stop here since this is encrypted stuff
+			default:
+				return true;
+		}
+	}
+	return false;	// overflow == invalid
 }
 
 bool IncomingPacket::_doFRAME(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer, int32_t flowId)
 {
-    Metrics::pkt_frame_in++;
-    int32_t _flowId = ZT_QOS_NO_FLOW;
-
-    if (size() > ZT_PROTO_VERB_EXT_FRAME_IDX_PAYLOAD) {
-        const unsigned int etherType = at<uint16_t>(ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE);
-        const unsigned int frameLen = size() - ZT_PROTO_VERB_FRAME_IDX_PAYLOAD;
-        const uint8_t* const frameData = reinterpret_cast<const uint8_t*>(data()) + ZT_PROTO_VERB_FRAME_IDX_PAYLOAD;
-
-        if (etherType == ZT_ETHERTYPE_IPV4 && (frameLen >= 20)) {
-            uint16_t srcPort = 0;
-            uint16_t dstPort = 0;
-            uint8_t proto = (reinterpret_cast<const uint8_t*>(frameData)[9]);
-            const unsigned int headerLen = 4 * (reinterpret_cast<const uint8_t*>(frameData)[0] & 0xf);
-            switch (proto) {
-                case 0x01:   // ICMP
-                    // flowId = 0x01;
-                    break;
-                // All these start with 16-bit source and destination port in that order
-                case 0x06:   // TCP
-                case 0x11:   // UDP
-                case 0x84:   // SCTP
-                case 0x88:   // UDPLite
-                    if (frameLen > (headerLen + 4)) {
-                        unsigned int pos = headerLen + 0;
-                        srcPort = (reinterpret_cast<const uint8_t*>(frameData)[pos++]) << 8;
-                        srcPort |= (reinterpret_cast<const uint8_t*>(frameData)[pos]);
-                        pos++;
-                        dstPort = (reinterpret_cast<const uint8_t*>(frameData)[pos++]) << 8;
-                        dstPort |= (reinterpret_cast<const uint8_t*>(frameData)[pos]);
-                        _flowId = dstPort ^ srcPort ^ proto;
-                    }
-                    break;
-            }
-        }
-
-        if (etherType == ZT_ETHERTYPE_IPV6 && (frameLen >= 40)) {
-            uint16_t srcPort = 0;
-            uint16_t dstPort = 0;
-            unsigned int pos;
-            unsigned int proto;
-            _ipv6GetPayload((const uint8_t*)frameData, frameLen, pos, proto);
-            switch (proto) {
-                case 0x3A:   // ICMPv6
-                    // flowId = 0x3A;
-                    break;
-                // All these start with 16-bit source and destination port in that order
-                case 0x06:   // TCP
-                case 0x11:   // UDP
-                case 0x84:   // SCTP
-                case 0x88:   // UDPLite
-                    if (frameLen > (pos + 4)) {
-                        srcPort = (reinterpret_cast<const uint8_t*>(frameData)[pos++]) << 8;
-                        srcPort |= (reinterpret_cast<const uint8_t*>(frameData)[pos]);
-                        pos++;
-                        dstPort = (reinterpret_cast<const uint8_t*>(frameData)[pos++]) << 8;
-                        dstPort |= (reinterpret_cast<const uint8_t*>(frameData)[pos]);
-                        _flowId = dstPort ^ srcPort ^ proto;
-                    }
-                    break;
-                default:
-                    break;
-            }
-        }
-    }
-
-    const uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID);
-    const SharedPtr<Network> network(RR->node->network(nwid));
-    bool trustEstablished = false;
-    if (network) {
-        if (network->gate(tPtr, peer)) {
-            trustEstablished = true;
-            if (size() > ZT_PROTO_VERB_FRAME_IDX_PAYLOAD) {
-                const unsigned int etherType = at<uint16_t>(ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE);
-                const MAC sourceMac(peer->address(), nwid);
-                const unsigned int frameLen = size() - ZT_PROTO_VERB_FRAME_IDX_PAYLOAD;
-                const uint8_t* const frameData = reinterpret_cast<const uint8_t*>(data()) + ZT_PROTO_VERB_FRAME_IDX_PAYLOAD;
-                if (network->filterIncomingPacket(tPtr, peer, RR->identity.address(), sourceMac, network->mac(), frameData, frameLen, etherType, 0) > 0) {
-                    RR->pm->putFrame(tPtr, nwid, network->userPtr(), sourceMac, network->mac(), etherType, 0, (const void*)frameData, frameLen, _flowId);
-                }
-            }
-        }
-        else {
-            _sendErrorNeedCredentials(RR, tPtr, peer, nwid);
-            return false;
-        }
-    }
-    peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_FRAME, 0, Packet::VERB_NOP, trustEstablished, nwid, _flowId);
-
-    return true;
+	Metrics::pkt_frame_in++;
+	int32_t _flowId = ZT_QOS_NO_FLOW;
+
+	if (size() > ZT_PROTO_VERB_EXT_FRAME_IDX_PAYLOAD) {
+		const unsigned int etherType = at<uint16_t>(ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE);
+		const unsigned int frameLen = size() - ZT_PROTO_VERB_FRAME_IDX_PAYLOAD;
+		const uint8_t* const frameData = reinterpret_cast<const uint8_t*>(data()) + ZT_PROTO_VERB_FRAME_IDX_PAYLOAD;
+
+		if (etherType == ZT_ETHERTYPE_IPV4 && (frameLen >= 20)) {
+			uint16_t srcPort = 0;
+			uint16_t dstPort = 0;
+			uint8_t proto = (reinterpret_cast<const uint8_t*>(frameData)[9]);
+			const unsigned int headerLen = 4 * (reinterpret_cast<const uint8_t*>(frameData)[0] & 0xf);
+			switch (proto) {
+				case 0x01:	 // ICMP
+					// flowId = 0x01;
+					break;
+				// All these start with 16-bit source and destination port in that order
+				case 0x06:	 // TCP
+				case 0x11:	 // UDP
+				case 0x84:	 // SCTP
+				case 0x88:	 // UDPLite
+					if (frameLen > (headerLen + 4)) {
+						unsigned int pos = headerLen + 0;
+						srcPort = (reinterpret_cast<const uint8_t*>(frameData)[pos++]) << 8;
+						srcPort |= (reinterpret_cast<const uint8_t*>(frameData)[pos]);
+						pos++;
+						dstPort = (reinterpret_cast<const uint8_t*>(frameData)[pos++]) << 8;
+						dstPort |= (reinterpret_cast<const uint8_t*>(frameData)[pos]);
+						_flowId = dstPort ^ srcPort ^ proto;
+					}
+					break;
+			}
+		}
+
+		if (etherType == ZT_ETHERTYPE_IPV6 && (frameLen >= 40)) {
+			uint16_t srcPort = 0;
+			uint16_t dstPort = 0;
+			unsigned int pos;
+			unsigned int proto;
+			_ipv6GetPayload((const uint8_t*)frameData, frameLen, pos, proto);
+			switch (proto) {
+				case 0x3A:	 // ICMPv6
+					// flowId = 0x3A;
+					break;
+				// All these start with 16-bit source and destination port in that order
+				case 0x06:	 // TCP
+				case 0x11:	 // UDP
+				case 0x84:	 // SCTP
+				case 0x88:	 // UDPLite
+					if (frameLen > (pos + 4)) {
+						srcPort = (reinterpret_cast<const uint8_t*>(frameData)[pos++]) << 8;
+						srcPort |= (reinterpret_cast<const uint8_t*>(frameData)[pos]);
+						pos++;
+						dstPort = (reinterpret_cast<const uint8_t*>(frameData)[pos++]) << 8;
+						dstPort |= (reinterpret_cast<const uint8_t*>(frameData)[pos]);
+						_flowId = dstPort ^ srcPort ^ proto;
+					}
+					break;
+				default:
+					break;
+			}
+		}
+	}
+
+	const uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID);
+	const SharedPtr<Network> network(RR->node->network(nwid));
+	bool trustEstablished = false;
+	if (network) {
+		if (network->gate(tPtr, peer)) {
+			trustEstablished = true;
+			if (size() > ZT_PROTO_VERB_FRAME_IDX_PAYLOAD) {
+				const unsigned int etherType = at<uint16_t>(ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE);
+				const MAC sourceMac(peer->address(), nwid);
+				const unsigned int frameLen = size() - ZT_PROTO_VERB_FRAME_IDX_PAYLOAD;
+				const uint8_t* const frameData = reinterpret_cast<const uint8_t*>(data()) + ZT_PROTO_VERB_FRAME_IDX_PAYLOAD;
+				if (network->filterIncomingPacket(tPtr, peer, RR->identity.address(), sourceMac, network->mac(), frameData, frameLen, etherType, 0) > 0) {
+					RR->pm->putFrame(tPtr, nwid, network->userPtr(), sourceMac, network->mac(), etherType, 0, (const void*)frameData, frameLen, _flowId);
+				}
+			}
+		}
+		else {
+			_sendErrorNeedCredentials(RR, tPtr, peer, nwid);
+			return false;
+		}
+	}
+	peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_FRAME, 0, Packet::VERB_NOP, trustEstablished, nwid, _flowId);
+
+	return true;
 }
 
 bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer, int32_t flowId)
 {
-    Metrics::pkt_ext_frame_in++;
-    const uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_EXT_FRAME_IDX_NETWORK_ID);
-    const SharedPtr<Network> network(RR->node->network(nwid));
-    if (network) {
-        const unsigned int flags = (*this)[ZT_PROTO_VERB_EXT_FRAME_IDX_FLAGS];
-
-        unsigned int comLen = 0;
-        if ((flags & 0x01) != 0) {   // inline COM with EXT_FRAME is deprecated but still used with old peers
-            CertificateOfMembership com;
-            comLen = com.deserialize(*this, ZT_PROTO_VERB_EXT_FRAME_IDX_COM);
-            if (com) {
-                network->addCredential(tPtr, com);
-            }
-        }
-
-        if (! network->gate(tPtr, peer)) {
-            RR->t->incomingNetworkAccessDenied(tPtr, network, _path, packetId(), size(), peer->address(), Packet::VERB_EXT_FRAME, true);
-            _sendErrorNeedCredentials(RR, tPtr, peer, nwid);
-            return false;
-        }
-
-        if (size() > ZT_PROTO_VERB_EXT_FRAME_IDX_PAYLOAD) {
-            const unsigned int etherType = at<uint16_t>(comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_ETHERTYPE);
-            const MAC to(field(comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_TO, ZT_PROTO_VERB_EXT_FRAME_LEN_TO), ZT_PROTO_VERB_EXT_FRAME_LEN_TO);
-            const MAC from(field(comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_FROM, ZT_PROTO_VERB_EXT_FRAME_LEN_FROM), ZT_PROTO_VERB_EXT_FRAME_LEN_FROM);
-            const unsigned int frameLen = size() - (comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_PAYLOAD);
-            const uint8_t* const frameData = (const uint8_t*)field(comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_PAYLOAD, frameLen);
-
-            if ((! from) || (from == network->mac())) {
-                peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_EXT_FRAME, 0, Packet::VERB_NOP, true, nwid, flowId);   // trustEstablished because COM is okay
-                return true;
-            }
-
-            switch (network->filterIncomingPacket(tPtr, peer, RR->identity.address(), from, to, frameData, frameLen, etherType, 0)) {
-                case 1:
-                    if (from != MAC(peer->address(), nwid)) {
-                        if (network->config().permitsBridging(peer->address())) {
-                            network->learnBridgeRoute(from, peer->address());
-                        }
-                        else {
-                            RR->t->incomingNetworkFrameDropped(tPtr, network, _path, packetId(), size(), peer->address(), Packet::VERB_EXT_FRAME, from, to, "bridging not allowed (remote)");
-                            peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_EXT_FRAME, 0, Packet::VERB_NOP, true, nwid, flowId);   // trustEstablished because COM is okay
-                            return true;
-                        }
-                    }
-                    else if (to != network->mac()) {
-                        if (to.isMulticast()) {
-                            if (network->config().multicastLimit == 0) {
-                                RR->t->incomingNetworkFrameDropped(tPtr, network, _path, packetId(), size(), peer->address(), Packet::VERB_EXT_FRAME, from, to, "multicast disabled");
-                                peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_EXT_FRAME, 0, Packet::VERB_NOP, true, nwid, flowId);   // trustEstablished because COM is okay
-                                return true;
-                            }
-                        }
-                        else if (! network->config().permitsBridging(RR->identity.address())) {
-                            RR->t->incomingNetworkFrameDropped(tPtr, network, _path, packetId(), size(), peer->address(), Packet::VERB_EXT_FRAME, from, to, "bridging not allowed (local)");
-                            peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_EXT_FRAME, 0, Packet::VERB_NOP, true, nwid, flowId);   // trustEstablished because COM is okay
-                            return true;
-                        }
-                    }
-                    // fall through -- 2 means accept regardless of bridging checks or other restrictions
-                case 2:
-                    RR->pm->putFrame(tPtr, nwid, network->userPtr(), from, to, etherType, 0, (const void*)frameData, frameLen, flowId);
-                    break;
-            }
-        }
-
-        if ((flags & 0x10) != 0) {   // ACK requested
-            Packet outp(peer->address(), RR->identity.address(), Packet::VERB_OK);
-            outp.append((uint8_t)Packet::VERB_EXT_FRAME);
-            outp.append((uint64_t)packetId());
-            outp.append((uint64_t)nwid);
-            const int64_t now = RR->node->now();
-            outp.armor(peer->key(), true, false, peer->aesKeysIfSupported(), peer->identity());
-            peer->recordOutgoingPacket(_path, outp.packetId(), outp.payloadLength(), outp.verb(), ZT_QOS_NO_FLOW, now);
-            Metrics::pkt_ok_out++;
-            _path->send(RR, tPtr, outp.data(), outp.size(), RR->node->now());
-        }
-
-        peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_EXT_FRAME, 0, Packet::VERB_NOP, true, nwid, flowId);
-    }
-    else {
-        peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_EXT_FRAME, 0, Packet::VERB_NOP, false, nwid, flowId);
-    }
-
-    return true;
+	Metrics::pkt_ext_frame_in++;
+	const uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_EXT_FRAME_IDX_NETWORK_ID);
+	const SharedPtr<Network> network(RR->node->network(nwid));
+	if (network) {
+		const unsigned int flags = (*this)[ZT_PROTO_VERB_EXT_FRAME_IDX_FLAGS];
+
+		unsigned int comLen = 0;
+		if ((flags & 0x01) != 0) {	 // inline COM with EXT_FRAME is deprecated but still used with old peers
+			CertificateOfMembership com;
+			comLen = com.deserialize(*this, ZT_PROTO_VERB_EXT_FRAME_IDX_COM);
+			if (com) {
+				network->addCredential(tPtr, com);
+			}
+		}
+
+		if (! network->gate(tPtr, peer)) {
+			RR->t->incomingNetworkAccessDenied(tPtr, network, _path, packetId(), size(), peer->address(), Packet::VERB_EXT_FRAME, true);
+			_sendErrorNeedCredentials(RR, tPtr, peer, nwid);
+			return false;
+		}
+
+		if (size() > ZT_PROTO_VERB_EXT_FRAME_IDX_PAYLOAD) {
+			const unsigned int etherType = at<uint16_t>(comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_ETHERTYPE);
+			const MAC to(field(comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_TO, ZT_PROTO_VERB_EXT_FRAME_LEN_TO), ZT_PROTO_VERB_EXT_FRAME_LEN_TO);
+			const MAC from(field(comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_FROM, ZT_PROTO_VERB_EXT_FRAME_LEN_FROM), ZT_PROTO_VERB_EXT_FRAME_LEN_FROM);
+			const unsigned int frameLen = size() - (comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_PAYLOAD);
+			const uint8_t* const frameData = (const uint8_t*)field(comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_PAYLOAD, frameLen);
+
+			if ((! from) || (from == network->mac())) {
+				peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_EXT_FRAME, 0, Packet::VERB_NOP, true, nwid, flowId);	 // trustEstablished because COM is okay
+				return true;
+			}
+
+			switch (network->filterIncomingPacket(tPtr, peer, RR->identity.address(), from, to, frameData, frameLen, etherType, 0)) {
+				case 1:
+					if (from != MAC(peer->address(), nwid)) {
+						if (network->config().permitsBridging(peer->address())) {
+							network->learnBridgeRoute(from, peer->address());
+						}
+						else {
+							RR->t->incomingNetworkFrameDropped(tPtr, network, _path, packetId(), size(), peer->address(), Packet::VERB_EXT_FRAME, from, to, "bridging not allowed (remote)");
+							peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_EXT_FRAME, 0, Packet::VERB_NOP, true, nwid, flowId);	 // trustEstablished because COM is okay
+							return true;
+						}
+					}
+					else if (to != network->mac()) {
+						if (to.isMulticast()) {
+							if (network->config().multicastLimit == 0) {
+								RR->t->incomingNetworkFrameDropped(tPtr, network, _path, packetId(), size(), peer->address(), Packet::VERB_EXT_FRAME, from, to, "multicast disabled");
+								peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_EXT_FRAME, 0, Packet::VERB_NOP, true, nwid, flowId);	 // trustEstablished because COM is okay
+								return true;
+							}
+						}
+						else if (! network->config().permitsBridging(RR->identity.address())) {
+							RR->t->incomingNetworkFrameDropped(tPtr, network, _path, packetId(), size(), peer->address(), Packet::VERB_EXT_FRAME, from, to, "bridging not allowed (local)");
+							peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_EXT_FRAME, 0, Packet::VERB_NOP, true, nwid, flowId);	 // trustEstablished because COM is okay
+							return true;
+						}
+					}
+					// fall through -- 2 means accept regardless of bridging checks or other restrictions
+				case 2:
+					RR->pm->putFrame(tPtr, nwid, network->userPtr(), from, to, etherType, 0, (const void*)frameData, frameLen, flowId);
+					break;
+			}
+		}
+
+		if ((flags & 0x10) != 0) {	 // ACK requested
+			Packet outp(peer->address(), RR->identity.address(), Packet::VERB_OK);
+			outp.append((uint8_t)Packet::VERB_EXT_FRAME);
+			outp.append((uint64_t)packetId());
+			outp.append((uint64_t)nwid);
+			const int64_t now = RR->node->now();
+			outp.armor(peer->key(), true, false, peer->aesKeysIfSupported(), peer->identity());
+			peer->recordOutgoingPacket(_path, outp.packetId(), outp.payloadLength(), outp.verb(), ZT_QOS_NO_FLOW, now);
+			Metrics::pkt_ok_out++;
+			_path->send(RR, tPtr, outp.data(), outp.size(), RR->node->now());
+		}
+
+		peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_EXT_FRAME, 0, Packet::VERB_NOP, true, nwid, flowId);
+	}
+	else {
+		peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_EXT_FRAME, 0, Packet::VERB_NOP, false, nwid, flowId);
+	}
+
+	return true;
 }
 
 bool IncomingPacket::_doECHO(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer)
 {
-    Metrics::pkt_echo_in++;
-    uint64_t now = RR->node->now();
-    if (! _path->rateGateEchoRequest(now)) {
-        return true;
-    }
-
-    const uint64_t pid = packetId();
-    Packet outp(peer->address(), RR->identity.address(), Packet::VERB_OK);
-    outp.append((unsigned char)Packet::VERB_ECHO);
-    outp.append((uint64_t)pid);
-    if (size() > ZT_PACKET_IDX_PAYLOAD) {
-        outp.append(reinterpret_cast<const unsigned char*>(data()) + ZT_PACKET_IDX_PAYLOAD, size() - ZT_PACKET_IDX_PAYLOAD);
-    }
-    outp.armor(peer->key(), true, false, peer->aesKeysIfSupported(), peer->identity());
-    peer->recordOutgoingPacket(_path, outp.packetId(), outp.payloadLength(), outp.verb(), ZT_QOS_NO_FLOW, now);
-    Metrics::pkt_ok_out++;
-    _path->send(RR, tPtr, outp.data(), outp.size(), RR->node->now());
-
-    peer->received(tPtr, _path, hops(), pid, payloadLength(), Packet::VERB_ECHO, 0, Packet::VERB_NOP, false, 0, ZT_QOS_NO_FLOW);
-
-    return true;
+	Metrics::pkt_echo_in++;
+	uint64_t now = RR->node->now();
+	if (! _path->rateGateEchoRequest(now)) {
+		return true;
+	}
+
+	const uint64_t pid = packetId();
+	Packet outp(peer->address(), RR->identity.address(), Packet::VERB_OK);
+	outp.append((unsigned char)Packet::VERB_ECHO);
+	outp.append((uint64_t)pid);
+	if (size() > ZT_PACKET_IDX_PAYLOAD) {
+		outp.append(reinterpret_cast<const unsigned char*>(data()) + ZT_PACKET_IDX_PAYLOAD, size() - ZT_PACKET_IDX_PAYLOAD);
+	}
+	outp.armor(peer->key(), true, false, peer->aesKeysIfSupported(), peer->identity());
+	peer->recordOutgoingPacket(_path, outp.packetId(), outp.payloadLength(), outp.verb(), ZT_QOS_NO_FLOW, now);
+	Metrics::pkt_ok_out++;
+	_path->send(RR, tPtr, outp.data(), outp.size(), RR->node->now());
+
+	peer->received(tPtr, _path, hops(), pid, payloadLength(), Packet::VERB_ECHO, 0, Packet::VERB_NOP, false, 0, ZT_QOS_NO_FLOW);
+
+	return true;
 }
 
 bool IncomingPacket::_doMULTICAST_LIKE(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer)
 {
-    Metrics::pkt_multicast_like_in++;
-    const int64_t now = RR->node->now();
-    bool authorized = false;
-    uint64_t lastNwid = 0;
-
-    // Packet contains a series of 18-byte network,MAC,ADI tuples
-    for (unsigned int ptr = ZT_PACKET_IDX_PAYLOAD; ptr < size(); ptr += 18) {
-        const uint64_t nwid = at<uint64_t>(ptr);
-        if (nwid != lastNwid) {
-            lastNwid = nwid;
-            SharedPtr<Network> network(RR->node->network(nwid));
-            if (network) {
-                authorized = network->gate(tPtr, peer);
-            }
-            if (! authorized) {
-                authorized = ((RR->topology->amUpstream()) || (RR->node->localControllerHasAuthorized(now, nwid, peer->address())));
-            }
-        }
-        if (authorized) {
-            RR->mc->add(tPtr, now, nwid, MulticastGroup(MAC(field(ptr + 8, 6), 6), at<uint32_t>(ptr + 14)), peer->address());
-        }
-    }
-
-    peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_MULTICAST_LIKE, 0, Packet::VERB_NOP, false, 0, ZT_QOS_NO_FLOW);
-    return true;
+	Metrics::pkt_multicast_like_in++;
+	const int64_t now = RR->node->now();
+	bool authorized = false;
+	uint64_t lastNwid = 0;
+
+	// Packet contains a series of 18-byte network,MAC,ADI tuples
+	for (unsigned int ptr = ZT_PACKET_IDX_PAYLOAD; ptr < size(); ptr += 18) {
+		const uint64_t nwid = at<uint64_t>(ptr);
+		if (nwid != lastNwid) {
+			lastNwid = nwid;
+			SharedPtr<Network> network(RR->node->network(nwid));
+			if (network) {
+				authorized = network->gate(tPtr, peer);
+			}
+			if (! authorized) {
+				authorized = ((RR->topology->amUpstream()) || (RR->node->localControllerHasAuthorized(now, nwid, peer->address())));
+			}
+		}
+		if (authorized) {
+			RR->mc->add(tPtr, now, nwid, MulticastGroup(MAC(field(ptr + 8, 6), 6), at<uint32_t>(ptr + 14)), peer->address());
+		}
+	}
+
+	peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_MULTICAST_LIKE, 0, Packet::VERB_NOP, false, 0, ZT_QOS_NO_FLOW);
+	return true;
 }
 
 bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer)
 {
-    Metrics::pkt_network_credentials_in++;
-    if (! peer->rateGateCredentialsReceived(RR->node->now())) {
-        return true;
-    }
-
-    CertificateOfMembership com;
-    Capability cap;
-    Tag tag;
-    Revocation revocation;
-    CertificateOfOwnership coo;
-    bool trustEstablished = false;
-    SharedPtr<Network> network;
-
-    unsigned int p = ZT_PACKET_IDX_PAYLOAD;
-    while ((p < size()) && ((*this)[p] != 0)) {
-        p += com.deserialize(*this, p);
-        if (com) {
-            network = RR->node->network(com.networkId());
-            if (network) {
-                switch (network->addCredential(tPtr, com)) {
-                    case Membership::ADD_REJECTED:
-                        break;
-                    case Membership::ADD_ACCEPTED_NEW:
-                    case Membership::ADD_ACCEPTED_REDUNDANT:
-                        trustEstablished = true;
-                        break;
-                    case Membership::ADD_DEFERRED_FOR_WHOIS:
-                        return false;
-                }
-            }
-        }
-    }
-    ++p;   // skip trailing 0 after COMs if present
-
-    if (p < size()) {   // older ZeroTier versions do not send capabilities, tags, or revocations
-        const unsigned int numCapabilities = at<uint16_t>(p);
-        p += 2;
-        for (unsigned int i = 0; i < numCapabilities; ++i) {
-            p += cap.deserialize(*this, p);
-            if ((! network) || (network->id() != cap.networkId())) {
-                network = RR->node->network(cap.networkId());
-            }
-            if (network) {
-                switch (network->addCredential(tPtr, cap)) {
-                    case Membership::ADD_REJECTED:
-                        break;
-                    case Membership::ADD_ACCEPTED_NEW:
-                    case Membership::ADD_ACCEPTED_REDUNDANT:
-                        trustEstablished = true;
-                        break;
-                    case Membership::ADD_DEFERRED_FOR_WHOIS:
-                        return false;
-                }
-            }
-        }
-
-        if (p >= size()) {
-            return true;
-        }
-
-        const unsigned int numTags = at<uint16_t>(p);
-        p += 2;
-        for (unsigned int i = 0; i < numTags; ++i) {
-            p += tag.deserialize(*this, p);
-            if ((! network) || (network->id() != tag.networkId())) {
-                network = RR->node->network(tag.networkId());
-            }
-            if (network) {
-                switch (network->addCredential(tPtr, tag)) {
-                    case Membership::ADD_REJECTED:
-                        break;
-                    case Membership::ADD_ACCEPTED_NEW:
-                    case Membership::ADD_ACCEPTED_REDUNDANT:
-                        trustEstablished = true;
-                        break;
-                    case Membership::ADD_DEFERRED_FOR_WHOIS:
-                        return false;
-                }
-            }
-        }
-
-        if (p >= size()) {
-            return true;
-        }
-
-        const unsigned int numRevocations = at<uint16_t>(p);
-        p += 2;
-        for (unsigned int i = 0; i < numRevocations; ++i) {
-            p += revocation.deserialize(*this, p);
-            if ((! network) || (network->id() != revocation.networkId())) {
-                network = RR->node->network(revocation.networkId());
-            }
-            if (network) {
-                switch (network->addCredential(tPtr, peer->address(), revocation)) {
-                    case Membership::ADD_REJECTED:
-                        break;
-                    case Membership::ADD_ACCEPTED_NEW:
-                    case Membership::ADD_ACCEPTED_REDUNDANT:
-                        trustEstablished = true;
-                        break;
-                    case Membership::ADD_DEFERRED_FOR_WHOIS:
-                        return false;
-                }
-            }
-        }
-
-        if (p >= size()) {
-            return true;
-        }
-
-        const unsigned int numCoos = at<uint16_t>(p);
-        p += 2;
-        for (unsigned int i = 0; i < numCoos; ++i) {
-            p += coo.deserialize(*this, p);
-            if ((! network) || (network->id() != coo.networkId())) {
-                network = RR->node->network(coo.networkId());
-            }
-            if (network) {
-                switch (network->addCredential(tPtr, coo)) {
-                    case Membership::ADD_REJECTED:
-                        break;
-                    case Membership::ADD_ACCEPTED_NEW:
-                    case Membership::ADD_ACCEPTED_REDUNDANT:
-                        trustEstablished = true;
-                        break;
-                    case Membership::ADD_DEFERRED_FOR_WHOIS:
-                        return false;
-                }
-            }
-        }
-    }
-
-    peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_NETWORK_CREDENTIALS, 0, Packet::VERB_NOP, trustEstablished, (network) ? network->id() : 0, ZT_QOS_NO_FLOW);
-
-    return true;
+	Metrics::pkt_network_credentials_in++;
+	if (! peer->rateGateCredentialsReceived(RR->node->now())) {
+		return true;
+	}
+
+	CertificateOfMembership com;
+	Capability cap;
+	Tag tag;
+	Revocation revocation;
+	CertificateOfOwnership coo;
+	bool trustEstablished = false;
+	SharedPtr<Network> network;
+
+	unsigned int p = ZT_PACKET_IDX_PAYLOAD;
+	while ((p < size()) && ((*this)[p] != 0)) {
+		p += com.deserialize(*this, p);
+		if (com) {
+			network = RR->node->network(com.networkId());
+			if (network) {
+				switch (network->addCredential(tPtr, com)) {
+					case Membership::ADD_REJECTED:
+						break;
+					case Membership::ADD_ACCEPTED_NEW:
+					case Membership::ADD_ACCEPTED_REDUNDANT:
+						trustEstablished = true;
+						break;
+					case Membership::ADD_DEFERRED_FOR_WHOIS:
+						return false;
+				}
+			}
+		}
+	}
+	++p;   // skip trailing 0 after COMs if present
+
+	if (p < size()) {	// older ZeroTier versions do not send capabilities, tags, or revocations
+		const unsigned int numCapabilities = at<uint16_t>(p);
+		p += 2;
+		for (unsigned int i = 0; i < numCapabilities; ++i) {
+			p += cap.deserialize(*this, p);
+			if ((! network) || (network->id() != cap.networkId())) {
+				network = RR->node->network(cap.networkId());
+			}
+			if (network) {
+				switch (network->addCredential(tPtr, cap)) {
+					case Membership::ADD_REJECTED:
+						break;
+					case Membership::ADD_ACCEPTED_NEW:
+					case Membership::ADD_ACCEPTED_REDUNDANT:
+						trustEstablished = true;
+						break;
+					case Membership::ADD_DEFERRED_FOR_WHOIS:
+						return false;
+				}
+			}
+		}
+
+		if (p >= size()) {
+			return true;
+		}
+
+		const unsigned int numTags = at<uint16_t>(p);
+		p += 2;
+		for (unsigned int i = 0; i < numTags; ++i) {
+			p += tag.deserialize(*this, p);
+			if ((! network) || (network->id() != tag.networkId())) {
+				network = RR->node->network(tag.networkId());
+			}
+			if (network) {
+				switch (network->addCredential(tPtr, tag)) {
+					case Membership::ADD_REJECTED:
+						break;
+					case Membership::ADD_ACCEPTED_NEW:
+					case Membership::ADD_ACCEPTED_REDUNDANT:
+						trustEstablished = true;
+						break;
+					case Membership::ADD_DEFERRED_FOR_WHOIS:
+						return false;
+				}
+			}
+		}
+
+		if (p >= size()) {
+			return true;
+		}
+
+		const unsigned int numRevocations = at<uint16_t>(p);
+		p += 2;
+		for (unsigned int i = 0; i < numRevocations; ++i) {
+			p += revocation.deserialize(*this, p);
+			if ((! network) || (network->id() != revocation.networkId())) {
+				network = RR->node->network(revocation.networkId());
+			}
+			if (network) {
+				switch (network->addCredential(tPtr, peer->address(), revocation)) {
+					case Membership::ADD_REJECTED:
+						break;
+					case Membership::ADD_ACCEPTED_NEW:
+					case Membership::ADD_ACCEPTED_REDUNDANT:
+						trustEstablished = true;
+						break;
+					case Membership::ADD_DEFERRED_FOR_WHOIS:
+						return false;
+				}
+			}
+		}
+
+		if (p >= size()) {
+			return true;
+		}
+
+		const unsigned int numCoos = at<uint16_t>(p);
+		p += 2;
+		for (unsigned int i = 0; i < numCoos; ++i) {
+			p += coo.deserialize(*this, p);
+			if ((! network) || (network->id() != coo.networkId())) {
+				network = RR->node->network(coo.networkId());
+			}
+			if (network) {
+				switch (network->addCredential(tPtr, coo)) {
+					case Membership::ADD_REJECTED:
+						break;
+					case Membership::ADD_ACCEPTED_NEW:
+					case Membership::ADD_ACCEPTED_REDUNDANT:
+						trustEstablished = true;
+						break;
+					case Membership::ADD_DEFERRED_FOR_WHOIS:
+						return false;
+				}
+			}
+		}
+	}
+
+	peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_NETWORK_CREDENTIALS, 0, Packet::VERB_NOP, trustEstablished, (network) ? network->id() : 0, ZT_QOS_NO_FLOW);
+
+	return true;
 }
 
 bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer)
 {
-    Metrics::pkt_network_config_request_in++;
-    const uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID);
-    const unsigned int hopCount = hops();
-    const uint64_t requestPacketId = packetId();
-
-    if (RR->localNetworkController) {
-        const unsigned int metaDataLength = (ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN <= size()) ? at<uint16_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN) : 0;
-        const char* metaDataBytes = (metaDataLength != 0) ? (const char*)field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT, metaDataLength) : (const char*)0;
-        const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> metaData(metaDataBytes, metaDataLength);
-        RR->localNetworkController->request(nwid, (hopCount > 0) ? InetAddress() : _path->address(), requestPacketId, peer->identity(), metaData);
-    }
-    else {
-        Packet outp(peer->address(), RR->identity.address(), Packet::VERB_ERROR);
-        outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST);
-        outp.append(requestPacketId);
-        outp.append((unsigned char)Packet::ERROR_UNSUPPORTED_OPERATION);
-        outp.append(nwid);
-        outp.armor(peer->key(), true, false, peer->aesKeysIfSupported(), peer->identity());
-        Metrics::pkt_error_out++;
-        Metrics::pkt_error_unsupported_op_out++;
-        _path->send(RR, tPtr, outp.data(), outp.size(), RR->node->now());
-    }
-
-    peer->received(tPtr, _path, hopCount, requestPacketId, payloadLength(), Packet::VERB_NETWORK_CONFIG_REQUEST, 0, Packet::VERB_NOP, false, nwid, ZT_QOS_NO_FLOW);
-
-    return true;
+	Metrics::pkt_network_config_request_in++;
+	const uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID);
+	const unsigned int hopCount = hops();
+	const uint64_t requestPacketId = packetId();
+
+	if (RR->localNetworkController) {
+		const unsigned int metaDataLength = (ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN <= size()) ? at<uint16_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN) : 0;
+		const char* metaDataBytes = (metaDataLength != 0) ? (const char*)field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT, metaDataLength) : (const char*)0;
+		const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> metaData(metaDataBytes, metaDataLength);
+		RR->localNetworkController->request(nwid, (hopCount > 0) ? InetAddress() : _path->address(), requestPacketId, peer->identity(), metaData);
+	}
+	else {
+		Packet outp(peer->address(), RR->identity.address(), Packet::VERB_ERROR);
+		outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST);
+		outp.append(requestPacketId);
+		outp.append((unsigned char)Packet::ERROR_UNSUPPORTED_OPERATION);
+		outp.append(nwid);
+		outp.armor(peer->key(), true, false, peer->aesKeysIfSupported(), peer->identity());
+		Metrics::pkt_error_out++;
+		Metrics::pkt_error_unsupported_op_out++;
+		_path->send(RR, tPtr, outp.data(), outp.size(), RR->node->now());
+	}
+
+	peer->received(tPtr, _path, hopCount, requestPacketId, payloadLength(), Packet::VERB_NETWORK_CONFIG_REQUEST, 0, Packet::VERB_NOP, false, nwid, ZT_QOS_NO_FLOW);
+
+	return true;
 }
 
 bool IncomingPacket::_doNETWORK_CONFIG(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer)
 {
-    Metrics::pkt_network_config_in++;
-    const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PACKET_IDX_PAYLOAD)));
-    if (network) {
-        const uint64_t configUpdateId = network->handleConfigChunk(tPtr, packetId(), source(), *this, ZT_PACKET_IDX_PAYLOAD);
-        if (configUpdateId) {
-            Packet outp(peer->address(), RR->identity.address(), Packet::VERB_OK);
-            outp.append((uint8_t)Packet::VERB_ECHO);
-            outp.append((uint64_t)packetId());
-            outp.append((uint64_t)network->id());
-            outp.append((uint64_t)configUpdateId);
-            const int64_t now = RR->node->now();
-            outp.armor(peer->key(), true, false, peer->aesKeysIfSupported(), peer->identity());
-            peer->recordOutgoingPacket(_path, outp.packetId(), outp.payloadLength(), outp.verb(), ZT_QOS_NO_FLOW, now);
-            Metrics::pkt_ok_out++;
-            _path->send(RR, tPtr, outp.data(), outp.size(), RR->node->now());
-        }
-    }
-
-    peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_NETWORK_CONFIG, 0, Packet::VERB_NOP, false, (network) ? network->id() : 0, ZT_QOS_NO_FLOW);
-
-    return true;
+	Metrics::pkt_network_config_in++;
+	const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PACKET_IDX_PAYLOAD)));
+	if (network) {
+		const uint64_t configUpdateId = network->handleConfigChunk(tPtr, packetId(), source(), *this, ZT_PACKET_IDX_PAYLOAD);
+		if (configUpdateId) {
+			Packet outp(peer->address(), RR->identity.address(), Packet::VERB_OK);
+			outp.append((uint8_t)Packet::VERB_ECHO);
+			outp.append((uint64_t)packetId());
+			outp.append((uint64_t)network->id());
+			outp.append((uint64_t)configUpdateId);
+			const int64_t now = RR->node->now();
+			outp.armor(peer->key(), true, false, peer->aesKeysIfSupported(), peer->identity());
+			peer->recordOutgoingPacket(_path, outp.packetId(), outp.payloadLength(), outp.verb(), ZT_QOS_NO_FLOW, now);
+			Metrics::pkt_ok_out++;
+			_path->send(RR, tPtr, outp.data(), outp.size(), RR->node->now());
+		}
+	}
+
+	peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_NETWORK_CONFIG, 0, Packet::VERB_NOP, false, (network) ? network->id() : 0, ZT_QOS_NO_FLOW);
+
+	return true;
 }
 
 bool IncomingPacket::_doMULTICAST_GATHER(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer)
 {
-    Metrics::pkt_multicast_gather_in++;
-    const uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_NETWORK_ID);
-    const unsigned int flags = (*this)[ZT_PROTO_VERB_MULTICAST_GATHER_IDX_FLAGS];
-    const MulticastGroup mg(MAC(field(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_MAC, 6), 6), at<uint32_t>(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_ADI));
-    const unsigned int gatherLimit = at<uint32_t>(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_GATHER_LIMIT);
-
-    const SharedPtr<Network> network(RR->node->network(nwid));
-
-    if ((flags & 0x01) != 0) {
-        try {
-            CertificateOfMembership com;
-            com.deserialize(*this, ZT_PROTO_VERB_MULTICAST_GATHER_IDX_COM);
-            if ((com) && (network)) {
-                network->addCredential(tPtr, com);
-            }
-        }
-        catch (...) {
-        }   // discard invalid COMs
-    }
-
-    const bool trustEstablished = (network) ? network->gate(tPtr, peer) : false;
-    const int64_t now = RR->node->now();
-    if ((gatherLimit > 0) && ((trustEstablished) || (RR->topology->amUpstream()) || (RR->node->localControllerHasAuthorized(now, nwid, peer->address())))) {
-        Packet outp(peer->address(), RR->identity.address(), Packet::VERB_OK);
-        outp.append((unsigned char)Packet::VERB_MULTICAST_GATHER);
-        outp.append(packetId());
-        outp.append(nwid);
-        mg.mac().appendTo(outp);
-        outp.append((uint32_t)mg.adi());
-        const unsigned int gatheredLocally = RR->mc->gather(peer->address(), nwid, mg, outp, gatherLimit);
-        if (gatheredLocally > 0) {
-            outp.armor(peer->key(), true, false, peer->aesKeysIfSupported(), peer->identity());
-            peer->recordOutgoingPacket(_path, outp.packetId(), outp.payloadLength(), outp.verb(), ZT_QOS_NO_FLOW, now);
-            Metrics::pkt_ok_out++;
-            _path->send(RR, tPtr, outp.data(), outp.size(), now);
-        }
-    }
-
-    peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_MULTICAST_GATHER, 0, Packet::VERB_NOP, trustEstablished, nwid, ZT_QOS_NO_FLOW);
-
-    return true;
+	Metrics::pkt_multicast_gather_in++;
+	const uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_NETWORK_ID);
+	const unsigned int flags = (*this)[ZT_PROTO_VERB_MULTICAST_GATHER_IDX_FLAGS];
+	const MulticastGroup mg(MAC(field(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_MAC, 6), 6), at<uint32_t>(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_ADI));
+	const unsigned int gatherLimit = at<uint32_t>(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_GATHER_LIMIT);
+
+	const SharedPtr<Network> network(RR->node->network(nwid));
+
+	if ((flags & 0x01) != 0) {
+		try {
+			CertificateOfMembership com;
+			com.deserialize(*this, ZT_PROTO_VERB_MULTICAST_GATHER_IDX_COM);
+			if ((com) && (network)) {
+				network->addCredential(tPtr, com);
+			}
+		}
+		catch (...) {
+		}	// discard invalid COMs
+	}
+
+	const bool trustEstablished = (network) ? network->gate(tPtr, peer) : false;
+	const int64_t now = RR->node->now();
+	if ((gatherLimit > 0) && ((trustEstablished) || (RR->topology->amUpstream()) || (RR->node->localControllerHasAuthorized(now, nwid, peer->address())))) {
+		Packet outp(peer->address(), RR->identity.address(), Packet::VERB_OK);
+		outp.append((unsigned char)Packet::VERB_MULTICAST_GATHER);
+		outp.append(packetId());
+		outp.append(nwid);
+		mg.mac().appendTo(outp);
+		outp.append((uint32_t)mg.adi());
+		const unsigned int gatheredLocally = RR->mc->gather(peer->address(), nwid, mg, outp, gatherLimit);
+		if (gatheredLocally > 0) {
+			outp.armor(peer->key(), true, false, peer->aesKeysIfSupported(), peer->identity());
+			peer->recordOutgoingPacket(_path, outp.packetId(), outp.payloadLength(), outp.verb(), ZT_QOS_NO_FLOW, now);
+			Metrics::pkt_ok_out++;
+			_path->send(RR, tPtr, outp.data(), outp.size(), now);
+		}
+	}
+
+	peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_MULTICAST_GATHER, 0, Packet::VERB_NOP, trustEstablished, nwid, ZT_QOS_NO_FLOW);
+
+	return true;
 }
 
 bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer)
 {
-    Metrics::pkt_multicast_frame_in++;
-    const uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID);
-    const unsigned int flags = (*this)[ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS];
-
-    const SharedPtr<Network> network(RR->node->network(nwid));
-    if (network) {
-        // Offset -- size of optional fields added to position of later fields
-        unsigned int offset = 0;
-
-        if ((flags & 0x01) != 0) {
-            // This is deprecated but may still be sent by old peers
-            CertificateOfMembership com;
-            offset += com.deserialize(*this, ZT_PROTO_VERB_MULTICAST_FRAME_IDX_COM);
-            if (com) {
-                network->addCredential(tPtr, com);
-            }
-        }
-
-        if (! network->gate(tPtr, peer)) {
-            _sendErrorNeedCredentials(RR, tPtr, peer, nwid);
-            return false;
-        }
-
-        unsigned int gatherLimit = 0;
-        if ((flags & 0x02) != 0) {
-            gatherLimit = at<uint32_t>(offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_GATHER_LIMIT);
-            offset += 4;
-        }
-
-        MAC from;
-        if ((flags & 0x04) != 0) {
-            from.setTo(field(offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SOURCE_MAC, 6), 6);
-            offset += 6;
-        }
-        else {
-            from.fromAddress(peer->address(), nwid);
-        }
-
-        const MulticastGroup to(MAC(field(offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DEST_MAC, 6), 6), at<uint32_t>(offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DEST_ADI));
-        const unsigned int etherType = at<uint16_t>(offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ETHERTYPE);
-        const unsigned int frameLen = size() - (offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME);
-
-        if (network->config().multicastLimit == 0) {
-            RR->t->incomingNetworkFrameDropped(tPtr, network, _path, packetId(), size(), peer->address(), Packet::VERB_MULTICAST_FRAME, from, to.mac(), "multicast disabled");
-            peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_MULTICAST_FRAME, 0, Packet::VERB_NOP, false, nwid, ZT_QOS_NO_FLOW);
-            return true;
-        }
-
-        if ((frameLen > 0) && (frameLen <= ZT_MAX_MTU)) {
-            if (! to.mac().isMulticast()) {
-                RR->t->incomingPacketInvalid(tPtr, _path, packetId(), source(), hops(), Packet::VERB_MULTICAST_FRAME, "destination not multicast");
-                peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_MULTICAST_FRAME, 0, Packet::VERB_NOP, true, nwid, ZT_QOS_NO_FLOW);   // trustEstablished because COM is okay
-                return true;
-            }
-            if ((! from) || (from.isMulticast()) || (from == network->mac())) {
-                RR->t->incomingPacketInvalid(tPtr, _path, packetId(), source(), hops(), Packet::VERB_MULTICAST_FRAME, "invalid source MAC");
-                peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_MULTICAST_FRAME, 0, Packet::VERB_NOP, true, nwid, ZT_QOS_NO_FLOW);   // trustEstablished because COM is okay
-                return true;
-            }
-
-            const uint8_t* const frameData = (const uint8_t*)field(offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME, frameLen);
-
-            if ((flags & 0x08) && (network->config().isMulticastReplicator(RR->identity.address()))) {
-                RR->mc->send(tPtr, RR->node->now(), network, peer->address(), to, from, etherType, frameData, frameLen);
-            }
-
-            if (from != MAC(peer->address(), nwid)) {
-                if (network->config().permitsBridging(peer->address())) {
-                    network->learnBridgeRoute(from, peer->address());
-                }
-                else {
-                    RR->t->incomingNetworkFrameDropped(tPtr, network, _path, packetId(), size(), peer->address(), Packet::VERB_MULTICAST_FRAME, from, to.mac(), "bridging not allowed (remote)");
-                    peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_MULTICAST_FRAME, 0, Packet::VERB_NOP, true, nwid, ZT_QOS_NO_FLOW);   // trustEstablished because COM is okay
-                    return true;
-                }
-            }
-
-            if (network->filterIncomingPacket(tPtr, peer, RR->identity.address(), from, to.mac(), frameData, frameLen, etherType, 0) > 0) {
-                RR->node->putFrame(tPtr, nwid, network->userPtr(), from, to.mac(), etherType, 0, (const void*)frameData, frameLen);
-            }
-        }
-
-        if (gatherLimit) {
-            Packet outp(source(), RR->identity.address(), Packet::VERB_OK);
-            outp.append((unsigned char)Packet::VERB_MULTICAST_FRAME);
-            outp.append(packetId());
-            outp.append(nwid);
-            to.mac().appendTo(outp);
-            outp.append((uint32_t)to.adi());
-            outp.append((unsigned char)0x02);   // flag 0x02 = contains gather results
-            if (RR->mc->gather(peer->address(), nwid, to, outp, gatherLimit)) {
-                const int64_t now = RR->node->now();
-                outp.armor(peer->key(), true, false, peer->aesKeysIfSupported(), peer->identity());
-                peer->recordOutgoingPacket(_path, outp.packetId(), outp.payloadLength(), outp.verb(), ZT_QOS_NO_FLOW, now);
-                Metrics::pkt_ok_out++;
-                _path->send(RR, tPtr, outp.data(), outp.size(), RR->node->now());
-            }
-        }
-
-        peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_MULTICAST_FRAME, 0, Packet::VERB_NOP, true, nwid, ZT_QOS_NO_FLOW);
-    }
-
-    return true;
+	Metrics::pkt_multicast_frame_in++;
+	const uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID);
+	const unsigned int flags = (*this)[ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS];
+
+	const SharedPtr<Network> network(RR->node->network(nwid));
+	if (network) {
+		// Offset -- size of optional fields added to position of later fields
+		unsigned int offset = 0;
+
+		if ((flags & 0x01) != 0) {
+			// This is deprecated but may still be sent by old peers
+			CertificateOfMembership com;
+			offset += com.deserialize(*this, ZT_PROTO_VERB_MULTICAST_FRAME_IDX_COM);
+			if (com) {
+				network->addCredential(tPtr, com);
+			}
+		}
+
+		if (! network->gate(tPtr, peer)) {
+			_sendErrorNeedCredentials(RR, tPtr, peer, nwid);
+			return false;
+		}
+
+		unsigned int gatherLimit = 0;
+		if ((flags & 0x02) != 0) {
+			gatherLimit = at<uint32_t>(offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_GATHER_LIMIT);
+			offset += 4;
+		}
+
+		MAC from;
+		if ((flags & 0x04) != 0) {
+			from.setTo(field(offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SOURCE_MAC, 6), 6);
+			offset += 6;
+		}
+		else {
+			from.fromAddress(peer->address(), nwid);
+		}
+
+		const MulticastGroup to(MAC(field(offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DEST_MAC, 6), 6), at<uint32_t>(offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DEST_ADI));
+		const unsigned int etherType = at<uint16_t>(offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ETHERTYPE);
+		const unsigned int frameLen = size() - (offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME);
+
+		if (network->config().multicastLimit == 0) {
+			RR->t->incomingNetworkFrameDropped(tPtr, network, _path, packetId(), size(), peer->address(), Packet::VERB_MULTICAST_FRAME, from, to.mac(), "multicast disabled");
+			peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_MULTICAST_FRAME, 0, Packet::VERB_NOP, false, nwid, ZT_QOS_NO_FLOW);
+			return true;
+		}
+
+		if ((frameLen > 0) && (frameLen <= ZT_MAX_MTU)) {
+			if (! to.mac().isMulticast()) {
+				RR->t->incomingPacketInvalid(tPtr, _path, packetId(), source(), hops(), Packet::VERB_MULTICAST_FRAME, "destination not multicast");
+				peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_MULTICAST_FRAME, 0, Packet::VERB_NOP, true, nwid, ZT_QOS_NO_FLOW);   // trustEstablished because COM is okay
+				return true;
+			}
+			if ((! from) || (from.isMulticast()) || (from == network->mac())) {
+				RR->t->incomingPacketInvalid(tPtr, _path, packetId(), source(), hops(), Packet::VERB_MULTICAST_FRAME, "invalid source MAC");
+				peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_MULTICAST_FRAME, 0, Packet::VERB_NOP, true, nwid, ZT_QOS_NO_FLOW);   // trustEstablished because COM is okay
+				return true;
+			}
+
+			const uint8_t* const frameData = (const uint8_t*)field(offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME, frameLen);
+
+			if ((flags & 0x08) && (network->config().isMulticastReplicator(RR->identity.address()))) {
+				RR->mc->send(tPtr, RR->node->now(), network, peer->address(), to, from, etherType, frameData, frameLen);
+			}
+
+			if (from != MAC(peer->address(), nwid)) {
+				if (network->config().permitsBridging(peer->address())) {
+					network->learnBridgeRoute(from, peer->address());
+				}
+				else {
+					RR->t->incomingNetworkFrameDropped(tPtr, network, _path, packetId(), size(), peer->address(), Packet::VERB_MULTICAST_FRAME, from, to.mac(), "bridging not allowed (remote)");
+					peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_MULTICAST_FRAME, 0, Packet::VERB_NOP, true, nwid, ZT_QOS_NO_FLOW);   // trustEstablished because COM is okay
+					return true;
+				}
+			}
+
+			if (network->filterIncomingPacket(tPtr, peer, RR->identity.address(), from, to.mac(), frameData, frameLen, etherType, 0) > 0) {
+				RR->node->putFrame(tPtr, nwid, network->userPtr(), from, to.mac(), etherType, 0, (const void*)frameData, frameLen);
+			}
+		}
+
+		if (gatherLimit) {
+			Packet outp(source(), RR->identity.address(), Packet::VERB_OK);
+			outp.append((unsigned char)Packet::VERB_MULTICAST_FRAME);
+			outp.append(packetId());
+			outp.append(nwid);
+			to.mac().appendTo(outp);
+			outp.append((uint32_t)to.adi());
+			outp.append((unsigned char)0x02);	// flag 0x02 = contains gather results
+			if (RR->mc->gather(peer->address(), nwid, to, outp, gatherLimit)) {
+				const int64_t now = RR->node->now();
+				outp.armor(peer->key(), true, false, peer->aesKeysIfSupported(), peer->identity());
+				peer->recordOutgoingPacket(_path, outp.packetId(), outp.payloadLength(), outp.verb(), ZT_QOS_NO_FLOW, now);
+				Metrics::pkt_ok_out++;
+				_path->send(RR, tPtr, outp.data(), outp.size(), RR->node->now());
+			}
+		}
+
+		peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_MULTICAST_FRAME, 0, Packet::VERB_NOP, true, nwid, ZT_QOS_NO_FLOW);
+	}
+
+	return true;
 }
 
 bool IncomingPacket::_doPUSH_DIRECT_PATHS(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer)
 {
-    Metrics::pkt_push_direct_paths_in++;
-    const int64_t now = RR->node->now();
-
-    if (! peer->rateGatePushDirectPaths(now)) {
-        peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_PUSH_DIRECT_PATHS, 0, Packet::VERB_NOP, false, 0, ZT_QOS_NO_FLOW);
-        return true;
-    }
-
-    // Second, limit addresses by scope and type
-    uint8_t countPerScope[ZT_INETADDRESS_MAX_SCOPE + 1][2];   // [][0] is v4, [][1] is v6
-    memset(countPerScope, 0, sizeof(countPerScope));
-
-    unsigned int count = at<uint16_t>(ZT_PACKET_IDX_PAYLOAD);
-    unsigned int ptr = ZT_PACKET_IDX_PAYLOAD + 2;
-
-    while (count--) {   // if ptr overflows Buffer will throw
-        unsigned int flags = (*this)[ptr++];
-        unsigned int extLen = at<uint16_t>(ptr);
-        ptr += 2;
-        ptr += extLen;   // unused right now
-        unsigned int addrType = (*this)[ptr++];
-        unsigned int addrLen = (*this)[ptr++];
-
-        switch (addrType) {
-            case 4: {
-                const InetAddress a(field(ptr, 4), 4, at<uint16_t>(ptr + 4));
-                if (((flags & ZT_PUSH_DIRECT_PATHS_FLAG_FORGET_PATH) == 0) &&                                                 // not being told to forget
-                    (! (((flags & ZT_PUSH_DIRECT_PATHS_FLAG_CLUSTER_REDIRECT) == 0) && (peer->hasActivePathTo(now, a)))) &&   // not already known
-                    (RR->node->shouldUsePathForZeroTierTraffic(tPtr, peer->address(), _path->localSocket(), a)))              // should use path
-                {
-                    if ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_CLUSTER_REDIRECT) != 0) {
-                        peer->clusterRedirect(tPtr, _path, a, now);
-                    }
-                    else if (++countPerScope[(int)a.ipScope()][0] <= ZT_PUSH_DIRECT_PATHS_MAX_PER_SCOPE_AND_FAMILY) {
-                        peer->attemptToContactAt(tPtr, InetAddress(), a, now, false);
-                    }
-                }
-            } break;
-            case 6: {
-                const InetAddress a(field(ptr, 16), 16, at<uint16_t>(ptr + 16));
-                if (((flags & ZT_PUSH_DIRECT_PATHS_FLAG_FORGET_PATH) == 0) &&                                                 // not being told to forget
-                    (! (((flags & ZT_PUSH_DIRECT_PATHS_FLAG_CLUSTER_REDIRECT) == 0) && (peer->hasActivePathTo(now, a)))) &&   // not already known
-                    (RR->node->shouldUsePathForZeroTierTraffic(tPtr, peer->address(), _path->localSocket(), a)))              // should use path
-                {
-                    if ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_CLUSTER_REDIRECT) != 0) {
-                        peer->clusterRedirect(tPtr, _path, a, now);
-                    }
-                    else if (++countPerScope[(int)a.ipScope()][1] <= ZT_PUSH_DIRECT_PATHS_MAX_PER_SCOPE_AND_FAMILY) {
-                        peer->attemptToContactAt(tPtr, InetAddress(), a, now, false);
-                    }
-                }
-            } break;
-        }
-        ptr += addrLen;
-    }
-
-    peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_PUSH_DIRECT_PATHS, 0, Packet::VERB_NOP, false, 0, ZT_QOS_NO_FLOW);
-
-    return true;
+	Metrics::pkt_push_direct_paths_in++;
+	const int64_t now = RR->node->now();
+
+	if (! peer->rateGatePushDirectPaths(now)) {
+		peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_PUSH_DIRECT_PATHS, 0, Packet::VERB_NOP, false, 0, ZT_QOS_NO_FLOW);
+		return true;
+	}
+
+	// Second, limit addresses by scope and type
+	uint8_t countPerScope[ZT_INETADDRESS_MAX_SCOPE + 1][2];	  // [][0] is v4, [][1] is v6
+	memset(countPerScope, 0, sizeof(countPerScope));
+
+	unsigned int count = at<uint16_t>(ZT_PACKET_IDX_PAYLOAD);
+	unsigned int ptr = ZT_PACKET_IDX_PAYLOAD + 2;
+
+	while (count--) {	// if ptr overflows Buffer will throw
+		unsigned int flags = (*this)[ptr++];
+		unsigned int extLen = at<uint16_t>(ptr);
+		ptr += 2;
+		ptr += extLen;	 // unused right now
+		unsigned int addrType = (*this)[ptr++];
+		unsigned int addrLen = (*this)[ptr++];
+
+		switch (addrType) {
+			case 4: {
+				const InetAddress a(field(ptr, 4), 4, at<uint16_t>(ptr + 4));
+				if (((flags & ZT_PUSH_DIRECT_PATHS_FLAG_FORGET_PATH) == 0) &&												  // not being told to forget
+					(! (((flags & ZT_PUSH_DIRECT_PATHS_FLAG_CLUSTER_REDIRECT) == 0) && (peer->hasActivePathTo(now, a)))) &&	  // not already known
+					(RR->node->shouldUsePathForZeroTierTraffic(tPtr, peer->address(), _path->localSocket(), a)))			  // should use path
+				{
+					if ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_CLUSTER_REDIRECT) != 0) {
+						peer->clusterRedirect(tPtr, _path, a, now);
+					}
+					else if (++countPerScope[(int)a.ipScope()][0] <= ZT_PUSH_DIRECT_PATHS_MAX_PER_SCOPE_AND_FAMILY) {
+						peer->attemptToContactAt(tPtr, InetAddress(), a, now, false);
+					}
+				}
+			} break;
+			case 6: {
+				const InetAddress a(field(ptr, 16), 16, at<uint16_t>(ptr + 16));
+				if (((flags & ZT_PUSH_DIRECT_PATHS_FLAG_FORGET_PATH) == 0) &&												  // not being told to forget
+					(! (((flags & ZT_PUSH_DIRECT_PATHS_FLAG_CLUSTER_REDIRECT) == 0) && (peer->hasActivePathTo(now, a)))) &&	  // not already known
+					(RR->node->shouldUsePathForZeroTierTraffic(tPtr, peer->address(), _path->localSocket(), a)))			  // should use path
+				{
+					if ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_CLUSTER_REDIRECT) != 0) {
+						peer->clusterRedirect(tPtr, _path, a, now);
+					}
+					else if (++countPerScope[(int)a.ipScope()][1] <= ZT_PUSH_DIRECT_PATHS_MAX_PER_SCOPE_AND_FAMILY) {
+						peer->attemptToContactAt(tPtr, InetAddress(), a, now, false);
+					}
+				}
+			} break;
+		}
+		ptr += addrLen;
+	}
+
+	peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_PUSH_DIRECT_PATHS, 0, Packet::VERB_NOP, false, 0, ZT_QOS_NO_FLOW);
+
+	return true;
 }
 
 bool IncomingPacket::_doUSER_MESSAGE(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer)
 {
-    Metrics::pkt_user_message_in++;
-    if (likely(size() >= (ZT_PACKET_IDX_PAYLOAD + 8))) {
-        ZT_UserMessage um;
-        um.origin = peer->address().toInt();
-        um.typeId = at<uint64_t>(ZT_PACKET_IDX_PAYLOAD);
-        um.data = reinterpret_cast<const void*>(reinterpret_cast<const uint8_t*>(data()) + ZT_PACKET_IDX_PAYLOAD + 8);
-        um.length = size() - (ZT_PACKET_IDX_PAYLOAD + 8);
-        RR->node->postEvent(tPtr, ZT_EVENT_USER_MESSAGE, reinterpret_cast<const void*>(&um));
-    }
-
-    peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_USER_MESSAGE, 0, Packet::VERB_NOP, false, 0, ZT_QOS_NO_FLOW);
-
-    return true;
+	Metrics::pkt_user_message_in++;
+	if (likely(size() >= (ZT_PACKET_IDX_PAYLOAD + 8))) {
+		ZT_UserMessage um;
+		um.origin = peer->address().toInt();
+		um.typeId = at<uint64_t>(ZT_PACKET_IDX_PAYLOAD);
+		um.data = reinterpret_cast<const void*>(reinterpret_cast<const uint8_t*>(data()) + ZT_PACKET_IDX_PAYLOAD + 8);
+		um.length = size() - (ZT_PACKET_IDX_PAYLOAD + 8);
+		RR->node->postEvent(tPtr, ZT_EVENT_USER_MESSAGE, reinterpret_cast<const void*>(&um));
+	}
+
+	peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_USER_MESSAGE, 0, Packet::VERB_NOP, false, 0, ZT_QOS_NO_FLOW);
+
+	return true;
 }
 
 bool IncomingPacket::_doREMOTE_TRACE(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer)
 {
-    Metrics::pkt_remote_trace_in++;
-    ZT_RemoteTrace rt;
-    const char* ptr = reinterpret_cast<const char*>(data()) + ZT_PACKET_IDX_PAYLOAD;
-    const char* const eof = reinterpret_cast<const char*>(data()) + size();
-    rt.origin = peer->address().toInt();
-    rt.data = const_cast<char*>(ptr);   // start of first string
-    while (ptr < eof) {
-        if (! *ptr) {   // end of string
-            rt.len = (unsigned int)(ptr - rt.data);
-            if ((rt.len > 0) && (rt.len <= ZT_MAX_REMOTE_TRACE_SIZE)) {
-                RR->node->postEvent(tPtr, ZT_EVENT_REMOTE_TRACE, &rt);
-            }
-            rt.data = const_cast<char*>(++ptr);   // start of next string, if any
-        }
-        else {
-            ++ptr;
-        }
-    }
-
-    peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_REMOTE_TRACE, 0, Packet::VERB_NOP, false, 0, ZT_QOS_NO_FLOW);
-
-    return true;
+	Metrics::pkt_remote_trace_in++;
+	ZT_RemoteTrace rt;
+	const char* ptr = reinterpret_cast<const char*>(data()) + ZT_PACKET_IDX_PAYLOAD;
+	const char* const eof = reinterpret_cast<const char*>(data()) + size();
+	rt.origin = peer->address().toInt();
+	rt.data = const_cast<char*>(ptr);	// start of first string
+	while (ptr < eof) {
+		if (! *ptr) {	// end of string
+			rt.len = (unsigned int)(ptr - rt.data);
+			if ((rt.len > 0) && (rt.len <= ZT_MAX_REMOTE_TRACE_SIZE)) {
+				RR->node->postEvent(tPtr, ZT_EVENT_REMOTE_TRACE, &rt);
+			}
+			rt.data = const_cast<char*>(++ptr);	  // start of next string, if any
+		}
+		else {
+			++ptr;
+		}
+	}
+
+	peer->received(tPtr, _path, hops(), packetId(), payloadLength(), Packet::VERB_REMOTE_TRACE, 0, Packet::VERB_NOP, false, 0, ZT_QOS_NO_FLOW);
+
+	return true;
 }
 
 bool IncomingPacket::_doPATH_NEGOTIATION_REQUEST(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer)
 {
-    Metrics::pkt_path_negotiation_request_in++;
-    uint64_t now = RR->node->now();
-    if (! peer->rateGatePathNegotiation(now, _path)) {
-        return true;
-    }
-    if (payloadLength() != sizeof(int16_t)) {
-        return true;
-    }
-    int16_t remoteUtility = 0;
-    memcpy(&remoteUtility, payload(), sizeof(int16_t));
-    peer->processIncomingPathNegotiationRequest(now, _path, Utils::ntoh(remoteUtility));
-    return true;
+	Metrics::pkt_path_negotiation_request_in++;
+	uint64_t now = RR->node->now();
+	if (! peer->rateGatePathNegotiation(now, _path)) {
+		return true;
+	}
+	if (payloadLength() != sizeof(int16_t)) {
+		return true;
+	}
+	int16_t remoteUtility = 0;
+	memcpy(&remoteUtility, payload(), sizeof(int16_t));
+	peer->processIncomingPathNegotiationRequest(now, _path, Utils::ntoh(remoteUtility));
+	return true;
 }
 
 void IncomingPacket::_sendErrorNeedCredentials(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer, const uint64_t nwid)
 {
-    Packet outp(source(), RR->identity.address(), Packet::VERB_ERROR);
-    outp.append((uint8_t)verb());
-    outp.append(packetId());
-    outp.append((uint8_t)Packet::ERROR_NEED_MEMBERSHIP_CERTIFICATE);
-    outp.append(nwid);
-    outp.armor(peer->key(), true, false, peer->aesKeysIfSupported(), peer->identity());
-    Metrics::pkt_error_out++;
-    Metrics::pkt_error_need_membership_cert_out++;
-    _path->send(RR, tPtr, outp.data(), outp.size(), RR->node->now());
+	Packet outp(source(), RR->identity.address(), Packet::VERB_ERROR);
+	outp.append((uint8_t)verb());
+	outp.append(packetId());
+	outp.append((uint8_t)Packet::ERROR_NEED_MEMBERSHIP_CERTIFICATE);
+	outp.append(nwid);
+	outp.armor(peer->key(), true, false, peer->aesKeysIfSupported(), peer->identity());
+	Metrics::pkt_error_out++;
+	Metrics::pkt_error_need_membership_cert_out++;
+	_path->send(RR, tPtr, outp.data(), outp.size(), RR->node->now());
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 85 - 85
node/IncomingPacket.hpp

@@ -48,94 +48,94 @@ class Network;
  */
 class IncomingPacket : public Packet {
   public:
-    IncomingPacket() : Packet(), _receiveTime(0), _path(), _authenticated(false)
-    {
-    }
-
-    /**
-     * Create a new packet-in-decode
-     *
-     * @param data Packet data
-     * @param len Packet length
-     * @param path Path over which packet arrived
-     * @param now Current time
-     * @throws std::out_of_range Range error processing packet
-     */
-    IncomingPacket(const void* data, unsigned int len, const SharedPtr<Path>& path, int64_t now) : Packet(data, len), _receiveTime(now), _path(path), _authenticated(false)
-    {
-    }
-
-    /**
-     * Init packet-in-decode in place
-     *
-     * @param data Packet data
-     * @param len Packet length
-     * @param path Path over which packet arrived
-     * @param now Current time
-     * @throws std::out_of_range Range error processing packet
-     */
-    inline void init(const void* data, unsigned int len, const SharedPtr<Path>& path, int64_t now)
-    {
-        copyFrom(data, len);
-        _receiveTime = now;
-        _path = path;
-        _authenticated = false;
-    }
-
-    /**
-     * Attempt to decode this packet
-     *
-     * Note that this returns 'true' if processing is complete. This says nothing
-     * about whether the packet was valid. A rejection is 'complete.'
-     *
-     * Once true is returned, this must not be called again. The packet's state
-     * may no longer be valid.
-     *
-     * @param RR Runtime environment
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @return True if decoding and processing is complete, false if caller should try again
-     */
-    bool tryDecode(const RuntimeEnvironment* RR, void* tPtr, int32_t flowId);
-
-    /**
-     * @return Time of packet receipt / start of decode
-     */
-    inline uint64_t receiveTime() const
-    {
-        return _receiveTime;
-    }
+	IncomingPacket() : Packet(), _receiveTime(0), _path(), _authenticated(false)
+	{
+	}
+
+	/**
+	 * Create a new packet-in-decode
+	 *
+	 * @param data Packet data
+	 * @param len Packet length
+	 * @param path Path over which packet arrived
+	 * @param now Current time
+	 * @throws std::out_of_range Range error processing packet
+	 */
+	IncomingPacket(const void* data, unsigned int len, const SharedPtr<Path>& path, int64_t now) : Packet(data, len), _receiveTime(now), _path(path), _authenticated(false)
+	{
+	}
+
+	/**
+	 * Init packet-in-decode in place
+	 *
+	 * @param data Packet data
+	 * @param len Packet length
+	 * @param path Path over which packet arrived
+	 * @param now Current time
+	 * @throws std::out_of_range Range error processing packet
+	 */
+	inline void init(const void* data, unsigned int len, const SharedPtr<Path>& path, int64_t now)
+	{
+		copyFrom(data, len);
+		_receiveTime = now;
+		_path = path;
+		_authenticated = false;
+	}
+
+	/**
+	 * Attempt to decode this packet
+	 *
+	 * Note that this returns 'true' if processing is complete. This says nothing
+	 * about whether the packet was valid. A rejection is 'complete.'
+	 *
+	 * Once true is returned, this must not be called again. The packet's state
+	 * may no longer be valid.
+	 *
+	 * @param RR Runtime environment
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @return True if decoding and processing is complete, false if caller should try again
+	 */
+	bool tryDecode(const RuntimeEnvironment* RR, void* tPtr, int32_t flowId);
+
+	/**
+	 * @return Time of packet receipt / start of decode
+	 */
+	inline uint64_t receiveTime() const
+	{
+		return _receiveTime;
+	}
 
   private:
-    // These are called internally to handle packet contents once it has
-    // been authenticated, decrypted, decompressed, and classified.
-    bool _doERROR(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
-    bool _doHELLO(const RuntimeEnvironment* RR, void* tPtr, const bool alreadyAuthenticated);
-    bool _doACK(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
-    bool _doQOS_MEASUREMENT(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
-    bool _doOK(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
-    bool _doWHOIS(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
-    bool _doRENDEZVOUS(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
-    bool _doFRAME(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer, int32_t flowId);
-    bool _doEXT_FRAME(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer, int32_t flowId);
-    bool _doECHO(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
-    bool _doMULTICAST_LIKE(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
-    bool _doNETWORK_CREDENTIALS(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
-    bool _doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
-    bool _doNETWORK_CONFIG(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
-    bool _doMULTICAST_GATHER(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
-    bool _doMULTICAST_FRAME(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
-    bool _doPUSH_DIRECT_PATHS(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
-    bool _doUSER_MESSAGE(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
-    bool _doREMOTE_TRACE(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
-    bool _doPATH_NEGOTIATION_REQUEST(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
-
-    void _sendErrorNeedCredentials(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer, const uint64_t nwid);
-
-    uint64_t _receiveTime;
-    SharedPtr<Path> _path;
-    bool _authenticated;
+	// These are called internally to handle packet contents once it has
+	// been authenticated, decrypted, decompressed, and classified.
+	bool _doERROR(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
+	bool _doHELLO(const RuntimeEnvironment* RR, void* tPtr, const bool alreadyAuthenticated);
+	bool _doACK(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
+	bool _doQOS_MEASUREMENT(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
+	bool _doOK(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
+	bool _doWHOIS(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
+	bool _doRENDEZVOUS(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
+	bool _doFRAME(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer, int32_t flowId);
+	bool _doEXT_FRAME(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer, int32_t flowId);
+	bool _doECHO(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
+	bool _doMULTICAST_LIKE(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
+	bool _doNETWORK_CREDENTIALS(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
+	bool _doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
+	bool _doNETWORK_CONFIG(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
+	bool _doMULTICAST_GATHER(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
+	bool _doMULTICAST_FRAME(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
+	bool _doPUSH_DIRECT_PATHS(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
+	bool _doUSER_MESSAGE(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
+	bool _doREMOTE_TRACE(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
+	bool _doPATH_NEGOTIATION_REQUEST(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer);
+
+	void _sendErrorNeedCredentials(const RuntimeEnvironment* RR, void* tPtr, const SharedPtr<Peer>& peer, const uint64_t nwid);
+
+	uint64_t _receiveTime;
+	SharedPtr<Path> _path;
+	bool _authenticated;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 435 - 435
node/InetAddress.cpp

@@ -28,515 +28,515 @@ const InetAddress InetAddress::LO6((const void*)("\x00\x00\x00\x00\x00\x00\x00\x
 
 InetAddress::IpScope InetAddress::ipScope() const
 {
-    switch (ss_family) {
-        case AF_INET: {
-            const uint32_t ip = Utils::ntoh((uint32_t)reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr);
-            switch (ip >> 24) {
-                case 0x00:
-                    return IP_SCOPE_NONE;   // 0.0.0.0/8 (reserved, never used)
-                case 0x06:
-                    return IP_SCOPE_PSEUDOPRIVATE;   // 6.0.0.0/8 (US Army)
-                case 0x0a:
-                    return IP_SCOPE_PRIVATE;   // 10.0.0.0/8
-                case 0x0b:
-                    return IP_SCOPE_PSEUDOPRIVATE;   // 11.0.0.0/8 (US DoD)
-                case 0x15:
-                    return IP_SCOPE_PSEUDOPRIVATE;   // 21.0.0.0/8 (US DDN-RVN)
-                case 0x16:
-                    return IP_SCOPE_PSEUDOPRIVATE;   // 22.0.0.0/8 (US DISA)
-                case 0x19:
-                    return IP_SCOPE_PSEUDOPRIVATE;   // 25.0.0.0/8 (UK Ministry of Defense)
-                case 0x1a:
-                    return IP_SCOPE_PSEUDOPRIVATE;   // 26.0.0.0/8 (US DISA)
-                case 0x1c:
-                    return IP_SCOPE_PSEUDOPRIVATE;   // 28.0.0.0/8 (US DSI-North)
-                case 0x1d:
-                    return IP_SCOPE_PSEUDOPRIVATE;   // 29.0.0.0/8 (US DISA)
-                case 0x1e:
-                    return IP_SCOPE_PSEUDOPRIVATE;   // 30.0.0.0/8 (US DISA)
-                case 0x33:
-                    return IP_SCOPE_PSEUDOPRIVATE;   // 51.0.0.0/8 (UK Department of Social Security)
-                case 0x37:
-                    return IP_SCOPE_PSEUDOPRIVATE;   // 55.0.0.0/8 (US DoD)
-                case 0x38:
-                    return IP_SCOPE_PSEUDOPRIVATE;   // 56.0.0.0/8 (US Postal Service)
-                case 0x64:
-                    if ((ip & 0xffc00000) == 0x64400000) {
-                        return IP_SCOPE_PRIVATE;   // 100.64.0.0/10
-                    }
-                    break;
-                case 0x7f:
-                    return IP_SCOPE_LOOPBACK;   // 127.0.0.0/8
-                case 0xa9:
-                    if ((ip & 0xffff0000) == 0xa9fe0000) {
-                        return IP_SCOPE_LINK_LOCAL;   // 169.254.0.0/16
-                    }
-                    break;
-                case 0xac:
-                    if ((ip & 0xfff00000) == 0xac100000) {
-                        return IP_SCOPE_PRIVATE;   // 172.16.0.0/12
-                    }
-                    break;
-                case 0xc0:
-                    if ((ip & 0xffff0000) == 0xc0a80000) {
-                        return IP_SCOPE_PRIVATE;   // 192.168.0.0/16
-                    }
-                    if ((ip & 0xffffff00) == 0xc0000200) {
-                        return IP_SCOPE_PRIVATE;   // 192.0.2.0/24
-                    }
-                    break;
-                case 0xc6:
-                    if ((ip & 0xfffe0000) == 0xc6120000) {
-                        return IP_SCOPE_PRIVATE;   // 198.18.0.0/15
-                    }
-                    if ((ip & 0xffffff00) == 0xc6336400) {
-                        return IP_SCOPE_PRIVATE;   // 198.51.100.0/24
-                    }
-                    break;
-                case 0xcb:
-                    if ((ip & 0xffffff00) == 0xcb007100) {
-                        return IP_SCOPE_PRIVATE;   // 203.0.113.0/24
-                    }
-                    break;
-                case 0xff:
-                    return IP_SCOPE_NONE;   // 255.0.0.0/8 (broadcast, or unused/unusable)
-            }
-            switch (ip >> 28) {
-                case 0xe:
-                    return IP_SCOPE_MULTICAST;   // 224.0.0.0/4
-                case 0xf:
-                    return IP_SCOPE_PSEUDOPRIVATE;   // 240.0.0.0/4 ("reserved," usually unusable)
-            }
-            return IP_SCOPE_GLOBAL;
-        } break;
+	switch (ss_family) {
+		case AF_INET: {
+			const uint32_t ip = Utils::ntoh((uint32_t)reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr);
+			switch (ip >> 24) {
+				case 0x00:
+					return IP_SCOPE_NONE;	// 0.0.0.0/8 (reserved, never used)
+				case 0x06:
+					return IP_SCOPE_PSEUDOPRIVATE;	 // 6.0.0.0/8 (US Army)
+				case 0x0a:
+					return IP_SCOPE_PRIVATE;   // 10.0.0.0/8
+				case 0x0b:
+					return IP_SCOPE_PSEUDOPRIVATE;	 // 11.0.0.0/8 (US DoD)
+				case 0x15:
+					return IP_SCOPE_PSEUDOPRIVATE;	 // 21.0.0.0/8 (US DDN-RVN)
+				case 0x16:
+					return IP_SCOPE_PSEUDOPRIVATE;	 // 22.0.0.0/8 (US DISA)
+				case 0x19:
+					return IP_SCOPE_PSEUDOPRIVATE;	 // 25.0.0.0/8 (UK Ministry of Defense)
+				case 0x1a:
+					return IP_SCOPE_PSEUDOPRIVATE;	 // 26.0.0.0/8 (US DISA)
+				case 0x1c:
+					return IP_SCOPE_PSEUDOPRIVATE;	 // 28.0.0.0/8 (US DSI-North)
+				case 0x1d:
+					return IP_SCOPE_PSEUDOPRIVATE;	 // 29.0.0.0/8 (US DISA)
+				case 0x1e:
+					return IP_SCOPE_PSEUDOPRIVATE;	 // 30.0.0.0/8 (US DISA)
+				case 0x33:
+					return IP_SCOPE_PSEUDOPRIVATE;	 // 51.0.0.0/8 (UK Department of Social Security)
+				case 0x37:
+					return IP_SCOPE_PSEUDOPRIVATE;	 // 55.0.0.0/8 (US DoD)
+				case 0x38:
+					return IP_SCOPE_PSEUDOPRIVATE;	 // 56.0.0.0/8 (US Postal Service)
+				case 0x64:
+					if ((ip & 0xffc00000) == 0x64400000) {
+						return IP_SCOPE_PRIVATE;   // 100.64.0.0/10
+					}
+					break;
+				case 0x7f:
+					return IP_SCOPE_LOOPBACK;	// 127.0.0.0/8
+				case 0xa9:
+					if ((ip & 0xffff0000) == 0xa9fe0000) {
+						return IP_SCOPE_LINK_LOCAL;	  // 169.254.0.0/16
+					}
+					break;
+				case 0xac:
+					if ((ip & 0xfff00000) == 0xac100000) {
+						return IP_SCOPE_PRIVATE;   // 172.16.0.0/12
+					}
+					break;
+				case 0xc0:
+					if ((ip & 0xffff0000) == 0xc0a80000) {
+						return IP_SCOPE_PRIVATE;   // 192.168.0.0/16
+					}
+					if ((ip & 0xffffff00) == 0xc0000200) {
+						return IP_SCOPE_PRIVATE;   // 192.0.2.0/24
+					}
+					break;
+				case 0xc6:
+					if ((ip & 0xfffe0000) == 0xc6120000) {
+						return IP_SCOPE_PRIVATE;   // 198.18.0.0/15
+					}
+					if ((ip & 0xffffff00) == 0xc6336400) {
+						return IP_SCOPE_PRIVATE;   // 198.51.100.0/24
+					}
+					break;
+				case 0xcb:
+					if ((ip & 0xffffff00) == 0xcb007100) {
+						return IP_SCOPE_PRIVATE;   // 203.0.113.0/24
+					}
+					break;
+				case 0xff:
+					return IP_SCOPE_NONE;	// 255.0.0.0/8 (broadcast, or unused/unusable)
+			}
+			switch (ip >> 28) {
+				case 0xe:
+					return IP_SCOPE_MULTICAST;	 // 224.0.0.0/4
+				case 0xf:
+					return IP_SCOPE_PSEUDOPRIVATE;	 // 240.0.0.0/4 ("reserved," usually unusable)
+			}
+			return IP_SCOPE_GLOBAL;
+		} break;
 
-        case AF_INET6: {
-            const unsigned char* ip = reinterpret_cast<const unsigned char*>(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr);
-            if ((ip[0] & 0xf0) == 0xf0) {
-                if (ip[0] == 0xff) {
-                    return IP_SCOPE_MULTICAST;   // ff00::/8
-                }
-                if ((ip[0] == 0xfe) && ((ip[1] & 0xc0) == 0x80)) {
-                    unsigned int k = 2;
-                    while ((! ip[k]) && (k < 15)) {
-                        ++k;
-                    }
-                    if ((k == 15) && (ip[15] == 0x01)) {
-                        return IP_SCOPE_LOOPBACK;   // fe80::1/128
-                    }
-                    else {
-                        return IP_SCOPE_LINK_LOCAL;   // fe80::/10
-                    }
-                }
-                if ((ip[0] & 0xfe) == 0xfc) {
-                    return IP_SCOPE_PRIVATE;   // fc00::/7
-                }
-            }
+		case AF_INET6: {
+			const unsigned char* ip = reinterpret_cast<const unsigned char*>(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr);
+			if ((ip[0] & 0xf0) == 0xf0) {
+				if (ip[0] == 0xff) {
+					return IP_SCOPE_MULTICAST;	 // ff00::/8
+				}
+				if ((ip[0] == 0xfe) && ((ip[1] & 0xc0) == 0x80)) {
+					unsigned int k = 2;
+					while ((! ip[k]) && (k < 15)) {
+						++k;
+					}
+					if ((k == 15) && (ip[15] == 0x01)) {
+						return IP_SCOPE_LOOPBACK;	// fe80::1/128
+					}
+					else {
+						return IP_SCOPE_LINK_LOCAL;	  // fe80::/10
+					}
+				}
+				if ((ip[0] & 0xfe) == 0xfc) {
+					return IP_SCOPE_PRIVATE;   // fc00::/7
+				}
+			}
 
-            // :::ffff:127.0.0.1
-            // 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0x7f, 0, 0, 1
-            unsigned int k = 0;
-            while ((! ip[k]) && (k < 9)) {
-                ++k;
-            }
-            if (k == 9) {
-                if (ip[10] == 0xff && ip[11] == 0xff && ip[12] == 0x7f) {
-                    return IP_SCOPE_LOOPBACK;
-                }
-            }
+			// :::ffff:127.0.0.1
+			// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0x7f, 0, 0, 1
+			unsigned int k = 0;
+			while ((! ip[k]) && (k < 9)) {
+				++k;
+			}
+			if (k == 9) {
+				if (ip[10] == 0xff && ip[11] == 0xff && ip[12] == 0x7f) {
+					return IP_SCOPE_LOOPBACK;
+				}
+			}
 
-            k = 0;
-            while ((! ip[k]) && (k < 15)) {
-                ++k;
-            }
-            if (k == 15) {   // all 0's except last byte
-                if (ip[15] == 0x01) {
-                    return IP_SCOPE_LOOPBACK;   // ::1/128
-                }
-                if (ip[15] == 0x00) {
-                    return IP_SCOPE_NONE;   // ::/128
-                }
-            }
-            return IP_SCOPE_GLOBAL;
-        } break;
-    }
+			k = 0;
+			while ((! ip[k]) && (k < 15)) {
+				++k;
+			}
+			if (k == 15) {	 // all 0's except last byte
+				if (ip[15] == 0x01) {
+					return IP_SCOPE_LOOPBACK;	// ::1/128
+				}
+				if (ip[15] == 0x00) {
+					return IP_SCOPE_NONE;	// ::/128
+				}
+			}
+			return IP_SCOPE_GLOBAL;
+		} break;
+	}
 
-    return IP_SCOPE_NONE;
+	return IP_SCOPE_NONE;
 }
 
 void InetAddress::set(const void* ipBytes, unsigned int ipLen, unsigned int port)
 {
-    memset(this, 0, sizeof(InetAddress));
-    if (ipLen == 4) {
-        uint32_t ipb[1];
-        memcpy(ipb, ipBytes, 4);
-        ss_family = AF_INET;
-        reinterpret_cast<struct sockaddr_in*>(this)->sin_addr.s_addr = ipb[0];
-        reinterpret_cast<struct sockaddr_in*>(this)->sin_port = Utils::hton((uint16_t)port);
-    }
-    else if (ipLen == 16) {
-        ss_family = AF_INET6;
-        memcpy(reinterpret_cast<struct sockaddr_in6*>(this)->sin6_addr.s6_addr, ipBytes, 16);
-        reinterpret_cast<struct sockaddr_in6*>(this)->sin6_port = Utils::hton((uint16_t)port);
-    }
+	memset(this, 0, sizeof(InetAddress));
+	if (ipLen == 4) {
+		uint32_t ipb[1];
+		memcpy(ipb, ipBytes, 4);
+		ss_family = AF_INET;
+		reinterpret_cast<struct sockaddr_in*>(this)->sin_addr.s_addr = ipb[0];
+		reinterpret_cast<struct sockaddr_in*>(this)->sin_port = Utils::hton((uint16_t)port);
+	}
+	else if (ipLen == 16) {
+		ss_family = AF_INET6;
+		memcpy(reinterpret_cast<struct sockaddr_in6*>(this)->sin6_addr.s6_addr, ipBytes, 16);
+		reinterpret_cast<struct sockaddr_in6*>(this)->sin6_port = Utils::hton((uint16_t)port);
+	}
 }
 
 char* InetAddress::toString(char buf[64]) const
 {
-    char* p = toIpString(buf);
-    if (*p) {
-        while (*p) {
-            ++p;
-        }
-        *(p++) = '/';
-        Utils::decimal(port(), p);
-    }
-    return buf;
+	char* p = toIpString(buf);
+	if (*p) {
+		while (*p) {
+			++p;
+		}
+		*(p++) = '/';
+		Utils::decimal(port(), p);
+	}
+	return buf;
 }
 
 char* InetAddress::toIpString(char buf[64]) const
 {
-    buf[0] = (char)0;
-    switch (ss_family) {
-        case AF_INET: {
+	buf[0] = (char)0;
+	switch (ss_family) {
+		case AF_INET: {
 #ifdef _WIN32
-            inet_ntop(AF_INET, (void*)&reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr, buf, INET_ADDRSTRLEN);
+			inet_ntop(AF_INET, (void*)&reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr, buf, INET_ADDRSTRLEN);
 #else
-            inet_ntop(AF_INET, &reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr, buf, INET_ADDRSTRLEN);
+			inet_ntop(AF_INET, &reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr, buf, INET_ADDRSTRLEN);
 #endif
-        } break;
+		} break;
 
-        case AF_INET6: {
+		case AF_INET6: {
 #ifdef _WIN32
-            inet_ntop(AF_INET6, (void*)reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr, buf, INET6_ADDRSTRLEN);
+			inet_ntop(AF_INET6, (void*)reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr, buf, INET6_ADDRSTRLEN);
 #else
-            inet_ntop(AF_INET6, reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr, buf, INET6_ADDRSTRLEN);
+			inet_ntop(AF_INET6, reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr, buf, INET6_ADDRSTRLEN);
 #endif
-        } break;
-    }
-    return buf;
+		} break;
+	}
+	return buf;
 }
 
 bool InetAddress::fromString(const char* ipSlashPort)
 {
-    char buf[64];
+	char buf[64];
 
-    memset(this, 0, sizeof(InetAddress));
+	memset(this, 0, sizeof(InetAddress));
 
-    if (! *ipSlashPort) {
-        return true;
-    }
-    if (! Utils::scopy(buf, sizeof(buf), ipSlashPort)) {
-        return false;
-    }
+	if (! *ipSlashPort) {
+		return true;
+	}
+	if (! Utils::scopy(buf, sizeof(buf), ipSlashPort)) {
+		return false;
+	}
 
-    char* portAt = buf;
-    while ((*portAt) && (*portAt != '/')) {
-        ++portAt;
-    }
-    unsigned int port = 0;
-    if (*portAt) {
-        *(portAt++) = (char)0;
-        port = Utils::strToUInt(portAt) & 0xffff;
-    }
+	char* portAt = buf;
+	while ((*portAt) && (*portAt != '/')) {
+		++portAt;
+	}
+	unsigned int port = 0;
+	if (*portAt) {
+		*(portAt++) = (char)0;
+		port = Utils::strToUInt(portAt) & 0xffff;
+	}
 
-    if (strchr(buf, ':')) {
-        struct sockaddr_in6* const in6 = reinterpret_cast<struct sockaddr_in6*>(this);
-        inet_pton(AF_INET6, buf, &in6->sin6_addr.s6_addr);
-        in6->sin6_family = AF_INET6;
-        in6->sin6_port = Utils::hton((uint16_t)port);
-        return true;
-    }
-    else if (strchr(buf, '.')) {
-        struct sockaddr_in* const in = reinterpret_cast<struct sockaddr_in*>(this);
-        inet_pton(AF_INET, buf, &in->sin_addr.s_addr);
-        in->sin_family = AF_INET;
-        in->sin_port = Utils::hton((uint16_t)port);
-        return true;
-    }
-    else {
-        return false;
-    }
+	if (strchr(buf, ':')) {
+		struct sockaddr_in6* const in6 = reinterpret_cast<struct sockaddr_in6*>(this);
+		inet_pton(AF_INET6, buf, &in6->sin6_addr.s6_addr);
+		in6->sin6_family = AF_INET6;
+		in6->sin6_port = Utils::hton((uint16_t)port);
+		return true;
+	}
+	else if (strchr(buf, '.')) {
+		struct sockaddr_in* const in = reinterpret_cast<struct sockaddr_in*>(this);
+		inet_pton(AF_INET, buf, &in->sin_addr.s_addr);
+		in->sin_family = AF_INET;
+		in->sin_port = Utils::hton((uint16_t)port);
+		return true;
+	}
+	else {
+		return false;
+	}
 }
 
 InetAddress InetAddress::netmask() const
 {
-    InetAddress r(*this);
-    switch (r.ss_family) {
-        case AF_INET:
-            reinterpret_cast<struct sockaddr_in*>(&r)->sin_addr.s_addr = Utils::hton((uint32_t)(0xffffffff << (32 - netmaskBits())));
-            break;
-        case AF_INET6: {
-            uint64_t nm[2];
-            const unsigned int bits = netmaskBits();
-            if (bits) {
-                nm[0] = Utils::hton((uint64_t)((bits >= 64) ? 0xffffffffffffffffULL : (0xffffffffffffffffULL << (64 - bits))));
-                nm[1] = Utils::hton((uint64_t)((bits <= 64) ? 0ULL : (0xffffffffffffffffULL << (128 - bits))));
-            }
-            else {
-                nm[0] = 0;
-                nm[1] = 0;
-            }
-            memcpy(reinterpret_cast<struct sockaddr_in6*>(&r)->sin6_addr.s6_addr, nm, 16);
-        } break;
-    }
-    return r;
+	InetAddress r(*this);
+	switch (r.ss_family) {
+		case AF_INET:
+			reinterpret_cast<struct sockaddr_in*>(&r)->sin_addr.s_addr = Utils::hton((uint32_t)(0xffffffff << (32 - netmaskBits())));
+			break;
+		case AF_INET6: {
+			uint64_t nm[2];
+			const unsigned int bits = netmaskBits();
+			if (bits) {
+				nm[0] = Utils::hton((uint64_t)((bits >= 64) ? 0xffffffffffffffffULL : (0xffffffffffffffffULL << (64 - bits))));
+				nm[1] = Utils::hton((uint64_t)((bits <= 64) ? 0ULL : (0xffffffffffffffffULL << (128 - bits))));
+			}
+			else {
+				nm[0] = 0;
+				nm[1] = 0;
+			}
+			memcpy(reinterpret_cast<struct sockaddr_in6*>(&r)->sin6_addr.s6_addr, nm, 16);
+		} break;
+	}
+	return r;
 }
 
 InetAddress InetAddress::broadcast() const
 {
-    if (ss_family == AF_INET) {
-        InetAddress r(*this);
-        reinterpret_cast<struct sockaddr_in*>(&r)->sin_addr.s_addr |= Utils::hton((uint32_t)(0xffffffff >> netmaskBits()));
-        return r;
-    }
-    return InetAddress();
+	if (ss_family == AF_INET) {
+		InetAddress r(*this);
+		reinterpret_cast<struct sockaddr_in*>(&r)->sin_addr.s_addr |= Utils::hton((uint32_t)(0xffffffff >> netmaskBits()));
+		return r;
+	}
+	return InetAddress();
 }
 
 InetAddress InetAddress::network() const
 {
-    InetAddress r(*this);
-    switch (r.ss_family) {
-        case AF_INET:
-            reinterpret_cast<struct sockaddr_in*>(&r)->sin_addr.s_addr &= Utils::hton((uint32_t)(0xffffffff << (32 - netmaskBits())));
-            break;
-        case AF_INET6: {
-            uint64_t nm[2];
-            const unsigned int bits = netmaskBits();
-            memcpy(nm, reinterpret_cast<struct sockaddr_in6*>(&r)->sin6_addr.s6_addr, 16);
-            nm[0] &= Utils::hton((uint64_t)((bits >= 64) ? 0xffffffffffffffffULL : (0xffffffffffffffffULL << (64 - bits))));
-            nm[1] &= Utils::hton((uint64_t)((bits <= 64) ? 0ULL : (0xffffffffffffffffULL << (128 - bits))));
-            memcpy(reinterpret_cast<struct sockaddr_in6*>(&r)->sin6_addr.s6_addr, nm, 16);
-        } break;
-    }
-    return r;
+	InetAddress r(*this);
+	switch (r.ss_family) {
+		case AF_INET:
+			reinterpret_cast<struct sockaddr_in*>(&r)->sin_addr.s_addr &= Utils::hton((uint32_t)(0xffffffff << (32 - netmaskBits())));
+			break;
+		case AF_INET6: {
+			uint64_t nm[2];
+			const unsigned int bits = netmaskBits();
+			memcpy(nm, reinterpret_cast<struct sockaddr_in6*>(&r)->sin6_addr.s6_addr, 16);
+			nm[0] &= Utils::hton((uint64_t)((bits >= 64) ? 0xffffffffffffffffULL : (0xffffffffffffffffULL << (64 - bits))));
+			nm[1] &= Utils::hton((uint64_t)((bits <= 64) ? 0ULL : (0xffffffffffffffffULL << (128 - bits))));
+			memcpy(reinterpret_cast<struct sockaddr_in6*>(&r)->sin6_addr.s6_addr, nm, 16);
+		} break;
+	}
+	return r;
 }
 
 bool InetAddress::isEqualPrefix(const InetAddress& addr) const
 {
-    if (addr.ss_family == ss_family) {
-        switch (ss_family) {
-            case AF_INET6: {
-                const InetAddress mask(netmask());
-                InetAddress addr_mask(addr.netmask());
-                const uint8_t* n = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(&addr_mask)->sin6_addr.s6_addr);
-                const uint8_t* m = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(&mask)->sin6_addr.s6_addr);
-                const uint8_t* a = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(&addr)->sin6_addr.s6_addr);
-                const uint8_t* b = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr);
-                for (unsigned int i = 0; i < 16; ++i) {
-                    if ((a[i] & m[i]) != (b[i] & n[i])) {
-                        return false;
-                    }
-                }
-                return true;
-            }
-        }
-    }
-    return false;
+	if (addr.ss_family == ss_family) {
+		switch (ss_family) {
+			case AF_INET6: {
+				const InetAddress mask(netmask());
+				InetAddress addr_mask(addr.netmask());
+				const uint8_t* n = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(&addr_mask)->sin6_addr.s6_addr);
+				const uint8_t* m = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(&mask)->sin6_addr.s6_addr);
+				const uint8_t* a = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(&addr)->sin6_addr.s6_addr);
+				const uint8_t* b = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr);
+				for (unsigned int i = 0; i < 16; ++i) {
+					if ((a[i] & m[i]) != (b[i] & n[i])) {
+						return false;
+					}
+				}
+				return true;
+			}
+		}
+	}
+	return false;
 }
 
 bool InetAddress::containsAddress(const InetAddress& addr) const
 {
-    if (addr.ss_family == ss_family) {
-        switch (ss_family) {
-            case AF_INET: {
-                const unsigned int bits = netmaskBits();
-                if (bits == 0) {
-                    return true;
-                }
-                return (
-                    (Utils::ntoh((uint32_t)reinterpret_cast<const struct sockaddr_in*>(&addr)->sin_addr.s_addr) >> (32 - bits)) == (Utils::ntoh((uint32_t)reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr) >> (32 - bits)));
-            }
-            case AF_INET6: {
-                const InetAddress mask(netmask());
-                const uint8_t* m = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(&mask)->sin6_addr.s6_addr);
-                const uint8_t* a = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(&addr)->sin6_addr.s6_addr);
-                const uint8_t* b = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr);
-                for (unsigned int i = 0; i < 16; ++i) {
-                    if ((a[i] & m[i]) != b[i]) {
-                        return false;
-                    }
-                }
-                return true;
-            }
-        }
-    }
-    return false;
+	if (addr.ss_family == ss_family) {
+		switch (ss_family) {
+			case AF_INET: {
+				const unsigned int bits = netmaskBits();
+				if (bits == 0) {
+					return true;
+				}
+				return (
+					(Utils::ntoh((uint32_t)reinterpret_cast<const struct sockaddr_in*>(&addr)->sin_addr.s_addr) >> (32 - bits)) == (Utils::ntoh((uint32_t)reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr) >> (32 - bits)));
+			}
+			case AF_INET6: {
+				const InetAddress mask(netmask());
+				const uint8_t* m = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(&mask)->sin6_addr.s6_addr);
+				const uint8_t* a = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(&addr)->sin6_addr.s6_addr);
+				const uint8_t* b = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr);
+				for (unsigned int i = 0; i < 16; ++i) {
+					if ((a[i] & m[i]) != b[i]) {
+						return false;
+					}
+				}
+				return true;
+			}
+		}
+	}
+	return false;
 }
 
 bool InetAddress::isNetwork() const
 {
-    switch (ss_family) {
-        case AF_INET: {
-            unsigned int bits = netmaskBits();
-            if (bits <= 0) {
-                return false;
-            }
-            if (bits >= 32) {
-                return false;
-            }
-            uint32_t ip = Utils::ntoh((uint32_t)reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr);
-            return ((ip & (0xffffffff >> bits)) == 0);
-        }
-        case AF_INET6: {
-            unsigned int bits = netmaskBits();
-            if (bits <= 0) {
-                return false;
-            }
-            if (bits >= 128) {
-                return false;
-            }
-            const unsigned char* ip = reinterpret_cast<const unsigned char*>(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr);
-            unsigned int p = bits / 8;
-            if ((ip[p++] & (0xff >> (bits % 8))) != 0) {
-                return false;
-            }
-            while (p < 16) {
-                if (ip[p++]) {
-                    return false;
-                }
-            }
-            return true;
-        }
-    }
-    return false;
+	switch (ss_family) {
+		case AF_INET: {
+			unsigned int bits = netmaskBits();
+			if (bits <= 0) {
+				return false;
+			}
+			if (bits >= 32) {
+				return false;
+			}
+			uint32_t ip = Utils::ntoh((uint32_t)reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr);
+			return ((ip & (0xffffffff >> bits)) == 0);
+		}
+		case AF_INET6: {
+			unsigned int bits = netmaskBits();
+			if (bits <= 0) {
+				return false;
+			}
+			if (bits >= 128) {
+				return false;
+			}
+			const unsigned char* ip = reinterpret_cast<const unsigned char*>(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr);
+			unsigned int p = bits / 8;
+			if ((ip[p++] & (0xff >> (bits % 8))) != 0) {
+				return false;
+			}
+			while (p < 16) {
+				if (ip[p++]) {
+					return false;
+				}
+			}
+			return true;
+		}
+	}
+	return false;
 }
 
 bool InetAddress::operator==(const InetAddress& a) const
 {
-    if (ss_family == a.ss_family) {
-        switch (ss_family) {
-            case AF_INET:
-                return (
-                    (reinterpret_cast<const struct sockaddr_in*>(this)->sin_port == reinterpret_cast<const struct sockaddr_in*>(&a)->sin_port)
-                    && (reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr == reinterpret_cast<const struct sockaddr_in*>(&a)->sin_addr.s_addr));
-                break;
-            case AF_INET6:
-                return (
-                    (reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_port == reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_port)
-                    && (reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_flowinfo == reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_flowinfo)
-                    && (memcmp(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr, reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_addr.s6_addr, 16) == 0)
-                    && (reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_scope_id == reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_scope_id));
-                break;
-            default:
-                return (memcmp(this, &a, sizeof(InetAddress)) == 0);
-        }
-    }
-    return false;
+	if (ss_family == a.ss_family) {
+		switch (ss_family) {
+			case AF_INET:
+				return (
+					(reinterpret_cast<const struct sockaddr_in*>(this)->sin_port == reinterpret_cast<const struct sockaddr_in*>(&a)->sin_port)
+					&& (reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr == reinterpret_cast<const struct sockaddr_in*>(&a)->sin_addr.s_addr));
+				break;
+			case AF_INET6:
+				return (
+					(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_port == reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_port)
+					&& (reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_flowinfo == reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_flowinfo)
+					&& (memcmp(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr, reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_addr.s6_addr, 16) == 0)
+					&& (reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_scope_id == reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_scope_id));
+				break;
+			default:
+				return (memcmp(this, &a, sizeof(InetAddress)) == 0);
+		}
+	}
+	return false;
 }
 
 bool InetAddress::operator<(const InetAddress& a) const
 {
-    if (ss_family < a.ss_family) {
-        return true;
-    }
-    else if (ss_family == a.ss_family) {
-        switch (ss_family) {
-            case AF_INET:
-                if (reinterpret_cast<const struct sockaddr_in*>(this)->sin_port < reinterpret_cast<const struct sockaddr_in*>(&a)->sin_port) {
-                    return true;
-                }
-                else if (reinterpret_cast<const struct sockaddr_in*>(this)->sin_port == reinterpret_cast<const struct sockaddr_in*>(&a)->sin_port) {
-                    if (reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr < reinterpret_cast<const struct sockaddr_in*>(&a)->sin_addr.s_addr) {
-                        return true;
-                    }
-                }
-                break;
-            case AF_INET6:
-                if (reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_port < reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_port) {
-                    return true;
-                }
-                else if (reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_port == reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_port) {
-                    if (reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_flowinfo < reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_flowinfo) {
-                        return true;
-                    }
-                    else if (reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_flowinfo == reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_flowinfo) {
-                        if (memcmp(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr, reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_addr.s6_addr, 16) < 0) {
-                            return true;
-                        }
-                        else if (memcmp(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr, reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_addr.s6_addr, 16) == 0) {
-                            if (reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_scope_id < reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_scope_id) {
-                                return true;
-                            }
-                        }
-                    }
-                }
-                break;
-            default:
-                return (memcmp(this, &a, sizeof(InetAddress)) < 0);
-        }
-    }
-    return false;
+	if (ss_family < a.ss_family) {
+		return true;
+	}
+	else if (ss_family == a.ss_family) {
+		switch (ss_family) {
+			case AF_INET:
+				if (reinterpret_cast<const struct sockaddr_in*>(this)->sin_port < reinterpret_cast<const struct sockaddr_in*>(&a)->sin_port) {
+					return true;
+				}
+				else if (reinterpret_cast<const struct sockaddr_in*>(this)->sin_port == reinterpret_cast<const struct sockaddr_in*>(&a)->sin_port) {
+					if (reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr < reinterpret_cast<const struct sockaddr_in*>(&a)->sin_addr.s_addr) {
+						return true;
+					}
+				}
+				break;
+			case AF_INET6:
+				if (reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_port < reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_port) {
+					return true;
+				}
+				else if (reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_port == reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_port) {
+					if (reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_flowinfo < reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_flowinfo) {
+						return true;
+					}
+					else if (reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_flowinfo == reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_flowinfo) {
+						if (memcmp(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr, reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_addr.s6_addr, 16) < 0) {
+							return true;
+						}
+						else if (memcmp(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr, reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_addr.s6_addr, 16) == 0) {
+							if (reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_scope_id < reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_scope_id) {
+								return true;
+							}
+						}
+					}
+				}
+				break;
+			default:
+				return (memcmp(this, &a, sizeof(InetAddress)) < 0);
+		}
+	}
+	return false;
 }
 
 InetAddress InetAddress::makeIpv6LinkLocal(const MAC& mac)
 {
-    struct sockaddr_in6 sin6;
-    sin6.sin6_family = AF_INET6;
-    sin6.sin6_addr.s6_addr[0] = 0xfe;
-    sin6.sin6_addr.s6_addr[1] = 0x80;
-    sin6.sin6_addr.s6_addr[2] = 0x00;
-    sin6.sin6_addr.s6_addr[3] = 0x00;
-    sin6.sin6_addr.s6_addr[4] = 0x00;
-    sin6.sin6_addr.s6_addr[5] = 0x00;
-    sin6.sin6_addr.s6_addr[6] = 0x00;
-    sin6.sin6_addr.s6_addr[7] = 0x00;
-    sin6.sin6_addr.s6_addr[8] = mac[0] & 0xfd;
-    sin6.sin6_addr.s6_addr[9] = mac[1];
-    sin6.sin6_addr.s6_addr[10] = mac[2];
-    sin6.sin6_addr.s6_addr[11] = 0xff;
-    sin6.sin6_addr.s6_addr[12] = 0xfe;
-    sin6.sin6_addr.s6_addr[13] = mac[3];
-    sin6.sin6_addr.s6_addr[14] = mac[4];
-    sin6.sin6_addr.s6_addr[15] = mac[5];
-    sin6.sin6_port = Utils::hton((uint16_t)64);
-    return InetAddress(sin6);
+	struct sockaddr_in6 sin6;
+	sin6.sin6_family = AF_INET6;
+	sin6.sin6_addr.s6_addr[0] = 0xfe;
+	sin6.sin6_addr.s6_addr[1] = 0x80;
+	sin6.sin6_addr.s6_addr[2] = 0x00;
+	sin6.sin6_addr.s6_addr[3] = 0x00;
+	sin6.sin6_addr.s6_addr[4] = 0x00;
+	sin6.sin6_addr.s6_addr[5] = 0x00;
+	sin6.sin6_addr.s6_addr[6] = 0x00;
+	sin6.sin6_addr.s6_addr[7] = 0x00;
+	sin6.sin6_addr.s6_addr[8] = mac[0] & 0xfd;
+	sin6.sin6_addr.s6_addr[9] = mac[1];
+	sin6.sin6_addr.s6_addr[10] = mac[2];
+	sin6.sin6_addr.s6_addr[11] = 0xff;
+	sin6.sin6_addr.s6_addr[12] = 0xfe;
+	sin6.sin6_addr.s6_addr[13] = mac[3];
+	sin6.sin6_addr.s6_addr[14] = mac[4];
+	sin6.sin6_addr.s6_addr[15] = mac[5];
+	sin6.sin6_port = Utils::hton((uint16_t)64);
+	return InetAddress(sin6);
 }
 
 InetAddress InetAddress::makeIpv6rfc4193(uint64_t nwid, uint64_t zeroTierAddress)
 {
-    InetAddress r;
-    struct sockaddr_in6* const sin6 = reinterpret_cast<struct sockaddr_in6*>(&r);
-    sin6->sin6_family = AF_INET6;
-    sin6->sin6_addr.s6_addr[0] = 0xfd;
-    sin6->sin6_addr.s6_addr[1] = (uint8_t)(nwid >> 56);
-    sin6->sin6_addr.s6_addr[2] = (uint8_t)(nwid >> 48);
-    sin6->sin6_addr.s6_addr[3] = (uint8_t)(nwid >> 40);
-    sin6->sin6_addr.s6_addr[4] = (uint8_t)(nwid >> 32);
-    sin6->sin6_addr.s6_addr[5] = (uint8_t)(nwid >> 24);
-    sin6->sin6_addr.s6_addr[6] = (uint8_t)(nwid >> 16);
-    sin6->sin6_addr.s6_addr[7] = (uint8_t)(nwid >> 8);
-    sin6->sin6_addr.s6_addr[8] = (uint8_t)nwid;
-    sin6->sin6_addr.s6_addr[9] = 0x99;
-    sin6->sin6_addr.s6_addr[10] = 0x93;
-    sin6->sin6_addr.s6_addr[11] = (uint8_t)(zeroTierAddress >> 32);
-    sin6->sin6_addr.s6_addr[12] = (uint8_t)(zeroTierAddress >> 24);
-    sin6->sin6_addr.s6_addr[13] = (uint8_t)(zeroTierAddress >> 16);
-    sin6->sin6_addr.s6_addr[14] = (uint8_t)(zeroTierAddress >> 8);
-    sin6->sin6_addr.s6_addr[15] = (uint8_t)zeroTierAddress;
-    sin6->sin6_port = Utils::hton((uint16_t)88);   // /88 includes 0xfd + network ID, discriminating by device ID below that
-    return r;
+	InetAddress r;
+	struct sockaddr_in6* const sin6 = reinterpret_cast<struct sockaddr_in6*>(&r);
+	sin6->sin6_family = AF_INET6;
+	sin6->sin6_addr.s6_addr[0] = 0xfd;
+	sin6->sin6_addr.s6_addr[1] = (uint8_t)(nwid >> 56);
+	sin6->sin6_addr.s6_addr[2] = (uint8_t)(nwid >> 48);
+	sin6->sin6_addr.s6_addr[3] = (uint8_t)(nwid >> 40);
+	sin6->sin6_addr.s6_addr[4] = (uint8_t)(nwid >> 32);
+	sin6->sin6_addr.s6_addr[5] = (uint8_t)(nwid >> 24);
+	sin6->sin6_addr.s6_addr[6] = (uint8_t)(nwid >> 16);
+	sin6->sin6_addr.s6_addr[7] = (uint8_t)(nwid >> 8);
+	sin6->sin6_addr.s6_addr[8] = (uint8_t)nwid;
+	sin6->sin6_addr.s6_addr[9] = 0x99;
+	sin6->sin6_addr.s6_addr[10] = 0x93;
+	sin6->sin6_addr.s6_addr[11] = (uint8_t)(zeroTierAddress >> 32);
+	sin6->sin6_addr.s6_addr[12] = (uint8_t)(zeroTierAddress >> 24);
+	sin6->sin6_addr.s6_addr[13] = (uint8_t)(zeroTierAddress >> 16);
+	sin6->sin6_addr.s6_addr[14] = (uint8_t)(zeroTierAddress >> 8);
+	sin6->sin6_addr.s6_addr[15] = (uint8_t)zeroTierAddress;
+	sin6->sin6_port = Utils::hton((uint16_t)88);   // /88 includes 0xfd + network ID, discriminating by device ID below that
+	return r;
 }
 
 InetAddress InetAddress::makeIpv66plane(uint64_t nwid, uint64_t zeroTierAddress)
 {
-    nwid ^= (nwid >> 32);
-    InetAddress r;
-    struct sockaddr_in6* const sin6 = reinterpret_cast<struct sockaddr_in6*>(&r);
-    sin6->sin6_family = AF_INET6;
-    sin6->sin6_addr.s6_addr[0] = 0xfc;
-    sin6->sin6_addr.s6_addr[1] = (uint8_t)(nwid >> 24);
-    sin6->sin6_addr.s6_addr[2] = (uint8_t)(nwid >> 16);
-    sin6->sin6_addr.s6_addr[3] = (uint8_t)(nwid >> 8);
-    sin6->sin6_addr.s6_addr[4] = (uint8_t)nwid;
-    sin6->sin6_addr.s6_addr[5] = (uint8_t)(zeroTierAddress >> 32);
-    sin6->sin6_addr.s6_addr[6] = (uint8_t)(zeroTierAddress >> 24);
-    sin6->sin6_addr.s6_addr[7] = (uint8_t)(zeroTierAddress >> 16);
-    sin6->sin6_addr.s6_addr[8] = (uint8_t)(zeroTierAddress >> 8);
-    sin6->sin6_addr.s6_addr[9] = (uint8_t)zeroTierAddress;
-    sin6->sin6_addr.s6_addr[15] = 0x01;
-    sin6->sin6_port = Utils::hton((uint16_t)40);
-    return r;
+	nwid ^= (nwid >> 32);
+	InetAddress r;
+	struct sockaddr_in6* const sin6 = reinterpret_cast<struct sockaddr_in6*>(&r);
+	sin6->sin6_family = AF_INET6;
+	sin6->sin6_addr.s6_addr[0] = 0xfc;
+	sin6->sin6_addr.s6_addr[1] = (uint8_t)(nwid >> 24);
+	sin6->sin6_addr.s6_addr[2] = (uint8_t)(nwid >> 16);
+	sin6->sin6_addr.s6_addr[3] = (uint8_t)(nwid >> 8);
+	sin6->sin6_addr.s6_addr[4] = (uint8_t)nwid;
+	sin6->sin6_addr.s6_addr[5] = (uint8_t)(zeroTierAddress >> 32);
+	sin6->sin6_addr.s6_addr[6] = (uint8_t)(zeroTierAddress >> 24);
+	sin6->sin6_addr.s6_addr[7] = (uint8_t)(zeroTierAddress >> 16);
+	sin6->sin6_addr.s6_addr[8] = (uint8_t)(zeroTierAddress >> 8);
+	sin6->sin6_addr.s6_addr[9] = (uint8_t)zeroTierAddress;
+	sin6->sin6_addr.s6_addr[15] = 0x01;
+	sin6->sin6_port = Utils::hton((uint16_t)40);
+	return r;
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 708 - 708
node/InetAddress.hpp

@@ -40,715 +40,715 @@ namespace ZeroTier {
  * adding non-static fields, since much code depends on this identity.
  */
 struct InetAddress : public sockaddr_storage {
-    /**
-     * Loopback IPv4 address (no port)
-     */
-    static const InetAddress LO4;
-
-    /**
-     * Loopback IPV6 address (no port)
-     */
-    static const InetAddress LO6;
-
-    /**
-     * IP address scope
-     *
-     * Note that these values are in ascending order of path preference and
-     * MUST remain that way or Path must be changed to reflect. Also be sure
-     * to change ZT_INETADDRESS_MAX_SCOPE if the max changes.
-     */
-    enum IpScope {
-        IP_SCOPE_NONE = 0,            // NULL or not an IP address
-        IP_SCOPE_MULTICAST = 1,       // 224.0.0.0 and other V4/V6 multicast IPs
-        IP_SCOPE_LOOPBACK = 2,        // 127.0.0.1, ::1, etc.
-        IP_SCOPE_PSEUDOPRIVATE = 3,   // 28.x.x.x, etc. -- unofficially unrouted IPv4 blocks often "bogarted"
-        IP_SCOPE_GLOBAL = 4,          // globally routable IP address (all others)
-        IP_SCOPE_LINK_LOCAL = 5,      // 169.254.x.x, IPv6 LL
-        IP_SCOPE_SHARED = 6,          // currently unused, formerly used for carrier-grade NAT ranges
-        IP_SCOPE_PRIVATE = 7          // 10.x.x.x, 192.168.x.x, etc.
-    };
-
-    // Can be used with the unordered maps and sets in c++11. We don't use C++11 in the core
-    // but this is safe to put here.
-    struct Hasher {
-        inline std::size_t operator()(const InetAddress& a) const
-        {
-            return (std::size_t)a.hashCode();
-        }
-    };
-
-    InetAddress()
-    {
-        memset(this, 0, sizeof(InetAddress));
-    }
-    InetAddress(const InetAddress& a)
-    {
-        memcpy(this, &a, sizeof(InetAddress));
-    }
-    InetAddress(const InetAddress* a)
-    {
-        memcpy(this, a, sizeof(InetAddress));
-    }
-    InetAddress(const struct sockaddr_storage& ss)
-    {
-        *this = ss;
-    }
-    InetAddress(const struct sockaddr_storage* ss)
-    {
-        *this = ss;
-    }
-    InetAddress(const struct sockaddr& sa)
-    {
-        *this = sa;
-    }
-    InetAddress(const struct sockaddr* sa)
-    {
-        *this = sa;
-    }
-    InetAddress(const struct sockaddr_in& sa)
-    {
-        *this = sa;
-    }
-    InetAddress(const struct sockaddr_in* sa)
-    {
-        *this = sa;
-    }
-    InetAddress(const struct sockaddr_in6& sa)
-    {
-        *this = sa;
-    }
-    InetAddress(const struct sockaddr_in6* sa)
-    {
-        *this = sa;
-    }
-    InetAddress(const void* ipBytes, unsigned int ipLen, unsigned int port)
-    {
-        this->set(ipBytes, ipLen, port);
-    }
-    InetAddress(const uint32_t ipv4, unsigned int port)
-    {
-        this->set(&ipv4, 4, port);
-    }
-    InetAddress(const char* ipSlashPort)
-    {
-        this->fromString(ipSlashPort);
-    }
-
-    inline InetAddress& operator=(const InetAddress& a)
-    {
-        if (&a != this) {
-            memcpy(this, &a, sizeof(InetAddress));
-        }
-        return *this;
-    }
-
-    inline InetAddress& operator=(const InetAddress* a)
-    {
-        if (a != this) {
-            memcpy(this, a, sizeof(InetAddress));
-        }
-        return *this;
-    }
-
-    inline InetAddress& operator=(const struct sockaddr_storage& ss)
-    {
-        if (reinterpret_cast<const InetAddress*>(&ss) != this) {
-            memcpy(this, &ss, sizeof(InetAddress));
-        }
-        return *this;
-    }
-
-    inline InetAddress& operator=(const struct sockaddr_storage* ss)
-    {
-        if (reinterpret_cast<const InetAddress*>(ss) != this) {
-            memcpy(this, ss, sizeof(InetAddress));
-        }
-        return *this;
-    }
-
-    inline InetAddress& operator=(const struct sockaddr_in& sa)
-    {
-        if (reinterpret_cast<const InetAddress*>(&sa) != this) {
-            memset(this, 0, sizeof(InetAddress));
-            memcpy(this, &sa, sizeof(struct sockaddr_in));
-        }
-        return *this;
-    }
-
-    inline InetAddress& operator=(const struct sockaddr_in* sa)
-    {
-        if (reinterpret_cast<const InetAddress*>(sa) != this) {
-            memset(this, 0, sizeof(InetAddress));
-            memcpy(this, sa, sizeof(struct sockaddr_in));
-        }
-        return *this;
-    }
-
-    inline InetAddress& operator=(const struct sockaddr_in6& sa)
-    {
-        if (reinterpret_cast<const InetAddress*>(&sa) != this) {
-            memset(this, 0, sizeof(InetAddress));
-            memcpy(this, &sa, sizeof(struct sockaddr_in6));
-        }
-        return *this;
-    }
-
-    inline InetAddress& operator=(const struct sockaddr_in6* sa)
-    {
-        if (reinterpret_cast<const InetAddress*>(sa) != this) {
-            memset(this, 0, sizeof(InetAddress));
-            memcpy(this, sa, sizeof(struct sockaddr_in6));
-        }
-        return *this;
-    }
-
-    inline InetAddress& operator=(const struct sockaddr& sa)
-    {
-        if (reinterpret_cast<const InetAddress*>(&sa) != this) {
-            memset(this, 0, sizeof(InetAddress));
-            switch (sa.sa_family) {
-                case AF_INET:
-                    memcpy(this, &sa, sizeof(struct sockaddr_in));
-                    break;
-                case AF_INET6:
-                    memcpy(this, &sa, sizeof(struct sockaddr_in6));
-                    break;
-            }
-        }
-        return *this;
-    }
-
-    inline InetAddress& operator=(const struct sockaddr* sa)
-    {
-        if (reinterpret_cast<const InetAddress*>(sa) != this) {
-            memset(this, 0, sizeof(InetAddress));
-            switch (sa->sa_family) {
-                case AF_INET:
-                    memcpy(this, sa, sizeof(struct sockaddr_in));
-                    break;
-                case AF_INET6:
-                    memcpy(this, sa, sizeof(struct sockaddr_in6));
-                    break;
-            }
-        }
-        return *this;
-    }
-
-    /**
-     * @return IP scope classification (e.g. loopback, link-local, private, global)
-     */
-    IpScope ipScope() const;
-
-    /**
-     * Set from a raw IP and port number
-     *
-     * @param ipBytes Bytes of IP address in network byte order
-     * @param ipLen Length of IP address: 4 or 16
-     * @param port Port number or 0 for none
-     */
-    void set(const void* ipBytes, unsigned int ipLen, unsigned int port);
-
-    /**
-     * Set the port component
-     *
-     * @param port Port, 0 to 65535
-     */
-    inline void setPort(unsigned int port)
-    {
-        switch (ss_family) {
-            case AF_INET:
-                reinterpret_cast<struct sockaddr_in*>(this)->sin_port = Utils::hton((uint16_t)port);
-                break;
-            case AF_INET6:
-                reinterpret_cast<struct sockaddr_in6*>(this)->sin6_port = Utils::hton((uint16_t)port);
-                break;
-        }
-    }
-
-    /**
-     * @return True if this network/netmask route describes a default route (e.g. 0.0.0.0/0)
-     */
-    inline bool isDefaultRoute() const
-    {
-        switch (ss_family) {
-            case AF_INET:
-                return ((reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr == 0) && (reinterpret_cast<const struct sockaddr_in*>(this)->sin_port == 0));
-            case AF_INET6:
-                const uint8_t* ipb = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr);
-                for (int i = 0; i < 16; ++i) {
-                    if (ipb[i]) {
-                        return false;
-                    }
-                }
-                return (reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_port == 0);
-        }
-        return false;
-    }
-
-    /**
-     * @return ASCII IP/port format representation
-     */
-    char* toString(char buf[64]) const;
-
-    /**
-     * @return IP portion only, in ASCII string format
-     */
-    char* toIpString(char buf[64]) const;
-
-    /**
-     * @param ipSlashPort IP/port (port is optional, will be 0 if not included)
-     * @return True if address appeared to be valid
-     */
-    bool fromString(const char* ipSlashPort);
-
-    /**
-     * @return Port or 0 if no port component defined
-     */
-    inline unsigned int port() const
-    {
-        switch (ss_family) {
-            case AF_INET:
-                return Utils::ntoh((uint16_t)(reinterpret_cast<const struct sockaddr_in*>(this)->sin_port));
-            case AF_INET6:
-                return Utils::ntoh((uint16_t)(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_port));
-            default:
-                return 0;
-        }
-    }
-
-    /**
-     * Alias for port()
-     *
-     * This just aliases port() to make code more readable when netmask bits
-     * are stuffed there, as they are in Network, EthernetTap, and a few other
-     * spots.
-     *
-     * @return Netmask bits
-     */
-    inline unsigned int netmaskBits() const
-    {
-        return port();
-    }
-
-    /**
-     * @return True if netmask bits is valid for the address type
-     */
-    inline bool netmaskBitsValid() const
-    {
-        const unsigned int n = port();
-        switch (ss_family) {
-            case AF_INET:
-                return (n <= 32);
-            case AF_INET6:
-                return (n <= 128);
-        }
-        return false;
-    }
-
-    /**
-     * Alias for port()
-     *
-     * This just aliases port() because for gateways we use this field to
-     * store the gateway metric.
-     *
-     * @return Gateway metric
-     */
-    inline unsigned int metric() const
-    {
-        return port();
-    }
-
-    /**
-     * Construct a full netmask as an InetAddress
-     *
-     * @return Netmask such as 255.255.255.0 if this address is /24 (port field will be unchanged)
-     */
-    InetAddress netmask() const;
-
-    /**
-     * Constructs a broadcast address from a network/netmask address
-     *
-     * This is only valid for IPv4 and will return a NULL InetAddress for other
-     * address families.
-     *
-     * @return Broadcast address (only IP portion is meaningful)
-     */
-    InetAddress broadcast() const;
-
-    /**
-     * Return the network -- a.k.a. the IP ANDed with the netmask
-     *
-     * @return Network e.g. 10.0.1.0/24 from 10.0.1.200/24
-     */
-    InetAddress network() const;
-
-    /**
-     * Test whether this IPv6 prefix matches the prefix of a given IPv6 address
-     *
-     * @param addr Address to check
-     * @return True if this IPv6 prefix matches the prefix of a given IPv6 address
-     */
-    bool isEqualPrefix(const InetAddress& addr) const;
-
-    /**
-     * Test whether this IP/netmask contains this address
-     *
-     * @param addr Address to check
-     * @return True if this IP/netmask (route) contains this address
-     */
-    bool containsAddress(const InetAddress& addr) const;
-
-    /**
-     * @return True if this is an IPv4 address
-     */
-    inline bool isV4() const
-    {
-        return (ss_family == AF_INET);
-    }
-
-    /**
-     * @return True if this is an IPv6 address
-     */
-    inline bool isV6() const
-    {
-        return (ss_family == AF_INET6);
-    }
-
-    /**
-     * @return pointer to raw address bytes or NULL if not available
-     */
-    inline const void* rawIpData() const
-    {
-        switch (ss_family) {
-            case AF_INET:
-                return (const void*)&(reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr);
-            case AF_INET6:
-                return (const void*)(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr);
-            default:
-                return 0;
-        }
-    }
-
-    /**
-     * @return InetAddress containing only the IP portion of this address and a zero port, or NULL if not IPv4 or IPv6
-     */
-    inline InetAddress ipOnly() const
-    {
-        InetAddress r;
-        switch (ss_family) {
-            case AF_INET:
-                r.ss_family = AF_INET;
-                reinterpret_cast<struct sockaddr_in*>(&r)->sin_addr.s_addr = reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr;
-                break;
-            case AF_INET6:
-                r.ss_family = AF_INET6;
-                memcpy(reinterpret_cast<struct sockaddr_in6*>(&r)->sin6_addr.s6_addr, reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr, 16);
-                break;
-        }
-        return r;
-    }
-
-    /**
-     * Performs an IP-only comparison or, if that is impossible, a memcmp()
-     *
-     * @param a InetAddress to compare again
-     * @return True if only IP portions are equal (false for non-IP or null addresses)
-     */
-    inline bool ipsEqual(const InetAddress& a) const
-    {
-        if (ss_family == a.ss_family) {
-            if (ss_family == AF_INET) {
-                return (reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr == reinterpret_cast<const struct sockaddr_in*>(&a)->sin_addr.s_addr);
-            }
-            if (ss_family == AF_INET6) {
-                return (memcmp(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr, reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_addr.s6_addr, 16) == 0);
-            }
-            return (memcmp(this, &a, sizeof(InetAddress)) == 0);
-        }
-        return false;
-    }
-
-    /**
-     * Performs an IP-only comparison or, if that is impossible, a memcmp()
-     *
-     * This version compares only the first 64 bits of IPv6 addresses.
-     *
-     * @param a InetAddress to compare again
-     * @return True if only IP portions are equal (false for non-IP or null addresses)
-     */
-    inline bool ipsEqual2(const InetAddress& a) const
-    {
-        if (ss_family == a.ss_family) {
-            if (ss_family == AF_INET) {
-                return (reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr == reinterpret_cast<const struct sockaddr_in*>(&a)->sin_addr.s_addr);
-            }
-            if (ss_family == AF_INET6) {
-                return (memcmp(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr, reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_addr.s6_addr, 8) == 0);
-            }
-            return (memcmp(this, &a, sizeof(InetAddress)) == 0);
-        }
-        return false;
-    }
-
-    inline unsigned long hashCode() const
-    {
-        if (ss_family == AF_INET) {
-            return ((unsigned long)reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr + (unsigned long)reinterpret_cast<const struct sockaddr_in*>(this)->sin_port);
-        }
-        else if (ss_family == AF_INET6) {
-            unsigned long tmp = reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_port;
-            const uint8_t* a = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr);
-            for (long i = 0; i < 16; ++i) {
-                reinterpret_cast<uint8_t*>(&tmp)[i % sizeof(tmp)] ^= a[i];
-            }
-            return tmp;
-        }
-        else {
-            unsigned long tmp = reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_port;
-            const uint8_t* a = reinterpret_cast<const uint8_t*>(this);
-            for (long i = 0; i < (long)sizeof(InetAddress); ++i) {
-                reinterpret_cast<uint8_t*>(&tmp)[i % sizeof(tmp)] ^= a[i];
-            }
-            return tmp;
-        }
-    }
-
-    /**
-     * Set to null/zero
-     */
-    inline void zero()
-    {
-        memset(this, 0, sizeof(InetAddress));
-    }
-
-    /**
-     * Check whether this is a network/route rather than an IP assignment
-     *
-     * A network is an IP/netmask where everything after the netmask is
-     * zero e.g. 10.0.0.0/8.
-     *
-     * @return True if everything after netmask bits is zero
-     */
-    bool isNetwork() const;
-
-    /**
-     * Find the total number of prefix bits that match between this IP and another
-     *
-     * @param b Second IP to compare with
-     * @return Number of matching prefix bits or 0 if none match or IPs are of different families (e.g. v4 and v6)
-     */
-    inline unsigned int matchingPrefixBits(const InetAddress& b) const
-    {
-        unsigned int c = 0;
-        if (ss_family == b.ss_family) {
-            switch (ss_family) {
-                case AF_INET: {
-                    uint32_t ip0 = Utils::ntoh((uint32_t)reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr);
-                    uint32_t ip1 = Utils::ntoh((uint32_t)reinterpret_cast<const struct sockaddr_in*>(&b)->sin_addr.s_addr);
-                    while ((ip0 >> 31) == (ip1 >> 31)) {
-                        ip0 <<= 1;
-                        ip1 <<= 1;
-                        if (++c == 32) {
-                            break;
-                        }
-                    }
-                } break;
-                case AF_INET6: {
-                    const uint8_t* ip0 = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr);
-                    const uint8_t* ip1 = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(&b)->sin6_addr.s6_addr);
-                    for (unsigned int i = 0; i < 16; ++i) {
-                        if (ip0[i] == ip1[i]) {
-                            c += 8;
-                        }
-                        else {
-                            uint8_t ip0b = ip0[i];
-                            uint8_t ip1b = ip1[i];
-                            uint8_t bit = 0x80;
-                            while (bit != 0) {
-                                if ((ip0b & bit) != (ip1b & bit)) {
-                                    break;
-                                }
-                                ++c;
-                                bit >>= 1;
-                            }
-                            break;
-                        }
-                    }
-                } break;
-            }
-        }
-        return c;
-    }
-
-    /**
-     * @return 14-bit (0-16383) hash of this IP's first 24 or 48 bits (for V4 or V6) for rate limiting code, or 0 if non-IP
-     */
-    inline unsigned long rateGateHash() const
-    {
-        unsigned long h = 0;
-        switch (ss_family) {
-            case AF_INET:
-                h = (Utils::ntoh((uint32_t)reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr) & 0xffffff00) >> 8;
-                h ^= (h >> 14);
-                break;
-            case AF_INET6: {
-                const uint8_t* ip = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr);
-                h = ((unsigned long)ip[0]);
-                h <<= 1;
-                h += ((unsigned long)ip[1]);
-                h <<= 1;
-                h += ((unsigned long)ip[2]);
-                h <<= 1;
-                h += ((unsigned long)ip[3]);
-                h <<= 1;
-                h += ((unsigned long)ip[4]);
-                h <<= 1;
-                h += ((unsigned long)ip[5]);
-            } break;
-        }
-        return (h & 0x3fff);
-    }
-
-    /**
-     * @return True if address family is non-zero
-     */
-    inline operator bool() const
-    {
-        return (ss_family != 0);
-    }
-
-    template <unsigned int C> inline void serialize(Buffer<C>& b) const
-    {
-        // This is used in the protocol and must be the same as describe in places
-        // like VERB_HELLO in Packet.hpp.
-        switch (ss_family) {
-            case AF_INET:
-                b.append((uint8_t)0x04);
-                b.append(&(reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr), 4);
-                b.append((uint16_t)port());   // just in case sin_port != uint16_t
-                return;
-            case AF_INET6:
-                b.append((uint8_t)0x06);
-                b.append(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr, 16);
-                b.append((uint16_t)port());   // just in case sin_port != uint16_t
-                return;
-            default:
-                b.append((uint8_t)0);
-                return;
-        }
-    }
-
-    template <unsigned int C> inline unsigned int deserialize(const Buffer<C>& b, unsigned int startAt = 0)
-    {
-        memset(this, 0, sizeof(InetAddress));
-        unsigned int p = startAt;
-        switch (b[p++]) {
-            case 0:
-                return 1;
-            case 0x01:
-                // TODO: Ethernet address (but accept for forward compatibility)
-                return 7;
-            case 0x02:
-                // TODO: Bluetooth address (but accept for forward compatibility)
-                return 7;
-            case 0x03:
-                // TODO: Other address types (but accept for forward compatibility)
-                // These could be extended/optional things like AF_UNIX, LTE Direct, shared memory, etc.
-                return (unsigned int)(b.template at<uint16_t>(p) + 3);   // other addresses begin with 16-bit non-inclusive length
-            case 0x04:
-                ss_family = AF_INET;
-                memcpy(&(reinterpret_cast<struct sockaddr_in*>(this)->sin_addr.s_addr), b.field(p, 4), 4);
-                p += 4;
-                reinterpret_cast<struct sockaddr_in*>(this)->sin_port = Utils::hton(b.template at<uint16_t>(p));
-                p += 2;
-                break;
-            case 0x06:
-                ss_family = AF_INET6;
-                memcpy(reinterpret_cast<struct sockaddr_in6*>(this)->sin6_addr.s6_addr, b.field(p, 16), 16);
-                p += 16;
-                reinterpret_cast<struct sockaddr_in*>(this)->sin_port = Utils::hton(b.template at<uint16_t>(p));
-                p += 2;
-                break;
-            default:
-                throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_BAD_ENCODING;
-        }
-        return (p - startAt);
-    }
-
-    bool operator==(const InetAddress& a) const;
-    bool operator<(const InetAddress& a) const;
-    inline bool operator!=(const InetAddress& a) const
-    {
-        return ! (*this == a);
-    }
-    inline bool operator>(const InetAddress& a) const
-    {
-        return (a < *this);
-    }
-    inline bool operator<=(const InetAddress& a) const
-    {
-        return ! (a < *this);
-    }
-    inline bool operator>=(const InetAddress& a) const
-    {
-        return ! (*this < a);
-    }
-
-    /**
-     * @param mac MAC address seed
-     * @return IPv6 link-local address
-     */
-    static InetAddress makeIpv6LinkLocal(const MAC& mac);
-
-    /**
-     * Compute private IPv6 unicast address from network ID and ZeroTier address
-     *
-     * This generates a private unicast IPv6 address that is mostly compliant
-     * with the letter of RFC4193 and certainly compliant in spirit.
-     *
-     * RFC4193 specifies a format of:
-     *
-     * | 7 bits |1|  40 bits   |  16 bits  |          64 bits           |
-     * | Prefix |L| Global ID  | Subnet ID |        Interface ID        |
-     *
-     * The 'L' bit is set to 1, yielding an address beginning with 0xfd. Then
-     * the network ID is filled into the global ID, subnet ID, and first byte
-     * of the "interface ID" field. Since the first 40 bits of the network ID
-     * is the unique ZeroTier address of its controller, this makes a very
-     * good random global ID. Since network IDs have 24 more bits, we let it
-     * overflow into the interface ID.
-     *
-     * After that we pad with two bytes: 0x99, 0x93, namely the default ZeroTier
-     * port in hex.
-     *
-     * Finally we fill the remaining 40 bits of the interface ID field with
-     * the 40-bit unique ZeroTier device ID of the network member.
-     *
-     * This yields a valid RFC4193 address with a random global ID, a
-     * meaningful subnet ID, and a unique interface ID, all mappable back onto
-     * ZeroTier space.
-     *
-     * This in turn could allow us, on networks numbered this way, to emulate
-     * IPv6 NDP and eliminate all multicast. This could be beneficial for
-     * small devices and huge networks, e.g. IoT applications.
-     *
-     * The returned address is given an odd prefix length of /88, since within
-     * a given network only the last 40 bits (device ID) are variable. This
-     * is a bit unusual but as far as we know should not cause any problems with
-     * any non-braindead IPv6 stack.
-     *
-     * @param nwid 64-bit network ID
-     * @param zeroTierAddress 40-bit device address (in least significant 40 bits, highest 24 bits ignored)
-     * @return IPv6 private unicast address with /88 netmask
-     */
-    static InetAddress makeIpv6rfc4193(uint64_t nwid, uint64_t zeroTierAddress);
-
-    /**
-     * Compute a private IPv6 "6plane" unicast address from network ID and ZeroTier address
-     */
-    static InetAddress makeIpv66plane(uint64_t nwid, uint64_t zeroTierAddress);
+	/**
+	 * Loopback IPv4 address (no port)
+	 */
+	static const InetAddress LO4;
+
+	/**
+	 * Loopback IPV6 address (no port)
+	 */
+	static const InetAddress LO6;
+
+	/**
+	 * IP address scope
+	 *
+	 * Note that these values are in ascending order of path preference and
+	 * MUST remain that way or Path must be changed to reflect. Also be sure
+	 * to change ZT_INETADDRESS_MAX_SCOPE if the max changes.
+	 */
+	enum IpScope {
+		IP_SCOPE_NONE = 0,			  // NULL or not an IP address
+		IP_SCOPE_MULTICAST = 1,		  // 224.0.0.0 and other V4/V6 multicast IPs
+		IP_SCOPE_LOOPBACK = 2,		  // 127.0.0.1, ::1, etc.
+		IP_SCOPE_PSEUDOPRIVATE = 3,	  // 28.x.x.x, etc. -- unofficially unrouted IPv4 blocks often "bogarted"
+		IP_SCOPE_GLOBAL = 4,		  // globally routable IP address (all others)
+		IP_SCOPE_LINK_LOCAL = 5,	  // 169.254.x.x, IPv6 LL
+		IP_SCOPE_SHARED = 6,		  // currently unused, formerly used for carrier-grade NAT ranges
+		IP_SCOPE_PRIVATE = 7		  // 10.x.x.x, 192.168.x.x, etc.
+	};
+
+	// Can be used with the unordered maps and sets in c++11. We don't use C++11 in the core
+	// but this is safe to put here.
+	struct Hasher {
+		inline std::size_t operator()(const InetAddress& a) const
+		{
+			return (std::size_t)a.hashCode();
+		}
+	};
+
+	InetAddress()
+	{
+		memset(this, 0, sizeof(InetAddress));
+	}
+	InetAddress(const InetAddress& a)
+	{
+		memcpy(this, &a, sizeof(InetAddress));
+	}
+	InetAddress(const InetAddress* a)
+	{
+		memcpy(this, a, sizeof(InetAddress));
+	}
+	InetAddress(const struct sockaddr_storage& ss)
+	{
+		*this = ss;
+	}
+	InetAddress(const struct sockaddr_storage* ss)
+	{
+		*this = ss;
+	}
+	InetAddress(const struct sockaddr& sa)
+	{
+		*this = sa;
+	}
+	InetAddress(const struct sockaddr* sa)
+	{
+		*this = sa;
+	}
+	InetAddress(const struct sockaddr_in& sa)
+	{
+		*this = sa;
+	}
+	InetAddress(const struct sockaddr_in* sa)
+	{
+		*this = sa;
+	}
+	InetAddress(const struct sockaddr_in6& sa)
+	{
+		*this = sa;
+	}
+	InetAddress(const struct sockaddr_in6* sa)
+	{
+		*this = sa;
+	}
+	InetAddress(const void* ipBytes, unsigned int ipLen, unsigned int port)
+	{
+		this->set(ipBytes, ipLen, port);
+	}
+	InetAddress(const uint32_t ipv4, unsigned int port)
+	{
+		this->set(&ipv4, 4, port);
+	}
+	InetAddress(const char* ipSlashPort)
+	{
+		this->fromString(ipSlashPort);
+	}
+
+	inline InetAddress& operator=(const InetAddress& a)
+	{
+		if (&a != this) {
+			memcpy(this, &a, sizeof(InetAddress));
+		}
+		return *this;
+	}
+
+	inline InetAddress& operator=(const InetAddress* a)
+	{
+		if (a != this) {
+			memcpy(this, a, sizeof(InetAddress));
+		}
+		return *this;
+	}
+
+	inline InetAddress& operator=(const struct sockaddr_storage& ss)
+	{
+		if (reinterpret_cast<const InetAddress*>(&ss) != this) {
+			memcpy(this, &ss, sizeof(InetAddress));
+		}
+		return *this;
+	}
+
+	inline InetAddress& operator=(const struct sockaddr_storage* ss)
+	{
+		if (reinterpret_cast<const InetAddress*>(ss) != this) {
+			memcpy(this, ss, sizeof(InetAddress));
+		}
+		return *this;
+	}
+
+	inline InetAddress& operator=(const struct sockaddr_in& sa)
+	{
+		if (reinterpret_cast<const InetAddress*>(&sa) != this) {
+			memset(this, 0, sizeof(InetAddress));
+			memcpy(this, &sa, sizeof(struct sockaddr_in));
+		}
+		return *this;
+	}
+
+	inline InetAddress& operator=(const struct sockaddr_in* sa)
+	{
+		if (reinterpret_cast<const InetAddress*>(sa) != this) {
+			memset(this, 0, sizeof(InetAddress));
+			memcpy(this, sa, sizeof(struct sockaddr_in));
+		}
+		return *this;
+	}
+
+	inline InetAddress& operator=(const struct sockaddr_in6& sa)
+	{
+		if (reinterpret_cast<const InetAddress*>(&sa) != this) {
+			memset(this, 0, sizeof(InetAddress));
+			memcpy(this, &sa, sizeof(struct sockaddr_in6));
+		}
+		return *this;
+	}
+
+	inline InetAddress& operator=(const struct sockaddr_in6* sa)
+	{
+		if (reinterpret_cast<const InetAddress*>(sa) != this) {
+			memset(this, 0, sizeof(InetAddress));
+			memcpy(this, sa, sizeof(struct sockaddr_in6));
+		}
+		return *this;
+	}
+
+	inline InetAddress& operator=(const struct sockaddr& sa)
+	{
+		if (reinterpret_cast<const InetAddress*>(&sa) != this) {
+			memset(this, 0, sizeof(InetAddress));
+			switch (sa.sa_family) {
+				case AF_INET:
+					memcpy(this, &sa, sizeof(struct sockaddr_in));
+					break;
+				case AF_INET6:
+					memcpy(this, &sa, sizeof(struct sockaddr_in6));
+					break;
+			}
+		}
+		return *this;
+	}
+
+	inline InetAddress& operator=(const struct sockaddr* sa)
+	{
+		if (reinterpret_cast<const InetAddress*>(sa) != this) {
+			memset(this, 0, sizeof(InetAddress));
+			switch (sa->sa_family) {
+				case AF_INET:
+					memcpy(this, sa, sizeof(struct sockaddr_in));
+					break;
+				case AF_INET6:
+					memcpy(this, sa, sizeof(struct sockaddr_in6));
+					break;
+			}
+		}
+		return *this;
+	}
+
+	/**
+	 * @return IP scope classification (e.g. loopback, link-local, private, global)
+	 */
+	IpScope ipScope() const;
+
+	/**
+	 * Set from a raw IP and port number
+	 *
+	 * @param ipBytes Bytes of IP address in network byte order
+	 * @param ipLen Length of IP address: 4 or 16
+	 * @param port Port number or 0 for none
+	 */
+	void set(const void* ipBytes, unsigned int ipLen, unsigned int port);
+
+	/**
+	 * Set the port component
+	 *
+	 * @param port Port, 0 to 65535
+	 */
+	inline void setPort(unsigned int port)
+	{
+		switch (ss_family) {
+			case AF_INET:
+				reinterpret_cast<struct sockaddr_in*>(this)->sin_port = Utils::hton((uint16_t)port);
+				break;
+			case AF_INET6:
+				reinterpret_cast<struct sockaddr_in6*>(this)->sin6_port = Utils::hton((uint16_t)port);
+				break;
+		}
+	}
+
+	/**
+	 * @return True if this network/netmask route describes a default route (e.g. 0.0.0.0/0)
+	 */
+	inline bool isDefaultRoute() const
+	{
+		switch (ss_family) {
+			case AF_INET:
+				return ((reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr == 0) && (reinterpret_cast<const struct sockaddr_in*>(this)->sin_port == 0));
+			case AF_INET6:
+				const uint8_t* ipb = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr);
+				for (int i = 0; i < 16; ++i) {
+					if (ipb[i]) {
+						return false;
+					}
+				}
+				return (reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_port == 0);
+		}
+		return false;
+	}
+
+	/**
+	 * @return ASCII IP/port format representation
+	 */
+	char* toString(char buf[64]) const;
+
+	/**
+	 * @return IP portion only, in ASCII string format
+	 */
+	char* toIpString(char buf[64]) const;
+
+	/**
+	 * @param ipSlashPort IP/port (port is optional, will be 0 if not included)
+	 * @return True if address appeared to be valid
+	 */
+	bool fromString(const char* ipSlashPort);
+
+	/**
+	 * @return Port or 0 if no port component defined
+	 */
+	inline unsigned int port() const
+	{
+		switch (ss_family) {
+			case AF_INET:
+				return Utils::ntoh((uint16_t)(reinterpret_cast<const struct sockaddr_in*>(this)->sin_port));
+			case AF_INET6:
+				return Utils::ntoh((uint16_t)(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_port));
+			default:
+				return 0;
+		}
+	}
+
+	/**
+	 * Alias for port()
+	 *
+	 * This just aliases port() to make code more readable when netmask bits
+	 * are stuffed there, as they are in Network, EthernetTap, and a few other
+	 * spots.
+	 *
+	 * @return Netmask bits
+	 */
+	inline unsigned int netmaskBits() const
+	{
+		return port();
+	}
+
+	/**
+	 * @return True if netmask bits is valid for the address type
+	 */
+	inline bool netmaskBitsValid() const
+	{
+		const unsigned int n = port();
+		switch (ss_family) {
+			case AF_INET:
+				return (n <= 32);
+			case AF_INET6:
+				return (n <= 128);
+		}
+		return false;
+	}
+
+	/**
+	 * Alias for port()
+	 *
+	 * This just aliases port() because for gateways we use this field to
+	 * store the gateway metric.
+	 *
+	 * @return Gateway metric
+	 */
+	inline unsigned int metric() const
+	{
+		return port();
+	}
+
+	/**
+	 * Construct a full netmask as an InetAddress
+	 *
+	 * @return Netmask such as 255.255.255.0 if this address is /24 (port field will be unchanged)
+	 */
+	InetAddress netmask() const;
+
+	/**
+	 * Constructs a broadcast address from a network/netmask address
+	 *
+	 * This is only valid for IPv4 and will return a NULL InetAddress for other
+	 * address families.
+	 *
+	 * @return Broadcast address (only IP portion is meaningful)
+	 */
+	InetAddress broadcast() const;
+
+	/**
+	 * Return the network -- a.k.a. the IP ANDed with the netmask
+	 *
+	 * @return Network e.g. 10.0.1.0/24 from 10.0.1.200/24
+	 */
+	InetAddress network() const;
+
+	/**
+	 * Test whether this IPv6 prefix matches the prefix of a given IPv6 address
+	 *
+	 * @param addr Address to check
+	 * @return True if this IPv6 prefix matches the prefix of a given IPv6 address
+	 */
+	bool isEqualPrefix(const InetAddress& addr) const;
+
+	/**
+	 * Test whether this IP/netmask contains this address
+	 *
+	 * @param addr Address to check
+	 * @return True if this IP/netmask (route) contains this address
+	 */
+	bool containsAddress(const InetAddress& addr) const;
+
+	/**
+	 * @return True if this is an IPv4 address
+	 */
+	inline bool isV4() const
+	{
+		return (ss_family == AF_INET);
+	}
+
+	/**
+	 * @return True if this is an IPv6 address
+	 */
+	inline bool isV6() const
+	{
+		return (ss_family == AF_INET6);
+	}
+
+	/**
+	 * @return pointer to raw address bytes or NULL if not available
+	 */
+	inline const void* rawIpData() const
+	{
+		switch (ss_family) {
+			case AF_INET:
+				return (const void*)&(reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr);
+			case AF_INET6:
+				return (const void*)(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr);
+			default:
+				return 0;
+		}
+	}
+
+	/**
+	 * @return InetAddress containing only the IP portion of this address and a zero port, or NULL if not IPv4 or IPv6
+	 */
+	inline InetAddress ipOnly() const
+	{
+		InetAddress r;
+		switch (ss_family) {
+			case AF_INET:
+				r.ss_family = AF_INET;
+				reinterpret_cast<struct sockaddr_in*>(&r)->sin_addr.s_addr = reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr;
+				break;
+			case AF_INET6:
+				r.ss_family = AF_INET6;
+				memcpy(reinterpret_cast<struct sockaddr_in6*>(&r)->sin6_addr.s6_addr, reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr, 16);
+				break;
+		}
+		return r;
+	}
+
+	/**
+	 * Performs an IP-only comparison or, if that is impossible, a memcmp()
+	 *
+	 * @param a InetAddress to compare again
+	 * @return True if only IP portions are equal (false for non-IP or null addresses)
+	 */
+	inline bool ipsEqual(const InetAddress& a) const
+	{
+		if (ss_family == a.ss_family) {
+			if (ss_family == AF_INET) {
+				return (reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr == reinterpret_cast<const struct sockaddr_in*>(&a)->sin_addr.s_addr);
+			}
+			if (ss_family == AF_INET6) {
+				return (memcmp(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr, reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_addr.s6_addr, 16) == 0);
+			}
+			return (memcmp(this, &a, sizeof(InetAddress)) == 0);
+		}
+		return false;
+	}
+
+	/**
+	 * Performs an IP-only comparison or, if that is impossible, a memcmp()
+	 *
+	 * This version compares only the first 64 bits of IPv6 addresses.
+	 *
+	 * @param a InetAddress to compare again
+	 * @return True if only IP portions are equal (false for non-IP or null addresses)
+	 */
+	inline bool ipsEqual2(const InetAddress& a) const
+	{
+		if (ss_family == a.ss_family) {
+			if (ss_family == AF_INET) {
+				return (reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr == reinterpret_cast<const struct sockaddr_in*>(&a)->sin_addr.s_addr);
+			}
+			if (ss_family == AF_INET6) {
+				return (memcmp(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr, reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_addr.s6_addr, 8) == 0);
+			}
+			return (memcmp(this, &a, sizeof(InetAddress)) == 0);
+		}
+		return false;
+	}
+
+	inline unsigned long hashCode() const
+	{
+		if (ss_family == AF_INET) {
+			return ((unsigned long)reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr + (unsigned long)reinterpret_cast<const struct sockaddr_in*>(this)->sin_port);
+		}
+		else if (ss_family == AF_INET6) {
+			unsigned long tmp = reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_port;
+			const uint8_t* a = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr);
+			for (long i = 0; i < 16; ++i) {
+				reinterpret_cast<uint8_t*>(&tmp)[i % sizeof(tmp)] ^= a[i];
+			}
+			return tmp;
+		}
+		else {
+			unsigned long tmp = reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_port;
+			const uint8_t* a = reinterpret_cast<const uint8_t*>(this);
+			for (long i = 0; i < (long)sizeof(InetAddress); ++i) {
+				reinterpret_cast<uint8_t*>(&tmp)[i % sizeof(tmp)] ^= a[i];
+			}
+			return tmp;
+		}
+	}
+
+	/**
+	 * Set to null/zero
+	 */
+	inline void zero()
+	{
+		memset(this, 0, sizeof(InetAddress));
+	}
+
+	/**
+	 * Check whether this is a network/route rather than an IP assignment
+	 *
+	 * A network is an IP/netmask where everything after the netmask is
+	 * zero e.g. 10.0.0.0/8.
+	 *
+	 * @return True if everything after netmask bits is zero
+	 */
+	bool isNetwork() const;
+
+	/**
+	 * Find the total number of prefix bits that match between this IP and another
+	 *
+	 * @param b Second IP to compare with
+	 * @return Number of matching prefix bits or 0 if none match or IPs are of different families (e.g. v4 and v6)
+	 */
+	inline unsigned int matchingPrefixBits(const InetAddress& b) const
+	{
+		unsigned int c = 0;
+		if (ss_family == b.ss_family) {
+			switch (ss_family) {
+				case AF_INET: {
+					uint32_t ip0 = Utils::ntoh((uint32_t)reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr);
+					uint32_t ip1 = Utils::ntoh((uint32_t)reinterpret_cast<const struct sockaddr_in*>(&b)->sin_addr.s_addr);
+					while ((ip0 >> 31) == (ip1 >> 31)) {
+						ip0 <<= 1;
+						ip1 <<= 1;
+						if (++c == 32) {
+							break;
+						}
+					}
+				} break;
+				case AF_INET6: {
+					const uint8_t* ip0 = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr);
+					const uint8_t* ip1 = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(&b)->sin6_addr.s6_addr);
+					for (unsigned int i = 0; i < 16; ++i) {
+						if (ip0[i] == ip1[i]) {
+							c += 8;
+						}
+						else {
+							uint8_t ip0b = ip0[i];
+							uint8_t ip1b = ip1[i];
+							uint8_t bit = 0x80;
+							while (bit != 0) {
+								if ((ip0b & bit) != (ip1b & bit)) {
+									break;
+								}
+								++c;
+								bit >>= 1;
+							}
+							break;
+						}
+					}
+				} break;
+			}
+		}
+		return c;
+	}
+
+	/**
+	 * @return 14-bit (0-16383) hash of this IP's first 24 or 48 bits (for V4 or V6) for rate limiting code, or 0 if non-IP
+	 */
+	inline unsigned long rateGateHash() const
+	{
+		unsigned long h = 0;
+		switch (ss_family) {
+			case AF_INET:
+				h = (Utils::ntoh((uint32_t)reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr) & 0xffffff00) >> 8;
+				h ^= (h >> 14);
+				break;
+			case AF_INET6: {
+				const uint8_t* ip = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr);
+				h = ((unsigned long)ip[0]);
+				h <<= 1;
+				h += ((unsigned long)ip[1]);
+				h <<= 1;
+				h += ((unsigned long)ip[2]);
+				h <<= 1;
+				h += ((unsigned long)ip[3]);
+				h <<= 1;
+				h += ((unsigned long)ip[4]);
+				h <<= 1;
+				h += ((unsigned long)ip[5]);
+			} break;
+		}
+		return (h & 0x3fff);
+	}
+
+	/**
+	 * @return True if address family is non-zero
+	 */
+	inline operator bool() const
+	{
+		return (ss_family != 0);
+	}
+
+	template <unsigned int C> inline void serialize(Buffer<C>& b) const
+	{
+		// This is used in the protocol and must be the same as describe in places
+		// like VERB_HELLO in Packet.hpp.
+		switch (ss_family) {
+			case AF_INET:
+				b.append((uint8_t)0x04);
+				b.append(&(reinterpret_cast<const struct sockaddr_in*>(this)->sin_addr.s_addr), 4);
+				b.append((uint16_t)port());	  // just in case sin_port != uint16_t
+				return;
+			case AF_INET6:
+				b.append((uint8_t)0x06);
+				b.append(reinterpret_cast<const struct sockaddr_in6*>(this)->sin6_addr.s6_addr, 16);
+				b.append((uint16_t)port());	  // just in case sin_port != uint16_t
+				return;
+			default:
+				b.append((uint8_t)0);
+				return;
+		}
+	}
+
+	template <unsigned int C> inline unsigned int deserialize(const Buffer<C>& b, unsigned int startAt = 0)
+	{
+		memset(this, 0, sizeof(InetAddress));
+		unsigned int p = startAt;
+		switch (b[p++]) {
+			case 0:
+				return 1;
+			case 0x01:
+				// TODO: Ethernet address (but accept for forward compatibility)
+				return 7;
+			case 0x02:
+				// TODO: Bluetooth address (but accept for forward compatibility)
+				return 7;
+			case 0x03:
+				// TODO: Other address types (but accept for forward compatibility)
+				// These could be extended/optional things like AF_UNIX, LTE Direct, shared memory, etc.
+				return (unsigned int)(b.template at<uint16_t>(p) + 3);	 // other addresses begin with 16-bit non-inclusive length
+			case 0x04:
+				ss_family = AF_INET;
+				memcpy(&(reinterpret_cast<struct sockaddr_in*>(this)->sin_addr.s_addr), b.field(p, 4), 4);
+				p += 4;
+				reinterpret_cast<struct sockaddr_in*>(this)->sin_port = Utils::hton(b.template at<uint16_t>(p));
+				p += 2;
+				break;
+			case 0x06:
+				ss_family = AF_INET6;
+				memcpy(reinterpret_cast<struct sockaddr_in6*>(this)->sin6_addr.s6_addr, b.field(p, 16), 16);
+				p += 16;
+				reinterpret_cast<struct sockaddr_in*>(this)->sin_port = Utils::hton(b.template at<uint16_t>(p));
+				p += 2;
+				break;
+			default:
+				throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_BAD_ENCODING;
+		}
+		return (p - startAt);
+	}
+
+	bool operator==(const InetAddress& a) const;
+	bool operator<(const InetAddress& a) const;
+	inline bool operator!=(const InetAddress& a) const
+	{
+		return ! (*this == a);
+	}
+	inline bool operator>(const InetAddress& a) const
+	{
+		return (a < *this);
+	}
+	inline bool operator<=(const InetAddress& a) const
+	{
+		return ! (a < *this);
+	}
+	inline bool operator>=(const InetAddress& a) const
+	{
+		return ! (*this < a);
+	}
+
+	/**
+	 * @param mac MAC address seed
+	 * @return IPv6 link-local address
+	 */
+	static InetAddress makeIpv6LinkLocal(const MAC& mac);
+
+	/**
+	 * Compute private IPv6 unicast address from network ID and ZeroTier address
+	 *
+	 * This generates a private unicast IPv6 address that is mostly compliant
+	 * with the letter of RFC4193 and certainly compliant in spirit.
+	 *
+	 * RFC4193 specifies a format of:
+	 *
+	 * | 7 bits |1|  40 bits   |  16 bits  |          64 bits           |
+	 * | Prefix |L| Global ID  | Subnet ID |        Interface ID        |
+	 *
+	 * The 'L' bit is set to 1, yielding an address beginning with 0xfd. Then
+	 * the network ID is filled into the global ID, subnet ID, and first byte
+	 * of the "interface ID" field. Since the first 40 bits of the network ID
+	 * is the unique ZeroTier address of its controller, this makes a very
+	 * good random global ID. Since network IDs have 24 more bits, we let it
+	 * overflow into the interface ID.
+	 *
+	 * After that we pad with two bytes: 0x99, 0x93, namely the default ZeroTier
+	 * port in hex.
+	 *
+	 * Finally we fill the remaining 40 bits of the interface ID field with
+	 * the 40-bit unique ZeroTier device ID of the network member.
+	 *
+	 * This yields a valid RFC4193 address with a random global ID, a
+	 * meaningful subnet ID, and a unique interface ID, all mappable back onto
+	 * ZeroTier space.
+	 *
+	 * This in turn could allow us, on networks numbered this way, to emulate
+	 * IPv6 NDP and eliminate all multicast. This could be beneficial for
+	 * small devices and huge networks, e.g. IoT applications.
+	 *
+	 * The returned address is given an odd prefix length of /88, since within
+	 * a given network only the last 40 bits (device ID) are variable. This
+	 * is a bit unusual but as far as we know should not cause any problems with
+	 * any non-braindead IPv6 stack.
+	 *
+	 * @param nwid 64-bit network ID
+	 * @param zeroTierAddress 40-bit device address (in least significant 40 bits, highest 24 bits ignored)
+	 * @return IPv6 private unicast address with /88 netmask
+	 */
+	static InetAddress makeIpv6rfc4193(uint64_t nwid, uint64_t zeroTierAddress);
+
+	/**
+	 * Compute a private IPv6 "6plane" unicast address from network ID and ZeroTier address
+	 */
+	static InetAddress makeIpv66plane(uint64_t nwid, uint64_t zeroTierAddress);
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 238 - 238
node/MAC.hpp

@@ -30,266 +30,266 @@ namespace ZeroTier {
  */
 class MAC {
   public:
-    MAC() : _m(0ULL)
-    {
-    }
-    MAC(const MAC& m) : _m(m._m)
-    {
-    }
+	MAC() : _m(0ULL)
+	{
+	}
+	MAC(const MAC& m) : _m(m._m)
+	{
+	}
 
-    MAC(const unsigned char a, const unsigned char b, const unsigned char c, const unsigned char d, const unsigned char e, const unsigned char f)
-        : _m(((((uint64_t)a) & 0xffULL) << 40) | ((((uint64_t)b) & 0xffULL) << 32) | ((((uint64_t)c) & 0xffULL) << 24) | ((((uint64_t)d) & 0xffULL) << 16) | ((((uint64_t)e) & 0xffULL) << 8) | (((uint64_t)f) & 0xffULL))
-    {
-    }
-    MAC(const void* bits, unsigned int len)
-    {
-        setTo(bits, len);
-    }
-    MAC(const Address& ztaddr, uint64_t nwid)
-    {
-        fromAddress(ztaddr, nwid);
-    }
-    MAC(const uint64_t m) : _m(m & 0xffffffffffffULL)
-    {
-    }
+	MAC(const unsigned char a, const unsigned char b, const unsigned char c, const unsigned char d, const unsigned char e, const unsigned char f)
+		: _m(((((uint64_t)a) & 0xffULL) << 40) | ((((uint64_t)b) & 0xffULL) << 32) | ((((uint64_t)c) & 0xffULL) << 24) | ((((uint64_t)d) & 0xffULL) << 16) | ((((uint64_t)e) & 0xffULL) << 8) | (((uint64_t)f) & 0xffULL))
+	{
+	}
+	MAC(const void* bits, unsigned int len)
+	{
+		setTo(bits, len);
+	}
+	MAC(const Address& ztaddr, uint64_t nwid)
+	{
+		fromAddress(ztaddr, nwid);
+	}
+	MAC(const uint64_t m) : _m(m & 0xffffffffffffULL)
+	{
+	}
 
-    /**
-     * @return MAC in 64-bit integer
-     */
-    inline uint64_t toInt() const
-    {
-        return _m;
-    }
+	/**
+	 * @return MAC in 64-bit integer
+	 */
+	inline uint64_t toInt() const
+	{
+		return _m;
+	}
 
-    /**
-     * Set MAC to zero
-     */
-    inline void zero()
-    {
-        _m = 0ULL;
-    }
+	/**
+	 * Set MAC to zero
+	 */
+	inline void zero()
+	{
+		_m = 0ULL;
+	}
 
-    /**
-     * @return True if MAC is non-zero
-     */
-    inline operator bool() const
-    {
-        return (_m != 0ULL);
-    }
+	/**
+	 * @return True if MAC is non-zero
+	 */
+	inline operator bool() const
+	{
+		return (_m != 0ULL);
+	}
 
-    /**
-     * @param bits Raw MAC in big-endian byte order
-     * @param len Length, must be >= 6 or result is zero
-     */
-    inline void setTo(const void* bits, unsigned int len)
-    {
-        if (len < 6) {
-            _m = 0ULL;
-            return;
-        }
-        const unsigned char* b = (const unsigned char*)bits;
-        _m = ((((uint64_t)*b) & 0xff) << 40);
-        ++b;
-        _m |= ((((uint64_t)*b) & 0xff) << 32);
-        ++b;
-        _m |= ((((uint64_t)*b) & 0xff) << 24);
-        ++b;
-        _m |= ((((uint64_t)*b) & 0xff) << 16);
-        ++b;
-        _m |= ((((uint64_t)*b) & 0xff) << 8);
-        ++b;
-        _m |= (((uint64_t)*b) & 0xff);
-    }
+	/**
+	 * @param bits Raw MAC in big-endian byte order
+	 * @param len Length, must be >= 6 or result is zero
+	 */
+	inline void setTo(const void* bits, unsigned int len)
+	{
+		if (len < 6) {
+			_m = 0ULL;
+			return;
+		}
+		const unsigned char* b = (const unsigned char*)bits;
+		_m = ((((uint64_t)*b) & 0xff) << 40);
+		++b;
+		_m |= ((((uint64_t)*b) & 0xff) << 32);
+		++b;
+		_m |= ((((uint64_t)*b) & 0xff) << 24);
+		++b;
+		_m |= ((((uint64_t)*b) & 0xff) << 16);
+		++b;
+		_m |= ((((uint64_t)*b) & 0xff) << 8);
+		++b;
+		_m |= (((uint64_t)*b) & 0xff);
+	}
 
-    /**
-     * @param buf Destination buffer for MAC in big-endian byte order
-     * @param len Length of buffer, must be >= 6 or nothing is copied
-     */
-    inline void copyTo(void* buf, unsigned int len) const
-    {
-        if (len < 6) {
-            return;
-        }
-        unsigned char* b = (unsigned char*)buf;
-        *(b++) = (unsigned char)((_m >> 40) & 0xff);
-        *(b++) = (unsigned char)((_m >> 32) & 0xff);
-        *(b++) = (unsigned char)((_m >> 24) & 0xff);
-        *(b++) = (unsigned char)((_m >> 16) & 0xff);
-        *(b++) = (unsigned char)((_m >> 8) & 0xff);
-        *b = (unsigned char)(_m & 0xff);
-    }
+	/**
+	 * @param buf Destination buffer for MAC in big-endian byte order
+	 * @param len Length of buffer, must be >= 6 or nothing is copied
+	 */
+	inline void copyTo(void* buf, unsigned int len) const
+	{
+		if (len < 6) {
+			return;
+		}
+		unsigned char* b = (unsigned char*)buf;
+		*(b++) = (unsigned char)((_m >> 40) & 0xff);
+		*(b++) = (unsigned char)((_m >> 32) & 0xff);
+		*(b++) = (unsigned char)((_m >> 24) & 0xff);
+		*(b++) = (unsigned char)((_m >> 16) & 0xff);
+		*(b++) = (unsigned char)((_m >> 8) & 0xff);
+		*b = (unsigned char)(_m & 0xff);
+	}
 
-    /**
-     * Append to a buffer in big-endian byte order
-     *
-     * @param b Buffer to append to
-     */
-    template <unsigned int C> inline void appendTo(Buffer<C>& b) const
-    {
-        unsigned char* p = (unsigned char*)b.appendField(6);
-        *(p++) = (unsigned char)((_m >> 40) & 0xff);
-        *(p++) = (unsigned char)((_m >> 32) & 0xff);
-        *(p++) = (unsigned char)((_m >> 24) & 0xff);
-        *(p++) = (unsigned char)((_m >> 16) & 0xff);
-        *(p++) = (unsigned char)((_m >> 8) & 0xff);
-        *p = (unsigned char)(_m & 0xff);
-    }
+	/**
+	 * Append to a buffer in big-endian byte order
+	 *
+	 * @param b Buffer to append to
+	 */
+	template <unsigned int C> inline void appendTo(Buffer<C>& b) const
+	{
+		unsigned char* p = (unsigned char*)b.appendField(6);
+		*(p++) = (unsigned char)((_m >> 40) & 0xff);
+		*(p++) = (unsigned char)((_m >> 32) & 0xff);
+		*(p++) = (unsigned char)((_m >> 24) & 0xff);
+		*(p++) = (unsigned char)((_m >> 16) & 0xff);
+		*(p++) = (unsigned char)((_m >> 8) & 0xff);
+		*p = (unsigned char)(_m & 0xff);
+	}
 
-    /**
-     * @return True if this is broadcast (all 0xff)
-     */
-    inline bool isBroadcast() const
-    {
-        return (_m == 0xffffffffffffULL);
-    }
+	/**
+	 * @return True if this is broadcast (all 0xff)
+	 */
+	inline bool isBroadcast() const
+	{
+		return (_m == 0xffffffffffffULL);
+	}
 
-    /**
-     * @return True if this is a multicast MAC
-     */
-    inline bool isMulticast() const
-    {
-        return ((_m & 0x010000000000ULL) != 0ULL);
-    }
+	/**
+	 * @return True if this is a multicast MAC
+	 */
+	inline bool isMulticast() const
+	{
+		return ((_m & 0x010000000000ULL) != 0ULL);
+	}
 
-    /**
-     * @param True if this is a locally-administered MAC
-     */
-    inline bool isLocallyAdministered() const
-    {
-        return ((_m & 0x020000000000ULL) != 0ULL);
-    }
+	/**
+	 * @param True if this is a locally-administered MAC
+	 */
+	inline bool isLocallyAdministered() const
+	{
+		return ((_m & 0x020000000000ULL) != 0ULL);
+	}
 
-    /**
-     * Set this MAC to a MAC derived from an address and a network ID
-     *
-     * @param ztaddr ZeroTier address
-     * @param nwid 64-bit network ID
-     */
-    inline void fromAddress(const Address& ztaddr, uint64_t nwid)
-    {
-        uint64_t m = ((uint64_t)firstOctetForNetwork(nwid)) << 40;
-        m |= ztaddr.toInt();   // a is 40 bits
-        m ^= ((nwid >> 8) & 0xff) << 32;
-        m ^= ((nwid >> 16) & 0xff) << 24;
-        m ^= ((nwid >> 24) & 0xff) << 16;
-        m ^= ((nwid >> 32) & 0xff) << 8;
-        m ^= (nwid >> 40) & 0xff;
-        _m = m;
-    }
+	/**
+	 * Set this MAC to a MAC derived from an address and a network ID
+	 *
+	 * @param ztaddr ZeroTier address
+	 * @param nwid 64-bit network ID
+	 */
+	inline void fromAddress(const Address& ztaddr, uint64_t nwid)
+	{
+		uint64_t m = ((uint64_t)firstOctetForNetwork(nwid)) << 40;
+		m |= ztaddr.toInt();   // a is 40 bits
+		m ^= ((nwid >> 8) & 0xff) << 32;
+		m ^= ((nwid >> 16) & 0xff) << 24;
+		m ^= ((nwid >> 24) & 0xff) << 16;
+		m ^= ((nwid >> 32) & 0xff) << 8;
+		m ^= (nwid >> 40) & 0xff;
+		_m = m;
+	}
 
-    /**
-     * Get the ZeroTier address for this MAC on this network (assuming no bridging of course, basic unicast)
-     *
-     * This just XORs the next-least-significant 5 bytes of the network ID again to unmask.
-     *
-     * @param nwid Network ID
-     */
-    inline Address toAddress(uint64_t nwid) const
-    {
-        uint64_t a = _m & 0xffffffffffULL;   // least significant 40 bits of MAC are formed from address
-        a ^= ((nwid >> 8) & 0xff) << 32;     // ... XORed with bits 8-48 of the nwid in little-endian byte order, so unmask it
-        a ^= ((nwid >> 16) & 0xff) << 24;
-        a ^= ((nwid >> 24) & 0xff) << 16;
-        a ^= ((nwid >> 32) & 0xff) << 8;
-        a ^= (nwid >> 40) & 0xff;
-        return Address(a);
-    }
+	/**
+	 * Get the ZeroTier address for this MAC on this network (assuming no bridging of course, basic unicast)
+	 *
+	 * This just XORs the next-least-significant 5 bytes of the network ID again to unmask.
+	 *
+	 * @param nwid Network ID
+	 */
+	inline Address toAddress(uint64_t nwid) const
+	{
+		uint64_t a = _m & 0xffffffffffULL;	 // least significant 40 bits of MAC are formed from address
+		a ^= ((nwid >> 8) & 0xff) << 32;	 // ... XORed with bits 8-48 of the nwid in little-endian byte order, so unmask it
+		a ^= ((nwid >> 16) & 0xff) << 24;
+		a ^= ((nwid >> 24) & 0xff) << 16;
+		a ^= ((nwid >> 32) & 0xff) << 8;
+		a ^= (nwid >> 40) & 0xff;
+		return Address(a);
+	}
 
-    /**
-     * @param nwid Network ID
-     * @return First octet of MAC for this network
-     */
-    static inline unsigned char firstOctetForNetwork(uint64_t nwid)
-    {
-        unsigned char a = ((unsigned char)(nwid & 0xfe) | 0x02);   // locally administered, not multicast, from LSB of network ID
-        return ((a == 0x52) ? 0x32 : a);                           // blacklist 0x52 since it's used by KVM, libvirt, and other popular virtualization engines... seems de-facto standard on Linux
-    }
+	/**
+	 * @param nwid Network ID
+	 * @return First octet of MAC for this network
+	 */
+	static inline unsigned char firstOctetForNetwork(uint64_t nwid)
+	{
+		unsigned char a = ((unsigned char)(nwid & 0xfe) | 0x02);   // locally administered, not multicast, from LSB of network ID
+		return ((a == 0x52) ? 0x32 : a);						   // blacklist 0x52 since it's used by KVM, libvirt, and other popular virtualization engines... seems de-facto standard on Linux
+	}
 
-    /**
-     * @param i Value from 0 to 5 (inclusive)
-     * @return Byte at said position (address interpreted in big-endian order)
-     */
-    inline unsigned char operator[](unsigned int i) const
-    {
-        return (unsigned char)((_m >> (40 - (i * 8))) & 0xff);
-    }
+	/**
+	 * @param i Value from 0 to 5 (inclusive)
+	 * @return Byte at said position (address interpreted in big-endian order)
+	 */
+	inline unsigned char operator[](unsigned int i) const
+	{
+		return (unsigned char)((_m >> (40 - (i * 8))) & 0xff);
+	}
 
-    /**
-     * @return 6, which is the number of bytes in a MAC, for container compliance
-     */
-    inline unsigned int size() const
-    {
-        return 6;
-    }
+	/**
+	 * @return 6, which is the number of bytes in a MAC, for container compliance
+	 */
+	inline unsigned int size() const
+	{
+		return 6;
+	}
 
-    inline unsigned long hashCode() const
-    {
-        return (unsigned long)_m;
-    }
+	inline unsigned long hashCode() const
+	{
+		return (unsigned long)_m;
+	}
 
-    inline char* toString(char buf[18]) const
-    {
-        buf[0] = Utils::HEXCHARS[(_m >> 44) & 0xf];
-        buf[1] = Utils::HEXCHARS[(_m >> 40) & 0xf];
-        buf[2] = ':';
-        buf[3] = Utils::HEXCHARS[(_m >> 36) & 0xf];
-        buf[4] = Utils::HEXCHARS[(_m >> 32) & 0xf];
-        buf[5] = ':';
-        buf[6] = Utils::HEXCHARS[(_m >> 28) & 0xf];
-        buf[7] = Utils::HEXCHARS[(_m >> 24) & 0xf];
-        buf[8] = ':';
-        buf[9] = Utils::HEXCHARS[(_m >> 20) & 0xf];
-        buf[10] = Utils::HEXCHARS[(_m >> 16) & 0xf];
-        buf[11] = ':';
-        buf[12] = Utils::HEXCHARS[(_m >> 12) & 0xf];
-        buf[13] = Utils::HEXCHARS[(_m >> 8) & 0xf];
-        buf[14] = ':';
-        buf[15] = Utils::HEXCHARS[(_m >> 4) & 0xf];
-        buf[16] = Utils::HEXCHARS[_m & 0xf];
-        buf[17] = (char)0;
-        return buf;
-    }
+	inline char* toString(char buf[18]) const
+	{
+		buf[0] = Utils::HEXCHARS[(_m >> 44) & 0xf];
+		buf[1] = Utils::HEXCHARS[(_m >> 40) & 0xf];
+		buf[2] = ':';
+		buf[3] = Utils::HEXCHARS[(_m >> 36) & 0xf];
+		buf[4] = Utils::HEXCHARS[(_m >> 32) & 0xf];
+		buf[5] = ':';
+		buf[6] = Utils::HEXCHARS[(_m >> 28) & 0xf];
+		buf[7] = Utils::HEXCHARS[(_m >> 24) & 0xf];
+		buf[8] = ':';
+		buf[9] = Utils::HEXCHARS[(_m >> 20) & 0xf];
+		buf[10] = Utils::HEXCHARS[(_m >> 16) & 0xf];
+		buf[11] = ':';
+		buf[12] = Utils::HEXCHARS[(_m >> 12) & 0xf];
+		buf[13] = Utils::HEXCHARS[(_m >> 8) & 0xf];
+		buf[14] = ':';
+		buf[15] = Utils::HEXCHARS[(_m >> 4) & 0xf];
+		buf[16] = Utils::HEXCHARS[_m & 0xf];
+		buf[17] = (char)0;
+		return buf;
+	}
 
-    inline MAC& operator=(const MAC& m)
-    {
-        _m = m._m;
-        return *this;
-    }
-    inline MAC& operator=(const uint64_t m)
-    {
-        _m = m;
-        return *this;
-    }
+	inline MAC& operator=(const MAC& m)
+	{
+		_m = m._m;
+		return *this;
+	}
+	inline MAC& operator=(const uint64_t m)
+	{
+		_m = m;
+		return *this;
+	}
 
-    inline bool operator==(const MAC& m) const
-    {
-        return (_m == m._m);
-    }
-    inline bool operator!=(const MAC& m) const
-    {
-        return (_m != m._m);
-    }
-    inline bool operator<(const MAC& m) const
-    {
-        return (_m < m._m);
-    }
-    inline bool operator<=(const MAC& m) const
-    {
-        return (_m <= m._m);
-    }
-    inline bool operator>(const MAC& m) const
-    {
-        return (_m > m._m);
-    }
-    inline bool operator>=(const MAC& m) const
-    {
-        return (_m >= m._m);
-    }
+	inline bool operator==(const MAC& m) const
+	{
+		return (_m == m._m);
+	}
+	inline bool operator!=(const MAC& m) const
+	{
+		return (_m != m._m);
+	}
+	inline bool operator<(const MAC& m) const
+	{
+		return (_m < m._m);
+	}
+	inline bool operator<=(const MAC& m) const
+	{
+		return (_m <= m._m);
+	}
+	inline bool operator>(const MAC& m) const
+	{
+		return (_m > m._m);
+	}
+	inline bool operator>=(const MAC& m) const
+	{
+		return (_m >= m._m);
+	}
 
   private:
-    uint64_t _m;
+	uint64_t _m;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 162 - 162
node/Membership.cpp

@@ -31,195 +31,195 @@ Membership::Membership() : _lastUpdatedMulticast(0), _comRevocationThreshold(0),
 
 void Membership::pushCredentials(const RuntimeEnvironment* RR, void* tPtr, const int64_t now, const Address& peerAddress, const NetworkConfig& nconf)
 {
-    const Capability* sendCaps[ZT_MAX_NETWORK_CAPABILITIES];
-    unsigned int sendCapCount = 0;
-    for (unsigned int c = 0; c < nconf.capabilityCount; ++c) {
-        sendCaps[sendCapCount++] = &(nconf.capabilities[c]);
-    }
-
-    const Tag* sendTags[ZT_MAX_NETWORK_TAGS];
-    unsigned int sendTagCount = 0;
-    for (unsigned int t = 0; t < nconf.tagCount; ++t) {
-        sendTags[sendTagCount++] = &(nconf.tags[t]);
-    }
-
-    const CertificateOfOwnership* sendCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP];
-    unsigned int sendCooCount = 0;
-    for (unsigned int c = 0; c < nconf.certificateOfOwnershipCount; ++c) {
-        sendCoos[sendCooCount++] = &(nconf.certificatesOfOwnership[c]);
-    }
-
-    unsigned int capPtr = 0;
-    unsigned int tagPtr = 0;
-    unsigned int cooPtr = 0;
-    bool sendCom = (bool)(nconf.com);
-    while ((capPtr < sendCapCount) || (tagPtr < sendTagCount) || (cooPtr < sendCooCount) || (sendCom)) {
-        Packet outp(peerAddress, RR->identity.address(), Packet::VERB_NETWORK_CREDENTIALS);
-
-        if (sendCom) {
-            sendCom = false;
-            nconf.com.serialize(outp);
-        }
-        outp.append((uint8_t)0x00);
-
-        const unsigned int capCountAt = outp.size();
-        outp.addSize(2);
-        unsigned int thisPacketCapCount = 0;
-        while ((capPtr < sendCapCount) && ((outp.size() + sizeof(Capability) + 16) < ZT_PROTO_MAX_PACKET_LENGTH)) {
-            sendCaps[capPtr++]->serialize(outp);
-            ++thisPacketCapCount;
-        }
-        outp.setAt(capCountAt, (uint16_t)thisPacketCapCount);
-
-        const unsigned int tagCountAt = outp.size();
-        outp.addSize(2);
-        unsigned int thisPacketTagCount = 0;
-        while ((tagPtr < sendTagCount) && ((outp.size() + sizeof(Tag) + 16) < ZT_PROTO_MAX_PACKET_LENGTH)) {
-            sendTags[tagPtr++]->serialize(outp);
-            ++thisPacketTagCount;
-        }
-        outp.setAt(tagCountAt, (uint16_t)thisPacketTagCount);
-
-        // No revocations, these propagate differently
-        outp.append((uint16_t)0);
-
-        const unsigned int cooCountAt = outp.size();
-        outp.addSize(2);
-        unsigned int thisPacketCooCount = 0;
-        while ((cooPtr < sendCooCount) && ((outp.size() + sizeof(CertificateOfOwnership) + 16) < ZT_PROTO_MAX_PACKET_LENGTH)) {
-            sendCoos[cooPtr++]->serialize(outp);
-            ++thisPacketCooCount;
-        }
-        outp.setAt(cooCountAt, (uint16_t)thisPacketCooCount);
-
-        outp.compress();
-        RR->sw->send(tPtr, outp, true);
-        Metrics::pkt_network_credentials_out++;
-    }
-
-    _lastPushedCredentials = now;
+	const Capability* sendCaps[ZT_MAX_NETWORK_CAPABILITIES];
+	unsigned int sendCapCount = 0;
+	for (unsigned int c = 0; c < nconf.capabilityCount; ++c) {
+		sendCaps[sendCapCount++] = &(nconf.capabilities[c]);
+	}
+
+	const Tag* sendTags[ZT_MAX_NETWORK_TAGS];
+	unsigned int sendTagCount = 0;
+	for (unsigned int t = 0; t < nconf.tagCount; ++t) {
+		sendTags[sendTagCount++] = &(nconf.tags[t]);
+	}
+
+	const CertificateOfOwnership* sendCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP];
+	unsigned int sendCooCount = 0;
+	for (unsigned int c = 0; c < nconf.certificateOfOwnershipCount; ++c) {
+		sendCoos[sendCooCount++] = &(nconf.certificatesOfOwnership[c]);
+	}
+
+	unsigned int capPtr = 0;
+	unsigned int tagPtr = 0;
+	unsigned int cooPtr = 0;
+	bool sendCom = (bool)(nconf.com);
+	while ((capPtr < sendCapCount) || (tagPtr < sendTagCount) || (cooPtr < sendCooCount) || (sendCom)) {
+		Packet outp(peerAddress, RR->identity.address(), Packet::VERB_NETWORK_CREDENTIALS);
+
+		if (sendCom) {
+			sendCom = false;
+			nconf.com.serialize(outp);
+		}
+		outp.append((uint8_t)0x00);
+
+		const unsigned int capCountAt = outp.size();
+		outp.addSize(2);
+		unsigned int thisPacketCapCount = 0;
+		while ((capPtr < sendCapCount) && ((outp.size() + sizeof(Capability) + 16) < ZT_PROTO_MAX_PACKET_LENGTH)) {
+			sendCaps[capPtr++]->serialize(outp);
+			++thisPacketCapCount;
+		}
+		outp.setAt(capCountAt, (uint16_t)thisPacketCapCount);
+
+		const unsigned int tagCountAt = outp.size();
+		outp.addSize(2);
+		unsigned int thisPacketTagCount = 0;
+		while ((tagPtr < sendTagCount) && ((outp.size() + sizeof(Tag) + 16) < ZT_PROTO_MAX_PACKET_LENGTH)) {
+			sendTags[tagPtr++]->serialize(outp);
+			++thisPacketTagCount;
+		}
+		outp.setAt(tagCountAt, (uint16_t)thisPacketTagCount);
+
+		// No revocations, these propagate differently
+		outp.append((uint16_t)0);
+
+		const unsigned int cooCountAt = outp.size();
+		outp.addSize(2);
+		unsigned int thisPacketCooCount = 0;
+		while ((cooPtr < sendCooCount) && ((outp.size() + sizeof(CertificateOfOwnership) + 16) < ZT_PROTO_MAX_PACKET_LENGTH)) {
+			sendCoos[cooPtr++]->serialize(outp);
+			++thisPacketCooCount;
+		}
+		outp.setAt(cooCountAt, (uint16_t)thisPacketCooCount);
+
+		outp.compress();
+		RR->sw->send(tPtr, outp, true);
+		Metrics::pkt_network_credentials_out++;
+	}
+
+	_lastPushedCredentials = now;
 }
 
 Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment* RR, void* tPtr, const NetworkConfig& nconf, const CertificateOfMembership& com)
 {
-    const int64_t newts = com.timestamp();
-    if (newts <= _comRevocationThreshold) {
-        RR->t->credentialRejected(tPtr, com, "revoked");
-        return ADD_REJECTED;
-    }
-
-    const int64_t oldts = _com.timestamp();
-    if (newts < oldts) {
-        RR->t->credentialRejected(tPtr, com, "old");
-        return ADD_REJECTED;
-    }
-    if (_com == com) {
-        return ADD_ACCEPTED_REDUNDANT;
-    }
-
-    switch (com.verify(RR, tPtr)) {
-        default:
-            RR->t->credentialRejected(tPtr, com, "invalid");
-            return ADD_REJECTED;
-        case 0:
-            // printf("%.16llx %.10llx replacing COM %lld with %lld\n", com.networkId(), com.issuedTo().toInt(), _com.timestamp(), com.timestamp()); fflush(stdout);
-            _com = com;
-            return ADD_ACCEPTED_NEW;
-        case 1:
-            return ADD_DEFERRED_FOR_WHOIS;
-    }
+	const int64_t newts = com.timestamp();
+	if (newts <= _comRevocationThreshold) {
+		RR->t->credentialRejected(tPtr, com, "revoked");
+		return ADD_REJECTED;
+	}
+
+	const int64_t oldts = _com.timestamp();
+	if (newts < oldts) {
+		RR->t->credentialRejected(tPtr, com, "old");
+		return ADD_REJECTED;
+	}
+	if (_com == com) {
+		return ADD_ACCEPTED_REDUNDANT;
+	}
+
+	switch (com.verify(RR, tPtr)) {
+		default:
+			RR->t->credentialRejected(tPtr, com, "invalid");
+			return ADD_REJECTED;
+		case 0:
+			// printf("%.16llx %.10llx replacing COM %lld with %lld\n", com.networkId(), com.issuedTo().toInt(), _com.timestamp(), com.timestamp()); fflush(stdout);
+			_com = com;
+			return ADD_ACCEPTED_NEW;
+		case 1:
+			return ADD_DEFERRED_FOR_WHOIS;
+	}
 }
 
 // Template out addCredential() for many cred types to avoid copypasta
 template <typename C>
 static Membership::AddCredentialResult _addCredImpl(Hashtable<uint32_t, C>& remoteCreds, const Hashtable<uint64_t, int64_t>& revocations, const RuntimeEnvironment* RR, void* tPtr, const NetworkConfig& nconf, const C& cred)
 {
-    C* rc = remoteCreds.get(cred.id());
-    if (rc) {
-        if (rc->timestamp() > cred.timestamp()) {
-            RR->t->credentialRejected(tPtr, cred, "old");
-            return Membership::ADD_REJECTED;
-        }
-        if (*rc == cred) {
-            return Membership::ADD_ACCEPTED_REDUNDANT;
-        }
-    }
-
-    const int64_t* const rt = revocations.get(Membership::credentialKey(C::credentialType(), cred.id()));
-    if ((rt) && (*rt >= cred.timestamp())) {
-        RR->t->credentialRejected(tPtr, cred, "revoked");
-        return Membership::ADD_REJECTED;
-    }
-
-    switch (cred.verify(RR, tPtr)) {
-        default:
-            RR->t->credentialRejected(tPtr, cred, "invalid");
-            return Membership::ADD_REJECTED;
-        case 0:
-            if (! rc) {
-                rc = &(remoteCreds[cred.id()]);
-            }
-            *rc = cred;
-            return Membership::ADD_ACCEPTED_NEW;
-        case 1:
-            return Membership::ADD_DEFERRED_FOR_WHOIS;
-    }
+	C* rc = remoteCreds.get(cred.id());
+	if (rc) {
+		if (rc->timestamp() > cred.timestamp()) {
+			RR->t->credentialRejected(tPtr, cred, "old");
+			return Membership::ADD_REJECTED;
+		}
+		if (*rc == cred) {
+			return Membership::ADD_ACCEPTED_REDUNDANT;
+		}
+	}
+
+	const int64_t* const rt = revocations.get(Membership::credentialKey(C::credentialType(), cred.id()));
+	if ((rt) && (*rt >= cred.timestamp())) {
+		RR->t->credentialRejected(tPtr, cred, "revoked");
+		return Membership::ADD_REJECTED;
+	}
+
+	switch (cred.verify(RR, tPtr)) {
+		default:
+			RR->t->credentialRejected(tPtr, cred, "invalid");
+			return Membership::ADD_REJECTED;
+		case 0:
+			if (! rc) {
+				rc = &(remoteCreds[cred.id()]);
+			}
+			*rc = cred;
+			return Membership::ADD_ACCEPTED_NEW;
+		case 1:
+			return Membership::ADD_DEFERRED_FOR_WHOIS;
+	}
 }
 
 Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment* RR, void* tPtr, const NetworkConfig& nconf, const Tag& tag)
 {
-    return _addCredImpl<Tag>(_remoteTags, _revocations, RR, tPtr, nconf, tag);
+	return _addCredImpl<Tag>(_remoteTags, _revocations, RR, tPtr, nconf, tag);
 }
 Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment* RR, void* tPtr, const NetworkConfig& nconf, const Capability& cap)
 {
-    return _addCredImpl<Capability>(_remoteCaps, _revocations, RR, tPtr, nconf, cap);
+	return _addCredImpl<Capability>(_remoteCaps, _revocations, RR, tPtr, nconf, cap);
 }
 Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment* RR, void* tPtr, const NetworkConfig& nconf, const CertificateOfOwnership& coo)
 {
-    return _addCredImpl<CertificateOfOwnership>(_remoteCoos, _revocations, RR, tPtr, nconf, coo);
+	return _addCredImpl<CertificateOfOwnership>(_remoteCoos, _revocations, RR, tPtr, nconf, coo);
 }
 
 Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment* RR, void* tPtr, const NetworkConfig& nconf, const Revocation& rev)
 {
-    int64_t* rt;
-    switch (rev.verify(RR, tPtr)) {
-        default:
-            RR->t->credentialRejected(tPtr, rev, "invalid");
-            return ADD_REJECTED;
-        case 0: {
-            const Credential::Type ct = rev.type();
-            switch (ct) {
-                case Credential::CREDENTIAL_TYPE_COM:
-                    if (rev.threshold() > _comRevocationThreshold) {
-                        _comRevocationThreshold = rev.threshold();
-                        return ADD_ACCEPTED_NEW;
-                    }
-                    return ADD_ACCEPTED_REDUNDANT;
-                case Credential::CREDENTIAL_TYPE_CAPABILITY:
-                case Credential::CREDENTIAL_TYPE_TAG:
-                case Credential::CREDENTIAL_TYPE_COO:
-                    rt = &(_revocations[credentialKey(ct, rev.credentialId())]);
-                    if (*rt < rev.threshold()) {
-                        *rt = rev.threshold();
-                        _comRevocationThreshold = rev.threshold();
-                        return ADD_ACCEPTED_NEW;
-                    }
-                    return ADD_ACCEPTED_REDUNDANT;
-                default:
-                    RR->t->credentialRejected(tPtr, rev, "invalid");
-                    return ADD_REJECTED;
-            }
-        }
-        case 1:
-            return ADD_DEFERRED_FOR_WHOIS;
-    }
+	int64_t* rt;
+	switch (rev.verify(RR, tPtr)) {
+		default:
+			RR->t->credentialRejected(tPtr, rev, "invalid");
+			return ADD_REJECTED;
+		case 0: {
+			const Credential::Type ct = rev.type();
+			switch (ct) {
+				case Credential::CREDENTIAL_TYPE_COM:
+					if (rev.threshold() > _comRevocationThreshold) {
+						_comRevocationThreshold = rev.threshold();
+						return ADD_ACCEPTED_NEW;
+					}
+					return ADD_ACCEPTED_REDUNDANT;
+				case Credential::CREDENTIAL_TYPE_CAPABILITY:
+				case Credential::CREDENTIAL_TYPE_TAG:
+				case Credential::CREDENTIAL_TYPE_COO:
+					rt = &(_revocations[credentialKey(ct, rev.credentialId())]);
+					if (*rt < rev.threshold()) {
+						*rt = rev.threshold();
+						_comRevocationThreshold = rev.threshold();
+						return ADD_ACCEPTED_NEW;
+					}
+					return ADD_ACCEPTED_REDUNDANT;
+				default:
+					RR->t->credentialRejected(tPtr, rev, "invalid");
+					return ADD_REJECTED;
+			}
+		}
+		case 1:
+			return ADD_DEFERRED_FOR_WHOIS;
+	}
 }
 
 void Membership::clean(const int64_t now, const NetworkConfig& nconf)
 {
-    _cleanCredImpl<Tag>(nconf, _remoteTags);
-    _cleanCredImpl<Capability>(nconf, _remoteCaps);
-    _cleanCredImpl<CertificateOfOwnership>(nconf, _remoteCoos);
+	_cleanCredImpl<Tag>(nconf, _remoteTags);
+	_cleanCredImpl<Capability>(nconf, _remoteCaps);
+	_cleanCredImpl<CertificateOfOwnership>(nconf, _remoteCoos);
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 243 - 243
node/Membership.hpp

@@ -42,254 +42,254 @@ class Network;
  */
 class Membership {
   public:
-    enum AddCredentialResult { ADD_REJECTED, ADD_ACCEPTED_NEW, ADD_ACCEPTED_REDUNDANT, ADD_DEFERRED_FOR_WHOIS };
-
-    Membership();
-
-    /**
-     * Send COM and other credentials to this peer
-     *
-     * @param RR Runtime environment
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param now Current time
-     * @param peerAddress Address of member peer (the one that this Membership describes)
-     * @param nconf My network config
-     */
-    void pushCredentials(const RuntimeEnvironment* RR, void* tPtr, const int64_t now, const Address& peerAddress, const NetworkConfig& nconf);
-
-    inline int64_t lastPushedCredentials()
-    {
-        return _lastPushedCredentials;
-    }
-    inline int64_t comTimestamp()
-    {
-        return _com.timestamp();
-    }
-    inline int64_t comRevocationThreshold()
-    {
-        return _comRevocationThreshold;
-    }
-
-    /**
-     * Check whether we should push MULTICAST_LIKEs to this peer, and update last sent time if true
-     *
-     * @param now Current time
-     * @return True if we should update multicasts
-     */
-    inline bool multicastLikeGate(const int64_t now)
-    {
-        if ((now - _lastUpdatedMulticast) >= ZT_MULTICAST_ANNOUNCE_PERIOD) {
-            _lastUpdatedMulticast = now;
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Check whether the peer represented by this Membership should be allowed on this network at all
-     *
-     * @param nconf Our network config
-     * @param otherNodeIdentity Identity of remote node
-     * @return True if this peer is allowed on this network at all
-     */
-    inline bool isAllowedOnNetwork(const NetworkConfig& thisNodeNetworkConfig, const Identity& otherNodeIdentity) const
-    {
-        return thisNodeNetworkConfig.isPublic() || (((_com.timestamp() > _comRevocationThreshold) && (thisNodeNetworkConfig.com.agreesWith(_com, otherNodeIdentity))));
-    }
-
-    inline bool recentlyAssociated(const int64_t now) const
-    {
-        return ((_com) && ((now - _com.timestamp()) < ZT_PEER_ACTIVITY_TIMEOUT));
-    }
-
-    /**
-     * Check whether the peer represented by this Membership owns a given resource
-     *
-     * @tparam Type of resource: InetAddress or MAC
-     * @param nconf Our network config
-     * @param r Resource to check
-     * @return True if this peer has a certificate of ownership for the given resource
-     */
-    template <typename T> inline bool hasCertificateOfOwnershipFor(const NetworkConfig& nconf, const T& r) const
-    {
-        uint32_t* k = (uint32_t*)0;
-        CertificateOfOwnership* v = (CertificateOfOwnership*)0;
-        Hashtable<uint32_t, CertificateOfOwnership>::Iterator i(*(const_cast<Hashtable<uint32_t, CertificateOfOwnership>*>(&_remoteCoos)));
-        while (i.next(k, v)) {
-            if (_isCredentialTimestampValid(nconf, *v) && (v->owns(r))) {
-                return true;
-            }
-        }
-        return _isV6NDPEmulated(nconf, r);
-    }
-
-    /**
-     * Get a remote member's tag (if we have it)
-     *
-     * @param nconf Network configuration
-     * @param id Tag ID
-     * @return Pointer to tag or NULL if not found
-     */
-    inline const Tag* getTag(const NetworkConfig& nconf, const uint32_t id) const
-    {
-        const Tag* const t = _remoteTags.get(id);
-        return (((t) && (_isCredentialTimestampValid(nconf, *t))) ? t : (Tag*)0);
-    }
-
-    /**
-     * Validate and add a credential if signature is okay and it's otherwise good
-     */
-    AddCredentialResult addCredential(const RuntimeEnvironment* RR, void* tPtr, const NetworkConfig& nconf, const CertificateOfMembership& com);
-
-    /**
-     * Validate and add a credential if signature is okay and it's otherwise good
-     */
-    AddCredentialResult addCredential(const RuntimeEnvironment* RR, void* tPtr, const NetworkConfig& nconf, const Tag& tag);
-
-    /**
-     * Validate and add a credential if signature is okay and it's otherwise good
-     */
-    AddCredentialResult addCredential(const RuntimeEnvironment* RR, void* tPtr, const NetworkConfig& nconf, const Capability& cap);
-
-    /**
-     * Validate and add a credential if signature is okay and it's otherwise good
-     */
-    AddCredentialResult addCredential(const RuntimeEnvironment* RR, void* tPtr, const NetworkConfig& nconf, const CertificateOfOwnership& coo);
-
-    /**
-     * Validate and add a credential if signature is okay and it's otherwise good
-     */
-    AddCredentialResult addCredential(const RuntimeEnvironment* RR, void* tPtr, const NetworkConfig& nconf, const Revocation& rev);
-
-    /**
-     * Clean internal databases of stale entries
-     *
-     * @param now Current time
-     * @param nconf Current network configuration
-     */
-    void clean(const int64_t now, const NetworkConfig& nconf);
-
-    /**
-     * Generates a key for the internal use in indexing credentials by type and credential ID
-     */
-    static uint64_t credentialKey(const Credential::Type& t, const uint32_t i)
-    {
-        return (((uint64_t)t << 32) | (uint64_t)i);
-    }
+	enum AddCredentialResult { ADD_REJECTED, ADD_ACCEPTED_NEW, ADD_ACCEPTED_REDUNDANT, ADD_DEFERRED_FOR_WHOIS };
+
+	Membership();
+
+	/**
+	 * Send COM and other credentials to this peer
+	 *
+	 * @param RR Runtime environment
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param now Current time
+	 * @param peerAddress Address of member peer (the one that this Membership describes)
+	 * @param nconf My network config
+	 */
+	void pushCredentials(const RuntimeEnvironment* RR, void* tPtr, const int64_t now, const Address& peerAddress, const NetworkConfig& nconf);
+
+	inline int64_t lastPushedCredentials()
+	{
+		return _lastPushedCredentials;
+	}
+	inline int64_t comTimestamp()
+	{
+		return _com.timestamp();
+	}
+	inline int64_t comRevocationThreshold()
+	{
+		return _comRevocationThreshold;
+	}
+
+	/**
+	 * Check whether we should push MULTICAST_LIKEs to this peer, and update last sent time if true
+	 *
+	 * @param now Current time
+	 * @return True if we should update multicasts
+	 */
+	inline bool multicastLikeGate(const int64_t now)
+	{
+		if ((now - _lastUpdatedMulticast) >= ZT_MULTICAST_ANNOUNCE_PERIOD) {
+			_lastUpdatedMulticast = now;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Check whether the peer represented by this Membership should be allowed on this network at all
+	 *
+	 * @param nconf Our network config
+	 * @param otherNodeIdentity Identity of remote node
+	 * @return True if this peer is allowed on this network at all
+	 */
+	inline bool isAllowedOnNetwork(const NetworkConfig& thisNodeNetworkConfig, const Identity& otherNodeIdentity) const
+	{
+		return thisNodeNetworkConfig.isPublic() || (((_com.timestamp() > _comRevocationThreshold) && (thisNodeNetworkConfig.com.agreesWith(_com, otherNodeIdentity))));
+	}
+
+	inline bool recentlyAssociated(const int64_t now) const
+	{
+		return ((_com) && ((now - _com.timestamp()) < ZT_PEER_ACTIVITY_TIMEOUT));
+	}
+
+	/**
+	 * Check whether the peer represented by this Membership owns a given resource
+	 *
+	 * @tparam Type of resource: InetAddress or MAC
+	 * @param nconf Our network config
+	 * @param r Resource to check
+	 * @return True if this peer has a certificate of ownership for the given resource
+	 */
+	template <typename T> inline bool hasCertificateOfOwnershipFor(const NetworkConfig& nconf, const T& r) const
+	{
+		uint32_t* k = (uint32_t*)0;
+		CertificateOfOwnership* v = (CertificateOfOwnership*)0;
+		Hashtable<uint32_t, CertificateOfOwnership>::Iterator i(*(const_cast<Hashtable<uint32_t, CertificateOfOwnership>*>(&_remoteCoos)));
+		while (i.next(k, v)) {
+			if (_isCredentialTimestampValid(nconf, *v) && (v->owns(r))) {
+				return true;
+			}
+		}
+		return _isV6NDPEmulated(nconf, r);
+	}
+
+	/**
+	 * Get a remote member's tag (if we have it)
+	 *
+	 * @param nconf Network configuration
+	 * @param id Tag ID
+	 * @return Pointer to tag or NULL if not found
+	 */
+	inline const Tag* getTag(const NetworkConfig& nconf, const uint32_t id) const
+	{
+		const Tag* const t = _remoteTags.get(id);
+		return (((t) && (_isCredentialTimestampValid(nconf, *t))) ? t : (Tag*)0);
+	}
+
+	/**
+	 * Validate and add a credential if signature is okay and it's otherwise good
+	 */
+	AddCredentialResult addCredential(const RuntimeEnvironment* RR, void* tPtr, const NetworkConfig& nconf, const CertificateOfMembership& com);
+
+	/**
+	 * Validate and add a credential if signature is okay and it's otherwise good
+	 */
+	AddCredentialResult addCredential(const RuntimeEnvironment* RR, void* tPtr, const NetworkConfig& nconf, const Tag& tag);
+
+	/**
+	 * Validate and add a credential if signature is okay and it's otherwise good
+	 */
+	AddCredentialResult addCredential(const RuntimeEnvironment* RR, void* tPtr, const NetworkConfig& nconf, const Capability& cap);
+
+	/**
+	 * Validate and add a credential if signature is okay and it's otherwise good
+	 */
+	AddCredentialResult addCredential(const RuntimeEnvironment* RR, void* tPtr, const NetworkConfig& nconf, const CertificateOfOwnership& coo);
+
+	/**
+	 * Validate and add a credential if signature is okay and it's otherwise good
+	 */
+	AddCredentialResult addCredential(const RuntimeEnvironment* RR, void* tPtr, const NetworkConfig& nconf, const Revocation& rev);
+
+	/**
+	 * Clean internal databases of stale entries
+	 *
+	 * @param now Current time
+	 * @param nconf Current network configuration
+	 */
+	void clean(const int64_t now, const NetworkConfig& nconf);
+
+	/**
+	 * Generates a key for the internal use in indexing credentials by type and credential ID
+	 */
+	static uint64_t credentialKey(const Credential::Type& t, const uint32_t i)
+	{
+		return (((uint64_t)t << 32) | (uint64_t)i);
+	}
 
   private:
-    inline bool _isV6NDPEmulated(const NetworkConfig& nconf, const MAC& m) const
-    {
-        return false;
-    }
-    inline bool _isV6NDPEmulated(const NetworkConfig& nconf, const InetAddress& ip) const
-    {
-        if ((ip.isV6()) && (nconf.ndpEmulation())) {
-            const InetAddress sixpl(InetAddress::makeIpv66plane(nconf.networkId, nconf.issuedTo.toInt()));
-            for (unsigned int i = 0; i < nconf.staticIpCount; ++i) {
-                if (nconf.staticIps[i].ipsEqual(sixpl)) {
-                    bool prefixMatches = true;
-                    for (unsigned int j = 0; j < 5; ++j) {   // check for match on /40
-                        if ((((const struct sockaddr_in6*)&ip)->sin6_addr.s6_addr)[j] != (((const struct sockaddr_in6*)&sixpl)->sin6_addr.s6_addr)[j]) {
-                            prefixMatches = false;
-                            break;
-                        }
-                    }
-                    if (prefixMatches) {
-                        return true;
-                    }
-                    break;
-                }
-            }
-
-            const InetAddress rfc4193(InetAddress::makeIpv6rfc4193(nconf.networkId, nconf.issuedTo.toInt()));
-            for (unsigned int i = 0; i < nconf.staticIpCount; ++i) {
-                if (nconf.staticIps[i].ipsEqual(rfc4193)) {
-                    bool prefixMatches = true;
-                    for (unsigned int j = 0; j < 11; ++j) {   // check for match on /88
-                        if ((((const struct sockaddr_in6*)&ip)->sin6_addr.s6_addr)[j] != (((const struct sockaddr_in6*)&rfc4193)->sin6_addr.s6_addr)[j]) {
-                            prefixMatches = false;
-                            break;
-                        }
-                    }
-                    if (prefixMatches) {
-                        return true;
-                    }
-                    break;
-                }
-            }
-        }
-        return false;
-    }
-
-    template <typename C> inline bool _isCredentialTimestampValid(const NetworkConfig& nconf, const C& remoteCredential) const
-    {
-        const int64_t ts = remoteCredential.timestamp();
-        if (((ts >= nconf.timestamp) ? (ts - nconf.timestamp) : (nconf.timestamp - ts)) <= nconf.credentialTimeMaxDelta) {
-            const int64_t* threshold = _revocations.get(credentialKey(C::credentialType(), remoteCredential.id()));
-            return ((! threshold) || (ts > *threshold));
-        }
-        return false;
-    }
-
-    template <typename C> inline void _cleanCredImpl(const NetworkConfig& nconf, Hashtable<uint32_t, C>& remoteCreds)
-    {
-        uint32_t* k = (uint32_t*)0;
-        C* v = (C*)0;
-        typename Hashtable<uint32_t, C>::Iterator i(remoteCreds);
-        while (i.next(k, v)) {
-            if (! _isCredentialTimestampValid(nconf, *v)) {
-                remoteCreds.erase(*k);
-            }
-        }
-    }
-
-    // Last time we pushed MULTICAST_LIKE(s)
-    int64_t _lastUpdatedMulticast;
-
-    // Revocation threshold for COM or 0 if none
-    int64_t _comRevocationThreshold;
-
-    // Time we last pushed credentials
-    int64_t _lastPushedCredentials;
-
-    // Remote member's latest network COM
-    CertificateOfMembership _com;
-
-    // Revocations by credentialKey()
-    Hashtable<uint64_t, int64_t> _revocations;
-
-    // Remote credentials that we have received from this member (and that are valid)
-    Hashtable<uint32_t, Tag> _remoteTags;
-    Hashtable<uint32_t, Capability> _remoteCaps;
-    Hashtable<uint32_t, CertificateOfOwnership> _remoteCoos;
+	inline bool _isV6NDPEmulated(const NetworkConfig& nconf, const MAC& m) const
+	{
+		return false;
+	}
+	inline bool _isV6NDPEmulated(const NetworkConfig& nconf, const InetAddress& ip) const
+	{
+		if ((ip.isV6()) && (nconf.ndpEmulation())) {
+			const InetAddress sixpl(InetAddress::makeIpv66plane(nconf.networkId, nconf.issuedTo.toInt()));
+			for (unsigned int i = 0; i < nconf.staticIpCount; ++i) {
+				if (nconf.staticIps[i].ipsEqual(sixpl)) {
+					bool prefixMatches = true;
+					for (unsigned int j = 0; j < 5; ++j) {	 // check for match on /40
+						if ((((const struct sockaddr_in6*)&ip)->sin6_addr.s6_addr)[j] != (((const struct sockaddr_in6*)&sixpl)->sin6_addr.s6_addr)[j]) {
+							prefixMatches = false;
+							break;
+						}
+					}
+					if (prefixMatches) {
+						return true;
+					}
+					break;
+				}
+			}
+
+			const InetAddress rfc4193(InetAddress::makeIpv6rfc4193(nconf.networkId, nconf.issuedTo.toInt()));
+			for (unsigned int i = 0; i < nconf.staticIpCount; ++i) {
+				if (nconf.staticIps[i].ipsEqual(rfc4193)) {
+					bool prefixMatches = true;
+					for (unsigned int j = 0; j < 11; ++j) {	  // check for match on /88
+						if ((((const struct sockaddr_in6*)&ip)->sin6_addr.s6_addr)[j] != (((const struct sockaddr_in6*)&rfc4193)->sin6_addr.s6_addr)[j]) {
+							prefixMatches = false;
+							break;
+						}
+					}
+					if (prefixMatches) {
+						return true;
+					}
+					break;
+				}
+			}
+		}
+		return false;
+	}
+
+	template <typename C> inline bool _isCredentialTimestampValid(const NetworkConfig& nconf, const C& remoteCredential) const
+	{
+		const int64_t ts = remoteCredential.timestamp();
+		if (((ts >= nconf.timestamp) ? (ts - nconf.timestamp) : (nconf.timestamp - ts)) <= nconf.credentialTimeMaxDelta) {
+			const int64_t* threshold = _revocations.get(credentialKey(C::credentialType(), remoteCredential.id()));
+			return ((! threshold) || (ts > *threshold));
+		}
+		return false;
+	}
+
+	template <typename C> inline void _cleanCredImpl(const NetworkConfig& nconf, Hashtable<uint32_t, C>& remoteCreds)
+	{
+		uint32_t* k = (uint32_t*)0;
+		C* v = (C*)0;
+		typename Hashtable<uint32_t, C>::Iterator i(remoteCreds);
+		while (i.next(k, v)) {
+			if (! _isCredentialTimestampValid(nconf, *v)) {
+				remoteCreds.erase(*k);
+			}
+		}
+	}
+
+	// Last time we pushed MULTICAST_LIKE(s)
+	int64_t _lastUpdatedMulticast;
+
+	// Revocation threshold for COM or 0 if none
+	int64_t _comRevocationThreshold;
+
+	// Time we last pushed credentials
+	int64_t _lastPushedCredentials;
+
+	// Remote member's latest network COM
+	CertificateOfMembership _com;
+
+	// Revocations by credentialKey()
+	Hashtable<uint64_t, int64_t> _revocations;
+
+	// Remote credentials that we have received from this member (and that are valid)
+	Hashtable<uint32_t, Tag> _remoteTags;
+	Hashtable<uint32_t, Capability> _remoteCaps;
+	Hashtable<uint32_t, CertificateOfOwnership> _remoteCoos;
 
   public:
-    class CapabilityIterator {
-      public:
-        CapabilityIterator(Membership& m, const NetworkConfig& nconf) : _hti(m._remoteCaps), _k((uint32_t*)0), _c((Capability*)0), _m(m), _nconf(nconf)
-        {
-        }
-
-        inline Capability* next()
-        {
-            while (_hti.next(_k, _c)) {
-                if (_m._isCredentialTimestampValid(_nconf, *_c)) {
-                    return _c;
-                }
-            }
-            return (Capability*)0;
-        }
-
-      private:
-        Hashtable<uint32_t, Capability>::Iterator _hti;
-        uint32_t* _k;
-        Capability* _c;
-        Membership& _m;
-        const NetworkConfig& _nconf;
-    };
+	class CapabilityIterator {
+	  public:
+		CapabilityIterator(Membership& m, const NetworkConfig& nconf) : _hti(m._remoteCaps), _k((uint32_t*)0), _c((Capability*)0), _m(m), _nconf(nconf)
+		{
+		}
+
+		inline Capability* next()
+		{
+			while (_hti.next(_k, _c)) {
+				if (_m._isCredentialTimestampValid(_nconf, *_c)) {
+					return _c;
+				}
+			}
+			return (Capability*)0;
+		}
+
+	  private:
+		Hashtable<uint32_t, Capability>::Iterator _hti;
+		uint32_t* _k;
+		Capability* _c;
+		Membership& _m;
+		const NetworkConfig& _nconf;
+	};
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 4 - 4
node/Metrics.cpp

@@ -17,8 +17,8 @@ namespace simpleapi {
 std::shared_ptr<Registry> registry_ptr = std::make_shared<Registry>();
 Registry& registry = *registry_ptr;
 SaveToFile saver;
-}   // namespace simpleapi
-}   // namespace prometheus
+}	// namespace simpleapi
+}	// namespace prometheus
 
 namespace ZeroTier {
 namespace Metrics {
@@ -159,5 +159,5 @@ prometheus::simpleapi::gauge_metric_t pool_avail { "controller_pgsql_available_c
 prometheus::simpleapi::gauge_metric_t pool_in_use { "controller_pgsql_in_use_conns", "number of postgres database connections in use" };
 prometheus::simpleapi::counter_metric_t pool_errors { "controller_pgsql_connection_errors", "number of connection errors the connection pool has seen" };
 #endif
-}   // namespace Metrics
-}   // namespace ZeroTier
+}	// namespace Metrics
+}	// namespace ZeroTier

+ 4 - 4
node/Metrics.hpp

@@ -20,7 +20,7 @@ namespace prometheus {
 namespace simpleapi {
 extern std::shared_ptr<Registry> registry_ptr;
 }
-}   // namespace prometheus
+}	// namespace prometheus
 
 namespace ZeroTier {
 namespace Metrics {
@@ -158,7 +158,7 @@ extern prometheus::simpleapi::gauge_metric_t pool_avail;
 extern prometheus::simpleapi::gauge_metric_t pool_in_use;
 extern prometheus::simpleapi::counter_metric_t pool_errors;
 #endif
-}   // namespace Metrics
-}   // namespace ZeroTier
+}	// namespace Metrics
+}	// namespace ZeroTier
 
-#endif   // METRICS_H_
+#endif	 // METRICS_H_

+ 82 - 82
node/MulticastGroup.hpp

@@ -38,97 +38,97 @@ namespace ZeroTier {
  */
 class MulticastGroup {
   public:
-    MulticastGroup() : _mac(), _adi(0)
-    {
-    }
+	MulticastGroup() : _mac(), _adi(0)
+	{
+	}
 
-    MulticastGroup(const MAC& m, uint32_t a) : _mac(m), _adi(a)
-    {
-    }
+	MulticastGroup(const MAC& m, uint32_t a) : _mac(m), _adi(a)
+	{
+	}
 
-    /**
-     * Derive the multicast group used for address resolution (ARP/NDP) for an IP
-     *
-     * @param ip IP address (port field is ignored)
-     * @return Multicast group for ARP/NDP
-     */
-    static inline MulticastGroup deriveMulticastGroupForAddressResolution(const InetAddress& ip)
-    {
-        if (ip.isV4()) {
-            // IPv4 wants broadcast MACs, so we shove the V4 address itself into
-            // the Multicast Group ADI field. Making V4 ARP work is basically why
-            // ADI was added, as well as handling other things that want mindless
-            // Ethernet broadcast to all.
-            return MulticastGroup(MAC(0xffffffffffffULL), Utils::ntoh(*((const uint32_t*)ip.rawIpData())));
-        }
-        else if (ip.isV6()) {
-            // IPv6 is better designed in this respect. We can compute the IPv6
-            // multicast address directly from the IP address, and it gives us
-            // 24 bits of uniqueness. Collisions aren't likely to be common enough
-            // to care about.
-            const unsigned char* a = (const unsigned char*)ip.rawIpData();
-            return MulticastGroup(MAC(0x33, 0x33, 0xff, a[13], a[14], a[15]), 0);
-        }
-        return MulticastGroup();
-    }
+	/**
+	 * Derive the multicast group used for address resolution (ARP/NDP) for an IP
+	 *
+	 * @param ip IP address (port field is ignored)
+	 * @return Multicast group for ARP/NDP
+	 */
+	static inline MulticastGroup deriveMulticastGroupForAddressResolution(const InetAddress& ip)
+	{
+		if (ip.isV4()) {
+			// IPv4 wants broadcast MACs, so we shove the V4 address itself into
+			// the Multicast Group ADI field. Making V4 ARP work is basically why
+			// ADI was added, as well as handling other things that want mindless
+			// Ethernet broadcast to all.
+			return MulticastGroup(MAC(0xffffffffffffULL), Utils::ntoh(*((const uint32_t*)ip.rawIpData())));
+		}
+		else if (ip.isV6()) {
+			// IPv6 is better designed in this respect. We can compute the IPv6
+			// multicast address directly from the IP address, and it gives us
+			// 24 bits of uniqueness. Collisions aren't likely to be common enough
+			// to care about.
+			const unsigned char* a = (const unsigned char*)ip.rawIpData();
+			return MulticastGroup(MAC(0x33, 0x33, 0xff, a[13], a[14], a[15]), 0);
+		}
+		return MulticastGroup();
+	}
 
-    /**
-     * @return Multicast address
-     */
-    inline const MAC& mac() const
-    {
-        return _mac;
-    }
+	/**
+	 * @return Multicast address
+	 */
+	inline const MAC& mac() const
+	{
+		return _mac;
+	}
 
-    /**
-     * @return Additional distinguishing information
-     */
-    inline uint32_t adi() const
-    {
-        return _adi;
-    }
+	/**
+	 * @return Additional distinguishing information
+	 */
+	inline uint32_t adi() const
+	{
+		return _adi;
+	}
 
-    inline unsigned long hashCode() const
-    {
-        return (_mac.hashCode() ^ (unsigned long)_adi);
-    }
+	inline unsigned long hashCode() const
+	{
+		return (_mac.hashCode() ^ (unsigned long)_adi);
+	}
 
-    inline bool operator==(const MulticastGroup& g) const
-    {
-        return ((_mac == g._mac) && (_adi == g._adi));
-    }
-    inline bool operator!=(const MulticastGroup& g) const
-    {
-        return ((_mac != g._mac) || (_adi != g._adi));
-    }
-    inline bool operator<(const MulticastGroup& g) const
-    {
-        if (_mac < g._mac) {
-            return true;
-        }
-        else if (_mac == g._mac) {
-            return (_adi < g._adi);
-        }
-        return false;
-    }
-    inline bool operator>(const MulticastGroup& g) const
-    {
-        return (g < *this);
-    }
-    inline bool operator<=(const MulticastGroup& g) const
-    {
-        return ! (g < *this);
-    }
-    inline bool operator>=(const MulticastGroup& g) const
-    {
-        return ! (*this < g);
-    }
+	inline bool operator==(const MulticastGroup& g) const
+	{
+		return ((_mac == g._mac) && (_adi == g._adi));
+	}
+	inline bool operator!=(const MulticastGroup& g) const
+	{
+		return ((_mac != g._mac) || (_adi != g._adi));
+	}
+	inline bool operator<(const MulticastGroup& g) const
+	{
+		if (_mac < g._mac) {
+			return true;
+		}
+		else if (_mac == g._mac) {
+			return (_adi < g._adi);
+		}
+		return false;
+	}
+	inline bool operator>(const MulticastGroup& g) const
+	{
+		return (g < *this);
+	}
+	inline bool operator<=(const MulticastGroup& g) const
+	{
+		return ! (g < *this);
+	}
+	inline bool operator>=(const MulticastGroup& g) const
+	{
+		return ! (*this < g);
+	}
 
   private:
-    MAC _mac;
-    uint32_t _adi;
+	MAC _mac;
+	uint32_t _adi;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 386 - 386
node/Multicaster.cpp

@@ -37,415 +37,415 @@ Multicaster::~Multicaster()
 
 void Multicaster::addMultiple(void* tPtr, int64_t now, uint64_t nwid, const MulticastGroup& mg, const void* addresses, unsigned int count, unsigned int totalKnown)
 {
-    const unsigned char* p = (const unsigned char*)addresses;
-    const unsigned char* e = p + (5 * count);
-    Mutex::Lock _l(_groups_m);
-    MulticastGroupStatus& gs = _groups[Multicaster::Key(nwid, mg)];
-    while (p != e) {
-        _add(tPtr, now, nwid, mg, gs, Address(p, 5));
-        p += 5;
-    }
+	const unsigned char* p = (const unsigned char*)addresses;
+	const unsigned char* e = p + (5 * count);
+	Mutex::Lock _l(_groups_m);
+	MulticastGroupStatus& gs = _groups[Multicaster::Key(nwid, mg)];
+	while (p != e) {
+		_add(tPtr, now, nwid, mg, gs, Address(p, 5));
+		p += 5;
+	}
 }
 
 void Multicaster::remove(uint64_t nwid, const MulticastGroup& mg, const Address& member)
 {
-    Mutex::Lock _l(_groups_m);
-    MulticastGroupStatus* s = _groups.get(Multicaster::Key(nwid, mg));
-    if (s) {
-        for (std::vector<MulticastGroupMember>::iterator m(s->members.begin()); m != s->members.end(); ++m) {
-            if (m->address == member) {
-                s->members.erase(m);
-                break;
-            }
-        }
-    }
+	Mutex::Lock _l(_groups_m);
+	MulticastGroupStatus* s = _groups.get(Multicaster::Key(nwid, mg));
+	if (s) {
+		for (std::vector<MulticastGroupMember>::iterator m(s->members.begin()); m != s->members.end(); ++m) {
+			if (m->address == member) {
+				s->members.erase(m);
+				break;
+			}
+		}
+	}
 }
 
 unsigned int Multicaster::gather(const Address& queryingPeer, uint64_t nwid, const MulticastGroup& mg, Buffer<ZT_PROTO_MAX_PACKET_LENGTH>& appendTo, unsigned int limit) const
 {
-    unsigned char* p;
-    unsigned int added = 0, i, k, rptr, totalKnown = 0;
-    uint64_t a, picked[(ZT_PROTO_MAX_PACKET_LENGTH / 5) + 2];
-
-    if (! limit) {
-        return 0;
-    }
-    else if (limit > 0xffff) {
-        limit = 0xffff;
-    }
-
-    const unsigned int totalAt = appendTo.size();
-    appendTo.addSize(4);   // sizeof(uint32_t)
-    const unsigned int addedAt = appendTo.size();
-    appendTo.addSize(2);   // sizeof(uint16_t)
-
-    {   // Return myself if I am a member of this group
-        SharedPtr<Network> network(RR->node->network(nwid));
-        if ((network) && (network->subscribedToMulticastGroup(mg, true))) {
-            RR->identity.address().appendTo(appendTo);
-            ++totalKnown;
-            ++added;
-        }
-    }
-
-    Mutex::Lock _l(_groups_m);
-
-    const MulticastGroupStatus* s = _groups.get(Multicaster::Key(nwid, mg));
-    if ((s) && (! s->members.empty())) {
-        totalKnown += (unsigned int)s->members.size();
-
-        // Members are returned in random order so that repeated gather queries
-        // will return different subsets of a large multicast group.
-        k = 0;
-        while ((added < limit) && (k < s->members.size()) && ((appendTo.size() + ZT_ADDRESS_LENGTH) <= ZT_PROTO_MAX_PACKET_LENGTH)) {
-            rptr = (unsigned int)RR->node->prng();
-
-        restart_member_scan:
-            a = s->members[rptr % (unsigned int)s->members.size()].address.toInt();
-            for (i = 0; i < k; ++i) {
-                if (picked[i] == a) {
-                    ++rptr;
-                    goto restart_member_scan;
-                }
-            }
-            picked[k++] = a;
-
-            if (queryingPeer.toInt() != a) {   // do not return the peer that is making the request as a result
-                p = (unsigned char*)appendTo.appendField(ZT_ADDRESS_LENGTH);
-                *(p++) = (unsigned char)((a >> 32) & 0xff);
-                *(p++) = (unsigned char)((a >> 24) & 0xff);
-                *(p++) = (unsigned char)((a >> 16) & 0xff);
-                *(p++) = (unsigned char)((a >> 8) & 0xff);
-                *p = (unsigned char)(a & 0xff);
-                ++added;
-            }
-        }
-    }
-
-    appendTo.setAt(totalAt, (uint32_t)totalKnown);
-    appendTo.setAt(addedAt, (uint16_t)added);
-
-    return added;
+	unsigned char* p;
+	unsigned int added = 0, i, k, rptr, totalKnown = 0;
+	uint64_t a, picked[(ZT_PROTO_MAX_PACKET_LENGTH / 5) + 2];
+
+	if (! limit) {
+		return 0;
+	}
+	else if (limit > 0xffff) {
+		limit = 0xffff;
+	}
+
+	const unsigned int totalAt = appendTo.size();
+	appendTo.addSize(4);   // sizeof(uint32_t)
+	const unsigned int addedAt = appendTo.size();
+	appendTo.addSize(2);   // sizeof(uint16_t)
+
+	{	// Return myself if I am a member of this group
+		SharedPtr<Network> network(RR->node->network(nwid));
+		if ((network) && (network->subscribedToMulticastGroup(mg, true))) {
+			RR->identity.address().appendTo(appendTo);
+			++totalKnown;
+			++added;
+		}
+	}
+
+	Mutex::Lock _l(_groups_m);
+
+	const MulticastGroupStatus* s = _groups.get(Multicaster::Key(nwid, mg));
+	if ((s) && (! s->members.empty())) {
+		totalKnown += (unsigned int)s->members.size();
+
+		// Members are returned in random order so that repeated gather queries
+		// will return different subsets of a large multicast group.
+		k = 0;
+		while ((added < limit) && (k < s->members.size()) && ((appendTo.size() + ZT_ADDRESS_LENGTH) <= ZT_PROTO_MAX_PACKET_LENGTH)) {
+			rptr = (unsigned int)RR->node->prng();
+
+		restart_member_scan:
+			a = s->members[rptr % (unsigned int)s->members.size()].address.toInt();
+			for (i = 0; i < k; ++i) {
+				if (picked[i] == a) {
+					++rptr;
+					goto restart_member_scan;
+				}
+			}
+			picked[k++] = a;
+
+			if (queryingPeer.toInt() != a) {   // do not return the peer that is making the request as a result
+				p = (unsigned char*)appendTo.appendField(ZT_ADDRESS_LENGTH);
+				*(p++) = (unsigned char)((a >> 32) & 0xff);
+				*(p++) = (unsigned char)((a >> 24) & 0xff);
+				*(p++) = (unsigned char)((a >> 16) & 0xff);
+				*(p++) = (unsigned char)((a >> 8) & 0xff);
+				*p = (unsigned char)(a & 0xff);
+				++added;
+			}
+		}
+	}
+
+	appendTo.setAt(totalAt, (uint32_t)totalKnown);
+	appendTo.setAt(addedAt, (uint16_t)added);
+
+	return added;
 }
 
 std::vector<Address> Multicaster::getMembers(uint64_t nwid, const MulticastGroup& mg, unsigned int limit) const
 {
-    std::vector<Address> ls;
-    Mutex::Lock _l(_groups_m);
-    const MulticastGroupStatus* s = _groups.get(Multicaster::Key(nwid, mg));
-    if (! s) {
-        return ls;
-    }
-    for (std::vector<MulticastGroupMember>::const_reverse_iterator m(s->members.rbegin()); m != s->members.rend(); ++m) {
-        ls.push_back(m->address);
-        if (ls.size() >= limit) {
-            break;
-        }
-    }
-    return ls;
+	std::vector<Address> ls;
+	Mutex::Lock _l(_groups_m);
+	const MulticastGroupStatus* s = _groups.get(Multicaster::Key(nwid, mg));
+	if (! s) {
+		return ls;
+	}
+	for (std::vector<MulticastGroupMember>::const_reverse_iterator m(s->members.rbegin()); m != s->members.rend(); ++m) {
+		ls.push_back(m->address);
+		if (ls.size() >= limit) {
+			break;
+		}
+	}
+	return ls;
 }
 
 void Multicaster::send(void* tPtr, int64_t now, const SharedPtr<Network>& network, const Address& origin, const MulticastGroup& mg, const MAC& src, unsigned int etherType, const void* data, unsigned int len)
 {
-    unsigned long idxbuf[4096];
-    unsigned long* indexes = idxbuf;
-
-    // If we're in hub-and-spoke designated multicast replication mode, see if we
-    // have a multicast replicator active. If so, pick the best and send it
-    // there. If we are a multicast replicator or if none are alive, fall back
-    // to sender replication. Note that bridges do not do this since this would
-    // break bridge route learning. This is sort of an edge case limitation of
-    // the current protocol and could be fixed, but fixing it would add more
-    // complexity than the fix is probably worth. Bridges are generally high
-    // bandwidth nodes.
-    if (! network->config().isActiveBridge(RR->identity.address())) {
-        Address multicastReplicators[ZT_MAX_NETWORK_SPECIALISTS];
-        const unsigned int multicastReplicatorCount = network->config().multicastReplicators(multicastReplicators);
-        if (multicastReplicatorCount) {
-            if (std::find(multicastReplicators, multicastReplicators + multicastReplicatorCount, RR->identity.address()) == (multicastReplicators + multicastReplicatorCount)) {
-                SharedPtr<Peer> bestMulticastReplicator;
-                SharedPtr<Path> bestMulticastReplicatorPath;
-                unsigned int bestMulticastReplicatorLatency = 0xffff;
-                for (unsigned int i = 0; i < multicastReplicatorCount; ++i) {
-                    const SharedPtr<Peer> p(RR->topology->getPeerNoCache(multicastReplicators[i]));
-                    if ((p) && (p->isAlive(now))) {
-                        const SharedPtr<Path> pp(p->getAppropriatePath(now, false));
-                        if ((pp) && (pp->latency() < bestMulticastReplicatorLatency)) {
-                            bestMulticastReplicatorLatency = pp->latency();
-                            bestMulticastReplicatorPath = pp;
-                            bestMulticastReplicator = p;
-                        }
-                    }
-                }
-                if (bestMulticastReplicator) {
-                    Packet outp(bestMulticastReplicator->address(), RR->identity.address(), Packet::VERB_MULTICAST_FRAME);
-                    outp.append((uint64_t)network->id());
-                    outp.append((uint8_t)0x0c);   // includes source MAC | please replicate
-                    ((src) ? src : MAC(RR->identity.address(), network->id())).appendTo(outp);
-                    mg.mac().appendTo(outp);
-                    outp.append((uint32_t)mg.adi());
-                    outp.append((uint16_t)etherType);
-                    outp.append(data, len);
-                    if (! network->config().disableCompression()) {
-                        outp.compress();
-                    }
-                    outp.armor(bestMulticastReplicator->key(), true, false, bestMulticastReplicator->aesKeysIfSupported(), bestMulticastReplicator->identity());
-                    Metrics::pkt_multicast_frame_out++;
-                    bestMulticastReplicatorPath->send(RR, tPtr, outp.data(), outp.size(), now);
-                    return;
-                }
-            }
-        }
-    }
-
-    try {
-        Mutex::Lock _l(_groups_m);
-        MulticastGroupStatus& gs = _groups[Multicaster::Key(network->id(), mg)];
-
-        if (! gs.members.empty()) {
-            // Allocate a memory buffer if group is monstrous
-            if (gs.members.size() > (sizeof(idxbuf) / sizeof(unsigned long))) {
-                indexes = new unsigned long[gs.members.size()];
-            }
-
-            // Generate a random permutation of member indexes
-            for (unsigned long i = 0; i < gs.members.size(); ++i) {
-                indexes[i] = i;
-            }
-            for (unsigned long i = (unsigned long)gs.members.size() - 1; i > 0; --i) {
-                unsigned long j = (unsigned long)RR->node->prng() % (i + 1);
-                unsigned long tmp = indexes[j];
-                indexes[j] = indexes[i];
-                indexes[i] = tmp;
-            }
-        }
-
-        Address activeBridges[ZT_MAX_NETWORK_SPECIALISTS];
-        const unsigned int activeBridgeCount = network->config().activeBridges(activeBridges);
-        const unsigned int limit = network->config().multicastLimit;
-
-        if (gs.members.size() >= limit) {
-            // Skip queue if we already have enough members to complete the send operation
-            OutboundMulticast out;
-
-            out.init(
-                RR,
-                now,
-                network->id(),
-                network->config().disableCompression(),
-                limit,
-                1,   // we'll still gather a little from peers to keep multicast list fresh
-                src,
-                mg,
-                etherType,
-                data,
-                len);
-
-            unsigned int count = 0;
-
-            for (unsigned int i = 0; i < activeBridgeCount; ++i) {
-                if ((activeBridges[i] != RR->identity.address()) && (activeBridges[i] != origin)) {
-                    out.sendOnly(RR, tPtr, activeBridges[i]);   // optimization: don't use dedup log if it's a one-pass send
-                    if (++count >= limit) {
-                        break;
-                    }
-                }
-            }
-
-            unsigned long idx = 0;
-            while ((count < limit) && (idx < gs.members.size())) {
-                const Address ma(gs.members[indexes[idx++]].address);
-                if ((std::find(activeBridges, activeBridges + activeBridgeCount, ma) == (activeBridges + activeBridgeCount)) && (ma != origin)) {
-                    out.sendOnly(RR, tPtr, ma);   // optimization: don't use dedup log if it's a one-pass send
-                    ++count;
-                }
-            }
-        }
-        else {
-            while (gs.txQueue.size() >= ZT_TX_QUEUE_SIZE) {
-                gs.txQueue.pop_front();
-            }
-
-            const unsigned int gatherLimit = (limit - (unsigned int)gs.members.size()) + 1;
-
-            int timerScale = RR->node->lowBandwidthModeEnabled() ? 3 : 1;
-            if ((gs.members.empty()) || ((now - gs.lastExplicitGather) >= (ZT_MULTICAST_EXPLICIT_GATHER_DELAY * timerScale))) {
-                gs.lastExplicitGather = now;
-
-                Address explicitGatherPeers[16];
-                unsigned int numExplicitGatherPeers = 0;
-
-                SharedPtr<Peer> bestRoot(RR->topology->getUpstreamPeer());
-                if (bestRoot) {
-                    explicitGatherPeers[numExplicitGatherPeers++] = bestRoot->address();
-                }
-
-                explicitGatherPeers[numExplicitGatherPeers++] = network->controller();
-
-                Address ac[ZT_MAX_NETWORK_SPECIALISTS];
-                const unsigned int accnt = network->config().alwaysContactAddresses(ac);
-                unsigned int shuffled[ZT_MAX_NETWORK_SPECIALISTS];
-                for (unsigned int i = 0; i < accnt; ++i) {
-                    shuffled[i] = i;
-                }
-                for (unsigned int i = 0, k = accnt >> 1; i < k; ++i) {
-                    const uint64_t x = RR->node->prng();
-                    const unsigned int x1 = shuffled[(unsigned int)x % accnt];
-                    const unsigned int x2 = shuffled[(unsigned int)(x >> 32) % accnt];
-                    const unsigned int tmp = shuffled[x1];
-                    shuffled[x1] = shuffled[x2];
-                    shuffled[x2] = tmp;
-                }
-                for (unsigned int i = 0; i < accnt; ++i) {
-                    explicitGatherPeers[numExplicitGatherPeers++] = ac[shuffled[i]];
-                    if (numExplicitGatherPeers == 16) {
-                        break;
-                    }
-                }
-
-                std::vector<Address> anchors(network->config().anchors());
-                for (std::vector<Address>::const_iterator a(anchors.begin()); a != anchors.end(); ++a) {
-                    if (*a != RR->identity.address()) {
-                        explicitGatherPeers[numExplicitGatherPeers++] = *a;
-                        if (numExplicitGatherPeers == 16) {
-                            break;
-                        }
-                    }
-                }
-
-                for (unsigned int k = 0; k < numExplicitGatherPeers; ++k) {
-                    const CertificateOfMembership* com = (network) ? ((network->config().com) ? &(network->config().com) : (const CertificateOfMembership*)0) : (const CertificateOfMembership*)0;
-                    Packet outp(explicitGatherPeers[k], RR->identity.address(), Packet::VERB_MULTICAST_GATHER);
-                    outp.append(network->id());
-                    outp.append((uint8_t)((com) ? 0x01 : 0x00));
-                    mg.mac().appendTo(outp);
-                    outp.append((uint32_t)mg.adi());
-                    outp.append((uint32_t)gatherLimit);
-                    if (com) {
-                        com->serialize(outp);
-                    }
-                    RR->node->expectReplyTo(outp.packetId());
-                    RR->sw->send(tPtr, outp, true);
-                    Metrics::pkt_multicast_gather_out++;
-                }
-            }
-
-            gs.txQueue.push_back(OutboundMulticast());
-            OutboundMulticast& out = gs.txQueue.back();
-
-            out.init(RR, now, network->id(), network->config().disableCompression(), limit, gatherLimit, src, mg, etherType, data, len);
-
-            if (origin) {
-                out.logAsSent(origin);
-            }
-
-            unsigned int count = 0;
-
-            for (unsigned int i = 0; i < activeBridgeCount; ++i) {
-                if (activeBridges[i] != RR->identity.address()) {
-                    out.sendAndLog(RR, tPtr, activeBridges[i]);
-                    if (++count >= limit) {
-                        break;
-                    }
-                }
-            }
-
-            unsigned long idx = 0;
-            while ((count < limit) && (idx < gs.members.size())) {
-                Address ma(gs.members[indexes[idx++]].address);
-                if (std::find(activeBridges, activeBridges + activeBridgeCount, ma) == (activeBridges + activeBridgeCount)) {
-                    out.sendAndLog(RR, tPtr, ma);
-                    ++count;
-                }
-            }
-        }
-    }
-    catch (...) {
-    }   // this is a sanity check to catch any failures and make sure indexes[] still gets deleted
-
-    // Free allocated memory buffer if any
-    if (indexes != idxbuf) {
-        delete[] indexes;
-    }
+	unsigned long idxbuf[4096];
+	unsigned long* indexes = idxbuf;
+
+	// If we're in hub-and-spoke designated multicast replication mode, see if we
+	// have a multicast replicator active. If so, pick the best and send it
+	// there. If we are a multicast replicator or if none are alive, fall back
+	// to sender replication. Note that bridges do not do this since this would
+	// break bridge route learning. This is sort of an edge case limitation of
+	// the current protocol and could be fixed, but fixing it would add more
+	// complexity than the fix is probably worth. Bridges are generally high
+	// bandwidth nodes.
+	if (! network->config().isActiveBridge(RR->identity.address())) {
+		Address multicastReplicators[ZT_MAX_NETWORK_SPECIALISTS];
+		const unsigned int multicastReplicatorCount = network->config().multicastReplicators(multicastReplicators);
+		if (multicastReplicatorCount) {
+			if (std::find(multicastReplicators, multicastReplicators + multicastReplicatorCount, RR->identity.address()) == (multicastReplicators + multicastReplicatorCount)) {
+				SharedPtr<Peer> bestMulticastReplicator;
+				SharedPtr<Path> bestMulticastReplicatorPath;
+				unsigned int bestMulticastReplicatorLatency = 0xffff;
+				for (unsigned int i = 0; i < multicastReplicatorCount; ++i) {
+					const SharedPtr<Peer> p(RR->topology->getPeerNoCache(multicastReplicators[i]));
+					if ((p) && (p->isAlive(now))) {
+						const SharedPtr<Path> pp(p->getAppropriatePath(now, false));
+						if ((pp) && (pp->latency() < bestMulticastReplicatorLatency)) {
+							bestMulticastReplicatorLatency = pp->latency();
+							bestMulticastReplicatorPath = pp;
+							bestMulticastReplicator = p;
+						}
+					}
+				}
+				if (bestMulticastReplicator) {
+					Packet outp(bestMulticastReplicator->address(), RR->identity.address(), Packet::VERB_MULTICAST_FRAME);
+					outp.append((uint64_t)network->id());
+					outp.append((uint8_t)0x0c);	  // includes source MAC | please replicate
+					((src) ? src : MAC(RR->identity.address(), network->id())).appendTo(outp);
+					mg.mac().appendTo(outp);
+					outp.append((uint32_t)mg.adi());
+					outp.append((uint16_t)etherType);
+					outp.append(data, len);
+					if (! network->config().disableCompression()) {
+						outp.compress();
+					}
+					outp.armor(bestMulticastReplicator->key(), true, false, bestMulticastReplicator->aesKeysIfSupported(), bestMulticastReplicator->identity());
+					Metrics::pkt_multicast_frame_out++;
+					bestMulticastReplicatorPath->send(RR, tPtr, outp.data(), outp.size(), now);
+					return;
+				}
+			}
+		}
+	}
+
+	try {
+		Mutex::Lock _l(_groups_m);
+		MulticastGroupStatus& gs = _groups[Multicaster::Key(network->id(), mg)];
+
+		if (! gs.members.empty()) {
+			// Allocate a memory buffer if group is monstrous
+			if (gs.members.size() > (sizeof(idxbuf) / sizeof(unsigned long))) {
+				indexes = new unsigned long[gs.members.size()];
+			}
+
+			// Generate a random permutation of member indexes
+			for (unsigned long i = 0; i < gs.members.size(); ++i) {
+				indexes[i] = i;
+			}
+			for (unsigned long i = (unsigned long)gs.members.size() - 1; i > 0; --i) {
+				unsigned long j = (unsigned long)RR->node->prng() % (i + 1);
+				unsigned long tmp = indexes[j];
+				indexes[j] = indexes[i];
+				indexes[i] = tmp;
+			}
+		}
+
+		Address activeBridges[ZT_MAX_NETWORK_SPECIALISTS];
+		const unsigned int activeBridgeCount = network->config().activeBridges(activeBridges);
+		const unsigned int limit = network->config().multicastLimit;
+
+		if (gs.members.size() >= limit) {
+			// Skip queue if we already have enough members to complete the send operation
+			OutboundMulticast out;
+
+			out.init(
+				RR,
+				now,
+				network->id(),
+				network->config().disableCompression(),
+				limit,
+				1,	 // we'll still gather a little from peers to keep multicast list fresh
+				src,
+				mg,
+				etherType,
+				data,
+				len);
+
+			unsigned int count = 0;
+
+			for (unsigned int i = 0; i < activeBridgeCount; ++i) {
+				if ((activeBridges[i] != RR->identity.address()) && (activeBridges[i] != origin)) {
+					out.sendOnly(RR, tPtr, activeBridges[i]);	// optimization: don't use dedup log if it's a one-pass send
+					if (++count >= limit) {
+						break;
+					}
+				}
+			}
+
+			unsigned long idx = 0;
+			while ((count < limit) && (idx < gs.members.size())) {
+				const Address ma(gs.members[indexes[idx++]].address);
+				if ((std::find(activeBridges, activeBridges + activeBridgeCount, ma) == (activeBridges + activeBridgeCount)) && (ma != origin)) {
+					out.sendOnly(RR, tPtr, ma);	  // optimization: don't use dedup log if it's a one-pass send
+					++count;
+				}
+			}
+		}
+		else {
+			while (gs.txQueue.size() >= ZT_TX_QUEUE_SIZE) {
+				gs.txQueue.pop_front();
+			}
+
+			const unsigned int gatherLimit = (limit - (unsigned int)gs.members.size()) + 1;
+
+			int timerScale = RR->node->lowBandwidthModeEnabled() ? 3 : 1;
+			if ((gs.members.empty()) || ((now - gs.lastExplicitGather) >= (ZT_MULTICAST_EXPLICIT_GATHER_DELAY * timerScale))) {
+				gs.lastExplicitGather = now;
+
+				Address explicitGatherPeers[16];
+				unsigned int numExplicitGatherPeers = 0;
+
+				SharedPtr<Peer> bestRoot(RR->topology->getUpstreamPeer());
+				if (bestRoot) {
+					explicitGatherPeers[numExplicitGatherPeers++] = bestRoot->address();
+				}
+
+				explicitGatherPeers[numExplicitGatherPeers++] = network->controller();
+
+				Address ac[ZT_MAX_NETWORK_SPECIALISTS];
+				const unsigned int accnt = network->config().alwaysContactAddresses(ac);
+				unsigned int shuffled[ZT_MAX_NETWORK_SPECIALISTS];
+				for (unsigned int i = 0; i < accnt; ++i) {
+					shuffled[i] = i;
+				}
+				for (unsigned int i = 0, k = accnt >> 1; i < k; ++i) {
+					const uint64_t x = RR->node->prng();
+					const unsigned int x1 = shuffled[(unsigned int)x % accnt];
+					const unsigned int x2 = shuffled[(unsigned int)(x >> 32) % accnt];
+					const unsigned int tmp = shuffled[x1];
+					shuffled[x1] = shuffled[x2];
+					shuffled[x2] = tmp;
+				}
+				for (unsigned int i = 0; i < accnt; ++i) {
+					explicitGatherPeers[numExplicitGatherPeers++] = ac[shuffled[i]];
+					if (numExplicitGatherPeers == 16) {
+						break;
+					}
+				}
+
+				std::vector<Address> anchors(network->config().anchors());
+				for (std::vector<Address>::const_iterator a(anchors.begin()); a != anchors.end(); ++a) {
+					if (*a != RR->identity.address()) {
+						explicitGatherPeers[numExplicitGatherPeers++] = *a;
+						if (numExplicitGatherPeers == 16) {
+							break;
+						}
+					}
+				}
+
+				for (unsigned int k = 0; k < numExplicitGatherPeers; ++k) {
+					const CertificateOfMembership* com = (network) ? ((network->config().com) ? &(network->config().com) : (const CertificateOfMembership*)0) : (const CertificateOfMembership*)0;
+					Packet outp(explicitGatherPeers[k], RR->identity.address(), Packet::VERB_MULTICAST_GATHER);
+					outp.append(network->id());
+					outp.append((uint8_t)((com) ? 0x01 : 0x00));
+					mg.mac().appendTo(outp);
+					outp.append((uint32_t)mg.adi());
+					outp.append((uint32_t)gatherLimit);
+					if (com) {
+						com->serialize(outp);
+					}
+					RR->node->expectReplyTo(outp.packetId());
+					RR->sw->send(tPtr, outp, true);
+					Metrics::pkt_multicast_gather_out++;
+				}
+			}
+
+			gs.txQueue.push_back(OutboundMulticast());
+			OutboundMulticast& out = gs.txQueue.back();
+
+			out.init(RR, now, network->id(), network->config().disableCompression(), limit, gatherLimit, src, mg, etherType, data, len);
+
+			if (origin) {
+				out.logAsSent(origin);
+			}
+
+			unsigned int count = 0;
+
+			for (unsigned int i = 0; i < activeBridgeCount; ++i) {
+				if (activeBridges[i] != RR->identity.address()) {
+					out.sendAndLog(RR, tPtr, activeBridges[i]);
+					if (++count >= limit) {
+						break;
+					}
+				}
+			}
+
+			unsigned long idx = 0;
+			while ((count < limit) && (idx < gs.members.size())) {
+				Address ma(gs.members[indexes[idx++]].address);
+				if (std::find(activeBridges, activeBridges + activeBridgeCount, ma) == (activeBridges + activeBridgeCount)) {
+					out.sendAndLog(RR, tPtr, ma);
+					++count;
+				}
+			}
+		}
+	}
+	catch (...) {
+	}	// this is a sanity check to catch any failures and make sure indexes[] still gets deleted
+
+	// Free allocated memory buffer if any
+	if (indexes != idxbuf) {
+		delete[] indexes;
+	}
 }
 
 void Multicaster::clean(int64_t now)
 {
-    Mutex::Lock _l(_groups_m);
-    Multicaster::Key* k = (Multicaster::Key*)0;
-    MulticastGroupStatus* s = (MulticastGroupStatus*)0;
-    Hashtable<Multicaster::Key, MulticastGroupStatus>::Iterator mm(_groups);
-    while (mm.next(k, s)) {
-        for (std::list<OutboundMulticast>::iterator tx(s->txQueue.begin()); tx != s->txQueue.end();) {
-            if ((tx->expired(now)) || (tx->atLimit())) {
-                s->txQueue.erase(tx++);
-            }
-            else {
-                ++tx;
-            }
-        }
-
-        unsigned long count = 0;
-        {
-            std::vector<MulticastGroupMember>::iterator reader(s->members.begin());
-            std::vector<MulticastGroupMember>::iterator writer(reader);
-            while (reader != s->members.end()) {
-                if ((now - reader->timestamp) < ZT_MULTICAST_LIKE_EXPIRE) {
-                    *writer = *reader;
-                    ++writer;
-                    ++count;
-                }
-                ++reader;
-            }
-        }
-
-        if (count) {
-            s->members.resize(count);
-        }
-        else if (s->txQueue.empty()) {
-            _groups.erase(*k);
-        }
-        else {
-            s->members.clear();
-        }
-    }
+	Mutex::Lock _l(_groups_m);
+	Multicaster::Key* k = (Multicaster::Key*)0;
+	MulticastGroupStatus* s = (MulticastGroupStatus*)0;
+	Hashtable<Multicaster::Key, MulticastGroupStatus>::Iterator mm(_groups);
+	while (mm.next(k, s)) {
+		for (std::list<OutboundMulticast>::iterator tx(s->txQueue.begin()); tx != s->txQueue.end();) {
+			if ((tx->expired(now)) || (tx->atLimit())) {
+				s->txQueue.erase(tx++);
+			}
+			else {
+				++tx;
+			}
+		}
+
+		unsigned long count = 0;
+		{
+			std::vector<MulticastGroupMember>::iterator reader(s->members.begin());
+			std::vector<MulticastGroupMember>::iterator writer(reader);
+			while (reader != s->members.end()) {
+				if ((now - reader->timestamp) < ZT_MULTICAST_LIKE_EXPIRE) {
+					*writer = *reader;
+					++writer;
+					++count;
+				}
+				++reader;
+			}
+		}
+
+		if (count) {
+			s->members.resize(count);
+		}
+		else if (s->txQueue.empty()) {
+			_groups.erase(*k);
+		}
+		else {
+			s->members.clear();
+		}
+	}
 }
 
 void Multicaster::_add(void* tPtr, int64_t now, uint64_t nwid, const MulticastGroup& mg, MulticastGroupStatus& gs, const Address& member)
 {
-    // assumes _groups_m is locked
-
-    // Do not add self -- even if someone else returns it
-    if (member == RR->identity.address()) {
-        return;
-    }
-
-    std::vector<MulticastGroupMember>::iterator m(std::lower_bound(gs.members.begin(), gs.members.end(), member));
-    if (m != gs.members.end()) {
-        if (m->address == member) {
-            m->timestamp = now;
-            return;
-        }
-        gs.members.insert(m, MulticastGroupMember(member, now));
-    }
-    else {
-        gs.members.push_back(MulticastGroupMember(member, now));
-    }
-
-    for (std::list<OutboundMulticast>::iterator tx(gs.txQueue.begin()); tx != gs.txQueue.end();) {
-        if (tx->atLimit()) {
-            gs.txQueue.erase(tx++);
-        }
-        else {
-            tx->sendIfNew(RR, tPtr, member);
-            if (tx->atLimit()) {
-                gs.txQueue.erase(tx++);
-            }
-            else {
-                ++tx;
-            }
-        }
-    }
+	// assumes _groups_m is locked
+
+	// Do not add self -- even if someone else returns it
+	if (member == RR->identity.address()) {
+		return;
+	}
+
+	std::vector<MulticastGroupMember>::iterator m(std::lower_bound(gs.members.begin(), gs.members.end(), member));
+	if (m != gs.members.end()) {
+		if (m->address == member) {
+			m->timestamp = now;
+			return;
+		}
+		gs.members.insert(m, MulticastGroupMember(member, now));
+	}
+	else {
+		gs.members.push_back(MulticastGroupMember(member, now));
+	}
+
+	for (std::list<OutboundMulticast>::iterator tx(gs.txQueue.begin()); tx != gs.txQueue.end();) {
+		if (tx->atLimit()) {
+			gs.txQueue.erase(tx++);
+		}
+		else {
+			tx->sendIfNew(RR, tPtr, member);
+			if (tx->atLimit()) {
+				gs.txQueue.erase(tx++);
+			}
+			else {
+				++tx;
+			}
+		}
+	}
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 170 - 170
node/Multicaster.hpp

@@ -42,179 +42,179 @@ class Network;
  */
 class Multicaster {
   public:
-    Multicaster(const RuntimeEnvironment* renv);
-    ~Multicaster();
-
-    /**
-     * Add or update a member in a multicast group
-     *
-     * @param now Current time
-     * @param nwid Network ID
-     * @param mg Multicast group
-     * @param member New member address
-     */
-    inline void add(void* tPtr, int64_t now, uint64_t nwid, const MulticastGroup& mg, const Address& member)
-    {
-        Mutex::Lock _l(_groups_m);
-        _add(tPtr, now, nwid, mg, _groups[Multicaster::Key(nwid, mg)], member);
-    }
-
-    /**
-     * Add multiple addresses from a binary array of 5-byte address fields
-     *
-     * It's up to the caller to check bounds on the array before calling this.
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param now Current time
-     * @param nwid Network ID
-     * @param mg Multicast group
-     * @param addresses Raw binary addresses in big-endian format, as a series of 5-byte fields
-     * @param count Number of addresses
-     * @param totalKnown Total number of known addresses as reported by peer
-     */
-    void addMultiple(void* tPtr, int64_t now, uint64_t nwid, const MulticastGroup& mg, const void* addresses, unsigned int count, unsigned int totalKnown);
-
-    /**
-     * Remove a multicast group member (if present)
-     *
-     * @param nwid Network ID
-     * @param mg Multicast group
-     * @param member Member to unsubscribe
-     */
-    void remove(uint64_t nwid, const MulticastGroup& mg, const Address& member);
-
-    /**
-     * Append gather results to a packet by choosing registered multicast recipients at random
-     *
-     * This appends the following fields to the packet:
-     *   <[4] 32-bit total number of known members in this multicast group>
-     *   <[2] 16-bit number of members enumerated in this packet>
-     *   <[...] series of 5-byte ZeroTier addresses of enumerated members>
-     *
-     * If zero is returned, the first two fields will still have been appended.
-     *
-     * @param queryingPeer Peer asking for gather (to skip in results)
-     * @param nwid Network ID
-     * @param mg Multicast group
-     * @param appendTo Packet to append to
-     * @param limit Maximum number of 5-byte addresses to append
-     * @return Number of addresses appended
-     * @throws std::out_of_range Buffer overflow writing to packet
-     */
-    unsigned int gather(const Address& queryingPeer, uint64_t nwid, const MulticastGroup& mg, Buffer<ZT_PROTO_MAX_PACKET_LENGTH>& appendTo, unsigned int limit) const;
-
-    /**
-     * Get subscribers to a multicast group
-     *
-     * @param nwid Network ID
-     * @param mg Multicast group
-     */
-    std::vector<Address> getMembers(uint64_t nwid, const MulticastGroup& mg, unsigned int limit) const;
-
-    /**
-     * Send a multicast
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param now Current time
-     * @param network Network
-     * @param origin Origin of multicast (to not return to sender) or NULL if none
-     * @param mg Multicast group
-     * @param src Source Ethernet MAC address or NULL to skip in packet and compute from ZT address (non-bridged mode)
-     * @param etherType Ethernet frame type
-     * @param data Packet data
-     * @param len Length of packet data
-     */
-    void send(void* tPtr, int64_t now, const SharedPtr<Network>& network, const Address& origin, const MulticastGroup& mg, const MAC& src, unsigned int etherType, const void* data, unsigned int len);
-
-    /**
-     * Clean database
-     *
-     * @param RR Runtime environment
-     * @param now Current time
-     */
-    void clean(int64_t now);
+	Multicaster(const RuntimeEnvironment* renv);
+	~Multicaster();
+
+	/**
+	 * Add or update a member in a multicast group
+	 *
+	 * @param now Current time
+	 * @param nwid Network ID
+	 * @param mg Multicast group
+	 * @param member New member address
+	 */
+	inline void add(void* tPtr, int64_t now, uint64_t nwid, const MulticastGroup& mg, const Address& member)
+	{
+		Mutex::Lock _l(_groups_m);
+		_add(tPtr, now, nwid, mg, _groups[Multicaster::Key(nwid, mg)], member);
+	}
+
+	/**
+	 * Add multiple addresses from a binary array of 5-byte address fields
+	 *
+	 * It's up to the caller to check bounds on the array before calling this.
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param now Current time
+	 * @param nwid Network ID
+	 * @param mg Multicast group
+	 * @param addresses Raw binary addresses in big-endian format, as a series of 5-byte fields
+	 * @param count Number of addresses
+	 * @param totalKnown Total number of known addresses as reported by peer
+	 */
+	void addMultiple(void* tPtr, int64_t now, uint64_t nwid, const MulticastGroup& mg, const void* addresses, unsigned int count, unsigned int totalKnown);
+
+	/**
+	 * Remove a multicast group member (if present)
+	 *
+	 * @param nwid Network ID
+	 * @param mg Multicast group
+	 * @param member Member to unsubscribe
+	 */
+	void remove(uint64_t nwid, const MulticastGroup& mg, const Address& member);
+
+	/**
+	 * Append gather results to a packet by choosing registered multicast recipients at random
+	 *
+	 * This appends the following fields to the packet:
+	 *   <[4] 32-bit total number of known members in this multicast group>
+	 *   <[2] 16-bit number of members enumerated in this packet>
+	 *   <[...] series of 5-byte ZeroTier addresses of enumerated members>
+	 *
+	 * If zero is returned, the first two fields will still have been appended.
+	 *
+	 * @param queryingPeer Peer asking for gather (to skip in results)
+	 * @param nwid Network ID
+	 * @param mg Multicast group
+	 * @param appendTo Packet to append to
+	 * @param limit Maximum number of 5-byte addresses to append
+	 * @return Number of addresses appended
+	 * @throws std::out_of_range Buffer overflow writing to packet
+	 */
+	unsigned int gather(const Address& queryingPeer, uint64_t nwid, const MulticastGroup& mg, Buffer<ZT_PROTO_MAX_PACKET_LENGTH>& appendTo, unsigned int limit) const;
+
+	/**
+	 * Get subscribers to a multicast group
+	 *
+	 * @param nwid Network ID
+	 * @param mg Multicast group
+	 */
+	std::vector<Address> getMembers(uint64_t nwid, const MulticastGroup& mg, unsigned int limit) const;
+
+	/**
+	 * Send a multicast
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param now Current time
+	 * @param network Network
+	 * @param origin Origin of multicast (to not return to sender) or NULL if none
+	 * @param mg Multicast group
+	 * @param src Source Ethernet MAC address or NULL to skip in packet and compute from ZT address (non-bridged mode)
+	 * @param etherType Ethernet frame type
+	 * @param data Packet data
+	 * @param len Length of packet data
+	 */
+	void send(void* tPtr, int64_t now, const SharedPtr<Network>& network, const Address& origin, const MulticastGroup& mg, const MAC& src, unsigned int etherType, const void* data, unsigned int len);
+
+	/**
+	 * Clean database
+	 *
+	 * @param RR Runtime environment
+	 * @param now Current time
+	 */
+	void clean(int64_t now);
 
   private:
-    struct Key {
-        Key() : nwid(0), mg()
-        {
-        }
-        Key(uint64_t n, const MulticastGroup& g) : nwid(n), mg(g)
-        {
-        }
-
-        uint64_t nwid;
-        MulticastGroup mg;
-
-        inline bool operator==(const Key& k) const
-        {
-            return ((nwid == k.nwid) && (mg == k.mg));
-        }
-        inline bool operator!=(const Key& k) const
-        {
-            return ((nwid != k.nwid) || (mg != k.mg));
-        }
-        inline unsigned long hashCode() const
-        {
-            return (mg.hashCode() ^ (unsigned long)(nwid ^ (nwid >> 32)));
-        }
-    };
-
-    struct MulticastGroupMember {
-        MulticastGroupMember()
-        {
-        }
-        MulticastGroupMember(const Address& a, uint64_t ts) : address(a), timestamp(ts)
-        {
-        }
-
-        inline bool operator<(const MulticastGroupMember& a) const
-        {
-            return (address < a.address);
-        }
-        inline bool operator==(const MulticastGroupMember& a) const
-        {
-            return (address == a.address);
-        }
-        inline bool operator!=(const MulticastGroupMember& a) const
-        {
-            return (address != a.address);
-        }
-        inline bool operator<(const Address& a) const
-        {
-            return (address < a);
-        }
-        inline bool operator==(const Address& a) const
-        {
-            return (address == a);
-        }
-        inline bool operator!=(const Address& a) const
-        {
-            return (address != a);
-        }
-
-        Address address;
-        int64_t timestamp;   // time of last notification
-    };
-
-    struct MulticastGroupStatus {
-        MulticastGroupStatus() : lastExplicitGather(0)
-        {
-        }
-
-        int64_t lastExplicitGather;
-        std::list<OutboundMulticast> txQueue;        // pending outbound multicasts
-        std::vector<MulticastGroupMember> members;   // members of this group
-    };
-
-    void _add(void* tPtr, int64_t now, uint64_t nwid, const MulticastGroup& mg, MulticastGroupStatus& gs, const Address& member);
-
-    const RuntimeEnvironment* const RR;
-
-    Hashtable<Multicaster::Key, MulticastGroupStatus> _groups;
-    Mutex _groups_m;
+	struct Key {
+		Key() : nwid(0), mg()
+		{
+		}
+		Key(uint64_t n, const MulticastGroup& g) : nwid(n), mg(g)
+		{
+		}
+
+		uint64_t nwid;
+		MulticastGroup mg;
+
+		inline bool operator==(const Key& k) const
+		{
+			return ((nwid == k.nwid) && (mg == k.mg));
+		}
+		inline bool operator!=(const Key& k) const
+		{
+			return ((nwid != k.nwid) || (mg != k.mg));
+		}
+		inline unsigned long hashCode() const
+		{
+			return (mg.hashCode() ^ (unsigned long)(nwid ^ (nwid >> 32)));
+		}
+	};
+
+	struct MulticastGroupMember {
+		MulticastGroupMember()
+		{
+		}
+		MulticastGroupMember(const Address& a, uint64_t ts) : address(a), timestamp(ts)
+		{
+		}
+
+		inline bool operator<(const MulticastGroupMember& a) const
+		{
+			return (address < a.address);
+		}
+		inline bool operator==(const MulticastGroupMember& a) const
+		{
+			return (address == a.address);
+		}
+		inline bool operator!=(const MulticastGroupMember& a) const
+		{
+			return (address != a.address);
+		}
+		inline bool operator<(const Address& a) const
+		{
+			return (address < a);
+		}
+		inline bool operator==(const Address& a) const
+		{
+			return (address == a);
+		}
+		inline bool operator!=(const Address& a) const
+		{
+			return (address != a);
+		}
+
+		Address address;
+		int64_t timestamp;	 // time of last notification
+	};
+
+	struct MulticastGroupStatus {
+		MulticastGroupStatus() : lastExplicitGather(0)
+		{
+		}
+
+		int64_t lastExplicitGather;
+		std::list<OutboundMulticast> txQueue;		 // pending outbound multicasts
+		std::vector<MulticastGroupMember> members;	 // members of this group
+	};
+
+	void _add(void* tPtr, int64_t now, uint64_t nwid, const MulticastGroup& mg, MulticastGroupStatus& gs, const Address& member);
+
+	const RuntimeEnvironment* const RR;
+
+	Hashtable<Multicaster::Key, MulticastGroupStatus> _groups;
+	Mutex _groups_m;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 111 - 111
node/Mutex.hpp

@@ -27,60 +27,60 @@ namespace ZeroTier {
 // libpthread based mutex lock
 class Mutex {
   public:
-    Mutex()
-    {
-        pthread_mutex_init(&_mh, (const pthread_mutexattr_t*)0);
-    }
-
-    ~Mutex()
-    {
-        pthread_mutex_destroy(&_mh);
-    }
-
-    inline void lock() const
-    {
-        pthread_mutex_lock(&((const_cast<Mutex*>(this))->_mh));
-    }
-
-    inline void unlock() const
-    {
-        pthread_mutex_unlock(&((const_cast<Mutex*>(this))->_mh));
-    }
-
-    class Lock {
-      public:
-        Lock(Mutex& m) : _m(&m)
-        {
-            m.lock();
-        }
-
-        Lock(const Mutex& m) : _m(const_cast<Mutex*>(&m))
-        {
-            _m->lock();
-        }
-
-        ~Lock()
-        {
-            _m->unlock();
-        }
-
-      private:
-        Mutex* const _m;
-    };
+	Mutex()
+	{
+		pthread_mutex_init(&_mh, (const pthread_mutexattr_t*)0);
+	}
+
+	~Mutex()
+	{
+		pthread_mutex_destroy(&_mh);
+	}
+
+	inline void lock() const
+	{
+		pthread_mutex_lock(&((const_cast<Mutex*>(this))->_mh));
+	}
+
+	inline void unlock() const
+	{
+		pthread_mutex_unlock(&((const_cast<Mutex*>(this))->_mh));
+	}
+
+	class Lock {
+	  public:
+		Lock(Mutex& m) : _m(&m)
+		{
+			m.lock();
+		}
+
+		Lock(const Mutex& m) : _m(const_cast<Mutex*>(&m))
+		{
+			_m->lock();
+		}
+
+		~Lock()
+		{
+			_m->unlock();
+		}
+
+	  private:
+		Mutex* const _m;
+	};
 
   private:
-    Mutex(const Mutex&)
-    {
-    }
-    const Mutex& operator=(const Mutex&)
-    {
-        return *this;
-    }
-
-    pthread_mutex_t _mh;
+	Mutex(const Mutex&)
+	{
+	}
+	const Mutex& operator=(const Mutex&)
+	{
+		return *this;
+	}
+
+	pthread_mutex_t _mh;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif
 
@@ -94,71 +94,71 @@ namespace ZeroTier {
 // Windows critical section based lock
 class Mutex {
   public:
-    Mutex()
-    {
-        InitializeCriticalSection(&_cs);
-    }
-
-    ~Mutex()
-    {
-        DeleteCriticalSection(&_cs);
-    }
-
-    inline void lock()
-    {
-        EnterCriticalSection(&_cs);
-    }
-
-    inline void unlock()
-    {
-        LeaveCriticalSection(&_cs);
-    }
-
-    inline void lock() const
-    {
-        (const_cast<Mutex*>(this))->lock();
-    }
-
-    inline void unlock() const
-    {
-        (const_cast<Mutex*>(this))->unlock();
-    }
-
-    class Lock {
-      public:
-        Lock(Mutex& m) : _m(&m)
-        {
-            m.lock();
-        }
-
-        Lock(const Mutex& m) : _m(const_cast<Mutex*>(&m))
-        {
-            _m->lock();
-        }
-
-        ~Lock()
-        {
-            _m->unlock();
-        }
-
-      private:
-        Mutex* const _m;
-    };
+	Mutex()
+	{
+		InitializeCriticalSection(&_cs);
+	}
+
+	~Mutex()
+	{
+		DeleteCriticalSection(&_cs);
+	}
+
+	inline void lock()
+	{
+		EnterCriticalSection(&_cs);
+	}
+
+	inline void unlock()
+	{
+		LeaveCriticalSection(&_cs);
+	}
+
+	inline void lock() const
+	{
+		(const_cast<Mutex*>(this))->lock();
+	}
+
+	inline void unlock() const
+	{
+		(const_cast<Mutex*>(this))->unlock();
+	}
+
+	class Lock {
+	  public:
+		Lock(Mutex& m) : _m(&m)
+		{
+			m.lock();
+		}
+
+		Lock(const Mutex& m) : _m(const_cast<Mutex*>(&m))
+		{
+			_m->lock();
+		}
+
+		~Lock()
+		{
+			_m->unlock();
+		}
+
+	  private:
+		Mutex* const _m;
+	};
 
   private:
-    Mutex(const Mutex&)
-    {
-    }
-    const Mutex& operator=(const Mutex&)
-    {
-        return *this;
-    }
-
-    CRITICAL_SECTION _cs;
+	Mutex(const Mutex&)
+	{
+	}
+	const Mutex& operator=(const Mutex&)
+	{
+		return *this;
+	}
+
+	CRITICAL_SECTION _cs;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
-#endif   // _WIN32
+#endif	 // _WIN32
 
 #endif

+ 1678 - 1678
node/Network.cpp

@@ -43,1821 +43,1821 @@ namespace {
 // Returns true if packet appears valid; pos and proto will be set
 static inline bool _ipv6GetPayload(const uint8_t* frameData, unsigned int frameLen, unsigned int& pos, unsigned int& proto)
 {
-    if (frameLen < 40) {
-        return false;
-    }
-    pos = 40;
-    proto = frameData[6];
-    while (pos <= frameLen) {
-        switch (proto) {
-            case 0:     // hop-by-hop options
-            case 43:    // routing
-            case 60:    // destination options
-            case 135:   // mobility options
-                if ((pos + 8) > frameLen) {
-                    return false;   // invalid!
-                }
-                proto = frameData[pos];
-                pos += ((unsigned int)frameData[pos + 1] * 8) + 8;
-                break;
-
-            // case 44: // fragment -- we currently can't parse these and they are deprecated in IPv6 anyway
-            // case 50:
-            // case 51: // IPSec ESP and AH -- we have to stop here since this is encrypted stuff
-            default:
-                return true;
-        }
-    }
-    return false;   // overflow == invalid
+	if (frameLen < 40) {
+		return false;
+	}
+	pos = 40;
+	proto = frameData[6];
+	while (pos <= frameLen) {
+		switch (proto) {
+			case 0:		// hop-by-hop options
+			case 43:	// routing
+			case 60:	// destination options
+			case 135:	// mobility options
+				if ((pos + 8) > frameLen) {
+					return false;	// invalid!
+				}
+				proto = frameData[pos];
+				pos += ((unsigned int)frameData[pos + 1] * 8) + 8;
+				break;
+
+			// case 44: // fragment -- we currently can't parse these and they are deprecated in IPv6 anyway
+			// case 50:
+			// case 51: // IPSec ESP and AH -- we have to stop here since this is encrypted stuff
+			default:
+				return true;
+		}
+	}
+	return false;	// overflow == invalid
 }
 
 enum _doZtFilterResult { DOZTFILTER_NO_MATCH, DOZTFILTER_DROP, DOZTFILTER_REDIRECT, DOZTFILTER_ACCEPT, DOZTFILTER_SUPER_ACCEPT };
 
 static _doZtFilterResult _doZtFilter(
-    const RuntimeEnvironment* RR,
-    Trace::RuleResultLog& rrl,
-    const NetworkConfig& nconf,
-    const Membership* membership,   // can be NULL
-    const bool inbound,
-    const Address& ztSource,
-    Address& ztDest,   // MUTABLE -- is changed on REDIRECT actions
-    const MAC& macSource,
-    const MAC& macDest,
-    const uint8_t* const frameData,
-    const unsigned int frameLen,
-    const unsigned int etherType,
-    const unsigned int vlanId,
-    const ZT_VirtualNetworkRule* rules,   // cannot be NULL
-    const unsigned int ruleCount,
-    Address& cc,              // MUTABLE -- set to TEE destination if TEE action is taken or left alone otherwise
-    unsigned int& ccLength,   // MUTABLE -- set to length of packet payload to TEE
-    bool& ccWatch,            // MUTABLE -- set to true for WATCH target as opposed to normal TEE
-    uint8_t& qosBucket)       // MUTABLE -- set to the value of the argument provided to PRIORITY
+	const RuntimeEnvironment* RR,
+	Trace::RuleResultLog& rrl,
+	const NetworkConfig& nconf,
+	const Membership* membership,	// can be NULL
+	const bool inbound,
+	const Address& ztSource,
+	Address& ztDest,   // MUTABLE -- is changed on REDIRECT actions
+	const MAC& macSource,
+	const MAC& macDest,
+	const uint8_t* const frameData,
+	const unsigned int frameLen,
+	const unsigned int etherType,
+	const unsigned int vlanId,
+	const ZT_VirtualNetworkRule* rules,	  // cannot be NULL
+	const unsigned int ruleCount,
+	Address& cc,			  // MUTABLE -- set to TEE destination if TEE action is taken or left alone otherwise
+	unsigned int& ccLength,	  // MUTABLE -- set to length of packet payload to TEE
+	bool& ccWatch,			  // MUTABLE -- set to true for WATCH target as opposed to normal TEE
+	uint8_t& qosBucket)		  // MUTABLE -- set to the value of the argument provided to PRIORITY
 {
-    // Set to true if we are a TEE/REDIRECT/WATCH target
-    bool superAccept = false;
+	// Set to true if we are a TEE/REDIRECT/WATCH target
+	bool superAccept = false;
 
-    // The default match state for each set of entries starts as 'true' since an
-    // ACTION with no MATCH entries preceding it is always taken.
-    uint8_t thisSetMatches = 1;
-    uint8_t skipDrop = 0;
+	// The default match state for each set of entries starts as 'true' since an
+	// ACTION with no MATCH entries preceding it is always taken.
+	uint8_t thisSetMatches = 1;
+	uint8_t skipDrop = 0;
 
-    rrl.clear();
+	rrl.clear();
 
-    // uncomment for easier debugging fprintf
-    // if (!ztDest) { return DOZTFILTER_ACCEPT; }
+	// uncomment for easier debugging fprintf
+	// if (!ztDest) { return DOZTFILTER_ACCEPT; }
 #ifdef ZT_TRACE
-    // char buf[40], buf2[40];
-    // fprintf(stderr, "\nsrc %s dest %s inbound: %d ethertype %u", ztSource.toString(buf), ztDest.toString(buf2), inbound, etherType);
+	// char buf[40], buf2[40];
+	// fprintf(stderr, "\nsrc %s dest %s inbound: %d ethertype %u", ztSource.toString(buf), ztDest.toString(buf2), inbound, etherType);
 #endif
 
-    for (unsigned int rn = 0; rn < ruleCount; ++rn) {
-        const ZT_VirtualNetworkRuleType rt = (ZT_VirtualNetworkRuleType)(rules[rn].t & 0x3f);
+	for (unsigned int rn = 0; rn < ruleCount; ++rn) {
+		const ZT_VirtualNetworkRuleType rt = (ZT_VirtualNetworkRuleType)(rules[rn].t & 0x3f);
 #ifdef ZT_TRACE
-        // fprintf(stderr, "\n%02u %02d", rn, rt);
+		// fprintf(stderr, "\n%02u %02d", rn, rt);
 #endif
 
-        // First check if this is an ACTION
-        if ((unsigned int)rt <= (unsigned int)ZT_NETWORK_RULE_ACTION__MAX_ID) {
-            if (thisSetMatches) {
-                switch (rt) {
-                    case ZT_NETWORK_RULE_ACTION_PRIORITY:
-                        qosBucket = (rules[rn].v.qosBucket <= 8) ? rules[rn].v.qosBucket : 4;   // 4 = default bucket (no priority)
-                        return DOZTFILTER_ACCEPT;
+		// First check if this is an ACTION
+		if ((unsigned int)rt <= (unsigned int)ZT_NETWORK_RULE_ACTION__MAX_ID) {
+			if (thisSetMatches) {
+				switch (rt) {
+					case ZT_NETWORK_RULE_ACTION_PRIORITY:
+						qosBucket = (rules[rn].v.qosBucket <= 8) ? rules[rn].v.qosBucket : 4;	// 4 = default bucket (no priority)
+						return DOZTFILTER_ACCEPT;
 
-                    case ZT_NETWORK_RULE_ACTION_DROP: {
-                        if (! ! skipDrop) {
+					case ZT_NETWORK_RULE_ACTION_DROP: {
+						if (! ! skipDrop) {
 #ifdef ZT_TRACE
-                            // fprintf(stderr, "\tskip Drop");
+							// fprintf(stderr, "\tskip Drop");
 #endif
-                            skipDrop = 0;
-                            continue;
-                        }
+							skipDrop = 0;
+							continue;
+						}
 #ifdef ZT_TRACE
-                        // fprintf(stderr, "\tDrop\n");
+						// fprintf(stderr, "\tDrop\n");
 #endif
-                        return DOZTFILTER_DROP;
-                    }
+						return DOZTFILTER_DROP;
+					}
 
-                    case ZT_NETWORK_RULE_ACTION_ACCEPT: {
+					case ZT_NETWORK_RULE_ACTION_ACCEPT: {
 #ifdef ZT_TRACE
-                        // fprintf(stderr, "\tAccept\n");
+						// fprintf(stderr, "\tAccept\n");
 #endif
-                        return (superAccept ? DOZTFILTER_SUPER_ACCEPT : DOZTFILTER_ACCEPT);   // match, accept packet
-                    }
-
-                    // These are initially handled together since preliminary logic is common
-                    case ZT_NETWORK_RULE_ACTION_TEE:
-                    case ZT_NETWORK_RULE_ACTION_WATCH:
-                    case ZT_NETWORK_RULE_ACTION_REDIRECT: {
-                        const Address fwdAddr(rules[rn].v.fwd.address);
-                        if (fwdAddr == ztSource) {
-                            // Skip as no-op since source is target
-                        }
-                        else if (fwdAddr == RR->identity.address()) {
-                            if (inbound) {
-                                return DOZTFILTER_SUPER_ACCEPT;
-                            }
-                            else {
-                            }
-                        }
-                        else if (fwdAddr == ztDest) {
-                        }
-                        else {
-                            if (rt == ZT_NETWORK_RULE_ACTION_REDIRECT) {
-                                ztDest = fwdAddr;
-                                return DOZTFILTER_REDIRECT;
-                            }
-                            else {
-                                cc = fwdAddr;
-                                ccLength = (rules[rn].v.fwd.length != 0) ? ((frameLen < (unsigned int)rules[rn].v.fwd.length) ? frameLen : (unsigned int)rules[rn].v.fwd.length) : frameLen;
-                                ccWatch = (rt == ZT_NETWORK_RULE_ACTION_WATCH);
-                            }
-                        }
-                    }
-                        continue;
-
-                    case ZT_NETWORK_RULE_ACTION_BREAK:
-                        return DOZTFILTER_NO_MATCH;
-
-                    // Unrecognized ACTIONs are ignored as no-ops
-                    default:
-                        continue;
-                }
-            }
-            else {
-                // If this is an incoming packet and we are a TEE or REDIRECT target, we should
-                // super-accept if we accept at all. This will cause us to accept redirected or
-                // tee'd packets in spite of MAC and ZT addressing checks.
-                if (inbound) {
-                    switch (rt) {
-                        case ZT_NETWORK_RULE_ACTION_TEE:
-                        case ZT_NETWORK_RULE_ACTION_WATCH:
-                        case ZT_NETWORK_RULE_ACTION_REDIRECT:
-                            if (RR->identity.address() == rules[rn].v.fwd.address) {
-                                superAccept = true;
-                            }
-                            break;
-                        default:
-                            break;
-                    }
-                }
-
-                thisSetMatches = 1;   // reset to default true for next batch of entries
-                continue;
-            }
-        }
-
-        // Circuit breaker: no need to evaluate an AND if the set's match state
-        // is currently false since anything AND false is false.
-        if ((! thisSetMatches) && (! (rules[rn].t & 0x40))) {
-            rrl.logSkipped(rn, thisSetMatches);
-            continue;
-        }
-
-        // If this was not an ACTION evaluate next MATCH and update thisSetMatches with (AND [result])
-        uint8_t thisRuleMatches = 0;
-        uint64_t ownershipVerificationMask = 1;     // this magic value means it hasn't been computed yet -- this is done lazily the first time it's needed
-        uint8_t hardYes = (rules[rn].t >> 7) ^ 1;   // XOR with the NOT bit of the rule
-        uint8_t hardNo = (rules[rn].t >> 7) ^ 0;
-
-        switch (rt) {
-            case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS:
-                thisRuleMatches = (uint8_t)(rules[rn].v.zt == ztSource.toInt());
-                break;
-            case ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS:
-                thisRuleMatches = (uint8_t)(rules[rn].v.zt == ztDest.toInt());
-                break;
-            case ZT_NETWORK_RULE_MATCH_VLAN_ID:
-                thisRuleMatches = (uint8_t)(rules[rn].v.vlanId == (uint16_t)vlanId);
-                break;
-            case ZT_NETWORK_RULE_MATCH_VLAN_PCP:
-                // NOT SUPPORTED YET
-                thisRuleMatches = (uint8_t)(rules[rn].v.vlanPcp == 0);
-                break;
-            case ZT_NETWORK_RULE_MATCH_VLAN_DEI:
-                // NOT SUPPORTED YET
-                thisRuleMatches = (uint8_t)(rules[rn].v.vlanDei == 0);
-                break;
-            case ZT_NETWORK_RULE_MATCH_MAC_SOURCE:
-                thisRuleMatches = (uint8_t)(MAC(rules[rn].v.mac, 6) == macSource);
-                break;
-            case ZT_NETWORK_RULE_MATCH_MAC_DEST:
-                thisRuleMatches = (uint8_t)(MAC(rules[rn].v.mac, 6) == macDest);
-                break;
-            case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE:
-                if ((etherType == ZT_ETHERTYPE_IPV4) && (frameLen >= 20)) {
-                    thisRuleMatches = (uint8_t)(InetAddress((const void*)&(rules[rn].v.ipv4.ip), 4, rules[rn].v.ipv4.mask).containsAddress(InetAddress((const void*)(frameData + 12), 4, 0)));
-                }
-                else {
-                    thisRuleMatches = hardNo;
-                }
-                break;
-            case ZT_NETWORK_RULE_MATCH_IPV4_DEST:
-                if ((etherType == ZT_ETHERTYPE_IPV4) && (frameLen >= 20)) {
-                    thisRuleMatches = (uint8_t)(InetAddress((const void*)&(rules[rn].v.ipv4.ip), 4, rules[rn].v.ipv4.mask).containsAddress(InetAddress((const void*)(frameData + 16), 4, 0)));
-                }
-                else {
-                    thisRuleMatches = hardNo;
-                }
-                break;
-            case ZT_NETWORK_RULE_MATCH_IPV6_SOURCE:
-                if ((etherType == ZT_ETHERTYPE_IPV6) && (frameLen >= 40)) {
-                    thisRuleMatches = (uint8_t)(InetAddress((const void*)rules[rn].v.ipv6.ip, 16, rules[rn].v.ipv6.mask).containsAddress(InetAddress((const void*)(frameData + 8), 16, 0)));
-                }
-                else {
-                    thisRuleMatches = hardNo;
-                }
-                break;
-            case ZT_NETWORK_RULE_MATCH_IPV6_DEST:
-                if ((etherType == ZT_ETHERTYPE_IPV6) && (frameLen >= 40)) {
-                    thisRuleMatches = (uint8_t)(InetAddress((const void*)rules[rn].v.ipv6.ip, 16, rules[rn].v.ipv6.mask).containsAddress(InetAddress((const void*)(frameData + 24), 16, 0)));
-                }
-                else {
-                    thisRuleMatches = hardNo;
-                }
-                break;
-            case ZT_NETWORK_RULE_MATCH_IP_TOS:
-                if ((etherType == ZT_ETHERTYPE_IPV4) && (frameLen >= 20)) {
-                    const uint8_t tosMasked = frameData[1] & rules[rn].v.ipTos.mask;
-                    thisRuleMatches = (uint8_t)((tosMasked >= rules[rn].v.ipTos.value[0]) && (tosMasked <= rules[rn].v.ipTos.value[1]));
-                }
-                else if ((etherType == ZT_ETHERTYPE_IPV6) && (frameLen >= 40)) {
-                    const uint8_t tosMasked = (((frameData[0] << 4) & 0xf0) | ((frameData[1] >> 4) & 0x0f)) & rules[rn].v.ipTos.mask;
-                    thisRuleMatches = (uint8_t)((tosMasked >= rules[rn].v.ipTos.value[0]) && (tosMasked <= rules[rn].v.ipTos.value[1]));
-                }
-                else {
-                    thisRuleMatches = hardNo;
-                }
-                break;
-            case ZT_NETWORK_RULE_MATCH_IP_PROTOCOL:
-                if ((etherType == ZT_ETHERTYPE_IPV4) && (frameLen >= 20)) {
-                    thisRuleMatches = (uint8_t)(rules[rn].v.ipProtocol == frameData[9]);
-                }
-                else if (etherType == ZT_ETHERTYPE_IPV6) {
-                    unsigned int pos = 0, proto = 0;
-                    if (_ipv6GetPayload(frameData, frameLen, pos, proto)) {
-                        thisRuleMatches = (uint8_t)(rules[rn].v.ipProtocol == (uint8_t)proto);
-                    }
-                    else {
-                        thisRuleMatches = hardNo;
-                    }
-                }
-                else {
-                    thisRuleMatches = hardNo;
-                }
-                break;
-            case ZT_NETWORK_RULE_MATCH_ETHERTYPE:
-                thisRuleMatches = (uint8_t)(rules[rn].v.etherType == (uint16_t)etherType);
-                break;
-            case ZT_NETWORK_RULE_MATCH_ICMP:
-                if ((etherType == ZT_ETHERTYPE_IPV4) && (frameLen >= 20)) {
-                    if (frameData[9] == 0x01) {   // IP protocol == ICMP
-                        const unsigned int ihl = (frameData[0] & 0xf) * 4;
-                        if (frameLen >= (ihl + 2)) {
-                            if (rules[rn].v.icmp.type == frameData[ihl]) {
-                                if ((rules[rn].v.icmp.flags & 0x01) != 0) {
-                                    thisRuleMatches = (uint8_t)(frameData[ihl + 1] == rules[rn].v.icmp.code);
-                                }
-                                else {
-                                    thisRuleMatches = hardYes;
-                                }
-                            }
-                            else {
-                                thisRuleMatches = hardNo;
-                            }
-                        }
-                        else {
-                            thisRuleMatches = hardNo;
-                        }
-                    }
-                    else {
-                        thisRuleMatches = hardNo;
-                    }
-                }
-                else if (etherType == ZT_ETHERTYPE_IPV6) {
-                    unsigned int pos = 0, proto = 0;
-                    if (_ipv6GetPayload(frameData, frameLen, pos, proto)) {
-                        if ((proto == 0x3a) && (frameLen >= (pos + 2))) {
-                            if (rules[rn].v.icmp.type == frameData[pos]) {
-                                if ((rules[rn].v.icmp.flags & 0x01) != 0) {
-                                    thisRuleMatches = (uint8_t)(frameData[pos + 1] == rules[rn].v.icmp.code);
-                                }
-                                else {
-                                    thisRuleMatches = hardYes;
-                                }
-                            }
-                            else {
-                                thisRuleMatches = hardNo;
-                            }
-                        }
-                        else {
-                            thisRuleMatches = hardNo;
-                        }
-                    }
-                    else {
-                        thisRuleMatches = hardNo;
-                    }
-                }
-                else {
-                    thisRuleMatches = hardNo;
-                }
-                break;
-            case ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE:
-            case ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE:
-                if ((etherType == ZT_ETHERTYPE_IPV4) && (frameLen >= 20)) {
-                    const unsigned int headerLen = 4 * (frameData[0] & 0xf);
-                    int p = -1;
-                    switch (frameData[9]) {   // IP protocol number
-                        // All these start with 16-bit source and destination port in that order
-                        case 0x06:   // TCP
-                        case 0x11:   // UDP
-                        case 0x84:   // SCTP
-                        case 0x88:   // UDPLite
-                            if (frameLen > (headerLen + 4)) {
-                                unsigned int pos = headerLen + ((rt == ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE) ? 2 : 0);
-                                p = (int)frameData[pos++] << 8;
-                                p |= (int)frameData[pos];
-                            }
-                            break;
-                    }
-
-                    thisRuleMatches = (p >= 0) ? (uint8_t)((p >= (int)rules[rn].v.port[0]) && (p <= (int)rules[rn].v.port[1])) : (uint8_t)0;
-                }
-                else if (etherType == ZT_ETHERTYPE_IPV6) {
-                    unsigned int pos = 0, proto = 0;
-                    if (_ipv6GetPayload(frameData, frameLen, pos, proto)) {
-                        int p = -1;
-                        switch (proto) {   // IP protocol number
-                            // All these start with 16-bit source and destination port in that order
-                            case 0x06:   // TCP
-                            case 0x11:   // UDP
-                            case 0x84:   // SCTP
-                            case 0x88:   // UDPLite
-                                if (frameLen > (pos + 4)) {
-                                    if (rt == ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE) {
-                                        pos += 2;
-                                    }
-                                    p = (int)frameData[pos++] << 8;
-                                    p |= (int)frameData[pos];
-                                }
-                                break;
-                        }
-                        thisRuleMatches = (p > 0) ? (uint8_t)((p >= (int)rules[rn].v.port[0]) && (p <= (int)rules[rn].v.port[1])) : (uint8_t)0;
-                    }
-                    else {
-                        thisRuleMatches = hardNo;
-                    }
-                }
-                else {
-                    thisRuleMatches = hardNo;
-                }
-                break;
-            case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS: {
-                uint64_t cf = (inbound) ? ZT_RULE_PACKET_CHARACTERISTICS_INBOUND : 0ULL;
-                if (macDest.isMulticast()) {
-                    cf |= ZT_RULE_PACKET_CHARACTERISTICS_MULTICAST;
-                }
-                if (macDest.isBroadcast()) {
-                    cf |= ZT_RULE_PACKET_CHARACTERISTICS_BROADCAST;
-                }
-                if (ownershipVerificationMask == 1) {
-                    ownershipVerificationMask = 0;
-                    InetAddress src;
-                    if ((etherType == ZT_ETHERTYPE_IPV4) && (frameLen >= 20)) {
-                        src.set((const void*)(frameData + 12), 4, 0);
-                    }
-                    else if ((etherType == ZT_ETHERTYPE_IPV6) && (frameLen >= 40)) {
-                        // IPv6 NDP requires special handling, since the src and dest IPs in the packet are empty or link-local.
-                        if ((frameLen >= (40 + 8 + 16)) && (frameData[6] == 0x3a) && ((frameData[40] == 0x87) || (frameData[40] == 0x88))) {
-                            if (frameData[40] == 0x87) {
-                                // Neighbor solicitations contain no reliable source address, so we implement a small
-                                // hack by considering them authenticated. Otherwise you would pretty much have to do
-                                // this manually in the rule set for IPv6 to work at all.
-                                ownershipVerificationMask |= ZT_RULE_PACKET_CHARACTERISTICS_SENDER_IP_AUTHENTICATED;
-                            }
-                            else {
-                                // Neighbor advertisements on the other hand can absolutely be authenticated.
-                                src.set((const void*)(frameData + 40 + 8), 16, 0);
-                            }
-                        }
-                        else {
-                            // Other IPv6 packets can be handled normally
-                            src.set((const void*)(frameData + 8), 16, 0);
-                        }
-                    }
-                    else if ((etherType == ZT_ETHERTYPE_ARP) && (frameLen >= 28)) {
-                        src.set((const void*)(frameData + 14), 4, 0);
-                    }
-                    if (inbound) {
-                        if (membership) {
-                            if ((src) && (membership->hasCertificateOfOwnershipFor<InetAddress>(nconf, src))) {
-                                ownershipVerificationMask |= ZT_RULE_PACKET_CHARACTERISTICS_SENDER_IP_AUTHENTICATED;
-                            }
-                            if (membership->hasCertificateOfOwnershipFor<MAC>(nconf, macSource)) {
-                                ownershipVerificationMask |= ZT_RULE_PACKET_CHARACTERISTICS_SENDER_MAC_AUTHENTICATED;
-                            }
-                        }
-                    }
-                    else {
-                        for (unsigned int i = 0; i < nconf.certificateOfOwnershipCount; ++i) {
-                            if ((src) && (nconf.certificatesOfOwnership[i].owns(src))) {
-                                ownershipVerificationMask |= ZT_RULE_PACKET_CHARACTERISTICS_SENDER_IP_AUTHENTICATED;
-                            }
-                            if (nconf.certificatesOfOwnership[i].owns(macSource)) {
-                                ownershipVerificationMask |= ZT_RULE_PACKET_CHARACTERISTICS_SENDER_MAC_AUTHENTICATED;
-                            }
-                        }
-                    }
-                }
-                cf |= ownershipVerificationMask;
-                if ((etherType == ZT_ETHERTYPE_IPV4) && (frameLen >= 20) && (frameData[9] == 0x06)) {
-                    const unsigned int headerLen = 4 * (frameData[0] & 0xf);
-                    cf |= (uint64_t)frameData[headerLen + 13];
-                    cf |= (((uint64_t)(frameData[headerLen + 12] & 0x0f)) << 8);
-                }
-                else if (etherType == ZT_ETHERTYPE_IPV6) {
-                    unsigned int pos = 0, proto = 0;
-                    if (_ipv6GetPayload(frameData, frameLen, pos, proto)) {
-                        if ((proto == 0x06) && (frameLen > (pos + 14))) {
-                            cf |= (uint64_t)frameData[pos + 13];
-                            cf |= (((uint64_t)(frameData[pos + 12] & 0x0f)) << 8);
-                        }
-                    }
-                }
-                thisRuleMatches = (uint8_t)((cf & rules[rn].v.characteristics) != 0);
-            } break;
-            case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE:
-                thisRuleMatches = (uint8_t)((frameLen >= (unsigned int)rules[rn].v.frameSize[0]) && (frameLen <= (unsigned int)rules[rn].v.frameSize[1]));
-                break;
-            case ZT_NETWORK_RULE_MATCH_RANDOM:
-                thisRuleMatches = (uint8_t)((uint32_t)(RR->node->prng() & 0xffffffffULL) <= rules[rn].v.randomProbability);
-                break;
-            case ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE:
-            case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND:
-            case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR:
-            case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR:
-            case ZT_NETWORK_RULE_MATCH_TAGS_EQUAL: {
-                const Tag* const localTag = std::lower_bound(&(nconf.tags[0]), &(nconf.tags[nconf.tagCount]), rules[rn].v.tag.id, Tag::IdComparePredicate());
-                if ((localTag != &(nconf.tags[nconf.tagCount])) && (localTag->id() == rules[rn].v.tag.id)) {
-                    const Tag* const remoteTag = ((membership) ? membership->getTag(nconf, rules[rn].v.tag.id) : (const Tag*)0);
+						return (superAccept ? DOZTFILTER_SUPER_ACCEPT : DOZTFILTER_ACCEPT);	  // match, accept packet
+					}
+
+					// These are initially handled together since preliminary logic is common
+					case ZT_NETWORK_RULE_ACTION_TEE:
+					case ZT_NETWORK_RULE_ACTION_WATCH:
+					case ZT_NETWORK_RULE_ACTION_REDIRECT: {
+						const Address fwdAddr(rules[rn].v.fwd.address);
+						if (fwdAddr == ztSource) {
+							// Skip as no-op since source is target
+						}
+						else if (fwdAddr == RR->identity.address()) {
+							if (inbound) {
+								return DOZTFILTER_SUPER_ACCEPT;
+							}
+							else {
+							}
+						}
+						else if (fwdAddr == ztDest) {
+						}
+						else {
+							if (rt == ZT_NETWORK_RULE_ACTION_REDIRECT) {
+								ztDest = fwdAddr;
+								return DOZTFILTER_REDIRECT;
+							}
+							else {
+								cc = fwdAddr;
+								ccLength = (rules[rn].v.fwd.length != 0) ? ((frameLen < (unsigned int)rules[rn].v.fwd.length) ? frameLen : (unsigned int)rules[rn].v.fwd.length) : frameLen;
+								ccWatch = (rt == ZT_NETWORK_RULE_ACTION_WATCH);
+							}
+						}
+					}
+						continue;
+
+					case ZT_NETWORK_RULE_ACTION_BREAK:
+						return DOZTFILTER_NO_MATCH;
+
+					// Unrecognized ACTIONs are ignored as no-ops
+					default:
+						continue;
+				}
+			}
+			else {
+				// If this is an incoming packet and we are a TEE or REDIRECT target, we should
+				// super-accept if we accept at all. This will cause us to accept redirected or
+				// tee'd packets in spite of MAC and ZT addressing checks.
+				if (inbound) {
+					switch (rt) {
+						case ZT_NETWORK_RULE_ACTION_TEE:
+						case ZT_NETWORK_RULE_ACTION_WATCH:
+						case ZT_NETWORK_RULE_ACTION_REDIRECT:
+							if (RR->identity.address() == rules[rn].v.fwd.address) {
+								superAccept = true;
+							}
+							break;
+						default:
+							break;
+					}
+				}
+
+				thisSetMatches = 1;	  // reset to default true for next batch of entries
+				continue;
+			}
+		}
+
+		// Circuit breaker: no need to evaluate an AND if the set's match state
+		// is currently false since anything AND false is false.
+		if ((! thisSetMatches) && (! (rules[rn].t & 0x40))) {
+			rrl.logSkipped(rn, thisSetMatches);
+			continue;
+		}
+
+		// If this was not an ACTION evaluate next MATCH and update thisSetMatches with (AND [result])
+		uint8_t thisRuleMatches = 0;
+		uint64_t ownershipVerificationMask = 1;		// this magic value means it hasn't been computed yet -- this is done lazily the first time it's needed
+		uint8_t hardYes = (rules[rn].t >> 7) ^ 1;	// XOR with the NOT bit of the rule
+		uint8_t hardNo = (rules[rn].t >> 7) ^ 0;
+
+		switch (rt) {
+			case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS:
+				thisRuleMatches = (uint8_t)(rules[rn].v.zt == ztSource.toInt());
+				break;
+			case ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS:
+				thisRuleMatches = (uint8_t)(rules[rn].v.zt == ztDest.toInt());
+				break;
+			case ZT_NETWORK_RULE_MATCH_VLAN_ID:
+				thisRuleMatches = (uint8_t)(rules[rn].v.vlanId == (uint16_t)vlanId);
+				break;
+			case ZT_NETWORK_RULE_MATCH_VLAN_PCP:
+				// NOT SUPPORTED YET
+				thisRuleMatches = (uint8_t)(rules[rn].v.vlanPcp == 0);
+				break;
+			case ZT_NETWORK_RULE_MATCH_VLAN_DEI:
+				// NOT SUPPORTED YET
+				thisRuleMatches = (uint8_t)(rules[rn].v.vlanDei == 0);
+				break;
+			case ZT_NETWORK_RULE_MATCH_MAC_SOURCE:
+				thisRuleMatches = (uint8_t)(MAC(rules[rn].v.mac, 6) == macSource);
+				break;
+			case ZT_NETWORK_RULE_MATCH_MAC_DEST:
+				thisRuleMatches = (uint8_t)(MAC(rules[rn].v.mac, 6) == macDest);
+				break;
+			case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE:
+				if ((etherType == ZT_ETHERTYPE_IPV4) && (frameLen >= 20)) {
+					thisRuleMatches = (uint8_t)(InetAddress((const void*)&(rules[rn].v.ipv4.ip), 4, rules[rn].v.ipv4.mask).containsAddress(InetAddress((const void*)(frameData + 12), 4, 0)));
+				}
+				else {
+					thisRuleMatches = hardNo;
+				}
+				break;
+			case ZT_NETWORK_RULE_MATCH_IPV4_DEST:
+				if ((etherType == ZT_ETHERTYPE_IPV4) && (frameLen >= 20)) {
+					thisRuleMatches = (uint8_t)(InetAddress((const void*)&(rules[rn].v.ipv4.ip), 4, rules[rn].v.ipv4.mask).containsAddress(InetAddress((const void*)(frameData + 16), 4, 0)));
+				}
+				else {
+					thisRuleMatches = hardNo;
+				}
+				break;
+			case ZT_NETWORK_RULE_MATCH_IPV6_SOURCE:
+				if ((etherType == ZT_ETHERTYPE_IPV6) && (frameLen >= 40)) {
+					thisRuleMatches = (uint8_t)(InetAddress((const void*)rules[rn].v.ipv6.ip, 16, rules[rn].v.ipv6.mask).containsAddress(InetAddress((const void*)(frameData + 8), 16, 0)));
+				}
+				else {
+					thisRuleMatches = hardNo;
+				}
+				break;
+			case ZT_NETWORK_RULE_MATCH_IPV6_DEST:
+				if ((etherType == ZT_ETHERTYPE_IPV6) && (frameLen >= 40)) {
+					thisRuleMatches = (uint8_t)(InetAddress((const void*)rules[rn].v.ipv6.ip, 16, rules[rn].v.ipv6.mask).containsAddress(InetAddress((const void*)(frameData + 24), 16, 0)));
+				}
+				else {
+					thisRuleMatches = hardNo;
+				}
+				break;
+			case ZT_NETWORK_RULE_MATCH_IP_TOS:
+				if ((etherType == ZT_ETHERTYPE_IPV4) && (frameLen >= 20)) {
+					const uint8_t tosMasked = frameData[1] & rules[rn].v.ipTos.mask;
+					thisRuleMatches = (uint8_t)((tosMasked >= rules[rn].v.ipTos.value[0]) && (tosMasked <= rules[rn].v.ipTos.value[1]));
+				}
+				else if ((etherType == ZT_ETHERTYPE_IPV6) && (frameLen >= 40)) {
+					const uint8_t tosMasked = (((frameData[0] << 4) & 0xf0) | ((frameData[1] >> 4) & 0x0f)) & rules[rn].v.ipTos.mask;
+					thisRuleMatches = (uint8_t)((tosMasked >= rules[rn].v.ipTos.value[0]) && (tosMasked <= rules[rn].v.ipTos.value[1]));
+				}
+				else {
+					thisRuleMatches = hardNo;
+				}
+				break;
+			case ZT_NETWORK_RULE_MATCH_IP_PROTOCOL:
+				if ((etherType == ZT_ETHERTYPE_IPV4) && (frameLen >= 20)) {
+					thisRuleMatches = (uint8_t)(rules[rn].v.ipProtocol == frameData[9]);
+				}
+				else if (etherType == ZT_ETHERTYPE_IPV6) {
+					unsigned int pos = 0, proto = 0;
+					if (_ipv6GetPayload(frameData, frameLen, pos, proto)) {
+						thisRuleMatches = (uint8_t)(rules[rn].v.ipProtocol == (uint8_t)proto);
+					}
+					else {
+						thisRuleMatches = hardNo;
+					}
+				}
+				else {
+					thisRuleMatches = hardNo;
+				}
+				break;
+			case ZT_NETWORK_RULE_MATCH_ETHERTYPE:
+				thisRuleMatches = (uint8_t)(rules[rn].v.etherType == (uint16_t)etherType);
+				break;
+			case ZT_NETWORK_RULE_MATCH_ICMP:
+				if ((etherType == ZT_ETHERTYPE_IPV4) && (frameLen >= 20)) {
+					if (frameData[9] == 0x01) {	  // IP protocol == ICMP
+						const unsigned int ihl = (frameData[0] & 0xf) * 4;
+						if (frameLen >= (ihl + 2)) {
+							if (rules[rn].v.icmp.type == frameData[ihl]) {
+								if ((rules[rn].v.icmp.flags & 0x01) != 0) {
+									thisRuleMatches = (uint8_t)(frameData[ihl + 1] == rules[rn].v.icmp.code);
+								}
+								else {
+									thisRuleMatches = hardYes;
+								}
+							}
+							else {
+								thisRuleMatches = hardNo;
+							}
+						}
+						else {
+							thisRuleMatches = hardNo;
+						}
+					}
+					else {
+						thisRuleMatches = hardNo;
+					}
+				}
+				else if (etherType == ZT_ETHERTYPE_IPV6) {
+					unsigned int pos = 0, proto = 0;
+					if (_ipv6GetPayload(frameData, frameLen, pos, proto)) {
+						if ((proto == 0x3a) && (frameLen >= (pos + 2))) {
+							if (rules[rn].v.icmp.type == frameData[pos]) {
+								if ((rules[rn].v.icmp.flags & 0x01) != 0) {
+									thisRuleMatches = (uint8_t)(frameData[pos + 1] == rules[rn].v.icmp.code);
+								}
+								else {
+									thisRuleMatches = hardYes;
+								}
+							}
+							else {
+								thisRuleMatches = hardNo;
+							}
+						}
+						else {
+							thisRuleMatches = hardNo;
+						}
+					}
+					else {
+						thisRuleMatches = hardNo;
+					}
+				}
+				else {
+					thisRuleMatches = hardNo;
+				}
+				break;
+			case ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE:
+			case ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE:
+				if ((etherType == ZT_ETHERTYPE_IPV4) && (frameLen >= 20)) {
+					const unsigned int headerLen = 4 * (frameData[0] & 0xf);
+					int p = -1;
+					switch (frameData[9]) {	  // IP protocol number
+						// All these start with 16-bit source and destination port in that order
+						case 0x06:	 // TCP
+						case 0x11:	 // UDP
+						case 0x84:	 // SCTP
+						case 0x88:	 // UDPLite
+							if (frameLen > (headerLen + 4)) {
+								unsigned int pos = headerLen + ((rt == ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE) ? 2 : 0);
+								p = (int)frameData[pos++] << 8;
+								p |= (int)frameData[pos];
+							}
+							break;
+					}
+
+					thisRuleMatches = (p >= 0) ? (uint8_t)((p >= (int)rules[rn].v.port[0]) && (p <= (int)rules[rn].v.port[1])) : (uint8_t)0;
+				}
+				else if (etherType == ZT_ETHERTYPE_IPV6) {
+					unsigned int pos = 0, proto = 0;
+					if (_ipv6GetPayload(frameData, frameLen, pos, proto)) {
+						int p = -1;
+						switch (proto) {   // IP protocol number
+							// All these start with 16-bit source and destination port in that order
+							case 0x06:	 // TCP
+							case 0x11:	 // UDP
+							case 0x84:	 // SCTP
+							case 0x88:	 // UDPLite
+								if (frameLen > (pos + 4)) {
+									if (rt == ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE) {
+										pos += 2;
+									}
+									p = (int)frameData[pos++] << 8;
+									p |= (int)frameData[pos];
+								}
+								break;
+						}
+						thisRuleMatches = (p > 0) ? (uint8_t)((p >= (int)rules[rn].v.port[0]) && (p <= (int)rules[rn].v.port[1])) : (uint8_t)0;
+					}
+					else {
+						thisRuleMatches = hardNo;
+					}
+				}
+				else {
+					thisRuleMatches = hardNo;
+				}
+				break;
+			case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS: {
+				uint64_t cf = (inbound) ? ZT_RULE_PACKET_CHARACTERISTICS_INBOUND : 0ULL;
+				if (macDest.isMulticast()) {
+					cf |= ZT_RULE_PACKET_CHARACTERISTICS_MULTICAST;
+				}
+				if (macDest.isBroadcast()) {
+					cf |= ZT_RULE_PACKET_CHARACTERISTICS_BROADCAST;
+				}
+				if (ownershipVerificationMask == 1) {
+					ownershipVerificationMask = 0;
+					InetAddress src;
+					if ((etherType == ZT_ETHERTYPE_IPV4) && (frameLen >= 20)) {
+						src.set((const void*)(frameData + 12), 4, 0);
+					}
+					else if ((etherType == ZT_ETHERTYPE_IPV6) && (frameLen >= 40)) {
+						// IPv6 NDP requires special handling, since the src and dest IPs in the packet are empty or link-local.
+						if ((frameLen >= (40 + 8 + 16)) && (frameData[6] == 0x3a) && ((frameData[40] == 0x87) || (frameData[40] == 0x88))) {
+							if (frameData[40] == 0x87) {
+								// Neighbor solicitations contain no reliable source address, so we implement a small
+								// hack by considering them authenticated. Otherwise you would pretty much have to do
+								// this manually in the rule set for IPv6 to work at all.
+								ownershipVerificationMask |= ZT_RULE_PACKET_CHARACTERISTICS_SENDER_IP_AUTHENTICATED;
+							}
+							else {
+								// Neighbor advertisements on the other hand can absolutely be authenticated.
+								src.set((const void*)(frameData + 40 + 8), 16, 0);
+							}
+						}
+						else {
+							// Other IPv6 packets can be handled normally
+							src.set((const void*)(frameData + 8), 16, 0);
+						}
+					}
+					else if ((etherType == ZT_ETHERTYPE_ARP) && (frameLen >= 28)) {
+						src.set((const void*)(frameData + 14), 4, 0);
+					}
+					if (inbound) {
+						if (membership) {
+							if ((src) && (membership->hasCertificateOfOwnershipFor<InetAddress>(nconf, src))) {
+								ownershipVerificationMask |= ZT_RULE_PACKET_CHARACTERISTICS_SENDER_IP_AUTHENTICATED;
+							}
+							if (membership->hasCertificateOfOwnershipFor<MAC>(nconf, macSource)) {
+								ownershipVerificationMask |= ZT_RULE_PACKET_CHARACTERISTICS_SENDER_MAC_AUTHENTICATED;
+							}
+						}
+					}
+					else {
+						for (unsigned int i = 0; i < nconf.certificateOfOwnershipCount; ++i) {
+							if ((src) && (nconf.certificatesOfOwnership[i].owns(src))) {
+								ownershipVerificationMask |= ZT_RULE_PACKET_CHARACTERISTICS_SENDER_IP_AUTHENTICATED;
+							}
+							if (nconf.certificatesOfOwnership[i].owns(macSource)) {
+								ownershipVerificationMask |= ZT_RULE_PACKET_CHARACTERISTICS_SENDER_MAC_AUTHENTICATED;
+							}
+						}
+					}
+				}
+				cf |= ownershipVerificationMask;
+				if ((etherType == ZT_ETHERTYPE_IPV4) && (frameLen >= 20) && (frameData[9] == 0x06)) {
+					const unsigned int headerLen = 4 * (frameData[0] & 0xf);
+					cf |= (uint64_t)frameData[headerLen + 13];
+					cf |= (((uint64_t)(frameData[headerLen + 12] & 0x0f)) << 8);
+				}
+				else if (etherType == ZT_ETHERTYPE_IPV6) {
+					unsigned int pos = 0, proto = 0;
+					if (_ipv6GetPayload(frameData, frameLen, pos, proto)) {
+						if ((proto == 0x06) && (frameLen > (pos + 14))) {
+							cf |= (uint64_t)frameData[pos + 13];
+							cf |= (((uint64_t)(frameData[pos + 12] & 0x0f)) << 8);
+						}
+					}
+				}
+				thisRuleMatches = (uint8_t)((cf & rules[rn].v.characteristics) != 0);
+			} break;
+			case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE:
+				thisRuleMatches = (uint8_t)((frameLen >= (unsigned int)rules[rn].v.frameSize[0]) && (frameLen <= (unsigned int)rules[rn].v.frameSize[1]));
+				break;
+			case ZT_NETWORK_RULE_MATCH_RANDOM:
+				thisRuleMatches = (uint8_t)((uint32_t)(RR->node->prng() & 0xffffffffULL) <= rules[rn].v.randomProbability);
+				break;
+			case ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE:
+			case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND:
+			case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR:
+			case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR:
+			case ZT_NETWORK_RULE_MATCH_TAGS_EQUAL: {
+				const Tag* const localTag = std::lower_bound(&(nconf.tags[0]), &(nconf.tags[nconf.tagCount]), rules[rn].v.tag.id, Tag::IdComparePredicate());
+				if ((localTag != &(nconf.tags[nconf.tagCount])) && (localTag->id() == rules[rn].v.tag.id)) {
+					const Tag* const remoteTag = ((membership) ? membership->getTag(nconf, rules[rn].v.tag.id) : (const Tag*)0);
 #ifdef ZT_TRACE
-                    /*fprintf(stderr, "\tlocal tag [%u: %u] remote tag [%u: %u] match [%u]",
-                            !!localTag ? localTag->id() : 0,
-                            !!localTag ? localTag->value() : 0,
-                            !!remoteTag ? remoteTag->id() : 0,
-                            !!remoteTag ? remoteTag->value() : 0,
-                            thisRuleMatches);*/
+					/*fprintf(stderr, "\tlocal tag [%u: %u] remote tag [%u: %u] match [%u]",
+							!!localTag ? localTag->id() : 0,
+							!!localTag ? localTag->value() : 0,
+							!!remoteTag ? remoteTag->id() : 0,
+							!!remoteTag ? remoteTag->value() : 0,
+							thisRuleMatches);*/
 #endif
-                    if (remoteTag) {
-                        const uint32_t ltv = localTag->value();
-                        const uint32_t rtv = remoteTag->value();
-                        if (rt == ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE) {
-                            const uint32_t diff = (ltv > rtv) ? (ltv - rtv) : (rtv - ltv);
-                            thisRuleMatches = (uint8_t)(diff <= rules[rn].v.tag.value);
-                        }
-                        else if (rt == ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND) {
-                            thisRuleMatches = (uint8_t)((ltv & rtv) == rules[rn].v.tag.value);
-                        }
-                        else if (rt == ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR) {
-                            thisRuleMatches = (uint8_t)((ltv | rtv) == rules[rn].v.tag.value);
-                        }
-                        else if (rt == ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR) {
-                            thisRuleMatches = (uint8_t)((ltv ^ rtv) == rules[rn].v.tag.value);
-                        }
-                        else if (rt == ZT_NETWORK_RULE_MATCH_TAGS_EQUAL) {
-                            thisRuleMatches = (uint8_t)((ltv == rules[rn].v.tag.value) && (rtv == rules[rn].v.tag.value));
-                        }
-                        else {   // sanity check, can't really happen
-                            thisRuleMatches = hardNo;
-                        }
-                    }
-                    else {
-                        if ((inbound) && (! superAccept)) {
-                            thisRuleMatches = hardNo;
+					if (remoteTag) {
+						const uint32_t ltv = localTag->value();
+						const uint32_t rtv = remoteTag->value();
+						if (rt == ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE) {
+							const uint32_t diff = (ltv > rtv) ? (ltv - rtv) : (rtv - ltv);
+							thisRuleMatches = (uint8_t)(diff <= rules[rn].v.tag.value);
+						}
+						else if (rt == ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND) {
+							thisRuleMatches = (uint8_t)((ltv & rtv) == rules[rn].v.tag.value);
+						}
+						else if (rt == ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR) {
+							thisRuleMatches = (uint8_t)((ltv | rtv) == rules[rn].v.tag.value);
+						}
+						else if (rt == ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR) {
+							thisRuleMatches = (uint8_t)((ltv ^ rtv) == rules[rn].v.tag.value);
+						}
+						else if (rt == ZT_NETWORK_RULE_MATCH_TAGS_EQUAL) {
+							thisRuleMatches = (uint8_t)((ltv == rules[rn].v.tag.value) && (rtv == rules[rn].v.tag.value));
+						}
+						else {	 // sanity check, can't really happen
+							thisRuleMatches = hardNo;
+						}
+					}
+					else {
+						if ((inbound) && (! superAccept)) {
+							thisRuleMatches = hardNo;
 #ifdef ZT_TRACE
-                            // fprintf(stderr, "\tinbound ");
+							// fprintf(stderr, "\tinbound ");
 #endif
-                        }
-                        else {
-                            // Outbound side is not strict since if we have to match both tags and
-                            // we are sending a first packet to a recipient, we probably do not know
-                            // about their tags yet. They will filter on inbound and we will filter
-                            // once we get their tag. If we are a tee/redirect target we are also
-                            // not strict since we likely do not have these tags.
-                            skipDrop = 1;
-                            thisRuleMatches = hardYes;
+						}
+						else {
+							// Outbound side is not strict since if we have to match both tags and
+							// we are sending a first packet to a recipient, we probably do not know
+							// about their tags yet. They will filter on inbound and we will filter
+							// once we get their tag. If we are a tee/redirect target we are also
+							// not strict since we likely do not have these tags.
+							skipDrop = 1;
+							thisRuleMatches = hardYes;
 #ifdef ZT_TRACE
-                            // fprintf(stderr, "\toutbound ");
+							// fprintf(stderr, "\toutbound ");
 #endif
-                        }
-                    }
-                }
-                else {
-                    thisRuleMatches = hardNo;
-                }
-            } break;
-            case ZT_NETWORK_RULE_MATCH_TAG_SENDER:
-            case ZT_NETWORK_RULE_MATCH_TAG_RECEIVER: {
-                const Tag* const localTag = std::lower_bound(&(nconf.tags[0]), &(nconf.tags[nconf.tagCount]), rules[rn].v.tag.id, Tag::IdComparePredicate());
+						}
+					}
+				}
+				else {
+					thisRuleMatches = hardNo;
+				}
+			} break;
+			case ZT_NETWORK_RULE_MATCH_TAG_SENDER:
+			case ZT_NETWORK_RULE_MATCH_TAG_RECEIVER: {
+				const Tag* const localTag = std::lower_bound(&(nconf.tags[0]), &(nconf.tags[nconf.tagCount]), rules[rn].v.tag.id, Tag::IdComparePredicate());
 #ifdef ZT_TRACE
-                /*const Tag *const remoteTag = ((membership) ? membership->getTag(nconf,rules[rn].v.tag.id) : (const Tag *)0);
-                fprintf(stderr, "\tlocal tag [%u: %u] remote tag [%u: %u] match [%u]",
-                        !!localTag ? localTag->id() : 0,
-                        !!localTag ? localTag->value() : 0,
-                        !!remoteTag ? remoteTag->id() : 0,
-                        !!remoteTag ? remoteTag->value() : 0,
-                        thisRuleMatches);*/
+				/*const Tag *const remoteTag = ((membership) ? membership->getTag(nconf,rules[rn].v.tag.id) : (const Tag *)0);
+				fprintf(stderr, "\tlocal tag [%u: %u] remote tag [%u: %u] match [%u]",
+						!!localTag ? localTag->id() : 0,
+						!!localTag ? localTag->value() : 0,
+						!!remoteTag ? remoteTag->id() : 0,
+						!!remoteTag ? remoteTag->value() : 0,
+						thisRuleMatches);*/
 #endif
-                if (superAccept) {
-                    skipDrop = 1;
-                    thisRuleMatches = hardYes;
-                }
-                else if (((rt == ZT_NETWORK_RULE_MATCH_TAG_SENDER) && (inbound)) || ((rt == ZT_NETWORK_RULE_MATCH_TAG_RECEIVER) && (! inbound))) {
-                    const Tag* const remoteTag = ((membership) ? membership->getTag(nconf, rules[rn].v.tag.id) : (const Tag*)0);
-                    if (remoteTag) {
-                        thisRuleMatches = (uint8_t)(remoteTag->value() == rules[rn].v.tag.value);
-                    }
-                    else {
-                        if (rt == ZT_NETWORK_RULE_MATCH_TAG_RECEIVER) {
-                            // If we are checking the receiver and this is an outbound packet, we
-                            // can't be strict since we may not yet know the receiver's tag.
-                            skipDrop = 1;
-                            thisRuleMatches = hardYes;
-                        }
-                        else {
-                            thisRuleMatches = hardNo;
-                        }
-                    }
-                }
-                else {   // sender and outbound or receiver and inbound
-                    if ((localTag != &(nconf.tags[nconf.tagCount])) && (localTag->id() == rules[rn].v.tag.id)) {
-                        thisRuleMatches = (uint8_t)(localTag->value() == rules[rn].v.tag.value);
-                    }
-                    else {
-                        thisRuleMatches = hardNo;
-                    }
-                }
-            } break;
-            case ZT_NETWORK_RULE_MATCH_INTEGER_RANGE: {
-                uint64_t integer = 0;
-                const unsigned int bits = (rules[rn].v.intRange.format & 63) + 1;
-                const unsigned int bytes = ((bits + 8 - 1) / 8);   // integer ceiling of division by 8
-                if ((rules[rn].v.intRange.format & 0x80) == 0) {
-                    // Big-endian
-                    unsigned int idx = rules[rn].v.intRange.idx + (8 - bytes);
-                    const unsigned int eof = idx + bytes;
-                    if (eof <= frameLen) {
-                        while (idx < eof) {
-                            integer <<= 8;
-                            integer |= frameData[idx++];
-                        }
-                    }
-                    integer &= 0xffffffffffffffffULL >> (64 - bits);
-                }
-                else {
-                    // Little-endian
-                    unsigned int idx = rules[rn].v.intRange.idx;
-                    const unsigned int eof = idx + bytes;
-                    if (eof <= frameLen) {
-                        while (idx < eof) {
-                            integer >>= 8;
-                            integer |= ((uint64_t)frameData[idx++]) << 56;
-                        }
-                    }
-                    integer >>= (64 - bits);
-                }
-                thisRuleMatches = (uint8_t)((integer >= rules[rn].v.intRange.start) && (integer <= (rules[rn].v.intRange.start + (uint64_t)rules[rn].v.intRange.end)));
-            } break;
-
-            // The result of an unsupported MATCH is configurable at the network
-            // level via a flag.
-            default:
-                thisRuleMatches = (uint8_t)((nconf.flags & ZT_NETWORKCONFIG_FLAG_RULES_RESULT_OF_UNSUPPORTED_MATCH) != 0);
-                break;
-        }
-
-        rrl.log(rn, thisRuleMatches, thisSetMatches);
-
-        if ((rules[rn].t & 0x40)) {
-            thisSetMatches |= (thisRuleMatches ^ ((rules[rn].t >> 7) & 1));
-        }
-        else {
-            thisSetMatches &= (thisRuleMatches ^ ((rules[rn].t >> 7) & 1));
-        }
-    }
-
-    return DOZTFILTER_NO_MATCH;
+				if (superAccept) {
+					skipDrop = 1;
+					thisRuleMatches = hardYes;
+				}
+				else if (((rt == ZT_NETWORK_RULE_MATCH_TAG_SENDER) && (inbound)) || ((rt == ZT_NETWORK_RULE_MATCH_TAG_RECEIVER) && (! inbound))) {
+					const Tag* const remoteTag = ((membership) ? membership->getTag(nconf, rules[rn].v.tag.id) : (const Tag*)0);
+					if (remoteTag) {
+						thisRuleMatches = (uint8_t)(remoteTag->value() == rules[rn].v.tag.value);
+					}
+					else {
+						if (rt == ZT_NETWORK_RULE_MATCH_TAG_RECEIVER) {
+							// If we are checking the receiver and this is an outbound packet, we
+							// can't be strict since we may not yet know the receiver's tag.
+							skipDrop = 1;
+							thisRuleMatches = hardYes;
+						}
+						else {
+							thisRuleMatches = hardNo;
+						}
+					}
+				}
+				else {	 // sender and outbound or receiver and inbound
+					if ((localTag != &(nconf.tags[nconf.tagCount])) && (localTag->id() == rules[rn].v.tag.id)) {
+						thisRuleMatches = (uint8_t)(localTag->value() == rules[rn].v.tag.value);
+					}
+					else {
+						thisRuleMatches = hardNo;
+					}
+				}
+			} break;
+			case ZT_NETWORK_RULE_MATCH_INTEGER_RANGE: {
+				uint64_t integer = 0;
+				const unsigned int bits = (rules[rn].v.intRange.format & 63) + 1;
+				const unsigned int bytes = ((bits + 8 - 1) / 8);   // integer ceiling of division by 8
+				if ((rules[rn].v.intRange.format & 0x80) == 0) {
+					// Big-endian
+					unsigned int idx = rules[rn].v.intRange.idx + (8 - bytes);
+					const unsigned int eof = idx + bytes;
+					if (eof <= frameLen) {
+						while (idx < eof) {
+							integer <<= 8;
+							integer |= frameData[idx++];
+						}
+					}
+					integer &= 0xffffffffffffffffULL >> (64 - bits);
+				}
+				else {
+					// Little-endian
+					unsigned int idx = rules[rn].v.intRange.idx;
+					const unsigned int eof = idx + bytes;
+					if (eof <= frameLen) {
+						while (idx < eof) {
+							integer >>= 8;
+							integer |= ((uint64_t)frameData[idx++]) << 56;
+						}
+					}
+					integer >>= (64 - bits);
+				}
+				thisRuleMatches = (uint8_t)((integer >= rules[rn].v.intRange.start) && (integer <= (rules[rn].v.intRange.start + (uint64_t)rules[rn].v.intRange.end)));
+			} break;
+
+			// The result of an unsupported MATCH is configurable at the network
+			// level via a flag.
+			default:
+				thisRuleMatches = (uint8_t)((nconf.flags & ZT_NETWORKCONFIG_FLAG_RULES_RESULT_OF_UNSUPPORTED_MATCH) != 0);
+				break;
+		}
+
+		rrl.log(rn, thisRuleMatches, thisSetMatches);
+
+		if ((rules[rn].t & 0x40)) {
+			thisSetMatches |= (thisRuleMatches ^ ((rules[rn].t >> 7) & 1));
+		}
+		else {
+			thisSetMatches &= (thisRuleMatches ^ ((rules[rn].t >> 7) & 1));
+		}
+	}
+
+	return DOZTFILTER_NO_MATCH;
 }
 
-}   // anonymous namespace
+}	// anonymous namespace
 
 const ZeroTier::MulticastGroup Network::BROADCAST(ZeroTier::MAC(0xffffffffffffULL), 0);
 
 Network::Network(const RuntimeEnvironment* renv, void* tPtr, uint64_t nwid, void* uptr, const NetworkConfig* nconf)
-    : RR(renv)
-    , _uPtr(uptr)
-    , _id(nwid)
-    , _nwidStr(OSUtils::networkIDStr(nwid))
-    , _lastAnnouncedMulticastGroupsUpstream(0)
-    , _mac(renv->identity.address(), nwid)
-    , _portInitialized(false)
-    , _lastConfigUpdate(0)
-    , _destroyed(false)
-    , _netconfFailure(NETCONF_FAILURE_NONE)
-    , _portError(0)
-    , _num_multicast_groups { Metrics::network_num_multicast_groups.Add({ { "network_id", _nwidStr } }) }
-    , _incoming_packets_accepted { Metrics::network_packets.Add({ { "direction", "rx" }, { "network_id", _nwidStr }, { "accepted", "yes" } }) }
-    , _incoming_packets_dropped { Metrics::network_packets.Add({ { "direction", "rx" }, { "network_id", _nwidStr }, { "accepted", "no" } }) }
-    , _outgoing_packets_accepted { Metrics::network_packets.Add({ { "direction", "tx" }, { "network_id", _nwidStr }, { "accepted", "yes" } }) }
-    , _outgoing_packets_dropped { Metrics::network_packets.Add({ { "direction", "tx" }, { "network_id", _nwidStr }, { "accepted", "no" } }) }
+	: RR(renv)
+	, _uPtr(uptr)
+	, _id(nwid)
+	, _nwidStr(OSUtils::networkIDStr(nwid))
+	, _lastAnnouncedMulticastGroupsUpstream(0)
+	, _mac(renv->identity.address(), nwid)
+	, _portInitialized(false)
+	, _lastConfigUpdate(0)
+	, _destroyed(false)
+	, _netconfFailure(NETCONF_FAILURE_NONE)
+	, _portError(0)
+	, _num_multicast_groups { Metrics::network_num_multicast_groups.Add({ { "network_id", _nwidStr } }) }
+	, _incoming_packets_accepted { Metrics::network_packets.Add({ { "direction", "rx" }, { "network_id", _nwidStr }, { "accepted", "yes" } }) }
+	, _incoming_packets_dropped { Metrics::network_packets.Add({ { "direction", "rx" }, { "network_id", _nwidStr }, { "accepted", "no" } }) }
+	, _outgoing_packets_accepted { Metrics::network_packets.Add({ { "direction", "tx" }, { "network_id", _nwidStr }, { "accepted", "yes" } }) }
+	, _outgoing_packets_dropped { Metrics::network_packets.Add({ { "direction", "tx" }, { "network_id", _nwidStr }, { "accepted", "no" } }) }
 {
-    for (int i = 0; i < ZT_NETWORK_MAX_INCOMING_UPDATES; ++i) {
-        _incomingConfigChunks[i].ts = 0;
-    }
-
-    if (nconf) {
-        this->setConfiguration(tPtr, *nconf, false);
-        _lastConfigUpdate = 0;   // still want to re-request since it's likely outdated
-    }
-    else {
-        uint64_t tmp[2];
-        tmp[0] = nwid;
-        tmp[1] = 0;
-
-        bool got = false;
-        Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>* dict = new Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>();
-        try {
-            int n = RR->node->stateObjectGet(tPtr, ZT_STATE_OBJECT_NETWORK_CONFIG, tmp, dict->unsafeData(), ZT_NETWORKCONFIG_DICT_CAPACITY - 1);
-            if (n > 1) {
-                NetworkConfig* nconf = new NetworkConfig();
-                try {
-                    if (nconf->fromDictionary(*dict)) {
-                        this->setConfiguration(tPtr, *nconf, false);
-                        _lastConfigUpdate = 0;   // still want to re-request an update since it's likely outdated
-                        got = true;
-                    }
-                }
-                catch (...) {
-                }
-                delete nconf;
-            }
-        }
-        catch (...) {
-        }
-        delete dict;
-
-        if (! got) {
-            RR->node->stateObjectPut(tPtr, ZT_STATE_OBJECT_NETWORK_CONFIG, tmp, "\n", 1);
-        }
-    }
-
-    if (! _portInitialized) {
-        ZT_VirtualNetworkConfig ctmp;
-        memset(&ctmp, 0, sizeof(ZT_VirtualNetworkConfig));
-        _externalConfig(&ctmp);
-        _portError = RR->node->configureVirtualNetworkPort(tPtr, _id, &_uPtr, ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP, &ctmp);
-        _portInitialized = true;
-    }
-
-    Metrics::network_num_joined++;
+	for (int i = 0; i < ZT_NETWORK_MAX_INCOMING_UPDATES; ++i) {
+		_incomingConfigChunks[i].ts = 0;
+	}
+
+	if (nconf) {
+		this->setConfiguration(tPtr, *nconf, false);
+		_lastConfigUpdate = 0;	 // still want to re-request since it's likely outdated
+	}
+	else {
+		uint64_t tmp[2];
+		tmp[0] = nwid;
+		tmp[1] = 0;
+
+		bool got = false;
+		Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>* dict = new Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>();
+		try {
+			int n = RR->node->stateObjectGet(tPtr, ZT_STATE_OBJECT_NETWORK_CONFIG, tmp, dict->unsafeData(), ZT_NETWORKCONFIG_DICT_CAPACITY - 1);
+			if (n > 1) {
+				NetworkConfig* nconf = new NetworkConfig();
+				try {
+					if (nconf->fromDictionary(*dict)) {
+						this->setConfiguration(tPtr, *nconf, false);
+						_lastConfigUpdate = 0;	 // still want to re-request an update since it's likely outdated
+						got = true;
+					}
+				}
+				catch (...) {
+				}
+				delete nconf;
+			}
+		}
+		catch (...) {
+		}
+		delete dict;
+
+		if (! got) {
+			RR->node->stateObjectPut(tPtr, ZT_STATE_OBJECT_NETWORK_CONFIG, tmp, "\n", 1);
+		}
+	}
+
+	if (! _portInitialized) {
+		ZT_VirtualNetworkConfig ctmp;
+		memset(&ctmp, 0, sizeof(ZT_VirtualNetworkConfig));
+		_externalConfig(&ctmp);
+		_portError = RR->node->configureVirtualNetworkPort(tPtr, _id, &_uPtr, ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP, &ctmp);
+		_portInitialized = true;
+	}
+
+	Metrics::network_num_joined++;
 }
 
 Network::~Network()
 {
-    ZT_VirtualNetworkConfig ctmp;
-    _externalConfig(&ctmp);
-    Metrics::network_num_joined--;
-    if (_destroyed) {
-        // This is done in Node::leave() so we can pass tPtr properly
-        // RR->node->configureVirtualNetworkPort((void *)0,_id,&_uPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY,&ctmp);
-    }
-    else {
-        RR->node->configureVirtualNetworkPort((void*)0, _id, &_uPtr, ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN, &ctmp);
-    }
+	ZT_VirtualNetworkConfig ctmp;
+	_externalConfig(&ctmp);
+	Metrics::network_num_joined--;
+	if (_destroyed) {
+		// This is done in Node::leave() so we can pass tPtr properly
+		// RR->node->configureVirtualNetworkPort((void *)0,_id,&_uPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY,&ctmp);
+	}
+	else {
+		RR->node->configureVirtualNetworkPort((void*)0, _id, &_uPtr, ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN, &ctmp);
+	}
 }
 
 bool Network::filterOutgoingPacket(
-    void* tPtr,
-    const bool noTee,
-    const Address& ztSource,
-    const Address& ztDest,
-    const MAC& macSource,
-    const MAC& macDest,
-    const uint8_t* frameData,
-    const unsigned int frameLen,
-    const unsigned int etherType,
-    const unsigned int vlanId,
-    uint8_t& qosBucket)
+	void* tPtr,
+	const bool noTee,
+	const Address& ztSource,
+	const Address& ztDest,
+	const MAC& macSource,
+	const MAC& macDest,
+	const uint8_t* frameData,
+	const unsigned int frameLen,
+	const unsigned int etherType,
+	const unsigned int vlanId,
+	uint8_t& qosBucket)
 {
-    Address ztFinalDest(ztDest);
-    int localCapabilityIndex = -1;
-    int accept = 0;
-    Trace::RuleResultLog rrl, crrl;
-    Address cc;
-    unsigned int ccLength = 0;
-    bool ccWatch = false;
-
-    Mutex::Lock _l(_lock);
-
-    Membership* const membership = (ztDest) ? _memberships.get(ztDest) : (Membership*)0;
-
-    switch (_doZtFilter(RR, rrl, _config, membership, false, ztSource, ztFinalDest, macSource, macDest, frameData, frameLen, etherType, vlanId, _config.rules, _config.ruleCount, cc, ccLength, ccWatch, qosBucket)) {
-        case DOZTFILTER_NO_MATCH: {
-            for (unsigned int c = 0; c < _config.capabilityCount; ++c) {
-                ztFinalDest = ztDest;   // sanity check, shouldn't be possible if there was no match
-                Address cc2;
-                unsigned int ccLength2 = 0;
-                bool ccWatch2 = false;
-                switch (_doZtFilter(
-                    RR,
-                    crrl,
-                    _config,
-                    membership,
-                    false,
-                    ztSource,
-                    ztFinalDest,
-                    macSource,
-                    macDest,
-                    frameData,
-                    frameLen,
-                    etherType,
-                    vlanId,
-                    _config.capabilities[c].rules(),
-                    _config.capabilities[c].ruleCount(),
-                    cc2,
-                    ccLength2,
-                    ccWatch2,
-                    qosBucket)) {
-                    case DOZTFILTER_NO_MATCH:
-                    case DOZTFILTER_DROP:   // explicit DROP in a capability just terminates its evaluation and is an anti-pattern
-                        break;
-
-                    case DOZTFILTER_REDIRECT:   // interpreted as ACCEPT but ztFinalDest will have been changed in _doZtFilter()
-                    case DOZTFILTER_ACCEPT:
-                    case DOZTFILTER_SUPER_ACCEPT:   // no difference in behavior on outbound side in capabilities
-                        localCapabilityIndex = (int)c;
-                        accept = 1;
-
-                        if ((! noTee) && (cc2)) {
-                            Packet outp(cc2, RR->identity.address(), Packet::VERB_EXT_FRAME);
-                            outp.append(_id);
-                            outp.append((uint8_t)(ccWatch2 ? 0x16 : 0x02));
-                            macDest.appendTo(outp);
-                            macSource.appendTo(outp);
-                            outp.append((uint16_t)etherType);
-                            outp.append(frameData, ccLength2);
-                            outp.compress();
-                            RR->sw->send(tPtr, outp, true);
-                        }
-
-                        break;
-                }
-                if (accept) {
-                    break;
-                }
-            }
-        } break;
-
-        case DOZTFILTER_DROP:
-            if (_config.remoteTraceTarget) {
-                RR->t->networkFilter(tPtr, *this, rrl, (Trace::RuleResultLog*)0, (Capability*)0, ztSource, ztDest, macSource, macDest, frameData, frameLen, etherType, vlanId, noTee, false, 0);
-            }
-            return false;
-
-        case DOZTFILTER_REDIRECT:   // interpreted as ACCEPT but ztFinalDest will have been changed in _doZtFilter()
-        case DOZTFILTER_ACCEPT:
-            accept = 1;
-            break;
-
-        case DOZTFILTER_SUPER_ACCEPT:
-            accept = 2;
-            break;
-    }
-
-    if (accept) {
-        _outgoing_packets_accepted++;
-        if ((! noTee) && (cc)) {
-            Packet outp(cc, RR->identity.address(), Packet::VERB_EXT_FRAME);
-            outp.append(_id);
-            outp.append((uint8_t)(ccWatch ? 0x16 : 0x02));
-            macDest.appendTo(outp);
-            macSource.appendTo(outp);
-            outp.append((uint16_t)etherType);
-            outp.append(frameData, ccLength);
-            outp.compress();
-            RR->sw->send(tPtr, outp, true);
-        }
-
-        if ((ztDest != ztFinalDest) && (ztFinalDest)) {
-            Packet outp(ztFinalDest, RR->identity.address(), Packet::VERB_EXT_FRAME);
-            outp.append(_id);
-            outp.append((uint8_t)0x04);
-            macDest.appendTo(outp);
-            macSource.appendTo(outp);
-            outp.append((uint16_t)etherType);
-            outp.append(frameData, frameLen);
-            outp.compress();
-            RR->sw->send(tPtr, outp, true);
-
-            if (_config.remoteTraceTarget) {
-                RR->t->networkFilter(
-                    tPtr,
-                    *this,
-                    rrl,
-                    (localCapabilityIndex >= 0) ? &crrl : (Trace::RuleResultLog*)0,
-                    (localCapabilityIndex >= 0) ? &(_config.capabilities[localCapabilityIndex]) : (Capability*)0,
-                    ztSource,
-                    ztDest,
-                    macSource,
-                    macDest,
-                    frameData,
-                    frameLen,
-                    etherType,
-                    vlanId,
-                    noTee,
-                    false,
-                    0);
-            }
-            return false;   // DROP locally, since we redirected
-        }
-        else {
-            if (_config.remoteTraceTarget) {
-                RR->t->networkFilter(
-                    tPtr,
-                    *this,
-                    rrl,
-                    (localCapabilityIndex >= 0) ? &crrl : (Trace::RuleResultLog*)0,
-                    (localCapabilityIndex >= 0) ? &(_config.capabilities[localCapabilityIndex]) : (Capability*)0,
-                    ztSource,
-                    ztDest,
-                    macSource,
-                    macDest,
-                    frameData,
-                    frameLen,
-                    etherType,
-                    vlanId,
-                    noTee,
-                    false,
-                    1);
-            }
-            return true;
-        }
-    }
-    else {
-        _outgoing_packets_dropped++;
-        if (_config.remoteTraceTarget) {
-            RR->t->networkFilter(
-                tPtr,
-                *this,
-                rrl,
-                (localCapabilityIndex >= 0) ? &crrl : (Trace::RuleResultLog*)0,
-                (localCapabilityIndex >= 0) ? &(_config.capabilities[localCapabilityIndex]) : (Capability*)0,
-                ztSource,
-                ztDest,
-                macSource,
-                macDest,
-                frameData,
-                frameLen,
-                etherType,
-                vlanId,
-                noTee,
-                false,
-                0);
-        }
-        return false;
-    }
+	Address ztFinalDest(ztDest);
+	int localCapabilityIndex = -1;
+	int accept = 0;
+	Trace::RuleResultLog rrl, crrl;
+	Address cc;
+	unsigned int ccLength = 0;
+	bool ccWatch = false;
+
+	Mutex::Lock _l(_lock);
+
+	Membership* const membership = (ztDest) ? _memberships.get(ztDest) : (Membership*)0;
+
+	switch (_doZtFilter(RR, rrl, _config, membership, false, ztSource, ztFinalDest, macSource, macDest, frameData, frameLen, etherType, vlanId, _config.rules, _config.ruleCount, cc, ccLength, ccWatch, qosBucket)) {
+		case DOZTFILTER_NO_MATCH: {
+			for (unsigned int c = 0; c < _config.capabilityCount; ++c) {
+				ztFinalDest = ztDest;	// sanity check, shouldn't be possible if there was no match
+				Address cc2;
+				unsigned int ccLength2 = 0;
+				bool ccWatch2 = false;
+				switch (_doZtFilter(
+					RR,
+					crrl,
+					_config,
+					membership,
+					false,
+					ztSource,
+					ztFinalDest,
+					macSource,
+					macDest,
+					frameData,
+					frameLen,
+					etherType,
+					vlanId,
+					_config.capabilities[c].rules(),
+					_config.capabilities[c].ruleCount(),
+					cc2,
+					ccLength2,
+					ccWatch2,
+					qosBucket)) {
+					case DOZTFILTER_NO_MATCH:
+					case DOZTFILTER_DROP:	// explicit DROP in a capability just terminates its evaluation and is an anti-pattern
+						break;
+
+					case DOZTFILTER_REDIRECT:	// interpreted as ACCEPT but ztFinalDest will have been changed in _doZtFilter()
+					case DOZTFILTER_ACCEPT:
+					case DOZTFILTER_SUPER_ACCEPT:	// no difference in behavior on outbound side in capabilities
+						localCapabilityIndex = (int)c;
+						accept = 1;
+
+						if ((! noTee) && (cc2)) {
+							Packet outp(cc2, RR->identity.address(), Packet::VERB_EXT_FRAME);
+							outp.append(_id);
+							outp.append((uint8_t)(ccWatch2 ? 0x16 : 0x02));
+							macDest.appendTo(outp);
+							macSource.appendTo(outp);
+							outp.append((uint16_t)etherType);
+							outp.append(frameData, ccLength2);
+							outp.compress();
+							RR->sw->send(tPtr, outp, true);
+						}
+
+						break;
+				}
+				if (accept) {
+					break;
+				}
+			}
+		} break;
+
+		case DOZTFILTER_DROP:
+			if (_config.remoteTraceTarget) {
+				RR->t->networkFilter(tPtr, *this, rrl, (Trace::RuleResultLog*)0, (Capability*)0, ztSource, ztDest, macSource, macDest, frameData, frameLen, etherType, vlanId, noTee, false, 0);
+			}
+			return false;
+
+		case DOZTFILTER_REDIRECT:	// interpreted as ACCEPT but ztFinalDest will have been changed in _doZtFilter()
+		case DOZTFILTER_ACCEPT:
+			accept = 1;
+			break;
+
+		case DOZTFILTER_SUPER_ACCEPT:
+			accept = 2;
+			break;
+	}
+
+	if (accept) {
+		_outgoing_packets_accepted++;
+		if ((! noTee) && (cc)) {
+			Packet outp(cc, RR->identity.address(), Packet::VERB_EXT_FRAME);
+			outp.append(_id);
+			outp.append((uint8_t)(ccWatch ? 0x16 : 0x02));
+			macDest.appendTo(outp);
+			macSource.appendTo(outp);
+			outp.append((uint16_t)etherType);
+			outp.append(frameData, ccLength);
+			outp.compress();
+			RR->sw->send(tPtr, outp, true);
+		}
+
+		if ((ztDest != ztFinalDest) && (ztFinalDest)) {
+			Packet outp(ztFinalDest, RR->identity.address(), Packet::VERB_EXT_FRAME);
+			outp.append(_id);
+			outp.append((uint8_t)0x04);
+			macDest.appendTo(outp);
+			macSource.appendTo(outp);
+			outp.append((uint16_t)etherType);
+			outp.append(frameData, frameLen);
+			outp.compress();
+			RR->sw->send(tPtr, outp, true);
+
+			if (_config.remoteTraceTarget) {
+				RR->t->networkFilter(
+					tPtr,
+					*this,
+					rrl,
+					(localCapabilityIndex >= 0) ? &crrl : (Trace::RuleResultLog*)0,
+					(localCapabilityIndex >= 0) ? &(_config.capabilities[localCapabilityIndex]) : (Capability*)0,
+					ztSource,
+					ztDest,
+					macSource,
+					macDest,
+					frameData,
+					frameLen,
+					etherType,
+					vlanId,
+					noTee,
+					false,
+					0);
+			}
+			return false;	// DROP locally, since we redirected
+		}
+		else {
+			if (_config.remoteTraceTarget) {
+				RR->t->networkFilter(
+					tPtr,
+					*this,
+					rrl,
+					(localCapabilityIndex >= 0) ? &crrl : (Trace::RuleResultLog*)0,
+					(localCapabilityIndex >= 0) ? &(_config.capabilities[localCapabilityIndex]) : (Capability*)0,
+					ztSource,
+					ztDest,
+					macSource,
+					macDest,
+					frameData,
+					frameLen,
+					etherType,
+					vlanId,
+					noTee,
+					false,
+					1);
+			}
+			return true;
+		}
+	}
+	else {
+		_outgoing_packets_dropped++;
+		if (_config.remoteTraceTarget) {
+			RR->t->networkFilter(
+				tPtr,
+				*this,
+				rrl,
+				(localCapabilityIndex >= 0) ? &crrl : (Trace::RuleResultLog*)0,
+				(localCapabilityIndex >= 0) ? &(_config.capabilities[localCapabilityIndex]) : (Capability*)0,
+				ztSource,
+				ztDest,
+				macSource,
+				macDest,
+				frameData,
+				frameLen,
+				etherType,
+				vlanId,
+				noTee,
+				false,
+				0);
+		}
+		return false;
+	}
 }
 
 int Network::filterIncomingPacket(
-    void* tPtr,
-    const SharedPtr<Peer>& sourcePeer,
-    const Address& ztDest,
-    const MAC& macSource,
-    const MAC& macDest,
-    const uint8_t* frameData,
-    const unsigned int frameLen,
-    const unsigned int etherType,
-    const unsigned int vlanId)
+	void* tPtr,
+	const SharedPtr<Peer>& sourcePeer,
+	const Address& ztDest,
+	const MAC& macSource,
+	const MAC& macDest,
+	const uint8_t* frameData,
+	const unsigned int frameLen,
+	const unsigned int etherType,
+	const unsigned int vlanId)
 {
-    Address ztFinalDest(ztDest);
-    Trace::RuleResultLog rrl, crrl;
-    int accept = 0;
-    Address cc;
-    unsigned int ccLength = 0;
-    bool ccWatch = false;
-    const Capability* c = (Capability*)0;
-
-    uint8_t qosBucket = 255;   // For incoming packets this is a dummy value
-
-    Mutex::Lock _l(_lock);
-
-    Membership& membership = _membership(sourcePeer->address());
-
-    switch (_doZtFilter(RR, rrl, _config, &membership, true, sourcePeer->address(), ztFinalDest, macSource, macDest, frameData, frameLen, etherType, vlanId, _config.rules, _config.ruleCount, cc, ccLength, ccWatch, qosBucket)) {
-        case DOZTFILTER_NO_MATCH: {
-            Membership::CapabilityIterator mci(membership, _config);
-            while ((c = mci.next())) {
-                ztFinalDest = ztDest;   // sanity check, should be unmodified if there was no match
-                Address cc2;
-                unsigned int ccLength2 = 0;
-                bool ccWatch2 = false;
-                switch (_doZtFilter(RR, crrl, _config, &membership, true, sourcePeer->address(), ztFinalDest, macSource, macDest, frameData, frameLen, etherType, vlanId, c->rules(), c->ruleCount(), cc2, ccLength2, ccWatch2, qosBucket)) {
-                    case DOZTFILTER_NO_MATCH:
-                    case DOZTFILTER_DROP:   // explicit DROP in a capability just terminates its evaluation and is an anti-pattern
-                        break;
-                    case DOZTFILTER_REDIRECT:   // interpreted as ACCEPT but ztDest will have been changed in _doZtFilter()
-                    case DOZTFILTER_ACCEPT:
-                        accept = 1;   // ACCEPT
-                        break;
-                    case DOZTFILTER_SUPER_ACCEPT:
-                        accept = 2;   // super-ACCEPT
-                        break;
-                }
-
-                if (accept) {
-                    if (cc2) {
-                        Packet outp(cc2, RR->identity.address(), Packet::VERB_EXT_FRAME);
-                        outp.append(_id);
-                        outp.append((uint8_t)(ccWatch2 ? 0x1c : 0x08));
-                        macDest.appendTo(outp);
-                        macSource.appendTo(outp);
-                        outp.append((uint16_t)etherType);
-                        outp.append(frameData, ccLength2);
-                        outp.compress();
-                        RR->sw->send(tPtr, outp, true);
-                    }
-                    break;
-                }
-            }
-        } break;
-
-        case DOZTFILTER_DROP:
-            if (_config.remoteTraceTarget) {
-                RR->t->networkFilter(tPtr, *this, rrl, (Trace::RuleResultLog*)0, (Capability*)0, sourcePeer->address(), ztDest, macSource, macDest, frameData, frameLen, etherType, vlanId, false, true, 0);
-            }
-            return 0;   // DROP
-
-        case DOZTFILTER_REDIRECT:   // interpreted as ACCEPT but ztFinalDest will have been changed in _doZtFilter()
-        case DOZTFILTER_ACCEPT:
-            accept = 1;   // ACCEPT
-            break;
-        case DOZTFILTER_SUPER_ACCEPT:
-            accept = 2;   // super-ACCEPT
-            break;
-    }
-
-    if (accept) {
-        _incoming_packets_accepted++;
-        if (cc) {
-            Packet outp(cc, RR->identity.address(), Packet::VERB_EXT_FRAME);
-            outp.append(_id);
-            outp.append((uint8_t)(ccWatch ? 0x1c : 0x08));
-            macDest.appendTo(outp);
-            macSource.appendTo(outp);
-            outp.append((uint16_t)etherType);
-            outp.append(frameData, ccLength);
-            outp.compress();
-            RR->sw->send(tPtr, outp, true);
-        }
-
-        if ((ztDest != ztFinalDest) && (ztFinalDest)) {
-            Packet outp(ztFinalDest, RR->identity.address(), Packet::VERB_EXT_FRAME);
-            outp.append(_id);
-            outp.append((uint8_t)0x0a);
-            macDest.appendTo(outp);
-            macSource.appendTo(outp);
-            outp.append((uint16_t)etherType);
-            outp.append(frameData, frameLen);
-            outp.compress();
-            RR->sw->send(tPtr, outp, true);
-
-            if (_config.remoteTraceTarget) {
-                RR->t->networkFilter(tPtr, *this, rrl, (c) ? &crrl : (Trace::RuleResultLog*)0, c, sourcePeer->address(), ztDest, macSource, macDest, frameData, frameLen, etherType, vlanId, false, true, 0);
-            }
-            return 0;   // DROP locally, since we redirected
-        }
-    }
-    else {
-        _incoming_packets_dropped++;
-    }
-
-    if (_config.remoteTraceTarget) {
-        RR->t->networkFilter(tPtr, *this, rrl, (c) ? &crrl : (Trace::RuleResultLog*)0, c, sourcePeer->address(), ztDest, macSource, macDest, frameData, frameLen, etherType, vlanId, false, true, accept);
-    }
-    return accept;
+	Address ztFinalDest(ztDest);
+	Trace::RuleResultLog rrl, crrl;
+	int accept = 0;
+	Address cc;
+	unsigned int ccLength = 0;
+	bool ccWatch = false;
+	const Capability* c = (Capability*)0;
+
+	uint8_t qosBucket = 255;   // For incoming packets this is a dummy value
+
+	Mutex::Lock _l(_lock);
+
+	Membership& membership = _membership(sourcePeer->address());
+
+	switch (_doZtFilter(RR, rrl, _config, &membership, true, sourcePeer->address(), ztFinalDest, macSource, macDest, frameData, frameLen, etherType, vlanId, _config.rules, _config.ruleCount, cc, ccLength, ccWatch, qosBucket)) {
+		case DOZTFILTER_NO_MATCH: {
+			Membership::CapabilityIterator mci(membership, _config);
+			while ((c = mci.next())) {
+				ztFinalDest = ztDest;	// sanity check, should be unmodified if there was no match
+				Address cc2;
+				unsigned int ccLength2 = 0;
+				bool ccWatch2 = false;
+				switch (_doZtFilter(RR, crrl, _config, &membership, true, sourcePeer->address(), ztFinalDest, macSource, macDest, frameData, frameLen, etherType, vlanId, c->rules(), c->ruleCount(), cc2, ccLength2, ccWatch2, qosBucket)) {
+					case DOZTFILTER_NO_MATCH:
+					case DOZTFILTER_DROP:	// explicit DROP in a capability just terminates its evaluation and is an anti-pattern
+						break;
+					case DOZTFILTER_REDIRECT:	// interpreted as ACCEPT but ztDest will have been changed in _doZtFilter()
+					case DOZTFILTER_ACCEPT:
+						accept = 1;	  // ACCEPT
+						break;
+					case DOZTFILTER_SUPER_ACCEPT:
+						accept = 2;	  // super-ACCEPT
+						break;
+				}
+
+				if (accept) {
+					if (cc2) {
+						Packet outp(cc2, RR->identity.address(), Packet::VERB_EXT_FRAME);
+						outp.append(_id);
+						outp.append((uint8_t)(ccWatch2 ? 0x1c : 0x08));
+						macDest.appendTo(outp);
+						macSource.appendTo(outp);
+						outp.append((uint16_t)etherType);
+						outp.append(frameData, ccLength2);
+						outp.compress();
+						RR->sw->send(tPtr, outp, true);
+					}
+					break;
+				}
+			}
+		} break;
+
+		case DOZTFILTER_DROP:
+			if (_config.remoteTraceTarget) {
+				RR->t->networkFilter(tPtr, *this, rrl, (Trace::RuleResultLog*)0, (Capability*)0, sourcePeer->address(), ztDest, macSource, macDest, frameData, frameLen, etherType, vlanId, false, true, 0);
+			}
+			return 0;	// DROP
+
+		case DOZTFILTER_REDIRECT:	// interpreted as ACCEPT but ztFinalDest will have been changed in _doZtFilter()
+		case DOZTFILTER_ACCEPT:
+			accept = 1;	  // ACCEPT
+			break;
+		case DOZTFILTER_SUPER_ACCEPT:
+			accept = 2;	  // super-ACCEPT
+			break;
+	}
+
+	if (accept) {
+		_incoming_packets_accepted++;
+		if (cc) {
+			Packet outp(cc, RR->identity.address(), Packet::VERB_EXT_FRAME);
+			outp.append(_id);
+			outp.append((uint8_t)(ccWatch ? 0x1c : 0x08));
+			macDest.appendTo(outp);
+			macSource.appendTo(outp);
+			outp.append((uint16_t)etherType);
+			outp.append(frameData, ccLength);
+			outp.compress();
+			RR->sw->send(tPtr, outp, true);
+		}
+
+		if ((ztDest != ztFinalDest) && (ztFinalDest)) {
+			Packet outp(ztFinalDest, RR->identity.address(), Packet::VERB_EXT_FRAME);
+			outp.append(_id);
+			outp.append((uint8_t)0x0a);
+			macDest.appendTo(outp);
+			macSource.appendTo(outp);
+			outp.append((uint16_t)etherType);
+			outp.append(frameData, frameLen);
+			outp.compress();
+			RR->sw->send(tPtr, outp, true);
+
+			if (_config.remoteTraceTarget) {
+				RR->t->networkFilter(tPtr, *this, rrl, (c) ? &crrl : (Trace::RuleResultLog*)0, c, sourcePeer->address(), ztDest, macSource, macDest, frameData, frameLen, etherType, vlanId, false, true, 0);
+			}
+			return 0;	// DROP locally, since we redirected
+		}
+	}
+	else {
+		_incoming_packets_dropped++;
+	}
+
+	if (_config.remoteTraceTarget) {
+		RR->t->networkFilter(tPtr, *this, rrl, (c) ? &crrl : (Trace::RuleResultLog*)0, c, sourcePeer->address(), ztDest, macSource, macDest, frameData, frameLen, etherType, vlanId, false, true, accept);
+	}
+	return accept;
 }
 
 bool Network::subscribedToMulticastGroup(const MulticastGroup& mg, bool includeBridgedGroups) const
 {
-    Mutex::Lock _l(_lock);
-    if (std::binary_search(_myMulticastGroups.begin(), _myMulticastGroups.end(), mg)) {
-        return true;
-    }
-    else if (includeBridgedGroups) {
-        return _multicastGroupsBehindMe.contains(mg);
-    }
-    return false;
+	Mutex::Lock _l(_lock);
+	if (std::binary_search(_myMulticastGroups.begin(), _myMulticastGroups.end(), mg)) {
+		return true;
+	}
+	else if (includeBridgedGroups) {
+		return _multicastGroupsBehindMe.contains(mg);
+	}
+	return false;
 }
 
 void Network::multicastSubscribe(void* tPtr, const MulticastGroup& mg)
 {
-    Mutex::Lock _l(_lock);
-    if (! std::binary_search(_myMulticastGroups.begin(), _myMulticastGroups.end(), mg)) {
-        _myMulticastGroups.insert(std::upper_bound(_myMulticastGroups.begin(), _myMulticastGroups.end(), mg), mg);
-        _sendUpdatesToMembers(tPtr, &mg);
-        _num_multicast_groups++;
-    }
+	Mutex::Lock _l(_lock);
+	if (! std::binary_search(_myMulticastGroups.begin(), _myMulticastGroups.end(), mg)) {
+		_myMulticastGroups.insert(std::upper_bound(_myMulticastGroups.begin(), _myMulticastGroups.end(), mg), mg);
+		_sendUpdatesToMembers(tPtr, &mg);
+		_num_multicast_groups++;
+	}
 }
 
 void Network::multicastUnsubscribe(const MulticastGroup& mg)
 {
-    Mutex::Lock _l(_lock);
-    std::vector<MulticastGroup>::iterator i(std::lower_bound(_myMulticastGroups.begin(), _myMulticastGroups.end(), mg));
-    if ((i != _myMulticastGroups.end()) && (*i == mg)) {
-        _myMulticastGroups.erase(i);
-        _num_multicast_groups--;
-    }
+	Mutex::Lock _l(_lock);
+	std::vector<MulticastGroup>::iterator i(std::lower_bound(_myMulticastGroups.begin(), _myMulticastGroups.end(), mg));
+	if ((i != _myMulticastGroups.end()) && (*i == mg)) {
+		_myMulticastGroups.erase(i);
+		_num_multicast_groups--;
+	}
 }
 
 uint64_t Network::handleConfigChunk(void* tPtr, const uint64_t packetId, const Address& source, const Buffer<ZT_PROTO_MAX_PACKET_LENGTH>& chunk, unsigned int ptr)
 {
-    if (_destroyed) {
-        return 0;
-    }
-
-    const unsigned int start = ptr;
-
-    ptr += 8;   // skip network ID, which is already obviously known
-    const unsigned int chunkLen = chunk.at<uint16_t>(ptr);
-    ptr += 2;
-    const void* chunkData = chunk.field(ptr, chunkLen);
-    ptr += chunkLen;
-
-    NetworkConfig* nc = (NetworkConfig*)0;
-    uint64_t configUpdateId;
-    {
-        Mutex::Lock _l(_lock);
-
-        _IncomingConfigChunk* c = (_IncomingConfigChunk*)0;
-        uint64_t chunkId = 0;
-        unsigned long totalLength, chunkIndex;
-        if (ptr < chunk.size()) {
-            const bool fastPropagate = ((chunk[ptr++] & 0x01) != 0);
-            configUpdateId = chunk.at<uint64_t>(ptr);
-            ptr += 8;
-            totalLength = chunk.at<uint32_t>(ptr);
-            ptr += 4;
-            chunkIndex = chunk.at<uint32_t>(ptr);
-            ptr += 4;
-
-            if (((chunkIndex + chunkLen) > totalLength) || (totalLength >= ZT_NETWORKCONFIG_DICT_CAPACITY)) {   // >= since we need room for a null at the end
-                return 0;
-            }
-            if ((chunk[ptr] != 1) || (chunk.at<uint16_t>(ptr + 1) != ZT_ECC_SIGNATURE_LEN)) {
-                return 0;
-            }
-            const uint8_t* sig = reinterpret_cast<const uint8_t*>(chunk.field(ptr + 3, ZT_ECC_SIGNATURE_LEN));
-
-            // We can use the signature, which is unique per chunk, to get a per-chunk ID for local deduplication use
-            for (unsigned int i = 0; i < 16; ++i) {
-                reinterpret_cast<uint8_t*>(&chunkId)[i & 7] ^= sig[i];
-            }
-
-            // Find existing or new slot for this update and check if this is a duplicate chunk
-            for (int i = 0; i < ZT_NETWORK_MAX_INCOMING_UPDATES; ++i) {
-                if (_incomingConfigChunks[i].updateId == configUpdateId) {
-                    c = &(_incomingConfigChunks[i]);
-
-                    for (unsigned long j = 0; j < c->haveChunks; ++j) {
-                        if (c->haveChunkIds[j] == chunkId) {
-                            return 0;
-                        }
-                    }
-
-                    break;
-                }
-                else if ((! c) || (_incomingConfigChunks[i].ts < c->ts)) {
-                    c = &(_incomingConfigChunks[i]);
-                }
-            }
-
-            // If it's not a duplicate, check chunk signature
-            const Identity controllerId(RR->topology->getIdentity(tPtr, controller()));
-            if (! controllerId) {   // we should always have the controller identity by now, otherwise how would we have queried it the first time?
-                return 0;
-            }
-            if (! controllerId.verify(chunk.field(start, ptr - start), ptr - start, sig, ZT_ECC_SIGNATURE_LEN)) {
-                return 0;
-            }
-
-            // New properly verified chunks can be flooded "virally" through the network
-            if (fastPropagate) {
-                Address* a = (Address*)0;
-                Membership* m = (Membership*)0;
-                Hashtable<Address, Membership>::Iterator i(_memberships);
-                while (i.next(a, m)) {
-                    if ((*a != source) && (*a != controller())) {
-                        Packet outp(*a, RR->identity.address(), Packet::VERB_NETWORK_CONFIG);
-                        outp.append(reinterpret_cast<const uint8_t*>(chunk.data()) + start, chunk.size() - start);
-                        RR->sw->send(tPtr, outp, true);
-                    }
-                }
-            }
-        }
-        else if ((source == controller()) || (! source)) {   // since old chunks aren't signed, only accept from controller itself (or via cluster backplane)
-            // Legacy support for OK(NETWORK_CONFIG_REQUEST) from older controllers
-            chunkId = packetId;
-            configUpdateId = chunkId;
-            totalLength = chunkLen;
-            chunkIndex = 0;
-
-            if (totalLength >= ZT_NETWORKCONFIG_DICT_CAPACITY) {
-                return 0;
-            }
-
-            for (int i = 0; i < ZT_NETWORK_MAX_INCOMING_UPDATES; ++i) {
-                if ((! c) || (_incomingConfigChunks[i].ts < c->ts)) {
-                    c = &(_incomingConfigChunks[i]);
-                }
-            }
-        }
-        else {
-            // Single-chunk unsigned legacy configs are only allowed from the controller itself
-            return 0;
-        }
-
-        ++c->ts;   // newer is higher, that's all we need
-
-        if (c->updateId != configUpdateId) {
-            c->updateId = configUpdateId;
-            c->haveChunks = 0;
-            c->haveBytes = 0;
-        }
-        if (c->haveChunks >= ZT_NETWORK_MAX_UPDATE_CHUNKS) {
-            return false;
-        }
-        c->haveChunkIds[c->haveChunks++] = chunkId;
-
-        memcpy(c->data.unsafeData() + chunkIndex, chunkData, chunkLen);
-        c->haveBytes += chunkLen;
-
-        if (c->haveBytes == totalLength) {
-            c->data.unsafeData()[c->haveBytes] = (char)0;   // ensure null terminated
-
-            nc = new NetworkConfig();
-            try {
-                if (! nc->fromDictionary(c->data)) {
-                    delete nc;
-                    nc = (NetworkConfig*)0;
-                }
-            }
-            catch (...) {
-                delete nc;
-                nc = (NetworkConfig*)0;
-            }
-        }
-    }
-
-    if (nc) {
-        this->setConfiguration(tPtr, *nc, true);
-        delete nc;
-        return configUpdateId;
-    }
-    else {
-        return 0;
-    }
-
-    return 0;
+	if (_destroyed) {
+		return 0;
+	}
+
+	const unsigned int start = ptr;
+
+	ptr += 8;	// skip network ID, which is already obviously known
+	const unsigned int chunkLen = chunk.at<uint16_t>(ptr);
+	ptr += 2;
+	const void* chunkData = chunk.field(ptr, chunkLen);
+	ptr += chunkLen;
+
+	NetworkConfig* nc = (NetworkConfig*)0;
+	uint64_t configUpdateId;
+	{
+		Mutex::Lock _l(_lock);
+
+		_IncomingConfigChunk* c = (_IncomingConfigChunk*)0;
+		uint64_t chunkId = 0;
+		unsigned long totalLength, chunkIndex;
+		if (ptr < chunk.size()) {
+			const bool fastPropagate = ((chunk[ptr++] & 0x01) != 0);
+			configUpdateId = chunk.at<uint64_t>(ptr);
+			ptr += 8;
+			totalLength = chunk.at<uint32_t>(ptr);
+			ptr += 4;
+			chunkIndex = chunk.at<uint32_t>(ptr);
+			ptr += 4;
+
+			if (((chunkIndex + chunkLen) > totalLength) || (totalLength >= ZT_NETWORKCONFIG_DICT_CAPACITY)) {	// >= since we need room for a null at the end
+				return 0;
+			}
+			if ((chunk[ptr] != 1) || (chunk.at<uint16_t>(ptr + 1) != ZT_ECC_SIGNATURE_LEN)) {
+				return 0;
+			}
+			const uint8_t* sig = reinterpret_cast<const uint8_t*>(chunk.field(ptr + 3, ZT_ECC_SIGNATURE_LEN));
+
+			// We can use the signature, which is unique per chunk, to get a per-chunk ID for local deduplication use
+			for (unsigned int i = 0; i < 16; ++i) {
+				reinterpret_cast<uint8_t*>(&chunkId)[i & 7] ^= sig[i];
+			}
+
+			// Find existing or new slot for this update and check if this is a duplicate chunk
+			for (int i = 0; i < ZT_NETWORK_MAX_INCOMING_UPDATES; ++i) {
+				if (_incomingConfigChunks[i].updateId == configUpdateId) {
+					c = &(_incomingConfigChunks[i]);
+
+					for (unsigned long j = 0; j < c->haveChunks; ++j) {
+						if (c->haveChunkIds[j] == chunkId) {
+							return 0;
+						}
+					}
+
+					break;
+				}
+				else if ((! c) || (_incomingConfigChunks[i].ts < c->ts)) {
+					c = &(_incomingConfigChunks[i]);
+				}
+			}
+
+			// If it's not a duplicate, check chunk signature
+			const Identity controllerId(RR->topology->getIdentity(tPtr, controller()));
+			if (! controllerId) {	// we should always have the controller identity by now, otherwise how would we have queried it the first time?
+				return 0;
+			}
+			if (! controllerId.verify(chunk.field(start, ptr - start), ptr - start, sig, ZT_ECC_SIGNATURE_LEN)) {
+				return 0;
+			}
+
+			// New properly verified chunks can be flooded "virally" through the network
+			if (fastPropagate) {
+				Address* a = (Address*)0;
+				Membership* m = (Membership*)0;
+				Hashtable<Address, Membership>::Iterator i(_memberships);
+				while (i.next(a, m)) {
+					if ((*a != source) && (*a != controller())) {
+						Packet outp(*a, RR->identity.address(), Packet::VERB_NETWORK_CONFIG);
+						outp.append(reinterpret_cast<const uint8_t*>(chunk.data()) + start, chunk.size() - start);
+						RR->sw->send(tPtr, outp, true);
+					}
+				}
+			}
+		}
+		else if ((source == controller()) || (! source)) {	 // since old chunks aren't signed, only accept from controller itself (or via cluster backplane)
+			// Legacy support for OK(NETWORK_CONFIG_REQUEST) from older controllers
+			chunkId = packetId;
+			configUpdateId = chunkId;
+			totalLength = chunkLen;
+			chunkIndex = 0;
+
+			if (totalLength >= ZT_NETWORKCONFIG_DICT_CAPACITY) {
+				return 0;
+			}
+
+			for (int i = 0; i < ZT_NETWORK_MAX_INCOMING_UPDATES; ++i) {
+				if ((! c) || (_incomingConfigChunks[i].ts < c->ts)) {
+					c = &(_incomingConfigChunks[i]);
+				}
+			}
+		}
+		else {
+			// Single-chunk unsigned legacy configs are only allowed from the controller itself
+			return 0;
+		}
+
+		++c->ts;   // newer is higher, that's all we need
+
+		if (c->updateId != configUpdateId) {
+			c->updateId = configUpdateId;
+			c->haveChunks = 0;
+			c->haveBytes = 0;
+		}
+		if (c->haveChunks >= ZT_NETWORK_MAX_UPDATE_CHUNKS) {
+			return false;
+		}
+		c->haveChunkIds[c->haveChunks++] = chunkId;
+
+		memcpy(c->data.unsafeData() + chunkIndex, chunkData, chunkLen);
+		c->haveBytes += chunkLen;
+
+		if (c->haveBytes == totalLength) {
+			c->data.unsafeData()[c->haveBytes] = (char)0;	// ensure null terminated
+
+			nc = new NetworkConfig();
+			try {
+				if (! nc->fromDictionary(c->data)) {
+					delete nc;
+					nc = (NetworkConfig*)0;
+				}
+			}
+			catch (...) {
+				delete nc;
+				nc = (NetworkConfig*)0;
+			}
+		}
+	}
+
+	if (nc) {
+		this->setConfiguration(tPtr, *nc, true);
+		delete nc;
+		return configUpdateId;
+	}
+	else {
+		return 0;
+	}
+
+	return 0;
 }
 
 int Network::setConfiguration(void* tPtr, const NetworkConfig& nconf, bool saveToDisk)
 {
-    if (_destroyed) {
-        return 0;
-    }
-
-    // _lock is NOT locked when this is called
-    try {
-        if ((nconf.issuedTo != RR->identity.address()) || (nconf.networkId != _id)) {
-            return 0;   // invalid config that is not for us or not for this network
-        }
-        if (_config == nconf) {
-            return 1;   // OK config, but duplicate of what we already have
-        }
-
-        ZT_VirtualNetworkConfig ctmp;
-        bool oldPortInitialized;
-        {   // do things that require lock here, but unlock before calling callbacks
-            Mutex::Lock _l(_lock);
-
-            _config = nconf;
-            _lastConfigUpdate = RR->node->now();
-            _netconfFailure = NETCONF_FAILURE_NONE;
-
-            oldPortInitialized = _portInitialized;
-            _portInitialized = true;
-
-            _externalConfig(&ctmp);
-        }
-
-        _portError = RR->node->configureVirtualNetworkPort(tPtr, _id, &_uPtr, (oldPortInitialized) ? ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE : ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP, &ctmp);
-        _authenticationURL = nconf.authenticationURL;
-
-        if (saveToDisk) {
-            Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>* const d = new Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>();
-            try {
-                if (nconf.toDictionary(*d, false)) {
-                    uint64_t tmp[2];
-                    tmp[0] = _id;
-                    tmp[1] = 0;
-                    RR->node->stateObjectPut(tPtr, ZT_STATE_OBJECT_NETWORK_CONFIG, tmp, d->data(), d->sizeBytes());
-                }
-            }
-            catch (...) {
-            }
-            delete d;
-        }
-
-        return 2;   // OK and configuration has changed
-    }
-    catch (...) {
-    }   // ignore invalid configs
-    return 0;
+	if (_destroyed) {
+		return 0;
+	}
+
+	// _lock is NOT locked when this is called
+	try {
+		if ((nconf.issuedTo != RR->identity.address()) || (nconf.networkId != _id)) {
+			return 0;	// invalid config that is not for us or not for this network
+		}
+		if (_config == nconf) {
+			return 1;	// OK config, but duplicate of what we already have
+		}
+
+		ZT_VirtualNetworkConfig ctmp;
+		bool oldPortInitialized;
+		{	// do things that require lock here, but unlock before calling callbacks
+			Mutex::Lock _l(_lock);
+
+			_config = nconf;
+			_lastConfigUpdate = RR->node->now();
+			_netconfFailure = NETCONF_FAILURE_NONE;
+
+			oldPortInitialized = _portInitialized;
+			_portInitialized = true;
+
+			_externalConfig(&ctmp);
+		}
+
+		_portError = RR->node->configureVirtualNetworkPort(tPtr, _id, &_uPtr, (oldPortInitialized) ? ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE : ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP, &ctmp);
+		_authenticationURL = nconf.authenticationURL;
+
+		if (saveToDisk) {
+			Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>* const d = new Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>();
+			try {
+				if (nconf.toDictionary(*d, false)) {
+					uint64_t tmp[2];
+					tmp[0] = _id;
+					tmp[1] = 0;
+					RR->node->stateObjectPut(tPtr, ZT_STATE_OBJECT_NETWORK_CONFIG, tmp, d->data(), d->sizeBytes());
+				}
+			}
+			catch (...) {
+			}
+			delete d;
+		}
+
+		return 2;	// OK and configuration has changed
+	}
+	catch (...) {
+	}	// ignore invalid configs
+	return 0;
 }
 
 void Network::requestConfiguration(void* tPtr)
 {
-    if (_destroyed) {
-        return;
-    }
-
-    if ((_id >> 56) == 0xff) {
-        if ((_id & 0xffffff) == 0) {
-            const uint16_t startPortRange = (uint16_t)((_id >> 40) & 0xffff);
-            const uint16_t endPortRange = (uint16_t)((_id >> 24) & 0xffff);
-            if (endPortRange >= startPortRange) {
-                NetworkConfig* const nconf = new NetworkConfig();
-
-                nconf->networkId = _id;
-                nconf->timestamp = RR->node->now();
-                nconf->credentialTimeMaxDelta = ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MAX_MAX_DELTA;
-                nconf->revision = 1;
-                nconf->issuedTo = RR->identity.address();
-                nconf->flags = ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION;
-                nconf->mtu = ZT_DEFAULT_MTU;
-                nconf->multicastLimit = 0;
-                nconf->staticIpCount = 1;
-                nconf->ruleCount = 14;
-                nconf->staticIps[0] = InetAddress::makeIpv66plane(_id, RR->identity.address().toInt());
-
-                // Drop everything but IPv6
-                nconf->rules[0].t = (uint8_t)ZT_NETWORK_RULE_MATCH_ETHERTYPE | 0x80;   // NOT
-                nconf->rules[0].v.etherType = 0x86dd;                                  // IPv6
-                nconf->rules[1].t = (uint8_t)ZT_NETWORK_RULE_ACTION_DROP;
-
-                // Allow ICMPv6
-                nconf->rules[2].t = (uint8_t)ZT_NETWORK_RULE_MATCH_IP_PROTOCOL;
-                nconf->rules[2].v.ipProtocol = 0x3a;   // ICMPv6
-                nconf->rules[3].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT;
-
-                // Allow destination ports within range
-                nconf->rules[4].t = (uint8_t)ZT_NETWORK_RULE_MATCH_IP_PROTOCOL;
-                nconf->rules[4].v.ipProtocol = 0x11;                                     // UDP
-                nconf->rules[5].t = (uint8_t)ZT_NETWORK_RULE_MATCH_IP_PROTOCOL | 0x40;   // OR
-                nconf->rules[5].v.ipProtocol = 0x06;                                     // TCP
-                nconf->rules[6].t = (uint8_t)ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE;
-                nconf->rules[6].v.port[0] = startPortRange;
-                nconf->rules[6].v.port[1] = endPortRange;
-                nconf->rules[7].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT;
-
-                // Allow non-SYN TCP packets to permit non-connection-initiating traffic
-                nconf->rules[8].t = (uint8_t)ZT_NETWORK_RULE_MATCH_CHARACTERISTICS | 0x80;   // NOT
-                nconf->rules[8].v.characteristics = ZT_RULE_PACKET_CHARACTERISTICS_TCP_SYN;
-                nconf->rules[9].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT;
-
-                // Also allow SYN+ACK which are replies to SYN
-                nconf->rules[10].t = (uint8_t)ZT_NETWORK_RULE_MATCH_CHARACTERISTICS;
-                nconf->rules[10].v.characteristics = ZT_RULE_PACKET_CHARACTERISTICS_TCP_SYN;
-                nconf->rules[11].t = (uint8_t)ZT_NETWORK_RULE_MATCH_CHARACTERISTICS;
-                nconf->rules[11].v.characteristics = ZT_RULE_PACKET_CHARACTERISTICS_TCP_ACK;
-                nconf->rules[12].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT;
-
-                nconf->rules[13].t = (uint8_t)ZT_NETWORK_RULE_ACTION_DROP;
-
-                nconf->type = ZT_NETWORK_TYPE_PUBLIC;
-
-                nconf->name[0] = 'a';
-                nconf->name[1] = 'd';
-                nconf->name[2] = 'h';
-                nconf->name[3] = 'o';
-                nconf->name[4] = 'c';
-                nconf->name[5] = '-';
-                Utils::hex((uint16_t)startPortRange, nconf->name + 6);
-                nconf->name[10] = '-';
-                Utils::hex((uint16_t)endPortRange, nconf->name + 11);
-                nconf->name[15] = (char)0;
-
-                this->setConfiguration(tPtr, *nconf, false);
-                delete nconf;
-            }
-            else {
-                this->setNotFound(tPtr);
-            }
-        }
-        else if ((_id & 0xff) == 0x01) {
-            // ffAAaaaaaaaaaa01 -- where AA is the IPv4 /8 to use and aaaaaaaaaa is the anchor node for multicast gather and replication
-            const uint64_t myAddress = RR->identity.address().toInt();
-            const uint64_t networkHub = (_id >> 8) & 0xffffffffffULL;
-
-            uint8_t ipv4[4];
-            ipv4[0] = (uint8_t)((_id >> 48) & 0xff);
-            ipv4[1] = (uint8_t)((myAddress >> 16) & 0xff);
-            ipv4[2] = (uint8_t)((myAddress >> 8) & 0xff);
-            ipv4[3] = (uint8_t)(myAddress & 0xff);
-
-            char v4ascii[24];
-            Utils::decimal(ipv4[0], v4ascii);
-
-            NetworkConfig* const nconf = new NetworkConfig();
-
-            nconf->networkId = _id;
-            nconf->timestamp = RR->node->now();
-            nconf->credentialTimeMaxDelta = ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MAX_MAX_DELTA;
-            nconf->revision = 1;
-            nconf->issuedTo = RR->identity.address();
-            nconf->flags = ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION;
-            nconf->mtu = ZT_DEFAULT_MTU;
-            nconf->multicastLimit = 1024;
-            nconf->specialistCount = (networkHub == 0) ? 0 : 1;
-            nconf->staticIpCount = 2;
-            nconf->ruleCount = 1;
-
-            if (networkHub != 0) {
-                nconf->specialists[0] = networkHub;
-            }
-
-            nconf->staticIps[0] = InetAddress::makeIpv66plane(_id, myAddress);
-            nconf->staticIps[1].set(ipv4, 4, 8);
-
-            nconf->rules[0].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT;
-
-            nconf->type = ZT_NETWORK_TYPE_PUBLIC;
-
-            nconf->name[0] = 'a';
-            nconf->name[1] = 'd';
-            nconf->name[2] = 'h';
-            nconf->name[3] = 'o';
-            nconf->name[4] = 'c';
-            nconf->name[5] = '-';
-            unsigned long nn = 6;
-            while ((nconf->name[nn] = v4ascii[nn - 6])) {
-                ++nn;
-            }
-            nconf->name[nn++] = '.';
-            nconf->name[nn++] = '0';
-            nconf->name[nn++] = '.';
-            nconf->name[nn++] = '0';
-            nconf->name[nn++] = '.';
-            nconf->name[nn++] = '0';
-            nconf->name[nn++] = (char)0;
-
-            this->setConfiguration(tPtr, *nconf, false);
-            delete nconf;
-        }
-        return;
-    }
-
-    const Address ctrl(controller());
-
-    Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> rmd;
-    rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION, (uint64_t)ZT_NETWORKCONFIG_VERSION);
-    rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_VENDOR, (uint64_t)ZT_VENDOR_ZEROTIER);
-    rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_PROTOCOL_VERSION, (uint64_t)ZT_PROTO_VERSION);
-    rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION, (uint64_t)ZEROTIER_ONE_VERSION_MAJOR);
-    rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION, (uint64_t)ZEROTIER_ONE_VERSION_MINOR);
-    rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION, (uint64_t)ZEROTIER_ONE_VERSION_REVISION);
-    rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_NETWORK_RULES, (uint64_t)ZT_MAX_NETWORK_RULES);
-    rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_NETWORK_CAPABILITIES, (uint64_t)ZT_MAX_NETWORK_CAPABILITIES);
-    rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_CAPABILITY_RULES, (uint64_t)ZT_MAX_CAPABILITY_RULES);
-    rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_NETWORK_TAGS, (uint64_t)ZT_MAX_NETWORK_TAGS);
-    rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_FLAGS, (uint64_t)0);
-    rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_RULES_ENGINE_REV, (uint64_t)ZT_RULES_ENGINE_REVISION);
-    rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_OS_ARCH, ZT_TARGET_NAME);
-
-    RR->t->networkConfigRequestSent(tPtr, *this, ctrl);
-
-    if (ctrl == RR->identity.address()) {
-        if (RR->localNetworkController) {
-            RR->localNetworkController->request(_id, InetAddress(), 0xffffffffffffffffULL, RR->identity, rmd);
-        }
-        else {
-            this->setNotFound(tPtr);
-        }
-        return;
-    }
-
-    Packet outp(ctrl, RR->identity.address(), Packet::VERB_NETWORK_CONFIG_REQUEST);
-    outp.append((uint64_t)_id);
-    const unsigned int rmdSize = rmd.sizeBytes();
-    outp.append((uint16_t)rmdSize);
-    outp.append((const void*)rmd.data(), rmdSize);
-    if (_config) {
-        outp.append((uint64_t)_config.revision);
-        outp.append((uint64_t)_config.timestamp);
-    }
-    else {
-        outp.append((unsigned char)0, 16);
-    }
-    outp.compress();
-    RR->node->expectReplyTo(outp.packetId());
-    RR->sw->send(tPtr, outp, true);
+	if (_destroyed) {
+		return;
+	}
+
+	if ((_id >> 56) == 0xff) {
+		if ((_id & 0xffffff) == 0) {
+			const uint16_t startPortRange = (uint16_t)((_id >> 40) & 0xffff);
+			const uint16_t endPortRange = (uint16_t)((_id >> 24) & 0xffff);
+			if (endPortRange >= startPortRange) {
+				NetworkConfig* const nconf = new NetworkConfig();
+
+				nconf->networkId = _id;
+				nconf->timestamp = RR->node->now();
+				nconf->credentialTimeMaxDelta = ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MAX_MAX_DELTA;
+				nconf->revision = 1;
+				nconf->issuedTo = RR->identity.address();
+				nconf->flags = ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION;
+				nconf->mtu = ZT_DEFAULT_MTU;
+				nconf->multicastLimit = 0;
+				nconf->staticIpCount = 1;
+				nconf->ruleCount = 14;
+				nconf->staticIps[0] = InetAddress::makeIpv66plane(_id, RR->identity.address().toInt());
+
+				// Drop everything but IPv6
+				nconf->rules[0].t = (uint8_t)ZT_NETWORK_RULE_MATCH_ETHERTYPE | 0x80;   // NOT
+				nconf->rules[0].v.etherType = 0x86dd;								   // IPv6
+				nconf->rules[1].t = (uint8_t)ZT_NETWORK_RULE_ACTION_DROP;
+
+				// Allow ICMPv6
+				nconf->rules[2].t = (uint8_t)ZT_NETWORK_RULE_MATCH_IP_PROTOCOL;
+				nconf->rules[2].v.ipProtocol = 0x3a;   // ICMPv6
+				nconf->rules[3].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT;
+
+				// Allow destination ports within range
+				nconf->rules[4].t = (uint8_t)ZT_NETWORK_RULE_MATCH_IP_PROTOCOL;
+				nconf->rules[4].v.ipProtocol = 0x11;									 // UDP
+				nconf->rules[5].t = (uint8_t)ZT_NETWORK_RULE_MATCH_IP_PROTOCOL | 0x40;	 // OR
+				nconf->rules[5].v.ipProtocol = 0x06;									 // TCP
+				nconf->rules[6].t = (uint8_t)ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE;
+				nconf->rules[6].v.port[0] = startPortRange;
+				nconf->rules[6].v.port[1] = endPortRange;
+				nconf->rules[7].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT;
+
+				// Allow non-SYN TCP packets to permit non-connection-initiating traffic
+				nconf->rules[8].t = (uint8_t)ZT_NETWORK_RULE_MATCH_CHARACTERISTICS | 0x80;	 // NOT
+				nconf->rules[8].v.characteristics = ZT_RULE_PACKET_CHARACTERISTICS_TCP_SYN;
+				nconf->rules[9].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT;
+
+				// Also allow SYN+ACK which are replies to SYN
+				nconf->rules[10].t = (uint8_t)ZT_NETWORK_RULE_MATCH_CHARACTERISTICS;
+				nconf->rules[10].v.characteristics = ZT_RULE_PACKET_CHARACTERISTICS_TCP_SYN;
+				nconf->rules[11].t = (uint8_t)ZT_NETWORK_RULE_MATCH_CHARACTERISTICS;
+				nconf->rules[11].v.characteristics = ZT_RULE_PACKET_CHARACTERISTICS_TCP_ACK;
+				nconf->rules[12].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT;
+
+				nconf->rules[13].t = (uint8_t)ZT_NETWORK_RULE_ACTION_DROP;
+
+				nconf->type = ZT_NETWORK_TYPE_PUBLIC;
+
+				nconf->name[0] = 'a';
+				nconf->name[1] = 'd';
+				nconf->name[2] = 'h';
+				nconf->name[3] = 'o';
+				nconf->name[4] = 'c';
+				nconf->name[5] = '-';
+				Utils::hex((uint16_t)startPortRange, nconf->name + 6);
+				nconf->name[10] = '-';
+				Utils::hex((uint16_t)endPortRange, nconf->name + 11);
+				nconf->name[15] = (char)0;
+
+				this->setConfiguration(tPtr, *nconf, false);
+				delete nconf;
+			}
+			else {
+				this->setNotFound(tPtr);
+			}
+		}
+		else if ((_id & 0xff) == 0x01) {
+			// ffAAaaaaaaaaaa01 -- where AA is the IPv4 /8 to use and aaaaaaaaaa is the anchor node for multicast gather and replication
+			const uint64_t myAddress = RR->identity.address().toInt();
+			const uint64_t networkHub = (_id >> 8) & 0xffffffffffULL;
+
+			uint8_t ipv4[4];
+			ipv4[0] = (uint8_t)((_id >> 48) & 0xff);
+			ipv4[1] = (uint8_t)((myAddress >> 16) & 0xff);
+			ipv4[2] = (uint8_t)((myAddress >> 8) & 0xff);
+			ipv4[3] = (uint8_t)(myAddress & 0xff);
+
+			char v4ascii[24];
+			Utils::decimal(ipv4[0], v4ascii);
+
+			NetworkConfig* const nconf = new NetworkConfig();
+
+			nconf->networkId = _id;
+			nconf->timestamp = RR->node->now();
+			nconf->credentialTimeMaxDelta = ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MAX_MAX_DELTA;
+			nconf->revision = 1;
+			nconf->issuedTo = RR->identity.address();
+			nconf->flags = ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION;
+			nconf->mtu = ZT_DEFAULT_MTU;
+			nconf->multicastLimit = 1024;
+			nconf->specialistCount = (networkHub == 0) ? 0 : 1;
+			nconf->staticIpCount = 2;
+			nconf->ruleCount = 1;
+
+			if (networkHub != 0) {
+				nconf->specialists[0] = networkHub;
+			}
+
+			nconf->staticIps[0] = InetAddress::makeIpv66plane(_id, myAddress);
+			nconf->staticIps[1].set(ipv4, 4, 8);
+
+			nconf->rules[0].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT;
+
+			nconf->type = ZT_NETWORK_TYPE_PUBLIC;
+
+			nconf->name[0] = 'a';
+			nconf->name[1] = 'd';
+			nconf->name[2] = 'h';
+			nconf->name[3] = 'o';
+			nconf->name[4] = 'c';
+			nconf->name[5] = '-';
+			unsigned long nn = 6;
+			while ((nconf->name[nn] = v4ascii[nn - 6])) {
+				++nn;
+			}
+			nconf->name[nn++] = '.';
+			nconf->name[nn++] = '0';
+			nconf->name[nn++] = '.';
+			nconf->name[nn++] = '0';
+			nconf->name[nn++] = '.';
+			nconf->name[nn++] = '0';
+			nconf->name[nn++] = (char)0;
+
+			this->setConfiguration(tPtr, *nconf, false);
+			delete nconf;
+		}
+		return;
+	}
+
+	const Address ctrl(controller());
+
+	Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> rmd;
+	rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION, (uint64_t)ZT_NETWORKCONFIG_VERSION);
+	rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_VENDOR, (uint64_t)ZT_VENDOR_ZEROTIER);
+	rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_PROTOCOL_VERSION, (uint64_t)ZT_PROTO_VERSION);
+	rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION, (uint64_t)ZEROTIER_ONE_VERSION_MAJOR);
+	rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION, (uint64_t)ZEROTIER_ONE_VERSION_MINOR);
+	rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION, (uint64_t)ZEROTIER_ONE_VERSION_REVISION);
+	rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_NETWORK_RULES, (uint64_t)ZT_MAX_NETWORK_RULES);
+	rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_NETWORK_CAPABILITIES, (uint64_t)ZT_MAX_NETWORK_CAPABILITIES);
+	rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_CAPABILITY_RULES, (uint64_t)ZT_MAX_CAPABILITY_RULES);
+	rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_NETWORK_TAGS, (uint64_t)ZT_MAX_NETWORK_TAGS);
+	rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_FLAGS, (uint64_t)0);
+	rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_RULES_ENGINE_REV, (uint64_t)ZT_RULES_ENGINE_REVISION);
+	rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_OS_ARCH, ZT_TARGET_NAME);
+
+	RR->t->networkConfigRequestSent(tPtr, *this, ctrl);
+
+	if (ctrl == RR->identity.address()) {
+		if (RR->localNetworkController) {
+			RR->localNetworkController->request(_id, InetAddress(), 0xffffffffffffffffULL, RR->identity, rmd);
+		}
+		else {
+			this->setNotFound(tPtr);
+		}
+		return;
+	}
+
+	Packet outp(ctrl, RR->identity.address(), Packet::VERB_NETWORK_CONFIG_REQUEST);
+	outp.append((uint64_t)_id);
+	const unsigned int rmdSize = rmd.sizeBytes();
+	outp.append((uint16_t)rmdSize);
+	outp.append((const void*)rmd.data(), rmdSize);
+	if (_config) {
+		outp.append((uint64_t)_config.revision);
+		outp.append((uint64_t)_config.timestamp);
+	}
+	else {
+		outp.append((unsigned char)0, 16);
+	}
+	outp.compress();
+	RR->node->expectReplyTo(outp.packetId());
+	RR->sw->send(tPtr, outp, true);
 }
 
 bool Network::gate(void* tPtr, const SharedPtr<Peer>& peer)
 {
-    const int64_t now = RR->node->now();
-    // int64_t comTimestamp = 0;
-    // int64_t comRevocationThreshold = 0;
-    Mutex::Lock _l(_lock);
-    try {
-        if (_config) {
-            Membership* m = _memberships.get(peer->address());
-            // if (m) {
-            //	comTimestamp = m->comTimestamp();
-            //	comRevocationThreshold = m->comRevocationThreshold();
-            // }
-            if ((_config.isPublic()) || ((m) && (m->isAllowedOnNetwork(_config, peer->identity())))) {
-                if (! m) {
-                    m = &(_membership(peer->address()));
-                }
-                if (m->multicastLikeGate(now)) {
-                    _announceMulticastGroupsTo(tPtr, peer->address(), _allMulticastGroups());
-                }
-                return true;
-            }
-        }
-    }
-    catch (...) {
-    }
-    // printf("%.16llx %.10llx not allowed, COM ts %lld revocation %lld\n", _id, peer->address().toInt(), comTimestamp, comRevocationThreshold); fflush(stdout);
-
-    return false;
+	const int64_t now = RR->node->now();
+	// int64_t comTimestamp = 0;
+	// int64_t comRevocationThreshold = 0;
+	Mutex::Lock _l(_lock);
+	try {
+		if (_config) {
+			Membership* m = _memberships.get(peer->address());
+			// if (m) {
+			//	comTimestamp = m->comTimestamp();
+			//	comRevocationThreshold = m->comRevocationThreshold();
+			// }
+			if ((_config.isPublic()) || ((m) && (m->isAllowedOnNetwork(_config, peer->identity())))) {
+				if (! m) {
+					m = &(_membership(peer->address()));
+				}
+				if (m->multicastLikeGate(now)) {
+					_announceMulticastGroupsTo(tPtr, peer->address(), _allMulticastGroups());
+				}
+				return true;
+			}
+		}
+	}
+	catch (...) {
+	}
+	// printf("%.16llx %.10llx not allowed, COM ts %lld revocation %lld\n", _id, peer->address().toInt(), comTimestamp, comRevocationThreshold); fflush(stdout);
+
+	return false;
 }
 
 bool Network::recentlyAssociatedWith(const Address& addr)
 {
-    Mutex::Lock _l(_lock);
-    const Membership* m = _memberships.get(addr);
-    return ((m) && (m->recentlyAssociated(RR->node->now())));
+	Mutex::Lock _l(_lock);
+	const Membership* m = _memberships.get(addr);
+	return ((m) && (m->recentlyAssociated(RR->node->now())));
 }
 
 void Network::clean()
 {
-    const int64_t now = RR->node->now();
-    Mutex::Lock _l(_lock);
-
-    if (_destroyed) {
-        return;
-    }
-
-    {
-        Hashtable<MulticastGroup, uint64_t>::Iterator i(_multicastGroupsBehindMe);
-        MulticastGroup* mg = (MulticastGroup*)0;
-        uint64_t* ts = (uint64_t*)0;
-        while (i.next(mg, ts)) {
-            if ((now - *ts) > (ZT_MULTICAST_LIKE_EXPIRE * 2)) {
-                _multicastGroupsBehindMe.erase(*mg);
-            }
-        }
-    }
-
-    {
-        Address* a = (Address*)0;
-        Membership* m = (Membership*)0;
-        Hashtable<Address, Membership>::Iterator i(_memberships);
-        while (i.next(a, m)) {
-            if (! RR->topology->getPeerNoCache(*a)) {
-                _memberships.erase(*a);
-            }
-            else {
-                m->clean(now, _config);
-            }
-        }
-    }
+	const int64_t now = RR->node->now();
+	Mutex::Lock _l(_lock);
+
+	if (_destroyed) {
+		return;
+	}
+
+	{
+		Hashtable<MulticastGroup, uint64_t>::Iterator i(_multicastGroupsBehindMe);
+		MulticastGroup* mg = (MulticastGroup*)0;
+		uint64_t* ts = (uint64_t*)0;
+		while (i.next(mg, ts)) {
+			if ((now - *ts) > (ZT_MULTICAST_LIKE_EXPIRE * 2)) {
+				_multicastGroupsBehindMe.erase(*mg);
+			}
+		}
+	}
+
+	{
+		Address* a = (Address*)0;
+		Membership* m = (Membership*)0;
+		Hashtable<Address, Membership>::Iterator i(_memberships);
+		while (i.next(a, m)) {
+			if (! RR->topology->getPeerNoCache(*a)) {
+				_memberships.erase(*a);
+			}
+			else {
+				m->clean(now, _config);
+			}
+		}
+	}
 }
 
 void Network::learnBridgeRoute(const MAC& mac, const Address& addr)
 {
-    Mutex::Lock _l(_lock);
-    _remoteBridgeRoutes[mac] = addr;
-
-    // Anti-DOS circuit breaker to prevent nodes from spamming us with absurd numbers of bridge routes
-    while (_remoteBridgeRoutes.size() > ZT_MAX_BRIDGE_ROUTES) {
-        Hashtable<Address, unsigned long> counts;
-        Address maxAddr;
-        unsigned long maxCount = 0;
-
-        MAC* m = (MAC*)0;
-        Address* a = (Address*)0;
-
-        // Find the address responsible for the most entries
-        {
-            Hashtable<MAC, Address>::Iterator i(_remoteBridgeRoutes);
-            while (i.next(m, a)) {
-                const unsigned long c = ++counts[*a];
-                if (c > maxCount) {
-                    maxCount = c;
-                    maxAddr = *a;
-                }
-            }
-        }
-
-        // Kill this address from our table, since it's most likely spamming us
-        {
-            Hashtable<MAC, Address>::Iterator i(_remoteBridgeRoutes);
-            while (i.next(m, a)) {
-                if (*a == maxAddr) {
-                    _remoteBridgeRoutes.erase(*m);
-                }
-            }
-        }
-    }
+	Mutex::Lock _l(_lock);
+	_remoteBridgeRoutes[mac] = addr;
+
+	// Anti-DOS circuit breaker to prevent nodes from spamming us with absurd numbers of bridge routes
+	while (_remoteBridgeRoutes.size() > ZT_MAX_BRIDGE_ROUTES) {
+		Hashtable<Address, unsigned long> counts;
+		Address maxAddr;
+		unsigned long maxCount = 0;
+
+		MAC* m = (MAC*)0;
+		Address* a = (Address*)0;
+
+		// Find the address responsible for the most entries
+		{
+			Hashtable<MAC, Address>::Iterator i(_remoteBridgeRoutes);
+			while (i.next(m, a)) {
+				const unsigned long c = ++counts[*a];
+				if (c > maxCount) {
+					maxCount = c;
+					maxAddr = *a;
+				}
+			}
+		}
+
+		// Kill this address from our table, since it's most likely spamming us
+		{
+			Hashtable<MAC, Address>::Iterator i(_remoteBridgeRoutes);
+			while (i.next(m, a)) {
+				if (*a == maxAddr) {
+					_remoteBridgeRoutes.erase(*m);
+				}
+			}
+		}
+	}
 }
 
 void Network::learnBridgedMulticastGroup(void* tPtr, const MulticastGroup& mg, int64_t now)
 {
-    Mutex::Lock _l(_lock);
-    const unsigned long tmp = (unsigned long)_multicastGroupsBehindMe.size();
-    _multicastGroupsBehindMe.set(mg, now);
-    if (tmp != _multicastGroupsBehindMe.size()) {
-        _sendUpdatesToMembers(tPtr, &mg);
-    }
+	Mutex::Lock _l(_lock);
+	const unsigned long tmp = (unsigned long)_multicastGroupsBehindMe.size();
+	_multicastGroupsBehindMe.set(mg, now);
+	if (tmp != _multicastGroupsBehindMe.size()) {
+		_sendUpdatesToMembers(tPtr, &mg);
+	}
 }
 
 Membership::AddCredentialResult Network::addCredential(void* tPtr, const CertificateOfMembership& com)
 {
-    if (com.networkId() != _id) {
-        return Membership::ADD_REJECTED;
-    }
-    Mutex::Lock _l(_lock);
-    return _membership(com.issuedTo()).addCredential(RR, tPtr, _config, com);
+	if (com.networkId() != _id) {
+		return Membership::ADD_REJECTED;
+	}
+	Mutex::Lock _l(_lock);
+	return _membership(com.issuedTo()).addCredential(RR, tPtr, _config, com);
 }
 
 Membership::AddCredentialResult Network::addCredential(void* tPtr, const Address& sentFrom, const Revocation& rev)
 {
-    if (rev.networkId() != _id) {
-        return Membership::ADD_REJECTED;
-    }
-
-    Mutex::Lock _l(_lock);
-    Membership& m = _membership(rev.target());
-
-    const Membership::AddCredentialResult result = m.addCredential(RR, tPtr, _config, rev);
-
-    if ((result == Membership::ADD_ACCEPTED_NEW) && (rev.fastPropagate())) {
-        Address* a = (Address*)0;
-        Membership* m = (Membership*)0;
-        Hashtable<Address, Membership>::Iterator i(_memberships);
-        while (i.next(a, m)) {
-            if ((*a != sentFrom) && (*a != rev.signer())) {
-                Packet outp(*a, RR->identity.address(), Packet::VERB_NETWORK_CREDENTIALS);
-                outp.append((uint8_t)0x00);   // no COM
-                outp.append((uint16_t)0);     // no capabilities
-                outp.append((uint16_t)0);     // no tags
-                outp.append((uint16_t)1);     // one revocation!
-                rev.serialize(outp);
-                outp.append((uint16_t)0);   // no certificates of ownership
-                RR->sw->send(tPtr, outp, true);
-            }
-        }
-    }
-
-    return result;
+	if (rev.networkId() != _id) {
+		return Membership::ADD_REJECTED;
+	}
+
+	Mutex::Lock _l(_lock);
+	Membership& m = _membership(rev.target());
+
+	const Membership::AddCredentialResult result = m.addCredential(RR, tPtr, _config, rev);
+
+	if ((result == Membership::ADD_ACCEPTED_NEW) && (rev.fastPropagate())) {
+		Address* a = (Address*)0;
+		Membership* m = (Membership*)0;
+		Hashtable<Address, Membership>::Iterator i(_memberships);
+		while (i.next(a, m)) {
+			if ((*a != sentFrom) && (*a != rev.signer())) {
+				Packet outp(*a, RR->identity.address(), Packet::VERB_NETWORK_CREDENTIALS);
+				outp.append((uint8_t)0x00);	  // no COM
+				outp.append((uint16_t)0);	  // no capabilities
+				outp.append((uint16_t)0);	  // no tags
+				outp.append((uint16_t)1);	  // one revocation!
+				rev.serialize(outp);
+				outp.append((uint16_t)0);	// no certificates of ownership
+				RR->sw->send(tPtr, outp, true);
+			}
+		}
+	}
+
+	return result;
 }
 
 void Network::destroy()
 {
-    Mutex::Lock _l(_lock);
-    _destroyed = true;
+	Mutex::Lock _l(_lock);
+	_destroyed = true;
 }
 
 ZT_VirtualNetworkStatus Network::_status() const
 {
-    // assumes _lock is locked
-    if (_portError) {
-        return ZT_NETWORK_STATUS_PORT_ERROR;
-    }
-    switch (_netconfFailure) {
-        case NETCONF_FAILURE_ACCESS_DENIED:
-            return ZT_NETWORK_STATUS_ACCESS_DENIED;
-        case NETCONF_FAILURE_NOT_FOUND:
-            return ZT_NETWORK_STATUS_NOT_FOUND;
-        case NETCONF_FAILURE_NONE:
-            return ((_config) ? ZT_NETWORK_STATUS_OK : ZT_NETWORK_STATUS_REQUESTING_CONFIGURATION);
-        case NETCONF_FAILURE_AUTHENTICATION_REQUIRED:
-            return ZT_NETWORK_STATUS_AUTHENTICATION_REQUIRED;
-        default:
-            return ZT_NETWORK_STATUS_PORT_ERROR;
-    }
+	// assumes _lock is locked
+	if (_portError) {
+		return ZT_NETWORK_STATUS_PORT_ERROR;
+	}
+	switch (_netconfFailure) {
+		case NETCONF_FAILURE_ACCESS_DENIED:
+			return ZT_NETWORK_STATUS_ACCESS_DENIED;
+		case NETCONF_FAILURE_NOT_FOUND:
+			return ZT_NETWORK_STATUS_NOT_FOUND;
+		case NETCONF_FAILURE_NONE:
+			return ((_config) ? ZT_NETWORK_STATUS_OK : ZT_NETWORK_STATUS_REQUESTING_CONFIGURATION);
+		case NETCONF_FAILURE_AUTHENTICATION_REQUIRED:
+			return ZT_NETWORK_STATUS_AUTHENTICATION_REQUIRED;
+		default:
+			return ZT_NETWORK_STATUS_PORT_ERROR;
+	}
 }
 
 void Network::_externalConfig(ZT_VirtualNetworkConfig* ec) const
 {
-    // assumes _lock is locked
-    ec->nwid = _id;
-    ec->mac = _mac.toInt();
-    if (_config) {
-        Utils::scopy(ec->name, sizeof(ec->name), _config.name);
-    }
-    else {
-        ec->name[0] = (char)0;
-    }
-    ec->status = _status();
-    ec->type = (_config) ? (_config.isPrivate() ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC) : ZT_NETWORK_TYPE_PRIVATE;
-    ec->mtu = (_config) ? _config.mtu : ZT_DEFAULT_MTU;
-    ec->dhcp = 0;
-    std::vector<Address> ab(_config.activeBridges());
-    ec->bridge = (std::find(ab.begin(), ab.end(), RR->identity.address()) != ab.end()) ? 1 : 0;
-    ec->broadcastEnabled = (_config) ? (_config.enableBroadcast() ? 1 : 0) : 0;
-    ec->portError = _portError;
-    ec->netconfRevision = (_config) ? (unsigned long)_config.revision : 0;
-
-    ec->assignedAddressCount = 0;
-    for (unsigned int i = 0; i < ZT_MAX_ZT_ASSIGNED_ADDRESSES; ++i) {
-        if (i < _config.staticIpCount) {
-            memcpy(&(ec->assignedAddresses[i]), &(_config.staticIps[i]), sizeof(struct sockaddr_storage));
-            ++ec->assignedAddressCount;
-        }
-        else {
-            memset(&(ec->assignedAddresses[i]), 0, sizeof(struct sockaddr_storage));
-        }
-    }
-
-    ec->routeCount = 0;
-    for (unsigned int i = 0; i < ZT_MAX_NETWORK_ROUTES; ++i) {
-        if (i < _config.routeCount) {
-            memcpy(&(ec->routes[i]), &(_config.routes[i]), sizeof(ZT_VirtualNetworkRoute));
-            ++ec->routeCount;
-        }
-        else {
-            memset(&(ec->routes[i]), 0, sizeof(ZT_VirtualNetworkRoute));
-        }
-    }
-
-    ec->multicastSubscriptionCount = (unsigned int)_myMulticastGroups.size();
-    for (unsigned long i = 0; i < (unsigned long)_myMulticastGroups.size(); ++i) {
-        ec->multicastSubscriptions[i].mac = _myMulticastGroups[i].mac().toInt();
-        ec->multicastSubscriptions[i].adi = _myMulticastGroups[i].adi();
-    }
-
-    memcpy(&ec->dns, &_config.dns, sizeof(ZT_VirtualNetworkDNS));
-
-    Utils::scopy(ec->authenticationURL, sizeof(ec->authenticationURL), _authenticationURL.c_str());
-    ec->ssoVersion = _config.ssoVersion;
-    ec->authenticationExpiryTime = _config.authenticationExpiryTime;
-    ec->ssoEnabled = _config.ssoEnabled;
-    Utils::scopy(ec->centralAuthURL, sizeof(ec->centralAuthURL), _config.centralAuthURL);
-    Utils::scopy(ec->issuerURL, sizeof(ec->issuerURL), _config.issuerURL);
-    Utils::scopy(ec->ssoNonce, sizeof(ec->ssoNonce), _config.ssoNonce);
-    Utils::scopy(ec->ssoState, sizeof(ec->ssoState), _config.ssoState);
-    Utils::scopy(ec->ssoClientID, sizeof(ec->ssoClientID), _config.ssoClientID);
-    Utils::scopy(ec->ssoProvider, sizeof(ec->ssoProvider), _config.ssoProvider);
+	// assumes _lock is locked
+	ec->nwid = _id;
+	ec->mac = _mac.toInt();
+	if (_config) {
+		Utils::scopy(ec->name, sizeof(ec->name), _config.name);
+	}
+	else {
+		ec->name[0] = (char)0;
+	}
+	ec->status = _status();
+	ec->type = (_config) ? (_config.isPrivate() ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC) : ZT_NETWORK_TYPE_PRIVATE;
+	ec->mtu = (_config) ? _config.mtu : ZT_DEFAULT_MTU;
+	ec->dhcp = 0;
+	std::vector<Address> ab(_config.activeBridges());
+	ec->bridge = (std::find(ab.begin(), ab.end(), RR->identity.address()) != ab.end()) ? 1 : 0;
+	ec->broadcastEnabled = (_config) ? (_config.enableBroadcast() ? 1 : 0) : 0;
+	ec->portError = _portError;
+	ec->netconfRevision = (_config) ? (unsigned long)_config.revision : 0;
+
+	ec->assignedAddressCount = 0;
+	for (unsigned int i = 0; i < ZT_MAX_ZT_ASSIGNED_ADDRESSES; ++i) {
+		if (i < _config.staticIpCount) {
+			memcpy(&(ec->assignedAddresses[i]), &(_config.staticIps[i]), sizeof(struct sockaddr_storage));
+			++ec->assignedAddressCount;
+		}
+		else {
+			memset(&(ec->assignedAddresses[i]), 0, sizeof(struct sockaddr_storage));
+		}
+	}
+
+	ec->routeCount = 0;
+	for (unsigned int i = 0; i < ZT_MAX_NETWORK_ROUTES; ++i) {
+		if (i < _config.routeCount) {
+			memcpy(&(ec->routes[i]), &(_config.routes[i]), sizeof(ZT_VirtualNetworkRoute));
+			++ec->routeCount;
+		}
+		else {
+			memset(&(ec->routes[i]), 0, sizeof(ZT_VirtualNetworkRoute));
+		}
+	}
+
+	ec->multicastSubscriptionCount = (unsigned int)_myMulticastGroups.size();
+	for (unsigned long i = 0; i < (unsigned long)_myMulticastGroups.size(); ++i) {
+		ec->multicastSubscriptions[i].mac = _myMulticastGroups[i].mac().toInt();
+		ec->multicastSubscriptions[i].adi = _myMulticastGroups[i].adi();
+	}
+
+	memcpy(&ec->dns, &_config.dns, sizeof(ZT_VirtualNetworkDNS));
+
+	Utils::scopy(ec->authenticationURL, sizeof(ec->authenticationURL), _authenticationURL.c_str());
+	ec->ssoVersion = _config.ssoVersion;
+	ec->authenticationExpiryTime = _config.authenticationExpiryTime;
+	ec->ssoEnabled = _config.ssoEnabled;
+	Utils::scopy(ec->centralAuthURL, sizeof(ec->centralAuthURL), _config.centralAuthURL);
+	Utils::scopy(ec->issuerURL, sizeof(ec->issuerURL), _config.issuerURL);
+	Utils::scopy(ec->ssoNonce, sizeof(ec->ssoNonce), _config.ssoNonce);
+	Utils::scopy(ec->ssoState, sizeof(ec->ssoState), _config.ssoState);
+	Utils::scopy(ec->ssoClientID, sizeof(ec->ssoClientID), _config.ssoClientID);
+	Utils::scopy(ec->ssoProvider, sizeof(ec->ssoProvider), _config.ssoProvider);
 }
 
 void Network::_sendUpdatesToMembers(void* tPtr, const MulticastGroup* const newMulticastGroup)
 {
-    // Assumes _lock is locked
-    const int64_t now = RR->node->now();
-
-    std::vector<MulticastGroup> groups;
-    if (newMulticastGroup) {
-        groups.push_back(*newMulticastGroup);
-    }
-    else {
-        groups = _allMulticastGroups();
-    }
-
-    std::vector<Address> alwaysAnnounceTo;
-
-    if ((newMulticastGroup) || ((now - _lastAnnouncedMulticastGroupsUpstream) >= ZT_MULTICAST_ANNOUNCE_PERIOD)) {
-        if (! newMulticastGroup) {
-            _lastAnnouncedMulticastGroupsUpstream = now;
-        }
-
-        alwaysAnnounceTo = _config.alwaysContactAddresses();
-        if (std::find(alwaysAnnounceTo.begin(), alwaysAnnounceTo.end(), controller()) == alwaysAnnounceTo.end()) {
-            alwaysAnnounceTo.push_back(controller());
-        }
-        const std::vector<Address> upstreams(RR->topology->upstreamAddresses());
-        for (std::vector<Address>::const_iterator a(upstreams.begin()); a != upstreams.end(); ++a) {
-            if (std::find(alwaysAnnounceTo.begin(), alwaysAnnounceTo.end(), *a) == alwaysAnnounceTo.end()) {
-                alwaysAnnounceTo.push_back(*a);
-            }
-        }
-        std::sort(alwaysAnnounceTo.begin(), alwaysAnnounceTo.end());
-
-        for (std::vector<Address>::const_iterator a(alwaysAnnounceTo.begin()); a != alwaysAnnounceTo.end(); ++a) {
-            /*
-            // push COM to non-members so they can do multicast request auth
-            if ( (_config.com) && (!_memberships.contains(*a)) && (*a != RR->identity.address()) ) {
-                Packet outp(*a,RR->identity.address(),Packet::VERB_NETWORK_CREDENTIALS);
-                _config.com.serialize(outp);
-                outp.append((uint8_t)0x00);
-                outp.append((uint16_t)0); // no capabilities
-                outp.append((uint16_t)0); // no tags
-                outp.append((uint16_t)0); // no revocations
-                outp.append((uint16_t)0); // no certificates of ownership
-                RR->sw->send(tPtr,outp,true);
-            }
-            */
-            _announceMulticastGroupsTo(tPtr, *a, groups);
-        }
-    }
-
-    {
-        Address* a = (Address*)0;
-        Membership* m = (Membership*)0;
-        Hashtable<Address, Membership>::Iterator i(_memberships);
-        while (i.next(a, m)) {
-            const Identity remoteIdentity(RR->topology->getIdentity(tPtr, *a));
-            if (remoteIdentity) {
-                if ((m->multicastLikeGate(now) || (newMulticastGroup)) && (m->isAllowedOnNetwork(_config, remoteIdentity)) && (! std::binary_search(alwaysAnnounceTo.begin(), alwaysAnnounceTo.end(), *a))) {
-                    _announceMulticastGroupsTo(tPtr, *a, groups);
-                }
-            }
-        }
-    }
+	// Assumes _lock is locked
+	const int64_t now = RR->node->now();
+
+	std::vector<MulticastGroup> groups;
+	if (newMulticastGroup) {
+		groups.push_back(*newMulticastGroup);
+	}
+	else {
+		groups = _allMulticastGroups();
+	}
+
+	std::vector<Address> alwaysAnnounceTo;
+
+	if ((newMulticastGroup) || ((now - _lastAnnouncedMulticastGroupsUpstream) >= ZT_MULTICAST_ANNOUNCE_PERIOD)) {
+		if (! newMulticastGroup) {
+			_lastAnnouncedMulticastGroupsUpstream = now;
+		}
+
+		alwaysAnnounceTo = _config.alwaysContactAddresses();
+		if (std::find(alwaysAnnounceTo.begin(), alwaysAnnounceTo.end(), controller()) == alwaysAnnounceTo.end()) {
+			alwaysAnnounceTo.push_back(controller());
+		}
+		const std::vector<Address> upstreams(RR->topology->upstreamAddresses());
+		for (std::vector<Address>::const_iterator a(upstreams.begin()); a != upstreams.end(); ++a) {
+			if (std::find(alwaysAnnounceTo.begin(), alwaysAnnounceTo.end(), *a) == alwaysAnnounceTo.end()) {
+				alwaysAnnounceTo.push_back(*a);
+			}
+		}
+		std::sort(alwaysAnnounceTo.begin(), alwaysAnnounceTo.end());
+
+		for (std::vector<Address>::const_iterator a(alwaysAnnounceTo.begin()); a != alwaysAnnounceTo.end(); ++a) {
+			/*
+			// push COM to non-members so they can do multicast request auth
+			if ( (_config.com) && (!_memberships.contains(*a)) && (*a != RR->identity.address()) ) {
+				Packet outp(*a,RR->identity.address(),Packet::VERB_NETWORK_CREDENTIALS);
+				_config.com.serialize(outp);
+				outp.append((uint8_t)0x00);
+				outp.append((uint16_t)0); // no capabilities
+				outp.append((uint16_t)0); // no tags
+				outp.append((uint16_t)0); // no revocations
+				outp.append((uint16_t)0); // no certificates of ownership
+				RR->sw->send(tPtr,outp,true);
+			}
+			*/
+			_announceMulticastGroupsTo(tPtr, *a, groups);
+		}
+	}
+
+	{
+		Address* a = (Address*)0;
+		Membership* m = (Membership*)0;
+		Hashtable<Address, Membership>::Iterator i(_memberships);
+		while (i.next(a, m)) {
+			const Identity remoteIdentity(RR->topology->getIdentity(tPtr, *a));
+			if (remoteIdentity) {
+				if ((m->multicastLikeGate(now) || (newMulticastGroup)) && (m->isAllowedOnNetwork(_config, remoteIdentity)) && (! std::binary_search(alwaysAnnounceTo.begin(), alwaysAnnounceTo.end(), *a))) {
+					_announceMulticastGroupsTo(tPtr, *a, groups);
+				}
+			}
+		}
+	}
 }
 
 void Network::_announceMulticastGroupsTo(void* tPtr, const Address& peer, const std::vector<MulticastGroup>& allMulticastGroups)
 {
-    // Assumes _lock is locked
-    Packet* const outp = new Packet(peer, RR->identity.address(), Packet::VERB_MULTICAST_LIKE);
-
-    for (std::vector<MulticastGroup>::const_iterator mg(allMulticastGroups.begin()); mg != allMulticastGroups.end(); ++mg) {
-        if ((outp->size() + 24) >= ZT_PROTO_MAX_PACKET_LENGTH) {
-            outp->compress();
-            RR->sw->send(tPtr, *outp, true);
-            outp->reset(peer, RR->identity.address(), Packet::VERB_MULTICAST_LIKE);
-        }
-
-        // network ID, MAC, ADI
-        outp->append((uint64_t)_id);
-        mg->mac().appendTo(*outp);
-        outp->append((uint32_t)mg->adi());
-    }
-
-    if (outp->size() > ZT_PROTO_MIN_PACKET_LENGTH) {
-        outp->compress();
-        RR->sw->send(tPtr, *outp, true);
-    }
-
-    delete outp;
+	// Assumes _lock is locked
+	Packet* const outp = new Packet(peer, RR->identity.address(), Packet::VERB_MULTICAST_LIKE);
+
+	for (std::vector<MulticastGroup>::const_iterator mg(allMulticastGroups.begin()); mg != allMulticastGroups.end(); ++mg) {
+		if ((outp->size() + 24) >= ZT_PROTO_MAX_PACKET_LENGTH) {
+			outp->compress();
+			RR->sw->send(tPtr, *outp, true);
+			outp->reset(peer, RR->identity.address(), Packet::VERB_MULTICAST_LIKE);
+		}
+
+		// network ID, MAC, ADI
+		outp->append((uint64_t)_id);
+		mg->mac().appendTo(*outp);
+		outp->append((uint32_t)mg->adi());
+	}
+
+	if (outp->size() > ZT_PROTO_MIN_PACKET_LENGTH) {
+		outp->compress();
+		RR->sw->send(tPtr, *outp, true);
+	}
+
+	delete outp;
 }
 
 std::vector<MulticastGroup> Network::_allMulticastGroups() const
 {
-    // Assumes _lock is locked
-    std::vector<MulticastGroup> mgs;
-    mgs.reserve(_myMulticastGroups.size() + _multicastGroupsBehindMe.size() + 1);
-    mgs.insert(mgs.end(), _myMulticastGroups.begin(), _myMulticastGroups.end());
-    _multicastGroupsBehindMe.appendKeys(mgs);
-    if ((_config) && (_config.enableBroadcast())) {
-        mgs.push_back(Network::BROADCAST);
-    }
-    std::sort(mgs.begin(), mgs.end());
-    mgs.erase(std::unique(mgs.begin(), mgs.end()), mgs.end());
-    return mgs;
+	// Assumes _lock is locked
+	std::vector<MulticastGroup> mgs;
+	mgs.reserve(_myMulticastGroups.size() + _multicastGroupsBehindMe.size() + 1);
+	mgs.insert(mgs.end(), _myMulticastGroups.begin(), _myMulticastGroups.end());
+	_multicastGroupsBehindMe.appendKeys(mgs);
+	if ((_config) && (_config.enableBroadcast())) {
+		mgs.push_back(Network::BROADCAST);
+	}
+	std::sort(mgs.begin(), mgs.end());
+	mgs.erase(std::unique(mgs.begin(), mgs.end()), mgs.end());
+	return mgs;
 }
 
 Membership& Network::_membership(const Address& a)
 {
-    // assumes _lock is locked
-    return _memberships[a];
+	// assumes _lock is locked
+	return _memberships[a];
 }
 
 void Network::setAuthenticationRequired(void* tPtr, const char* issuerURL, const char* centralEndpoint, const char* clientID, const char* ssoProvider, const char* nonce, const char* state)
 {
-    Mutex::Lock _l(_lock);
-    _netconfFailure = NETCONF_FAILURE_AUTHENTICATION_REQUIRED;
-    _config.ssoEnabled = true;
-    _config.ssoVersion = 1;
-
-    Utils::scopy(_config.issuerURL, sizeof(_config.issuerURL), issuerURL);
-    Utils::scopy(_config.centralAuthURL, sizeof(_config.centralAuthURL), centralEndpoint);
-    Utils::scopy(_config.ssoClientID, sizeof(_config.ssoClientID), clientID);
-    Utils::scopy(_config.ssoNonce, sizeof(_config.ssoNonce), nonce);
-    Utils::scopy(_config.ssoState, sizeof(_config.ssoState), state);
-    Utils::scopy(_config.ssoProvider, sizeof(_config.ssoProvider), ssoProvider);
-    _sendUpdateEvent(tPtr);
+	Mutex::Lock _l(_lock);
+	_netconfFailure = NETCONF_FAILURE_AUTHENTICATION_REQUIRED;
+	_config.ssoEnabled = true;
+	_config.ssoVersion = 1;
+
+	Utils::scopy(_config.issuerURL, sizeof(_config.issuerURL), issuerURL);
+	Utils::scopy(_config.centralAuthURL, sizeof(_config.centralAuthURL), centralEndpoint);
+	Utils::scopy(_config.ssoClientID, sizeof(_config.ssoClientID), clientID);
+	Utils::scopy(_config.ssoNonce, sizeof(_config.ssoNonce), nonce);
+	Utils::scopy(_config.ssoState, sizeof(_config.ssoState), state);
+	Utils::scopy(_config.ssoProvider, sizeof(_config.ssoProvider), ssoProvider);
+	_sendUpdateEvent(tPtr);
 }
 
 void Network::_sendUpdateEvent(void* tPtr)
 {
-    ZT_VirtualNetworkConfig ctmp;
-    _externalConfig(&ctmp);
-    RR->node->configureVirtualNetworkPort(tPtr, _id, &_uPtr, (_portInitialized) ? ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE : ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP, &ctmp);
+	ZT_VirtualNetworkConfig ctmp;
+	_externalConfig(&ctmp);
+	RR->node->configureVirtualNetworkPort(tPtr, _id, &_uPtr, (_portInitialized) ? ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE : ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP, &ctmp);
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 463 - 463
node/Network.hpp

@@ -38,7 +38,7 @@
 #include <vector>
 
 #define ZT_NETWORK_MAX_INCOMING_UPDATES 3
-#define ZT_NETWORK_MAX_UPDATE_CHUNKS    ((ZT_NETWORKCONFIG_DICT_CAPACITY / 1024) + 1)
+#define ZT_NETWORK_MAX_UPDATE_CHUNKS	((ZT_NETWORKCONFIG_DICT_CAPACITY / 1024) + 1)
 
 namespace ZeroTier {
 
@@ -49,473 +49,473 @@ class Peer;
  * A virtual LAN
  */
 class Network {
-    friend class SharedPtr<Network>;
+	friend class SharedPtr<Network>;
 
   public:
-    /**
-     * Broadcast multicast group: ff:ff:ff:ff:ff:ff / 0
-     */
-    static const MulticastGroup BROADCAST;
-
-    /**
-     * Compute primary controller device ID from network ID
-     */
-    static inline Address controllerFor(uint64_t nwid)
-    {
-        return Address(nwid >> 24);
-    }
-
-    /**
-     * Construct a new network
-     *
-     * Note that init() should be called immediately after the network is
-     * constructed to actually configure the port.
-     *
-     * @param renv Runtime environment
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param nwid Network ID
-     * @param uptr Arbitrary pointer used by externally-facing API (for user use)
-     * @param nconf Network config, if known
-     */
-    Network(const RuntimeEnvironment* renv, void* tPtr, uint64_t nwid, void* uptr, const NetworkConfig* nconf);
-
-    ~Network();
-
-    inline uint64_t id() const
-    {
-        return _id;
-    }
-    inline Address controller() const
-    {
-        return Address(_id >> 24);
-    }
-    inline bool multicastEnabled() const
-    {
-        return (_config.multicastLimit > 0);
-    }
-    inline bool hasConfig() const
-    {
-        return (_config);
-    }
-    inline uint64_t lastConfigUpdate() const
-    {
-        return _lastConfigUpdate;
-    }
-    inline ZT_VirtualNetworkStatus status() const
-    {
-        Mutex::Lock _l(_lock);
-        return _status();
-    }
-    inline const NetworkConfig& config() const
-    {
-        return _config;
-    }
-    inline const MAC& mac() const
-    {
-        return _mac;
-    }
-
-    /**
-     * Apply filters to an outgoing packet
-     *
-     * This applies filters from our network config and, if that doesn't match,
-     * our capabilities in ascending order of capability ID. Additional actions
-     * such as TEE may be taken, and credentials may be pushed, so this is not
-     * side-effect-free. It's basically step one in sending something over VL2.
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param noTee If true, do not TEE anything anywhere (for two-pass filtering as done with multicast and bridging)
-     * @param ztSource Source ZeroTier address
-     * @param ztDest Destination ZeroTier address
-     * @param macSource Ethernet layer source address
-     * @param macDest Ethernet layer destination address
-     * @param frameData Ethernet frame data
-     * @param frameLen Ethernet frame payload length
-     * @param etherType 16-bit ethernet type ID
-     * @param vlanId 16-bit VLAN ID
-     * @return True if packet should be sent, false if dropped or redirected
-     */
-    bool filterOutgoingPacket(
-        void* tPtr,
-        const bool noTee,
-        const Address& ztSource,
-        const Address& ztDest,
-        const MAC& macSource,
-        const MAC& macDest,
-        const uint8_t* frameData,
-        const unsigned int frameLen,
-        const unsigned int etherType,
-        const unsigned int vlanId,
-        uint8_t& qosBucket);
-
-    /**
-     * Apply filters to an incoming packet
-     *
-     * This applies filters from our network config and, if that doesn't match,
-     * the peer's capabilities in ascending order of capability ID. If there is
-     * a match certain actions may be taken such as sending a copy of the packet
-     * to a TEE or REDIRECT target.
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param sourcePeer Source Peer
-     * @param ztDest Destination ZeroTier address
-     * @param macSource Ethernet layer source address
-     * @param macDest Ethernet layer destination address
-     * @param frameData Ethernet frame data
-     * @param frameLen Ethernet frame payload length
-     * @param etherType 16-bit ethernet type ID
-     * @param vlanId 16-bit VLAN ID
-     * @return 0 == drop, 1 == accept, 2 == accept even if bridged
-     */
-    int filterIncomingPacket(
-        void* tPtr,
-        const SharedPtr<Peer>& sourcePeer,
-        const Address& ztDest,
-        const MAC& macSource,
-        const MAC& macDest,
-        const uint8_t* frameData,
-        const unsigned int frameLen,
-        const unsigned int etherType,
-        const unsigned int vlanId);
-
-    /**
-     * Check whether we are subscribed to a multicast group
-     *
-     * @param mg Multicast group
-     * @param includeBridgedGroups If true, also check groups we've learned via bridging
-     * @return True if this network endpoint / peer is a member
-     */
-    bool subscribedToMulticastGroup(const MulticastGroup& mg, bool includeBridgedGroups) const;
-
-    /**
-     * Subscribe to a multicast group
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param mg New multicast group
-     */
-    void multicastSubscribe(void* tPtr, const MulticastGroup& mg);
-
-    /**
-     * Unsubscribe from a multicast group
-     *
-     * @param mg Multicast group
-     */
-    void multicastUnsubscribe(const MulticastGroup& mg);
-
-    /**
-     * Handle an inbound network config chunk
-     *
-     * This is called from IncomingPacket to handle incoming network config
-     * chunks via OK(NETWORK_CONFIG_REQUEST) or NETWORK_CONFIG. It verifies
-     * each chunk and once assembled applies the configuration.
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param packetId Packet ID or 0 if none (e.g. via cluster path)
-     * @param source Address of sender of chunk or NULL if none (e.g. via cluster path)
-     * @param chunk Buffer containing chunk
-     * @param ptr Index of chunk and related fields in packet
-     * @return Update ID if update was fully assembled and accepted or 0 otherwise
-     */
-    uint64_t handleConfigChunk(void* tPtr, const uint64_t packetId, const Address& source, const Buffer<ZT_PROTO_MAX_PACKET_LENGTH>& chunk, unsigned int ptr);
-
-    /**
-     * Set network configuration
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param nconf Network configuration
-     * @param saveToDisk Save to disk? Used during loading, should usually be true otherwise.
-     * @return 0 == bad, 1 == accepted but duplicate/unchanged, 2 == accepted and new
-     */
-    int setConfiguration(void* tPtr, const NetworkConfig& nconf, bool saveToDisk);
-
-    /**
-     * Set netconf failure to 'access denied' -- called in IncomingPacket when controller reports this
-     */
-    inline void setAccessDenied(void* tPtr)
-    {
-        Mutex::Lock _l(_lock);
-        _netconfFailure = NETCONF_FAILURE_ACCESS_DENIED;
-
-        _sendUpdateEvent(tPtr);
-    }
-
-    /**
-     * Set netconf failure to 'not found' -- called by IncomingPacket when controller reports this
-     */
-    inline void setNotFound(void* tPtr)
-    {
-        Mutex::Lock _l(_lock);
-        _netconfFailure = NETCONF_FAILURE_NOT_FOUND;
-
-        _sendUpdateEvent(tPtr);
-    }
-
-    /**
-     * Set netconf failure to 'authentication required' possibly with an authorization URL
-     */
-    inline void setAuthenticationRequired(void* tPtr, const char* url)
-    {
-        Mutex::Lock _l(_lock);
-        _netconfFailure = NETCONF_FAILURE_AUTHENTICATION_REQUIRED;
-        _authenticationURL = (url) ? url : "";
-        _config.ssoEnabled = true;
-        _config.ssoVersion = 0;
-        _sendUpdateEvent(tPtr);
-    }
-
-    /**
-     * set netconf failure to 'authentication required' along with info needed
-     * for sso full flow authentication.
-     */
-    void setAuthenticationRequired(void* tPtr, const char* issuerURL, const char* centralEndpoint, const char* clientID, const char* ssoProvider, const char* nonce, const char* state);
-
-    /**
-     * Causes this network to request an updated configuration from its master node now
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     */
-    void requestConfiguration(void* tPtr);
-
-    /**
-     * Determine whether this peer is permitted to communicate on this network
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param peer Peer to check
-     */
-    bool gate(void* tPtr, const SharedPtr<Peer>& peer);
-
-    /**
-     * Check whether a given peer has recently had an association with this network
-     *
-     * This checks whether a peer has communicated with us recently about this
-     * network and has possessed a valid certificate of membership. This may return
-     * true even if the peer has been offline for a while or no longer has a valid
-     * certificate of membership but had one recently.
-     *
-     * @param addr Peer address
-     * @return True if peer has recently associated
-     */
-    bool recentlyAssociatedWith(const Address& addr);
-
-    /**
-     * Do periodic cleanup and housekeeping tasks
-     */
-    void clean();
-
-    /**
-     * Push state to members such as multicast group memberships and latest COM (if needed)
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     */
-    inline void sendUpdatesToMembers(void* tPtr)
-    {
-        Mutex::Lock _l(_lock);
-        _sendUpdatesToMembers(tPtr, (const MulticastGroup*)0);
-    }
-
-    /**
-     * Find the node on this network that has this MAC behind it (if any)
-     *
-     * @param mac MAC address
-     * @return ZeroTier address of bridge to this MAC
-     */
-    inline Address findBridgeTo(const MAC& mac) const
-    {
-        Mutex::Lock _l(_lock);
-        const Address* const br = _remoteBridgeRoutes.get(mac);
-        return ((br) ? *br : Address());
-    }
-
-    /**
-     * @return True if QoS is in effect for this network
-     */
-    inline bool qosEnabled()
-    {
-        return false;
-    }
-
-    /**
-     * Set a bridge route
-     *
-     * @param mac MAC address of destination
-     * @param addr Bridge this MAC is reachable behind
-     */
-    void learnBridgeRoute(const MAC& mac, const Address& addr);
-
-    /**
-     * Learn a multicast group that is bridged to our tap device
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param mg Multicast group
-     * @param now Current time
-     */
-    void learnBridgedMulticastGroup(void* tPtr, const MulticastGroup& mg, int64_t now);
-
-    /**
-     * Validate a credential and learn it if it passes certificate and other checks
-     */
-    Membership::AddCredentialResult addCredential(void* tPtr, const CertificateOfMembership& com);
-
-    /**
-     * Validate a credential and learn it if it passes certificate and other checks
-     */
-    inline Membership::AddCredentialResult addCredential(void* tPtr, const Capability& cap)
-    {
-        if (cap.networkId() != _id) {
-            return Membership::ADD_REJECTED;
-        }
-        Mutex::Lock _l(_lock);
-        return _membership(cap.issuedTo()).addCredential(RR, tPtr, _config, cap);
-    }
-
-    /**
-     * Validate a credential and learn it if it passes certificate and other checks
-     */
-    inline Membership::AddCredentialResult addCredential(void* tPtr, const Tag& tag)
-    {
-        if (tag.networkId() != _id) {
-            return Membership::ADD_REJECTED;
-        }
-        Mutex::Lock _l(_lock);
-        return _membership(tag.issuedTo()).addCredential(RR, tPtr, _config, tag);
-    }
-
-    /**
-     * Validate a credential and learn it if it passes certificate and other checks
-     */
-    Membership::AddCredentialResult addCredential(void* tPtr, const Address& sentFrom, const Revocation& rev);
-
-    /**
-     * Validate a credential and learn it if it passes certificate and other checks
-     */
-    inline Membership::AddCredentialResult addCredential(void* tPtr, const CertificateOfOwnership& coo)
-    {
-        if (coo.networkId() != _id) {
-            return Membership::ADD_REJECTED;
-        }
-        Mutex::Lock _l(_lock);
-        return _membership(coo.issuedTo()).addCredential(RR, tPtr, _config, coo);
-    }
-
-    /**
-     * Force push credentials (COM, etc.) to a peer now
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param to Destination peer address
-     * @param now Current time
-     */
-    inline void peerRequestedCredentials(void* tPtr, const Address& to, const int64_t now)
-    {
-        Mutex::Lock _l(_lock);
-        Membership& m = _membership(to);
-        const int64_t lastPushed = m.lastPushedCredentials();
-        if ((lastPushed < _lastConfigUpdate) || ((now - lastPushed) > ZT_PEER_CREDENTIALS_REQUEST_RATE_LIMIT)) {
-            m.pushCredentials(RR, tPtr, now, to, _config);
-        }
-    }
-
-    /**
-     * Push credentials if we haven't done so in a very long time
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param to Destination peer address
-     * @param now Current time
-     */
-    inline void pushCredentialsIfNeeded(void* tPtr, const Address& to, const int64_t now)
-    {
-        Mutex::Lock _l(_lock);
-        Membership& m = _membership(to);
-        const int64_t lastPushed = m.lastPushedCredentials();
-        if ((lastPushed < _lastConfigUpdate) || ((now - lastPushed) > ZT_PEER_ACTIVITY_TIMEOUT)) {
-            m.pushCredentials(RR, tPtr, now, to, _config);
-        }
-    }
-
-    /**
-     * Destroy this network
-     *
-     * This sets the network to completely remove itself on delete. This also prevents the
-     * call of the normal port shutdown event on delete.
-     */
-    void destroy();
-
-    /**
-     * Get this network's config for export via the ZT core API
-     *
-     * @param ec Buffer to fill with externally-visible network configuration
-     */
-    inline void externalConfig(ZT_VirtualNetworkConfig* ec) const
-    {
-        Mutex::Lock _l(_lock);
-        _externalConfig(ec);
-    }
-
-    /**
-     * @return Externally usable pointer-to-pointer exported via the core API
-     */
-    inline void** userPtr()
-    {
-        return &_uPtr;
-    }
+	/**
+	 * Broadcast multicast group: ff:ff:ff:ff:ff:ff / 0
+	 */
+	static const MulticastGroup BROADCAST;
+
+	/**
+	 * Compute primary controller device ID from network ID
+	 */
+	static inline Address controllerFor(uint64_t nwid)
+	{
+		return Address(nwid >> 24);
+	}
+
+	/**
+	 * Construct a new network
+	 *
+	 * Note that init() should be called immediately after the network is
+	 * constructed to actually configure the port.
+	 *
+	 * @param renv Runtime environment
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param nwid Network ID
+	 * @param uptr Arbitrary pointer used by externally-facing API (for user use)
+	 * @param nconf Network config, if known
+	 */
+	Network(const RuntimeEnvironment* renv, void* tPtr, uint64_t nwid, void* uptr, const NetworkConfig* nconf);
+
+	~Network();
+
+	inline uint64_t id() const
+	{
+		return _id;
+	}
+	inline Address controller() const
+	{
+		return Address(_id >> 24);
+	}
+	inline bool multicastEnabled() const
+	{
+		return (_config.multicastLimit > 0);
+	}
+	inline bool hasConfig() const
+	{
+		return (_config);
+	}
+	inline uint64_t lastConfigUpdate() const
+	{
+		return _lastConfigUpdate;
+	}
+	inline ZT_VirtualNetworkStatus status() const
+	{
+		Mutex::Lock _l(_lock);
+		return _status();
+	}
+	inline const NetworkConfig& config() const
+	{
+		return _config;
+	}
+	inline const MAC& mac() const
+	{
+		return _mac;
+	}
+
+	/**
+	 * Apply filters to an outgoing packet
+	 *
+	 * This applies filters from our network config and, if that doesn't match,
+	 * our capabilities in ascending order of capability ID. Additional actions
+	 * such as TEE may be taken, and credentials may be pushed, so this is not
+	 * side-effect-free. It's basically step one in sending something over VL2.
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param noTee If true, do not TEE anything anywhere (for two-pass filtering as done with multicast and bridging)
+	 * @param ztSource Source ZeroTier address
+	 * @param ztDest Destination ZeroTier address
+	 * @param macSource Ethernet layer source address
+	 * @param macDest Ethernet layer destination address
+	 * @param frameData Ethernet frame data
+	 * @param frameLen Ethernet frame payload length
+	 * @param etherType 16-bit ethernet type ID
+	 * @param vlanId 16-bit VLAN ID
+	 * @return True if packet should be sent, false if dropped or redirected
+	 */
+	bool filterOutgoingPacket(
+		void* tPtr,
+		const bool noTee,
+		const Address& ztSource,
+		const Address& ztDest,
+		const MAC& macSource,
+		const MAC& macDest,
+		const uint8_t* frameData,
+		const unsigned int frameLen,
+		const unsigned int etherType,
+		const unsigned int vlanId,
+		uint8_t& qosBucket);
+
+	/**
+	 * Apply filters to an incoming packet
+	 *
+	 * This applies filters from our network config and, if that doesn't match,
+	 * the peer's capabilities in ascending order of capability ID. If there is
+	 * a match certain actions may be taken such as sending a copy of the packet
+	 * to a TEE or REDIRECT target.
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param sourcePeer Source Peer
+	 * @param ztDest Destination ZeroTier address
+	 * @param macSource Ethernet layer source address
+	 * @param macDest Ethernet layer destination address
+	 * @param frameData Ethernet frame data
+	 * @param frameLen Ethernet frame payload length
+	 * @param etherType 16-bit ethernet type ID
+	 * @param vlanId 16-bit VLAN ID
+	 * @return 0 == drop, 1 == accept, 2 == accept even if bridged
+	 */
+	int filterIncomingPacket(
+		void* tPtr,
+		const SharedPtr<Peer>& sourcePeer,
+		const Address& ztDest,
+		const MAC& macSource,
+		const MAC& macDest,
+		const uint8_t* frameData,
+		const unsigned int frameLen,
+		const unsigned int etherType,
+		const unsigned int vlanId);
+
+	/**
+	 * Check whether we are subscribed to a multicast group
+	 *
+	 * @param mg Multicast group
+	 * @param includeBridgedGroups If true, also check groups we've learned via bridging
+	 * @return True if this network endpoint / peer is a member
+	 */
+	bool subscribedToMulticastGroup(const MulticastGroup& mg, bool includeBridgedGroups) const;
+
+	/**
+	 * Subscribe to a multicast group
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param mg New multicast group
+	 */
+	void multicastSubscribe(void* tPtr, const MulticastGroup& mg);
+
+	/**
+	 * Unsubscribe from a multicast group
+	 *
+	 * @param mg Multicast group
+	 */
+	void multicastUnsubscribe(const MulticastGroup& mg);
+
+	/**
+	 * Handle an inbound network config chunk
+	 *
+	 * This is called from IncomingPacket to handle incoming network config
+	 * chunks via OK(NETWORK_CONFIG_REQUEST) or NETWORK_CONFIG. It verifies
+	 * each chunk and once assembled applies the configuration.
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param packetId Packet ID or 0 if none (e.g. via cluster path)
+	 * @param source Address of sender of chunk or NULL if none (e.g. via cluster path)
+	 * @param chunk Buffer containing chunk
+	 * @param ptr Index of chunk and related fields in packet
+	 * @return Update ID if update was fully assembled and accepted or 0 otherwise
+	 */
+	uint64_t handleConfigChunk(void* tPtr, const uint64_t packetId, const Address& source, const Buffer<ZT_PROTO_MAX_PACKET_LENGTH>& chunk, unsigned int ptr);
+
+	/**
+	 * Set network configuration
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param nconf Network configuration
+	 * @param saveToDisk Save to disk? Used during loading, should usually be true otherwise.
+	 * @return 0 == bad, 1 == accepted but duplicate/unchanged, 2 == accepted and new
+	 */
+	int setConfiguration(void* tPtr, const NetworkConfig& nconf, bool saveToDisk);
+
+	/**
+	 * Set netconf failure to 'access denied' -- called in IncomingPacket when controller reports this
+	 */
+	inline void setAccessDenied(void* tPtr)
+	{
+		Mutex::Lock _l(_lock);
+		_netconfFailure = NETCONF_FAILURE_ACCESS_DENIED;
+
+		_sendUpdateEvent(tPtr);
+	}
+
+	/**
+	 * Set netconf failure to 'not found' -- called by IncomingPacket when controller reports this
+	 */
+	inline void setNotFound(void* tPtr)
+	{
+		Mutex::Lock _l(_lock);
+		_netconfFailure = NETCONF_FAILURE_NOT_FOUND;
+
+		_sendUpdateEvent(tPtr);
+	}
+
+	/**
+	 * Set netconf failure to 'authentication required' possibly with an authorization URL
+	 */
+	inline void setAuthenticationRequired(void* tPtr, const char* url)
+	{
+		Mutex::Lock _l(_lock);
+		_netconfFailure = NETCONF_FAILURE_AUTHENTICATION_REQUIRED;
+		_authenticationURL = (url) ? url : "";
+		_config.ssoEnabled = true;
+		_config.ssoVersion = 0;
+		_sendUpdateEvent(tPtr);
+	}
+
+	/**
+	 * set netconf failure to 'authentication required' along with info needed
+	 * for sso full flow authentication.
+	 */
+	void setAuthenticationRequired(void* tPtr, const char* issuerURL, const char* centralEndpoint, const char* clientID, const char* ssoProvider, const char* nonce, const char* state);
+
+	/**
+	 * Causes this network to request an updated configuration from its master node now
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 */
+	void requestConfiguration(void* tPtr);
+
+	/**
+	 * Determine whether this peer is permitted to communicate on this network
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param peer Peer to check
+	 */
+	bool gate(void* tPtr, const SharedPtr<Peer>& peer);
+
+	/**
+	 * Check whether a given peer has recently had an association with this network
+	 *
+	 * This checks whether a peer has communicated with us recently about this
+	 * network and has possessed a valid certificate of membership. This may return
+	 * true even if the peer has been offline for a while or no longer has a valid
+	 * certificate of membership but had one recently.
+	 *
+	 * @param addr Peer address
+	 * @return True if peer has recently associated
+	 */
+	bool recentlyAssociatedWith(const Address& addr);
+
+	/**
+	 * Do periodic cleanup and housekeeping tasks
+	 */
+	void clean();
+
+	/**
+	 * Push state to members such as multicast group memberships and latest COM (if needed)
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 */
+	inline void sendUpdatesToMembers(void* tPtr)
+	{
+		Mutex::Lock _l(_lock);
+		_sendUpdatesToMembers(tPtr, (const MulticastGroup*)0);
+	}
+
+	/**
+	 * Find the node on this network that has this MAC behind it (if any)
+	 *
+	 * @param mac MAC address
+	 * @return ZeroTier address of bridge to this MAC
+	 */
+	inline Address findBridgeTo(const MAC& mac) const
+	{
+		Mutex::Lock _l(_lock);
+		const Address* const br = _remoteBridgeRoutes.get(mac);
+		return ((br) ? *br : Address());
+	}
+
+	/**
+	 * @return True if QoS is in effect for this network
+	 */
+	inline bool qosEnabled()
+	{
+		return false;
+	}
+
+	/**
+	 * Set a bridge route
+	 *
+	 * @param mac MAC address of destination
+	 * @param addr Bridge this MAC is reachable behind
+	 */
+	void learnBridgeRoute(const MAC& mac, const Address& addr);
+
+	/**
+	 * Learn a multicast group that is bridged to our tap device
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param mg Multicast group
+	 * @param now Current time
+	 */
+	void learnBridgedMulticastGroup(void* tPtr, const MulticastGroup& mg, int64_t now);
+
+	/**
+	 * Validate a credential and learn it if it passes certificate and other checks
+	 */
+	Membership::AddCredentialResult addCredential(void* tPtr, const CertificateOfMembership& com);
+
+	/**
+	 * Validate a credential and learn it if it passes certificate and other checks
+	 */
+	inline Membership::AddCredentialResult addCredential(void* tPtr, const Capability& cap)
+	{
+		if (cap.networkId() != _id) {
+			return Membership::ADD_REJECTED;
+		}
+		Mutex::Lock _l(_lock);
+		return _membership(cap.issuedTo()).addCredential(RR, tPtr, _config, cap);
+	}
+
+	/**
+	 * Validate a credential and learn it if it passes certificate and other checks
+	 */
+	inline Membership::AddCredentialResult addCredential(void* tPtr, const Tag& tag)
+	{
+		if (tag.networkId() != _id) {
+			return Membership::ADD_REJECTED;
+		}
+		Mutex::Lock _l(_lock);
+		return _membership(tag.issuedTo()).addCredential(RR, tPtr, _config, tag);
+	}
+
+	/**
+	 * Validate a credential and learn it if it passes certificate and other checks
+	 */
+	Membership::AddCredentialResult addCredential(void* tPtr, const Address& sentFrom, const Revocation& rev);
+
+	/**
+	 * Validate a credential and learn it if it passes certificate and other checks
+	 */
+	inline Membership::AddCredentialResult addCredential(void* tPtr, const CertificateOfOwnership& coo)
+	{
+		if (coo.networkId() != _id) {
+			return Membership::ADD_REJECTED;
+		}
+		Mutex::Lock _l(_lock);
+		return _membership(coo.issuedTo()).addCredential(RR, tPtr, _config, coo);
+	}
+
+	/**
+	 * Force push credentials (COM, etc.) to a peer now
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param to Destination peer address
+	 * @param now Current time
+	 */
+	inline void peerRequestedCredentials(void* tPtr, const Address& to, const int64_t now)
+	{
+		Mutex::Lock _l(_lock);
+		Membership& m = _membership(to);
+		const int64_t lastPushed = m.lastPushedCredentials();
+		if ((lastPushed < _lastConfigUpdate) || ((now - lastPushed) > ZT_PEER_CREDENTIALS_REQUEST_RATE_LIMIT)) {
+			m.pushCredentials(RR, tPtr, now, to, _config);
+		}
+	}
+
+	/**
+	 * Push credentials if we haven't done so in a very long time
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param to Destination peer address
+	 * @param now Current time
+	 */
+	inline void pushCredentialsIfNeeded(void* tPtr, const Address& to, const int64_t now)
+	{
+		Mutex::Lock _l(_lock);
+		Membership& m = _membership(to);
+		const int64_t lastPushed = m.lastPushedCredentials();
+		if ((lastPushed < _lastConfigUpdate) || ((now - lastPushed) > ZT_PEER_ACTIVITY_TIMEOUT)) {
+			m.pushCredentials(RR, tPtr, now, to, _config);
+		}
+	}
+
+	/**
+	 * Destroy this network
+	 *
+	 * This sets the network to completely remove itself on delete. This also prevents the
+	 * call of the normal port shutdown event on delete.
+	 */
+	void destroy();
+
+	/**
+	 * Get this network's config for export via the ZT core API
+	 *
+	 * @param ec Buffer to fill with externally-visible network configuration
+	 */
+	inline void externalConfig(ZT_VirtualNetworkConfig* ec) const
+	{
+		Mutex::Lock _l(_lock);
+		_externalConfig(ec);
+	}
+
+	/**
+	 * @return Externally usable pointer-to-pointer exported via the core API
+	 */
+	inline void** userPtr()
+	{
+		return &_uPtr;
+	}
 
   private:
-    ZT_VirtualNetworkStatus _status() const;
-    void _externalConfig(ZT_VirtualNetworkConfig* ec) const;   // assumes _lock is locked
-    bool _gate(const SharedPtr<Peer>& peer);
-    void _sendUpdatesToMembers(void* tPtr, const MulticastGroup* const newMulticastGroup);
-    void _announceMulticastGroupsTo(void* tPtr, const Address& peer, const std::vector<MulticastGroup>& allMulticastGroups);
-    std::vector<MulticastGroup> _allMulticastGroups() const;
-    Membership& _membership(const Address& a);
-    void _sendUpdateEvent(void* tPtr);
-
-    const RuntimeEnvironment* const RR;
-    void* _uPtr;
-    const uint64_t _id;
-    std::string _nwidStr;
-    uint64_t _lastAnnouncedMulticastGroupsUpstream;
-    MAC _mac;   // local MAC address
-    bool _portInitialized;
-
-    std::vector<MulticastGroup> _myMulticastGroups;                 // multicast groups that we belong to (according to tap)
-    Hashtable<MulticastGroup, uint64_t> _multicastGroupsBehindMe;   // multicast groups that seem to be behind us and when we last saw them (if we are a bridge)
-    Hashtable<MAC, Address> _remoteBridgeRoutes;                    // remote addresses where given MACs are reachable (for tracking devices behind remote bridges)
-
-    NetworkConfig _config;
-    int64_t _lastConfigUpdate;
-
-    struct _IncomingConfigChunk {
-        _IncomingConfigChunk()
-        {
-            memset(this, 0, sizeof(_IncomingConfigChunk));
-        }
-        uint64_t ts;
-        uint64_t updateId;
-        uint64_t haveChunkIds[ZT_NETWORK_MAX_UPDATE_CHUNKS];
-        unsigned long haveChunks;
-        unsigned long haveBytes;
-        Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> data;
-    };
-    _IncomingConfigChunk _incomingConfigChunks[ZT_NETWORK_MAX_INCOMING_UPDATES];
-
-    bool _destroyed;
-
-    enum { NETCONF_FAILURE_NONE, NETCONF_FAILURE_ACCESS_DENIED, NETCONF_FAILURE_NOT_FOUND, NETCONF_FAILURE_INIT_FAILED, NETCONF_FAILURE_AUTHENTICATION_REQUIRED } _netconfFailure;
-    int _portError;   // return value from port config callback
-    std::string _authenticationURL;
-
-    Hashtable<Address, Membership> _memberships;
-
-    Mutex _lock;
-
-    AtomicCounter __refCount;
-
-    prometheus::simpleapi::gauge_metric_t _num_multicast_groups;
-    prometheus::simpleapi::counter_metric_t _incoming_packets_accepted;
-    prometheus::simpleapi::counter_metric_t _incoming_packets_dropped;
-    prometheus::simpleapi::counter_metric_t _outgoing_packets_accepted;
-    prometheus::simpleapi::counter_metric_t _outgoing_packets_dropped;
+	ZT_VirtualNetworkStatus _status() const;
+	void _externalConfig(ZT_VirtualNetworkConfig* ec) const;   // assumes _lock is locked
+	bool _gate(const SharedPtr<Peer>& peer);
+	void _sendUpdatesToMembers(void* tPtr, const MulticastGroup* const newMulticastGroup);
+	void _announceMulticastGroupsTo(void* tPtr, const Address& peer, const std::vector<MulticastGroup>& allMulticastGroups);
+	std::vector<MulticastGroup> _allMulticastGroups() const;
+	Membership& _membership(const Address& a);
+	void _sendUpdateEvent(void* tPtr);
+
+	const RuntimeEnvironment* const RR;
+	void* _uPtr;
+	const uint64_t _id;
+	std::string _nwidStr;
+	uint64_t _lastAnnouncedMulticastGroupsUpstream;
+	MAC _mac;	// local MAC address
+	bool _portInitialized;
+
+	std::vector<MulticastGroup> _myMulticastGroups;					// multicast groups that we belong to (according to tap)
+	Hashtable<MulticastGroup, uint64_t> _multicastGroupsBehindMe;	// multicast groups that seem to be behind us and when we last saw them (if we are a bridge)
+	Hashtable<MAC, Address> _remoteBridgeRoutes;					// remote addresses where given MACs are reachable (for tracking devices behind remote bridges)
+
+	NetworkConfig _config;
+	int64_t _lastConfigUpdate;
+
+	struct _IncomingConfigChunk {
+		_IncomingConfigChunk()
+		{
+			memset(this, 0, sizeof(_IncomingConfigChunk));
+		}
+		uint64_t ts;
+		uint64_t updateId;
+		uint64_t haveChunkIds[ZT_NETWORK_MAX_UPDATE_CHUNKS];
+		unsigned long haveChunks;
+		unsigned long haveBytes;
+		Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> data;
+	};
+	_IncomingConfigChunk _incomingConfigChunks[ZT_NETWORK_MAX_INCOMING_UPDATES];
+
+	bool _destroyed;
+
+	enum { NETCONF_FAILURE_NONE, NETCONF_FAILURE_ACCESS_DENIED, NETCONF_FAILURE_NOT_FOUND, NETCONF_FAILURE_INIT_FAILED, NETCONF_FAILURE_AUTHENTICATION_REQUIRED } _netconfFailure;
+	int _portError;	  // return value from port config callback
+	std::string _authenticationURL;
+
+	Hashtable<Address, Membership> _memberships;
+
+	Mutex _lock;
+
+	AtomicCounter __refCount;
+
+	prometheus::simpleapi::gauge_metric_t _num_multicast_groups;
+	prometheus::simpleapi::counter_metric_t _incoming_packets_accepted;
+	prometheus::simpleapi::counter_metric_t _incoming_packets_dropped;
+	prometheus::simpleapi::counter_metric_t _outgoing_packets_accepted;
+	prometheus::simpleapi::counter_metric_t _outgoing_packets_dropped;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 561 - 561
node/NetworkConfig.cpp

@@ -20,574 +20,574 @@ namespace ZeroTier {
 
 bool NetworkConfig::toDictionary(Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>& d, bool includeLegacy) const
 {
-    Buffer<ZT_NETWORKCONFIG_DICT_CAPACITY>* tmp = new Buffer<ZT_NETWORKCONFIG_DICT_CAPACITY>();
-    char tmp2[128] = { 0 };
-
-    try {
-        d.clear();
-
-        // Try to put the more human-readable fields first
-
-        if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_VERSION, (uint64_t)ZT_NETWORKCONFIG_VERSION)) {
-            delete tmp;
-            return false;
-        }
-        if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID, this->networkId)) {
-            delete tmp;
-            return false;
-        }
-        if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP, this->timestamp)) {
-            delete tmp;
-            return false;
-        }
-        if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_CREDENTIAL_TIME_MAX_DELTA, this->credentialTimeMaxDelta)) {
-            delete tmp;
-            return false;
-        }
-        if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_REVISION, this->revision)) {
-            delete tmp;
-            return false;
-        }
-        if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO, this->issuedTo.toString(tmp2))) {
-            delete tmp;
-            return false;
-        }
-        if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_REMOTE_TRACE_TARGET, this->remoteTraceTarget.toString(tmp2))) {
-            delete tmp;
-            return false;
-        }
-        if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_REMOTE_TRACE_LEVEL, (uint64_t)this->remoteTraceLevel)) {
-            delete tmp;
-            return false;
-        }
-        if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_FLAGS, this->flags)) {
-            delete tmp;
-            return false;
-        }
-        if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT, (uint64_t)this->multicastLimit)) {
-            delete tmp;
-            return false;
-        }
-        if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_TYPE, (uint64_t)this->type)) {
-            delete tmp;
-            return false;
-        }
-        if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_NAME, this->name)) {
-            delete tmp;
-            return false;
-        }
-        if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_MTU, (uint64_t)this->mtu)) {
-            delete tmp;
-            return false;
-        }
+	Buffer<ZT_NETWORKCONFIG_DICT_CAPACITY>* tmp = new Buffer<ZT_NETWORKCONFIG_DICT_CAPACITY>();
+	char tmp2[128] = { 0 };
+
+	try {
+		d.clear();
+
+		// Try to put the more human-readable fields first
+
+		if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_VERSION, (uint64_t)ZT_NETWORKCONFIG_VERSION)) {
+			delete tmp;
+			return false;
+		}
+		if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID, this->networkId)) {
+			delete tmp;
+			return false;
+		}
+		if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP, this->timestamp)) {
+			delete tmp;
+			return false;
+		}
+		if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_CREDENTIAL_TIME_MAX_DELTA, this->credentialTimeMaxDelta)) {
+			delete tmp;
+			return false;
+		}
+		if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_REVISION, this->revision)) {
+			delete tmp;
+			return false;
+		}
+		if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO, this->issuedTo.toString(tmp2))) {
+			delete tmp;
+			return false;
+		}
+		if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_REMOTE_TRACE_TARGET, this->remoteTraceTarget.toString(tmp2))) {
+			delete tmp;
+			return false;
+		}
+		if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_REMOTE_TRACE_LEVEL, (uint64_t)this->remoteTraceLevel)) {
+			delete tmp;
+			return false;
+		}
+		if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_FLAGS, this->flags)) {
+			delete tmp;
+			return false;
+		}
+		if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT, (uint64_t)this->multicastLimit)) {
+			delete tmp;
+			return false;
+		}
+		if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_TYPE, (uint64_t)this->type)) {
+			delete tmp;
+			return false;
+		}
+		if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_NAME, this->name)) {
+			delete tmp;
+			return false;
+		}
+		if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_MTU, (uint64_t)this->mtu)) {
+			delete tmp;
+			return false;
+		}
 
 #ifdef ZT_SUPPORT_OLD_STYLE_NETCONF
-        if (includeLegacy) {
-            if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST_OLD, this->enableBroadcast())) {
-                return false;
-            }
-            if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_PRIVATE_OLD, this->isPrivate())) {
-                return false;
-            }
-
-            std::string v4s;
-            for (unsigned int i = 0; i < staticIpCount; ++i) {
-                if (this->staticIps[i].ss_family == AF_INET) {
-                    if (v4s.length() > 0) {
-                        v4s.push_back(',');
-                    }
-                    char buf[64];
-                    v4s.append(this->staticIps[i].toString(buf));
-                }
-            }
-            if (v4s.length() > 0) {
-                if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC_OLD, v4s.c_str())) {
-                    return false;
-                }
-            }
-            std::string v6s;
-            for (unsigned int i = 0; i < staticIpCount; ++i) {
-                if (this->staticIps[i].ss_family == AF_INET6) {
-                    if (v6s.length() > 0) {
-                        v6s.push_back(',');
-                    }
-                    char buf[64];
-                    v6s.append(this->staticIps[i].toString(buf));
-                }
-            }
-            if (v6s.length() > 0) {
-                if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC_OLD, v6s.c_str())) {
-                    return false;
-                }
-            }
-
-            std::string ets;
-            unsigned int et = 0;
-            ZT_VirtualNetworkRuleType lastrt = ZT_NETWORK_RULE_ACTION_ACCEPT;
-            for (unsigned int i = 0; i < ruleCount; ++i) {
-                ZT_VirtualNetworkRuleType rt = (ZT_VirtualNetworkRuleType)(rules[i].t & 0x7f);
-                if (rt == ZT_NETWORK_RULE_MATCH_ETHERTYPE) {
-                    et = rules[i].v.etherType;
-                }
-                else if (rt == ZT_NETWORK_RULE_ACTION_ACCEPT) {
-                    if (((int)lastrt < 32) || (lastrt == ZT_NETWORK_RULE_MATCH_ETHERTYPE)) {
-                        if (ets.length() > 0) {
-                            ets.push_back(',');
-                        }
-                        char tmp2[16] = { 0 };
-                        ets.append(Utils::hex((uint16_t)et, tmp2));
-                    }
-                    et = 0;
-                }
-                lastrt = rt;
-            }
-            if (ets.length() > 0) {
-                if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES_OLD, ets.c_str())) {
-                    return false;
-                }
-            }
-
-            if (this->com) {
-                if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP_OLD, this->com.toString().c_str())) {
-                    return false;
-                }
-            }
-
-            std::string ab;
-            for (unsigned int i = 0; i < this->specialistCount; ++i) {
-                if ((this->specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE) != 0) {
-                    if (ab.length() > 0) {
-                        ab.push_back(',');
-                    }
-                    char tmp2[16] = { 0 };
-                    ab.append(Address(this->specialists[i]).toString(tmp2));
-                }
-            }
-            if (ab.length() > 0) {
-                if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES_OLD, ab.c_str())) {
-                    return false;
-                }
-            }
-        }
-#endif   // ZT_SUPPORT_OLD_STYLE_NETCONF
-
-        // Then add binary blobs
-
-        if (this->com) {
-            tmp->clear();
-            this->com.serialize(*tmp);
-            if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_COM, *tmp)) {
-                return false;
-            }
-        }
-
-        tmp->clear();
-        for (unsigned int i = 0; i < this->capabilityCount; ++i) {
-            this->capabilities[i].serialize(*tmp);
-        }
-        if (tmp->size()) {
-            if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_CAPABILITIES, *tmp)) {
-                return false;
-            }
-        }
-
-        tmp->clear();
-        for (unsigned int i = 0; i < this->tagCount; ++i) {
-            this->tags[i].serialize(*tmp);
-        }
-        if (tmp->size()) {
-            if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_TAGS, *tmp)) {
-                return false;
-            }
-        }
-
-        tmp->clear();
-        for (unsigned int i = 0; i < this->certificateOfOwnershipCount; ++i) {
-            this->certificatesOfOwnership[i].serialize(*tmp);
-        }
-        if (tmp->size()) {
-            if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATES_OF_OWNERSHIP, *tmp)) {
-                return false;
-            }
-        }
-
-        tmp->clear();
-        for (unsigned int i = 0; i < this->specialistCount; ++i) {
-            tmp->append((uint64_t)this->specialists[i]);
-        }
-        if (tmp->size()) {
-            if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_SPECIALISTS, *tmp)) {
-                return false;
-            }
-        }
-
-        tmp->clear();
-        for (unsigned int i = 0; i < this->routeCount; ++i) {
-            reinterpret_cast<const InetAddress*>(&(this->routes[i].target))->serialize(*tmp);
-            reinterpret_cast<const InetAddress*>(&(this->routes[i].via))->serialize(*tmp);
-            tmp->append((uint16_t)this->routes[i].flags);
-            tmp->append((uint16_t)this->routes[i].metric);
-        }
-        if (tmp->size()) {
-            if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_ROUTES, *tmp)) {
-                return false;
-            }
-        }
-
-        tmp->clear();
-        for (unsigned int i = 0; i < this->staticIpCount; ++i) {
-            this->staticIps[i].serialize(*tmp);
-        }
-        if (tmp->size()) {
-            if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_STATIC_IPS, *tmp)) {
-                return false;
-            }
-        }
-
-        if (this->ruleCount) {
-            tmp->clear();
-            Capability::serializeRules(*tmp, rules, ruleCount);
-            if (tmp->size()) {
-                if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_RULES, *tmp)) {
-                    return false;
-                }
-            }
-        }
-
-        tmp->clear();
-        DNS::serializeDNS(*tmp, &dns);
-        if (tmp->size()) {
-            if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_DNS, *tmp)) {
-                return false;
-            }
-        }
-
-        if (this->ssoVersion == 0) {
-            if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_VERSION, this->ssoVersion)) {
-                return false;
-            }
-            if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_ENABLED, this->ssoEnabled)) {
-                return false;
-            }
-
-            if (this->ssoEnabled) {
-                if (this->authenticationURL[0]) {
-                    if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL)) {
-                        return false;
-                    }
-                }
-                if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_EXPIRY_TIME, this->authenticationExpiryTime)) {
-                    return false;
-                }
-            }
-        }
-        else if (this->ssoVersion == 1) {
-            if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_VERSION, this->ssoVersion)) {
-                return false;
-            }
-            if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_ENABLED, this->ssoEnabled)) {
-                return false;
-            }
-            // if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL)) return false;
-            if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_ISSUER_URL, this->issuerURL)) {
-                return false;
-            }
-            if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_CENTRAL_ENDPOINT_URL, this->centralAuthURL)) {
-                return false;
-            }
-            if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_NONCE, this->ssoNonce)) {
-                return false;
-            }
-            if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_STATE, this->ssoState)) {
-                return false;
-            }
-            if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_CLIENT_ID, this->ssoClientID)) {
-                return false;
-            }
-            if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_PROVIDER, this->ssoProvider)) {
-                return false;
-            }
-        }
-
-        delete tmp;
-    }
-    catch (...) {
-        delete tmp;
-        throw;
-    }
-
-    return true;
+		if (includeLegacy) {
+			if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST_OLD, this->enableBroadcast())) {
+				return false;
+			}
+			if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_PRIVATE_OLD, this->isPrivate())) {
+				return false;
+			}
+
+			std::string v4s;
+			for (unsigned int i = 0; i < staticIpCount; ++i) {
+				if (this->staticIps[i].ss_family == AF_INET) {
+					if (v4s.length() > 0) {
+						v4s.push_back(',');
+					}
+					char buf[64];
+					v4s.append(this->staticIps[i].toString(buf));
+				}
+			}
+			if (v4s.length() > 0) {
+				if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC_OLD, v4s.c_str())) {
+					return false;
+				}
+			}
+			std::string v6s;
+			for (unsigned int i = 0; i < staticIpCount; ++i) {
+				if (this->staticIps[i].ss_family == AF_INET6) {
+					if (v6s.length() > 0) {
+						v6s.push_back(',');
+					}
+					char buf[64];
+					v6s.append(this->staticIps[i].toString(buf));
+				}
+			}
+			if (v6s.length() > 0) {
+				if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC_OLD, v6s.c_str())) {
+					return false;
+				}
+			}
+
+			std::string ets;
+			unsigned int et = 0;
+			ZT_VirtualNetworkRuleType lastrt = ZT_NETWORK_RULE_ACTION_ACCEPT;
+			for (unsigned int i = 0; i < ruleCount; ++i) {
+				ZT_VirtualNetworkRuleType rt = (ZT_VirtualNetworkRuleType)(rules[i].t & 0x7f);
+				if (rt == ZT_NETWORK_RULE_MATCH_ETHERTYPE) {
+					et = rules[i].v.etherType;
+				}
+				else if (rt == ZT_NETWORK_RULE_ACTION_ACCEPT) {
+					if (((int)lastrt < 32) || (lastrt == ZT_NETWORK_RULE_MATCH_ETHERTYPE)) {
+						if (ets.length() > 0) {
+							ets.push_back(',');
+						}
+						char tmp2[16] = { 0 };
+						ets.append(Utils::hex((uint16_t)et, tmp2));
+					}
+					et = 0;
+				}
+				lastrt = rt;
+			}
+			if (ets.length() > 0) {
+				if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES_OLD, ets.c_str())) {
+					return false;
+				}
+			}
+
+			if (this->com) {
+				if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP_OLD, this->com.toString().c_str())) {
+					return false;
+				}
+			}
+
+			std::string ab;
+			for (unsigned int i = 0; i < this->specialistCount; ++i) {
+				if ((this->specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE) != 0) {
+					if (ab.length() > 0) {
+						ab.push_back(',');
+					}
+					char tmp2[16] = { 0 };
+					ab.append(Address(this->specialists[i]).toString(tmp2));
+				}
+			}
+			if (ab.length() > 0) {
+				if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES_OLD, ab.c_str())) {
+					return false;
+				}
+			}
+		}
+#endif	 // ZT_SUPPORT_OLD_STYLE_NETCONF
+
+		// Then add binary blobs
+
+		if (this->com) {
+			tmp->clear();
+			this->com.serialize(*tmp);
+			if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_COM, *tmp)) {
+				return false;
+			}
+		}
+
+		tmp->clear();
+		for (unsigned int i = 0; i < this->capabilityCount; ++i) {
+			this->capabilities[i].serialize(*tmp);
+		}
+		if (tmp->size()) {
+			if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_CAPABILITIES, *tmp)) {
+				return false;
+			}
+		}
+
+		tmp->clear();
+		for (unsigned int i = 0; i < this->tagCount; ++i) {
+			this->tags[i].serialize(*tmp);
+		}
+		if (tmp->size()) {
+			if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_TAGS, *tmp)) {
+				return false;
+			}
+		}
+
+		tmp->clear();
+		for (unsigned int i = 0; i < this->certificateOfOwnershipCount; ++i) {
+			this->certificatesOfOwnership[i].serialize(*tmp);
+		}
+		if (tmp->size()) {
+			if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATES_OF_OWNERSHIP, *tmp)) {
+				return false;
+			}
+		}
+
+		tmp->clear();
+		for (unsigned int i = 0; i < this->specialistCount; ++i) {
+			tmp->append((uint64_t)this->specialists[i]);
+		}
+		if (tmp->size()) {
+			if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_SPECIALISTS, *tmp)) {
+				return false;
+			}
+		}
+
+		tmp->clear();
+		for (unsigned int i = 0; i < this->routeCount; ++i) {
+			reinterpret_cast<const InetAddress*>(&(this->routes[i].target))->serialize(*tmp);
+			reinterpret_cast<const InetAddress*>(&(this->routes[i].via))->serialize(*tmp);
+			tmp->append((uint16_t)this->routes[i].flags);
+			tmp->append((uint16_t)this->routes[i].metric);
+		}
+		if (tmp->size()) {
+			if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_ROUTES, *tmp)) {
+				return false;
+			}
+		}
+
+		tmp->clear();
+		for (unsigned int i = 0; i < this->staticIpCount; ++i) {
+			this->staticIps[i].serialize(*tmp);
+		}
+		if (tmp->size()) {
+			if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_STATIC_IPS, *tmp)) {
+				return false;
+			}
+		}
+
+		if (this->ruleCount) {
+			tmp->clear();
+			Capability::serializeRules(*tmp, rules, ruleCount);
+			if (tmp->size()) {
+				if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_RULES, *tmp)) {
+					return false;
+				}
+			}
+		}
+
+		tmp->clear();
+		DNS::serializeDNS(*tmp, &dns);
+		if (tmp->size()) {
+			if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_DNS, *tmp)) {
+				return false;
+			}
+		}
+
+		if (this->ssoVersion == 0) {
+			if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_VERSION, this->ssoVersion)) {
+				return false;
+			}
+			if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_ENABLED, this->ssoEnabled)) {
+				return false;
+			}
+
+			if (this->ssoEnabled) {
+				if (this->authenticationURL[0]) {
+					if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL)) {
+						return false;
+					}
+				}
+				if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_EXPIRY_TIME, this->authenticationExpiryTime)) {
+					return false;
+				}
+			}
+		}
+		else if (this->ssoVersion == 1) {
+			if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_VERSION, this->ssoVersion)) {
+				return false;
+			}
+			if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_ENABLED, this->ssoEnabled)) {
+				return false;
+			}
+			// if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL)) return false;
+			if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_ISSUER_URL, this->issuerURL)) {
+				return false;
+			}
+			if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_CENTRAL_ENDPOINT_URL, this->centralAuthURL)) {
+				return false;
+			}
+			if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_NONCE, this->ssoNonce)) {
+				return false;
+			}
+			if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_STATE, this->ssoState)) {
+				return false;
+			}
+			if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_CLIENT_ID, this->ssoClientID)) {
+				return false;
+			}
+			if (! d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_PROVIDER, this->ssoProvider)) {
+				return false;
+			}
+		}
+
+		delete tmp;
+	}
+	catch (...) {
+		delete tmp;
+		throw;
+	}
+
+	return true;
 }
 
 bool NetworkConfig::fromDictionary(const Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>& d)
 {
-    static const NetworkConfig NIL_NC;
-    Buffer<ZT_NETWORKCONFIG_DICT_CAPACITY>* tmp = new Buffer<ZT_NETWORKCONFIG_DICT_CAPACITY>();
-
-    try {
-        *this = NIL_NC;
-
-        // Fields that are always present, new or old
-        this->networkId = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID, 0);
-        if (! this->networkId) {
-            delete tmp;
-            return false;
-        }
-        this->timestamp = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP, 0);
-        this->credentialTimeMaxDelta = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_CREDENTIAL_TIME_MAX_DELTA, 0);
-        this->revision = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_REVISION, 0);
-        this->issuedTo = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO, 0);
-        if (! this->issuedTo) {
-            delete tmp;
-            return false;
-        }
-        this->remoteTraceTarget = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_REMOTE_TRACE_TARGET);
-        this->remoteTraceLevel = (Trace::Level)d.getUI(ZT_NETWORKCONFIG_DICT_KEY_REMOTE_TRACE_LEVEL);
-        this->multicastLimit = (unsigned int)d.getUI(ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT, 0);
-        d.get(ZT_NETWORKCONFIG_DICT_KEY_NAME, this->name, sizeof(this->name));
-
-        this->mtu = (unsigned int)d.getUI(ZT_NETWORKCONFIG_DICT_KEY_MTU, ZT_DEFAULT_MTU);
-        if (this->mtu < 1280) {
-            this->mtu = 1280;   // minimum MTU allowed by IPv6 standard and others
-        }
-        else if (this->mtu > ZT_MAX_MTU) {
-            this->mtu = ZT_MAX_MTU;
-        }
-
-        if (d.getUI(ZT_NETWORKCONFIG_DICT_KEY_VERSION, 0) < 6) {
+	static const NetworkConfig NIL_NC;
+	Buffer<ZT_NETWORKCONFIG_DICT_CAPACITY>* tmp = new Buffer<ZT_NETWORKCONFIG_DICT_CAPACITY>();
+
+	try {
+		*this = NIL_NC;
+
+		// Fields that are always present, new or old
+		this->networkId = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID, 0);
+		if (! this->networkId) {
+			delete tmp;
+			return false;
+		}
+		this->timestamp = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP, 0);
+		this->credentialTimeMaxDelta = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_CREDENTIAL_TIME_MAX_DELTA, 0);
+		this->revision = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_REVISION, 0);
+		this->issuedTo = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO, 0);
+		if (! this->issuedTo) {
+			delete tmp;
+			return false;
+		}
+		this->remoteTraceTarget = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_REMOTE_TRACE_TARGET);
+		this->remoteTraceLevel = (Trace::Level)d.getUI(ZT_NETWORKCONFIG_DICT_KEY_REMOTE_TRACE_LEVEL);
+		this->multicastLimit = (unsigned int)d.getUI(ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT, 0);
+		d.get(ZT_NETWORKCONFIG_DICT_KEY_NAME, this->name, sizeof(this->name));
+
+		this->mtu = (unsigned int)d.getUI(ZT_NETWORKCONFIG_DICT_KEY_MTU, ZT_DEFAULT_MTU);
+		if (this->mtu < 1280) {
+			this->mtu = 1280;	// minimum MTU allowed by IPv6 standard and others
+		}
+		else if (this->mtu > ZT_MAX_MTU) {
+			this->mtu = ZT_MAX_MTU;
+		}
+
+		if (d.getUI(ZT_NETWORKCONFIG_DICT_KEY_VERSION, 0) < 6) {
 #ifdef ZT_SUPPORT_OLD_STYLE_NETCONF
-            char tmp2[1024] = { 0 };
-
-            // Decode legacy fields if version is old
-            if (d.getB(ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST_OLD)) {
-                this->flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST;
-            }
-            this->flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION;   // always enable for old-style netconf
-            this->type = (d.getB(ZT_NETWORKCONFIG_DICT_KEY_PRIVATE_OLD, true)) ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC;
-
-            if (d.get(ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC_OLD, tmp2, sizeof(tmp2)) > 0) {
-                char* saveptr = (char*)0;
-                for (char* f = Utils::stok(tmp2, ",", &saveptr); (f); f = Utils::stok((char*)0, ",", &saveptr)) {
-                    if (this->staticIpCount >= ZT_MAX_ZT_ASSIGNED_ADDRESSES) {
-                        break;
-                    }
-                    InetAddress ip(f);
-                    if (! ip.isNetwork()) {
-                        this->staticIps[this->staticIpCount++] = ip;
-                    }
-                }
-            }
-            if (d.get(ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC_OLD, tmp2, sizeof(tmp2)) > 0) {
-                char* saveptr = (char*)0;
-                for (char* f = Utils::stok(tmp2, ",", &saveptr); (f); f = Utils::stok((char*)0, ",", &saveptr)) {
-                    if (this->staticIpCount >= ZT_MAX_ZT_ASSIGNED_ADDRESSES) {
-                        break;
-                    }
-                    InetAddress ip(f);
-                    if (! ip.isNetwork()) {
-                        this->staticIps[this->staticIpCount++] = ip;
-                    }
-                }
-            }
-
-            if (d.get(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP_OLD, tmp2, sizeof(tmp2)) > 0) {
-                this->com.fromString(tmp2);
-            }
-
-            if (d.get(ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES_OLD, tmp2, sizeof(tmp2)) > 0) {
-                char* saveptr = (char*)0;
-                for (char* f = Utils::stok(tmp2, ",", &saveptr); (f); f = Utils::stok((char*)0, ",", &saveptr)) {
-                    unsigned int et = Utils::hexStrToUInt(f) & 0xffff;
-                    if ((this->ruleCount + 2) > ZT_MAX_NETWORK_RULES) {
-                        break;
-                    }
-                    if (et > 0) {
-                        this->rules[this->ruleCount].t = (uint8_t)ZT_NETWORK_RULE_MATCH_ETHERTYPE;
-                        this->rules[this->ruleCount].v.etherType = (uint16_t)et;
-                        ++this->ruleCount;
-                    }
-                    this->rules[this->ruleCount++].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT;
-                }
-            }
-            else {
-                this->rules[0].t = ZT_NETWORK_RULE_ACTION_ACCEPT;
-                this->ruleCount = 1;
-            }
-
-            if (d.get(ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES_OLD, tmp2, sizeof(tmp2)) > 0) {
-                char* saveptr = (char*)0;
-                for (char* f = Utils::stok(tmp2, ",", &saveptr); (f); f = Utils::stok((char*)0, ",", &saveptr)) {
-                    this->addSpecialist(Address(Utils::hexStrToU64(f)), ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE);
-                }
-            }
+			char tmp2[1024] = { 0 };
+
+			// Decode legacy fields if version is old
+			if (d.getB(ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST_OLD)) {
+				this->flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST;
+			}
+			this->flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION;	  // always enable for old-style netconf
+			this->type = (d.getB(ZT_NETWORKCONFIG_DICT_KEY_PRIVATE_OLD, true)) ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC;
+
+			if (d.get(ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC_OLD, tmp2, sizeof(tmp2)) > 0) {
+				char* saveptr = (char*)0;
+				for (char* f = Utils::stok(tmp2, ",", &saveptr); (f); f = Utils::stok((char*)0, ",", &saveptr)) {
+					if (this->staticIpCount >= ZT_MAX_ZT_ASSIGNED_ADDRESSES) {
+						break;
+					}
+					InetAddress ip(f);
+					if (! ip.isNetwork()) {
+						this->staticIps[this->staticIpCount++] = ip;
+					}
+				}
+			}
+			if (d.get(ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC_OLD, tmp2, sizeof(tmp2)) > 0) {
+				char* saveptr = (char*)0;
+				for (char* f = Utils::stok(tmp2, ",", &saveptr); (f); f = Utils::stok((char*)0, ",", &saveptr)) {
+					if (this->staticIpCount >= ZT_MAX_ZT_ASSIGNED_ADDRESSES) {
+						break;
+					}
+					InetAddress ip(f);
+					if (! ip.isNetwork()) {
+						this->staticIps[this->staticIpCount++] = ip;
+					}
+				}
+			}
+
+			if (d.get(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP_OLD, tmp2, sizeof(tmp2)) > 0) {
+				this->com.fromString(tmp2);
+			}
+
+			if (d.get(ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES_OLD, tmp2, sizeof(tmp2)) > 0) {
+				char* saveptr = (char*)0;
+				for (char* f = Utils::stok(tmp2, ",", &saveptr); (f); f = Utils::stok((char*)0, ",", &saveptr)) {
+					unsigned int et = Utils::hexStrToUInt(f) & 0xffff;
+					if ((this->ruleCount + 2) > ZT_MAX_NETWORK_RULES) {
+						break;
+					}
+					if (et > 0) {
+						this->rules[this->ruleCount].t = (uint8_t)ZT_NETWORK_RULE_MATCH_ETHERTYPE;
+						this->rules[this->ruleCount].v.etherType = (uint16_t)et;
+						++this->ruleCount;
+					}
+					this->rules[this->ruleCount++].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT;
+				}
+			}
+			else {
+				this->rules[0].t = ZT_NETWORK_RULE_ACTION_ACCEPT;
+				this->ruleCount = 1;
+			}
+
+			if (d.get(ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES_OLD, tmp2, sizeof(tmp2)) > 0) {
+				char* saveptr = (char*)0;
+				for (char* f = Utils::stok(tmp2, ",", &saveptr); (f); f = Utils::stok((char*)0, ",", &saveptr)) {
+					this->addSpecialist(Address(Utils::hexStrToU64(f)), ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE);
+				}
+			}
 #else
-            delete tmp;
-            return false;
-#endif   // ZT_SUPPORT_OLD_STYLE_NETCONF
-        }
-        else {
-            // Otherwise we can use the new fields
-            this->flags = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_FLAGS, 0);
-            this->type = (ZT_VirtualNetworkType)d.getUI(ZT_NETWORKCONFIG_DICT_KEY_TYPE, (uint64_t)ZT_NETWORK_TYPE_PRIVATE);
-
-            if (d.get(ZT_NETWORKCONFIG_DICT_KEY_COM, *tmp)) {
-                this->com.deserialize(*tmp, 0);
-            }
-
-            if (d.get(ZT_NETWORKCONFIG_DICT_KEY_CAPABILITIES, *tmp)) {
-                try {
-                    unsigned int p = 0;
-                    while (p < tmp->size()) {
-                        Capability cap;
-                        p += cap.deserialize(*tmp, p);
-                        this->capabilities[this->capabilityCount++] = cap;
-                    }
-                }
-                catch (...) {
-                }
-                std::sort(&(this->capabilities[0]), &(this->capabilities[this->capabilityCount]));
-            }
-
-            if (d.get(ZT_NETWORKCONFIG_DICT_KEY_TAGS, *tmp)) {
-                try {
-                    unsigned int p = 0;
-                    while (p < tmp->size()) {
-                        Tag tag;
-                        p += tag.deserialize(*tmp, p);
-                        this->tags[this->tagCount++] = tag;
-                    }
-                }
-                catch (...) {
-                }
-                std::sort(&(this->tags[0]), &(this->tags[this->tagCount]));
-            }
-
-            if (d.get(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATES_OF_OWNERSHIP, *tmp)) {
-                unsigned int p = 0;
-                while (p < tmp->size()) {
-                    if (certificateOfOwnershipCount < ZT_MAX_CERTIFICATES_OF_OWNERSHIP) {
-                        p += certificatesOfOwnership[certificateOfOwnershipCount++].deserialize(*tmp, p);
-                    }
-                    else {
-                        CertificateOfOwnership foo;
-                        p += foo.deserialize(*tmp, p);
-                    }
-                }
-            }
-
-            if (d.get(ZT_NETWORKCONFIG_DICT_KEY_SPECIALISTS, *tmp)) {
-                unsigned int p = 0;
-                while ((p + 8) <= tmp->size()) {
-                    if (specialistCount < ZT_MAX_NETWORK_SPECIALISTS) {
-                        this->specialists[this->specialistCount++] = tmp->at<uint64_t>(p);
-                    }
-                    p += 8;
-                }
-            }
-
-            if (d.get(ZT_NETWORKCONFIG_DICT_KEY_ROUTES, *tmp)) {
-                unsigned int p = 0;
-                while ((p < tmp->size()) && (routeCount < ZT_MAX_NETWORK_ROUTES)) {
-                    p += reinterpret_cast<InetAddress*>(&(this->routes[this->routeCount].target))->deserialize(*tmp, p);
-                    p += reinterpret_cast<InetAddress*>(&(this->routes[this->routeCount].via))->deserialize(*tmp, p);
-                    this->routes[this->routeCount].flags = tmp->at<uint16_t>(p);
-                    p += 2;
-                    this->routes[this->routeCount].metric = tmp->at<uint16_t>(p);
-                    p += 2;
-                    ++this->routeCount;
-                }
-            }
-
-            if (d.get(ZT_NETWORKCONFIG_DICT_KEY_STATIC_IPS, *tmp)) {
-                unsigned int p = 0;
-                while ((p < tmp->size()) && (staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) {
-                    p += this->staticIps[this->staticIpCount++].deserialize(*tmp, p);
-                }
-            }
-
-            if (d.get(ZT_NETWORKCONFIG_DICT_KEY_RULES, *tmp)) {
-                this->ruleCount = 0;
-                unsigned int p = 0;
-                Capability::deserializeRules(*tmp, p, this->rules, this->ruleCount, ZT_MAX_NETWORK_RULES);
-            }
-
-            if (d.get(ZT_NETWORKCONFIG_DICT_KEY_DNS, *tmp)) {
-                unsigned int p = 0;
-                DNS::deserializeDNS(*tmp, p, &dns);
-            }
-
-            this->ssoVersion = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_SSO_VERSION, 0ULL);
-            this->ssoEnabled = d.getB(ZT_NETWORKCONFIG_DICT_KEY_SSO_ENABLED, false);
-
-            if (this->ssoVersion == 0) {
-                // implicit flow
-                if (this->ssoEnabled) {
-                    if (d.get(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL, (unsigned int)sizeof(this->authenticationURL)) > 0) {
-                        this->authenticationURL[sizeof(this->authenticationURL) - 1] = 0;   // ensure null terminated
-                    }
-                    else {
-                        this->authenticationURL[0] = 0;
-                    }
-                    this->authenticationExpiryTime = d.getI(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_EXPIRY_TIME, 0);
-                }
-                else {
-                    this->authenticationURL[0] = 0;
-                    this->authenticationExpiryTime = 0;
-                }
-            }
-            else if (this->ssoVersion == 1) {
-                // full flow
-                if (this->ssoEnabled) {
-                    if (d.get(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL, (unsigned int)sizeof(this->authenticationURL)) > 0) {
-                        this->authenticationURL[sizeof(this->authenticationURL) - 1] = 0;
-                    }
-                    if (d.get(ZT_NETWORKCONFIG_DICT_KEY_ISSUER_URL, this->issuerURL, (unsigned int)sizeof(this->issuerURL)) > 0) {
-                        this->issuerURL[sizeof(this->issuerURL) - 1] = 0;
-                    }
-                    if (d.get(ZT_NETWORKCONFIG_DICT_KEY_CENTRAL_ENDPOINT_URL, this->centralAuthURL, (unsigned int)sizeof(this->centralAuthURL)) > 0) {
-                        this->centralAuthURL[sizeof(this->centralAuthURL) - 1] = 0;
-                    }
-                    if (d.get(ZT_NETWORKCONFIG_DICT_KEY_NONCE, this->ssoNonce, (unsigned int)sizeof(this->ssoNonce)) > 0) {
-                        this->ssoNonce[sizeof(this->ssoNonce) - 1] = 0;
-                    }
-                    if (d.get(ZT_NETWORKCONFIG_DICT_KEY_STATE, this->ssoState, (unsigned int)sizeof(this->ssoState)) > 0) {
-                        this->ssoState[sizeof(this->ssoState) - 1] = 0;
-                    }
-                    if (d.get(ZT_NETWORKCONFIG_DICT_KEY_CLIENT_ID, this->ssoClientID, (unsigned int)sizeof(this->ssoClientID)) > 0) {
-                        this->ssoClientID[sizeof(this->ssoClientID) - 1] = 0;
-                    }
-                    if (d.get(ZT_NETWORKCONFIG_DICT_KEY_SSO_PROVIDER, this->ssoProvider, (unsigned int)(sizeof(this->ssoProvider))) > 0) {
-                        this->ssoProvider[sizeof(this->ssoProvider) - 1] = 0;
-                    }
-                    else {
-                        strncpy(this->ssoProvider, "default", sizeof(this->ssoProvider));
-                        this->ssoProvider[sizeof(this->ssoProvider) - 1] = 0;
-                    }
-                }
-                else {
-                    this->authenticationURL[0] = 0;
-                    this->authenticationExpiryTime = 0;
-                    this->centralAuthURL[0] = 0;
-                    this->ssoNonce[0] = 0;
-                    this->ssoState[0] = 0;
-                    this->ssoClientID[0] = 0;
-                    this->issuerURL[0] = 0;
-                    this->ssoProvider[0] = 0;
-                }
-            }
-        }
-
-        // printf("~~~\n%s\n~~~\n",d.data());
-        // dump();
-        // printf("~~~\n");
-
-        delete tmp;
-        return true;
-    }
-    catch (...) {
-        delete tmp;
-        return false;
-    }
+			delete tmp;
+			return false;
+#endif	 // ZT_SUPPORT_OLD_STYLE_NETCONF
+		}
+		else {
+			// Otherwise we can use the new fields
+			this->flags = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_FLAGS, 0);
+			this->type = (ZT_VirtualNetworkType)d.getUI(ZT_NETWORKCONFIG_DICT_KEY_TYPE, (uint64_t)ZT_NETWORK_TYPE_PRIVATE);
+
+			if (d.get(ZT_NETWORKCONFIG_DICT_KEY_COM, *tmp)) {
+				this->com.deserialize(*tmp, 0);
+			}
+
+			if (d.get(ZT_NETWORKCONFIG_DICT_KEY_CAPABILITIES, *tmp)) {
+				try {
+					unsigned int p = 0;
+					while (p < tmp->size()) {
+						Capability cap;
+						p += cap.deserialize(*tmp, p);
+						this->capabilities[this->capabilityCount++] = cap;
+					}
+				}
+				catch (...) {
+				}
+				std::sort(&(this->capabilities[0]), &(this->capabilities[this->capabilityCount]));
+			}
+
+			if (d.get(ZT_NETWORKCONFIG_DICT_KEY_TAGS, *tmp)) {
+				try {
+					unsigned int p = 0;
+					while (p < tmp->size()) {
+						Tag tag;
+						p += tag.deserialize(*tmp, p);
+						this->tags[this->tagCount++] = tag;
+					}
+				}
+				catch (...) {
+				}
+				std::sort(&(this->tags[0]), &(this->tags[this->tagCount]));
+			}
+
+			if (d.get(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATES_OF_OWNERSHIP, *tmp)) {
+				unsigned int p = 0;
+				while (p < tmp->size()) {
+					if (certificateOfOwnershipCount < ZT_MAX_CERTIFICATES_OF_OWNERSHIP) {
+						p += certificatesOfOwnership[certificateOfOwnershipCount++].deserialize(*tmp, p);
+					}
+					else {
+						CertificateOfOwnership foo;
+						p += foo.deserialize(*tmp, p);
+					}
+				}
+			}
+
+			if (d.get(ZT_NETWORKCONFIG_DICT_KEY_SPECIALISTS, *tmp)) {
+				unsigned int p = 0;
+				while ((p + 8) <= tmp->size()) {
+					if (specialistCount < ZT_MAX_NETWORK_SPECIALISTS) {
+						this->specialists[this->specialistCount++] = tmp->at<uint64_t>(p);
+					}
+					p += 8;
+				}
+			}
+
+			if (d.get(ZT_NETWORKCONFIG_DICT_KEY_ROUTES, *tmp)) {
+				unsigned int p = 0;
+				while ((p < tmp->size()) && (routeCount < ZT_MAX_NETWORK_ROUTES)) {
+					p += reinterpret_cast<InetAddress*>(&(this->routes[this->routeCount].target))->deserialize(*tmp, p);
+					p += reinterpret_cast<InetAddress*>(&(this->routes[this->routeCount].via))->deserialize(*tmp, p);
+					this->routes[this->routeCount].flags = tmp->at<uint16_t>(p);
+					p += 2;
+					this->routes[this->routeCount].metric = tmp->at<uint16_t>(p);
+					p += 2;
+					++this->routeCount;
+				}
+			}
+
+			if (d.get(ZT_NETWORKCONFIG_DICT_KEY_STATIC_IPS, *tmp)) {
+				unsigned int p = 0;
+				while ((p < tmp->size()) && (staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) {
+					p += this->staticIps[this->staticIpCount++].deserialize(*tmp, p);
+				}
+			}
+
+			if (d.get(ZT_NETWORKCONFIG_DICT_KEY_RULES, *tmp)) {
+				this->ruleCount = 0;
+				unsigned int p = 0;
+				Capability::deserializeRules(*tmp, p, this->rules, this->ruleCount, ZT_MAX_NETWORK_RULES);
+			}
+
+			if (d.get(ZT_NETWORKCONFIG_DICT_KEY_DNS, *tmp)) {
+				unsigned int p = 0;
+				DNS::deserializeDNS(*tmp, p, &dns);
+			}
+
+			this->ssoVersion = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_SSO_VERSION, 0ULL);
+			this->ssoEnabled = d.getB(ZT_NETWORKCONFIG_DICT_KEY_SSO_ENABLED, false);
+
+			if (this->ssoVersion == 0) {
+				// implicit flow
+				if (this->ssoEnabled) {
+					if (d.get(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL, (unsigned int)sizeof(this->authenticationURL)) > 0) {
+						this->authenticationURL[sizeof(this->authenticationURL) - 1] = 0;	// ensure null terminated
+					}
+					else {
+						this->authenticationURL[0] = 0;
+					}
+					this->authenticationExpiryTime = d.getI(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_EXPIRY_TIME, 0);
+				}
+				else {
+					this->authenticationURL[0] = 0;
+					this->authenticationExpiryTime = 0;
+				}
+			}
+			else if (this->ssoVersion == 1) {
+				// full flow
+				if (this->ssoEnabled) {
+					if (d.get(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL, (unsigned int)sizeof(this->authenticationURL)) > 0) {
+						this->authenticationURL[sizeof(this->authenticationURL) - 1] = 0;
+					}
+					if (d.get(ZT_NETWORKCONFIG_DICT_KEY_ISSUER_URL, this->issuerURL, (unsigned int)sizeof(this->issuerURL)) > 0) {
+						this->issuerURL[sizeof(this->issuerURL) - 1] = 0;
+					}
+					if (d.get(ZT_NETWORKCONFIG_DICT_KEY_CENTRAL_ENDPOINT_URL, this->centralAuthURL, (unsigned int)sizeof(this->centralAuthURL)) > 0) {
+						this->centralAuthURL[sizeof(this->centralAuthURL) - 1] = 0;
+					}
+					if (d.get(ZT_NETWORKCONFIG_DICT_KEY_NONCE, this->ssoNonce, (unsigned int)sizeof(this->ssoNonce)) > 0) {
+						this->ssoNonce[sizeof(this->ssoNonce) - 1] = 0;
+					}
+					if (d.get(ZT_NETWORKCONFIG_DICT_KEY_STATE, this->ssoState, (unsigned int)sizeof(this->ssoState)) > 0) {
+						this->ssoState[sizeof(this->ssoState) - 1] = 0;
+					}
+					if (d.get(ZT_NETWORKCONFIG_DICT_KEY_CLIENT_ID, this->ssoClientID, (unsigned int)sizeof(this->ssoClientID)) > 0) {
+						this->ssoClientID[sizeof(this->ssoClientID) - 1] = 0;
+					}
+					if (d.get(ZT_NETWORKCONFIG_DICT_KEY_SSO_PROVIDER, this->ssoProvider, (unsigned int)(sizeof(this->ssoProvider))) > 0) {
+						this->ssoProvider[sizeof(this->ssoProvider) - 1] = 0;
+					}
+					else {
+						strncpy(this->ssoProvider, "default", sizeof(this->ssoProvider));
+						this->ssoProvider[sizeof(this->ssoProvider) - 1] = 0;
+					}
+				}
+				else {
+					this->authenticationURL[0] = 0;
+					this->authenticationExpiryTime = 0;
+					this->centralAuthURL[0] = 0;
+					this->ssoNonce[0] = 0;
+					this->ssoState[0] = 0;
+					this->ssoClientID[0] = 0;
+					this->issuerURL[0] = 0;
+					this->ssoProvider[0] = 0;
+				}
+			}
+		}
+
+		// printf("~~~\n%s\n~~~\n",d.data());
+		// dump();
+		// printf("~~~\n");
+
+		delete tmp;
+		return true;
+	}
+	catch (...) {
+		delete tmp;
+		return false;
+	}
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 497 - 497
node/NetworkConfig.hpp

@@ -92,8 +92,8 @@ namespace ZeroTier {
 
 // Dictionary capacity needed for max size network config
 #define ZT_NETWORKCONFIG_DICT_CAPACITY                                                                                                                                                                                                         \
-    (4096 + (sizeof(ZT_VirtualNetworkConfig)) + (sizeof(ZT_VirtualNetworkRule) * ZT_MAX_NETWORK_RULES) + (sizeof(Capability) * ZT_MAX_NETWORK_CAPABILITIES) + (sizeof(Tag) * ZT_MAX_NETWORK_TAGS)                                              \
-     + (sizeof(CertificateOfOwnership) * ZT_MAX_CERTIFICATES_OF_OWNERSHIP))
+	(4096 + (sizeof(ZT_VirtualNetworkConfig)) + (sizeof(ZT_VirtualNetworkRule) * ZT_MAX_NETWORK_RULES) + (sizeof(Capability) * ZT_MAX_NETWORK_CAPABILITIES) + (sizeof(Tag) * ZT_MAX_NETWORK_TAGS)                                              \
+	 + (sizeof(CertificateOfOwnership) * ZT_MAX_CERTIFICATES_OF_OWNERSHIP))
 
 // Dictionary capacity needed for max size network meta-data
 #define ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY 1024
@@ -250,505 +250,505 @@ namespace ZeroTier {
  */
 class NetworkConfig {
   public:
-    NetworkConfig()
-        : networkId(0)
-        , timestamp(0)
-        , credentialTimeMaxDelta(0)
-        , revision(0)
-        , issuedTo()
-        , remoteTraceTarget()
-        , flags(0)
-        , remoteTraceLevel(Trace::LEVEL_NORMAL)
-        , mtu(0)
-        , multicastLimit(0)
-        , specialistCount(0)
-        , routeCount(0)
-        , staticIpCount(0)
-        , ruleCount(0)
-        , capabilityCount(0)
-        , tagCount(0)
-        , certificateOfOwnershipCount(0)
-        , capabilities()
-        , tags()
-        , certificatesOfOwnership()
-        , type(ZT_NETWORK_TYPE_PRIVATE)
-        , dnsCount(0)
-        , ssoEnabled(false)
-        , authenticationURL()
-        , authenticationExpiryTime(0)
-        , issuerURL()
-        , centralAuthURL()
-        , ssoNonce()
-        , ssoState()
-        , ssoClientID()
-    {
-        name[0] = 0;
-        memset(specialists, 0, sizeof(uint64_t) * ZT_MAX_NETWORK_SPECIALISTS);
-        memset(routes, 0, sizeof(ZT_VirtualNetworkRoute) * ZT_MAX_NETWORK_ROUTES);
-        memset(staticIps, 0, sizeof(InetAddress) * ZT_MAX_ZT_ASSIGNED_ADDRESSES);
-        memset(rules, 0, sizeof(ZT_VirtualNetworkRule) * ZT_MAX_NETWORK_RULES);
-        memset(&dns, 0, sizeof(ZT_VirtualNetworkDNS));
-        memset(authenticationURL, 0, sizeof(authenticationURL));
-        memset(issuerURL, 0, sizeof(issuerURL));
-        memset(centralAuthURL, 0, sizeof(centralAuthURL));
-        memset(ssoNonce, 0, sizeof(ssoNonce));
-        memset(ssoState, 0, sizeof(ssoState));
-        memset(ssoClientID, 0, sizeof(ssoClientID));
-        strncpy(ssoProvider, "default", sizeof(ssoProvider));
-    }
-
-    /**
-     * Write this network config to a dictionary for transport
-     *
-     * @param d Dictionary
-     * @param includeLegacy If true, include legacy fields for old node versions
-     * @return True if dictionary was successfully created, false if e.g. overflow
-     */
-    bool toDictionary(Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>& d, bool includeLegacy) const;
-
-    /**
-     * Read this network config from a dictionary
-     *
-     * @param d Dictionary (non-const since it might be modified during parse, should not be used after call)
-     * @return True if dictionary was valid and network config successfully initialized
-     */
-    bool fromDictionary(const Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>& d);
-
-    /**
-     * @return True if broadcast (ff:ff:ff:ff:ff:ff) address should work on this network
-     */
-    inline bool enableBroadcast() const
-    {
-        return ((this->flags & ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST) != 0);
-    }
-
-    /**
-     * @return True if IPv6 NDP emulation should be allowed for certain "magic" IPv6 address patterns
-     */
-    inline bool ndpEmulation() const
-    {
-        return ((this->flags & ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION) != 0);
-    }
-
-    /**
-     * @return True if frames should not be compressed
-     */
-    inline bool disableCompression() const
-    {
+	NetworkConfig()
+		: networkId(0)
+		, timestamp(0)
+		, credentialTimeMaxDelta(0)
+		, revision(0)
+		, issuedTo()
+		, remoteTraceTarget()
+		, flags(0)
+		, remoteTraceLevel(Trace::LEVEL_NORMAL)
+		, mtu(0)
+		, multicastLimit(0)
+		, specialistCount(0)
+		, routeCount(0)
+		, staticIpCount(0)
+		, ruleCount(0)
+		, capabilityCount(0)
+		, tagCount(0)
+		, certificateOfOwnershipCount(0)
+		, capabilities()
+		, tags()
+		, certificatesOfOwnership()
+		, type(ZT_NETWORK_TYPE_PRIVATE)
+		, dnsCount(0)
+		, ssoEnabled(false)
+		, authenticationURL()
+		, authenticationExpiryTime(0)
+		, issuerURL()
+		, centralAuthURL()
+		, ssoNonce()
+		, ssoState()
+		, ssoClientID()
+	{
+		name[0] = 0;
+		memset(specialists, 0, sizeof(uint64_t) * ZT_MAX_NETWORK_SPECIALISTS);
+		memset(routes, 0, sizeof(ZT_VirtualNetworkRoute) * ZT_MAX_NETWORK_ROUTES);
+		memset(staticIps, 0, sizeof(InetAddress) * ZT_MAX_ZT_ASSIGNED_ADDRESSES);
+		memset(rules, 0, sizeof(ZT_VirtualNetworkRule) * ZT_MAX_NETWORK_RULES);
+		memset(&dns, 0, sizeof(ZT_VirtualNetworkDNS));
+		memset(authenticationURL, 0, sizeof(authenticationURL));
+		memset(issuerURL, 0, sizeof(issuerURL));
+		memset(centralAuthURL, 0, sizeof(centralAuthURL));
+		memset(ssoNonce, 0, sizeof(ssoNonce));
+		memset(ssoState, 0, sizeof(ssoState));
+		memset(ssoClientID, 0, sizeof(ssoClientID));
+		strncpy(ssoProvider, "default", sizeof(ssoProvider));
+	}
+
+	/**
+	 * Write this network config to a dictionary for transport
+	 *
+	 * @param d Dictionary
+	 * @param includeLegacy If true, include legacy fields for old node versions
+	 * @return True if dictionary was successfully created, false if e.g. overflow
+	 */
+	bool toDictionary(Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>& d, bool includeLegacy) const;
+
+	/**
+	 * Read this network config from a dictionary
+	 *
+	 * @param d Dictionary (non-const since it might be modified during parse, should not be used after call)
+	 * @return True if dictionary was valid and network config successfully initialized
+	 */
+	bool fromDictionary(const Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>& d);
+
+	/**
+	 * @return True if broadcast (ff:ff:ff:ff:ff:ff) address should work on this network
+	 */
+	inline bool enableBroadcast() const
+	{
+		return ((this->flags & ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST) != 0);
+	}
+
+	/**
+	 * @return True if IPv6 NDP emulation should be allowed for certain "magic" IPv6 address patterns
+	 */
+	inline bool ndpEmulation() const
+	{
+		return ((this->flags & ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION) != 0);
+	}
+
+	/**
+	 * @return True if frames should not be compressed
+	 */
+	inline bool disableCompression() const
+	{
 #ifndef ZT_DISABLE_COMPRESSION
-        return ((this->flags & ZT_NETWORKCONFIG_FLAG_DISABLE_COMPRESSION) != 0);
+		return ((this->flags & ZT_NETWORKCONFIG_FLAG_DISABLE_COMPRESSION) != 0);
 #else
-        /* Compression is disabled for libzt builds since it causes non-obvious chaotic
-        interference with lwIP's TCP congestion algorithm. Compression is also disabled
-        for some NAS builds due to the usage of low-performance processors in certain
-        older and budget models. */
-        return false;
+		/* Compression is disabled for libzt builds since it causes non-obvious chaotic
+		interference with lwIP's TCP congestion algorithm. Compression is also disabled
+		for some NAS builds due to the usage of low-performance processors in certain
+		older and budget models. */
+		return false;
 #endif
-    }
-
-    /**
-     * @return Network type is public (no access control)
-     */
-    inline bool isPublic() const
-    {
-        return (this->type == ZT_NETWORK_TYPE_PUBLIC);
-    }
-
-    /**
-     * @return Network type is private (certificate access control)
-     */
-    inline bool isPrivate() const
-    {
-        return (this->type == ZT_NETWORK_TYPE_PRIVATE);
-    }
-
-    /**
-     * @return ZeroTier addresses of devices on this network designated as active bridges
-     */
-    inline std::vector<Address> activeBridges() const
-    {
-        std::vector<Address> r;
-        for (unsigned int i = 0; i < specialistCount; ++i) {
-            if ((specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE) != 0) {
-                r.push_back(Address(specialists[i]));
-            }
-        }
-        return r;
-    }
-
-    inline unsigned int activeBridges(Address ab[ZT_MAX_NETWORK_SPECIALISTS]) const
-    {
-        unsigned int c = 0;
-        for (unsigned int i = 0; i < specialistCount; ++i) {
-            if ((specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE) != 0) {
-                ab[c++] = specialists[i];
-            }
-        }
-        return c;
-    }
-
-    inline bool isActiveBridge(const Address& a) const
-    {
-        for (unsigned int i = 0; i < specialistCount; ++i) {
-            if (((specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE) != 0) && (a == specialists[i])) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    inline std::vector<Address> anchors() const
-    {
-        std::vector<Address> r;
-        for (unsigned int i = 0; i < specialistCount; ++i) {
-            if ((specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_ANCHOR) != 0) {
-                r.push_back(Address(specialists[i]));
-            }
-        }
-        return r;
-    }
-
-    inline std::vector<Address> multicastReplicators() const
-    {
-        std::vector<Address> r;
-        for (unsigned int i = 0; i < specialistCount; ++i) {
-            if ((specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_MULTICAST_REPLICATOR) != 0) {
-                r.push_back(Address(specialists[i]));
-            }
-        }
-        return r;
-    }
-
-    inline unsigned int multicastReplicators(Address mr[ZT_MAX_NETWORK_SPECIALISTS]) const
-    {
-        unsigned int c = 0;
-        for (unsigned int i = 0; i < specialistCount; ++i) {
-            if ((specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_MULTICAST_REPLICATOR) != 0) {
-                mr[c++] = specialists[i];
-            }
-        }
-        return c;
-    }
-
-    inline bool isMulticastReplicator(const Address& a) const
-    {
-        for (unsigned int i = 0; i < specialistCount; ++i) {
-            if (((specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_MULTICAST_REPLICATOR) != 0) && (a == specialists[i])) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    inline std::vector<Address> alwaysContactAddresses() const
-    {
-        std::vector<Address> r;
-        for (unsigned int i = 0; i < specialistCount; ++i) {
-            if ((specialists[i] & (ZT_NETWORKCONFIG_SPECIALIST_TYPE_ANCHOR | ZT_NETWORKCONFIG_SPECIALIST_TYPE_MULTICAST_REPLICATOR)) != 0) {
-                r.push_back(Address(specialists[i]));
-            }
-        }
-        return r;
-    }
-
-    inline unsigned int alwaysContactAddresses(Address ac[ZT_MAX_NETWORK_SPECIALISTS]) const
-    {
-        unsigned int c = 0;
-        for (unsigned int i = 0; i < specialistCount; ++i) {
-            if ((specialists[i] & (ZT_NETWORKCONFIG_SPECIALIST_TYPE_ANCHOR | ZT_NETWORKCONFIG_SPECIALIST_TYPE_MULTICAST_REPLICATOR)) != 0) {
-                ac[c++] = specialists[i];
-            }
-        }
-        return c;
-    }
-
-    inline void alwaysContactAddresses(Hashtable<Address, std::vector<InetAddress> >& a) const
-    {
-        for (unsigned int i = 0; i < specialistCount; ++i) {
-            if ((specialists[i] & (ZT_NETWORKCONFIG_SPECIALIST_TYPE_ANCHOR | ZT_NETWORKCONFIG_SPECIALIST_TYPE_MULTICAST_REPLICATOR)) != 0) {
-                a[Address(specialists[i])];
-            }
-        }
-    }
-
-    /**
-     * @param fromPeer Peer attempting to bridge other Ethernet peers onto network
-     * @return True if this network allows bridging
-     */
-    inline bool permitsBridging(const Address& fromPeer) const
-    {
-        for (unsigned int i = 0; i < specialistCount; ++i) {
-            if ((fromPeer == specialists[i]) && ((specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE) != 0)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    inline operator bool() const
-    {
-        return (networkId != 0);
-    }
-    inline bool operator==(const NetworkConfig& nc) const
-    {
-        return (memcmp(this, &nc, sizeof(NetworkConfig)) == 0);
-    }
-    inline bool operator!=(const NetworkConfig& nc) const
-    {
-        return (! (*this == nc));
-    }
-
-    /**
-     * Add a specialist or mask flags if already present
-     *
-     * This masks the existing flags if the specialist is already here or adds
-     * it otherwise.
-     *
-     * @param a Address of specialist
-     * @param f Flags (OR of specialist role/type flags)
-     * @return True if successfully masked or added
-     */
-    inline bool addSpecialist(const Address& a, const uint64_t f)
-    {
-        const uint64_t aint = a.toInt();
-        for (unsigned int i = 0; i < specialistCount; ++i) {
-            if ((specialists[i] & 0xffffffffffULL) == aint) {
-                specialists[i] |= f;
-                return true;
-            }
-        }
-        if (specialistCount < ZT_MAX_NETWORK_SPECIALISTS) {
-            specialists[specialistCount++] = f | aint;
-            return true;
-        }
-        return false;
-    }
-
-    const Capability* capability(const uint32_t id) const
-    {
-        for (unsigned int i = 0; i < capabilityCount; ++i) {
-            if (capabilities[i].id() == id) {
-                return &(capabilities[i]);
-            }
-        }
-        return (Capability*)0;
-    }
-
-    const Tag* tag(const uint32_t id) const
-    {
-        for (unsigned int i = 0; i < tagCount; ++i) {
-            if (tags[i].id() == id) {
-                return &(tags[i]);
-            }
-        }
-        return (Tag*)0;
-    }
-
-    /**
-     * Network ID that this configuration applies to
-     */
-    uint64_t networkId;
-
-    /**
-     * Controller-side time of config generation/issue
-     */
-    int64_t timestamp;
-
-    /**
-     * Max difference between timestamp and tag/capability timestamp
-     */
-    int64_t credentialTimeMaxDelta;
-
-    /**
-     * Controller-side revision counter for this configuration
-     */
-    uint64_t revision;
-
-    /**
-     * Address of device to which this config is issued
-     */
-    Address issuedTo;
-
-    /**
-     * If non-NULL, remote traces related to this network are sent here
-     */
-    Address remoteTraceTarget;
-
-    /**
-     * Flags (64-bit)
-     */
-    uint64_t flags;
-
-    /**
-     * Remote trace level
-     */
-    Trace::Level remoteTraceLevel;
-
-    /**
-     * Network MTU
-     */
-    unsigned int mtu;
-
-    /**
-     * Maximum number of recipients per multicast (not including active bridges)
-     */
-    unsigned int multicastLimit;
-
-    /**
-     * Number of specialists
-     */
-    unsigned int specialistCount;
-
-    /**
-     * Number of routes
-     */
-    unsigned int routeCount;
-
-    /**
-     * Number of ZT-managed static IP assignments
-     */
-    unsigned int staticIpCount;
-
-    /**
-     * Number of rule table entries
-     */
-    unsigned int ruleCount;
-
-    /**
-     * Number of capabilities
-     */
-    unsigned int capabilityCount;
-
-    /**
-     * Number of tags
-     */
-    unsigned int tagCount;
-
-    /**
-     * Number of certificates of ownership
-     */
-    unsigned int certificateOfOwnershipCount;
-
-    /**
-     * Specialist devices
-     *
-     * For each entry the least significant 40 bits are the device's ZeroTier
-     * address and the most significant 24 bits are flags indicating its role.
-     */
-    uint64_t specialists[ZT_MAX_NETWORK_SPECIALISTS];
-
-    /**
-     * Statically defined "pushed" routes (including default gateways)
-     */
-    ZT_VirtualNetworkRoute routes[ZT_MAX_NETWORK_ROUTES];
-
-    /**
-     * Static IP assignments
-     */
-    InetAddress staticIps[ZT_MAX_ZT_ASSIGNED_ADDRESSES];
-
-    /**
-     * Base network rules
-     */
-    ZT_VirtualNetworkRule rules[ZT_MAX_NETWORK_RULES];
-
-    /**
-     * Capabilities for this node on this network, in ascending order of capability ID
-     */
-    Capability capabilities[ZT_MAX_NETWORK_CAPABILITIES];
-
-    /**
-     * Tags for this node on this network, in ascending order of tag ID
-     */
-    Tag tags[ZT_MAX_NETWORK_TAGS];
-
-    /**
-     * Certificates of ownership for this network member
-     */
-    CertificateOfOwnership certificatesOfOwnership[ZT_MAX_CERTIFICATES_OF_OWNERSHIP];
-
-    /**
-     * Network type (currently just public or private)
-     */
-    ZT_VirtualNetworkType type;
-
-    /**
-     * Network short name or empty string if not defined
-     */
-    char name[ZT_MAX_NETWORK_SHORT_NAME_LENGTH + 1];
-
-    /**
-     * Certificate of membership (for private networks)
-     */
-    CertificateOfMembership com;
-
-    /**
-     * Number of ZT-pushed DNS configurations
-     */
-    unsigned int dnsCount;
-
-    /**
-     * ZT pushed DNS configuration
-     */
-    ZT_VirtualNetworkDNS dns;
-
-    /**
-     * SSO enabled flag.
-     */
-    bool ssoEnabled;
-
-    /**
-     * SSO version
-     */
-    uint64_t ssoVersion;
-
-    /**
-     * Authentication URL if authentication is required
-     */
-    char authenticationURL[2048];
-
-    /**
-     * Time current authentication expires or 0 if external authentication is disabled
-     *
-     * Not used if authVersion >= 1
-     */
-    uint64_t authenticationExpiryTime;
-
-    /**
-     * OIDC issuer URL
-     */
-    char issuerURL[2048];
-
-    /**
-     * central base URL.
-     */
-    char centralAuthURL[2048];
-
-    /**
-     * sso nonce
-     */
-    char ssoNonce[128];
-
-    /**
-     * sso state
-     */
-    char ssoState[256];
-
-    /**
-     * oidc client id
-     */
-    char ssoClientID[256];
-
-    /**
-     * oidc provider
-     *
-     * because certain providers require specific scopes to be requested
-     * and others to be not requested in order to make everything work
-     * correctly
-     **/
-    char ssoProvider[64];
+	}
+
+	/**
+	 * @return Network type is public (no access control)
+	 */
+	inline bool isPublic() const
+	{
+		return (this->type == ZT_NETWORK_TYPE_PUBLIC);
+	}
+
+	/**
+	 * @return Network type is private (certificate access control)
+	 */
+	inline bool isPrivate() const
+	{
+		return (this->type == ZT_NETWORK_TYPE_PRIVATE);
+	}
+
+	/**
+	 * @return ZeroTier addresses of devices on this network designated as active bridges
+	 */
+	inline std::vector<Address> activeBridges() const
+	{
+		std::vector<Address> r;
+		for (unsigned int i = 0; i < specialistCount; ++i) {
+			if ((specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE) != 0) {
+				r.push_back(Address(specialists[i]));
+			}
+		}
+		return r;
+	}
+
+	inline unsigned int activeBridges(Address ab[ZT_MAX_NETWORK_SPECIALISTS]) const
+	{
+		unsigned int c = 0;
+		for (unsigned int i = 0; i < specialistCount; ++i) {
+			if ((specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE) != 0) {
+				ab[c++] = specialists[i];
+			}
+		}
+		return c;
+	}
+
+	inline bool isActiveBridge(const Address& a) const
+	{
+		for (unsigned int i = 0; i < specialistCount; ++i) {
+			if (((specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE) != 0) && (a == specialists[i])) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	inline std::vector<Address> anchors() const
+	{
+		std::vector<Address> r;
+		for (unsigned int i = 0; i < specialistCount; ++i) {
+			if ((specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_ANCHOR) != 0) {
+				r.push_back(Address(specialists[i]));
+			}
+		}
+		return r;
+	}
+
+	inline std::vector<Address> multicastReplicators() const
+	{
+		std::vector<Address> r;
+		for (unsigned int i = 0; i < specialistCount; ++i) {
+			if ((specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_MULTICAST_REPLICATOR) != 0) {
+				r.push_back(Address(specialists[i]));
+			}
+		}
+		return r;
+	}
+
+	inline unsigned int multicastReplicators(Address mr[ZT_MAX_NETWORK_SPECIALISTS]) const
+	{
+		unsigned int c = 0;
+		for (unsigned int i = 0; i < specialistCount; ++i) {
+			if ((specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_MULTICAST_REPLICATOR) != 0) {
+				mr[c++] = specialists[i];
+			}
+		}
+		return c;
+	}
+
+	inline bool isMulticastReplicator(const Address& a) const
+	{
+		for (unsigned int i = 0; i < specialistCount; ++i) {
+			if (((specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_MULTICAST_REPLICATOR) != 0) && (a == specialists[i])) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	inline std::vector<Address> alwaysContactAddresses() const
+	{
+		std::vector<Address> r;
+		for (unsigned int i = 0; i < specialistCount; ++i) {
+			if ((specialists[i] & (ZT_NETWORKCONFIG_SPECIALIST_TYPE_ANCHOR | ZT_NETWORKCONFIG_SPECIALIST_TYPE_MULTICAST_REPLICATOR)) != 0) {
+				r.push_back(Address(specialists[i]));
+			}
+		}
+		return r;
+	}
+
+	inline unsigned int alwaysContactAddresses(Address ac[ZT_MAX_NETWORK_SPECIALISTS]) const
+	{
+		unsigned int c = 0;
+		for (unsigned int i = 0; i < specialistCount; ++i) {
+			if ((specialists[i] & (ZT_NETWORKCONFIG_SPECIALIST_TYPE_ANCHOR | ZT_NETWORKCONFIG_SPECIALIST_TYPE_MULTICAST_REPLICATOR)) != 0) {
+				ac[c++] = specialists[i];
+			}
+		}
+		return c;
+	}
+
+	inline void alwaysContactAddresses(Hashtable<Address, std::vector<InetAddress> >& a) const
+	{
+		for (unsigned int i = 0; i < specialistCount; ++i) {
+			if ((specialists[i] & (ZT_NETWORKCONFIG_SPECIALIST_TYPE_ANCHOR | ZT_NETWORKCONFIG_SPECIALIST_TYPE_MULTICAST_REPLICATOR)) != 0) {
+				a[Address(specialists[i])];
+			}
+		}
+	}
+
+	/**
+	 * @param fromPeer Peer attempting to bridge other Ethernet peers onto network
+	 * @return True if this network allows bridging
+	 */
+	inline bool permitsBridging(const Address& fromPeer) const
+	{
+		for (unsigned int i = 0; i < specialistCount; ++i) {
+			if ((fromPeer == specialists[i]) && ((specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE) != 0)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	inline operator bool() const
+	{
+		return (networkId != 0);
+	}
+	inline bool operator==(const NetworkConfig& nc) const
+	{
+		return (memcmp(this, &nc, sizeof(NetworkConfig)) == 0);
+	}
+	inline bool operator!=(const NetworkConfig& nc) const
+	{
+		return (! (*this == nc));
+	}
+
+	/**
+	 * Add a specialist or mask flags if already present
+	 *
+	 * This masks the existing flags if the specialist is already here or adds
+	 * it otherwise.
+	 *
+	 * @param a Address of specialist
+	 * @param f Flags (OR of specialist role/type flags)
+	 * @return True if successfully masked or added
+	 */
+	inline bool addSpecialist(const Address& a, const uint64_t f)
+	{
+		const uint64_t aint = a.toInt();
+		for (unsigned int i = 0; i < specialistCount; ++i) {
+			if ((specialists[i] & 0xffffffffffULL) == aint) {
+				specialists[i] |= f;
+				return true;
+			}
+		}
+		if (specialistCount < ZT_MAX_NETWORK_SPECIALISTS) {
+			specialists[specialistCount++] = f | aint;
+			return true;
+		}
+		return false;
+	}
+
+	const Capability* capability(const uint32_t id) const
+	{
+		for (unsigned int i = 0; i < capabilityCount; ++i) {
+			if (capabilities[i].id() == id) {
+				return &(capabilities[i]);
+			}
+		}
+		return (Capability*)0;
+	}
+
+	const Tag* tag(const uint32_t id) const
+	{
+		for (unsigned int i = 0; i < tagCount; ++i) {
+			if (tags[i].id() == id) {
+				return &(tags[i]);
+			}
+		}
+		return (Tag*)0;
+	}
+
+	/**
+	 * Network ID that this configuration applies to
+	 */
+	uint64_t networkId;
+
+	/**
+	 * Controller-side time of config generation/issue
+	 */
+	int64_t timestamp;
+
+	/**
+	 * Max difference between timestamp and tag/capability timestamp
+	 */
+	int64_t credentialTimeMaxDelta;
+
+	/**
+	 * Controller-side revision counter for this configuration
+	 */
+	uint64_t revision;
+
+	/**
+	 * Address of device to which this config is issued
+	 */
+	Address issuedTo;
+
+	/**
+	 * If non-NULL, remote traces related to this network are sent here
+	 */
+	Address remoteTraceTarget;
+
+	/**
+	 * Flags (64-bit)
+	 */
+	uint64_t flags;
+
+	/**
+	 * Remote trace level
+	 */
+	Trace::Level remoteTraceLevel;
+
+	/**
+	 * Network MTU
+	 */
+	unsigned int mtu;
+
+	/**
+	 * Maximum number of recipients per multicast (not including active bridges)
+	 */
+	unsigned int multicastLimit;
+
+	/**
+	 * Number of specialists
+	 */
+	unsigned int specialistCount;
+
+	/**
+	 * Number of routes
+	 */
+	unsigned int routeCount;
+
+	/**
+	 * Number of ZT-managed static IP assignments
+	 */
+	unsigned int staticIpCount;
+
+	/**
+	 * Number of rule table entries
+	 */
+	unsigned int ruleCount;
+
+	/**
+	 * Number of capabilities
+	 */
+	unsigned int capabilityCount;
+
+	/**
+	 * Number of tags
+	 */
+	unsigned int tagCount;
+
+	/**
+	 * Number of certificates of ownership
+	 */
+	unsigned int certificateOfOwnershipCount;
+
+	/**
+	 * Specialist devices
+	 *
+	 * For each entry the least significant 40 bits are the device's ZeroTier
+	 * address and the most significant 24 bits are flags indicating its role.
+	 */
+	uint64_t specialists[ZT_MAX_NETWORK_SPECIALISTS];
+
+	/**
+	 * Statically defined "pushed" routes (including default gateways)
+	 */
+	ZT_VirtualNetworkRoute routes[ZT_MAX_NETWORK_ROUTES];
+
+	/**
+	 * Static IP assignments
+	 */
+	InetAddress staticIps[ZT_MAX_ZT_ASSIGNED_ADDRESSES];
+
+	/**
+	 * Base network rules
+	 */
+	ZT_VirtualNetworkRule rules[ZT_MAX_NETWORK_RULES];
+
+	/**
+	 * Capabilities for this node on this network, in ascending order of capability ID
+	 */
+	Capability capabilities[ZT_MAX_NETWORK_CAPABILITIES];
+
+	/**
+	 * Tags for this node on this network, in ascending order of tag ID
+	 */
+	Tag tags[ZT_MAX_NETWORK_TAGS];
+
+	/**
+	 * Certificates of ownership for this network member
+	 */
+	CertificateOfOwnership certificatesOfOwnership[ZT_MAX_CERTIFICATES_OF_OWNERSHIP];
+
+	/**
+	 * Network type (currently just public or private)
+	 */
+	ZT_VirtualNetworkType type;
+
+	/**
+	 * Network short name or empty string if not defined
+	 */
+	char name[ZT_MAX_NETWORK_SHORT_NAME_LENGTH + 1];
+
+	/**
+	 * Certificate of membership (for private networks)
+	 */
+	CertificateOfMembership com;
+
+	/**
+	 * Number of ZT-pushed DNS configurations
+	 */
+	unsigned int dnsCount;
+
+	/**
+	 * ZT pushed DNS configuration
+	 */
+	ZT_VirtualNetworkDNS dns;
+
+	/**
+	 * SSO enabled flag.
+	 */
+	bool ssoEnabled;
+
+	/**
+	 * SSO version
+	 */
+	uint64_t ssoVersion;
+
+	/**
+	 * Authentication URL if authentication is required
+	 */
+	char authenticationURL[2048];
+
+	/**
+	 * Time current authentication expires or 0 if external authentication is disabled
+	 *
+	 * Not used if authVersion >= 1
+	 */
+	uint64_t authenticationExpiryTime;
+
+	/**
+	 * OIDC issuer URL
+	 */
+	char issuerURL[2048];
+
+	/**
+	 * central base URL.
+	 */
+	char centralAuthURL[2048];
+
+	/**
+	 * sso nonce
+	 */
+	char ssoNonce[128];
+
+	/**
+	 * sso state
+	 */
+	char ssoState[256];
+
+	/**
+	 * oidc client id
+	 */
+	char ssoClientID[256];
+
+	/**
+	 * oidc provider
+	 *
+	 * because certain providers require specific scopes to be requested
+	 * and others to be not requested in order to make everything work
+	 * correctly
+	 **/
+	char ssoProvider[64];
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 63 - 63
node/NetworkController.hpp

@@ -32,76 +32,76 @@ struct InetAddress;
  */
 class NetworkController {
   public:
-    enum ErrorCode { NC_ERROR_NONE = 0, NC_ERROR_OBJECT_NOT_FOUND = 1, NC_ERROR_ACCESS_DENIED = 2, NC_ERROR_INTERNAL_SERVER_ERROR = 3, NC_ERROR_AUTHENTICATION_REQUIRED = 4 };
+	enum ErrorCode { NC_ERROR_NONE = 0, NC_ERROR_OBJECT_NOT_FOUND = 1, NC_ERROR_ACCESS_DENIED = 2, NC_ERROR_INTERNAL_SERVER_ERROR = 3, NC_ERROR_AUTHENTICATION_REQUIRED = 4 };
 
-    /**
-     * Interface for sender used to send pushes and replies
-     */
-    class Sender {
-      public:
-        /**
-         * Send a configuration to a remote peer
-         *
-         * @param nwid Network ID
-         * @param requestPacketId Request packet ID to send OK(NETWORK_CONFIG_REQUEST) or 0 to send NETWORK_CONFIG (push)
-         * @param destination Destination peer Address
-         * @param nc Network configuration to send
-         * @param sendLegacyFormatConfig If true, send an old-format network config
-         */
-        virtual void ncSendConfig(uint64_t nwid, uint64_t requestPacketId, const Address& destination, const NetworkConfig& nc, bool sendLegacyFormatConfig) = 0;
+	/**
+	 * Interface for sender used to send pushes and replies
+	 */
+	class Sender {
+	  public:
+		/**
+		 * Send a configuration to a remote peer
+		 *
+		 * @param nwid Network ID
+		 * @param requestPacketId Request packet ID to send OK(NETWORK_CONFIG_REQUEST) or 0 to send NETWORK_CONFIG (push)
+		 * @param destination Destination peer Address
+		 * @param nc Network configuration to send
+		 * @param sendLegacyFormatConfig If true, send an old-format network config
+		 */
+		virtual void ncSendConfig(uint64_t nwid, uint64_t requestPacketId, const Address& destination, const NetworkConfig& nc, bool sendLegacyFormatConfig) = 0;
 
-        /**
-         * Send revocation to a node
-         *
-         * @param destination Destination node address
-         * @param rev Revocation to send
-         */
-        virtual void ncSendRevocation(const Address& destination, const Revocation& rev) = 0;
+		/**
+		 * Send revocation to a node
+		 *
+		 * @param destination Destination node address
+		 * @param rev Revocation to send
+		 */
+		virtual void ncSendRevocation(const Address& destination, const Revocation& rev) = 0;
 
-        /**
-         * Send a network configuration request error
-         *
-         * If errorData/errorDataSize are provided they must point to a valid serialized
-         * Dictionary containing error data. They can be null/zero if not specified.
-         *
-         * @param nwid Network ID
-         * @param requestPacketId Request packet ID or 0 if none
-         * @param destination Destination peer Address
-         * @param errorCode Error code
-         * @param errorData Data associated with error or NULL if none
-         * @param errorDataSize Size of errorData in bytes
-         */
-        virtual void ncSendError(uint64_t nwid, uint64_t requestPacketId, const Address& destination, NetworkController::ErrorCode errorCode, const void* errorData, unsigned int errorDataSize) = 0;
-    };
+		/**
+		 * Send a network configuration request error
+		 *
+		 * If errorData/errorDataSize are provided they must point to a valid serialized
+		 * Dictionary containing error data. They can be null/zero if not specified.
+		 *
+		 * @param nwid Network ID
+		 * @param requestPacketId Request packet ID or 0 if none
+		 * @param destination Destination peer Address
+		 * @param errorCode Error code
+		 * @param errorData Data associated with error or NULL if none
+		 * @param errorDataSize Size of errorData in bytes
+		 */
+		virtual void ncSendError(uint64_t nwid, uint64_t requestPacketId, const Address& destination, NetworkController::ErrorCode errorCode, const void* errorData, unsigned int errorDataSize) = 0;
+	};
 
-    NetworkController()
-    {
-    }
-    virtual ~NetworkController()
-    {
-    }
+	NetworkController()
+	{
+	}
+	virtual ~NetworkController()
+	{
+	}
 
-    /**
-     * Called when this is added to a Node to initialize and supply info
-     *
-     * @param signingId Identity for signing of network configurations, certs, etc.
-     * @param sender Sender implementation for sending replies or config pushes
-     */
-    virtual void init(const Identity& signingId, Sender* sender) = 0;
+	/**
+	 * Called when this is added to a Node to initialize and supply info
+	 *
+	 * @param signingId Identity for signing of network configurations, certs, etc.
+	 * @param sender Sender implementation for sending replies or config pushes
+	 */
+	virtual void init(const Identity& signingId, Sender* sender) = 0;
 
-    /**
-     * Handle a network configuration request
-     *
-     * @param nwid 64-bit network ID
-     * @param fromAddr Originating wire address or null address if packet is not direct (or from self)
-     * @param requestPacketId Packet ID of request packet or 0 if not initiated by remote request
-     * @param identity ZeroTier identity of originating peer
-     * @param metaData Meta-data bundled with request (if any)
-     * @return Returns NETCONF_QUERY_OK if result 'nc' is valid, or an error code on error
-     */
-    virtual void request(uint64_t nwid, const InetAddress& fromAddr, uint64_t requestPacketId, const Identity& identity, const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY>& metaData) = 0;
+	/**
+	 * Handle a network configuration request
+	 *
+	 * @param nwid 64-bit network ID
+	 * @param fromAddr Originating wire address or null address if packet is not direct (or from self)
+	 * @param requestPacketId Packet ID of request packet or 0 if not initiated by remote request
+	 * @param identity ZeroTier identity of originating peer
+	 * @param metaData Meta-data bundled with request (if any)
+	 * @return Returns NETCONF_QUERY_OK if result 'nc' is valid, or an error code on error
+	 */
+	virtual void request(uint64_t nwid, const InetAddress& fromAddr, uint64_t requestPacketId, const Identity& identity, const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY>& metaData) = 0;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

File diff suppressed because it is too large
+ 572 - 572
node/Node.cpp


+ 304 - 304
node/Node.hpp

@@ -49,317 +49,317 @@ class World;
  */
 class Node : public NetworkController::Sender {
   public:
-    Node(void* uptr, void* tptr, const struct ZT_Node_Callbacks* callbacks, int64_t now);
-    virtual ~Node();
+	Node(void* uptr, void* tptr, const struct ZT_Node_Callbacks* callbacks, int64_t now);
+	virtual ~Node();
 
-    // Get rid of alignment warnings on 32-bit Windows and possibly improve performance
+	// Get rid of alignment warnings on 32-bit Windows and possibly improve performance
 #ifdef __WINDOWS__
-    void* operator new(size_t i)
-    {
-        return _mm_malloc(i, 16);
-    }
-    void operator delete(void* p)
-    {
-        _mm_free(p);
-    }
+	void* operator new(size_t i)
+	{
+		return _mm_malloc(i, 16);
+	}
+	void operator delete(void* p)
+	{
+		_mm_free(p);
+	}
 #endif
 
-    // Public API Functions ----------------------------------------------------
-
-    ZT_ResultCode processWirePacket(void* tptr, int64_t now, int64_t localSocket, const struct sockaddr_storage* remoteAddress, const void* packetData, unsigned int packetLength, volatile int64_t* nextBackgroundTaskDeadline);
-    ZT_ResultCode processVirtualNetworkFrame(
-        void* tptr,
-        int64_t now,
-        uint64_t nwid,
-        uint64_t sourceMac,
-        uint64_t destMac,
-        unsigned int etherType,
-        unsigned int vlanId,
-        const void* frameData,
-        unsigned int frameLength,
-        volatile int64_t* nextBackgroundTaskDeadline);
-    ZT_ResultCode processBackgroundTasks(void* tptr, int64_t now, volatile int64_t* nextBackgroundTaskDeadline);
-    ZT_ResultCode join(uint64_t nwid, void* uptr, void* tptr);
-    ZT_ResultCode leave(uint64_t nwid, void** uptr, void* tptr);
-    ZT_ResultCode multicastSubscribe(void* tptr, uint64_t nwid, uint64_t multicastGroup, unsigned long multicastAdi);
-    ZT_ResultCode multicastUnsubscribe(uint64_t nwid, uint64_t multicastGroup, unsigned long multicastAdi);
-    ZT_ResultCode orbit(void* tptr, uint64_t moonWorldId, uint64_t moonSeed);
-    ZT_ResultCode deorbit(void* tptr, uint64_t moonWorldId);
-    uint64_t address() const;
-    void status(ZT_NodeStatus* status) const;
-    ZT_PeerList* peers() const;
-    ZT_VirtualNetworkConfig* networkConfig(uint64_t nwid) const;
-    ZT_VirtualNetworkList* networks() const;
-    void freeQueryResult(void* qr);
-    int addLocalInterfaceAddress(const struct sockaddr_storage* addr);
-    void clearLocalInterfaceAddresses();
-    int sendUserMessage(void* tptr, uint64_t dest, uint64_t typeId, const void* data, unsigned int len);
-    void setNetconfMaster(void* networkControllerInstance);
-
-    // Internal functions ------------------------------------------------------
-
-    inline int64_t now() const
-    {
-        return _now;
-    }
-
-    inline bool putPacket(void* tPtr, const int64_t localSocket, const InetAddress& addr, const void* data, unsigned int len, unsigned int ttl = 0)
-    {
-        return (_cb.wirePacketSendFunction(reinterpret_cast<ZT_Node*>(this), _uPtr, tPtr, localSocket, reinterpret_cast<const struct sockaddr_storage*>(&addr), data, len, ttl) == 0);
-    }
-
-    inline void putFrame(void* tPtr, uint64_t nwid, void** nuptr, const MAC& source, const MAC& dest, unsigned int etherType, unsigned int vlanId, const void* data, unsigned int len)
-    {
-        _cb.virtualNetworkFrameFunction(reinterpret_cast<ZT_Node*>(this), _uPtr, tPtr, nwid, nuptr, source.toInt(), dest.toInt(), etherType, vlanId, data, len);
-    }
-
-    inline SharedPtr<Network> network(uint64_t nwid) const
-    {
-        Mutex::Lock _l(_networks_m);
-        const SharedPtr<Network>* n = _networks.get(nwid);
-        if (n) {
-            return *n;
-        }
-        return SharedPtr<Network>();
-    }
-
-    inline bool belongsToNetwork(uint64_t nwid) const
-    {
-        Mutex::Lock _l(_networks_m);
-        return _networks.contains(nwid);
-    }
-
-    inline std::vector<SharedPtr<Network> > allNetworks() const
-    {
-        std::vector<SharedPtr<Network> > nw;
-        Mutex::Lock _l(_networks_m);
-        Hashtable<uint64_t, SharedPtr<Network> >::Iterator i(*const_cast<Hashtable<uint64_t, SharedPtr<Network> >*>(&_networks));
-        uint64_t* k = (uint64_t*)0;
-        SharedPtr<Network>* v = (SharedPtr<Network>*)0;
-        while (i.next(k, v)) {
-            nw.push_back(*v);
-        }
-        return nw;
-    }
-
-    inline std::vector<InetAddress> directPaths() const
-    {
-        Mutex::Lock _l(_directPaths_m);
-        return _directPaths;
-    }
-
-    inline void postEvent(void* tPtr, ZT_Event ev, const void* md = (const void*)0)
-    {
-        _cb.eventCallback(reinterpret_cast<ZT_Node*>(this), _uPtr, tPtr, ev, md);
-    }
-
-    inline int configureVirtualNetworkPort(void* tPtr, uint64_t nwid, void** nuptr, ZT_VirtualNetworkConfigOperation op, const ZT_VirtualNetworkConfig* nc)
-    {
-        return _cb.virtualNetworkConfigFunction(reinterpret_cast<ZT_Node*>(this), _uPtr, tPtr, nwid, nuptr, op, nc);
-    }
-
-    inline bool online() const
-    {
-        return _online;
-    }
-
-    inline int stateObjectGet(void* const tPtr, ZT_StateObjectType type, const uint64_t id[2], void* const data, const unsigned int maxlen)
-    {
-        return _cb.stateGetFunction(reinterpret_cast<ZT_Node*>(this), _uPtr, tPtr, type, id, data, maxlen);
-    }
-    inline void stateObjectPut(void* const tPtr, ZT_StateObjectType type, const uint64_t id[2], const void* const data, const unsigned int len)
-    {
-        _cb.statePutFunction(reinterpret_cast<ZT_Node*>(this), _uPtr, tPtr, type, id, data, (int)len);
-    }
-    inline void stateObjectDelete(void* const tPtr, ZT_StateObjectType type, const uint64_t id[2])
-    {
-        _cb.statePutFunction(reinterpret_cast<ZT_Node*>(this), _uPtr, tPtr, type, id, (const void*)0, -1);
-    }
-
-    bool shouldUsePathForZeroTierTraffic(void* tPtr, const Address& ztaddr, const int64_t localSocket, const InetAddress& remoteAddress);
-    inline bool externalPathLookup(void* tPtr, const Address& ztaddr, int family, InetAddress& addr)
-    {
-        return ((_cb.pathLookupFunction) ? (_cb.pathLookupFunction(reinterpret_cast<ZT_Node*>(this), _uPtr, tPtr, ztaddr.toInt(), family, reinterpret_cast<struct sockaddr_storage*>(&addr)) != 0) : false);
-    }
-
-    uint64_t prng();
-    ZT_ResultCode setPhysicalPathConfiguration(const struct sockaddr_storage* pathNetwork, const ZT_PhysicalPathConfiguration* pathConfig);
-
-    World planet() const;
-    std::vector<World> moons() const;
-
-    inline const Identity& identity() const
-    {
-        return _RR.identity;
-    }
-
-    inline const std::vector<InetAddress> SurfaceAddresses() const
-    {
-        return _RR.sa->whoami();
-    }
-
-    inline Bond* bondController() const
-    {
-        return _RR.bc;
-    }
-
-    /**
-     * Register that we are expecting a reply to a packet ID
-     *
-     * This only uses the most significant bits of the packet ID, both to save space
-     * and to avoid using the higher bits that can be modified during armor() to
-     * mask against the packet send counter used for QoS detection.
-     *
-     * @param packetId Packet ID to expect reply to
-     */
-    inline void expectReplyTo(const uint64_t packetId)
-    {
-        const unsigned long pid2 = (unsigned long)(packetId >> 32);
-        const unsigned long bucket = (unsigned long)(pid2 & ZT_EXPECTING_REPLIES_BUCKET_MASK1);
-        _expectingRepliesTo[bucket][_expectingRepliesToBucketPtr[bucket]++ & ZT_EXPECTING_REPLIES_BUCKET_MASK2] = (uint32_t)pid2;
-    }
-
-    /**
-     * Check whether a given packet ID is something we are expecting a reply to
-     *
-     * This only uses the most significant bits of the packet ID, both to save space
-     * and to avoid using the higher bits that can be modified during armor() to
-     * mask against the packet send counter used for QoS detection.
-     *
-     * @param packetId Packet ID to check
-     * @return True if we're expecting a reply
-     */
-    inline bool expectingReplyTo(const uint64_t packetId) const
-    {
-        const uint32_t pid2 = (uint32_t)(packetId >> 32);
-        const unsigned long bucket = (unsigned long)(pid2 & ZT_EXPECTING_REPLIES_BUCKET_MASK1);
-        for (unsigned long i = 0; i <= ZT_EXPECTING_REPLIES_BUCKET_MASK2; ++i) {
-            if (_expectingRepliesTo[bucket][i] == pid2) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Check whether we should do potentially expensive identity verification (rate limit)
-     *
-     * @param now Current time
-     * @param from Source address of packet
-     * @return True if within rate limits
-     */
-    inline bool rateGateIdentityVerification(const int64_t now, const InetAddress& from)
-    {
-        unsigned long iph = from.rateGateHash();
-        if ((now - _lastIdentityVerification[iph]) >= ZT_IDENTITY_VALIDATION_SOURCE_RATE_LIMIT) {
-            _lastIdentityVerification[iph] = now;
-            return true;
-        }
-        return false;
-    }
-
-    virtual void ncSendConfig(uint64_t nwid, uint64_t requestPacketId, const Address& destination, const NetworkConfig& nc, bool sendLegacyFormatConfig);
-    virtual void ncSendRevocation(const Address& destination, const Revocation& rev);
-    virtual void ncSendError(uint64_t nwid, uint64_t requestPacketId, const Address& destination, NetworkController::ErrorCode errorCode, const void* errorData, unsigned int errorDataSize);
-
-    inline const Address& remoteTraceTarget() const
-    {
-        return _remoteTraceTarget;
-    }
-    inline Trace::Level remoteTraceLevel() const
-    {
-        return _remoteTraceLevel;
-    }
-
-    inline bool localControllerHasAuthorized(const int64_t now, const uint64_t nwid, const Address& addr) const
-    {
-        _localControllerAuthorizations_m.lock();
-        const int64_t* const at = _localControllerAuthorizations.get(_LocalControllerAuth(nwid, addr));
-        _localControllerAuthorizations_m.unlock();
-        if (at) {
-            return ((now - *at) < (ZT_NETWORK_AUTOCONF_DELAY * 3));
-        }
-        return false;
-    }
-
-    inline void statsLogVerb(const unsigned int v, const unsigned int bytes)
-    {
-        ++_stats.inVerbCounts[v];
-        _stats.inVerbBytes[v] += (uint64_t)bytes;
-    }
-
-    inline void setLowBandwidthMode(bool isEnabled)
-    {
-        _lowBandwidthMode = isEnabled;
-    }
-
-    inline bool lowBandwidthModeEnabled()
-    {
-        return _lowBandwidthMode;
-    }
-
-    void initMultithreading(unsigned int concurrency, bool cpuPinningEnabled);
+	// Public API Functions ----------------------------------------------------
+
+	ZT_ResultCode processWirePacket(void* tptr, int64_t now, int64_t localSocket, const struct sockaddr_storage* remoteAddress, const void* packetData, unsigned int packetLength, volatile int64_t* nextBackgroundTaskDeadline);
+	ZT_ResultCode processVirtualNetworkFrame(
+		void* tptr,
+		int64_t now,
+		uint64_t nwid,
+		uint64_t sourceMac,
+		uint64_t destMac,
+		unsigned int etherType,
+		unsigned int vlanId,
+		const void* frameData,
+		unsigned int frameLength,
+		volatile int64_t* nextBackgroundTaskDeadline);
+	ZT_ResultCode processBackgroundTasks(void* tptr, int64_t now, volatile int64_t* nextBackgroundTaskDeadline);
+	ZT_ResultCode join(uint64_t nwid, void* uptr, void* tptr);
+	ZT_ResultCode leave(uint64_t nwid, void** uptr, void* tptr);
+	ZT_ResultCode multicastSubscribe(void* tptr, uint64_t nwid, uint64_t multicastGroup, unsigned long multicastAdi);
+	ZT_ResultCode multicastUnsubscribe(uint64_t nwid, uint64_t multicastGroup, unsigned long multicastAdi);
+	ZT_ResultCode orbit(void* tptr, uint64_t moonWorldId, uint64_t moonSeed);
+	ZT_ResultCode deorbit(void* tptr, uint64_t moonWorldId);
+	uint64_t address() const;
+	void status(ZT_NodeStatus* status) const;
+	ZT_PeerList* peers() const;
+	ZT_VirtualNetworkConfig* networkConfig(uint64_t nwid) const;
+	ZT_VirtualNetworkList* networks() const;
+	void freeQueryResult(void* qr);
+	int addLocalInterfaceAddress(const struct sockaddr_storage* addr);
+	void clearLocalInterfaceAddresses();
+	int sendUserMessage(void* tptr, uint64_t dest, uint64_t typeId, const void* data, unsigned int len);
+	void setNetconfMaster(void* networkControllerInstance);
+
+	// Internal functions ------------------------------------------------------
+
+	inline int64_t now() const
+	{
+		return _now;
+	}
+
+	inline bool putPacket(void* tPtr, const int64_t localSocket, const InetAddress& addr, const void* data, unsigned int len, unsigned int ttl = 0)
+	{
+		return (_cb.wirePacketSendFunction(reinterpret_cast<ZT_Node*>(this), _uPtr, tPtr, localSocket, reinterpret_cast<const struct sockaddr_storage*>(&addr), data, len, ttl) == 0);
+	}
+
+	inline void putFrame(void* tPtr, uint64_t nwid, void** nuptr, const MAC& source, const MAC& dest, unsigned int etherType, unsigned int vlanId, const void* data, unsigned int len)
+	{
+		_cb.virtualNetworkFrameFunction(reinterpret_cast<ZT_Node*>(this), _uPtr, tPtr, nwid, nuptr, source.toInt(), dest.toInt(), etherType, vlanId, data, len);
+	}
+
+	inline SharedPtr<Network> network(uint64_t nwid) const
+	{
+		Mutex::Lock _l(_networks_m);
+		const SharedPtr<Network>* n = _networks.get(nwid);
+		if (n) {
+			return *n;
+		}
+		return SharedPtr<Network>();
+	}
+
+	inline bool belongsToNetwork(uint64_t nwid) const
+	{
+		Mutex::Lock _l(_networks_m);
+		return _networks.contains(nwid);
+	}
+
+	inline std::vector<SharedPtr<Network> > allNetworks() const
+	{
+		std::vector<SharedPtr<Network> > nw;
+		Mutex::Lock _l(_networks_m);
+		Hashtable<uint64_t, SharedPtr<Network> >::Iterator i(*const_cast<Hashtable<uint64_t, SharedPtr<Network> >*>(&_networks));
+		uint64_t* k = (uint64_t*)0;
+		SharedPtr<Network>* v = (SharedPtr<Network>*)0;
+		while (i.next(k, v)) {
+			nw.push_back(*v);
+		}
+		return nw;
+	}
+
+	inline std::vector<InetAddress> directPaths() const
+	{
+		Mutex::Lock _l(_directPaths_m);
+		return _directPaths;
+	}
+
+	inline void postEvent(void* tPtr, ZT_Event ev, const void* md = (const void*)0)
+	{
+		_cb.eventCallback(reinterpret_cast<ZT_Node*>(this), _uPtr, tPtr, ev, md);
+	}
+
+	inline int configureVirtualNetworkPort(void* tPtr, uint64_t nwid, void** nuptr, ZT_VirtualNetworkConfigOperation op, const ZT_VirtualNetworkConfig* nc)
+	{
+		return _cb.virtualNetworkConfigFunction(reinterpret_cast<ZT_Node*>(this), _uPtr, tPtr, nwid, nuptr, op, nc);
+	}
+
+	inline bool online() const
+	{
+		return _online;
+	}
+
+	inline int stateObjectGet(void* const tPtr, ZT_StateObjectType type, const uint64_t id[2], void* const data, const unsigned int maxlen)
+	{
+		return _cb.stateGetFunction(reinterpret_cast<ZT_Node*>(this), _uPtr, tPtr, type, id, data, maxlen);
+	}
+	inline void stateObjectPut(void* const tPtr, ZT_StateObjectType type, const uint64_t id[2], const void* const data, const unsigned int len)
+	{
+		_cb.statePutFunction(reinterpret_cast<ZT_Node*>(this), _uPtr, tPtr, type, id, data, (int)len);
+	}
+	inline void stateObjectDelete(void* const tPtr, ZT_StateObjectType type, const uint64_t id[2])
+	{
+		_cb.statePutFunction(reinterpret_cast<ZT_Node*>(this), _uPtr, tPtr, type, id, (const void*)0, -1);
+	}
+
+	bool shouldUsePathForZeroTierTraffic(void* tPtr, const Address& ztaddr, const int64_t localSocket, const InetAddress& remoteAddress);
+	inline bool externalPathLookup(void* tPtr, const Address& ztaddr, int family, InetAddress& addr)
+	{
+		return ((_cb.pathLookupFunction) ? (_cb.pathLookupFunction(reinterpret_cast<ZT_Node*>(this), _uPtr, tPtr, ztaddr.toInt(), family, reinterpret_cast<struct sockaddr_storage*>(&addr)) != 0) : false);
+	}
+
+	uint64_t prng();
+	ZT_ResultCode setPhysicalPathConfiguration(const struct sockaddr_storage* pathNetwork, const ZT_PhysicalPathConfiguration* pathConfig);
+
+	World planet() const;
+	std::vector<World> moons() const;
+
+	inline const Identity& identity() const
+	{
+		return _RR.identity;
+	}
+
+	inline const std::vector<InetAddress> SurfaceAddresses() const
+	{
+		return _RR.sa->whoami();
+	}
+
+	inline Bond* bondController() const
+	{
+		return _RR.bc;
+	}
+
+	/**
+	 * Register that we are expecting a reply to a packet ID
+	 *
+	 * This only uses the most significant bits of the packet ID, both to save space
+	 * and to avoid using the higher bits that can be modified during armor() to
+	 * mask against the packet send counter used for QoS detection.
+	 *
+	 * @param packetId Packet ID to expect reply to
+	 */
+	inline void expectReplyTo(const uint64_t packetId)
+	{
+		const unsigned long pid2 = (unsigned long)(packetId >> 32);
+		const unsigned long bucket = (unsigned long)(pid2 & ZT_EXPECTING_REPLIES_BUCKET_MASK1);
+		_expectingRepliesTo[bucket][_expectingRepliesToBucketPtr[bucket]++ & ZT_EXPECTING_REPLIES_BUCKET_MASK2] = (uint32_t)pid2;
+	}
+
+	/**
+	 * Check whether a given packet ID is something we are expecting a reply to
+	 *
+	 * This only uses the most significant bits of the packet ID, both to save space
+	 * and to avoid using the higher bits that can be modified during armor() to
+	 * mask against the packet send counter used for QoS detection.
+	 *
+	 * @param packetId Packet ID to check
+	 * @return True if we're expecting a reply
+	 */
+	inline bool expectingReplyTo(const uint64_t packetId) const
+	{
+		const uint32_t pid2 = (uint32_t)(packetId >> 32);
+		const unsigned long bucket = (unsigned long)(pid2 & ZT_EXPECTING_REPLIES_BUCKET_MASK1);
+		for (unsigned long i = 0; i <= ZT_EXPECTING_REPLIES_BUCKET_MASK2; ++i) {
+			if (_expectingRepliesTo[bucket][i] == pid2) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Check whether we should do potentially expensive identity verification (rate limit)
+	 *
+	 * @param now Current time
+	 * @param from Source address of packet
+	 * @return True if within rate limits
+	 */
+	inline bool rateGateIdentityVerification(const int64_t now, const InetAddress& from)
+	{
+		unsigned long iph = from.rateGateHash();
+		if ((now - _lastIdentityVerification[iph]) >= ZT_IDENTITY_VALIDATION_SOURCE_RATE_LIMIT) {
+			_lastIdentityVerification[iph] = now;
+			return true;
+		}
+		return false;
+	}
+
+	virtual void ncSendConfig(uint64_t nwid, uint64_t requestPacketId, const Address& destination, const NetworkConfig& nc, bool sendLegacyFormatConfig);
+	virtual void ncSendRevocation(const Address& destination, const Revocation& rev);
+	virtual void ncSendError(uint64_t nwid, uint64_t requestPacketId, const Address& destination, NetworkController::ErrorCode errorCode, const void* errorData, unsigned int errorDataSize);
+
+	inline const Address& remoteTraceTarget() const
+	{
+		return _remoteTraceTarget;
+	}
+	inline Trace::Level remoteTraceLevel() const
+	{
+		return _remoteTraceLevel;
+	}
+
+	inline bool localControllerHasAuthorized(const int64_t now, const uint64_t nwid, const Address& addr) const
+	{
+		_localControllerAuthorizations_m.lock();
+		const int64_t* const at = _localControllerAuthorizations.get(_LocalControllerAuth(nwid, addr));
+		_localControllerAuthorizations_m.unlock();
+		if (at) {
+			return ((now - *at) < (ZT_NETWORK_AUTOCONF_DELAY * 3));
+		}
+		return false;
+	}
+
+	inline void statsLogVerb(const unsigned int v, const unsigned int bytes)
+	{
+		++_stats.inVerbCounts[v];
+		_stats.inVerbBytes[v] += (uint64_t)bytes;
+	}
+
+	inline void setLowBandwidthMode(bool isEnabled)
+	{
+		_lowBandwidthMode = isEnabled;
+	}
+
+	inline bool lowBandwidthModeEnabled()
+	{
+		return _lowBandwidthMode;
+	}
+
+	void initMultithreading(unsigned int concurrency, bool cpuPinningEnabled);
 
   public:
-    RuntimeEnvironment _RR;
-    RuntimeEnvironment* RR;
-    void* _uPtr;   // _uptr (lower case) is reserved in Visual Studio :P
-    ZT_Node_Callbacks _cb;
-
-    // For tracking packet IDs to filter out OK/ERROR replies to packets we did not send
-    uint8_t _expectingRepliesToBucketPtr[ZT_EXPECTING_REPLIES_BUCKET_MASK1 + 1];
-    uint32_t _expectingRepliesTo[ZT_EXPECTING_REPLIES_BUCKET_MASK1 + 1][ZT_EXPECTING_REPLIES_BUCKET_MASK2 + 1];
-
-    // Time of last identity verification indexed by InetAddress.rateGateHash() -- used in IncomingPacket::_doHELLO() via rateGateIdentityVerification()
-    int64_t _lastIdentityVerification[16384];
-
-    // Statistics about stuff happening
-    volatile ZT_NodeStatistics _stats;
-
-    // Map that remembers if we have recently sent a network config to someone
-    // querying us as a controller.
-    struct _LocalControllerAuth {
-        uint64_t nwid, address;
-        _LocalControllerAuth(const uint64_t nwid_, const Address& address_) : nwid(nwid_), address(address_.toInt())
-        {
-        }
-        inline unsigned long hashCode() const
-        {
-            return (unsigned long)(nwid ^ address);
-        }
-        inline bool operator==(const _LocalControllerAuth& a) const
-        {
-            return ((a.nwid == nwid) && (a.address == address));
-        }
-        inline bool operator!=(const _LocalControllerAuth& a) const
-        {
-            return ((a.nwid != nwid) || (a.address != address));
-        }
-    };
-    Hashtable<_LocalControllerAuth, int64_t> _localControllerAuthorizations;
-    Mutex _localControllerAuthorizations_m;
-
-    Hashtable<uint64_t, SharedPtr<Network> > _networks;
-    Mutex _networks_m;
-
-    std::vector<InetAddress> _directPaths;
-    Mutex _directPaths_m;
-
-    Mutex _backgroundTasksLock;
-
-    Address _remoteTraceTarget;
-    enum Trace::Level _remoteTraceLevel;
-
-    volatile int64_t _now;
-    int64_t _lastPingCheck;
-    int64_t _lastGratuitousPingCheck;
-    int64_t _lastHousekeepingRun;
-    int64_t _lastMemoizedTraceSettings;
-    volatile int64_t _prngState[2];
-    bool _online;
-    bool _lowBandwidthMode;
+	RuntimeEnvironment _RR;
+	RuntimeEnvironment* RR;
+	void* _uPtr;   // _uptr (lower case) is reserved in Visual Studio :P
+	ZT_Node_Callbacks _cb;
+
+	// For tracking packet IDs to filter out OK/ERROR replies to packets we did not send
+	uint8_t _expectingRepliesToBucketPtr[ZT_EXPECTING_REPLIES_BUCKET_MASK1 + 1];
+	uint32_t _expectingRepliesTo[ZT_EXPECTING_REPLIES_BUCKET_MASK1 + 1][ZT_EXPECTING_REPLIES_BUCKET_MASK2 + 1];
+
+	// Time of last identity verification indexed by InetAddress.rateGateHash() -- used in IncomingPacket::_doHELLO() via rateGateIdentityVerification()
+	int64_t _lastIdentityVerification[16384];
+
+	// Statistics about stuff happening
+	volatile ZT_NodeStatistics _stats;
+
+	// Map that remembers if we have recently sent a network config to someone
+	// querying us as a controller.
+	struct _LocalControllerAuth {
+		uint64_t nwid, address;
+		_LocalControllerAuth(const uint64_t nwid_, const Address& address_) : nwid(nwid_), address(address_.toInt())
+		{
+		}
+		inline unsigned long hashCode() const
+		{
+			return (unsigned long)(nwid ^ address);
+		}
+		inline bool operator==(const _LocalControllerAuth& a) const
+		{
+			return ((a.nwid == nwid) && (a.address == address));
+		}
+		inline bool operator!=(const _LocalControllerAuth& a) const
+		{
+			return ((a.nwid != nwid) || (a.address != address));
+		}
+	};
+	Hashtable<_LocalControllerAuth, int64_t> _localControllerAuthorizations;
+	Mutex _localControllerAuthorizations_m;
+
+	Hashtable<uint64_t, SharedPtr<Network> > _networks;
+	Mutex _networks_m;
+
+	std::vector<InetAddress> _directPaths;
+	Mutex _directPaths_m;
+
+	Mutex _backgroundTasksLock;
+
+	Address _remoteTraceTarget;
+	enum Trace::Level _remoteTraceLevel;
+
+	volatile int64_t _now;
+	int64_t _lastPingCheck;
+	int64_t _lastGratuitousPingCheck;
+	int64_t _lastHousekeepingRun;
+	int64_t _lastMemoizedTraceSettings;
+	volatile int64_t _prngState[2];
+	bool _online;
+	bool _lowBandwidthMode;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 57 - 57
node/OutboundMulticast.cpp

@@ -24,71 +24,71 @@
 namespace ZeroTier {
 
 void OutboundMulticast::init(
-    const RuntimeEnvironment* RR,
-    uint64_t timestamp,
-    uint64_t nwid,
-    bool disableCompression,
-    unsigned int limit,
-    unsigned int gatherLimit,
-    const MAC& src,
-    const MulticastGroup& dest,
-    unsigned int etherType,
-    const void* payload,
-    unsigned int len)
+	const RuntimeEnvironment* RR,
+	uint64_t timestamp,
+	uint64_t nwid,
+	bool disableCompression,
+	unsigned int limit,
+	unsigned int gatherLimit,
+	const MAC& src,
+	const MulticastGroup& dest,
+	unsigned int etherType,
+	const void* payload,
+	unsigned int len)
 {
-    uint8_t flags = 0;
+	uint8_t flags = 0;
 
-    _timestamp = timestamp;
-    _nwid = nwid;
-    if (src) {
-        _macSrc = src;
-        flags |= 0x04;
-    }
-    else {
-        _macSrc.fromAddress(RR->identity.address(), nwid);
-    }
-    _macDest = dest.mac();
-    _limit = limit;
-    _frameLen = (len < ZT_MAX_MTU) ? len : ZT_MAX_MTU;
-    _etherType = etherType;
+	_timestamp = timestamp;
+	_nwid = nwid;
+	if (src) {
+		_macSrc = src;
+		flags |= 0x04;
+	}
+	else {
+		_macSrc.fromAddress(RR->identity.address(), nwid);
+	}
+	_macDest = dest.mac();
+	_limit = limit;
+	_frameLen = (len < ZT_MAX_MTU) ? len : ZT_MAX_MTU;
+	_etherType = etherType;
 
-    if (gatherLimit) {
-        flags |= 0x02;
-    }
+	if (gatherLimit) {
+		flags |= 0x02;
+	}
 
-    _packet.setSource(RR->identity.address());
-    _packet.setVerb(Packet::VERB_MULTICAST_FRAME);
-    _packet.append((uint64_t)nwid);
-    _packet.append(flags);
-    if (gatherLimit) {
-        _packet.append((uint32_t)gatherLimit);
-    }
-    if (src) {
-        src.appendTo(_packet);
-    }
-    dest.mac().appendTo(_packet);
-    _packet.append((uint32_t)dest.adi());
-    _packet.append((uint16_t)etherType);
-    _packet.append(payload, _frameLen);
-    if (! disableCompression) {
-        _packet.compress();
-    }
+	_packet.setSource(RR->identity.address());
+	_packet.setVerb(Packet::VERB_MULTICAST_FRAME);
+	_packet.append((uint64_t)nwid);
+	_packet.append(flags);
+	if (gatherLimit) {
+		_packet.append((uint32_t)gatherLimit);
+	}
+	if (src) {
+		src.appendTo(_packet);
+	}
+	dest.mac().appendTo(_packet);
+	_packet.append((uint32_t)dest.adi());
+	_packet.append((uint16_t)etherType);
+	_packet.append(payload, _frameLen);
+	if (! disableCompression) {
+		_packet.compress();
+	}
 
-    memcpy(_frameData, payload, _frameLen);
+	memcpy(_frameData, payload, _frameLen);
 }
 
 void OutboundMulticast::sendOnly(const RuntimeEnvironment* RR, void* tPtr, const Address& toAddr)
 {
-    const SharedPtr<Network> nw(RR->node->network(_nwid));
-    uint8_t QoSBucket = 255;   // Dummy value
-    if ((nw) && (nw->filterOutgoingPacket(tPtr, true, RR->identity.address(), toAddr, _macSrc, _macDest, _frameData, _frameLen, _etherType, 0, QoSBucket))) {
-        nw->pushCredentialsIfNeeded(tPtr, toAddr, RR->node->now());
-        _packet.newInitializationVector();
-        _packet.setDestination(toAddr);
-        RR->node->expectReplyTo(_packet.packetId());
-        _tmp = _packet;
-        RR->sw->send(tPtr, _tmp, true);
-    }
+	const SharedPtr<Network> nw(RR->node->network(_nwid));
+	uint8_t QoSBucket = 255;   // Dummy value
+	if ((nw) && (nw->filterOutgoingPacket(tPtr, true, RR->identity.address(), toAddr, _macSrc, _macDest, _frameData, _frameLen, _etherType, 0, QoSBucket))) {
+		nw->pushCredentialsIfNeeded(tPtr, toAddr, RR->node->now());
+		_packet.newInitializationVector();
+		_packet.setDestination(toAddr);
+		RR->node->expectReplyTo(_packet.packetId());
+		_tmp = _packet;
+		RR->sw->send(tPtr, _tmp, true);
+	}
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 124 - 124
node/OutboundMulticast.hpp

@@ -36,133 +36,133 @@ class RuntimeEnvironment;
  */
 class OutboundMulticast {
   public:
-    /**
-     * Create an uninitialized outbound multicast
-     *
-     * It must be initialized with init().
-     */
-    OutboundMulticast()
-    {
-    }
-
-    /**
-     * Initialize outbound multicast
-     *
-     * @param RR Runtime environment
-     * @param timestamp Creation time
-     * @param nwid Network ID
-     * @param disableCompression Disable compression of frame payload
-     * @param limit Multicast limit for desired number of packets to send
-     * @param gatherLimit Number to lazily/implicitly gather with this frame or 0 for none
-     * @param src Source MAC address of frame or NULL to imply compute from sender ZT address
-     * @param dest Destination multicast group (MAC + ADI)
-     * @param etherType 16-bit Ethernet type ID
-     * @param payload Data
-     * @param len Length of data
-     * @throws std::out_of_range Data too large to fit in a MULTICAST_FRAME
-     */
-    void init(
-        const RuntimeEnvironment* RR,
-        uint64_t timestamp,
-        uint64_t nwid,
-        bool disableCompression,
-        unsigned int limit,
-        unsigned int gatherLimit,
-        const MAC& src,
-        const MulticastGroup& dest,
-        unsigned int etherType,
-        const void* payload,
-        unsigned int len);
-
-    /**
-     * @return Multicast creation time
-     */
-    inline uint64_t timestamp() const
-    {
-        return _timestamp;
-    }
-
-    /**
-     * @param now Current time
-     * @return True if this multicast is expired (has exceeded transmit timeout)
-     */
-    inline bool expired(int64_t now) const
-    {
-        return ((now - _timestamp) >= ZT_MULTICAST_TRANSMIT_TIMEOUT);
-    }
-
-    /**
-     * @return True if this outbound multicast has been sent to enough peers
-     */
-    inline bool atLimit() const
-    {
-        return (_alreadySentTo.size() >= _limit);
-    }
-
-    /**
-     * Just send without checking log
-     *
-     * @param RR Runtime environment
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param toAddr Destination address
-     */
-    void sendOnly(const RuntimeEnvironment* RR, void* tPtr, const Address& toAddr);
-
-    /**
-     * Just send and log but do not check sent log
-     *
-     * @param RR Runtime environment
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param toAddr Destination address
-     */
-    inline void sendAndLog(const RuntimeEnvironment* RR, void* tPtr, const Address& toAddr)
-    {
-        _alreadySentTo.push_back(toAddr);
-        sendOnly(RR, tPtr, toAddr);
-    }
-
-    /**
-     * Log an address as having been used so we will not send there in the future
-     *
-     * @param toAddr Address to log as sent
-     */
-    inline void logAsSent(const Address& toAddr)
-    {
-        _alreadySentTo.push_back(toAddr);
-    }
-
-    /**
-     * Try to send this to a given peer if it hasn't been sent to them already
-     *
-     * @param RR Runtime environment
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param toAddr Destination address
-     * @return True if address is new and packet was sent to switch, false if duplicate
-     */
-    inline bool sendIfNew(const RuntimeEnvironment* RR, void* tPtr, const Address& toAddr)
-    {
-        if (std::find(_alreadySentTo.begin(), _alreadySentTo.end(), toAddr) == _alreadySentTo.end()) {
-            sendAndLog(RR, tPtr, toAddr);
-            return true;
-        }
-        else {
-            return false;
-        }
-    }
+	/**
+	 * Create an uninitialized outbound multicast
+	 *
+	 * It must be initialized with init().
+	 */
+	OutboundMulticast()
+	{
+	}
+
+	/**
+	 * Initialize outbound multicast
+	 *
+	 * @param RR Runtime environment
+	 * @param timestamp Creation time
+	 * @param nwid Network ID
+	 * @param disableCompression Disable compression of frame payload
+	 * @param limit Multicast limit for desired number of packets to send
+	 * @param gatherLimit Number to lazily/implicitly gather with this frame or 0 for none
+	 * @param src Source MAC address of frame or NULL to imply compute from sender ZT address
+	 * @param dest Destination multicast group (MAC + ADI)
+	 * @param etherType 16-bit Ethernet type ID
+	 * @param payload Data
+	 * @param len Length of data
+	 * @throws std::out_of_range Data too large to fit in a MULTICAST_FRAME
+	 */
+	void init(
+		const RuntimeEnvironment* RR,
+		uint64_t timestamp,
+		uint64_t nwid,
+		bool disableCompression,
+		unsigned int limit,
+		unsigned int gatherLimit,
+		const MAC& src,
+		const MulticastGroup& dest,
+		unsigned int etherType,
+		const void* payload,
+		unsigned int len);
+
+	/**
+	 * @return Multicast creation time
+	 */
+	inline uint64_t timestamp() const
+	{
+		return _timestamp;
+	}
+
+	/**
+	 * @param now Current time
+	 * @return True if this multicast is expired (has exceeded transmit timeout)
+	 */
+	inline bool expired(int64_t now) const
+	{
+		return ((now - _timestamp) >= ZT_MULTICAST_TRANSMIT_TIMEOUT);
+	}
+
+	/**
+	 * @return True if this outbound multicast has been sent to enough peers
+	 */
+	inline bool atLimit() const
+	{
+		return (_alreadySentTo.size() >= _limit);
+	}
+
+	/**
+	 * Just send without checking log
+	 *
+	 * @param RR Runtime environment
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param toAddr Destination address
+	 */
+	void sendOnly(const RuntimeEnvironment* RR, void* tPtr, const Address& toAddr);
+
+	/**
+	 * Just send and log but do not check sent log
+	 *
+	 * @param RR Runtime environment
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param toAddr Destination address
+	 */
+	inline void sendAndLog(const RuntimeEnvironment* RR, void* tPtr, const Address& toAddr)
+	{
+		_alreadySentTo.push_back(toAddr);
+		sendOnly(RR, tPtr, toAddr);
+	}
+
+	/**
+	 * Log an address as having been used so we will not send there in the future
+	 *
+	 * @param toAddr Address to log as sent
+	 */
+	inline void logAsSent(const Address& toAddr)
+	{
+		_alreadySentTo.push_back(toAddr);
+	}
+
+	/**
+	 * Try to send this to a given peer if it hasn't been sent to them already
+	 *
+	 * @param RR Runtime environment
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param toAddr Destination address
+	 * @return True if address is new and packet was sent to switch, false if duplicate
+	 */
+	inline bool sendIfNew(const RuntimeEnvironment* RR, void* tPtr, const Address& toAddr)
+	{
+		if (std::find(_alreadySentTo.begin(), _alreadySentTo.end(), toAddr) == _alreadySentTo.end()) {
+			sendAndLog(RR, tPtr, toAddr);
+			return true;
+		}
+		else {
+			return false;
+		}
+	}
 
   private:
-    uint64_t _timestamp;
-    uint64_t _nwid;
-    MAC _macSrc;
-    MAC _macDest;
-    unsigned int _limit;
-    unsigned int _frameLen;
-    unsigned int _etherType;
-    Packet _packet, _tmp;
-    std::vector<Address> _alreadySentTo;
-    uint8_t _frameData[ZT_MAX_MTU];
+	uint64_t _timestamp;
+	uint64_t _nwid;
+	MAC _macSrc;
+	MAC _macDest;
+	unsigned int _limit;
+	unsigned int _frameLen;
+	unsigned int _etherType;
+	Packet _packet, _tmp;
+	std::vector<Address> _alreadySentTo;
+	uint8_t _frameData[ZT_MAX_MTU];
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

File diff suppressed because it is too large
+ 702 - 702
node/Packet.cpp


+ 1169 - 1169
node/Packet.hpp

@@ -171,12 +171,12 @@
 #define ZT_PUSH_DIRECT_PATHS_FLAG_CLUSTER_REDIRECT 0x02
 
 // Field indexes in packet header
-#define ZT_PACKET_IDX_IV      0
-#define ZT_PACKET_IDX_DEST    8
+#define ZT_PACKET_IDX_IV	  0
+#define ZT_PACKET_IDX_DEST	  8
 #define ZT_PACKET_IDX_SOURCE  13
-#define ZT_PACKET_IDX_FLAGS   18
-#define ZT_PACKET_IDX_MAC     19
-#define ZT_PACKET_IDX_VERB    27
+#define ZT_PACKET_IDX_FLAGS	  18
+#define ZT_PACKET_IDX_MAC	  19
+#define ZT_PACKET_IDX_VERB	  27
 #define ZT_PACKET_IDX_PAYLOAD 28
 
 /**
@@ -195,12 +195,12 @@
 #define ZT_PROTO_MIN_PACKET_LENGTH ZT_PACKET_IDX_PAYLOAD
 
 // Indexes of fields in fragment header
-#define ZT_PACKET_FRAGMENT_IDX_PACKET_ID          0
-#define ZT_PACKET_FRAGMENT_IDX_DEST               8
+#define ZT_PACKET_FRAGMENT_IDX_PACKET_ID		  0
+#define ZT_PACKET_FRAGMENT_IDX_DEST				  8
 #define ZT_PACKET_FRAGMENT_IDX_FRAGMENT_INDICATOR 13
-#define ZT_PACKET_FRAGMENT_IDX_FRAGMENT_NO        14
-#define ZT_PACKET_FRAGMENT_IDX_HOPS               15
-#define ZT_PACKET_FRAGMENT_IDX_PAYLOAD            16
+#define ZT_PACKET_FRAGMENT_IDX_FRAGMENT_NO		  14
+#define ZT_PACKET_FRAGMENT_IDX_HOPS				  15
+#define ZT_PACKET_FRAGMENT_IDX_PAYLOAD			  16
 
 /**
  * Magic number found at ZT_PACKET_FRAGMENT_IDX_FRAGMENT_INDICATOR
@@ -219,89 +219,89 @@
 // See their respective handler functions.
 
 #define ZT_PROTO_VERB_HELLO_IDX_PROTOCOL_VERSION (ZT_PACKET_IDX_PAYLOAD)
-#define ZT_PROTO_VERB_HELLO_IDX_MAJOR_VERSION    (ZT_PROTO_VERB_HELLO_IDX_PROTOCOL_VERSION + 1)
-#define ZT_PROTO_VERB_HELLO_IDX_MINOR_VERSION    (ZT_PROTO_VERB_HELLO_IDX_MAJOR_VERSION + 1)
-#define ZT_PROTO_VERB_HELLO_IDX_REVISION         (ZT_PROTO_VERB_HELLO_IDX_MINOR_VERSION + 1)
-#define ZT_PROTO_VERB_HELLO_IDX_TIMESTAMP        (ZT_PROTO_VERB_HELLO_IDX_REVISION + 2)
-#define ZT_PROTO_VERB_HELLO_IDX_IDENTITY         (ZT_PROTO_VERB_HELLO_IDX_TIMESTAMP + 8)
+#define ZT_PROTO_VERB_HELLO_IDX_MAJOR_VERSION	 (ZT_PROTO_VERB_HELLO_IDX_PROTOCOL_VERSION + 1)
+#define ZT_PROTO_VERB_HELLO_IDX_MINOR_VERSION	 (ZT_PROTO_VERB_HELLO_IDX_MAJOR_VERSION + 1)
+#define ZT_PROTO_VERB_HELLO_IDX_REVISION		 (ZT_PROTO_VERB_HELLO_IDX_MINOR_VERSION + 1)
+#define ZT_PROTO_VERB_HELLO_IDX_TIMESTAMP		 (ZT_PROTO_VERB_HELLO_IDX_REVISION + 2)
+#define ZT_PROTO_VERB_HELLO_IDX_IDENTITY		 (ZT_PROTO_VERB_HELLO_IDX_TIMESTAMP + 8)
 
-#define ZT_PROTO_VERB_ERROR_IDX_IN_RE_VERB      (ZT_PACKET_IDX_PAYLOAD)
+#define ZT_PROTO_VERB_ERROR_IDX_IN_RE_VERB		(ZT_PACKET_IDX_PAYLOAD)
 #define ZT_PROTO_VERB_ERROR_IDX_IN_RE_PACKET_ID (ZT_PROTO_VERB_ERROR_IDX_IN_RE_VERB + 1)
-#define ZT_PROTO_VERB_ERROR_IDX_ERROR_CODE      (ZT_PROTO_VERB_ERROR_IDX_IN_RE_PACKET_ID + 8)
-#define ZT_PROTO_VERB_ERROR_IDX_PAYLOAD         (ZT_PROTO_VERB_ERROR_IDX_ERROR_CODE + 1)
+#define ZT_PROTO_VERB_ERROR_IDX_ERROR_CODE		(ZT_PROTO_VERB_ERROR_IDX_IN_RE_PACKET_ID + 8)
+#define ZT_PROTO_VERB_ERROR_IDX_PAYLOAD			(ZT_PROTO_VERB_ERROR_IDX_ERROR_CODE + 1)
 
-#define ZT_PROTO_VERB_OK_IDX_IN_RE_VERB      (ZT_PACKET_IDX_PAYLOAD)
+#define ZT_PROTO_VERB_OK_IDX_IN_RE_VERB		 (ZT_PACKET_IDX_PAYLOAD)
 #define ZT_PROTO_VERB_OK_IDX_IN_RE_PACKET_ID (ZT_PROTO_VERB_OK_IDX_IN_RE_VERB + 1)
-#define ZT_PROTO_VERB_OK_IDX_PAYLOAD         (ZT_PROTO_VERB_OK_IDX_IN_RE_PACKET_ID + 8)
+#define ZT_PROTO_VERB_OK_IDX_PAYLOAD		 (ZT_PROTO_VERB_OK_IDX_IN_RE_PACKET_ID + 8)
 
 #define ZT_PROTO_VERB_WHOIS_IDX_ZTADDRESS (ZT_PACKET_IDX_PAYLOAD)
 
-#define ZT_PROTO_VERB_RENDEZVOUS_IDX_FLAGS     (ZT_PACKET_IDX_PAYLOAD)
+#define ZT_PROTO_VERB_RENDEZVOUS_IDX_FLAGS	   (ZT_PACKET_IDX_PAYLOAD)
 #define ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS (ZT_PROTO_VERB_RENDEZVOUS_IDX_FLAGS + 1)
-#define ZT_PROTO_VERB_RENDEZVOUS_IDX_PORT      (ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS + 5)
+#define ZT_PROTO_VERB_RENDEZVOUS_IDX_PORT	   (ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS + 5)
 #define ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRLEN   (ZT_PROTO_VERB_RENDEZVOUS_IDX_PORT + 2)
 #define ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRESS   (ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRLEN + 1)
 
 #define ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID (ZT_PACKET_IDX_PAYLOAD)
 #define ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE  (ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID + 8)
-#define ZT_PROTO_VERB_FRAME_IDX_PAYLOAD    (ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE + 2)
+#define ZT_PROTO_VERB_FRAME_IDX_PAYLOAD	   (ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE + 2)
 
 #define ZT_PROTO_VERB_EXT_FRAME_IDX_NETWORK_ID (ZT_PACKET_IDX_PAYLOAD)
 #define ZT_PROTO_VERB_EXT_FRAME_LEN_NETWORK_ID 8
-#define ZT_PROTO_VERB_EXT_FRAME_IDX_FLAGS      (ZT_PROTO_VERB_EXT_FRAME_IDX_NETWORK_ID + ZT_PROTO_VERB_EXT_FRAME_LEN_NETWORK_ID)
-#define ZT_PROTO_VERB_EXT_FRAME_LEN_FLAGS      1
-#define ZT_PROTO_VERB_EXT_FRAME_IDX_COM        (ZT_PROTO_VERB_EXT_FRAME_IDX_FLAGS + ZT_PROTO_VERB_EXT_FRAME_LEN_FLAGS)
-#define ZT_PROTO_VERB_EXT_FRAME_IDX_TO         (ZT_PROTO_VERB_EXT_FRAME_IDX_FLAGS + ZT_PROTO_VERB_EXT_FRAME_LEN_FLAGS)
-#define ZT_PROTO_VERB_EXT_FRAME_LEN_TO         6
-#define ZT_PROTO_VERB_EXT_FRAME_IDX_FROM       (ZT_PROTO_VERB_EXT_FRAME_IDX_TO + ZT_PROTO_VERB_EXT_FRAME_LEN_TO)
-#define ZT_PROTO_VERB_EXT_FRAME_LEN_FROM       6
+#define ZT_PROTO_VERB_EXT_FRAME_IDX_FLAGS	   (ZT_PROTO_VERB_EXT_FRAME_IDX_NETWORK_ID + ZT_PROTO_VERB_EXT_FRAME_LEN_NETWORK_ID)
+#define ZT_PROTO_VERB_EXT_FRAME_LEN_FLAGS	   1
+#define ZT_PROTO_VERB_EXT_FRAME_IDX_COM		   (ZT_PROTO_VERB_EXT_FRAME_IDX_FLAGS + ZT_PROTO_VERB_EXT_FRAME_LEN_FLAGS)
+#define ZT_PROTO_VERB_EXT_FRAME_IDX_TO		   (ZT_PROTO_VERB_EXT_FRAME_IDX_FLAGS + ZT_PROTO_VERB_EXT_FRAME_LEN_FLAGS)
+#define ZT_PROTO_VERB_EXT_FRAME_LEN_TO		   6
+#define ZT_PROTO_VERB_EXT_FRAME_IDX_FROM	   (ZT_PROTO_VERB_EXT_FRAME_IDX_TO + ZT_PROTO_VERB_EXT_FRAME_LEN_TO)
+#define ZT_PROTO_VERB_EXT_FRAME_LEN_FROM	   6
 #define ZT_PROTO_VERB_EXT_FRAME_IDX_ETHERTYPE  (ZT_PROTO_VERB_EXT_FRAME_IDX_FROM + ZT_PROTO_VERB_EXT_FRAME_LEN_FROM)
 #define ZT_PROTO_VERB_EXT_FRAME_LEN_ETHERTYPE  2
-#define ZT_PROTO_VERB_EXT_FRAME_IDX_PAYLOAD    (ZT_PROTO_VERB_EXT_FRAME_IDX_ETHERTYPE + ZT_PROTO_VERB_EXT_FRAME_LEN_ETHERTYPE)
+#define ZT_PROTO_VERB_EXT_FRAME_IDX_PAYLOAD	   (ZT_PROTO_VERB_EXT_FRAME_IDX_ETHERTYPE + ZT_PROTO_VERB_EXT_FRAME_LEN_ETHERTYPE)
 
 #define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID (ZT_PACKET_IDX_PAYLOAD)
-#define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN   (ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID + 8)
-#define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT       (ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN + 2)
+#define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN	(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID + 8)
+#define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT		(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN + 2)
 
-#define ZT_PROTO_VERB_MULTICAST_GATHER_IDX_NETWORK_ID   (ZT_PACKET_IDX_PAYLOAD)
-#define ZT_PROTO_VERB_MULTICAST_GATHER_IDX_FLAGS        (ZT_PROTO_VERB_MULTICAST_GATHER_IDX_NETWORK_ID + 8)
-#define ZT_PROTO_VERB_MULTICAST_GATHER_IDX_MAC          (ZT_PROTO_VERB_MULTICAST_GATHER_IDX_FLAGS + 1)
-#define ZT_PROTO_VERB_MULTICAST_GATHER_IDX_ADI          (ZT_PROTO_VERB_MULTICAST_GATHER_IDX_MAC + 6)
+#define ZT_PROTO_VERB_MULTICAST_GATHER_IDX_NETWORK_ID	(ZT_PACKET_IDX_PAYLOAD)
+#define ZT_PROTO_VERB_MULTICAST_GATHER_IDX_FLAGS		(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_NETWORK_ID + 8)
+#define ZT_PROTO_VERB_MULTICAST_GATHER_IDX_MAC			(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_FLAGS + 1)
+#define ZT_PROTO_VERB_MULTICAST_GATHER_IDX_ADI			(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_MAC + 6)
 #define ZT_PROTO_VERB_MULTICAST_GATHER_IDX_GATHER_LIMIT (ZT_PROTO_VERB_MULTICAST_GATHER_IDX_ADI + 4)
-#define ZT_PROTO_VERB_MULTICAST_GATHER_IDX_COM          (ZT_PROTO_VERB_MULTICAST_GATHER_IDX_GATHER_LIMIT + 4)
+#define ZT_PROTO_VERB_MULTICAST_GATHER_IDX_COM			(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_GATHER_LIMIT + 4)
 
 // Note: COM, GATHER_LIMIT, and SOURCE_MAC are optional, and so are specified without size
 #define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID   (ZT_PACKET_IDX_PAYLOAD)
-#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS        (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID + 8)
-#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_COM          (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS + 1)
+#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS		   (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID + 8)
+#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_COM		   (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS + 1)
 #define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_GATHER_LIMIT (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS + 1)
 #define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SOURCE_MAC   (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS + 1)
-#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DEST_MAC     (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS + 1)
-#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DEST_ADI     (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DEST_MAC + 6)
-#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ETHERTYPE    (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DEST_ADI + 4)
-#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME        (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ETHERTYPE + 2)
+#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DEST_MAC	   (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS + 1)
+#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DEST_ADI	   (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DEST_MAC + 6)
+#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ETHERTYPE	   (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DEST_ADI + 4)
+#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME		   (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ETHERTYPE + 2)
 
-#define ZT_PROTO_VERB_HELLO__OK__IDX_TIMESTAMP        (ZT_PROTO_VERB_OK_IDX_PAYLOAD)
+#define ZT_PROTO_VERB_HELLO__OK__IDX_TIMESTAMP		  (ZT_PROTO_VERB_OK_IDX_PAYLOAD)
 #define ZT_PROTO_VERB_HELLO__OK__IDX_PROTOCOL_VERSION (ZT_PROTO_VERB_HELLO__OK__IDX_TIMESTAMP + 8)
-#define ZT_PROTO_VERB_HELLO__OK__IDX_MAJOR_VERSION    (ZT_PROTO_VERB_HELLO__OK__IDX_PROTOCOL_VERSION + 1)
-#define ZT_PROTO_VERB_HELLO__OK__IDX_MINOR_VERSION    (ZT_PROTO_VERB_HELLO__OK__IDX_MAJOR_VERSION + 1)
-#define ZT_PROTO_VERB_HELLO__OK__IDX_REVISION         (ZT_PROTO_VERB_HELLO__OK__IDX_MINOR_VERSION + 1)
+#define ZT_PROTO_VERB_HELLO__OK__IDX_MAJOR_VERSION	  (ZT_PROTO_VERB_HELLO__OK__IDX_PROTOCOL_VERSION + 1)
+#define ZT_PROTO_VERB_HELLO__OK__IDX_MINOR_VERSION	  (ZT_PROTO_VERB_HELLO__OK__IDX_MAJOR_VERSION + 1)
+#define ZT_PROTO_VERB_HELLO__OK__IDX_REVISION		  (ZT_PROTO_VERB_HELLO__OK__IDX_MINOR_VERSION + 1)
 
 #define ZT_PROTO_VERB_WHOIS__OK__IDX_IDENTITY (ZT_PROTO_VERB_OK_IDX_PAYLOAD)
 
 #define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_NETWORK_ID (ZT_PROTO_VERB_OK_IDX_PAYLOAD)
-#define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT_LEN   (ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_NETWORK_ID + 8)
-#define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT       (ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT_LEN + 2)
+#define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT_LEN	 (ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_NETWORK_ID + 8)
+#define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT		 (ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT_LEN + 2)
 
-#define ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_NETWORK_ID     (ZT_PROTO_VERB_OK_IDX_PAYLOAD)
-#define ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_MAC            (ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_NETWORK_ID + 8)
-#define ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_ADI            (ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_MAC + 6)
+#define ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_NETWORK_ID	   (ZT_PROTO_VERB_OK_IDX_PAYLOAD)
+#define ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_MAC			   (ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_NETWORK_ID + 8)
+#define ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_ADI			   (ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_MAC + 6)
 #define ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_GATHER_RESULTS (ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_ADI + 4)
 
-#define ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_NETWORK_ID             (ZT_PROTO_VERB_OK_IDX_PAYLOAD)
-#define ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_MAC                    (ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_NETWORK_ID + 8)
-#define ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_ADI                    (ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_MAC + 6)
-#define ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_FLAGS                  (ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_ADI + 4)
+#define ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_NETWORK_ID			  (ZT_PROTO_VERB_OK_IDX_PAYLOAD)
+#define ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_MAC					  (ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_NETWORK_ID + 8)
+#define ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_ADI					  (ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_MAC + 6)
+#define ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_FLAGS				  (ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_ADI + 4)
 #define ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_COM_AND_GATHER_RESULTS (ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_FLAGS + 1)
 
 // ---------------------------------------------------------------------------
@@ -345,1121 +345,1121 @@ namespace ZeroTier {
  */
 class Packet : public Buffer<ZT_PROTO_MAX_PACKET_LENGTH> {
   public:
-    /**
-     * A packet fragment
-     *
-     * Fragments are sent if a packet is larger than UDP MTU. The first fragment
-     * is sent with its normal header with the fragmented flag set. Remaining
-     * fragments are sent this way.
-     *
-     * The fragmented bit indicates that there is at least one fragment. Fragments
-     * themselves contain the total, so the receiver must "learn" this from the
-     * first fragment it receives.
-     *
-     * Fragments are sent with the following format:
-     *   <[8] packet ID of packet whose fragment this belongs to>
-     *   <[5] destination ZT address>
-     *   <[1] 0xff, a reserved address, signals that this isn't a normal packet>
-     *   <[1] total fragments (most significant 4 bits), fragment no (LS 4 bits)>
-     *   <[1] ZT hop count (top 5 bits unused and must be zero)>
-     *   <[...] fragment data>
-     *
-     * The protocol supports a maximum of 16 fragments. If a fragment is received
-     * before its main packet header, it should be cached for a brief period of
-     * time to see if its parent arrives. Loss of any fragment constitutes packet
-     * loss; there is no retransmission mechanism. The receiver must wait for full
-     * receipt to authenticate and decrypt; there is no per-fragment MAC. (But if
-     * fragments are corrupt, the MAC will fail for the whole assembled packet.)
-     */
-    class Fragment : public Buffer<ZT_PROTO_MAX_PACKET_LENGTH> {
-      public:
-        Fragment() : Buffer<ZT_PROTO_MAX_PACKET_LENGTH>()
-        {
-        }
-
-        template <unsigned int C2> Fragment(const Buffer<C2>& b) : Buffer<ZT_PROTO_MAX_PACKET_LENGTH>(b)
-        {
-        }
-
-        Fragment(const void* data, unsigned int len) : Buffer<ZT_PROTO_MAX_PACKET_LENGTH>(data, len)
-        {
-        }
-
-        /**
-         * Initialize from a packet
-         *
-         * @param p Original assembled packet
-         * @param fragStart Start of fragment (raw index in packet data)
-         * @param fragLen Length of fragment in bytes
-         * @param fragNo Which fragment (>= 1, since 0 is Packet with end chopped off)
-         * @param fragTotal Total number of fragments (including 0)
-         */
-        Fragment(const Packet& p, unsigned int fragStart, unsigned int fragLen, unsigned int fragNo, unsigned int fragTotal)
-        {
-            init(p, fragStart, fragLen, fragNo, fragTotal);
-        }
-
-        /**
-         * Initialize from a packet
-         *
-         * @param p Original assembled packet
-         * @param fragStart Start of fragment (raw index in packet data)
-         * @param fragLen Length of fragment in bytes
-         * @param fragNo Which fragment (>= 1, since 0 is Packet with end chopped off)
-         * @param fragTotal Total number of fragments (including 0)
-         */
-        inline void init(const Packet& p, unsigned int fragStart, unsigned int fragLen, unsigned int fragNo, unsigned int fragTotal)
-        {
-            if ((fragStart + fragLen) > p.size()) {
-                throw ZT_EXCEPTION_OUT_OF_BOUNDS;
-            }
-            setSize(fragLen + ZT_PROTO_MIN_FRAGMENT_LENGTH);
-
-            // NOTE: this copies both the IV/packet ID and the destination address.
-            memcpy(field(ZT_PACKET_FRAGMENT_IDX_PACKET_ID, 13), p.field(ZT_PACKET_IDX_IV, 13), 13);
-
-            (*this)[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_INDICATOR] = ZT_PACKET_FRAGMENT_INDICATOR;
-            (*this)[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_NO] = (char)(((fragTotal & 0xf) << 4) | (fragNo & 0xf));
-            (*this)[ZT_PACKET_FRAGMENT_IDX_HOPS] = 0;
-
-            memcpy(field(ZT_PACKET_FRAGMENT_IDX_PAYLOAD, fragLen), p.field(fragStart, fragLen), fragLen);
-        }
-
-        /**
-         * Get this fragment's destination
-         *
-         * @return Destination ZT address
-         */
-        inline Address destination() const
-        {
-            return Address(field(ZT_PACKET_FRAGMENT_IDX_DEST, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
-        }
-
-        /**
-         * @return True if fragment is of a valid length
-         */
-        inline bool lengthValid() const
-        {
-            return (size() >= ZT_PACKET_FRAGMENT_IDX_PAYLOAD);
-        }
-
-        /**
-         * @return ID of packet this is a fragment of
-         */
-        inline uint64_t packetId() const
-        {
-            return at<uint64_t>(ZT_PACKET_FRAGMENT_IDX_PACKET_ID);
-        }
-
-        /**
-         * @return Total number of fragments in packet
-         */
-        inline unsigned int totalFragments() const
-        {
-            return (((unsigned int)((*this)[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_NO]) >> 4) & 0xf);
-        }
-
-        /**
-         * @return Fragment number of this fragment
-         */
-        inline unsigned int fragmentNumber() const
-        {
-            return ((unsigned int)((*this)[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_NO]) & 0xf);
-        }
-
-        /**
-         * @return Fragment ZT hop count
-         */
-        inline unsigned int hops() const
-        {
-            return (unsigned int)((*this)[ZT_PACKET_FRAGMENT_IDX_HOPS]);
-        }
-
-        /**
-         * Increment this packet's hop count
-         */
-        inline void incrementHops()
-        {
-            (*this)[ZT_PACKET_FRAGMENT_IDX_HOPS] = (((*this)[ZT_PACKET_FRAGMENT_IDX_HOPS]) + 1) & ZT_PROTO_MAX_HOPS;
-        }
-
-        /**
-         * @return Length of payload in bytes
-         */
-        inline unsigned int payloadLength() const
-        {
-            return ((size() > ZT_PACKET_FRAGMENT_IDX_PAYLOAD) ? (size() - ZT_PACKET_FRAGMENT_IDX_PAYLOAD) : 0);
-        }
-
-        /**
-         * @return Raw packet payload
-         */
-        inline const unsigned char* payload() const
-        {
-            return field(ZT_PACKET_FRAGMENT_IDX_PAYLOAD, size() - ZT_PACKET_FRAGMENT_IDX_PAYLOAD);
-        }
-    };
-
-    /**
-     * ZeroTier protocol verbs
-     */
-    enum Verb /* Max value: 32 (5 bits) */
-    {
-        /**
-         * No operation (ignored, no reply)
-         */
-        VERB_NOP = 0x00,
-
-        /**
-         * Announcement of a node's existence and vitals:
-         *   <[1] protocol version>
-         *   <[1] software major version>
-         *   <[1] software minor version>
-         *   <[2] software revision>
-         *   <[8] timestamp for determining latency>
-         *   <[...] binary serialized identity (see Identity)>
-         *   <[...] physical destination address of packet>
-         *   <[8] 64-bit world ID of current planet>
-         *   <[8] 64-bit timestamp of current planet>
-         *   [... remainder if packet is encrypted using cryptField() ...]
-         *   <[2] 16-bit number of moons>
-         *   [<[1] 8-bit type ID of moon>]
-         *   [<[8] 64-bit world ID of moon>]
-         *   [<[8] 64-bit timestamp of moon>]
-         *   [... additional moon type/ID/timestamp tuples ...]
-         *
-         * HELLO is sent in the clear as it is how peers share their identity
-         * public keys. A few additional fields are sent in the clear too, but
-         * these are things that are public info or are easy to determine. As
-         * of 1.2.0 we have added a few more fields, but since these could have
-         * the potential to be sensitive we introduced the encryption of the
-         * remainder of the packet. See cryptField(). Packet MAC is still
-         * performed of course, so authentication occurs as normal.
-         *
-         * Destination address is the actual wire address to which the packet
-         * was sent. See InetAddress::serialize() for format.
-         *
-         * OK payload:
-         *   <[8] HELLO timestamp field echo>
-         *   <[1] protocol version>
-         *   <[1] software major version>
-         *   <[1] software minor version>
-         *   <[2] software revision>
-         *   <[...] physical destination address of packet>
-         *   <[2] 16-bit length of world update(s) or 0 if none>
-         *   [[...] updates to planets and/or moons]
-         *
-         * With the exception of the timestamp, the other fields pertain to the
-         * respondent who is sending OK and are not echoes.
-         *
-         * Note that OK is fully encrypted so no selective cryptField() of
-         * potentially sensitive fields is needed.
-         *
-         * ERROR has no payload.
-         */
-        VERB_HELLO = 0x01,
-
-        /**
-         * Error response:
-         *   <[1] in-re verb>
-         *   <[8] in-re packet ID>
-         *   <[1] error code>
-         *   <[...] error-dependent payload>
-         */
-        VERB_ERROR = 0x02,
-
-        /**
-         * Success response:
-         *   <[1] in-re verb>
-         *   <[8] in-re packet ID>
-         *   <[...] request-specific payload>
-         */
-        VERB_OK = 0x03,
-
-        /**
-         * Query an identity by address:
-         *   <[5] address to look up>
-         *   [<[...] additional addresses to look up>
-         *
-         * OK response payload:
-         *   <[...] binary serialized identity>
-         *  [<[...] additional binary serialized identities>]
-         *
-         * If querying a cluster, duplicate OK responses may occasionally occur.
-         * These must be tolerated, which is easy since they'll have info you
-         * already have.
-         *
-         * If the address is not found, no response is generated. The semantics
-         * of WHOIS is similar to ARP and NDP in that persistent retrying can
-         * be performed.
-         */
-        VERB_WHOIS = 0x04,
-
-        /**
-         * Relay-mediated NAT traversal or firewall punching initiation:
-         *   <[1] flags (unused, currently 0)>
-         *   <[5] ZeroTier address of peer that might be found at this address>
-         *   <[2] 16-bit protocol address port>
-         *   <[1] protocol address length (4 for IPv4, 16 for IPv6)>
-         *   <[...] protocol address (network byte order)>
-         *
-         * An upstream node can send this to inform both sides of a relay of
-         * information they might use to establish a direct connection.
-         *
-         * Upon receipt a peer sends HELLO to establish a direct link.
-         *
-         * No OK or ERROR is generated.
-         */
-        VERB_RENDEZVOUS = 0x05,
-
-        /**
-         * ZT-to-ZT unicast ethernet frame (shortened EXT_FRAME):
-         *   <[8] 64-bit network ID>
-         *   <[2] 16-bit ethertype>
-         *   <[...] ethernet payload>
-         *
-         * MAC addresses are derived from the packet's source and destination
-         * ZeroTier addresses. This is a shortened EXT_FRAME that elides full
-         * Ethernet framing and other optional flags and features when they
-         * are not necessary.
-         *
-         * ERROR may be generated if a membership certificate is needed for a
-         * closed network. Payload will be network ID.
-         */
-        VERB_FRAME = 0x06,
-
-        /**
-         * Full Ethernet frame with MAC addressing and optional fields:
-         *   <[8] 64-bit network ID>
-         *   <[1] flags>
-         *   <[6] destination MAC or all zero for destination node>
-         *   <[6] source MAC or all zero for node of origin>
-         *   <[2] 16-bit ethertype>
-         *   <[...] ethernet payload>
-         *
-         * Flags:
-         *   0x01 - Certificate of network membership attached (DEPRECATED)
-         *   0x02 - Most significant bit of subtype (see below)
-         *   0x04 - Middle bit of subtype (see below)
-         *   0x08 - Least significant bit of subtype (see below)
-         *   0x10 - ACK requested in the form of OK(EXT_FRAME)
-         *
-         * Subtypes (0..7):
-         *   0x0 - Normal frame (bridging can be determined by checking MAC)
-         *   0x1 - TEEd outbound frame
-         *   0x2 - REDIRECTed outbound frame
-         *   0x3 - WATCHed outbound frame (TEE with ACK, ACK bit also set)
-         *   0x4 - TEEd inbound frame
-         *   0x5 - REDIRECTed inbound frame
-         *   0x6 - WATCHed inbound frame
-         *   0x7 - (reserved for future use)
-         *
-         * An extended frame carries full MAC addressing, making it a
-         * superset of VERB_FRAME. It is used for bridged traffic,
-         * redirected or observed traffic via rules, and can in theory
-         * be used for multicast though MULTICAST_FRAME exists for that
-         * purpose and has additional options and capabilities.
-         *
-         * OK payload (if ACK flag is set):
-         *   <[8] 64-bit network ID>
-         */
-        VERB_EXT_FRAME = 0x07,
-
-        /**
-         * ECHO request (a.k.a. ping):
-         *   <[...] arbitrary payload>
-         *
-         * This generates OK with a copy of the transmitted payload. No ERROR
-         * is generated. Response to ECHO requests is optional and ECHO may be
-         * ignored if a node detects a possible flood.
-         */
-        VERB_ECHO = 0x08,
-
-        /**
-         * Announce interest in multicast group(s):
-         *   <[8] 64-bit network ID>
-         *   <[6] multicast Ethernet address>
-         *   <[4] multicast additional distinguishing information (ADI)>
-         *   [... additional tuples of network/address/adi ...]
-         *
-         * LIKEs may be sent to any peer, though a good implementation should
-         * restrict them to peers on the same network they're for and to network
-         * controllers and root servers. In the current network, root servers
-         * will provide the service of final multicast cache.
-         *
-         * VERB_NETWORK_CREDENTIALS should be pushed along with this, especially
-         * if using upstream (e.g. root) nodes as multicast databases. This allows
-         * GATHERs to be authenticated.
-         *
-         * OK/ERROR are not generated.
-         */
-        VERB_MULTICAST_LIKE = 0x09,
-
-        /**
-         * Network credentials push:
-         *   [<[...] one or more certificates of membership>]
-         *   <[1] 0x00, null byte marking end of COM array>
-         *   <[2] 16-bit number of capabilities>
-         *   <[...] one or more serialized Capability>
-         *   <[2] 16-bit number of tags>
-         *   <[...] one or more serialized Tags>
-         *   <[2] 16-bit number of revocations>
-         *   <[...] one or more serialized Revocations>
-         *   <[2] 16-bit number of certificates of ownership>
-         *   <[...] one or more serialized CertificateOfOwnership>
-         *
-         * This can be sent by anyone at any time to push network credentials.
-         * These will of course only be accepted if they are properly signed.
-         * Credentials can be for any number of networks.
-         *
-         * The use of a zero byte to terminate the COM section is for legacy
-         * backward compatibility. Newer fields are prefixed with a length.
-         *
-         * OK/ERROR are not generated.
-         */
-        VERB_NETWORK_CREDENTIALS = 0x0a,
-
-        /**
-         * Network configuration request:
-         *   <[8] 64-bit network ID>
-         *   <[2] 16-bit length of request meta-data dictionary>
-         *   <[...] string-serialized request meta-data>
-         *   <[8] 64-bit revision of netconf we currently have>
-         *   <[8] 64-bit timestamp of netconf we currently have>
-         *
-         * This message requests network configuration from a node capable of
-         * providing it.
-         *
-         * Responses to this are always whole configs intended for the recipient.
-         * For patches and other updates a NETWORK_CONFIG is sent instead.
-         *
-         * It would be valid and correct as of 1.2.0 to use NETWORK_CONFIG always,
-         * but OK(NETWORK_CONFIG_REQUEST) should be sent for compatibility.
-         *
-         * OK response payload:
-         *   <[8] 64-bit network ID>
-         *   <[2] 16-bit length of network configuration dictionary chunk>
-         *   <[...] network configuration dictionary (may be incomplete)>
-         *   [ ... end of legacy single chunk response ... ]
-         *   <[1] 8-bit flags>
-         *   <[8] 64-bit config update ID (should never be 0)>
-         *   <[4] 32-bit total length of assembled dictionary>
-         *   <[4] 32-bit index of chunk>
-         *   [ ... end signed portion ... ]
-         *   <[1] 8-bit chunk signature type>
-         *   <[2] 16-bit length of chunk signature>
-         *   <[...] chunk signature>
-         *
-         * The chunk signature signs the entire payload of the OK response.
-         * Currently only one signature type is supported: ed25519 (1).
-         *
-         * Each config chunk is signed to prevent memory exhaustion or
-         * traffic crowding DOS attacks against config fragment assembly.
-         *
-         * If the packet is from the network controller it is permitted to end
-         * before the config update ID or other chunking related or signature
-         * fields. This is to support older controllers that don't include
-         * these fields and may be removed in the future.
-         *
-         * ERROR response payload:
-         *   <[8] 64-bit network ID>
-         *   <[2] 16-bit length of error-related data (optional)>
-         *   <[...] error-related data (optional)>
-         *
-         * Error related data is a Dictionary containing things like a URL
-         * for authentication or a human-readable error message, and is
-         * optional and may be absent or empty.
-         */
-        VERB_NETWORK_CONFIG_REQUEST = 0x0b,
-
-        /**
-         * Network configuration data push:
-         *   <[8] 64-bit network ID>
-         *   <[2] 16-bit length of network configuration dictionary chunk>
-         *   <[...] network configuration dictionary (may be incomplete)>
-         *   <[1] 8-bit flags>
-         *   <[8] 64-bit config update ID (should never be 0)>
-         *   <[4] 32-bit total length of assembled dictionary>
-         *   <[4] 32-bit index of chunk>
-         *   [ ... end signed portion ... ]
-         *   <[1] 8-bit chunk signature type>
-         *   <[2] 16-bit length of chunk signature>
-         *   <[...] chunk signature>
-         *
-         * This is a direct push variant for network config updates. It otherwise
-         * carries the same payload as OK(NETWORK_CONFIG_REQUEST) and has the same
-         * semantics.
-         *
-         * The legacy mode missing the additional chunking fields is not supported
-         * here.
-         *
-         * Flags:
-         *   0x01 - Use fast propagation
-         *
-         * An OK should be sent if the config is successfully received and
-         * accepted.
-         *
-         * OK payload:
-         *   <[8] 64-bit network ID>
-         *   <[8] 64-bit config update ID>
-         */
-        VERB_NETWORK_CONFIG = 0x0c,
-
-        /**
-         * Request endpoints for multicast distribution:
-         *   <[8] 64-bit network ID>
-         *   <[1] flags>
-         *   <[6] MAC address of multicast group being queried>
-         *   <[4] 32-bit ADI for multicast group being queried>
-         *   <[4] 32-bit requested max number of multicast peers>
-         *   [<[...] network certificate of membership>]
-         *
-         * Flags:
-         *   0x01 - COM is attached
-         *
-         * This message asks a peer for additional known endpoints that have
-         * LIKEd a given multicast group. It's sent when the sender wishes
-         * to send multicast but does not have the desired number of recipient
-         * peers.
-         *
-         * More than one OK response can occur if the response is broken up across
-         * multiple packets or if querying a clustered node.
-         *
-         * The COM should be included so that upstream nodes that are not
-         * members of our network can validate our request.
-         *
-         * OK response payload:
-         *   <[8] 64-bit network ID>
-         *   <[6] MAC address of multicast group being queried>
-         *   <[4] 32-bit ADI for multicast group being queried>
-         *   [begin gather results -- these same fields can be in OK(MULTICAST_FRAME)]
-         *   <[4] 32-bit total number of known members in this multicast group>
-         *   <[2] 16-bit number of members enumerated in this packet>
-         *   <[...] series of 5-byte ZeroTier addresses of enumerated members>
-         *
-         * ERROR is not generated; queries that return no response are dropped.
-         */
-        VERB_MULTICAST_GATHER = 0x0d,
-
-        /**
-         * Multicast frame:
-         *   <[8] 64-bit network ID>
-         *   <[1] flags>
-         *  [<[4] 32-bit implicit gather limit>]
-         *  [<[6] source MAC>]
-         *   <[6] destination MAC (multicast address)>
-         *   <[4] 32-bit multicast ADI (multicast address extension)>
-         *   <[2] 16-bit ethertype>
-         *   <[...] ethernet payload>
-         *
-         * Flags:
-         *   0x01 - Network certificate of membership attached (DEPRECATED)
-         *   0x02 - Implicit gather limit field is present
-         *   0x04 - Source MAC is specified -- otherwise it's computed from sender
-         *   0x08 - Please replicate (sent to multicast replicators)
-         *
-         * OK and ERROR responses are optional. OK may be generated if there are
-         * implicit gather results or if the recipient wants to send its own
-         * updated certificate of network membership to the sender. ERROR may be
-         * generated if a certificate is needed or if multicasts to this group
-         * are no longer wanted (multicast unsubscribe).
-         *
-         * OK response payload:
-         *   <[8] 64-bit network ID>
-         *   <[6] MAC address of multicast group>
-         *   <[4] 32-bit ADI for multicast group>
-         *   <[1] flags>
-         *  [<[...] network certificate of membership (DEPRECATED)>]
-         *  [<[...] implicit gather results if flag 0x01 is set>]
-         *
-         * OK flags (same bits as request flags):
-         *   0x01 - OK includes certificate of network membership (DEPRECATED)
-         *   0x02 - OK includes implicit gather results
-         *
-         * ERROR response payload:
-         *   <[8] 64-bit network ID>
-         *   <[6] multicast group MAC>
-         *   <[4] 32-bit multicast group ADI>
-         */
-        VERB_MULTICAST_FRAME = 0x0e,
-
-        /**
-         * Push of potential endpoints for direct communication:
-         *   <[2] 16-bit number of paths>
-         *   <[...] paths>
-         *
-         * Path record format:
-         *   <[1] 8-bit path flags>
-         *   <[2] length of extended path characteristics or 0 for none>
-         *   <[...] extended path characteristics>
-         *   <[1] address type>
-         *   <[1] address length in bytes>
-         *   <[...] address>
-         *
-         * Path record flags:
-         *   0x01 - Forget this path if currently known (not implemented yet)
-         *   0x02 - Cluster redirect -- use this in preference to others
-         *
-         * The receiver may, upon receiving a push, attempt to establish a
-         * direct link to one or more of the indicated addresses. It is the
-         * responsibility of the sender to limit which peers it pushes direct
-         * paths to to those with whom it has a trust relationship. The receiver
-         * must obey any restrictions provided such as exclusivity or blacklists.
-         * OK responses to this message are optional.
-         *
-         * Note that a direct path push does not imply that learned paths can't
-         * be used unless they are blacklisted explicitly or unless flag 0x01
-         * is set.
-         *
-         * OK and ERROR are not generated.
-         */
-        VERB_PUSH_DIRECT_PATHS = 0x10,
-
-        // 0x11 -- deprecated
-
-        /**
-         * An acknowledgment of receipt of a series of recent packets from another
-         * peer. This is used to calculate relative throughput values and to detect
-         * packet loss. Only VERB_FRAME and VERB_EXT_FRAME packets are counted.
-         *
-         * ACK response format:
-         *  <[4] 32-bit number of bytes received since last ACK>
-         *
-         * Upon receipt of this packet, the local peer will verify that the correct
-         * number of bytes were received by the remote peer. If these values do
-         * not agree that could be an indication of packet loss.
-         *
-         * Additionally, the local peer knows the interval of time that has
-         * elapsed since the last received ACK. With this information it can compute
-         * a rough estimate of the current throughput.
-         *
-         * This is sent at a maximum rate of once per every ZT_QOS_ACK_INTERVAL
-         */
-        VERB_ACK = 0x12,
-
-        /**
-         * A packet containing timing measurements useful for estimating path quality.
-         * Composed of a list of <packet ID:internal sojourn time> pairs for an
-         * arbitrary set of recent packets. This is used to sample for latency and
-         * packet delay variance (PDV, "jitter").
-         *
-         * QoS record format:
-         *
-         *  <[8] 64-bit packet ID of previously-received packet>
-         *  <[1] 8-bit packet sojourn time>
-         *  <...repeat until end of max 1400 byte packet...>
-         *
-         * The number of possible records per QoS packet is: (1400 * 8) / 72 = 155
-         * This packet should be sent very rarely (every few seconds) as it can be
-         * somewhat large if the connection is saturated. Future versions might use
-         * a bloom table to probabilistically determine these values in a vastly
-         * more space-efficient manner.
-         *
-         * Note: The 'internal packet sojourn time' is a slight misnomer as it is a
-         * measure of the amount of time between when a packet was received and the
-         * egress time of its tracking QoS packet.
-         *
-         * This is sent at a maximum rate of once per every
-         * ZT_QOS_MEASUREMENT_INTERVAL
-         */
-        VERB_QOS_MEASUREMENT = 0x13,
-
-        /**
-         * A message with arbitrary user-definable content:
-         *   <[8] 64-bit arbitrary message type ID>
-         *  [<[...] message payload>]
-         *
-         * This can be used to send arbitrary messages over VL1. It generates no
-         * OK or ERROR and has no special semantics outside of whatever the user
-         * (via the ZeroTier core API) chooses to give it.
-         *
-         * Message type IDs less than or equal to 65535 are reserved for use by
-         * ZeroTier, Inc. itself. We recommend making up random ones for your own
-         * implementations.
-         */
-        VERB_USER_MESSAGE = 0x14,
-
-        /**
-         * A trace for remote debugging or diagnostics:
-         *   <[...] null-terminated dictionary containing trace information>
-         *  [<[...] additional null-terminated dictionaries>]
-         *
-         * This message contains a remote trace event. Remote trace events can
-         * be sent to observers configured at the network level for those that
-         * pertain directly to activity on a network, or to global observers if
-         * locally configured.
-         *
-         * The instance ID is a random 64-bit value generated by each ZeroTier
-         * node on startup. This is helpful in identifying traces from different
-         * members of a cluster.
-         */
-        VERB_REMOTE_TRACE = 0x15,
-
-        /**
-         * A request to a peer to use a specific path in a multi-path scenario:
-         * <[2] 16-bit unsigned integer that encodes a path choice utility>
-         *
-         * This is sent when a node operating in multipath mode observes that
-         * its inbound and outbound traffic aren't going over the same path. The
-         * node will compute its perceived utility for using its chosen outbound
-         * path and send this to a peer in an attempt to petition it to send
-         * its traffic over this same path.
-         *
-         * Scenarios:
-         *
-         * (1) Remote peer utility is GREATER than ours:
-         *     - Remote peer will refuse the petition and continue using current path
-         * (2) Remote peer utility is LESS than than ours:
-         *     - Remote peer will accept the petition and switch to our chosen path
-         * (3) Remote peer utility is EQUAL to our own:
-         *     - To prevent confusion and flapping, both side will agree to use the
-         *       numerical values of their identities to determine which path to use.
-         *       The peer with the greatest identity will win.
-         *
-         * If a node petitions a peer repeatedly with no effect it will regard
-         * that as a refusal by the remote peer, in this case if the utility is
-         * negligible it will voluntarily switch to the remote peer's chosen path.
-         */
-        VERB_PATH_NEGOTIATION_REQUEST = 0x16
-    };
-
-    /**
-     * Error codes for VERB_ERROR
-     */
-    enum ErrorCode {
-        /* No error, not actually used in transit */
-        ERROR_NONE = 0x00,
-
-        /* Invalid request */
-        ERROR_INVALID_REQUEST = 0x01,
-
-        /* Bad/unsupported protocol version */
-        ERROR_BAD_PROTOCOL_VERSION = 0x02,
-
-        /* Unknown object queried */
-        ERROR_OBJ_NOT_FOUND = 0x03,
-
-        /* HELLO pushed an identity whose address is already claimed */
-        ERROR_IDENTITY_COLLISION = 0x04,
-
-        /* Verb or use case not supported/enabled by this node */
-        ERROR_UNSUPPORTED_OPERATION = 0x05,
-
-        /* Network membership certificate update needed */
-        ERROR_NEED_MEMBERSHIP_CERTIFICATE = 0x06,
-
-        /* Tried to join network, but you're not a member */
-        ERROR_NETWORK_ACCESS_DENIED_ = 0x07, /* extra _ at end to avoid Windows name conflict */
-
-        /* Multicasts to this group are not wanted */
-        ERROR_UNWANTED_MULTICAST = 0x08,
-
-        /* Network requires external or 2FA authentication (e.g. SSO). */
-        ERROR_NETWORK_AUTHENTICATION_REQUIRED = 0x09
-    };
-
-    template <unsigned int C2> Packet(const Buffer<C2>& b) : Buffer<ZT_PROTO_MAX_PACKET_LENGTH>(b)
-    {
-    }
-
-    Packet(const void* data, unsigned int len) : Buffer<ZT_PROTO_MAX_PACKET_LENGTH>(data, len)
-    {
-    }
-
-    /**
-     * Construct a new empty packet with a unique random packet ID
-     *
-     * Flags and hops will be zero. Other fields and data region are undefined.
-     * Use the header access methods (setDestination() and friends) to fill out
-     * the header. Payload should be appended; initial size is header size.
-     */
-    Packet() : Buffer<ZT_PROTO_MAX_PACKET_LENGTH>(ZT_PROTO_MIN_PACKET_LENGTH)
-    {
-        Utils::getSecureRandom(field(ZT_PACKET_IDX_IV, 8), 8);
-        (*this)[ZT_PACKET_IDX_FLAGS] = 0;   // zero flags, cipher ID, and hops
-    }
-
-    /**
-     * Make a copy of a packet with a new initialization vector and destination address
-     *
-     * This can be used to take one draft prototype packet and quickly make copies to
-     * encrypt for different destinations.
-     *
-     * @param prototype Prototype packet
-     * @param dest Destination ZeroTier address for new packet
-     */
-    Packet(const Packet& prototype, const Address& dest) : Buffer<ZT_PROTO_MAX_PACKET_LENGTH>(prototype)
-    {
-        Utils::getSecureRandom(field(ZT_PACKET_IDX_IV, 8), 8);
-        setDestination(dest);
-    }
-
-    /**
-     * Construct a new empty packet with a unique random packet ID
-     *
-     * @param dest Destination ZT address
-     * @param source Source ZT address
-     * @param v Verb
-     */
-    Packet(const Address& dest, const Address& source, const Verb v) : Buffer<ZT_PROTO_MAX_PACKET_LENGTH>(ZT_PROTO_MIN_PACKET_LENGTH)
-    {
-        Utils::getSecureRandom(field(ZT_PACKET_IDX_IV, 8), 8);
-        setDestination(dest);
-        setSource(source);
-        (*this)[ZT_PACKET_IDX_FLAGS] = 0;   // zero flags and hops
-        setVerb(v);
-    }
-
-    /**
-     * Reset this packet structure for reuse in place
-     *
-     * @param dest Destination ZT address
-     * @param source Source ZT address
-     * @param v Verb
-     */
-    inline void reset(const Address& dest, const Address& source, const Verb v)
-    {
-        setSize(ZT_PROTO_MIN_PACKET_LENGTH);
-        Utils::getSecureRandom(field(ZT_PACKET_IDX_IV, 8), 8);
-        setDestination(dest);
-        setSource(source);
-        (*this)[ZT_PACKET_IDX_FLAGS] = 0;   // zero flags, cipher ID, and hops
-        setVerb(v);
-    }
-
-    /**
-     * Generate a new IV / packet ID in place
-     *
-     * This can be used to re-use a packet buffer multiple times to send
-     * technically different but otherwise identical copies of the same
-     * packet.
-     */
-    inline void newInitializationVector()
-    {
-        Utils::getSecureRandom(field(ZT_PACKET_IDX_IV, 8), 8);
-    }
-
-    /**
-     * Set this packet's destination
-     *
-     * @param dest ZeroTier address of destination
-     */
-    inline void setDestination(const Address& dest)
-    {
-        dest.copyTo(field(ZT_PACKET_IDX_DEST, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
-    }
-
-    /**
-     * Set this packet's source
-     *
-     * @param source ZeroTier address of source
-     */
-    inline void setSource(const Address& source)
-    {
-        source.copyTo(field(ZT_PACKET_IDX_SOURCE, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
-    }
-
-    /**
-     * Get this packet's destination
-     *
-     * @return Destination ZT address
-     */
-    inline Address destination() const
-    {
-        return Address(field(ZT_PACKET_IDX_DEST, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
-    }
-
-    /**
-     * Get this packet's source
-     *
-     * @return Source ZT address
-     */
-    inline Address source() const
-    {
-        return Address(field(ZT_PACKET_IDX_SOURCE, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
-    }
-
-    /**
-     * @return True if packet is of valid length
-     */
-    inline bool lengthValid() const
-    {
-        return (size() >= ZT_PROTO_MIN_PACKET_LENGTH);
-    }
-
-    /**
-     * @return True if packet is fragmented (expect fragments)
-     */
-    inline bool fragmented() const
-    {
-        return (((unsigned char)(*this)[ZT_PACKET_IDX_FLAGS] & ZT_PROTO_FLAG_FRAGMENTED) != 0);
-    }
-
-    /**
-     * Set this packet's fragmented flag
-     *
-     * @param f Fragmented flag value
-     */
-    inline void setFragmented(bool f)
-    {
-        if (f) {
-            (*this)[ZT_PACKET_IDX_FLAGS] |= (char)ZT_PROTO_FLAG_FRAGMENTED;
-        }
-        else {
-            (*this)[ZT_PACKET_IDX_FLAGS] &= (char)(~ZT_PROTO_FLAG_FRAGMENTED);
-        }
-    }
-
-    /**
-     * @return True if packet is encrypted with an extra ephemeral key
-     */
-    inline bool extendedArmor() const
-    {
-        return (((unsigned char)(*this)[ZT_PACKET_IDX_FLAGS] & ZT_PROTO_FLAG_EXTENDED_ARMOR) != 0);
-    }
-
-    /**
-     * Set this packet's extended armor flag
-     *
-     * @param f Extended armor flag value
-     */
-    inline void setExtendedArmor(bool f)
-    {
-        if (f) {
-            (*this)[ZT_PACKET_IDX_FLAGS] |= (char)ZT_PROTO_FLAG_EXTENDED_ARMOR;
-        }
-        else {
-            (*this)[ZT_PACKET_IDX_FLAGS] &= (char)(~ZT_PROTO_FLAG_EXTENDED_ARMOR);
-        }
-    }
-
-    /**
-     * @return True if compressed (result only valid if unencrypted)
-     */
-    inline bool compressed() const
-    {
-        return (((unsigned char)(*this)[ZT_PACKET_IDX_VERB] & ZT_PROTO_VERB_FLAG_COMPRESSED) != 0);
-    }
-
-    /**
-     * @return ZeroTier forwarding hops (0 to 7)
-     */
-    inline unsigned int hops() const
-    {
-        return ((unsigned int)(*this)[ZT_PACKET_IDX_FLAGS] & 0x07);
-    }
-
-    /**
-     * Increment this packet's hop count
-     */
-    inline void incrementHops()
-    {
-        unsigned char& b = (*this)[ZT_PACKET_IDX_FLAGS];
-        b = (b & 0xf8) | ((b + 1) & 0x07);
-    }
-
-    /**
-     * @return Cipher suite selector: 0 - 7 (see #defines)
-     */
-    inline unsigned int cipher() const
-    {
-        return (((unsigned int)(*this)[ZT_PACKET_IDX_FLAGS] & 0x38) >> 3);
-    }
-
-    /**
-     * @return Whether this packet is currently encrypted
-     */
-    inline bool isEncrypted() const
-    {
-        return (cipher() == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) || (cipher() == ZT_PROTO_CIPHER_SUITE__AES_GMAC_SIV);
-    }
-
-    /**
-     * Set this packet's cipher suite
-     */
-    inline void setCipher(unsigned int c)
-    {
-        unsigned char& b = (*this)[ZT_PACKET_IDX_FLAGS];
-        b = (b & 0xc7) | (unsigned char)((c << 3) & 0x38);   // bits: FFCCCHHH
-    }
-
-    /**
-     * Get the trusted path ID for this packet (only meaningful if cipher is trusted path)
-     *
-     * @return Trusted path ID (from MAC field)
-     */
-    inline uint64_t trustedPathId() const
-    {
-        return at<uint64_t>(ZT_PACKET_IDX_MAC);
-    }
-
-    /**
-     * Set this packet's trusted path ID and set the cipher spec to trusted path
-     *
-     * @param tpid Trusted path ID
-     */
-    inline void setTrusted(const uint64_t tpid)
-    {
-        setCipher(ZT_PROTO_CIPHER_SUITE__NO_CRYPTO_TRUSTED_PATH);
-        setAt(ZT_PACKET_IDX_MAC, tpid);
-    }
-
-    /**
-     * Get this packet's unique ID (the IV field interpreted as uint64_t)
-     *
-     * Note that the least significant 3 bits of this ID will change when armor()
-     * is called to armor the packet for transport. This is because armor() will
-     * mask the last 3 bits against the send counter for QoS monitoring use prior
-     * to actually using the IV to encrypt and MAC the packet. Be aware of this
-     * when grabbing the packetId of a new packet prior to armor/send.
-     *
-     * @return Packet ID
-     */
-    inline uint64_t packetId() const
-    {
-        return at<uint64_t>(ZT_PACKET_IDX_IV);
-    }
-
-    /**
-     * Set packet verb
-     *
-     * This also has the side-effect of clearing any verb flags, such as
-     * compressed, and so must only be done during packet composition.
-     *
-     * @param v New packet verb
-     */
-    inline void setVerb(Verb v)
-    {
-        (*this)[ZT_PACKET_IDX_VERB] = (char)v;
-    }
-
-    /**
-     * @return Packet verb (not including flag bits)
-     */
-    inline Verb verb() const
-    {
-        return (Verb)((*this)[ZT_PACKET_IDX_VERB] & 0x1f);
-    }
-
-    /**
-     * @return Length of packet payload
-     */
-    inline unsigned int payloadLength() const
-    {
-        return ((size() < ZT_PROTO_MIN_PACKET_LENGTH) ? 0 : (size() - ZT_PROTO_MIN_PACKET_LENGTH));
-    }
-
-    /**
-     * @return Raw packet payload
-     */
-    inline const unsigned char* payload() const
-    {
-        return field(ZT_PACKET_IDX_PAYLOAD, size() - ZT_PACKET_IDX_PAYLOAD);
-    }
-
-    /**
-     * Armor packet for transport
-     *
-     * @param key 32-byte key
-     * @param encryptPayload If true, encrypt packet payload, else just MAC
-     * @param extendedArmor Use an ephemeral key to encrypt payload (for encrypted HELLO)
-     * @param identity Identity of packet recipient/destination
-     * @param aesKeys If non-NULL these are the two keys for AES-GMAC-SIV
-     */
-    void armor(const void* key, bool encryptPayload, bool extendedArmor, const AES aesKeys[2], const Identity& identity);
-
-    /**
-     * Verify and (if encrypted) decrypt packet
-     *
-     * This does not handle trusted path mode packets and will return false
-     * for these. These are handled in IncomingPacket if the sending physical
-     * address and MAC field match a trusted path.
-     *
-     * @param key 32-byte key
-     * @param aesKeys If non-NULL these are the two keys for AES-GMAC-SIV
-     * @param identity Receiver's identity (must include secret)
-     * @return False if packet is invalid or failed MAC authenticity check
-     */
-    bool dearmor(const void* key, const AES aesKeys[2], const Identity& identity);
-
-    /**
-     * Encrypt/decrypt a separately armored portion of a packet
-     *
-     * This is currently only used to mask portions of HELLO as an extra
-     * security precaution since most of that message is sent in the clear.
-     *
-     * This must NEVER be used more than once in the same packet, as doing
-     * so will result in re-use of the same key stream.
-     *
-     * @param key 32-byte key
-     * @param start Start of encrypted portion
-     * @param len Length of encrypted portion
-     */
-    void cryptField(const void* key, unsigned int start, unsigned int len);
-
-    /**
-     * Attempt to compress payload if not already (must be unencrypted)
-     *
-     * This requires that the payload at least contain the verb byte already
-     * set. The compressed flag in the verb is set if compression successfully
-     * results in a size reduction. If no size reduction occurs, compression
-     * is not done and the flag is left cleared.
-     *
-     * @return True if compression occurred
-     */
-    bool compress();
-
-    /**
-     * Attempt to decompress payload if it is compressed (must be unencrypted)
-     *
-     * If payload is compressed, it is decompressed and the compressed verb
-     * flag is cleared. Otherwise nothing is done and true is returned.
-     *
-     * @return True if data is now decompressed and valid, false on error
-     */
-    bool uncompress();
+	/**
+	 * A packet fragment
+	 *
+	 * Fragments are sent if a packet is larger than UDP MTU. The first fragment
+	 * is sent with its normal header with the fragmented flag set. Remaining
+	 * fragments are sent this way.
+	 *
+	 * The fragmented bit indicates that there is at least one fragment. Fragments
+	 * themselves contain the total, so the receiver must "learn" this from the
+	 * first fragment it receives.
+	 *
+	 * Fragments are sent with the following format:
+	 *   <[8] packet ID of packet whose fragment this belongs to>
+	 *   <[5] destination ZT address>
+	 *   <[1] 0xff, a reserved address, signals that this isn't a normal packet>
+	 *   <[1] total fragments (most significant 4 bits), fragment no (LS 4 bits)>
+	 *   <[1] ZT hop count (top 5 bits unused and must be zero)>
+	 *   <[...] fragment data>
+	 *
+	 * The protocol supports a maximum of 16 fragments. If a fragment is received
+	 * before its main packet header, it should be cached for a brief period of
+	 * time to see if its parent arrives. Loss of any fragment constitutes packet
+	 * loss; there is no retransmission mechanism. The receiver must wait for full
+	 * receipt to authenticate and decrypt; there is no per-fragment MAC. (But if
+	 * fragments are corrupt, the MAC will fail for the whole assembled packet.)
+	 */
+	class Fragment : public Buffer<ZT_PROTO_MAX_PACKET_LENGTH> {
+	  public:
+		Fragment() : Buffer<ZT_PROTO_MAX_PACKET_LENGTH>()
+		{
+		}
+
+		template <unsigned int C2> Fragment(const Buffer<C2>& b) : Buffer<ZT_PROTO_MAX_PACKET_LENGTH>(b)
+		{
+		}
+
+		Fragment(const void* data, unsigned int len) : Buffer<ZT_PROTO_MAX_PACKET_LENGTH>(data, len)
+		{
+		}
+
+		/**
+		 * Initialize from a packet
+		 *
+		 * @param p Original assembled packet
+		 * @param fragStart Start of fragment (raw index in packet data)
+		 * @param fragLen Length of fragment in bytes
+		 * @param fragNo Which fragment (>= 1, since 0 is Packet with end chopped off)
+		 * @param fragTotal Total number of fragments (including 0)
+		 */
+		Fragment(const Packet& p, unsigned int fragStart, unsigned int fragLen, unsigned int fragNo, unsigned int fragTotal)
+		{
+			init(p, fragStart, fragLen, fragNo, fragTotal);
+		}
+
+		/**
+		 * Initialize from a packet
+		 *
+		 * @param p Original assembled packet
+		 * @param fragStart Start of fragment (raw index in packet data)
+		 * @param fragLen Length of fragment in bytes
+		 * @param fragNo Which fragment (>= 1, since 0 is Packet with end chopped off)
+		 * @param fragTotal Total number of fragments (including 0)
+		 */
+		inline void init(const Packet& p, unsigned int fragStart, unsigned int fragLen, unsigned int fragNo, unsigned int fragTotal)
+		{
+			if ((fragStart + fragLen) > p.size()) {
+				throw ZT_EXCEPTION_OUT_OF_BOUNDS;
+			}
+			setSize(fragLen + ZT_PROTO_MIN_FRAGMENT_LENGTH);
+
+			// NOTE: this copies both the IV/packet ID and the destination address.
+			memcpy(field(ZT_PACKET_FRAGMENT_IDX_PACKET_ID, 13), p.field(ZT_PACKET_IDX_IV, 13), 13);
+
+			(*this)[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_INDICATOR] = ZT_PACKET_FRAGMENT_INDICATOR;
+			(*this)[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_NO] = (char)(((fragTotal & 0xf) << 4) | (fragNo & 0xf));
+			(*this)[ZT_PACKET_FRAGMENT_IDX_HOPS] = 0;
+
+			memcpy(field(ZT_PACKET_FRAGMENT_IDX_PAYLOAD, fragLen), p.field(fragStart, fragLen), fragLen);
+		}
+
+		/**
+		 * Get this fragment's destination
+		 *
+		 * @return Destination ZT address
+		 */
+		inline Address destination() const
+		{
+			return Address(field(ZT_PACKET_FRAGMENT_IDX_DEST, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
+		}
+
+		/**
+		 * @return True if fragment is of a valid length
+		 */
+		inline bool lengthValid() const
+		{
+			return (size() >= ZT_PACKET_FRAGMENT_IDX_PAYLOAD);
+		}
+
+		/**
+		 * @return ID of packet this is a fragment of
+		 */
+		inline uint64_t packetId() const
+		{
+			return at<uint64_t>(ZT_PACKET_FRAGMENT_IDX_PACKET_ID);
+		}
+
+		/**
+		 * @return Total number of fragments in packet
+		 */
+		inline unsigned int totalFragments() const
+		{
+			return (((unsigned int)((*this)[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_NO]) >> 4) & 0xf);
+		}
+
+		/**
+		 * @return Fragment number of this fragment
+		 */
+		inline unsigned int fragmentNumber() const
+		{
+			return ((unsigned int)((*this)[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_NO]) & 0xf);
+		}
+
+		/**
+		 * @return Fragment ZT hop count
+		 */
+		inline unsigned int hops() const
+		{
+			return (unsigned int)((*this)[ZT_PACKET_FRAGMENT_IDX_HOPS]);
+		}
+
+		/**
+		 * Increment this packet's hop count
+		 */
+		inline void incrementHops()
+		{
+			(*this)[ZT_PACKET_FRAGMENT_IDX_HOPS] = (((*this)[ZT_PACKET_FRAGMENT_IDX_HOPS]) + 1) & ZT_PROTO_MAX_HOPS;
+		}
+
+		/**
+		 * @return Length of payload in bytes
+		 */
+		inline unsigned int payloadLength() const
+		{
+			return ((size() > ZT_PACKET_FRAGMENT_IDX_PAYLOAD) ? (size() - ZT_PACKET_FRAGMENT_IDX_PAYLOAD) : 0);
+		}
+
+		/**
+		 * @return Raw packet payload
+		 */
+		inline const unsigned char* payload() const
+		{
+			return field(ZT_PACKET_FRAGMENT_IDX_PAYLOAD, size() - ZT_PACKET_FRAGMENT_IDX_PAYLOAD);
+		}
+	};
+
+	/**
+	 * ZeroTier protocol verbs
+	 */
+	enum Verb /* Max value: 32 (5 bits) */
+	{
+		/**
+		 * No operation (ignored, no reply)
+		 */
+		VERB_NOP = 0x00,
+
+		/**
+		 * Announcement of a node's existence and vitals:
+		 *   <[1] protocol version>
+		 *   <[1] software major version>
+		 *   <[1] software minor version>
+		 *   <[2] software revision>
+		 *   <[8] timestamp for determining latency>
+		 *   <[...] binary serialized identity (see Identity)>
+		 *   <[...] physical destination address of packet>
+		 *   <[8] 64-bit world ID of current planet>
+		 *   <[8] 64-bit timestamp of current planet>
+		 *   [... remainder if packet is encrypted using cryptField() ...]
+		 *   <[2] 16-bit number of moons>
+		 *   [<[1] 8-bit type ID of moon>]
+		 *   [<[8] 64-bit world ID of moon>]
+		 *   [<[8] 64-bit timestamp of moon>]
+		 *   [... additional moon type/ID/timestamp tuples ...]
+		 *
+		 * HELLO is sent in the clear as it is how peers share their identity
+		 * public keys. A few additional fields are sent in the clear too, but
+		 * these are things that are public info or are easy to determine. As
+		 * of 1.2.0 we have added a few more fields, but since these could have
+		 * the potential to be sensitive we introduced the encryption of the
+		 * remainder of the packet. See cryptField(). Packet MAC is still
+		 * performed of course, so authentication occurs as normal.
+		 *
+		 * Destination address is the actual wire address to which the packet
+		 * was sent. See InetAddress::serialize() for format.
+		 *
+		 * OK payload:
+		 *   <[8] HELLO timestamp field echo>
+		 *   <[1] protocol version>
+		 *   <[1] software major version>
+		 *   <[1] software minor version>
+		 *   <[2] software revision>
+		 *   <[...] physical destination address of packet>
+		 *   <[2] 16-bit length of world update(s) or 0 if none>
+		 *   [[...] updates to planets and/or moons]
+		 *
+		 * With the exception of the timestamp, the other fields pertain to the
+		 * respondent who is sending OK and are not echoes.
+		 *
+		 * Note that OK is fully encrypted so no selective cryptField() of
+		 * potentially sensitive fields is needed.
+		 *
+		 * ERROR has no payload.
+		 */
+		VERB_HELLO = 0x01,
+
+		/**
+		 * Error response:
+		 *   <[1] in-re verb>
+		 *   <[8] in-re packet ID>
+		 *   <[1] error code>
+		 *   <[...] error-dependent payload>
+		 */
+		VERB_ERROR = 0x02,
+
+		/**
+		 * Success response:
+		 *   <[1] in-re verb>
+		 *   <[8] in-re packet ID>
+		 *   <[...] request-specific payload>
+		 */
+		VERB_OK = 0x03,
+
+		/**
+		 * Query an identity by address:
+		 *   <[5] address to look up>
+		 *   [<[...] additional addresses to look up>
+		 *
+		 * OK response payload:
+		 *   <[...] binary serialized identity>
+		 *  [<[...] additional binary serialized identities>]
+		 *
+		 * If querying a cluster, duplicate OK responses may occasionally occur.
+		 * These must be tolerated, which is easy since they'll have info you
+		 * already have.
+		 *
+		 * If the address is not found, no response is generated. The semantics
+		 * of WHOIS is similar to ARP and NDP in that persistent retrying can
+		 * be performed.
+		 */
+		VERB_WHOIS = 0x04,
+
+		/**
+		 * Relay-mediated NAT traversal or firewall punching initiation:
+		 *   <[1] flags (unused, currently 0)>
+		 *   <[5] ZeroTier address of peer that might be found at this address>
+		 *   <[2] 16-bit protocol address port>
+		 *   <[1] protocol address length (4 for IPv4, 16 for IPv6)>
+		 *   <[...] protocol address (network byte order)>
+		 *
+		 * An upstream node can send this to inform both sides of a relay of
+		 * information they might use to establish a direct connection.
+		 *
+		 * Upon receipt a peer sends HELLO to establish a direct link.
+		 *
+		 * No OK or ERROR is generated.
+		 */
+		VERB_RENDEZVOUS = 0x05,
+
+		/**
+		 * ZT-to-ZT unicast ethernet frame (shortened EXT_FRAME):
+		 *   <[8] 64-bit network ID>
+		 *   <[2] 16-bit ethertype>
+		 *   <[...] ethernet payload>
+		 *
+		 * MAC addresses are derived from the packet's source and destination
+		 * ZeroTier addresses. This is a shortened EXT_FRAME that elides full
+		 * Ethernet framing and other optional flags and features when they
+		 * are not necessary.
+		 *
+		 * ERROR may be generated if a membership certificate is needed for a
+		 * closed network. Payload will be network ID.
+		 */
+		VERB_FRAME = 0x06,
+
+		/**
+		 * Full Ethernet frame with MAC addressing and optional fields:
+		 *   <[8] 64-bit network ID>
+		 *   <[1] flags>
+		 *   <[6] destination MAC or all zero for destination node>
+		 *   <[6] source MAC or all zero for node of origin>
+		 *   <[2] 16-bit ethertype>
+		 *   <[...] ethernet payload>
+		 *
+		 * Flags:
+		 *   0x01 - Certificate of network membership attached (DEPRECATED)
+		 *   0x02 - Most significant bit of subtype (see below)
+		 *   0x04 - Middle bit of subtype (see below)
+		 *   0x08 - Least significant bit of subtype (see below)
+		 *   0x10 - ACK requested in the form of OK(EXT_FRAME)
+		 *
+		 * Subtypes (0..7):
+		 *   0x0 - Normal frame (bridging can be determined by checking MAC)
+		 *   0x1 - TEEd outbound frame
+		 *   0x2 - REDIRECTed outbound frame
+		 *   0x3 - WATCHed outbound frame (TEE with ACK, ACK bit also set)
+		 *   0x4 - TEEd inbound frame
+		 *   0x5 - REDIRECTed inbound frame
+		 *   0x6 - WATCHed inbound frame
+		 *   0x7 - (reserved for future use)
+		 *
+		 * An extended frame carries full MAC addressing, making it a
+		 * superset of VERB_FRAME. It is used for bridged traffic,
+		 * redirected or observed traffic via rules, and can in theory
+		 * be used for multicast though MULTICAST_FRAME exists for that
+		 * purpose and has additional options and capabilities.
+		 *
+		 * OK payload (if ACK flag is set):
+		 *   <[8] 64-bit network ID>
+		 */
+		VERB_EXT_FRAME = 0x07,
+
+		/**
+		 * ECHO request (a.k.a. ping):
+		 *   <[...] arbitrary payload>
+		 *
+		 * This generates OK with a copy of the transmitted payload. No ERROR
+		 * is generated. Response to ECHO requests is optional and ECHO may be
+		 * ignored if a node detects a possible flood.
+		 */
+		VERB_ECHO = 0x08,
+
+		/**
+		 * Announce interest in multicast group(s):
+		 *   <[8] 64-bit network ID>
+		 *   <[6] multicast Ethernet address>
+		 *   <[4] multicast additional distinguishing information (ADI)>
+		 *   [... additional tuples of network/address/adi ...]
+		 *
+		 * LIKEs may be sent to any peer, though a good implementation should
+		 * restrict them to peers on the same network they're for and to network
+		 * controllers and root servers. In the current network, root servers
+		 * will provide the service of final multicast cache.
+		 *
+		 * VERB_NETWORK_CREDENTIALS should be pushed along with this, especially
+		 * if using upstream (e.g. root) nodes as multicast databases. This allows
+		 * GATHERs to be authenticated.
+		 *
+		 * OK/ERROR are not generated.
+		 */
+		VERB_MULTICAST_LIKE = 0x09,
+
+		/**
+		 * Network credentials push:
+		 *   [<[...] one or more certificates of membership>]
+		 *   <[1] 0x00, null byte marking end of COM array>
+		 *   <[2] 16-bit number of capabilities>
+		 *   <[...] one or more serialized Capability>
+		 *   <[2] 16-bit number of tags>
+		 *   <[...] one or more serialized Tags>
+		 *   <[2] 16-bit number of revocations>
+		 *   <[...] one or more serialized Revocations>
+		 *   <[2] 16-bit number of certificates of ownership>
+		 *   <[...] one or more serialized CertificateOfOwnership>
+		 *
+		 * This can be sent by anyone at any time to push network credentials.
+		 * These will of course only be accepted if they are properly signed.
+		 * Credentials can be for any number of networks.
+		 *
+		 * The use of a zero byte to terminate the COM section is for legacy
+		 * backward compatibility. Newer fields are prefixed with a length.
+		 *
+		 * OK/ERROR are not generated.
+		 */
+		VERB_NETWORK_CREDENTIALS = 0x0a,
+
+		/**
+		 * Network configuration request:
+		 *   <[8] 64-bit network ID>
+		 *   <[2] 16-bit length of request meta-data dictionary>
+		 *   <[...] string-serialized request meta-data>
+		 *   <[8] 64-bit revision of netconf we currently have>
+		 *   <[8] 64-bit timestamp of netconf we currently have>
+		 *
+		 * This message requests network configuration from a node capable of
+		 * providing it.
+		 *
+		 * Responses to this are always whole configs intended for the recipient.
+		 * For patches and other updates a NETWORK_CONFIG is sent instead.
+		 *
+		 * It would be valid and correct as of 1.2.0 to use NETWORK_CONFIG always,
+		 * but OK(NETWORK_CONFIG_REQUEST) should be sent for compatibility.
+		 *
+		 * OK response payload:
+		 *   <[8] 64-bit network ID>
+		 *   <[2] 16-bit length of network configuration dictionary chunk>
+		 *   <[...] network configuration dictionary (may be incomplete)>
+		 *   [ ... end of legacy single chunk response ... ]
+		 *   <[1] 8-bit flags>
+		 *   <[8] 64-bit config update ID (should never be 0)>
+		 *   <[4] 32-bit total length of assembled dictionary>
+		 *   <[4] 32-bit index of chunk>
+		 *   [ ... end signed portion ... ]
+		 *   <[1] 8-bit chunk signature type>
+		 *   <[2] 16-bit length of chunk signature>
+		 *   <[...] chunk signature>
+		 *
+		 * The chunk signature signs the entire payload of the OK response.
+		 * Currently only one signature type is supported: ed25519 (1).
+		 *
+		 * Each config chunk is signed to prevent memory exhaustion or
+		 * traffic crowding DOS attacks against config fragment assembly.
+		 *
+		 * If the packet is from the network controller it is permitted to end
+		 * before the config update ID or other chunking related or signature
+		 * fields. This is to support older controllers that don't include
+		 * these fields and may be removed in the future.
+		 *
+		 * ERROR response payload:
+		 *   <[8] 64-bit network ID>
+		 *   <[2] 16-bit length of error-related data (optional)>
+		 *   <[...] error-related data (optional)>
+		 *
+		 * Error related data is a Dictionary containing things like a URL
+		 * for authentication or a human-readable error message, and is
+		 * optional and may be absent or empty.
+		 */
+		VERB_NETWORK_CONFIG_REQUEST = 0x0b,
+
+		/**
+		 * Network configuration data push:
+		 *   <[8] 64-bit network ID>
+		 *   <[2] 16-bit length of network configuration dictionary chunk>
+		 *   <[...] network configuration dictionary (may be incomplete)>
+		 *   <[1] 8-bit flags>
+		 *   <[8] 64-bit config update ID (should never be 0)>
+		 *   <[4] 32-bit total length of assembled dictionary>
+		 *   <[4] 32-bit index of chunk>
+		 *   [ ... end signed portion ... ]
+		 *   <[1] 8-bit chunk signature type>
+		 *   <[2] 16-bit length of chunk signature>
+		 *   <[...] chunk signature>
+		 *
+		 * This is a direct push variant for network config updates. It otherwise
+		 * carries the same payload as OK(NETWORK_CONFIG_REQUEST) and has the same
+		 * semantics.
+		 *
+		 * The legacy mode missing the additional chunking fields is not supported
+		 * here.
+		 *
+		 * Flags:
+		 *   0x01 - Use fast propagation
+		 *
+		 * An OK should be sent if the config is successfully received and
+		 * accepted.
+		 *
+		 * OK payload:
+		 *   <[8] 64-bit network ID>
+		 *   <[8] 64-bit config update ID>
+		 */
+		VERB_NETWORK_CONFIG = 0x0c,
+
+		/**
+		 * Request endpoints for multicast distribution:
+		 *   <[8] 64-bit network ID>
+		 *   <[1] flags>
+		 *   <[6] MAC address of multicast group being queried>
+		 *   <[4] 32-bit ADI for multicast group being queried>
+		 *   <[4] 32-bit requested max number of multicast peers>
+		 *   [<[...] network certificate of membership>]
+		 *
+		 * Flags:
+		 *   0x01 - COM is attached
+		 *
+		 * This message asks a peer for additional known endpoints that have
+		 * LIKEd a given multicast group. It's sent when the sender wishes
+		 * to send multicast but does not have the desired number of recipient
+		 * peers.
+		 *
+		 * More than one OK response can occur if the response is broken up across
+		 * multiple packets or if querying a clustered node.
+		 *
+		 * The COM should be included so that upstream nodes that are not
+		 * members of our network can validate our request.
+		 *
+		 * OK response payload:
+		 *   <[8] 64-bit network ID>
+		 *   <[6] MAC address of multicast group being queried>
+		 *   <[4] 32-bit ADI for multicast group being queried>
+		 *   [begin gather results -- these same fields can be in OK(MULTICAST_FRAME)]
+		 *   <[4] 32-bit total number of known members in this multicast group>
+		 *   <[2] 16-bit number of members enumerated in this packet>
+		 *   <[...] series of 5-byte ZeroTier addresses of enumerated members>
+		 *
+		 * ERROR is not generated; queries that return no response are dropped.
+		 */
+		VERB_MULTICAST_GATHER = 0x0d,
+
+		/**
+		 * Multicast frame:
+		 *   <[8] 64-bit network ID>
+		 *   <[1] flags>
+		 *  [<[4] 32-bit implicit gather limit>]
+		 *  [<[6] source MAC>]
+		 *   <[6] destination MAC (multicast address)>
+		 *   <[4] 32-bit multicast ADI (multicast address extension)>
+		 *   <[2] 16-bit ethertype>
+		 *   <[...] ethernet payload>
+		 *
+		 * Flags:
+		 *   0x01 - Network certificate of membership attached (DEPRECATED)
+		 *   0x02 - Implicit gather limit field is present
+		 *   0x04 - Source MAC is specified -- otherwise it's computed from sender
+		 *   0x08 - Please replicate (sent to multicast replicators)
+		 *
+		 * OK and ERROR responses are optional. OK may be generated if there are
+		 * implicit gather results or if the recipient wants to send its own
+		 * updated certificate of network membership to the sender. ERROR may be
+		 * generated if a certificate is needed or if multicasts to this group
+		 * are no longer wanted (multicast unsubscribe).
+		 *
+		 * OK response payload:
+		 *   <[8] 64-bit network ID>
+		 *   <[6] MAC address of multicast group>
+		 *   <[4] 32-bit ADI for multicast group>
+		 *   <[1] flags>
+		 *  [<[...] network certificate of membership (DEPRECATED)>]
+		 *  [<[...] implicit gather results if flag 0x01 is set>]
+		 *
+		 * OK flags (same bits as request flags):
+		 *   0x01 - OK includes certificate of network membership (DEPRECATED)
+		 *   0x02 - OK includes implicit gather results
+		 *
+		 * ERROR response payload:
+		 *   <[8] 64-bit network ID>
+		 *   <[6] multicast group MAC>
+		 *   <[4] 32-bit multicast group ADI>
+		 */
+		VERB_MULTICAST_FRAME = 0x0e,
+
+		/**
+		 * Push of potential endpoints for direct communication:
+		 *   <[2] 16-bit number of paths>
+		 *   <[...] paths>
+		 *
+		 * Path record format:
+		 *   <[1] 8-bit path flags>
+		 *   <[2] length of extended path characteristics or 0 for none>
+		 *   <[...] extended path characteristics>
+		 *   <[1] address type>
+		 *   <[1] address length in bytes>
+		 *   <[...] address>
+		 *
+		 * Path record flags:
+		 *   0x01 - Forget this path if currently known (not implemented yet)
+		 *   0x02 - Cluster redirect -- use this in preference to others
+		 *
+		 * The receiver may, upon receiving a push, attempt to establish a
+		 * direct link to one or more of the indicated addresses. It is the
+		 * responsibility of the sender to limit which peers it pushes direct
+		 * paths to to those with whom it has a trust relationship. The receiver
+		 * must obey any restrictions provided such as exclusivity or blacklists.
+		 * OK responses to this message are optional.
+		 *
+		 * Note that a direct path push does not imply that learned paths can't
+		 * be used unless they are blacklisted explicitly or unless flag 0x01
+		 * is set.
+		 *
+		 * OK and ERROR are not generated.
+		 */
+		VERB_PUSH_DIRECT_PATHS = 0x10,
+
+		// 0x11 -- deprecated
+
+		/**
+		 * An acknowledgment of receipt of a series of recent packets from another
+		 * peer. This is used to calculate relative throughput values and to detect
+		 * packet loss. Only VERB_FRAME and VERB_EXT_FRAME packets are counted.
+		 *
+		 * ACK response format:
+		 *  <[4] 32-bit number of bytes received since last ACK>
+		 *
+		 * Upon receipt of this packet, the local peer will verify that the correct
+		 * number of bytes were received by the remote peer. If these values do
+		 * not agree that could be an indication of packet loss.
+		 *
+		 * Additionally, the local peer knows the interval of time that has
+		 * elapsed since the last received ACK. With this information it can compute
+		 * a rough estimate of the current throughput.
+		 *
+		 * This is sent at a maximum rate of once per every ZT_QOS_ACK_INTERVAL
+		 */
+		VERB_ACK = 0x12,
+
+		/**
+		 * A packet containing timing measurements useful for estimating path quality.
+		 * Composed of a list of <packet ID:internal sojourn time> pairs for an
+		 * arbitrary set of recent packets. This is used to sample for latency and
+		 * packet delay variance (PDV, "jitter").
+		 *
+		 * QoS record format:
+		 *
+		 *  <[8] 64-bit packet ID of previously-received packet>
+		 *  <[1] 8-bit packet sojourn time>
+		 *  <...repeat until end of max 1400 byte packet...>
+		 *
+		 * The number of possible records per QoS packet is: (1400 * 8) / 72 = 155
+		 * This packet should be sent very rarely (every few seconds) as it can be
+		 * somewhat large if the connection is saturated. Future versions might use
+		 * a bloom table to probabilistically determine these values in a vastly
+		 * more space-efficient manner.
+		 *
+		 * Note: The 'internal packet sojourn time' is a slight misnomer as it is a
+		 * measure of the amount of time between when a packet was received and the
+		 * egress time of its tracking QoS packet.
+		 *
+		 * This is sent at a maximum rate of once per every
+		 * ZT_QOS_MEASUREMENT_INTERVAL
+		 */
+		VERB_QOS_MEASUREMENT = 0x13,
+
+		/**
+		 * A message with arbitrary user-definable content:
+		 *   <[8] 64-bit arbitrary message type ID>
+		 *  [<[...] message payload>]
+		 *
+		 * This can be used to send arbitrary messages over VL1. It generates no
+		 * OK or ERROR and has no special semantics outside of whatever the user
+		 * (via the ZeroTier core API) chooses to give it.
+		 *
+		 * Message type IDs less than or equal to 65535 are reserved for use by
+		 * ZeroTier, Inc. itself. We recommend making up random ones for your own
+		 * implementations.
+		 */
+		VERB_USER_MESSAGE = 0x14,
+
+		/**
+		 * A trace for remote debugging or diagnostics:
+		 *   <[...] null-terminated dictionary containing trace information>
+		 *  [<[...] additional null-terminated dictionaries>]
+		 *
+		 * This message contains a remote trace event. Remote trace events can
+		 * be sent to observers configured at the network level for those that
+		 * pertain directly to activity on a network, or to global observers if
+		 * locally configured.
+		 *
+		 * The instance ID is a random 64-bit value generated by each ZeroTier
+		 * node on startup. This is helpful in identifying traces from different
+		 * members of a cluster.
+		 */
+		VERB_REMOTE_TRACE = 0x15,
+
+		/**
+		 * A request to a peer to use a specific path in a multi-path scenario:
+		 * <[2] 16-bit unsigned integer that encodes a path choice utility>
+		 *
+		 * This is sent when a node operating in multipath mode observes that
+		 * its inbound and outbound traffic aren't going over the same path. The
+		 * node will compute its perceived utility for using its chosen outbound
+		 * path and send this to a peer in an attempt to petition it to send
+		 * its traffic over this same path.
+		 *
+		 * Scenarios:
+		 *
+		 * (1) Remote peer utility is GREATER than ours:
+		 *     - Remote peer will refuse the petition and continue using current path
+		 * (2) Remote peer utility is LESS than than ours:
+		 *     - Remote peer will accept the petition and switch to our chosen path
+		 * (3) Remote peer utility is EQUAL to our own:
+		 *     - To prevent confusion and flapping, both side will agree to use the
+		 *       numerical values of their identities to determine which path to use.
+		 *       The peer with the greatest identity will win.
+		 *
+		 * If a node petitions a peer repeatedly with no effect it will regard
+		 * that as a refusal by the remote peer, in this case if the utility is
+		 * negligible it will voluntarily switch to the remote peer's chosen path.
+		 */
+		VERB_PATH_NEGOTIATION_REQUEST = 0x16
+	};
+
+	/**
+	 * Error codes for VERB_ERROR
+	 */
+	enum ErrorCode {
+		/* No error, not actually used in transit */
+		ERROR_NONE = 0x00,
+
+		/* Invalid request */
+		ERROR_INVALID_REQUEST = 0x01,
+
+		/* Bad/unsupported protocol version */
+		ERROR_BAD_PROTOCOL_VERSION = 0x02,
+
+		/* Unknown object queried */
+		ERROR_OBJ_NOT_FOUND = 0x03,
+
+		/* HELLO pushed an identity whose address is already claimed */
+		ERROR_IDENTITY_COLLISION = 0x04,
+
+		/* Verb or use case not supported/enabled by this node */
+		ERROR_UNSUPPORTED_OPERATION = 0x05,
+
+		/* Network membership certificate update needed */
+		ERROR_NEED_MEMBERSHIP_CERTIFICATE = 0x06,
+
+		/* Tried to join network, but you're not a member */
+		ERROR_NETWORK_ACCESS_DENIED_ = 0x07, /* extra _ at end to avoid Windows name conflict */
+
+		/* Multicasts to this group are not wanted */
+		ERROR_UNWANTED_MULTICAST = 0x08,
+
+		/* Network requires external or 2FA authentication (e.g. SSO). */
+		ERROR_NETWORK_AUTHENTICATION_REQUIRED = 0x09
+	};
+
+	template <unsigned int C2> Packet(const Buffer<C2>& b) : Buffer<ZT_PROTO_MAX_PACKET_LENGTH>(b)
+	{
+	}
+
+	Packet(const void* data, unsigned int len) : Buffer<ZT_PROTO_MAX_PACKET_LENGTH>(data, len)
+	{
+	}
+
+	/**
+	 * Construct a new empty packet with a unique random packet ID
+	 *
+	 * Flags and hops will be zero. Other fields and data region are undefined.
+	 * Use the header access methods (setDestination() and friends) to fill out
+	 * the header. Payload should be appended; initial size is header size.
+	 */
+	Packet() : Buffer<ZT_PROTO_MAX_PACKET_LENGTH>(ZT_PROTO_MIN_PACKET_LENGTH)
+	{
+		Utils::getSecureRandom(field(ZT_PACKET_IDX_IV, 8), 8);
+		(*this)[ZT_PACKET_IDX_FLAGS] = 0;	// zero flags, cipher ID, and hops
+	}
+
+	/**
+	 * Make a copy of a packet with a new initialization vector and destination address
+	 *
+	 * This can be used to take one draft prototype packet and quickly make copies to
+	 * encrypt for different destinations.
+	 *
+	 * @param prototype Prototype packet
+	 * @param dest Destination ZeroTier address for new packet
+	 */
+	Packet(const Packet& prototype, const Address& dest) : Buffer<ZT_PROTO_MAX_PACKET_LENGTH>(prototype)
+	{
+		Utils::getSecureRandom(field(ZT_PACKET_IDX_IV, 8), 8);
+		setDestination(dest);
+	}
+
+	/**
+	 * Construct a new empty packet with a unique random packet ID
+	 *
+	 * @param dest Destination ZT address
+	 * @param source Source ZT address
+	 * @param v Verb
+	 */
+	Packet(const Address& dest, const Address& source, const Verb v) : Buffer<ZT_PROTO_MAX_PACKET_LENGTH>(ZT_PROTO_MIN_PACKET_LENGTH)
+	{
+		Utils::getSecureRandom(field(ZT_PACKET_IDX_IV, 8), 8);
+		setDestination(dest);
+		setSource(source);
+		(*this)[ZT_PACKET_IDX_FLAGS] = 0;	// zero flags and hops
+		setVerb(v);
+	}
+
+	/**
+	 * Reset this packet structure for reuse in place
+	 *
+	 * @param dest Destination ZT address
+	 * @param source Source ZT address
+	 * @param v Verb
+	 */
+	inline void reset(const Address& dest, const Address& source, const Verb v)
+	{
+		setSize(ZT_PROTO_MIN_PACKET_LENGTH);
+		Utils::getSecureRandom(field(ZT_PACKET_IDX_IV, 8), 8);
+		setDestination(dest);
+		setSource(source);
+		(*this)[ZT_PACKET_IDX_FLAGS] = 0;	// zero flags, cipher ID, and hops
+		setVerb(v);
+	}
+
+	/**
+	 * Generate a new IV / packet ID in place
+	 *
+	 * This can be used to re-use a packet buffer multiple times to send
+	 * technically different but otherwise identical copies of the same
+	 * packet.
+	 */
+	inline void newInitializationVector()
+	{
+		Utils::getSecureRandom(field(ZT_PACKET_IDX_IV, 8), 8);
+	}
+
+	/**
+	 * Set this packet's destination
+	 *
+	 * @param dest ZeroTier address of destination
+	 */
+	inline void setDestination(const Address& dest)
+	{
+		dest.copyTo(field(ZT_PACKET_IDX_DEST, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
+	}
+
+	/**
+	 * Set this packet's source
+	 *
+	 * @param source ZeroTier address of source
+	 */
+	inline void setSource(const Address& source)
+	{
+		source.copyTo(field(ZT_PACKET_IDX_SOURCE, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
+	}
+
+	/**
+	 * Get this packet's destination
+	 *
+	 * @return Destination ZT address
+	 */
+	inline Address destination() const
+	{
+		return Address(field(ZT_PACKET_IDX_DEST, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
+	}
+
+	/**
+	 * Get this packet's source
+	 *
+	 * @return Source ZT address
+	 */
+	inline Address source() const
+	{
+		return Address(field(ZT_PACKET_IDX_SOURCE, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
+	}
+
+	/**
+	 * @return True if packet is of valid length
+	 */
+	inline bool lengthValid() const
+	{
+		return (size() >= ZT_PROTO_MIN_PACKET_LENGTH);
+	}
+
+	/**
+	 * @return True if packet is fragmented (expect fragments)
+	 */
+	inline bool fragmented() const
+	{
+		return (((unsigned char)(*this)[ZT_PACKET_IDX_FLAGS] & ZT_PROTO_FLAG_FRAGMENTED) != 0);
+	}
+
+	/**
+	 * Set this packet's fragmented flag
+	 *
+	 * @param f Fragmented flag value
+	 */
+	inline void setFragmented(bool f)
+	{
+		if (f) {
+			(*this)[ZT_PACKET_IDX_FLAGS] |= (char)ZT_PROTO_FLAG_FRAGMENTED;
+		}
+		else {
+			(*this)[ZT_PACKET_IDX_FLAGS] &= (char)(~ZT_PROTO_FLAG_FRAGMENTED);
+		}
+	}
+
+	/**
+	 * @return True if packet is encrypted with an extra ephemeral key
+	 */
+	inline bool extendedArmor() const
+	{
+		return (((unsigned char)(*this)[ZT_PACKET_IDX_FLAGS] & ZT_PROTO_FLAG_EXTENDED_ARMOR) != 0);
+	}
+
+	/**
+	 * Set this packet's extended armor flag
+	 *
+	 * @param f Extended armor flag value
+	 */
+	inline void setExtendedArmor(bool f)
+	{
+		if (f) {
+			(*this)[ZT_PACKET_IDX_FLAGS] |= (char)ZT_PROTO_FLAG_EXTENDED_ARMOR;
+		}
+		else {
+			(*this)[ZT_PACKET_IDX_FLAGS] &= (char)(~ZT_PROTO_FLAG_EXTENDED_ARMOR);
+		}
+	}
+
+	/**
+	 * @return True if compressed (result only valid if unencrypted)
+	 */
+	inline bool compressed() const
+	{
+		return (((unsigned char)(*this)[ZT_PACKET_IDX_VERB] & ZT_PROTO_VERB_FLAG_COMPRESSED) != 0);
+	}
+
+	/**
+	 * @return ZeroTier forwarding hops (0 to 7)
+	 */
+	inline unsigned int hops() const
+	{
+		return ((unsigned int)(*this)[ZT_PACKET_IDX_FLAGS] & 0x07);
+	}
+
+	/**
+	 * Increment this packet's hop count
+	 */
+	inline void incrementHops()
+	{
+		unsigned char& b = (*this)[ZT_PACKET_IDX_FLAGS];
+		b = (b & 0xf8) | ((b + 1) & 0x07);
+	}
+
+	/**
+	 * @return Cipher suite selector: 0 - 7 (see #defines)
+	 */
+	inline unsigned int cipher() const
+	{
+		return (((unsigned int)(*this)[ZT_PACKET_IDX_FLAGS] & 0x38) >> 3);
+	}
+
+	/**
+	 * @return Whether this packet is currently encrypted
+	 */
+	inline bool isEncrypted() const
+	{
+		return (cipher() == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) || (cipher() == ZT_PROTO_CIPHER_SUITE__AES_GMAC_SIV);
+	}
+
+	/**
+	 * Set this packet's cipher suite
+	 */
+	inline void setCipher(unsigned int c)
+	{
+		unsigned char& b = (*this)[ZT_PACKET_IDX_FLAGS];
+		b = (b & 0xc7) | (unsigned char)((c << 3) & 0x38);	 // bits: FFCCCHHH
+	}
+
+	/**
+	 * Get the trusted path ID for this packet (only meaningful if cipher is trusted path)
+	 *
+	 * @return Trusted path ID (from MAC field)
+	 */
+	inline uint64_t trustedPathId() const
+	{
+		return at<uint64_t>(ZT_PACKET_IDX_MAC);
+	}
+
+	/**
+	 * Set this packet's trusted path ID and set the cipher spec to trusted path
+	 *
+	 * @param tpid Trusted path ID
+	 */
+	inline void setTrusted(const uint64_t tpid)
+	{
+		setCipher(ZT_PROTO_CIPHER_SUITE__NO_CRYPTO_TRUSTED_PATH);
+		setAt(ZT_PACKET_IDX_MAC, tpid);
+	}
+
+	/**
+	 * Get this packet's unique ID (the IV field interpreted as uint64_t)
+	 *
+	 * Note that the least significant 3 bits of this ID will change when armor()
+	 * is called to armor the packet for transport. This is because armor() will
+	 * mask the last 3 bits against the send counter for QoS monitoring use prior
+	 * to actually using the IV to encrypt and MAC the packet. Be aware of this
+	 * when grabbing the packetId of a new packet prior to armor/send.
+	 *
+	 * @return Packet ID
+	 */
+	inline uint64_t packetId() const
+	{
+		return at<uint64_t>(ZT_PACKET_IDX_IV);
+	}
+
+	/**
+	 * Set packet verb
+	 *
+	 * This also has the side-effect of clearing any verb flags, such as
+	 * compressed, and so must only be done during packet composition.
+	 *
+	 * @param v New packet verb
+	 */
+	inline void setVerb(Verb v)
+	{
+		(*this)[ZT_PACKET_IDX_VERB] = (char)v;
+	}
+
+	/**
+	 * @return Packet verb (not including flag bits)
+	 */
+	inline Verb verb() const
+	{
+		return (Verb)((*this)[ZT_PACKET_IDX_VERB] & 0x1f);
+	}
+
+	/**
+	 * @return Length of packet payload
+	 */
+	inline unsigned int payloadLength() const
+	{
+		return ((size() < ZT_PROTO_MIN_PACKET_LENGTH) ? 0 : (size() - ZT_PROTO_MIN_PACKET_LENGTH));
+	}
+
+	/**
+	 * @return Raw packet payload
+	 */
+	inline const unsigned char* payload() const
+	{
+		return field(ZT_PACKET_IDX_PAYLOAD, size() - ZT_PACKET_IDX_PAYLOAD);
+	}
+
+	/**
+	 * Armor packet for transport
+	 *
+	 * @param key 32-byte key
+	 * @param encryptPayload If true, encrypt packet payload, else just MAC
+	 * @param extendedArmor Use an ephemeral key to encrypt payload (for encrypted HELLO)
+	 * @param identity Identity of packet recipient/destination
+	 * @param aesKeys If non-NULL these are the two keys for AES-GMAC-SIV
+	 */
+	void armor(const void* key, bool encryptPayload, bool extendedArmor, const AES aesKeys[2], const Identity& identity);
+
+	/**
+	 * Verify and (if encrypted) decrypt packet
+	 *
+	 * This does not handle trusted path mode packets and will return false
+	 * for these. These are handled in IncomingPacket if the sending physical
+	 * address and MAC field match a trusted path.
+	 *
+	 * @param key 32-byte key
+	 * @param aesKeys If non-NULL these are the two keys for AES-GMAC-SIV
+	 * @param identity Receiver's identity (must include secret)
+	 * @return False if packet is invalid or failed MAC authenticity check
+	 */
+	bool dearmor(const void* key, const AES aesKeys[2], const Identity& identity);
+
+	/**
+	 * Encrypt/decrypt a separately armored portion of a packet
+	 *
+	 * This is currently only used to mask portions of HELLO as an extra
+	 * security precaution since most of that message is sent in the clear.
+	 *
+	 * This must NEVER be used more than once in the same packet, as doing
+	 * so will result in re-use of the same key stream.
+	 *
+	 * @param key 32-byte key
+	 * @param start Start of encrypted portion
+	 * @param len Length of encrypted portion
+	 */
+	void cryptField(const void* key, unsigned int start, unsigned int len);
+
+	/**
+	 * Attempt to compress payload if not already (must be unencrypted)
+	 *
+	 * This requires that the payload at least contain the verb byte already
+	 * set. The compressed flag in the verb is set if compression successfully
+	 * results in a size reduction. If no size reduction occurs, compression
+	 * is not done and the flag is left cleared.
+	 *
+	 * @return True if compression occurred
+	 */
+	bool compress();
+
+	/**
+	 * Attempt to decompress payload if it is compressed (must be unencrypted)
+	 *
+	 * If payload is compressed, it is decompressed and the compressed verb
+	 * flag is cleared. Otherwise nothing is done and true is returned.
+	 *
+	 * @return True if data is now decompressed and valid, false on error
+	 */
+	bool uncompress();
 
   private:
-    static const unsigned char ZERO_KEY[32];
-
-    /**
-     * Deterministically mangle a 256-bit crypto key based on packet
-     *
-     * This uses extra data from the packet to mangle the secret, giving us an
-     * effective IV that is somewhat more than 64 bits. This is "free" for
-     * Salsa20 since it has negligible key setup time so using a different
-     * key each time is fine.
-     *
-     * @param in Input key (32 bytes)
-     * @param out Output buffer (32 bytes)
-     */
-    inline void _salsa20MangleKey(const unsigned char* in, unsigned char* out) const
-    {
-        const unsigned char* d = (const unsigned char*)data();
-
-        // IV and source/destination addresses. Using the addresses divides the
-        // key space into two halves-- A->B and B->A (since order will change).
-        for (unsigned int i = 0; i < 18; ++i) {   // 8 + (ZT_ADDRESS_LENGTH * 2) == 18
-            out[i] = in[i] ^ d[i];
-        }
-
-        // Flags, but with hop count masked off. Hop count is altered by forwarding
-        // nodes. It's one of the only parts of a packet modifiable by people
-        // without the key.
-        out[18] = in[18] ^ (d[ZT_PACKET_IDX_FLAGS] & 0xf8);
-
-        // Raw packet size in bytes -- thus each packet size defines a new
-        // key space.
-        out[19] = in[19] ^ (unsigned char)(size() & 0xff);
-        out[20] = in[20] ^ (unsigned char)((size() >> 8) & 0xff);   // little endian
-
-        // Rest of raw key is used unchanged
-        for (unsigned int i = 21; i < 32; ++i) {
-            out[i] = in[i];
-        }
-    }
+	static const unsigned char ZERO_KEY[32];
+
+	/**
+	 * Deterministically mangle a 256-bit crypto key based on packet
+	 *
+	 * This uses extra data from the packet to mangle the secret, giving us an
+	 * effective IV that is somewhat more than 64 bits. This is "free" for
+	 * Salsa20 since it has negligible key setup time so using a different
+	 * key each time is fine.
+	 *
+	 * @param in Input key (32 bytes)
+	 * @param out Output buffer (32 bytes)
+	 */
+	inline void _salsa20MangleKey(const unsigned char* in, unsigned char* out) const
+	{
+		const unsigned char* d = (const unsigned char*)data();
+
+		// IV and source/destination addresses. Using the addresses divides the
+		// key space into two halves-- A->B and B->A (since order will change).
+		for (unsigned int i = 0; i < 18; ++i) {	  // 8 + (ZT_ADDRESS_LENGTH * 2) == 18
+			out[i] = in[i] ^ d[i];
+		}
+
+		// Flags, but with hop count masked off. Hop count is altered by forwarding
+		// nodes. It's one of the only parts of a packet modifiable by people
+		// without the key.
+		out[18] = in[18] ^ (d[ZT_PACKET_IDX_FLAGS] & 0xf8);
+
+		// Raw packet size in bytes -- thus each packet size defines a new
+		// key space.
+		out[19] = in[19] ^ (unsigned char)(size() & 0xff);
+		out[20] = in[20] ^ (unsigned char)((size() >> 8) & 0xff);	// little endian
+
+		// Rest of raw key is used unchanged
+		for (unsigned int i = 21; i < 32; ++i) {
+			out[i] = in[i];
+		}
+	}
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 81 - 81
node/PacketMultiplexer.cpp

@@ -24,99 +24,99 @@ namespace ZeroTier {
 
 PacketMultiplexer::PacketMultiplexer(const RuntimeEnvironment* renv)
 {
-    RR = renv;
+	RR = renv;
 };
 
 void PacketMultiplexer::putFrame(void* tPtr, uint64_t nwid, void** nuptr, const MAC& source, const MAC& dest, unsigned int etherType, unsigned int vlanId, const void* data, unsigned int len, unsigned int flowId)
 {
 #if defined(__APPLE__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__WINDOWS__)
-    RR->node->putFrame(tPtr, nwid, nuptr, source, dest, etherType, vlanId, (const void*)data, len);
-    return;
+	RR->node->putFrame(tPtr, nwid, nuptr, source, dest, etherType, vlanId, (const void*)data, len);
+	return;
 #endif
 
-    if (! _enabled) {
-        RR->node->putFrame(tPtr, nwid, nuptr, source, dest, etherType, vlanId, (const void*)data, len);
-        return;
-    }
-
-    PacketRecord* packet;
-    _rxPacketVector_m.lock();
-    if (_rxPacketVector.empty()) {
-        packet = new PacketRecord;
-    }
-    else {
-        packet = _rxPacketVector.back();
-        _rxPacketVector.pop_back();
-    }
-    _rxPacketVector_m.unlock();
-
-    packet->tPtr = tPtr;
-    packet->nwid = nwid;
-    packet->nuptr = nuptr;
-    packet->source = source.toInt();
-    packet->dest = dest.toInt();
-    packet->etherType = etherType;
-    packet->vlanId = vlanId;
-    packet->len = len;
-    packet->flowId = flowId;
-    memcpy(packet->data, data, len);
-
-    int bucket = flowId % _concurrency;
-    _rxPacketQueues[bucket]->postLimit(packet, 2048);
+	if (! _enabled) {
+		RR->node->putFrame(tPtr, nwid, nuptr, source, dest, etherType, vlanId, (const void*)data, len);
+		return;
+	}
+
+	PacketRecord* packet;
+	_rxPacketVector_m.lock();
+	if (_rxPacketVector.empty()) {
+		packet = new PacketRecord;
+	}
+	else {
+		packet = _rxPacketVector.back();
+		_rxPacketVector.pop_back();
+	}
+	_rxPacketVector_m.unlock();
+
+	packet->tPtr = tPtr;
+	packet->nwid = nwid;
+	packet->nuptr = nuptr;
+	packet->source = source.toInt();
+	packet->dest = dest.toInt();
+	packet->etherType = etherType;
+	packet->vlanId = vlanId;
+	packet->len = len;
+	packet->flowId = flowId;
+	memcpy(packet->data, data, len);
+
+	int bucket = flowId % _concurrency;
+	_rxPacketQueues[bucket]->postLimit(packet, 2048);
 }
 
 void PacketMultiplexer::setUpPostDecodeReceiveThreads(unsigned int concurrency, bool cpuPinningEnabled)
 {
 #if defined(__APPLE__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__WINDOWS__)
-    return;
+	return;
 #endif
-    _enabled = true;
-    _concurrency = concurrency;
-    bool _enablePinning = cpuPinningEnabled;
-
-    for (unsigned int i = 0; i < _concurrency; ++i) {
-        fprintf(stderr, "Reserved queue for thread %d\n", i);
-        _rxPacketQueues.push_back(new BlockingQueue<PacketRecord*>());
-    }
-
-    // Each thread picks from its own queue to feed into the core
-    for (unsigned int i = 0; i < _concurrency; ++i) {
-        _rxThreads.push_back(std::thread([this, i, _enablePinning]() {
-            fprintf(stderr, "Created post-decode packet ingestion thread %d\n", i);
-
-            PacketRecord* packet = nullptr;
-            for (;;) {
-                if (! _rxPacketQueues[i]->get(packet)) {
-                    break;
-                }
-                if (! packet) {
-                    break;
-                }
-
-                // fprintf(stderr, "popped packet from queue %d\n", i);
-
-                MAC sourceMac = MAC(packet->source);
-                MAC destMac = MAC(packet->dest);
-
-                RR->node->putFrame(packet->tPtr, packet->nwid, packet->nuptr, sourceMac, destMac, packet->etherType, 0, (const void*)packet->data, packet->len);
-                {
-                    Mutex::Lock l(_rxPacketVector_m);
-                    _rxPacketVector.push_back(packet);
-                }
-                /*
-                if (ZT_ResultCode_isFatal(err)) {
-                    char tmp[256];
-                    OSUtils::ztsnprintf(tmp, sizeof(tmp), "error processing packet: %d", (int)err);
-                    Mutex::Lock _l(_termReason_m);
-                    _termReason = ONE_UNRECOVERABLE_ERROR;
-                    _fatalErrorMessage = tmp;
-                    this->terminate();
-                    break;
-                }
-                */
-            }
-        }));
-    }
+	_enabled = true;
+	_concurrency = concurrency;
+	bool _enablePinning = cpuPinningEnabled;
+
+	for (unsigned int i = 0; i < _concurrency; ++i) {
+		fprintf(stderr, "Reserved queue for thread %d\n", i);
+		_rxPacketQueues.push_back(new BlockingQueue<PacketRecord*>());
+	}
+
+	// Each thread picks from its own queue to feed into the core
+	for (unsigned int i = 0; i < _concurrency; ++i) {
+		_rxThreads.push_back(std::thread([this, i, _enablePinning]() {
+			fprintf(stderr, "Created post-decode packet ingestion thread %d\n", i);
+
+			PacketRecord* packet = nullptr;
+			for (;;) {
+				if (! _rxPacketQueues[i]->get(packet)) {
+					break;
+				}
+				if (! packet) {
+					break;
+				}
+
+				// fprintf(stderr, "popped packet from queue %d\n", i);
+
+				MAC sourceMac = MAC(packet->source);
+				MAC destMac = MAC(packet->dest);
+
+				RR->node->putFrame(packet->tPtr, packet->nwid, packet->nuptr, sourceMac, destMac, packet->etherType, 0, (const void*)packet->data, packet->len);
+				{
+					Mutex::Lock l(_rxPacketVector_m);
+					_rxPacketVector.push_back(packet);
+				}
+				/*
+				if (ZT_ResultCode_isFatal(err)) {
+					char tmp[256];
+					OSUtils::ztsnprintf(tmp, sizeof(tmp), "error processing packet: %d", (int)err);
+					Mutex::Lock _l(_termReason_m);
+					_termReason = ONE_UNRECOVERABLE_ERROR;
+					_fatalErrorMessage = tmp;
+					this->terminate();
+					break;
+				}
+				*/
+			}
+		}));
+	}
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 25 - 25
node/PacketMultiplexer.hpp

@@ -25,41 +25,41 @@
 namespace ZeroTier {
 
 struct PacketRecord {
-    void* tPtr;
-    uint64_t nwid;
-    void** nuptr;
-    uint64_t source;
-    uint64_t dest;
-    unsigned int etherType;
-    unsigned int vlanId;
-    uint8_t data[ZT_MAX_MTU];
-    unsigned int len;
-    unsigned int flowId;
+	void* tPtr;
+	uint64_t nwid;
+	void** nuptr;
+	uint64_t source;
+	uint64_t dest;
+	unsigned int etherType;
+	unsigned int vlanId;
+	uint8_t data[ZT_MAX_MTU];
+	unsigned int len;
+	unsigned int flowId;
 };
 
 class PacketMultiplexer {
   public:
-    const RuntimeEnvironment* RR;
+	const RuntimeEnvironment* RR;
 
-    PacketMultiplexer(const RuntimeEnvironment* renv);
+	PacketMultiplexer(const RuntimeEnvironment* renv);
 
-    void setUpPostDecodeReceiveThreads(unsigned int concurrency, bool cpuPinningEnabled);
+	void setUpPostDecodeReceiveThreads(unsigned int concurrency, bool cpuPinningEnabled);
 
-    void putFrame(void* tPtr, uint64_t nwid, void** nuptr, const MAC& source, const MAC& dest, unsigned int etherType, unsigned int vlanId, const void* data, unsigned int len, unsigned int flowId);
+	void putFrame(void* tPtr, uint64_t nwid, void** nuptr, const MAC& source, const MAC& dest, unsigned int etherType, unsigned int vlanId, const void* data, unsigned int len, unsigned int flowId);
 
-    std::vector<BlockingQueue<PacketRecord*>*> _rxPacketQueues;
+	std::vector<BlockingQueue<PacketRecord*>*> _rxPacketQueues;
 
-    unsigned int _concurrency;
-    // pool
-    std::vector<PacketRecord*> _rxPacketVector;
-    std::vector<std::thread> _rxPacketThreads;
-    Mutex _rxPacketVector_m, _rxPacketThreads_m;
+	unsigned int _concurrency;
+	// pool
+	std::vector<PacketRecord*> _rxPacketVector;
+	std::vector<std::thread> _rxPacketThreads;
+	Mutex _rxPacketVector_m, _rxPacketThreads_m;
 
-    std::vector<std::thread> _rxThreads;
-    unsigned int _rxThreadCount;
-    bool _enabled;
+	std::vector<std::thread> _rxThreads;
+	unsigned int _rxThreadCount;
+	bool _enabled;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
-#endif   // ZT_PACKET_MULTIPLEXER_HPP
+#endif	 // ZT_PACKET_MULTIPLEXER_HPP

+ 6 - 6
node/Path.cpp

@@ -20,11 +20,11 @@ namespace ZeroTier {
 
 bool Path::send(const RuntimeEnvironment* RR, void* tPtr, const void* data, unsigned int len, int64_t now)
 {
-    if (RR->node->putPacket(tPtr, _localSocket, _addr, data, len)) {
-        _lastOut = now;
-        return true;
-    }
-    return false;
+	if (RR->node->putPacket(tPtr, _localSocket, _addr, data, len)) {
+		_lastOut = now;
+		return true;
+	}
+	return false;
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 443 - 443
node/Path.hpp

@@ -41,454 +41,454 @@ class RuntimeEnvironment;
  * A path across the physical network
  */
 class Path {
-    friend class SharedPtr<Path>;
-    friend class Bond;
+	friend class SharedPtr<Path>;
+	friend class Bond;
 
   public:
-    /**
-     * Efficient unique key for paths in a Hashtable
-     */
-    class HashKey {
-      public:
-        HashKey()
-        {
-        }
-
-        HashKey(const int64_t l, const InetAddress& r)
-        {
-            if (r.ss_family == AF_INET) {
-                _k[0] = (uint64_t)reinterpret_cast<const struct sockaddr_in*>(&r)->sin_addr.s_addr;
-                _k[1] = (uint64_t)reinterpret_cast<const struct sockaddr_in*>(&r)->sin_port;
-                _k[2] = (uint64_t)l;
-            }
-            else if (r.ss_family == AF_INET6) {
-                memcpy(_k, reinterpret_cast<const struct sockaddr_in6*>(&r)->sin6_addr.s6_addr, 16);
-                _k[2] = ((uint64_t)reinterpret_cast<const struct sockaddr_in6*>(&r)->sin6_port << 32) ^ (uint64_t)l;
-            }
-            else {
-                memcpy(_k, &r, std::min(sizeof(_k), sizeof(InetAddress)));
-                _k[2] += (uint64_t)l;
-            }
-        }
-
-        inline unsigned long hashCode() const
-        {
-            return (unsigned long)(_k[0] + _k[1] + _k[2]);
-        }
-
-        inline bool operator==(const HashKey& k) const
-        {
-            return ((_k[0] == k._k[0]) && (_k[1] == k._k[1]) && (_k[2] == k._k[2]));
-        }
-        inline bool operator!=(const HashKey& k) const
-        {
-            return (! (*this == k));
-        }
-
-      private:
-        uint64_t _k[3];
-    };
-
-    Path()
-        : _lastOut(0)
-        , _lastIn(0)
-        , _lastTrustEstablishedPacketReceived(0)
-        , _lastEchoRequestReceived(0)
-        , _localPort(0)
-        , _localSocket(-1)
-        , _latencyMean(0.0)
-        , _latencyVariance(0.0)
-        , _packetLossRatio(0.0)
-        , _packetErrorRatio(0.0)
-        , _assignedFlowCount(0)
-        , _valid(true)
-        , _eligible(false)
-        , _bonded(false)
-        , _mtu(0)
-        , _givenLinkSpeed(0)
-        , _relativeQuality(0)
-        , _latency(0xffff)
-        , _addr()
-        , _ipScope(InetAddress::IP_SCOPE_NONE)
-    {
-    }
-
-    Path(const int64_t localSocket, const InetAddress& addr)
-        : _lastOut(0)
-        , _lastIn(0)
-        , _lastTrustEstablishedPacketReceived(0)
-        , _lastEchoRequestReceived(0)
-        , _localPort(0)
-        , _localSocket(localSocket)
-        , _latencyMean(0.0)
-        , _latencyVariance(0.0)
-        , _packetLossRatio(0.0)
-        , _packetErrorRatio(0.0)
-        , _assignedFlowCount(0)
-        , _valid(true)
-        , _eligible(false)
-        , _bonded(false)
-        , _mtu(0)
-        , _givenLinkSpeed(0)
-        , _relativeQuality(0)
-        , _latency(0xffff)
-        , _addr(addr)
-        , _ipScope(addr.ipScope())
-    {
-    }
-
-    /**
-     * Called when a packet is received from this remote path, regardless of content
-     *
-     * @param t Time of receive
-     */
-    inline void received(const uint64_t t)
-    {
-        _lastIn = t;
-    }
-
-    /**
-     * Set time last trusted packet was received (done in Peer::received())
-     */
-    inline void trustedPacketReceived(const uint64_t t)
-    {
-        _lastTrustEstablishedPacketReceived = t;
-    }
-
-    /**
-     * Send a packet via this path (last out time is also updated)
-     *
-     * @param RR Runtime environment
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param data Packet data
-     * @param len Packet length
-     * @param now Current time
-     * @return True if transport reported success
-     */
-    bool send(const RuntimeEnvironment* RR, void* tPtr, const void* data, unsigned int len, int64_t now);
-
-    /**
-     * Manually update last sent time
-     *
-     * @param t Time of send
-     */
-    inline void sent(const int64_t t)
-    {
-        _lastOut = t;
-    }
-
-    /**
-     * Update path latency with a new measurement
-     *
-     * @param l Measured latency
-     */
-    inline void updateLatency(const unsigned int l, int64_t now)
-    {
-        unsigned int pl = _latency;
-        if (pl < 0xffff) {
-            _latency = (pl + l) / 2;
-        }
-        else {
-            _latency = l;
-        }
-    }
-
-    /**
-     * @return Local socket as specified by external code
-     */
-    inline int64_t localSocket() const
-    {
-        return _localSocket;
-    }
-
-    /**
-     * @return Local port corresponding to the localSocket
-     */
-    inline int64_t localPort() const
-    {
-        return _localPort;
-    }
-
-    /**
-     * @return Physical address
-     */
-    inline const InetAddress& address() const
-    {
-        return _addr;
-    }
-
-    /**
-     * @return IP scope -- faster shortcut for address().ipScope()
-     */
-    inline InetAddress::IpScope ipScope() const
-    {
-        return _ipScope;
-    }
-
-    /**
-     * @return True if path has received a trust established packet (e.g. common network membership) in the past ZT_TRUST_EXPIRATION ms
-     */
-    inline bool trustEstablished(const int64_t now) const
-    {
-        return ((now - _lastTrustEstablishedPacketReceived) < ZT_TRUST_EXPIRATION);
-    }
-
-    /**
-     * @return Preference rank, higher == better
-     */
-    inline unsigned int preferenceRank() const
-    {
-        // This causes us to rank paths in order of IP scope rank (see InetAddress.hpp) but
-        // within each IP scope class to prefer IPv6 over IPv4.
-        return (((unsigned int)_ipScope << 1) | (unsigned int)(_addr.ss_family == AF_INET6));
-    }
-
-    /**
-     * Check whether this address is valid for a ZeroTier path
-     *
-     * This checks the address type and scope against address types and scopes
-     * that we currently support for ZeroTier communication.
-     *
-     * @param a Address to check
-     * @return True if address is good for ZeroTier path use
-     */
-    static inline bool isAddressValidForPath(const InetAddress& a)
-    {
-        if ((a.ss_family == AF_INET) || (a.ss_family == AF_INET6)) {
-            switch (a.ipScope()) {
-                /* Note: we don't do link-local at the moment. Unfortunately these
-                 * cause several issues. The first is that they usually require a
-                 * device qualifier, which we don't handle yet and can't portably
-                 * push in PUSH_DIRECT_PATHS. The second is that some OSes assign
-                 * these very ephemerally or otherwise strangely. So we'll use
-                 * private, pseudo-private, shared (e.g. carrier grade NAT), or
-                 * global IP addresses. */
-                case InetAddress::IP_SCOPE_PRIVATE:
-                case InetAddress::IP_SCOPE_PSEUDOPRIVATE:
-                case InetAddress::IP_SCOPE_SHARED:
-                case InetAddress::IP_SCOPE_GLOBAL:
-                    if (a.ss_family == AF_INET6) {
-                        // TEMPORARY HACK: for now, we are going to blacklist he.net IPv6
-                        // tunnels due to very spotty performance and low MTU issues over
-                        // these IPv6 tunnel links.
-                        const uint8_t* ipd = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_addr.s6_addr);
-                        if ((ipd[0] == 0x20) && (ipd[1] == 0x01) && (ipd[2] == 0x04) && (ipd[3] == 0x70)) {
-                            return false;
-                        }
-                    }
-                    return true;
-                default:
-                    return false;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * @return Latency or 0xffff if unknown
-     */
-    inline unsigned int latency() const
-    {
-        return _latency;
-    }
-
-    /**
-     * @return Path quality -- lower is better
-     */
-    inline long quality(const int64_t now) const
-    {
-        const int l = (long)_latency;
-        const int age = (long)std::min((now - _lastIn), (int64_t)(ZT_PATH_HEARTBEAT_PERIOD * 10));   // set an upper sanity limit to avoid overflow
-        return (((age < (ZT_PATH_HEARTBEAT_PERIOD + 5000)) ? l : (l + 0xffff + age)) * (long)((ZT_INETADDRESS_MAX_SCOPE - _ipScope) + 1));
-    }
-
-    /**
-     * @return True if this path is alive (receiving heartbeats)
-     */
-    inline bool alive(const int64_t now) const
-    {
-        return (now - _lastIn) < (ZT_PATH_HEARTBEAT_PERIOD + 5000);
-    }
-
-    /**
-     * @return True if this path needs a heartbeat
-     */
-    inline bool needsHeartbeat(const int64_t now) const
-    {
-        return ((now - _lastOut) >= ZT_PATH_HEARTBEAT_PERIOD);
-    }
-
-    /**
-     * @return Last time we sent something
-     */
-    inline int64_t lastOut() const
-    {
-        return _lastOut;
-    }
-
-    /**
-     * @return Last time we received anything
-     */
-    inline int64_t lastIn() const
-    {
-        return _lastIn;
-    }
-
-    /**
-     * @return the age of the path in terms of receiving packets
-     */
-    inline int64_t age(int64_t now)
-    {
-        return (now - _lastIn);
-    }
-
-    /**
-     * @return Time last trust-established packet was received
-     */
-    inline int64_t lastTrustEstablishedPacketReceived() const
-    {
-        return _lastTrustEstablishedPacketReceived;
-    }
-
-    /**
-     * Rate limit gate for inbound ECHO requests
-     */
-    inline bool rateGateEchoRequest(const int64_t now)
-    {
-        if ((now - _lastEchoRequestReceived) >= (ZT_PEER_GENERAL_RATE_LIMIT / 6)) {
-            _lastEchoRequestReceived = now;
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * @return Mean latency as reported by the bonding layer
-     */
-    inline float latencyMean() const
-    {
-        return _latencyMean;
-    }
-
-    /**
-     * @return Latency variance as reported by the bonding layer
-     */
-    inline float latencyVariance() const
-    {
-        return _latencyVariance;
-    }
-
-    /**
-     * @return Packet Loss Ratio as reported by the bonding layer
-     */
-    inline float packetLossRatio() const
-    {
-        return _packetLossRatio;
-    }
-
-    /**
-     * @return Packet Error Ratio as reported by the bonding layer
-     */
-    inline float packetErrorRatio() const
-    {
-        return _packetErrorRatio;
-    }
-
-    /**
-     * @return Number of flows assigned to this path
-     */
-    inline unsigned int assignedFlowCount() const
-    {
-        return _assignedFlowCount;
-    }
-
-    /**
-     * @return Whether this path is valid as reported by the bonding layer. The bonding layer
-     * actually checks with Phy to see if the interface is still up
-     */
-    inline bool valid() const
-    {
-        return _valid;
-    }
-
-    /**
-     * @return Whether this path is eligible for use in a bond as reported by the bonding layer
-     */
-    inline bool eligible() const
-    {
-        return _eligible;
-    }
-
-    /**
-     * @return Whether this path is bonded as reported by the bonding layer
-     */
-    inline bool bonded() const
-    {
-        return _bonded;
-    }
-
-    /**
-     * @return Whether the user-specified MTU for this path (determined by MTU for parent link)
-     */
-    inline uint16_t mtu() const
-    {
-        return _mtu;
-    }
-
-    /**
-     * @return Given link capacity as reported by the bonding layer
-     */
-    inline uint32_t givenLinkSpeed() const
-    {
-        return _givenLinkSpeed;
-    }
-
-    /**
-     * @return Path's quality as reported by the bonding layer
-     */
-    inline float relativeQuality() const
-    {
-        return _relativeQuality;
-    }
-
-    /**
-     * @return Physical interface name that this path lives on
-     */
-    char* ifname()
-    {
-        return _ifname;
-    }
+	/**
+	 * Efficient unique key for paths in a Hashtable
+	 */
+	class HashKey {
+	  public:
+		HashKey()
+		{
+		}
+
+		HashKey(const int64_t l, const InetAddress& r)
+		{
+			if (r.ss_family == AF_INET) {
+				_k[0] = (uint64_t)reinterpret_cast<const struct sockaddr_in*>(&r)->sin_addr.s_addr;
+				_k[1] = (uint64_t)reinterpret_cast<const struct sockaddr_in*>(&r)->sin_port;
+				_k[2] = (uint64_t)l;
+			}
+			else if (r.ss_family == AF_INET6) {
+				memcpy(_k, reinterpret_cast<const struct sockaddr_in6*>(&r)->sin6_addr.s6_addr, 16);
+				_k[2] = ((uint64_t)reinterpret_cast<const struct sockaddr_in6*>(&r)->sin6_port << 32) ^ (uint64_t)l;
+			}
+			else {
+				memcpy(_k, &r, std::min(sizeof(_k), sizeof(InetAddress)));
+				_k[2] += (uint64_t)l;
+			}
+		}
+
+		inline unsigned long hashCode() const
+		{
+			return (unsigned long)(_k[0] + _k[1] + _k[2]);
+		}
+
+		inline bool operator==(const HashKey& k) const
+		{
+			return ((_k[0] == k._k[0]) && (_k[1] == k._k[1]) && (_k[2] == k._k[2]));
+		}
+		inline bool operator!=(const HashKey& k) const
+		{
+			return (! (*this == k));
+		}
+
+	  private:
+		uint64_t _k[3];
+	};
+
+	Path()
+		: _lastOut(0)
+		, _lastIn(0)
+		, _lastTrustEstablishedPacketReceived(0)
+		, _lastEchoRequestReceived(0)
+		, _localPort(0)
+		, _localSocket(-1)
+		, _latencyMean(0.0)
+		, _latencyVariance(0.0)
+		, _packetLossRatio(0.0)
+		, _packetErrorRatio(0.0)
+		, _assignedFlowCount(0)
+		, _valid(true)
+		, _eligible(false)
+		, _bonded(false)
+		, _mtu(0)
+		, _givenLinkSpeed(0)
+		, _relativeQuality(0)
+		, _latency(0xffff)
+		, _addr()
+		, _ipScope(InetAddress::IP_SCOPE_NONE)
+	{
+	}
+
+	Path(const int64_t localSocket, const InetAddress& addr)
+		: _lastOut(0)
+		, _lastIn(0)
+		, _lastTrustEstablishedPacketReceived(0)
+		, _lastEchoRequestReceived(0)
+		, _localPort(0)
+		, _localSocket(localSocket)
+		, _latencyMean(0.0)
+		, _latencyVariance(0.0)
+		, _packetLossRatio(0.0)
+		, _packetErrorRatio(0.0)
+		, _assignedFlowCount(0)
+		, _valid(true)
+		, _eligible(false)
+		, _bonded(false)
+		, _mtu(0)
+		, _givenLinkSpeed(0)
+		, _relativeQuality(0)
+		, _latency(0xffff)
+		, _addr(addr)
+		, _ipScope(addr.ipScope())
+	{
+	}
+
+	/**
+	 * Called when a packet is received from this remote path, regardless of content
+	 *
+	 * @param t Time of receive
+	 */
+	inline void received(const uint64_t t)
+	{
+		_lastIn = t;
+	}
+
+	/**
+	 * Set time last trusted packet was received (done in Peer::received())
+	 */
+	inline void trustedPacketReceived(const uint64_t t)
+	{
+		_lastTrustEstablishedPacketReceived = t;
+	}
+
+	/**
+	 * Send a packet via this path (last out time is also updated)
+	 *
+	 * @param RR Runtime environment
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param data Packet data
+	 * @param len Packet length
+	 * @param now Current time
+	 * @return True if transport reported success
+	 */
+	bool send(const RuntimeEnvironment* RR, void* tPtr, const void* data, unsigned int len, int64_t now);
+
+	/**
+	 * Manually update last sent time
+	 *
+	 * @param t Time of send
+	 */
+	inline void sent(const int64_t t)
+	{
+		_lastOut = t;
+	}
+
+	/**
+	 * Update path latency with a new measurement
+	 *
+	 * @param l Measured latency
+	 */
+	inline void updateLatency(const unsigned int l, int64_t now)
+	{
+		unsigned int pl = _latency;
+		if (pl < 0xffff) {
+			_latency = (pl + l) / 2;
+		}
+		else {
+			_latency = l;
+		}
+	}
+
+	/**
+	 * @return Local socket as specified by external code
+	 */
+	inline int64_t localSocket() const
+	{
+		return _localSocket;
+	}
+
+	/**
+	 * @return Local port corresponding to the localSocket
+	 */
+	inline int64_t localPort() const
+	{
+		return _localPort;
+	}
+
+	/**
+	 * @return Physical address
+	 */
+	inline const InetAddress& address() const
+	{
+		return _addr;
+	}
+
+	/**
+	 * @return IP scope -- faster shortcut for address().ipScope()
+	 */
+	inline InetAddress::IpScope ipScope() const
+	{
+		return _ipScope;
+	}
+
+	/**
+	 * @return True if path has received a trust established packet (e.g. common network membership) in the past ZT_TRUST_EXPIRATION ms
+	 */
+	inline bool trustEstablished(const int64_t now) const
+	{
+		return ((now - _lastTrustEstablishedPacketReceived) < ZT_TRUST_EXPIRATION);
+	}
+
+	/**
+	 * @return Preference rank, higher == better
+	 */
+	inline unsigned int preferenceRank() const
+	{
+		// This causes us to rank paths in order of IP scope rank (see InetAddress.hpp) but
+		// within each IP scope class to prefer IPv6 over IPv4.
+		return (((unsigned int)_ipScope << 1) | (unsigned int)(_addr.ss_family == AF_INET6));
+	}
+
+	/**
+	 * Check whether this address is valid for a ZeroTier path
+	 *
+	 * This checks the address type and scope against address types and scopes
+	 * that we currently support for ZeroTier communication.
+	 *
+	 * @param a Address to check
+	 * @return True if address is good for ZeroTier path use
+	 */
+	static inline bool isAddressValidForPath(const InetAddress& a)
+	{
+		if ((a.ss_family == AF_INET) || (a.ss_family == AF_INET6)) {
+			switch (a.ipScope()) {
+				/* Note: we don't do link-local at the moment. Unfortunately these
+				 * cause several issues. The first is that they usually require a
+				 * device qualifier, which we don't handle yet and can't portably
+				 * push in PUSH_DIRECT_PATHS. The second is that some OSes assign
+				 * these very ephemerally or otherwise strangely. So we'll use
+				 * private, pseudo-private, shared (e.g. carrier grade NAT), or
+				 * global IP addresses. */
+				case InetAddress::IP_SCOPE_PRIVATE:
+				case InetAddress::IP_SCOPE_PSEUDOPRIVATE:
+				case InetAddress::IP_SCOPE_SHARED:
+				case InetAddress::IP_SCOPE_GLOBAL:
+					if (a.ss_family == AF_INET6) {
+						// TEMPORARY HACK: for now, we are going to blacklist he.net IPv6
+						// tunnels due to very spotty performance and low MTU issues over
+						// these IPv6 tunnel links.
+						const uint8_t* ipd = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(&a)->sin6_addr.s6_addr);
+						if ((ipd[0] == 0x20) && (ipd[1] == 0x01) && (ipd[2] == 0x04) && (ipd[3] == 0x70)) {
+							return false;
+						}
+					}
+					return true;
+				default:
+					return false;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * @return Latency or 0xffff if unknown
+	 */
+	inline unsigned int latency() const
+	{
+		return _latency;
+	}
+
+	/**
+	 * @return Path quality -- lower is better
+	 */
+	inline long quality(const int64_t now) const
+	{
+		const int l = (long)_latency;
+		const int age = (long)std::min((now - _lastIn), (int64_t)(ZT_PATH_HEARTBEAT_PERIOD * 10));	 // set an upper sanity limit to avoid overflow
+		return (((age < (ZT_PATH_HEARTBEAT_PERIOD + 5000)) ? l : (l + 0xffff + age)) * (long)((ZT_INETADDRESS_MAX_SCOPE - _ipScope) + 1));
+	}
+
+	/**
+	 * @return True if this path is alive (receiving heartbeats)
+	 */
+	inline bool alive(const int64_t now) const
+	{
+		return (now - _lastIn) < (ZT_PATH_HEARTBEAT_PERIOD + 5000);
+	}
+
+	/**
+	 * @return True if this path needs a heartbeat
+	 */
+	inline bool needsHeartbeat(const int64_t now) const
+	{
+		return ((now - _lastOut) >= ZT_PATH_HEARTBEAT_PERIOD);
+	}
+
+	/**
+	 * @return Last time we sent something
+	 */
+	inline int64_t lastOut() const
+	{
+		return _lastOut;
+	}
+
+	/**
+	 * @return Last time we received anything
+	 */
+	inline int64_t lastIn() const
+	{
+		return _lastIn;
+	}
+
+	/**
+	 * @return the age of the path in terms of receiving packets
+	 */
+	inline int64_t age(int64_t now)
+	{
+		return (now - _lastIn);
+	}
+
+	/**
+	 * @return Time last trust-established packet was received
+	 */
+	inline int64_t lastTrustEstablishedPacketReceived() const
+	{
+		return _lastTrustEstablishedPacketReceived;
+	}
+
+	/**
+	 * Rate limit gate for inbound ECHO requests
+	 */
+	inline bool rateGateEchoRequest(const int64_t now)
+	{
+		if ((now - _lastEchoRequestReceived) >= (ZT_PEER_GENERAL_RATE_LIMIT / 6)) {
+			_lastEchoRequestReceived = now;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * @return Mean latency as reported by the bonding layer
+	 */
+	inline float latencyMean() const
+	{
+		return _latencyMean;
+	}
+
+	/**
+	 * @return Latency variance as reported by the bonding layer
+	 */
+	inline float latencyVariance() const
+	{
+		return _latencyVariance;
+	}
+
+	/**
+	 * @return Packet Loss Ratio as reported by the bonding layer
+	 */
+	inline float packetLossRatio() const
+	{
+		return _packetLossRatio;
+	}
+
+	/**
+	 * @return Packet Error Ratio as reported by the bonding layer
+	 */
+	inline float packetErrorRatio() const
+	{
+		return _packetErrorRatio;
+	}
+
+	/**
+	 * @return Number of flows assigned to this path
+	 */
+	inline unsigned int assignedFlowCount() const
+	{
+		return _assignedFlowCount;
+	}
+
+	/**
+	 * @return Whether this path is valid as reported by the bonding layer. The bonding layer
+	 * actually checks with Phy to see if the interface is still up
+	 */
+	inline bool valid() const
+	{
+		return _valid;
+	}
+
+	/**
+	 * @return Whether this path is eligible for use in a bond as reported by the bonding layer
+	 */
+	inline bool eligible() const
+	{
+		return _eligible;
+	}
+
+	/**
+	 * @return Whether this path is bonded as reported by the bonding layer
+	 */
+	inline bool bonded() const
+	{
+		return _bonded;
+	}
+
+	/**
+	 * @return Whether the user-specified MTU for this path (determined by MTU for parent link)
+	 */
+	inline uint16_t mtu() const
+	{
+		return _mtu;
+	}
+
+	/**
+	 * @return Given link capacity as reported by the bonding layer
+	 */
+	inline uint32_t givenLinkSpeed() const
+	{
+		return _givenLinkSpeed;
+	}
+
+	/**
+	 * @return Path's quality as reported by the bonding layer
+	 */
+	inline float relativeQuality() const
+	{
+		return _relativeQuality;
+	}
+
+	/**
+	 * @return Physical interface name that this path lives on
+	 */
+	char* ifname()
+	{
+		return _ifname;
+	}
 
   private:
-    char _ifname[ZT_MAX_PHYSIFNAME] = {};
-
-    volatile int64_t _lastOut;
-    volatile int64_t _lastIn;
-    volatile int64_t _lastTrustEstablishedPacketReceived;
-
-    int64_t _lastEchoRequestReceived;
-
-    uint16_t _localPort;
-    int64_t _localSocket;
-
-    volatile float _latencyMean;
-    volatile float _latencyVariance;
-    volatile float _packetLossRatio;
-    volatile float _packetErrorRatio;
-    volatile uint16_t _assignedFlowCount;
-    volatile bool _valid;
-    volatile bool _eligible;
-    volatile bool _bonded;
-    volatile uint16_t _mtu;
-    volatile uint32_t _givenLinkSpeed;
-    volatile float _relativeQuality;
-
-    volatile unsigned int _latency;
-    InetAddress _addr;
-    InetAddress::IpScope _ipScope;   // memoize this since it's a computed value checked often
-    AtomicCounter __refCount;
+	char _ifname[ZT_MAX_PHYSIFNAME] = {};
+
+	volatile int64_t _lastOut;
+	volatile int64_t _lastIn;
+	volatile int64_t _lastTrustEstablishedPacketReceived;
+
+	int64_t _lastEchoRequestReceived;
+
+	uint16_t _localPort;
+	int64_t _localSocket;
+
+	volatile float _latencyMean;
+	volatile float _latencyVariance;
+	volatile float _packetLossRatio;
+	volatile float _packetErrorRatio;
+	volatile uint16_t _assignedFlowCount;
+	volatile bool _valid;
+	volatile bool _eligible;
+	volatile bool _bonded;
+	volatile uint16_t _mtu;
+	volatile uint32_t _givenLinkSpeed;
+	volatile float _relativeQuality;
+
+	volatile unsigned int _latency;
+	InetAddress _addr;
+	InetAddress::IpScope _ipScope;	 // memoize this since it's a computed value checked often
+	AtomicCounter __refCount;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 619 - 620
node/Peer.cpp

@@ -25,696 +25,695 @@
 #include "Switch.hpp"
 #include "Trace.hpp"
 #include "Utils.hpp"
-#include "Switch.hpp"
 
 namespace ZeroTier {
 
 static unsigned char s_freeRandomByteCounter = 0;
 
 Peer::Peer(const RuntimeEnvironment* renv, const Identity& myIdentity, const Identity& peerIdentity)
-    : RR(renv)
-    , _lastReceive(0)
-    , _lastNontrivialReceive(0)
-    , _lastTriedMemorizedPath(0)
-    , _lastDirectPathPushSent(0)
-    , _lastDirectPathPushReceive(0)
-    , _lastCredentialRequestSent(0)
-    , _lastWhoisRequestReceived(0)
-    , _lastCredentialsReceived(0)
-    , _lastTrustEstablishedPacketReceived(0)
-    , _lastSentFullHello(0)
-    , _lastEchoCheck(0)
-    , _freeRandomByte((unsigned char)((uintptr_t)this >> 4) ^ ++s_freeRandomByteCounter)
-    , _vProto(0)
-    , _vMajor(0)
-    , _vMinor(0)
-    , _vRevision(0)
-    , _id(peerIdentity)
-    , _directPathPushCutoffCount(0)
-    , _echoRequestCutoffCount(0)
-    , _localMultipathSupported(false)
-    , _lastComputedAggregateMeanLatency(0)
+	: RR(renv)
+	, _lastReceive(0)
+	, _lastNontrivialReceive(0)
+	, _lastTriedMemorizedPath(0)
+	, _lastDirectPathPushSent(0)
+	, _lastDirectPathPushReceive(0)
+	, _lastCredentialRequestSent(0)
+	, _lastWhoisRequestReceived(0)
+	, _lastCredentialsReceived(0)
+	, _lastTrustEstablishedPacketReceived(0)
+	, _lastSentFullHello(0)
+	, _lastEchoCheck(0)
+	, _freeRandomByte((unsigned char)((uintptr_t)this >> 4) ^ ++s_freeRandomByteCounter)
+	, _vProto(0)
+	, _vMajor(0)
+	, _vMinor(0)
+	, _vRevision(0)
+	, _id(peerIdentity)
+	, _directPathPushCutoffCount(0)
+	, _echoRequestCutoffCount(0)
+	, _localMultipathSupported(false)
+	, _lastComputedAggregateMeanLatency(0)
 #ifndef ZT_NO_PEER_METRICS
-    , _peer_latency { Metrics::peer_latency.Add({ { "node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt()) } }, std::vector<uint64_t> { 1, 3, 6, 10, 30, 60, 100, 300, 600, 1000 }) }
-    , _alive_path_count { Metrics::peer_path_count.Add({ { "node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt()) }, { "status", "alive" } }) }
-    , _dead_path_count { Metrics::peer_path_count.Add({ { "node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt()) }, { "status", "dead" } }) }
-    , _incoming_packet { Metrics::peer_packets.Add({ { "direction", "rx" }, { "node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt()) } }) }
-    , _outgoing_packet { Metrics::peer_packets.Add({ { "direction", "tx" }, { "node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt()) } }) }
-    , _packet_errors { Metrics::peer_packet_errors.Add({ { "node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt()) } }) }
+	, _peer_latency { Metrics::peer_latency.Add({ { "node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt()) } }, std::vector<uint64_t> { 1, 3, 6, 10, 30, 60, 100, 300, 600, 1000 }) }
+	, _alive_path_count { Metrics::peer_path_count.Add({ { "node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt()) }, { "status", "alive" } }) }
+	, _dead_path_count { Metrics::peer_path_count.Add({ { "node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt()) }, { "status", "dead" } }) }
+	, _incoming_packet { Metrics::peer_packets.Add({ { "direction", "rx" }, { "node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt()) } }) }
+	, _outgoing_packet { Metrics::peer_packets.Add({ { "direction", "tx" }, { "node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt()) } }) }
+	, _packet_errors { Metrics::peer_packet_errors.Add({ { "node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt()) } }) }
 #endif
 {
-    if (! myIdentity.agree(peerIdentity, _key)) {
-        throw ZT_EXCEPTION_INVALID_ARGUMENT;
-    }
-
-    uint8_t ktmp[ZT_SYMMETRIC_KEY_SIZE];
-    KBKDFHMACSHA384(_key, ZT_KBKDF_LABEL_AES_GMAC_SIV_K0, 0, 0, ktmp);
-    _aesKeys[0].init(ktmp);
-    KBKDFHMACSHA384(_key, ZT_KBKDF_LABEL_AES_GMAC_SIV_K1, 0, 0, ktmp);
-    _aesKeys[1].init(ktmp);
-    Utils::burn(ktmp, ZT_SYMMETRIC_KEY_SIZE);
+	if (! myIdentity.agree(peerIdentity, _key)) {
+		throw ZT_EXCEPTION_INVALID_ARGUMENT;
+	}
+
+	uint8_t ktmp[ZT_SYMMETRIC_KEY_SIZE];
+	KBKDFHMACSHA384(_key, ZT_KBKDF_LABEL_AES_GMAC_SIV_K0, 0, 0, ktmp);
+	_aesKeys[0].init(ktmp);
+	KBKDFHMACSHA384(_key, ZT_KBKDF_LABEL_AES_GMAC_SIV_K1, 0, 0, ktmp);
+	_aesKeys[1].init(ktmp);
+	Utils::burn(ktmp, ZT_SYMMETRIC_KEY_SIZE);
 }
 
 void Peer::received(
-    void* tPtr,
-    const SharedPtr<Path>& path,
-    const unsigned int hops,
-    const uint64_t packetId,
-    const unsigned int payloadLength,
-    const Packet::Verb verb,
-    const uint64_t inRePacketId,
-    const Packet::Verb inReVerb,
-    const bool trustEstablished,
-    const uint64_t networkId,
-    const int32_t flowId)
+	void* tPtr,
+	const SharedPtr<Path>& path,
+	const unsigned int hops,
+	const uint64_t packetId,
+	const unsigned int payloadLength,
+	const Packet::Verb verb,
+	const uint64_t inRePacketId,
+	const Packet::Verb inReVerb,
+	const bool trustEstablished,
+	const uint64_t networkId,
+	const int32_t flowId)
 {
-    const int64_t now = RR->node->now();
-
-    _lastReceive = now;
-    switch (verb) {
-        case Packet::VERB_FRAME:
-        case Packet::VERB_EXT_FRAME:
-        case Packet::VERB_NETWORK_CONFIG_REQUEST:
-        case Packet::VERB_NETWORK_CONFIG:
-        case Packet::VERB_MULTICAST_FRAME:
-            _lastNontrivialReceive = now;
-            break;
-        default:
-            break;
-    }
+	const int64_t now = RR->node->now();
+
+	_lastReceive = now;
+	switch (verb) {
+		case Packet::VERB_FRAME:
+		case Packet::VERB_EXT_FRAME:
+		case Packet::VERB_NETWORK_CONFIG_REQUEST:
+		case Packet::VERB_NETWORK_CONFIG:
+		case Packet::VERB_MULTICAST_FRAME:
+			_lastNontrivialReceive = now;
+			break;
+		default:
+			break;
+	}
 #ifndef ZT_NO_PEER_METRICS
-    _incoming_packet++;
+	_incoming_packet++;
 #endif
-    recordIncomingPacket(path, packetId, payloadLength, verb, flowId, now);
-
-    if (trustEstablished) {
-        _lastTrustEstablishedPacketReceived = now;
-        path->trustedPacketReceived(now);
-    }
-
-    if (hops == 0) {
-        // If this is a direct packet (no hops), update existing paths or learn new ones
-        bool havePath = false;
-        {
-            Mutex::Lock _l(_paths_m);
-            for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-                if (_paths[i].p) {
-                    if (_paths[i].p == path) {
-                        _paths[i].lr = now;
-                        havePath = true;
-                        break;
-                    }
-                    // If same address on same interface then don't learn unless existing path isn't alive (prevents learning loop)
-                    if (_paths[i].p->address().ipsEqual(path->address()) && _paths[i].p->localSocket() == path->localSocket()) {
-                        if (_paths[i].p->alive(now) && ! _bond) {
-                            havePath = true;
-                            break;
-                        }
-                    }
-                }
-                else {
-                    break;
-                }
-            }
-        }
-
-        if ((! havePath) && RR->node->shouldUsePathForZeroTierTraffic(tPtr, _id.address(), path->localSocket(), path->address())) {
-            if (verb == Packet::VERB_OK) {
-                Mutex::Lock _l(_paths_m);
-                unsigned int oldestPathIdx = ZT_MAX_PEER_NETWORK_PATHS;
-                unsigned int oldestPathAge = 0;
-                unsigned int replacePath = ZT_MAX_PEER_NETWORK_PATHS;
-
-                for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-                    if (_paths[i].p) {
-                        // Keep track of oldest path as a last resort option
-                        unsigned int currAge = _paths[i].p->age(now);
-                        if (currAge > oldestPathAge) {
-                            oldestPathAge = currAge;
-                            oldestPathIdx = i;
-                        }
-                        if (_paths[i].p->address().ipsEqual(path->address())) {
-                            if (_paths[i].p->localSocket() == path->localSocket()) {
-                                if (! _paths[i].p->alive(now)) {
-                                    replacePath = i;
-                                    break;
-                                }
-                            }
-                        }
-                    }
-                    else {
-                        replacePath = i;
-                        break;
-                    }
-                }
-
-                // If we didn't find a good candidate then resort to replacing oldest path
-                replacePath = (replacePath == ZT_MAX_PEER_NETWORK_PATHS) ? oldestPathIdx : replacePath;
-                if (replacePath != ZT_MAX_PEER_NETWORK_PATHS) {
-                    RR->t->peerLearnedNewPath(tPtr, networkId, *this, path, packetId);
-                    _paths[replacePath].lr = now;
-                    _paths[replacePath].p = path;
-                    _paths[replacePath].priority = 1;
-                    Mutex::Lock _l(_bond_m);
-                    if (_bond) {
-                        _bond->nominatePathToBond(_paths[replacePath].p, now);
-                    }
-                }
-            }
-            else {
-                Mutex::Lock ltl(_lastTriedPath_m);
-
-                bool triedTooRecently = false;
-                for (std::list<std::pair<Path*, int64_t> >::iterator i(_lastTriedPath.begin()); i != _lastTriedPath.end();) {
-                    if ((now - i->second) > 1000) {
-                        _lastTriedPath.erase(i++);
-                    }
-                    else if (i->first == path.ptr()) {
-                        ++i;
-                        triedTooRecently = true;
-                    }
-                    else {
-                        ++i;
-                    }
-                }
-
-                if (! triedTooRecently) {
-                    _lastTriedPath.push_back(std::pair<Path*, int64_t>(path.ptr(), now));
-                    attemptToContactAt(tPtr, path->localSocket(), path->address(), now, true);
-                    path->sent(now);
-                    RR->t->peerConfirmingUnknownPath(tPtr, networkId, *this, path, packetId, verb);
-                }
-            }
-        }
-    }
-
-    // If we have a trust relationship periodically push a message enumerating
-    // all known external addresses for ourselves. If we already have a path this
-    // is done less frequently.
-    if (this->trustEstablished(now)) {
-        const int64_t sinceLastPush = now - _lastDirectPathPushSent;
-        bool lowBandwidth = RR->node->lowBandwidthModeEnabled();
-        int timerScale = lowBandwidth ? 16 : 1;
-        if (sinceLastPush >= ((hops == 0) ? ZT_DIRECT_PATH_PUSH_INTERVAL_HAVEPATH * timerScale : ZT_DIRECT_PATH_PUSH_INTERVAL)) {
-            _lastDirectPathPushSent = now;
-            std::vector<InetAddress> pathsToPush(RR->node->directPaths());
-            std::vector<InetAddress> ma = RR->sa->whoami();
-            pathsToPush.insert(pathsToPush.end(), ma.begin(), ma.end());
-            if (! pathsToPush.empty()) {
-                std::vector<InetAddress>::const_iterator p(pathsToPush.begin());
-                while (p != pathsToPush.end()) {
-                    Packet* const outp = new Packet(_id.address(), RR->identity.address(), Packet::VERB_PUSH_DIRECT_PATHS);
-                    outp->addSize(2);   // leave room for count
-                    unsigned int count = 0;
-                    while ((p != pathsToPush.end()) && ((outp->size() + 24) < 1200)) {
-                        uint8_t addressType = 4;
-                        switch (p->ss_family) {
-                            case AF_INET:
-                                break;
-                            case AF_INET6:
-                                addressType = 6;
-                                break;
-                            default:   // we currently only push IP addresses
-                                ++p;
-                                continue;
-                        }
-
-                        outp->append((uint8_t)0);    // no flags
-                        outp->append((uint16_t)0);   // no extensions
-                        outp->append(addressType);
-                        outp->append((uint8_t)((addressType == 4) ? 6 : 18));
-                        outp->append(p->rawIpData(), ((addressType == 4) ? 4 : 16));
-                        outp->append((uint16_t)p->port());
-
-                        ++count;
-                        ++p;
-                    }
-                    if (count) {
-                        Metrics::pkt_push_direct_paths_out++;
-                        outp->setAt(ZT_PACKET_IDX_PAYLOAD, (uint16_t)count);
-                        outp->compress();
-                        outp->armor(_key, true, false, aesKeysIfSupported(), _id);
-                        Metrics::pkt_push_direct_paths_out++;
-                        path->send(RR, tPtr, outp->data(), outp->size(), now);
-                    }
-                    delete outp;
-                }
-            }
-        }
-    }
+	recordIncomingPacket(path, packetId, payloadLength, verb, flowId, now);
+
+	if (trustEstablished) {
+		_lastTrustEstablishedPacketReceived = now;
+		path->trustedPacketReceived(now);
+	}
+
+	if (hops == 0) {
+		// If this is a direct packet (no hops), update existing paths or learn new ones
+		bool havePath = false;
+		{
+			Mutex::Lock _l(_paths_m);
+			for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+				if (_paths[i].p) {
+					if (_paths[i].p == path) {
+						_paths[i].lr = now;
+						havePath = true;
+						break;
+					}
+					// If same address on same interface then don't learn unless existing path isn't alive (prevents learning loop)
+					if (_paths[i].p->address().ipsEqual(path->address()) && _paths[i].p->localSocket() == path->localSocket()) {
+						if (_paths[i].p->alive(now) && ! _bond) {
+							havePath = true;
+							break;
+						}
+					}
+				}
+				else {
+					break;
+				}
+			}
+		}
+
+		if ((! havePath) && RR->node->shouldUsePathForZeroTierTraffic(tPtr, _id.address(), path->localSocket(), path->address())) {
+			if (verb == Packet::VERB_OK) {
+				Mutex::Lock _l(_paths_m);
+				unsigned int oldestPathIdx = ZT_MAX_PEER_NETWORK_PATHS;
+				unsigned int oldestPathAge = 0;
+				unsigned int replacePath = ZT_MAX_PEER_NETWORK_PATHS;
+
+				for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+					if (_paths[i].p) {
+						// Keep track of oldest path as a last resort option
+						unsigned int currAge = _paths[i].p->age(now);
+						if (currAge > oldestPathAge) {
+							oldestPathAge = currAge;
+							oldestPathIdx = i;
+						}
+						if (_paths[i].p->address().ipsEqual(path->address())) {
+							if (_paths[i].p->localSocket() == path->localSocket()) {
+								if (! _paths[i].p->alive(now)) {
+									replacePath = i;
+									break;
+								}
+							}
+						}
+					}
+					else {
+						replacePath = i;
+						break;
+					}
+				}
+
+				// If we didn't find a good candidate then resort to replacing oldest path
+				replacePath = (replacePath == ZT_MAX_PEER_NETWORK_PATHS) ? oldestPathIdx : replacePath;
+				if (replacePath != ZT_MAX_PEER_NETWORK_PATHS) {
+					RR->t->peerLearnedNewPath(tPtr, networkId, *this, path, packetId);
+					_paths[replacePath].lr = now;
+					_paths[replacePath].p = path;
+					_paths[replacePath].priority = 1;
+					Mutex::Lock _l(_bond_m);
+					if (_bond) {
+						_bond->nominatePathToBond(_paths[replacePath].p, now);
+					}
+				}
+			}
+			else {
+				Mutex::Lock ltl(_lastTriedPath_m);
+
+				bool triedTooRecently = false;
+				for (std::list<std::pair<Path*, int64_t> >::iterator i(_lastTriedPath.begin()); i != _lastTriedPath.end();) {
+					if ((now - i->second) > 1000) {
+						_lastTriedPath.erase(i++);
+					}
+					else if (i->first == path.ptr()) {
+						++i;
+						triedTooRecently = true;
+					}
+					else {
+						++i;
+					}
+				}
+
+				if (! triedTooRecently) {
+					_lastTriedPath.push_back(std::pair<Path*, int64_t>(path.ptr(), now));
+					attemptToContactAt(tPtr, path->localSocket(), path->address(), now, true);
+					path->sent(now);
+					RR->t->peerConfirmingUnknownPath(tPtr, networkId, *this, path, packetId, verb);
+				}
+			}
+		}
+	}
+
+	// If we have a trust relationship periodically push a message enumerating
+	// all known external addresses for ourselves. If we already have a path this
+	// is done less frequently.
+	if (this->trustEstablished(now)) {
+		const int64_t sinceLastPush = now - _lastDirectPathPushSent;
+		bool lowBandwidth = RR->node->lowBandwidthModeEnabled();
+		int timerScale = lowBandwidth ? 16 : 1;
+		if (sinceLastPush >= ((hops == 0) ? ZT_DIRECT_PATH_PUSH_INTERVAL_HAVEPATH * timerScale : ZT_DIRECT_PATH_PUSH_INTERVAL)) {
+			_lastDirectPathPushSent = now;
+			std::vector<InetAddress> pathsToPush(RR->node->directPaths());
+			std::vector<InetAddress> ma = RR->sa->whoami();
+			pathsToPush.insert(pathsToPush.end(), ma.begin(), ma.end());
+			if (! pathsToPush.empty()) {
+				std::vector<InetAddress>::const_iterator p(pathsToPush.begin());
+				while (p != pathsToPush.end()) {
+					Packet* const outp = new Packet(_id.address(), RR->identity.address(), Packet::VERB_PUSH_DIRECT_PATHS);
+					outp->addSize(2);	// leave room for count
+					unsigned int count = 0;
+					while ((p != pathsToPush.end()) && ((outp->size() + 24) < 1200)) {
+						uint8_t addressType = 4;
+						switch (p->ss_family) {
+							case AF_INET:
+								break;
+							case AF_INET6:
+								addressType = 6;
+								break;
+							default:   // we currently only push IP addresses
+								++p;
+								continue;
+						}
+
+						outp->append((uint8_t)0);	 // no flags
+						outp->append((uint16_t)0);	 // no extensions
+						outp->append(addressType);
+						outp->append((uint8_t)((addressType == 4) ? 6 : 18));
+						outp->append(p->rawIpData(), ((addressType == 4) ? 4 : 16));
+						outp->append((uint16_t)p->port());
+
+						++count;
+						++p;
+					}
+					if (count) {
+						Metrics::pkt_push_direct_paths_out++;
+						outp->setAt(ZT_PACKET_IDX_PAYLOAD, (uint16_t)count);
+						outp->compress();
+						outp->armor(_key, true, false, aesKeysIfSupported(), _id);
+						Metrics::pkt_push_direct_paths_out++;
+						path->send(RR, tPtr, outp->data(), outp->size(), now);
+					}
+					delete outp;
+				}
+			}
+		}
+	}
 }
 
 SharedPtr<Path> Peer::getAppropriatePath(int64_t now, bool includeExpired, int32_t flowId)
 {
-    Mutex::Lock _l(_paths_m);
-    Mutex::Lock _lb(_bond_m);
-    if (_bond && _bond->isReady()) {
-        return _bond->getAppropriatePath(now, flowId);
-    }
-    unsigned int bestPath = ZT_MAX_PEER_NETWORK_PATHS;
-    /**
-     * Send traffic across the highest quality path only. This algorithm will still
-     * use the old path quality metric from protocol version 9.
-     */
-    long bestPathQuality = 2147483647;
-    for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-        if (_paths[i].p) {
-            if ((includeExpired) || ((now - _paths[i].lr) < ZT_PEER_PATH_EXPIRATION)) {
-                const long q = _paths[i].p->quality(now) / _paths[i].priority;
-                if (q <= bestPathQuality) {
-                    bestPathQuality = q;
-                    bestPath = i;
-                }
-            }
-        }
-        else {
-            break;
-        }
-    }
-    if (bestPath != ZT_MAX_PEER_NETWORK_PATHS) {
-        return _paths[bestPath].p;
-    }
-    return SharedPtr<Path>();
+	Mutex::Lock _l(_paths_m);
+	Mutex::Lock _lb(_bond_m);
+	if (_bond && _bond->isReady()) {
+		return _bond->getAppropriatePath(now, flowId);
+	}
+	unsigned int bestPath = ZT_MAX_PEER_NETWORK_PATHS;
+	/**
+	 * Send traffic across the highest quality path only. This algorithm will still
+	 * use the old path quality metric from protocol version 9.
+	 */
+	long bestPathQuality = 2147483647;
+	for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+		if (_paths[i].p) {
+			if ((includeExpired) || ((now - _paths[i].lr) < ZT_PEER_PATH_EXPIRATION)) {
+				const long q = _paths[i].p->quality(now) / _paths[i].priority;
+				if (q <= bestPathQuality) {
+					bestPathQuality = q;
+					bestPath = i;
+				}
+			}
+		}
+		else {
+			break;
+		}
+	}
+	if (bestPath != ZT_MAX_PEER_NETWORK_PATHS) {
+		return _paths[bestPath].p;
+	}
+	return SharedPtr<Path>();
 }
 
 void Peer::introduce(void* const tPtr, const int64_t now, const SharedPtr<Peer>& other) const
 {
-    unsigned int myBestV4ByScope[ZT_INETADDRESS_MAX_SCOPE + 1];
-    unsigned int myBestV6ByScope[ZT_INETADDRESS_MAX_SCOPE + 1];
-    long myBestV4QualityByScope[ZT_INETADDRESS_MAX_SCOPE + 1];
-    long myBestV6QualityByScope[ZT_INETADDRESS_MAX_SCOPE + 1];
-    unsigned int theirBestV4ByScope[ZT_INETADDRESS_MAX_SCOPE + 1];
-    unsigned int theirBestV6ByScope[ZT_INETADDRESS_MAX_SCOPE + 1];
-    long theirBestV4QualityByScope[ZT_INETADDRESS_MAX_SCOPE + 1];
-    long theirBestV6QualityByScope[ZT_INETADDRESS_MAX_SCOPE + 1];
-    for (int i = 0; i <= ZT_INETADDRESS_MAX_SCOPE; ++i) {
-        myBestV4ByScope[i] = ZT_MAX_PEER_NETWORK_PATHS;
-        myBestV6ByScope[i] = ZT_MAX_PEER_NETWORK_PATHS;
-        myBestV4QualityByScope[i] = 2147483647;
-        myBestV6QualityByScope[i] = 2147483647;
-        theirBestV4ByScope[i] = ZT_MAX_PEER_NETWORK_PATHS;
-        theirBestV6ByScope[i] = ZT_MAX_PEER_NETWORK_PATHS;
-        theirBestV4QualityByScope[i] = 2147483647;
-        theirBestV6QualityByScope[i] = 2147483647;
-    }
-
-    Mutex::Lock _l1(_paths_m);
-
-    for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-        if (_paths[i].p) {
-            const long q = _paths[i].p->quality(now) / _paths[i].priority;
-            const unsigned int s = (unsigned int)_paths[i].p->ipScope();
-            switch (_paths[i].p->address().ss_family) {
-                case AF_INET:
-                    if (q <= myBestV4QualityByScope[s]) {
-                        myBestV4QualityByScope[s] = q;
-                        myBestV4ByScope[s] = i;
-                    }
-                    break;
-                case AF_INET6:
-                    if (q <= myBestV6QualityByScope[s]) {
-                        myBestV6QualityByScope[s] = q;
-                        myBestV6ByScope[s] = i;
-                    }
-                    break;
-            }
-        }
-        else {
-            break;
-        }
-    }
-
-    Mutex::Lock _l2(other->_paths_m);
-
-    for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-        if (other->_paths[i].p) {
-            const long q = other->_paths[i].p->quality(now) / other->_paths[i].priority;
-            const unsigned int s = (unsigned int)other->_paths[i].p->ipScope();
-            switch (other->_paths[i].p->address().ss_family) {
-                case AF_INET:
-                    if (q <= theirBestV4QualityByScope[s]) {
-                        theirBestV4QualityByScope[s] = q;
-                        theirBestV4ByScope[s] = i;
-                    }
-                    break;
-                case AF_INET6:
-                    if (q <= theirBestV6QualityByScope[s]) {
-                        theirBestV6QualityByScope[s] = q;
-                        theirBestV6ByScope[s] = i;
-                    }
-                    break;
-            }
-        }
-        else {
-            break;
-        }
-    }
-
-    unsigned int mine = ZT_MAX_PEER_NETWORK_PATHS;
-    unsigned int theirs = ZT_MAX_PEER_NETWORK_PATHS;
-
-    for (int s = ZT_INETADDRESS_MAX_SCOPE; s >= 0; --s) {
-        if ((myBestV6ByScope[s] != ZT_MAX_PEER_NETWORK_PATHS) && (theirBestV6ByScope[s] != ZT_MAX_PEER_NETWORK_PATHS)) {
-            mine = myBestV6ByScope[s];
-            theirs = theirBestV6ByScope[s];
-            break;
-        }
-        if ((myBestV4ByScope[s] != ZT_MAX_PEER_NETWORK_PATHS) && (theirBestV4ByScope[s] != ZT_MAX_PEER_NETWORK_PATHS)) {
-            mine = myBestV4ByScope[s];
-            theirs = theirBestV4ByScope[s];
-            break;
-        }
-    }
-
-    if (mine != ZT_MAX_PEER_NETWORK_PATHS) {
-        unsigned int alt = (unsigned int)RR->node->prng() & 1;   // randomize which hint we send first for black magickal NAT-t reasons
-        const unsigned int completed = alt + 2;
-        while (alt != completed) {
-            if ((alt & 1) == 0) {
-                Packet outp(_id.address(), RR->identity.address(), Packet::VERB_RENDEZVOUS);
-                outp.append((uint8_t)0);
-                other->_id.address().appendTo(outp);
-                outp.append((uint16_t)other->_paths[theirs].p->address().port());
-                if (other->_paths[theirs].p->address().ss_family == AF_INET6) {
-                    outp.append((uint8_t)16);
-                    outp.append(other->_paths[theirs].p->address().rawIpData(), 16);
-                }
-                else {
-                    outp.append((uint8_t)4);
-                    outp.append(other->_paths[theirs].p->address().rawIpData(), 4);
-                }
-                outp.armor(_key, true, false, aesKeysIfSupported(), _id);
-                Metrics::pkt_rendezvous_out++;
-                _paths[mine].p->send(RR, tPtr, outp.data(), outp.size(), now);
-            }
-            else {
-                Packet outp(other->_id.address(), RR->identity.address(), Packet::VERB_RENDEZVOUS);
-                outp.append((uint8_t)0);
-                _id.address().appendTo(outp);
-                outp.append((uint16_t)_paths[mine].p->address().port());
-                if (_paths[mine].p->address().ss_family == AF_INET6) {
-                    outp.append((uint8_t)16);
-                    outp.append(_paths[mine].p->address().rawIpData(), 16);
-                }
-                else {
-                    outp.append((uint8_t)4);
-                    outp.append(_paths[mine].p->address().rawIpData(), 4);
-                }
-                outp.armor(other->_key, true, false, other->aesKeysIfSupported(), other->identity());
-                Metrics::pkt_rendezvous_out++;
-                other->_paths[theirs].p->send(RR, tPtr, outp.data(), outp.size(), now);
-            }
-            ++alt;
-        }
-    }
+	unsigned int myBestV4ByScope[ZT_INETADDRESS_MAX_SCOPE + 1];
+	unsigned int myBestV6ByScope[ZT_INETADDRESS_MAX_SCOPE + 1];
+	long myBestV4QualityByScope[ZT_INETADDRESS_MAX_SCOPE + 1];
+	long myBestV6QualityByScope[ZT_INETADDRESS_MAX_SCOPE + 1];
+	unsigned int theirBestV4ByScope[ZT_INETADDRESS_MAX_SCOPE + 1];
+	unsigned int theirBestV6ByScope[ZT_INETADDRESS_MAX_SCOPE + 1];
+	long theirBestV4QualityByScope[ZT_INETADDRESS_MAX_SCOPE + 1];
+	long theirBestV6QualityByScope[ZT_INETADDRESS_MAX_SCOPE + 1];
+	for (int i = 0; i <= ZT_INETADDRESS_MAX_SCOPE; ++i) {
+		myBestV4ByScope[i] = ZT_MAX_PEER_NETWORK_PATHS;
+		myBestV6ByScope[i] = ZT_MAX_PEER_NETWORK_PATHS;
+		myBestV4QualityByScope[i] = 2147483647;
+		myBestV6QualityByScope[i] = 2147483647;
+		theirBestV4ByScope[i] = ZT_MAX_PEER_NETWORK_PATHS;
+		theirBestV6ByScope[i] = ZT_MAX_PEER_NETWORK_PATHS;
+		theirBestV4QualityByScope[i] = 2147483647;
+		theirBestV6QualityByScope[i] = 2147483647;
+	}
+
+	Mutex::Lock _l1(_paths_m);
+
+	for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+		if (_paths[i].p) {
+			const long q = _paths[i].p->quality(now) / _paths[i].priority;
+			const unsigned int s = (unsigned int)_paths[i].p->ipScope();
+			switch (_paths[i].p->address().ss_family) {
+				case AF_INET:
+					if (q <= myBestV4QualityByScope[s]) {
+						myBestV4QualityByScope[s] = q;
+						myBestV4ByScope[s] = i;
+					}
+					break;
+				case AF_INET6:
+					if (q <= myBestV6QualityByScope[s]) {
+						myBestV6QualityByScope[s] = q;
+						myBestV6ByScope[s] = i;
+					}
+					break;
+			}
+		}
+		else {
+			break;
+		}
+	}
+
+	Mutex::Lock _l2(other->_paths_m);
+
+	for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+		if (other->_paths[i].p) {
+			const long q = other->_paths[i].p->quality(now) / other->_paths[i].priority;
+			const unsigned int s = (unsigned int)other->_paths[i].p->ipScope();
+			switch (other->_paths[i].p->address().ss_family) {
+				case AF_INET:
+					if (q <= theirBestV4QualityByScope[s]) {
+						theirBestV4QualityByScope[s] = q;
+						theirBestV4ByScope[s] = i;
+					}
+					break;
+				case AF_INET6:
+					if (q <= theirBestV6QualityByScope[s]) {
+						theirBestV6QualityByScope[s] = q;
+						theirBestV6ByScope[s] = i;
+					}
+					break;
+			}
+		}
+		else {
+			break;
+		}
+	}
+
+	unsigned int mine = ZT_MAX_PEER_NETWORK_PATHS;
+	unsigned int theirs = ZT_MAX_PEER_NETWORK_PATHS;
+
+	for (int s = ZT_INETADDRESS_MAX_SCOPE; s >= 0; --s) {
+		if ((myBestV6ByScope[s] != ZT_MAX_PEER_NETWORK_PATHS) && (theirBestV6ByScope[s] != ZT_MAX_PEER_NETWORK_PATHS)) {
+			mine = myBestV6ByScope[s];
+			theirs = theirBestV6ByScope[s];
+			break;
+		}
+		if ((myBestV4ByScope[s] != ZT_MAX_PEER_NETWORK_PATHS) && (theirBestV4ByScope[s] != ZT_MAX_PEER_NETWORK_PATHS)) {
+			mine = myBestV4ByScope[s];
+			theirs = theirBestV4ByScope[s];
+			break;
+		}
+	}
+
+	if (mine != ZT_MAX_PEER_NETWORK_PATHS) {
+		unsigned int alt = (unsigned int)RR->node->prng() & 1;	 // randomize which hint we send first for black magickal NAT-t reasons
+		const unsigned int completed = alt + 2;
+		while (alt != completed) {
+			if ((alt & 1) == 0) {
+				Packet outp(_id.address(), RR->identity.address(), Packet::VERB_RENDEZVOUS);
+				outp.append((uint8_t)0);
+				other->_id.address().appendTo(outp);
+				outp.append((uint16_t)other->_paths[theirs].p->address().port());
+				if (other->_paths[theirs].p->address().ss_family == AF_INET6) {
+					outp.append((uint8_t)16);
+					outp.append(other->_paths[theirs].p->address().rawIpData(), 16);
+				}
+				else {
+					outp.append((uint8_t)4);
+					outp.append(other->_paths[theirs].p->address().rawIpData(), 4);
+				}
+				outp.armor(_key, true, false, aesKeysIfSupported(), _id);
+				Metrics::pkt_rendezvous_out++;
+				_paths[mine].p->send(RR, tPtr, outp.data(), outp.size(), now);
+			}
+			else {
+				Packet outp(other->_id.address(), RR->identity.address(), Packet::VERB_RENDEZVOUS);
+				outp.append((uint8_t)0);
+				_id.address().appendTo(outp);
+				outp.append((uint16_t)_paths[mine].p->address().port());
+				if (_paths[mine].p->address().ss_family == AF_INET6) {
+					outp.append((uint8_t)16);
+					outp.append(_paths[mine].p->address().rawIpData(), 16);
+				}
+				else {
+					outp.append((uint8_t)4);
+					outp.append(_paths[mine].p->address().rawIpData(), 4);
+				}
+				outp.armor(other->_key, true, false, other->aesKeysIfSupported(), other->identity());
+				Metrics::pkt_rendezvous_out++;
+				other->_paths[theirs].p->send(RR, tPtr, outp.data(), outp.size(), now);
+			}
+			++alt;
+		}
+	}
 }
 
 void Peer::sendHELLO(void* tPtr, const int64_t localSocket, const InetAddress& atAddress, int64_t now)
 {
-    Packet outp(_id.address(), RR->identity.address(), Packet::VERB_HELLO);
-
-    outp.append((unsigned char)ZT_PROTO_VERSION);
-    outp.append((unsigned char)ZEROTIER_ONE_VERSION_MAJOR);
-    outp.append((unsigned char)ZEROTIER_ONE_VERSION_MINOR);
-    outp.append((uint16_t)ZEROTIER_ONE_VERSION_REVISION);
-    outp.append(now);
-    RR->identity.serialize(outp, false);
-    atAddress.serialize(outp);
-
-    outp.append((uint64_t)RR->topology->planetWorldId());
-    outp.append((uint64_t)RR->topology->planetWorldTimestamp());
-
-    const unsigned int startCryptedPortionAt = outp.size();
-
-    std::vector<World> moons(RR->topology->moons());
-    std::vector<uint64_t> moonsWanted(RR->topology->moonsWanted());
-    outp.append((uint16_t)(moons.size() + moonsWanted.size()));
-    for (std::vector<World>::const_iterator m(moons.begin()); m != moons.end(); ++m) {
-        outp.append((uint8_t)m->type());
-        outp.append((uint64_t)m->id());
-        outp.append((uint64_t)m->timestamp());
-    }
-    for (std::vector<uint64_t>::const_iterator m(moonsWanted.begin()); m != moonsWanted.end(); ++m) {
-        outp.append((uint8_t)World::TYPE_MOON);
-        outp.append(*m);
-        outp.append((uint64_t)0);
-    }
-
-    outp.cryptField(_key, startCryptedPortionAt, outp.size() - startCryptedPortionAt);
-
-    Metrics::pkt_hello_out++;
-
-    if (atAddress) {
-        outp.armor(_key, false, true, nullptr, _id);
-        RR->node->expectReplyTo(outp.packetId());
-        RR->node->putPacket(tPtr, RR->node->lowBandwidthModeEnabled() ? localSocket : -1, atAddress, outp.data(), outp.size());
-    }
-    else {
-        RR->node->expectReplyTo(outp.packetId());
-        RR->sw->send(tPtr, outp, true);
-    }
+	Packet outp(_id.address(), RR->identity.address(), Packet::VERB_HELLO);
+
+	outp.append((unsigned char)ZT_PROTO_VERSION);
+	outp.append((unsigned char)ZEROTIER_ONE_VERSION_MAJOR);
+	outp.append((unsigned char)ZEROTIER_ONE_VERSION_MINOR);
+	outp.append((uint16_t)ZEROTIER_ONE_VERSION_REVISION);
+	outp.append(now);
+	RR->identity.serialize(outp, false);
+	atAddress.serialize(outp);
+
+	outp.append((uint64_t)RR->topology->planetWorldId());
+	outp.append((uint64_t)RR->topology->planetWorldTimestamp());
+
+	const unsigned int startCryptedPortionAt = outp.size();
+
+	std::vector<World> moons(RR->topology->moons());
+	std::vector<uint64_t> moonsWanted(RR->topology->moonsWanted());
+	outp.append((uint16_t)(moons.size() + moonsWanted.size()));
+	for (std::vector<World>::const_iterator m(moons.begin()); m != moons.end(); ++m) {
+		outp.append((uint8_t)m->type());
+		outp.append((uint64_t)m->id());
+		outp.append((uint64_t)m->timestamp());
+	}
+	for (std::vector<uint64_t>::const_iterator m(moonsWanted.begin()); m != moonsWanted.end(); ++m) {
+		outp.append((uint8_t)World::TYPE_MOON);
+		outp.append(*m);
+		outp.append((uint64_t)0);
+	}
+
+	outp.cryptField(_key, startCryptedPortionAt, outp.size() - startCryptedPortionAt);
+
+	Metrics::pkt_hello_out++;
+
+	if (atAddress) {
+		outp.armor(_key, false, true, nullptr, _id);
+		RR->node->expectReplyTo(outp.packetId());
+		RR->node->putPacket(tPtr, RR->node->lowBandwidthModeEnabled() ? localSocket : -1, atAddress, outp.data(), outp.size());
+	}
+	else {
+		RR->node->expectReplyTo(outp.packetId());
+		RR->sw->send(tPtr, outp, true);
+	}
 }
 
 void Peer::attemptToContactAt(void* tPtr, const int64_t localSocket, const InetAddress& atAddress, int64_t now, bool sendFullHello)
 {
-    if ((! sendFullHello) && (_vProto >= 5) && (! ((_vMajor == 1) && (_vMinor == 1) && (_vRevision == 0)))) {
-        Packet outp(_id.address(), RR->identity.address(), Packet::VERB_ECHO);
-        outp.armor(_key, true, false, aesKeysIfSupported(), _id);
-        Metrics::pkt_echo_out++;
-        RR->node->expectReplyTo(outp.packetId());
-        RR->node->putPacket(tPtr, localSocket, atAddress, outp.data(), outp.size());
-    }
-    else {
-        sendHELLO(tPtr, localSocket, atAddress, now);
-    }
+	if ((! sendFullHello) && (_vProto >= 5) && (! ((_vMajor == 1) && (_vMinor == 1) && (_vRevision == 0)))) {
+		Packet outp(_id.address(), RR->identity.address(), Packet::VERB_ECHO);
+		outp.armor(_key, true, false, aesKeysIfSupported(), _id);
+		Metrics::pkt_echo_out++;
+		RR->node->expectReplyTo(outp.packetId());
+		RR->node->putPacket(tPtr, localSocket, atAddress, outp.data(), outp.size());
+	}
+	else {
+		sendHELLO(tPtr, localSocket, atAddress, now);
+	}
 }
 
 void Peer::tryMemorizedPath(void* tPtr, int64_t now)
 {
-    if ((now - _lastTriedMemorizedPath) >= ZT_TRY_MEMORIZED_PATH_INTERVAL) {
-        _lastTriedMemorizedPath = now;
-        InetAddress mp;
-        if (RR->node->externalPathLookup(tPtr, _id.address(), -1, mp)) {
-            attemptToContactAt(tPtr, -1, mp, now, true);
-        }
-    }
+	if ((now - _lastTriedMemorizedPath) >= ZT_TRY_MEMORIZED_PATH_INTERVAL) {
+		_lastTriedMemorizedPath = now;
+		InetAddress mp;
+		if (RR->node->externalPathLookup(tPtr, _id.address(), -1, mp)) {
+			attemptToContactAt(tPtr, -1, mp, now, true);
+		}
+	}
 }
 
 void Peer::performMultipathStateCheck(void* tPtr, int64_t now)
 {
-    Mutex::Lock _l(_bond_m);
-    /**
-     * Check for conditions required for multipath bonding and create a bond
-     * if allowed.
-     */
-    int numAlivePaths = 0;
-    bool atLeastOneNonExpired = false;
-    for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-        if (_paths[i].p) {
-            if (_paths[i].p->alive(now)) {
-                numAlivePaths++;
-            }
-            if ((now - _paths[i].lr) < ZT_PEER_PATH_EXPIRATION) {
-                atLeastOneNonExpired = true;
-            }
-        }
-    }
-    if (_bond) {
-        if (numAlivePaths == 0 && ! atLeastOneNonExpired) {
-            _bond = SharedPtr<Bond>();
-            RR->bc->destroyBond(_id.address().toInt());
-        }
-        return;
-    }
-    _localMultipathSupported = ((numAlivePaths >= 1) && (RR->bc->inUse()) && (ZT_PROTO_VERSION > 9));
-    if (_localMultipathSupported && ! _bond) {
-        if (RR->bc) {
-            _bond = RR->bc->createBond(RR, this);
-            /**
-             * Allow new bond to retroactively learn all paths known to this peer
-             */
-            if (_bond) {
-                for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-                    if (_paths[i].p) {
-                        _bond->nominatePathToBond(_paths[i].p, now);
-                    }
-                }
-            }
-        }
-    }
+	Mutex::Lock _l(_bond_m);
+	/**
+	 * Check for conditions required for multipath bonding and create a bond
+	 * if allowed.
+	 */
+	int numAlivePaths = 0;
+	bool atLeastOneNonExpired = false;
+	for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+		if (_paths[i].p) {
+			if (_paths[i].p->alive(now)) {
+				numAlivePaths++;
+			}
+			if ((now - _paths[i].lr) < ZT_PEER_PATH_EXPIRATION) {
+				atLeastOneNonExpired = true;
+			}
+		}
+	}
+	if (_bond) {
+		if (numAlivePaths == 0 && ! atLeastOneNonExpired) {
+			_bond = SharedPtr<Bond>();
+			RR->bc->destroyBond(_id.address().toInt());
+		}
+		return;
+	}
+	_localMultipathSupported = ((numAlivePaths >= 1) && (RR->bc->inUse()) && (ZT_PROTO_VERSION > 9));
+	if (_localMultipathSupported && ! _bond) {
+		if (RR->bc) {
+			_bond = RR->bc->createBond(RR, this);
+			/**
+			 * Allow new bond to retroactively learn all paths known to this peer
+			 */
+			if (_bond) {
+				for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+					if (_paths[i].p) {
+						_bond->nominatePathToBond(_paths[i].p, now);
+					}
+				}
+			}
+		}
+	}
 }
 
 unsigned int Peer::doPingAndKeepalive(void* tPtr, int64_t now)
 {
-    unsigned int sent = 0;
-    {
-        Mutex::Lock _l(_paths_m);
-
-        performMultipathStateCheck(tPtr, now);
-
-        const bool sendFullHello = ((now - _lastSentFullHello) >= ZT_PEER_PING_PERIOD);
-        if (sendFullHello) {
-            _lastSentFullHello = now;
-        }
-
-        // Right now we only keep pinging links that have the maximum priority. The
-        // priority is used to track cluster redirections, meaning that when a cluster
-        // redirects us its redirect target links override all other links and we
-        // let those old links expire.
-        long maxPriority = 0;
-        for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-            if (_paths[i].p) {
-                maxPriority = std::max(_paths[i].priority, maxPriority);
-            }
-            else {
-                break;
-            }
-        }
-
-        bool deletionOccurred = false;
-        for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-            if (_paths[i].p) {
-                // Clean expired and reduced priority paths
-                if (((now - _paths[i].lr) < ZT_PEER_PATH_EXPIRATION) && (_paths[i].priority == maxPriority)) {
-                    if ((sendFullHello) || (_paths[i].p->needsHeartbeat(now))) {
-                        attemptToContactAt(tPtr, _paths[i].p->localSocket(), _paths[i].p->address(), now, sendFullHello);
-                        _paths[i].p->sent(now);
-                        sent |= (_paths[i].p->address().ss_family == AF_INET) ? 0x1 : 0x2;
-                    }
-                }
-                else {
-                    _paths[i] = _PeerPath();
-                    deletionOccurred = true;
-                }
-            }
-            if (! _paths[i].p || deletionOccurred) {
-                for (unsigned int j = i; j < ZT_MAX_PEER_NETWORK_PATHS; ++j) {
-                    if (_paths[j].p && i != j) {
-                        _paths[i] = _paths[j];
-                        _paths[j] = _PeerPath();
-                        break;
-                    }
-                }
-                deletionOccurred = false;
-            }
-        }
+	unsigned int sent = 0;
+	{
+		Mutex::Lock _l(_paths_m);
+
+		performMultipathStateCheck(tPtr, now);
+
+		const bool sendFullHello = ((now - _lastSentFullHello) >= ZT_PEER_PING_PERIOD);
+		if (sendFullHello) {
+			_lastSentFullHello = now;
+		}
+
+		// Right now we only keep pinging links that have the maximum priority. The
+		// priority is used to track cluster redirections, meaning that when a cluster
+		// redirects us its redirect target links override all other links and we
+		// let those old links expire.
+		long maxPriority = 0;
+		for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+			if (_paths[i].p) {
+				maxPriority = std::max(_paths[i].priority, maxPriority);
+			}
+			else {
+				break;
+			}
+		}
+
+		bool deletionOccurred = false;
+		for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+			if (_paths[i].p) {
+				// Clean expired and reduced priority paths
+				if (((now - _paths[i].lr) < ZT_PEER_PATH_EXPIRATION) && (_paths[i].priority == maxPriority)) {
+					if ((sendFullHello) || (_paths[i].p->needsHeartbeat(now))) {
+						attemptToContactAt(tPtr, _paths[i].p->localSocket(), _paths[i].p->address(), now, sendFullHello);
+						_paths[i].p->sent(now);
+						sent |= (_paths[i].p->address().ss_family == AF_INET) ? 0x1 : 0x2;
+					}
+				}
+				else {
+					_paths[i] = _PeerPath();
+					deletionOccurred = true;
+				}
+			}
+			if (! _paths[i].p || deletionOccurred) {
+				for (unsigned int j = i; j < ZT_MAX_PEER_NETWORK_PATHS; ++j) {
+					if (_paths[j].p && i != j) {
+						_paths[i] = _paths[j];
+						_paths[j] = _PeerPath();
+						break;
+					}
+				}
+				deletionOccurred = false;
+			}
+		}
 #ifndef ZT_NO_PEER_METRICS
-        uint16_t alive_path_count_tmp = 0, dead_path_count_tmp = 0;
-        for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-            if (_paths[i].p) {
-                if (_paths[i].p->alive(now)) {
-                    alive_path_count_tmp++;
-                }
-                else {
-                    dead_path_count_tmp++;
-                }
-            }
-        }
-        _alive_path_count = alive_path_count_tmp;
-        _dead_path_count = dead_path_count_tmp;
+		uint16_t alive_path_count_tmp = 0, dead_path_count_tmp = 0;
+		for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+			if (_paths[i].p) {
+				if (_paths[i].p->alive(now)) {
+					alive_path_count_tmp++;
+				}
+				else {
+					dead_path_count_tmp++;
+				}
+			}
+		}
+		_alive_path_count = alive_path_count_tmp;
+		_dead_path_count = dead_path_count_tmp;
 #endif
-    }
+	}
 #ifndef ZT_NO_PEER_METRICS
-    _peer_latency.Observe(latency(now));
+	_peer_latency.Observe(latency(now));
 #endif
-    return sent;
+	return sent;
 }
 
 void Peer::clusterRedirect(void* tPtr, const SharedPtr<Path>& originatingPath, const InetAddress& remoteAddress, const int64_t now)
 {
-    SharedPtr<Path> np(RR->topology->getPath(originatingPath->localSocket(), remoteAddress));
-    RR->t->peerRedirected(tPtr, 0, *this, np);
-
-    attemptToContactAt(tPtr, originatingPath->localSocket(), remoteAddress, now, true);
-
-    {
-        Mutex::Lock _l(_paths_m);
-
-        // New priority is higher than the priority of the originating path (if known)
-        long newPriority = 1;
-        for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-            if (_paths[i].p) {
-                if (_paths[i].p == originatingPath) {
-                    newPriority = _paths[i].priority;
-                    break;
-                }
-            }
-            else {
-                break;
-            }
-        }
-        newPriority += 2;
-
-        // Erase any paths with lower priority than this one or that are duplicate
-        // IPs and add this path.
-        unsigned int j = 0;
-        for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-            if (_paths[i].p) {
-                if ((_paths[i].priority >= newPriority) && (! _paths[i].p->address().ipsEqual2(remoteAddress))) {
-                    if (i != j) {
-                        _paths[j] = _paths[i];
-                    }
-                    ++j;
-                }
-            }
-        }
-        if (j < ZT_MAX_PEER_NETWORK_PATHS) {
-            _paths[j].lr = now;
-            _paths[j].p = np;
-            _paths[j].priority = newPriority;
-            ++j;
-            while (j < ZT_MAX_PEER_NETWORK_PATHS) {
-                _paths[j].lr = 0;
-                _paths[j].p.zero();
-                _paths[j].priority = 1;
-                ++j;
-            }
-        }
-    }
+	SharedPtr<Path> np(RR->topology->getPath(originatingPath->localSocket(), remoteAddress));
+	RR->t->peerRedirected(tPtr, 0, *this, np);
+
+	attemptToContactAt(tPtr, originatingPath->localSocket(), remoteAddress, now, true);
+
+	{
+		Mutex::Lock _l(_paths_m);
+
+		// New priority is higher than the priority of the originating path (if known)
+		long newPriority = 1;
+		for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+			if (_paths[i].p) {
+				if (_paths[i].p == originatingPath) {
+					newPriority = _paths[i].priority;
+					break;
+				}
+			}
+			else {
+				break;
+			}
+		}
+		newPriority += 2;
+
+		// Erase any paths with lower priority than this one or that are duplicate
+		// IPs and add this path.
+		unsigned int j = 0;
+		for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+			if (_paths[i].p) {
+				if ((_paths[i].priority >= newPriority) && (! _paths[i].p->address().ipsEqual2(remoteAddress))) {
+					if (i != j) {
+						_paths[j] = _paths[i];
+					}
+					++j;
+				}
+			}
+		}
+		if (j < ZT_MAX_PEER_NETWORK_PATHS) {
+			_paths[j].lr = now;
+			_paths[j].p = np;
+			_paths[j].priority = newPriority;
+			++j;
+			while (j < ZT_MAX_PEER_NETWORK_PATHS) {
+				_paths[j].lr = 0;
+				_paths[j].p.zero();
+				_paths[j].priority = 1;
+				++j;
+			}
+		}
+	}
 }
 
 void Peer::resetWithinScope(void* tPtr, InetAddress::IpScope scope, int inetAddressFamily, int64_t now)
 {
-    Mutex::Lock _l(_paths_m);
-    for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-        if (_paths[i].p) {
-            if ((_paths[i].p->address().ss_family == inetAddressFamily) && (_paths[i].p->ipScope() == scope)) {
-                attemptToContactAt(tPtr, _paths[i].p->localSocket(), _paths[i].p->address(), now, false);
-                _paths[i].p->sent(now);
-                _paths[i].lr = 0;   // path will not be used unless it speaks again
-            }
-        }
-        else {
-            break;
-        }
-    }
+	Mutex::Lock _l(_paths_m);
+	for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+		if (_paths[i].p) {
+			if ((_paths[i].p->address().ss_family == inetAddressFamily) && (_paths[i].p->ipScope() == scope)) {
+				attemptToContactAt(tPtr, _paths[i].p->localSocket(), _paths[i].p->address(), now, false);
+				_paths[i].p->sent(now);
+				_paths[i].lr = 0;	// path will not be used unless it speaks again
+			}
+		}
+		else {
+			break;
+		}
+	}
 }
 
 void Peer::recordOutgoingPacket(const SharedPtr<Path>& path, const uint64_t packetId, uint16_t payloadLength, const Packet::Verb verb, const int32_t flowId, int64_t now)
 {
 #ifndef ZT_NO_PEER_METRICS
-    _outgoing_packet++;
+	_outgoing_packet++;
 #endif
-    if (_localMultipathSupported && _bond) {
-        _bond->recordOutgoingPacket(path, packetId, payloadLength, verb, flowId, now);
-    }
+	if (_localMultipathSupported && _bond) {
+		_bond->recordOutgoingPacket(path, packetId, payloadLength, verb, flowId, now);
+	}
 }
 
 void Peer::recordIncomingInvalidPacket(const SharedPtr<Path>& path)
 {
 #ifndef ZT_NO_PEER_METRICS
-    _packet_errors++;
+	_packet_errors++;
 #endif
-    if (_localMultipathSupported && _bond) {
-        _bond->recordIncomingInvalidPacket(path);
-    }
+	if (_localMultipathSupported && _bond) {
+		_bond->recordIncomingInvalidPacket(path);
+	}
 }
 
 void Peer::recordIncomingPacket(const SharedPtr<Path>& path, const uint64_t packetId, uint16_t payloadLength, const Packet::Verb verb, const int32_t flowId, int64_t now)
 {
-    if (_localMultipathSupported && _bond) {
-        _bond->recordIncomingPacket(path, packetId, payloadLength, verb, flowId, now);
-    }
+	if (_localMultipathSupported && _bond) {
+		_bond->recordIncomingPacket(path, packetId, payloadLength, verb, flowId, now);
+	}
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 673 - 673
node/Peer.hpp

@@ -43,710 +43,710 @@ namespace ZeroTier {
  * Peer on P2P Network (virtual layer 1)
  */
 class Peer {
-    friend class SharedPtr<Peer>;
-    friend class SharedPtr<Bond>;
-    friend class Switch;
-    friend class Bond;
+	friend class SharedPtr<Peer>;
+	friend class SharedPtr<Bond>;
+	friend class Switch;
+	friend class Bond;
 
   private:
-    Peer() = delete;   // disabled to prevent bugs -- should not be constructed uninitialized
+	Peer() = delete;   // disabled to prevent bugs -- should not be constructed uninitialized
 
   public:
-    ~Peer()
-    {
-        Utils::burn(_key, sizeof(_key));
-    }
-
-    /**
-     * Construct a new peer
-     *
-     * @param renv Runtime environment
-     * @param myIdentity Identity of THIS node (for key agreement)
-     * @param peerIdentity Identity of peer
-     * @throws std::runtime_error Key agreement with peer's identity failed
-     */
-    Peer(const RuntimeEnvironment* renv, const Identity& myIdentity, const Identity& peerIdentity);
-
-    /**
-     * @return This peer's ZT address (short for identity().address())
-     */
-    inline const Address& address() const
-    {
-        return _id.address();
-    }
-
-    /**
-     * @return This peer's identity
-     */
-    inline const Identity& identity() const
-    {
-        return _id;
-    }
-
-    /**
-     * Log receipt of an authenticated packet
-     *
-     * This is called by the decode pipe when a packet is proven to be authentic
-     * and appears to be valid.
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param path Path over which packet was received
-     * @param hops ZeroTier (not IP) hops
-     * @param packetId Packet ID
-     * @param verb Packet verb
-     * @param inRePacketId Packet ID in reply to (default: none)
-     * @param inReVerb Verb in reply to (for OK/ERROR, default: VERB_NOP)
-     * @param trustEstablished If true, some form of non-trivial trust (like allowed in network) has been established
-     * @param networkId Network ID if this pertains to a network, or 0 otherwise
-     */
-    void received(
-        void* tPtr,
-        const SharedPtr<Path>& path,
-        const unsigned int hops,
-        const uint64_t packetId,
-        const unsigned int payloadLength,
-        const Packet::Verb verb,
-        const uint64_t inRePacketId,
-        const Packet::Verb inReVerb,
-        const bool trustEstablished,
-        const uint64_t networkId,
-        const int32_t flowId);
-
-    /**
-     * Check whether we have an active path to this peer via the given address
-     *
-     * @param now Current time
-     * @param addr Remote address
-     * @return True if we have an active path to this destination
-     */
-    inline bool hasActivePathTo(int64_t now, const InetAddress& addr) const
-    {
-        Mutex::Lock _l(_paths_m);
-        for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-            if (_paths[i].p) {
-                if (((now - _paths[i].lr) < ZT_PEER_PATH_EXPIRATION) && (_paths[i].p->address() == addr)) {
-                    return true;
-                }
-            }
-            else {
-                break;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Send via best direct path
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param data Packet data
-     * @param len Packet length
-     * @param now Current time
-     * @param force If true, send even if path is not alive
-     * @return True if we actually sent something
-     */
-    inline bool sendDirect(void* tPtr, const void* data, unsigned int len, int64_t now, bool force)
-    {
-        SharedPtr<Path> bp(getAppropriatePath(now, force));
-        if (bp) {
-            return bp->send(RR, tPtr, data, len, now);
-        }
-        return false;
-    }
-
-    /**
-     * Record incoming packets to
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param path Path over which packet was received
-     * @param packetId Packet ID
-     * @param payloadLength Length of packet data payload
-     * @param verb Packet verb
-     * @param flowId Flow ID
-     * @param now Current time
-     */
-    void recordIncomingPacket(const SharedPtr<Path>& path, const uint64_t packetId, uint16_t payloadLength, const Packet::Verb verb, const int32_t flowId, int64_t now);
-
-    /**
-     *
-     * @param path Path over which packet is being sent
-     * @param packetId Packet ID
-     * @param payloadLength Length of packet data payload
-     * @param verb Packet verb
-     * @param flowId Flow ID
-     * @param now Current time
-     */
-    void recordOutgoingPacket(const SharedPtr<Path>& path, const uint64_t packetId, uint16_t payloadLength, const Packet::Verb verb, const int32_t flowId, int64_t now);
-
-    /**
-     * Record an invalid incoming packet. This packet failed
-     * MAC/compression/cipher checks and will now contribute to a
-     * Packet Error Ratio (PER).
-     *
-     * @param path Path over which packet was received
-     */
-    void recordIncomingInvalidPacket(const SharedPtr<Path>& path);
-
-    /**
-     * Get the most appropriate direct path based on current multipath and QoS configuration
-     *
-     * @param now Current time
-     * @param includeExpired If true, include even expired paths
-     * @return Best current path or NULL if none
-     */
-    SharedPtr<Path> getAppropriatePath(int64_t now, bool includeExpired, int32_t flowId = -1);
-
-    /**
-     * Send VERB_RENDEZVOUS to this and another peer via the best common IP scope and path
-     */
-    void introduce(void* const tPtr, const int64_t now, const SharedPtr<Peer>& other) const;
-
-    /**
-     * Send a HELLO to this peer at a specified physical address
-     *
-     * No statistics or sent times are updated here.
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param localSocket Local source socket
-     * @param atAddress Destination address
-     * @param now Current time
-     */
-    void sendHELLO(void* tPtr, const int64_t localSocket, const InetAddress& atAddress, int64_t now);
-
-    /**
-     * Send ECHO (or HELLO for older peers) to this peer at the given address
-     *
-     * No statistics or sent times are updated here.
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param localSocket Local source socket
-     * @param atAddress Destination address
-     * @param now Current time
-     * @param sendFullHello If true, always send a full HELLO instead of just an ECHO
-     */
-    void attemptToContactAt(void* tPtr, const int64_t localSocket, const InetAddress& atAddress, int64_t now, bool sendFullHello);
-
-    /**
-     * Try a memorized or statically defined path if any are known
-     *
-     * Under the hood this is done periodically based on ZT_TRY_MEMORIZED_PATH_INTERVAL.
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param now Current time
-     */
-    void tryMemorizedPath(void* tPtr, int64_t now);
-
-    /**
-     * A check to be performed periodically which determines whether multipath communication is
-     * possible with this peer. This check should be performed early in the life-cycle of the peer
-     * as well as during the process of learning new paths.
-     */
-    void performMultipathStateCheck(void* tPtr, int64_t now);
-
-    /**
-     * Send pings or keepalives depending on configured timeouts
-     *
-     * This also cleans up some internal data structures. It's called periodically from Node.
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param now Current time
-     * @param inetAddressFamily Keep this address family alive, or -1 for any
-     * @return 0 if nothing sent or bit mask: bit 0x1 if IPv4 sent, bit 0x2 if IPv6 sent (0x3 means both sent)
-     */
-    unsigned int doPingAndKeepalive(void* tPtr, int64_t now);
-
-    /**
-     * Process a cluster redirect sent by this peer
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param originatingPath Path from which redirect originated
-     * @param remoteAddress Remote address
-     * @param now Current time
-     */
-    void clusterRedirect(void* tPtr, const SharedPtr<Path>& originatingPath, const InetAddress& remoteAddress, const int64_t now);
-
-    /**
-     * Reset paths within a given IP scope and address family
-     *
-     * Resetting a path involves sending an ECHO to it and then deactivating
-     * it until or unless it responds. This is done when we detect a change
-     * to our external IP or another system change that might invalidate
-     * many or all current paths.
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param scope IP scope
-     * @param inetAddressFamily Family e.g. AF_INET
-     * @param now Current time
-     */
-    void resetWithinScope(void* tPtr, InetAddress::IpScope scope, int inetAddressFamily, int64_t now);
-
-    /**
-     * @param now Current time
-     * @return All known paths to this peer
-     */
-    inline std::vector<SharedPtr<Path> > paths(const int64_t now) const
-    {
-        std::vector<SharedPtr<Path> > pp;
-        Mutex::Lock _l(_paths_m);
-        for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-            if (! _paths[i].p) {
-                break;
-            }
-            pp.push_back(_paths[i].p);
-        }
-        return pp;
-    }
-
-    /**
-     * @return Time of last receive of anything, whether direct or relayed
-     */
-    inline int64_t lastReceive() const
-    {
-        return _lastReceive;
-    }
-
-    /**
-     * @return True if we've heard from this peer in less than ZT_PEER_ACTIVITY_TIMEOUT
-     */
-    inline bool isAlive(const int64_t now) const
-    {
-        return ((now - _lastReceive) < ZT_PEER_ACTIVITY_TIMEOUT);
-    }
-
-    /**
-     * @return True if this peer has sent us real network traffic recently
-     */
-    inline int64_t isActive(int64_t now) const
-    {
-        return ((now - _lastNontrivialReceive) < ZT_PEER_ACTIVITY_TIMEOUT);
-    }
-
-    inline int64_t lastSentFullHello()
-    {
-        return _lastSentFullHello;
-    }
-
-    /**
-     * @return Latency in milliseconds of best/aggregate path or 0xffff if unknown / no paths
-     */
-    inline unsigned int latency(const int64_t now)
-    {
-        if (_localMultipathSupported) {
-            return (int)_lastComputedAggregateMeanLatency;
-        }
-        else {
-            SharedPtr<Path> bp(getAppropriatePath(now, false));
-            if (bp) {
-                return (unsigned int)bp->latency();
-            }
-            return 0xffff;
-        }
-    }
-
-    /**
-     * This computes a quality score for relays and root servers
-     *
-     * If we haven't heard anything from these in ZT_PEER_ACTIVITY_TIMEOUT, they
-     * receive the worst possible quality (max unsigned int). Otherwise the
-     * quality is a product of latency and the number of potential missed
-     * pings. This causes roots and relays to switch over a bit faster if they
-     * fail.
-     *
-     * @return Relay quality score computed from latency and other factors, lower is better
-     */
-    inline unsigned int relayQuality(const int64_t now)
-    {
-        const uint64_t tsr = now - _lastReceive;
-        if (tsr >= ZT_PEER_ACTIVITY_TIMEOUT) {
-            return (~(unsigned int)0);
-        }
-        unsigned int l = latency(now);
-        if (! l) {
-            l = 0xffff;
-        }
-        return (l * (((unsigned int)tsr / (ZT_PEER_PING_PERIOD + 1000)) + 1));
-    }
-
-    /**
-     * @return 256-bit secret symmetric encryption key
-     */
-    inline const unsigned char* key() const
-    {
-        return _key;
-    }
-
-    /**
-     * Set the currently known remote version of this peer's client
-     *
-     * @param vproto Protocol version
-     * @param vmaj Major version
-     * @param vmin Minor version
-     * @param vrev Revision
-     */
-    inline void setRemoteVersion(unsigned int vproto, unsigned int vmaj, unsigned int vmin, unsigned int vrev)
-    {
-        _vProto = (uint16_t)vproto;
-        _vMajor = (uint16_t)vmaj;
-        _vMinor = (uint16_t)vmin;
-        _vRevision = (uint16_t)vrev;
-    }
-
-    inline unsigned int remoteVersionProtocol() const
-    {
-        return _vProto;
-    }
-    inline unsigned int remoteVersionMajor() const
-    {
-        return _vMajor;
-    }
-    inline unsigned int remoteVersionMinor() const
-    {
-        return _vMinor;
-    }
-    inline unsigned int remoteVersionRevision() const
-    {
-        return _vRevision;
-    }
-
-    inline bool remoteVersionKnown() const
-    {
-        return ((_vMajor > 0) || (_vMinor > 0) || (_vRevision > 0));
-    }
-
-    /**
-     * @return True if peer has received a trust established packet (e.g. common network membership) in the past ZT_TRUST_EXPIRATION ms
-     */
-    inline bool trustEstablished(const int64_t now) const
-    {
-        return ((now - _lastTrustEstablishedPacketReceived) < ZT_TRUST_EXPIRATION);
-    }
-
-    /**
-     * Rate limit gate for VERB_PUSH_DIRECT_PATHS
-     */
-    inline bool rateGatePushDirectPaths(const int64_t now)
-    {
-        if ((now - _lastDirectPathPushReceive) <= ZT_PUSH_DIRECT_PATHS_CUTOFF_TIME) {
-            ++_directPathPushCutoffCount;
-        }
-        else {
-            _directPathPushCutoffCount = 0;
-        }
-        _lastDirectPathPushReceive = now;
-        return (_directPathPushCutoffCount < ZT_PUSH_DIRECT_PATHS_CUTOFF_LIMIT);
-    }
-
-    /**
-     * Rate limit gate for VERB_NETWORK_CREDENTIALS
-     */
-    inline bool rateGateCredentialsReceived(const int64_t now)
-    {
-        if ((now - _lastCredentialsReceived) >= ZT_PEER_CREDENTIALS_RATE_LIMIT) {
-            _lastCredentialsReceived = now;
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Rate limit gate for sending of ERROR_NEED_MEMBERSHIP_CERTIFICATE
-     */
-    inline bool rateGateRequestCredentials(const int64_t now)
-    {
-        if ((now - _lastCredentialRequestSent) >= ZT_PEER_GENERAL_RATE_LIMIT) {
-            _lastCredentialRequestSent = now;
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Rate limit gate for inbound WHOIS requests
-     */
-    inline bool rateGateInboundWhoisRequest(const int64_t now)
-    {
-        if ((now - _lastWhoisRequestReceived) >= ZT_PEER_WHOIS_RATE_LIMIT) {
-            _lastWhoisRequestReceived = now;
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * See definition in Bond
-     */
-    inline bool rateGateQoS(int64_t now, SharedPtr<Path>& path)
-    {
-        Mutex::Lock _l(_bond_m);
-        if (_bond) {
-            return _bond->rateGateQoS(now, path);
-        }
-        return false;   // Default behavior. If there is no bond, we drop these
-    }
-
-    /**
-     * See definition in Bond
-     */
-    void receivedQoS(const SharedPtr<Path>& path, int64_t now, int count, uint64_t* rx_id, uint16_t* rx_ts)
-    {
-        Mutex::Lock _l(_bond_m);
-        if (_bond) {
-            _bond->receivedQoS(path, now, count, rx_id, rx_ts);
-        }
-    }
-
-    /**
-     * See definition in Bond
-     */
-    void processIncomingPathNegotiationRequest(uint64_t now, SharedPtr<Path>& path, int16_t remoteUtility)
-    {
-        Mutex::Lock _l(_bond_m);
-        if (_bond) {
-            _bond->processIncomingPathNegotiationRequest(now, path, remoteUtility);
-        }
-    }
-
-    /**
-     * See definition in Bond
-     */
-    inline bool rateGatePathNegotiation(int64_t now, SharedPtr<Path>& path)
-    {
-        Mutex::Lock _l(_bond_m);
-        if (_bond) {
-            return _bond->rateGatePathNegotiation(now, path);
-        }
-        return false;   // Default behavior. If there is no bond, we drop these
-    }
-
-    /**
-     * See definition in Bond
-     */
-    bool flowHashingSupported()
-    {
-        Mutex::Lock _l(_bond_m);
-        if (_bond) {
-            return _bond->flowHashingSupported();
-        }
-        return false;
-    }
-
-    /**
-     * Serialize a peer for storage in local cache
-     *
-     * This does not serialize everything, just non-ephemeral information.
-     */
-    template <unsigned int C> inline void serializeForCache(Buffer<C>& b) const
-    {
-        b.append((uint8_t)2);
-
-        _id.serialize(b);
-
-        b.append((uint16_t)_vProto);
-        b.append((uint16_t)_vMajor);
-        b.append((uint16_t)_vMinor);
-        b.append((uint16_t)_vRevision);
-
-        {
-            Mutex::Lock _l(_paths_m);
-            unsigned int pc = 0;
-            for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-                if (_paths[i].p) {
-                    ++pc;
-                }
-                else {
-                    break;
-                }
-            }
-            b.append((uint16_t)pc);
-            for (unsigned int i = 0; i < pc; ++i) {
-                _paths[i].p->address().serialize(b);
-            }
-        }
-    }
-
-    template <unsigned int C> inline static SharedPtr<Peer> deserializeFromCache(int64_t now, void* tPtr, Buffer<C>& b, const RuntimeEnvironment* renv)
-    {
-        try {
-            unsigned int ptr = 0;
-            if (b[ptr++] != 2) {
-                return SharedPtr<Peer>();
-            }
-
-            Identity id;
-            ptr += id.deserialize(b, ptr);
-            if (! id) {
-                return SharedPtr<Peer>();
-            }
-
-            SharedPtr<Peer> p(new Peer(renv, renv->identity, id));
-
-            p->_vProto = b.template at<uint16_t>(ptr);
-            ptr += 2;
-            p->_vMajor = b.template at<uint16_t>(ptr);
-            ptr += 2;
-            p->_vMinor = b.template at<uint16_t>(ptr);
-            ptr += 2;
-            p->_vRevision = b.template at<uint16_t>(ptr);
-            ptr += 2;
-
-            // When we deserialize from the cache we don't actually restore paths. We
-            // just try them and then re-learn them if they happen to still be up.
-            // Paths are fairly ephemeral in the real world in most cases.
-            const unsigned int tryPathCount = b.template at<uint16_t>(ptr);
-            ptr += 2;
-            for (unsigned int i = 0; i < tryPathCount; ++i) {
-                InetAddress inaddr;
-                try {
-                    ptr += inaddr.deserialize(b, ptr);
-                    if (inaddr) {
-                        p->attemptToContactAt(tPtr, -1, inaddr, now, true);
-                    }
-                }
-                catch (...) {
-                    break;
-                }
-            }
-
-            return p;
-        }
-        catch (...) {
-            return SharedPtr<Peer>();
-        }
-    }
-
-    /**
-     * @return The bonding policy used to reach this peer
-     */
-    SharedPtr<Bond> bond()
-    {
-        return _bond;
-    }
-
-    /**
-     * @return The bonding policy used to reach this peer
-     */
-    inline int8_t bondingPolicy()
-    {
-        Mutex::Lock _l(_bond_m);
-        if (_bond) {
-            return _bond->policy();
-        }
-        return ZT_BOND_POLICY_NONE;
-    }
-
-    /**
-     * @return the number of links in this bond which are considered alive
-     */
-    inline uint8_t getNumAliveLinks()
-    {
-        Mutex::Lock _l(_paths_m);
-        if (_bond) {
-            return _bond->getNumAliveLinks();
-        }
-        return 0;
-    }
-
-    /**
-     * @return the number of links in this bond
-     */
-    inline uint8_t getNumTotalLinks()
-    {
-        Mutex::Lock _l(_paths_m);
-        if (_bond) {
-            return _bond->getNumTotalLinks();
-        }
-        return 0;
-    }
-
-    // inline const AES *aesKeysIfSupported() const
-    //{ return (const AES *)0; }
-
-    inline const AES* aesKeysIfSupported() const
-    {
-        return (_vProto >= 12) ? _aesKeys : (const AES*)0;
-    }
-
-    inline const AES* aesKeys() const
-    {
-        return _aesKeys;
-    }
+	~Peer()
+	{
+		Utils::burn(_key, sizeof(_key));
+	}
+
+	/**
+	 * Construct a new peer
+	 *
+	 * @param renv Runtime environment
+	 * @param myIdentity Identity of THIS node (for key agreement)
+	 * @param peerIdentity Identity of peer
+	 * @throws std::runtime_error Key agreement with peer's identity failed
+	 */
+	Peer(const RuntimeEnvironment* renv, const Identity& myIdentity, const Identity& peerIdentity);
+
+	/**
+	 * @return This peer's ZT address (short for identity().address())
+	 */
+	inline const Address& address() const
+	{
+		return _id.address();
+	}
+
+	/**
+	 * @return This peer's identity
+	 */
+	inline const Identity& identity() const
+	{
+		return _id;
+	}
+
+	/**
+	 * Log receipt of an authenticated packet
+	 *
+	 * This is called by the decode pipe when a packet is proven to be authentic
+	 * and appears to be valid.
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param path Path over which packet was received
+	 * @param hops ZeroTier (not IP) hops
+	 * @param packetId Packet ID
+	 * @param verb Packet verb
+	 * @param inRePacketId Packet ID in reply to (default: none)
+	 * @param inReVerb Verb in reply to (for OK/ERROR, default: VERB_NOP)
+	 * @param trustEstablished If true, some form of non-trivial trust (like allowed in network) has been established
+	 * @param networkId Network ID if this pertains to a network, or 0 otherwise
+	 */
+	void received(
+		void* tPtr,
+		const SharedPtr<Path>& path,
+		const unsigned int hops,
+		const uint64_t packetId,
+		const unsigned int payloadLength,
+		const Packet::Verb verb,
+		const uint64_t inRePacketId,
+		const Packet::Verb inReVerb,
+		const bool trustEstablished,
+		const uint64_t networkId,
+		const int32_t flowId);
+
+	/**
+	 * Check whether we have an active path to this peer via the given address
+	 *
+	 * @param now Current time
+	 * @param addr Remote address
+	 * @return True if we have an active path to this destination
+	 */
+	inline bool hasActivePathTo(int64_t now, const InetAddress& addr) const
+	{
+		Mutex::Lock _l(_paths_m);
+		for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+			if (_paths[i].p) {
+				if (((now - _paths[i].lr) < ZT_PEER_PATH_EXPIRATION) && (_paths[i].p->address() == addr)) {
+					return true;
+				}
+			}
+			else {
+				break;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Send via best direct path
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param data Packet data
+	 * @param len Packet length
+	 * @param now Current time
+	 * @param force If true, send even if path is not alive
+	 * @return True if we actually sent something
+	 */
+	inline bool sendDirect(void* tPtr, const void* data, unsigned int len, int64_t now, bool force)
+	{
+		SharedPtr<Path> bp(getAppropriatePath(now, force));
+		if (bp) {
+			return bp->send(RR, tPtr, data, len, now);
+		}
+		return false;
+	}
+
+	/**
+	 * Record incoming packets to
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param path Path over which packet was received
+	 * @param packetId Packet ID
+	 * @param payloadLength Length of packet data payload
+	 * @param verb Packet verb
+	 * @param flowId Flow ID
+	 * @param now Current time
+	 */
+	void recordIncomingPacket(const SharedPtr<Path>& path, const uint64_t packetId, uint16_t payloadLength, const Packet::Verb verb, const int32_t flowId, int64_t now);
+
+	/**
+	 *
+	 * @param path Path over which packet is being sent
+	 * @param packetId Packet ID
+	 * @param payloadLength Length of packet data payload
+	 * @param verb Packet verb
+	 * @param flowId Flow ID
+	 * @param now Current time
+	 */
+	void recordOutgoingPacket(const SharedPtr<Path>& path, const uint64_t packetId, uint16_t payloadLength, const Packet::Verb verb, const int32_t flowId, int64_t now);
+
+	/**
+	 * Record an invalid incoming packet. This packet failed
+	 * MAC/compression/cipher checks and will now contribute to a
+	 * Packet Error Ratio (PER).
+	 *
+	 * @param path Path over which packet was received
+	 */
+	void recordIncomingInvalidPacket(const SharedPtr<Path>& path);
+
+	/**
+	 * Get the most appropriate direct path based on current multipath and QoS configuration
+	 *
+	 * @param now Current time
+	 * @param includeExpired If true, include even expired paths
+	 * @return Best current path or NULL if none
+	 */
+	SharedPtr<Path> getAppropriatePath(int64_t now, bool includeExpired, int32_t flowId = -1);
+
+	/**
+	 * Send VERB_RENDEZVOUS to this and another peer via the best common IP scope and path
+	 */
+	void introduce(void* const tPtr, const int64_t now, const SharedPtr<Peer>& other) const;
+
+	/**
+	 * Send a HELLO to this peer at a specified physical address
+	 *
+	 * No statistics or sent times are updated here.
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param localSocket Local source socket
+	 * @param atAddress Destination address
+	 * @param now Current time
+	 */
+	void sendHELLO(void* tPtr, const int64_t localSocket, const InetAddress& atAddress, int64_t now);
+
+	/**
+	 * Send ECHO (or HELLO for older peers) to this peer at the given address
+	 *
+	 * No statistics or sent times are updated here.
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param localSocket Local source socket
+	 * @param atAddress Destination address
+	 * @param now Current time
+	 * @param sendFullHello If true, always send a full HELLO instead of just an ECHO
+	 */
+	void attemptToContactAt(void* tPtr, const int64_t localSocket, const InetAddress& atAddress, int64_t now, bool sendFullHello);
+
+	/**
+	 * Try a memorized or statically defined path if any are known
+	 *
+	 * Under the hood this is done periodically based on ZT_TRY_MEMORIZED_PATH_INTERVAL.
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param now Current time
+	 */
+	void tryMemorizedPath(void* tPtr, int64_t now);
+
+	/**
+	 * A check to be performed periodically which determines whether multipath communication is
+	 * possible with this peer. This check should be performed early in the life-cycle of the peer
+	 * as well as during the process of learning new paths.
+	 */
+	void performMultipathStateCheck(void* tPtr, int64_t now);
+
+	/**
+	 * Send pings or keepalives depending on configured timeouts
+	 *
+	 * This also cleans up some internal data structures. It's called periodically from Node.
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param now Current time
+	 * @param inetAddressFamily Keep this address family alive, or -1 for any
+	 * @return 0 if nothing sent or bit mask: bit 0x1 if IPv4 sent, bit 0x2 if IPv6 sent (0x3 means both sent)
+	 */
+	unsigned int doPingAndKeepalive(void* tPtr, int64_t now);
+
+	/**
+	 * Process a cluster redirect sent by this peer
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param originatingPath Path from which redirect originated
+	 * @param remoteAddress Remote address
+	 * @param now Current time
+	 */
+	void clusterRedirect(void* tPtr, const SharedPtr<Path>& originatingPath, const InetAddress& remoteAddress, const int64_t now);
+
+	/**
+	 * Reset paths within a given IP scope and address family
+	 *
+	 * Resetting a path involves sending an ECHO to it and then deactivating
+	 * it until or unless it responds. This is done when we detect a change
+	 * to our external IP or another system change that might invalidate
+	 * many or all current paths.
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param scope IP scope
+	 * @param inetAddressFamily Family e.g. AF_INET
+	 * @param now Current time
+	 */
+	void resetWithinScope(void* tPtr, InetAddress::IpScope scope, int inetAddressFamily, int64_t now);
+
+	/**
+	 * @param now Current time
+	 * @return All known paths to this peer
+	 */
+	inline std::vector<SharedPtr<Path> > paths(const int64_t now) const
+	{
+		std::vector<SharedPtr<Path> > pp;
+		Mutex::Lock _l(_paths_m);
+		for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+			if (! _paths[i].p) {
+				break;
+			}
+			pp.push_back(_paths[i].p);
+		}
+		return pp;
+	}
+
+	/**
+	 * @return Time of last receive of anything, whether direct or relayed
+	 */
+	inline int64_t lastReceive() const
+	{
+		return _lastReceive;
+	}
+
+	/**
+	 * @return True if we've heard from this peer in less than ZT_PEER_ACTIVITY_TIMEOUT
+	 */
+	inline bool isAlive(const int64_t now) const
+	{
+		return ((now - _lastReceive) < ZT_PEER_ACTIVITY_TIMEOUT);
+	}
+
+	/**
+	 * @return True if this peer has sent us real network traffic recently
+	 */
+	inline int64_t isActive(int64_t now) const
+	{
+		return ((now - _lastNontrivialReceive) < ZT_PEER_ACTIVITY_TIMEOUT);
+	}
+
+	inline int64_t lastSentFullHello()
+	{
+		return _lastSentFullHello;
+	}
+
+	/**
+	 * @return Latency in milliseconds of best/aggregate path or 0xffff if unknown / no paths
+	 */
+	inline unsigned int latency(const int64_t now)
+	{
+		if (_localMultipathSupported) {
+			return (int)_lastComputedAggregateMeanLatency;
+		}
+		else {
+			SharedPtr<Path> bp(getAppropriatePath(now, false));
+			if (bp) {
+				return (unsigned int)bp->latency();
+			}
+			return 0xffff;
+		}
+	}
+
+	/**
+	 * This computes a quality score for relays and root servers
+	 *
+	 * If we haven't heard anything from these in ZT_PEER_ACTIVITY_TIMEOUT, they
+	 * receive the worst possible quality (max unsigned int). Otherwise the
+	 * quality is a product of latency and the number of potential missed
+	 * pings. This causes roots and relays to switch over a bit faster if they
+	 * fail.
+	 *
+	 * @return Relay quality score computed from latency and other factors, lower is better
+	 */
+	inline unsigned int relayQuality(const int64_t now)
+	{
+		const uint64_t tsr = now - _lastReceive;
+		if (tsr >= ZT_PEER_ACTIVITY_TIMEOUT) {
+			return (~(unsigned int)0);
+		}
+		unsigned int l = latency(now);
+		if (! l) {
+			l = 0xffff;
+		}
+		return (l * (((unsigned int)tsr / (ZT_PEER_PING_PERIOD + 1000)) + 1));
+	}
+
+	/**
+	 * @return 256-bit secret symmetric encryption key
+	 */
+	inline const unsigned char* key() const
+	{
+		return _key;
+	}
+
+	/**
+	 * Set the currently known remote version of this peer's client
+	 *
+	 * @param vproto Protocol version
+	 * @param vmaj Major version
+	 * @param vmin Minor version
+	 * @param vrev Revision
+	 */
+	inline void setRemoteVersion(unsigned int vproto, unsigned int vmaj, unsigned int vmin, unsigned int vrev)
+	{
+		_vProto = (uint16_t)vproto;
+		_vMajor = (uint16_t)vmaj;
+		_vMinor = (uint16_t)vmin;
+		_vRevision = (uint16_t)vrev;
+	}
+
+	inline unsigned int remoteVersionProtocol() const
+	{
+		return _vProto;
+	}
+	inline unsigned int remoteVersionMajor() const
+	{
+		return _vMajor;
+	}
+	inline unsigned int remoteVersionMinor() const
+	{
+		return _vMinor;
+	}
+	inline unsigned int remoteVersionRevision() const
+	{
+		return _vRevision;
+	}
+
+	inline bool remoteVersionKnown() const
+	{
+		return ((_vMajor > 0) || (_vMinor > 0) || (_vRevision > 0));
+	}
+
+	/**
+	 * @return True if peer has received a trust established packet (e.g. common network membership) in the past ZT_TRUST_EXPIRATION ms
+	 */
+	inline bool trustEstablished(const int64_t now) const
+	{
+		return ((now - _lastTrustEstablishedPacketReceived) < ZT_TRUST_EXPIRATION);
+	}
+
+	/**
+	 * Rate limit gate for VERB_PUSH_DIRECT_PATHS
+	 */
+	inline bool rateGatePushDirectPaths(const int64_t now)
+	{
+		if ((now - _lastDirectPathPushReceive) <= ZT_PUSH_DIRECT_PATHS_CUTOFF_TIME) {
+			++_directPathPushCutoffCount;
+		}
+		else {
+			_directPathPushCutoffCount = 0;
+		}
+		_lastDirectPathPushReceive = now;
+		return (_directPathPushCutoffCount < ZT_PUSH_DIRECT_PATHS_CUTOFF_LIMIT);
+	}
+
+	/**
+	 * Rate limit gate for VERB_NETWORK_CREDENTIALS
+	 */
+	inline bool rateGateCredentialsReceived(const int64_t now)
+	{
+		if ((now - _lastCredentialsReceived) >= ZT_PEER_CREDENTIALS_RATE_LIMIT) {
+			_lastCredentialsReceived = now;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Rate limit gate for sending of ERROR_NEED_MEMBERSHIP_CERTIFICATE
+	 */
+	inline bool rateGateRequestCredentials(const int64_t now)
+	{
+		if ((now - _lastCredentialRequestSent) >= ZT_PEER_GENERAL_RATE_LIMIT) {
+			_lastCredentialRequestSent = now;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Rate limit gate for inbound WHOIS requests
+	 */
+	inline bool rateGateInboundWhoisRequest(const int64_t now)
+	{
+		if ((now - _lastWhoisRequestReceived) >= ZT_PEER_WHOIS_RATE_LIMIT) {
+			_lastWhoisRequestReceived = now;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * See definition in Bond
+	 */
+	inline bool rateGateQoS(int64_t now, SharedPtr<Path>& path)
+	{
+		Mutex::Lock _l(_bond_m);
+		if (_bond) {
+			return _bond->rateGateQoS(now, path);
+		}
+		return false;	// Default behavior. If there is no bond, we drop these
+	}
+
+	/**
+	 * See definition in Bond
+	 */
+	void receivedQoS(const SharedPtr<Path>& path, int64_t now, int count, uint64_t* rx_id, uint16_t* rx_ts)
+	{
+		Mutex::Lock _l(_bond_m);
+		if (_bond) {
+			_bond->receivedQoS(path, now, count, rx_id, rx_ts);
+		}
+	}
+
+	/**
+	 * See definition in Bond
+	 */
+	void processIncomingPathNegotiationRequest(uint64_t now, SharedPtr<Path>& path, int16_t remoteUtility)
+	{
+		Mutex::Lock _l(_bond_m);
+		if (_bond) {
+			_bond->processIncomingPathNegotiationRequest(now, path, remoteUtility);
+		}
+	}
+
+	/**
+	 * See definition in Bond
+	 */
+	inline bool rateGatePathNegotiation(int64_t now, SharedPtr<Path>& path)
+	{
+		Mutex::Lock _l(_bond_m);
+		if (_bond) {
+			return _bond->rateGatePathNegotiation(now, path);
+		}
+		return false;	// Default behavior. If there is no bond, we drop these
+	}
+
+	/**
+	 * See definition in Bond
+	 */
+	bool flowHashingSupported()
+	{
+		Mutex::Lock _l(_bond_m);
+		if (_bond) {
+			return _bond->flowHashingSupported();
+		}
+		return false;
+	}
+
+	/**
+	 * Serialize a peer for storage in local cache
+	 *
+	 * This does not serialize everything, just non-ephemeral information.
+	 */
+	template <unsigned int C> inline void serializeForCache(Buffer<C>& b) const
+	{
+		b.append((uint8_t)2);
+
+		_id.serialize(b);
+
+		b.append((uint16_t)_vProto);
+		b.append((uint16_t)_vMajor);
+		b.append((uint16_t)_vMinor);
+		b.append((uint16_t)_vRevision);
+
+		{
+			Mutex::Lock _l(_paths_m);
+			unsigned int pc = 0;
+			for (unsigned int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+				if (_paths[i].p) {
+					++pc;
+				}
+				else {
+					break;
+				}
+			}
+			b.append((uint16_t)pc);
+			for (unsigned int i = 0; i < pc; ++i) {
+				_paths[i].p->address().serialize(b);
+			}
+		}
+	}
+
+	template <unsigned int C> inline static SharedPtr<Peer> deserializeFromCache(int64_t now, void* tPtr, Buffer<C>& b, const RuntimeEnvironment* renv)
+	{
+		try {
+			unsigned int ptr = 0;
+			if (b[ptr++] != 2) {
+				return SharedPtr<Peer>();
+			}
+
+			Identity id;
+			ptr += id.deserialize(b, ptr);
+			if (! id) {
+				return SharedPtr<Peer>();
+			}
+
+			SharedPtr<Peer> p(new Peer(renv, renv->identity, id));
+
+			p->_vProto = b.template at<uint16_t>(ptr);
+			ptr += 2;
+			p->_vMajor = b.template at<uint16_t>(ptr);
+			ptr += 2;
+			p->_vMinor = b.template at<uint16_t>(ptr);
+			ptr += 2;
+			p->_vRevision = b.template at<uint16_t>(ptr);
+			ptr += 2;
+
+			// When we deserialize from the cache we don't actually restore paths. We
+			// just try them and then re-learn them if they happen to still be up.
+			// Paths are fairly ephemeral in the real world in most cases.
+			const unsigned int tryPathCount = b.template at<uint16_t>(ptr);
+			ptr += 2;
+			for (unsigned int i = 0; i < tryPathCount; ++i) {
+				InetAddress inaddr;
+				try {
+					ptr += inaddr.deserialize(b, ptr);
+					if (inaddr) {
+						p->attemptToContactAt(tPtr, -1, inaddr, now, true);
+					}
+				}
+				catch (...) {
+					break;
+				}
+			}
+
+			return p;
+		}
+		catch (...) {
+			return SharedPtr<Peer>();
+		}
+	}
+
+	/**
+	 * @return The bonding policy used to reach this peer
+	 */
+	SharedPtr<Bond> bond()
+	{
+		return _bond;
+	}
+
+	/**
+	 * @return The bonding policy used to reach this peer
+	 */
+	inline int8_t bondingPolicy()
+	{
+		Mutex::Lock _l(_bond_m);
+		if (_bond) {
+			return _bond->policy();
+		}
+		return ZT_BOND_POLICY_NONE;
+	}
+
+	/**
+	 * @return the number of links in this bond which are considered alive
+	 */
+	inline uint8_t getNumAliveLinks()
+	{
+		Mutex::Lock _l(_paths_m);
+		if (_bond) {
+			return _bond->getNumAliveLinks();
+		}
+		return 0;
+	}
+
+	/**
+	 * @return the number of links in this bond
+	 */
+	inline uint8_t getNumTotalLinks()
+	{
+		Mutex::Lock _l(_paths_m);
+		if (_bond) {
+			return _bond->getNumTotalLinks();
+		}
+		return 0;
+	}
+
+	// inline const AES *aesKeysIfSupported() const
+	//{ return (const AES *)0; }
+
+	inline const AES* aesKeysIfSupported() const
+	{
+		return (_vProto >= 12) ? _aesKeys : (const AES*)0;
+	}
+
+	inline const AES* aesKeys() const
+	{
+		return _aesKeys;
+	}
 
   private:
-    struct _PeerPath {
-        _PeerPath() : lr(0), p(), priority(1)
-        {
-        }
-        int64_t lr;   // time of last valid ZeroTier packet
-        SharedPtr<Path> p;
-        long priority;   // >= 1, higher is better
-    };
+	struct _PeerPath {
+		_PeerPath() : lr(0), p(), priority(1)
+		{
+		}
+		int64_t lr;	  // time of last valid ZeroTier packet
+		SharedPtr<Path> p;
+		long priority;	 // >= 1, higher is better
+	};
 
-    uint8_t _key[ZT_SYMMETRIC_KEY_SIZE];
-    AES _aesKeys[2];
+	uint8_t _key[ZT_SYMMETRIC_KEY_SIZE];
+	AES _aesKeys[2];
 
-    const RuntimeEnvironment* RR;
+	const RuntimeEnvironment* RR;
 
-    int64_t _lastReceive;             // direct or indirect
-    int64_t _lastNontrivialReceive;   // frames, things like netconf, etc.
-    int64_t _lastTriedMemorizedPath;
-    int64_t _lastDirectPathPushSent;
-    int64_t _lastDirectPathPushReceive;
-    int64_t _lastCredentialRequestSent;
-    int64_t _lastWhoisRequestReceived;
-    int64_t _lastCredentialsReceived;
-    int64_t _lastTrustEstablishedPacketReceived;
-    int64_t _lastSentFullHello;
-    int64_t _lastEchoCheck;
+	int64_t _lastReceive;			  // direct or indirect
+	int64_t _lastNontrivialReceive;	  // frames, things like netconf, etc.
+	int64_t _lastTriedMemorizedPath;
+	int64_t _lastDirectPathPushSent;
+	int64_t _lastDirectPathPushReceive;
+	int64_t _lastCredentialRequestSent;
+	int64_t _lastWhoisRequestReceived;
+	int64_t _lastCredentialsReceived;
+	int64_t _lastTrustEstablishedPacketReceived;
+	int64_t _lastSentFullHello;
+	int64_t _lastEchoCheck;
 
-    unsigned char _freeRandomByte;
+	unsigned char _freeRandomByte;
 
-    uint16_t _vProto;
-    uint16_t _vMajor;
-    uint16_t _vMinor;
-    uint16_t _vRevision;
+	uint16_t _vProto;
+	uint16_t _vMajor;
+	uint16_t _vMinor;
+	uint16_t _vRevision;
 
-    std::list<std::pair<Path*, int64_t> > _lastTriedPath;
-    Mutex _lastTriedPath_m;
+	std::list<std::pair<Path*, int64_t> > _lastTriedPath;
+	Mutex _lastTriedPath_m;
 
-    _PeerPath _paths[ZT_MAX_PEER_NETWORK_PATHS];
-    Mutex _paths_m;
-    Mutex _bond_m;
+	_PeerPath _paths[ZT_MAX_PEER_NETWORK_PATHS];
+	Mutex _paths_m;
+	Mutex _bond_m;
 
-    bool _isLeaf;
+	bool _isLeaf;
 
-    Identity _id;
+	Identity _id;
 
-    unsigned int _directPathPushCutoffCount;
-    unsigned int _echoRequestCutoffCount;
+	unsigned int _directPathPushCutoffCount;
+	unsigned int _echoRequestCutoffCount;
 
-    AtomicCounter __refCount;
+	AtomicCounter __refCount;
 
-    bool _localMultipathSupported;
+	bool _localMultipathSupported;
 
-    volatile bool _shouldCollectPathStatistics;
+	volatile bool _shouldCollectPathStatistics;
 
-    int32_t _lastComputedAggregateMeanLatency;
+	int32_t _lastComputedAggregateMeanLatency;
 
-    SharedPtr<Bond> _bond;
+	SharedPtr<Bond> _bond;
 
 #ifndef ZT_NO_PEER_METRICS
-    prometheus::Histogram<uint64_t>& _peer_latency;
-    prometheus::simpleapi::gauge_metric_t _alive_path_count;
-    prometheus::simpleapi::gauge_metric_t _dead_path_count;
-    prometheus::simpleapi::counter_metric_t _incoming_packet;
-    prometheus::simpleapi::counter_metric_t _outgoing_packet;
-    prometheus::simpleapi::counter_metric_t _packet_errors;
+	prometheus::Histogram<uint64_t>& _peer_latency;
+	prometheus::simpleapi::gauge_metric_t _alive_path_count;
+	prometheus::simpleapi::gauge_metric_t _dead_path_count;
+	prometheus::simpleapi::counter_metric_t _incoming_packet;
+	prometheus::simpleapi::counter_metric_t _outgoing_packet;
+	prometheus::simpleapi::counter_metric_t _packet_errors;
 #endif
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 // Add a swap() for shared ptr's to peers to speed up peer sorts
 namespace std {
 template <> inline void swap(ZeroTier::SharedPtr<ZeroTier::Peer>& a, ZeroTier::SharedPtr<ZeroTier::Peer>& b)
 {
-    a.swap(b);
+	a.swap(b);
 }
-}   // namespace std
+}	// namespace std
 
 #endif

+ 457 - 457
node/Poly1305.cpp

@@ -22,8 +22,8 @@ namespace ZeroTier {
 namespace {
 
 typedef struct poly1305_context {
-    size_t aligner;
-    unsigned char opaque[136];
+	size_t aligner;
+	unsigned char opaque[136];
 } poly1305_context;
 
 #if (defined(_MSC_VER) || defined(__GNUC__)) && (defined(__amd64) || defined(__amd64__) || defined(__x86_64) || defined(__x86_64__) || defined(__AMD64) || defined(__AMD64__) || defined(_M_X64))
@@ -35,25 +35,25 @@ typedef struct poly1305_context {
 #include <intrin.h>
 
 typedef struct uint128_t {
-    unsigned long long lo;
-    unsigned long long hi;
+	unsigned long long lo;
+	unsigned long long hi;
 } uint128_t;
 
 #define MUL(out, x, y) out.lo = _umul128((x), (y), &out.hi)
 #define ADD(out, in)                                                                                                                                                                                                                           \
-    {                                                                                                                                                                                                                                          \
-        unsigned long long t = out.lo;                                                                                                                                                                                                         \
-        out.lo += in.lo;                                                                                                                                                                                                                       \
-        out.hi += (out.lo < t) + in.hi;                                                                                                                                                                                                        \
-    }
+	{                                                                                                                                                                                                                                          \
+		unsigned long long t = out.lo;                                                                                                                                                                                                         \
+		out.lo += in.lo;                                                                                                                                                                                                                       \
+		out.hi += (out.lo < t) + in.hi;                                                                                                                                                                                                        \
+	}
 #define ADDLO(out, in)                                                                                                                                                                                                                         \
-    {                                                                                                                                                                                                                                          \
-        unsigned long long t = out.lo;                                                                                                                                                                                                         \
-        out.lo += in;                                                                                                                                                                                                                          \
-        out.hi += (out.lo < t);                                                                                                                                                                                                                \
-    }
+	{                                                                                                                                                                                                                                          \
+		unsigned long long t = out.lo;                                                                                                                                                                                                         \
+		out.lo += in;                                                                                                                                                                                                                          \
+		out.hi += (out.lo < t);                                                                                                                                                                                                                \
+	}
 #define SHR(in, shift) (__shiftright128(in.lo, in.hi, (shift)))
-#define LO(in)         (in.lo)
+#define LO(in)		   (in.lo)
 
 //  #define POLY1305_NOINLINE __declspec(noinline)
 #elif defined(__GNUC__)
@@ -67,7 +67,7 @@ typedef unsigned uint128_t __attribute__((mode(TI)));
 #define ADD(out, in)   out += in
 #define ADDLO(out, in) out += in
 #define SHR(in, shift) (unsigned long long)(in >> (shift))
-#define LO(in)         (unsigned long long)(in)
+#define LO(in)		   (unsigned long long)(in)
 
 //  #define POLY1305_NOINLINE __attribute__((noinline))
 #endif
@@ -76,20 +76,20 @@ typedef unsigned uint128_t __attribute__((mode(TI)));
 
 /* 17 + sizeof(size_t) + 8*sizeof(unsigned long long) */
 typedef struct poly1305_state_internal_t {
-    unsigned long long r[3];
-    unsigned long long h[3];
-    unsigned long long pad[2];
-    size_t leftover;
-    unsigned char buffer[poly1305_block_size];
-    unsigned char final;
+	unsigned long long r[3];
+	unsigned long long h[3];
+	unsigned long long pad[2];
+	size_t leftover;
+	unsigned char buffer[poly1305_block_size];
+	unsigned char final;
 } poly1305_state_internal_t;
 
 #if defined(ZT_NO_TYPE_PUNNING) || (__BYTE_ORDER != __LITTLE_ENDIAN)
 static inline unsigned long long U8TO64(const unsigned char* p)
 {
-    return (
-        ((unsigned long long)(p[0] & 0xff)) | ((unsigned long long)(p[1] & 0xff) << 8) | ((unsigned long long)(p[2] & 0xff) << 16) | ((unsigned long long)(p[3] & 0xff) << 24) | ((unsigned long long)(p[4] & 0xff) << 32)
-        | ((unsigned long long)(p[5] & 0xff) << 40) | ((unsigned long long)(p[6] & 0xff) << 48) | ((unsigned long long)(p[7] & 0xff) << 56));
+	return (
+		((unsigned long long)(p[0] & 0xff)) | ((unsigned long long)(p[1] & 0xff) << 8) | ((unsigned long long)(p[2] & 0xff) << 16) | ((unsigned long long)(p[3] & 0xff) << 24) | ((unsigned long long)(p[4] & 0xff) << 32)
+		| ((unsigned long long)(p[5] & 0xff) << 40) | ((unsigned long long)(p[6] & 0xff) << 48) | ((unsigned long long)(p[7] & 0xff) << 56));
 }
 #else
 #define U8TO64(p) (*reinterpret_cast<const unsigned long long*>(p))
@@ -98,14 +98,14 @@ static inline unsigned long long U8TO64(const unsigned char* p)
 #if defined(ZT_NO_TYPE_PUNNING) || (__BYTE_ORDER != __LITTLE_ENDIAN)
 static inline void U64TO8(unsigned char* p, unsigned long long v)
 {
-    p[0] = (v) & 0xff;
-    p[1] = (v >> 8) & 0xff;
-    p[2] = (v >> 16) & 0xff;
-    p[3] = (v >> 24) & 0xff;
-    p[4] = (v >> 32) & 0xff;
-    p[5] = (v >> 40) & 0xff;
-    p[6] = (v >> 48) & 0xff;
-    p[7] = (v >> 56) & 0xff;
+	p[0] = (v) & 0xff;
+	p[1] = (v >> 8) & 0xff;
+	p[2] = (v >> 16) & 0xff;
+	p[3] = (v >> 24) & 0xff;
+	p[4] = (v >> 32) & 0xff;
+	p[5] = (v >> 40) & 0xff;
+	p[6] = (v >> 48) & 0xff;
+	p[7] = (v >> 56) & 0xff;
 }
 #else
 #define U64TO8(p, v) ((*reinterpret_cast<unsigned long long*>(p)) = (v))
@@ -113,191 +113,191 @@ static inline void U64TO8(unsigned char* p, unsigned long long v)
 
 static inline void poly1305_init(poly1305_context* ctx, const unsigned char key[32])
 {
-    poly1305_state_internal_t* st = (poly1305_state_internal_t*)ctx;
-    unsigned long long t0, t1;
+	poly1305_state_internal_t* st = (poly1305_state_internal_t*)ctx;
+	unsigned long long t0, t1;
 
-    /* r &= 0xffffffc0ffffffc0ffffffc0fffffff */
-    t0 = U8TO64(&key[0]);
-    t1 = U8TO64(&key[8]);
+	/* r &= 0xffffffc0ffffffc0ffffffc0fffffff */
+	t0 = U8TO64(&key[0]);
+	t1 = U8TO64(&key[8]);
 
-    st->r[0] = (t0) & 0xffc0fffffff;
-    st->r[1] = ((t0 >> 44) | (t1 << 20)) & 0xfffffc0ffff;
-    st->r[2] = ((t1 >> 24)) & 0x00ffffffc0f;
+	st->r[0] = (t0) & 0xffc0fffffff;
+	st->r[1] = ((t0 >> 44) | (t1 << 20)) & 0xfffffc0ffff;
+	st->r[2] = ((t1 >> 24)) & 0x00ffffffc0f;
 
-    /* h = 0 */
-    st->h[0] = 0;
-    st->h[1] = 0;
-    st->h[2] = 0;
+	/* h = 0 */
+	st->h[0] = 0;
+	st->h[1] = 0;
+	st->h[2] = 0;
 
-    /* save pad for later */
-    st->pad[0] = U8TO64(&key[16]);
-    st->pad[1] = U8TO64(&key[24]);
+	/* save pad for later */
+	st->pad[0] = U8TO64(&key[16]);
+	st->pad[1] = U8TO64(&key[24]);
 
-    st->leftover = 0;
-    st->final = 0;
+	st->leftover = 0;
+	st->final = 0;
 }
 
 static inline void poly1305_blocks(poly1305_state_internal_t* st, const unsigned char* m, size_t bytes)
 {
-    const unsigned long long hibit = (st->final) ? 0 : ((unsigned long long)1 << 40); /* 1 << 128 */
-    unsigned long long r0, r1, r2;
-    unsigned long long s1, s2;
-    unsigned long long h0, h1, h2;
-    unsigned long long c;
-    uint128_t d0, d1, d2, d;
-
-    r0 = st->r[0];
-    r1 = st->r[1];
-    r2 = st->r[2];
-
-    h0 = st->h[0];
-    h1 = st->h[1];
-    h2 = st->h[2];
-
-    s1 = r1 * (5 << 2);
-    s2 = r2 * (5 << 2);
-
-    while (bytes >= poly1305_block_size) {
-        unsigned long long t0, t1;
-
-        /* h += m[i] */
-        t0 = U8TO64(&m[0]);
-        t1 = U8TO64(&m[8]);
-
-        h0 += ((t0) & 0xfffffffffff);
-        h1 += (((t0 >> 44) | (t1 << 20)) & 0xfffffffffff);
-        h2 += (((t1 >> 24)) & 0x3ffffffffff) | hibit;
-
-        /* h *= r */
-        MUL(d0, h0, r0);
-        MUL(d, h1, s2);
-        ADD(d0, d);
-        MUL(d, h2, s1);
-        ADD(d0, d);
-        MUL(d1, h0, r1);
-        MUL(d, h1, r0);
-        ADD(d1, d);
-        MUL(d, h2, s2);
-        ADD(d1, d);
-        MUL(d2, h0, r2);
-        MUL(d, h1, r1);
-        ADD(d2, d);
-        MUL(d, h2, r0);
-        ADD(d2, d);
-
-        /* (partial) h %= p */
-        c = SHR(d0, 44);
-        h0 = LO(d0) & 0xfffffffffff;
-        ADDLO(d1, c);
-        c = SHR(d1, 44);
-        h1 = LO(d1) & 0xfffffffffff;
-        ADDLO(d2, c);
-        c = SHR(d2, 42);
-        h2 = LO(d2) & 0x3ffffffffff;
-        h0 += c * 5;
-        c = (h0 >> 44);
-        h0 = h0 & 0xfffffffffff;
-        h1 += c;
-
-        m += poly1305_block_size;
-        bytes -= poly1305_block_size;
-    }
-
-    st->h[0] = h0;
-    st->h[1] = h1;
-    st->h[2] = h2;
+	const unsigned long long hibit = (st->final) ? 0 : ((unsigned long long)1 << 40); /* 1 << 128 */
+	unsigned long long r0, r1, r2;
+	unsigned long long s1, s2;
+	unsigned long long h0, h1, h2;
+	unsigned long long c;
+	uint128_t d0, d1, d2, d;
+
+	r0 = st->r[0];
+	r1 = st->r[1];
+	r2 = st->r[2];
+
+	h0 = st->h[0];
+	h1 = st->h[1];
+	h2 = st->h[2];
+
+	s1 = r1 * (5 << 2);
+	s2 = r2 * (5 << 2);
+
+	while (bytes >= poly1305_block_size) {
+		unsigned long long t0, t1;
+
+		/* h += m[i] */
+		t0 = U8TO64(&m[0]);
+		t1 = U8TO64(&m[8]);
+
+		h0 += ((t0) & 0xfffffffffff);
+		h1 += (((t0 >> 44) | (t1 << 20)) & 0xfffffffffff);
+		h2 += (((t1 >> 24)) & 0x3ffffffffff) | hibit;
+
+		/* h *= r */
+		MUL(d0, h0, r0);
+		MUL(d, h1, s2);
+		ADD(d0, d);
+		MUL(d, h2, s1);
+		ADD(d0, d);
+		MUL(d1, h0, r1);
+		MUL(d, h1, r0);
+		ADD(d1, d);
+		MUL(d, h2, s2);
+		ADD(d1, d);
+		MUL(d2, h0, r2);
+		MUL(d, h1, r1);
+		ADD(d2, d);
+		MUL(d, h2, r0);
+		ADD(d2, d);
+
+		/* (partial) h %= p */
+		c = SHR(d0, 44);
+		h0 = LO(d0) & 0xfffffffffff;
+		ADDLO(d1, c);
+		c = SHR(d1, 44);
+		h1 = LO(d1) & 0xfffffffffff;
+		ADDLO(d2, c);
+		c = SHR(d2, 42);
+		h2 = LO(d2) & 0x3ffffffffff;
+		h0 += c * 5;
+		c = (h0 >> 44);
+		h0 = h0 & 0xfffffffffff;
+		h1 += c;
+
+		m += poly1305_block_size;
+		bytes -= poly1305_block_size;
+	}
+
+	st->h[0] = h0;
+	st->h[1] = h1;
+	st->h[2] = h2;
 }
 
 static inline void poly1305_finish(poly1305_context* ctx, unsigned char mac[16])
 {
-    poly1305_state_internal_t* st = (poly1305_state_internal_t*)ctx;
-    unsigned long long h0, h1, h2, c;
-    unsigned long long g0, g1, g2;
-    unsigned long long t0, t1;
-
-    /* process the remaining block */
-    if (st->leftover) {
-        size_t i = st->leftover;
-        st->buffer[i] = 1;
-        for (i = i + 1; i < poly1305_block_size; i++) {
-            st->buffer[i] = 0;
-        }
-        st->final = 1;
-        poly1305_blocks(st, st->buffer, poly1305_block_size);
-    }
-
-    /* fully carry h */
-    h0 = st->h[0];
-    h1 = st->h[1];
-    h2 = st->h[2];
-
-    c = (h1 >> 44);
-    h1 &= 0xfffffffffff;
-    h2 += c;
-    c = (h2 >> 42);
-    h2 &= 0x3ffffffffff;
-    h0 += c * 5;
-    c = (h0 >> 44);
-    h0 &= 0xfffffffffff;
-    h1 += c;
-    c = (h1 >> 44);
-    h1 &= 0xfffffffffff;
-    h2 += c;
-    c = (h2 >> 42);
-    h2 &= 0x3ffffffffff;
-    h0 += c * 5;
-    c = (h0 >> 44);
-    h0 &= 0xfffffffffff;
-    h1 += c;
-
-    /* compute h + -p */
-    g0 = h0 + 5;
-    c = (g0 >> 44);
-    g0 &= 0xfffffffffff;
-    g1 = h1 + c;
-    c = (g1 >> 44);
-    g1 &= 0xfffffffffff;
-    g2 = h2 + c - ((unsigned long long)1 << 42);
-
-    /* select h if h < p, or h + -p if h >= p */
-    c = (g2 >> ((sizeof(unsigned long long) * 8) - 1)) - 1;
-    g0 &= c;
-    g1 &= c;
-    g2 &= c;
-    c = ~c;
-    h0 = (h0 & c) | g0;
-    h1 = (h1 & c) | g1;
-    h2 = (h2 & c) | g2;
-
-    /* h = (h + pad) */
-    t0 = st->pad[0];
-    t1 = st->pad[1];
-
-    h0 += ((t0) & 0xfffffffffff);
-    c = (h0 >> 44);
-    h0 &= 0xfffffffffff;
-    h1 += (((t0 >> 44) | (t1 << 20)) & 0xfffffffffff) + c;
-    c = (h1 >> 44);
-    h1 &= 0xfffffffffff;
-    h2 += (((t1 >> 24)) & 0x3ffffffffff) + c;
-    h2 &= 0x3ffffffffff;
-
-    /* mac = h % (2^128) */
-    h0 = ((h0) | (h1 << 44));
-    h1 = ((h1 >> 20) | (h2 << 24));
-
-    U64TO8(&mac[0], h0);
-    U64TO8(&mac[8], h1);
-
-    /* zero out the state */
-    st->h[0] = 0;
-    st->h[1] = 0;
-    st->h[2] = 0;
-    st->r[0] = 0;
-    st->r[1] = 0;
-    st->r[2] = 0;
-    st->pad[0] = 0;
-    st->pad[1] = 0;
+	poly1305_state_internal_t* st = (poly1305_state_internal_t*)ctx;
+	unsigned long long h0, h1, h2, c;
+	unsigned long long g0, g1, g2;
+	unsigned long long t0, t1;
+
+	/* process the remaining block */
+	if (st->leftover) {
+		size_t i = st->leftover;
+		st->buffer[i] = 1;
+		for (i = i + 1; i < poly1305_block_size; i++) {
+			st->buffer[i] = 0;
+		}
+		st->final = 1;
+		poly1305_blocks(st, st->buffer, poly1305_block_size);
+	}
+
+	/* fully carry h */
+	h0 = st->h[0];
+	h1 = st->h[1];
+	h2 = st->h[2];
+
+	c = (h1 >> 44);
+	h1 &= 0xfffffffffff;
+	h2 += c;
+	c = (h2 >> 42);
+	h2 &= 0x3ffffffffff;
+	h0 += c * 5;
+	c = (h0 >> 44);
+	h0 &= 0xfffffffffff;
+	h1 += c;
+	c = (h1 >> 44);
+	h1 &= 0xfffffffffff;
+	h2 += c;
+	c = (h2 >> 42);
+	h2 &= 0x3ffffffffff;
+	h0 += c * 5;
+	c = (h0 >> 44);
+	h0 &= 0xfffffffffff;
+	h1 += c;
+
+	/* compute h + -p */
+	g0 = h0 + 5;
+	c = (g0 >> 44);
+	g0 &= 0xfffffffffff;
+	g1 = h1 + c;
+	c = (g1 >> 44);
+	g1 &= 0xfffffffffff;
+	g2 = h2 + c - ((unsigned long long)1 << 42);
+
+	/* select h if h < p, or h + -p if h >= p */
+	c = (g2 >> ((sizeof(unsigned long long) * 8) - 1)) - 1;
+	g0 &= c;
+	g1 &= c;
+	g2 &= c;
+	c = ~c;
+	h0 = (h0 & c) | g0;
+	h1 = (h1 & c) | g1;
+	h2 = (h2 & c) | g2;
+
+	/* h = (h + pad) */
+	t0 = st->pad[0];
+	t1 = st->pad[1];
+
+	h0 += ((t0) & 0xfffffffffff);
+	c = (h0 >> 44);
+	h0 &= 0xfffffffffff;
+	h1 += (((t0 >> 44) | (t1 << 20)) & 0xfffffffffff) + c;
+	c = (h1 >> 44);
+	h1 &= 0xfffffffffff;
+	h2 += (((t1 >> 24)) & 0x3ffffffffff) + c;
+	h2 &= 0x3ffffffffff;
+
+	/* mac = h % (2^128) */
+	h0 = ((h0) | (h1 << 44));
+	h1 = ((h1 >> 20) | (h2 << 24));
+
+	U64TO8(&mac[0], h0);
+	U64TO8(&mac[8], h1);
+
+	/* zero out the state */
+	st->h[0] = 0;
+	st->h[1] = 0;
+	st->h[2] = 0;
+	st->r[0] = 0;
+	st->r[1] = 0;
+	st->r[2] = 0;
+	st->pad[0] = 0;
+	st->pad[1] = 0;
 }
 
 //////////////////////////////////////////////////////////////////////////////
@@ -311,291 +311,291 @@ static inline void poly1305_finish(poly1305_context* ctx, unsigned char mac[16])
 
 /* 17 + sizeof(size_t) + 14*sizeof(unsigned long) */
 typedef struct poly1305_state_internal_t {
-    unsigned long r[5];
-    unsigned long h[5];
-    unsigned long pad[4];
-    size_t leftover;
-    unsigned char buffer[poly1305_block_size];
-    unsigned char final;
+	unsigned long r[5];
+	unsigned long h[5];
+	unsigned long pad[4];
+	size_t leftover;
+	unsigned char buffer[poly1305_block_size];
+	unsigned char final;
 } poly1305_state_internal_t;
 
 /* interpret four 8 bit unsigned integers as a 32 bit unsigned integer in little endian */
 static unsigned long U8TO32(const unsigned char* p)
 {
-    return (((unsigned long)(p[0] & 0xff)) | ((unsigned long)(p[1] & 0xff) << 8) | ((unsigned long)(p[2] & 0xff) << 16) | ((unsigned long)(p[3] & 0xff) << 24));
+	return (((unsigned long)(p[0] & 0xff)) | ((unsigned long)(p[1] & 0xff) << 8) | ((unsigned long)(p[2] & 0xff) << 16) | ((unsigned long)(p[3] & 0xff) << 24));
 }
 
 /* store a 32 bit unsigned integer as four 8 bit unsigned integers in little endian */
 static void U32TO8(unsigned char* p, unsigned long v)
 {
-    p[0] = (v) & 0xff;
-    p[1] = (v >> 8) & 0xff;
-    p[2] = (v >> 16) & 0xff;
-    p[3] = (v >> 24) & 0xff;
+	p[0] = (v) & 0xff;
+	p[1] = (v >> 8) & 0xff;
+	p[2] = (v >> 16) & 0xff;
+	p[3] = (v >> 24) & 0xff;
 }
 
 static inline void poly1305_init(poly1305_context* ctx, const unsigned char key[32])
 {
-    poly1305_state_internal_t* st = (poly1305_state_internal_t*)ctx;
-
-    /* r &= 0xffffffc0ffffffc0ffffffc0fffffff */
-    st->r[0] = (U8TO32(&key[0])) & 0x3ffffff;
-    st->r[1] = (U8TO32(&key[3]) >> 2) & 0x3ffff03;
-    st->r[2] = (U8TO32(&key[6]) >> 4) & 0x3ffc0ff;
-    st->r[3] = (U8TO32(&key[9]) >> 6) & 0x3f03fff;
-    st->r[4] = (U8TO32(&key[12]) >> 8) & 0x00fffff;
-
-    /* h = 0 */
-    st->h[0] = 0;
-    st->h[1] = 0;
-    st->h[2] = 0;
-    st->h[3] = 0;
-    st->h[4] = 0;
-
-    /* save pad for later */
-    st->pad[0] = U8TO32(&key[16]);
-    st->pad[1] = U8TO32(&key[20]);
-    st->pad[2] = U8TO32(&key[24]);
-    st->pad[3] = U8TO32(&key[28]);
-
-    st->leftover = 0;
-    st->final = 0;
+	poly1305_state_internal_t* st = (poly1305_state_internal_t*)ctx;
+
+	/* r &= 0xffffffc0ffffffc0ffffffc0fffffff */
+	st->r[0] = (U8TO32(&key[0])) & 0x3ffffff;
+	st->r[1] = (U8TO32(&key[3]) >> 2) & 0x3ffff03;
+	st->r[2] = (U8TO32(&key[6]) >> 4) & 0x3ffc0ff;
+	st->r[3] = (U8TO32(&key[9]) >> 6) & 0x3f03fff;
+	st->r[4] = (U8TO32(&key[12]) >> 8) & 0x00fffff;
+
+	/* h = 0 */
+	st->h[0] = 0;
+	st->h[1] = 0;
+	st->h[2] = 0;
+	st->h[3] = 0;
+	st->h[4] = 0;
+
+	/* save pad for later */
+	st->pad[0] = U8TO32(&key[16]);
+	st->pad[1] = U8TO32(&key[20]);
+	st->pad[2] = U8TO32(&key[24]);
+	st->pad[3] = U8TO32(&key[28]);
+
+	st->leftover = 0;
+	st->final = 0;
 }
 
 static inline void poly1305_blocks(poly1305_state_internal_t* st, const unsigned char* m, size_t bytes)
 {
-    const unsigned long hibit = (st->final) ? 0 : (1 << 24); /* 1 << 128 */
-    unsigned long r0, r1, r2, r3, r4;
-    unsigned long s1, s2, s3, s4;
-    unsigned long h0, h1, h2, h3, h4;
-    unsigned long long d0, d1, d2, d3, d4;
-    unsigned long c;
-
-    r0 = st->r[0];
-    r1 = st->r[1];
-    r2 = st->r[2];
-    r3 = st->r[3];
-    r4 = st->r[4];
-
-    s1 = r1 * 5;
-    s2 = r2 * 5;
-    s3 = r3 * 5;
-    s4 = r4 * 5;
-
-    h0 = st->h[0];
-    h1 = st->h[1];
-    h2 = st->h[2];
-    h3 = st->h[3];
-    h4 = st->h[4];
-
-    while (bytes >= poly1305_block_size) {
-        /* h += m[i] */
-        h0 += (U8TO32(m + 0)) & 0x3ffffff;
-        h1 += (U8TO32(m + 3) >> 2) & 0x3ffffff;
-        h2 += (U8TO32(m + 6) >> 4) & 0x3ffffff;
-        h3 += (U8TO32(m + 9) >> 6) & 0x3ffffff;
-        h4 += (U8TO32(m + 12) >> 8) | hibit;
-
-        /* h *= r */
-        d0 = ((unsigned long long)h0 * r0) + ((unsigned long long)h1 * s4) + ((unsigned long long)h2 * s3) + ((unsigned long long)h3 * s2) + ((unsigned long long)h4 * s1);
-        d1 = ((unsigned long long)h0 * r1) + ((unsigned long long)h1 * r0) + ((unsigned long long)h2 * s4) + ((unsigned long long)h3 * s3) + ((unsigned long long)h4 * s2);
-        d2 = ((unsigned long long)h0 * r2) + ((unsigned long long)h1 * r1) + ((unsigned long long)h2 * r0) + ((unsigned long long)h3 * s4) + ((unsigned long long)h4 * s3);
-        d3 = ((unsigned long long)h0 * r3) + ((unsigned long long)h1 * r2) + ((unsigned long long)h2 * r1) + ((unsigned long long)h3 * r0) + ((unsigned long long)h4 * s4);
-        d4 = ((unsigned long long)h0 * r4) + ((unsigned long long)h1 * r3) + ((unsigned long long)h2 * r2) + ((unsigned long long)h3 * r1) + ((unsigned long long)h4 * r0);
-
-        /* (partial) h %= p */
-        c = (unsigned long)(d0 >> 26);
-        h0 = (unsigned long)d0 & 0x3ffffff;
-        d1 += c;
-        c = (unsigned long)(d1 >> 26);
-        h1 = (unsigned long)d1 & 0x3ffffff;
-        d2 += c;
-        c = (unsigned long)(d2 >> 26);
-        h2 = (unsigned long)d2 & 0x3ffffff;
-        d3 += c;
-        c = (unsigned long)(d3 >> 26);
-        h3 = (unsigned long)d3 & 0x3ffffff;
-        d4 += c;
-        c = (unsigned long)(d4 >> 26);
-        h4 = (unsigned long)d4 & 0x3ffffff;
-        h0 += c * 5;
-        c = (h0 >> 26);
-        h0 = h0 & 0x3ffffff;
-        h1 += c;
-
-        m += poly1305_block_size;
-        bytes -= poly1305_block_size;
-    }
-
-    st->h[0] = h0;
-    st->h[1] = h1;
-    st->h[2] = h2;
-    st->h[3] = h3;
-    st->h[4] = h4;
+	const unsigned long hibit = (st->final) ? 0 : (1 << 24); /* 1 << 128 */
+	unsigned long r0, r1, r2, r3, r4;
+	unsigned long s1, s2, s3, s4;
+	unsigned long h0, h1, h2, h3, h4;
+	unsigned long long d0, d1, d2, d3, d4;
+	unsigned long c;
+
+	r0 = st->r[0];
+	r1 = st->r[1];
+	r2 = st->r[2];
+	r3 = st->r[3];
+	r4 = st->r[4];
+
+	s1 = r1 * 5;
+	s2 = r2 * 5;
+	s3 = r3 * 5;
+	s4 = r4 * 5;
+
+	h0 = st->h[0];
+	h1 = st->h[1];
+	h2 = st->h[2];
+	h3 = st->h[3];
+	h4 = st->h[4];
+
+	while (bytes >= poly1305_block_size) {
+		/* h += m[i] */
+		h0 += (U8TO32(m + 0)) & 0x3ffffff;
+		h1 += (U8TO32(m + 3) >> 2) & 0x3ffffff;
+		h2 += (U8TO32(m + 6) >> 4) & 0x3ffffff;
+		h3 += (U8TO32(m + 9) >> 6) & 0x3ffffff;
+		h4 += (U8TO32(m + 12) >> 8) | hibit;
+
+		/* h *= r */
+		d0 = ((unsigned long long)h0 * r0) + ((unsigned long long)h1 * s4) + ((unsigned long long)h2 * s3) + ((unsigned long long)h3 * s2) + ((unsigned long long)h4 * s1);
+		d1 = ((unsigned long long)h0 * r1) + ((unsigned long long)h1 * r0) + ((unsigned long long)h2 * s4) + ((unsigned long long)h3 * s3) + ((unsigned long long)h4 * s2);
+		d2 = ((unsigned long long)h0 * r2) + ((unsigned long long)h1 * r1) + ((unsigned long long)h2 * r0) + ((unsigned long long)h3 * s4) + ((unsigned long long)h4 * s3);
+		d3 = ((unsigned long long)h0 * r3) + ((unsigned long long)h1 * r2) + ((unsigned long long)h2 * r1) + ((unsigned long long)h3 * r0) + ((unsigned long long)h4 * s4);
+		d4 = ((unsigned long long)h0 * r4) + ((unsigned long long)h1 * r3) + ((unsigned long long)h2 * r2) + ((unsigned long long)h3 * r1) + ((unsigned long long)h4 * r0);
+
+		/* (partial) h %= p */
+		c = (unsigned long)(d0 >> 26);
+		h0 = (unsigned long)d0 & 0x3ffffff;
+		d1 += c;
+		c = (unsigned long)(d1 >> 26);
+		h1 = (unsigned long)d1 & 0x3ffffff;
+		d2 += c;
+		c = (unsigned long)(d2 >> 26);
+		h2 = (unsigned long)d2 & 0x3ffffff;
+		d3 += c;
+		c = (unsigned long)(d3 >> 26);
+		h3 = (unsigned long)d3 & 0x3ffffff;
+		d4 += c;
+		c = (unsigned long)(d4 >> 26);
+		h4 = (unsigned long)d4 & 0x3ffffff;
+		h0 += c * 5;
+		c = (h0 >> 26);
+		h0 = h0 & 0x3ffffff;
+		h1 += c;
+
+		m += poly1305_block_size;
+		bytes -= poly1305_block_size;
+	}
+
+	st->h[0] = h0;
+	st->h[1] = h1;
+	st->h[2] = h2;
+	st->h[3] = h3;
+	st->h[4] = h4;
 }
 
 static inline void poly1305_finish(poly1305_context* ctx, unsigned char mac[16])
 {
-    poly1305_state_internal_t* st = (poly1305_state_internal_t*)ctx;
-    unsigned long h0, h1, h2, h3, h4, c;
-    unsigned long g0, g1, g2, g3, g4;
-    unsigned long long f;
-    unsigned long mask;
-
-    /* process the remaining block */
-    if (st->leftover) {
-        size_t i = st->leftover;
-        st->buffer[i++] = 1;
-        for (; i < poly1305_block_size; i++) {
-            st->buffer[i] = 0;
-        }
-        st->final = 1;
-        poly1305_blocks(st, st->buffer, poly1305_block_size);
-    }
-
-    /* fully carry h */
-    h0 = st->h[0];
-    h1 = st->h[1];
-    h2 = st->h[2];
-    h3 = st->h[3];
-    h4 = st->h[4];
-
-    c = h1 >> 26;
-    h1 = h1 & 0x3ffffff;
-    h2 += c;
-    c = h2 >> 26;
-    h2 = h2 & 0x3ffffff;
-    h3 += c;
-    c = h3 >> 26;
-    h3 = h3 & 0x3ffffff;
-    h4 += c;
-    c = h4 >> 26;
-    h4 = h4 & 0x3ffffff;
-    h0 += c * 5;
-    c = h0 >> 26;
-    h0 = h0 & 0x3ffffff;
-    h1 += c;
-
-    /* compute h + -p */
-    g0 = h0 + 5;
-    c = g0 >> 26;
-    g0 &= 0x3ffffff;
-    g1 = h1 + c;
-    c = g1 >> 26;
-    g1 &= 0x3ffffff;
-    g2 = h2 + c;
-    c = g2 >> 26;
-    g2 &= 0x3ffffff;
-    g3 = h3 + c;
-    c = g3 >> 26;
-    g3 &= 0x3ffffff;
-    g4 = h4 + c - (1 << 26);
-
-    /* select h if h < p, or h + -p if h >= p */
-    mask = (g4 >> ((sizeof(unsigned long) * 8) - 1)) - 1;
-    g0 &= mask;
-    g1 &= mask;
-    g2 &= mask;
-    g3 &= mask;
-    g4 &= mask;
-    mask = ~mask;
-    h0 = (h0 & mask) | g0;
-    h1 = (h1 & mask) | g1;
-    h2 = (h2 & mask) | g2;
-    h3 = (h3 & mask) | g3;
-    h4 = (h4 & mask) | g4;
-
-    /* h = h % (2^128) */
-    h0 = ((h0) | (h1 << 26)) & 0xffffffff;
-    h1 = ((h1 >> 6) | (h2 << 20)) & 0xffffffff;
-    h2 = ((h2 >> 12) | (h3 << 14)) & 0xffffffff;
-    h3 = ((h3 >> 18) | (h4 << 8)) & 0xffffffff;
-
-    /* mac = (h + pad) % (2^128) */
-    f = (unsigned long long)h0 + st->pad[0];
-    h0 = (unsigned long)f;
-    f = (unsigned long long)h1 + st->pad[1] + (f >> 32);
-    h1 = (unsigned long)f;
-    f = (unsigned long long)h2 + st->pad[2] + (f >> 32);
-    h2 = (unsigned long)f;
-    f = (unsigned long long)h3 + st->pad[3] + (f >> 32);
-    h3 = (unsigned long)f;
-
-    U32TO8(mac + 0, h0);
-    U32TO8(mac + 4, h1);
-    U32TO8(mac + 8, h2);
-    U32TO8(mac + 12, h3);
-
-    /* zero out the state */
-    st->h[0] = 0;
-    st->h[1] = 0;
-    st->h[2] = 0;
-    st->h[3] = 0;
-    st->h[4] = 0;
-    st->r[0] = 0;
-    st->r[1] = 0;
-    st->r[2] = 0;
-    st->r[3] = 0;
-    st->r[4] = 0;
-    st->pad[0] = 0;
-    st->pad[1] = 0;
-    st->pad[2] = 0;
-    st->pad[3] = 0;
+	poly1305_state_internal_t* st = (poly1305_state_internal_t*)ctx;
+	unsigned long h0, h1, h2, h3, h4, c;
+	unsigned long g0, g1, g2, g3, g4;
+	unsigned long long f;
+	unsigned long mask;
+
+	/* process the remaining block */
+	if (st->leftover) {
+		size_t i = st->leftover;
+		st->buffer[i++] = 1;
+		for (; i < poly1305_block_size; i++) {
+			st->buffer[i] = 0;
+		}
+		st->final = 1;
+		poly1305_blocks(st, st->buffer, poly1305_block_size);
+	}
+
+	/* fully carry h */
+	h0 = st->h[0];
+	h1 = st->h[1];
+	h2 = st->h[2];
+	h3 = st->h[3];
+	h4 = st->h[4];
+
+	c = h1 >> 26;
+	h1 = h1 & 0x3ffffff;
+	h2 += c;
+	c = h2 >> 26;
+	h2 = h2 & 0x3ffffff;
+	h3 += c;
+	c = h3 >> 26;
+	h3 = h3 & 0x3ffffff;
+	h4 += c;
+	c = h4 >> 26;
+	h4 = h4 & 0x3ffffff;
+	h0 += c * 5;
+	c = h0 >> 26;
+	h0 = h0 & 0x3ffffff;
+	h1 += c;
+
+	/* compute h + -p */
+	g0 = h0 + 5;
+	c = g0 >> 26;
+	g0 &= 0x3ffffff;
+	g1 = h1 + c;
+	c = g1 >> 26;
+	g1 &= 0x3ffffff;
+	g2 = h2 + c;
+	c = g2 >> 26;
+	g2 &= 0x3ffffff;
+	g3 = h3 + c;
+	c = g3 >> 26;
+	g3 &= 0x3ffffff;
+	g4 = h4 + c - (1 << 26);
+
+	/* select h if h < p, or h + -p if h >= p */
+	mask = (g4 >> ((sizeof(unsigned long) * 8) - 1)) - 1;
+	g0 &= mask;
+	g1 &= mask;
+	g2 &= mask;
+	g3 &= mask;
+	g4 &= mask;
+	mask = ~mask;
+	h0 = (h0 & mask) | g0;
+	h1 = (h1 & mask) | g1;
+	h2 = (h2 & mask) | g2;
+	h3 = (h3 & mask) | g3;
+	h4 = (h4 & mask) | g4;
+
+	/* h = h % (2^128) */
+	h0 = ((h0) | (h1 << 26)) & 0xffffffff;
+	h1 = ((h1 >> 6) | (h2 << 20)) & 0xffffffff;
+	h2 = ((h2 >> 12) | (h3 << 14)) & 0xffffffff;
+	h3 = ((h3 >> 18) | (h4 << 8)) & 0xffffffff;
+
+	/* mac = (h + pad) % (2^128) */
+	f = (unsigned long long)h0 + st->pad[0];
+	h0 = (unsigned long)f;
+	f = (unsigned long long)h1 + st->pad[1] + (f >> 32);
+	h1 = (unsigned long)f;
+	f = (unsigned long long)h2 + st->pad[2] + (f >> 32);
+	h2 = (unsigned long)f;
+	f = (unsigned long long)h3 + st->pad[3] + (f >> 32);
+	h3 = (unsigned long)f;
+
+	U32TO8(mac + 0, h0);
+	U32TO8(mac + 4, h1);
+	U32TO8(mac + 8, h2);
+	U32TO8(mac + 12, h3);
+
+	/* zero out the state */
+	st->h[0] = 0;
+	st->h[1] = 0;
+	st->h[2] = 0;
+	st->h[3] = 0;
+	st->h[4] = 0;
+	st->r[0] = 0;
+	st->r[1] = 0;
+	st->r[2] = 0;
+	st->r[3] = 0;
+	st->r[4] = 0;
+	st->pad[0] = 0;
+	st->pad[1] = 0;
+	st->pad[2] = 0;
+	st->pad[3] = 0;
 }
 
 //////////////////////////////////////////////////////////////////////////////
 
-#endif   // MSC/GCC or not
+#endif	 // MSC/GCC or not
 
 static inline void poly1305_update(poly1305_context* ctx, const unsigned char* m, size_t bytes)
 {
-    poly1305_state_internal_t* st = (poly1305_state_internal_t*)ctx;
-    size_t i;
-
-    /* handle leftover */
-    if (st->leftover) {
-        size_t want = (poly1305_block_size - st->leftover);
-        if (want > bytes) {
-            want = bytes;
-        }
-        for (i = 0; i < want; i++) {
-            st->buffer[st->leftover + i] = m[i];
-        }
-        bytes -= want;
-        m += want;
-        st->leftover += want;
-        if (st->leftover < poly1305_block_size) {
-            return;
-        }
-        poly1305_blocks(st, st->buffer, poly1305_block_size);
-        st->leftover = 0;
-    }
-
-    /* process full blocks */
-    if (bytes >= poly1305_block_size) {
-        size_t want = (bytes & ~(poly1305_block_size - 1));
-        poly1305_blocks(st, m, want);
-        m += want;
-        bytes -= want;
-    }
-
-    /* store leftover */
-    if (bytes) {
-        for (i = 0; i < bytes; i++) {
-            st->buffer[st->leftover + i] = m[i];
-        }
-        st->leftover += bytes;
-    }
+	poly1305_state_internal_t* st = (poly1305_state_internal_t*)ctx;
+	size_t i;
+
+	/* handle leftover */
+	if (st->leftover) {
+		size_t want = (poly1305_block_size - st->leftover);
+		if (want > bytes) {
+			want = bytes;
+		}
+		for (i = 0; i < want; i++) {
+			st->buffer[st->leftover + i] = m[i];
+		}
+		bytes -= want;
+		m += want;
+		st->leftover += want;
+		if (st->leftover < poly1305_block_size) {
+			return;
+		}
+		poly1305_blocks(st, st->buffer, poly1305_block_size);
+		st->leftover = 0;
+	}
+
+	/* process full blocks */
+	if (bytes >= poly1305_block_size) {
+		size_t want = (bytes & ~(poly1305_block_size - 1));
+		poly1305_blocks(st, m, want);
+		m += want;
+		bytes -= want;
+	}
+
+	/* store leftover */
+	if (bytes) {
+		for (i = 0; i < bytes; i++) {
+			st->buffer[st->leftover + i] = m[i];
+		}
+		st->leftover += bytes;
+	}
 }
 
-}   // anonymous namespace
+}	// anonymous namespace
 
 void Poly1305::compute(void* auth, const void* data, unsigned int len, const void* key)
 {
-    poly1305_context ctx;
-    poly1305_init(&ctx, reinterpret_cast<const unsigned char*>(key));
-    poly1305_update(&ctx, reinterpret_cast<const unsigned char*>(data), (size_t)len);
-    poly1305_finish(&ctx, reinterpret_cast<unsigned char*>(auth));
+	poly1305_context ctx;
+	poly1305_init(&ctx, reinterpret_cast<const unsigned char*>(key));
+	poly1305_update(&ctx, reinterpret_cast<const unsigned char*>(data), (size_t)len);
+	poly1305_finish(&ctx, reinterpret_cast<unsigned char*>(auth));
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 10 - 10
node/Poly1305.hpp

@@ -32,17 +32,17 @@ namespace ZeroTier {
  */
 class Poly1305 {
   public:
-    /**
-     * Compute a one-time authentication code
-     *
-     * @param auth Buffer to receive code -- MUST be 16 bytes in length
-     * @param data Data to authenticate
-     * @param len Length of data to authenticate in bytes
-     * @param key 32-byte one-time use key to authenticate data (must not be reused)
-     */
-    static void compute(void* auth, const void* data, unsigned int len, const void* key);
+	/**
+	 * Compute a one-time authentication code
+	 *
+	 * @param auth Buffer to receive code -- MUST be 16 bytes in length
+	 * @param data Data to authenticate
+	 * @param len Length of data to authenticate in bytes
+	 * @param key 32-byte one-time use key to authenticate data (must not be reused)
+	 */
+	static void compute(void* auth, const void* data, unsigned int len, const void* key);
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 17 - 17
node/Revocation.cpp

@@ -24,22 +24,22 @@ namespace ZeroTier {
 
 int Revocation::verify(const RuntimeEnvironment* RR, void* tPtr) const
 {
-    if ((! _signedBy) || (_signedBy != Network::controllerFor(_networkId))) {
-        return -1;
-    }
-    const Identity id(RR->topology->getIdentity(tPtr, _signedBy));
-    if (! id) {
-        RR->sw->requestWhois(tPtr, RR->node->now(), _signedBy);
-        return 1;
-    }
-    try {
-        Buffer<sizeof(Revocation) + 64> tmp;
-        this->serialize(tmp, true);
-        return (id.verify(tmp.data(), tmp.size(), _signature) ? 0 : -1);
-    }
-    catch (...) {
-        return -1;
-    }
+	if ((! _signedBy) || (_signedBy != Network::controllerFor(_networkId))) {
+		return -1;
+	}
+	const Identity id(RR->topology->getIdentity(tPtr, _signedBy));
+	if (! id) {
+		RR->sw->requestWhois(tPtr, RR->node->now(), _signedBy);
+		return 1;
+	}
+	try {
+		Buffer<sizeof(Revocation) + 64> tmp;
+		this->serialize(tmp, true);
+		return (id.verify(tmp.data(), tmp.size(), _signature) ? 0 : -1);
+	}
+	catch (...) {
+		return -1;
+	}
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 177 - 177
node/Revocation.hpp

@@ -42,186 +42,186 @@ class RuntimeEnvironment;
  */
 class Revocation : public Credential {
   public:
-    static inline Credential::Type credentialType()
-    {
-        return Credential::CREDENTIAL_TYPE_REVOCATION;
-    }
-
-    Revocation() : _id(0), _credentialId(0), _networkId(0), _threshold(0), _flags(0), _target(), _signedBy(), _type(Credential::CREDENTIAL_TYPE_NULL)
-    {
-        memset(_signature.data, 0, sizeof(_signature.data));
-    }
-
-    /**
-     * @param i ID (arbitrary for revocations, currently random)
-     * @param nwid Network ID
-     * @param cid Credential ID being revoked (0 for all or for COMs, which lack IDs)
-     * @param thr Revocation time threshold before which credentials will be revoked
-     * @param fl Flags
-     * @param tgt Target node whose credential(s) are being revoked
-     * @param ct Credential type being revoked
-     */
-    Revocation(const uint32_t i, const uint64_t nwid, const uint32_t cid, const int64_t thr, const uint64_t fl, const Address& tgt, const Credential::Type ct)
-        : _id(i)
-        , _credentialId(cid)
-        , _networkId(nwid)
-        , _threshold(thr)
-        , _flags(fl)
-        , _target(tgt)
-        , _signedBy()
-        , _type(ct)
-    {
-        memset(_signature.data, 0, sizeof(_signature.data));
-    }
-
-    inline uint32_t id() const
-    {
-        return _id;
-    }
-    inline uint32_t credentialId() const
-    {
-        return _credentialId;
-    }
-    inline uint64_t networkId() const
-    {
-        return _networkId;
-    }
-    inline int64_t threshold() const
-    {
-        return _threshold;
-    }
-    inline const Address& target() const
-    {
-        return _target;
-    }
-    inline const Address& signer() const
-    {
-        return _signedBy;
-    }
-    inline Credential::Type type() const
-    {
-        return _type;
-    }
-
-    inline bool fastPropagate() const
-    {
-        return ((_flags & ZT_REVOCATION_FLAG_FAST_PROPAGATE) != 0);
-    }
-
-    /**
-     * @param signer Signing identity, must have private key
-     * @return True if signature was successful
-     */
-    inline bool sign(const Identity& signer)
-    {
-        if (signer.hasPrivate()) {
-            Buffer<sizeof(Revocation) + 64> tmp;
-            _signedBy = signer.address();
-            this->serialize(tmp, true);
-            _signature = signer.sign(tmp.data(), tmp.size());
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Verify this revocation's signature
-     *
-     * @param RR Runtime environment to provide for peer lookup, etc.
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or chain
-     */
-    int verify(const RuntimeEnvironment* RR, void* tPtr) const;
-
-    template <unsigned int C> inline void serialize(Buffer<C>& b, const bool forSign = false) const
-    {
-        if (forSign) {
-            b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
-        }
-
-        b.append((uint32_t)0);   // 4 unused bytes, currently set to 0
-        b.append(_id);
-        b.append(_networkId);
-        b.append((uint32_t)0);   // 4 unused bytes, currently set to 0
-        b.append(_credentialId);
-        b.append(_threshold);
-        b.append(_flags);
-        _target.appendTo(b);
-        _signedBy.appendTo(b);
-        b.append((uint8_t)_type);
-
-        if (! forSign) {
-            b.append((uint8_t)1);   // 1 == Ed25519 signature
-            b.append((uint16_t)ZT_ECC_SIGNATURE_LEN);
-            b.append(_signature.data, ZT_ECC_SIGNATURE_LEN);
-        }
-
-        // This is the size of any additional fields, currently 0.
-        b.append((uint16_t)0);
-
-        if (forSign) {
-            b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
-        }
-    }
-
-    template <unsigned int C> inline unsigned int deserialize(const Buffer<C>& b, unsigned int startAt = 0)
-    {
-        *this = Revocation();
-
-        unsigned int p = startAt;
-
-        p += 4;   // 4 bytes, currently unused
-        _id = b.template at<uint32_t>(p);
-        p += 4;
-        _networkId = b.template at<uint64_t>(p);
-        p += 8;
-        p += 4;   // 4 bytes, currently unused
-        _credentialId = b.template at<uint32_t>(p);
-        p += 4;
-        _threshold = (int64_t)b.template at<uint64_t>(p);
-        p += 8;
-        _flags = b.template at<uint64_t>(p);
-        p += 8;
-        _target.setTo(b.field(p, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
-        p += ZT_ADDRESS_LENGTH;
-        _signedBy.setTo(b.field(p, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
-        p += ZT_ADDRESS_LENGTH;
-        _type = (Credential::Type)b[p++];
-
-        if (b[p++] == 1) {
-            if (b.template at<uint16_t>(p) == ZT_ECC_SIGNATURE_LEN) {
-                p += 2;
-                memcpy(_signature.data, b.field(p, ZT_ECC_SIGNATURE_LEN), ZT_ECC_SIGNATURE_LEN);
-                p += ZT_ECC_SIGNATURE_LEN;
-            }
-            else {
-                throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_CRYPTOGRAPHIC_TOKEN;
-            }
-        }
-        else {
-            p += 2 + b.template at<uint16_t>(p);
-        }
-
-        p += 2 + b.template at<uint16_t>(p);
-        if (p > b.size()) {
-            throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW;
-        }
-
-        return (p - startAt);
-    }
+	static inline Credential::Type credentialType()
+	{
+		return Credential::CREDENTIAL_TYPE_REVOCATION;
+	}
+
+	Revocation() : _id(0), _credentialId(0), _networkId(0), _threshold(0), _flags(0), _target(), _signedBy(), _type(Credential::CREDENTIAL_TYPE_NULL)
+	{
+		memset(_signature.data, 0, sizeof(_signature.data));
+	}
+
+	/**
+	 * @param i ID (arbitrary for revocations, currently random)
+	 * @param nwid Network ID
+	 * @param cid Credential ID being revoked (0 for all or for COMs, which lack IDs)
+	 * @param thr Revocation time threshold before which credentials will be revoked
+	 * @param fl Flags
+	 * @param tgt Target node whose credential(s) are being revoked
+	 * @param ct Credential type being revoked
+	 */
+	Revocation(const uint32_t i, const uint64_t nwid, const uint32_t cid, const int64_t thr, const uint64_t fl, const Address& tgt, const Credential::Type ct)
+		: _id(i)
+		, _credentialId(cid)
+		, _networkId(nwid)
+		, _threshold(thr)
+		, _flags(fl)
+		, _target(tgt)
+		, _signedBy()
+		, _type(ct)
+	{
+		memset(_signature.data, 0, sizeof(_signature.data));
+	}
+
+	inline uint32_t id() const
+	{
+		return _id;
+	}
+	inline uint32_t credentialId() const
+	{
+		return _credentialId;
+	}
+	inline uint64_t networkId() const
+	{
+		return _networkId;
+	}
+	inline int64_t threshold() const
+	{
+		return _threshold;
+	}
+	inline const Address& target() const
+	{
+		return _target;
+	}
+	inline const Address& signer() const
+	{
+		return _signedBy;
+	}
+	inline Credential::Type type() const
+	{
+		return _type;
+	}
+
+	inline bool fastPropagate() const
+	{
+		return ((_flags & ZT_REVOCATION_FLAG_FAST_PROPAGATE) != 0);
+	}
+
+	/**
+	 * @param signer Signing identity, must have private key
+	 * @return True if signature was successful
+	 */
+	inline bool sign(const Identity& signer)
+	{
+		if (signer.hasPrivate()) {
+			Buffer<sizeof(Revocation) + 64> tmp;
+			_signedBy = signer.address();
+			this->serialize(tmp, true);
+			_signature = signer.sign(tmp.data(), tmp.size());
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Verify this revocation's signature
+	 *
+	 * @param RR Runtime environment to provide for peer lookup, etc.
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or chain
+	 */
+	int verify(const RuntimeEnvironment* RR, void* tPtr) const;
+
+	template <unsigned int C> inline void serialize(Buffer<C>& b, const bool forSign = false) const
+	{
+		if (forSign) {
+			b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
+		}
+
+		b.append((uint32_t)0);	 // 4 unused bytes, currently set to 0
+		b.append(_id);
+		b.append(_networkId);
+		b.append((uint32_t)0);	 // 4 unused bytes, currently set to 0
+		b.append(_credentialId);
+		b.append(_threshold);
+		b.append(_flags);
+		_target.appendTo(b);
+		_signedBy.appendTo(b);
+		b.append((uint8_t)_type);
+
+		if (! forSign) {
+			b.append((uint8_t)1);	// 1 == Ed25519 signature
+			b.append((uint16_t)ZT_ECC_SIGNATURE_LEN);
+			b.append(_signature.data, ZT_ECC_SIGNATURE_LEN);
+		}
+
+		// This is the size of any additional fields, currently 0.
+		b.append((uint16_t)0);
+
+		if (forSign) {
+			b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
+		}
+	}
+
+	template <unsigned int C> inline unsigned int deserialize(const Buffer<C>& b, unsigned int startAt = 0)
+	{
+		*this = Revocation();
+
+		unsigned int p = startAt;
+
+		p += 4;	  // 4 bytes, currently unused
+		_id = b.template at<uint32_t>(p);
+		p += 4;
+		_networkId = b.template at<uint64_t>(p);
+		p += 8;
+		p += 4;	  // 4 bytes, currently unused
+		_credentialId = b.template at<uint32_t>(p);
+		p += 4;
+		_threshold = (int64_t)b.template at<uint64_t>(p);
+		p += 8;
+		_flags = b.template at<uint64_t>(p);
+		p += 8;
+		_target.setTo(b.field(p, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
+		p += ZT_ADDRESS_LENGTH;
+		_signedBy.setTo(b.field(p, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
+		p += ZT_ADDRESS_LENGTH;
+		_type = (Credential::Type)b[p++];
+
+		if (b[p++] == 1) {
+			if (b.template at<uint16_t>(p) == ZT_ECC_SIGNATURE_LEN) {
+				p += 2;
+				memcpy(_signature.data, b.field(p, ZT_ECC_SIGNATURE_LEN), ZT_ECC_SIGNATURE_LEN);
+				p += ZT_ECC_SIGNATURE_LEN;
+			}
+			else {
+				throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_CRYPTOGRAPHIC_TOKEN;
+			}
+		}
+		else {
+			p += 2 + b.template at<uint16_t>(p);
+		}
+
+		p += 2 + b.template at<uint16_t>(p);
+		if (p > b.size()) {
+			throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW;
+		}
+
+		return (p - startAt);
+	}
 
   private:
-    uint32_t _id;
-    uint32_t _credentialId;
-    uint64_t _networkId;
-    int64_t _threshold;
-    uint64_t _flags;
-    Address _target;
-    Address _signedBy;
-    Credential::Type _type;
-    ECC::Signature _signature;
+	uint32_t _id;
+	uint32_t _credentialId;
+	uint64_t _networkId;
+	int64_t _threshold;
+	uint64_t _flags;
+	Address _target;
+	Address _signedBy;
+	Credential::Type _type;
+	ECC::Signature _signature;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 281 - 281
node/RingBuffer.hpp

@@ -36,308 +36,308 @@ namespace ZeroTier {
 
 template <class T, size_t S> class RingBuffer {
   private:
-    T buf[S];
-    size_t begin;
-    size_t end;
-    bool wrap;
+	T buf[S];
+	size_t begin;
+	size_t end;
+	bool wrap;
 
   public:
-    RingBuffer() : begin(0), end(0), wrap(false)
-    {
-        memset(buf, 0, sizeof(T) * S);
-    }
+	RingBuffer() : begin(0), end(0), wrap(false)
+	{
+		memset(buf, 0, sizeof(T) * S);
+	}
 
-    /**
-     * @return A pointer to the underlying buffer
-     */
-    inline T* get_buf()
-    {
-        return buf + begin;
-    }
+	/**
+	 * @return A pointer to the underlying buffer
+	 */
+	inline T* get_buf()
+	{
+		return buf + begin;
+	}
 
-    /**
-     * Adjust buffer index pointer as if we copied data in
-     * @param n Number of elements to copy in
-     * @return Number of elements we copied in
-     */
-    inline size_t produce(size_t n)
-    {
-        n = std::min(n, getFree());
-        if (n == 0) {
-            return n;
-        }
-        const size_t first_chunk = std::min(n, S - end);
-        end = (end + first_chunk) % S;
-        if (first_chunk < n) {
-            const size_t second_chunk = n - first_chunk;
-            end = (end + second_chunk) % S;
-        }
-        if (begin == end) {
-            wrap = true;
-        }
-        return n;
-    }
+	/**
+	 * Adjust buffer index pointer as if we copied data in
+	 * @param n Number of elements to copy in
+	 * @return Number of elements we copied in
+	 */
+	inline size_t produce(size_t n)
+	{
+		n = std::min(n, getFree());
+		if (n == 0) {
+			return n;
+		}
+		const size_t first_chunk = std::min(n, S - end);
+		end = (end + first_chunk) % S;
+		if (first_chunk < n) {
+			const size_t second_chunk = n - first_chunk;
+			end = (end + second_chunk) % S;
+		}
+		if (begin == end) {
+			wrap = true;
+		}
+		return n;
+	}
 
-    /**
-     * Fast erase, O(1).
-     * Merely reset the buffer pointer, doesn't erase contents
-     */
-    inline void reset()
-    {
-        consume(count());
-    }
+	/**
+	 * Fast erase, O(1).
+	 * Merely reset the buffer pointer, doesn't erase contents
+	 */
+	inline void reset()
+	{
+		consume(count());
+	}
 
-    /**
-     * adjust buffer index pointer as if we copied data out
-     * @param n Number of elements we copied from the buffer
-     * @return Number of elements actually available from the buffer
-     */
-    inline size_t consume(size_t n)
-    {
-        n = std::min(n, count());
-        if (n == 0) {
-            return n;
-        }
-        if (wrap) {
-            wrap = false;
-        }
-        const size_t first_chunk = std::min(n, S - begin);
-        begin = (begin + first_chunk) % S;
-        if (first_chunk < n) {
-            const size_t second_chunk = n - first_chunk;
-            begin = (begin + second_chunk) % S;
-        }
-        return n;
-    }
+	/**
+	 * adjust buffer index pointer as if we copied data out
+	 * @param n Number of elements we copied from the buffer
+	 * @return Number of elements actually available from the buffer
+	 */
+	inline size_t consume(size_t n)
+	{
+		n = std::min(n, count());
+		if (n == 0) {
+			return n;
+		}
+		if (wrap) {
+			wrap = false;
+		}
+		const size_t first_chunk = std::min(n, S - begin);
+		begin = (begin + first_chunk) % S;
+		if (first_chunk < n) {
+			const size_t second_chunk = n - first_chunk;
+			begin = (begin + second_chunk) % S;
+		}
+		return n;
+	}
 
-    /**
-     * @param data Buffer that is to be written to the ring
-     * @param n Number of elements to write to the buffer
-     */
-    inline size_t write(const T* data, size_t n)
-    {
-        n = std::min(n, getFree());
-        if (n == 0) {
-            return n;
-        }
-        const size_t first_chunk = std::min(n, S - end);
-        memcpy(buf + end, data, first_chunk * sizeof(T));
-        end = (end + first_chunk) % S;
-        if (first_chunk < n) {
-            const size_t second_chunk = n - first_chunk;
-            memcpy(buf + end, data + first_chunk, second_chunk * sizeof(T));
-            end = (end + second_chunk) % S;
-        }
-        if (begin == end) {
-            wrap = true;
-        }
-        return n;
-    }
+	/**
+	 * @param data Buffer that is to be written to the ring
+	 * @param n Number of elements to write to the buffer
+	 */
+	inline size_t write(const T* data, size_t n)
+	{
+		n = std::min(n, getFree());
+		if (n == 0) {
+			return n;
+		}
+		const size_t first_chunk = std::min(n, S - end);
+		memcpy(buf + end, data, first_chunk * sizeof(T));
+		end = (end + first_chunk) % S;
+		if (first_chunk < n) {
+			const size_t second_chunk = n - first_chunk;
+			memcpy(buf + end, data + first_chunk, second_chunk * sizeof(T));
+			end = (end + second_chunk) % S;
+		}
+		if (begin == end) {
+			wrap = true;
+		}
+		return n;
+	}
 
-    /**
-     * Place a single value on the buffer. If the buffer is full, consume a value first.
-     *
-     * @param value A single value to be placed in the buffer
-     */
-    inline void push(const T value)
-    {
-        if (count() == S) {
-            consume(1);
-        }
-        const size_t first_chunk = std::min((size_t)1, S - end);
-        *(buf + end) = value;
-        end = (end + first_chunk) % S;
-        if (begin == end) {
-            wrap = true;
-        }
-    }
+	/**
+	 * Place a single value on the buffer. If the buffer is full, consume a value first.
+	 *
+	 * @param value A single value to be placed in the buffer
+	 */
+	inline void push(const T value)
+	{
+		if (count() == S) {
+			consume(1);
+		}
+		const size_t first_chunk = std::min((size_t)1, S - end);
+		*(buf + end) = value;
+		end = (end + first_chunk) % S;
+		if (begin == end) {
+			wrap = true;
+		}
+	}
 
-    /**
-     * @return The most recently pushed element on the buffer
-     */
-    inline T get_most_recent()
-    {
-        return *(buf + end);
-    }
+	/**
+	 * @return The most recently pushed element on the buffer
+	 */
+	inline T get_most_recent()
+	{
+		return *(buf + end);
+	}
 
-    /**
-     * @param dest Destination buffer
-     * @param n Size (in terms of number of elements) of the destination buffer
-     * @return Number of elements read from the buffer
-     */
-    inline size_t read(T* dest, size_t n)
-    {
-        n = std::min(n, count());
-        if (n == 0) {
-            return n;
-        }
-        if (wrap) {
-            wrap = false;
-        }
-        const size_t first_chunk = std::min(n, S - begin);
-        memcpy(dest, buf + begin, first_chunk * sizeof(T));
-        begin = (begin + first_chunk) % S;
-        if (first_chunk < n) {
-            const size_t second_chunk = n - first_chunk;
-            memcpy(dest + first_chunk, buf + begin, second_chunk * sizeof(T));
-            begin = (begin + second_chunk) % S;
-        }
-        return n;
-    }
+	/**
+	 * @param dest Destination buffer
+	 * @param n Size (in terms of number of elements) of the destination buffer
+	 * @return Number of elements read from the buffer
+	 */
+	inline size_t read(T* dest, size_t n)
+	{
+		n = std::min(n, count());
+		if (n == 0) {
+			return n;
+		}
+		if (wrap) {
+			wrap = false;
+		}
+		const size_t first_chunk = std::min(n, S - begin);
+		memcpy(dest, buf + begin, first_chunk * sizeof(T));
+		begin = (begin + first_chunk) % S;
+		if (first_chunk < n) {
+			const size_t second_chunk = n - first_chunk;
+			memcpy(dest + first_chunk, buf + begin, second_chunk * sizeof(T));
+			begin = (begin + second_chunk) % S;
+		}
+		return n;
+	}
 
-    /**
-     * Return how many elements are in the buffer, O(1).
-     *
-     * @return The number of elements in the buffer
-     */
-    inline size_t count()
-    {
-        if (end == begin) {
-            return wrap ? S : 0;
-        }
-        else if (end > begin) {
-            return end - begin;
-        }
-        else {
-            return S + end - begin;
-        }
-    }
+	/**
+	 * Return how many elements are in the buffer, O(1).
+	 *
+	 * @return The number of elements in the buffer
+	 */
+	inline size_t count()
+	{
+		if (end == begin) {
+			return wrap ? S : 0;
+		}
+		else if (end > begin) {
+			return end - begin;
+		}
+		else {
+			return S + end - begin;
+		}
+	}
 
-    /**
-     * @return The number of slots that are unused in the buffer
-     */
-    inline size_t getFree()
-    {
-        return S - count();
-    }
+	/**
+	 * @return The number of slots that are unused in the buffer
+	 */
+	inline size_t getFree()
+	{
+		return S - count();
+	}
 
-    /**
-     * @return The arithmetic mean of the contents of the buffer
-     */
-    inline float mean()
-    {
-        size_t iterator = begin;
-        float subtotal = 0;
-        size_t curr_cnt = count();
-        for (size_t i = 0; i < curr_cnt; i++) {
-            iterator = (iterator + S - 1) % curr_cnt;
-            subtotal += (float)*(buf + iterator);
-        }
-        return curr_cnt ? subtotal / (float)curr_cnt : 0;
-    }
+	/**
+	 * @return The arithmetic mean of the contents of the buffer
+	 */
+	inline float mean()
+	{
+		size_t iterator = begin;
+		float subtotal = 0;
+		size_t curr_cnt = count();
+		for (size_t i = 0; i < curr_cnt; i++) {
+			iterator = (iterator + S - 1) % curr_cnt;
+			subtotal += (float)*(buf + iterator);
+		}
+		return curr_cnt ? subtotal / (float)curr_cnt : 0;
+	}
 
-    /**
-     * @return The arithmetic mean of the most recent 'n' elements of the buffer
-     */
-    inline float mean(size_t n)
-    {
-        n = n < S ? n : S;
-        size_t iterator = begin;
-        float subtotal = 0;
-        size_t curr_cnt = count();
-        for (size_t i = 0; i < n; i++) {
-            iterator = (iterator + S - 1) % curr_cnt;
-            subtotal += (float)*(buf + iterator);
-        }
-        return curr_cnt ? subtotal / (float)curr_cnt : 0;
-    }
+	/**
+	 * @return The arithmetic mean of the most recent 'n' elements of the buffer
+	 */
+	inline float mean(size_t n)
+	{
+		n = n < S ? n : S;
+		size_t iterator = begin;
+		float subtotal = 0;
+		size_t curr_cnt = count();
+		for (size_t i = 0; i < n; i++) {
+			iterator = (iterator + S - 1) % curr_cnt;
+			subtotal += (float)*(buf + iterator);
+		}
+		return curr_cnt ? subtotal / (float)curr_cnt : 0;
+	}
 
-    /**
-     * @return The sum of the contents of the buffer
-     */
-    inline float sum()
-    {
-        size_t iterator = begin;
-        float total = 0;
-        size_t curr_cnt = count();
-        for (size_t i = 0; i < curr_cnt; i++) {
-            iterator = (iterator + S - 1) % curr_cnt;
-            total += (float)*(buf + iterator);
-        }
-        return total;
-    }
+	/**
+	 * @return The sum of the contents of the buffer
+	 */
+	inline float sum()
+	{
+		size_t iterator = begin;
+		float total = 0;
+		size_t curr_cnt = count();
+		for (size_t i = 0; i < curr_cnt; i++) {
+			iterator = (iterator + S - 1) % curr_cnt;
+			total += (float)*(buf + iterator);
+		}
+		return total;
+	}
 
-    /**
-     * @return The sample standard deviation of element values
-     */
-    inline float stddev()
-    {
-        return sqrt(variance());
-    }
+	/**
+	 * @return The sample standard deviation of element values
+	 */
+	inline float stddev()
+	{
+		return sqrt(variance());
+	}
 
-    /**
-     * @return The variance of element values
-     */
-    inline float variance()
-    {
-        size_t iterator = begin;
-        float cached_mean = mean();
-        size_t curr_cnt = count();
-        T sum_of_squared_deviations = 0;
-        for (size_t i = 0; i < curr_cnt; i++) {
-            iterator = (iterator + S - 1) % curr_cnt;
-            float deviation = (buf[i] - cached_mean);
-            sum_of_squared_deviations += (T)(deviation * deviation);
-        }
-        float variance = (float)sum_of_squared_deviations / (float)(S - 1);
-        return variance;
-    }
+	/**
+	 * @return The variance of element values
+	 */
+	inline float variance()
+	{
+		size_t iterator = begin;
+		float cached_mean = mean();
+		size_t curr_cnt = count();
+		T sum_of_squared_deviations = 0;
+		for (size_t i = 0; i < curr_cnt; i++) {
+			iterator = (iterator + S - 1) % curr_cnt;
+			float deviation = (buf[i] - cached_mean);
+			sum_of_squared_deviations += (T)(deviation * deviation);
+		}
+		float variance = (float)sum_of_squared_deviations / (float)(S - 1);
+		return variance;
+	}
 
-    /**
-     * @return The number of elements of zero value
-     */
-    inline size_t zeroCount()
-    {
-        size_t iterator = begin;
-        size_t zeros = 0;
-        size_t curr_cnt = count();
-        for (size_t i = 0; i < curr_cnt; i++) {
-            iterator = (iterator + S - 1) % curr_cnt;
-            if (*(buf + iterator) == 0) {
-                zeros++;
-            }
-        }
-        return zeros;
-    }
+	/**
+	 * @return The number of elements of zero value
+	 */
+	inline size_t zeroCount()
+	{
+		size_t iterator = begin;
+		size_t zeros = 0;
+		size_t curr_cnt = count();
+		for (size_t i = 0; i < curr_cnt; i++) {
+			iterator = (iterator + S - 1) % curr_cnt;
+			if (*(buf + iterator) == 0) {
+				zeros++;
+			}
+		}
+		return zeros;
+	}
 
-    /**
-     * @param value Value to match against in buffer
-     * @return The number of values held in the ring buffer which match a given value
-     */
-    inline size_t countValue(T value)
-    {
-        size_t iterator = begin;
-        size_t cnt = 0;
-        size_t curr_cnt = count();
-        for (size_t i = 0; i < curr_cnt; i++) {
-            iterator = (iterator + S - 1) % curr_cnt;
-            if (*(buf + iterator) == value) {
-                cnt++;
-            }
-        }
-        return cnt;
-    }
+	/**
+	 * @param value Value to match against in buffer
+	 * @return The number of values held in the ring buffer which match a given value
+	 */
+	inline size_t countValue(T value)
+	{
+		size_t iterator = begin;
+		size_t cnt = 0;
+		size_t curr_cnt = count();
+		for (size_t i = 0; i < curr_cnt; i++) {
+			iterator = (iterator + S - 1) % curr_cnt;
+			if (*(buf + iterator) == value) {
+				cnt++;
+			}
+		}
+		return cnt;
+	}
 
-    /**
-     * Print the contents of the buffer
-     */
-    /*
-    inline void dump()
-    {
-        size_t iterator = begin;
-        for (size_t i=0; i<S; i++) {
-            iterator = (iterator + S - 1) % S;
-            if (typeid(T) == typeid(int)) {
-                fprintf(stderr, "buf[%2zu]=%2d\n", iterator, (int)*(buf + iterator));
-            }
-            else {
-                fprintf(stderr, "buf[%2zu]=%2f\n", iterator, (float)*(buf + iterator));
-            }
-        }
-    }
-    */
+	/**
+	 * Print the contents of the buffer
+	 */
+	/*
+	inline void dump()
+	{
+		size_t iterator = begin;
+		for (size_t i=0; i<S; i++) {
+			iterator = (iterator + S - 1) % S;
+			if (typeid(T) == typeid(int)) {
+				fprintf(stderr, "buf[%2zu]=%2d\n", iterator, (int)*(buf + iterator));
+			}
+			else {
+				fprintf(stderr, "buf[%2zu]=%2f\n", iterator, (float)*(buf + iterator));
+			}
+		}
+	}
+	*/
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 32 - 32
node/RuntimeEnvironment.hpp

@@ -38,46 +38,46 @@ class PacketMultiplexer;
  */
 class RuntimeEnvironment {
   public:
-    RuntimeEnvironment(Node* n) : node(n), localNetworkController((NetworkController*)0), rtmem((void*)0), sw((Switch*)0), mc((Multicaster*)0), topology((Topology*)0), sa((SelfAwareness*)0)
-    {
-        publicIdentityStr[0] = (char)0;
-        secretIdentityStr[0] = (char)0;
-    }
+	RuntimeEnvironment(Node* n) : node(n), localNetworkController((NetworkController*)0), rtmem((void*)0), sw((Switch*)0), mc((Multicaster*)0), topology((Topology*)0), sa((SelfAwareness*)0)
+	{
+		publicIdentityStr[0] = (char)0;
+		secretIdentityStr[0] = (char)0;
+	}
 
-    ~RuntimeEnvironment()
-    {
-        Utils::burn(secretIdentityStr, sizeof(secretIdentityStr));
-    }
+	~RuntimeEnvironment()
+	{
+		Utils::burn(secretIdentityStr, sizeof(secretIdentityStr));
+	}
 
-    // Node instance that owns this RuntimeEnvironment
-    Node* const node;
+	// Node instance that owns this RuntimeEnvironment
+	Node* const node;
 
-    // This is set externally to an instance of this base class
-    NetworkController* localNetworkController;
+	// This is set externally to an instance of this base class
+	NetworkController* localNetworkController;
 
-    // Memory actually occupied by Trace, Switch, etc.
-    void* rtmem;
+	// Memory actually occupied by Trace, Switch, etc.
+	void* rtmem;
 
-    /* Order matters a bit here. These are constructed in this order
-     * and then deleted in the opposite order on Node exit. The order ensures
-     * that things that are needed are there before they're needed.
-     *
-     * These are constant and never null after startup unless indicated. */
+	/* Order matters a bit here. These are constructed in this order
+	 * and then deleted in the opposite order on Node exit. The order ensures
+	 * that things that are needed are there before they're needed.
+	 *
+	 * These are constant and never null after startup unless indicated. */
 
-    Trace* t;
-    Switch* sw;
-    Multicaster* mc;
-    Topology* topology;
-    SelfAwareness* sa;
-    Bond* bc;
-    PacketMultiplexer* pm;
+	Trace* t;
+	Switch* sw;
+	Multicaster* mc;
+	Topology* topology;
+	SelfAwareness* sa;
+	Bond* bc;
+	PacketMultiplexer* pm;
 
-    // This node's identity and string representations thereof
-    Identity identity;
-    char publicIdentityStr[ZT_IDENTITY_STRING_BUFFER_LENGTH];
-    char secretIdentityStr[ZT_IDENTITY_STRING_BUFFER_LENGTH];
+	// This node's identity and string representations thereof
+	Identity identity;
+	char publicIdentityStr[ZT_IDENTITY_STRING_BUFFER_LENGTH];
+	char secretIdentityStr[ZT_IDENTITY_STRING_BUFFER_LENGTH];
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 191 - 191
node/SHA512.cpp

@@ -13,264 +13,264 @@ namespace ZeroTier {
 namespace {
 
 struct sha512_state {
-    uint64_t length, state[8];
-    unsigned long curlen;
-    uint8_t buf[128];
+	uint64_t length, state[8];
+	unsigned long curlen;
+	uint8_t buf[128];
 };
 
 static const uint64_t K[80] = { 0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, 0xd807aa98a3030242ULL,
-                                0x12835b0145706fbeULL, 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL, 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL,
-                                0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL,
-                                0xbf597fc7beef0ee4ULL, 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL,
-                                0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, 0xd192e819d6ef5218ULL,
-                                0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL,
-                                0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL,
-                                0xc67178f2e372532bULL, 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, 0x113f9804bef90daeULL, 0x1b710b35131c471bULL,
-                                0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL, 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL };
+								0x12835b0145706fbeULL, 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL, 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL,
+								0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL,
+								0xbf597fc7beef0ee4ULL, 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL,
+								0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, 0xd192e819d6ef5218ULL,
+								0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL,
+								0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL,
+								0xc67178f2e372532bULL, 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, 0x113f9804bef90daeULL, 0x1b710b35131c471bULL,
+								0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL, 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL };
 
 #define STORE64H(x, y) Utils::storeBigEndian<uint64_t>(y, x)
 #define LOAD64H(x, y)  x = Utils::loadBigEndian<uint64_t>(y)
 #define ROL64c(x, y)   (((x) << (y)) | ((x) >> (64 - (y))))
 #define ROR64c(x, y)   (((x) >> (y)) | ((x) << (64 - (y))))
-#define Ch(x, y, z)    (z ^ (x & (y ^ z)))
+#define Ch(x, y, z)	   (z ^ (x & (y ^ z)))
 #define Maj(x, y, z)   (((x | y) & z) | (x & y))
-#define S(x, n)        ROR64c(x, n)
-#define R(x, n)        ((x) >> (n))
-#define Sigma0(x)      (S(x, 28) ^ S(x, 34) ^ S(x, 39))
-#define Sigma1(x)      (S(x, 14) ^ S(x, 18) ^ S(x, 41))
-#define Gamma0(x)      (S(x, 1) ^ S(x, 8) ^ R(x, 7))
-#define Gamma1(x)      (S(x, 19) ^ S(x, 61) ^ R(x, 6))
+#define S(x, n)		   ROR64c(x, n)
+#define R(x, n)		   ((x) >> (n))
+#define Sigma0(x)	   (S(x, 28) ^ S(x, 34) ^ S(x, 39))
+#define Sigma1(x)	   (S(x, 14) ^ S(x, 18) ^ S(x, 41))
+#define Gamma0(x)	   (S(x, 1) ^ S(x, 8) ^ R(x, 7))
+#define Gamma1(x)	   (S(x, 19) ^ S(x, 61) ^ R(x, 6))
 
 static ZT_INLINE void sha512_compress(sha512_state* const md, uint8_t* const buf)
 {
-    uint64_t S[8], W[80], t0, t1;
-    int i;
-
-    for (i = 0; i < 8; i++) {
-        S[i] = md->state[i];
-    }
-    for (i = 0; i < 16; i++) {
-        LOAD64H(W[i], buf + (8 * i));
-    }
-    for (i = 16; i < 80; i++) {
-        W[i] = Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16];
-    }
+	uint64_t S[8], W[80], t0, t1;
+	int i;
+
+	for (i = 0; i < 8; i++) {
+		S[i] = md->state[i];
+	}
+	for (i = 0; i < 16; i++) {
+		LOAD64H(W[i], buf + (8 * i));
+	}
+	for (i = 16; i < 80; i++) {
+		W[i] = Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16];
+	}
 
 #define RND(a, b, c, d, e, f, g, h, i)                                                                                                                                                                                                         \
-    t0 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i];                                                                                                                                                                                            \
-    t1 = Sigma0(a) + Maj(a, b, c);                                                                                                                                                                                                             \
-    d += t0;                                                                                                                                                                                                                                   \
-    h = t0 + t1;
-
-    for (i = 0; i < 80; i += 8) {
-        RND(S[0], S[1], S[2], S[3], S[4], S[5], S[6], S[7], i + 0);
-        RND(S[7], S[0], S[1], S[2], S[3], S[4], S[5], S[6], i + 1);
-        RND(S[6], S[7], S[0], S[1], S[2], S[3], S[4], S[5], i + 2);
-        RND(S[5], S[6], S[7], S[0], S[1], S[2], S[3], S[4], i + 3);
-        RND(S[4], S[5], S[6], S[7], S[0], S[1], S[2], S[3], i + 4);
-        RND(S[3], S[4], S[5], S[6], S[7], S[0], S[1], S[2], i + 5);
-        RND(S[2], S[3], S[4], S[5], S[6], S[7], S[0], S[1], i + 6);
-        RND(S[1], S[2], S[3], S[4], S[5], S[6], S[7], S[0], i + 7);
-    }
-
-    for (i = 0; i < 8; i++) {
-        md->state[i] = md->state[i] + S[i];
-    }
+	t0 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i];                                                                                                                                                                                            \
+	t1 = Sigma0(a) + Maj(a, b, c);                                                                                                                                                                                                             \
+	d += t0;                                                                                                                                                                                                                                   \
+	h = t0 + t1;
+
+	for (i = 0; i < 80; i += 8) {
+		RND(S[0], S[1], S[2], S[3], S[4], S[5], S[6], S[7], i + 0);
+		RND(S[7], S[0], S[1], S[2], S[3], S[4], S[5], S[6], i + 1);
+		RND(S[6], S[7], S[0], S[1], S[2], S[3], S[4], S[5], i + 2);
+		RND(S[5], S[6], S[7], S[0], S[1], S[2], S[3], S[4], i + 3);
+		RND(S[4], S[5], S[6], S[7], S[0], S[1], S[2], S[3], i + 4);
+		RND(S[3], S[4], S[5], S[6], S[7], S[0], S[1], S[2], i + 5);
+		RND(S[2], S[3], S[4], S[5], S[6], S[7], S[0], S[1], i + 6);
+		RND(S[1], S[2], S[3], S[4], S[5], S[6], S[7], S[0], i + 7);
+	}
+
+	for (i = 0; i < 8; i++) {
+		md->state[i] = md->state[i] + S[i];
+	}
 }
 
 static ZT_INLINE void sha384_init(sha512_state* const md)
 {
-    md->curlen = 0;
-    md->length = 0;
-    md->state[0] = 0xcbbb9d5dc1059ed8ULL;
-    md->state[1] = 0x629a292a367cd507ULL;
-    md->state[2] = 0x9159015a3070dd17ULL;
-    md->state[3] = 0x152fecd8f70e5939ULL;
-    md->state[4] = 0x67332667ffc00b31ULL;
-    md->state[5] = 0x8eb44a8768581511ULL;
-    md->state[6] = 0xdb0c2e0d64f98fa7ULL;
-    md->state[7] = 0x47b5481dbefa4fa4ULL;
+	md->curlen = 0;
+	md->length = 0;
+	md->state[0] = 0xcbbb9d5dc1059ed8ULL;
+	md->state[1] = 0x629a292a367cd507ULL;
+	md->state[2] = 0x9159015a3070dd17ULL;
+	md->state[3] = 0x152fecd8f70e5939ULL;
+	md->state[4] = 0x67332667ffc00b31ULL;
+	md->state[5] = 0x8eb44a8768581511ULL;
+	md->state[6] = 0xdb0c2e0d64f98fa7ULL;
+	md->state[7] = 0x47b5481dbefa4fa4ULL;
 }
 
 static ZT_INLINE void sha512_init(sha512_state* const md)
 {
-    md->curlen = 0;
-    md->length = 0;
-    md->state[0] = 0x6a09e667f3bcc908ULL;
-    md->state[1] = 0xbb67ae8584caa73bULL;
-    md->state[2] = 0x3c6ef372fe94f82bULL;
-    md->state[3] = 0xa54ff53a5f1d36f1ULL;
-    md->state[4] = 0x510e527fade682d1ULL;
-    md->state[5] = 0x9b05688c2b3e6c1fULL;
-    md->state[6] = 0x1f83d9abfb41bd6bULL;
-    md->state[7] = 0x5be0cd19137e2179ULL;
+	md->curlen = 0;
+	md->length = 0;
+	md->state[0] = 0x6a09e667f3bcc908ULL;
+	md->state[1] = 0xbb67ae8584caa73bULL;
+	md->state[2] = 0x3c6ef372fe94f82bULL;
+	md->state[3] = 0xa54ff53a5f1d36f1ULL;
+	md->state[4] = 0x510e527fade682d1ULL;
+	md->state[5] = 0x9b05688c2b3e6c1fULL;
+	md->state[6] = 0x1f83d9abfb41bd6bULL;
+	md->state[7] = 0x5be0cd19137e2179ULL;
 }
 
 static void sha512_process(sha512_state* const md, const uint8_t* in, unsigned long inlen)
 {
-    while (inlen > 0) {
-        if (md->curlen == 0 && inlen >= 128) {
-            sha512_compress(md, (uint8_t*)in);
-            md->length += 128 * 8;
-            in += 128;
-            inlen -= 128;
-        }
-        else {
-            unsigned long n = std::min(inlen, (128 - md->curlen));
-            Utils::copy(md->buf + md->curlen, in, n);
-            md->curlen += n;
-            in += n;
-            inlen -= n;
-            if (md->curlen == 128) {
-                sha512_compress(md, md->buf);
-                md->length += 8 * 128;
-                md->curlen = 0;
-            }
-        }
-    }
+	while (inlen > 0) {
+		if (md->curlen == 0 && inlen >= 128) {
+			sha512_compress(md, (uint8_t*)in);
+			md->length += 128 * 8;
+			in += 128;
+			inlen -= 128;
+		}
+		else {
+			unsigned long n = std::min(inlen, (128 - md->curlen));
+			Utils::copy(md->buf + md->curlen, in, n);
+			md->curlen += n;
+			in += n;
+			inlen -= n;
+			if (md->curlen == 128) {
+				sha512_compress(md, md->buf);
+				md->length += 8 * 128;
+				md->curlen = 0;
+			}
+		}
+	}
 }
 
 static ZT_INLINE void sha512_done(sha512_state* const md, uint8_t* out)
 {
-    int i;
+	int i;
 
-    md->length += md->curlen * 8ULL;
-    md->buf[md->curlen++] = (uint8_t)0x80;
+	md->length += md->curlen * 8ULL;
+	md->buf[md->curlen++] = (uint8_t)0x80;
 
-    if (md->curlen > 112) {
-        while (md->curlen < 128) {
-            md->buf[md->curlen++] = (uint8_t)0;
-        }
-        sha512_compress(md, md->buf);
-        md->curlen = 0;
-    }
+	if (md->curlen > 112) {
+		while (md->curlen < 128) {
+			md->buf[md->curlen++] = (uint8_t)0;
+		}
+		sha512_compress(md, md->buf);
+		md->curlen = 0;
+	}
 
-    while (md->curlen < 120) {
-        md->buf[md->curlen++] = (uint8_t)0;
-    }
+	while (md->curlen < 120) {
+		md->buf[md->curlen++] = (uint8_t)0;
+	}
 
-    STORE64H(md->length, md->buf + 120);
-    sha512_compress(md, md->buf);
+	STORE64H(md->length, md->buf + 120);
+	sha512_compress(md, md->buf);
 
-    for (i = 0; i < 8; i++) {
-        STORE64H(md->state[i], out + (8 * i));
-    }
+	for (i = 0; i < 8; i++) {
+		STORE64H(md->state[i], out + (8 * i));
+	}
 }
 
-}   // anonymous namespace
+}	// anonymous namespace
 
 void SHA512(void* digest, const void* data, unsigned int len)
 {
-    sha512_state state;
-    sha512_init(&state);
-    sha512_process(&state, (uint8_t*)data, (unsigned long)len);
-    sha512_done(&state, (uint8_t*)digest);
+	sha512_state state;
+	sha512_init(&state);
+	sha512_process(&state, (uint8_t*)data, (unsigned long)len);
+	sha512_done(&state, (uint8_t*)digest);
 }
 
 void SHA384(void* digest, const void* data, unsigned int len)
 {
-    uint8_t tmp[64];
-    sha512_state state;
-    sha384_init(&state);
-    sha512_process(&state, (uint8_t*)data, (unsigned long)len);
-    sha512_done(&state, tmp);
-    Utils::copy<48>(digest, tmp);
+	uint8_t tmp[64];
+	sha512_state state;
+	sha384_init(&state);
+	sha512_process(&state, (uint8_t*)data, (unsigned long)len);
+	sha512_done(&state, tmp);
+	Utils::copy<48>(digest, tmp);
 }
 
 void SHA384(void* digest, const void* data0, unsigned int len0, const void* data1, unsigned int len1)
 {
-    uint8_t tmp[64];
-    sha512_state state;
-    sha384_init(&state);
-    sha512_process(&state, (uint8_t*)data0, (unsigned long)len0);
-    sha512_process(&state, (uint8_t*)data1, (unsigned long)len1);
-    sha512_done(&state, tmp);
-    Utils::copy<48>(digest, tmp);
+	uint8_t tmp[64];
+	sha512_state state;
+	sha384_init(&state);
+	sha512_process(&state, (uint8_t*)data0, (unsigned long)len0);
+	sha512_process(&state, (uint8_t*)data1, (unsigned long)len1);
+	sha512_done(&state, tmp);
+	Utils::copy<48>(digest, tmp);
 }
 
-#endif   // !ZT_HAVE_NATIVE_SHA512
+#endif	 // !ZT_HAVE_NATIVE_SHA512
 
 void HMACSHA384(const uint8_t key[ZT_SYMMETRIC_KEY_SIZE], const void* msg, const unsigned int msglen, uint8_t mac[48])
 {
-    uint64_t kInPadded[16];   // input padded key
-    uint64_t outer[22];       // output padded key | H(input padded key | msg)
-
-    const uint64_t k0 = Utils::loadMachineEndian<uint64_t>(key);
-    const uint64_t k1 = Utils::loadMachineEndian<uint64_t>(key + 8);
-    const uint64_t k2 = Utils::loadMachineEndian<uint64_t>(key + 16);
-    const uint64_t k3 = Utils::loadMachineEndian<uint64_t>(key + 24);
-    const uint64_t k4 = Utils::loadMachineEndian<uint64_t>(key + 32);
-    const uint64_t k5 = Utils::loadMachineEndian<uint64_t>(key + 40);
-
-    const uint64_t ipad = 0x3636363636363636ULL;
-    kInPadded[0] = k0 ^ ipad;
-    kInPadded[1] = k1 ^ ipad;
-    kInPadded[2] = k2 ^ ipad;
-    kInPadded[3] = k3 ^ ipad;
-    kInPadded[4] = k4 ^ ipad;
-    kInPadded[5] = k5 ^ ipad;
-    kInPadded[6] = ipad;
-    kInPadded[7] = ipad;
-    kInPadded[8] = ipad;
-    kInPadded[9] = ipad;
-    kInPadded[10] = ipad;
-    kInPadded[11] = ipad;
-    kInPadded[12] = ipad;
-    kInPadded[13] = ipad;
-    kInPadded[14] = ipad;
-    kInPadded[15] = ipad;
-
-    const uint64_t opad = 0x5c5c5c5c5c5c5c5cULL;
-    outer[0] = k0 ^ opad;
-    outer[1] = k1 ^ opad;
-    outer[2] = k2 ^ opad;
-    outer[3] = k3 ^ opad;
-    outer[4] = k4 ^ opad;
-    outer[5] = k5 ^ opad;
-    outer[6] = opad;
-    outer[7] = opad;
-    outer[8] = opad;
-    outer[9] = opad;
-    outer[10] = opad;
-    outer[11] = opad;
-    outer[12] = opad;
-    outer[13] = opad;
-    outer[14] = opad;
-    outer[15] = opad;
-
-    // H(output padded key | H(input padded key | msg))
-    SHA384(reinterpret_cast<uint8_t*>(outer) + 128, kInPadded, 128, msg, msglen);
-    SHA384(mac, outer, 176);
+	uint64_t kInPadded[16];	  // input padded key
+	uint64_t outer[22];		  // output padded key | H(input padded key | msg)
+
+	const uint64_t k0 = Utils::loadMachineEndian<uint64_t>(key);
+	const uint64_t k1 = Utils::loadMachineEndian<uint64_t>(key + 8);
+	const uint64_t k2 = Utils::loadMachineEndian<uint64_t>(key + 16);
+	const uint64_t k3 = Utils::loadMachineEndian<uint64_t>(key + 24);
+	const uint64_t k4 = Utils::loadMachineEndian<uint64_t>(key + 32);
+	const uint64_t k5 = Utils::loadMachineEndian<uint64_t>(key + 40);
+
+	const uint64_t ipad = 0x3636363636363636ULL;
+	kInPadded[0] = k0 ^ ipad;
+	kInPadded[1] = k1 ^ ipad;
+	kInPadded[2] = k2 ^ ipad;
+	kInPadded[3] = k3 ^ ipad;
+	kInPadded[4] = k4 ^ ipad;
+	kInPadded[5] = k5 ^ ipad;
+	kInPadded[6] = ipad;
+	kInPadded[7] = ipad;
+	kInPadded[8] = ipad;
+	kInPadded[9] = ipad;
+	kInPadded[10] = ipad;
+	kInPadded[11] = ipad;
+	kInPadded[12] = ipad;
+	kInPadded[13] = ipad;
+	kInPadded[14] = ipad;
+	kInPadded[15] = ipad;
+
+	const uint64_t opad = 0x5c5c5c5c5c5c5c5cULL;
+	outer[0] = k0 ^ opad;
+	outer[1] = k1 ^ opad;
+	outer[2] = k2 ^ opad;
+	outer[3] = k3 ^ opad;
+	outer[4] = k4 ^ opad;
+	outer[5] = k5 ^ opad;
+	outer[6] = opad;
+	outer[7] = opad;
+	outer[8] = opad;
+	outer[9] = opad;
+	outer[10] = opad;
+	outer[11] = opad;
+	outer[12] = opad;
+	outer[13] = opad;
+	outer[14] = opad;
+	outer[15] = opad;
+
+	// H(output padded key | H(input padded key | msg))
+	SHA384(reinterpret_cast<uint8_t*>(outer) + 128, kInPadded, 128, msg, msglen);
+	SHA384(mac, outer, 176);
 }
 
 void KBKDFHMACSHA384(const uint8_t key[ZT_SYMMETRIC_KEY_SIZE], const char label, const char context, const uint32_t iter, uint8_t out[ZT_SYMMETRIC_KEY_SIZE])
 {
-    uint8_t kbkdfMsg[13];
+	uint8_t kbkdfMsg[13];
 
-    Utils::storeBigEndian<uint32_t>(kbkdfMsg, (uint32_t)iter);
+	Utils::storeBigEndian<uint32_t>(kbkdfMsg, (uint32_t)iter);
 
-    kbkdfMsg[4] = (uint8_t)'Z';
-    kbkdfMsg[5] = (uint8_t)'T';   // preface our labels with something ZT-specific
-    kbkdfMsg[6] = (uint8_t)label;
-    kbkdfMsg[7] = 0;
+	kbkdfMsg[4] = (uint8_t)'Z';
+	kbkdfMsg[5] = (uint8_t)'T';	  // preface our labels with something ZT-specific
+	kbkdfMsg[6] = (uint8_t)label;
+	kbkdfMsg[7] = 0;
 
-    kbkdfMsg[8] = (uint8_t)context;
+	kbkdfMsg[8] = (uint8_t)context;
 
-    // Output key length: 384 bits (as 32-bit big-endian value)
-    kbkdfMsg[9] = 0;
-    kbkdfMsg[10] = 0;
-    kbkdfMsg[11] = 0x01;
-    kbkdfMsg[12] = 0x80;
+	// Output key length: 384 bits (as 32-bit big-endian value)
+	kbkdfMsg[9] = 0;
+	kbkdfMsg[10] = 0;
+	kbkdfMsg[11] = 0x01;
+	kbkdfMsg[12] = 0x80;
 
-    static_assert(ZT_SYMMETRIC_KEY_SIZE == ZT_SHA384_DIGEST_SIZE, "sizeof(out) != ZT_SHA384_DIGEST_SIZE");
-    HMACSHA384(key, &kbkdfMsg, sizeof(kbkdfMsg), out);
+	static_assert(ZT_SYMMETRIC_KEY_SIZE == ZT_SHA384_DIGEST_SIZE, "sizeof(out) != ZT_SHA384_DIGEST_SIZE");
+	HMACSHA384(key, &kbkdfMsg, sizeof(kbkdfMsg), out);
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 // Internally re-export to included C code, which includes some fast crypto code ported in on some platforms.
 // This eliminates the need to link against a third party SHA512() from this code
 extern "C" void ZT_sha512internal(void* digest, const void* data, unsigned int len)
 {
-    ZeroTier::SHA512(digest, data, len);
+	ZeroTier::SHA512(digest, data, len);
 }

+ 14 - 14
node/SHA512.hpp

@@ -34,25 +34,25 @@ namespace ZeroTier {
 #define ZT_HAVE_NATIVE_SHA512 1
 static ZT_INLINE void SHA512(void* digest, const void* data, unsigned int len)
 {
-    CC_SHA512_CTX ctx;
-    CC_SHA512_Init(&ctx);
-    CC_SHA512_Update(&ctx, data, len);
-    CC_SHA512_Final(reinterpret_cast<unsigned char*>(digest), &ctx);
+	CC_SHA512_CTX ctx;
+	CC_SHA512_Init(&ctx);
+	CC_SHA512_Update(&ctx, data, len);
+	CC_SHA512_Final(reinterpret_cast<unsigned char*>(digest), &ctx);
 }
 static ZT_INLINE void SHA384(void* digest, const void* data, unsigned int len)
 {
-    CC_SHA512_CTX ctx;
-    CC_SHA384_Init(&ctx);
-    CC_SHA384_Update(&ctx, data, len);
-    CC_SHA384_Final(reinterpret_cast<unsigned char*>(digest), &ctx);
+	CC_SHA512_CTX ctx;
+	CC_SHA384_Init(&ctx);
+	CC_SHA384_Update(&ctx, data, len);
+	CC_SHA384_Final(reinterpret_cast<unsigned char*>(digest), &ctx);
 }
 static ZT_INLINE void SHA384(void* digest, const void* data0, unsigned int len0, const void* data1, unsigned int len1)
 {
-    CC_SHA512_CTX ctx;
-    CC_SHA384_Init(&ctx);
-    CC_SHA384_Update(&ctx, data0, len0);
-    CC_SHA384_Update(&ctx, data1, len1);
-    CC_SHA384_Final(reinterpret_cast<unsigned char*>(digest), &ctx);
+	CC_SHA512_CTX ctx;
+	CC_SHA384_Init(&ctx);
+	CC_SHA384_Update(&ctx, data0, len0);
+	CC_SHA384_Update(&ctx, data1, len1);
+	CC_SHA384_Final(reinterpret_cast<unsigned char*>(digest), &ctx);
 }
 #endif
 
@@ -83,6 +83,6 @@ void HMACSHA384(const uint8_t key[ZT_SYMMETRIC_KEY_SIZE], const void* msg, unsig
  */
 void KBKDFHMACSHA384(const uint8_t key[ZT_SYMMETRIC_KEY_SIZE], char label, char context, uint32_t iter, uint8_t out[ZT_SYMMETRIC_KEY_SIZE]);
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 1252 - 1252
node/Salsa20.cpp

@@ -12,8 +12,8 @@
 #include "Constants.hpp"
 
 #define ROTATE(v, c) (((v) << (c)) | ((v) >> (32 - (c))))
-#define XOR(v, w)    ((v) ^ (w))
-#define PLUS(v, w)   ((uint32_t)((v) + (w)))
+#define XOR(v, w)	 ((v) ^ (w))
+#define PLUS(v, w)	 ((uint32_t)((v) + (w)))
 
 // Set up load/store macros with appropriate endianness (we don't use these in SSE mode)
 #ifndef ZT_SALSA20_SSE
@@ -25,53 +25,53 @@
 #define U8TO32_LITTLE(p) (((uint32_t)(p)[0]) | ((uint32_t)(p)[1] << 8) | ((uint32_t)(p)[2] << 16) | ((uint32_t)(p)[3] << 24))
 static inline void U32TO8_LITTLE(uint8_t* const c, const uint32_t v)
 {
-    c[0] = (uint8_t)v;
-    c[1] = (uint8_t)(v >> 8);
-    c[2] = (uint8_t)(v >> 16);
-    c[3] = (uint8_t)(v >> 24);
+	c[0] = (uint8_t)v;
+	c[1] = (uint8_t)(v >> 8);
+	c[2] = (uint8_t)(v >> 16);
+	c[3] = (uint8_t)(v >> 24);
 }
 #else
 // Fast version that just does 32-bit load/store
-#define U8TO32_LITTLE(p)    (*((const uint32_t*)((const void*)(p))))
+#define U8TO32_LITTLE(p)	(*((const uint32_t*)((const void*)(p))))
 #define U32TO8_LITTLE(c, v) *((uint32_t*)((void*)(c))) = (v)
-#endif   // ZT_NO_TYPE_PUNNING
+#endif	 // ZT_NO_TYPE_PUNNING
 
-#else   // __BYTE_ORDER == __BIG_ENDIAN (we don't support anything else... does MIDDLE_ENDIAN even still exist?)
+#else	// __BYTE_ORDER == __BIG_ENDIAN (we don't support anything else... does MIDDLE_ENDIAN even still exist?)
 
 #ifdef __GNUC__
 
 // Use GNUC builtin bswap macros on big-endian machines if available
-#define U8TO32_LITTLE(p)    __builtin_bswap32(*((const uint32_t*)((const void*)(p))))
+#define U8TO32_LITTLE(p)	__builtin_bswap32(*((const uint32_t*)((const void*)(p))))
 #define U32TO8_LITTLE(c, v) *((uint32_t*)((void*)(c))) = __builtin_bswap32((v))
 
-#else   // no __GNUC__
+#else	// no __GNUC__
 
 // Otherwise do it the slow, manual way on BE machines
 #define U8TO32_LITTLE(p) (((uint32_t)(p)[0]) | ((uint32_t)(p)[1] << 8) | ((uint32_t)(p)[2] << 16) | ((uint32_t)(p)[3] << 24))
 static inline void U32TO8_LITTLE(uint8_t* const c, const uint32_t v)
 {
-    c[0] = (uint8_t)v;
-    c[1] = (uint8_t)(v >> 8);
-    c[2] = (uint8_t)(v >> 16);
-    c[3] = (uint8_t)(v >> 24);
+	c[0] = (uint8_t)v;
+	c[1] = (uint8_t)(v >> 8);
+	c[2] = (uint8_t)(v >> 16);
+	c[3] = (uint8_t)(v >> 24);
 }
 
-#endif   // __GNUC__ or not
+#endif	 // __GNUC__ or not
 
-#endif   // __BYTE_ORDER little or big?
+#endif	 // __BYTE_ORDER little or big?
 
-#endif   // !ZT_SALSA20_SSE
+#endif	 // !ZT_SALSA20_SSE
 
 // Statically compute and define SSE constants
 #ifdef ZT_SALSA20_SSE
 class _s20sseconsts {
   public:
-    _s20sseconsts()
-    {
-        maskLo32 = _mm_shuffle_epi32(_mm_cvtsi32_si128(-1), _MM_SHUFFLE(1, 0, 1, 0));
-        maskHi32 = _mm_slli_epi64(maskLo32, 32);
-    }
-    __m128i maskLo32, maskHi32;
+	_s20sseconsts()
+	{
+		maskLo32 = _mm_shuffle_epi32(_mm_cvtsi32_si128(-1), _MM_SHUFFLE(1, 0, 1, 0));
+		maskHi32 = _mm_slli_epi64(maskLo32, 32);
+	}
+	__m128i maskLo32, maskHi32;
 };
 static const _s20sseconsts _S20SSECONSTANTS;
 #endif
@@ -81,1279 +81,1279 @@ namespace ZeroTier {
 void Salsa20::init(const void* key, const void* iv)
 {
 #ifdef ZT_SALSA20_SSE
-    const uint32_t* const k = (const uint32_t*)key;
-    _state.i[0] = 0x61707865;
-    _state.i[1] = 0x3320646e;
-    _state.i[2] = 0x79622d32;
-    _state.i[3] = 0x6b206574;
-    _state.i[4] = k[3];
-    _state.i[5] = 0;
-    _state.i[6] = k[7];
-    _state.i[7] = k[2];
-    _state.i[8] = 0;
-    _state.i[9] = k[6];
-    _state.i[10] = k[1];
-    _state.i[11] = ((const uint32_t*)iv)[1];
-    _state.i[12] = k[5];
-    _state.i[13] = k[0];
-    _state.i[14] = ((const uint32_t*)iv)[0];
-    _state.i[15] = k[4];
+	const uint32_t* const k = (const uint32_t*)key;
+	_state.i[0] = 0x61707865;
+	_state.i[1] = 0x3320646e;
+	_state.i[2] = 0x79622d32;
+	_state.i[3] = 0x6b206574;
+	_state.i[4] = k[3];
+	_state.i[5] = 0;
+	_state.i[6] = k[7];
+	_state.i[7] = k[2];
+	_state.i[8] = 0;
+	_state.i[9] = k[6];
+	_state.i[10] = k[1];
+	_state.i[11] = ((const uint32_t*)iv)[1];
+	_state.i[12] = k[5];
+	_state.i[13] = k[0];
+	_state.i[14] = ((const uint32_t*)iv)[0];
+	_state.i[15] = k[4];
 #else
-    const char* const constants = "expand 32-byte k";
-    const uint8_t* const k = (const uint8_t*)key;
-    _state.i[0] = U8TO32_LITTLE(constants + 0);
-    _state.i[1] = U8TO32_LITTLE(k + 0);
-    _state.i[2] = U8TO32_LITTLE(k + 4);
-    _state.i[3] = U8TO32_LITTLE(k + 8);
-    _state.i[4] = U8TO32_LITTLE(k + 12);
-    _state.i[5] = U8TO32_LITTLE(constants + 4);
-    _state.i[6] = U8TO32_LITTLE(((const uint8_t*)iv) + 0);
-    _state.i[7] = U8TO32_LITTLE(((const uint8_t*)iv) + 4);
-    _state.i[8] = 0;
-    _state.i[9] = 0;
-    _state.i[10] = U8TO32_LITTLE(constants + 8);
-    _state.i[11] = U8TO32_LITTLE(k + 16);
-    _state.i[12] = U8TO32_LITTLE(k + 20);
-    _state.i[13] = U8TO32_LITTLE(k + 24);
-    _state.i[14] = U8TO32_LITTLE(k + 28);
-    _state.i[15] = U8TO32_LITTLE(constants + 12);
+	const char* const constants = "expand 32-byte k";
+	const uint8_t* const k = (const uint8_t*)key;
+	_state.i[0] = U8TO32_LITTLE(constants + 0);
+	_state.i[1] = U8TO32_LITTLE(k + 0);
+	_state.i[2] = U8TO32_LITTLE(k + 4);
+	_state.i[3] = U8TO32_LITTLE(k + 8);
+	_state.i[4] = U8TO32_LITTLE(k + 12);
+	_state.i[5] = U8TO32_LITTLE(constants + 4);
+	_state.i[6] = U8TO32_LITTLE(((const uint8_t*)iv) + 0);
+	_state.i[7] = U8TO32_LITTLE(((const uint8_t*)iv) + 4);
+	_state.i[8] = 0;
+	_state.i[9] = 0;
+	_state.i[10] = U8TO32_LITTLE(constants + 8);
+	_state.i[11] = U8TO32_LITTLE(k + 16);
+	_state.i[12] = U8TO32_LITTLE(k + 20);
+	_state.i[13] = U8TO32_LITTLE(k + 24);
+	_state.i[14] = U8TO32_LITTLE(k + 28);
+	_state.i[15] = U8TO32_LITTLE(constants + 12);
 #endif
 }
 
 void Salsa20::crypt12(const void* in, void* out, unsigned int bytes)
 {
-    uint8_t tmp[64];
-    const uint8_t* m = (const uint8_t*)in;
-    uint8_t* c = (uint8_t*)out;
-    uint8_t* ctarget = c;
-    unsigned int i;
+	uint8_t tmp[64];
+	const uint8_t* m = (const uint8_t*)in;
+	uint8_t* c = (uint8_t*)out;
+	uint8_t* ctarget = c;
+	unsigned int i;
 
 #ifndef ZT_SALSA20_SSE
-    uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
-    uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
+	uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
+	uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
 #endif
 
-    if (! bytes) {
-        return;
-    }
+	if (! bytes) {
+		return;
+	}
 
 #ifndef ZT_SALSA20_SSE
-    j0 = _state.i[0];
-    j1 = _state.i[1];
-    j2 = _state.i[2];
-    j3 = _state.i[3];
-    j4 = _state.i[4];
-    j5 = _state.i[5];
-    j6 = _state.i[6];
-    j7 = _state.i[7];
-    j8 = _state.i[8];
-    j9 = _state.i[9];
-    j10 = _state.i[10];
-    j11 = _state.i[11];
-    j12 = _state.i[12];
-    j13 = _state.i[13];
-    j14 = _state.i[14];
-    j15 = _state.i[15];
+	j0 = _state.i[0];
+	j1 = _state.i[1];
+	j2 = _state.i[2];
+	j3 = _state.i[3];
+	j4 = _state.i[4];
+	j5 = _state.i[5];
+	j6 = _state.i[6];
+	j7 = _state.i[7];
+	j8 = _state.i[8];
+	j9 = _state.i[9];
+	j10 = _state.i[10];
+	j11 = _state.i[11];
+	j12 = _state.i[12];
+	j13 = _state.i[13];
+	j14 = _state.i[14];
+	j15 = _state.i[15];
 #endif
 
-    for (;;) {
-        if (bytes < 64) {
-            for (i = 0; i < bytes; ++i) {
-                tmp[i] = m[i];
-            }
-            m = tmp;
-            ctarget = c;
-            c = tmp;
-        }
+	for (;;) {
+		if (bytes < 64) {
+			for (i = 0; i < bytes; ++i) {
+				tmp[i] = m[i];
+			}
+			m = tmp;
+			ctarget = c;
+			c = tmp;
+		}
 
 #ifdef ZT_SALSA20_SSE
-        __m128i X0 = _mm_loadu_si128((const __m128i*)&(_state.v[0]));
-        __m128i X1 = _mm_loadu_si128((const __m128i*)&(_state.v[1]));
-        __m128i X2 = _mm_loadu_si128((const __m128i*)&(_state.v[2]));
-        __m128i X3 = _mm_loadu_si128((const __m128i*)&(_state.v[3]));
-        __m128i T;
-        __m128i X0s = X0;
-        __m128i X1s = X1;
-        __m128i X2s = X2;
-        __m128i X3s = X3;
-
-        // 2X round -------------------------------------------------------------
-        T = _mm_add_epi32(X0, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X1, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X3, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x93);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x39);
-        T = _mm_add_epi32(X0, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X3, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X1, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x39);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x93);
-
-        // 2X round -------------------------------------------------------------
-        T = _mm_add_epi32(X0, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X1, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X3, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x93);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x39);
-        T = _mm_add_epi32(X0, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X3, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X1, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x39);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x93);
-
-        // 2X round -------------------------------------------------------------
-        T = _mm_add_epi32(X0, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X1, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X3, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x93);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x39);
-        T = _mm_add_epi32(X0, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X3, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X1, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x39);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x93);
-
-        // 2X round -------------------------------------------------------------
-        T = _mm_add_epi32(X0, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X1, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X3, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x93);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x39);
-        T = _mm_add_epi32(X0, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X3, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X1, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x39);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x93);
-
-        // 2X round -------------------------------------------------------------
-        T = _mm_add_epi32(X0, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X1, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X3, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x93);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x39);
-        T = _mm_add_epi32(X0, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X3, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X1, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x39);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x93);
-
-        // 2X round -------------------------------------------------------------
-        T = _mm_add_epi32(X0, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X1, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X3, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x93);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x39);
-        T = _mm_add_epi32(X0, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X3, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X1, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x39);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x93);
-
-        X0 = _mm_add_epi32(X0s, X0);
-        X1 = _mm_add_epi32(X1s, X1);
-        X2 = _mm_add_epi32(X2s, X2);
-        X3 = _mm_add_epi32(X3s, X3);
-
-        __m128i k02 = _mm_shuffle_epi32(_mm_or_si128(_mm_slli_epi64(X0, 32), _mm_srli_epi64(X3, 32)), _MM_SHUFFLE(0, 1, 2, 3));
-        __m128i k13 = _mm_shuffle_epi32(_mm_or_si128(_mm_slli_epi64(X1, 32), _mm_srli_epi64(X0, 32)), _MM_SHUFFLE(0, 1, 2, 3));
-        __m128i k20 = _mm_or_si128(_mm_and_si128(X2, _S20SSECONSTANTS.maskLo32), _mm_and_si128(X1, _S20SSECONSTANTS.maskHi32));
-        __m128i k31 = _mm_or_si128(_mm_and_si128(X3, _S20SSECONSTANTS.maskLo32), _mm_and_si128(X2, _S20SSECONSTANTS.maskHi32));
-        _mm_storeu_ps(reinterpret_cast<float*>(c), _mm_castsi128_ps(_mm_xor_si128(_mm_unpackhi_epi64(k02, k20), _mm_castps_si128(_mm_loadu_ps(reinterpret_cast<const float*>(m))))));
-        _mm_storeu_ps(reinterpret_cast<float*>(c) + 4, _mm_castsi128_ps(_mm_xor_si128(_mm_unpackhi_epi64(k13, k31), _mm_castps_si128(_mm_loadu_ps(reinterpret_cast<const float*>(m) + 4)))));
-        _mm_storeu_ps(reinterpret_cast<float*>(c) + 8, _mm_castsi128_ps(_mm_xor_si128(_mm_unpacklo_epi64(k20, k02), _mm_castps_si128(_mm_loadu_ps(reinterpret_cast<const float*>(m) + 8)))));
-        _mm_storeu_ps(reinterpret_cast<float*>(c) + 12, _mm_castsi128_ps(_mm_xor_si128(_mm_unpacklo_epi64(k31, k13), _mm_castps_si128(_mm_loadu_ps(reinterpret_cast<const float*>(m) + 12)))));
-
-        if (! (++_state.i[8])) {
-            ++_state.i[5];   // state reordered for SSE
-                             /* stopping at 2^70 bytes per nonce is user's responsibility */
-        }
+		__m128i X0 = _mm_loadu_si128((const __m128i*)&(_state.v[0]));
+		__m128i X1 = _mm_loadu_si128((const __m128i*)&(_state.v[1]));
+		__m128i X2 = _mm_loadu_si128((const __m128i*)&(_state.v[2]));
+		__m128i X3 = _mm_loadu_si128((const __m128i*)&(_state.v[3]));
+		__m128i T;
+		__m128i X0s = X0;
+		__m128i X1s = X1;
+		__m128i X2s = X2;
+		__m128i X3s = X3;
+
+		// 2X round -------------------------------------------------------------
+		T = _mm_add_epi32(X0, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X1, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X3, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x93);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x39);
+		T = _mm_add_epi32(X0, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X3, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X1, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x39);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x93);
+
+		// 2X round -------------------------------------------------------------
+		T = _mm_add_epi32(X0, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X1, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X3, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x93);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x39);
+		T = _mm_add_epi32(X0, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X3, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X1, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x39);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x93);
+
+		// 2X round -------------------------------------------------------------
+		T = _mm_add_epi32(X0, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X1, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X3, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x93);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x39);
+		T = _mm_add_epi32(X0, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X3, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X1, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x39);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x93);
+
+		// 2X round -------------------------------------------------------------
+		T = _mm_add_epi32(X0, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X1, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X3, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x93);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x39);
+		T = _mm_add_epi32(X0, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X3, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X1, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x39);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x93);
+
+		// 2X round -------------------------------------------------------------
+		T = _mm_add_epi32(X0, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X1, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X3, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x93);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x39);
+		T = _mm_add_epi32(X0, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X3, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X1, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x39);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x93);
+
+		// 2X round -------------------------------------------------------------
+		T = _mm_add_epi32(X0, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X1, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X3, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x93);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x39);
+		T = _mm_add_epi32(X0, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X3, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X1, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x39);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x93);
+
+		X0 = _mm_add_epi32(X0s, X0);
+		X1 = _mm_add_epi32(X1s, X1);
+		X2 = _mm_add_epi32(X2s, X2);
+		X3 = _mm_add_epi32(X3s, X3);
+
+		__m128i k02 = _mm_shuffle_epi32(_mm_or_si128(_mm_slli_epi64(X0, 32), _mm_srli_epi64(X3, 32)), _MM_SHUFFLE(0, 1, 2, 3));
+		__m128i k13 = _mm_shuffle_epi32(_mm_or_si128(_mm_slli_epi64(X1, 32), _mm_srli_epi64(X0, 32)), _MM_SHUFFLE(0, 1, 2, 3));
+		__m128i k20 = _mm_or_si128(_mm_and_si128(X2, _S20SSECONSTANTS.maskLo32), _mm_and_si128(X1, _S20SSECONSTANTS.maskHi32));
+		__m128i k31 = _mm_or_si128(_mm_and_si128(X3, _S20SSECONSTANTS.maskLo32), _mm_and_si128(X2, _S20SSECONSTANTS.maskHi32));
+		_mm_storeu_ps(reinterpret_cast<float*>(c), _mm_castsi128_ps(_mm_xor_si128(_mm_unpackhi_epi64(k02, k20), _mm_castps_si128(_mm_loadu_ps(reinterpret_cast<const float*>(m))))));
+		_mm_storeu_ps(reinterpret_cast<float*>(c) + 4, _mm_castsi128_ps(_mm_xor_si128(_mm_unpackhi_epi64(k13, k31), _mm_castps_si128(_mm_loadu_ps(reinterpret_cast<const float*>(m) + 4)))));
+		_mm_storeu_ps(reinterpret_cast<float*>(c) + 8, _mm_castsi128_ps(_mm_xor_si128(_mm_unpacklo_epi64(k20, k02), _mm_castps_si128(_mm_loadu_ps(reinterpret_cast<const float*>(m) + 8)))));
+		_mm_storeu_ps(reinterpret_cast<float*>(c) + 12, _mm_castsi128_ps(_mm_xor_si128(_mm_unpacklo_epi64(k31, k13), _mm_castps_si128(_mm_loadu_ps(reinterpret_cast<const float*>(m) + 12)))));
+
+		if (! (++_state.i[8])) {
+			++_state.i[5];	 // state reordered for SSE
+							 /* stopping at 2^70 bytes per nonce is user's responsibility */
+		}
 #else
-        x0 = j0;
-        x1 = j1;
-        x2 = j2;
-        x3 = j3;
-        x4 = j4;
-        x5 = j5;
-        x6 = j6;
-        x7 = j7;
-        x8 = j8;
-        x9 = j9;
-        x10 = j10;
-        x11 = j11;
-        x12 = j12;
-        x13 = j13;
-        x14 = j14;
-        x15 = j15;
-
-        // 2X round -------------------------------------------------------------
-        x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
-        x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
-        x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
-        x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
-        x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
-        x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
-        x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
-        x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
-        x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
-        x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
-        x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
-        x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
-        x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
-        x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
-        x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
-        x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
-
-        // 2X round -------------------------------------------------------------
-        x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
-        x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
-        x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
-        x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
-        x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
-        x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
-        x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
-        x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
-        x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
-        x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
-        x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
-        x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
-        x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
-        x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
-        x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
-        x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
-
-        // 2X round -------------------------------------------------------------
-        x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
-        x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
-        x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
-        x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
-        x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
-        x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
-        x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
-        x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
-        x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
-        x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
-        x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
-        x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
-        x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
-        x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
-        x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
-        x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
-
-        // 2X round -------------------------------------------------------------
-        x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
-        x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
-        x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
-        x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
-        x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
-        x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
-        x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
-        x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
-        x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
-        x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
-        x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
-        x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
-        x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
-        x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
-        x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
-        x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
-
-        // 2X round -------------------------------------------------------------
-        x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
-        x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
-        x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
-        x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
-        x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
-        x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
-        x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
-        x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
-        x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
-        x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
-        x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
-        x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
-        x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
-        x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
-        x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
-        x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
-
-        // 2X round -------------------------------------------------------------
-        x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
-        x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
-        x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
-        x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
-        x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
-        x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
-        x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
-        x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
-        x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
-        x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
-        x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
-        x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
-        x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
-        x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
-        x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
-        x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
-
-        x0 = PLUS(x0, j0);
-        x1 = PLUS(x1, j1);
-        x2 = PLUS(x2, j2);
-        x3 = PLUS(x3, j3);
-        x4 = PLUS(x4, j4);
-        x5 = PLUS(x5, j5);
-        x6 = PLUS(x6, j6);
-        x7 = PLUS(x7, j7);
-        x8 = PLUS(x8, j8);
-        x9 = PLUS(x9, j9);
-        x10 = PLUS(x10, j10);
-        x11 = PLUS(x11, j11);
-        x12 = PLUS(x12, j12);
-        x13 = PLUS(x13, j13);
-        x14 = PLUS(x14, j14);
-        x15 = PLUS(x15, j15);
-
-        U32TO8_LITTLE(c + 0, XOR(x0, U8TO32_LITTLE(m + 0)));
-        U32TO8_LITTLE(c + 4, XOR(x1, U8TO32_LITTLE(m + 4)));
-        U32TO8_LITTLE(c + 8, XOR(x2, U8TO32_LITTLE(m + 8)));
-        U32TO8_LITTLE(c + 12, XOR(x3, U8TO32_LITTLE(m + 12)));
-        U32TO8_LITTLE(c + 16, XOR(x4, U8TO32_LITTLE(m + 16)));
-        U32TO8_LITTLE(c + 20, XOR(x5, U8TO32_LITTLE(m + 20)));
-        U32TO8_LITTLE(c + 24, XOR(x6, U8TO32_LITTLE(m + 24)));
-        U32TO8_LITTLE(c + 28, XOR(x7, U8TO32_LITTLE(m + 28)));
-        U32TO8_LITTLE(c + 32, XOR(x8, U8TO32_LITTLE(m + 32)));
-        U32TO8_LITTLE(c + 36, XOR(x9, U8TO32_LITTLE(m + 36)));
-        U32TO8_LITTLE(c + 40, XOR(x10, U8TO32_LITTLE(m + 40)));
-        U32TO8_LITTLE(c + 44, XOR(x11, U8TO32_LITTLE(m + 44)));
-        U32TO8_LITTLE(c + 48, XOR(x12, U8TO32_LITTLE(m + 48)));
-        U32TO8_LITTLE(c + 52, XOR(x13, U8TO32_LITTLE(m + 52)));
-        U32TO8_LITTLE(c + 56, XOR(x14, U8TO32_LITTLE(m + 56)));
-        U32TO8_LITTLE(c + 60, XOR(x15, U8TO32_LITTLE(m + 60)));
-
-        if (! (++j8)) {
-            ++j9;
-            /* stopping at 2^70 bytes per nonce is user's responsibility */
-        }
+		x0 = j0;
+		x1 = j1;
+		x2 = j2;
+		x3 = j3;
+		x4 = j4;
+		x5 = j5;
+		x6 = j6;
+		x7 = j7;
+		x8 = j8;
+		x9 = j9;
+		x10 = j10;
+		x11 = j11;
+		x12 = j12;
+		x13 = j13;
+		x14 = j14;
+		x15 = j15;
+
+		// 2X round -------------------------------------------------------------
+		x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
+		x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
+		x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
+		x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
+		x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
+		x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
+		x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
+		x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
+		x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
+		x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
+		x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
+		x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
+		x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
+		x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
+		x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
+		x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
+
+		// 2X round -------------------------------------------------------------
+		x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
+		x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
+		x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
+		x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
+		x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
+		x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
+		x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
+		x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
+		x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
+		x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
+		x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
+		x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
+		x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
+		x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
+		x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
+		x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
+
+		// 2X round -------------------------------------------------------------
+		x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
+		x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
+		x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
+		x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
+		x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
+		x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
+		x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
+		x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
+		x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
+		x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
+		x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
+		x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
+		x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
+		x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
+		x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
+		x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
+
+		// 2X round -------------------------------------------------------------
+		x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
+		x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
+		x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
+		x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
+		x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
+		x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
+		x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
+		x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
+		x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
+		x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
+		x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
+		x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
+		x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
+		x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
+		x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
+		x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
+
+		// 2X round -------------------------------------------------------------
+		x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
+		x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
+		x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
+		x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
+		x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
+		x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
+		x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
+		x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
+		x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
+		x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
+		x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
+		x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
+		x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
+		x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
+		x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
+		x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
+
+		// 2X round -------------------------------------------------------------
+		x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
+		x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
+		x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
+		x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
+		x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
+		x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
+		x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
+		x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
+		x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
+		x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
+		x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
+		x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
+		x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
+		x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
+		x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
+		x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
+
+		x0 = PLUS(x0, j0);
+		x1 = PLUS(x1, j1);
+		x2 = PLUS(x2, j2);
+		x3 = PLUS(x3, j3);
+		x4 = PLUS(x4, j4);
+		x5 = PLUS(x5, j5);
+		x6 = PLUS(x6, j6);
+		x7 = PLUS(x7, j7);
+		x8 = PLUS(x8, j8);
+		x9 = PLUS(x9, j9);
+		x10 = PLUS(x10, j10);
+		x11 = PLUS(x11, j11);
+		x12 = PLUS(x12, j12);
+		x13 = PLUS(x13, j13);
+		x14 = PLUS(x14, j14);
+		x15 = PLUS(x15, j15);
+
+		U32TO8_LITTLE(c + 0, XOR(x0, U8TO32_LITTLE(m + 0)));
+		U32TO8_LITTLE(c + 4, XOR(x1, U8TO32_LITTLE(m + 4)));
+		U32TO8_LITTLE(c + 8, XOR(x2, U8TO32_LITTLE(m + 8)));
+		U32TO8_LITTLE(c + 12, XOR(x3, U8TO32_LITTLE(m + 12)));
+		U32TO8_LITTLE(c + 16, XOR(x4, U8TO32_LITTLE(m + 16)));
+		U32TO8_LITTLE(c + 20, XOR(x5, U8TO32_LITTLE(m + 20)));
+		U32TO8_LITTLE(c + 24, XOR(x6, U8TO32_LITTLE(m + 24)));
+		U32TO8_LITTLE(c + 28, XOR(x7, U8TO32_LITTLE(m + 28)));
+		U32TO8_LITTLE(c + 32, XOR(x8, U8TO32_LITTLE(m + 32)));
+		U32TO8_LITTLE(c + 36, XOR(x9, U8TO32_LITTLE(m + 36)));
+		U32TO8_LITTLE(c + 40, XOR(x10, U8TO32_LITTLE(m + 40)));
+		U32TO8_LITTLE(c + 44, XOR(x11, U8TO32_LITTLE(m + 44)));
+		U32TO8_LITTLE(c + 48, XOR(x12, U8TO32_LITTLE(m + 48)));
+		U32TO8_LITTLE(c + 52, XOR(x13, U8TO32_LITTLE(m + 52)));
+		U32TO8_LITTLE(c + 56, XOR(x14, U8TO32_LITTLE(m + 56)));
+		U32TO8_LITTLE(c + 60, XOR(x15, U8TO32_LITTLE(m + 60)));
+
+		if (! (++j8)) {
+			++j9;
+			/* stopping at 2^70 bytes per nonce is user's responsibility */
+		}
 #endif
 
-        if (bytes <= 64) {
-            if (bytes < 64) {
-                for (i = 0; i < bytes; ++i) {
-                    ctarget[i] = c[i];
-                }
-            }
+		if (bytes <= 64) {
+			if (bytes < 64) {
+				for (i = 0; i < bytes; ++i) {
+					ctarget[i] = c[i];
+				}
+			}
 
 #ifndef ZT_SALSA20_SSE
-            _state.i[8] = j8;
-            _state.i[9] = j9;
+			_state.i[8] = j8;
+			_state.i[9] = j9;
 #endif
 
-            return;
-        }
+			return;
+		}
 
-        bytes -= 64;
-        c += 64;
-        m += 64;
-    }
+		bytes -= 64;
+		c += 64;
+		m += 64;
+	}
 }
 
 void Salsa20::crypt20(const void* in, void* out, unsigned int bytes)
 {
-    uint8_t tmp[64];
-    const uint8_t* m = (const uint8_t*)in;
-    uint8_t* c = (uint8_t*)out;
-    uint8_t* ctarget = c;
-    unsigned int i;
+	uint8_t tmp[64];
+	const uint8_t* m = (const uint8_t*)in;
+	uint8_t* c = (uint8_t*)out;
+	uint8_t* ctarget = c;
+	unsigned int i;
 
 #ifndef ZT_SALSA20_SSE
-    uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
-    uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
+	uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
+	uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
 #endif
 
-    if (! bytes) {
-        return;
-    }
+	if (! bytes) {
+		return;
+	}
 
 #ifndef ZT_SALSA20_SSE
-    j0 = _state.i[0];
-    j1 = _state.i[1];
-    j2 = _state.i[2];
-    j3 = _state.i[3];
-    j4 = _state.i[4];
-    j5 = _state.i[5];
-    j6 = _state.i[6];
-    j7 = _state.i[7];
-    j8 = _state.i[8];
-    j9 = _state.i[9];
-    j10 = _state.i[10];
-    j11 = _state.i[11];
-    j12 = _state.i[12];
-    j13 = _state.i[13];
-    j14 = _state.i[14];
-    j15 = _state.i[15];
+	j0 = _state.i[0];
+	j1 = _state.i[1];
+	j2 = _state.i[2];
+	j3 = _state.i[3];
+	j4 = _state.i[4];
+	j5 = _state.i[5];
+	j6 = _state.i[6];
+	j7 = _state.i[7];
+	j8 = _state.i[8];
+	j9 = _state.i[9];
+	j10 = _state.i[10];
+	j11 = _state.i[11];
+	j12 = _state.i[12];
+	j13 = _state.i[13];
+	j14 = _state.i[14];
+	j15 = _state.i[15];
 #endif
 
-    for (;;) {
-        if (bytes < 64) {
-            for (i = 0; i < bytes; ++i) {
-                tmp[i] = m[i];
-            }
-            m = tmp;
-            ctarget = c;
-            c = tmp;
-        }
+	for (;;) {
+		if (bytes < 64) {
+			for (i = 0; i < bytes; ++i) {
+				tmp[i] = m[i];
+			}
+			m = tmp;
+			ctarget = c;
+			c = tmp;
+		}
 
 #ifdef ZT_SALSA20_SSE
-        __m128i X0 = _mm_loadu_si128((const __m128i*)&(_state.v[0]));
-        __m128i X1 = _mm_loadu_si128((const __m128i*)&(_state.v[1]));
-        __m128i X2 = _mm_loadu_si128((const __m128i*)&(_state.v[2]));
-        __m128i X3 = _mm_loadu_si128((const __m128i*)&(_state.v[3]));
-        __m128i T;
-        __m128i X0s = X0;
-        __m128i X1s = X1;
-        __m128i X2s = X2;
-        __m128i X3s = X3;
-
-        // 2X round -------------------------------------------------------------
-        T = _mm_add_epi32(X0, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X1, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X3, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x93);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x39);
-        T = _mm_add_epi32(X0, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X3, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X1, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x39);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x93);
-
-        // 2X round -------------------------------------------------------------
-        T = _mm_add_epi32(X0, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X1, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X3, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x93);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x39);
-        T = _mm_add_epi32(X0, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X3, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X1, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x39);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x93);
-
-        // 2X round -------------------------------------------------------------
-        T = _mm_add_epi32(X0, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X1, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X3, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x93);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x39);
-        T = _mm_add_epi32(X0, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X3, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X1, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x39);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x93);
-
-        // 2X round -------------------------------------------------------------
-        T = _mm_add_epi32(X0, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X1, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X3, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x93);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x39);
-        T = _mm_add_epi32(X0, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X3, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X1, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x39);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x93);
-
-        // 2X round -------------------------------------------------------------
-        T = _mm_add_epi32(X0, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X1, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X3, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x93);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x39);
-        T = _mm_add_epi32(X0, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X3, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X1, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x39);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x93);
-
-        // 2X round -------------------------------------------------------------
-        T = _mm_add_epi32(X0, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X1, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X3, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x93);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x39);
-        T = _mm_add_epi32(X0, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X3, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X1, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x39);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x93);
-
-        // 2X round -------------------------------------------------------------
-        T = _mm_add_epi32(X0, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X1, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X3, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x93);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x39);
-        T = _mm_add_epi32(X0, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X3, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X1, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x39);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x93);
-
-        // 2X round -------------------------------------------------------------
-        T = _mm_add_epi32(X0, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X1, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X3, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x93);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x39);
-        T = _mm_add_epi32(X0, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X3, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X1, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x39);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x93);
-
-        // 2X round -------------------------------------------------------------
-        T = _mm_add_epi32(X0, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X1, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X3, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x93);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x39);
-        T = _mm_add_epi32(X0, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X3, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X1, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x39);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x93);
-
-        // 2X round -------------------------------------------------------------
-        T = _mm_add_epi32(X0, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X1, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X3, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x93);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x39);
-        T = _mm_add_epi32(X0, X1);
-        X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
-        T = _mm_add_epi32(X3, X0);
-        X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
-        T = _mm_add_epi32(X2, X3);
-        X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
-        T = _mm_add_epi32(X1, X2);
-        X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
-        X1 = _mm_shuffle_epi32(X1, 0x39);
-        X2 = _mm_shuffle_epi32(X2, 0x4E);
-        X3 = _mm_shuffle_epi32(X3, 0x93);
-
-        X0 = _mm_add_epi32(X0s, X0);
-        X1 = _mm_add_epi32(X1s, X1);
-        X2 = _mm_add_epi32(X2s, X2);
-        X3 = _mm_add_epi32(X3s, X3);
-
-        __m128i k02 = _mm_shuffle_epi32(_mm_or_si128(_mm_slli_epi64(X0, 32), _mm_srli_epi64(X3, 32)), _MM_SHUFFLE(0, 1, 2, 3));
-        __m128i k13 = _mm_shuffle_epi32(_mm_or_si128(_mm_slli_epi64(X1, 32), _mm_srli_epi64(X0, 32)), _MM_SHUFFLE(0, 1, 2, 3));
-        __m128i k20 = _mm_or_si128(_mm_and_si128(X2, _S20SSECONSTANTS.maskLo32), _mm_and_si128(X1, _S20SSECONSTANTS.maskHi32));
-        __m128i k31 = _mm_or_si128(_mm_and_si128(X3, _S20SSECONSTANTS.maskLo32), _mm_and_si128(X2, _S20SSECONSTANTS.maskHi32));
-        _mm_storeu_ps(reinterpret_cast<float*>(c), _mm_castsi128_ps(_mm_xor_si128(_mm_unpackhi_epi64(k02, k20), _mm_castps_si128(_mm_loadu_ps(reinterpret_cast<const float*>(m))))));
-        _mm_storeu_ps(reinterpret_cast<float*>(c) + 4, _mm_castsi128_ps(_mm_xor_si128(_mm_unpackhi_epi64(k13, k31), _mm_castps_si128(_mm_loadu_ps(reinterpret_cast<const float*>(m) + 4)))));
-        _mm_storeu_ps(reinterpret_cast<float*>(c) + 8, _mm_castsi128_ps(_mm_xor_si128(_mm_unpacklo_epi64(k20, k02), _mm_castps_si128(_mm_loadu_ps(reinterpret_cast<const float*>(m) + 8)))));
-        _mm_storeu_ps(reinterpret_cast<float*>(c) + 12, _mm_castsi128_ps(_mm_xor_si128(_mm_unpacklo_epi64(k31, k13), _mm_castps_si128(_mm_loadu_ps(reinterpret_cast<const float*>(m) + 12)))));
-
-        if (! (++_state.i[8])) {
-            ++_state.i[5];   // state reordered for SSE
-                             /* stopping at 2^70 bytes per nonce is user's responsibility */
-        }
+		__m128i X0 = _mm_loadu_si128((const __m128i*)&(_state.v[0]));
+		__m128i X1 = _mm_loadu_si128((const __m128i*)&(_state.v[1]));
+		__m128i X2 = _mm_loadu_si128((const __m128i*)&(_state.v[2]));
+		__m128i X3 = _mm_loadu_si128((const __m128i*)&(_state.v[3]));
+		__m128i T;
+		__m128i X0s = X0;
+		__m128i X1s = X1;
+		__m128i X2s = X2;
+		__m128i X3s = X3;
+
+		// 2X round -------------------------------------------------------------
+		T = _mm_add_epi32(X0, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X1, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X3, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x93);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x39);
+		T = _mm_add_epi32(X0, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X3, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X1, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x39);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x93);
+
+		// 2X round -------------------------------------------------------------
+		T = _mm_add_epi32(X0, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X1, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X3, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x93);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x39);
+		T = _mm_add_epi32(X0, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X3, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X1, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x39);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x93);
+
+		// 2X round -------------------------------------------------------------
+		T = _mm_add_epi32(X0, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X1, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X3, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x93);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x39);
+		T = _mm_add_epi32(X0, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X3, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X1, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x39);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x93);
+
+		// 2X round -------------------------------------------------------------
+		T = _mm_add_epi32(X0, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X1, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X3, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x93);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x39);
+		T = _mm_add_epi32(X0, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X3, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X1, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x39);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x93);
+
+		// 2X round -------------------------------------------------------------
+		T = _mm_add_epi32(X0, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X1, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X3, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x93);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x39);
+		T = _mm_add_epi32(X0, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X3, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X1, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x39);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x93);
+
+		// 2X round -------------------------------------------------------------
+		T = _mm_add_epi32(X0, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X1, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X3, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x93);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x39);
+		T = _mm_add_epi32(X0, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X3, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X1, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x39);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x93);
+
+		// 2X round -------------------------------------------------------------
+		T = _mm_add_epi32(X0, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X1, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X3, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x93);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x39);
+		T = _mm_add_epi32(X0, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X3, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X1, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x39);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x93);
+
+		// 2X round -------------------------------------------------------------
+		T = _mm_add_epi32(X0, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X1, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X3, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x93);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x39);
+		T = _mm_add_epi32(X0, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X3, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X1, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x39);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x93);
+
+		// 2X round -------------------------------------------------------------
+		T = _mm_add_epi32(X0, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X1, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X3, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x93);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x39);
+		T = _mm_add_epi32(X0, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X3, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X1, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x39);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x93);
+
+		// 2X round -------------------------------------------------------------
+		T = _mm_add_epi32(X0, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X1, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X3, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x93);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x39);
+		T = _mm_add_epi32(X0, X1);
+		X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X3, X0);
+		X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X3);
+		X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X1, X2);
+		X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14));
+		X1 = _mm_shuffle_epi32(X1, 0x39);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x93);
+
+		X0 = _mm_add_epi32(X0s, X0);
+		X1 = _mm_add_epi32(X1s, X1);
+		X2 = _mm_add_epi32(X2s, X2);
+		X3 = _mm_add_epi32(X3s, X3);
+
+		__m128i k02 = _mm_shuffle_epi32(_mm_or_si128(_mm_slli_epi64(X0, 32), _mm_srli_epi64(X3, 32)), _MM_SHUFFLE(0, 1, 2, 3));
+		__m128i k13 = _mm_shuffle_epi32(_mm_or_si128(_mm_slli_epi64(X1, 32), _mm_srli_epi64(X0, 32)), _MM_SHUFFLE(0, 1, 2, 3));
+		__m128i k20 = _mm_or_si128(_mm_and_si128(X2, _S20SSECONSTANTS.maskLo32), _mm_and_si128(X1, _S20SSECONSTANTS.maskHi32));
+		__m128i k31 = _mm_or_si128(_mm_and_si128(X3, _S20SSECONSTANTS.maskLo32), _mm_and_si128(X2, _S20SSECONSTANTS.maskHi32));
+		_mm_storeu_ps(reinterpret_cast<float*>(c), _mm_castsi128_ps(_mm_xor_si128(_mm_unpackhi_epi64(k02, k20), _mm_castps_si128(_mm_loadu_ps(reinterpret_cast<const float*>(m))))));
+		_mm_storeu_ps(reinterpret_cast<float*>(c) + 4, _mm_castsi128_ps(_mm_xor_si128(_mm_unpackhi_epi64(k13, k31), _mm_castps_si128(_mm_loadu_ps(reinterpret_cast<const float*>(m) + 4)))));
+		_mm_storeu_ps(reinterpret_cast<float*>(c) + 8, _mm_castsi128_ps(_mm_xor_si128(_mm_unpacklo_epi64(k20, k02), _mm_castps_si128(_mm_loadu_ps(reinterpret_cast<const float*>(m) + 8)))));
+		_mm_storeu_ps(reinterpret_cast<float*>(c) + 12, _mm_castsi128_ps(_mm_xor_si128(_mm_unpacklo_epi64(k31, k13), _mm_castps_si128(_mm_loadu_ps(reinterpret_cast<const float*>(m) + 12)))));
+
+		if (! (++_state.i[8])) {
+			++_state.i[5];	 // state reordered for SSE
+							 /* stopping at 2^70 bytes per nonce is user's responsibility */
+		}
 #else
-        x0 = j0;
-        x1 = j1;
-        x2 = j2;
-        x3 = j3;
-        x4 = j4;
-        x5 = j5;
-        x6 = j6;
-        x7 = j7;
-        x8 = j8;
-        x9 = j9;
-        x10 = j10;
-        x11 = j11;
-        x12 = j12;
-        x13 = j13;
-        x14 = j14;
-        x15 = j15;
-
-        // 2X round -------------------------------------------------------------
-        x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
-        x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
-        x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
-        x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
-        x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
-        x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
-        x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
-        x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
-        x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
-        x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
-        x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
-        x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
-        x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
-        x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
-        x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
-        x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
-
-        // 2X round -------------------------------------------------------------
-        x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
-        x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
-        x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
-        x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
-        x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
-        x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
-        x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
-        x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
-        x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
-        x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
-        x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
-        x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
-        x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
-        x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
-        x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
-        x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
-
-        // 2X round -------------------------------------------------------------
-        x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
-        x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
-        x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
-        x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
-        x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
-        x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
-        x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
-        x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
-        x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
-        x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
-        x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
-        x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
-        x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
-        x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
-        x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
-        x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
-
-        // 2X round -------------------------------------------------------------
-        x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
-        x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
-        x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
-        x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
-        x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
-        x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
-        x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
-        x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
-        x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
-        x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
-        x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
-        x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
-        x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
-        x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
-        x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
-        x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
-
-        // 2X round -------------------------------------------------------------
-        x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
-        x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
-        x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
-        x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
-        x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
-        x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
-        x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
-        x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
-        x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
-        x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
-        x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
-        x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
-        x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
-        x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
-        x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
-        x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
-
-        // 2X round -------------------------------------------------------------
-        x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
-        x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
-        x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
-        x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
-        x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
-        x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
-        x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
-        x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
-        x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
-        x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
-        x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
-        x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
-        x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
-        x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
-        x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
-        x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
-
-        // 2X round -------------------------------------------------------------
-        x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
-        x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
-        x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
-        x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
-        x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
-        x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
-        x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
-        x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
-        x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
-        x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
-        x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
-        x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
-        x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
-        x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
-        x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
-        x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
-
-        // 2X round -------------------------------------------------------------
-        x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
-        x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
-        x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
-        x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
-        x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
-        x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
-        x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
-        x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
-        x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
-        x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
-        x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
-        x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
-        x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
-        x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
-        x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
-        x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
-
-        // 2X round -------------------------------------------------------------
-        x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
-        x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
-        x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
-        x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
-        x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
-        x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
-        x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
-        x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
-        x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
-        x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
-        x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
-        x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
-        x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
-        x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
-        x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
-        x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
-
-        // 2X round -------------------------------------------------------------
-        x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
-        x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
-        x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
-        x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
-        x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
-        x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
-        x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
-        x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
-        x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
-        x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
-        x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
-        x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
-        x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
-        x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
-        x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
-        x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
-        x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
-        x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
-        x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
-        x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
-        x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
-        x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
-        x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
-        x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
-
-        x0 = PLUS(x0, j0);
-        x1 = PLUS(x1, j1);
-        x2 = PLUS(x2, j2);
-        x3 = PLUS(x3, j3);
-        x4 = PLUS(x4, j4);
-        x5 = PLUS(x5, j5);
-        x6 = PLUS(x6, j6);
-        x7 = PLUS(x7, j7);
-        x8 = PLUS(x8, j8);
-        x9 = PLUS(x9, j9);
-        x10 = PLUS(x10, j10);
-        x11 = PLUS(x11, j11);
-        x12 = PLUS(x12, j12);
-        x13 = PLUS(x13, j13);
-        x14 = PLUS(x14, j14);
-        x15 = PLUS(x15, j15);
-
-        U32TO8_LITTLE(c + 0, XOR(x0, U8TO32_LITTLE(m + 0)));
-        U32TO8_LITTLE(c + 4, XOR(x1, U8TO32_LITTLE(m + 4)));
-        U32TO8_LITTLE(c + 8, XOR(x2, U8TO32_LITTLE(m + 8)));
-        U32TO8_LITTLE(c + 12, XOR(x3, U8TO32_LITTLE(m + 12)));
-        U32TO8_LITTLE(c + 16, XOR(x4, U8TO32_LITTLE(m + 16)));
-        U32TO8_LITTLE(c + 20, XOR(x5, U8TO32_LITTLE(m + 20)));
-        U32TO8_LITTLE(c + 24, XOR(x6, U8TO32_LITTLE(m + 24)));
-        U32TO8_LITTLE(c + 28, XOR(x7, U8TO32_LITTLE(m + 28)));
-        U32TO8_LITTLE(c + 32, XOR(x8, U8TO32_LITTLE(m + 32)));
-        U32TO8_LITTLE(c + 36, XOR(x9, U8TO32_LITTLE(m + 36)));
-        U32TO8_LITTLE(c + 40, XOR(x10, U8TO32_LITTLE(m + 40)));
-        U32TO8_LITTLE(c + 44, XOR(x11, U8TO32_LITTLE(m + 44)));
-        U32TO8_LITTLE(c + 48, XOR(x12, U8TO32_LITTLE(m + 48)));
-        U32TO8_LITTLE(c + 52, XOR(x13, U8TO32_LITTLE(m + 52)));
-        U32TO8_LITTLE(c + 56, XOR(x14, U8TO32_LITTLE(m + 56)));
-        U32TO8_LITTLE(c + 60, XOR(x15, U8TO32_LITTLE(m + 60)));
-
-        if (! (++j8)) {
-            ++j9;
-            /* stopping at 2^70 bytes per nonce is user's responsibility */
-        }
+		x0 = j0;
+		x1 = j1;
+		x2 = j2;
+		x3 = j3;
+		x4 = j4;
+		x5 = j5;
+		x6 = j6;
+		x7 = j7;
+		x8 = j8;
+		x9 = j9;
+		x10 = j10;
+		x11 = j11;
+		x12 = j12;
+		x13 = j13;
+		x14 = j14;
+		x15 = j15;
+
+		// 2X round -------------------------------------------------------------
+		x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
+		x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
+		x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
+		x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
+		x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
+		x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
+		x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
+		x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
+		x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
+		x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
+		x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
+		x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
+		x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
+		x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
+		x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
+		x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
+
+		// 2X round -------------------------------------------------------------
+		x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
+		x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
+		x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
+		x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
+		x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
+		x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
+		x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
+		x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
+		x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
+		x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
+		x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
+		x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
+		x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
+		x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
+		x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
+		x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
+
+		// 2X round -------------------------------------------------------------
+		x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
+		x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
+		x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
+		x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
+		x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
+		x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
+		x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
+		x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
+		x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
+		x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
+		x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
+		x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
+		x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
+		x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
+		x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
+		x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
+
+		// 2X round -------------------------------------------------------------
+		x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
+		x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
+		x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
+		x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
+		x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
+		x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
+		x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
+		x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
+		x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
+		x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
+		x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
+		x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
+		x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
+		x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
+		x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
+		x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
+
+		// 2X round -------------------------------------------------------------
+		x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
+		x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
+		x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
+		x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
+		x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
+		x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
+		x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
+		x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
+		x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
+		x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
+		x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
+		x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
+		x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
+		x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
+		x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
+		x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
+
+		// 2X round -------------------------------------------------------------
+		x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
+		x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
+		x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
+		x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
+		x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
+		x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
+		x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
+		x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
+		x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
+		x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
+		x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
+		x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
+		x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
+		x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
+		x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
+		x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
+
+		// 2X round -------------------------------------------------------------
+		x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
+		x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
+		x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
+		x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
+		x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
+		x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
+		x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
+		x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
+		x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
+		x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
+		x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
+		x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
+		x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
+		x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
+		x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
+		x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
+
+		// 2X round -------------------------------------------------------------
+		x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
+		x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
+		x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
+		x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
+		x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
+		x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
+		x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
+		x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
+		x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
+		x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
+		x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
+		x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
+		x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
+		x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
+		x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
+		x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
+
+		// 2X round -------------------------------------------------------------
+		x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
+		x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
+		x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
+		x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
+		x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
+		x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
+		x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
+		x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
+		x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
+		x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
+		x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
+		x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
+		x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
+		x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
+		x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
+		x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
+
+		// 2X round -------------------------------------------------------------
+		x4 = XOR(x4, ROTATE(PLUS(x0, x12), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x4, x0), 9));
+		x12 = XOR(x12, ROTATE(PLUS(x8, x4), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x12, x8), 18));
+		x9 = XOR(x9, ROTATE(PLUS(x5, x1), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x9, x5), 9));
+		x1 = XOR(x1, ROTATE(PLUS(x13, x9), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x1, x13), 18));
+		x14 = XOR(x14, ROTATE(PLUS(x10, x6), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x14, x10), 9));
+		x6 = XOR(x6, ROTATE(PLUS(x2, x14), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x6, x2), 18));
+		x3 = XOR(x3, ROTATE(PLUS(x15, x11), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x3, x15), 9));
+		x11 = XOR(x11, ROTATE(PLUS(x7, x3), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x11, x7), 18));
+		x1 = XOR(x1, ROTATE(PLUS(x0, x3), 7));
+		x2 = XOR(x2, ROTATE(PLUS(x1, x0), 9));
+		x3 = XOR(x3, ROTATE(PLUS(x2, x1), 13));
+		x0 = XOR(x0, ROTATE(PLUS(x3, x2), 18));
+		x6 = XOR(x6, ROTATE(PLUS(x5, x4), 7));
+		x7 = XOR(x7, ROTATE(PLUS(x6, x5), 9));
+		x4 = XOR(x4, ROTATE(PLUS(x7, x6), 13));
+		x5 = XOR(x5, ROTATE(PLUS(x4, x7), 18));
+		x11 = XOR(x11, ROTATE(PLUS(x10, x9), 7));
+		x8 = XOR(x8, ROTATE(PLUS(x11, x10), 9));
+		x9 = XOR(x9, ROTATE(PLUS(x8, x11), 13));
+		x10 = XOR(x10, ROTATE(PLUS(x9, x8), 18));
+		x12 = XOR(x12, ROTATE(PLUS(x15, x14), 7));
+		x13 = XOR(x13, ROTATE(PLUS(x12, x15), 9));
+		x14 = XOR(x14, ROTATE(PLUS(x13, x12), 13));
+		x15 = XOR(x15, ROTATE(PLUS(x14, x13), 18));
+
+		x0 = PLUS(x0, j0);
+		x1 = PLUS(x1, j1);
+		x2 = PLUS(x2, j2);
+		x3 = PLUS(x3, j3);
+		x4 = PLUS(x4, j4);
+		x5 = PLUS(x5, j5);
+		x6 = PLUS(x6, j6);
+		x7 = PLUS(x7, j7);
+		x8 = PLUS(x8, j8);
+		x9 = PLUS(x9, j9);
+		x10 = PLUS(x10, j10);
+		x11 = PLUS(x11, j11);
+		x12 = PLUS(x12, j12);
+		x13 = PLUS(x13, j13);
+		x14 = PLUS(x14, j14);
+		x15 = PLUS(x15, j15);
+
+		U32TO8_LITTLE(c + 0, XOR(x0, U8TO32_LITTLE(m + 0)));
+		U32TO8_LITTLE(c + 4, XOR(x1, U8TO32_LITTLE(m + 4)));
+		U32TO8_LITTLE(c + 8, XOR(x2, U8TO32_LITTLE(m + 8)));
+		U32TO8_LITTLE(c + 12, XOR(x3, U8TO32_LITTLE(m + 12)));
+		U32TO8_LITTLE(c + 16, XOR(x4, U8TO32_LITTLE(m + 16)));
+		U32TO8_LITTLE(c + 20, XOR(x5, U8TO32_LITTLE(m + 20)));
+		U32TO8_LITTLE(c + 24, XOR(x6, U8TO32_LITTLE(m + 24)));
+		U32TO8_LITTLE(c + 28, XOR(x7, U8TO32_LITTLE(m + 28)));
+		U32TO8_LITTLE(c + 32, XOR(x8, U8TO32_LITTLE(m + 32)));
+		U32TO8_LITTLE(c + 36, XOR(x9, U8TO32_LITTLE(m + 36)));
+		U32TO8_LITTLE(c + 40, XOR(x10, U8TO32_LITTLE(m + 40)));
+		U32TO8_LITTLE(c + 44, XOR(x11, U8TO32_LITTLE(m + 44)));
+		U32TO8_LITTLE(c + 48, XOR(x12, U8TO32_LITTLE(m + 48)));
+		U32TO8_LITTLE(c + 52, XOR(x13, U8TO32_LITTLE(m + 52)));
+		U32TO8_LITTLE(c + 56, XOR(x14, U8TO32_LITTLE(m + 56)));
+		U32TO8_LITTLE(c + 60, XOR(x15, U8TO32_LITTLE(m + 60)));
+
+		if (! (++j8)) {
+			++j9;
+			/* stopping at 2^70 bytes per nonce is user's responsibility */
+		}
 #endif
 
-        if (bytes <= 64) {
-            if (bytes < 64) {
-                for (i = 0; i < bytes; ++i) {
-                    ctarget[i] = c[i];
-                }
-            }
+		if (bytes <= 64) {
+			if (bytes < 64) {
+				for (i = 0; i < bytes; ++i) {
+					ctarget[i] = c[i];
+				}
+			}
 
 #ifndef ZT_SALSA20_SSE
-            _state.i[8] = j8;
-            _state.i[9] = j9;
+			_state.i[8] = j8;
+			_state.i[9] = j9;
 #endif
 
-            return;
-        }
+			return;
+		}
 
-        bytes -= 64;
-        c += 64;
-        m += 64;
-    }
+		bytes -= 64;
+		c += 64;
+		m += 64;
+	}
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 115 - 115
node/Salsa20.hpp

@@ -21,7 +21,7 @@
 
 #ifdef ZT_SALSA20_SSE
 #include <emmintrin.h>
-#endif   // ZT_SALSA20_SSE
+#endif	 // ZT_SALSA20_SSE
 
 namespace ZeroTier {
 
@@ -30,134 +30,134 @@ namespace ZeroTier {
  */
 class Salsa20 {
   public:
-    Salsa20()
-    {
-    }
-    ~Salsa20()
-    {
-        Utils::burn(&_state, sizeof(_state));
-    }
+	Salsa20()
+	{
+	}
+	~Salsa20()
+	{
+		Utils::burn(&_state, sizeof(_state));
+	}
 
-    /**
-     * XOR d with s
-     *
-     * This is done efficiently using e.g. SSE if available. It's used when
-     * alternative Salsa20 implementations are used in Packet and is here
-     * since this is where all the SSE stuff is already included.
-     *
-     * @param d Destination to XOR
-     * @param s Source bytes to XOR with destination
-     * @param len Length of s and d
-     */
-    static inline void memxor(uint8_t* d, const uint8_t* s, unsigned int len)
-    {
+	/**
+	 * XOR d with s
+	 *
+	 * This is done efficiently using e.g. SSE if available. It's used when
+	 * alternative Salsa20 implementations are used in Packet and is here
+	 * since this is where all the SSE stuff is already included.
+	 *
+	 * @param d Destination to XOR
+	 * @param s Source bytes to XOR with destination
+	 * @param len Length of s and d
+	 */
+	static inline void memxor(uint8_t* d, const uint8_t* s, unsigned int len)
+	{
 #ifdef ZT_SALSA20_SSE
-        while (len >= 128) {
-            __m128i s0 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(s));
-            __m128i s1 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(s + 16));
-            __m128i s2 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(s + 32));
-            __m128i s3 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(s + 48));
-            __m128i s4 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(s + 64));
-            __m128i s5 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(s + 80));
-            __m128i s6 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(s + 96));
-            __m128i s7 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(s + 112));
-            __m128i d0 = _mm_loadu_si128(reinterpret_cast<__m128i*>(d));
-            __m128i d1 = _mm_loadu_si128(reinterpret_cast<__m128i*>(d + 16));
-            __m128i d2 = _mm_loadu_si128(reinterpret_cast<__m128i*>(d + 32));
-            __m128i d3 = _mm_loadu_si128(reinterpret_cast<__m128i*>(d + 48));
-            __m128i d4 = _mm_loadu_si128(reinterpret_cast<__m128i*>(d + 64));
-            __m128i d5 = _mm_loadu_si128(reinterpret_cast<__m128i*>(d + 80));
-            __m128i d6 = _mm_loadu_si128(reinterpret_cast<__m128i*>(d + 96));
-            __m128i d7 = _mm_loadu_si128(reinterpret_cast<__m128i*>(d + 112));
-            d0 = _mm_xor_si128(d0, s0);
-            d1 = _mm_xor_si128(d1, s1);
-            d2 = _mm_xor_si128(d2, s2);
-            d3 = _mm_xor_si128(d3, s3);
-            d4 = _mm_xor_si128(d4, s4);
-            d5 = _mm_xor_si128(d5, s5);
-            d6 = _mm_xor_si128(d6, s6);
-            d7 = _mm_xor_si128(d7, s7);
-            _mm_storeu_si128(reinterpret_cast<__m128i*>(d), d0);
-            _mm_storeu_si128(reinterpret_cast<__m128i*>(d + 16), d1);
-            _mm_storeu_si128(reinterpret_cast<__m128i*>(d + 32), d2);
-            _mm_storeu_si128(reinterpret_cast<__m128i*>(d + 48), d3);
-            _mm_storeu_si128(reinterpret_cast<__m128i*>(d + 64), d4);
-            _mm_storeu_si128(reinterpret_cast<__m128i*>(d + 80), d5);
-            _mm_storeu_si128(reinterpret_cast<__m128i*>(d + 96), d6);
-            _mm_storeu_si128(reinterpret_cast<__m128i*>(d + 112), d7);
-            s += 128;
-            d += 128;
-            len -= 128;
-        }
-        while (len >= 16) {
-            _mm_storeu_si128(reinterpret_cast<__m128i*>(d), _mm_xor_si128(_mm_loadu_si128(reinterpret_cast<__m128i*>(d)), _mm_loadu_si128(reinterpret_cast<const __m128i*>(s))));
-            s += 16;
-            d += 16;
-            len -= 16;
-        }
+		while (len >= 128) {
+			__m128i s0 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(s));
+			__m128i s1 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(s + 16));
+			__m128i s2 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(s + 32));
+			__m128i s3 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(s + 48));
+			__m128i s4 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(s + 64));
+			__m128i s5 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(s + 80));
+			__m128i s6 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(s + 96));
+			__m128i s7 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(s + 112));
+			__m128i d0 = _mm_loadu_si128(reinterpret_cast<__m128i*>(d));
+			__m128i d1 = _mm_loadu_si128(reinterpret_cast<__m128i*>(d + 16));
+			__m128i d2 = _mm_loadu_si128(reinterpret_cast<__m128i*>(d + 32));
+			__m128i d3 = _mm_loadu_si128(reinterpret_cast<__m128i*>(d + 48));
+			__m128i d4 = _mm_loadu_si128(reinterpret_cast<__m128i*>(d + 64));
+			__m128i d5 = _mm_loadu_si128(reinterpret_cast<__m128i*>(d + 80));
+			__m128i d6 = _mm_loadu_si128(reinterpret_cast<__m128i*>(d + 96));
+			__m128i d7 = _mm_loadu_si128(reinterpret_cast<__m128i*>(d + 112));
+			d0 = _mm_xor_si128(d0, s0);
+			d1 = _mm_xor_si128(d1, s1);
+			d2 = _mm_xor_si128(d2, s2);
+			d3 = _mm_xor_si128(d3, s3);
+			d4 = _mm_xor_si128(d4, s4);
+			d5 = _mm_xor_si128(d5, s5);
+			d6 = _mm_xor_si128(d6, s6);
+			d7 = _mm_xor_si128(d7, s7);
+			_mm_storeu_si128(reinterpret_cast<__m128i*>(d), d0);
+			_mm_storeu_si128(reinterpret_cast<__m128i*>(d + 16), d1);
+			_mm_storeu_si128(reinterpret_cast<__m128i*>(d + 32), d2);
+			_mm_storeu_si128(reinterpret_cast<__m128i*>(d + 48), d3);
+			_mm_storeu_si128(reinterpret_cast<__m128i*>(d + 64), d4);
+			_mm_storeu_si128(reinterpret_cast<__m128i*>(d + 80), d5);
+			_mm_storeu_si128(reinterpret_cast<__m128i*>(d + 96), d6);
+			_mm_storeu_si128(reinterpret_cast<__m128i*>(d + 112), d7);
+			s += 128;
+			d += 128;
+			len -= 128;
+		}
+		while (len >= 16) {
+			_mm_storeu_si128(reinterpret_cast<__m128i*>(d), _mm_xor_si128(_mm_loadu_si128(reinterpret_cast<__m128i*>(d)), _mm_loadu_si128(reinterpret_cast<const __m128i*>(s))));
+			s += 16;
+			d += 16;
+			len -= 16;
+		}
 #else
 #ifndef ZT_NO_TYPE_PUNNING
-        while (len >= 16) {
-            (*reinterpret_cast<uint64_t*>(d)) ^= (*reinterpret_cast<const uint64_t*>(s));
-            s += 8;
-            d += 8;
-            (*reinterpret_cast<uint64_t*>(d)) ^= (*reinterpret_cast<const uint64_t*>(s));
-            s += 8;
-            d += 8;
-            len -= 16;
-        }
+		while (len >= 16) {
+			(*reinterpret_cast<uint64_t*>(d)) ^= (*reinterpret_cast<const uint64_t*>(s));
+			s += 8;
+			d += 8;
+			(*reinterpret_cast<uint64_t*>(d)) ^= (*reinterpret_cast<const uint64_t*>(s));
+			s += 8;
+			d += 8;
+			len -= 16;
+		}
 #endif
 #endif
-        while (len) {
-            --len;
-            *(d++) ^= *(s++);
-        }
-    }
+		while (len) {
+			--len;
+			*(d++) ^= *(s++);
+		}
+	}
 
-    /**
-     * @param key 256-bit (32 byte) key
-     * @param iv 64-bit initialization vector
-     */
-    Salsa20(const void* key, const void* iv)
-    {
-        init(key, iv);
-    }
+	/**
+	 * @param key 256-bit (32 byte) key
+	 * @param iv 64-bit initialization vector
+	 */
+	Salsa20(const void* key, const void* iv)
+	{
+		init(key, iv);
+	}
 
-    /**
-     * Initialize cipher
-     *
-     * @param key Key bits
-     * @param iv 64-bit initialization vector
-     */
-    void init(const void* key, const void* iv);
+	/**
+	 * Initialize cipher
+	 *
+	 * @param key Key bits
+	 * @param iv 64-bit initialization vector
+	 */
+	void init(const void* key, const void* iv);
 
-    /**
-     * Encrypt/decrypt data using Salsa20/12
-     *
-     * @param in Input data
-     * @param out Output buffer
-     * @param bytes Length of data
-     */
-    void crypt12(const void* in, void* out, unsigned int bytes);
+	/**
+	 * Encrypt/decrypt data using Salsa20/12
+	 *
+	 * @param in Input data
+	 * @param out Output buffer
+	 * @param bytes Length of data
+	 */
+	void crypt12(const void* in, void* out, unsigned int bytes);
 
-    /**
-     * Encrypt/decrypt data using Salsa20/20
-     *
-     * @param in Input data
-     * @param out Output buffer
-     * @param bytes Length of data
-     */
-    void crypt20(const void* in, void* out, unsigned int bytes);
+	/**
+	 * Encrypt/decrypt data using Salsa20/20
+	 *
+	 * @param in Input data
+	 * @param out Output buffer
+	 * @param bytes Length of data
+	 */
+	void crypt20(const void* in, void* out, unsigned int bytes);
 
   private:
-    union {
+	union {
 #ifdef ZT_SALSA20_SSE
-        __m128i v[4];
-#endif   // ZT_SALSA20_SSE
-        uint32_t i[16];
-    } _state;
+		__m128i v[4];
+#endif	 // ZT_SALSA20_SSE
+		uint32_t i[16];
+	} _state;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 73 - 73
node/SelfAwareness.cpp

@@ -35,20 +35,20 @@ namespace ZeroTier {
 
 class _ResetWithinScope {
   public:
-    _ResetWithinScope(void* tPtr, int64_t now, int inetAddressFamily, InetAddress::IpScope scope) : _now(now), _tPtr(tPtr), _family(inetAddressFamily), _scope(scope)
-    {
-    }
+	_ResetWithinScope(void* tPtr, int64_t now, int inetAddressFamily, InetAddress::IpScope scope) : _now(now), _tPtr(tPtr), _family(inetAddressFamily), _scope(scope)
+	{
+	}
 
-    inline void operator()(Topology& t, const SharedPtr<Peer>& p)
-    {
-        p->resetWithinScope(_tPtr, _scope, _family, _now);
-    }
+	inline void operator()(Topology& t, const SharedPtr<Peer>& p)
+	{
+		p->resetWithinScope(_tPtr, _scope, _family, _now);
+	}
 
   private:
-    uint64_t _now;
-    void* _tPtr;
-    int _family;
-    InetAddress::IpScope _scope;
+	uint64_t _now;
+	void* _tPtr;
+	int _family;
+	InetAddress::IpScope _scope;
 };
 
 SelfAwareness::SelfAwareness(const RuntimeEnvironment* renv) : RR(renv), _phy(128)
@@ -57,75 +57,75 @@ SelfAwareness::SelfAwareness(const RuntimeEnvironment* renv) : RR(renv), _phy(12
 
 void SelfAwareness::iam(void* tPtr, const Address& reporter, const int64_t receivedOnLocalSocket, const InetAddress& reporterPhysicalAddress, const InetAddress& myPhysicalAddress, bool trusted, int64_t now)
 {
-    const InetAddress::IpScope scope = myPhysicalAddress.ipScope();
-
-    if ((scope != reporterPhysicalAddress.ipScope()) || (scope == InetAddress::IP_SCOPE_NONE) || (scope == InetAddress::IP_SCOPE_LOOPBACK) || (scope == InetAddress::IP_SCOPE_MULTICAST)) {
-        return;
-    }
-
-    Mutex::Lock _l(_phy_m);
-    PhySurfaceEntry& entry = _phy[PhySurfaceKey(reporter, receivedOnLocalSocket, reporterPhysicalAddress, scope)];
-
-    if ((trusted) && ((now - entry.ts) < ZT_SELFAWARENESS_ENTRY_TIMEOUT) && (! entry.mySurface.ipsEqual(myPhysicalAddress))) {
-        // Changes to external surface reported by trusted peers causes path reset in this scope
-        RR->t->resettingPathsInScope(tPtr, reporter, reporterPhysicalAddress, myPhysicalAddress, scope);
-
-        entry.mySurface = myPhysicalAddress;
-        entry.ts = now;
-        entry.trusted = trusted;
-
-        // Erase all entries in this scope that were not reported from this remote address to prevent 'thrashing'
-        // due to multiple reports of endpoint change.
-        // Don't use 'entry' after this since hash table gets modified.
-        {
-            Hashtable<PhySurfaceKey, PhySurfaceEntry>::Iterator i(_phy);
-            PhySurfaceKey* k = (PhySurfaceKey*)0;
-            PhySurfaceEntry* e = (PhySurfaceEntry*)0;
-            while (i.next(k, e)) {
-                if ((k->reporterPhysicalAddress != reporterPhysicalAddress) && (k->scope == scope)) {
-                    _phy.erase(*k);
-                }
-            }
-        }
-
-        // Reset all paths within this scope and address family
-        _ResetWithinScope rset(tPtr, now, myPhysicalAddress.ss_family, (InetAddress::IpScope)scope);
-        RR->topology->eachPeer<_ResetWithinScope&>(rset);
-    }
-    else {
-        // Otherwise just update DB to use to determine external surface info
-        entry.mySurface = myPhysicalAddress;
-        entry.ts = now;
-        entry.trusted = trusted;
-    }
+	const InetAddress::IpScope scope = myPhysicalAddress.ipScope();
+
+	if ((scope != reporterPhysicalAddress.ipScope()) || (scope == InetAddress::IP_SCOPE_NONE) || (scope == InetAddress::IP_SCOPE_LOOPBACK) || (scope == InetAddress::IP_SCOPE_MULTICAST)) {
+		return;
+	}
+
+	Mutex::Lock _l(_phy_m);
+	PhySurfaceEntry& entry = _phy[PhySurfaceKey(reporter, receivedOnLocalSocket, reporterPhysicalAddress, scope)];
+
+	if ((trusted) && ((now - entry.ts) < ZT_SELFAWARENESS_ENTRY_TIMEOUT) && (! entry.mySurface.ipsEqual(myPhysicalAddress))) {
+		// Changes to external surface reported by trusted peers causes path reset in this scope
+		RR->t->resettingPathsInScope(tPtr, reporter, reporterPhysicalAddress, myPhysicalAddress, scope);
+
+		entry.mySurface = myPhysicalAddress;
+		entry.ts = now;
+		entry.trusted = trusted;
+
+		// Erase all entries in this scope that were not reported from this remote address to prevent 'thrashing'
+		// due to multiple reports of endpoint change.
+		// Don't use 'entry' after this since hash table gets modified.
+		{
+			Hashtable<PhySurfaceKey, PhySurfaceEntry>::Iterator i(_phy);
+			PhySurfaceKey* k = (PhySurfaceKey*)0;
+			PhySurfaceEntry* e = (PhySurfaceEntry*)0;
+			while (i.next(k, e)) {
+				if ((k->reporterPhysicalAddress != reporterPhysicalAddress) && (k->scope == scope)) {
+					_phy.erase(*k);
+				}
+			}
+		}
+
+		// Reset all paths within this scope and address family
+		_ResetWithinScope rset(tPtr, now, myPhysicalAddress.ss_family, (InetAddress::IpScope)scope);
+		RR->topology->eachPeer<_ResetWithinScope&>(rset);
+	}
+	else {
+		// Otherwise just update DB to use to determine external surface info
+		entry.mySurface = myPhysicalAddress;
+		entry.ts = now;
+		entry.trusted = trusted;
+	}
 }
 
 std::vector<InetAddress> SelfAwareness::whoami()
 {
-    std::vector<InetAddress> surfaceAddresses;
-    Mutex::Lock _l(_phy_m);
-    Hashtable<PhySurfaceKey, PhySurfaceEntry>::Iterator i(_phy);
-    PhySurfaceKey* k = (PhySurfaceKey*)0;
-    PhySurfaceEntry* e = (PhySurfaceEntry*)0;
-    while (i.next(k, e)) {
-        if (std::find(surfaceAddresses.begin(), surfaceAddresses.end(), e->mySurface) == surfaceAddresses.end()) {
-            surfaceAddresses.push_back(e->mySurface);
-        }
-    }
-    return surfaceAddresses;
+	std::vector<InetAddress> surfaceAddresses;
+	Mutex::Lock _l(_phy_m);
+	Hashtable<PhySurfaceKey, PhySurfaceEntry>::Iterator i(_phy);
+	PhySurfaceKey* k = (PhySurfaceKey*)0;
+	PhySurfaceEntry* e = (PhySurfaceEntry*)0;
+	while (i.next(k, e)) {
+		if (std::find(surfaceAddresses.begin(), surfaceAddresses.end(), e->mySurface) == surfaceAddresses.end()) {
+			surfaceAddresses.push_back(e->mySurface);
+		}
+	}
+	return surfaceAddresses;
 }
 
 void SelfAwareness::clean(int64_t now)
 {
-    Mutex::Lock _l(_phy_m);
-    Hashtable<PhySurfaceKey, PhySurfaceEntry>::Iterator i(_phy);
-    PhySurfaceKey* k = (PhySurfaceKey*)0;
-    PhySurfaceEntry* e = (PhySurfaceEntry*)0;
-    while (i.next(k, e)) {
-        if ((now - e->ts) >= ZT_SELFAWARENESS_ENTRY_TIMEOUT) {
-            _phy.erase(*k);
-        }
-    }
+	Mutex::Lock _l(_phy_m);
+	Hashtable<PhySurfaceKey, PhySurfaceEntry>::Iterator i(_phy);
+	PhySurfaceKey* k = (PhySurfaceKey*)0;
+	PhySurfaceEntry* e = (PhySurfaceEntry*)0;
+	while (i.next(k, e)) {
+		if ((now - e->ts) >= ZT_SELFAWARENESS_ENTRY_TIMEOUT) {
+			_phy.erase(*k);
+		}
+	}
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 67 - 67
node/SelfAwareness.hpp

@@ -29,76 +29,76 @@ class RuntimeEnvironment;
  */
 class SelfAwareness {
   public:
-    SelfAwareness(const RuntimeEnvironment* renv);
-
-    /**
-     * Called when a trusted remote peer informs us of our external network address
-     *
-     * @param reporter ZeroTier address of reporting peer
-     * @param receivedOnLocalAddress Local address on which report was received
-     * @param reporterPhysicalAddress Physical address that reporting peer seems to have
-     * @param myPhysicalAddress Physical address that peer says we have
-     * @param trusted True if this peer is trusted as an authority to inform us of external address changes
-     * @param now Current time
-     */
-    void iam(void* tPtr, const Address& reporter, const int64_t receivedOnLocalSocket, const InetAddress& reporterPhysicalAddress, const InetAddress& myPhysicalAddress, bool trusted, int64_t now);
-
-    /**
-     * Return all known external surface addresses reported by peers
-     *
-     * @return A vector of InetAddress(es)
-     */
-    std::vector<InetAddress> whoami();
-
-    /**
-     * Clean up database periodically
-     *
-     * @param now Current time
-     */
-    void clean(int64_t now);
+	SelfAwareness(const RuntimeEnvironment* renv);
+
+	/**
+	 * Called when a trusted remote peer informs us of our external network address
+	 *
+	 * @param reporter ZeroTier address of reporting peer
+	 * @param receivedOnLocalAddress Local address on which report was received
+	 * @param reporterPhysicalAddress Physical address that reporting peer seems to have
+	 * @param myPhysicalAddress Physical address that peer says we have
+	 * @param trusted True if this peer is trusted as an authority to inform us of external address changes
+	 * @param now Current time
+	 */
+	void iam(void* tPtr, const Address& reporter, const int64_t receivedOnLocalSocket, const InetAddress& reporterPhysicalAddress, const InetAddress& myPhysicalAddress, bool trusted, int64_t now);
+
+	/**
+	 * Return all known external surface addresses reported by peers
+	 *
+	 * @return A vector of InetAddress(es)
+	 */
+	std::vector<InetAddress> whoami();
+
+	/**
+	 * Clean up database periodically
+	 *
+	 * @param now Current time
+	 */
+	void clean(int64_t now);
 
   private:
-    struct PhySurfaceKey {
-        Address reporter;
-        int64_t receivedOnLocalSocket;
-        InetAddress reporterPhysicalAddress;
-        InetAddress::IpScope scope;
-
-        PhySurfaceKey() : reporter(), scope(InetAddress::IP_SCOPE_NONE)
-        {
-        }
-        PhySurfaceKey(const Address& r, const int64_t rol, const InetAddress& ra, InetAddress::IpScope s) : reporter(r), receivedOnLocalSocket(rol), reporterPhysicalAddress(ra), scope(s)
-        {
-        }
-
-        inline unsigned long hashCode() const
-        {
-            return ((unsigned long)reporter.toInt() + (unsigned long)scope);
-        }
-        inline bool operator==(const PhySurfaceKey& k) const
-        {
-            return ((reporter == k.reporter) && (receivedOnLocalSocket == k.receivedOnLocalSocket) && (reporterPhysicalAddress == k.reporterPhysicalAddress) && (scope == k.scope));
-        }
-    };
-    struct PhySurfaceEntry {
-        InetAddress mySurface;
-        uint64_t ts;
-        bool trusted;
-
-        PhySurfaceEntry() : mySurface(), ts(0), trusted(false)
-        {
-        }
-        PhySurfaceEntry(const InetAddress& a, const uint64_t t) : mySurface(a), ts(t), trusted(false)
-        {
-        }
-    };
-
-    const RuntimeEnvironment* RR;
-
-    Hashtable<PhySurfaceKey, PhySurfaceEntry> _phy;
-    Mutex _phy_m;
+	struct PhySurfaceKey {
+		Address reporter;
+		int64_t receivedOnLocalSocket;
+		InetAddress reporterPhysicalAddress;
+		InetAddress::IpScope scope;
+
+		PhySurfaceKey() : reporter(), scope(InetAddress::IP_SCOPE_NONE)
+		{
+		}
+		PhySurfaceKey(const Address& r, const int64_t rol, const InetAddress& ra, InetAddress::IpScope s) : reporter(r), receivedOnLocalSocket(rol), reporterPhysicalAddress(ra), scope(s)
+		{
+		}
+
+		inline unsigned long hashCode() const
+		{
+			return ((unsigned long)reporter.toInt() + (unsigned long)scope);
+		}
+		inline bool operator==(const PhySurfaceKey& k) const
+		{
+			return ((reporter == k.reporter) && (receivedOnLocalSocket == k.receivedOnLocalSocket) && (reporterPhysicalAddress == k.reporterPhysicalAddress) && (scope == k.scope));
+		}
+	};
+	struct PhySurfaceEntry {
+		InetAddress mySurface;
+		uint64_t ts;
+		bool trusted;
+
+		PhySurfaceEntry() : mySurface(), ts(0), trusted(false)
+		{
+		}
+		PhySurfaceEntry(const InetAddress& a, const uint64_t t) : mySurface(a), ts(t), trusted(false)
+		{
+		}
+	};
+
+	const RuntimeEnvironment* RR;
+
+	Hashtable<PhySurfaceKey, PhySurfaceEntry> _phy;
+	Mutex _phy_m;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 139 - 139
node/SharedPtr.hpp

@@ -28,148 +28,148 @@ namespace ZeroTier {
  */
 template <typename T> class SharedPtr {
   public:
-    SharedPtr() : _ptr((T*)0)
-    {
-    }
-    SharedPtr(T* obj) : _ptr(obj)
-    {
-        ++obj->__refCount;
-    }
-    SharedPtr(const SharedPtr& sp) : _ptr(sp._getAndInc())
-    {
-    }
-
-    ~SharedPtr()
-    {
-        if (_ptr) {
-            if (--_ptr->__refCount <= 0) {
-                delete _ptr;
-            }
-        }
-    }
-
-    inline SharedPtr& operator=(const SharedPtr& sp)
-    {
-        if (_ptr != sp._ptr) {
-            T* p = sp._getAndInc();
-            if (_ptr) {
-                if (--_ptr->__refCount <= 0) {
-                    delete _ptr;
-                }
-            }
-            _ptr = p;
-        }
-        return *this;
-    }
-
-    /**
-     * Set to a naked pointer and increment its reference count
-     *
-     * This assumes this SharedPtr is NULL and that ptr is not a 'zombie.' No
-     * checks are performed.
-     *
-     * @param ptr Naked pointer to assign
-     */
-    inline void set(T* ptr)
-    {
-        zero();
-        ++ptr->__refCount;
-        _ptr = ptr;
-    }
-
-    /**
-     * Swap with another pointer 'for free' without ref count overhead
-     *
-     * @param with Pointer to swap with
-     */
-    inline void swap(SharedPtr& with)
-    {
-        T* tmp = _ptr;
-        _ptr = with._ptr;
-        with._ptr = tmp;
-    }
-
-    inline operator bool() const
-    {
-        return (_ptr != (T*)0);
-    }
-    inline T& operator*() const
-    {
-        return *_ptr;
-    }
-    inline T* operator->() const
-    {
-        return _ptr;
-    }
-
-    /**
-     * @return Raw pointer to held object
-     */
-    inline T* ptr() const
-    {
-        return _ptr;
-    }
-
-    /**
-     * Set this pointer to NULL
-     */
-    inline void zero()
-    {
-        if (_ptr) {
-            if (--_ptr->__refCount <= 0) {
-                delete _ptr;
-            }
-            _ptr = (T*)0;
-        }
-    }
-
-    /**
-     * @return Number of references according to this object's ref count or 0 if NULL
-     */
-    inline int references()
-    {
-        if (_ptr) {
-            return _ptr->__refCount.load();
-        }
-        return 0;
-    }
-
-    inline bool operator==(const SharedPtr& sp) const
-    {
-        return (_ptr == sp._ptr);
-    }
-    inline bool operator!=(const SharedPtr& sp) const
-    {
-        return (_ptr != sp._ptr);
-    }
-    inline bool operator>(const SharedPtr& sp) const
-    {
-        return (_ptr > sp._ptr);
-    }
-    inline bool operator<(const SharedPtr& sp) const
-    {
-        return (_ptr < sp._ptr);
-    }
-    inline bool operator>=(const SharedPtr& sp) const
-    {
-        return (_ptr >= sp._ptr);
-    }
-    inline bool operator<=(const SharedPtr& sp) const
-    {
-        return (_ptr <= sp._ptr);
-    }
+	SharedPtr() : _ptr((T*)0)
+	{
+	}
+	SharedPtr(T* obj) : _ptr(obj)
+	{
+		++obj->__refCount;
+	}
+	SharedPtr(const SharedPtr& sp) : _ptr(sp._getAndInc())
+	{
+	}
+
+	~SharedPtr()
+	{
+		if (_ptr) {
+			if (--_ptr->__refCount <= 0) {
+				delete _ptr;
+			}
+		}
+	}
+
+	inline SharedPtr& operator=(const SharedPtr& sp)
+	{
+		if (_ptr != sp._ptr) {
+			T* p = sp._getAndInc();
+			if (_ptr) {
+				if (--_ptr->__refCount <= 0) {
+					delete _ptr;
+				}
+			}
+			_ptr = p;
+		}
+		return *this;
+	}
+
+	/**
+	 * Set to a naked pointer and increment its reference count
+	 *
+	 * This assumes this SharedPtr is NULL and that ptr is not a 'zombie.' No
+	 * checks are performed.
+	 *
+	 * @param ptr Naked pointer to assign
+	 */
+	inline void set(T* ptr)
+	{
+		zero();
+		++ptr->__refCount;
+		_ptr = ptr;
+	}
+
+	/**
+	 * Swap with another pointer 'for free' without ref count overhead
+	 *
+	 * @param with Pointer to swap with
+	 */
+	inline void swap(SharedPtr& with)
+	{
+		T* tmp = _ptr;
+		_ptr = with._ptr;
+		with._ptr = tmp;
+	}
+
+	inline operator bool() const
+	{
+		return (_ptr != (T*)0);
+	}
+	inline T& operator*() const
+	{
+		return *_ptr;
+	}
+	inline T* operator->() const
+	{
+		return _ptr;
+	}
+
+	/**
+	 * @return Raw pointer to held object
+	 */
+	inline T* ptr() const
+	{
+		return _ptr;
+	}
+
+	/**
+	 * Set this pointer to NULL
+	 */
+	inline void zero()
+	{
+		if (_ptr) {
+			if (--_ptr->__refCount <= 0) {
+				delete _ptr;
+			}
+			_ptr = (T*)0;
+		}
+	}
+
+	/**
+	 * @return Number of references according to this object's ref count or 0 if NULL
+	 */
+	inline int references()
+	{
+		if (_ptr) {
+			return _ptr->__refCount.load();
+		}
+		return 0;
+	}
+
+	inline bool operator==(const SharedPtr& sp) const
+	{
+		return (_ptr == sp._ptr);
+	}
+	inline bool operator!=(const SharedPtr& sp) const
+	{
+		return (_ptr != sp._ptr);
+	}
+	inline bool operator>(const SharedPtr& sp) const
+	{
+		return (_ptr > sp._ptr);
+	}
+	inline bool operator<(const SharedPtr& sp) const
+	{
+		return (_ptr < sp._ptr);
+	}
+	inline bool operator>=(const SharedPtr& sp) const
+	{
+		return (_ptr >= sp._ptr);
+	}
+	inline bool operator<=(const SharedPtr& sp) const
+	{
+		return (_ptr <= sp._ptr);
+	}
 
   private:
-    inline T* _getAndInc() const
-    {
-        if (_ptr) {
-            ++_ptr->__refCount;
-        }
-        return _ptr;
-    }
-    T* _ptr;
+	inline T* _getAndInc() const
+	{
+		if (_ptr) {
+			++_ptr->__refCount;
+		}
+		return _ptr;
+	}
+	T* _ptr;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 1120 - 1124
node/Switch.cpp

@@ -34,1199 +34,1195 @@
 
 namespace ZeroTier {
 
-Switch::Switch(const RuntimeEnvironment* renv) : RR(renv), _lastBeaconResponse(0), _lastCheckedQueues(0), _lastUniteAttempt(8)   // only really used on root servers and upstreams, and it'll grow there just fine
+Switch::Switch(const RuntimeEnvironment* renv) : RR(renv), _lastBeaconResponse(0), _lastCheckedQueues(0), _lastUniteAttempt(8)	 // only really used on root servers and upstreams, and it'll grow there just fine
 {
 }
 
 // Returns true if packet appears valid; pos and proto will be set
 static bool _ipv6GetPayload(const uint8_t* frameData, unsigned int frameLen, unsigned int& pos, unsigned int& proto)
 {
-    if (frameLen < 40) {
-        return false;
-    }
-    pos = 40;
-    proto = frameData[6];
-    while (pos <= frameLen) {
-        switch (proto) {
-            case 0:     // hop-by-hop options
-            case 43:    // routing
-            case 60:    // destination options
-            case 135:   // mobility options
-                if ((pos + 8) > frameLen) {
-                    return false;   // invalid!
-                }
-                proto = frameData[pos];
-                pos += ((unsigned int)frameData[pos + 1] * 8) + 8;
-                break;
-
-            // case 44: // fragment -- we currently can't parse these and they are deprecated in IPv6 anyway
-            // case 50:
-            // case 51: // IPSec ESP and AH -- we have to stop here since this is encrypted stuff
-            default:
-                return true;
-        }
-    }
-    return false;   // overflow == invalid
+	if (frameLen < 40) {
+		return false;
+	}
+	pos = 40;
+	proto = frameData[6];
+	while (pos <= frameLen) {
+		switch (proto) {
+			case 0:		// hop-by-hop options
+			case 43:	// routing
+			case 60:	// destination options
+			case 135:	// mobility options
+				if ((pos + 8) > frameLen) {
+					return false;	// invalid!
+				}
+				proto = frameData[pos];
+				pos += ((unsigned int)frameData[pos + 1] * 8) + 8;
+				break;
+
+			// case 44: // fragment -- we currently can't parse these and they are deprecated in IPv6 anyway
+			// case 50:
+			// case 51: // IPSec ESP and AH -- we have to stop here since this is encrypted stuff
+			default:
+				return true;
+		}
+	}
+	return false;	// overflow == invalid
 }
 
 void Switch::onRemotePacket(void* tPtr, const int64_t localSocket, const InetAddress& fromAddr, const void* data, unsigned int len)
 {
-    int32_t flowId = ZT_QOS_NO_FLOW;
-    try {
-        const int64_t now = RR->node->now();
-
-        const SharedPtr<Path> path(RR->topology->getPath(localSocket, fromAddr));
-        path->received(now);
-
-        if (len > ZT_PROTO_MIN_FRAGMENT_LENGTH) {
-            if (reinterpret_cast<const uint8_t*>(data)[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_INDICATOR] == ZT_PACKET_FRAGMENT_INDICATOR) {
-                // Handle fragment ----------------------------------------------------
-
-                Packet::Fragment fragment(data, len);
-                const Address destination(fragment.destination());
-
-                if (destination != RR->identity.address()) {
-                    if ((! RR->topology->amUpstream()) && (! path->trustEstablished(now))) {
-                        return;
-                    }
-
-                    if (fragment.hops() < ZT_RELAY_MAX_HOPS) {
-                        fragment.incrementHops();
-
-                        // Note: we don't bother initiating NAT-t for fragments, since heads will set that off.
-                        // It wouldn't hurt anything, just redundant and unnecessary.
-                        SharedPtr<Peer> relayTo = RR->topology->getPeer(tPtr, destination);
-                        if ((! relayTo) || (! relayTo->sendDirect(tPtr, fragment.data(), fragment.size(), now, false))) {
-                            // Don't know peer or no direct path -- so relay via someone upstream
-                            relayTo = RR->topology->getUpstreamPeer();
-                            if (relayTo) {
-                                relayTo->sendDirect(tPtr, fragment.data(), fragment.size(), now, true);
-                            }
-                        }
-                    }
-                }
-                else {
-                    // Fragment looks like ours
-                    const uint64_t fragmentPacketId = fragment.packetId();
-                    const unsigned int fragmentNumber = fragment.fragmentNumber();
-                    const unsigned int totalFragments = fragment.totalFragments();
-
-                    if ((totalFragments <= ZT_MAX_PACKET_FRAGMENTS) && (fragmentNumber < ZT_MAX_PACKET_FRAGMENTS) && (fragmentNumber > 0) && (totalFragments > 1)) {
-                        // Fragment appears basically sane. Its fragment number must be
-                        // 1 or more, since a Packet with fragmented bit set is fragment 0.
-                        // Total fragments must be more than 1, otherwise why are we
-                        // seeing a Packet::Fragment?
-
-                        RXQueueEntry* const rq = _findRXQueueEntry(fragmentPacketId);
-                        Mutex::Lock rql(rq->lock);
-                        if (rq->packetId != fragmentPacketId) {
-                            // No packet found, so we received a fragment without its head.
-
-                            rq->flowId = flowId;
-                            rq->timestamp = now;
-                            rq->packetId = fragmentPacketId;
-                            rq->frags[fragmentNumber - 1] = fragment;
-                            rq->totalFragments = totalFragments;       // total fragment count is known
-                            rq->haveFragments = 1 << fragmentNumber;   // we have only this fragment
-                            rq->complete = false;
-                        }
-                        else if (! (rq->haveFragments & (1 << fragmentNumber))) {
-                            // We have other fragments and maybe the head, so add this one and check
-
-                            rq->frags[fragmentNumber - 1] = fragment;
-                            rq->totalFragments = totalFragments;
-
-                            if (Utils::countBits(rq->haveFragments |= (1 << fragmentNumber)) == totalFragments) {
-                                // We have all fragments -- assemble and process full Packet
-
-                                for (unsigned int f = 1; f < totalFragments; ++f) {
-                                    rq->frag0.append(rq->frags[f - 1].payload(), rq->frags[f - 1].payloadLength());
-                                }
-
-                                if (rq->frag0.tryDecode(RR, tPtr, flowId)) {
-                                    rq->timestamp = 0;   // packet decoded, free entry
-                                }
-                                else {
-                                    rq->complete = true;   // set complete flag but leave entry since it probably needs WHOIS or something
-                                }
-                            }
-                        }   // else this is a duplicate fragment, ignore
-                    }
-                }
-
-                // --------------------------------------------------------------------
-            }
-            else if (len >= ZT_PROTO_MIN_PACKET_LENGTH) {   // min length check is important!
-                // Handle packet head -------------------------------------------------
-
-                const Address destination(reinterpret_cast<const uint8_t*>(data) + 8, ZT_ADDRESS_LENGTH);
-                const Address source(reinterpret_cast<const uint8_t*>(data) + 13, ZT_ADDRESS_LENGTH);
-
-                if (source == RR->identity.address()) {
-                    return;
-                }
-
-                if (destination != RR->identity.address()) {
-                    if ((! RR->topology->amUpstream()) && (! path->trustEstablished(now)) && (source != RR->identity.address())) {
-                        return;
-                    }
-
-                    Packet packet(data, len);
-
-                    if (packet.hops() < ZT_RELAY_MAX_HOPS) {
-                        packet.incrementHops();
-                        SharedPtr<Peer> relayTo = RR->topology->getPeer(tPtr, destination);
-                        if ((relayTo) && (relayTo->sendDirect(tPtr, packet.data(), packet.size(), now, false))) {
-                            if ((source != RR->identity.address()) && (_shouldUnite(now, source, destination))) {
-                                const SharedPtr<Peer> sourcePeer(RR->topology->getPeer(tPtr, source));
-                                if (sourcePeer) {
-                                    relayTo->introduce(tPtr, now, sourcePeer);
-                                }
-                            }
-                        }
-                        else {
-                            relayTo = RR->topology->getUpstreamPeer();
-                            if ((relayTo) && (relayTo->address() != source)) {
-                                if (relayTo->sendDirect(tPtr, packet.data(), packet.size(), now, true)) {
-                                    const SharedPtr<Peer> sourcePeer(RR->topology->getPeer(tPtr, source));
-                                    if (sourcePeer) {
-                                        relayTo->introduce(tPtr, now, sourcePeer);
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-                else if ((reinterpret_cast<const uint8_t*>(data)[ZT_PACKET_IDX_FLAGS] & ZT_PROTO_FLAG_FRAGMENTED) != 0) {
-                    // Packet is the head of a fragmented packet series
-
-                    const uint64_t packetId =
-                        ((((uint64_t)reinterpret_cast<const uint8_t*>(data)[0]) << 56) | (((uint64_t)reinterpret_cast<const uint8_t*>(data)[1]) << 48) | (((uint64_t)reinterpret_cast<const uint8_t*>(data)[2]) << 40)
-                         | (((uint64_t)reinterpret_cast<const uint8_t*>(data)[3]) << 32) | (((uint64_t)reinterpret_cast<const uint8_t*>(data)[4]) << 24) | (((uint64_t)reinterpret_cast<const uint8_t*>(data)[5]) << 16)
-                         | (((uint64_t)reinterpret_cast<const uint8_t*>(data)[6]) << 8) | ((uint64_t)reinterpret_cast<const uint8_t*>(data)[7]));
-
-                    RXQueueEntry* const rq = _findRXQueueEntry(packetId);
-                    Mutex::Lock rql(rq->lock);
-                    if (rq->packetId != packetId) {
-                        // If we have no other fragments yet, create an entry and save the head
-
-                        rq->flowId = flowId;
-                        rq->timestamp = now;
-                        rq->packetId = packetId;
-                        rq->frag0.init(data, len, path, now);
-                        rq->totalFragments = 0;
-                        rq->haveFragments = 1;
-                        rq->complete = false;
-                    }
-                    else if (! (rq->haveFragments & 1)) {
-                        // If we have other fragments but no head, see if we are complete with the head
-
-                        if ((rq->totalFragments > 1) && (Utils::countBits(rq->haveFragments |= 1) == rq->totalFragments)) {
-                            // We have all fragments -- assemble and process full Packet
-
-                            rq->frag0.init(data, len, path, now);
-                            for (unsigned int f = 1; f < rq->totalFragments; ++f) {
-                                rq->frag0.append(rq->frags[f - 1].payload(), rq->frags[f - 1].payloadLength());
-                            }
-
-                            if (rq->frag0.tryDecode(RR, tPtr, flowId)) {
-                                rq->timestamp = 0;   // packet decoded, free entry
-                            }
-                            else {
-                                rq->complete = true;   // set complete flag but leave entry since it probably needs WHOIS or something
-                            }
-                        }
-                        else {
-                            // Still waiting on more fragments, but keep the head
-                            rq->frag0.init(data, len, path, now);
-                        }
-                    }   // else this is a duplicate head, ignore
-                }
-                else {
-                    // Packet is unfragmented, so just process it
-                    IncomingPacket packet(data, len, path, now);
-                    if (! packet.tryDecode(RR, tPtr, flowId)) {
-                        RXQueueEntry* const rq = _nextRXQueueEntry();
-                        Mutex::Lock rql(rq->lock);
-                        rq->flowId = flowId;
-                        rq->timestamp = now;
-                        rq->packetId = packet.packetId();
-                        rq->frag0 = packet;
-                        rq->totalFragments = 1;
-                        rq->haveFragments = 1;
-                        rq->complete = true;
-                    }
-                }
-
-                // --------------------------------------------------------------------
-            }
-        }
-    }
-    catch (...) {
-    }   // sanity check, should be caught elsewhere
+	int32_t flowId = ZT_QOS_NO_FLOW;
+	try {
+		const int64_t now = RR->node->now();
+
+		const SharedPtr<Path> path(RR->topology->getPath(localSocket, fromAddr));
+		path->received(now);
+
+		if (len > ZT_PROTO_MIN_FRAGMENT_LENGTH) {
+			if (reinterpret_cast<const uint8_t*>(data)[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_INDICATOR] == ZT_PACKET_FRAGMENT_INDICATOR) {
+				// Handle fragment ----------------------------------------------------
+
+				Packet::Fragment fragment(data, len);
+				const Address destination(fragment.destination());
+
+				if (destination != RR->identity.address()) {
+					if ((! RR->topology->amUpstream()) && (! path->trustEstablished(now))) {
+						return;
+					}
+
+					if (fragment.hops() < ZT_RELAY_MAX_HOPS) {
+						fragment.incrementHops();
+
+						// Note: we don't bother initiating NAT-t for fragments, since heads will set that off.
+						// It wouldn't hurt anything, just redundant and unnecessary.
+						SharedPtr<Peer> relayTo = RR->topology->getPeer(tPtr, destination);
+						if ((! relayTo) || (! relayTo->sendDirect(tPtr, fragment.data(), fragment.size(), now, false))) {
+							// Don't know peer or no direct path -- so relay via someone upstream
+							relayTo = RR->topology->getUpstreamPeer();
+							if (relayTo) {
+								relayTo->sendDirect(tPtr, fragment.data(), fragment.size(), now, true);
+							}
+						}
+					}
+				}
+				else {
+					// Fragment looks like ours
+					const uint64_t fragmentPacketId = fragment.packetId();
+					const unsigned int fragmentNumber = fragment.fragmentNumber();
+					const unsigned int totalFragments = fragment.totalFragments();
+
+					if ((totalFragments <= ZT_MAX_PACKET_FRAGMENTS) && (fragmentNumber < ZT_MAX_PACKET_FRAGMENTS) && (fragmentNumber > 0) && (totalFragments > 1)) {
+						// Fragment appears basically sane. Its fragment number must be
+						// 1 or more, since a Packet with fragmented bit set is fragment 0.
+						// Total fragments must be more than 1, otherwise why are we
+						// seeing a Packet::Fragment?
+
+						RXQueueEntry* const rq = _findRXQueueEntry(fragmentPacketId);
+						Mutex::Lock rql(rq->lock);
+						if (rq->packetId != fragmentPacketId) {
+							// No packet found, so we received a fragment without its head.
+
+							rq->flowId = flowId;
+							rq->timestamp = now;
+							rq->packetId = fragmentPacketId;
+							rq->frags[fragmentNumber - 1] = fragment;
+							rq->totalFragments = totalFragments;	   // total fragment count is known
+							rq->haveFragments = 1 << fragmentNumber;   // we have only this fragment
+							rq->complete = false;
+						}
+						else if (! (rq->haveFragments & (1 << fragmentNumber))) {
+							// We have other fragments and maybe the head, so add this one and check
+
+							rq->frags[fragmentNumber - 1] = fragment;
+							rq->totalFragments = totalFragments;
+
+							if (Utils::countBits(rq->haveFragments |= (1 << fragmentNumber)) == totalFragments) {
+								// We have all fragments -- assemble and process full Packet
+
+								for (unsigned int f = 1; f < totalFragments; ++f) {
+									rq->frag0.append(rq->frags[f - 1].payload(), rq->frags[f - 1].payloadLength());
+								}
+
+								if (rq->frag0.tryDecode(RR, tPtr, flowId)) {
+									rq->timestamp = 0;	 // packet decoded, free entry
+								}
+								else {
+									rq->complete = true;   // set complete flag but leave entry since it probably needs WHOIS or something
+								}
+							}
+						}	// else this is a duplicate fragment, ignore
+					}
+				}
+
+				// --------------------------------------------------------------------
+			}
+			else if (len >= ZT_PROTO_MIN_PACKET_LENGTH) {	// min length check is important!
+				// Handle packet head -------------------------------------------------
+
+				const Address destination(reinterpret_cast<const uint8_t*>(data) + 8, ZT_ADDRESS_LENGTH);
+				const Address source(reinterpret_cast<const uint8_t*>(data) + 13, ZT_ADDRESS_LENGTH);
+
+				if (source == RR->identity.address()) {
+					return;
+				}
+
+				if (destination != RR->identity.address()) {
+					if ((! RR->topology->amUpstream()) && (! path->trustEstablished(now)) && (source != RR->identity.address())) {
+						return;
+					}
+
+					Packet packet(data, len);
+
+					if (packet.hops() < ZT_RELAY_MAX_HOPS) {
+						packet.incrementHops();
+						SharedPtr<Peer> relayTo = RR->topology->getPeer(tPtr, destination);
+						if ((relayTo) && (relayTo->sendDirect(tPtr, packet.data(), packet.size(), now, false))) {
+							if ((source != RR->identity.address()) && (_shouldUnite(now, source, destination))) {
+								const SharedPtr<Peer> sourcePeer(RR->topology->getPeer(tPtr, source));
+								if (sourcePeer) {
+									relayTo->introduce(tPtr, now, sourcePeer);
+								}
+							}
+						}
+						else {
+							relayTo = RR->topology->getUpstreamPeer();
+							if ((relayTo) && (relayTo->address() != source)) {
+								if (relayTo->sendDirect(tPtr, packet.data(), packet.size(), now, true)) {
+									const SharedPtr<Peer> sourcePeer(RR->topology->getPeer(tPtr, source));
+									if (sourcePeer) {
+										relayTo->introduce(tPtr, now, sourcePeer);
+									}
+								}
+							}
+						}
+					}
+				}
+				else if ((reinterpret_cast<const uint8_t*>(data)[ZT_PACKET_IDX_FLAGS] & ZT_PROTO_FLAG_FRAGMENTED) != 0) {
+					// Packet is the head of a fragmented packet series
+
+					const uint64_t packetId =
+						((((uint64_t)reinterpret_cast<const uint8_t*>(data)[0]) << 56) | (((uint64_t)reinterpret_cast<const uint8_t*>(data)[1]) << 48) | (((uint64_t)reinterpret_cast<const uint8_t*>(data)[2]) << 40)
+						 | (((uint64_t)reinterpret_cast<const uint8_t*>(data)[3]) << 32) | (((uint64_t)reinterpret_cast<const uint8_t*>(data)[4]) << 24) | (((uint64_t)reinterpret_cast<const uint8_t*>(data)[5]) << 16)
+						 | (((uint64_t)reinterpret_cast<const uint8_t*>(data)[6]) << 8) | ((uint64_t)reinterpret_cast<const uint8_t*>(data)[7]));
+
+					RXQueueEntry* const rq = _findRXQueueEntry(packetId);
+					Mutex::Lock rql(rq->lock);
+					if (rq->packetId != packetId) {
+						// If we have no other fragments yet, create an entry and save the head
+
+						rq->flowId = flowId;
+						rq->timestamp = now;
+						rq->packetId = packetId;
+						rq->frag0.init(data, len, path, now);
+						rq->totalFragments = 0;
+						rq->haveFragments = 1;
+						rq->complete = false;
+					}
+					else if (! (rq->haveFragments & 1)) {
+						// If we have other fragments but no head, see if we are complete with the head
+
+						if ((rq->totalFragments > 1) && (Utils::countBits(rq->haveFragments |= 1) == rq->totalFragments)) {
+							// We have all fragments -- assemble and process full Packet
+
+							rq->frag0.init(data, len, path, now);
+							for (unsigned int f = 1; f < rq->totalFragments; ++f) {
+								rq->frag0.append(rq->frags[f - 1].payload(), rq->frags[f - 1].payloadLength());
+							}
+
+							if (rq->frag0.tryDecode(RR, tPtr, flowId)) {
+								rq->timestamp = 0;	 // packet decoded, free entry
+							}
+							else {
+								rq->complete = true;   // set complete flag but leave entry since it probably needs WHOIS or something
+							}
+						}
+						else {
+							// Still waiting on more fragments, but keep the head
+							rq->frag0.init(data, len, path, now);
+						}
+					}	// else this is a duplicate head, ignore
+				}
+				else {
+					// Packet is unfragmented, so just process it
+					IncomingPacket packet(data, len, path, now);
+					if (! packet.tryDecode(RR, tPtr, flowId)) {
+						RXQueueEntry* const rq = _nextRXQueueEntry();
+						Mutex::Lock rql(rq->lock);
+						rq->flowId = flowId;
+						rq->timestamp = now;
+						rq->packetId = packet.packetId();
+						rq->frag0 = packet;
+						rq->totalFragments = 1;
+						rq->haveFragments = 1;
+						rq->complete = true;
+					}
+				}
+
+				// --------------------------------------------------------------------
+			}
+		}
+	}
+	catch (...) {
+	}	// sanity check, should be caught elsewhere
 }
 
 void Switch::onLocalEthernet(void* tPtr, const SharedPtr<Network>& network, const MAC& from, const MAC& to, unsigned int etherType, unsigned int vlanId, const void* data, unsigned int len)
 {
-    if (! network->hasConfig()) {
-        return;
-    }
-
-    // Check if this packet is from someone other than the tap -- i.e. bridged in
-    bool fromBridged;
-    if ((fromBridged = (from != network->mac()))) {
-        if (! network->config().permitsBridging(RR->identity.address())) {
-            RR->t->outgoingNetworkFrameDropped(tPtr, network, from, to, etherType, vlanId, len, "not a bridge");
-            return;
-        }
-    }
-
-    uint8_t qosBucket = ZT_AQM_DEFAULT_BUCKET;
-
-    /**
-     * A pseudo-unique identifier used by balancing and bonding policies to
-     * categorize individual flows/conversations for assignment to a specific
-     * physical path. This identifier consists of the source port and
-     * destination port of the encapsulated frame.
-     *
-     * A flowId of -1 will indicate that there is no preference for how this
-     * packet shall be sent. An example of this would be an ICMP packet.
-     */
-
-    int32_t flowId = ZT_QOS_NO_FLOW;
-
-    if (etherType == ZT_ETHERTYPE_IPV4 && (len >= 20)) {
-        uint16_t srcPort = 0;
-        uint16_t dstPort = 0;
-        uint8_t proto = (reinterpret_cast<const uint8_t*>(data)[9]);
-        const unsigned int headerLen = 4 * (reinterpret_cast<const uint8_t*>(data)[0] & 0xf);
-        switch (proto) {
-            case 0x01:   // ICMP
-                // flowId = 0x01;
-                break;
-            // All these start with 16-bit source and destination port in that order
-            case 0x06:   // TCP
-            case 0x11:   // UDP
-            case 0x84:   // SCTP
-            case 0x88:   // UDPLite
-                if (len > (headerLen + 4)) {
-                    unsigned int pos = headerLen + 0;
-                    srcPort = (reinterpret_cast<const uint8_t*>(data)[pos++]) << 8;
-                    srcPort |= (reinterpret_cast<const uint8_t*>(data)[pos]);
-                    pos++;
-                    dstPort = (reinterpret_cast<const uint8_t*>(data)[pos++]) << 8;
-                    dstPort |= (reinterpret_cast<const uint8_t*>(data)[pos]);
-                    flowId = dstPort ^ srcPort ^ proto;
-                }
-                break;
-        }
-    }
-
-    if (etherType == ZT_ETHERTYPE_IPV6 && (len >= 40)) {
-        uint16_t srcPort = 0;
-        uint16_t dstPort = 0;
-        unsigned int pos;
-        unsigned int proto;
-        _ipv6GetPayload((const uint8_t*)data, len, pos, proto);
-        switch (proto) {
-            case 0x3A:   // ICMPv6
-                // flowId = 0x3A;
-                break;
-            // All these start with 16-bit source and destination port in that order
-            case 0x06:   // TCP
-            case 0x11:   // UDP
-            case 0x84:   // SCTP
-            case 0x88:   // UDPLite
-                if (len > (pos + 4)) {
-                    srcPort = (reinterpret_cast<const uint8_t*>(data)[pos++]) << 8;
-                    srcPort |= (reinterpret_cast<const uint8_t*>(data)[pos]);
-                    pos++;
-                    dstPort = (reinterpret_cast<const uint8_t*>(data)[pos++]) << 8;
-                    dstPort |= (reinterpret_cast<const uint8_t*>(data)[pos]);
-                    flowId = dstPort ^ srcPort ^ proto;
-                }
-                break;
-            default:
-                break;
-        }
-    }
-
-    if (to.isMulticast()) {
-        MulticastGroup multicastGroup(to, 0);
-
-        if (to.isBroadcast()) {
-            if ((etherType == ZT_ETHERTYPE_ARP) && (len >= 28)
-                && ((((const uint8_t*)data)[2] == 0x08) && (((const uint8_t*)data)[3] == 0x00) && (((const uint8_t*)data)[4] == 6) && (((const uint8_t*)data)[5] == 4) && (((const uint8_t*)data)[7] == 0x01))) {
-                /* IPv4 ARP is one of the few special cases that we impose upon what is
-                 * otherwise a straightforward Ethernet switch emulation. Vanilla ARP
-                 * is dumb old broadcast and simply doesn't scale. ZeroTier multicast
-                 * groups have an additional field called ADI (additional distinguishing
-                 * information) which was added specifically for ARP though it could
-                 * be used for other things too. We then take ARP broadcasts and turn
-                 * them into multicasts by stuffing the IP address being queried into
-                 * the 32-bit ADI field. In practice this uses our multicast pub/sub
-                 * system to implement a kind of extended/distributed ARP table. */
-                multicastGroup = MulticastGroup::deriveMulticastGroupForAddressResolution(InetAddress(((const unsigned char*)data) + 24, 4, 0));
-            }
-            else if (! network->config().enableBroadcast()) {
-                // Don't transmit broadcasts if this network doesn't want them
-                RR->t->outgoingNetworkFrameDropped(tPtr, network, from, to, etherType, vlanId, len, "broadcast disabled");
-                return;
-            }
-        }
-        else if ((etherType == ZT_ETHERTYPE_IPV6) && (len >= (40 + 8 + 16))) {
-            // IPv6 NDP emulation for certain very special patterns of private IPv6 addresses -- if enabled
-            if ((network->config().ndpEmulation()) && (reinterpret_cast<const uint8_t*>(data)[6] == 0x3a) && (reinterpret_cast<const uint8_t*>(data)[40] == 0x87)) {   // ICMPv6 neighbor solicitation
-                Address v6EmbeddedAddress;
-                const uint8_t* const pkt6 = reinterpret_cast<const uint8_t*>(data) + 40 + 8;
-                const uint8_t* my6 = (const uint8_t*)0;
-
-                // ZT-RFC4193 address: fdNN:NNNN:NNNN:NNNN:NN99:93DD:DDDD:DDDD / 88 (one /128 per actual host)
-
-                // ZT-6PLANE address:  fcXX:XXXX:XXDD:DDDD:DDDD:####:####:#### / 40 (one /80 per actual host)
-                // (XX - lower 32 bits of network ID XORed with higher 32 bits)
-
-                // For these to work, we must have a ZT-managed address assigned in one of the
-                // above formats, and the query must match its prefix.
-                for (unsigned int sipk = 0; sipk < network->config().staticIpCount; ++sipk) {
-                    const InetAddress* const sip = &(network->config().staticIps[sipk]);
-                    if (sip->ss_family == AF_INET6) {
-                        my6 = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(&(*sip))->sin6_addr.s6_addr);
-                        const unsigned int sipNetmaskBits = Utils::ntoh((uint16_t)reinterpret_cast<const struct sockaddr_in6*>(&(*sip))->sin6_port);
-                        if ((sipNetmaskBits == 88) && (my6[0] == 0xfd) && (my6[9] == 0x99) && (my6[10] == 0x93)) {   // ZT-RFC4193 /88 ???
-                            unsigned int ptr = 0;
-                            while (ptr != 11) {
-                                if (pkt6[ptr] != my6[ptr]) {
-                                    break;
-                                }
-                                ++ptr;
-                            }
-                            if (ptr == 11) {   // prefix match!
-                                v6EmbeddedAddress.setTo(pkt6 + ptr, 5);
-                                break;
-                            }
-                        }
-                        else if (sipNetmaskBits == 40) {   // ZT-6PLANE /40 ???
-                            const uint32_t nwid32 = (uint32_t)((network->id() ^ (network->id() >> 32)) & 0xffffffff);
-                            if ((my6[0] == 0xfc) && (my6[1] == (uint8_t)((nwid32 >> 24) & 0xff)) && (my6[2] == (uint8_t)((nwid32 >> 16) & 0xff)) && (my6[3] == (uint8_t)((nwid32 >> 8) & 0xff)) && (my6[4] == (uint8_t)(nwid32 & 0xff))) {
-                                unsigned int ptr = 0;
-                                while (ptr != 5) {
-                                    if (pkt6[ptr] != my6[ptr]) {
-                                        break;
-                                    }
-                                    ++ptr;
-                                }
-                                if (ptr == 5) {   // prefix match!
-                                    v6EmbeddedAddress.setTo(pkt6 + ptr, 5);
-                                    break;
-                                }
-                            }
-                        }
-                    }
-                }
-
-                if ((v6EmbeddedAddress) && (v6EmbeddedAddress != RR->identity.address())) {
-                    const MAC peerMac(v6EmbeddedAddress, network->id());
-
-                    uint8_t adv[72];
-                    adv[0] = 0x60;
-                    adv[1] = 0x00;
-                    adv[2] = 0x00;
-                    adv[3] = 0x00;
-                    adv[4] = 0x00;
-                    adv[5] = 0x20;
-                    adv[6] = 0x3a;
-                    adv[7] = 0xff;
-                    for (int i = 0; i < 16; ++i) {
-                        adv[8 + i] = pkt6[i];
-                    }
-                    for (int i = 0; i < 16; ++i) {
-                        adv[24 + i] = my6[i];
-                    }
-                    adv[40] = 0x88;
-                    adv[41] = 0x00;
-                    adv[42] = 0x00;
-                    adv[43] = 0x00;   // future home of checksum
-                    adv[44] = 0x60;
-                    adv[45] = 0x00;
-                    adv[46] = 0x00;
-                    adv[47] = 0x00;
-                    for (int i = 0; i < 16; ++i) {
-                        adv[48 + i] = pkt6[i];
-                    }
-                    adv[64] = 0x02;
-                    adv[65] = 0x01;
-                    adv[66] = peerMac[0];
-                    adv[67] = peerMac[1];
-                    adv[68] = peerMac[2];
-                    adv[69] = peerMac[3];
-                    adv[70] = peerMac[4];
-                    adv[71] = peerMac[5];
-
-                    uint16_t pseudo_[36];
-                    uint8_t* const pseudo = reinterpret_cast<uint8_t*>(pseudo_);
-                    for (int i = 0; i < 32; ++i) {
-                        pseudo[i] = adv[8 + i];
-                    }
-                    pseudo[32] = 0x00;
-                    pseudo[33] = 0x00;
-                    pseudo[34] = 0x00;
-                    pseudo[35] = 0x20;
-                    pseudo[36] = 0x00;
-                    pseudo[37] = 0x00;
-                    pseudo[38] = 0x00;
-                    pseudo[39] = 0x3a;
-                    for (int i = 0; i < 32; ++i) {
-                        pseudo[40 + i] = adv[40 + i];
-                    }
-                    uint32_t checksum = 0;
-                    for (int i = 0; i < 36; ++i) {
-                        checksum += Utils::hton(pseudo_[i]);
-                    }
-                    while ((checksum >> 16)) {
-                        checksum = (checksum & 0xffff) + (checksum >> 16);
-                    }
-                    checksum = ~checksum;
-                    adv[42] = (checksum >> 8) & 0xff;
-                    adv[43] = checksum & 0xff;
-
-                    //
-                    // call on separate background thread
-                    // this prevents problems related to trying to do rx while inside of doing tx, such as acquiring same lock recursively
-                    //
-
-                    std::thread([=]() {
-                        RR->node->putFrame(tPtr, network->id(), network->userPtr(), peerMac, from, ZT_ETHERTYPE_IPV6, 0, adv, 72);
-                    }).detach();
-
-                    return;   // NDP emulation done. We have forged a "fake" reply, so no need to send actual NDP query.
-                }   // else no NDP emulation
-            }   // else no NDP emulation
-        }
-
-        // Check this after NDP emulation, since that has to be allowed in exactly this case
-        if (network->config().multicastLimit == 0) {
-            RR->t->outgoingNetworkFrameDropped(tPtr, network, from, to, etherType, vlanId, len, "multicast disabled");
-            return;
-        }
-
-        /* Learn multicast groups for bridged-in hosts.
-         * Note that some OSes, most notably Linux, do this for you by learning
-         * multicast addresses on bridge interfaces and subscribing each slave.
-         * But in that case this does no harm, as the sets are just merged. */
-        if (fromBridged) {
-            network->learnBridgedMulticastGroup(tPtr, multicastGroup, RR->node->now());
-        }
-
-        // First pass sets noTee to false, but noTee is set to true in OutboundMulticast to prevent duplicates.
-        if (! network->filterOutgoingPacket(tPtr, false, RR->identity.address(), Address(), from, to, (const uint8_t*)data, len, etherType, vlanId, qosBucket)) {
-            RR->t->outgoingNetworkFrameDropped(tPtr, network, from, to, etherType, vlanId, len, "filter blocked");
-            return;
-        }
-
-        RR->mc->send(tPtr, RR->node->now(), network, Address(), multicastGroup, (fromBridged) ? from : MAC(), etherType, data, len);
-    }
-    else if (to == network->mac()) {
-        // Destination is this node, so just reinject it
-
-        //
-        // same pattern as putFrame call above
-        //
-        std::thread([=]() {
-            RR->node->putFrame(tPtr, network->id(), network->userPtr(), from, to, etherType, vlanId, data, len);
-        }).detach();
-    }
-    else if (to[0] == MAC::firstOctetForNetwork(network->id())) {
-        // Destination is another ZeroTier peer on the same network
-
-        Address toZT(to.toAddress(network->id()));   // since in-network MACs are derived from addresses and network IDs, we can reverse this
-        SharedPtr<Peer> toPeer(RR->topology->getPeer(tPtr, toZT));
-
-        if (! network->filterOutgoingPacket(tPtr, false, RR->identity.address(), toZT, from, to, (const uint8_t*)data, len, etherType, vlanId, qosBucket)) {
-            RR->t->outgoingNetworkFrameDropped(tPtr, network, from, to, etherType, vlanId, len, "filter blocked");
-            return;
-        }
-
-        network->pushCredentialsIfNeeded(tPtr, toZT, RR->node->now());
-
-        if (! fromBridged) {
-            Packet outp(toZT, RR->identity.address(), Packet::VERB_FRAME);
-            outp.append(network->id());
-            outp.append((uint16_t)etherType);
-            outp.append(data, len);
-            // 1.4.8: disable compression for unicast as it almost never helps
-            // if (!network->config().disableCompression())
-            //	outp.compress();
-            aqm_enqueue(tPtr, network, outp, true, qosBucket, flowId);
-        }
-        else {
-            Packet outp(toZT, RR->identity.address(), Packet::VERB_EXT_FRAME);
-            outp.append(network->id());
-            outp.append((unsigned char)0x00);
-            to.appendTo(outp);
-            from.appendTo(outp);
-            outp.append((uint16_t)etherType);
-            outp.append(data, len);
-            // 1.4.8: disable compression for unicast as it almost never helps
-            // if (!network->config().disableCompression())
-            //	outp.compress();
-            aqm_enqueue(tPtr, network, outp, true, qosBucket, flowId);
-        }
-    }
-    else {
-        // Destination is bridged behind a remote peer
-
-        // We filter with a NULL destination ZeroTier address first. Filtrations
-        // for each ZT destination are also done below. This is the same rationale
-        // and design as for multicast.
-        if (! network->filterOutgoingPacket(tPtr, false, RR->identity.address(), Address(), from, to, (const uint8_t*)data, len, etherType, vlanId, qosBucket)) {
-            RR->t->outgoingNetworkFrameDropped(tPtr, network, from, to, etherType, vlanId, len, "filter blocked");
-            return;
-        }
-
-        Address bridges[ZT_MAX_BRIDGE_SPAM];
-        unsigned int numBridges = 0;
-
-        /* Create an array of up to ZT_MAX_BRIDGE_SPAM recipients for this bridged frame. */
-        bridges[0] = network->findBridgeTo(to);
-        std::vector<Address> activeBridges(network->config().activeBridges());
-        if ((bridges[0]) && (bridges[0] != RR->identity.address()) && (network->config().permitsBridging(bridges[0]))) {
-            /* We have a known bridge route for this MAC, send it there. */
-            ++numBridges;
-        }
-        else if (! activeBridges.empty()) {
-            /* If there is no known route, spam to up to ZT_MAX_BRIDGE_SPAM active
-             * bridges. If someone responds, we'll learn the route. */
-            std::vector<Address>::const_iterator ab(activeBridges.begin());
-            if (activeBridges.size() <= ZT_MAX_BRIDGE_SPAM) {
-                // If there are <= ZT_MAX_BRIDGE_SPAM active bridges, spam them all
-                while (ab != activeBridges.end()) {
-                    bridges[numBridges++] = *ab;
-                    ++ab;
-                }
-            }
-            else {
-                // Otherwise pick a random set of them
-                while (numBridges < ZT_MAX_BRIDGE_SPAM) {
-                    if (ab == activeBridges.end()) {
-                        ab = activeBridges.begin();
-                    }
-                    if (((unsigned long)RR->node->prng() % (unsigned long)activeBridges.size()) == 0) {
-                        bridges[numBridges++] = *ab;
-                        ++ab;
-                    }
-                    else {
-                        ++ab;
-                    }
-                }
-            }
-        }
-
-        for (unsigned int b = 0; b < numBridges; ++b) {
-            if (network->filterOutgoingPacket(tPtr, true, RR->identity.address(), bridges[b], from, to, (const uint8_t*)data, len, etherType, vlanId, qosBucket)) {
-                Packet outp(bridges[b], RR->identity.address(), Packet::VERB_EXT_FRAME);
-                outp.append(network->id());
-                outp.append((uint8_t)0x00);
-                to.appendTo(outp);
-                from.appendTo(outp);
-                outp.append((uint16_t)etherType);
-                outp.append(data, len);
-                // 1.4.8: disable compression for unicast as it almost never helps
-                // if (!network->config().disableCompression())
-                //	outp.compress();
-                aqm_enqueue(tPtr, network, outp, true, qosBucket, flowId);
-            }
-            else {
-                RR->t->outgoingNetworkFrameDropped(tPtr, network, from, to, etherType, vlanId, len, "filter blocked (bridge replication)");
-            }
-        }
-    }
+	if (! network->hasConfig()) {
+		return;
+	}
+
+	// Check if this packet is from someone other than the tap -- i.e. bridged in
+	bool fromBridged;
+	if ((fromBridged = (from != network->mac()))) {
+		if (! network->config().permitsBridging(RR->identity.address())) {
+			RR->t->outgoingNetworkFrameDropped(tPtr, network, from, to, etherType, vlanId, len, "not a bridge");
+			return;
+		}
+	}
+
+	uint8_t qosBucket = ZT_AQM_DEFAULT_BUCKET;
+
+	/**
+	 * A pseudo-unique identifier used by balancing and bonding policies to
+	 * categorize individual flows/conversations for assignment to a specific
+	 * physical path. This identifier consists of the source port and
+	 * destination port of the encapsulated frame.
+	 *
+	 * A flowId of -1 will indicate that there is no preference for how this
+	 * packet shall be sent. An example of this would be an ICMP packet.
+	 */
+
+	int32_t flowId = ZT_QOS_NO_FLOW;
+
+	if (etherType == ZT_ETHERTYPE_IPV4 && (len >= 20)) {
+		uint16_t srcPort = 0;
+		uint16_t dstPort = 0;
+		uint8_t proto = (reinterpret_cast<const uint8_t*>(data)[9]);
+		const unsigned int headerLen = 4 * (reinterpret_cast<const uint8_t*>(data)[0] & 0xf);
+		switch (proto) {
+			case 0x01:	 // ICMP
+				// flowId = 0x01;
+				break;
+			// All these start with 16-bit source and destination port in that order
+			case 0x06:	 // TCP
+			case 0x11:	 // UDP
+			case 0x84:	 // SCTP
+			case 0x88:	 // UDPLite
+				if (len > (headerLen + 4)) {
+					unsigned int pos = headerLen + 0;
+					srcPort = (reinterpret_cast<const uint8_t*>(data)[pos++]) << 8;
+					srcPort |= (reinterpret_cast<const uint8_t*>(data)[pos]);
+					pos++;
+					dstPort = (reinterpret_cast<const uint8_t*>(data)[pos++]) << 8;
+					dstPort |= (reinterpret_cast<const uint8_t*>(data)[pos]);
+					flowId = dstPort ^ srcPort ^ proto;
+				}
+				break;
+		}
+	}
+
+	if (etherType == ZT_ETHERTYPE_IPV6 && (len >= 40)) {
+		uint16_t srcPort = 0;
+		uint16_t dstPort = 0;
+		unsigned int pos;
+		unsigned int proto;
+		_ipv6GetPayload((const uint8_t*)data, len, pos, proto);
+		switch (proto) {
+			case 0x3A:	 // ICMPv6
+				// flowId = 0x3A;
+				break;
+			// All these start with 16-bit source and destination port in that order
+			case 0x06:	 // TCP
+			case 0x11:	 // UDP
+			case 0x84:	 // SCTP
+			case 0x88:	 // UDPLite
+				if (len > (pos + 4)) {
+					srcPort = (reinterpret_cast<const uint8_t*>(data)[pos++]) << 8;
+					srcPort |= (reinterpret_cast<const uint8_t*>(data)[pos]);
+					pos++;
+					dstPort = (reinterpret_cast<const uint8_t*>(data)[pos++]) << 8;
+					dstPort |= (reinterpret_cast<const uint8_t*>(data)[pos]);
+					flowId = dstPort ^ srcPort ^ proto;
+				}
+				break;
+			default:
+				break;
+		}
+	}
+
+	if (to.isMulticast()) {
+		MulticastGroup multicastGroup(to, 0);
+
+		if (to.isBroadcast()) {
+			if ((etherType == ZT_ETHERTYPE_ARP) && (len >= 28)
+				&& ((((const uint8_t*)data)[2] == 0x08) && (((const uint8_t*)data)[3] == 0x00) && (((const uint8_t*)data)[4] == 6) && (((const uint8_t*)data)[5] == 4) && (((const uint8_t*)data)[7] == 0x01))) {
+				/* IPv4 ARP is one of the few special cases that we impose upon what is
+				 * otherwise a straightforward Ethernet switch emulation. Vanilla ARP
+				 * is dumb old broadcast and simply doesn't scale. ZeroTier multicast
+				 * groups have an additional field called ADI (additional distinguishing
+				 * information) which was added specifically for ARP though it could
+				 * be used for other things too. We then take ARP broadcasts and turn
+				 * them into multicasts by stuffing the IP address being queried into
+				 * the 32-bit ADI field. In practice this uses our multicast pub/sub
+				 * system to implement a kind of extended/distributed ARP table. */
+				multicastGroup = MulticastGroup::deriveMulticastGroupForAddressResolution(InetAddress(((const unsigned char*)data) + 24, 4, 0));
+			}
+			else if (! network->config().enableBroadcast()) {
+				// Don't transmit broadcasts if this network doesn't want them
+				RR->t->outgoingNetworkFrameDropped(tPtr, network, from, to, etherType, vlanId, len, "broadcast disabled");
+				return;
+			}
+		}
+		else if ((etherType == ZT_ETHERTYPE_IPV6) && (len >= (40 + 8 + 16))) {
+			// IPv6 NDP emulation for certain very special patterns of private IPv6 addresses -- if enabled
+			if ((network->config().ndpEmulation()) && (reinterpret_cast<const uint8_t*>(data)[6] == 0x3a) && (reinterpret_cast<const uint8_t*>(data)[40] == 0x87)) {   // ICMPv6 neighbor solicitation
+				Address v6EmbeddedAddress;
+				const uint8_t* const pkt6 = reinterpret_cast<const uint8_t*>(data) + 40 + 8;
+				const uint8_t* my6 = (const uint8_t*)0;
+
+				// ZT-RFC4193 address: fdNN:NNNN:NNNN:NNNN:NN99:93DD:DDDD:DDDD / 88 (one /128 per actual host)
+
+				// ZT-6PLANE address:  fcXX:XXXX:XXDD:DDDD:DDDD:####:####:#### / 40 (one /80 per actual host)
+				// (XX - lower 32 bits of network ID XORed with higher 32 bits)
+
+				// For these to work, we must have a ZT-managed address assigned in one of the
+				// above formats, and the query must match its prefix.
+				for (unsigned int sipk = 0; sipk < network->config().staticIpCount; ++sipk) {
+					const InetAddress* const sip = &(network->config().staticIps[sipk]);
+					if (sip->ss_family == AF_INET6) {
+						my6 = reinterpret_cast<const uint8_t*>(reinterpret_cast<const struct sockaddr_in6*>(&(*sip))->sin6_addr.s6_addr);
+						const unsigned int sipNetmaskBits = Utils::ntoh((uint16_t)reinterpret_cast<const struct sockaddr_in6*>(&(*sip))->sin6_port);
+						if ((sipNetmaskBits == 88) && (my6[0] == 0xfd) && (my6[9] == 0x99) && (my6[10] == 0x93)) {	 // ZT-RFC4193 /88 ???
+							unsigned int ptr = 0;
+							while (ptr != 11) {
+								if (pkt6[ptr] != my6[ptr]) {
+									break;
+								}
+								++ptr;
+							}
+							if (ptr == 11) {   // prefix match!
+								v6EmbeddedAddress.setTo(pkt6 + ptr, 5);
+								break;
+							}
+						}
+						else if (sipNetmaskBits == 40) {   // ZT-6PLANE /40 ???
+							const uint32_t nwid32 = (uint32_t)((network->id() ^ (network->id() >> 32)) & 0xffffffff);
+							if ((my6[0] == 0xfc) && (my6[1] == (uint8_t)((nwid32 >> 24) & 0xff)) && (my6[2] == (uint8_t)((nwid32 >> 16) & 0xff)) && (my6[3] == (uint8_t)((nwid32 >> 8) & 0xff)) && (my6[4] == (uint8_t)(nwid32 & 0xff))) {
+								unsigned int ptr = 0;
+								while (ptr != 5) {
+									if (pkt6[ptr] != my6[ptr]) {
+										break;
+									}
+									++ptr;
+								}
+								if (ptr == 5) {	  // prefix match!
+									v6EmbeddedAddress.setTo(pkt6 + ptr, 5);
+									break;
+								}
+							}
+						}
+					}
+				}
+
+				if ((v6EmbeddedAddress) && (v6EmbeddedAddress != RR->identity.address())) {
+					const MAC peerMac(v6EmbeddedAddress, network->id());
+
+					uint8_t adv[72];
+					adv[0] = 0x60;
+					adv[1] = 0x00;
+					adv[2] = 0x00;
+					adv[3] = 0x00;
+					adv[4] = 0x00;
+					adv[5] = 0x20;
+					adv[6] = 0x3a;
+					adv[7] = 0xff;
+					for (int i = 0; i < 16; ++i) {
+						adv[8 + i] = pkt6[i];
+					}
+					for (int i = 0; i < 16; ++i) {
+						adv[24 + i] = my6[i];
+					}
+					adv[40] = 0x88;
+					adv[41] = 0x00;
+					adv[42] = 0x00;
+					adv[43] = 0x00;	  // future home of checksum
+					adv[44] = 0x60;
+					adv[45] = 0x00;
+					adv[46] = 0x00;
+					adv[47] = 0x00;
+					for (int i = 0; i < 16; ++i) {
+						adv[48 + i] = pkt6[i];
+					}
+					adv[64] = 0x02;
+					adv[65] = 0x01;
+					adv[66] = peerMac[0];
+					adv[67] = peerMac[1];
+					adv[68] = peerMac[2];
+					adv[69] = peerMac[3];
+					adv[70] = peerMac[4];
+					adv[71] = peerMac[5];
+
+					uint16_t pseudo_[36];
+					uint8_t* const pseudo = reinterpret_cast<uint8_t*>(pseudo_);
+					for (int i = 0; i < 32; ++i) {
+						pseudo[i] = adv[8 + i];
+					}
+					pseudo[32] = 0x00;
+					pseudo[33] = 0x00;
+					pseudo[34] = 0x00;
+					pseudo[35] = 0x20;
+					pseudo[36] = 0x00;
+					pseudo[37] = 0x00;
+					pseudo[38] = 0x00;
+					pseudo[39] = 0x3a;
+					for (int i = 0; i < 32; ++i) {
+						pseudo[40 + i] = adv[40 + i];
+					}
+					uint32_t checksum = 0;
+					for (int i = 0; i < 36; ++i) {
+						checksum += Utils::hton(pseudo_[i]);
+					}
+					while ((checksum >> 16)) {
+						checksum = (checksum & 0xffff) + (checksum >> 16);
+					}
+					checksum = ~checksum;
+					adv[42] = (checksum >> 8) & 0xff;
+					adv[43] = checksum & 0xff;
+
+					//
+					// call on separate background thread
+					// this prevents problems related to trying to do rx while inside of doing tx, such as acquiring same lock recursively
+					//
+
+					std::thread([=]() { RR->node->putFrame(tPtr, network->id(), network->userPtr(), peerMac, from, ZT_ETHERTYPE_IPV6, 0, adv, 72); }).detach();
+
+					return;	  // NDP emulation done. We have forged a "fake" reply, so no need to send actual NDP query.
+				}	// else no NDP emulation
+			}	// else no NDP emulation
+		}
+
+		// Check this after NDP emulation, since that has to be allowed in exactly this case
+		if (network->config().multicastLimit == 0) {
+			RR->t->outgoingNetworkFrameDropped(tPtr, network, from, to, etherType, vlanId, len, "multicast disabled");
+			return;
+		}
+
+		/* Learn multicast groups for bridged-in hosts.
+		 * Note that some OSes, most notably Linux, do this for you by learning
+		 * multicast addresses on bridge interfaces and subscribing each slave.
+		 * But in that case this does no harm, as the sets are just merged. */
+		if (fromBridged) {
+			network->learnBridgedMulticastGroup(tPtr, multicastGroup, RR->node->now());
+		}
+
+		// First pass sets noTee to false, but noTee is set to true in OutboundMulticast to prevent duplicates.
+		if (! network->filterOutgoingPacket(tPtr, false, RR->identity.address(), Address(), from, to, (const uint8_t*)data, len, etherType, vlanId, qosBucket)) {
+			RR->t->outgoingNetworkFrameDropped(tPtr, network, from, to, etherType, vlanId, len, "filter blocked");
+			return;
+		}
+
+		RR->mc->send(tPtr, RR->node->now(), network, Address(), multicastGroup, (fromBridged) ? from : MAC(), etherType, data, len);
+	}
+	else if (to == network->mac()) {
+		// Destination is this node, so just reinject it
+
+		//
+		// same pattern as putFrame call above
+		//
+		std::thread([=]() { RR->node->putFrame(tPtr, network->id(), network->userPtr(), from, to, etherType, vlanId, data, len); }).detach();
+	}
+	else if (to[0] == MAC::firstOctetForNetwork(network->id())) {
+		// Destination is another ZeroTier peer on the same network
+
+		Address toZT(to.toAddress(network->id()));	 // since in-network MACs are derived from addresses and network IDs, we can reverse this
+		SharedPtr<Peer> toPeer(RR->topology->getPeer(tPtr, toZT));
+
+		if (! network->filterOutgoingPacket(tPtr, false, RR->identity.address(), toZT, from, to, (const uint8_t*)data, len, etherType, vlanId, qosBucket)) {
+			RR->t->outgoingNetworkFrameDropped(tPtr, network, from, to, etherType, vlanId, len, "filter blocked");
+			return;
+		}
+
+		network->pushCredentialsIfNeeded(tPtr, toZT, RR->node->now());
+
+		if (! fromBridged) {
+			Packet outp(toZT, RR->identity.address(), Packet::VERB_FRAME);
+			outp.append(network->id());
+			outp.append((uint16_t)etherType);
+			outp.append(data, len);
+			// 1.4.8: disable compression for unicast as it almost never helps
+			// if (!network->config().disableCompression())
+			//	outp.compress();
+			aqm_enqueue(tPtr, network, outp, true, qosBucket, flowId);
+		}
+		else {
+			Packet outp(toZT, RR->identity.address(), Packet::VERB_EXT_FRAME);
+			outp.append(network->id());
+			outp.append((unsigned char)0x00);
+			to.appendTo(outp);
+			from.appendTo(outp);
+			outp.append((uint16_t)etherType);
+			outp.append(data, len);
+			// 1.4.8: disable compression for unicast as it almost never helps
+			// if (!network->config().disableCompression())
+			//	outp.compress();
+			aqm_enqueue(tPtr, network, outp, true, qosBucket, flowId);
+		}
+	}
+	else {
+		// Destination is bridged behind a remote peer
+
+		// We filter with a NULL destination ZeroTier address first. Filtrations
+		// for each ZT destination are also done below. This is the same rationale
+		// and design as for multicast.
+		if (! network->filterOutgoingPacket(tPtr, false, RR->identity.address(), Address(), from, to, (const uint8_t*)data, len, etherType, vlanId, qosBucket)) {
+			RR->t->outgoingNetworkFrameDropped(tPtr, network, from, to, etherType, vlanId, len, "filter blocked");
+			return;
+		}
+
+		Address bridges[ZT_MAX_BRIDGE_SPAM];
+		unsigned int numBridges = 0;
+
+		/* Create an array of up to ZT_MAX_BRIDGE_SPAM recipients for this bridged frame. */
+		bridges[0] = network->findBridgeTo(to);
+		std::vector<Address> activeBridges(network->config().activeBridges());
+		if ((bridges[0]) && (bridges[0] != RR->identity.address()) && (network->config().permitsBridging(bridges[0]))) {
+			/* We have a known bridge route for this MAC, send it there. */
+			++numBridges;
+		}
+		else if (! activeBridges.empty()) {
+			/* If there is no known route, spam to up to ZT_MAX_BRIDGE_SPAM active
+			 * bridges. If someone responds, we'll learn the route. */
+			std::vector<Address>::const_iterator ab(activeBridges.begin());
+			if (activeBridges.size() <= ZT_MAX_BRIDGE_SPAM) {
+				// If there are <= ZT_MAX_BRIDGE_SPAM active bridges, spam them all
+				while (ab != activeBridges.end()) {
+					bridges[numBridges++] = *ab;
+					++ab;
+				}
+			}
+			else {
+				// Otherwise pick a random set of them
+				while (numBridges < ZT_MAX_BRIDGE_SPAM) {
+					if (ab == activeBridges.end()) {
+						ab = activeBridges.begin();
+					}
+					if (((unsigned long)RR->node->prng() % (unsigned long)activeBridges.size()) == 0) {
+						bridges[numBridges++] = *ab;
+						++ab;
+					}
+					else {
+						++ab;
+					}
+				}
+			}
+		}
+
+		for (unsigned int b = 0; b < numBridges; ++b) {
+			if (network->filterOutgoingPacket(tPtr, true, RR->identity.address(), bridges[b], from, to, (const uint8_t*)data, len, etherType, vlanId, qosBucket)) {
+				Packet outp(bridges[b], RR->identity.address(), Packet::VERB_EXT_FRAME);
+				outp.append(network->id());
+				outp.append((uint8_t)0x00);
+				to.appendTo(outp);
+				from.appendTo(outp);
+				outp.append((uint16_t)etherType);
+				outp.append(data, len);
+				// 1.4.8: disable compression for unicast as it almost never helps
+				// if (!network->config().disableCompression())
+				//	outp.compress();
+				aqm_enqueue(tPtr, network, outp, true, qosBucket, flowId);
+			}
+			else {
+				RR->t->outgoingNetworkFrameDropped(tPtr, network, from, to, etherType, vlanId, len, "filter blocked (bridge replication)");
+			}
+		}
+	}
 }
 
 void Switch::aqm_enqueue(void* tPtr, const SharedPtr<Network>& network, Packet& packet, bool encrypt, int qosBucket, int32_t flowId)
 {
-    if (! network->qosEnabled()) {
-        send(tPtr, packet, encrypt, flowId);
-        return;
-    }
-    NetworkQoSControlBlock* nqcb = _netQueueControlBlock[network->id()];
-    if (! nqcb) {
-        nqcb = new NetworkQoSControlBlock();
-        _netQueueControlBlock[network->id()] = nqcb;
-        // Initialize ZT_QOS_NUM_BUCKETS queues and place them in the INACTIVE list
-        // These queues will be shuffled between the new/old/inactive lists by the enqueue/dequeue algorithm
-        for (int i = 0; i < ZT_AQM_NUM_BUCKETS; i++) {
-            nqcb->inactiveQueues.push_back(new ManagedQueue(i));
-        }
-    }
-    // Don't apply QoS scheduling to ZT protocol traffic
-    if (packet.verb() != Packet::VERB_FRAME && packet.verb() != Packet::VERB_EXT_FRAME) {
-        send(tPtr, packet, encrypt, flowId);
-    }
-
-    _aqm_m.lock();
-
-    // Enqueue packet and move queue to appropriate list
-
-    const Address dest(packet.destination());
-    TXQueueEntry* txEntry = new TXQueueEntry(dest, RR->node->now(), packet, encrypt, flowId);
-
-    ManagedQueue* selectedQueue = nullptr;
-    for (size_t i = 0; i < ZT_AQM_NUM_BUCKETS; i++) {
-        if (i < nqcb->oldQueues.size()) {   // search old queues first (I think this is best since old would imply most recent usage of the queue)
-            if (nqcb->oldQueues[i]->id == qosBucket) {
-                selectedQueue = nqcb->oldQueues[i];
-            }
-        }
-        if (i < nqcb->newQueues.size()) {   // search new queues (this would imply not often-used queues)
-            if (nqcb->newQueues[i]->id == qosBucket) {
-                selectedQueue = nqcb->newQueues[i];
-            }
-        }
-        if (i < nqcb->inactiveQueues.size()) {   // search inactive queues
-            if (nqcb->inactiveQueues[i]->id == qosBucket) {
-                selectedQueue = nqcb->inactiveQueues[i];
-                // move queue to end of NEW queue list
-                selectedQueue->byteCredit = ZT_AQM_QUANTUM;
-                // DEBUG_INFO("moving q=%p from INACTIVE to NEW list", selectedQueue);
-                nqcb->newQueues.push_back(selectedQueue);
-                nqcb->inactiveQueues.erase(nqcb->inactiveQueues.begin() + i);
-            }
-        }
-    }
-    if (! selectedQueue) {
-        _aqm_m.unlock();
-        return;
-    }
-
-    selectedQueue->q.push_back(txEntry);
-    selectedQueue->byteLength += txEntry->packet.payloadLength();
-    nqcb->_currEnqueuedPackets++;
-
-    // DEBUG_INFO("nq=%2lu, oq=%2lu, iq=%2lu, nqcb.size()=%3d, bucket=%2d, q=%p", nqcb->newQueues.size(), nqcb->oldQueues.size(), nqcb->inactiveQueues.size(), nqcb->_currEnqueuedPackets, qosBucket, selectedQueue);
-
-    // Drop a packet if necessary
-    ManagedQueue* selectedQueueToDropFrom = nullptr;
-    if (nqcb->_currEnqueuedPackets > ZT_AQM_MAX_ENQUEUED_PACKETS) {
-        // DEBUG_INFO("too many enqueued packets (%d), finding packet to drop", nqcb->_currEnqueuedPackets);
-        int maxQueueLength = 0;
-        for (size_t i = 0; i < ZT_AQM_NUM_BUCKETS; i++) {
-            if (i < nqcb->oldQueues.size()) {
-                if (nqcb->oldQueues[i]->byteLength > maxQueueLength) {
-                    maxQueueLength = nqcb->oldQueues[i]->byteLength;
-                    selectedQueueToDropFrom = nqcb->oldQueues[i];
-                }
-            }
-            if (i < nqcb->newQueues.size()) {
-                if (nqcb->newQueues[i]->byteLength > maxQueueLength) {
-                    maxQueueLength = nqcb->newQueues[i]->byteLength;
-                    selectedQueueToDropFrom = nqcb->newQueues[i];
-                }
-            }
-            if (i < nqcb->inactiveQueues.size()) {
-                if (nqcb->inactiveQueues[i]->byteLength > maxQueueLength) {
-                    maxQueueLength = nqcb->inactiveQueues[i]->byteLength;
-                    selectedQueueToDropFrom = nqcb->inactiveQueues[i];
-                }
-            }
-        }
-        if (selectedQueueToDropFrom) {
-            // DEBUG_INFO("dropping packet from head of largest queue (%d payload bytes)", maxQueueLength);
-            int sizeOfDroppedPacket = selectedQueueToDropFrom->q.front()->packet.payloadLength();
-            delete selectedQueueToDropFrom->q.front();
-            selectedQueueToDropFrom->q.pop_front();
-            selectedQueueToDropFrom->byteLength -= sizeOfDroppedPacket;
-            nqcb->_currEnqueuedPackets--;
-        }
-    }
-    _aqm_m.unlock();
-    aqm_dequeue(tPtr);
+	if (! network->qosEnabled()) {
+		send(tPtr, packet, encrypt, flowId);
+		return;
+	}
+	NetworkQoSControlBlock* nqcb = _netQueueControlBlock[network->id()];
+	if (! nqcb) {
+		nqcb = new NetworkQoSControlBlock();
+		_netQueueControlBlock[network->id()] = nqcb;
+		// Initialize ZT_QOS_NUM_BUCKETS queues and place them in the INACTIVE list
+		// These queues will be shuffled between the new/old/inactive lists by the enqueue/dequeue algorithm
+		for (int i = 0; i < ZT_AQM_NUM_BUCKETS; i++) {
+			nqcb->inactiveQueues.push_back(new ManagedQueue(i));
+		}
+	}
+	// Don't apply QoS scheduling to ZT protocol traffic
+	if (packet.verb() != Packet::VERB_FRAME && packet.verb() != Packet::VERB_EXT_FRAME) {
+		send(tPtr, packet, encrypt, flowId);
+	}
+
+	_aqm_m.lock();
+
+	// Enqueue packet and move queue to appropriate list
+
+	const Address dest(packet.destination());
+	TXQueueEntry* txEntry = new TXQueueEntry(dest, RR->node->now(), packet, encrypt, flowId);
+
+	ManagedQueue* selectedQueue = nullptr;
+	for (size_t i = 0; i < ZT_AQM_NUM_BUCKETS; i++) {
+		if (i < nqcb->oldQueues.size()) {	// search old queues first (I think this is best since old would imply most recent usage of the queue)
+			if (nqcb->oldQueues[i]->id == qosBucket) {
+				selectedQueue = nqcb->oldQueues[i];
+			}
+		}
+		if (i < nqcb->newQueues.size()) {	// search new queues (this would imply not often-used queues)
+			if (nqcb->newQueues[i]->id == qosBucket) {
+				selectedQueue = nqcb->newQueues[i];
+			}
+		}
+		if (i < nqcb->inactiveQueues.size()) {	 // search inactive queues
+			if (nqcb->inactiveQueues[i]->id == qosBucket) {
+				selectedQueue = nqcb->inactiveQueues[i];
+				// move queue to end of NEW queue list
+				selectedQueue->byteCredit = ZT_AQM_QUANTUM;
+				// DEBUG_INFO("moving q=%p from INACTIVE to NEW list", selectedQueue);
+				nqcb->newQueues.push_back(selectedQueue);
+				nqcb->inactiveQueues.erase(nqcb->inactiveQueues.begin() + i);
+			}
+		}
+	}
+	if (! selectedQueue) {
+		_aqm_m.unlock();
+		return;
+	}
+
+	selectedQueue->q.push_back(txEntry);
+	selectedQueue->byteLength += txEntry->packet.payloadLength();
+	nqcb->_currEnqueuedPackets++;
+
+	// DEBUG_INFO("nq=%2lu, oq=%2lu, iq=%2lu, nqcb.size()=%3d, bucket=%2d, q=%p", nqcb->newQueues.size(), nqcb->oldQueues.size(), nqcb->inactiveQueues.size(), nqcb->_currEnqueuedPackets, qosBucket, selectedQueue);
+
+	// Drop a packet if necessary
+	ManagedQueue* selectedQueueToDropFrom = nullptr;
+	if (nqcb->_currEnqueuedPackets > ZT_AQM_MAX_ENQUEUED_PACKETS) {
+		// DEBUG_INFO("too many enqueued packets (%d), finding packet to drop", nqcb->_currEnqueuedPackets);
+		int maxQueueLength = 0;
+		for (size_t i = 0; i < ZT_AQM_NUM_BUCKETS; i++) {
+			if (i < nqcb->oldQueues.size()) {
+				if (nqcb->oldQueues[i]->byteLength > maxQueueLength) {
+					maxQueueLength = nqcb->oldQueues[i]->byteLength;
+					selectedQueueToDropFrom = nqcb->oldQueues[i];
+				}
+			}
+			if (i < nqcb->newQueues.size()) {
+				if (nqcb->newQueues[i]->byteLength > maxQueueLength) {
+					maxQueueLength = nqcb->newQueues[i]->byteLength;
+					selectedQueueToDropFrom = nqcb->newQueues[i];
+				}
+			}
+			if (i < nqcb->inactiveQueues.size()) {
+				if (nqcb->inactiveQueues[i]->byteLength > maxQueueLength) {
+					maxQueueLength = nqcb->inactiveQueues[i]->byteLength;
+					selectedQueueToDropFrom = nqcb->inactiveQueues[i];
+				}
+			}
+		}
+		if (selectedQueueToDropFrom) {
+			// DEBUG_INFO("dropping packet from head of largest queue (%d payload bytes)", maxQueueLength);
+			int sizeOfDroppedPacket = selectedQueueToDropFrom->q.front()->packet.payloadLength();
+			delete selectedQueueToDropFrom->q.front();
+			selectedQueueToDropFrom->q.pop_front();
+			selectedQueueToDropFrom->byteLength -= sizeOfDroppedPacket;
+			nqcb->_currEnqueuedPackets--;
+		}
+	}
+	_aqm_m.unlock();
+	aqm_dequeue(tPtr);
 }
 
 uint64_t Switch::control_law(uint64_t t, int count)
 {
-    return (uint64_t)(t + ZT_AQM_INTERVAL / sqrt(count));
+	return (uint64_t)(t + ZT_AQM_INTERVAL / sqrt(count));
 }
 
 Switch::dqr Switch::dodequeue(ManagedQueue* q, uint64_t now)
 {
-    dqr r;
-    r.ok_to_drop = false;
-    r.p = q->q.front();
-
-    if (r.p == NULL) {
-        q->first_above_time = 0;
-        return r;
-    }
-    uint64_t sojourn_time = now - r.p->creationTime;
-    if (sojourn_time < ZT_AQM_TARGET || q->byteLength <= ZT_DEFAULT_MTU) {
-        // went below - stay below for at least interval
-        q->first_above_time = 0;
-    }
-    else {
-        if (q->first_above_time == 0) {
-            // just went above from below. if still above at
-            // first_above_time, will say it's ok to drop.
-            q->first_above_time = now + ZT_AQM_INTERVAL;
-        }
-        else if (now >= q->first_above_time) {
-            r.ok_to_drop = true;
-        }
-    }
-    return r;
+	dqr r;
+	r.ok_to_drop = false;
+	r.p = q->q.front();
+
+	if (r.p == NULL) {
+		q->first_above_time = 0;
+		return r;
+	}
+	uint64_t sojourn_time = now - r.p->creationTime;
+	if (sojourn_time < ZT_AQM_TARGET || q->byteLength <= ZT_DEFAULT_MTU) {
+		// went below - stay below for at least interval
+		q->first_above_time = 0;
+	}
+	else {
+		if (q->first_above_time == 0) {
+			// just went above from below. if still above at
+			// first_above_time, will say it's ok to drop.
+			q->first_above_time = now + ZT_AQM_INTERVAL;
+		}
+		else if (now >= q->first_above_time) {
+			r.ok_to_drop = true;
+		}
+	}
+	return r;
 }
 
 Switch::TXQueueEntry* Switch::CoDelDequeue(ManagedQueue* q, bool isNew, uint64_t now)
 {
-    dqr r = dodequeue(q, now);
-
-    if (q->dropping) {
-        if (! r.ok_to_drop) {
-            q->dropping = false;
-        }
-        while (now >= q->drop_next && q->dropping) {
-            q->q.pop_front();   // drop
-            r = dodequeue(q, now);
-            if (! r.ok_to_drop) {
-                // leave dropping state
-                q->dropping = false;
-            }
-            else {
-                ++(q->count);
-                // schedule the next drop.
-                q->drop_next = control_law(q->drop_next, q->count);
-            }
-        }
-    }
-    else if (r.ok_to_drop) {
-        q->q.pop_front();   // drop
-        r = dodequeue(q, now);
-        q->dropping = true;
-        q->count = (q->count > 2 && now - q->drop_next < 8 * ZT_AQM_INTERVAL) ? q->count - 2 : 1;
-        q->drop_next = control_law(now, q->count);
-    }
-    return r.p;
+	dqr r = dodequeue(q, now);
+
+	if (q->dropping) {
+		if (! r.ok_to_drop) {
+			q->dropping = false;
+		}
+		while (now >= q->drop_next && q->dropping) {
+			q->q.pop_front();	// drop
+			r = dodequeue(q, now);
+			if (! r.ok_to_drop) {
+				// leave dropping state
+				q->dropping = false;
+			}
+			else {
+				++(q->count);
+				// schedule the next drop.
+				q->drop_next = control_law(q->drop_next, q->count);
+			}
+		}
+	}
+	else if (r.ok_to_drop) {
+		q->q.pop_front();	// drop
+		r = dodequeue(q, now);
+		q->dropping = true;
+		q->count = (q->count > 2 && now - q->drop_next < 8 * ZT_AQM_INTERVAL) ? q->count - 2 : 1;
+		q->drop_next = control_law(now, q->count);
+	}
+	return r.p;
 }
 
 void Switch::aqm_dequeue(void* tPtr)
 {
-    // Cycle through network-specific QoS control blocks
-    for (std::map<uint64_t, NetworkQoSControlBlock*>::iterator nqcb(_netQueueControlBlock.begin()); nqcb != _netQueueControlBlock.end();) {
-        if (! (*nqcb).second->_currEnqueuedPackets) {
-            return;
-        }
-
-        uint64_t now = RR->node->now();
-        TXQueueEntry* entryToEmit = nullptr;
-        std::vector<ManagedQueue*>* currQueues = &((*nqcb).second->newQueues);
-        std::vector<ManagedQueue*>* oldQueues = &((*nqcb).second->oldQueues);
-        std::vector<ManagedQueue*>* inactiveQueues = &((*nqcb).second->inactiveQueues);
-
-        _aqm_m.lock();
-
-        // Attempt dequeue from queues in NEW list
-        bool examiningNewQueues = true;
-        while (currQueues->size()) {
-            ManagedQueue* queueAtFrontOfList = currQueues->front();
-            if (queueAtFrontOfList->byteCredit < 0) {
-                queueAtFrontOfList->byteCredit += ZT_AQM_QUANTUM;
-                // Move to list of OLD queues
-                // DEBUG_INFO("moving q=%p from NEW to OLD list", queueAtFrontOfList);
-                oldQueues->push_back(queueAtFrontOfList);
-                currQueues->erase(currQueues->begin());
-            }
-            else {
-                entryToEmit = CoDelDequeue(queueAtFrontOfList, examiningNewQueues, now);
-                if (! entryToEmit) {
-                    // Move to end of list of OLD queues
-                    // DEBUG_INFO("moving q=%p from NEW to OLD list", queueAtFrontOfList);
-                    oldQueues->push_back(queueAtFrontOfList);
-                    currQueues->erase(currQueues->begin());
-                }
-                else {
-                    int len = entryToEmit->packet.payloadLength();
-                    queueAtFrontOfList->byteLength -= len;
-                    queueAtFrontOfList->byteCredit -= len;
-                    // Send the packet!
-                    queueAtFrontOfList->q.pop_front();
-                    send(tPtr, entryToEmit->packet, entryToEmit->encrypt, entryToEmit->flowId);
-                    (*nqcb).second->_currEnqueuedPackets--;
-                }
-                if (queueAtFrontOfList) {
-                    // DEBUG_INFO("dequeuing from q=%p, len=%lu in NEW list (byteCredit=%d)", queueAtFrontOfList, queueAtFrontOfList->q.size(), queueAtFrontOfList->byteCredit);
-                }
-                break;
-            }
-        }
-
-        // Attempt dequeue from queues in OLD list
-        examiningNewQueues = false;
-        currQueues = &((*nqcb).second->oldQueues);
-        while (currQueues->size()) {
-            ManagedQueue* queueAtFrontOfList = currQueues->front();
-            if (queueAtFrontOfList->byteCredit < 0) {
-                queueAtFrontOfList->byteCredit += ZT_AQM_QUANTUM;
-                oldQueues->push_back(queueAtFrontOfList);
-                currQueues->erase(currQueues->begin());
-            }
-            else {
-                entryToEmit = CoDelDequeue(queueAtFrontOfList, examiningNewQueues, now);
-                if (! entryToEmit) {
-                    // DEBUG_INFO("moving q=%p from OLD to INACTIVE list", queueAtFrontOfList);
-                    //  Move to inactive list of queues
-                    inactiveQueues->push_back(queueAtFrontOfList);
-                    currQueues->erase(currQueues->begin());
-                }
-                else {
-                    int len = entryToEmit->packet.payloadLength();
-                    queueAtFrontOfList->byteLength -= len;
-                    queueAtFrontOfList->byteCredit -= len;
-                    queueAtFrontOfList->q.pop_front();
-                    send(tPtr, entryToEmit->packet, entryToEmit->encrypt, entryToEmit->flowId);
-                    (*nqcb).second->_currEnqueuedPackets--;
-                }
-                if (queueAtFrontOfList) {
-                    // DEBUG_INFO("dequeuing from q=%p, len=%lu in OLD list (byteCredit=%d)", queueAtFrontOfList, queueAtFrontOfList->q.size(), queueAtFrontOfList->byteCredit);
-                }
-                break;
-            }
-        }
-        nqcb++;
-        _aqm_m.unlock();
-    }
+	// Cycle through network-specific QoS control blocks
+	for (std::map<uint64_t, NetworkQoSControlBlock*>::iterator nqcb(_netQueueControlBlock.begin()); nqcb != _netQueueControlBlock.end();) {
+		if (! (*nqcb).second->_currEnqueuedPackets) {
+			return;
+		}
+
+		uint64_t now = RR->node->now();
+		TXQueueEntry* entryToEmit = nullptr;
+		std::vector<ManagedQueue*>* currQueues = &((*nqcb).second->newQueues);
+		std::vector<ManagedQueue*>* oldQueues = &((*nqcb).second->oldQueues);
+		std::vector<ManagedQueue*>* inactiveQueues = &((*nqcb).second->inactiveQueues);
+
+		_aqm_m.lock();
+
+		// Attempt dequeue from queues in NEW list
+		bool examiningNewQueues = true;
+		while (currQueues->size()) {
+			ManagedQueue* queueAtFrontOfList = currQueues->front();
+			if (queueAtFrontOfList->byteCredit < 0) {
+				queueAtFrontOfList->byteCredit += ZT_AQM_QUANTUM;
+				// Move to list of OLD queues
+				// DEBUG_INFO("moving q=%p from NEW to OLD list", queueAtFrontOfList);
+				oldQueues->push_back(queueAtFrontOfList);
+				currQueues->erase(currQueues->begin());
+			}
+			else {
+				entryToEmit = CoDelDequeue(queueAtFrontOfList, examiningNewQueues, now);
+				if (! entryToEmit) {
+					// Move to end of list of OLD queues
+					// DEBUG_INFO("moving q=%p from NEW to OLD list", queueAtFrontOfList);
+					oldQueues->push_back(queueAtFrontOfList);
+					currQueues->erase(currQueues->begin());
+				}
+				else {
+					int len = entryToEmit->packet.payloadLength();
+					queueAtFrontOfList->byteLength -= len;
+					queueAtFrontOfList->byteCredit -= len;
+					// Send the packet!
+					queueAtFrontOfList->q.pop_front();
+					send(tPtr, entryToEmit->packet, entryToEmit->encrypt, entryToEmit->flowId);
+					(*nqcb).second->_currEnqueuedPackets--;
+				}
+				if (queueAtFrontOfList) {
+					// DEBUG_INFO("dequeuing from q=%p, len=%lu in NEW list (byteCredit=%d)", queueAtFrontOfList, queueAtFrontOfList->q.size(), queueAtFrontOfList->byteCredit);
+				}
+				break;
+			}
+		}
+
+		// Attempt dequeue from queues in OLD list
+		examiningNewQueues = false;
+		currQueues = &((*nqcb).second->oldQueues);
+		while (currQueues->size()) {
+			ManagedQueue* queueAtFrontOfList = currQueues->front();
+			if (queueAtFrontOfList->byteCredit < 0) {
+				queueAtFrontOfList->byteCredit += ZT_AQM_QUANTUM;
+				oldQueues->push_back(queueAtFrontOfList);
+				currQueues->erase(currQueues->begin());
+			}
+			else {
+				entryToEmit = CoDelDequeue(queueAtFrontOfList, examiningNewQueues, now);
+				if (! entryToEmit) {
+					// DEBUG_INFO("moving q=%p from OLD to INACTIVE list", queueAtFrontOfList);
+					//  Move to inactive list of queues
+					inactiveQueues->push_back(queueAtFrontOfList);
+					currQueues->erase(currQueues->begin());
+				}
+				else {
+					int len = entryToEmit->packet.payloadLength();
+					queueAtFrontOfList->byteLength -= len;
+					queueAtFrontOfList->byteCredit -= len;
+					queueAtFrontOfList->q.pop_front();
+					send(tPtr, entryToEmit->packet, entryToEmit->encrypt, entryToEmit->flowId);
+					(*nqcb).second->_currEnqueuedPackets--;
+				}
+				if (queueAtFrontOfList) {
+					// DEBUG_INFO("dequeuing from q=%p, len=%lu in OLD list (byteCredit=%d)", queueAtFrontOfList, queueAtFrontOfList->q.size(), queueAtFrontOfList->byteCredit);
+				}
+				break;
+			}
+		}
+		nqcb++;
+		_aqm_m.unlock();
+	}
 }
 
 void Switch::removeNetworkQoSControlBlock(uint64_t nwid)
 {
-    NetworkQoSControlBlock* nq = _netQueueControlBlock[nwid];
-    if (nq) {
-        _netQueueControlBlock.erase(nwid);
-        delete nq;
-        nq = NULL;
-    }
+	NetworkQoSControlBlock* nq = _netQueueControlBlock[nwid];
+	if (nq) {
+		_netQueueControlBlock.erase(nwid);
+		delete nq;
+		nq = NULL;
+	}
 }
 
 void Switch::send(void* tPtr, Packet& packet, bool encrypt, int32_t flowId)
 {
-    const Address dest(packet.destination());
-    if (dest == RR->identity.address()) {
-        return;
-    }
-    _recordOutgoingPacketMetrics(packet);
-    if (! _trySend(tPtr, packet, encrypt, flowId)) {
-        {
-            Mutex::Lock _l(_txQueue_m);
-            if (_txQueue.size() >= ZT_TX_QUEUE_SIZE) {
-                _txQueue.pop_front();
-            }
-            _txQueue.push_back(TXQueueEntry(dest, RR->node->now(), packet, encrypt, flowId));
-        }
-        if (! RR->topology->getPeer(tPtr, dest)) {
-            requestWhois(tPtr, RR->node->now(), dest);
-        }
-    }
+	const Address dest(packet.destination());
+	if (dest == RR->identity.address()) {
+		return;
+	}
+	_recordOutgoingPacketMetrics(packet);
+	if (! _trySend(tPtr, packet, encrypt, flowId)) {
+		{
+			Mutex::Lock _l(_txQueue_m);
+			if (_txQueue.size() >= ZT_TX_QUEUE_SIZE) {
+				_txQueue.pop_front();
+			}
+			_txQueue.push_back(TXQueueEntry(dest, RR->node->now(), packet, encrypt, flowId));
+		}
+		if (! RR->topology->getPeer(tPtr, dest)) {
+			requestWhois(tPtr, RR->node->now(), dest);
+		}
+	}
 }
 
 void Switch::requestWhois(void* tPtr, const int64_t now, const Address& addr)
 {
-    if (addr == RR->identity.address()) {
-        return;
-    }
-
-    {
-        Mutex::Lock _l(_lastSentWhoisRequest_m);
-        int64_t& last = _lastSentWhoisRequest[addr];
-        if ((now - last) < ZT_WHOIS_RETRY_DELAY) {
-            return;
-        }
-        else {
-            last = now;
-        }
-    }
-
-    const SharedPtr<Peer> upstream(RR->topology->getUpstreamPeer());
-    if (upstream) {
-        int32_t flowId = ZT_QOS_NO_FLOW;
-        Packet outp(upstream->address(), RR->identity.address(), Packet::VERB_WHOIS);
-        addr.appendTo(outp);
-        send(tPtr, outp, true, flowId);
-    }
+	if (addr == RR->identity.address()) {
+		return;
+	}
+
+	{
+		Mutex::Lock _l(_lastSentWhoisRequest_m);
+		int64_t& last = _lastSentWhoisRequest[addr];
+		if ((now - last) < ZT_WHOIS_RETRY_DELAY) {
+			return;
+		}
+		else {
+			last = now;
+		}
+	}
+
+	const SharedPtr<Peer> upstream(RR->topology->getUpstreamPeer());
+	if (upstream) {
+		int32_t flowId = ZT_QOS_NO_FLOW;
+		Packet outp(upstream->address(), RR->identity.address(), Packet::VERB_WHOIS);
+		addr.appendTo(outp);
+		send(tPtr, outp, true, flowId);
+	}
 }
 
 void Switch::doAnythingWaitingForPeer(void* tPtr, const SharedPtr<Peer>& peer)
 {
-    {
-        Mutex::Lock _l(_lastSentWhoisRequest_m);
-        _lastSentWhoisRequest.erase(peer->address());
-    }
-
-    const int64_t now = RR->node->now();
-    for (unsigned int ptr = 0; ptr < ZT_RX_QUEUE_SIZE; ++ptr) {
-        RXQueueEntry* const rq = &(_rxQueue[ptr]);
-        Mutex::Lock rql(rq->lock);
-        if ((rq->timestamp) && (rq->complete)) {
-            if ((rq->frag0.tryDecode(RR, tPtr, rq->flowId)) || ((now - rq->timestamp) > ZT_RECEIVE_QUEUE_TIMEOUT)) {
-                rq->timestamp = 0;
-            }
-        }
-    }
-
-    {
-        Mutex::Lock _l(_txQueue_m);
-        for (std::list<TXQueueEntry>::iterator txi(_txQueue.begin()); txi != _txQueue.end();) {
-            if (txi->dest == peer->address()) {
-                if (_trySend(tPtr, txi->packet, txi->encrypt, txi->flowId)) {
-                    _txQueue.erase(txi++);
-                }
-                else {
-                    ++txi;
-                }
-            }
-            else {
-                ++txi;
-            }
-        }
-    }
+	{
+		Mutex::Lock _l(_lastSentWhoisRequest_m);
+		_lastSentWhoisRequest.erase(peer->address());
+	}
+
+	const int64_t now = RR->node->now();
+	for (unsigned int ptr = 0; ptr < ZT_RX_QUEUE_SIZE; ++ptr) {
+		RXQueueEntry* const rq = &(_rxQueue[ptr]);
+		Mutex::Lock rql(rq->lock);
+		if ((rq->timestamp) && (rq->complete)) {
+			if ((rq->frag0.tryDecode(RR, tPtr, rq->flowId)) || ((now - rq->timestamp) > ZT_RECEIVE_QUEUE_TIMEOUT)) {
+				rq->timestamp = 0;
+			}
+		}
+	}
+
+	{
+		Mutex::Lock _l(_txQueue_m);
+		for (std::list<TXQueueEntry>::iterator txi(_txQueue.begin()); txi != _txQueue.end();) {
+			if (txi->dest == peer->address()) {
+				if (_trySend(tPtr, txi->packet, txi->encrypt, txi->flowId)) {
+					_txQueue.erase(txi++);
+				}
+				else {
+					++txi;
+				}
+			}
+			else {
+				++txi;
+			}
+		}
+	}
 }
 
 unsigned long Switch::doTimerTasks(void* tPtr, int64_t now)
 {
-    const uint64_t timeSinceLastCheck = now - _lastCheckedQueues;
-    if (timeSinceLastCheck < ZT_WHOIS_RETRY_DELAY) {
-        return (unsigned long)(ZT_WHOIS_RETRY_DELAY - timeSinceLastCheck);
-    }
-    _lastCheckedQueues = now;
-
-    std::vector<Address> needWhois;
-    {
-        Mutex::Lock _l(_txQueue_m);
-
-        for (std::list<TXQueueEntry>::iterator txi(_txQueue.begin()); txi != _txQueue.end();) {
-            if (_trySend(tPtr, txi->packet, txi->encrypt, txi->flowId)) {
-                _txQueue.erase(txi++);
-            }
-            else if ((now - txi->creationTime) > ZT_TRANSMIT_QUEUE_TIMEOUT) {
-                _txQueue.erase(txi++);
-            }
-            else {
-                if (! RR->topology->getPeer(tPtr, txi->dest)) {
-                    needWhois.push_back(txi->dest);
-                }
-                ++txi;
-            }
-        }
-    }
-    for (std::vector<Address>::const_iterator i(needWhois.begin()); i != needWhois.end(); ++i) {
-        requestWhois(tPtr, now, *i);
-    }
-
-    for (unsigned int ptr = 0; ptr < ZT_RX_QUEUE_SIZE; ++ptr) {
-        RXQueueEntry* const rq = &(_rxQueue[ptr]);
-        Mutex::Lock rql(rq->lock);
-        if ((rq->timestamp) && (rq->complete)) {
-            if ((rq->frag0.tryDecode(RR, tPtr, rq->flowId)) || ((now - rq->timestamp) > ZT_RECEIVE_QUEUE_TIMEOUT)) {
-                rq->timestamp = 0;
-            }
-            else {
-                const Address src(rq->frag0.source());
-                if (! RR->topology->getPeer(tPtr, src)) {
-                    requestWhois(tPtr, now, src);
-                }
-            }
-        }
-    }
-
-    {
-        Mutex::Lock _l(_lastUniteAttempt_m);
-        Hashtable<_LastUniteKey, uint64_t>::Iterator i(_lastUniteAttempt);
-        _LastUniteKey* k = (_LastUniteKey*)0;
-        uint64_t* v = (uint64_t*)0;
-        while (i.next(k, v)) {
-            if ((now - *v) >= (ZT_MIN_UNITE_INTERVAL * 8)) {
-                _lastUniteAttempt.erase(*k);
-            }
-        }
-    }
-
-    {
-        Mutex::Lock _l(_lastSentWhoisRequest_m);
-        Hashtable<Address, int64_t>::Iterator i(_lastSentWhoisRequest);
-        Address* a = (Address*)0;
-        int64_t* ts = (int64_t*)0;
-        while (i.next(a, ts)) {
-            if ((now - *ts) > (ZT_WHOIS_RETRY_DELAY * 2)) {
-                _lastSentWhoisRequest.erase(*a);
-            }
-        }
-    }
-
-    return ZT_WHOIS_RETRY_DELAY;
+	const uint64_t timeSinceLastCheck = now - _lastCheckedQueues;
+	if (timeSinceLastCheck < ZT_WHOIS_RETRY_DELAY) {
+		return (unsigned long)(ZT_WHOIS_RETRY_DELAY - timeSinceLastCheck);
+	}
+	_lastCheckedQueues = now;
+
+	std::vector<Address> needWhois;
+	{
+		Mutex::Lock _l(_txQueue_m);
+
+		for (std::list<TXQueueEntry>::iterator txi(_txQueue.begin()); txi != _txQueue.end();) {
+			if (_trySend(tPtr, txi->packet, txi->encrypt, txi->flowId)) {
+				_txQueue.erase(txi++);
+			}
+			else if ((now - txi->creationTime) > ZT_TRANSMIT_QUEUE_TIMEOUT) {
+				_txQueue.erase(txi++);
+			}
+			else {
+				if (! RR->topology->getPeer(tPtr, txi->dest)) {
+					needWhois.push_back(txi->dest);
+				}
+				++txi;
+			}
+		}
+	}
+	for (std::vector<Address>::const_iterator i(needWhois.begin()); i != needWhois.end(); ++i) {
+		requestWhois(tPtr, now, *i);
+	}
+
+	for (unsigned int ptr = 0; ptr < ZT_RX_QUEUE_SIZE; ++ptr) {
+		RXQueueEntry* const rq = &(_rxQueue[ptr]);
+		Mutex::Lock rql(rq->lock);
+		if ((rq->timestamp) && (rq->complete)) {
+			if ((rq->frag0.tryDecode(RR, tPtr, rq->flowId)) || ((now - rq->timestamp) > ZT_RECEIVE_QUEUE_TIMEOUT)) {
+				rq->timestamp = 0;
+			}
+			else {
+				const Address src(rq->frag0.source());
+				if (! RR->topology->getPeer(tPtr, src)) {
+					requestWhois(tPtr, now, src);
+				}
+			}
+		}
+	}
+
+	{
+		Mutex::Lock _l(_lastUniteAttempt_m);
+		Hashtable<_LastUniteKey, uint64_t>::Iterator i(_lastUniteAttempt);
+		_LastUniteKey* k = (_LastUniteKey*)0;
+		uint64_t* v = (uint64_t*)0;
+		while (i.next(k, v)) {
+			if ((now - *v) >= (ZT_MIN_UNITE_INTERVAL * 8)) {
+				_lastUniteAttempt.erase(*k);
+			}
+		}
+	}
+
+	{
+		Mutex::Lock _l(_lastSentWhoisRequest_m);
+		Hashtable<Address, int64_t>::Iterator i(_lastSentWhoisRequest);
+		Address* a = (Address*)0;
+		int64_t* ts = (int64_t*)0;
+		while (i.next(a, ts)) {
+			if ((now - *ts) > (ZT_WHOIS_RETRY_DELAY * 2)) {
+				_lastSentWhoisRequest.erase(*a);
+			}
+		}
+	}
+
+	return ZT_WHOIS_RETRY_DELAY;
 }
 
 bool Switch::_shouldUnite(const int64_t now, const Address& source, const Address& destination)
 {
-    Mutex::Lock _l(_lastUniteAttempt_m);
-    uint64_t& ts = _lastUniteAttempt[_LastUniteKey(source, destination)];
-    if ((now - ts) >= ZT_MIN_UNITE_INTERVAL) {
-        ts = now;
-        return true;
-    }
-    return false;
+	Mutex::Lock _l(_lastUniteAttempt_m);
+	uint64_t& ts = _lastUniteAttempt[_LastUniteKey(source, destination)];
+	if ((now - ts) >= ZT_MIN_UNITE_INTERVAL) {
+		ts = now;
+		return true;
+	}
+	return false;
 }
 
 bool Switch::_trySend(void* tPtr, Packet& packet, bool encrypt, int32_t flowId)
 {
-    SharedPtr<Path> viaPath;
-    const int64_t now = RR->node->now();
-    const Address destination(packet.destination());
-
-    const SharedPtr<Peer> peer(RR->topology->getPeer(tPtr, destination));
-    if (peer) {
-        if ((peer->bondingPolicy() == ZT_BOND_POLICY_BROADCAST) && (packet.verb() == Packet::VERB_FRAME || packet.verb() == Packet::VERB_EXT_FRAME)) {
-            const SharedPtr<Peer> relay(RR->topology->getUpstreamPeer());
-            Mutex::Lock _l(peer->_paths_m);
-            for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
-                if (peer->_paths[i].p && peer->_paths[i].p->alive(now)) {
-                    uint16_t userSpecifiedMtu = peer->_paths[i].p->mtu();
-                    _sendViaSpecificPath(tPtr, peer, peer->_paths[i].p, userSpecifiedMtu, now, packet, encrypt, flowId);
-                }
-            }
-            return true;
-        }
-        else {
-            viaPath = peer->getAppropriatePath(now, false, flowId);
-            if (! viaPath) {
-                peer->tryMemorizedPath(tPtr, now);   // periodically attempt memorized or statically defined paths, if any are known
-                const SharedPtr<Peer> relay(RR->topology->getUpstreamPeer());
-                if ((! relay) || (! (viaPath = relay->getAppropriatePath(now, false, flowId)))) {
-                    if (! (viaPath = peer->getAppropriatePath(now, true, flowId))) {
-                        return false;
-                    }
-                }
-            }
-            if (viaPath) {
-                uint16_t userSpecifiedMtu = viaPath->mtu();
-                _sendViaSpecificPath(tPtr, peer, viaPath, userSpecifiedMtu, now, packet, encrypt, flowId);
-                return true;
-            }
-        }
-    }
-    return false;
+	SharedPtr<Path> viaPath;
+	const int64_t now = RR->node->now();
+	const Address destination(packet.destination());
+
+	const SharedPtr<Peer> peer(RR->topology->getPeer(tPtr, destination));
+	if (peer) {
+		if ((peer->bondingPolicy() == ZT_BOND_POLICY_BROADCAST) && (packet.verb() == Packet::VERB_FRAME || packet.verb() == Packet::VERB_EXT_FRAME)) {
+			const SharedPtr<Peer> relay(RR->topology->getUpstreamPeer());
+			Mutex::Lock _l(peer->_paths_m);
+			for (int i = 0; i < ZT_MAX_PEER_NETWORK_PATHS; ++i) {
+				if (peer->_paths[i].p && peer->_paths[i].p->alive(now)) {
+					uint16_t userSpecifiedMtu = peer->_paths[i].p->mtu();
+					_sendViaSpecificPath(tPtr, peer, peer->_paths[i].p, userSpecifiedMtu, now, packet, encrypt, flowId);
+				}
+			}
+			return true;
+		}
+		else {
+			viaPath = peer->getAppropriatePath(now, false, flowId);
+			if (! viaPath) {
+				peer->tryMemorizedPath(tPtr, now);	 // periodically attempt memorized or statically defined paths, if any are known
+				const SharedPtr<Peer> relay(RR->topology->getUpstreamPeer());
+				if ((! relay) || (! (viaPath = relay->getAppropriatePath(now, false, flowId)))) {
+					if (! (viaPath = peer->getAppropriatePath(now, true, flowId))) {
+						return false;
+					}
+				}
+			}
+			if (viaPath) {
+				uint16_t userSpecifiedMtu = viaPath->mtu();
+				_sendViaSpecificPath(tPtr, peer, viaPath, userSpecifiedMtu, now, packet, encrypt, flowId);
+				return true;
+			}
+		}
+	}
+	return false;
 }
 
 void Switch::_sendViaSpecificPath(void* tPtr, SharedPtr<Peer> peer, SharedPtr<Path> viaPath, uint16_t userSpecifiedMtu, int64_t now, Packet& packet, bool encrypt, int32_t flowId)
 {
-    unsigned int mtu = ZT_DEFAULT_PHYSMTU;
-    uint64_t trustedPathId = 0;
-    RR->topology->getOutboundPathInfo(viaPath->address(), mtu, trustedPathId);
-
-    if (userSpecifiedMtu > 0) {
-        mtu = userSpecifiedMtu;
-    }
-    unsigned int chunkSize = std::min(packet.size(), mtu);
-    packet.setFragmented(chunkSize < packet.size());
-
-    if (trustedPathId) {
-        packet.setTrusted(trustedPathId);
-    }
-    else {
-        if (! packet.isEncrypted()) {
-            packet.armor(peer->key(), encrypt, false, peer->aesKeysIfSupported(), peer->identity());
-        }
-        RR->node->expectReplyTo(packet.packetId());
-    }
-
-    peer->recordOutgoingPacket(viaPath, packet.packetId(), packet.payloadLength(), packet.verb(), flowId, now);
-
-    if (viaPath->send(RR, tPtr, packet.data(), chunkSize, now)) {
-        if (chunkSize < packet.size()) {
-            // Too big for one packet, fragment the rest
-            unsigned int fragStart = chunkSize;
-            unsigned int remaining = packet.size() - chunkSize;
-            unsigned int fragsRemaining = (remaining / (mtu - ZT_PROTO_MIN_FRAGMENT_LENGTH));
-            if ((fragsRemaining * (mtu - ZT_PROTO_MIN_FRAGMENT_LENGTH)) < remaining) {
-                ++fragsRemaining;
-            }
-            const unsigned int totalFragments = fragsRemaining + 1;
-
-            for (unsigned int fno = 1; fno < totalFragments; ++fno) {
-                chunkSize = std::min(remaining, (unsigned int)(mtu - ZT_PROTO_MIN_FRAGMENT_LENGTH));
-                Packet::Fragment frag(packet, fragStart, chunkSize, fno, totalFragments);
-                viaPath->send(RR, tPtr, frag.data(), frag.size(), now);
-                fragStart += chunkSize;
-                remaining -= chunkSize;
-            }
-        }
-    }
+	unsigned int mtu = ZT_DEFAULT_PHYSMTU;
+	uint64_t trustedPathId = 0;
+	RR->topology->getOutboundPathInfo(viaPath->address(), mtu, trustedPathId);
+
+	if (userSpecifiedMtu > 0) {
+		mtu = userSpecifiedMtu;
+	}
+	unsigned int chunkSize = std::min(packet.size(), mtu);
+	packet.setFragmented(chunkSize < packet.size());
+
+	if (trustedPathId) {
+		packet.setTrusted(trustedPathId);
+	}
+	else {
+		if (! packet.isEncrypted()) {
+			packet.armor(peer->key(), encrypt, false, peer->aesKeysIfSupported(), peer->identity());
+		}
+		RR->node->expectReplyTo(packet.packetId());
+	}
+
+	peer->recordOutgoingPacket(viaPath, packet.packetId(), packet.payloadLength(), packet.verb(), flowId, now);
+
+	if (viaPath->send(RR, tPtr, packet.data(), chunkSize, now)) {
+		if (chunkSize < packet.size()) {
+			// Too big for one packet, fragment the rest
+			unsigned int fragStart = chunkSize;
+			unsigned int remaining = packet.size() - chunkSize;
+			unsigned int fragsRemaining = (remaining / (mtu - ZT_PROTO_MIN_FRAGMENT_LENGTH));
+			if ((fragsRemaining * (mtu - ZT_PROTO_MIN_FRAGMENT_LENGTH)) < remaining) {
+				++fragsRemaining;
+			}
+			const unsigned int totalFragments = fragsRemaining + 1;
+
+			for (unsigned int fno = 1; fno < totalFragments; ++fno) {
+				chunkSize = std::min(remaining, (unsigned int)(mtu - ZT_PROTO_MIN_FRAGMENT_LENGTH));
+				Packet::Fragment frag(packet, fragStart, chunkSize, fno, totalFragments);
+				viaPath->send(RR, tPtr, frag.data(), frag.size(), now);
+				fragStart += chunkSize;
+				remaining -= chunkSize;
+			}
+		}
+	}
 }
 
 void Switch::_recordOutgoingPacketMetrics(const Packet& p)
 {
-    switch (p.verb()) {
-        case Packet::VERB_NOP:
-            Metrics::pkt_nop_out++;
-            break;
-        case Packet::VERB_HELLO:
-            Metrics::pkt_hello_out++;
-            break;
-        case Packet::VERB_ERROR:
-            Metrics::pkt_error_out++;
-            break;
-        case Packet::VERB_OK:
-            Metrics::pkt_ok_out++;
-            break;
-        case Packet::VERB_WHOIS:
-            Metrics::pkt_whois_out++;
-            break;
-        case Packet::VERB_RENDEZVOUS:
-            Metrics::pkt_rendezvous_out++;
-            break;
-        case Packet::VERB_FRAME:
-            Metrics::pkt_frame_out++;
-            break;
-        case Packet::VERB_EXT_FRAME:
-            Metrics::pkt_ext_frame_out++;
-            break;
-        case Packet::VERB_ECHO:
-            Metrics::pkt_echo_out++;
-            break;
-        case Packet::VERB_MULTICAST_LIKE:
-            Metrics::pkt_multicast_like_out++;
-            break;
-        case Packet::VERB_NETWORK_CREDENTIALS:
-            Metrics::pkt_network_credentials_out++;
-            break;
-        case Packet::VERB_NETWORK_CONFIG_REQUEST:
-            Metrics::pkt_network_config_request_out++;
-            break;
-        case Packet::VERB_NETWORK_CONFIG:
-            Metrics::pkt_network_config_out++;
-            break;
-        case Packet::VERB_MULTICAST_GATHER:
-            Metrics::pkt_multicast_gather_out++;
-            break;
-        case Packet::VERB_MULTICAST_FRAME:
-            Metrics::pkt_multicast_frame_out++;
-            break;
-        case Packet::VERB_PUSH_DIRECT_PATHS:
-            Metrics::pkt_push_direct_paths_out++;
-            break;
-        case Packet::VERB_ACK:
-            Metrics::pkt_ack_out++;
-            break;
-        case Packet::VERB_QOS_MEASUREMENT:
-            Metrics::pkt_qos_out++;
-            break;
-        case Packet::VERB_USER_MESSAGE:
-            Metrics::pkt_user_message_out++;
-            break;
-        case Packet::VERB_REMOTE_TRACE:
-            Metrics::pkt_remote_trace_out++;
-            break;
-        case Packet::VERB_PATH_NEGOTIATION_REQUEST:
-            Metrics::pkt_path_negotiation_request_out++;
-            break;
-    }
+	switch (p.verb()) {
+		case Packet::VERB_NOP:
+			Metrics::pkt_nop_out++;
+			break;
+		case Packet::VERB_HELLO:
+			Metrics::pkt_hello_out++;
+			break;
+		case Packet::VERB_ERROR:
+			Metrics::pkt_error_out++;
+			break;
+		case Packet::VERB_OK:
+			Metrics::pkt_ok_out++;
+			break;
+		case Packet::VERB_WHOIS:
+			Metrics::pkt_whois_out++;
+			break;
+		case Packet::VERB_RENDEZVOUS:
+			Metrics::pkt_rendezvous_out++;
+			break;
+		case Packet::VERB_FRAME:
+			Metrics::pkt_frame_out++;
+			break;
+		case Packet::VERB_EXT_FRAME:
+			Metrics::pkt_ext_frame_out++;
+			break;
+		case Packet::VERB_ECHO:
+			Metrics::pkt_echo_out++;
+			break;
+		case Packet::VERB_MULTICAST_LIKE:
+			Metrics::pkt_multicast_like_out++;
+			break;
+		case Packet::VERB_NETWORK_CREDENTIALS:
+			Metrics::pkt_network_credentials_out++;
+			break;
+		case Packet::VERB_NETWORK_CONFIG_REQUEST:
+			Metrics::pkt_network_config_request_out++;
+			break;
+		case Packet::VERB_NETWORK_CONFIG:
+			Metrics::pkt_network_config_out++;
+			break;
+		case Packet::VERB_MULTICAST_GATHER:
+			Metrics::pkt_multicast_gather_out++;
+			break;
+		case Packet::VERB_MULTICAST_FRAME:
+			Metrics::pkt_multicast_frame_out++;
+			break;
+		case Packet::VERB_PUSH_DIRECT_PATHS:
+			Metrics::pkt_push_direct_paths_out++;
+			break;
+		case Packet::VERB_ACK:
+			Metrics::pkt_ack_out++;
+			break;
+		case Packet::VERB_QOS_MEASUREMENT:
+			Metrics::pkt_qos_out++;
+			break;
+		case Packet::VERB_USER_MESSAGE:
+			Metrics::pkt_user_message_out++;
+			break;
+		case Packet::VERB_REMOTE_TRACE:
+			Metrics::pkt_remote_trace_out++;
+			break;
+		case Packet::VERB_PATH_NEGOTIATION_REQUEST:
+			Metrics::pkt_path_negotiation_request_out++;
+			break;
+	}
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 266 - 266
node/Switch.hpp

@@ -55,279 +55,279 @@ class Peer;
  * wraps/unwraps accordingly. It also handles queues and timeouts and such.
  */
 class Switch {
-    struct ManagedQueue;
-    struct TXQueueEntry;
+	struct ManagedQueue;
+	struct TXQueueEntry;
 
-    friend class SharedPtr<Peer>;
+	friend class SharedPtr<Peer>;
 
-    typedef struct {
-        TXQueueEntry* p;
-        bool ok_to_drop;
-    } dqr;
+	typedef struct {
+		TXQueueEntry* p;
+		bool ok_to_drop;
+	} dqr;
 
   public:
-    Switch(const RuntimeEnvironment* renv);
-
-    /**
-     * Called when a packet is received from the real network
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param localSocket Local I/O socket as supplied by external code
-     * @param fromAddr Internet IP address of origin
-     * @param data Packet data
-     * @param len Packet length
-     */
-    void onRemotePacket(void* tPtr, const int64_t localSocket, const InetAddress& fromAddr, const void* data, unsigned int len);
-
-    /**
-     * Returns whether our bonding or balancing policy is aware of flows.
-     */
-    bool isFlowAware();
-
-    /**
-     * Called when a packet comes from a local Ethernet tap
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param network Which network's TAP did this packet come from?
-     * @param from Originating MAC address
-     * @param to Destination MAC address
-     * @param etherType Ethernet packet type
-     * @param vlanId VLAN ID or 0 if none
-     * @param data Ethernet payload
-     * @param len Frame length
-     */
-    void onLocalEthernet(void* tPtr, const SharedPtr<Network>& network, const MAC& from, const MAC& to, unsigned int etherType, unsigned int vlanId, const void* data, unsigned int len);
-
-    /**
-     * Determines the next drop schedule for packets in the TX queue
-     *
-     * @param t Current time
-     * @param count Number of packets dropped this round
-     */
-    uint64_t control_law(uint64_t t, int count);
-
-    /**
-     * Selects a packet eligible for transmission from a TX queue. According to the control law, multiple packets
-     * may be intentionally dropped before a packet is returned to the AQM scheduler.
-     *
-     * @param q The TX queue that is being dequeued from
-     * @param now Current time
-     */
-    dqr dodequeue(ManagedQueue* q, uint64_t now);
-
-    /**
-     * Presents a packet to the AQM scheduler.
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param network Network that the packet shall be sent over
-     * @param packet Packet to be sent
-     * @param encrypt Encrypt packet payload? (always true except for HELLO)
-     * @param qosBucket Which bucket the rule-system determined this packet should fall into
-     */
-    void aqm_enqueue(void* tPtr, const SharedPtr<Network>& network, Packet& packet, bool encrypt, int qosBucket, int32_t flowId = ZT_QOS_NO_FLOW);
-
-    /**
-     * Performs a single AQM cycle and dequeues and transmits all eligible packets on all networks
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     */
-    void aqm_dequeue(void* tPtr);
-
-    /**
-     * Calls the dequeue mechanism and adjust queue state variables
-     *
-     * @param q The TX queue that is being dequeued from
-     * @param isNew Whether or not this queue is in the NEW list
-     * @param now Current time
-     */
-    Switch::TXQueueEntry* CoDelDequeue(ManagedQueue* q, bool isNew, uint64_t now);
-
-    /**
-     * Removes QoS Queues and flow state variables for a specific network. These queues are created
-     * automatically upon the transmission of the first packet from this peer to another peer on the
-     * given network.
-     *
-     * The reason for existence of queues and flow state variables specific to each network is so that
-     * each network's QoS rules function independently.
-     *
-     * @param nwid Network ID
-     */
-    void removeNetworkQoSControlBlock(uint64_t nwid);
-
-    /**
-     * Send a packet to a ZeroTier address (destination in packet)
-     *
-     * The packet must be fully composed with source and destination but not
-     * yet encrypted. If the destination peer is known the packet
-     * is sent immediately. Otherwise it is queued and a WHOIS is dispatched.
-     *
-     * The packet may be compressed. Compression isn't done here.
-     *
-     * Needless to say, the packet's source must be this node. Otherwise it
-     * won't be encrypted right. (This is not used for relaying.)
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param packet Packet to send (buffer may be modified)
-     * @param encrypt Encrypt packet payload? (always true except for HELLO)
-     */
-    void send(void* tPtr, Packet& packet, bool encrypt, int32_t flowId = ZT_QOS_NO_FLOW);
-
-    /**
-     * Request WHOIS on a given address
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param now Current time
-     * @param addr Address to look up
-     */
-    void requestWhois(void* tPtr, const int64_t now, const Address& addr);
-
-    /**
-     * Run any processes that are waiting for this peer's identity
-     *
-     * Called when we learn of a peer's identity from HELLO, OK(WHOIS), etc.
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param peer New peer
-     */
-    void doAnythingWaitingForPeer(void* tPtr, const SharedPtr<Peer>& peer);
-
-    /**
-     * Perform retries and other periodic timer tasks
-     *
-     * This can return a very long delay if there are no pending timer
-     * tasks. The caller should cap this comparatively vs. other values.
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param now Current time
-     * @return Number of milliseconds until doTimerTasks() should be run again
-     */
-    unsigned long doTimerTasks(void* tPtr, int64_t now);
+	Switch(const RuntimeEnvironment* renv);
+
+	/**
+	 * Called when a packet is received from the real network
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param localSocket Local I/O socket as supplied by external code
+	 * @param fromAddr Internet IP address of origin
+	 * @param data Packet data
+	 * @param len Packet length
+	 */
+	void onRemotePacket(void* tPtr, const int64_t localSocket, const InetAddress& fromAddr, const void* data, unsigned int len);
+
+	/**
+	 * Returns whether our bonding or balancing policy is aware of flows.
+	 */
+	bool isFlowAware();
+
+	/**
+	 * Called when a packet comes from a local Ethernet tap
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param network Which network's TAP did this packet come from?
+	 * @param from Originating MAC address
+	 * @param to Destination MAC address
+	 * @param etherType Ethernet packet type
+	 * @param vlanId VLAN ID or 0 if none
+	 * @param data Ethernet payload
+	 * @param len Frame length
+	 */
+	void onLocalEthernet(void* tPtr, const SharedPtr<Network>& network, const MAC& from, const MAC& to, unsigned int etherType, unsigned int vlanId, const void* data, unsigned int len);
+
+	/**
+	 * Determines the next drop schedule for packets in the TX queue
+	 *
+	 * @param t Current time
+	 * @param count Number of packets dropped this round
+	 */
+	uint64_t control_law(uint64_t t, int count);
+
+	/**
+	 * Selects a packet eligible for transmission from a TX queue. According to the control law, multiple packets
+	 * may be intentionally dropped before a packet is returned to the AQM scheduler.
+	 *
+	 * @param q The TX queue that is being dequeued from
+	 * @param now Current time
+	 */
+	dqr dodequeue(ManagedQueue* q, uint64_t now);
+
+	/**
+	 * Presents a packet to the AQM scheduler.
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param network Network that the packet shall be sent over
+	 * @param packet Packet to be sent
+	 * @param encrypt Encrypt packet payload? (always true except for HELLO)
+	 * @param qosBucket Which bucket the rule-system determined this packet should fall into
+	 */
+	void aqm_enqueue(void* tPtr, const SharedPtr<Network>& network, Packet& packet, bool encrypt, int qosBucket, int32_t flowId = ZT_QOS_NO_FLOW);
+
+	/**
+	 * Performs a single AQM cycle and dequeues and transmits all eligible packets on all networks
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 */
+	void aqm_dequeue(void* tPtr);
+
+	/**
+	 * Calls the dequeue mechanism and adjust queue state variables
+	 *
+	 * @param q The TX queue that is being dequeued from
+	 * @param isNew Whether or not this queue is in the NEW list
+	 * @param now Current time
+	 */
+	Switch::TXQueueEntry* CoDelDequeue(ManagedQueue* q, bool isNew, uint64_t now);
+
+	/**
+	 * Removes QoS Queues and flow state variables for a specific network. These queues are created
+	 * automatically upon the transmission of the first packet from this peer to another peer on the
+	 * given network.
+	 *
+	 * The reason for existence of queues and flow state variables specific to each network is so that
+	 * each network's QoS rules function independently.
+	 *
+	 * @param nwid Network ID
+	 */
+	void removeNetworkQoSControlBlock(uint64_t nwid);
+
+	/**
+	 * Send a packet to a ZeroTier address (destination in packet)
+	 *
+	 * The packet must be fully composed with source and destination but not
+	 * yet encrypted. If the destination peer is known the packet
+	 * is sent immediately. Otherwise it is queued and a WHOIS is dispatched.
+	 *
+	 * The packet may be compressed. Compression isn't done here.
+	 *
+	 * Needless to say, the packet's source must be this node. Otherwise it
+	 * won't be encrypted right. (This is not used for relaying.)
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param packet Packet to send (buffer may be modified)
+	 * @param encrypt Encrypt packet payload? (always true except for HELLO)
+	 */
+	void send(void* tPtr, Packet& packet, bool encrypt, int32_t flowId = ZT_QOS_NO_FLOW);
+
+	/**
+	 * Request WHOIS on a given address
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param now Current time
+	 * @param addr Address to look up
+	 */
+	void requestWhois(void* tPtr, const int64_t now, const Address& addr);
+
+	/**
+	 * Run any processes that are waiting for this peer's identity
+	 *
+	 * Called when we learn of a peer's identity from HELLO, OK(WHOIS), etc.
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param peer New peer
+	 */
+	void doAnythingWaitingForPeer(void* tPtr, const SharedPtr<Peer>& peer);
+
+	/**
+	 * Perform retries and other periodic timer tasks
+	 *
+	 * This can return a very long delay if there are no pending timer
+	 * tasks. The caller should cap this comparatively vs. other values.
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param now Current time
+	 * @return Number of milliseconds until doTimerTasks() should be run again
+	 */
+	unsigned long doTimerTasks(void* tPtr, int64_t now);
 
   private:
-    bool _shouldUnite(const int64_t now, const Address& source, const Address& destination);
-    bool _trySend(void* tPtr, Packet& packet, bool encrypt, int32_t flowId = ZT_QOS_NO_FLOW);   // packet is modified if return is true
-    void _sendViaSpecificPath(void* tPtr, SharedPtr<Peer> peer, SharedPtr<Path> viaPath, uint16_t userSpecifiedMtu, int64_t now, Packet& packet, bool encrypt, int32_t flowId);
-    void _recordOutgoingPacketMetrics(const Packet& p);
-
-    const RuntimeEnvironment* const RR;
-    int64_t _lastBeaconResponse;
-    volatile int64_t _lastCheckedQueues;
-
-    // Time we last sent a WHOIS request for each address
-    Hashtable<Address, int64_t> _lastSentWhoisRequest;
-    Mutex _lastSentWhoisRequest_m;
-
-    // Packets waiting for WHOIS replies or other decode info or missing fragments
-    struct RXQueueEntry {
-        RXQueueEntry() : timestamp(0)
-        {
-        }
-        volatile int64_t timestamp;   // 0 if entry is not in use
-        volatile uint64_t packetId;
-        IncomingPacket frag0;                                  // head of packet
-        Packet::Fragment frags[ZT_MAX_PACKET_FRAGMENTS - 1];   // later fragments (if any)
-        unsigned int totalFragments;                           // 0 if only frag0 received, waiting for frags
-        uint32_t haveFragments;                                // bit mask, LSB to MSB
-        volatile bool complete;                                // if true, packet is complete
-        volatile int32_t flowId;
-        Mutex lock;
-    };
-    RXQueueEntry _rxQueue[ZT_RX_QUEUE_SIZE];
-    AtomicCounter _rxQueuePtr;
-
-    // Returns matching or next available RX queue entry
-    inline RXQueueEntry* _findRXQueueEntry(uint64_t packetId)
-    {
-        const unsigned int current = static_cast<unsigned int>(_rxQueuePtr.load());
-        for (unsigned int k = 1; k <= ZT_RX_QUEUE_SIZE; ++k) {
-            RXQueueEntry* rq = &(_rxQueue[(current - k) % ZT_RX_QUEUE_SIZE]);
-            if ((rq->packetId == packetId) && (rq->timestamp)) {
-                return rq;
-            }
-        }
-        ++_rxQueuePtr;
-        return &(_rxQueue[static_cast<unsigned int>(current) % ZT_RX_QUEUE_SIZE]);
-    }
-
-    // Returns current entry in rx queue ring buffer and increments ring pointer
-    inline RXQueueEntry* _nextRXQueueEntry()
-    {
-        return &(_rxQueue[static_cast<unsigned int>((++_rxQueuePtr) - 1) % ZT_RX_QUEUE_SIZE]);
-    }
-
-    // ZeroTier-layer TX queue entry
-    struct TXQueueEntry {
-        TXQueueEntry()
-        {
-        }
-        TXQueueEntry(Address d, uint64_t ct, const Packet& p, bool enc, int32_t fid) : dest(d), creationTime(ct), packet(p), encrypt(enc), flowId(fid)
-        {
-        }
-
-        Address dest;
-        uint64_t creationTime;
-        Packet packet;   // unencrypted/unMAC'd packet -- this is done at send time
-        bool encrypt;
-        int32_t flowId;
-    };
-    std::list<TXQueueEntry> _txQueue;
-    Mutex _txQueue_m;
-    Mutex _aqm_m;
-
-    // Tracks sending of VERB_RENDEZVOUS to relaying peers
-    struct _LastUniteKey {
-        _LastUniteKey() : x(0), y(0)
-        {
-        }
-        _LastUniteKey(const Address& a1, const Address& a2)
-        {
-            if (a1 > a2) {
-                x = a2.toInt();
-                y = a1.toInt();
-            }
-            else {
-                x = a1.toInt();
-                y = a2.toInt();
-            }
-        }
-        inline unsigned long hashCode() const
-        {
-            return ((unsigned long)x ^ (unsigned long)y);
-        }
-        inline bool operator==(const _LastUniteKey& k) const
-        {
-            return ((x == k.x) && (y == k.y));
-        }
-        uint64_t x, y;
-    };
-    Hashtable<_LastUniteKey, uint64_t> _lastUniteAttempt;   // key is always sorted in ascending order, for set-like behavior
-    Mutex _lastUniteAttempt_m;
-
-    // Queue with additional flow state variables
-    struct ManagedQueue {
-        ManagedQueue(int id) : id(id), byteCredit(ZT_AQM_QUANTUM), byteLength(0), dropping(false)
-        {
-        }
-        int id;
-        int byteCredit;
-        int byteLength;
-        uint64_t first_above_time;
-        uint32_t count;
-        uint64_t drop_next;
-        bool dropping;
-        uint64_t drop_next_time;
-        std::list<TXQueueEntry*> q;
-    };
-    // To implement fq_codel we need to maintain a queue of queues
-    struct NetworkQoSControlBlock {
-        int _currEnqueuedPackets;
-        std::vector<ManagedQueue*> newQueues;
-        std::vector<ManagedQueue*> oldQueues;
-        std::vector<ManagedQueue*> inactiveQueues;
-    };
-    std::map<uint64_t, NetworkQoSControlBlock*> _netQueueControlBlock;
+	bool _shouldUnite(const int64_t now, const Address& source, const Address& destination);
+	bool _trySend(void* tPtr, Packet& packet, bool encrypt, int32_t flowId = ZT_QOS_NO_FLOW);	// packet is modified if return is true
+	void _sendViaSpecificPath(void* tPtr, SharedPtr<Peer> peer, SharedPtr<Path> viaPath, uint16_t userSpecifiedMtu, int64_t now, Packet& packet, bool encrypt, int32_t flowId);
+	void _recordOutgoingPacketMetrics(const Packet& p);
+
+	const RuntimeEnvironment* const RR;
+	int64_t _lastBeaconResponse;
+	volatile int64_t _lastCheckedQueues;
+
+	// Time we last sent a WHOIS request for each address
+	Hashtable<Address, int64_t> _lastSentWhoisRequest;
+	Mutex _lastSentWhoisRequest_m;
+
+	// Packets waiting for WHOIS replies or other decode info or missing fragments
+	struct RXQueueEntry {
+		RXQueueEntry() : timestamp(0)
+		{
+		}
+		volatile int64_t timestamp;	  // 0 if entry is not in use
+		volatile uint64_t packetId;
+		IncomingPacket frag0;								   // head of packet
+		Packet::Fragment frags[ZT_MAX_PACKET_FRAGMENTS - 1];   // later fragments (if any)
+		unsigned int totalFragments;						   // 0 if only frag0 received, waiting for frags
+		uint32_t haveFragments;								   // bit mask, LSB to MSB
+		volatile bool complete;								   // if true, packet is complete
+		volatile int32_t flowId;
+		Mutex lock;
+	};
+	RXQueueEntry _rxQueue[ZT_RX_QUEUE_SIZE];
+	AtomicCounter _rxQueuePtr;
+
+	// Returns matching or next available RX queue entry
+	inline RXQueueEntry* _findRXQueueEntry(uint64_t packetId)
+	{
+		const unsigned int current = static_cast<unsigned int>(_rxQueuePtr.load());
+		for (unsigned int k = 1; k <= ZT_RX_QUEUE_SIZE; ++k) {
+			RXQueueEntry* rq = &(_rxQueue[(current - k) % ZT_RX_QUEUE_SIZE]);
+			if ((rq->packetId == packetId) && (rq->timestamp)) {
+				return rq;
+			}
+		}
+		++_rxQueuePtr;
+		return &(_rxQueue[static_cast<unsigned int>(current) % ZT_RX_QUEUE_SIZE]);
+	}
+
+	// Returns current entry in rx queue ring buffer and increments ring pointer
+	inline RXQueueEntry* _nextRXQueueEntry()
+	{
+		return &(_rxQueue[static_cast<unsigned int>((++_rxQueuePtr) - 1) % ZT_RX_QUEUE_SIZE]);
+	}
+
+	// ZeroTier-layer TX queue entry
+	struct TXQueueEntry {
+		TXQueueEntry()
+		{
+		}
+		TXQueueEntry(Address d, uint64_t ct, const Packet& p, bool enc, int32_t fid) : dest(d), creationTime(ct), packet(p), encrypt(enc), flowId(fid)
+		{
+		}
+
+		Address dest;
+		uint64_t creationTime;
+		Packet packet;	 // unencrypted/unMAC'd packet -- this is done at send time
+		bool encrypt;
+		int32_t flowId;
+	};
+	std::list<TXQueueEntry> _txQueue;
+	Mutex _txQueue_m;
+	Mutex _aqm_m;
+
+	// Tracks sending of VERB_RENDEZVOUS to relaying peers
+	struct _LastUniteKey {
+		_LastUniteKey() : x(0), y(0)
+		{
+		}
+		_LastUniteKey(const Address& a1, const Address& a2)
+		{
+			if (a1 > a2) {
+				x = a2.toInt();
+				y = a1.toInt();
+			}
+			else {
+				x = a1.toInt();
+				y = a2.toInt();
+			}
+		}
+		inline unsigned long hashCode() const
+		{
+			return ((unsigned long)x ^ (unsigned long)y);
+		}
+		inline bool operator==(const _LastUniteKey& k) const
+		{
+			return ((x == k.x) && (y == k.y));
+		}
+		uint64_t x, y;
+	};
+	Hashtable<_LastUniteKey, uint64_t> _lastUniteAttempt;	// key is always sorted in ascending order, for set-like behavior
+	Mutex _lastUniteAttempt_m;
+
+	// Queue with additional flow state variables
+	struct ManagedQueue {
+		ManagedQueue(int id) : id(id), byteCredit(ZT_AQM_QUANTUM), byteLength(0), dropping(false)
+		{
+		}
+		int id;
+		int byteCredit;
+		int byteLength;
+		uint64_t first_above_time;
+		uint32_t count;
+		uint64_t drop_next;
+		bool dropping;
+		uint64_t drop_next_time;
+		std::list<TXQueueEntry*> q;
+	};
+	// To implement fq_codel we need to maintain a queue of queues
+	struct NetworkQoSControlBlock {
+		int _currEnqueuedPackets;
+		std::vector<ManagedQueue*> newQueues;
+		std::vector<ManagedQueue*> oldQueues;
+		std::vector<ManagedQueue*> inactiveQueues;
+	};
+	std::map<uint64_t, NetworkQoSControlBlock*> _netQueueControlBlock;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 17 - 17
node/Tag.cpp

@@ -24,22 +24,22 @@ namespace ZeroTier {
 
 int Tag::verify(const RuntimeEnvironment* RR, void* tPtr) const
 {
-    if ((! _signedBy) || (_signedBy != Network::controllerFor(_networkId))) {
-        return -1;
-    }
-    const Identity id(RR->topology->getIdentity(tPtr, _signedBy));
-    if (! id) {
-        RR->sw->requestWhois(tPtr, RR->node->now(), _signedBy);
-        return 1;
-    }
-    try {
-        Buffer<(sizeof(Tag) * 2)> tmp;
-        this->serialize(tmp, true);
-        return (id.verify(tmp.data(), tmp.size(), _signature) ? 0 : -1);
-    }
-    catch (...) {
-        return -1;
-    }
+	if ((! _signedBy) || (_signedBy != Network::controllerFor(_networkId))) {
+		return -1;
+	}
+	const Identity id(RR->topology->getIdentity(tPtr, _signedBy));
+	if (! id) {
+		RR->sw->requestWhois(tPtr, RR->node->now(), _signedBy);
+		return 1;
+	}
+	try {
+		Buffer<(sizeof(Tag) * 2)> tmp;
+		this->serialize(tmp, true);
+		return (id.verify(tmp.data(), tmp.size(), _signature) ? 0 : -1);
+	}
+	catch (...) {
+		return -1;
+	}
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 182 - 182
node/Tag.hpp

@@ -49,211 +49,211 @@ class RuntimeEnvironment;
  */
 class Tag : public Credential {
   public:
-    static inline Credential::Type credentialType()
-    {
-        return Credential::CREDENTIAL_TYPE_TAG;
-    }
+	static inline Credential::Type credentialType()
+	{
+		return Credential::CREDENTIAL_TYPE_TAG;
+	}
 
-    Tag() : _id(0), _value(0), _networkId(0), _ts(0)
-    {
-        memset(_signature.data, 0, sizeof(_signature.data));
-    }
+	Tag() : _id(0), _value(0), _networkId(0), _ts(0)
+	{
+		memset(_signature.data, 0, sizeof(_signature.data));
+	}
 
-    /**
-     * @param nwid Network ID
-     * @param ts Timestamp
-     * @param issuedTo Address to which this tag was issued
-     * @param id Tag ID
-     * @param value Tag value
-     */
-    Tag(const uint64_t nwid, const int64_t ts, const Address& issuedTo, const uint32_t id, const uint32_t value) : _id(id), _value(value), _networkId(nwid), _ts(ts), _issuedTo(issuedTo), _signedBy()
-    {
-        memset(_signature.data, 0, sizeof(_signature.data));
-    }
+	/**
+	 * @param nwid Network ID
+	 * @param ts Timestamp
+	 * @param issuedTo Address to which this tag was issued
+	 * @param id Tag ID
+	 * @param value Tag value
+	 */
+	Tag(const uint64_t nwid, const int64_t ts, const Address& issuedTo, const uint32_t id, const uint32_t value) : _id(id), _value(value), _networkId(nwid), _ts(ts), _issuedTo(issuedTo), _signedBy()
+	{
+		memset(_signature.data, 0, sizeof(_signature.data));
+	}
 
-    inline uint32_t id() const
-    {
-        return _id;
-    }
-    inline const uint32_t& value() const
-    {
-        return _value;
-    }
-    inline uint64_t networkId() const
-    {
-        return _networkId;
-    }
-    inline int64_t timestamp() const
-    {
-        return _ts;
-    }
-    inline const Address& issuedTo() const
-    {
-        return _issuedTo;
-    }
-    inline const Address& signedBy() const
-    {
-        return _signedBy;
-    }
+	inline uint32_t id() const
+	{
+		return _id;
+	}
+	inline const uint32_t& value() const
+	{
+		return _value;
+	}
+	inline uint64_t networkId() const
+	{
+		return _networkId;
+	}
+	inline int64_t timestamp() const
+	{
+		return _ts;
+	}
+	inline const Address& issuedTo() const
+	{
+		return _issuedTo;
+	}
+	inline const Address& signedBy() const
+	{
+		return _signedBy;
+	}
 
-    /**
-     * Sign this tag
-     *
-     * @param signer Signing identity, must have private key
-     * @return True if signature was successful
-     */
-    inline bool sign(const Identity& signer)
-    {
-        if (signer.hasPrivate()) {
-            Buffer<sizeof(Tag) + 64> tmp;
-            _signedBy = signer.address();
-            this->serialize(tmp, true);
-            _signature = signer.sign(tmp.data(), tmp.size());
-            return true;
-        }
-        return false;
-    }
+	/**
+	 * Sign this tag
+	 *
+	 * @param signer Signing identity, must have private key
+	 * @return True if signature was successful
+	 */
+	inline bool sign(const Identity& signer)
+	{
+		if (signer.hasPrivate()) {
+			Buffer<sizeof(Tag) + 64> tmp;
+			_signedBy = signer.address();
+			this->serialize(tmp, true);
+			_signature = signer.sign(tmp.data(), tmp.size());
+			return true;
+		}
+		return false;
+	}
 
-    /**
-     * Check this tag's signature
-     *
-     * @param RR Runtime environment to allow identity lookup for signedBy
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or tag
-     */
-    int verify(const RuntimeEnvironment* RR, void* tPtr) const;
+	/**
+	 * Check this tag's signature
+	 *
+	 * @param RR Runtime environment to allow identity lookup for signedBy
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or tag
+	 */
+	int verify(const RuntimeEnvironment* RR, void* tPtr) const;
 
-    template <unsigned int C> inline void serialize(Buffer<C>& b, const bool forSign = false) const
-    {
-        if (forSign) {
-            b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
-        }
+	template <unsigned int C> inline void serialize(Buffer<C>& b, const bool forSign = false) const
+	{
+		if (forSign) {
+			b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
+		}
 
-        b.append(_networkId);
-        b.append(_ts);
-        b.append(_id);
-        b.append(_value);
+		b.append(_networkId);
+		b.append(_ts);
+		b.append(_id);
+		b.append(_value);
 
-        _issuedTo.appendTo(b);
-        _signedBy.appendTo(b);
-        if (! forSign) {
-            b.append((uint8_t)1);                       // 1 == Ed25519
-            b.append((uint16_t)ZT_ECC_SIGNATURE_LEN);   // length of signature
-            b.append(_signature.data, ZT_ECC_SIGNATURE_LEN);
-        }
+		_issuedTo.appendTo(b);
+		_signedBy.appendTo(b);
+		if (! forSign) {
+			b.append((uint8_t)1);						// 1 == Ed25519
+			b.append((uint16_t)ZT_ECC_SIGNATURE_LEN);	// length of signature
+			b.append(_signature.data, ZT_ECC_SIGNATURE_LEN);
+		}
 
-        b.append((uint16_t)0);   // length of additional fields, currently 0
+		b.append((uint16_t)0);	 // length of additional fields, currently 0
 
-        if (forSign) {
-            b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
-        }
-    }
+		if (forSign) {
+			b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
+		}
+	}
 
-    template <unsigned int C> inline unsigned int deserialize(const Buffer<C>& b, unsigned int startAt = 0)
-    {
-        unsigned int p = startAt;
+	template <unsigned int C> inline unsigned int deserialize(const Buffer<C>& b, unsigned int startAt = 0)
+	{
+		unsigned int p = startAt;
 
-        *this = Tag();
+		*this = Tag();
 
-        _networkId = b.template at<uint64_t>(p);
-        p += 8;
-        _ts = b.template at<uint64_t>(p);
-        p += 8;
-        _id = b.template at<uint32_t>(p);
-        p += 4;
+		_networkId = b.template at<uint64_t>(p);
+		p += 8;
+		_ts = b.template at<uint64_t>(p);
+		p += 8;
+		_id = b.template at<uint32_t>(p);
+		p += 4;
 
-        _value = b.template at<uint32_t>(p);
-        p += 4;
+		_value = b.template at<uint32_t>(p);
+		p += 4;
 
-        _issuedTo.setTo(b.field(p, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
-        p += ZT_ADDRESS_LENGTH;
-        _signedBy.setTo(b.field(p, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
-        p += ZT_ADDRESS_LENGTH;
-        if (b[p++] == 1) {
-            if (b.template at<uint16_t>(p) != ZT_ECC_SIGNATURE_LEN) {
-                throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_CRYPTOGRAPHIC_TOKEN;
-            }
-            p += 2;
-            memcpy(_signature.data, b.field(p, ZT_ECC_SIGNATURE_LEN), ZT_ECC_SIGNATURE_LEN);
-            p += ZT_ECC_SIGNATURE_LEN;
-        }
-        else {
-            p += 2 + b.template at<uint16_t>(p);
-        }
+		_issuedTo.setTo(b.field(p, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
+		p += ZT_ADDRESS_LENGTH;
+		_signedBy.setTo(b.field(p, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH);
+		p += ZT_ADDRESS_LENGTH;
+		if (b[p++] == 1) {
+			if (b.template at<uint16_t>(p) != ZT_ECC_SIGNATURE_LEN) {
+				throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_CRYPTOGRAPHIC_TOKEN;
+			}
+			p += 2;
+			memcpy(_signature.data, b.field(p, ZT_ECC_SIGNATURE_LEN), ZT_ECC_SIGNATURE_LEN);
+			p += ZT_ECC_SIGNATURE_LEN;
+		}
+		else {
+			p += 2 + b.template at<uint16_t>(p);
+		}
 
-        p += 2 + b.template at<uint16_t>(p);
-        if (p > b.size()) {
-            throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW;
-        }
+		p += 2 + b.template at<uint16_t>(p);
+		if (p > b.size()) {
+			throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW;
+		}
 
-        return (p - startAt);
-    }
+		return (p - startAt);
+	}
 
-    // Provides natural sort order by ID
-    inline bool operator<(const Tag& t) const
-    {
-        return (_id < t._id);
-    }
+	// Provides natural sort order by ID
+	inline bool operator<(const Tag& t) const
+	{
+		return (_id < t._id);
+	}
 
-    inline bool operator==(const Tag& t) const
-    {
-        return (memcmp(this, &t, sizeof(Tag)) == 0);
-    }
-    inline bool operator!=(const Tag& t) const
-    {
-        return (memcmp(this, &t, sizeof(Tag)) != 0);
-    }
+	inline bool operator==(const Tag& t) const
+	{
+		return (memcmp(this, &t, sizeof(Tag)) == 0);
+	}
+	inline bool operator!=(const Tag& t) const
+	{
+		return (memcmp(this, &t, sizeof(Tag)) != 0);
+	}
 
-    // For searching sorted arrays or lists of Tags by ID
-    struct IdComparePredicate {
-        inline bool operator()(const Tag& a, const Tag& b) const
-        {
-            return (a.id() < b.id());
-        }
-        inline bool operator()(const uint32_t a, const Tag& b) const
-        {
-            return (a < b.id());
-        }
-        inline bool operator()(const Tag& a, const uint32_t b) const
-        {
-            return (a.id() < b);
-        }
-        inline bool operator()(const Tag* a, const Tag* b) const
-        {
-            return (a->id() < b->id());
-        }
-        inline bool operator()(const Tag* a, const Tag& b) const
-        {
-            return (a->id() < b.id());
-        }
-        inline bool operator()(const Tag& a, const Tag* b) const
-        {
-            return (a.id() < b->id());
-        }
-        inline bool operator()(const uint32_t a, const Tag* b) const
-        {
-            return (a < b->id());
-        }
-        inline bool operator()(const Tag* a, const uint32_t b) const
-        {
-            return (a->id() < b);
-        }
-        inline bool operator()(const uint32_t a, const uint32_t b) const
-        {
-            return (a < b);
-        }
-    };
+	// For searching sorted arrays or lists of Tags by ID
+	struct IdComparePredicate {
+		inline bool operator()(const Tag& a, const Tag& b) const
+		{
+			return (a.id() < b.id());
+		}
+		inline bool operator()(const uint32_t a, const Tag& b) const
+		{
+			return (a < b.id());
+		}
+		inline bool operator()(const Tag& a, const uint32_t b) const
+		{
+			return (a.id() < b);
+		}
+		inline bool operator()(const Tag* a, const Tag* b) const
+		{
+			return (a->id() < b->id());
+		}
+		inline bool operator()(const Tag* a, const Tag& b) const
+		{
+			return (a->id() < b.id());
+		}
+		inline bool operator()(const Tag& a, const Tag* b) const
+		{
+			return (a.id() < b->id());
+		}
+		inline bool operator()(const uint32_t a, const Tag* b) const
+		{
+			return (a < b->id());
+		}
+		inline bool operator()(const Tag* a, const uint32_t b) const
+		{
+			return (a->id() < b);
+		}
+		inline bool operator()(const uint32_t a, const uint32_t b) const
+		{
+			return (a < b);
+		}
+	};
 
   private:
-    uint32_t _id;
-    uint32_t _value;
-    uint64_t _networkId;
-    int64_t _ts;
-    Address _issuedTo;
-    Address _signedBy;
-    ECC::Signature _signature;
+	uint32_t _id;
+	uint32_t _value;
+	uint64_t _networkId;
+	int64_t _ts;
+	Address _issuedTo;
+	Address _signedBy;
+	ECC::Signature _signature;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 374 - 374
node/Topology.cpp

@@ -25,443 +25,443 @@ namespace ZeroTier {
 
 #define ZT_DEFAULT_WORLD_LENGTH 570
 static const unsigned char ZT_DEFAULT_WORLD[ZT_DEFAULT_WORLD_LENGTH] = {
-    0x01, 0x00, 0x00, 0x00, 0x00, 0x08, 0xea, 0xc9, 0x0a, 0x00, 0x00, 0x01, 0x7e, 0xe9, 0x57, 0x60, 0xcd, 0xb8, 0xb3, 0x88, 0xa4, 0x69, 0x22, 0x14, 0x91, 0xaa, 0x9a, 0xcd, 0x66, 0xcc, 0x76, 0x4c, 0xde, 0xfd, 0x56, 0x03, 0x9f, 0x10,
-    0x67, 0xae, 0x15, 0xe6, 0x9c, 0x6f, 0xb4, 0x2d, 0x7b, 0x55, 0x33, 0x0e, 0x3f, 0xda, 0xac, 0x52, 0x9c, 0x07, 0x92, 0xfd, 0x73, 0x40, 0xa6, 0xaa, 0x21, 0xab, 0xa8, 0xa4, 0x89, 0xfd, 0xae, 0xa4, 0x4a, 0x39, 0xbf, 0x2d, 0x00, 0x65,
-    0x9a, 0xc9, 0xc8, 0x18, 0xeb, 0x36, 0x00, 0x92, 0x76, 0x37, 0xef, 0x4d, 0x14, 0x04, 0xa4, 0x4d, 0x54, 0x46, 0x84, 0x85, 0x13, 0x79, 0x75, 0x1f, 0xaa, 0x79, 0xb4, 0xc4, 0xea, 0x85, 0x04, 0x01, 0x75, 0xea, 0x06, 0x58, 0x60, 0x48,
-    0x24, 0x02, 0xe1, 0xeb, 0x34, 0x20, 0x52, 0x00, 0x0e, 0x62, 0x90, 0x06, 0x1a, 0x9b, 0xe0, 0xcd, 0x29, 0x3c, 0x8b, 0x55, 0xf1, 0xc3, 0xd2, 0x52, 0x48, 0x08, 0xaf, 0xc5, 0x49, 0x22, 0x08, 0x0e, 0x35, 0x39, 0xa7, 0x5a, 0xdd, 0xc3,
-    0xce, 0xf0, 0xf6, 0xad, 0x26, 0x0d, 0x58, 0x82, 0x93, 0xbb, 0x77, 0x86, 0xe7, 0x1e, 0xfa, 0x4b, 0x90, 0x57, 0xda, 0xd9, 0x86, 0x7a, 0xfe, 0x12, 0xdd, 0x04, 0xca, 0xfe, 0x9e, 0xfe, 0xb9, 0x00, 0xcc, 0xde, 0xf7, 0x6b, 0xc7, 0xb9,
-    0x7d, 0xed, 0x90, 0x4e, 0xab, 0xc5, 0xdf, 0x09, 0x88, 0x6d, 0x9c, 0x15, 0x14, 0xa6, 0x10, 0x03, 0x6c, 0xb9, 0x13, 0x9c, 0xc2, 0x14, 0x00, 0x1a, 0x29, 0x58, 0x97, 0x8e, 0xfc, 0xec, 0x15, 0x71, 0x2d, 0xd3, 0x94, 0x8c, 0x6e, 0x6b,
-    0x3a, 0x8e, 0x89, 0x3d, 0xf0, 0x1f, 0xf4, 0x93, 0xd1, 0xf8, 0xd9, 0x80, 0x6a, 0x86, 0x0c, 0x54, 0x20, 0x57, 0x1b, 0xf0, 0x00, 0x02, 0x04, 0x68, 0xc2, 0x08, 0x86, 0x27, 0x09, 0x06, 0x26, 0x05, 0x98, 0x80, 0x02, 0x00, 0x12, 0x00,
-    0x00, 0x30, 0x05, 0x71, 0x0e, 0x34, 0x00, 0x51, 0x27, 0x09, 0x77, 0x8c, 0xde, 0x71, 0x90, 0x00, 0x3f, 0x66, 0x81, 0xa9, 0x9e, 0x5a, 0xd1, 0x89, 0x5e, 0x9f, 0xba, 0x33, 0xe6, 0x21, 0x2d, 0x44, 0x54, 0xe1, 0x68, 0xbc, 0xec, 0x71,
-    0x12, 0x10, 0x1b, 0xf0, 0x00, 0x95, 0x6e, 0xd8, 0xe9, 0x2e, 0x42, 0x89, 0x2c, 0xb6, 0xf2, 0xec, 0x41, 0x08, 0x81, 0xa8, 0x4a, 0xb1, 0x9d, 0xa5, 0x0e, 0x12, 0x87, 0xba, 0x3d, 0x92, 0x6c, 0x3a, 0x1f, 0x75, 0x5c, 0xcc, 0xf2, 0x99,
-    0xa1, 0x20, 0x70, 0x55, 0x00, 0x02, 0x04, 0x67, 0xc3, 0x67, 0x42, 0x27, 0x09, 0x06, 0x26, 0x05, 0x98, 0x80, 0x04, 0x00, 0x00, 0xc3, 0x02, 0x54, 0xf2, 0xbc, 0xa1, 0xf7, 0x00, 0x19, 0x27, 0x09, 0x62, 0xf8, 0x65, 0xae, 0x71, 0x00,
-    0xe2, 0x07, 0x6c, 0x57, 0xde, 0x87, 0x0e, 0x62, 0x88, 0xd7, 0xd5, 0xe7, 0x40, 0x44, 0x08, 0xb1, 0x54, 0x5e, 0xfc, 0xa3, 0x7d, 0x67, 0xf7, 0x7b, 0x87, 0xe9, 0xe5, 0x41, 0x68, 0xc2, 0x5d, 0x3e, 0xf1, 0xa9, 0xab, 0xf2, 0x90, 0x5e,
-    0xa5, 0xe7, 0x85, 0xc0, 0x1d, 0xff, 0x23, 0x88, 0x7a, 0xd4, 0x23, 0x2d, 0x95, 0xc7, 0xa8, 0xfd, 0x2c, 0x27, 0x11, 0x1a, 0x72, 0xbd, 0x15, 0x93, 0x22, 0xdc, 0x00, 0x02, 0x04, 0x32, 0x07, 0xfc, 0x8a, 0x27, 0x09, 0x06, 0x20, 0x01,
-    0x49, 0xf0, 0xd0, 0xdb, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x27, 0x09, 0xca, 0xfe, 0x04, 0xeb, 0xa9, 0x00, 0x6c, 0x6a, 0x9d, 0x1d, 0xea, 0x55, 0xc1, 0x61, 0x6b, 0xfe, 0x2a, 0x2b, 0x8f, 0x0f, 0xf9, 0xa8,
-    0xca, 0xca, 0xf7, 0x03, 0x74, 0xfb, 0x1f, 0x39, 0xe3, 0xbe, 0xf8, 0x1c, 0xbf, 0xeb, 0xef, 0x17, 0xb7, 0x22, 0x82, 0x68, 0xa0, 0xa2, 0xa2, 0x9d, 0x34, 0x88, 0xc7, 0x52, 0x56, 0x5c, 0x6c, 0x96, 0x5c, 0xbd, 0x65, 0x06, 0xec, 0x24,
-    0x39, 0x7c, 0xc8, 0xa5, 0xd9, 0xd1, 0x52, 0x85, 0xa8, 0x7f, 0x00, 0x02, 0x04, 0x54, 0x11, 0x35, 0x9b, 0x27, 0x09, 0x06, 0x2a, 0x02, 0x6e, 0xa0, 0xd4, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x93, 0x27, 0x09
+	0x01, 0x00, 0x00, 0x00, 0x00, 0x08, 0xea, 0xc9, 0x0a, 0x00, 0x00, 0x01, 0x7e, 0xe9, 0x57, 0x60, 0xcd, 0xb8, 0xb3, 0x88, 0xa4, 0x69, 0x22, 0x14, 0x91, 0xaa, 0x9a, 0xcd, 0x66, 0xcc, 0x76, 0x4c, 0xde, 0xfd, 0x56, 0x03, 0x9f, 0x10,
+	0x67, 0xae, 0x15, 0xe6, 0x9c, 0x6f, 0xb4, 0x2d, 0x7b, 0x55, 0x33, 0x0e, 0x3f, 0xda, 0xac, 0x52, 0x9c, 0x07, 0x92, 0xfd, 0x73, 0x40, 0xa6, 0xaa, 0x21, 0xab, 0xa8, 0xa4, 0x89, 0xfd, 0xae, 0xa4, 0x4a, 0x39, 0xbf, 0x2d, 0x00, 0x65,
+	0x9a, 0xc9, 0xc8, 0x18, 0xeb, 0x36, 0x00, 0x92, 0x76, 0x37, 0xef, 0x4d, 0x14, 0x04, 0xa4, 0x4d, 0x54, 0x46, 0x84, 0x85, 0x13, 0x79, 0x75, 0x1f, 0xaa, 0x79, 0xb4, 0xc4, 0xea, 0x85, 0x04, 0x01, 0x75, 0xea, 0x06, 0x58, 0x60, 0x48,
+	0x24, 0x02, 0xe1, 0xeb, 0x34, 0x20, 0x52, 0x00, 0x0e, 0x62, 0x90, 0x06, 0x1a, 0x9b, 0xe0, 0xcd, 0x29, 0x3c, 0x8b, 0x55, 0xf1, 0xc3, 0xd2, 0x52, 0x48, 0x08, 0xaf, 0xc5, 0x49, 0x22, 0x08, 0x0e, 0x35, 0x39, 0xa7, 0x5a, 0xdd, 0xc3,
+	0xce, 0xf0, 0xf6, 0xad, 0x26, 0x0d, 0x58, 0x82, 0x93, 0xbb, 0x77, 0x86, 0xe7, 0x1e, 0xfa, 0x4b, 0x90, 0x57, 0xda, 0xd9, 0x86, 0x7a, 0xfe, 0x12, 0xdd, 0x04, 0xca, 0xfe, 0x9e, 0xfe, 0xb9, 0x00, 0xcc, 0xde, 0xf7, 0x6b, 0xc7, 0xb9,
+	0x7d, 0xed, 0x90, 0x4e, 0xab, 0xc5, 0xdf, 0x09, 0x88, 0x6d, 0x9c, 0x15, 0x14, 0xa6, 0x10, 0x03, 0x6c, 0xb9, 0x13, 0x9c, 0xc2, 0x14, 0x00, 0x1a, 0x29, 0x58, 0x97, 0x8e, 0xfc, 0xec, 0x15, 0x71, 0x2d, 0xd3, 0x94, 0x8c, 0x6e, 0x6b,
+	0x3a, 0x8e, 0x89, 0x3d, 0xf0, 0x1f, 0xf4, 0x93, 0xd1, 0xf8, 0xd9, 0x80, 0x6a, 0x86, 0x0c, 0x54, 0x20, 0x57, 0x1b, 0xf0, 0x00, 0x02, 0x04, 0x68, 0xc2, 0x08, 0x86, 0x27, 0x09, 0x06, 0x26, 0x05, 0x98, 0x80, 0x02, 0x00, 0x12, 0x00,
+	0x00, 0x30, 0x05, 0x71, 0x0e, 0x34, 0x00, 0x51, 0x27, 0x09, 0x77, 0x8c, 0xde, 0x71, 0x90, 0x00, 0x3f, 0x66, 0x81, 0xa9, 0x9e, 0x5a, 0xd1, 0x89, 0x5e, 0x9f, 0xba, 0x33, 0xe6, 0x21, 0x2d, 0x44, 0x54, 0xe1, 0x68, 0xbc, 0xec, 0x71,
+	0x12, 0x10, 0x1b, 0xf0, 0x00, 0x95, 0x6e, 0xd8, 0xe9, 0x2e, 0x42, 0x89, 0x2c, 0xb6, 0xf2, 0xec, 0x41, 0x08, 0x81, 0xa8, 0x4a, 0xb1, 0x9d, 0xa5, 0x0e, 0x12, 0x87, 0xba, 0x3d, 0x92, 0x6c, 0x3a, 0x1f, 0x75, 0x5c, 0xcc, 0xf2, 0x99,
+	0xa1, 0x20, 0x70, 0x55, 0x00, 0x02, 0x04, 0x67, 0xc3, 0x67, 0x42, 0x27, 0x09, 0x06, 0x26, 0x05, 0x98, 0x80, 0x04, 0x00, 0x00, 0xc3, 0x02, 0x54, 0xf2, 0xbc, 0xa1, 0xf7, 0x00, 0x19, 0x27, 0x09, 0x62, 0xf8, 0x65, 0xae, 0x71, 0x00,
+	0xe2, 0x07, 0x6c, 0x57, 0xde, 0x87, 0x0e, 0x62, 0x88, 0xd7, 0xd5, 0xe7, 0x40, 0x44, 0x08, 0xb1, 0x54, 0x5e, 0xfc, 0xa3, 0x7d, 0x67, 0xf7, 0x7b, 0x87, 0xe9, 0xe5, 0x41, 0x68, 0xc2, 0x5d, 0x3e, 0xf1, 0xa9, 0xab, 0xf2, 0x90, 0x5e,
+	0xa5, 0xe7, 0x85, 0xc0, 0x1d, 0xff, 0x23, 0x88, 0x7a, 0xd4, 0x23, 0x2d, 0x95, 0xc7, 0xa8, 0xfd, 0x2c, 0x27, 0x11, 0x1a, 0x72, 0xbd, 0x15, 0x93, 0x22, 0xdc, 0x00, 0x02, 0x04, 0x32, 0x07, 0xfc, 0x8a, 0x27, 0x09, 0x06, 0x20, 0x01,
+	0x49, 0xf0, 0xd0, 0xdb, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x27, 0x09, 0xca, 0xfe, 0x04, 0xeb, 0xa9, 0x00, 0x6c, 0x6a, 0x9d, 0x1d, 0xea, 0x55, 0xc1, 0x61, 0x6b, 0xfe, 0x2a, 0x2b, 0x8f, 0x0f, 0xf9, 0xa8,
+	0xca, 0xca, 0xf7, 0x03, 0x74, 0xfb, 0x1f, 0x39, 0xe3, 0xbe, 0xf8, 0x1c, 0xbf, 0xeb, 0xef, 0x17, 0xb7, 0x22, 0x82, 0x68, 0xa0, 0xa2, 0xa2, 0x9d, 0x34, 0x88, 0xc7, 0x52, 0x56, 0x5c, 0x6c, 0x96, 0x5c, 0xbd, 0x65, 0x06, 0xec, 0x24,
+	0x39, 0x7c, 0xc8, 0xa5, 0xd9, 0xd1, 0x52, 0x85, 0xa8, 0x7f, 0x00, 0x02, 0x04, 0x54, 0x11, 0x35, 0x9b, 0x27, 0x09, 0x06, 0x2a, 0x02, 0x6e, 0xa0, 0xd4, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x93, 0x27, 0x09
 };
 
 Topology::Topology(const RuntimeEnvironment* renv, void* tPtr) : RR(renv), _numConfiguredPhysicalPaths(0), _amUpstream(false)
 {
-    uint8_t tmp[ZT_WORLD_MAX_SERIALIZED_LENGTH];
-    uint64_t idtmp[2];
-    idtmp[0] = 0;
-    idtmp[1] = 0;
-    int n = RR->node->stateObjectGet(tPtr, ZT_STATE_OBJECT_PLANET, idtmp, tmp, sizeof(tmp));
-    if (n > 0) {
-        try {
-            World cachedPlanet;
-            cachedPlanet.deserialize(Buffer<ZT_WORLD_MAX_SERIALIZED_LENGTH>(tmp, (unsigned int)n), 0);
-            addWorld(tPtr, cachedPlanet, false);
-        }
-        catch (...) {
-        }   // ignore invalid cached planets
-    }
-
-    World defaultPlanet;
-    {
-        Buffer<ZT_DEFAULT_WORLD_LENGTH> wtmp(ZT_DEFAULT_WORLD, ZT_DEFAULT_WORLD_LENGTH);
-        defaultPlanet.deserialize(wtmp, 0);   // throws on error, which would indicate a bad static variable up top
-    }
-    addWorld(tPtr, defaultPlanet, false);
+	uint8_t tmp[ZT_WORLD_MAX_SERIALIZED_LENGTH];
+	uint64_t idtmp[2];
+	idtmp[0] = 0;
+	idtmp[1] = 0;
+	int n = RR->node->stateObjectGet(tPtr, ZT_STATE_OBJECT_PLANET, idtmp, tmp, sizeof(tmp));
+	if (n > 0) {
+		try {
+			World cachedPlanet;
+			cachedPlanet.deserialize(Buffer<ZT_WORLD_MAX_SERIALIZED_LENGTH>(tmp, (unsigned int)n), 0);
+			addWorld(tPtr, cachedPlanet, false);
+		}
+		catch (...) {
+		}	// ignore invalid cached planets
+	}
+
+	World defaultPlanet;
+	{
+		Buffer<ZT_DEFAULT_WORLD_LENGTH> wtmp(ZT_DEFAULT_WORLD, ZT_DEFAULT_WORLD_LENGTH);
+		defaultPlanet.deserialize(wtmp, 0);	  // throws on error, which would indicate a bad static variable up top
+	}
+	addWorld(tPtr, defaultPlanet, false);
 }
 
 Topology::~Topology()
 {
-    Hashtable<Address, SharedPtr<Peer> >::Iterator i(_peers);
-    Address* a = (Address*)0;
-    SharedPtr<Peer>* p = (SharedPtr<Peer>*)0;
-    while (i.next(a, p)) {
-        _savePeer((void*)0, *p);
-    }
+	Hashtable<Address, SharedPtr<Peer> >::Iterator i(_peers);
+	Address* a = (Address*)0;
+	SharedPtr<Peer>* p = (SharedPtr<Peer>*)0;
+	while (i.next(a, p)) {
+		_savePeer((void*)0, *p);
+	}
 }
 
 SharedPtr<Peer> Topology::addPeer(void* tPtr, const SharedPtr<Peer>& peer)
 {
-    SharedPtr<Peer> np;
-    {
-        Mutex::Lock _l(_peers_m);
-        SharedPtr<Peer>& hp = _peers[peer->address()];
-        if (! hp) {
-            hp = peer;
-        }
-        np = hp;
-    }
-    return np;
+	SharedPtr<Peer> np;
+	{
+		Mutex::Lock _l(_peers_m);
+		SharedPtr<Peer>& hp = _peers[peer->address()];
+		if (! hp) {
+			hp = peer;
+		}
+		np = hp;
+	}
+	return np;
 }
 
 SharedPtr<Peer> Topology::getPeer(void* tPtr, const Address& zta)
 {
-    if (zta == RR->identity.address()) {
-        return SharedPtr<Peer>();
-    }
-
-    {
-        Mutex::Lock _l(_peers_m);
-        const SharedPtr<Peer>* const ap = _peers.get(zta);
-        if (ap) {
-            return *ap;
-        }
-    }
-
-    try {
-        Buffer<ZT_PEER_MAX_SERIALIZED_STATE_SIZE> buf;
-        uint64_t idbuf[2];
-        idbuf[0] = zta.toInt();
-        idbuf[1] = 0;
-        int len = RR->node->stateObjectGet(tPtr, ZT_STATE_OBJECT_PEER, idbuf, buf.unsafeData(), ZT_PEER_MAX_SERIALIZED_STATE_SIZE);
-        if (len > 0) {
-            buf.setSize(len);
-            Mutex::Lock _l(_peers_m);
-            SharedPtr<Peer>& ap = _peers[zta];
-            if (ap) {
-                return ap;
-            }
-            ap = Peer::deserializeFromCache(RR->node->now(), tPtr, buf, RR);
-            if (! ap) {
-                _peers.erase(zta);
-            }
-            return SharedPtr<Peer>();
-        }
-    }
-    catch (...) {
-    }   // ignore invalid identities or other strange failures
-
-    return SharedPtr<Peer>();
+	if (zta == RR->identity.address()) {
+		return SharedPtr<Peer>();
+	}
+
+	{
+		Mutex::Lock _l(_peers_m);
+		const SharedPtr<Peer>* const ap = _peers.get(zta);
+		if (ap) {
+			return *ap;
+		}
+	}
+
+	try {
+		Buffer<ZT_PEER_MAX_SERIALIZED_STATE_SIZE> buf;
+		uint64_t idbuf[2];
+		idbuf[0] = zta.toInt();
+		idbuf[1] = 0;
+		int len = RR->node->stateObjectGet(tPtr, ZT_STATE_OBJECT_PEER, idbuf, buf.unsafeData(), ZT_PEER_MAX_SERIALIZED_STATE_SIZE);
+		if (len > 0) {
+			buf.setSize(len);
+			Mutex::Lock _l(_peers_m);
+			SharedPtr<Peer>& ap = _peers[zta];
+			if (ap) {
+				return ap;
+			}
+			ap = Peer::deserializeFromCache(RR->node->now(), tPtr, buf, RR);
+			if (! ap) {
+				_peers.erase(zta);
+			}
+			return SharedPtr<Peer>();
+		}
+	}
+	catch (...) {
+	}	// ignore invalid identities or other strange failures
+
+	return SharedPtr<Peer>();
 }
 
 Identity Topology::getIdentity(void* tPtr, const Address& zta)
 {
-    if (zta == RR->identity.address()) {
-        return RR->identity;
-    }
-    else {
-        Mutex::Lock _l(_peers_m);
-        const SharedPtr<Peer>* const ap = _peers.get(zta);
-        if (ap) {
-            return (*ap)->identity();
-        }
-    }
-    return Identity();
+	if (zta == RR->identity.address()) {
+		return RR->identity;
+	}
+	else {
+		Mutex::Lock _l(_peers_m);
+		const SharedPtr<Peer>* const ap = _peers.get(zta);
+		if (ap) {
+			return (*ap)->identity();
+		}
+	}
+	return Identity();
 }
 
 SharedPtr<Peer> Topology::getUpstreamPeer()
 {
-    const int64_t now = RR->node->now();
-    unsigned int bestq = ~((unsigned int)0);
-    const SharedPtr<Peer>* best = (const SharedPtr<Peer>*)0;
-
-    Mutex::Lock _l2(_peers_m);
-    Mutex::Lock _l1(_upstreams_m);
-
-    for (std::vector<Address>::const_iterator a(_upstreamAddresses.begin()); a != _upstreamAddresses.end(); ++a) {
-        const SharedPtr<Peer>* p = _peers.get(*a);
-        if (p) {
-            const unsigned int q = (*p)->relayQuality(now);
-            if (q <= bestq) {
-                bestq = q;
-                best = p;
-            }
-        }
-    }
-
-    if (! best) {
-        return SharedPtr<Peer>();
-    }
-    return *best;
+	const int64_t now = RR->node->now();
+	unsigned int bestq = ~((unsigned int)0);
+	const SharedPtr<Peer>* best = (const SharedPtr<Peer>*)0;
+
+	Mutex::Lock _l2(_peers_m);
+	Mutex::Lock _l1(_upstreams_m);
+
+	for (std::vector<Address>::const_iterator a(_upstreamAddresses.begin()); a != _upstreamAddresses.end(); ++a) {
+		const SharedPtr<Peer>* p = _peers.get(*a);
+		if (p) {
+			const unsigned int q = (*p)->relayQuality(now);
+			if (q <= bestq) {
+				bestq = q;
+				best = p;
+			}
+		}
+	}
+
+	if (! best) {
+		return SharedPtr<Peer>();
+	}
+	return *best;
 }
 
 bool Topology::isUpstream(const Identity& id) const
 {
-    Mutex::Lock _l(_upstreams_m);
-    return (std::find(_upstreamAddresses.begin(), _upstreamAddresses.end(), id.address()) != _upstreamAddresses.end());
+	Mutex::Lock _l(_upstreams_m);
+	return (std::find(_upstreamAddresses.begin(), _upstreamAddresses.end(), id.address()) != _upstreamAddresses.end());
 }
 
 bool Topology::shouldAcceptWorldUpdateFrom(const Address& addr) const
 {
-    Mutex::Lock _l(_upstreams_m);
-    if (std::find(_upstreamAddresses.begin(), _upstreamAddresses.end(), addr) != _upstreamAddresses.end()) {
-        return true;
-    }
-    for (std::vector<std::pair<uint64_t, Address> >::const_iterator s(_moonSeeds.begin()); s != _moonSeeds.end(); ++s) {
-        if (s->second == addr) {
-            return true;
-        }
-    }
-    return false;
+	Mutex::Lock _l(_upstreams_m);
+	if (std::find(_upstreamAddresses.begin(), _upstreamAddresses.end(), addr) != _upstreamAddresses.end()) {
+		return true;
+	}
+	for (std::vector<std::pair<uint64_t, Address> >::const_iterator s(_moonSeeds.begin()); s != _moonSeeds.end(); ++s) {
+		if (s->second == addr) {
+			return true;
+		}
+	}
+	return false;
 }
 
 ZT_PeerRole Topology::role(const Address& ztaddr) const
 {
-    Mutex::Lock _l(_upstreams_m);
-    if (std::find(_upstreamAddresses.begin(), _upstreamAddresses.end(), ztaddr) != _upstreamAddresses.end()) {
-        for (std::vector<World::Root>::const_iterator i(_planet.roots().begin()); i != _planet.roots().end(); ++i) {
-            if (i->identity.address() == ztaddr) {
-                return ZT_PEER_ROLE_PLANET;
-            }
-        }
-        return ZT_PEER_ROLE_MOON;
-    }
-    return ZT_PEER_ROLE_LEAF;
+	Mutex::Lock _l(_upstreams_m);
+	if (std::find(_upstreamAddresses.begin(), _upstreamAddresses.end(), ztaddr) != _upstreamAddresses.end()) {
+		for (std::vector<World::Root>::const_iterator i(_planet.roots().begin()); i != _planet.roots().end(); ++i) {
+			if (i->identity.address() == ztaddr) {
+				return ZT_PEER_ROLE_PLANET;
+			}
+		}
+		return ZT_PEER_ROLE_MOON;
+	}
+	return ZT_PEER_ROLE_LEAF;
 }
 
 bool Topology::isProhibitedEndpoint(const Address& ztaddr, const InetAddress& ipaddr) const
 {
-    Mutex::Lock _l(_upstreams_m);
-
-    // For roots the only permitted addresses are those defined. This adds just a little
-    // bit of extra security against spoofing, replaying, etc.
-    if (std::find(_upstreamAddresses.begin(), _upstreamAddresses.end(), ztaddr) != _upstreamAddresses.end()) {
-        for (std::vector<World::Root>::const_iterator r(_planet.roots().begin()); r != _planet.roots().end(); ++r) {
-            if (r->identity.address() == ztaddr) {
-                if (r->stableEndpoints.empty()) {
-                    return false;   // no stable endpoints specified, so allow dynamic paths
-                }
-                for (std::vector<InetAddress>::const_iterator e(r->stableEndpoints.begin()); e != r->stableEndpoints.end(); ++e) {
-                    if (ipaddr.ipsEqual(*e)) {
-                        return false;
-                    }
-                }
-            }
-        }
-        for (std::vector<World>::const_iterator m(_moons.begin()); m != _moons.end(); ++m) {
-            for (std::vector<World::Root>::const_iterator r(m->roots().begin()); r != m->roots().end(); ++r) {
-                if (r->identity.address() == ztaddr) {
-                    if (r->stableEndpoints.empty()) {
-                        return false;   // no stable endpoints specified, so allow dynamic paths
-                    }
-                    for (std::vector<InetAddress>::const_iterator e(r->stableEndpoints.begin()); e != r->stableEndpoints.end(); ++e) {
-                        if (ipaddr.ipsEqual(*e)) {
-                            return false;
-                        }
-                    }
-                }
-            }
-        }
-        return true;
-    }
-
-    return false;
+	Mutex::Lock _l(_upstreams_m);
+
+	// For roots the only permitted addresses are those defined. This adds just a little
+	// bit of extra security against spoofing, replaying, etc.
+	if (std::find(_upstreamAddresses.begin(), _upstreamAddresses.end(), ztaddr) != _upstreamAddresses.end()) {
+		for (std::vector<World::Root>::const_iterator r(_planet.roots().begin()); r != _planet.roots().end(); ++r) {
+			if (r->identity.address() == ztaddr) {
+				if (r->stableEndpoints.empty()) {
+					return false;	// no stable endpoints specified, so allow dynamic paths
+				}
+				for (std::vector<InetAddress>::const_iterator e(r->stableEndpoints.begin()); e != r->stableEndpoints.end(); ++e) {
+					if (ipaddr.ipsEqual(*e)) {
+						return false;
+					}
+				}
+			}
+		}
+		for (std::vector<World>::const_iterator m(_moons.begin()); m != _moons.end(); ++m) {
+			for (std::vector<World::Root>::const_iterator r(m->roots().begin()); r != m->roots().end(); ++r) {
+				if (r->identity.address() == ztaddr) {
+					if (r->stableEndpoints.empty()) {
+						return false;	// no stable endpoints specified, so allow dynamic paths
+					}
+					for (std::vector<InetAddress>::const_iterator e(r->stableEndpoints.begin()); e != r->stableEndpoints.end(); ++e) {
+						if (ipaddr.ipsEqual(*e)) {
+							return false;
+						}
+					}
+				}
+			}
+		}
+		return true;
+	}
+
+	return false;
 }
 
 bool Topology::addWorld(void* tPtr, const World& newWorld, bool alwaysAcceptNew)
 {
-    if ((newWorld.type() != World::TYPE_PLANET) && (newWorld.type() != World::TYPE_MOON)) {
-        return false;
-    }
-
-    Mutex::Lock _l2(_peers_m);
-    Mutex::Lock _l1(_upstreams_m);
-
-    World* existing = (World*)0;
-    switch (newWorld.type()) {
-        case World::TYPE_PLANET:
-            existing = &_planet;
-            break;
-        case World::TYPE_MOON:
-            for (std::vector<World>::iterator m(_moons.begin()); m != _moons.end(); ++m) {
-                if (m->id() == newWorld.id()) {
-                    existing = &(*m);
-                    break;
-                }
-            }
-            break;
-        default:
-            return false;
-    }
-
-    if (existing) {
-        if (existing->shouldBeReplacedBy(newWorld)) {
-            *existing = newWorld;
-        }
-        else {
-            return false;
-        }
-    }
-    else if (newWorld.type() == World::TYPE_MOON) {
-        if (alwaysAcceptNew) {
-            _moons.push_back(newWorld);
-            existing = &(_moons.back());
-        }
-        else {
-            for (std::vector<std::pair<uint64_t, Address> >::iterator m(_moonSeeds.begin()); m != _moonSeeds.end(); ++m) {
-                if (m->first == newWorld.id()) {
-                    for (std::vector<World::Root>::const_iterator r(newWorld.roots().begin()); r != newWorld.roots().end(); ++r) {
-                        if (r->identity.address() == m->second) {
-                            _moonSeeds.erase(m);
-                            _moons.push_back(newWorld);
-                            existing = &(_moons.back());
-                            break;
-                        }
-                    }
-                    if (existing) {
-                        break;
-                    }
-                }
-            }
-        }
-        if (! existing) {
-            return false;
-        }
-    }
-    else {
-        return false;
-    }
-
-    try {
-        Buffer<ZT_WORLD_MAX_SERIALIZED_LENGTH> sbuf;
-        existing->serialize(sbuf, false);
-        uint64_t idtmp[2];
-        idtmp[0] = existing->id();
-        idtmp[1] = 0;
-        RR->node->stateObjectPut(tPtr, (existing->type() == World::TYPE_PLANET) ? ZT_STATE_OBJECT_PLANET : ZT_STATE_OBJECT_MOON, idtmp, sbuf.data(), sbuf.size());
-    }
-    catch (...) {
-    }
-
-    _memoizeUpstreams(tPtr);
-
-    return true;
+	if ((newWorld.type() != World::TYPE_PLANET) && (newWorld.type() != World::TYPE_MOON)) {
+		return false;
+	}
+
+	Mutex::Lock _l2(_peers_m);
+	Mutex::Lock _l1(_upstreams_m);
+
+	World* existing = (World*)0;
+	switch (newWorld.type()) {
+		case World::TYPE_PLANET:
+			existing = &_planet;
+			break;
+		case World::TYPE_MOON:
+			for (std::vector<World>::iterator m(_moons.begin()); m != _moons.end(); ++m) {
+				if (m->id() == newWorld.id()) {
+					existing = &(*m);
+					break;
+				}
+			}
+			break;
+		default:
+			return false;
+	}
+
+	if (existing) {
+		if (existing->shouldBeReplacedBy(newWorld)) {
+			*existing = newWorld;
+		}
+		else {
+			return false;
+		}
+	}
+	else if (newWorld.type() == World::TYPE_MOON) {
+		if (alwaysAcceptNew) {
+			_moons.push_back(newWorld);
+			existing = &(_moons.back());
+		}
+		else {
+			for (std::vector<std::pair<uint64_t, Address> >::iterator m(_moonSeeds.begin()); m != _moonSeeds.end(); ++m) {
+				if (m->first == newWorld.id()) {
+					for (std::vector<World::Root>::const_iterator r(newWorld.roots().begin()); r != newWorld.roots().end(); ++r) {
+						if (r->identity.address() == m->second) {
+							_moonSeeds.erase(m);
+							_moons.push_back(newWorld);
+							existing = &(_moons.back());
+							break;
+						}
+					}
+					if (existing) {
+						break;
+					}
+				}
+			}
+		}
+		if (! existing) {
+			return false;
+		}
+	}
+	else {
+		return false;
+	}
+
+	try {
+		Buffer<ZT_WORLD_MAX_SERIALIZED_LENGTH> sbuf;
+		existing->serialize(sbuf, false);
+		uint64_t idtmp[2];
+		idtmp[0] = existing->id();
+		idtmp[1] = 0;
+		RR->node->stateObjectPut(tPtr, (existing->type() == World::TYPE_PLANET) ? ZT_STATE_OBJECT_PLANET : ZT_STATE_OBJECT_MOON, idtmp, sbuf.data(), sbuf.size());
+	}
+	catch (...) {
+	}
+
+	_memoizeUpstreams(tPtr);
+
+	return true;
 }
 
 void Topology::addMoon(void* tPtr, const uint64_t id, const Address& seed)
 {
-    char tmp[ZT_WORLD_MAX_SERIALIZED_LENGTH];
-    uint64_t idtmp[2];
-    idtmp[0] = id;
-    idtmp[1] = 0;
-    int n = RR->node->stateObjectGet(tPtr, ZT_STATE_OBJECT_MOON, idtmp, tmp, sizeof(tmp));
-    if (n > 0) {
-        try {
-            World w;
-            w.deserialize(Buffer<ZT_WORLD_MAX_SERIALIZED_LENGTH>(tmp, (unsigned int)n));
-            if ((w.type() == World::TYPE_MOON) && (w.id() == id)) {
-                addWorld(tPtr, w, true);
-                return;
-            }
-        }
-        catch (...) {
-        }
-    }
-
-    if (seed) {
-        Mutex::Lock _l(_upstreams_m);
-        if (std::find(_moonSeeds.begin(), _moonSeeds.end(), std::pair<uint64_t, Address>(id, seed)) == _moonSeeds.end()) {
-            _moonSeeds.push_back(std::pair<uint64_t, Address>(id, seed));
-        }
-    }
+	char tmp[ZT_WORLD_MAX_SERIALIZED_LENGTH];
+	uint64_t idtmp[2];
+	idtmp[0] = id;
+	idtmp[1] = 0;
+	int n = RR->node->stateObjectGet(tPtr, ZT_STATE_OBJECT_MOON, idtmp, tmp, sizeof(tmp));
+	if (n > 0) {
+		try {
+			World w;
+			w.deserialize(Buffer<ZT_WORLD_MAX_SERIALIZED_LENGTH>(tmp, (unsigned int)n));
+			if ((w.type() == World::TYPE_MOON) && (w.id() == id)) {
+				addWorld(tPtr, w, true);
+				return;
+			}
+		}
+		catch (...) {
+		}
+	}
+
+	if (seed) {
+		Mutex::Lock _l(_upstreams_m);
+		if (std::find(_moonSeeds.begin(), _moonSeeds.end(), std::pair<uint64_t, Address>(id, seed)) == _moonSeeds.end()) {
+			_moonSeeds.push_back(std::pair<uint64_t, Address>(id, seed));
+		}
+	}
 }
 
 void Topology::removeMoon(void* tPtr, const uint64_t id)
 {
-    Mutex::Lock _l2(_peers_m);
-    Mutex::Lock _l1(_upstreams_m);
-
-    std::vector<World> nm;
-    for (std::vector<World>::const_iterator m(_moons.begin()); m != _moons.end(); ++m) {
-        if (m->id() != id) {
-            nm.push_back(*m);
-        }
-        else {
-            uint64_t idtmp[2];
-            idtmp[0] = id;
-            idtmp[1] = 0;
-            RR->node->stateObjectDelete(tPtr, ZT_STATE_OBJECT_MOON, idtmp);
-        }
-    }
-    _moons.swap(nm);
-
-    std::vector<std::pair<uint64_t, Address> > cm;
-    for (std::vector<std::pair<uint64_t, Address> >::const_iterator m(_moonSeeds.begin()); m != _moonSeeds.end(); ++m) {
-        if (m->first != id) {
-            cm.push_back(*m);
-        }
-    }
-    _moonSeeds.swap(cm);
-
-    _memoizeUpstreams(tPtr);
+	Mutex::Lock _l2(_peers_m);
+	Mutex::Lock _l1(_upstreams_m);
+
+	std::vector<World> nm;
+	for (std::vector<World>::const_iterator m(_moons.begin()); m != _moons.end(); ++m) {
+		if (m->id() != id) {
+			nm.push_back(*m);
+		}
+		else {
+			uint64_t idtmp[2];
+			idtmp[0] = id;
+			idtmp[1] = 0;
+			RR->node->stateObjectDelete(tPtr, ZT_STATE_OBJECT_MOON, idtmp);
+		}
+	}
+	_moons.swap(nm);
+
+	std::vector<std::pair<uint64_t, Address> > cm;
+	for (std::vector<std::pair<uint64_t, Address> >::const_iterator m(_moonSeeds.begin()); m != _moonSeeds.end(); ++m) {
+		if (m->first != id) {
+			cm.push_back(*m);
+		}
+	}
+	_moonSeeds.swap(cm);
+
+	_memoizeUpstreams(tPtr);
 }
 
 void Topology::doPeriodicTasks(void* tPtr, int64_t now)
 {
-    {
-        Mutex::Lock _l1(_peers_m);
-        Mutex::Lock _l2(_upstreams_m);
-        Hashtable<Address, SharedPtr<Peer> >::Iterator i(_peers);
-        Address* a = (Address*)0;
-        SharedPtr<Peer>* p = (SharedPtr<Peer>*)0;
-        while (i.next(a, p)) {
-            if ((! (*p)->isAlive(now)) && (std::find(_upstreamAddresses.begin(), _upstreamAddresses.end(), *a) == _upstreamAddresses.end())) {
-                _savePeer(tPtr, *p);
-                _peers.erase(*a);
-            }
-        }
-    }
-
-    {
-        Mutex::Lock _l(_paths_m);
-        Hashtable<Path::HashKey, SharedPtr<Path> >::Iterator i(_paths);
-        Path::HashKey* k = (Path::HashKey*)0;
-        SharedPtr<Path>* p = (SharedPtr<Path>*)0;
-        while (i.next(k, p)) {
-            if (p->references() <= 1) {
-                _paths.erase(*k);
-            }
-        }
-    }
+	{
+		Mutex::Lock _l1(_peers_m);
+		Mutex::Lock _l2(_upstreams_m);
+		Hashtable<Address, SharedPtr<Peer> >::Iterator i(_peers);
+		Address* a = (Address*)0;
+		SharedPtr<Peer>* p = (SharedPtr<Peer>*)0;
+		while (i.next(a, p)) {
+			if ((! (*p)->isAlive(now)) && (std::find(_upstreamAddresses.begin(), _upstreamAddresses.end(), *a) == _upstreamAddresses.end())) {
+				_savePeer(tPtr, *p);
+				_peers.erase(*a);
+			}
+		}
+	}
+
+	{
+		Mutex::Lock _l(_paths_m);
+		Hashtable<Path::HashKey, SharedPtr<Path> >::Iterator i(_paths);
+		Path::HashKey* k = (Path::HashKey*)0;
+		SharedPtr<Path>* p = (SharedPtr<Path>*)0;
+		while (i.next(k, p)) {
+			if (p->references() <= 1) {
+				_paths.erase(*k);
+			}
+		}
+	}
 }
 
 void Topology::_memoizeUpstreams(void* tPtr)
 {
-    // assumes _upstreams_m and _peers_m are locked
-    _upstreamAddresses.clear();
-    _amUpstream = false;
-
-    for (std::vector<World::Root>::const_iterator i(_planet.roots().begin()); i != _planet.roots().end(); ++i) {
-        const Identity& id = i->identity;
-        if (id == RR->identity) {
-            _amUpstream = true;
-        }
-        else if (std::find(_upstreamAddresses.begin(), _upstreamAddresses.end(), id.address()) == _upstreamAddresses.end()) {
-            _upstreamAddresses.push_back(id.address());
-            SharedPtr<Peer>& hp = _peers[id.address()];
-            if (! hp) {
-                hp = new Peer(RR, RR->identity, id);
-            }
-        }
-    }
-
-    for (std::vector<World>::const_iterator m(_moons.begin()); m != _moons.end(); ++m) {
-        for (std::vector<World::Root>::const_iterator i(m->roots().begin()); i != m->roots().end(); ++i) {
-            if (i->identity == RR->identity) {
-                _amUpstream = true;
-            }
-            else if (std::find(_upstreamAddresses.begin(), _upstreamAddresses.end(), i->identity.address()) == _upstreamAddresses.end()) {
-                _upstreamAddresses.push_back(i->identity.address());
-                SharedPtr<Peer>& hp = _peers[i->identity.address()];
-                if (! hp) {
-                    hp = new Peer(RR, RR->identity, i->identity);
-                }
-            }
-        }
-    }
-
-    std::sort(_upstreamAddresses.begin(), _upstreamAddresses.end());
+	// assumes _upstreams_m and _peers_m are locked
+	_upstreamAddresses.clear();
+	_amUpstream = false;
+
+	for (std::vector<World::Root>::const_iterator i(_planet.roots().begin()); i != _planet.roots().end(); ++i) {
+		const Identity& id = i->identity;
+		if (id == RR->identity) {
+			_amUpstream = true;
+		}
+		else if (std::find(_upstreamAddresses.begin(), _upstreamAddresses.end(), id.address()) == _upstreamAddresses.end()) {
+			_upstreamAddresses.push_back(id.address());
+			SharedPtr<Peer>& hp = _peers[id.address()];
+			if (! hp) {
+				hp = new Peer(RR, RR->identity, id);
+			}
+		}
+	}
+
+	for (std::vector<World>::const_iterator m(_moons.begin()); m != _moons.end(); ++m) {
+		for (std::vector<World::Root>::const_iterator i(m->roots().begin()); i != m->roots().end(); ++i) {
+			if (i->identity == RR->identity) {
+				_amUpstream = true;
+			}
+			else if (std::find(_upstreamAddresses.begin(), _upstreamAddresses.end(), i->identity.address()) == _upstreamAddresses.end()) {
+				_upstreamAddresses.push_back(i->identity.address());
+				SharedPtr<Peer>& hp = _peers[i->identity.address()];
+				if (! hp) {
+					hp = new Peer(RR, RR->identity, i->identity);
+				}
+			}
+		}
+	}
+
+	std::sort(_upstreamAddresses.begin(), _upstreamAddresses.end());
 }
 
 void Topology::_savePeer(void* tPtr, const SharedPtr<Peer>& peer)
 {
-    try {
-        Buffer<ZT_PEER_MAX_SERIALIZED_STATE_SIZE> buf;
-        peer->serializeForCache(buf);
-        uint64_t tmpid[2];
-        tmpid[0] = peer->address().toInt();
-        tmpid[1] = 0;
-        RR->node->stateObjectPut(tPtr, ZT_STATE_OBJECT_PEER, tmpid, buf.data(), buf.size());
-    }
-    catch (...) {
-    }   // sanity check, discard invalid entries
+	try {
+		Buffer<ZT_PEER_MAX_SERIALIZED_STATE_SIZE> buf;
+		peer->serializeForCache(buf);
+		uint64_t tmpid[2];
+		tmpid[0] = peer->address().toInt();
+		tmpid[1] = 0;
+		RR->node->stateObjectPut(tPtr, ZT_STATE_OBJECT_PEER, tmpid, buf.data(), buf.size());
+	}
+	catch (...) {
+	}	// sanity check, discard invalid entries
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 417 - 417
node/Topology.hpp

@@ -41,431 +41,431 @@ class RuntimeEnvironment;
  */
 class Topology {
   public:
-    Topology(const RuntimeEnvironment* renv, void* tPtr);
-    ~Topology();
-
-    /**
-     * Add a peer to database
-     *
-     * This will not replace existing peers. In that case the existing peer
-     * record is returned.
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param peer Peer to add
-     * @return New or existing peer (should replace 'peer')
-     */
-    SharedPtr<Peer> addPeer(void* tPtr, const SharedPtr<Peer>& peer);
-
-    /**
-     * Get a peer from its address
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param zta ZeroTier address of peer
-     * @return Peer or NULL if not found
-     */
-    SharedPtr<Peer> getPeer(void* tPtr, const Address& zta);
-
-    /**
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param zta ZeroTier address of peer
-     * @return Identity or NULL identity if not found
-     */
-    Identity getIdentity(void* tPtr, const Address& zta);
-
-    /**
-     * Get a peer only if it is presently in memory (no disk cache)
-     *
-     * This also does not update the lastUsed() time for peers, which means
-     * that it won't prevent them from falling out of RAM. This is currently
-     * used in the Cluster code to update peer info without forcing all peers
-     * across the entire cluster to remain in memory cache.
-     *
-     * @param zta ZeroTier address
-     */
-    inline SharedPtr<Peer> getPeerNoCache(const Address& zta)
-    {
-        Mutex::Lock _l(_peers_m);
-        const SharedPtr<Peer>* const ap = _peers.get(zta);
-        if (ap) {
-            return *ap;
-        }
-        return SharedPtr<Peer>();
-    }
-
-    /**
-     * Get a Path object for a given local and remote physical address, creating if needed
-     *
-     * @param l Local socket
-     * @param r Remote address
-     * @return Pointer to canonicalized Path object
-     */
-    inline SharedPtr<Path> getPath(const int64_t l, const InetAddress& r)
-    {
-        Mutex::Lock _l(_paths_m);
-        SharedPtr<Path>& p = _paths[Path::HashKey(l, r)];
-        if (! p) {
-            p.set(new Path(l, r));
-        }
-        return p;
-    }
-
-    /**
-     * Get the current best upstream peer
-     *
-     * @return Upstream or NULL if none available
-     */
-    SharedPtr<Peer> getUpstreamPeer();
-
-    /**
-     * @param id Identity to check
-     * @return True if this is a root server or a network preferred relay from one of our networks
-     */
-    bool isUpstream(const Identity& id) const;
-
-    /**
-     * @param addr Address to check
-     * @return True if we should accept a world update from this address
-     */
-    bool shouldAcceptWorldUpdateFrom(const Address& addr) const;
-
-    /**
-     * @param ztaddr ZeroTier address
-     * @return Peer role for this device
-     */
-    ZT_PeerRole role(const Address& ztaddr) const;
-
-    /**
-     * Check for prohibited endpoints
-     *
-     * Right now this returns true if the designated ZT address is a root and if
-     * the IP (IP only, not port) does not equal any of the IPs defined in the
-     * current World. This is an extra little security feature in case root keys
-     * get appropriated or something.
-     *
-     * Otherwise it returns false.
-     *
-     * @param ztaddr ZeroTier address
-     * @param ipaddr IP address
-     * @return True if this ZT/IP pair should not be allowed to be used
-     */
-    bool isProhibitedEndpoint(const Address& ztaddr, const InetAddress& ipaddr) const;
-
-    /**
-     * Gets upstreams to contact and their stable endpoints (if known)
-     *
-     * @param eps Hash table to fill with addresses and their stable endpoints
-     */
-    inline void getUpstreamsToContact(Hashtable<Address, std::vector<InetAddress> >& eps) const
-    {
-        Mutex::Lock _l(_upstreams_m);
-        for (std::vector<World::Root>::const_iterator i(_planet.roots().begin()); i != _planet.roots().end(); ++i) {
-            if (i->identity != RR->identity) {
-                std::vector<InetAddress>& ips = eps[i->identity.address()];
-                for (std::vector<InetAddress>::const_iterator j(i->stableEndpoints.begin()); j != i->stableEndpoints.end(); ++j) {
-                    if (std::find(ips.begin(), ips.end(), *j) == ips.end()) {
-                        ips.push_back(*j);
-                    }
-                }
-            }
-        }
-        for (std::vector<World>::const_iterator m(_moons.begin()); m != _moons.end(); ++m) {
-            for (std::vector<World::Root>::const_iterator i(m->roots().begin()); i != m->roots().end(); ++i) {
-                if (i->identity != RR->identity) {
-                    std::vector<InetAddress>& ips = eps[i->identity.address()];
-                    for (std::vector<InetAddress>::const_iterator j(i->stableEndpoints.begin()); j != i->stableEndpoints.end(); ++j) {
-                        if (std::find(ips.begin(), ips.end(), *j) == ips.end()) {
-                            ips.push_back(*j);
-                        }
-                    }
-                }
-            }
-        }
-        for (std::vector<std::pair<uint64_t, Address> >::const_iterator m(_moonSeeds.begin()); m != _moonSeeds.end(); ++m) {
-            eps[m->second];
-        }
-    }
-
-    /**
-     * @return Vector of active upstream addresses (including roots)
-     */
-    inline std::vector<Address> upstreamAddresses() const
-    {
-        Mutex::Lock _l(_upstreams_m);
-        return _upstreamAddresses;
-    }
-
-    /**
-     * @return Current moons
-     */
-    inline std::vector<World> moons() const
-    {
-        Mutex::Lock _l(_upstreams_m);
-        return _moons;
-    }
-
-    /**
-     * @return Moon IDs we are waiting for from seeds
-     */
-    inline std::vector<uint64_t> moonsWanted() const
-    {
-        Mutex::Lock _l(_upstreams_m);
-        std::vector<uint64_t> mw;
-        for (std::vector<std::pair<uint64_t, Address> >::const_iterator s(_moonSeeds.begin()); s != _moonSeeds.end(); ++s) {
-            if (std::find(mw.begin(), mw.end(), s->first) == mw.end()) {
-                mw.push_back(s->first);
-            }
-        }
-        return mw;
-    }
-
-    /**
-     * @return Current planet
-     */
-    inline World planet() const
-    {
-        Mutex::Lock _l(_upstreams_m);
-        return _planet;
-    }
-
-    /**
-     * @return Current planet's world ID
-     */
-    inline uint64_t planetWorldId() const
-    {
-        return _planet.id();   // safe to read without lock, and used from within eachPeer() so don't lock
-    }
-
-    /**
-     * @return Current planet's world timestamp
-     */
-    inline uint64_t planetWorldTimestamp() const
-    {
-        return _planet.timestamp();   // safe to read without lock, and used from within eachPeer() so don't lock
-    }
-
-    /**
-     * Validate new world and update if newer and signature is okay
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param newWorld A new or updated planet or moon to learn
-     * @param alwaysAcceptNew If true, always accept new moons even if we're not waiting for one
-     * @return True if it was valid and newer than current (or totally new for moons)
-     */
-    bool addWorld(void* tPtr, const World& newWorld, bool alwaysAcceptNew);
-
-    /**
-     * Add a moon
-     *
-     * This loads it from moons.d if present, and if not adds it to
-     * a list of moons that we want to contact.
-     *
-     * @param id Moon ID
-     * @param seed If non-NULL, an address of any member of the moon to contact
-     */
-    void addMoon(void* tPtr, const uint64_t id, const Address& seed);
-
-    /**
-     * Remove a moon
-     *
-     * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
-     * @param id Moon's world ID
-     */
-    void removeMoon(void* tPtr, const uint64_t id);
-
-    /**
-     * Clean and flush database
-     */
-    void doPeriodicTasks(void* tPtr, int64_t now);
-
-    /**
-     * @param now Current time
-     * @return Number of peers with active direct paths
-     */
-    inline unsigned long countActive(int64_t now) const
-    {
-        unsigned long cnt = 0;
-        Mutex::Lock _l(_peers_m);
-        Hashtable<Address, SharedPtr<Peer> >::Iterator i(const_cast<Topology*>(this)->_peers);
-        Address* a = (Address*)0;
-        SharedPtr<Peer>* p = (SharedPtr<Peer>*)0;
-        while (i.next(a, p)) {
-            const SharedPtr<Path> pp((*p)->getAppropriatePath(now, false));
-            if (pp) {
-                ++cnt;
-            }
-        }
-        return cnt;
-    }
-
-    /**
-     * Apply a function or function object to all peers
-     *
-     * @param f Function to apply
-     * @tparam F Function or function object type
-     */
-    template <typename F> inline void eachPeer(F f)
-    {
-        Mutex::Lock _l(_peers_m);
-        Hashtable<Address, SharedPtr<Peer> >::Iterator i(_peers);
-        Address* a = (Address*)0;
-        SharedPtr<Peer>* p = (SharedPtr<Peer>*)0;
-        while (i.next(a, p)) {
-            f(*this, *((const SharedPtr<Peer>*)p));
-        }
-    }
-
-    /**
-     * @return All currently active peers by address (unsorted)
-     */
-    inline std::vector<std::pair<Address, SharedPtr<Peer> > > allPeers() const
-    {
-        Mutex::Lock _l(_peers_m);
-        return _peers.entries();
-    }
-
-    /**
-     * @return True if I am a root server in a planet or moon
-     */
-    inline bool amUpstream() const
-    {
-        return _amUpstream;
-    }
-
-    /**
-     * Get info about a path
-     *
-     * The supplied result variables are not modified if no special config info is found.
-     *
-     * @param physicalAddress Physical endpoint address
-     * @param mtu Variable set to MTU
-     * @param trustedPathId Variable set to trusted path ID
-     */
-    inline void getOutboundPathInfo(const InetAddress& physicalAddress, unsigned int& mtu, uint64_t& trustedPathId)
-    {
-        for (unsigned int i = 0, j = _numConfiguredPhysicalPaths; i < j; ++i) {
-            if (_physicalPathConfig[i].first.containsAddress(physicalAddress)) {
-                trustedPathId = _physicalPathConfig[i].second.trustedPathId;
-                mtu = _physicalPathConfig[i].second.mtu;
-                return;
-            }
-        }
-    }
-
-    /**
-     * Get the payload MTU for an outbound physical path (returns default if not configured)
-     *
-     * @param physicalAddress Physical endpoint address
-     * @return MTU
-     */
-    inline unsigned int getOutboundPathMtu(const InetAddress& physicalAddress)
-    {
-        for (unsigned int i = 0, j = _numConfiguredPhysicalPaths; i < j; ++i) {
-            if (_physicalPathConfig[i].first.containsAddress(physicalAddress)) {
-                return _physicalPathConfig[i].second.mtu;
-            }
-        }
-        return ZT_DEFAULT_PHYSMTU;
-    }
-
-    /**
-     * Get the outbound trusted path ID for a physical address, or 0 if none
-     *
-     * @param physicalAddress Physical address to which we are sending the packet
-     * @return Trusted path ID or 0 if none (0 is not a valid trusted path ID)
-     */
-    inline uint64_t getOutboundPathTrust(const InetAddress& physicalAddress)
-    {
-        for (unsigned int i = 0, j = _numConfiguredPhysicalPaths; i < j; ++i) {
-            if (_physicalPathConfig[i].first.containsAddress(physicalAddress)) {
-                return _physicalPathConfig[i].second.trustedPathId;
-            }
-        }
-        return 0;
-    }
-
-    /**
-     * Check whether in incoming trusted path marked packet is valid
-     *
-     * @param physicalAddress Originating physical address
-     * @param trustedPathId Trusted path ID from packet (from MAC field)
-     */
-    inline bool shouldInboundPathBeTrusted(const InetAddress& physicalAddress, const uint64_t trustedPathId)
-    {
-        for (unsigned int i = 0, j = _numConfiguredPhysicalPaths; i < j; ++i) {
-            if ((_physicalPathConfig[i].second.trustedPathId == trustedPathId) && (_physicalPathConfig[i].first.containsAddress(physicalAddress))) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Set or clear physical path configuration (called via Node::setPhysicalPathConfiguration)
-     */
-    inline void setPhysicalPathConfiguration(const struct sockaddr_storage* pathNetwork, const ZT_PhysicalPathConfiguration* pathConfig)
-    {
-        if (! pathNetwork) {
-            _numConfiguredPhysicalPaths = 0;
-        }
-        else {
-            std::map<InetAddress, ZT_PhysicalPathConfiguration> cpaths;
-            for (unsigned int i = 0, j = _numConfiguredPhysicalPaths; i < j; ++i) {
-                cpaths[_physicalPathConfig[i].first] = _physicalPathConfig[i].second;
-            }
-
-            if (pathConfig) {
-                ZT_PhysicalPathConfiguration pc(*pathConfig);
-
-                if (pc.mtu <= 0) {
-                    pc.mtu = ZT_DEFAULT_PHYSMTU;
-                }
-                else if (pc.mtu < ZT_MIN_PHYSMTU) {
-                    pc.mtu = ZT_MIN_PHYSMTU;
-                }
-                else if (pc.mtu > ZT_MAX_PHYSMTU) {
-                    pc.mtu = ZT_MAX_PHYSMTU;
-                }
-
-                cpaths[*(reinterpret_cast<const InetAddress*>(pathNetwork))] = pc;
-            }
-            else {
-                cpaths.erase(*(reinterpret_cast<const InetAddress*>(pathNetwork)));
-            }
-
-            unsigned int cnt = 0;
-            for (std::map<InetAddress, ZT_PhysicalPathConfiguration>::const_iterator i(cpaths.begin()); ((i != cpaths.end()) && (cnt < ZT_MAX_CONFIGURABLE_PATHS)); ++i) {
-                _physicalPathConfig[cnt].first = i->first;
-                _physicalPathConfig[cnt].second = i->second;
-                ++cnt;
-            }
-            _numConfiguredPhysicalPaths = cnt;
-        }
-    }
+	Topology(const RuntimeEnvironment* renv, void* tPtr);
+	~Topology();
+
+	/**
+	 * Add a peer to database
+	 *
+	 * This will not replace existing peers. In that case the existing peer
+	 * record is returned.
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param peer Peer to add
+	 * @return New or existing peer (should replace 'peer')
+	 */
+	SharedPtr<Peer> addPeer(void* tPtr, const SharedPtr<Peer>& peer);
+
+	/**
+	 * Get a peer from its address
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param zta ZeroTier address of peer
+	 * @return Peer or NULL if not found
+	 */
+	SharedPtr<Peer> getPeer(void* tPtr, const Address& zta);
+
+	/**
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param zta ZeroTier address of peer
+	 * @return Identity or NULL identity if not found
+	 */
+	Identity getIdentity(void* tPtr, const Address& zta);
+
+	/**
+	 * Get a peer only if it is presently in memory (no disk cache)
+	 *
+	 * This also does not update the lastUsed() time for peers, which means
+	 * that it won't prevent them from falling out of RAM. This is currently
+	 * used in the Cluster code to update peer info without forcing all peers
+	 * across the entire cluster to remain in memory cache.
+	 *
+	 * @param zta ZeroTier address
+	 */
+	inline SharedPtr<Peer> getPeerNoCache(const Address& zta)
+	{
+		Mutex::Lock _l(_peers_m);
+		const SharedPtr<Peer>* const ap = _peers.get(zta);
+		if (ap) {
+			return *ap;
+		}
+		return SharedPtr<Peer>();
+	}
+
+	/**
+	 * Get a Path object for a given local and remote physical address, creating if needed
+	 *
+	 * @param l Local socket
+	 * @param r Remote address
+	 * @return Pointer to canonicalized Path object
+	 */
+	inline SharedPtr<Path> getPath(const int64_t l, const InetAddress& r)
+	{
+		Mutex::Lock _l(_paths_m);
+		SharedPtr<Path>& p = _paths[Path::HashKey(l, r)];
+		if (! p) {
+			p.set(new Path(l, r));
+		}
+		return p;
+	}
+
+	/**
+	 * Get the current best upstream peer
+	 *
+	 * @return Upstream or NULL if none available
+	 */
+	SharedPtr<Peer> getUpstreamPeer();
+
+	/**
+	 * @param id Identity to check
+	 * @return True if this is a root server or a network preferred relay from one of our networks
+	 */
+	bool isUpstream(const Identity& id) const;
+
+	/**
+	 * @param addr Address to check
+	 * @return True if we should accept a world update from this address
+	 */
+	bool shouldAcceptWorldUpdateFrom(const Address& addr) const;
+
+	/**
+	 * @param ztaddr ZeroTier address
+	 * @return Peer role for this device
+	 */
+	ZT_PeerRole role(const Address& ztaddr) const;
+
+	/**
+	 * Check for prohibited endpoints
+	 *
+	 * Right now this returns true if the designated ZT address is a root and if
+	 * the IP (IP only, not port) does not equal any of the IPs defined in the
+	 * current World. This is an extra little security feature in case root keys
+	 * get appropriated or something.
+	 *
+	 * Otherwise it returns false.
+	 *
+	 * @param ztaddr ZeroTier address
+	 * @param ipaddr IP address
+	 * @return True if this ZT/IP pair should not be allowed to be used
+	 */
+	bool isProhibitedEndpoint(const Address& ztaddr, const InetAddress& ipaddr) const;
+
+	/**
+	 * Gets upstreams to contact and their stable endpoints (if known)
+	 *
+	 * @param eps Hash table to fill with addresses and their stable endpoints
+	 */
+	inline void getUpstreamsToContact(Hashtable<Address, std::vector<InetAddress> >& eps) const
+	{
+		Mutex::Lock _l(_upstreams_m);
+		for (std::vector<World::Root>::const_iterator i(_planet.roots().begin()); i != _planet.roots().end(); ++i) {
+			if (i->identity != RR->identity) {
+				std::vector<InetAddress>& ips = eps[i->identity.address()];
+				for (std::vector<InetAddress>::const_iterator j(i->stableEndpoints.begin()); j != i->stableEndpoints.end(); ++j) {
+					if (std::find(ips.begin(), ips.end(), *j) == ips.end()) {
+						ips.push_back(*j);
+					}
+				}
+			}
+		}
+		for (std::vector<World>::const_iterator m(_moons.begin()); m != _moons.end(); ++m) {
+			for (std::vector<World::Root>::const_iterator i(m->roots().begin()); i != m->roots().end(); ++i) {
+				if (i->identity != RR->identity) {
+					std::vector<InetAddress>& ips = eps[i->identity.address()];
+					for (std::vector<InetAddress>::const_iterator j(i->stableEndpoints.begin()); j != i->stableEndpoints.end(); ++j) {
+						if (std::find(ips.begin(), ips.end(), *j) == ips.end()) {
+							ips.push_back(*j);
+						}
+					}
+				}
+			}
+		}
+		for (std::vector<std::pair<uint64_t, Address> >::const_iterator m(_moonSeeds.begin()); m != _moonSeeds.end(); ++m) {
+			eps[m->second];
+		}
+	}
+
+	/**
+	 * @return Vector of active upstream addresses (including roots)
+	 */
+	inline std::vector<Address> upstreamAddresses() const
+	{
+		Mutex::Lock _l(_upstreams_m);
+		return _upstreamAddresses;
+	}
+
+	/**
+	 * @return Current moons
+	 */
+	inline std::vector<World> moons() const
+	{
+		Mutex::Lock _l(_upstreams_m);
+		return _moons;
+	}
+
+	/**
+	 * @return Moon IDs we are waiting for from seeds
+	 */
+	inline std::vector<uint64_t> moonsWanted() const
+	{
+		Mutex::Lock _l(_upstreams_m);
+		std::vector<uint64_t> mw;
+		for (std::vector<std::pair<uint64_t, Address> >::const_iterator s(_moonSeeds.begin()); s != _moonSeeds.end(); ++s) {
+			if (std::find(mw.begin(), mw.end(), s->first) == mw.end()) {
+				mw.push_back(s->first);
+			}
+		}
+		return mw;
+	}
+
+	/**
+	 * @return Current planet
+	 */
+	inline World planet() const
+	{
+		Mutex::Lock _l(_upstreams_m);
+		return _planet;
+	}
+
+	/**
+	 * @return Current planet's world ID
+	 */
+	inline uint64_t planetWorldId() const
+	{
+		return _planet.id();   // safe to read without lock, and used from within eachPeer() so don't lock
+	}
+
+	/**
+	 * @return Current planet's world timestamp
+	 */
+	inline uint64_t planetWorldTimestamp() const
+	{
+		return _planet.timestamp();	  // safe to read without lock, and used from within eachPeer() so don't lock
+	}
+
+	/**
+	 * Validate new world and update if newer and signature is okay
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param newWorld A new or updated planet or moon to learn
+	 * @param alwaysAcceptNew If true, always accept new moons even if we're not waiting for one
+	 * @return True if it was valid and newer than current (or totally new for moons)
+	 */
+	bool addWorld(void* tPtr, const World& newWorld, bool alwaysAcceptNew);
+
+	/**
+	 * Add a moon
+	 *
+	 * This loads it from moons.d if present, and if not adds it to
+	 * a list of moons that we want to contact.
+	 *
+	 * @param id Moon ID
+	 * @param seed If non-NULL, an address of any member of the moon to contact
+	 */
+	void addMoon(void* tPtr, const uint64_t id, const Address& seed);
+
+	/**
+	 * Remove a moon
+	 *
+	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
+	 * @param id Moon's world ID
+	 */
+	void removeMoon(void* tPtr, const uint64_t id);
+
+	/**
+	 * Clean and flush database
+	 */
+	void doPeriodicTasks(void* tPtr, int64_t now);
+
+	/**
+	 * @param now Current time
+	 * @return Number of peers with active direct paths
+	 */
+	inline unsigned long countActive(int64_t now) const
+	{
+		unsigned long cnt = 0;
+		Mutex::Lock _l(_peers_m);
+		Hashtable<Address, SharedPtr<Peer> >::Iterator i(const_cast<Topology*>(this)->_peers);
+		Address* a = (Address*)0;
+		SharedPtr<Peer>* p = (SharedPtr<Peer>*)0;
+		while (i.next(a, p)) {
+			const SharedPtr<Path> pp((*p)->getAppropriatePath(now, false));
+			if (pp) {
+				++cnt;
+			}
+		}
+		return cnt;
+	}
+
+	/**
+	 * Apply a function or function object to all peers
+	 *
+	 * @param f Function to apply
+	 * @tparam F Function or function object type
+	 */
+	template <typename F> inline void eachPeer(F f)
+	{
+		Mutex::Lock _l(_peers_m);
+		Hashtable<Address, SharedPtr<Peer> >::Iterator i(_peers);
+		Address* a = (Address*)0;
+		SharedPtr<Peer>* p = (SharedPtr<Peer>*)0;
+		while (i.next(a, p)) {
+			f(*this, *((const SharedPtr<Peer>*)p));
+		}
+	}
+
+	/**
+	 * @return All currently active peers by address (unsorted)
+	 */
+	inline std::vector<std::pair<Address, SharedPtr<Peer> > > allPeers() const
+	{
+		Mutex::Lock _l(_peers_m);
+		return _peers.entries();
+	}
+
+	/**
+	 * @return True if I am a root server in a planet or moon
+	 */
+	inline bool amUpstream() const
+	{
+		return _amUpstream;
+	}
+
+	/**
+	 * Get info about a path
+	 *
+	 * The supplied result variables are not modified if no special config info is found.
+	 *
+	 * @param physicalAddress Physical endpoint address
+	 * @param mtu Variable set to MTU
+	 * @param trustedPathId Variable set to trusted path ID
+	 */
+	inline void getOutboundPathInfo(const InetAddress& physicalAddress, unsigned int& mtu, uint64_t& trustedPathId)
+	{
+		for (unsigned int i = 0, j = _numConfiguredPhysicalPaths; i < j; ++i) {
+			if (_physicalPathConfig[i].first.containsAddress(physicalAddress)) {
+				trustedPathId = _physicalPathConfig[i].second.trustedPathId;
+				mtu = _physicalPathConfig[i].second.mtu;
+				return;
+			}
+		}
+	}
+
+	/**
+	 * Get the payload MTU for an outbound physical path (returns default if not configured)
+	 *
+	 * @param physicalAddress Physical endpoint address
+	 * @return MTU
+	 */
+	inline unsigned int getOutboundPathMtu(const InetAddress& physicalAddress)
+	{
+		for (unsigned int i = 0, j = _numConfiguredPhysicalPaths; i < j; ++i) {
+			if (_physicalPathConfig[i].first.containsAddress(physicalAddress)) {
+				return _physicalPathConfig[i].second.mtu;
+			}
+		}
+		return ZT_DEFAULT_PHYSMTU;
+	}
+
+	/**
+	 * Get the outbound trusted path ID for a physical address, or 0 if none
+	 *
+	 * @param physicalAddress Physical address to which we are sending the packet
+	 * @return Trusted path ID or 0 if none (0 is not a valid trusted path ID)
+	 */
+	inline uint64_t getOutboundPathTrust(const InetAddress& physicalAddress)
+	{
+		for (unsigned int i = 0, j = _numConfiguredPhysicalPaths; i < j; ++i) {
+			if (_physicalPathConfig[i].first.containsAddress(physicalAddress)) {
+				return _physicalPathConfig[i].second.trustedPathId;
+			}
+		}
+		return 0;
+	}
+
+	/**
+	 * Check whether in incoming trusted path marked packet is valid
+	 *
+	 * @param physicalAddress Originating physical address
+	 * @param trustedPathId Trusted path ID from packet (from MAC field)
+	 */
+	inline bool shouldInboundPathBeTrusted(const InetAddress& physicalAddress, const uint64_t trustedPathId)
+	{
+		for (unsigned int i = 0, j = _numConfiguredPhysicalPaths; i < j; ++i) {
+			if ((_physicalPathConfig[i].second.trustedPathId == trustedPathId) && (_physicalPathConfig[i].first.containsAddress(physicalAddress))) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Set or clear physical path configuration (called via Node::setPhysicalPathConfiguration)
+	 */
+	inline void setPhysicalPathConfiguration(const struct sockaddr_storage* pathNetwork, const ZT_PhysicalPathConfiguration* pathConfig)
+	{
+		if (! pathNetwork) {
+			_numConfiguredPhysicalPaths = 0;
+		}
+		else {
+			std::map<InetAddress, ZT_PhysicalPathConfiguration> cpaths;
+			for (unsigned int i = 0, j = _numConfiguredPhysicalPaths; i < j; ++i) {
+				cpaths[_physicalPathConfig[i].first] = _physicalPathConfig[i].second;
+			}
+
+			if (pathConfig) {
+				ZT_PhysicalPathConfiguration pc(*pathConfig);
+
+				if (pc.mtu <= 0) {
+					pc.mtu = ZT_DEFAULT_PHYSMTU;
+				}
+				else if (pc.mtu < ZT_MIN_PHYSMTU) {
+					pc.mtu = ZT_MIN_PHYSMTU;
+				}
+				else if (pc.mtu > ZT_MAX_PHYSMTU) {
+					pc.mtu = ZT_MAX_PHYSMTU;
+				}
+
+				cpaths[*(reinterpret_cast<const InetAddress*>(pathNetwork))] = pc;
+			}
+			else {
+				cpaths.erase(*(reinterpret_cast<const InetAddress*>(pathNetwork)));
+			}
+
+			unsigned int cnt = 0;
+			for (std::map<InetAddress, ZT_PhysicalPathConfiguration>::const_iterator i(cpaths.begin()); ((i != cpaths.end()) && (cnt < ZT_MAX_CONFIGURABLE_PATHS)); ++i) {
+				_physicalPathConfig[cnt].first = i->first;
+				_physicalPathConfig[cnt].second = i->second;
+				++cnt;
+			}
+			_numConfiguredPhysicalPaths = cnt;
+		}
+	}
 
   private:
-    Identity _getIdentity(void* tPtr, const Address& zta);
-    void _memoizeUpstreams(void* tPtr);
-    void _savePeer(void* tPtr, const SharedPtr<Peer>& peer);
+	Identity _getIdentity(void* tPtr, const Address& zta);
+	void _memoizeUpstreams(void* tPtr);
+	void _savePeer(void* tPtr, const SharedPtr<Peer>& peer);
 
-    const RuntimeEnvironment* const RR;
+	const RuntimeEnvironment* const RR;
 
-    std::pair<InetAddress, ZT_PhysicalPathConfiguration> _physicalPathConfig[ZT_MAX_CONFIGURABLE_PATHS];
-    volatile unsigned int _numConfiguredPhysicalPaths;
+	std::pair<InetAddress, ZT_PhysicalPathConfiguration> _physicalPathConfig[ZT_MAX_CONFIGURABLE_PATHS];
+	volatile unsigned int _numConfiguredPhysicalPaths;
 
-    Hashtable<Address, SharedPtr<Peer> > _peers;
-    Mutex _peers_m;
+	Hashtable<Address, SharedPtr<Peer> > _peers;
+	Mutex _peers_m;
 
-    Hashtable<Path::HashKey, SharedPtr<Path> > _paths;
-    Mutex _paths_m;
+	Hashtable<Path::HashKey, SharedPtr<Path> > _paths;
+	Mutex _paths_m;
 
-    World _planet;
-    std::vector<World> _moons;
-    std::vector<std::pair<uint64_t, Address> > _moonSeeds;
-    std::vector<Address> _upstreamAddresses;
-    bool _amUpstream;
-    Mutex _upstreams_m;   // locks worlds, upstream info, moon info, etc.
+	World _planet;
+	std::vector<World> _moons;
+	std::vector<std::pair<uint64_t, Address> > _moonSeeds;
+	std::vector<Address> _upstreamAddresses;
+	bool _amUpstream;
+	Mutex _upstreams_m;	  // locks worlds, upstream info, moon info, etc.
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 520 - 520
node/Trace.cpp

@@ -35,13 +35,13 @@ namespace ZeroTier {
 #ifdef ZT_TRACE
 static void ZT_LOCAL_TRACE(void* const tPtr, const RuntimeEnvironment* const RR, const char* const fmt, ...)
 {
-    char traceMsgBuf[1024];
-    va_list ap;
-    va_start(ap, fmt);
-    vsnprintf(traceMsgBuf, sizeof(traceMsgBuf), fmt, ap);
-    va_end(ap);
-    traceMsgBuf[sizeof(traceMsgBuf) - 1] = (char)0;
-    RR->node->postEvent(tPtr, ZT_EVENT_TRACE, traceMsgBuf);
+	char traceMsgBuf[1024];
+	va_list ap;
+	va_start(ap, fmt);
+	vsnprintf(traceMsgBuf, sizeof(traceMsgBuf), fmt, ap);
+	va_end(ap);
+	traceMsgBuf[sizeof(traceMsgBuf) - 1] = (char)0;
+	RR->node->postEvent(tPtr, ZT_EVENT_TRACE, traceMsgBuf);
 }
 #else
 #define ZT_LOCAL_TRACE(...)
@@ -49,603 +49,603 @@ static void ZT_LOCAL_TRACE(void* const tPtr, const RuntimeEnvironment* const RR,
 
 void Trace::resettingPathsInScope(void* const tPtr, const Address& reporter, const InetAddress& reporterPhysicalAddress, const InetAddress& myPhysicalAddress, const InetAddress::IpScope scope)
 {
-    char tmp[128];
+	char tmp[128];
 
-    ZT_LOCAL_TRACE(tPtr, RR, "RESET and revalidate paths in scope %d; new phy address %s reported by trusted peer %.10llx", (int)scope, myPhysicalAddress.toIpString(tmp), reporter.toInt());
+	ZT_LOCAL_TRACE(tPtr, RR, "RESET and revalidate paths in scope %d; new phy address %s reported by trusted peer %.10llx", (int)scope, myPhysicalAddress.toIpString(tmp), reporter.toInt());
 
-    Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
-    d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__RESETTING_PATHS_IN_SCOPE_S);
-    d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_ZTADDR, reporter);
-    d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR, reporterPhysicalAddress.toString(tmp));
-    d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_PHYADDR, myPhysicalAddress.toString(tmp));
-    d.add(ZT_REMOTE_TRACE_FIELD__IP_SCOPE, (uint64_t)scope);
+	Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
+	d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__RESETTING_PATHS_IN_SCOPE_S);
+	d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_ZTADDR, reporter);
+	d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR, reporterPhysicalAddress.toString(tmp));
+	d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_PHYADDR, myPhysicalAddress.toString(tmp));
+	d.add(ZT_REMOTE_TRACE_FIELD__IP_SCOPE, (uint64_t)scope);
 
-    if (_globalTarget) {
-        _send(tPtr, d, _globalTarget);
-    }
-    _spamToAllNetworks(tPtr, d, Trace::LEVEL_NORMAL);
+	if (_globalTarget) {
+		_send(tPtr, d, _globalTarget);
+	}
+	_spamToAllNetworks(tPtr, d, Trace::LEVEL_NORMAL);
 }
 
 void Trace::peerConfirmingUnknownPath(void* const tPtr, const uint64_t networkId, Peer& peer, const SharedPtr<Path>& path, const uint64_t packetId, const Packet::Verb verb)
 {
-    char tmp[128];
-    if (! path) {
-        return;   // sanity check
-    }
-
-    ZT_LOCAL_TRACE(tPtr, RR, "trying unknown path %s to %.10llx (packet %.16llx verb %d local socket %lld network %.16llx)", path->address().toString(tmp), peer.address().toInt(), packetId, verb, path->localSocket(), networkId);
-
-    std::pair<Address, Trace::Level> byn;
-    if (networkId) {
-        Mutex::Lock l(_byNet_m);
-        _byNet.get(networkId, byn);
-    }
-
-    if ((_globalTarget) || (byn.first)) {
-        Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
-        d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__PEER_CONFIRMING_UNKNOWN_PATH_S);
-        d.add(ZT_REMOTE_TRACE_FIELD__PACKET_ID, packetId);
-        d.add(ZT_REMOTE_TRACE_FIELD__PACKET_VERB, (uint64_t)verb);
-        if (networkId) {
-            d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, networkId);
-        }
-        d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_ZTADDR, peer.address());
-        if (path) {
-            d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR, path->address().toString(tmp));
-            d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET, path->localSocket());
-        }
-
-        if (_globalTarget) {
-            _send(tPtr, d, _globalTarget);
-        }
-        if (byn.first) {
-            _send(tPtr, d, byn.first);
-        }
-    }
+	char tmp[128];
+	if (! path) {
+		return;	  // sanity check
+	}
+
+	ZT_LOCAL_TRACE(tPtr, RR, "trying unknown path %s to %.10llx (packet %.16llx verb %d local socket %lld network %.16llx)", path->address().toString(tmp), peer.address().toInt(), packetId, verb, path->localSocket(), networkId);
+
+	std::pair<Address, Trace::Level> byn;
+	if (networkId) {
+		Mutex::Lock l(_byNet_m);
+		_byNet.get(networkId, byn);
+	}
+
+	if ((_globalTarget) || (byn.first)) {
+		Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
+		d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__PEER_CONFIRMING_UNKNOWN_PATH_S);
+		d.add(ZT_REMOTE_TRACE_FIELD__PACKET_ID, packetId);
+		d.add(ZT_REMOTE_TRACE_FIELD__PACKET_VERB, (uint64_t)verb);
+		if (networkId) {
+			d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, networkId);
+		}
+		d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_ZTADDR, peer.address());
+		if (path) {
+			d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR, path->address().toString(tmp));
+			d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET, path->localSocket());
+		}
+
+		if (_globalTarget) {
+			_send(tPtr, d, _globalTarget);
+		}
+		if (byn.first) {
+			_send(tPtr, d, byn.first);
+		}
+	}
 }
 
 void Trace::bondStateMessage(void* const tPtr, char* msg)
 {
-    ZT_LOCAL_TRACE(tPtr, RR, "%s", msg);
+	ZT_LOCAL_TRACE(tPtr, RR, "%s", msg);
 }
 
 void Trace::peerLearnedNewPath(void* const tPtr, const uint64_t networkId, Peer& peer, const SharedPtr<Path>& newPath, const uint64_t packetId)
 {
-    char tmp[128];
-    if (! newPath) {
-        return;   // sanity check
-    }
-
-    ZT_LOCAL_TRACE(tPtr, RR, "learned new path %s to %.10llx (packet %.16llx local socket %lld network %.16llx)", newPath->address().toString(tmp), peer.address().toInt(), packetId, newPath->localSocket(), networkId);
-
-    std::pair<Address, Trace::Level> byn;
-    if (networkId) {
-        Mutex::Lock l(_byNet_m);
-        _byNet.get(networkId, byn);
-    }
-
-    if ((_globalTarget) || (byn.first)) {
-        Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
-        d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__PEER_LEARNED_NEW_PATH_S);
-        d.add(ZT_REMOTE_TRACE_FIELD__PACKET_ID, packetId);
-        if (networkId) {
-            d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, networkId);
-        }
-        d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_ZTADDR, peer.address());
-        d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR, newPath->address().toString(tmp));
-        d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET, newPath->localSocket());
-
-        if (_globalTarget) {
-            _send(tPtr, d, _globalTarget);
-        }
-        if (byn.first) {
-            _send(tPtr, d, byn.first);
-        }
-    }
+	char tmp[128];
+	if (! newPath) {
+		return;	  // sanity check
+	}
+
+	ZT_LOCAL_TRACE(tPtr, RR, "learned new path %s to %.10llx (packet %.16llx local socket %lld network %.16llx)", newPath->address().toString(tmp), peer.address().toInt(), packetId, newPath->localSocket(), networkId);
+
+	std::pair<Address, Trace::Level> byn;
+	if (networkId) {
+		Mutex::Lock l(_byNet_m);
+		_byNet.get(networkId, byn);
+	}
+
+	if ((_globalTarget) || (byn.first)) {
+		Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
+		d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__PEER_LEARNED_NEW_PATH_S);
+		d.add(ZT_REMOTE_TRACE_FIELD__PACKET_ID, packetId);
+		if (networkId) {
+			d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, networkId);
+		}
+		d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_ZTADDR, peer.address());
+		d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR, newPath->address().toString(tmp));
+		d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET, newPath->localSocket());
+
+		if (_globalTarget) {
+			_send(tPtr, d, _globalTarget);
+		}
+		if (byn.first) {
+			_send(tPtr, d, byn.first);
+		}
+	}
 }
 
 void Trace::peerRedirected(void* const tPtr, const uint64_t networkId, Peer& peer, const SharedPtr<Path>& newPath)
 {
-    char tmp[128];
-    if (! newPath) {
-        return;   // sanity check
-    }
-
-    ZT_LOCAL_TRACE(tPtr, RR, "explicit redirect from %.10llx to path %s", peer.address().toInt(), newPath->address().toString(tmp));
-
-    std::pair<Address, Trace::Level> byn;
-    if (networkId) {
-        Mutex::Lock l(_byNet_m);
-        _byNet.get(networkId, byn);
-    }
-
-    if ((_globalTarget) || (byn.first)) {
-        Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
-        d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__PEER_REDIRECTED_S);
-        if (networkId) {
-            d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, networkId);
-        }
-        d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_ZTADDR, peer.address());
-        d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR, newPath->address().toString(tmp));
-        d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET, newPath->localSocket());
-
-        if (_globalTarget) {
-            _send(tPtr, d, _globalTarget);
-        }
-        if (byn.first) {
-            _send(tPtr, d, byn.first);
-        }
-    }
+	char tmp[128];
+	if (! newPath) {
+		return;	  // sanity check
+	}
+
+	ZT_LOCAL_TRACE(tPtr, RR, "explicit redirect from %.10llx to path %s", peer.address().toInt(), newPath->address().toString(tmp));
+
+	std::pair<Address, Trace::Level> byn;
+	if (networkId) {
+		Mutex::Lock l(_byNet_m);
+		_byNet.get(networkId, byn);
+	}
+
+	if ((_globalTarget) || (byn.first)) {
+		Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
+		d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__PEER_REDIRECTED_S);
+		if (networkId) {
+			d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, networkId);
+		}
+		d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_ZTADDR, peer.address());
+		d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR, newPath->address().toString(tmp));
+		d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET, newPath->localSocket());
+
+		if (_globalTarget) {
+			_send(tPtr, d, _globalTarget);
+		}
+		if (byn.first) {
+			_send(tPtr, d, byn.first);
+		}
+	}
 }
 
 void Trace::outgoingNetworkFrameDropped(void* const tPtr, const SharedPtr<Network>& network, const MAC& sourceMac, const MAC& destMac, const unsigned int etherType, const unsigned int vlanId, const unsigned int frameLen, const char* reason)
 {
 #ifdef ZT_TRACE
-    char tmp[128], tmp2[128];
+	char tmp[128], tmp2[128];
 #endif
-    if (! network) {
-        return;   // sanity check
-    }
-
-    ZT_LOCAL_TRACE(tPtr, RR, "%.16llx DROP frame %s -> %s etherType %.4x size %u (%s)", network->id(), sourceMac.toString(tmp), destMac.toString(tmp2), etherType, frameLen, (reason) ? reason : "unknown reason");
-
-    std::pair<Address, Trace::Level> byn;
-    {
-        Mutex::Lock l(_byNet_m);
-        _byNet.get(network->id(), byn);
-    }
-
-    if (((_globalTarget) && ((int)_globalLevel >= (int)Trace::LEVEL_VERBOSE)) || ((byn.first) && ((int)byn.second >= (int)Trace::LEVEL_VERBOSE))) {
-        Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
-        d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__OUTGOING_NETWORK_FRAME_DROPPED_S);
-        d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, network->id());
-        d.add(ZT_REMOTE_TRACE_FIELD__SOURCE_MAC, sourceMac.toInt());
-        d.add(ZT_REMOTE_TRACE_FIELD__DEST_MAC, destMac.toInt());
-        d.add(ZT_REMOTE_TRACE_FIELD__ETHERTYPE, (uint64_t)etherType);
-        d.add(ZT_REMOTE_TRACE_FIELD__VLAN_ID, (uint64_t)vlanId);
-        d.add(ZT_REMOTE_TRACE_FIELD__FRAME_LENGTH, (uint64_t)frameLen);
-        if (reason) {
-            d.add(ZT_REMOTE_TRACE_FIELD__REASON, reason);
-        }
-
-        if ((_globalTarget) && ((int)_globalLevel >= (int)Trace::LEVEL_VERBOSE)) {
-            _send(tPtr, d, _globalTarget);
-        }
-        if ((byn.first) && ((int)byn.second >= (int)Trace::LEVEL_VERBOSE)) {
-            _send(tPtr, d, byn.first);
-        }
-    }
+	if (! network) {
+		return;	  // sanity check
+	}
+
+	ZT_LOCAL_TRACE(tPtr, RR, "%.16llx DROP frame %s -> %s etherType %.4x size %u (%s)", network->id(), sourceMac.toString(tmp), destMac.toString(tmp2), etherType, frameLen, (reason) ? reason : "unknown reason");
+
+	std::pair<Address, Trace::Level> byn;
+	{
+		Mutex::Lock l(_byNet_m);
+		_byNet.get(network->id(), byn);
+	}
+
+	if (((_globalTarget) && ((int)_globalLevel >= (int)Trace::LEVEL_VERBOSE)) || ((byn.first) && ((int)byn.second >= (int)Trace::LEVEL_VERBOSE))) {
+		Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
+		d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__OUTGOING_NETWORK_FRAME_DROPPED_S);
+		d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, network->id());
+		d.add(ZT_REMOTE_TRACE_FIELD__SOURCE_MAC, sourceMac.toInt());
+		d.add(ZT_REMOTE_TRACE_FIELD__DEST_MAC, destMac.toInt());
+		d.add(ZT_REMOTE_TRACE_FIELD__ETHERTYPE, (uint64_t)etherType);
+		d.add(ZT_REMOTE_TRACE_FIELD__VLAN_ID, (uint64_t)vlanId);
+		d.add(ZT_REMOTE_TRACE_FIELD__FRAME_LENGTH, (uint64_t)frameLen);
+		if (reason) {
+			d.add(ZT_REMOTE_TRACE_FIELD__REASON, reason);
+		}
+
+		if ((_globalTarget) && ((int)_globalLevel >= (int)Trace::LEVEL_VERBOSE)) {
+			_send(tPtr, d, _globalTarget);
+		}
+		if ((byn.first) && ((int)byn.second >= (int)Trace::LEVEL_VERBOSE)) {
+			_send(tPtr, d, byn.first);
+		}
+	}
 }
 
 void Trace::incomingNetworkAccessDenied(
-    void* const tPtr,
-    const SharedPtr<Network>& network,
-    const SharedPtr<Path>& path,
-    const uint64_t packetId,
-    const unsigned int packetLength,
-    const Address& source,
-    const Packet::Verb verb,
-    bool credentialsRequested)
+	void* const tPtr,
+	const SharedPtr<Network>& network,
+	const SharedPtr<Path>& path,
+	const uint64_t packetId,
+	const unsigned int packetLength,
+	const Address& source,
+	const Packet::Verb verb,
+	bool credentialsRequested)
 {
-    char tmp[128];
-    if (! network) {
-        return;   // sanity check
-    }
-
-    ZT_LOCAL_TRACE(
-        tPtr,
-        RR,
-        "%.16llx DENIED packet from %.10llx(%s) verb %d size %u%s",
-        network->id(),
-        source.toInt(),
-        (path) ? (path->address().toString(tmp)) : "???",
-        (int)verb,
-        packetLength,
-        credentialsRequested ? " (credentials requested)" : " (credentials not requested)");
-
-    std::pair<Address, Trace::Level> byn;
-    {
-        Mutex::Lock l(_byNet_m);
-        _byNet.get(network->id(), byn);
-    }
-
-    if (((_globalTarget) && ((int)_globalLevel >= (int)Trace::LEVEL_VERBOSE)) || ((byn.first) && ((int)byn.second >= (int)Trace::LEVEL_VERBOSE))) {
-        Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
-        d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__INCOMING_NETWORK_ACCESS_DENIED_S);
-        d.add(ZT_REMOTE_TRACE_FIELD__PACKET_ID, packetId);
-        d.add(ZT_REMOTE_TRACE_FIELD__PACKET_VERB, (uint64_t)verb);
-        d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_ZTADDR, source);
-        if (path) {
-            d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR, path->address().toString(tmp));
-            d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET, path->localSocket());
-        }
-        d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, network->id());
-
-        if ((_globalTarget) && ((int)_globalLevel >= (int)Trace::LEVEL_VERBOSE)) {
-            _send(tPtr, d, _globalTarget);
-        }
-        if ((byn.first) && ((int)byn.second >= (int)Trace::LEVEL_VERBOSE)) {
-            _send(tPtr, d, byn.first);
-        }
-    }
+	char tmp[128];
+	if (! network) {
+		return;	  // sanity check
+	}
+
+	ZT_LOCAL_TRACE(
+		tPtr,
+		RR,
+		"%.16llx DENIED packet from %.10llx(%s) verb %d size %u%s",
+		network->id(),
+		source.toInt(),
+		(path) ? (path->address().toString(tmp)) : "???",
+		(int)verb,
+		packetLength,
+		credentialsRequested ? " (credentials requested)" : " (credentials not requested)");
+
+	std::pair<Address, Trace::Level> byn;
+	{
+		Mutex::Lock l(_byNet_m);
+		_byNet.get(network->id(), byn);
+	}
+
+	if (((_globalTarget) && ((int)_globalLevel >= (int)Trace::LEVEL_VERBOSE)) || ((byn.first) && ((int)byn.second >= (int)Trace::LEVEL_VERBOSE))) {
+		Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
+		d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__INCOMING_NETWORK_ACCESS_DENIED_S);
+		d.add(ZT_REMOTE_TRACE_FIELD__PACKET_ID, packetId);
+		d.add(ZT_REMOTE_TRACE_FIELD__PACKET_VERB, (uint64_t)verb);
+		d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_ZTADDR, source);
+		if (path) {
+			d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR, path->address().toString(tmp));
+			d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET, path->localSocket());
+		}
+		d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, network->id());
+
+		if ((_globalTarget) && ((int)_globalLevel >= (int)Trace::LEVEL_VERBOSE)) {
+			_send(tPtr, d, _globalTarget);
+		}
+		if ((byn.first) && ((int)byn.second >= (int)Trace::LEVEL_VERBOSE)) {
+			_send(tPtr, d, byn.first);
+		}
+	}
 }
 
 void Trace::incomingNetworkFrameDropped(
-    void* const tPtr,
-    const SharedPtr<Network>& network,
-    const SharedPtr<Path>& path,
-    const uint64_t packetId,
-    const unsigned int packetLength,
-    const Address& source,
-    const Packet::Verb verb,
-    const MAC& sourceMac,
-    const MAC& destMac,
-    const char* reason)
+	void* const tPtr,
+	const SharedPtr<Network>& network,
+	const SharedPtr<Path>& path,
+	const uint64_t packetId,
+	const unsigned int packetLength,
+	const Address& source,
+	const Packet::Verb verb,
+	const MAC& sourceMac,
+	const MAC& destMac,
+	const char* reason)
 {
-    char tmp[128];
-    if (! network) {
-        return;   // sanity check
-    }
-
-    ZT_LOCAL_TRACE(tPtr, RR, "%.16llx DROPPED frame from %.10llx(%s) verb %d size %u", network->id(), source.toInt(), (path) ? (path->address().toString(tmp)) : "???", (int)verb, packetLength);
-
-    std::pair<Address, Trace::Level> byn;
-    {
-        Mutex::Lock l(_byNet_m);
-        _byNet.get(network->id(), byn);
-    }
-
-    if (((_globalTarget) && ((int)_globalLevel >= (int)Trace::LEVEL_VERBOSE)) || ((byn.first) && ((int)byn.second >= (int)Trace::LEVEL_VERBOSE))) {
-        Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
-        d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__INCOMING_NETWORK_FRAME_DROPPED_S);
-        d.add(ZT_REMOTE_TRACE_FIELD__PACKET_ID, packetId);
-        d.add(ZT_REMOTE_TRACE_FIELD__PACKET_VERB, (uint64_t)verb);
-        d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_ZTADDR, source);
-        if (path) {
-            d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR, path->address().toString(tmp));
-            d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET, path->localSocket());
-        }
-        d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, network->id());
-        d.add(ZT_REMOTE_TRACE_FIELD__SOURCE_MAC, sourceMac.toInt());
-        d.add(ZT_REMOTE_TRACE_FIELD__DEST_MAC, destMac.toInt());
-        if (reason) {
-            d.add(ZT_REMOTE_TRACE_FIELD__REASON, reason);
-        }
-
-        if ((_globalTarget) && ((int)_globalLevel >= (int)Trace::LEVEL_VERBOSE)) {
-            _send(tPtr, d, _globalTarget);
-        }
-        if ((byn.first) && ((int)byn.second >= (int)Trace::LEVEL_VERBOSE)) {
-            _send(tPtr, d, byn.first);
-        }
-    }
+	char tmp[128];
+	if (! network) {
+		return;	  // sanity check
+	}
+
+	ZT_LOCAL_TRACE(tPtr, RR, "%.16llx DROPPED frame from %.10llx(%s) verb %d size %u", network->id(), source.toInt(), (path) ? (path->address().toString(tmp)) : "???", (int)verb, packetLength);
+
+	std::pair<Address, Trace::Level> byn;
+	{
+		Mutex::Lock l(_byNet_m);
+		_byNet.get(network->id(), byn);
+	}
+
+	if (((_globalTarget) && ((int)_globalLevel >= (int)Trace::LEVEL_VERBOSE)) || ((byn.first) && ((int)byn.second >= (int)Trace::LEVEL_VERBOSE))) {
+		Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
+		d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__INCOMING_NETWORK_FRAME_DROPPED_S);
+		d.add(ZT_REMOTE_TRACE_FIELD__PACKET_ID, packetId);
+		d.add(ZT_REMOTE_TRACE_FIELD__PACKET_VERB, (uint64_t)verb);
+		d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_ZTADDR, source);
+		if (path) {
+			d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR, path->address().toString(tmp));
+			d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET, path->localSocket());
+		}
+		d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, network->id());
+		d.add(ZT_REMOTE_TRACE_FIELD__SOURCE_MAC, sourceMac.toInt());
+		d.add(ZT_REMOTE_TRACE_FIELD__DEST_MAC, destMac.toInt());
+		if (reason) {
+			d.add(ZT_REMOTE_TRACE_FIELD__REASON, reason);
+		}
+
+		if ((_globalTarget) && ((int)_globalLevel >= (int)Trace::LEVEL_VERBOSE)) {
+			_send(tPtr, d, _globalTarget);
+		}
+		if ((byn.first) && ((int)byn.second >= (int)Trace::LEVEL_VERBOSE)) {
+			_send(tPtr, d, byn.first);
+		}
+	}
 }
 
 void Trace::incomingPacketMessageAuthenticationFailure(void* const tPtr, const SharedPtr<Path>& path, const uint64_t packetId, const Address& source, const unsigned int hops, const char* reason)
 {
-    char tmp[128];
-
-    ZT_LOCAL_TRACE(tPtr, RR, "MAC failed for packet %.16llx from %.10llx(%s)", packetId, source.toInt(), (path) ? path->address().toString(tmp) : "???");
-
-    if ((_globalTarget) && ((int)_globalLevel >= Trace::LEVEL_DEBUG)) {
-        Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
-        d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__PACKET_MAC_FAILURE_S);
-        d.add(ZT_REMOTE_TRACE_FIELD__PACKET_ID, packetId);
-        d.add(ZT_REMOTE_TRACE_FIELD__PACKET_HOPS, (uint64_t)hops);
-        d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_ZTADDR, source);
-        if (path) {
-            d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR, path->address().toString(tmp));
-            d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET, path->localSocket());
-        }
-        if (reason) {
-            d.add(ZT_REMOTE_TRACE_FIELD__REASON, reason);
-        }
-
-        _send(tPtr, d, _globalTarget);
-    }
+	char tmp[128];
+
+	ZT_LOCAL_TRACE(tPtr, RR, "MAC failed for packet %.16llx from %.10llx(%s)", packetId, source.toInt(), (path) ? path->address().toString(tmp) : "???");
+
+	if ((_globalTarget) && ((int)_globalLevel >= Trace::LEVEL_DEBUG)) {
+		Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
+		d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__PACKET_MAC_FAILURE_S);
+		d.add(ZT_REMOTE_TRACE_FIELD__PACKET_ID, packetId);
+		d.add(ZT_REMOTE_TRACE_FIELD__PACKET_HOPS, (uint64_t)hops);
+		d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_ZTADDR, source);
+		if (path) {
+			d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR, path->address().toString(tmp));
+			d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET, path->localSocket());
+		}
+		if (reason) {
+			d.add(ZT_REMOTE_TRACE_FIELD__REASON, reason);
+		}
+
+		_send(tPtr, d, _globalTarget);
+	}
 }
 
 void Trace::incomingPacketInvalid(void* const tPtr, const SharedPtr<Path>& path, const uint64_t packetId, const Address& source, const unsigned int hops, const Packet::Verb verb, const char* reason)
 {
-    char tmp[128];
-
-    ZT_LOCAL_TRACE(tPtr, RR, "INVALID packet %.16llx from %.10llx(%s) (%s)", packetId, source.toInt(), (path) ? path->address().toString(tmp) : "???", (reason) ? reason : "unknown reason");
-
-    if ((_globalTarget) && ((int)_globalLevel >= Trace::LEVEL_DEBUG)) {
-        Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
-        d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__PACKET_INVALID_S);
-        d.add(ZT_REMOTE_TRACE_FIELD__PACKET_ID, packetId);
-        d.add(ZT_REMOTE_TRACE_FIELD__PACKET_VERB, (uint64_t)verb);
-        d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_ZTADDR, source);
-        if (path) {
-            d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR, path->address().toString(tmp));
-            d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET, path->localSocket());
-        }
-        d.add(ZT_REMOTE_TRACE_FIELD__PACKET_HOPS, (uint64_t)hops);
-        if (reason) {
-            d.add(ZT_REMOTE_TRACE_FIELD__REASON, reason);
-        }
-
-        _send(tPtr, d, _globalTarget);
-    }
+	char tmp[128];
+
+	ZT_LOCAL_TRACE(tPtr, RR, "INVALID packet %.16llx from %.10llx(%s) (%s)", packetId, source.toInt(), (path) ? path->address().toString(tmp) : "???", (reason) ? reason : "unknown reason");
+
+	if ((_globalTarget) && ((int)_globalLevel >= Trace::LEVEL_DEBUG)) {
+		Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
+		d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__PACKET_INVALID_S);
+		d.add(ZT_REMOTE_TRACE_FIELD__PACKET_ID, packetId);
+		d.add(ZT_REMOTE_TRACE_FIELD__PACKET_VERB, (uint64_t)verb);
+		d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_ZTADDR, source);
+		if (path) {
+			d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR, path->address().toString(tmp));
+			d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET, path->localSocket());
+		}
+		d.add(ZT_REMOTE_TRACE_FIELD__PACKET_HOPS, (uint64_t)hops);
+		if (reason) {
+			d.add(ZT_REMOTE_TRACE_FIELD__REASON, reason);
+		}
+
+		_send(tPtr, d, _globalTarget);
+	}
 }
 
 void Trace::incomingPacketDroppedHELLO(void* const tPtr, const SharedPtr<Path>& path, const uint64_t packetId, const Address& source, const char* reason)
 {
-    char tmp[128];
-
-    ZT_LOCAL_TRACE(tPtr, RR, "DROPPED HELLO from %.10llx(%s) (%s)", source.toInt(), (path) ? path->address().toString(tmp) : "???", (reason) ? reason : "???");
-
-    if ((_globalTarget) && ((int)_globalLevel >= Trace::LEVEL_DEBUG)) {
-        Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
-        d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__PACKET_INVALID_S);
-        d.add(ZT_REMOTE_TRACE_FIELD__PACKET_ID, packetId);
-        d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_ZTADDR, source);
-        if (path) {
-            d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR, path->address().toString(tmp));
-            d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET, path->localSocket());
-        }
-        if (reason) {
-            d.add(ZT_REMOTE_TRACE_FIELD__REASON, reason);
-        }
-
-        _send(tPtr, d, _globalTarget);
-    }
+	char tmp[128];
+
+	ZT_LOCAL_TRACE(tPtr, RR, "DROPPED HELLO from %.10llx(%s) (%s)", source.toInt(), (path) ? path->address().toString(tmp) : "???", (reason) ? reason : "???");
+
+	if ((_globalTarget) && ((int)_globalLevel >= Trace::LEVEL_DEBUG)) {
+		Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
+		d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__PACKET_INVALID_S);
+		d.add(ZT_REMOTE_TRACE_FIELD__PACKET_ID, packetId);
+		d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_ZTADDR, source);
+		if (path) {
+			d.add(ZT_REMOTE_TRACE_FIELD__REMOTE_PHYADDR, path->address().toString(tmp));
+			d.add(ZT_REMOTE_TRACE_FIELD__LOCAL_SOCKET, path->localSocket());
+		}
+		if (reason) {
+			d.add(ZT_REMOTE_TRACE_FIELD__REASON, reason);
+		}
+
+		_send(tPtr, d, _globalTarget);
+	}
 }
 
 void Trace::networkConfigRequestSent(void* const tPtr, const Network& network, const Address& controller)
 {
-    ZT_LOCAL_TRACE(tPtr, RR, "requesting configuration for network %.16llx", network.id());
-    if ((_globalTarget) && ((int)_globalLevel >= Trace::LEVEL_DEBUG)) {
-        Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
-        d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__NETWORK_CONFIG_REQUEST_SENT_S);
-        d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, network.id());
-        d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_CONTROLLER_ID, controller);
-        _send(tPtr, d, _globalTarget);
-    }
+	ZT_LOCAL_TRACE(tPtr, RR, "requesting configuration for network %.16llx", network.id());
+	if ((_globalTarget) && ((int)_globalLevel >= Trace::LEVEL_DEBUG)) {
+		Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
+		d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__NETWORK_CONFIG_REQUEST_SENT_S);
+		d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, network.id());
+		d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_CONTROLLER_ID, controller);
+		_send(tPtr, d, _globalTarget);
+	}
 }
 
 void Trace::networkFilter(
-    void* const tPtr,
-    const Network& network,
-    const RuleResultLog& primaryRuleSetLog,
-    const RuleResultLog* const matchingCapabilityRuleSetLog,
-    const Capability* const matchingCapability,
-    const Address& ztSource,
-    const Address& ztDest,
-    const MAC& macSource,
-    const MAC& macDest,
-    const uint8_t* const frameData,
-    const unsigned int frameLen,
-    const unsigned int etherType,
-    const unsigned int vlanId,
-    const bool noTee,
-    const bool inbound,
-    const int accept)
+	void* const tPtr,
+	const Network& network,
+	const RuleResultLog& primaryRuleSetLog,
+	const RuleResultLog* const matchingCapabilityRuleSetLog,
+	const Capability* const matchingCapability,
+	const Address& ztSource,
+	const Address& ztDest,
+	const MAC& macSource,
+	const MAC& macDest,
+	const uint8_t* const frameData,
+	const unsigned int frameLen,
+	const unsigned int etherType,
+	const unsigned int vlanId,
+	const bool noTee,
+	const bool inbound,
+	const int accept)
 {
-    std::pair<Address, Trace::Level> byn;
-    {
-        Mutex::Lock l(_byNet_m);
-        _byNet.get(network.id(), byn);
-    }
-
-    if (((_globalTarget) && ((int)_globalLevel >= (int)Trace::LEVEL_RULES)) || ((byn.first) && ((int)byn.second >= (int)Trace::LEVEL_RULES))) {
-        Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
-        d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__NETWORK_FILTER_TRACE_S);
-        d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, network.id());
-        d.add(ZT_REMOTE_TRACE_FIELD__SOURCE_ZTADDR, ztSource);
-        d.add(ZT_REMOTE_TRACE_FIELD__DEST_ZTADDR, ztDest);
-        d.add(ZT_REMOTE_TRACE_FIELD__SOURCE_MAC, macSource.toInt());
-        d.add(ZT_REMOTE_TRACE_FIELD__DEST_MAC, macDest.toInt());
-        d.add(ZT_REMOTE_TRACE_FIELD__ETHERTYPE, (uint64_t)etherType);
-        d.add(ZT_REMOTE_TRACE_FIELD__VLAN_ID, (uint64_t)vlanId);
-        d.add(ZT_REMOTE_TRACE_FIELD__FILTER_FLAG_NOTEE, noTee ? "1" : "0");
-        d.add(ZT_REMOTE_TRACE_FIELD__FILTER_FLAG_INBOUND, inbound ? "1" : "0");
-        d.add(ZT_REMOTE_TRACE_FIELD__FILTER_RESULT, (int64_t)accept);
-        d.add(ZT_REMOTE_TRACE_FIELD__FILTER_BASE_RULE_LOG, (const char*)primaryRuleSetLog.data(), (int)primaryRuleSetLog.sizeBytes());
-        if (matchingCapabilityRuleSetLog) {
-            d.add(ZT_REMOTE_TRACE_FIELD__FILTER_CAP_RULE_LOG, (const char*)matchingCapabilityRuleSetLog->data(), (int)matchingCapabilityRuleSetLog->sizeBytes());
-        }
-        if (matchingCapability) {
-            d.add(ZT_REMOTE_TRACE_FIELD__FILTER_CAP_ID, (uint64_t)matchingCapability->id());
-        }
-        d.add(ZT_REMOTE_TRACE_FIELD__FRAME_LENGTH, (uint64_t)frameLen);
-        if (frameLen > 0) {
-            d.add(ZT_REMOTE_TRACE_FIELD__FRAME_DATA, (const char*)frameData, (frameLen > 256) ? (int)256 : (int)frameLen);
-        }
-
-        if ((_globalTarget) && ((int)_globalLevel >= (int)Trace::LEVEL_RULES)) {
-            _send(tPtr, d, _globalTarget);
-        }
-        if ((byn.first) && ((int)byn.second >= (int)Trace::LEVEL_RULES)) {
-            _send(tPtr, d, byn.first);
-        }
-    }
+	std::pair<Address, Trace::Level> byn;
+	{
+		Mutex::Lock l(_byNet_m);
+		_byNet.get(network.id(), byn);
+	}
+
+	if (((_globalTarget) && ((int)_globalLevel >= (int)Trace::LEVEL_RULES)) || ((byn.first) && ((int)byn.second >= (int)Trace::LEVEL_RULES))) {
+		Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
+		d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__NETWORK_FILTER_TRACE_S);
+		d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, network.id());
+		d.add(ZT_REMOTE_TRACE_FIELD__SOURCE_ZTADDR, ztSource);
+		d.add(ZT_REMOTE_TRACE_FIELD__DEST_ZTADDR, ztDest);
+		d.add(ZT_REMOTE_TRACE_FIELD__SOURCE_MAC, macSource.toInt());
+		d.add(ZT_REMOTE_TRACE_FIELD__DEST_MAC, macDest.toInt());
+		d.add(ZT_REMOTE_TRACE_FIELD__ETHERTYPE, (uint64_t)etherType);
+		d.add(ZT_REMOTE_TRACE_FIELD__VLAN_ID, (uint64_t)vlanId);
+		d.add(ZT_REMOTE_TRACE_FIELD__FILTER_FLAG_NOTEE, noTee ? "1" : "0");
+		d.add(ZT_REMOTE_TRACE_FIELD__FILTER_FLAG_INBOUND, inbound ? "1" : "0");
+		d.add(ZT_REMOTE_TRACE_FIELD__FILTER_RESULT, (int64_t)accept);
+		d.add(ZT_REMOTE_TRACE_FIELD__FILTER_BASE_RULE_LOG, (const char*)primaryRuleSetLog.data(), (int)primaryRuleSetLog.sizeBytes());
+		if (matchingCapabilityRuleSetLog) {
+			d.add(ZT_REMOTE_TRACE_FIELD__FILTER_CAP_RULE_LOG, (const char*)matchingCapabilityRuleSetLog->data(), (int)matchingCapabilityRuleSetLog->sizeBytes());
+		}
+		if (matchingCapability) {
+			d.add(ZT_REMOTE_TRACE_FIELD__FILTER_CAP_ID, (uint64_t)matchingCapability->id());
+		}
+		d.add(ZT_REMOTE_TRACE_FIELD__FRAME_LENGTH, (uint64_t)frameLen);
+		if (frameLen > 0) {
+			d.add(ZT_REMOTE_TRACE_FIELD__FRAME_DATA, (const char*)frameData, (frameLen > 256) ? (int)256 : (int)frameLen);
+		}
+
+		if ((_globalTarget) && ((int)_globalLevel >= (int)Trace::LEVEL_RULES)) {
+			_send(tPtr, d, _globalTarget);
+		}
+		if ((byn.first) && ((int)byn.second >= (int)Trace::LEVEL_RULES)) {
+			_send(tPtr, d, byn.first);
+		}
+	}
 }
 
 void Trace::credentialRejected(void* const tPtr, const CertificateOfMembership& c, const char* reason)
 {
-    std::pair<Address, Trace::Level> byn;
-    if (c.networkId()) {
-        Mutex::Lock l(_byNet_m);
-        _byNet.get(c.networkId(), byn);
-    }
-
-    if ((_globalTarget) || (byn.first)) {
-        Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
-        d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__CREDENTIAL_REJECTED_S);
-        d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, c.networkId());
-        d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TYPE, (uint64_t)c.credentialType());
-        d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ID, (uint64_t)c.id());
-        d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TIMESTAMP, c.timestamp());
-        d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ISSUED_TO, c.issuedTo());
-        if (reason) {
-            d.add(ZT_REMOTE_TRACE_FIELD__REASON, reason);
-        }
-
-        if (_globalTarget) {
-            _send(tPtr, d, _globalTarget);
-        }
-        if (byn.first) {
-            _send(tPtr, d, byn.first);
-        }
-    }
+	std::pair<Address, Trace::Level> byn;
+	if (c.networkId()) {
+		Mutex::Lock l(_byNet_m);
+		_byNet.get(c.networkId(), byn);
+	}
+
+	if ((_globalTarget) || (byn.first)) {
+		Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
+		d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__CREDENTIAL_REJECTED_S);
+		d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, c.networkId());
+		d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TYPE, (uint64_t)c.credentialType());
+		d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ID, (uint64_t)c.id());
+		d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TIMESTAMP, c.timestamp());
+		d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ISSUED_TO, c.issuedTo());
+		if (reason) {
+			d.add(ZT_REMOTE_TRACE_FIELD__REASON, reason);
+		}
+
+		if (_globalTarget) {
+			_send(tPtr, d, _globalTarget);
+		}
+		if (byn.first) {
+			_send(tPtr, d, byn.first);
+		}
+	}
 }
 
 void Trace::credentialRejected(void* const tPtr, const CertificateOfOwnership& c, const char* reason)
 {
-    std::pair<Address, Trace::Level> byn;
-    if (c.networkId()) {
-        Mutex::Lock l(_byNet_m);
-        _byNet.get(c.networkId(), byn);
-    }
-
-    if ((_globalTarget) || (byn.first)) {
-        Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
-        d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__CREDENTIAL_REJECTED_S);
-        d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, c.networkId());
-        d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TYPE, (uint64_t)c.credentialType());
-        d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ID, (uint64_t)c.id());
-        d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TIMESTAMP, c.timestamp());
-        d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ISSUED_TO, c.issuedTo());
-        if (reason) {
-            d.add(ZT_REMOTE_TRACE_FIELD__REASON, reason);
-        }
-
-        if (_globalTarget) {
-            _send(tPtr, d, _globalTarget);
-        }
-        if (byn.first) {
-            _send(tPtr, d, byn.first);
-        }
-    }
+	std::pair<Address, Trace::Level> byn;
+	if (c.networkId()) {
+		Mutex::Lock l(_byNet_m);
+		_byNet.get(c.networkId(), byn);
+	}
+
+	if ((_globalTarget) || (byn.first)) {
+		Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
+		d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__CREDENTIAL_REJECTED_S);
+		d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, c.networkId());
+		d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TYPE, (uint64_t)c.credentialType());
+		d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ID, (uint64_t)c.id());
+		d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TIMESTAMP, c.timestamp());
+		d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ISSUED_TO, c.issuedTo());
+		if (reason) {
+			d.add(ZT_REMOTE_TRACE_FIELD__REASON, reason);
+		}
+
+		if (_globalTarget) {
+			_send(tPtr, d, _globalTarget);
+		}
+		if (byn.first) {
+			_send(tPtr, d, byn.first);
+		}
+	}
 }
 
 void Trace::credentialRejected(void* const tPtr, const Capability& c, const char* reason)
 {
-    std::pair<Address, Trace::Level> byn;
-    if (c.networkId()) {
-        Mutex::Lock l(_byNet_m);
-        _byNet.get(c.networkId(), byn);
-    }
-
-    if ((_globalTarget) || (byn.first)) {
-        Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
-        d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__CREDENTIAL_REJECTED_S);
-        d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, c.networkId());
-        d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TYPE, (uint64_t)c.credentialType());
-        d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ID, (uint64_t)c.id());
-        d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TIMESTAMP, c.timestamp());
-        d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ISSUED_TO, c.issuedTo());
-        if (reason) {
-            d.add(ZT_REMOTE_TRACE_FIELD__REASON, reason);
-        }
-
-        if (_globalTarget) {
-            _send(tPtr, d, _globalTarget);
-        }
-        if (byn.first) {
-            _send(tPtr, d, byn.first);
-        }
-    }
+	std::pair<Address, Trace::Level> byn;
+	if (c.networkId()) {
+		Mutex::Lock l(_byNet_m);
+		_byNet.get(c.networkId(), byn);
+	}
+
+	if ((_globalTarget) || (byn.first)) {
+		Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
+		d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__CREDENTIAL_REJECTED_S);
+		d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, c.networkId());
+		d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TYPE, (uint64_t)c.credentialType());
+		d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ID, (uint64_t)c.id());
+		d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TIMESTAMP, c.timestamp());
+		d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ISSUED_TO, c.issuedTo());
+		if (reason) {
+			d.add(ZT_REMOTE_TRACE_FIELD__REASON, reason);
+		}
+
+		if (_globalTarget) {
+			_send(tPtr, d, _globalTarget);
+		}
+		if (byn.first) {
+			_send(tPtr, d, byn.first);
+		}
+	}
 }
 
 void Trace::credentialRejected(void* const tPtr, const Tag& c, const char* reason)
 {
-    std::pair<Address, Trace::Level> byn;
-    if (c.networkId()) {
-        Mutex::Lock l(_byNet_m);
-        _byNet.get(c.networkId(), byn);
-    }
-
-    if ((_globalTarget) || (byn.first)) {
-        Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
-        d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__CREDENTIAL_REJECTED_S);
-        d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, c.networkId());
-        d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TYPE, (uint64_t)c.credentialType());
-        d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ID, (uint64_t)c.id());
-        d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TIMESTAMP, c.timestamp());
-        d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ISSUED_TO, c.issuedTo());
-        d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_INFO, (uint64_t)c.value());
-        if (reason) {
-            d.add(ZT_REMOTE_TRACE_FIELD__REASON, reason);
-        }
-
-        if (_globalTarget) {
-            _send(tPtr, d, _globalTarget);
-        }
-        if (byn.first) {
-            _send(tPtr, d, byn.first);
-        }
-    }
+	std::pair<Address, Trace::Level> byn;
+	if (c.networkId()) {
+		Mutex::Lock l(_byNet_m);
+		_byNet.get(c.networkId(), byn);
+	}
+
+	if ((_globalTarget) || (byn.first)) {
+		Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
+		d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__CREDENTIAL_REJECTED_S);
+		d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, c.networkId());
+		d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TYPE, (uint64_t)c.credentialType());
+		d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ID, (uint64_t)c.id());
+		d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TIMESTAMP, c.timestamp());
+		d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ISSUED_TO, c.issuedTo());
+		d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_INFO, (uint64_t)c.value());
+		if (reason) {
+			d.add(ZT_REMOTE_TRACE_FIELD__REASON, reason);
+		}
+
+		if (_globalTarget) {
+			_send(tPtr, d, _globalTarget);
+		}
+		if (byn.first) {
+			_send(tPtr, d, byn.first);
+		}
+	}
 }
 
 void Trace::credentialRejected(void* const tPtr, const Revocation& c, const char* reason)
 {
-    std::pair<Address, Trace::Level> byn;
-    if (c.networkId()) {
-        Mutex::Lock l(_byNet_m);
-        _byNet.get(c.networkId(), byn);
-    }
-
-    if ((_globalTarget) || (byn.first)) {
-        Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
-        d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__CREDENTIAL_REJECTED_S);
-        d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, c.networkId());
-        d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TYPE, (uint64_t)c.credentialType());
-        d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ID, (uint64_t)c.id());
-        d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_REVOCATION_TARGET, c.target());
-        if (reason) {
-            d.add(ZT_REMOTE_TRACE_FIELD__REASON, reason);
-        }
-
-        if (_globalTarget) {
-            _send(tPtr, d, _globalTarget);
-        }
-        if (byn.first) {
-            _send(tPtr, d, byn.first);
-        }
-    }
+	std::pair<Address, Trace::Level> byn;
+	if (c.networkId()) {
+		Mutex::Lock l(_byNet_m);
+		_byNet.get(c.networkId(), byn);
+	}
+
+	if ((_globalTarget) || (byn.first)) {
+		Dictionary<ZT_MAX_REMOTE_TRACE_SIZE> d;
+		d.add(ZT_REMOTE_TRACE_FIELD__EVENT, ZT_REMOTE_TRACE_EVENT__CREDENTIAL_REJECTED_S);
+		d.add(ZT_REMOTE_TRACE_FIELD__NETWORK_ID, c.networkId());
+		d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_TYPE, (uint64_t)c.credentialType());
+		d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_ID, (uint64_t)c.id());
+		d.add(ZT_REMOTE_TRACE_FIELD__CREDENTIAL_REVOCATION_TARGET, c.target());
+		if (reason) {
+			d.add(ZT_REMOTE_TRACE_FIELD__REASON, reason);
+		}
+
+		if (_globalTarget) {
+			_send(tPtr, d, _globalTarget);
+		}
+		if (byn.first) {
+			_send(tPtr, d, byn.first);
+		}
+	}
 }
 
 void Trace::updateMemoizedSettings()
 {
-    _globalTarget = RR->node->remoteTraceTarget();
-    _globalLevel = RR->node->remoteTraceLevel();
-    const std::vector<SharedPtr<Network> > nws(RR->node->allNetworks());
-    {
-        Mutex::Lock l(_byNet_m);
-        _byNet.clear();
-        for (std::vector<SharedPtr<Network> >::const_iterator n(nws.begin()); n != nws.end(); ++n) {
-            const Address dest((*n)->config().remoteTraceTarget);
-            if (dest) {
-                std::pair<Address, Trace::Level>& m = _byNet[(*n)->id()];
-                m.first = dest;
-                m.second = (*n)->config().remoteTraceLevel;
-            }
-        }
-    }
+	_globalTarget = RR->node->remoteTraceTarget();
+	_globalLevel = RR->node->remoteTraceLevel();
+	const std::vector<SharedPtr<Network> > nws(RR->node->allNetworks());
+	{
+		Mutex::Lock l(_byNet_m);
+		_byNet.clear();
+		for (std::vector<SharedPtr<Network> >::const_iterator n(nws.begin()); n != nws.end(); ++n) {
+			const Address dest((*n)->config().remoteTraceTarget);
+			if (dest) {
+				std::pair<Address, Trace::Level>& m = _byNet[(*n)->id()];
+				m.first = dest;
+				m.second = (*n)->config().remoteTraceLevel;
+			}
+		}
+	}
 }
 
 void Trace::_send(void* const tPtr, const Dictionary<ZT_MAX_REMOTE_TRACE_SIZE>& d, const Address& dest)
 {
-    Packet outp(dest, RR->identity.address(), Packet::VERB_REMOTE_TRACE);
-    outp.appendCString(d.data());
-    outp.compress();
-    RR->sw->send(tPtr, outp, true);
+	Packet outp(dest, RR->identity.address(), Packet::VERB_REMOTE_TRACE);
+	outp.appendCString(d.data());
+	outp.compress();
+	RR->sw->send(tPtr, outp, true);
 }
 
 void Trace::_spamToAllNetworks(void* const tPtr, const Dictionary<ZT_MAX_REMOTE_TRACE_SIZE>& d, const Level level)
 {
-    Mutex::Lock l(_byNet_m);
-    Hashtable<uint64_t, std::pair<Address, Trace::Level> >::Iterator i(_byNet);
-    uint64_t* k = (uint64_t*)0;
-    std::pair<Address, Trace::Level>* v = (std::pair<Address, Trace::Level>*)0;
-    while (i.next(k, v)) {
-        if ((v) && (v->first) && ((int)v->second >= (int)level)) {
-            _send(tPtr, d, v->first);
-        }
-    }
+	Mutex::Lock l(_byNet_m);
+	Hashtable<uint64_t, std::pair<Address, Trace::Level> >::Iterator i(_byNet);
+	uint64_t* k = (uint64_t*)0;
+	std::pair<Address, Trace::Level>* v = (std::pair<Address, Trace::Level>*)0;
+	while (i.next(k, v)) {
+		if ((v) && (v->first) && ((int)v->second >= (int)level)) {
+			_send(tPtr, d, v->first);
+		}
+	}
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 120 - 120
node/Trace.hpp

@@ -50,131 +50,131 @@ class Capability;
  */
 class Trace {
   public:
-    /**
-     * Trace verbosity level
-     */
-    enum Level { LEVEL_NORMAL = 0, LEVEL_VERBOSE = 10, LEVEL_RULES = 15, LEVEL_DEBUG = 20, LEVEL_INSANE = 30 };
-
-    /**
-     * Filter rule evaluation result log
-     *
-     * Each rule in a rule set gets a four-bit log entry. A log entry
-     * of zero means not evaluated. Otherwise each four-bit log entry
-     * contains two two-bit values of 01 for 'false' and 10 for 'true'.
-     * As with four-bit rules an 00 value here means this was not
-     * evaluated or was not relevant.
-     */
-    class RuleResultLog {
-      public:
-        RuleResultLog()
-        {
-        }
-
-        inline void log(const unsigned int rn, const uint8_t thisRuleMatches, const uint8_t thisSetMatches)
-        {
-            _l[rn >> 1] |= (((thisRuleMatches + 1) << 2) | (thisSetMatches + 1)) << ((rn & 1) << 2);
-        }
-        inline void logSkipped(const unsigned int rn, const uint8_t thisSetMatches)
-        {
-            _l[rn >> 1] |= (thisSetMatches + 1) << ((rn & 1) << 2);
-        }
-
-        inline void clear()
-        {
-            memset(_l, 0, sizeof(_l));
-        }
-
-        inline const uint8_t* data() const
-        {
-            return _l;
-        }
-        inline unsigned int sizeBytes() const
-        {
-            return (ZT_MAX_NETWORK_RULES / 2);
-        }
-
-      private:
-        uint8_t _l[ZT_MAX_NETWORK_RULES / 2];
-    };
-
-    Trace(const RuntimeEnvironment* renv) : RR(renv), _byNet(8)
-    {
-    }
-
-    void resettingPathsInScope(void* const tPtr, const Address& reporter, const InetAddress& reporterPhysicalAddress, const InetAddress& myPhysicalAddress, const InetAddress::IpScope scope);
-
-    void peerConfirmingUnknownPath(void* const tPtr, const uint64_t networkId, Peer& peer, const SharedPtr<Path>& path, const uint64_t packetId, const Packet::Verb verb);
-
-    void bondStateMessage(void* const tPtr, char* msg);
-
-    void peerLearnedNewPath(void* const tPtr, const uint64_t networkId, Peer& peer, const SharedPtr<Path>& newPath, const uint64_t packetId);
-    void peerRedirected(void* const tPtr, const uint64_t networkId, Peer& peer, const SharedPtr<Path>& newPath);
-
-    void incomingPacketMessageAuthenticationFailure(void* const tPtr, const SharedPtr<Path>& path, const uint64_t packetId, const Address& source, const unsigned int hops, const char* reason);
-    void incomingPacketInvalid(void* const tPtr, const SharedPtr<Path>& path, const uint64_t packetId, const Address& source, const unsigned int hops, const Packet::Verb verb, const char* reason);
-    void incomingPacketDroppedHELLO(void* const tPtr, const SharedPtr<Path>& path, const uint64_t packetId, const Address& source, const char* reason);
-
-    void outgoingNetworkFrameDropped(void* const tPtr, const SharedPtr<Network>& network, const MAC& sourceMac, const MAC& destMac, const unsigned int etherType, const unsigned int vlanId, const unsigned int frameLen, const char* reason);
-    void incomingNetworkAccessDenied(
-        void* const tPtr,
-        const SharedPtr<Network>& network,
-        const SharedPtr<Path>& path,
-        const uint64_t packetId,
-        const unsigned int packetLength,
-        const Address& source,
-        const Packet::Verb verb,
-        bool credentialsRequested);
-    void incomingNetworkFrameDropped(
-        void* const tPtr,
-        const SharedPtr<Network>& network,
-        const SharedPtr<Path>& path,
-        const uint64_t packetId,
-        const unsigned int packetLength,
-        const Address& source,
-        const Packet::Verb verb,
-        const MAC& sourceMac,
-        const MAC& destMac,
-        const char* reason);
-
-    void networkConfigRequestSent(void* const tPtr, const Network& network, const Address& controller);
-    void networkFilter(
-        void* const tPtr,
-        const Network& network,
-        const RuleResultLog& primaryRuleSetLog,
-        const RuleResultLog* const matchingCapabilityRuleSetLog,
-        const Capability* const matchingCapability,
-        const Address& ztSource,
-        const Address& ztDest,
-        const MAC& macSource,
-        const MAC& macDest,
-        const uint8_t* const frameData,
-        const unsigned int frameLen,
-        const unsigned int etherType,
-        const unsigned int vlanId,
-        const bool noTee,
-        const bool inbound,
-        const int accept);
-
-    void credentialRejected(void* const tPtr, const CertificateOfMembership& c, const char* reason);
-    void credentialRejected(void* const tPtr, const CertificateOfOwnership& c, const char* reason);
-    void credentialRejected(void* const tPtr, const Capability& c, const char* reason);
-    void credentialRejected(void* const tPtr, const Tag& c, const char* reason);
-    void credentialRejected(void* const tPtr, const Revocation& c, const char* reason);
-
-    void updateMemoizedSettings();
+	/**
+	 * Trace verbosity level
+	 */
+	enum Level { LEVEL_NORMAL = 0, LEVEL_VERBOSE = 10, LEVEL_RULES = 15, LEVEL_DEBUG = 20, LEVEL_INSANE = 30 };
+
+	/**
+	 * Filter rule evaluation result log
+	 *
+	 * Each rule in a rule set gets a four-bit log entry. A log entry
+	 * of zero means not evaluated. Otherwise each four-bit log entry
+	 * contains two two-bit values of 01 for 'false' and 10 for 'true'.
+	 * As with four-bit rules an 00 value here means this was not
+	 * evaluated or was not relevant.
+	 */
+	class RuleResultLog {
+	  public:
+		RuleResultLog()
+		{
+		}
+
+		inline void log(const unsigned int rn, const uint8_t thisRuleMatches, const uint8_t thisSetMatches)
+		{
+			_l[rn >> 1] |= (((thisRuleMatches + 1) << 2) | (thisSetMatches + 1)) << ((rn & 1) << 2);
+		}
+		inline void logSkipped(const unsigned int rn, const uint8_t thisSetMatches)
+		{
+			_l[rn >> 1] |= (thisSetMatches + 1) << ((rn & 1) << 2);
+		}
+
+		inline void clear()
+		{
+			memset(_l, 0, sizeof(_l));
+		}
+
+		inline const uint8_t* data() const
+		{
+			return _l;
+		}
+		inline unsigned int sizeBytes() const
+		{
+			return (ZT_MAX_NETWORK_RULES / 2);
+		}
+
+	  private:
+		uint8_t _l[ZT_MAX_NETWORK_RULES / 2];
+	};
+
+	Trace(const RuntimeEnvironment* renv) : RR(renv), _byNet(8)
+	{
+	}
+
+	void resettingPathsInScope(void* const tPtr, const Address& reporter, const InetAddress& reporterPhysicalAddress, const InetAddress& myPhysicalAddress, const InetAddress::IpScope scope);
+
+	void peerConfirmingUnknownPath(void* const tPtr, const uint64_t networkId, Peer& peer, const SharedPtr<Path>& path, const uint64_t packetId, const Packet::Verb verb);
+
+	void bondStateMessage(void* const tPtr, char* msg);
+
+	void peerLearnedNewPath(void* const tPtr, const uint64_t networkId, Peer& peer, const SharedPtr<Path>& newPath, const uint64_t packetId);
+	void peerRedirected(void* const tPtr, const uint64_t networkId, Peer& peer, const SharedPtr<Path>& newPath);
+
+	void incomingPacketMessageAuthenticationFailure(void* const tPtr, const SharedPtr<Path>& path, const uint64_t packetId, const Address& source, const unsigned int hops, const char* reason);
+	void incomingPacketInvalid(void* const tPtr, const SharedPtr<Path>& path, const uint64_t packetId, const Address& source, const unsigned int hops, const Packet::Verb verb, const char* reason);
+	void incomingPacketDroppedHELLO(void* const tPtr, const SharedPtr<Path>& path, const uint64_t packetId, const Address& source, const char* reason);
+
+	void outgoingNetworkFrameDropped(void* const tPtr, const SharedPtr<Network>& network, const MAC& sourceMac, const MAC& destMac, const unsigned int etherType, const unsigned int vlanId, const unsigned int frameLen, const char* reason);
+	void incomingNetworkAccessDenied(
+		void* const tPtr,
+		const SharedPtr<Network>& network,
+		const SharedPtr<Path>& path,
+		const uint64_t packetId,
+		const unsigned int packetLength,
+		const Address& source,
+		const Packet::Verb verb,
+		bool credentialsRequested);
+	void incomingNetworkFrameDropped(
+		void* const tPtr,
+		const SharedPtr<Network>& network,
+		const SharedPtr<Path>& path,
+		const uint64_t packetId,
+		const unsigned int packetLength,
+		const Address& source,
+		const Packet::Verb verb,
+		const MAC& sourceMac,
+		const MAC& destMac,
+		const char* reason);
+
+	void networkConfigRequestSent(void* const tPtr, const Network& network, const Address& controller);
+	void networkFilter(
+		void* const tPtr,
+		const Network& network,
+		const RuleResultLog& primaryRuleSetLog,
+		const RuleResultLog* const matchingCapabilityRuleSetLog,
+		const Capability* const matchingCapability,
+		const Address& ztSource,
+		const Address& ztDest,
+		const MAC& macSource,
+		const MAC& macDest,
+		const uint8_t* const frameData,
+		const unsigned int frameLen,
+		const unsigned int etherType,
+		const unsigned int vlanId,
+		const bool noTee,
+		const bool inbound,
+		const int accept);
+
+	void credentialRejected(void* const tPtr, const CertificateOfMembership& c, const char* reason);
+	void credentialRejected(void* const tPtr, const CertificateOfOwnership& c, const char* reason);
+	void credentialRejected(void* const tPtr, const Capability& c, const char* reason);
+	void credentialRejected(void* const tPtr, const Tag& c, const char* reason);
+	void credentialRejected(void* const tPtr, const Revocation& c, const char* reason);
+
+	void updateMemoizedSettings();
 
   private:
-    const RuntimeEnvironment* const RR;
+	const RuntimeEnvironment* const RR;
 
-    void _send(void* const tPtr, const Dictionary<ZT_MAX_REMOTE_TRACE_SIZE>& d, const Address& dest);
-    void _spamToAllNetworks(void* const tPtr, const Dictionary<ZT_MAX_REMOTE_TRACE_SIZE>& d, const Level level);
+	void _send(void* const tPtr, const Dictionary<ZT_MAX_REMOTE_TRACE_SIZE>& d, const Address& dest);
+	void _spamToAllNetworks(void* const tPtr, const Dictionary<ZT_MAX_REMOTE_TRACE_SIZE>& d, const Level level);
 
-    Address _globalTarget;
-    Trace::Level _globalLevel;
-    Hashtable<uint64_t, std::pair<Address, Trace::Level> > _byNet;
-    Mutex _byNet_m;
+	Address _globalTarget;
+	Trace::Level _globalLevel;
+	Hashtable<uint64_t, std::pair<Address, Trace::Level> > _byNet;
+	Mutex _byNet_m;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 153 - 153
node/Utils.cpp

@@ -64,9 +64,9 @@
 #include <sys/auxv.h>
 static inline long getauxval(int caps)
 {
-    long hwcaps = 0;
-    elf_aux_info(caps, &hwcaps, sizeof(hwcaps));
-    return hwcaps;
+	long hwcaps = 0;
+	elf_aux_info(caps, &hwcaps, sizeof(hwcaps));
+	return hwcaps;
 }
 #endif
 
@@ -87,7 +87,7 @@ static inline long getauxval(int caps)
 #define HWCAP_SHA2 0
 #endif
 
-#endif   // ZT_ARCH_ARM_HAS_NEON
+#endif	 // ZT_ARCH_ARM_HAS_NEON
 
 namespace ZeroTier {
 
@@ -100,36 +100,36 @@ Utils::ARMCapabilities::ARMCapabilities() noexcept
 {
 #ifdef __APPLE__
 
-    this->aes = true;
-    this->crc32 = true;
-    this->pmull = true;
-    this->sha1 = true;
-    this->sha2 = true;
+	this->aes = true;
+	this->crc32 = true;
+	this->pmull = true;
+	this->sha1 = true;
+	this->sha2 = true;
 
 #else
 
 #ifdef HWCAP2_AES
-    if (sizeof(void*) == 4) {
-        const long hwcaps2 = getauxval(AT_HWCAP2);
-        this->aes = (hwcaps2 & HWCAP2_AES) != 0;
-        this->crc32 = (hwcaps2 & HWCAP2_CRC32) != 0;
-        this->pmull = (hwcaps2 & HWCAP2_PMULL) != 0;
-        this->sha1 = (hwcaps2 & HWCAP2_SHA1) != 0;
-        this->sha2 = (hwcaps2 & HWCAP2_SHA2) != 0;
-    }
-    else {
+	if (sizeof(void*) == 4) {
+		const long hwcaps2 = getauxval(AT_HWCAP2);
+		this->aes = (hwcaps2 & HWCAP2_AES) != 0;
+		this->crc32 = (hwcaps2 & HWCAP2_CRC32) != 0;
+		this->pmull = (hwcaps2 & HWCAP2_PMULL) != 0;
+		this->sha1 = (hwcaps2 & HWCAP2_SHA1) != 0;
+		this->sha2 = (hwcaps2 & HWCAP2_SHA2) != 0;
+	}
+	else {
 #endif
-        const long hwcaps = getauxval(AT_HWCAP);
-        this->aes = (hwcaps & HWCAP_AES) != 0;
-        this->crc32 = (hwcaps & HWCAP_CRC32) != 0;
-        this->pmull = (hwcaps & HWCAP_PMULL) != 0;
-        this->sha1 = (hwcaps & HWCAP_SHA1) != 0;
-        this->sha2 = (hwcaps & HWCAP_SHA2) != 0;
+		const long hwcaps = getauxval(AT_HWCAP);
+		this->aes = (hwcaps & HWCAP_AES) != 0;
+		this->crc32 = (hwcaps & HWCAP_CRC32) != 0;
+		this->pmull = (hwcaps & HWCAP_PMULL) != 0;
+		this->sha1 = (hwcaps & HWCAP_SHA1) != 0;
+		this->sha2 = (hwcaps & HWCAP_SHA2) != 0;
 #ifdef HWCAP2_AES
-    }
+	}
 #endif
 
-#endif   // __APPLE__
+#endif	 // __APPLE__
 }
 
 const Utils::ARMCapabilities Utils::ARMCAP;
@@ -139,39 +139,39 @@ const Utils::ARMCapabilities Utils::ARMCAP;
 
 Utils::CPUIDRegisters::CPUIDRegisters() noexcept
 {
-    uint32_t eax, ebx, ecx, edx;
+	uint32_t eax, ebx, ecx, edx;
 
 #ifdef __WINDOWS__
-    int regs[4];
-    __cpuid(regs, 1);
-    eax = (uint32_t)regs[0];
-    ebx = (uint32_t)regs[1];
-    ecx = (uint32_t)regs[2];
-    edx = (uint32_t)regs[3];
+	int regs[4];
+	__cpuid(regs, 1);
+	eax = (uint32_t)regs[0];
+	ebx = (uint32_t)regs[1];
+	ecx = (uint32_t)regs[2];
+	edx = (uint32_t)regs[3];
 #else
-    __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) : "a"(1), "c"(0));
+	__asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) : "a"(1), "c"(0));
 #endif
 
-    rdrand = ((ecx & (1U << 30U)) != 0);
-    aes = (((ecx & (1U << 25U)) != 0) && ((ecx & (1U << 19U)) != 0) && ((ecx & (1U << 1U)) != 0));
-    avx = ((ecx & (1U << 25U)) != 0);
+	rdrand = ((ecx & (1U << 30U)) != 0);
+	aes = (((ecx & (1U << 25U)) != 0) && ((ecx & (1U << 19U)) != 0) && ((ecx & (1U << 1U)) != 0));
+	avx = ((ecx & (1U << 25U)) != 0);
 
 #ifdef __WINDOWS__
-    __cpuid(regs, 7);
-    eax = (uint32_t)regs[0];
-    ebx = (uint32_t)regs[1];
-    ecx = (uint32_t)regs[2];
-    edx = (uint32_t)regs[3];
+	__cpuid(regs, 7);
+	eax = (uint32_t)regs[0];
+	ebx = (uint32_t)regs[1];
+	ecx = (uint32_t)regs[2];
+	edx = (uint32_t)regs[3];
 #else
-    __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) : "a"(7), "c"(0));
+	__asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) : "a"(7), "c"(0));
 #endif
 
-    vaes = aes && avx && ((ecx & (1U << 9U)) != 0);
-    vpclmulqdq = aes && avx && ((ecx & (1U << 10U)) != 0);
-    avx2 = avx && ((ebx & (1U << 5U)) != 0);
-    avx512f = avx && ((ebx & (1U << 16U)) != 0);
-    sha = ((ebx & (1U << 29U)) != 0);
-    fsrm = ((edx & (1U << 4U)) != 0);
+	vaes = aes && avx && ((ecx & (1U << 9U)) != 0);
+	vpclmulqdq = aes && avx && ((ecx & (1U << 10U)) != 0);
+	avx2 = avx && ((ebx & (1U << 5U)) != 0);
+	avx512f = avx && ((ebx & (1U << 16U)) != 0);
+	sha = ((ebx & (1U << 29U)) != 0);
+	fsrm = ((edx & (1U << 4U)) != 0);
 }
 
 const Utils::CPUIDRegisters Utils::CPUID;
@@ -180,127 +180,127 @@ const Utils::CPUIDRegisters Utils::CPUID;
 // Crazy hack to force memory to be securely zeroed in spite of the best efforts of optimizing compilers.
 static void _Utils_doBurn(volatile uint8_t* ptr, unsigned int len)
 {
-    volatile uint8_t* const end = ptr + len;
-    while (ptr != end) {
-        *(ptr++) = (uint8_t)0;
-    }
+	volatile uint8_t* const end = ptr + len;
+	while (ptr != end) {
+		*(ptr++) = (uint8_t)0;
+	}
 }
 static void (*volatile _Utils_doBurn_ptr)(volatile uint8_t*, unsigned int) = _Utils_doBurn;
 void Utils::burn(void* ptr, unsigned int len)
 {
-    (_Utils_doBurn_ptr)((volatile uint8_t*)ptr, len);
+	(_Utils_doBurn_ptr)((volatile uint8_t*)ptr, len);
 }
 
 static unsigned long _Utils_itoa(unsigned long n, char* s)
 {
-    if (n == 0) {
-        return 0;
-    }
-    unsigned long pos = _Utils_itoa(n / 10, s);
-    if (pos >= 22) {   // sanity check, should be impossible
-        pos = 22;
-    }
-    s[pos] = '0' + (char)(n % 10);
-    return pos + 1;
+	if (n == 0) {
+		return 0;
+	}
+	unsigned long pos = _Utils_itoa(n / 10, s);
+	if (pos >= 22) {   // sanity check, should be impossible
+		pos = 22;
+	}
+	s[pos] = '0' + (char)(n % 10);
+	return pos + 1;
 }
 char* Utils::decimal(unsigned long n, char s[24])
 {
-    if (n == 0) {
-        s[0] = '0';
-        s[1] = (char)0;
-        return s;
-    }
-    s[_Utils_itoa(n, s)] = (char)0;
-    return s;
+	if (n == 0) {
+		s[0] = '0';
+		s[1] = (char)0;
+		return s;
+	}
+	s[_Utils_itoa(n, s)] = (char)0;
+	return s;
 }
 
 void Utils::getSecureRandom(void* buf, unsigned int bytes)
 {
-    static Mutex globalLock;
-    static Salsa20 s20;
-    static bool s20Initialized = false;
-    static uint8_t randomBuf[65536];
-    static unsigned int randomPtr = sizeof(randomBuf);
-
-    Mutex::Lock _l(globalLock);
-
-    /* Just for posterity we Salsa20 encrypt the result of whatever system
-     * CSPRNG we use. There have been several bugs at the OS or OS distribution
-     * level in the past that resulted in systematically weak or predictable
-     * keys due to random seeding problems. This mitigates that by grabbing
-     * a bit of extra entropy and further randomizing the result, and comes
-     * at almost no cost and with no real downside if the random source is
-     * good. */
-    if (! s20Initialized) {
-        s20Initialized = true;
-        uint64_t s20Key[4];
-        s20Key[0] = (uint64_t)time(0);   // system clock
-        s20Key[1] = (uint64_t)buf;       // address of buf
-        s20Key[2] = (uint64_t)s20Key;    // address of s20Key[]
-        s20Key[3] = (uint64_t)&s20;      // address of s20
-        s20.init(s20Key, s20Key);
-    }
+	static Mutex globalLock;
+	static Salsa20 s20;
+	static bool s20Initialized = false;
+	static uint8_t randomBuf[65536];
+	static unsigned int randomPtr = sizeof(randomBuf);
+
+	Mutex::Lock _l(globalLock);
+
+	/* Just for posterity we Salsa20 encrypt the result of whatever system
+	 * CSPRNG we use. There have been several bugs at the OS or OS distribution
+	 * level in the past that resulted in systematically weak or predictable
+	 * keys due to random seeding problems. This mitigates that by grabbing
+	 * a bit of extra entropy and further randomizing the result, and comes
+	 * at almost no cost and with no real downside if the random source is
+	 * good. */
+	if (! s20Initialized) {
+		s20Initialized = true;
+		uint64_t s20Key[4];
+		s20Key[0] = (uint64_t)time(0);	 // system clock
+		s20Key[1] = (uint64_t)buf;		 // address of buf
+		s20Key[2] = (uint64_t)s20Key;	 // address of s20Key[]
+		s20Key[3] = (uint64_t)&s20;		 // address of s20
+		s20.init(s20Key, s20Key);
+	}
 
 #ifdef __WINDOWS__
 
-    static HCRYPTPROV cryptProvider = NULL;
-
-    for (unsigned int i = 0; i < bytes; ++i) {
-        if (randomPtr >= sizeof(randomBuf)) {
-            if (cryptProvider == NULL) {
-                if (! CryptAcquireContextA(&cryptProvider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) {
-                    fprintf(stderr, "FATAL ERROR: Utils::getSecureRandom() unable to obtain WinCrypt context!\r\n");
-                    exit(1);
-                }
-            }
-            if (! CryptGenRandom(cryptProvider, (DWORD)sizeof(randomBuf), (BYTE*)randomBuf)) {
-                fprintf(stderr, "FATAL ERROR: Utils::getSecureRandom() CryptGenRandom failed!\r\n");
-                exit(1);
-            }
-            randomPtr = 0;
-            s20.crypt12(randomBuf, randomBuf, sizeof(randomBuf));
-            s20.init(randomBuf, randomBuf);
-        }
-        ((uint8_t*)buf)[i] = randomBuf[randomPtr++];
-    }
-
-#else   // not __WINDOWS__
-
-    static int devURandomFd = -1;
-
-    if (devURandomFd < 0) {
-        devURandomFd = ::open("/dev/urandom", O_RDONLY);
-        if (devURandomFd < 0) {
-            fprintf(stderr, "FATAL ERROR: Utils::getSecureRandom() unable to open /dev/urandom\n");
-            exit(1);
-            return;
-        }
-    }
-
-    for (unsigned int i = 0; i < bytes; ++i) {
-        if (randomPtr >= sizeof(randomBuf)) {
-            for (;;) {
-                if ((int)::read(devURandomFd, randomBuf, sizeof(randomBuf)) != (int)sizeof(randomBuf)) {
-                    ::close(devURandomFd);
-                    devURandomFd = ::open("/dev/urandom", O_RDONLY);
-                    if (devURandomFd < 0) {
-                        fprintf(stderr, "FATAL ERROR: Utils::getSecureRandom() unable to open /dev/urandom\n");
-                        exit(1);
-                        return;
-                    }
-                }
-                else {
-                    break;
-                }
-            }
-            randomPtr = 0;
-            s20.crypt12(randomBuf, randomBuf, sizeof(randomBuf));
-            s20.init(randomBuf, randomBuf);
-        }
-        ((uint8_t*)buf)[i] = randomBuf[randomPtr++];
-    }
-
-#endif   // __WINDOWS__ or not
+	static HCRYPTPROV cryptProvider = NULL;
+
+	for (unsigned int i = 0; i < bytes; ++i) {
+		if (randomPtr >= sizeof(randomBuf)) {
+			if (cryptProvider == NULL) {
+				if (! CryptAcquireContextA(&cryptProvider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) {
+					fprintf(stderr, "FATAL ERROR: Utils::getSecureRandom() unable to obtain WinCrypt context!\r\n");
+					exit(1);
+				}
+			}
+			if (! CryptGenRandom(cryptProvider, (DWORD)sizeof(randomBuf), (BYTE*)randomBuf)) {
+				fprintf(stderr, "FATAL ERROR: Utils::getSecureRandom() CryptGenRandom failed!\r\n");
+				exit(1);
+			}
+			randomPtr = 0;
+			s20.crypt12(randomBuf, randomBuf, sizeof(randomBuf));
+			s20.init(randomBuf, randomBuf);
+		}
+		((uint8_t*)buf)[i] = randomBuf[randomPtr++];
+	}
+
+#else	// not __WINDOWS__
+
+	static int devURandomFd = -1;
+
+	if (devURandomFd < 0) {
+		devURandomFd = ::open("/dev/urandom", O_RDONLY);
+		if (devURandomFd < 0) {
+			fprintf(stderr, "FATAL ERROR: Utils::getSecureRandom() unable to open /dev/urandom\n");
+			exit(1);
+			return;
+		}
+	}
+
+	for (unsigned int i = 0; i < bytes; ++i) {
+		if (randomPtr >= sizeof(randomBuf)) {
+			for (;;) {
+				if ((int)::read(devURandomFd, randomBuf, sizeof(randomBuf)) != (int)sizeof(randomBuf)) {
+					::close(devURandomFd);
+					devURandomFd = ::open("/dev/urandom", O_RDONLY);
+					if (devURandomFd < 0) {
+						fprintf(stderr, "FATAL ERROR: Utils::getSecureRandom() unable to open /dev/urandom\n");
+						exit(1);
+						return;
+					}
+				}
+				else {
+					break;
+				}
+			}
+			randomPtr = 0;
+			s20.crypt12(randomBuf, randomBuf, sizeof(randomBuf));
+			s20.init(randomBuf, randomBuf);
+		}
+		((uint8_t*)buf)[i] = randomBuf[randomPtr++];
+	}
+
+#endif	 // __WINDOWS__ or not
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 739 - 739
node/Utils.hpp

@@ -34,8 +34,8 @@
 #if __BYTE_ORDER == __LITTLE_ENDIAN
 #define ZT_CONST_TO_BE_UINT16(x) ((uint16_t)((uint16_t)((uint16_t)(x) << 8U) | (uint16_t)((uint16_t)(x) >> 8U)))
 #define ZT_CONST_TO_BE_UINT64(x)                                                                                                                                                                                                               \
-    ((((uint64_t)(x) & 0x00000000000000ffULL) << 56U) | (((uint64_t)(x) & 0x000000000000ff00ULL) << 40U) | (((uint64_t)(x) & 0x0000000000ff0000ULL) << 24U) | (((uint64_t)(x) & 0x00000000ff000000ULL) << 8U)                                  \
-     | (((uint64_t)(x) & 0x000000ff00000000ULL) >> 8U) | (((uint64_t)(x) & 0x0000ff0000000000ULL) >> 24U) | (((uint64_t)(x) & 0x00ff000000000000ULL) >> 40U) | (((uint64_t)(x) & 0xff00000000000000ULL) >> 56U))
+	((((uint64_t)(x) & 0x00000000000000ffULL) << 56U) | (((uint64_t)(x) & 0x000000000000ff00ULL) << 40U) | (((uint64_t)(x) & 0x0000000000ff0000ULL) << 24U) | (((uint64_t)(x) & 0x00000000ff000000ULL) << 8U)                                  \
+	 | (((uint64_t)(x) & 0x000000ff00000000ULL) >> 8U) | (((uint64_t)(x) & 0x0000ff0000000000ULL) >> 24U) | (((uint64_t)(x) & 0x00ff000000000000ULL) >> 40U) | (((uint64_t)(x) & 0xff00000000000000ULL) >> 56U))
 #else
 #define ZT_CONST_TO_BE_UINT16(x) ((uint16_t)(x))
 #define ZT_CONST_TO_BE_UINT64(x) ((uint64_t)(x))
@@ -53,823 +53,823 @@ namespace ZeroTier {
  */
 class Utils {
   public:
-    static const uint64_t ZERO256[4];
+	static const uint64_t ZERO256[4];
 
 #ifdef ZT_ARCH_ARM_HAS_NEON
-    struct ARMCapabilities {
-        ARMCapabilities() noexcept;
-
-        bool aes;
-        bool crc32;
-        bool pmull;
-        bool sha1;
-        bool sha2;
-    };
-    static const ARMCapabilities ARMCAP;
+	struct ARMCapabilities {
+		ARMCapabilities() noexcept;
+
+		bool aes;
+		bool crc32;
+		bool pmull;
+		bool sha1;
+		bool sha2;
+	};
+	static const ARMCapabilities ARMCAP;
 #endif
 
 #ifdef ZT_ARCH_X64
-    struct CPUIDRegisters {
-        CPUIDRegisters() noexcept;
-
-        bool rdrand;
-        bool aes;
-        bool avx;
-        bool vaes;         // implies AVX
-        bool vpclmulqdq;   // implies AVX
-        bool avx2;
-        bool avx512f;
-        bool sha;
-        bool fsrm;
-    };
-    static const CPUIDRegisters CPUID;
+	struct CPUIDRegisters {
+		CPUIDRegisters() noexcept;
+
+		bool rdrand;
+		bool aes;
+		bool avx;
+		bool vaes;		   // implies AVX
+		bool vpclmulqdq;   // implies AVX
+		bool avx2;
+		bool avx512f;
+		bool sha;
+		bool fsrm;
+	};
+	static const CPUIDRegisters CPUID;
 #endif
 
-    /**
-     * Compute the log2 (most significant bit set) of a 32-bit integer
-     *
-     * @param v Integer to compute
-     * @return log2 or 0 if v is 0
-     */
-    static inline unsigned int log2(uint32_t v)
-    {
-        uint32_t r = (v > 0xffff) << 4;
-        v >>= r;
-        uint32_t shift = (v > 0xff) << 3;
-        v >>= shift;
-        r |= shift;
-        shift = (v > 0xf) << 2;
-        v >>= shift;
-        r |= shift;
-        shift = (v > 0x3) << 1;
-        v >>= shift;
-        r |= shift;
-        r |= (v >> 1);
-        return (unsigned int)r;
-    }
-
-    /**
-     * Perform a time-invariant binary comparison
-     *
-     * @param a First binary string
-     * @param b Second binary string
-     * @param len Length of strings
-     * @return True if strings are equal
-     */
-    static inline bool secureEq(const void* a, const void* b, unsigned int len)
-    {
-        uint8_t diff = 0;
-        for (unsigned int i = 0; i < len; ++i) {
-            diff |= ((reinterpret_cast<const uint8_t*>(a))[i] ^ (reinterpret_cast<const uint8_t*>(b))[i]);
-        }
-        return (diff == 0);
-    }
-
-    /**
-     * Securely zero memory, avoiding compiler optimizations and such
-     */
-    static void burn(void* ptr, unsigned int len);
-
-    /**
-     * @param n Number to convert
-     * @param s Buffer, at least 24 bytes in size
-     * @return String containing 'n' in base 10 form
-     */
-    static char* decimal(unsigned long n, char s[24]);
-
-    static inline char* hex(uint64_t i, char s[17])
-    {
-        s[0] = HEXCHARS[(i >> 60) & 0xf];
-        s[1] = HEXCHARS[(i >> 56) & 0xf];
-        s[2] = HEXCHARS[(i >> 52) & 0xf];
-        s[3] = HEXCHARS[(i >> 48) & 0xf];
-        s[4] = HEXCHARS[(i >> 44) & 0xf];
-        s[5] = HEXCHARS[(i >> 40) & 0xf];
-        s[6] = HEXCHARS[(i >> 36) & 0xf];
-        s[7] = HEXCHARS[(i >> 32) & 0xf];
-        s[8] = HEXCHARS[(i >> 28) & 0xf];
-        s[9] = HEXCHARS[(i >> 24) & 0xf];
-        s[10] = HEXCHARS[(i >> 20) & 0xf];
-        s[11] = HEXCHARS[(i >> 16) & 0xf];
-        s[12] = HEXCHARS[(i >> 12) & 0xf];
-        s[13] = HEXCHARS[(i >> 8) & 0xf];
-        s[14] = HEXCHARS[(i >> 4) & 0xf];
-        s[15] = HEXCHARS[i & 0xf];
-        s[16] = (char)0;
-        return s;
-    }
-
-    static inline char* hex10(uint64_t i, char s[11])
-    {
-        s[0] = HEXCHARS[(i >> 36) & 0xf];
-        s[1] = HEXCHARS[(i >> 32) & 0xf];
-        s[2] = HEXCHARS[(i >> 28) & 0xf];
-        s[3] = HEXCHARS[(i >> 24) & 0xf];
-        s[4] = HEXCHARS[(i >> 20) & 0xf];
-        s[5] = HEXCHARS[(i >> 16) & 0xf];
-        s[6] = HEXCHARS[(i >> 12) & 0xf];
-        s[7] = HEXCHARS[(i >> 8) & 0xf];
-        s[8] = HEXCHARS[(i >> 4) & 0xf];
-        s[9] = HEXCHARS[i & 0xf];
-        s[10] = (char)0;
-        return s;
-    }
-
-    static inline char* hex(uint32_t i, char s[9])
-    {
-        s[0] = HEXCHARS[(i >> 28) & 0xf];
-        s[1] = HEXCHARS[(i >> 24) & 0xf];
-        s[2] = HEXCHARS[(i >> 20) & 0xf];
-        s[3] = HEXCHARS[(i >> 16) & 0xf];
-        s[4] = HEXCHARS[(i >> 12) & 0xf];
-        s[5] = HEXCHARS[(i >> 8) & 0xf];
-        s[6] = HEXCHARS[(i >> 4) & 0xf];
-        s[7] = HEXCHARS[i & 0xf];
-        s[8] = (char)0;
-        return s;
-    }
-
-    static inline char* hex(uint16_t i, char s[5])
-    {
-        s[0] = HEXCHARS[(i >> 12) & 0xf];
-        s[1] = HEXCHARS[(i >> 8) & 0xf];
-        s[2] = HEXCHARS[(i >> 4) & 0xf];
-        s[3] = HEXCHARS[i & 0xf];
-        s[4] = (char)0;
-        return s;
-    }
-
-    static inline char* hex(uint8_t i, char s[3])
-    {
-        s[0] = HEXCHARS[(i >> 4) & 0xf];
-        s[1] = HEXCHARS[i & 0xf];
-        s[2] = (char)0;
-        return s;
-    }
-
-    static inline char* hex(const void* d, unsigned int l, char* s)
-    {
-        char* const save = s;
-        for (unsigned int i = 0; i < l; ++i) {
-            const unsigned int b = reinterpret_cast<const uint8_t*>(d)[i];
-            *(s++) = HEXCHARS[b >> 4];
-            *(s++) = HEXCHARS[b & 0xf];
-        }
-        *s = (char)0;
-        return save;
-    }
-
-    static inline unsigned int unhex(const char* h, void* buf, unsigned int buflen)
-    {
-        unsigned int l = 0;
-        while (l < buflen) {
-            uint8_t hc = *(reinterpret_cast<const uint8_t*>(h++));
-            if (! hc) {
-                break;
-            }
-
-            uint8_t c = 0;
-            if ((hc >= 48) && (hc <= 57)) {   // 0..9
-                c = hc - 48;
-            }
-            else if ((hc >= 97) && (hc <= 102)) {   // a..f
-                c = hc - 87;
-            }
-            else if ((hc >= 65) && (hc <= 70)) {   // A..F
-                c = hc - 55;
-            }
-
-            hc = *(reinterpret_cast<const uint8_t*>(h++));
-            if (! hc) {
-                break;
-            }
-
-            c <<= 4;
-            if ((hc >= 48) && (hc <= 57)) {
-                c |= hc - 48;
-            }
-            else if ((hc >= 97) && (hc <= 102)) {
-                c |= hc - 87;
-            }
-            else if ((hc >= 65) && (hc <= 70)) {
-                c |= hc - 55;
-            }
-
-            reinterpret_cast<uint8_t*>(buf)[l++] = c;
-        }
-        return l;
-    }
-
-    static inline unsigned int unhex(const char* h, unsigned int hlen, void* buf, unsigned int buflen)
-    {
-        unsigned int l = 0;
-        const char* hend = h + hlen;
-        while (l < buflen) {
-            if (h == hend) {
-                break;
-            }
-            uint8_t hc = *(reinterpret_cast<const uint8_t*>(h++));
-            if (! hc) {
-                break;
-            }
-
-            uint8_t c = 0;
-            if ((hc >= 48) && (hc <= 57)) {
-                c = hc - 48;
-            }
-            else if ((hc >= 97) && (hc <= 102)) {
-                c = hc - 87;
-            }
-            else if ((hc >= 65) && (hc <= 70)) {
-                c = hc - 55;
-            }
-
-            if (h == hend) {
-                break;
-            }
-            hc = *(reinterpret_cast<const uint8_t*>(h++));
-            if (! hc) {
-                break;
-            }
-
-            c <<= 4;
-            if ((hc >= 48) && (hc <= 57)) {
-                c |= hc - 48;
-            }
-            else if ((hc >= 97) && (hc <= 102)) {
-                c |= hc - 87;
-            }
-            else if ((hc >= 65) && (hc <= 70)) {
-                c |= hc - 55;
-            }
-
-            reinterpret_cast<uint8_t*>(buf)[l++] = c;
-        }
-        return l;
-    }
-
-    static inline float normalize(float value, float bigMin, float bigMax, float targetMin, float targetMax)
-    {
-        float bigSpan = bigMax - bigMin;
-        float smallSpan = targetMax - targetMin;
-        float valueScaled = (value - bigMin) / bigSpan;
-        return targetMin + valueScaled * smallSpan;
-    }
-
-    /**
-     * Generate secure random bytes
-     *
-     * This will try to use whatever OS sources of entropy are available. It's
-     * guarded by an internal mutex so it's thread-safe.
-     *
-     * @param buf Buffer to fill
-     * @param bytes Number of random bytes to generate
-     */
-    static void getSecureRandom(void* buf, unsigned int bytes);
-
-    /**
-     * Tokenize a string (alias for strtok_r or strtok_s depending on platform)
-     *
-     * @param str String to split
-     * @param delim Delimiters
-     * @param saveptr Pointer to a char * for temporary reentrant storage
-     */
-    static inline char* stok(char* str, const char* delim, char** saveptr)
-    {
+	/**
+	 * Compute the log2 (most significant bit set) of a 32-bit integer
+	 *
+	 * @param v Integer to compute
+	 * @return log2 or 0 if v is 0
+	 */
+	static inline unsigned int log2(uint32_t v)
+	{
+		uint32_t r = (v > 0xffff) << 4;
+		v >>= r;
+		uint32_t shift = (v > 0xff) << 3;
+		v >>= shift;
+		r |= shift;
+		shift = (v > 0xf) << 2;
+		v >>= shift;
+		r |= shift;
+		shift = (v > 0x3) << 1;
+		v >>= shift;
+		r |= shift;
+		r |= (v >> 1);
+		return (unsigned int)r;
+	}
+
+	/**
+	 * Perform a time-invariant binary comparison
+	 *
+	 * @param a First binary string
+	 * @param b Second binary string
+	 * @param len Length of strings
+	 * @return True if strings are equal
+	 */
+	static inline bool secureEq(const void* a, const void* b, unsigned int len)
+	{
+		uint8_t diff = 0;
+		for (unsigned int i = 0; i < len; ++i) {
+			diff |= ((reinterpret_cast<const uint8_t*>(a))[i] ^ (reinterpret_cast<const uint8_t*>(b))[i]);
+		}
+		return (diff == 0);
+	}
+
+	/**
+	 * Securely zero memory, avoiding compiler optimizations and such
+	 */
+	static void burn(void* ptr, unsigned int len);
+
+	/**
+	 * @param n Number to convert
+	 * @param s Buffer, at least 24 bytes in size
+	 * @return String containing 'n' in base 10 form
+	 */
+	static char* decimal(unsigned long n, char s[24]);
+
+	static inline char* hex(uint64_t i, char s[17])
+	{
+		s[0] = HEXCHARS[(i >> 60) & 0xf];
+		s[1] = HEXCHARS[(i >> 56) & 0xf];
+		s[2] = HEXCHARS[(i >> 52) & 0xf];
+		s[3] = HEXCHARS[(i >> 48) & 0xf];
+		s[4] = HEXCHARS[(i >> 44) & 0xf];
+		s[5] = HEXCHARS[(i >> 40) & 0xf];
+		s[6] = HEXCHARS[(i >> 36) & 0xf];
+		s[7] = HEXCHARS[(i >> 32) & 0xf];
+		s[8] = HEXCHARS[(i >> 28) & 0xf];
+		s[9] = HEXCHARS[(i >> 24) & 0xf];
+		s[10] = HEXCHARS[(i >> 20) & 0xf];
+		s[11] = HEXCHARS[(i >> 16) & 0xf];
+		s[12] = HEXCHARS[(i >> 12) & 0xf];
+		s[13] = HEXCHARS[(i >> 8) & 0xf];
+		s[14] = HEXCHARS[(i >> 4) & 0xf];
+		s[15] = HEXCHARS[i & 0xf];
+		s[16] = (char)0;
+		return s;
+	}
+
+	static inline char* hex10(uint64_t i, char s[11])
+	{
+		s[0] = HEXCHARS[(i >> 36) & 0xf];
+		s[1] = HEXCHARS[(i >> 32) & 0xf];
+		s[2] = HEXCHARS[(i >> 28) & 0xf];
+		s[3] = HEXCHARS[(i >> 24) & 0xf];
+		s[4] = HEXCHARS[(i >> 20) & 0xf];
+		s[5] = HEXCHARS[(i >> 16) & 0xf];
+		s[6] = HEXCHARS[(i >> 12) & 0xf];
+		s[7] = HEXCHARS[(i >> 8) & 0xf];
+		s[8] = HEXCHARS[(i >> 4) & 0xf];
+		s[9] = HEXCHARS[i & 0xf];
+		s[10] = (char)0;
+		return s;
+	}
+
+	static inline char* hex(uint32_t i, char s[9])
+	{
+		s[0] = HEXCHARS[(i >> 28) & 0xf];
+		s[1] = HEXCHARS[(i >> 24) & 0xf];
+		s[2] = HEXCHARS[(i >> 20) & 0xf];
+		s[3] = HEXCHARS[(i >> 16) & 0xf];
+		s[4] = HEXCHARS[(i >> 12) & 0xf];
+		s[5] = HEXCHARS[(i >> 8) & 0xf];
+		s[6] = HEXCHARS[(i >> 4) & 0xf];
+		s[7] = HEXCHARS[i & 0xf];
+		s[8] = (char)0;
+		return s;
+	}
+
+	static inline char* hex(uint16_t i, char s[5])
+	{
+		s[0] = HEXCHARS[(i >> 12) & 0xf];
+		s[1] = HEXCHARS[(i >> 8) & 0xf];
+		s[2] = HEXCHARS[(i >> 4) & 0xf];
+		s[3] = HEXCHARS[i & 0xf];
+		s[4] = (char)0;
+		return s;
+	}
+
+	static inline char* hex(uint8_t i, char s[3])
+	{
+		s[0] = HEXCHARS[(i >> 4) & 0xf];
+		s[1] = HEXCHARS[i & 0xf];
+		s[2] = (char)0;
+		return s;
+	}
+
+	static inline char* hex(const void* d, unsigned int l, char* s)
+	{
+		char* const save = s;
+		for (unsigned int i = 0; i < l; ++i) {
+			const unsigned int b = reinterpret_cast<const uint8_t*>(d)[i];
+			*(s++) = HEXCHARS[b >> 4];
+			*(s++) = HEXCHARS[b & 0xf];
+		}
+		*s = (char)0;
+		return save;
+	}
+
+	static inline unsigned int unhex(const char* h, void* buf, unsigned int buflen)
+	{
+		unsigned int l = 0;
+		while (l < buflen) {
+			uint8_t hc = *(reinterpret_cast<const uint8_t*>(h++));
+			if (! hc) {
+				break;
+			}
+
+			uint8_t c = 0;
+			if ((hc >= 48) && (hc <= 57)) {	  // 0..9
+				c = hc - 48;
+			}
+			else if ((hc >= 97) && (hc <= 102)) {	// a..f
+				c = hc - 87;
+			}
+			else if ((hc >= 65) && (hc <= 70)) {   // A..F
+				c = hc - 55;
+			}
+
+			hc = *(reinterpret_cast<const uint8_t*>(h++));
+			if (! hc) {
+				break;
+			}
+
+			c <<= 4;
+			if ((hc >= 48) && (hc <= 57)) {
+				c |= hc - 48;
+			}
+			else if ((hc >= 97) && (hc <= 102)) {
+				c |= hc - 87;
+			}
+			else if ((hc >= 65) && (hc <= 70)) {
+				c |= hc - 55;
+			}
+
+			reinterpret_cast<uint8_t*>(buf)[l++] = c;
+		}
+		return l;
+	}
+
+	static inline unsigned int unhex(const char* h, unsigned int hlen, void* buf, unsigned int buflen)
+	{
+		unsigned int l = 0;
+		const char* hend = h + hlen;
+		while (l < buflen) {
+			if (h == hend) {
+				break;
+			}
+			uint8_t hc = *(reinterpret_cast<const uint8_t*>(h++));
+			if (! hc) {
+				break;
+			}
+
+			uint8_t c = 0;
+			if ((hc >= 48) && (hc <= 57)) {
+				c = hc - 48;
+			}
+			else if ((hc >= 97) && (hc <= 102)) {
+				c = hc - 87;
+			}
+			else if ((hc >= 65) && (hc <= 70)) {
+				c = hc - 55;
+			}
+
+			if (h == hend) {
+				break;
+			}
+			hc = *(reinterpret_cast<const uint8_t*>(h++));
+			if (! hc) {
+				break;
+			}
+
+			c <<= 4;
+			if ((hc >= 48) && (hc <= 57)) {
+				c |= hc - 48;
+			}
+			else if ((hc >= 97) && (hc <= 102)) {
+				c |= hc - 87;
+			}
+			else if ((hc >= 65) && (hc <= 70)) {
+				c |= hc - 55;
+			}
+
+			reinterpret_cast<uint8_t*>(buf)[l++] = c;
+		}
+		return l;
+	}
+
+	static inline float normalize(float value, float bigMin, float bigMax, float targetMin, float targetMax)
+	{
+		float bigSpan = bigMax - bigMin;
+		float smallSpan = targetMax - targetMin;
+		float valueScaled = (value - bigMin) / bigSpan;
+		return targetMin + valueScaled * smallSpan;
+	}
+
+	/**
+	 * Generate secure random bytes
+	 *
+	 * This will try to use whatever OS sources of entropy are available. It's
+	 * guarded by an internal mutex so it's thread-safe.
+	 *
+	 * @param buf Buffer to fill
+	 * @param bytes Number of random bytes to generate
+	 */
+	static void getSecureRandom(void* buf, unsigned int bytes);
+
+	/**
+	 * Tokenize a string (alias for strtok_r or strtok_s depending on platform)
+	 *
+	 * @param str String to split
+	 * @param delim Delimiters
+	 * @param saveptr Pointer to a char * for temporary reentrant storage
+	 */
+	static inline char* stok(char* str, const char* delim, char** saveptr)
+	{
 #ifdef __WINDOWS__
-        return strtok_s(str, delim, saveptr);
+		return strtok_s(str, delim, saveptr);
 #else
-        return strtok_r(str, delim, saveptr);
+		return strtok_r(str, delim, saveptr);
 #endif
-    }
-
-    static inline unsigned int strToUInt(const char* s)
-    {
-        return (unsigned int)strtoul(s, (char**)0, 10);
-    }
-    static inline int strToInt(const char* s)
-    {
-        return (int)strtol(s, (char**)0, 10);
-    }
-    static inline unsigned long strToULong(const char* s)
-    {
-        return strtoul(s, (char**)0, 10);
-    }
-    static inline long strToLong(const char* s)
-    {
-        return strtol(s, (char**)0, 10);
-    }
-    static inline double strToDouble(const char* s)
-    {
-        return strtod(s, NULL);
-    }
-    static inline unsigned long long strToU64(const char* s)
-    {
+	}
+
+	static inline unsigned int strToUInt(const char* s)
+	{
+		return (unsigned int)strtoul(s, (char**)0, 10);
+	}
+	static inline int strToInt(const char* s)
+	{
+		return (int)strtol(s, (char**)0, 10);
+	}
+	static inline unsigned long strToULong(const char* s)
+	{
+		return strtoul(s, (char**)0, 10);
+	}
+	static inline long strToLong(const char* s)
+	{
+		return strtol(s, (char**)0, 10);
+	}
+	static inline double strToDouble(const char* s)
+	{
+		return strtod(s, NULL);
+	}
+	static inline unsigned long long strToU64(const char* s)
+	{
 #ifdef __WINDOWS__
-        return (unsigned long long)_strtoui64(s, (char**)0, 10);
+		return (unsigned long long)_strtoui64(s, (char**)0, 10);
 #else
-        return strtoull(s, (char**)0, 10);
+		return strtoull(s, (char**)0, 10);
 #endif
-    }
-    static inline long long strTo64(const char* s)
-    {
+	}
+	static inline long long strTo64(const char* s)
+	{
 #ifdef __WINDOWS__
-        return (long long)_strtoi64(s, (char**)0, 10);
+		return (long long)_strtoi64(s, (char**)0, 10);
 #else
-        return strtoll(s, (char**)0, 10);
+		return strtoll(s, (char**)0, 10);
 #endif
-    }
-    static inline unsigned int hexStrToUInt(const char* s)
-    {
-        return (unsigned int)strtoul(s, (char**)0, 16);
-    }
-    static inline int hexStrToInt(const char* s)
-    {
-        return (int)strtol(s, (char**)0, 16);
-    }
-    static inline unsigned long hexStrToULong(const char* s)
-    {
-        return strtoul(s, (char**)0, 16);
-    }
-    static inline long hexStrToLong(const char* s)
-    {
-        return strtol(s, (char**)0, 16);
-    }
-    static inline unsigned long long hexStrToU64(const char* s)
-    {
+	}
+	static inline unsigned int hexStrToUInt(const char* s)
+	{
+		return (unsigned int)strtoul(s, (char**)0, 16);
+	}
+	static inline int hexStrToInt(const char* s)
+	{
+		return (int)strtol(s, (char**)0, 16);
+	}
+	static inline unsigned long hexStrToULong(const char* s)
+	{
+		return strtoul(s, (char**)0, 16);
+	}
+	static inline long hexStrToLong(const char* s)
+	{
+		return strtol(s, (char**)0, 16);
+	}
+	static inline unsigned long long hexStrToU64(const char* s)
+	{
 #ifdef __WINDOWS__
-        return (unsigned long long)_strtoui64(s, (char**)0, 16);
+		return (unsigned long long)_strtoui64(s, (char**)0, 16);
 #else
-        return strtoull(s, (char**)0, 16);
+		return strtoull(s, (char**)0, 16);
 #endif
-    }
-    static inline long long hexStrTo64(const char* s)
-    {
+	}
+	static inline long long hexStrTo64(const char* s)
+	{
 #ifdef __WINDOWS__
-        return (long long)_strtoi64(s, (char**)0, 16);
+		return (long long)_strtoi64(s, (char**)0, 16);
 #else
-        return strtoll(s, (char**)0, 16);
+		return strtoll(s, (char**)0, 16);
 #endif
-    }
-
-    /**
-     * Perform a safe C string copy, ALWAYS null-terminating the result
-     *
-     * This will never ever EVER result in dest[] not being null-terminated
-     * regardless of any input parameter (other than len==0 which is invalid).
-     *
-     * @param dest Destination buffer (must not be NULL)
-     * @param len Length of dest[] (if zero, false is returned and nothing happens)
-     * @param src Source string (if NULL, dest will receive a zero-length string and true is returned)
-     * @return True on success, false on overflow (buffer will still be 0-terminated)
-     */
-    static inline bool scopy(char* dest, unsigned int len, const char* src)
-    {
-        if (! len) {
-            return false;   // sanity check
-        }
-        if (! src) {
-            *dest = (char)0;
-            return true;
-        }
-        char* end = dest + len;
-        while ((*dest++ = *src++)) {
-            if (dest == end) {
-                *(--dest) = (char)0;
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Count the number of bits set in an integer
-     *
-     * @param v 32-bit integer
-     * @return Number of bits set in this integer (0-32)
-     */
-    static inline uint32_t countBits(uint32_t v)
-    {
-        v = v - ((v >> 1) & (uint32_t)0x55555555);
-        v = (v & (uint32_t)0x33333333) + ((v >> 2) & (uint32_t)0x33333333);
-        return ((((v + (v >> 4)) & (uint32_t)0xF0F0F0F) * (uint32_t)0x1010101) >> 24);
-    }
-
-    /**
-     * Count the number of bits set in an integer
-     *
-     * @param v 64-bit integer
-     * @return Number of bits set in this integer (0-64)
-     */
-    static inline uint64_t countBits(uint64_t v)
-    {
-        v = v - ((v >> 1) & (uint64_t)~(uint64_t)0 / 3);
-        v = (v & (uint64_t)~(uint64_t)0 / 15 * 3) + ((v >> 2) & (uint64_t)~(uint64_t)0 / 15 * 3);
-        v = (v + (v >> 4)) & (uint64_t)~(uint64_t)0 / 255 * 15;
-        return (uint64_t)(v * ((uint64_t)~(uint64_t)0 / 255)) >> 56;
-    }
-
-    /**
-     * Check if a memory buffer is all-zero
-     *
-     * @param p Memory to scan
-     * @param len Length of memory
-     * @return True if memory is all zero
-     */
-    static inline bool isZero(const void* p, unsigned int len)
-    {
-        for (unsigned int i = 0; i < len; ++i) {
-            if (((const unsigned char*)p)[i]) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Unconditionally swap bytes regardless of host byte order
-     *
-     * @param n Integer to swap
-     * @return Integer with bytes reversed
-     */
-    static ZT_INLINE uint64_t swapBytes(const uint64_t n) noexcept
-    {
+	}
+
+	/**
+	 * Perform a safe C string copy, ALWAYS null-terminating the result
+	 *
+	 * This will never ever EVER result in dest[] not being null-terminated
+	 * regardless of any input parameter (other than len==0 which is invalid).
+	 *
+	 * @param dest Destination buffer (must not be NULL)
+	 * @param len Length of dest[] (if zero, false is returned and nothing happens)
+	 * @param src Source string (if NULL, dest will receive a zero-length string and true is returned)
+	 * @return True on success, false on overflow (buffer will still be 0-terminated)
+	 */
+	static inline bool scopy(char* dest, unsigned int len, const char* src)
+	{
+		if (! len) {
+			return false;	// sanity check
+		}
+		if (! src) {
+			*dest = (char)0;
+			return true;
+		}
+		char* end = dest + len;
+		while ((*dest++ = *src++)) {
+			if (dest == end) {
+				*(--dest) = (char)0;
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/**
+	 * Count the number of bits set in an integer
+	 *
+	 * @param v 32-bit integer
+	 * @return Number of bits set in this integer (0-32)
+	 */
+	static inline uint32_t countBits(uint32_t v)
+	{
+		v = v - ((v >> 1) & (uint32_t)0x55555555);
+		v = (v & (uint32_t)0x33333333) + ((v >> 2) & (uint32_t)0x33333333);
+		return ((((v + (v >> 4)) & (uint32_t)0xF0F0F0F) * (uint32_t)0x1010101) >> 24);
+	}
+
+	/**
+	 * Count the number of bits set in an integer
+	 *
+	 * @param v 64-bit integer
+	 * @return Number of bits set in this integer (0-64)
+	 */
+	static inline uint64_t countBits(uint64_t v)
+	{
+		v = v - ((v >> 1) & (uint64_t)~(uint64_t)0 / 3);
+		v = (v & (uint64_t)~(uint64_t)0 / 15 * 3) + ((v >> 2) & (uint64_t)~(uint64_t)0 / 15 * 3);
+		v = (v + (v >> 4)) & (uint64_t)~(uint64_t)0 / 255 * 15;
+		return (uint64_t)(v * ((uint64_t)~(uint64_t)0 / 255)) >> 56;
+	}
+
+	/**
+	 * Check if a memory buffer is all-zero
+	 *
+	 * @param p Memory to scan
+	 * @param len Length of memory
+	 * @return True if memory is all zero
+	 */
+	static inline bool isZero(const void* p, unsigned int len)
+	{
+		for (unsigned int i = 0; i < len; ++i) {
+			if (((const unsigned char*)p)[i]) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/**
+	 * Unconditionally swap bytes regardless of host byte order
+	 *
+	 * @param n Integer to swap
+	 * @return Integer with bytes reversed
+	 */
+	static ZT_INLINE uint64_t swapBytes(const uint64_t n) noexcept
+	{
 #ifdef __GNUC__
-        return __builtin_bswap64(n);
+		return __builtin_bswap64(n);
 #else
 #ifdef _MSC_VER
-        return (uint64_t)_byteswap_uint64((unsigned __int64)n);
+		return (uint64_t)_byteswap_uint64((unsigned __int64)n);
 #else
-        return (
-            ((n & 0x00000000000000ffULL) << 56) | ((n & 0x000000000000ff00ULL) << 40) | ((n & 0x0000000000ff0000ULL) << 24) | ((n & 0x00000000ff000000ULL) << 8) | ((n & 0x000000ff00000000ULL) >> 8) | ((n & 0x0000ff0000000000ULL) >> 24)
-            | ((n & 0x00ff000000000000ULL) >> 40) | ((n & 0xff00000000000000ULL) >> 56));
+		return (
+			((n & 0x00000000000000ffULL) << 56) | ((n & 0x000000000000ff00ULL) << 40) | ((n & 0x0000000000ff0000ULL) << 24) | ((n & 0x00000000ff000000ULL) << 8) | ((n & 0x000000ff00000000ULL) >> 8) | ((n & 0x0000ff0000000000ULL) >> 24)
+			| ((n & 0x00ff000000000000ULL) >> 40) | ((n & 0xff00000000000000ULL) >> 56));
 #endif
 #endif
-    }
-
-    /**
-     * Unconditionally swap bytes regardless of host byte order
-     *
-     * @param n Integer to swap
-     * @return Integer with bytes reversed
-     */
-    static ZT_INLINE uint32_t swapBytes(const uint32_t n) noexcept
-    {
+	}
+
+	/**
+	 * Unconditionally swap bytes regardless of host byte order
+	 *
+	 * @param n Integer to swap
+	 * @return Integer with bytes reversed
+	 */
+	static ZT_INLINE uint32_t swapBytes(const uint32_t n) noexcept
+	{
 #if defined(__GNUC__)
-        return __builtin_bswap32(n);
+		return __builtin_bswap32(n);
 #else
 #ifdef _MSC_VER
-        return (uint32_t)_byteswap_ulong((unsigned long)n);
+		return (uint32_t)_byteswap_ulong((unsigned long)n);
 #else
-        return htonl(n);
+		return htonl(n);
 #endif
 #endif
-    }
-
-    /**
-     * Unconditionally swap bytes regardless of host byte order
-     *
-     * @param n Integer to swap
-     * @return Integer with bytes reversed
-     */
-    static ZT_INLINE uint16_t swapBytes(const uint16_t n) noexcept
-    {
+	}
+
+	/**
+	 * Unconditionally swap bytes regardless of host byte order
+	 *
+	 * @param n Integer to swap
+	 * @return Integer with bytes reversed
+	 */
+	static ZT_INLINE uint16_t swapBytes(const uint16_t n) noexcept
+	{
 #if defined(__GNUC__)
-        return __builtin_bswap16(n);
+		return __builtin_bswap16(n);
 #else
 #ifdef _MSC_VER
-        return (uint16_t)_byteswap_ushort((unsigned short)n);
+		return (uint16_t)_byteswap_ushort((unsigned short)n);
 #else
-        return htons(n);
+		return htons(n);
 #endif
 #endif
-    }
-
-    // These are helper adapters to load and swap integer types special cased by size
-    // to work with all typedef'd variants, signed/unsigned, etc.
-    template <typename I, unsigned int S> class _swap_bytes_bysize;
-
-    template <typename I> class _swap_bytes_bysize<I, 1> {
-      public:
-        static ZT_INLINE I s(const I n) noexcept
-        {
-            return n;
-        }
-    };
-
-    template <typename I> class _swap_bytes_bysize<I, 2> {
-      public:
-        static ZT_INLINE I s(const I n) noexcept
-        {
-            return (I)swapBytes((uint16_t)n);
-        }
-    };
-
-    template <typename I> class _swap_bytes_bysize<I, 4> {
-      public:
-        static ZT_INLINE I s(const I n) noexcept
-        {
-            return (I)swapBytes((uint32_t)n);
-        }
-    };
-
-    template <typename I> class _swap_bytes_bysize<I, 8> {
-      public:
-        static ZT_INLINE I s(const I n) noexcept
-        {
-            return (I)swapBytes((uint64_t)n);
-        }
-    };
-
-    template <typename I, unsigned int S> class _load_be_bysize;
-
-    template <typename I> class _load_be_bysize<I, 1> {
-      public:
-        static ZT_INLINE I l(const uint8_t* const p) noexcept
-        {
-            return p[0];
-        }
-    };
-
-    template <typename I> class _load_be_bysize<I, 2> {
-      public:
-        static ZT_INLINE I l(const uint8_t* const p) noexcept
-        {
-            return (I)(((unsigned int)p[0] << 8U) | (unsigned int)p[1]);
-        }
-    };
-
-    template <typename I> class _load_be_bysize<I, 4> {
-      public:
-        static ZT_INLINE I l(const uint8_t* const p) noexcept
-        {
-            return (I)(((uint32_t)p[0] << 24U) | ((uint32_t)p[1] << 16U) | ((uint32_t)p[2] << 8U) | (uint32_t)p[3]);
-        }
-    };
-
-    template <typename I> class _load_be_bysize<I, 8> {
-      public:
-        static ZT_INLINE I l(const uint8_t* const p) noexcept
-        {
-            return (I)(((uint64_t)p[0] << 56U) | ((uint64_t)p[1] << 48U) | ((uint64_t)p[2] << 40U) | ((uint64_t)p[3] << 32U) | ((uint64_t)p[4] << 24U) | ((uint64_t)p[5] << 16U) | ((uint64_t)p[6] << 8U) | (uint64_t)p[7]);
-        }
-    };
-
-    template <typename I, unsigned int S> class _load_le_bysize;
-
-    template <typename I> class _load_le_bysize<I, 1> {
-      public:
-        static ZT_INLINE I l(const uint8_t* const p) noexcept
-        {
-            return p[0];
-        }
-    };
-
-    template <typename I> class _load_le_bysize<I, 2> {
-      public:
-        static ZT_INLINE I l(const uint8_t* const p) noexcept
-        {
-            return (I)((unsigned int)p[0] | ((unsigned int)p[1] << 8U));
-        }
-    };
-
-    template <typename I> class _load_le_bysize<I, 4> {
-      public:
-        static ZT_INLINE I l(const uint8_t* const p) noexcept
-        {
-            return (I)((uint32_t)p[0] | ((uint32_t)p[1] << 8U) | ((uint32_t)p[2] << 16U) | ((uint32_t)p[3] << 24U));
-        }
-    };
-
-    template <typename I> class _load_le_bysize<I, 8> {
-      public:
-        static ZT_INLINE I l(const uint8_t* const p) noexcept
-        {
-            return (I)((uint64_t)p[0] | ((uint64_t)p[1] << 8U) | ((uint64_t)p[2] << 16U) | ((uint64_t)p[3] << 24U) | ((uint64_t)p[4] << 32U) | ((uint64_t)p[5] << 40U) | ((uint64_t)p[6] << 48U) | ((uint64_t)p[7]) << 56U);
-        }
-    };
-
-    /**
-     * Convert any signed or unsigned integer type to big-endian ("network") byte order
-     *
-     * @tparam I Integer type (usually inferred)
-     * @param n Value to convert
-     * @return Value in big-endian order
-     */
-    template <typename I> static ZT_INLINE I hton(const I n) noexcept
-    {
+	}
+
+	// These are helper adapters to load and swap integer types special cased by size
+	// to work with all typedef'd variants, signed/unsigned, etc.
+	template <typename I, unsigned int S> class _swap_bytes_bysize;
+
+	template <typename I> class _swap_bytes_bysize<I, 1> {
+	  public:
+		static ZT_INLINE I s(const I n) noexcept
+		{
+			return n;
+		}
+	};
+
+	template <typename I> class _swap_bytes_bysize<I, 2> {
+	  public:
+		static ZT_INLINE I s(const I n) noexcept
+		{
+			return (I)swapBytes((uint16_t)n);
+		}
+	};
+
+	template <typename I> class _swap_bytes_bysize<I, 4> {
+	  public:
+		static ZT_INLINE I s(const I n) noexcept
+		{
+			return (I)swapBytes((uint32_t)n);
+		}
+	};
+
+	template <typename I> class _swap_bytes_bysize<I, 8> {
+	  public:
+		static ZT_INLINE I s(const I n) noexcept
+		{
+			return (I)swapBytes((uint64_t)n);
+		}
+	};
+
+	template <typename I, unsigned int S> class _load_be_bysize;
+
+	template <typename I> class _load_be_bysize<I, 1> {
+	  public:
+		static ZT_INLINE I l(const uint8_t* const p) noexcept
+		{
+			return p[0];
+		}
+	};
+
+	template <typename I> class _load_be_bysize<I, 2> {
+	  public:
+		static ZT_INLINE I l(const uint8_t* const p) noexcept
+		{
+			return (I)(((unsigned int)p[0] << 8U) | (unsigned int)p[1]);
+		}
+	};
+
+	template <typename I> class _load_be_bysize<I, 4> {
+	  public:
+		static ZT_INLINE I l(const uint8_t* const p) noexcept
+		{
+			return (I)(((uint32_t)p[0] << 24U) | ((uint32_t)p[1] << 16U) | ((uint32_t)p[2] << 8U) | (uint32_t)p[3]);
+		}
+	};
+
+	template <typename I> class _load_be_bysize<I, 8> {
+	  public:
+		static ZT_INLINE I l(const uint8_t* const p) noexcept
+		{
+			return (I)(((uint64_t)p[0] << 56U) | ((uint64_t)p[1] << 48U) | ((uint64_t)p[2] << 40U) | ((uint64_t)p[3] << 32U) | ((uint64_t)p[4] << 24U) | ((uint64_t)p[5] << 16U) | ((uint64_t)p[6] << 8U) | (uint64_t)p[7]);
+		}
+	};
+
+	template <typename I, unsigned int S> class _load_le_bysize;
+
+	template <typename I> class _load_le_bysize<I, 1> {
+	  public:
+		static ZT_INLINE I l(const uint8_t* const p) noexcept
+		{
+			return p[0];
+		}
+	};
+
+	template <typename I> class _load_le_bysize<I, 2> {
+	  public:
+		static ZT_INLINE I l(const uint8_t* const p) noexcept
+		{
+			return (I)((unsigned int)p[0] | ((unsigned int)p[1] << 8U));
+		}
+	};
+
+	template <typename I> class _load_le_bysize<I, 4> {
+	  public:
+		static ZT_INLINE I l(const uint8_t* const p) noexcept
+		{
+			return (I)((uint32_t)p[0] | ((uint32_t)p[1] << 8U) | ((uint32_t)p[2] << 16U) | ((uint32_t)p[3] << 24U));
+		}
+	};
+
+	template <typename I> class _load_le_bysize<I, 8> {
+	  public:
+		static ZT_INLINE I l(const uint8_t* const p) noexcept
+		{
+			return (I)((uint64_t)p[0] | ((uint64_t)p[1] << 8U) | ((uint64_t)p[2] << 16U) | ((uint64_t)p[3] << 24U) | ((uint64_t)p[4] << 32U) | ((uint64_t)p[5] << 40U) | ((uint64_t)p[6] << 48U) | ((uint64_t)p[7]) << 56U);
+		}
+	};
+
+	/**
+	 * Convert any signed or unsigned integer type to big-endian ("network") byte order
+	 *
+	 * @tparam I Integer type (usually inferred)
+	 * @param n Value to convert
+	 * @return Value in big-endian order
+	 */
+	template <typename I> static ZT_INLINE I hton(const I n) noexcept
+	{
 #if __BYTE_ORDER == __LITTLE_ENDIAN
-        return _swap_bytes_bysize<I, sizeof(I)>::s(n);
+		return _swap_bytes_bysize<I, sizeof(I)>::s(n);
 #else
-        return n;
+		return n;
 #endif
-    }
-
-    /**
-     * Convert any signed or unsigned integer type to host byte order from big-endian ("network") byte order
-     *
-     * @tparam I Integer type (usually inferred)
-     * @param n Value to convert
-     * @return Value in host byte order
-     */
-    template <typename I> static ZT_INLINE I ntoh(const I n) noexcept
-    {
+	}
+
+	/**
+	 * Convert any signed or unsigned integer type to host byte order from big-endian ("network") byte order
+	 *
+	 * @tparam I Integer type (usually inferred)
+	 * @param n Value to convert
+	 * @return Value in host byte order
+	 */
+	template <typename I> static ZT_INLINE I ntoh(const I n) noexcept
+	{
 #if __BYTE_ORDER == __LITTLE_ENDIAN
-        return _swap_bytes_bysize<I, sizeof(I)>::s(n);
+		return _swap_bytes_bysize<I, sizeof(I)>::s(n);
 #else
-        return n;
+		return n;
 #endif
-    }
-
-    /**
-     * Copy bits from memory into an integer type without modifying their order
-     *
-     * @tparam I Type to load
-     * @param p Byte stream, must be at least sizeof(I) in size
-     * @return Loaded raw integer
-     */
-    template <typename I> static ZT_INLINE I loadMachineEndian(const void* const p) noexcept
-    {
+	}
+
+	/**
+	 * Copy bits from memory into an integer type without modifying their order
+	 *
+	 * @tparam I Type to load
+	 * @param p Byte stream, must be at least sizeof(I) in size
+	 * @return Loaded raw integer
+	 */
+	template <typename I> static ZT_INLINE I loadMachineEndian(const void* const p) noexcept
+	{
 #ifdef ZT_NO_UNALIGNED_ACCESS
-        I tmp;
-        for (int i = 0; i < (int)sizeof(I); ++i) {
-            reinterpret_cast<uint8_t*>(&tmp)[i] = reinterpret_cast<const uint8_t*>(p)[i];
-        }
-        return tmp;
+		I tmp;
+		for (int i = 0; i < (int)sizeof(I); ++i) {
+			reinterpret_cast<uint8_t*>(&tmp)[i] = reinterpret_cast<const uint8_t*>(p)[i];
+		}
+		return tmp;
 #else
-        return *reinterpret_cast<const I*>(p);
+		return *reinterpret_cast<const I*>(p);
 #endif
-    }
-
-    /**
-     * Copy bits from memory into an integer type without modifying their order
-     *
-     * @tparam I Type to store
-     * @param p Byte array (must be at least sizeof(I))
-     * @param i Integer to store
-     */
-    template <typename I> static ZT_INLINE void storeMachineEndian(void* const p, const I i) noexcept
-    {
+	}
+
+	/**
+	 * Copy bits from memory into an integer type without modifying their order
+	 *
+	 * @tparam I Type to store
+	 * @param p Byte array (must be at least sizeof(I))
+	 * @param i Integer to store
+	 */
+	template <typename I> static ZT_INLINE void storeMachineEndian(void* const p, const I i) noexcept
+	{
 #ifdef ZT_NO_UNALIGNED_ACCESS
-        for (unsigned int k = 0; k < sizeof(I); ++k) {
-            reinterpret_cast<uint8_t*>(p)[k] = reinterpret_cast<const uint8_t*>(&i)[k];
-        }
+		for (unsigned int k = 0; k < sizeof(I); ++k) {
+			reinterpret_cast<uint8_t*>(p)[k] = reinterpret_cast<const uint8_t*>(&i)[k];
+		}
 #else
-        *reinterpret_cast<I*>(p) = i;
+		*reinterpret_cast<I*>(p) = i;
 #endif
-    }
-
-    /**
-     * Decode a big-endian value from a byte stream
-     *
-     * @tparam I Type to decode (should be unsigned e.g. uint32_t or uint64_t)
-     * @param p Byte stream, must be at least sizeof(I) in size
-     * @return Decoded integer
-     */
-    template <typename I> static ZT_INLINE I loadBigEndian(const void* const p) noexcept
-    {
+	}
+
+	/**
+	 * Decode a big-endian value from a byte stream
+	 *
+	 * @tparam I Type to decode (should be unsigned e.g. uint32_t or uint64_t)
+	 * @param p Byte stream, must be at least sizeof(I) in size
+	 * @return Decoded integer
+	 */
+	template <typename I> static ZT_INLINE I loadBigEndian(const void* const p) noexcept
+	{
 #ifdef ZT_NO_UNALIGNED_ACCESS
-        return _load_be_bysize<I, sizeof(I)>::l(reinterpret_cast<const uint8_t*>(p));
+		return _load_be_bysize<I, sizeof(I)>::l(reinterpret_cast<const uint8_t*>(p));
 #else
-        return ntoh(*reinterpret_cast<const I*>(p));
+		return ntoh(*reinterpret_cast<const I*>(p));
 #endif
-    }
-
-    /**
-     * Save an integer in big-endian format
-     *
-     * @tparam I Integer type to store (usually inferred)
-     * @param p Byte stream to write (must be at least sizeof(I))
-     * #param i Integer to write
-     */
-    template <typename I> static ZT_INLINE void storeBigEndian(void* const p, I i) noexcept
-    {
+	}
+
+	/**
+	 * Save an integer in big-endian format
+	 *
+	 * @tparam I Integer type to store (usually inferred)
+	 * @param p Byte stream to write (must be at least sizeof(I))
+	 * #param i Integer to write
+	 */
+	template <typename I> static ZT_INLINE void storeBigEndian(void* const p, I i) noexcept
+	{
 #ifdef ZT_NO_UNALIGNED_ACCESS
-        storeMachineEndian(p, hton(i));
+		storeMachineEndian(p, hton(i));
 #else
-        *reinterpret_cast<I*>(p) = hton(i);
+		*reinterpret_cast<I*>(p) = hton(i);
 #endif
-    }
-
-    /**
-     * Decode a little-endian value from a byte stream
-     *
-     * @tparam I Type to decode
-     * @param p Byte stream, must be at least sizeof(I) in size
-     * @return Decoded integer
-     */
-    template <typename I> static ZT_INLINE I loadLittleEndian(const void* const p) noexcept
-    {
+	}
+
+	/**
+	 * Decode a little-endian value from a byte stream
+	 *
+	 * @tparam I Type to decode
+	 * @param p Byte stream, must be at least sizeof(I) in size
+	 * @return Decoded integer
+	 */
+	template <typename I> static ZT_INLINE I loadLittleEndian(const void* const p) noexcept
+	{
 #if __BYTE_ORDER == __BIG_ENDIAN || defined(ZT_NO_UNALIGNED_ACCESS)
-        return _load_le_bysize<I, sizeof(I)>::l(reinterpret_cast<const uint8_t*>(p));
+		return _load_le_bysize<I, sizeof(I)>::l(reinterpret_cast<const uint8_t*>(p));
 #else
-        return *reinterpret_cast<const I*>(p);
+		return *reinterpret_cast<const I*>(p);
 #endif
-    }
-
-    /**
-     * Save an integer in little-endian format
-     *
-     * @tparam I Integer type to store (usually inferred)
-     * @param p Byte stream to write (must be at least sizeof(I))
-     * #param i Integer to write
-     */
-    template <typename I> static ZT_INLINE void storeLittleEndian(void* const p, const I i) noexcept
-    {
+	}
+
+	/**
+	 * Save an integer in little-endian format
+	 *
+	 * @tparam I Integer type to store (usually inferred)
+	 * @param p Byte stream to write (must be at least sizeof(I))
+	 * #param i Integer to write
+	 */
+	template <typename I> static ZT_INLINE void storeLittleEndian(void* const p, const I i) noexcept
+	{
 #if __BYTE_ORDER == __BIG_ENDIAN
-        storeMachineEndian(p, _swap_bytes_bysize<I, sizeof(I)>::s(i));
+		storeMachineEndian(p, _swap_bytes_bysize<I, sizeof(I)>::s(i));
 #else
 #ifdef ZT_NO_UNALIGNED_ACCESS
-        storeMachineEndian(p, i);
+		storeMachineEndian(p, i);
 #else
-        *reinterpret_cast<I*>(p) = i;
+		*reinterpret_cast<I*>(p) = i;
 #endif
 #endif
-    }
-
-    /**
-     * Copy memory block whose size is known at compile time.
-     *
-     * @tparam L Size of memory
-     * @param dest Destination memory
-     * @param src Source memory
-     */
-    template <unsigned long L> static ZT_INLINE void copy(void* dest, const void* src) noexcept
-    {
+	}
+
+	/**
+	 * Copy memory block whose size is known at compile time.
+	 *
+	 * @tparam L Size of memory
+	 * @param dest Destination memory
+	 * @param src Source memory
+	 */
+	template <unsigned long L> static ZT_INLINE void copy(void* dest, const void* src) noexcept
+	{
 #if defined(ZT_ARCH_X64) && defined(__GNUC__)
-        uintptr_t l = L;
-        __asm__ __volatile__("cld ; rep movsb" : "+c"(l), "+S"(src), "+D"(dest)::"memory");
+		uintptr_t l = L;
+		__asm__ __volatile__("cld ; rep movsb" : "+c"(l), "+S"(src), "+D"(dest)::"memory");
 #else
-        memcpy(dest, src, L);
+		memcpy(dest, src, L);
 #endif
-    }
-
-    /**
-     * Copy memory block whose size is known at run time
-     *
-     * @param dest Destination memory
-     * @param src Source memory
-     * @param len Bytes to copy
-     */
-    static ZT_INLINE void copy(void* dest, const void* src, unsigned long len) noexcept
-    {
+	}
+
+	/**
+	 * Copy memory block whose size is known at run time
+	 *
+	 * @param dest Destination memory
+	 * @param src Source memory
+	 * @param len Bytes to copy
+	 */
+	static ZT_INLINE void copy(void* dest, const void* src, unsigned long len) noexcept
+	{
 #if defined(ZT_ARCH_X64) && defined(__GNUC__)
-        __asm__ __volatile__("cld ; rep movsb" : "+c"(len), "+S"(src), "+D"(dest)::"memory");
+		__asm__ __volatile__("cld ; rep movsb" : "+c"(len), "+S"(src), "+D"(dest)::"memory");
 #else
-        memcpy(dest, src, len);
+		memcpy(dest, src, len);
 #endif
-    }
-
-    /**
-     * Zero memory block whose size is known at compile time
-     *
-     * @tparam L Size in bytes
-     * @param dest Memory to zero
-     */
-    template <unsigned long L> static ZT_INLINE void zero(void* dest) noexcept
-    {
+	}
+
+	/**
+	 * Zero memory block whose size is known at compile time
+	 *
+	 * @tparam L Size in bytes
+	 * @param dest Memory to zero
+	 */
+	template <unsigned long L> static ZT_INLINE void zero(void* dest) noexcept
+	{
 #if defined(ZT_ARCH_X64) && defined(__GNUC__)
-        uintptr_t l = L;
-        __asm__ __volatile__("cld ; rep stosb" : "+c"(l), "+D"(dest) : "a"(0) : "memory");
+		uintptr_t l = L;
+		__asm__ __volatile__("cld ; rep stosb" : "+c"(l), "+D"(dest) : "a"(0) : "memory");
 #else
-        memset(dest, 0, L);
+		memset(dest, 0, L);
 #endif
-    }
-
-    /**
-     * Zero memory block whose size is known at run time
-     *
-     * @param dest Memory to zero
-     * @param len Size in bytes
-     */
-    static ZT_INLINE void zero(void* dest, unsigned long len) noexcept
-    {
+	}
+
+	/**
+	 * Zero memory block whose size is known at run time
+	 *
+	 * @param dest Memory to zero
+	 * @param len Size in bytes
+	 */
+	static ZT_INLINE void zero(void* dest, unsigned long len) noexcept
+	{
 #if defined(ZT_ARCH_X64) && defined(__GNUC__)
-        __asm__ __volatile__("cld ; rep stosb" : "+c"(len), "+D"(dest) : "a"(0) : "memory");
+		__asm__ __volatile__("cld ; rep stosb" : "+c"(len), "+D"(dest) : "a"(0) : "memory");
 #else
-        memset(dest, 0, len);
+		memset(dest, 0, len);
 #endif
-    }
-
-    /**
-     * Hexadecimal characters 0-f
-     */
-    static const char HEXCHARS[16];
-
-    /*
-     * Remove `-` and `:` from a MAC address (in-place).
-     *
-     * @param mac The MAC address
-     */
-    static inline void cleanMac(std::string& mac)
-    {
-        auto start = mac.begin();
-        auto end = mac.end();
-        auto new_end = std::remove_if(start, end, [](char c) { return c == 45 || c == 58; });
-        mac.erase(new_end, end);
-    }
+	}
+
+	/**
+	 * Hexadecimal characters 0-f
+	 */
+	static const char HEXCHARS[16];
+
+	/*
+	 * Remove `-` and `:` from a MAC address (in-place).
+	 *
+	 * @param mac The MAC address
+	 */
+	static inline void cleanMac(std::string& mac)
+	{
+		auto start = mac.begin();
+		auto end = mac.end();
+		auto new_end = std::remove_if(start, end, [](char c) { return c == 45 || c == 58; });
+		mac.erase(new_end, end);
+	}
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 238 - 238
node/World.hpp

@@ -78,247 +78,247 @@ namespace ZeroTier {
  */
 class World {
   public:
-    /**
-     * World type -- do not change IDs
-     */
-    enum Type {
-        TYPE_NULL = 0,
-        TYPE_PLANET = 1,   // Planets, of which there is currently one (Earth)
-        TYPE_MOON = 127    // Moons, which are user-created and many
-    };
-
-    /**
-     * Upstream server definition in world/moon
-     */
-    struct Root {
-        Identity identity;
-        std::vector<InetAddress> stableEndpoints;
-
-        inline bool operator==(const Root& r) const
-        {
-            return ((identity == r.identity) && (stableEndpoints == r.stableEndpoints));
-        }
-        inline bool operator!=(const Root& r) const
-        {
-            return (! (*this == r));
-        }
-        inline bool operator<(const Root& r) const
-        {
-            return (identity < r.identity);
-        }   // for sorting
-    };
-
-    /**
-     * Construct an empty / null World
-     */
-    World() : _id(0), _ts(0), _type(TYPE_NULL)
-    {
-    }
-
-    /**
-     * @return Root servers for this world and their stable endpoints
-     */
-    inline const std::vector<World::Root>& roots() const
-    {
-        return _roots;
-    }
-
-    /**
-     * @return World type: planet or moon
-     */
-    inline Type type() const
-    {
-        return _type;
-    }
-
-    /**
-     * @return World unique identifier
-     */
-    inline uint64_t id() const
-    {
-        return _id;
-    }
-
-    /**
-     * @return World definition timestamp
-     */
-    inline uint64_t timestamp() const
-    {
-        return _ts;
-    }
-
-    /**
-     * @return C25519 signature
-     */
-    inline const ECC::Signature& signature() const
-    {
-        return _signature;
-    }
-
-    /**
-     * @return Public key that must sign next update
-     */
-    inline const ECC::Public& updatesMustBeSignedBy() const
-    {
-        return _updatesMustBeSignedBy;
-    }
-
-    /**
-     * Check whether a world update should replace this one
-     *
-     * @param update Candidate update
-     * @return True if update is newer than current, matches its ID and type, and is properly signed (or if current is NULL)
-     */
-    inline bool shouldBeReplacedBy(const World& update)
-    {
-        if ((_id == 0) || (_type == TYPE_NULL)) {
-            return true;
-        }
-        if ((_id == update._id) && (_ts < update._ts) && (_type == update._type)) {
-            Buffer<ZT_WORLD_MAX_SERIALIZED_LENGTH> tmp;
-            update.serialize(tmp, true);
-            return ECC::verify(_updatesMustBeSignedBy, tmp.data(), tmp.size(), update._signature);
-        }
-        return false;
-    }
-
-    /**
-     * @return True if this World is non-empty
-     */
-    inline operator bool() const
-    {
-        return (_type != TYPE_NULL);
-    }
-
-    template <unsigned int C> inline void serialize(Buffer<C>& b, bool forSign = false) const
-    {
-        if (forSign) {
-            b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
-        }
-
-        b.append((uint8_t)_type);
-        b.append((uint64_t)_id);
-        b.append((uint64_t)_ts);
-        b.append(_updatesMustBeSignedBy.data, ZT_ECC_PUBLIC_KEY_SET_LEN);
-        if (! forSign) {
-            b.append(_signature.data, ZT_ECC_SIGNATURE_LEN);
-        }
-        b.append((uint8_t)_roots.size());
-        for (std::vector<Root>::const_iterator r(_roots.begin()); r != _roots.end(); ++r) {
-            r->identity.serialize(b);
-            b.append((uint8_t)r->stableEndpoints.size());
-            for (std::vector<InetAddress>::const_iterator ep(r->stableEndpoints.begin()); ep != r->stableEndpoints.end(); ++ep) {
-                ep->serialize(b);
-            }
-        }
-        if (_type == TYPE_MOON) {
-            b.append((uint16_t)0);   // no attached dictionary (for future use)
-        }
-
-        if (forSign) {
-            b.append((uint64_t)0xf7f7f7f7f7f7f7f7ULL);
-        }
-    }
-
-    template <unsigned int C> inline unsigned int deserialize(const Buffer<C>& b, unsigned int startAt = 0)
-    {
-        unsigned int p = startAt;
-
-        _roots.clear();
-
-        switch ((Type)b[p++]) {
-            case TYPE_NULL:   // shouldn't ever really happen in serialized data but it's not invalid
-                _type = TYPE_NULL;
-                break;
-            case TYPE_PLANET:
-                _type = TYPE_PLANET;
-                break;
-            case TYPE_MOON:
-                _type = TYPE_MOON;
-                break;
-            default:
-                throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_TYPE;
-        }
-
-        _id = b.template at<uint64_t>(p);
-        p += 8;
-        _ts = b.template at<uint64_t>(p);
-        p += 8;
-        memcpy(_updatesMustBeSignedBy.data, b.field(p, ZT_ECC_PUBLIC_KEY_SET_LEN), ZT_ECC_PUBLIC_KEY_SET_LEN);
-        p += ZT_ECC_PUBLIC_KEY_SET_LEN;
-        memcpy(_signature.data, b.field(p, ZT_ECC_SIGNATURE_LEN), ZT_ECC_SIGNATURE_LEN);
-        p += ZT_ECC_SIGNATURE_LEN;
-        const unsigned int numRoots = (unsigned int)b[p++];
-        if (numRoots > ZT_WORLD_MAX_ROOTS) {
-            throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW;
-        }
-        for (unsigned int k = 0; k < numRoots; ++k) {
-            _roots.push_back(Root());
-            Root& r = _roots.back();
-            p += r.identity.deserialize(b, p);
-            unsigned int numStableEndpoints = b[p++];
-            if (numStableEndpoints > ZT_WORLD_MAX_STABLE_ENDPOINTS_PER_ROOT) {
-                throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW;
-            }
-            for (unsigned int kk = 0; kk < numStableEndpoints; ++kk) {
-                r.stableEndpoints.push_back(InetAddress());
-                p += r.stableEndpoints.back().deserialize(b, p);
-            }
-        }
-        if (_type == TYPE_MOON) {
-            p += b.template at<uint16_t>(p) + 2;
-        }
-
-        return (p - startAt);
-    }
-
-    inline bool operator==(const World& w) const
-    {
-        return (
-            (_id == w._id) && (_ts == w._ts) && (memcmp(_updatesMustBeSignedBy.data, w._updatesMustBeSignedBy.data, ZT_ECC_PUBLIC_KEY_SET_LEN) == 0) && (memcmp(_signature.data, w._signature.data, ZT_ECC_SIGNATURE_LEN) == 0)
-            && (_roots == w._roots) && (_type == w._type));
-    }
-    inline bool operator!=(const World& w) const
-    {
-        return (! (*this == w));
-    }
-
-    /**
-     * Create a World object signed with a key pair
-     *
-     * @param t World type
-     * @param id World ID
-     * @param ts World timestamp / revision
-     * @param sk Key that must be used to sign the next future update to this world
-     * @param roots Roots and their stable endpoints
-     * @param signWith Key to sign this World with (can have the same public as the next-update signing key, but doesn't have to)
-     * @return Signed World object
-     */
-    static inline World make(World::Type t, uint64_t id, uint64_t ts, const ECC::Public& sk, const std::vector<World::Root>& roots, const ECC::Pair& signWith)
-    {
-        World w;
-        w._id = id;
-        w._ts = ts;
-        w._type = t;
-        w._updatesMustBeSignedBy = sk;
-        w._roots = roots;
-
-        Buffer<ZT_WORLD_MAX_SERIALIZED_LENGTH> tmp;
-        w.serialize(tmp, true);
-        w._signature = ECC::sign(signWith, tmp.data(), tmp.size());
-
-        return w;
-    }
+	/**
+	 * World type -- do not change IDs
+	 */
+	enum Type {
+		TYPE_NULL = 0,
+		TYPE_PLANET = 1,   // Planets, of which there is currently one (Earth)
+		TYPE_MOON = 127	   // Moons, which are user-created and many
+	};
+
+	/**
+	 * Upstream server definition in world/moon
+	 */
+	struct Root {
+		Identity identity;
+		std::vector<InetAddress> stableEndpoints;
+
+		inline bool operator==(const Root& r) const
+		{
+			return ((identity == r.identity) && (stableEndpoints == r.stableEndpoints));
+		}
+		inline bool operator!=(const Root& r) const
+		{
+			return (! (*this == r));
+		}
+		inline bool operator<(const Root& r) const
+		{
+			return (identity < r.identity);
+		}	// for sorting
+	};
+
+	/**
+	 * Construct an empty / null World
+	 */
+	World() : _id(0), _ts(0), _type(TYPE_NULL)
+	{
+	}
+
+	/**
+	 * @return Root servers for this world and their stable endpoints
+	 */
+	inline const std::vector<World::Root>& roots() const
+	{
+		return _roots;
+	}
+
+	/**
+	 * @return World type: planet or moon
+	 */
+	inline Type type() const
+	{
+		return _type;
+	}
+
+	/**
+	 * @return World unique identifier
+	 */
+	inline uint64_t id() const
+	{
+		return _id;
+	}
+
+	/**
+	 * @return World definition timestamp
+	 */
+	inline uint64_t timestamp() const
+	{
+		return _ts;
+	}
+
+	/**
+	 * @return C25519 signature
+	 */
+	inline const ECC::Signature& signature() const
+	{
+		return _signature;
+	}
+
+	/**
+	 * @return Public key that must sign next update
+	 */
+	inline const ECC::Public& updatesMustBeSignedBy() const
+	{
+		return _updatesMustBeSignedBy;
+	}
+
+	/**
+	 * Check whether a world update should replace this one
+	 *
+	 * @param update Candidate update
+	 * @return True if update is newer than current, matches its ID and type, and is properly signed (or if current is NULL)
+	 */
+	inline bool shouldBeReplacedBy(const World& update)
+	{
+		if ((_id == 0) || (_type == TYPE_NULL)) {
+			return true;
+		}
+		if ((_id == update._id) && (_ts < update._ts) && (_type == update._type)) {
+			Buffer<ZT_WORLD_MAX_SERIALIZED_LENGTH> tmp;
+			update.serialize(tmp, true);
+			return ECC::verify(_updatesMustBeSignedBy, tmp.data(), tmp.size(), update._signature);
+		}
+		return false;
+	}
+
+	/**
+	 * @return True if this World is non-empty
+	 */
+	inline operator bool() const
+	{
+		return (_type != TYPE_NULL);
+	}
+
+	template <unsigned int C> inline void serialize(Buffer<C>& b, bool forSign = false) const
+	{
+		if (forSign) {
+			b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
+		}
+
+		b.append((uint8_t)_type);
+		b.append((uint64_t)_id);
+		b.append((uint64_t)_ts);
+		b.append(_updatesMustBeSignedBy.data, ZT_ECC_PUBLIC_KEY_SET_LEN);
+		if (! forSign) {
+			b.append(_signature.data, ZT_ECC_SIGNATURE_LEN);
+		}
+		b.append((uint8_t)_roots.size());
+		for (std::vector<Root>::const_iterator r(_roots.begin()); r != _roots.end(); ++r) {
+			r->identity.serialize(b);
+			b.append((uint8_t)r->stableEndpoints.size());
+			for (std::vector<InetAddress>::const_iterator ep(r->stableEndpoints.begin()); ep != r->stableEndpoints.end(); ++ep) {
+				ep->serialize(b);
+			}
+		}
+		if (_type == TYPE_MOON) {
+			b.append((uint16_t)0);	 // no attached dictionary (for future use)
+		}
+
+		if (forSign) {
+			b.append((uint64_t)0xf7f7f7f7f7f7f7f7ULL);
+		}
+	}
+
+	template <unsigned int C> inline unsigned int deserialize(const Buffer<C>& b, unsigned int startAt = 0)
+	{
+		unsigned int p = startAt;
+
+		_roots.clear();
+
+		switch ((Type)b[p++]) {
+			case TYPE_NULL:	  // shouldn't ever really happen in serialized data but it's not invalid
+				_type = TYPE_NULL;
+				break;
+			case TYPE_PLANET:
+				_type = TYPE_PLANET;
+				break;
+			case TYPE_MOON:
+				_type = TYPE_MOON;
+				break;
+			default:
+				throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_INVALID_TYPE;
+		}
+
+		_id = b.template at<uint64_t>(p);
+		p += 8;
+		_ts = b.template at<uint64_t>(p);
+		p += 8;
+		memcpy(_updatesMustBeSignedBy.data, b.field(p, ZT_ECC_PUBLIC_KEY_SET_LEN), ZT_ECC_PUBLIC_KEY_SET_LEN);
+		p += ZT_ECC_PUBLIC_KEY_SET_LEN;
+		memcpy(_signature.data, b.field(p, ZT_ECC_SIGNATURE_LEN), ZT_ECC_SIGNATURE_LEN);
+		p += ZT_ECC_SIGNATURE_LEN;
+		const unsigned int numRoots = (unsigned int)b[p++];
+		if (numRoots > ZT_WORLD_MAX_ROOTS) {
+			throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW;
+		}
+		for (unsigned int k = 0; k < numRoots; ++k) {
+			_roots.push_back(Root());
+			Root& r = _roots.back();
+			p += r.identity.deserialize(b, p);
+			unsigned int numStableEndpoints = b[p++];
+			if (numStableEndpoints > ZT_WORLD_MAX_STABLE_ENDPOINTS_PER_ROOT) {
+				throw ZT_EXCEPTION_INVALID_SERIALIZED_DATA_OVERFLOW;
+			}
+			for (unsigned int kk = 0; kk < numStableEndpoints; ++kk) {
+				r.stableEndpoints.push_back(InetAddress());
+				p += r.stableEndpoints.back().deserialize(b, p);
+			}
+		}
+		if (_type == TYPE_MOON) {
+			p += b.template at<uint16_t>(p) + 2;
+		}
+
+		return (p - startAt);
+	}
+
+	inline bool operator==(const World& w) const
+	{
+		return (
+			(_id == w._id) && (_ts == w._ts) && (memcmp(_updatesMustBeSignedBy.data, w._updatesMustBeSignedBy.data, ZT_ECC_PUBLIC_KEY_SET_LEN) == 0) && (memcmp(_signature.data, w._signature.data, ZT_ECC_SIGNATURE_LEN) == 0)
+			&& (_roots == w._roots) && (_type == w._type));
+	}
+	inline bool operator!=(const World& w) const
+	{
+		return (! (*this == w));
+	}
+
+	/**
+	 * Create a World object signed with a key pair
+	 *
+	 * @param t World type
+	 * @param id World ID
+	 * @param ts World timestamp / revision
+	 * @param sk Key that must be used to sign the next future update to this world
+	 * @param roots Roots and their stable endpoints
+	 * @param signWith Key to sign this World with (can have the same public as the next-update signing key, but doesn't have to)
+	 * @return Signed World object
+	 */
+	static inline World make(World::Type t, uint64_t id, uint64_t ts, const ECC::Public& sk, const std::vector<World::Root>& roots, const ECC::Pair& signWith)
+	{
+		World w;
+		w._id = id;
+		w._ts = ts;
+		w._type = t;
+		w._updatesMustBeSignedBy = sk;
+		w._roots = roots;
+
+		Buffer<ZT_WORLD_MAX_SERIALIZED_LENGTH> tmp;
+		w.serialize(tmp, true);
+		w._signature = ECC::sign(signWith, tmp.data(), tmp.size());
+
+		return w;
+	}
 
   protected:
-    uint64_t _id;
-    uint64_t _ts;
-    Type _type;
-    ECC::Public _updatesMustBeSignedBy;
-    ECC::Signature _signature;
-    std::vector<Root> _roots;
+	uint64_t _id;
+	uint64_t _ts;
+	Type _type;
+	ECC::Public _updatesMustBeSignedBy;
+	ECC::Signature _signature;
+	std::vector<Root> _roots;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 80 - 80
osdep/Arp.cpp

@@ -30,97 +30,97 @@ Arp::Arp() : _cache(256), _lastCleaned(OSUtils::now())
 
 void Arp::addLocal(uint32_t ip, const MAC& mac)
 {
-    _ArpEntry& e = _cache[ip];
-    e.lastQuerySent = 0;          // local IP
-    e.lastResponseReceived = 0;   // local IP
-    e.mac = mac;
-    e.local = true;
+	_ArpEntry& e = _cache[ip];
+	e.lastQuerySent = 0;		  // local IP
+	e.lastResponseReceived = 0;	  // local IP
+	e.mac = mac;
+	e.local = true;
 }
 
 void Arp::remove(uint32_t ip)
 {
-    _cache.erase(ip);
+	_cache.erase(ip);
 }
 
 uint32_t Arp::processIncomingArp(const void* arp, unsigned int len, void* response, unsigned int& responseLen, MAC& responseDest)
 {
-    const uint64_t now = OSUtils::now();
-    uint32_t ip = 0;
-
-    responseLen = 0;
-    responseDest.zero();
-
-    if (len >= 28) {
-        if (! memcmp(arp, ARP_REQUEST_HEADER, 8)) {
-            // Respond to ARP requests for locally-known IPs
-            _ArpEntry* targetEntry = _cache.get(reinterpret_cast<const uint32_t*>(arp)[6]);
-            if ((targetEntry) && (targetEntry->local)) {
-                memcpy(response, ARP_RESPONSE_HEADER, 8);
-                targetEntry->mac.copyTo(reinterpret_cast<uint8_t*>(response) + 8, 6);
-                memcpy(reinterpret_cast<uint8_t*>(response) + 14, reinterpret_cast<const uint8_t*>(arp) + 24, 4);
-                memcpy(reinterpret_cast<uint8_t*>(response) + 18, reinterpret_cast<const uint8_t*>(arp) + 8, 10);
-                responseLen = 28;
-                responseDest.setTo(reinterpret_cast<const uint8_t*>(arp) + 8, 6);
-            }
-        }
-        else if (! memcmp(arp, ARP_RESPONSE_HEADER, 8)) {
-            // Learn cache entries for remote IPs from relevant ARP replies
-            uint32_t responseIp = 0;
-            memcpy(&responseIp, reinterpret_cast<const uint8_t*>(arp) + 14, 4);
-            _ArpEntry* queryEntry = _cache.get(responseIp);
-            if ((queryEntry) && (! queryEntry->local) && ((now - queryEntry->lastQuerySent) <= ZT_ARP_QUERY_MAX_TTL)) {
-                queryEntry->lastResponseReceived = now;
-                queryEntry->mac.setTo(reinterpret_cast<const uint8_t*>(arp) + 8, 6);
-                ip = responseIp;
-            }
-        }
-    }
-
-    if ((now - _lastCleaned) >= ZT_ARP_EXPIRE) {
-        _lastCleaned = now;
-        Hashtable<uint32_t, _ArpEntry>::Iterator i(_cache);
-        uint32_t* k = (uint32_t*)0;
-        _ArpEntry* v = (_ArpEntry*)0;
-        while (i.next(k, v)) {
-            if ((! v->local) && ((now - v->lastResponseReceived) >= ZT_ARP_EXPIRE))
-                _cache.erase(*k);
-        }
-    }
-
-    return ip;
+	const uint64_t now = OSUtils::now();
+	uint32_t ip = 0;
+
+	responseLen = 0;
+	responseDest.zero();
+
+	if (len >= 28) {
+		if (! memcmp(arp, ARP_REQUEST_HEADER, 8)) {
+			// Respond to ARP requests for locally-known IPs
+			_ArpEntry* targetEntry = _cache.get(reinterpret_cast<const uint32_t*>(arp)[6]);
+			if ((targetEntry) && (targetEntry->local)) {
+				memcpy(response, ARP_RESPONSE_HEADER, 8);
+				targetEntry->mac.copyTo(reinterpret_cast<uint8_t*>(response) + 8, 6);
+				memcpy(reinterpret_cast<uint8_t*>(response) + 14, reinterpret_cast<const uint8_t*>(arp) + 24, 4);
+				memcpy(reinterpret_cast<uint8_t*>(response) + 18, reinterpret_cast<const uint8_t*>(arp) + 8, 10);
+				responseLen = 28;
+				responseDest.setTo(reinterpret_cast<const uint8_t*>(arp) + 8, 6);
+			}
+		}
+		else if (! memcmp(arp, ARP_RESPONSE_HEADER, 8)) {
+			// Learn cache entries for remote IPs from relevant ARP replies
+			uint32_t responseIp = 0;
+			memcpy(&responseIp, reinterpret_cast<const uint8_t*>(arp) + 14, 4);
+			_ArpEntry* queryEntry = _cache.get(responseIp);
+			if ((queryEntry) && (! queryEntry->local) && ((now - queryEntry->lastQuerySent) <= ZT_ARP_QUERY_MAX_TTL)) {
+				queryEntry->lastResponseReceived = now;
+				queryEntry->mac.setTo(reinterpret_cast<const uint8_t*>(arp) + 8, 6);
+				ip = responseIp;
+			}
+		}
+	}
+
+	if ((now - _lastCleaned) >= ZT_ARP_EXPIRE) {
+		_lastCleaned = now;
+		Hashtable<uint32_t, _ArpEntry>::Iterator i(_cache);
+		uint32_t* k = (uint32_t*)0;
+		_ArpEntry* v = (_ArpEntry*)0;
+		while (i.next(k, v)) {
+			if ((! v->local) && ((now - v->lastResponseReceived) >= ZT_ARP_EXPIRE))
+				_cache.erase(*k);
+		}
+	}
+
+	return ip;
 }
 
 MAC Arp::query(const MAC& localMac, uint32_t localIp, uint32_t targetIp, void* query, unsigned int& queryLen, MAC& queryDest)
 {
-    const uint64_t now = OSUtils::now();
-
-    _ArpEntry& e = _cache[targetIp];
-
-    if (((e.mac) && ((now - e.lastResponseReceived) >= (ZT_ARP_EXPIRE / 3))) || ((! e.mac) && ((now - e.lastQuerySent) >= ZT_ARP_QUERY_INTERVAL))) {
-        e.lastQuerySent = now;
-
-        uint8_t* q = reinterpret_cast<uint8_t*>(query);
-        memcpy(q, ARP_REQUEST_HEADER, 8);
-        q += 8;   // ARP request header information, always the same
-        localMac.copyTo(q, 6);
-        q += 6;   // sending host MAC address
-        memcpy(q, &localIp, 4);
-        q += 4;   // sending host IP (IP already in big-endian byte order)
-        memset(q, 0, 6);
-        q += 6;                    // sending zeros for target MAC address as thats what we want to find
-        memcpy(q, &targetIp, 4);   // target IP address for resolution (IP already in big-endian byte order)
-        queryLen = 28;
-        if (e.mac)
-            queryDest = e.mac;   // confirmation query, send directly to address holder
-        else
-            queryDest = (uint64_t)0xffffffffffffULL;   // broadcast query
-    }
-    else {
-        queryLen = 0;
-        queryDest.zero();
-    }
-
-    return e.mac;
+	const uint64_t now = OSUtils::now();
+
+	_ArpEntry& e = _cache[targetIp];
+
+	if (((e.mac) && ((now - e.lastResponseReceived) >= (ZT_ARP_EXPIRE / 3))) || ((! e.mac) && ((now - e.lastQuerySent) >= ZT_ARP_QUERY_INTERVAL))) {
+		e.lastQuerySent = now;
+
+		uint8_t* q = reinterpret_cast<uint8_t*>(query);
+		memcpy(q, ARP_REQUEST_HEADER, 8);
+		q += 8;	  // ARP request header information, always the same
+		localMac.copyTo(q, 6);
+		q += 6;	  // sending host MAC address
+		memcpy(q, &localIp, 4);
+		q += 4;	  // sending host IP (IP already in big-endian byte order)
+		memset(q, 0, 6);
+		q += 6;					   // sending zeros for target MAC address as thats what we want to find
+		memcpy(q, &targetIp, 4);   // target IP address for resolution (IP already in big-endian byte order)
+		queryLen = 28;
+		if (e.mac)
+			queryDest = e.mac;	 // confirmation query, send directly to address holder
+		else
+			queryDest = (uint64_t)0xffffffffffffULL;   // broadcast query
+	}
+	else {
+		queryLen = 0;
+		queryDest.zero();
+	}
+
+	return e.mac;
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 66 - 66
osdep/Arp.hpp

@@ -68,75 +68,75 @@ namespace ZeroTier {
  */
 class Arp {
   public:
-    Arp();
-
-    /**
-     * Set a local IP entry that we should respond to ARPs for
-     *
-     * @param mac Our local MAC address
-     * @param ip IP in big-endian byte order (sin_addr.s_addr)
-     */
-    void addLocal(uint32_t ip, const MAC& mac);
-
-    /**
-     * Delete a local IP entry or a cached ARP entry
-     *
-     * @param ip IP in big-endian byte order (sin_addr.s_addr)
-     */
-    void remove(uint32_t ip);
-
-    /**
-     * Process ARP packets
-     *
-     * For ARP queries, a response is generated and responseLen is set to its
-     * frame payload length in bytes.
-     *
-     * For ARP responses, the cache is populated and the IP address entry that
-     * was learned is returned.
-     *
-     * @param arp ARP frame data
-     * @param len Length of ARP frame (usually 28)
-     * @param response Response buffer -- MUST be a minimum of ZT_ARP_BUF_LENGTH in size
-     * @param responseLen Response length, or set to 0 if no response
-     * @param responseDest Destination of response, or set to null if no response
-     * @return IP address learned or 0 if no new IPs in cache
-     */
-    uint32_t processIncomingArp(const void* arp, unsigned int len, void* response, unsigned int& responseLen, MAC& responseDest);
-
-    /**
-     * Get the MAC corresponding to an IP, generating a query if needed
-     *
-     * This returns a MAC for a remote IP. The local MAC is returned for local
-     * IPs as well. It may also generate a query if the IP is not known or the
-     * entry needs to be refreshed. In this case queryLen will be set to a
-     * non-zero value, so this should always be checked on return even if the
-     * MAC returned is non-null.
-     *
-     * @param localMac Local MAC address of host interface
-     * @param localIp Local IP address of host interface
-     * @param targetIp IP to look up
-     * @param query Buffer for generated query -- MUST be a minimum of ZT_ARP_BUF_LENGTH in size
-     * @param queryLen Length of generated query, or set to 0 if no query generated
-     * @param queryDest Destination of query, or set to null if no query generated
-     * @return MAC or 0 if no cached entry for this IP
-     */
-    MAC query(const MAC& localMac, uint32_t localIp, uint32_t targetIp, void* query, unsigned int& queryLen, MAC& queryDest);
+	Arp();
+
+	/**
+	 * Set a local IP entry that we should respond to ARPs for
+	 *
+	 * @param mac Our local MAC address
+	 * @param ip IP in big-endian byte order (sin_addr.s_addr)
+	 */
+	void addLocal(uint32_t ip, const MAC& mac);
+
+	/**
+	 * Delete a local IP entry or a cached ARP entry
+	 *
+	 * @param ip IP in big-endian byte order (sin_addr.s_addr)
+	 */
+	void remove(uint32_t ip);
+
+	/**
+	 * Process ARP packets
+	 *
+	 * For ARP queries, a response is generated and responseLen is set to its
+	 * frame payload length in bytes.
+	 *
+	 * For ARP responses, the cache is populated and the IP address entry that
+	 * was learned is returned.
+	 *
+	 * @param arp ARP frame data
+	 * @param len Length of ARP frame (usually 28)
+	 * @param response Response buffer -- MUST be a minimum of ZT_ARP_BUF_LENGTH in size
+	 * @param responseLen Response length, or set to 0 if no response
+	 * @param responseDest Destination of response, or set to null if no response
+	 * @return IP address learned or 0 if no new IPs in cache
+	 */
+	uint32_t processIncomingArp(const void* arp, unsigned int len, void* response, unsigned int& responseLen, MAC& responseDest);
+
+	/**
+	 * Get the MAC corresponding to an IP, generating a query if needed
+	 *
+	 * This returns a MAC for a remote IP. The local MAC is returned for local
+	 * IPs as well. It may also generate a query if the IP is not known or the
+	 * entry needs to be refreshed. In this case queryLen will be set to a
+	 * non-zero value, so this should always be checked on return even if the
+	 * MAC returned is non-null.
+	 *
+	 * @param localMac Local MAC address of host interface
+	 * @param localIp Local IP address of host interface
+	 * @param targetIp IP to look up
+	 * @param query Buffer for generated query -- MUST be a minimum of ZT_ARP_BUF_LENGTH in size
+	 * @param queryLen Length of generated query, or set to 0 if no query generated
+	 * @param queryDest Destination of query, or set to null if no query generated
+	 * @return MAC or 0 if no cached entry for this IP
+	 */
+	MAC query(const MAC& localMac, uint32_t localIp, uint32_t targetIp, void* query, unsigned int& queryLen, MAC& queryDest);
 
   private:
-    struct _ArpEntry {
-        _ArpEntry() : lastQuerySent(0), lastResponseReceived(0), mac(), local(false)
-        {
-        }
-        uint64_t lastQuerySent;          // Time last query was sent or 0 for local IP
-        uint64_t lastResponseReceived;   // Time of last ARP response or 0 for local IP
-        MAC mac;                         // MAC address of device responsible for IP or null if not known yet
-        bool local;                      // True if this is a local ARP entry
-    };
-
-    Hashtable<uint32_t, _ArpEntry> _cache;
-    uint64_t _lastCleaned;
+	struct _ArpEntry {
+		_ArpEntry() : lastQuerySent(0), lastResponseReceived(0), mac(), local(false)
+		{
+		}
+		uint64_t lastQuerySent;			 // Time last query was sent or 0 for local IP
+		uint64_t lastResponseReceived;	 // Time of last ARP response or 0 for local IP
+		MAC mac;						 // MAC address of device responsible for IP or null if not known yet
+		bool local;						 // True if this is a local ARP entry
+	};
+
+	Hashtable<uint32_t, _ArpEntry> _cache;
+	uint64_t _lastCleaned;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 320 - 321
osdep/BSDEthernetTap.cpp

@@ -60,313 +60,313 @@ static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC
 namespace ZeroTier {
 
 BSDEthernetTap::BSDEthernetTap(
-    const char* homePath,
-    unsigned int concurrency,
-    bool pinning,
-    const MAC& mac,
-    unsigned int mtu,
-    unsigned int metric,
-    uint64_t nwid,
-    const char* friendlyName,
-    void (*handler)(void*, void*, uint64_t, const MAC&, const MAC&, unsigned int, unsigned int, const void*, unsigned int),
-    void* arg)
-    : _handler(handler)
-    , _concurrency(concurrency)
-    , _pinning(pinning)
-    , _arg(arg)
-    , _nwid(nwid)
-    , _mtu(mtu)
-    , _metric(metric)
-    , _fd(0)
-    , _enabled(true)
-    , _lastIfAddrsUpdate(0)
+	const char* homePath,
+	unsigned int concurrency,
+	bool pinning,
+	const MAC& mac,
+	unsigned int mtu,
+	unsigned int metric,
+	uint64_t nwid,
+	const char* friendlyName,
+	void (*handler)(void*, void*, uint64_t, const MAC&, const MAC&, unsigned int, unsigned int, const void*, unsigned int),
+	void* arg)
+	: _handler(handler)
+	, _concurrency(concurrency)
+	, _pinning(pinning)
+	, _arg(arg)
+	, _nwid(nwid)
+	, _mtu(mtu)
+	, _metric(metric)
+	, _fd(0)
+	, _enabled(true)
+	, _lastIfAddrsUpdate(0)
 {
-    static Mutex globalTapCreateLock;
-    char devpath[64], ethaddr[64], mtustr[32], metstr[32], tmpdevname[32];
+	static Mutex globalTapCreateLock;
+	char devpath[64], ethaddr[64], mtustr[32], metstr[32], tmpdevname[32];
 
-    Mutex::Lock _gl(globalTapCreateLock);
+	Mutex::Lock _gl(globalTapCreateLock);
 
 #ifdef __FreeBSD__
-    /* FreeBSD allows long interface names and interface renaming */
-
-    _dev = "zt";
-    _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 60) & 0x1f)]);
-    _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 55) & 0x1f)]);
-    _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 50) & 0x1f)]);
-    _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 45) & 0x1f)]);
-    _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 40) & 0x1f)]);
-    _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 35) & 0x1f)]);
-    _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 30) & 0x1f)]);
-    _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 25) & 0x1f)]);
-    _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 20) & 0x1f)]);
-    _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 15) & 0x1f)]);
-    _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 10) & 0x1f)]);
-    _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 5) & 0x1f)]);
-    _dev.push_back(ZT_BASE32_CHARS[(unsigned long)(nwid & 0x1f)]);
-
-    std::vector<std::string> devFiles(OSUtils::listDirectory("/dev"));
-    for (int i = 9993; i < (9993 + 128); ++i) {
-        OSUtils::ztsnprintf(tmpdevname, sizeof(tmpdevname), "tap%d", i);
-        OSUtils::ztsnprintf(devpath, sizeof(devpath), "/dev/%s", tmpdevname);
-        if (std::find(devFiles.begin(), devFiles.end(), std::string(tmpdevname)) == devFiles.end()) {
-            long cpid = (long)vfork();
-            if (cpid == 0) {
+	/* FreeBSD allows long interface names and interface renaming */
+
+	_dev = "zt";
+	_dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 60) & 0x1f)]);
+	_dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 55) & 0x1f)]);
+	_dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 50) & 0x1f)]);
+	_dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 45) & 0x1f)]);
+	_dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 40) & 0x1f)]);
+	_dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 35) & 0x1f)]);
+	_dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 30) & 0x1f)]);
+	_dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 25) & 0x1f)]);
+	_dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 20) & 0x1f)]);
+	_dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 15) & 0x1f)]);
+	_dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 10) & 0x1f)]);
+	_dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 5) & 0x1f)]);
+	_dev.push_back(ZT_BASE32_CHARS[(unsigned long)(nwid & 0x1f)]);
+
+	std::vector<std::string> devFiles(OSUtils::listDirectory("/dev"));
+	for (int i = 9993; i < (9993 + 128); ++i) {
+		OSUtils::ztsnprintf(tmpdevname, sizeof(tmpdevname), "tap%d", i);
+		OSUtils::ztsnprintf(devpath, sizeof(devpath), "/dev/%s", tmpdevname);
+		if (std::find(devFiles.begin(), devFiles.end(), std::string(tmpdevname)) == devFiles.end()) {
+			long cpid = (long)vfork();
+			if (cpid == 0) {
 #ifdef ZT_TRACE
-                fprintf(stderr, "DEBUG: ifconfig %s create" ZT_EOL_S, tmpdevname);
+				fprintf(stderr, "DEBUG: ifconfig %s create" ZT_EOL_S, tmpdevname);
 #endif
-                ::execl("/sbin/ifconfig", "/sbin/ifconfig", tmpdevname, "create", (const char*)0);
-                ::_exit(-1);
-            }
-            else if (cpid > 0) {
-                int exitcode = -1;
-                ::waitpid(cpid, &exitcode, 0);
-            }
-            else
-                throw std::runtime_error("fork() failed");
-
-            struct stat stattmp;
-            if (! stat(devpath, &stattmp)) {
-                cpid = (long)vfork();
-                if (cpid == 0) {
+				::execl("/sbin/ifconfig", "/sbin/ifconfig", tmpdevname, "create", (const char*)0);
+				::_exit(-1);
+			}
+			else if (cpid > 0) {
+				int exitcode = -1;
+				::waitpid(cpid, &exitcode, 0);
+			}
+			else
+				throw std::runtime_error("fork() failed");
+
+			struct stat stattmp;
+			if (! stat(devpath, &stattmp)) {
+				cpid = (long)vfork();
+				if (cpid == 0) {
 #ifdef ZT_TRACE
-                    fprintf(stderr, "DEBUG: ifconfig %s name %s" ZT_EOL_S, tmpdevname, _dev.c_str());
+					fprintf(stderr, "DEBUG: ifconfig %s name %s" ZT_EOL_S, tmpdevname, _dev.c_str());
 #endif
-                    ::execl("/sbin/ifconfig", "/sbin/ifconfig", tmpdevname, "name", _dev.c_str(), (const char*)0);
-                    ::_exit(-1);
-                }
-                else if (cpid > 0) {
-                    int exitcode = -1;
-                    ::waitpid(cpid, &exitcode, 0);
-                    if (exitcode)
-                        throw std::runtime_error("ifconfig rename operation failed");
-                }
-                else
-                    throw std::runtime_error("fork() failed");
-
-                _fd = ::open(devpath, O_RDWR);
-                if (_fd > 0)
-                    break;
-                else
-                    throw std::runtime_error("unable to open created tap device");
-            }
-            else {
-                throw std::runtime_error("cannot find /dev node for newly created tap device");
-            }
-        }
-    }
+					::execl("/sbin/ifconfig", "/sbin/ifconfig", tmpdevname, "name", _dev.c_str(), (const char*)0);
+					::_exit(-1);
+				}
+				else if (cpid > 0) {
+					int exitcode = -1;
+					::waitpid(cpid, &exitcode, 0);
+					if (exitcode)
+						throw std::runtime_error("ifconfig rename operation failed");
+				}
+				else
+					throw std::runtime_error("fork() failed");
+
+				_fd = ::open(devpath, O_RDWR);
+				if (_fd > 0)
+					break;
+				else
+					throw std::runtime_error("unable to open created tap device");
+			}
+			else {
+				throw std::runtime_error("cannot find /dev node for newly created tap device");
+			}
+		}
+	}
 #else
-    /* Other BSDs like OpenBSD only have a limited number of tap devices that cannot be renamed */
-
-    for (int i = 0; i < 64; ++i) {
-        OSUtils::ztsnprintf(tmpdevname, sizeof(tmpdevname), "tap%d", i);
-        OSUtils::ztsnprintf(devpath, sizeof(devpath), "/dev/%s", tmpdevname);
-        _fd = ::open(devpath, O_RDWR);
-        if (_fd > 0) {
-            _dev = tmpdevname;
-            break;
-        }
-    }
+	/* Other BSDs like OpenBSD only have a limited number of tap devices that cannot be renamed */
+
+	for (int i = 0; i < 64; ++i) {
+		OSUtils::ztsnprintf(tmpdevname, sizeof(tmpdevname), "tap%d", i);
+		OSUtils::ztsnprintf(devpath, sizeof(devpath), "/dev/%s", tmpdevname);
+		_fd = ::open(devpath, O_RDWR);
+		if (_fd > 0) {
+			_dev = tmpdevname;
+			break;
+		}
+	}
 #endif
 
-    if (_fd <= 0)
-        throw std::runtime_error("unable to open TAP device or no more devices available");
+	if (_fd <= 0)
+		throw std::runtime_error("unable to open TAP device or no more devices available");
 
-    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");
-    }
+	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
-    OSUtils::ztsnprintf(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]);
-    OSUtils::ztsnprintf(mtustr, sizeof(mtustr), "%u", _mtu);
-    OSUtils::ztsnprintf(metstr, sizeof(metstr), "%u", _metric);
-    long cpid = (long)vfork();
-    if (cpid == 0) {
+	// Configure MAC address and MTU, bring interface up
+	OSUtils::ztsnprintf(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]);
+	OSUtils::ztsnprintf(mtustr, sizeof(mtustr), "%u", _mtu);
+	OSUtils::ztsnprintf(metstr, sizeof(metstr), "%u", _metric);
+	long cpid = (long)vfork();
+	if (cpid == 0) {
 #ifdef ZT_TRACE
-        fprintf(stderr, "DEBUG: ifconfig %s lladdr %s mtu %s metric %s up" ZT_EOL_S, _dev.c_str(), ethaddr, mtustr, metstr);
+		fprintf(stderr, "DEBUG: ifconfig %s lladdr %s mtu %s metric %s up" ZT_EOL_S, _dev.c_str(), ethaddr, mtustr, metstr);
 #endif
-        ::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");
-        }
-    }
-
-    // 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);
-
-    _thread = Thread::start(this);
+		::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");
+		}
+	}
+
+	// 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);
+
+	_thread = Thread::start(this);
 }
 
 BSDEthernetTap::~BSDEthernetTap()
 {
-    ::write(_shutdownSignalPipe[1], "\0", 1);   // causes thread to exit
-    ::close(_fd);
-    ::close(_shutdownSignalPipe[0]);
-    ::close(_shutdownSignalPipe[1]);
-    long cpid = (long)vfork();
-    if (cpid == 0) {
+	::write(_shutdownSignalPipe[1], "\0", 1);	// causes thread to exit
+	::close(_fd);
+	::close(_shutdownSignalPipe[0]);
+	::close(_shutdownSignalPipe[1]);
+	long cpid = (long)vfork();
+	if (cpid == 0) {
 #ifdef ZT_TRACE
-        fprintf(stderr, "DEBUG: ifconfig %s destroy" ZT_EOL_S, _dev.c_str());
+		fprintf(stderr, "DEBUG: ifconfig %s destroy" ZT_EOL_S, _dev.c_str());
 #endif
-        ::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);
-    }
-    Thread::join(_thread);
-    for (std::thread& t : _rxThreads) {
-        t.join();
-    }
+		::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);
+	}
+	Thread::join(_thread);
+	for (std::thread& t : _rxThreads) {
+		t.join();
+	}
 }
 
 void BSDEthernetTap::setEnabled(bool en)
 {
-    _enabled = en;
+	_enabled = en;
 }
 
 bool BSDEthernetTap::enabled() const
 {
-    return _enabled;
+	return _enabled;
 }
 
 static bool ___removeIp(const std::string& _dev, const InetAddress& ip)
 {
-    long cpid = (long)vfork();
-    if (cpid == 0) {
-        char ipbuf[64];
+	long cpid = (long)vfork();
+	if (cpid == 0) {
+		char ipbuf[64];
 #ifdef ZT_TRACE
-        fprintf(stderr, "DEBUG: ifconfig %s inet %s -alias" ZT_EOL_S, _dev.c_str(), ip.toIpString(ipbuf));
+		fprintf(stderr, "DEBUG: ifconfig %s inet %s -alias" ZT_EOL_S, _dev.c_str(), ip.toIpString(ipbuf));
 #endif
-        execl("/sbin/ifconfig", "/sbin/ifconfig", _dev.c_str(), "inet", ip.toIpString(ipbuf), "-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
+		execl("/sbin/ifconfig", "/sbin/ifconfig", _dev.c_str(), "inet", ip.toIpString(ipbuf), "-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 BSDEthernetTap::addIp(const InetAddress& ip)
 {
-    if (! ip)
-        return false;
-
-    std::vector<InetAddress> allIps(ips());
-    if (std::find(allIps.begin(), allIps.end(), ip) != allIps.end())
-        return true;   // IP/netmask already assigned
-
-    // 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) {
-        char tmp[128];
+	if (! ip)
+		return false;
+
+	std::vector<InetAddress> allIps(ips());
+	if (std::find(allIps.begin(), allIps.end(), ip) != allIps.end())
+		return true;   // IP/netmask already assigned
+
+	// 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) {
+		char tmp[128];
 #ifdef ZT_TRACE
-        fprintf(stderr, "DEBUG: ifconfig %s %s %s alias" ZT_EOL_S, _dev.c_str(), ip.isV4() ? "inet" : "inet6", ip.toString(tmp));
+		fprintf(stderr, "DEBUG: ifconfig %s %s %s alias" ZT_EOL_S, _dev.c_str(), ip.isV4() ? "inet" : "inet6", ip.toString(tmp));
 #endif
-        ::execl("/sbin/ifconfig", "/sbin/ifconfig", _dev.c_str(), ip.isV4() ? "inet" : "inet6", ip.toString(tmp), "alias", (const char*)0);
-        ::_exit(-1);
-    }
-    else if (cpid > 0) {
-        int exitcode = -1;
-        ::waitpid(cpid, &exitcode, 0);
-        return (exitcode == 0);
-    }
-    return false;
+		::execl("/sbin/ifconfig", "/sbin/ifconfig", _dev.c_str(), ip.isV4() ? "inet" : "inet6", ip.toString(tmp), "alias", (const char*)0);
+		::_exit(-1);
+	}
+	else if (cpid > 0) {
+		int exitcode = -1;
+		::waitpid(cpid, &exitcode, 0);
+		return (exitcode == 0);
+	}
+	return false;
 }
 
 bool BSDEthernetTap::removeIp(const InetAddress& ip)
 {
-    if (! ip)
-        return false;
-    std::vector<InetAddress> allIps(ips());
-    if (std::find(allIps.begin(), allIps.end(), ip) != allIps.end()) {
-        if (___removeIp(_dev, ip))
-            return true;
-    }
-    return false;
+	if (! ip)
+		return false;
+	std::vector<InetAddress> allIps(ips());
+	if (std::find(allIps.begin(), allIps.end(), ip) != allIps.end()) {
+		if (___removeIp(_dev, ip))
+			return true;
+	}
+	return false;
 }
 
 std::vector<InetAddress> BSDEthernetTap::ips() const
 {
-    uint64_t now = OSUtils::now();
-
-    if ((now - _lastIfAddrsUpdate) <= GETIFADDRS_CACHE_TIME) {
-        return _ifaddrs;
-    }
-    _lastIfAddrsUpdate = now;
-
-    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());
-
-    _ifaddrs = r;
-
-    return r;
+	uint64_t now = OSUtils::now();
+
+	if ((now - _lastIfAddrsUpdate) <= GETIFADDRS_CACHE_TIME) {
+		return _ifaddrs;
+	}
+	_lastIfAddrsUpdate = now;
+
+	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());
+
+	_ifaddrs = r;
+
+	return r;
 }
 
 void BSDEthernetTap::put(const MAC& from, const MAC& to, unsigned int etherType, const void* data, unsigned int len)
 {
-    char putBuf[ZT_MAX_MTU + 64];
-    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);
-    }
+	char putBuf[ZT_MAX_MTU + 64];
+	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 BSDEthernetTap::deviceName() const
 {
-    return _dev;
+	return _dev;
 }
 
 void BSDEthernetTap::setFriendlyName(const char* friendlyName)
@@ -375,63 +375,63 @@ void BSDEthernetTap::setFriendlyName(const char* friendlyName)
 
 void BSDEthernetTap::scanMulticastGroups(std::vector<MulticastGroup>& added, std::vector<MulticastGroup>& removed)
 {
-    std::vector<MulticastGroup> newGroups;
+	std::vector<MulticastGroup> newGroups;
 
 #ifndef __OpenBSD__
-    struct ifmaddrs* ifmap = (struct ifmaddrs*)0;
-    if (! getifmaddrs(&ifmap)) {
-        struct 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;
-        }
-        freeifmaddrs(ifmap);
-    }
-#endif   // __OpenBSD__
-
-    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);
+	struct ifmaddrs* ifmap = (struct ifmaddrs*)0;
+	if (! getifmaddrs(&ifmap)) {
+		struct 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;
+		}
+		freeifmaddrs(ifmap);
+	}
+#endif	 // __OpenBSD__
+
+	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 BSDEthernetTap::setMtu(unsigned int mtu)
 {
-    if (mtu != _mtu) {
-        _mtu = mtu;
-        long cpid = (long)vfork();
-        if (cpid == 0) {
-            char tmp[64];
-            OSUtils::ztsnprintf(tmp, sizeof(tmp), "%u", mtu);
+	if (mtu != _mtu) {
+		_mtu = mtu;
+		long cpid = (long)vfork();
+		if (cpid == 0) {
+			char tmp[64];
+			OSUtils::ztsnprintf(tmp, sizeof(tmp), "%u", mtu);
 #ifdef ZT_TRACE
-            fprintf(stderr, "DEBUG: ifconfig %s mtu %s" ZT_EOL_S, _dev.c_str(), tmp);
+			fprintf(stderr, "DEBUG: ifconfig %s mtu %s" ZT_EOL_S, _dev.c_str(), tmp);
 #endif
-            execl("/sbin/ifconfig", "/sbin/ifconfig", _dev.c_str(), "mtu", tmp, (const char*)0);
-            _exit(-1);
-        }
-        else if (cpid > 0) {
-            int exitcode = -1;
-            waitpid(cpid, &exitcode, 0);
-        }
-    }
+			execl("/sbin/ifconfig", "/sbin/ifconfig", _dev.c_str(), "mtu", tmp, (const char*)0);
+			_exit(-1);
+		}
+		else if (cpid > 0) {
+			int exitcode = -1;
+			waitpid(cpid, &exitcode, 0);
+		}
+	}
 }
 
 void BSDEthernetTap::threadMain() throw()
@@ -445,7 +445,6 @@ void BSDEthernetTap::threadMain() throw()
 
 	for (unsigned int i = 0; i < _concurrency; ++i) {
 		_rxThreads.push_back(std::thread([this, i, pinning] {
-
 			if (pinning) {
 				int pinCore = i % _concurrency;
 				fprintf(stderr, "Pinning thread %d to core %d\n", i, pinCore);
@@ -453,15 +452,14 @@ void BSDEthernetTap::threadMain() throw()
 				cpu_set_t cpuset;
 				CPU_ZERO(&cpuset);
 				CPU_SET(pinCore, &cpuset);
-				//int rc = sched_setaffinity(self, sizeof(cpu_set_t), &cpuset);
+				// int rc = sched_setaffinity(self, sizeof(cpu_set_t), &cpuset);
 				int rc = pthread_setaffinity_np(self, sizeof(cpu_set_t), &cpuset);
-				if (rc != 0)
-				{
+				if (rc != 0) {
 					fprintf(stderr, "Failed to pin thread %d to core %d: %s\n", i, pinCore, strerror(errno));
 					exit(1);
 				}
 			}
-#endif // __OpenBSD__
+#endif	 // __OpenBSD__
 
 			uint8_t b[ZT_TAP_BUF_SIZE];
 			MAC to, from;
@@ -470,37 +468,38 @@ void BSDEthernetTap::threadMain() throw()
 
 			FD_ZERO(&readfds);
 			FD_ZERO(&nullfds);
-			nfds = (int)std::max(_shutdownSignalPipe[0],_fd) + 1;
+			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);
+			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
+				if (FD_ISSET(_shutdownSignalPipe[0], &readfds))	  // writes to shutdown pipe terminate thread
 					break;
 
-				if (FD_ISSET(_fd,&readfds)) {
-					n = (int)::read(_fd,b + r,sizeof(b) - r);
+				if (FD_ISSET(_fd, &readfds)) {
+					n = (int)::read(_fd, b + r, sizeof(b) - r);
 					if (n < 0) {
-						if ((errno != EINTR)&&(errno != ETIMEDOUT))
+						if ((errno != EINTR) && (errno != ETIMEDOUT))
 							break;
-					} else {
+					}
+					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
+							if (r > ((int)_mtu + 14))	// sanity check for weird TAP behavior on some platforms
 								r = _mtu + 14;
 
 							if (_enabled) {
-								to.setTo(b,6);
-								from.setTo(b + 6,6);
-								unsigned int etherType = ntohs(((const uint16_t *)b)[6]);
-								_handler(_arg,(void *)0,_nwid,from,to,etherType,0,(const void *)(b + 14),r - 14);
+								to.setTo(b, 6);
+								from.setTo(b + 6, 6);
+								unsigned int etherType = ntohs(((const uint16_t*)b)[6]);
+								_handler(_arg, (void*)0, _nwid, from, to, etherType, 0, (const void*)(b + 14), r - 14);
 							}
 
 							r = 0;
@@ -511,7 +510,7 @@ void BSDEthernetTap::threadMain() throw()
 #ifndef __OpenBSD__
 		}));
 	}
-#endif // __OpenBSD__
+#endif	 // __OpenBSD__
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

+ 43 - 43
osdep/BSDEthernetTap.hpp

@@ -31,55 +31,55 @@ namespace ZeroTier {
 
 class BSDEthernetTap : public EthernetTap {
   public:
-    BSDEthernetTap(
-        const char* homePath,
-        unsigned int concurrency,
-        bool pinning,
-        const MAC& mac,
-        unsigned int mtu,
-        unsigned int metric,
-        uint64_t nwid,
-        const char* friendlyName,
-        void (*handler)(void*, void*, uint64_t, const MAC&, const MAC&, unsigned int, unsigned int, const void*, unsigned int),
-        void* arg);
+	BSDEthernetTap(
+		const char* homePath,
+		unsigned int concurrency,
+		bool pinning,
+		const MAC& mac,
+		unsigned int mtu,
+		unsigned int metric,
+		uint64_t nwid,
+		const char* friendlyName,
+		void (*handler)(void*, void*, uint64_t, const MAC&, const MAC&, unsigned int, unsigned int, const void*, unsigned int),
+		void* arg);
 
-    virtual ~BSDEthernetTap();
+	virtual ~BSDEthernetTap();
 
-    virtual void setEnabled(bool en);
-    virtual bool enabled() const;
-    virtual bool addIp(const InetAddress& ip);
-    virtual bool removeIp(const InetAddress& ip);
-    virtual std::vector<InetAddress> ips() const;
-    virtual void put(const MAC& from, const MAC& to, unsigned int etherType, const void* data, unsigned int len);
-    virtual std::string deviceName() const;
-    virtual void setFriendlyName(const char* friendlyName);
-    virtual void scanMulticastGroups(std::vector<MulticastGroup>& added, std::vector<MulticastGroup>& removed);
-    virtual void setMtu(unsigned int mtu);
-    virtual void setDns(const char* domain, const std::vector<InetAddress>& servers)
-    {
-    }
+	virtual void setEnabled(bool en);
+	virtual bool enabled() const;
+	virtual bool addIp(const InetAddress& ip);
+	virtual bool removeIp(const InetAddress& ip);
+	virtual std::vector<InetAddress> ips() const;
+	virtual void put(const MAC& from, const MAC& to, unsigned int etherType, const void* data, unsigned int len);
+	virtual std::string deviceName() const;
+	virtual void setFriendlyName(const char* friendlyName);
+	virtual void scanMulticastGroups(std::vector<MulticastGroup>& added, std::vector<MulticastGroup>& removed);
+	virtual void setMtu(unsigned int mtu);
+	virtual void setDns(const char* domain, const std::vector<InetAddress>& servers)
+	{
+	}
 
-    void threadMain() throw();
+	void threadMain() throw();
 
   private:
-    void (*_handler)(void*, void*, uint64_t, const MAC&, const MAC&, unsigned int, unsigned int, const void*, unsigned int);
-    void* _arg;
-    unsigned int _concurrency;
-    bool _pinning;
-    uint64_t _nwid;
-    Thread _thread;
-    std::string _dev;
-    std::vector<MulticastGroup> _multicastGroups;
-    unsigned int _mtu;
-    unsigned int _metric;
-    int _fd;
-    int _shutdownSignalPipe[2];
-    volatile bool _enabled;
-    mutable std::vector<InetAddress> _ifaddrs;
-    mutable uint64_t _lastIfAddrsUpdate;
-    std::vector<std::thread> _rxThreads;
+	void (*_handler)(void*, void*, uint64_t, const MAC&, const MAC&, unsigned int, unsigned int, const void*, unsigned int);
+	void* _arg;
+	unsigned int _concurrency;
+	bool _pinning;
+	uint64_t _nwid;
+	Thread _thread;
+	std::string _dev;
+	std::vector<MulticastGroup> _multicastGroups;
+	unsigned int _mtu;
+	unsigned int _metric;
+	int _fd;
+	int _shutdownSignalPipe[2];
+	volatile bool _enabled;
+	mutable std::vector<InetAddress> _ifaddrs;
+	mutable uint64_t _lastIfAddrsUpdate;
+	std::vector<std::thread> _rxThreads;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 428 - 428
osdep/Binder.hpp

@@ -86,459 +86,459 @@ namespace ZeroTier {
  */
 class Binder {
   private:
-    struct _Binding {
-        _Binding() : udpSock((PhySocket*)0)
-        {
-        }
-        PhySocket* udpSock;
-        InetAddress address;
-        char ifname[256] = {};
-    };
+	struct _Binding {
+		_Binding() : udpSock((PhySocket*)0)
+		{
+		}
+		PhySocket* udpSock;
+		InetAddress address;
+		char ifname[256] = {};
+	};
 
   public:
-    Binder() : _bindingCount(0)
-    {
-    }
-
-    /**
-     * Close all bound ports, should be called on shutdown
-     *
-     * @param phy Physical interface
-     */
-    template <typename PHY_HANDLER_TYPE> void closeAll(Phy<PHY_HANDLER_TYPE>& phy)
-    {
-        Mutex::Lock _l(_lock);
-        for (unsigned int b = 0, c = _bindingCount; b < c; ++b) {
-            phy.close(_bindings[b].udpSock, false);
-        }
-        _bindingCount = 0;
-    }
-
-    /**
-     * Scan local devices and addresses and rebind TCP and UDP
-     *
-     * This should be called after wake from sleep, on detected network device
-     * changes, on startup, or periodically (e.g. every 30-60s).
-     *
-     * @param phy Physical interface
-     * @param ports Ports to bind on all interfaces
-     * @param portCount Number of ports
-     * @param explicitBind If present, override interface IP detection and bind to these (if possible)
-     * @param ifChecker Interface checker function to see if an interface should be used
-     * @tparam PHY_HANDLER_TYPE Type for Phy<> template
-     * @tparam INTERFACE_CHECKER Type for class containing shouldBindInterface() method
-     */
-    template <typename PHY_HANDLER_TYPE, typename INTERFACE_CHECKER> void refresh(Phy<PHY_HANDLER_TYPE>& phy, unsigned int* ports, unsigned int portCount, const std::vector<InetAddress> explicitBind, INTERFACE_CHECKER& ifChecker)
-    {
-        std::map<InetAddress, std::string> localIfAddrs;
-        PhySocket* udps;
-        Mutex::Lock _l(_lock);
-        bool interfacesEnumerated = true;
-
-        if (explicitBind.empty()) {
+	Binder() : _bindingCount(0)
+	{
+	}
+
+	/**
+	 * Close all bound ports, should be called on shutdown
+	 *
+	 * @param phy Physical interface
+	 */
+	template <typename PHY_HANDLER_TYPE> void closeAll(Phy<PHY_HANDLER_TYPE>& phy)
+	{
+		Mutex::Lock _l(_lock);
+		for (unsigned int b = 0, c = _bindingCount; b < c; ++b) {
+			phy.close(_bindings[b].udpSock, false);
+		}
+		_bindingCount = 0;
+	}
+
+	/**
+	 * Scan local devices and addresses and rebind TCP and UDP
+	 *
+	 * This should be called after wake from sleep, on detected network device
+	 * changes, on startup, or periodically (e.g. every 30-60s).
+	 *
+	 * @param phy Physical interface
+	 * @param ports Ports to bind on all interfaces
+	 * @param portCount Number of ports
+	 * @param explicitBind If present, override interface IP detection and bind to these (if possible)
+	 * @param ifChecker Interface checker function to see if an interface should be used
+	 * @tparam PHY_HANDLER_TYPE Type for Phy<> template
+	 * @tparam INTERFACE_CHECKER Type for class containing shouldBindInterface() method
+	 */
+	template <typename PHY_HANDLER_TYPE, typename INTERFACE_CHECKER> void refresh(Phy<PHY_HANDLER_TYPE>& phy, unsigned int* ports, unsigned int portCount, const std::vector<InetAddress> explicitBind, INTERFACE_CHECKER& ifChecker)
+	{
+		std::map<InetAddress, std::string> localIfAddrs;
+		PhySocket* udps;
+		Mutex::Lock _l(_lock);
+		bool interfacesEnumerated = true;
+
+		if (explicitBind.empty()) {
 #ifdef __WINDOWS__
 
-            char aabuf[32768];
-            ULONG aalen = sizeof(aabuf);
-            if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER, (void*)0, reinterpret_cast<PIP_ADAPTER_ADDRESSES>(aabuf), &aalen) == NO_ERROR) {
-                PIP_ADAPTER_ADDRESSES a = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(aabuf);
-                while (a) {
-                    PIP_ADAPTER_UNICAST_ADDRESS ua = a->FirstUnicastAddress;
-                    while (ua) {
-                        // Don't bind temporary/random IPv6 addresses
-                        if (ua->SuffixOrigin != IpSuffixOriginRandom) {
-                            InetAddress ip(ua->Address.lpSockaddr);
-                            char strBuf[128] = { 0 };
-                            wcstombs(strBuf, a->FriendlyName, sizeof(strBuf));
-                            if (ifChecker.shouldBindInterface(strBuf, ip)) {
-                                switch (ip.ipScope()) {
-                                    default:
-                                        break;
-                                    case InetAddress::IP_SCOPE_PSEUDOPRIVATE:
-                                    case InetAddress::IP_SCOPE_GLOBAL:
-                                    case InetAddress::IP_SCOPE_SHARED:
-                                    case InetAddress::IP_SCOPE_PRIVATE:
-                                        for (int x = 0; x < (int)portCount; ++x) {
-                                            ip.setPort(ports[x]);
-                                            localIfAddrs.insert(std::pair<InetAddress, std::string>(ip, std::string()));
-                                        }
-                                        break;
-                                }
-                            }
-                        }
-                        ua = ua->Next;
-                    }
-                    a = a->Next;
-                }
-            }
-            else {
-                interfacesEnumerated = false;
-            }
-
-#else   // not __WINDOWS__
-
-            /* On Linux we use an alternative method if available since getifaddrs()
-             * gets very slow when there are lots of network namespaces. This won't
-             * work unless /proc/PID/net/if_inet6 exists and it may not on some
-             * embedded systems, so revert to getifaddrs() there. */
+			char aabuf[32768];
+			ULONG aalen = sizeof(aabuf);
+			if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER, (void*)0, reinterpret_cast<PIP_ADAPTER_ADDRESSES>(aabuf), &aalen) == NO_ERROR) {
+				PIP_ADAPTER_ADDRESSES a = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(aabuf);
+				while (a) {
+					PIP_ADAPTER_UNICAST_ADDRESS ua = a->FirstUnicastAddress;
+					while (ua) {
+						// Don't bind temporary/random IPv6 addresses
+						if (ua->SuffixOrigin != IpSuffixOriginRandom) {
+							InetAddress ip(ua->Address.lpSockaddr);
+							char strBuf[128] = { 0 };
+							wcstombs(strBuf, a->FriendlyName, sizeof(strBuf));
+							if (ifChecker.shouldBindInterface(strBuf, ip)) {
+								switch (ip.ipScope()) {
+									default:
+										break;
+									case InetAddress::IP_SCOPE_PSEUDOPRIVATE:
+									case InetAddress::IP_SCOPE_GLOBAL:
+									case InetAddress::IP_SCOPE_SHARED:
+									case InetAddress::IP_SCOPE_PRIVATE:
+										for (int x = 0; x < (int)portCount; ++x) {
+											ip.setPort(ports[x]);
+											localIfAddrs.insert(std::pair<InetAddress, std::string>(ip, std::string()));
+										}
+										break;
+								}
+							}
+						}
+						ua = ua->Next;
+					}
+					a = a->Next;
+				}
+			}
+			else {
+				interfacesEnumerated = false;
+			}
+
+#else	// not __WINDOWS__
+
+			/* On Linux we use an alternative method if available since getifaddrs()
+			 * gets very slow when there are lots of network namespaces. This won't
+			 * work unless /proc/PID/net/if_inet6 exists and it may not on some
+			 * embedded systems, so revert to getifaddrs() there. */
 
 #ifdef __LINUX__
-            char fn[256], tmp[256];
-            std::set<std::string> ifnames;
-            const unsigned long pid = (unsigned long)getpid();
-
-            // Get all device names
-            OSUtils::ztsnprintf(fn, sizeof(fn), "/proc/%lu/net/dev", pid);
-            FILE* procf = fopen(fn, "r");
-            if (procf) {
-                while (fgets(tmp, sizeof(tmp), procf)) {
-                    tmp[255] = 0;
-                    char* saveptr = (char*)0;
-                    for (char* f = Utils::stok(tmp, " \t\r\n:|", &saveptr); (f); f = Utils::stok((char*)0, " \t\r\n:|", &saveptr)) {
-                        if ((strcmp(f, "Inter-") != 0) && (strcmp(f, "face") != 0) && (f[0] != 0))
-                            ifnames.insert(f);
-                        break;   // we only want the first field
-                    }
-                }
-                fclose(procf);
-            }
-            else {
-                interfacesEnumerated = false;
-            }
-
-            // Get IPv6 addresses (and any device names we don't already know)
-            OSUtils::ztsnprintf(fn, sizeof(fn), "/proc/%lu/net/if_inet6", pid);
-            procf = fopen(fn, "r");
-            if (procf) {
-                while (fgets(tmp, sizeof(tmp), procf)) {
-                    tmp[255] = 0;
-                    char* saveptr = (char*)0;
-                    unsigned char ipbits[16];
-                    memset(ipbits, 0, sizeof(ipbits));
-                    char* devname = (char*)0;
-                    int flags = 0;
-                    int n = 0;
-                    for (char* f = Utils::stok(tmp, " \t\r\n", &saveptr); (f); f = Utils::stok((char*)0, " \t\r\n", &saveptr)) {
-                        switch (n++) {
-                            case 0:   // IP in hex
-                                Utils::unhex(f, 32, ipbits, 16);
-                                break;
-                            case 4:
-                                flags = atoi(f);
-                                break;
-                            case 5:   // device name
-                                devname = f;
-                                break;
-                        }
-                    }
-
-                    if ((flags & IFA_F_TEMPORARY) != 0) {
-                        continue;
-                    }
-                    if (devname) {
-                        ifnames.insert(devname);
-                        InetAddress ip(ipbits, 16, 0);
-                        if (ifChecker.shouldBindInterface(devname, ip)) {
-                            switch (ip.ipScope()) {
-                                default:
-                                    break;
-                                case InetAddress::IP_SCOPE_PSEUDOPRIVATE:
-                                case InetAddress::IP_SCOPE_GLOBAL:
-                                case InetAddress::IP_SCOPE_SHARED:
-                                case InetAddress::IP_SCOPE_PRIVATE:
-                                    for (int x = 0; x < (int)portCount; ++x) {
-                                        ip.setPort(ports[x]);
-                                        localIfAddrs.insert(std::pair<InetAddress, std::string>(ip, std::string(devname)));
-                                    }
-                                    break;
-                            }
-                        }
-                    }
-                }
-                fclose(procf);
-            }
-
-            // Get IPv4 addresses for each device
-            if (! ifnames.empty()) {
-                const int controlfd = (int)socket(AF_INET, SOCK_DGRAM, 0);
-                struct ifconf configuration;
-                configuration.ifc_len = 0;
-                configuration.ifc_buf = nullptr;
-
-                if (controlfd < 0)
-                    goto ip4_address_error;
-                if (ioctl(controlfd, SIOCGIFCONF, &configuration) < 0)
-                    goto ip4_address_error;
-                configuration.ifc_buf = (char*)malloc(configuration.ifc_len);
-                if (ioctl(controlfd, SIOCGIFCONF, &configuration) < 0)
-                    goto ip4_address_error;
-
-                for (int i = 0; i < (int)(configuration.ifc_len / sizeof(ifreq)); i++) {
-                    struct ifreq& request = configuration.ifc_req[i];
-                    struct sockaddr* addr = &request.ifr_ifru.ifru_addr;
-                    if (addr->sa_family != AF_INET)
-                        continue;
-                    std::string ifname = request.ifr_ifrn.ifrn_name;
-                    // name can either be just interface name or interface name followed by ':' and arbitrary label
-                    if (ifname.find(':') != std::string::npos)
-                        ifname = ifname.substr(0, ifname.find(':'));
-
-                    InetAddress ip(&(((struct sockaddr_in*)addr)->sin_addr), 4, 0);
-                    if (ifChecker.shouldBindInterface(ifname.c_str(), ip)) {
-                        switch (ip.ipScope()) {
-                            default:
-                                break;
-                            case InetAddress::IP_SCOPE_PSEUDOPRIVATE:
-                            case InetAddress::IP_SCOPE_GLOBAL:
-                            case InetAddress::IP_SCOPE_SHARED:
-                            case InetAddress::IP_SCOPE_PRIVATE:
-                                for (int x = 0; x < (int)portCount; ++x) {
-                                    ip.setPort(ports[x]);
-                                    localIfAddrs.insert(std::pair<InetAddress, std::string>(ip, ifname));
-                                }
-                                break;
-                        }
-                    }
-                }
-
-            ip4_address_error:
-                free(configuration.ifc_buf);
-                if (controlfd > 0)
-                    close(controlfd);
-            }
-
-            const bool gotViaProc = (! localIfAddrs.empty());
+			char fn[256], tmp[256];
+			std::set<std::string> ifnames;
+			const unsigned long pid = (unsigned long)getpid();
+
+			// Get all device names
+			OSUtils::ztsnprintf(fn, sizeof(fn), "/proc/%lu/net/dev", pid);
+			FILE* procf = fopen(fn, "r");
+			if (procf) {
+				while (fgets(tmp, sizeof(tmp), procf)) {
+					tmp[255] = 0;
+					char* saveptr = (char*)0;
+					for (char* f = Utils::stok(tmp, " \t\r\n:|", &saveptr); (f); f = Utils::stok((char*)0, " \t\r\n:|", &saveptr)) {
+						if ((strcmp(f, "Inter-") != 0) && (strcmp(f, "face") != 0) && (f[0] != 0))
+							ifnames.insert(f);
+						break;	 // we only want the first field
+					}
+				}
+				fclose(procf);
+			}
+			else {
+				interfacesEnumerated = false;
+			}
+
+			// Get IPv6 addresses (and any device names we don't already know)
+			OSUtils::ztsnprintf(fn, sizeof(fn), "/proc/%lu/net/if_inet6", pid);
+			procf = fopen(fn, "r");
+			if (procf) {
+				while (fgets(tmp, sizeof(tmp), procf)) {
+					tmp[255] = 0;
+					char* saveptr = (char*)0;
+					unsigned char ipbits[16];
+					memset(ipbits, 0, sizeof(ipbits));
+					char* devname = (char*)0;
+					int flags = 0;
+					int n = 0;
+					for (char* f = Utils::stok(tmp, " \t\r\n", &saveptr); (f); f = Utils::stok((char*)0, " \t\r\n", &saveptr)) {
+						switch (n++) {
+							case 0:	  // IP in hex
+								Utils::unhex(f, 32, ipbits, 16);
+								break;
+							case 4:
+								flags = atoi(f);
+								break;
+							case 5:	  // device name
+								devname = f;
+								break;
+						}
+					}
+
+					if ((flags & IFA_F_TEMPORARY) != 0) {
+						continue;
+					}
+					if (devname) {
+						ifnames.insert(devname);
+						InetAddress ip(ipbits, 16, 0);
+						if (ifChecker.shouldBindInterface(devname, ip)) {
+							switch (ip.ipScope()) {
+								default:
+									break;
+								case InetAddress::IP_SCOPE_PSEUDOPRIVATE:
+								case InetAddress::IP_SCOPE_GLOBAL:
+								case InetAddress::IP_SCOPE_SHARED:
+								case InetAddress::IP_SCOPE_PRIVATE:
+									for (int x = 0; x < (int)portCount; ++x) {
+										ip.setPort(ports[x]);
+										localIfAddrs.insert(std::pair<InetAddress, std::string>(ip, std::string(devname)));
+									}
+									break;
+							}
+						}
+					}
+				}
+				fclose(procf);
+			}
+
+			// Get IPv4 addresses for each device
+			if (! ifnames.empty()) {
+				const int controlfd = (int)socket(AF_INET, SOCK_DGRAM, 0);
+				struct ifconf configuration;
+				configuration.ifc_len = 0;
+				configuration.ifc_buf = nullptr;
+
+				if (controlfd < 0)
+					goto ip4_address_error;
+				if (ioctl(controlfd, SIOCGIFCONF, &configuration) < 0)
+					goto ip4_address_error;
+				configuration.ifc_buf = (char*)malloc(configuration.ifc_len);
+				if (ioctl(controlfd, SIOCGIFCONF, &configuration) < 0)
+					goto ip4_address_error;
+
+				for (int i = 0; i < (int)(configuration.ifc_len / sizeof(ifreq)); i++) {
+					struct ifreq& request = configuration.ifc_req[i];
+					struct sockaddr* addr = &request.ifr_ifru.ifru_addr;
+					if (addr->sa_family != AF_INET)
+						continue;
+					std::string ifname = request.ifr_ifrn.ifrn_name;
+					// name can either be just interface name or interface name followed by ':' and arbitrary label
+					if (ifname.find(':') != std::string::npos)
+						ifname = ifname.substr(0, ifname.find(':'));
+
+					InetAddress ip(&(((struct sockaddr_in*)addr)->sin_addr), 4, 0);
+					if (ifChecker.shouldBindInterface(ifname.c_str(), ip)) {
+						switch (ip.ipScope()) {
+							default:
+								break;
+							case InetAddress::IP_SCOPE_PSEUDOPRIVATE:
+							case InetAddress::IP_SCOPE_GLOBAL:
+							case InetAddress::IP_SCOPE_SHARED:
+							case InetAddress::IP_SCOPE_PRIVATE:
+								for (int x = 0; x < (int)portCount; ++x) {
+									ip.setPort(ports[x]);
+									localIfAddrs.insert(std::pair<InetAddress, std::string>(ip, ifname));
+								}
+								break;
+						}
+					}
+				}
+
+			ip4_address_error:
+				free(configuration.ifc_buf);
+				if (controlfd > 0)
+					close(controlfd);
+			}
+
+			const bool gotViaProc = (! localIfAddrs.empty());
 #else
-            const bool gotViaProc = false;
+			const bool gotViaProc = false;
 #endif
 
-            //
-            // prevent:
-            // warning: unused variable 'gotViaProc'
-            //
-            (void)gotViaProc;
+			//
+			// prevent:
+			// warning: unused variable 'gotViaProc'
+			//
+			(void)gotViaProc;
 
-#if ! defined(__ANDROID__)   // getifaddrs() freeifaddrs() not available on Android
-            if (! gotViaProc) {
-                struct ifaddrs* ifatbl = (struct ifaddrs*)0;
-                struct ifaddrs* ifa;
+#if ! defined(__ANDROID__)	 // getifaddrs() freeifaddrs() not available on Android
+			if (! gotViaProc) {
+				struct ifaddrs* ifatbl = (struct ifaddrs*)0;
+				struct ifaddrs* ifa;
 #if (defined(__unix__) || defined(__APPLE__)) && ! defined(__LINUX__) && ! defined(ZT_SDK)
-                // set up an IPv6 socket so we can check the state of interfaces via SIOCGIFAFLAG_IN6
-                int infoSock = socket(AF_INET6, SOCK_DGRAM, 0);
+				// set up an IPv6 socket so we can check the state of interfaces via SIOCGIFAFLAG_IN6
+				int infoSock = socket(AF_INET6, SOCK_DGRAM, 0);
 #endif
-                if ((getifaddrs(&ifatbl) == 0) && (ifatbl)) {
-                    ifa = ifatbl;
-                    while (ifa) {
-                        if ((ifa->ifa_name) && (ifa->ifa_addr)) {
-                            InetAddress ip = *(ifa->ifa_addr);
+				if ((getifaddrs(&ifatbl) == 0) && (ifatbl)) {
+					ifa = ifatbl;
+					while (ifa) {
+						if ((ifa->ifa_name) && (ifa->ifa_addr)) {
+							InetAddress ip = *(ifa->ifa_addr);
 #if (defined(__unix__) || defined(__APPLE__)) && ! defined(__LINUX__) && ! defined(ZT_SDK) && TARGET_OS_OSX
-                            // Check if the address is an IPv6 Temporary Address, macOS/BSD version
-                            if (ifa->ifa_addr->sa_family == AF_INET6) {
-                                struct sockaddr_in6* sa6 = (struct sockaddr_in6*)ifa->ifa_addr;
-                                struct in6_ifreq ifr6;
-                                memset(&ifr6, 0, sizeof(ifr6));
-                                strcpy(ifr6.ifr_name, ifa->ifa_name);
-                                ifr6.ifr_ifru.ifru_addr = *sa6;
-
-                                int flags = 0;
-                                if (ioctl(infoSock, SIOCGIFAFLAG_IN6, (unsigned long long)&ifr6) != -1) {
-                                    flags = ifr6.ifr_ifru.ifru_flags6;
-                                }
-
-                                // if this is a temporary IPv6 address, skip to the next address
-                                if (flags & IN6_IFF_TEMPORARY) {
+							// Check if the address is an IPv6 Temporary Address, macOS/BSD version
+							if (ifa->ifa_addr->sa_family == AF_INET6) {
+								struct sockaddr_in6* sa6 = (struct sockaddr_in6*)ifa->ifa_addr;
+								struct in6_ifreq ifr6;
+								memset(&ifr6, 0, sizeof(ifr6));
+								strcpy(ifr6.ifr_name, ifa->ifa_name);
+								ifr6.ifr_ifru.ifru_addr = *sa6;
+
+								int flags = 0;
+								if (ioctl(infoSock, SIOCGIFAFLAG_IN6, (unsigned long long)&ifr6) != -1) {
+									flags = ifr6.ifr_ifru.ifru_flags6;
+								}
+
+								// if this is a temporary IPv6 address, skip to the next address
+								if (flags & IN6_IFF_TEMPORARY) {
 #ifdef ZT_TRACE
-                                    char buf[64];
-                                    fprintf(stderr, "skip binding to temporary IPv6 address: %s\n", ip.toIpString(buf));
+									char buf[64];
+									fprintf(stderr, "skip binding to temporary IPv6 address: %s\n", ip.toIpString(buf));
 #endif
-                                    ifa = ifa->ifa_next;
-                                    continue;
-                                }
-                            }
+									ifa = ifa->ifa_next;
+									continue;
+								}
+							}
 #endif
-                            if (ifChecker.shouldBindInterface(ifa->ifa_name, ip)) {
-                                switch (ip.ipScope()) {
-                                    default:
-                                        break;
-                                    case InetAddress::IP_SCOPE_PSEUDOPRIVATE:
-                                    case InetAddress::IP_SCOPE_GLOBAL:
-                                    case InetAddress::IP_SCOPE_SHARED:
-                                    case InetAddress::IP_SCOPE_PRIVATE:
-                                        for (int x = 0; x < (int)portCount; ++x) {
-                                            ip.setPort(ports[x]);
-                                            localIfAddrs.insert(std::pair<InetAddress, std::string>(ip, std::string(ifa->ifa_name)));
-                                        }
-                                        break;
-                                }
-                            }
-                        }
-                        ifa = ifa->ifa_next;
-                    }
-                    freeifaddrs(ifatbl);
-                }
-                else {
-                    interfacesEnumerated = false;
-                }
+							if (ifChecker.shouldBindInterface(ifa->ifa_name, ip)) {
+								switch (ip.ipScope()) {
+									default:
+										break;
+									case InetAddress::IP_SCOPE_PSEUDOPRIVATE:
+									case InetAddress::IP_SCOPE_GLOBAL:
+									case InetAddress::IP_SCOPE_SHARED:
+									case InetAddress::IP_SCOPE_PRIVATE:
+										for (int x = 0; x < (int)portCount; ++x) {
+											ip.setPort(ports[x]);
+											localIfAddrs.insert(std::pair<InetAddress, std::string>(ip, std::string(ifa->ifa_name)));
+										}
+										break;
+								}
+							}
+						}
+						ifa = ifa->ifa_next;
+					}
+					freeifaddrs(ifatbl);
+				}
+				else {
+					interfacesEnumerated = false;
+				}
 #if (defined(__unix__) || defined(__APPLE__)) && ! defined(__LINUX__) && ! defined(ZT_SDK)
-                close(infoSock);
+				close(infoSock);
 #endif
-            }
+			}
 #endif
 
 #endif
-        }
-        else {
-            for (std::vector<InetAddress>::const_iterator i(explicitBind.begin()); i != explicitBind.end(); ++i) {
-                InetAddress ip = InetAddress(*i);
-                for (int x = 0; x < (int)portCount; ++x) {
-                    ip.setPort(ports[x]);
-                    localIfAddrs.insert(std::pair<InetAddress, std::string>(ip, std::string()));
-                }
-            }
-        }
-
-        // Default to binding to wildcard if we can't enumerate addresses
-        if (! interfacesEnumerated && localIfAddrs.empty()) {
-            for (int x = 0; x < (int)portCount; ++x) {
-                localIfAddrs.insert(std::pair<InetAddress, std::string>(InetAddress((uint32_t)0, ports[x]), std::string()));
-                localIfAddrs.insert(std::pair<InetAddress, std::string>(InetAddress((const void*)"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16, ports[x]), std::string()));
-            }
-        }
-
-        const unsigned int oldBindingCount = _bindingCount;
-        _bindingCount = 0;
-
-        // Save bindings that are still valid, close those that are not
-        for (unsigned int b = 0; b < oldBindingCount; ++b) {
-            if (localIfAddrs.find(_bindings[b].address) != localIfAddrs.end()) {
-                if (_bindingCount != b)
-                    _bindings[(unsigned int)_bindingCount] = _bindings[b];
-                ++_bindingCount;
-            }
-            else {
-                PhySocket* const udps = _bindings[b].udpSock;
-                _bindings[b].udpSock = (PhySocket*)0;
-                phy.close(udps, false);
-            }
-        }
-
-        // Create new bindings for those not already bound
-        for (std::map<InetAddress, std::string>::const_iterator ii(localIfAddrs.begin()); ii != localIfAddrs.end(); ++ii) {
-            unsigned int bi = 0;
-            while (bi != _bindingCount) {
-                if (_bindings[bi].address == ii->first)
-                    break;
-                ++bi;
-            }
-            if (bi == _bindingCount) {
-                udps = phy.udpBind(reinterpret_cast<const struct sockaddr*>(&(ii->first)), (void*)0, ZT_UDP_DESIRED_BUF_SIZE);
-                if (udps) {
+		}
+		else {
+			for (std::vector<InetAddress>::const_iterator i(explicitBind.begin()); i != explicitBind.end(); ++i) {
+				InetAddress ip = InetAddress(*i);
+				for (int x = 0; x < (int)portCount; ++x) {
+					ip.setPort(ports[x]);
+					localIfAddrs.insert(std::pair<InetAddress, std::string>(ip, std::string()));
+				}
+			}
+		}
+
+		// Default to binding to wildcard if we can't enumerate addresses
+		if (! interfacesEnumerated && localIfAddrs.empty()) {
+			for (int x = 0; x < (int)portCount; ++x) {
+				localIfAddrs.insert(std::pair<InetAddress, std::string>(InetAddress((uint32_t)0, ports[x]), std::string()));
+				localIfAddrs.insert(std::pair<InetAddress, std::string>(InetAddress((const void*)"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16, ports[x]), std::string()));
+			}
+		}
+
+		const unsigned int oldBindingCount = _bindingCount;
+		_bindingCount = 0;
+
+		// Save bindings that are still valid, close those that are not
+		for (unsigned int b = 0; b < oldBindingCount; ++b) {
+			if (localIfAddrs.find(_bindings[b].address) != localIfAddrs.end()) {
+				if (_bindingCount != b)
+					_bindings[(unsigned int)_bindingCount] = _bindings[b];
+				++_bindingCount;
+			}
+			else {
+				PhySocket* const udps = _bindings[b].udpSock;
+				_bindings[b].udpSock = (PhySocket*)0;
+				phy.close(udps, false);
+			}
+		}
+
+		// Create new bindings for those not already bound
+		for (std::map<InetAddress, std::string>::const_iterator ii(localIfAddrs.begin()); ii != localIfAddrs.end(); ++ii) {
+			unsigned int bi = 0;
+			while (bi != _bindingCount) {
+				if (_bindings[bi].address == ii->first)
+					break;
+				++bi;
+			}
+			if (bi == _bindingCount) {
+				udps = phy.udpBind(reinterpret_cast<const struct sockaddr*>(&(ii->first)), (void*)0, ZT_UDP_DESIRED_BUF_SIZE);
+				if (udps) {
 #ifdef __LINUX__
-                    // Bind Linux sockets to their device so routes that we manage do not override physical routes (wish all platforms had this!)
-                    if (ii->second.length() > 0) {
-                        char tmp[256];
-                        Utils::scopy(tmp, sizeof(tmp), ii->second.c_str());
-                        int fd = (int)Phy<PHY_HANDLER_TYPE>::getDescriptor(udps);
-                        if (fd >= 0) {
-                            setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, tmp, strlen(tmp));
-                        }
-                    }
-#endif   // __LINUX__
-                    if (_bindingCount < ZT_BINDER_MAX_BINDINGS) {
-                        _bindings[_bindingCount].udpSock = udps;
-                        _bindings[_bindingCount].address = ii->first;
-                        memcpy(_bindings[_bindingCount].ifname, (char*)ii->second.c_str(), (int)ii->second.length());
-                        ++_bindingCount;
-                    }
-                }
-                else {
-                    phy.close(udps, false);
-                }
-            }
-        }
-    }
-
-    /**
-     * @return All currently bound local interface addresses
-     */
-    inline std::vector<InetAddress> allBoundLocalInterfaceAddresses() const
-    {
-        std::vector<InetAddress> aa;
-        Mutex::Lock _l(_lock);
-        for (unsigned int b = 0, c = _bindingCount; b < c; ++b)
-            aa.push_back(_bindings[b].address);
-        return aa;
-    }
-
-    /**
-     * Send from all bound UDP sockets
-     */
-    template <typename PHY_HANDLER_TYPE> inline bool udpSendAll(Phy<PHY_HANDLER_TYPE>& phy, const struct sockaddr_storage* addr, const void* data, unsigned int len, unsigned int ttl)
-    {
-        bool r = false;
-        Mutex::Lock _l(_lock);
-        for (unsigned int b = 0, c = _bindingCount; b < c; ++b) {
-            if (ttl)
-                phy.setIp4UdpTtl(_bindings[b].udpSock, ttl);
-            if (phy.udpSend(_bindings[b].udpSock, (const struct sockaddr*)addr, data, len))
-                r = true;
-            if (ttl)
-                phy.setIp4UdpTtl(_bindings[b].udpSock, 255);
-        }
-        return r;
-    }
-
-    /**
-     * @param addr Address to check
-     * @return True if this is a bound local interface address
-     */
-    inline bool isBoundLocalInterfaceAddress(const InetAddress& addr) const
-    {
-        Mutex::Lock _l(_lock);
-        for (unsigned int b = 0; b < _bindingCount; ++b) {
-            if (_bindings[b].address == addr)
-                return true;
-        }
-        return false;
-    }
-
-    /**
-     * Quickly check that a UDP socket is valid
-     *
-     * @param udpSock UDP socket to check
-     * @return True if socket is currently bound/allocated
-     */
-    inline bool isUdpSocketValid(PhySocket* const udpSock)
-    {
-        for (unsigned int b = 0, c = _bindingCount; b < c; ++b) {
-            if (_bindings[b].udpSock == udpSock)
-                return (b < _bindingCount);   // double check atomic which may have changed
-        }
-        return false;
-    }
-
-    /**
-     * @param s Socket object
-     * @param nameBuf Buffer to store name of interface which this Socket object is bound to
-     * @param buflen Length of buffer to copy name into
-     */
-    void getIfName(PhySocket* s, char* nameBuf, int buflen) const
-    {
-        Mutex::Lock _l(_lock);
-        for (unsigned int b = 0, c = _bindingCount; b < c; ++b) {
-            if (_bindings[b].udpSock == s) {
-                memcpy(nameBuf, _bindings[b].ifname, buflen);
-                break;
-            }
-        }
-    }
+					// Bind Linux sockets to their device so routes that we manage do not override physical routes (wish all platforms had this!)
+					if (ii->second.length() > 0) {
+						char tmp[256];
+						Utils::scopy(tmp, sizeof(tmp), ii->second.c_str());
+						int fd = (int)Phy<PHY_HANDLER_TYPE>::getDescriptor(udps);
+						if (fd >= 0) {
+							setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, tmp, strlen(tmp));
+						}
+					}
+#endif	 // __LINUX__
+					if (_bindingCount < ZT_BINDER_MAX_BINDINGS) {
+						_bindings[_bindingCount].udpSock = udps;
+						_bindings[_bindingCount].address = ii->first;
+						memcpy(_bindings[_bindingCount].ifname, (char*)ii->second.c_str(), (int)ii->second.length());
+						++_bindingCount;
+					}
+				}
+				else {
+					phy.close(udps, false);
+				}
+			}
+		}
+	}
+
+	/**
+	 * @return All currently bound local interface addresses
+	 */
+	inline std::vector<InetAddress> allBoundLocalInterfaceAddresses() const
+	{
+		std::vector<InetAddress> aa;
+		Mutex::Lock _l(_lock);
+		for (unsigned int b = 0, c = _bindingCount; b < c; ++b)
+			aa.push_back(_bindings[b].address);
+		return aa;
+	}
+
+	/**
+	 * Send from all bound UDP sockets
+	 */
+	template <typename PHY_HANDLER_TYPE> inline bool udpSendAll(Phy<PHY_HANDLER_TYPE>& phy, const struct sockaddr_storage* addr, const void* data, unsigned int len, unsigned int ttl)
+	{
+		bool r = false;
+		Mutex::Lock _l(_lock);
+		for (unsigned int b = 0, c = _bindingCount; b < c; ++b) {
+			if (ttl)
+				phy.setIp4UdpTtl(_bindings[b].udpSock, ttl);
+			if (phy.udpSend(_bindings[b].udpSock, (const struct sockaddr*)addr, data, len))
+				r = true;
+			if (ttl)
+				phy.setIp4UdpTtl(_bindings[b].udpSock, 255);
+		}
+		return r;
+	}
+
+	/**
+	 * @param addr Address to check
+	 * @return True if this is a bound local interface address
+	 */
+	inline bool isBoundLocalInterfaceAddress(const InetAddress& addr) const
+	{
+		Mutex::Lock _l(_lock);
+		for (unsigned int b = 0; b < _bindingCount; ++b) {
+			if (_bindings[b].address == addr)
+				return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Quickly check that a UDP socket is valid
+	 *
+	 * @param udpSock UDP socket to check
+	 * @return True if socket is currently bound/allocated
+	 */
+	inline bool isUdpSocketValid(PhySocket* const udpSock)
+	{
+		for (unsigned int b = 0, c = _bindingCount; b < c; ++b) {
+			if (_bindings[b].udpSock == udpSock)
+				return (b < _bindingCount);	  // double check atomic which may have changed
+		}
+		return false;
+	}
+
+	/**
+	 * @param s Socket object
+	 * @param nameBuf Buffer to store name of interface which this Socket object is bound to
+	 * @param buflen Length of buffer to copy name into
+	 */
+	void getIfName(PhySocket* s, char* nameBuf, int buflen) const
+	{
+		Mutex::Lock _l(_lock);
+		for (unsigned int b = 0, c = _bindingCount; b < c; ++b) {
+			if (_bindings[b].udpSock == s) {
+				memcpy(nameBuf, _bindings[b].ifname, buflen);
+				break;
+			}
+		}
+	}
 
   private:
-    _Binding _bindings[ZT_BINDER_MAX_BINDINGS];
-    std::atomic<unsigned int> _bindingCount;
-    Mutex _lock;
+	_Binding _bindings[ZT_BINDER_MAX_BINDINGS];
+	std::atomic<unsigned int> _bindingCount;
+	Mutex _lock;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 82 - 82
osdep/BlockingQueue.hpp

@@ -30,99 +30,99 @@ namespace ZeroTier {
  */
 template <class T> class BlockingQueue {
   public:
-    BlockingQueue(void) : r(true)
-    {
-    }
+	BlockingQueue(void) : r(true)
+	{
+	}
 
-    inline void post(T t)
-    {
-        std::lock_guard<std::mutex> lock(m);
-        q.push(t);
-        c.notify_one();
-    }
+	inline void post(T t)
+	{
+		std::lock_guard<std::mutex> lock(m);
+		q.push(t);
+		c.notify_one();
+	}
 
-    inline void postLimit(T t, const unsigned long limit)
-    {
-        std::unique_lock<std::mutex> lock(m);
-        for (;;) {
-            if (q.size() < limit) {
-                q.push(t);
-                c.notify_one();
-                break;
-            }
-            if (! r)
-                break;
-            gc.wait(lock);
-        }
-    }
+	inline void postLimit(T t, const unsigned long limit)
+	{
+		std::unique_lock<std::mutex> lock(m);
+		for (;;) {
+			if (q.size() < limit) {
+				q.push(t);
+				c.notify_one();
+				break;
+			}
+			if (! r)
+				break;
+			gc.wait(lock);
+		}
+	}
 
-    inline void stop(void)
-    {
-        std::lock_guard<std::mutex> lock(m);
-        r = false;
-        c.notify_all();
-        gc.notify_all();
-    }
+	inline void stop(void)
+	{
+		std::lock_guard<std::mutex> lock(m);
+		r = false;
+		c.notify_all();
+		gc.notify_all();
+	}
 
-    inline bool get(T& value)
-    {
-        std::unique_lock<std::mutex> lock(m);
-        if (! r)
-            return false;
-        while (q.empty()) {
-            c.wait(lock);
-            if (! r) {
-                gc.notify_all();
-                return false;
-            }
-        }
-        value = q.front();
-        q.pop();
-        gc.notify_all();
-        return true;
-    }
+	inline bool get(T& value)
+	{
+		std::unique_lock<std::mutex> lock(m);
+		if (! r)
+			return false;
+		while (q.empty()) {
+			c.wait(lock);
+			if (! r) {
+				gc.notify_all();
+				return false;
+			}
+		}
+		value = q.front();
+		q.pop();
+		gc.notify_all();
+		return true;
+	}
 
-    inline std::vector<T> drain()
-    {
-        std::vector<T> v;
-        while (! q.empty()) {
-            v.push_back(q.front());
-            q.pop();
-        }
-        return v;
-    }
+	inline std::vector<T> drain()
+	{
+		std::vector<T> v;
+		while (! q.empty()) {
+			v.push_back(q.front());
+			q.pop();
+		}
+		return v;
+	}
 
-    enum TimedWaitResult { OK, TIMED_OUT, STOP };
+	enum TimedWaitResult { OK, TIMED_OUT, STOP };
 
-    inline TimedWaitResult get(T& value, const unsigned long ms)
-    {
-        const std::chrono::milliseconds ms2 { ms };
-        std::unique_lock<std::mutex> lock(m);
-        if (! r)
-            return STOP;
-        while (q.empty()) {
-            if (c.wait_for(lock, ms2) == std::cv_status::timeout)
-                return ((r) ? TIMED_OUT : STOP);
-            else if (! r)
-                return STOP;
-        }
-        value = q.front();
-        q.pop();
-        return OK;
-    }
+	inline TimedWaitResult get(T& value, const unsigned long ms)
+	{
+		const std::chrono::milliseconds ms2 { ms };
+		std::unique_lock<std::mutex> lock(m);
+		if (! r)
+			return STOP;
+		while (q.empty()) {
+			if (c.wait_for(lock, ms2) == std::cv_status::timeout)
+				return ((r) ? TIMED_OUT : STOP);
+			else if (! r)
+				return STOP;
+		}
+		value = q.front();
+		q.pop();
+		return OK;
+	}
 
-    inline size_t size() const
-    {
-        return q.size();
-    }
+	inline size_t size() const
+	{
+		return q.size();
+	}
 
   private:
-    std::queue<T> q;
-    mutable std::mutex m;
-    mutable std::condition_variable c, gc;
-    std::atomic_bool r;
+	std::queue<T> q;
+	mutable std::mutex m;
+	mutable std::condition_variable c, gc;
+	std::atomic_bool r;
 };
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier
 
 #endif

+ 76 - 76
osdep/EthernetTap.cpp

@@ -31,113 +31,113 @@
 #include "MacKextEthernetTap.hpp"
 
 #include <sys/sysctl.h>
-#endif   // __APPLE__
+#endif	 // __APPLE__
 
 #ifdef __LINUX__
 #include "LinuxEthernetTap.hpp"
-#endif   // __LINUX__
+#endif	 // __LINUX__
 
 #ifdef __WINDOWS__
 #include "WindowsEthernetTap.hpp"
-#endif   // __WINDOWS__
+#endif	 // __WINDOWS__
 
 #ifdef __FreeBSD__
 #include "BSDEthernetTap.hpp"
-#endif   // __FreeBSD__
+#endif	 // __FreeBSD__
 
 #ifdef __NetBSD__
 #include "NetBSDEthernetTap.hpp"
-#endif   // __NetBSD__
+#endif	 // __NetBSD__
 
 #ifdef __OpenBSD__
 #include "BSDEthernetTap.hpp"
-#endif   // __OpenBSD__
+#endif	 // __OpenBSD__
 
 #endif
 
 namespace ZeroTier {
 
 std::shared_ptr<EthernetTap> EthernetTap::newInstance(
-    const char* tapDeviceType,   // OS-specific, NULL for default
-    unsigned int concurrency,
-    bool pinning,
-    const char* homePath,
-    const MAC& mac,
-    unsigned int mtu,
-    unsigned int metric,
-    uint64_t nwid,
-    const char* friendlyName,
-    void (*handler)(void*, void*, uint64_t, const MAC&, const MAC&, unsigned int, unsigned int, const void*, unsigned int),
-    void* arg)
+	const char* tapDeviceType,	 // OS-specific, NULL for default
+	unsigned int concurrency,
+	bool pinning,
+	const char* homePath,
+	const MAC& mac,
+	unsigned int mtu,
+	unsigned int metric,
+	uint64_t nwid,
+	const char* friendlyName,
+	void (*handler)(void*, void*, uint64_t, const MAC&, const MAC&, unsigned int, unsigned int, const void*, unsigned int),
+	void* arg)
 {
 #ifdef ZT_SDK
 
-    return std::shared_ptr<EthernetTap>(new VirtualTap(homePath, mac, mtu, metric, nwid, friendlyName, handler, arg));
+	return std::shared_ptr<EthernetTap>(new VirtualTap(homePath, mac, mtu, metric, nwid, friendlyName, handler, arg));
 
-#else   // not ZT_SDK
+#else	// not ZT_SDK
 
 #ifdef __APPLE__
-    char osrelease[256];
-    size_t size = sizeof(osrelease);
-    if (sysctlbyname("kern.osrelease", osrelease, &size, nullptr, 0) == 0) {
-        char* dotAt = strchr(osrelease, '.');
-        if (dotAt) {
-            *dotAt = (char)0;
-            // The "feth" virtual Ethernet device type appeared in Darwin 17.x.x. Older versions
-            // (Sierra and earlier) must use the a kernel extension.
-            if (strtol(osrelease, (char**)0, 10) < 17) {
-                return std::shared_ptr<EthernetTap>(new MacKextEthernetTap(homePath, mac, mtu, metric, nwid, friendlyName, handler, arg));
-            }
-            else {
-                return std::shared_ptr<EthernetTap>(new MacEthernetTap(homePath, mac, mtu, metric, nwid, friendlyName, handler, arg));
-            }
-        }
-    }
-#endif   // __APPLE__
+	char osrelease[256];
+	size_t size = sizeof(osrelease);
+	if (sysctlbyname("kern.osrelease", osrelease, &size, nullptr, 0) == 0) {
+		char* dotAt = strchr(osrelease, '.');
+		if (dotAt) {
+			*dotAt = (char)0;
+			// The "feth" virtual Ethernet device type appeared in Darwin 17.x.x. Older versions
+			// (Sierra and earlier) must use the a kernel extension.
+			if (strtol(osrelease, (char**)0, 10) < 17) {
+				return std::shared_ptr<EthernetTap>(new MacKextEthernetTap(homePath, mac, mtu, metric, nwid, friendlyName, handler, arg));
+			}
+			else {
+				return std::shared_ptr<EthernetTap>(new MacEthernetTap(homePath, mac, mtu, metric, nwid, friendlyName, handler, arg));
+			}
+		}
+	}
+#endif	 // __APPLE__
 
 #ifdef __LINUX__
-    return std::shared_ptr<EthernetTap>(new LinuxEthernetTap(homePath, concurrency, pinning, mac, mtu, metric, nwid, friendlyName, handler, arg));
-#endif   // __LINUX__
+	return std::shared_ptr<EthernetTap>(new LinuxEthernetTap(homePath, concurrency, pinning, mac, mtu, metric, nwid, friendlyName, handler, arg));
+#endif	 // __LINUX__
 
 #ifdef __WINDOWS__
-    HRESULT hres = CoInitializeEx(0, COINIT_MULTITHREADED);
-    if (FAILED(hres)) {
-        throw std::runtime_error("WinEthernetTap: COM initialization failed");
-    }
-
-    static bool _comInit = false;
-    static Mutex _comInit_m;
-
-    {
-        Mutex::Lock l(_comInit_m);
-        if (! _comInit) {
-            hres = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_PKT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);
-            if (FAILED(hres)) {
-                CoUninitialize();
-                fprintf(stderr, "WinEthernetTap: Failed to initialize security");
-                throw std::runtime_error("WinEthernetTap: Failed to initialize security");
-            }
-            _comInit = true;
-        }
-    }
-    return std::shared_ptr<EthernetTap>(new WindowsEthernetTap(homePath, mac, mtu, metric, nwid, friendlyName, handler, arg));
-#endif   // __WINDOWS__
+	HRESULT hres = CoInitializeEx(0, COINIT_MULTITHREADED);
+	if (FAILED(hres)) {
+		throw std::runtime_error("WinEthernetTap: COM initialization failed");
+	}
+
+	static bool _comInit = false;
+	static Mutex _comInit_m;
+
+	{
+		Mutex::Lock l(_comInit_m);
+		if (! _comInit) {
+			hres = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_PKT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);
+			if (FAILED(hres)) {
+				CoUninitialize();
+				fprintf(stderr, "WinEthernetTap: Failed to initialize security");
+				throw std::runtime_error("WinEthernetTap: Failed to initialize security");
+			}
+			_comInit = true;
+		}
+	}
+	return std::shared_ptr<EthernetTap>(new WindowsEthernetTap(homePath, mac, mtu, metric, nwid, friendlyName, handler, arg));
+#endif	 // __WINDOWS__
 
 #ifdef __FreeBSD__
-    return std::shared_ptr<EthernetTap>(new BSDEthernetTap(homePath, concurrency, pinning, mac, mtu, metric, nwid, friendlyName, handler, arg));
-#endif   // __FreeBSD__
+	return std::shared_ptr<EthernetTap>(new BSDEthernetTap(homePath, concurrency, pinning, mac, mtu, metric, nwid, friendlyName, handler, arg));
+#endif	 // __FreeBSD__
 
 #ifdef __NetBSD__
-    return std::shared_ptr<EthernetTap>(new NetBSDEthernetTap(homePath, mac, mtu, metric, nwid, friendlyName, handler, arg));
-#endif   // __NetBSD__
+	return std::shared_ptr<EthernetTap>(new NetBSDEthernetTap(homePath, mac, mtu, metric, nwid, friendlyName, handler, arg));
+#endif	 // __NetBSD__
 
 #ifdef __OpenBSD__
-	return std::shared_ptr<EthernetTap>(new BSDEthernetTap(homePath,concurrency,pinning,mac,mtu,metric,nwid,friendlyName,handler,arg));
-#endif // __OpenBSD__
+	return std::shared_ptr<EthernetTap>(new BSDEthernetTap(homePath, concurrency, pinning, mac, mtu, metric, nwid, friendlyName, handler, arg));
+#endif	 // __OpenBSD__
 
-#endif   // ZT_SDK?
+#endif	 // ZT_SDK?
 
-    return std::shared_ptr<EthernetTap>();
+	return std::shared_ptr<EthernetTap>();
 }
 
 EthernetTap::EthernetTap()
@@ -149,17 +149,17 @@ EthernetTap::~EthernetTap()
 
 bool EthernetTap::addIps(std::vector<InetAddress> ips)
 {
-    for (std::vector<InetAddress>::const_iterator i(ips.begin()); i != ips.end(); ++i) {
-        if (! addIp(*i))
-            return false;
-    }
-    return true;
+	for (std::vector<InetAddress>::const_iterator i(ips.begin()); i != ips.end(); ++i) {
+		if (! addIp(*i))
+			return false;
+	}
+	return true;
 }
 
 std::string EthernetTap::friendlyName() const
 {
-    // Most platforms do not have this.
-    return std::string();
+	// Most platforms do not have this.
+	return std::string();
 }
 
-}   // namespace ZeroTier
+}	// namespace ZeroTier

Some files were not shown because too many files changed in this diff