Browse Source

Faster hash table, various other optimizations and simplification.

Adam Ierymenko 5 years ago
parent
commit
87da45b3f5

+ 2 - 2
node/Hashtable.hpp → attic/Hashtable.hpp

@@ -355,11 +355,11 @@ public:
 private:
 private:
 	template<typename O>
 	template<typename O>
 	static ZT_INLINE unsigned long _hc(const O &obj) { return (unsigned long)obj.hashCode(); }
 	static ZT_INLINE unsigned long _hc(const O &obj) { return (unsigned long)obj.hashCode(); }
-	static ZT_INLINE unsigned long _hc(const uint64_t i) noexcept { return (unsigned long)(i ^ (i >> 32U)); }
+	static ZT_INLINE unsigned long _hc(const uint64_t i) noexcept { return (unsigned long)(i + (i >> 32U)); }
 	static ZT_INLINE unsigned long _hc(const uint32_t i) noexcept { return ((unsigned long)i * (unsigned long)0x9e3779b1); }
 	static ZT_INLINE unsigned long _hc(const uint32_t i) noexcept { return ((unsigned long)i * (unsigned long)0x9e3779b1); }
 	static ZT_INLINE unsigned long _hc(const uint16_t i) noexcept { return ((unsigned long)i * (unsigned long)0x9e3779b1); }
 	static ZT_INLINE unsigned long _hc(const uint16_t i) noexcept { return ((unsigned long)i * (unsigned long)0x9e3779b1); }
 	static ZT_INLINE unsigned long _hc(const uint8_t i) noexcept { return ((unsigned long)i * (unsigned long)0x9e3779b1); }
 	static ZT_INLINE unsigned long _hc(const uint8_t i) noexcept { return ((unsigned long)i * (unsigned long)0x9e3779b1); }
-	static ZT_INLINE unsigned long _hc(const int64_t i) noexcept { return (unsigned long)((unsigned long long)i ^ ((unsigned long long)i >> 32U)); }
+	static ZT_INLINE unsigned long _hc(const int64_t i) noexcept { return (unsigned long)((unsigned long long)i + ((unsigned long long)i >> 32U)); }
 	static ZT_INLINE unsigned long _hc(const int32_t i) noexcept { return ((unsigned long)i * (unsigned long)0x9e3779b1); }
 	static ZT_INLINE unsigned long _hc(const int32_t i) noexcept { return ((unsigned long)i * (unsigned long)0x9e3779b1); }
 	static ZT_INLINE unsigned long _hc(const int16_t i) noexcept { return ((unsigned long)i * (unsigned long)0x9e3779b1); }
 	static ZT_INLINE unsigned long _hc(const int16_t i) noexcept { return ((unsigned long)i * (unsigned long)0x9e3779b1); }
 	static ZT_INLINE unsigned long _hc(const int8_t i) noexcept { return ((unsigned long)i * (unsigned long)0x9e3779b1); }
 	static ZT_INLINE unsigned long _hc(const int8_t i) noexcept { return ((unsigned long)i * (unsigned long)0x9e3779b1); }

+ 1 - 5
node/Address.hpp

@@ -70,11 +70,6 @@ public:
 	 */
 	 */
 	ZT_INLINE void zero() noexcept { _a = 0; }
 	ZT_INLINE void zero() noexcept { _a = 0; }
 
 
-	/**
-	 * @return Hash code for use with Hashtable
-	 */
-	ZT_INLINE unsigned long hashCode() const noexcept { return (unsigned long)_a; }
-
 	/**
 	/**
 	 * @param s String with at least 11 characters of space available (10 + terminating NULL)
 	 * @param s String with at least 11 characters of space available (10 + terminating NULL)
 	 * @return Hexadecimal string
 	 * @return Hexadecimal string
@@ -108,6 +103,7 @@ public:
 	 */
 	 */
 	ZT_INLINE bool isReserved() const noexcept { return ((!_a) || ((_a >> 32U) == ZT_ADDRESS_RESERVED_PREFIX)); }
 	ZT_INLINE bool isReserved() const noexcept { return ((!_a) || ((_a >> 32U) == ZT_ADDRESS_RESERVED_PREFIX)); }
 
 
+	ZT_INLINE unsigned long hashCode() const noexcept { return (unsigned long)_a; }
 	ZT_INLINE operator bool() const noexcept { return (_a != 0); }
 	ZT_INLINE operator bool() const noexcept { return (_a != 0); }
 
 
 	ZT_INLINE bool operator==(const Address &a) const noexcept { return _a == a._a; }
 	ZT_INLINE bool operator==(const Address &a) const noexcept { return _a == a._a; }

+ 1 - 1
node/CMakeLists.txt

@@ -17,7 +17,7 @@ set(core_headers
 	Expect.hpp
 	Expect.hpp
 	FCV.hpp
 	FCV.hpp
 	Fingerprint.hpp
 	Fingerprint.hpp
-	Hashtable.hpp
+	FlatMap.hpp
 	Identity.hpp
 	Identity.hpp
 	InetAddress.hpp
 	InetAddress.hpp
 	Locator.hpp
 	Locator.hpp

+ 21 - 13
node/Defragmenter.hpp

@@ -17,10 +17,10 @@
 #include "Constants.hpp"
 #include "Constants.hpp"
 #include "Buf.hpp"
 #include "Buf.hpp"
 #include "SharedPtr.hpp"
 #include "SharedPtr.hpp"
-#include "Hashtable.hpp"
 #include "Mutex.hpp"
 #include "Mutex.hpp"
 #include "Path.hpp"
 #include "Path.hpp"
 #include "FCV.hpp"
 #include "FCV.hpp"
+#include "FlatMap.hpp"
 
 
 #include <cstring>
 #include <cstring>
 #include <cstdlib>
 #include <cstdlib>
@@ -89,10 +89,7 @@ public:
 		ERR_OUT_OF_MEMORY
 		ERR_OUT_OF_MEMORY
 	};
 	};
 
 
-	ZT_INLINE Defragmenter() :
-		_messages(GCT * 2)
-	{
-	}
+	ZT_INLINE Defragmenter() {}
 
 
 	/**
 	/**
 	 * Process a fragment of a multi-part message
 	 * Process a fragment of a multi-part message
@@ -167,12 +164,8 @@ public:
 				std::vector<std::pair<int64_t,uint64_t> > messagesByLastUsedTime;
 				std::vector<std::pair<int64_t,uint64_t> > messagesByLastUsedTime;
 				messagesByLastUsedTime.reserve(_messages.size());
 				messagesByLastUsedTime.reserve(_messages.size());
 
 
-				typename Hashtable<uint64_t,_E>::Iterator i(_messages);
-				uint64_t *mk = nullptr;
-				_E *mv = nullptr;
-				while (i.next(mk,mv))
-					messagesByLastUsedTime.push_back(std::pair<int64_t,uint64_t>(mv->lastUsed,*mk));
-
+				for(typename FlatMap< uint64_t,_E >::const_iterator i(_messages.begin());i!=_messages.end();++i)
+					messagesByLastUsedTime.push_back(std::pair<int64_t,uint64_t>(i->second.lastUsed,i->first));
 				std::sort(messagesByLastUsedTime.begin(),messagesByLastUsedTime.end());
 				std::sort(messagesByLastUsedTime.begin(),messagesByLastUsedTime.end());
 
 
 				ml.writing(); // acquire write lock on _messages
 				ml.writing(); // acquire write lock on _messages
@@ -295,7 +288,21 @@ public:
 private:
 private:
 	struct _E
 	struct _E
 	{
 	{
-		ZT_INLINE _E() noexcept : id(0),lastUsed(0),totalFragmentsExpected(0),fragmentsReceived(0),via(),message(),lock() {}
+		ZT_INLINE _E() noexcept :
+			id(0),
+			lastUsed(0),
+			totalFragmentsExpected(0),
+			fragmentsReceived(0) {}
+
+		ZT_INLINE _E(const _E &e) noexcept :
+			id(e.id),
+			lastUsed(e.lastUsed),
+			totalFragmentsExpected(e.totalFragmentsExpected),
+			fragmentsReceived(e.fragmentsReceived),
+			via(e.via),
+			message(e.message),
+			lock() {}
+
 		ZT_INLINE ~_E()
 		ZT_INLINE ~_E()
 		{
 		{
 			if (via) {
 			if (via) {
@@ -304,6 +311,7 @@ private:
 				via->_inboundFragmentedMessages_l.unlock();
 				via->_inboundFragmentedMessages_l.unlock();
 			}
 			}
 		}
 		}
+
 		uint64_t id;
 		uint64_t id;
 		volatile int64_t lastUsed;
 		volatile int64_t lastUsed;
 		unsigned int totalFragmentsExpected;
 		unsigned int totalFragmentsExpected;
@@ -313,7 +321,7 @@ private:
 		Mutex lock;
 		Mutex lock;
 	};
 	};
 
 
-	Hashtable< uint64_t,_E > _messages;
+	FlatMap< uint64_t,_E > _messages;
 	RWMutex _messages_l;
 	RWMutex _messages_l;
 };
 };
 
 

+ 5 - 8
node/Dictionary.cpp

@@ -44,8 +44,8 @@ std::vector<uint8_t> &Dictionary::operator[](const char *k)
 const std::vector<uint8_t> &Dictionary::operator[](const char *k) const
 const std::vector<uint8_t> &Dictionary::operator[](const char *k) const
 {
 {
 	static const std::vector<uint8_t> emptyEntry;
 	static const std::vector<uint8_t> emptyEntry;
-	const std::vector<uint8_t> *const e = _t.get(_toKey(k));
-	return (e) ? *e : emptyEntry;
+	std::map< uint64_t,std::vector<uint8_t> >::const_iterator e(_t.find(_toKey(k)));
+	return (e == _t.end()) ? emptyEntry : e->second;
 }
 }
 
 
 void Dictionary::add(const char *k,bool v)
 void Dictionary::add(const char *k,bool v)
@@ -170,11 +170,8 @@ void Dictionary::encode(std::vector<uint8_t> &out) const
 
 
 	out.clear();
 	out.clear();
 
 
-	Hashtable< uint64_t,std::vector<uint8_t> >::Iterator ti(const_cast<Dictionary *>(this)->_t);
-	uint64_t *kk = nullptr;
-	std::vector<uint8_t> *vv = nullptr;
-	while (ti.next(kk,vv)) {
-		str[0] = *kk;
+	for(std::map< uint64_t,std::vector<uint8_t> >::const_iterator ti(_t.begin());ti!=_t.end();++ti) {
+		str[0] = ti->first;
 		const char *k = (const char *)str;
 		const char *k = (const char *)str;
 
 
 		for(;;) {
 		for(;;) {
@@ -186,7 +183,7 @@ void Dictionary::encode(std::vector<uint8_t> &out) const
 
 
 		out.push_back(61); // =
 		out.push_back(61); // =
 
 
-		for(std::vector<uint8_t>::const_iterator i(vv->begin());i!=vv->end();++i) {
+		for(std::vector<uint8_t>::const_iterator i(ti->second.begin());i!=ti->second.end();++i) {
 			uint8_t c = *i;
 			uint8_t c = *i;
 			switch(c) {
 			switch(c) {
 				case 0:
 				case 0:

+ 2 - 2
node/Dictionary.hpp

@@ -18,10 +18,10 @@
 #include "Utils.hpp"
 #include "Utils.hpp"
 #include "Address.hpp"
 #include "Address.hpp"
 #include "Buf.hpp"
 #include "Buf.hpp"
-#include "Hashtable.hpp"
 
 
 #include <cstdint>
 #include <cstdint>
 #include <vector>
 #include <vector>
+#include <map>
 
 
 namespace ZeroTier {
 namespace ZeroTier {
 
 
@@ -168,7 +168,7 @@ public:
 	bool decode(const void *data,unsigned int len);
 	bool decode(const void *data,unsigned int len);
 
 
 private:
 private:
-	Hashtable< uint64_t,std::vector<uint8_t> > _t;
+	std::map< uint64_t,std::vector<uint8_t> > _t;
 };
 };
 
 
 } // namespace ZeroTier
 } // namespace ZeroTier

+ 245 - 0
node/FlatMap.hpp

@@ -0,0 +1,245 @@
+/*
+ * Copyright (c)2013-2020 ZeroTier, Inc.
+ *
+ * Use of this software is governed by the Business Source License included
+ * in the LICENSE.TXT file in the project's root directory.
+ *
+ * Change Date: 2024-01-01
+ *
+ * On the date above, in accordance with the Business Source License, use
+ * of this software will be governed by version 2.0 of the Apache License.
+ */
+/****/
+
+#ifndef ZT_FLATMAP_HPP
+#define ZT_FLATMAP_HPP
+
+#include "Constants.hpp"
+#include "Utils.hpp"
+
+#include <list>
+#include <vector>
+#include <utility>
+
+namespace ZeroTier {
+
+/**
+ * A very simple and fast flat hash table
+ *
+ * This is designed to prioritize performance over memory, though with
+ * endpoint ZeroTier nodes this is used in cases where the data set is not
+ * that large (e.g. networks, peers) and so memory consumption should not
+ * be much.
+ *
+ * It creates a flat hash table that maps keys to values and doubles its
+ * size whenever a collision occurs. The size is always a power of two so
+ * that only very fast operations like add, XOR, and bit mask can be used
+ * for hash table lookup.
+ *
+ * It also saves actual key/value pairs in a linked list for relatively
+ * fast iteration compared to a hash table where you have to iterate through
+ * a bunch of empty buckets. Iteration order should be considered undefined.
+ *
+ * @tparam K Key type
+ * @tparam V Value type
+ * @tparam IC Initial capacity (must be a power of two)
+ */
+template<typename K,typename V,unsigned long IC = 16>
+class FlatMap
+{
+public:
+	typedef K key_type;
+	typedef V mapped_type;
+	typedef std::pair<K,V> value_type;
+	typedef typename std::list< std::pair<K,V> >::iterator iterator;
+	typedef typename std::list< std::pair<K,V> >::const_iterator const_iterator;
+
+	ZT_INLINE FlatMap() :
+		_d(),
+		_null(),
+		_b(new iterator[IC]),
+		_s(IC),
+		_m(IC - 1)
+	{
+	}
+
+	ZT_INLINE FlatMap(const FlatMap &m) :
+		_d(m._d),
+		_null(),
+		_b(new iterator[m._s]),
+		_s(m._s),
+		_m(m._m)
+	{
+		try {
+			for (iterator i=_d.begin();i!=_d.end();++i)
+				_b[_hc(i->first) & _m] = i;
+		} catch ( ... ) {
+			delete [] _b;
+			throw;
+		}
+	}
+
+	ZT_INLINE ~FlatMap()
+	{
+		delete [] _b;
+	}
+
+	ZT_INLINE FlatMap &operator=(const FlatMap &m)
+	{
+		_d = m._d;
+		delete [] _b;
+		_b = nullptr;
+		_b = new iterator[m._s];
+		_s = m._s;
+		_m = m._m;
+		for(iterator i=_d.begin();i!=_d.end();++i)
+			_b[_hc(i->first) & _m] = i;
+	}
+
+	ZT_INLINE void clear()
+	{
+		_d.clear();
+		delete [] _b;
+		_b = nullptr;
+		_b = new iterator[IC];
+		_s = IC;
+		_m = IC - 1;
+	}
+
+	ZT_INLINE V *get(const K &key) noexcept
+	{
+		const unsigned long hb = _hc(key) & _m;
+		if ((_b[hb] != _null)&&(_b[hb]->first == key))
+			return &(_b[hb]->second);
+		return nullptr;
+	}
+
+	ZT_INLINE const V *get(const K &key) const noexcept
+	{
+		const unsigned long hb = _hc(key) & _m;
+		if ((_b[hb] != _null)&&(_b[hb]->first == key))
+			return &(_b[hb]->second);
+		return nullptr;
+	}
+
+	ZT_INLINE iterator find(const K &key) noexcept
+	{
+		const unsigned long hb = _hc(key) & _m;
+		if ((_b[hb] != _null)&&(_b[hb]->first == key))
+			return _b[hb];
+		return _d.end();
+	}
+
+	ZT_INLINE const_iterator find(const K &key) const noexcept
+	{
+		const unsigned long hb = _hc(key) & _m;
+		if ((_b[hb] != _null)&&(_b[hb]->first == key))
+			return _b[hb];
+		return _d.end();
+	}
+
+	ZT_INLINE V &set(const K &key,const V &value)
+	{
+		unsigned long hb = _hc(key) & _m;
+		if (_b[hb] == _null) {
+#ifdef __CPP11__
+			_d.emplace_back(key,value);
+#else
+			_d.push_back(std::pair<K,V>(key,value));
+#endif
+			return (_b[hb] = --_d.end())->second;
+		} else {
+			if (_b[hb]->first == key) {
+				_b[hb]->second = value;
+				return _b[hb]->second;
+			}
+#ifdef __CPP11__
+			_d.emplace_back(key,value);
+#else
+			_d.push_back(std::pair<K,V>(key,value));
+#endif
+			_grow();
+			return _b[_hc(key) & _m]->second;
+		}
+	}
+
+	ZT_INLINE V &operator[](const K &key)
+	{
+		unsigned long hb = _hc(key) & _m;
+		if (_b[hb] == _null) {
+#ifdef __CPP11__
+			_d.emplace_back(key,V());
+#else
+			_d.push_back(std::pair<K,V>(key,V()));
+#endif
+			return (_b[hb] = --_d.end())->second;
+		} else {
+			if (_b[hb]->first == key)
+				return _b[hb]->second;
+#ifdef __CPP11__
+			_d.emplace_back(key,V());
+#else
+			_d.push_back(std::pair<K,V>(key,V()));
+#endif
+			_grow();
+			return _b[_hc(key) & _m]->second;
+		}
+	}
+
+	ZT_INLINE void erase(const iterator &i)
+	{
+		_b[_hc(i->first) & _m] = _null;
+		_d.erase(i);
+	}
+
+	ZT_INLINE void erase(const K &key)
+	{
+		iterator e(find(key));
+		if (e != end())
+			erase(e);
+	}
+
+	ZT_INLINE iterator begin() noexcept { return _d.begin(); }
+	ZT_INLINE iterator end() noexcept { return _d.end(); }
+	ZT_INLINE const_iterator begin() const noexcept { return _d.begin(); }
+	ZT_INLINE const_iterator end() const noexcept { return _d.end(); }
+
+	ZT_INLINE unsigned long size() const { return (unsigned long)_d.size(); }
+	ZT_INLINE bool empty() const { return _d.empty(); }
+	ZT_INLINE unsigned long hashSize() const { return _s; }
+
+private:
+	template<typename O>
+	static ZT_INLINE unsigned long _hc(const O &obj) { return (unsigned long)obj.hashCode(); }
+	static ZT_INLINE unsigned long _hc(const uint64_t i) noexcept { return (unsigned long)(i + (i >> 32U)); }
+	static ZT_INLINE unsigned long _hc(const int64_t i) noexcept { return (unsigned long)((uint64_t)i + ((uint64_t)i >> 32U)); }
+	static ZT_INLINE unsigned long _hc(const uint32_t i) noexcept { return Utils::hash32(i); }
+
+	ZT_INLINE void _grow()
+	{
+		unsigned long hb;
+enlarge_again:
+		delete [] _b;
+		_b = nullptr; // in case 'new' throws
+		_b = new iterator[_s <<= 1U];
+		_m = _s - 1;
+		for(iterator i=_d.begin();i!=_d.end();++i) {
+			hb = _hc(i->first) & _m;
+			if (_b[hb] == _null) {
+				_b[hb] = i;
+			} else {
+				goto enlarge_again;
+			}
+		}
+	}
+
+	std::list< std::pair<K,V> > _d;
+	const iterator _null; // prototype of a "null" / default iterator
+	iterator *_b;
+	unsigned long _s;
+	unsigned long _m;
+};
+
+} // namespace ZeroTier
+
+#endif

+ 3 - 7
node/Membership.cpp

@@ -25,11 +25,7 @@ Membership::Membership() :
 	_comRevocationThreshold(0),
 	_comRevocationThreshold(0),
 	_lastPushedCredentials(0),
 	_lastPushedCredentials(0),
 	_comAgreementLocalTimestamp(0),
 	_comAgreementLocalTimestamp(0),
-	_comAgreementRemoteTimestamp(0),
-	_revocations(4),
-	_remoteTags(4),
-	_remoteCaps(4),
-	_remoteCoos(4)
+	_comAgreementRemoteTimestamp(0)
 {
 {
 }
 }
 
 
@@ -163,8 +159,8 @@ Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironme
 // 3/5 of the credential types have identical addCredential() code
 // 3/5 of the credential types have identical addCredential() code
 template<typename C>
 template<typename C>
 static ZT_INLINE Membership::AddCredentialResult _addCredImpl(
 static ZT_INLINE Membership::AddCredentialResult _addCredImpl(
-	Hashtable<uint32_t,C> &remoteCreds,
-	const Hashtable<uint64_t,int64_t> &revocations,
+	FlatMap<uint32_t,C> &remoteCreds,
+	const FlatMap<uint64_t,int64_t> &revocations,
 	const RuntimeEnvironment *const RR,
 	const RuntimeEnvironment *const RR,
 	void *const tPtr,
 	void *const tPtr,
 	const Identity &sourcePeerIdentity,
 	const Identity &sourcePeerIdentity,

+ 18 - 26
node/Membership.hpp

@@ -18,7 +18,7 @@
 
 
 #include "Constants.hpp"
 #include "Constants.hpp"
 #include "Credential.hpp"
 #include "Credential.hpp"
-#include "Hashtable.hpp"
+#include "FlatMap.hpp"
 #include "CertificateOfMembership.hpp"
 #include "CertificateOfMembership.hpp"
 #include "Capability.hpp"
 #include "Capability.hpp"
 #include "Tag.hpp"
 #include "Tag.hpp"
@@ -106,11 +106,8 @@ public:
 	{
 	{
 		if (_isUnspoofableAddress(nconf,r))
 		if (_isUnspoofableAddress(nconf,r))
 			return true;
 			return true;
-		uint32_t *k = nullptr;
-		CertificateOfOwnership *v = nullptr;
-		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)))
+		for(FlatMap< uint32_t,CertificateOfOwnership >::const_iterator i(_remoteCoos.begin());i!=_remoteCoos.end();++i) {
+			if (_isCredentialTimestampValid(nconf,i->second)&&(i->second.owns(r)))
 				return true;
 				return true;
 		}
 		}
 		return false;
 		return false;
@@ -179,14 +176,12 @@ private:
 	}
 	}
 
 
 	template<typename C>
 	template<typename C>
-	ZT_INLINE void _cleanCredImpl(const NetworkConfig &nconf,Hashtable<uint32_t,C> &remoteCreds)
+	ZT_INLINE void _cleanCredImpl(const NetworkConfig &nconf,FlatMap<uint32_t,C> &remoteCreds)
 	{
 	{
-		uint32_t *k = nullptr;
-		C *v = nullptr;
-		typename Hashtable<uint32_t,C>::Iterator i(remoteCreds);
-		while (i.next(k,v)) {
-			if (!_isCredentialTimestampValid(nconf,*v))
-				remoteCreds.erase(*k);
+		for(typename FlatMap<uint32_t,C>::iterator i(remoteCreds.begin());i!=remoteCreds.end();) {
+			if (!_isCredentialTimestampValid(nconf,i->second))
+				remoteCreds.erase(i++);
+			else ++i;
 		}
 		}
 	}
 	}
 
 
@@ -203,21 +198,19 @@ private:
 	CertificateOfMembership _com;
 	CertificateOfMembership _com;
 
 
 	// Revocations by credentialKey()
 	// Revocations by credentialKey()
-	Hashtable< uint64_t,int64_t > _revocations;
+	FlatMap< uint64_t,int64_t > _revocations;
 
 
 	// Remote credentials that we have received from this member (and that are valid)
 	// 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;
+	FlatMap< uint32_t,Tag > _remoteTags;
+	FlatMap< uint32_t,Capability > _remoteCaps;
+	FlatMap< uint32_t,CertificateOfOwnership > _remoteCoos;
 
 
 public:
 public:
 	class CapabilityIterator
 	class CapabilityIterator
 	{
 	{
 	public:
 	public:
 		ZT_INLINE CapabilityIterator(Membership &m,const NetworkConfig &nconf) noexcept :
 		ZT_INLINE CapabilityIterator(Membership &m,const NetworkConfig &nconf) noexcept :
-			_hti(m._remoteCaps),
-			_k(nullptr),
-			_c(nullptr),
+			_hti(m._remoteCaps.begin()),
 			_m(m),
 			_m(m),
 			_nconf(nconf)
 			_nconf(nconf)
 		{
 		{
@@ -225,17 +218,16 @@ public:
 
 
 		ZT_INLINE Capability *next() noexcept
 		ZT_INLINE Capability *next() noexcept
 		{
 		{
-			while (_hti.next(_k,_c)) {
-				if (_m._isCredentialTimestampValid(_nconf,*_c))
-					return _c;
+			while (_hti != _m._remoteCaps.end()) {
+				FlatMap< uint32_t,Capability >::iterator i(_hti++);
+				if (_m._isCredentialTimestampValid(_nconf,i->second))
+					return &(i->second);
 			}
 			}
 			return nullptr;
 			return nullptr;
 		}
 		}
 
 
 	private:
 	private:
-		Hashtable< uint32_t,Capability >::Iterator _hti;
-		uint32_t *_k;
-		Capability *_c;
+		FlatMap< uint32_t,Capability >::iterator _hti;
 		Membership &_m;
 		Membership &_m;
 		const NetworkConfig &_nconf;
 		const NetworkConfig &_nconf;
 	};
 	};

+ 49 - 61
node/Network.cpp

@@ -598,7 +598,7 @@ Network::~Network()
 		// This is done in Node::leave() so we can pass tPtr properly
 		// 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);
 		//RR->node->configureVirtualNetworkPort((void *)0,_id,&_uPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY,&ctmp);
 	} else {
 	} else {
-		RR->node->configureVirtualNetworkPort((void *)0,_id,&_uPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN,&ctmp);
+		RR->node->configureVirtualNetworkPort(nullptr,_id,&_uPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN,&ctmp);
 	}
 	}
 }
 }
 
 
@@ -626,7 +626,7 @@ bool Network::filterOutgoingPacket(
 	Mutex::Lock l1(_memberships_l);
 	Mutex::Lock l1(_memberships_l);
 	Mutex::Lock l2(_config_l);
 	Mutex::Lock l2(_config_l);
 
 
-	Membership *const membership = (ztDest) ? _memberships.get(ztDest) : (Membership *)0;
+	Membership *const membership = (ztDest) ? _memberships.get(ztDest) : nullptr;
 
 
 	switch(_doZtFilter(RR,rrl,_config,membership,false,ztSource,ztFinalDest,macSource,macDest,frameData,frameLen,etherType,vlanId,_config.rules,_config.ruleCount,cc,ccLength,ccWatch,qosBucket)) {
 	switch(_doZtFilter(RR,rrl,_config,membership,false,ztSource,ztFinalDest,macSource,macDest,frameData,frameLen,etherType,vlanId,_config.rules,_config.ruleCount,cc,ccLength,ccWatch,qosBucket)) {
 
 
@@ -745,7 +745,7 @@ int Network::filterIncomingPacket(
 	Address cc;
 	Address cc;
 	unsigned int ccLength = 0;
 	unsigned int ccLength = 0;
 	bool ccWatch = false;
 	bool ccWatch = false;
-	const Capability *c = (Capability *)0;
+	const Capability *c = nullptr;
 
 
 	uint8_t qosBucket = 255; // For incoming packets this is a dummy value
 	uint8_t qosBucket = 255; // For incoming packets this is a dummy value
 
 
@@ -1099,13 +1099,8 @@ void Network::doPeriodicTasks(void *tPtr,const int64_t now)
 	{
 	{
 		Mutex::Lock l1(_memberships_l);
 		Mutex::Lock l1(_memberships_l);
 
 
-		{
-			Address *a = nullptr;
-			Membership *m = nullptr;
-			Hashtable<Address,Membership>::Iterator i(_memberships);
-			while (i.next(a,m))
-				m->clean(now,_config);
-		}
+		for(FlatMap<Address,Membership>::iterator i(_memberships.begin());i!=_memberships.end();++i)
+			i->second.clean(now,_config);
 
 
 		{
 		{
 			Mutex::Lock l2(_myMulticastGroups_l);
 			Mutex::Lock l2(_myMulticastGroups_l);
@@ -1133,32 +1128,24 @@ void Network::learnBridgeRoute(const MAC &mac,const Address &addr)
 
 
 	// Anti-DOS circuit breaker to prevent nodes from spamming us with absurd numbers of bridge routes
 	// Anti-DOS circuit breaker to prevent nodes from spamming us with absurd numbers of bridge routes
 	while (_remoteBridgeRoutes.size() > ZT_MAX_BRIDGE_ROUTES) {
 	while (_remoteBridgeRoutes.size() > ZT_MAX_BRIDGE_ROUTES) {
-		Hashtable< Address,unsigned long > counts;
+		FlatMap< Address,unsigned long > counts;
 		Address maxAddr;
 		Address maxAddr;
 		unsigned long maxCount = 0;
 		unsigned long maxCount = 0;
 
 
-		MAC *m = nullptr;
-		Address *a = nullptr;
-
 		// Find the address responsible for the most entries
 		// 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;
-				}
+		for(FlatMap<MAC,Address>::iterator i(_remoteBridgeRoutes.begin());i!=_remoteBridgeRoutes.end();++i) {
+			const unsigned long c = ++counts[i->second];
+			if (c > maxCount) {
+				maxCount = c;
+				maxAddr = i->second;
 			}
 			}
 		}
 		}
 
 
 		// Kill this address from our table, since it's most likely spamming us
 		// 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);
-			}
+		for(FlatMap<MAC,Address>::iterator i(_remoteBridgeRoutes.begin());i!=_remoteBridgeRoutes.end();) {
+			if (i->second == maxAddr)
+				_remoteBridgeRoutes.erase(i++);
+			else ++i;
 		}
 		}
 	}
 	}
 }
 }
@@ -1198,24 +1185,24 @@ Membership::AddCredentialResult Network::addCredential(void *tPtr,const Identity
 	const Membership::AddCredentialResult result = m.addCredential(RR,tPtr,sourcePeerIdentity,_config,rev);
 	const Membership::AddCredentialResult result = m.addCredential(RR,tPtr,sourcePeerIdentity,_config,rev);
 
 
 	if ((result == Membership::ADD_ACCEPTED_NEW)&&(rev.fastPropagate())) {
 	if ((result == Membership::ADD_ACCEPTED_NEW)&&(rev.fastPropagate())) {
-		Address *a = nullptr;
-		Membership *m = nullptr;
-		Hashtable<Address,Membership>::Iterator i(_memberships);
-		while (i.next(a,m)) {
-			if ((*a != sourcePeerIdentity.address())&&(*a != rev.signer())) {
-				// TODO
-				/*
-				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);
-				*/
-			}
-		}
+		// TODO
+		/*
+			Address *a = nullptr;
+			Membership *m = nullptr;
+			Hashtable<Address,Membership>::Iterator i(_memberships);
+			while (i.next(a,m)) {
+				if ((*a != sourcePeerIdentity.address())&&(*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;
 	return result;
@@ -1493,21 +1480,21 @@ void Network::_announceMulticastGroups(void *tPtr,bool force)
 	// Assumes _myMulticastGroups_l and _memberships_l are locked
 	// Assumes _myMulticastGroups_l and _memberships_l are locked
 	const std::vector<MulticastGroup> groups(_allMulticastGroups());
 	const std::vector<MulticastGroup> groups(_allMulticastGroups());
 	_announceMulticastGroupsTo(tPtr,controller(),groups);
 	_announceMulticastGroupsTo(tPtr,controller(),groups);
-	{
-		Address *a = nullptr;
-		Membership *m = nullptr;
-		Hashtable<Address,Membership>::Iterator i(_memberships);
-		while (i.next(a,m)) {
-			// TODO
-			/*
-			bool announce = m->multicastLikeGate(now); // force this to be called even if 'force' is true since it updates last push time
-			if ((!announce)&&(force))
-				announce = true;
-			if ((announce)&&(m->isAllowedOnNetwork(_config)))
-				_announceMulticastGroupsTo(tPtr,*a,groups);
-			*/
+
+		// TODO
+		/*
+	Address *a = nullptr;
+	Membership *m = nullptr;
+	Hashtable<Address,Membership>::Iterator i(_memberships);
+	while (i.next(a,m)) {
+		bool announce = m->multicastLikeGate(now); // force this to be called even if 'force' is true since it updates last push time
+		if ((!announce)&&(force))
+			announce = true;
+		if ((announce)&&(m->isAllowedOnNetwork(_config)))
+			_announceMulticastGroupsTo(tPtr,*a,groups);
 		}
 		}
-	}
+		*/
+
 }
 }
 
 
 void Network::_announceMulticastGroupsTo(void *tPtr,const Address &peer,const std::vector<MulticastGroup> &allMulticastGroups)
 void Network::_announceMulticastGroupsTo(void *tPtr,const Address &peer,const std::vector<MulticastGroup> &allMulticastGroups)
@@ -1542,7 +1529,8 @@ std::vector<MulticastGroup> Network::_allMulticastGroups() const
 	std::vector<MulticastGroup> mgs;
 	std::vector<MulticastGroup> mgs;
 	mgs.reserve(_myMulticastGroups.size() + _multicastGroupsBehindMe.size() + 1);
 	mgs.reserve(_myMulticastGroups.size() + _multicastGroupsBehindMe.size() + 1);
 	mgs.insert(mgs.end(),_myMulticastGroups.begin(),_myMulticastGroups.end());
 	mgs.insert(mgs.end(),_myMulticastGroups.begin(),_myMulticastGroups.end());
-	_multicastGroupsBehindMe.appendKeys(mgs);
+	for(FlatMap< MulticastGroup,uint64_t >::const_iterator i(_multicastGroupsBehindMe.begin());i!=_multicastGroupsBehindMe.end();++i)
+		mgs.push_back(i->first);
 	if ((_config)&&(_config.enableBroadcast()))
 	if ((_config)&&(_config.enableBroadcast()))
 		mgs.push_back(Network::BROADCAST);
 		mgs.push_back(Network::BROADCAST);
 	std::sort(mgs.begin(),mgs.end());
 	std::sort(mgs.begin(),mgs.end());

+ 7 - 10
node/Network.hpp

@@ -15,7 +15,6 @@
 #define ZT_NETWORK_HPP
 #define ZT_NETWORK_HPP
 
 
 #include "Constants.hpp"
 #include "Constants.hpp"
-#include "Hashtable.hpp"
 #include "Address.hpp"
 #include "Address.hpp"
 #include "Mutex.hpp"
 #include "Mutex.hpp"
 #include "SharedPtr.hpp"
 #include "SharedPtr.hpp"
@@ -26,6 +25,7 @@
 #include "Membership.hpp"
 #include "Membership.hpp"
 #include "NetworkConfig.hpp"
 #include "NetworkConfig.hpp"
 #include "CertificateOfMembership.hpp"
 #include "CertificateOfMembership.hpp"
+#include "FlatMap.hpp"
 
 
 #include <cstdint>
 #include <cstdint>
 #include <string>
 #include <string>
@@ -160,7 +160,7 @@ public:
 		if (std::binary_search(_myMulticastGroups.begin(),_myMulticastGroups.end(),mg))
 		if (std::binary_search(_myMulticastGroups.begin(),_myMulticastGroups.end(),mg))
 			return true;
 			return true;
 		else if (includeBridgedGroups)
 		else if (includeBridgedGroups)
-			return _multicastGroupsBehindMe.contains(mg);
+			return (_multicastGroupsBehindMe.find(mg) != _multicastGroupsBehindMe.end());
 		return false;
 		return false;
 	}
 	}
 
 
@@ -326,11 +326,8 @@ public:
 	ZT_INLINE void eachMember(F f)
 	ZT_INLINE void eachMember(F f)
 	{
 	{
 		Mutex::Lock ml(_memberships_l);
 		Mutex::Lock ml(_memberships_l);
-		Hashtable<Address,Membership>::Iterator i(_memberships);
-		Address *a = nullptr;
-		Membership *m = nullptr;
-		while (i.next(a,m)) {
-			if (!f(*a,*m))
+		for(FlatMap<Address,Membership>::iterator i(_memberships.begin());i!=_memberships.end();++i) {
+			if (!f(i->first,i->second))
 				break;
 				break;
 		}
 		}
 	}
 	}
@@ -356,8 +353,8 @@ private:
 	bool _portInitialized;
 	bool _portInitialized;
 
 
 	std::vector< MulticastGroup > _myMulticastGroups; // multicast groups that we belong to (according to tap)
 	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)
+	FlatMap< MulticastGroup,uint64_t > _multicastGroupsBehindMe; // multicast groups that seem to be behind us and when we last saw them (if we are a bridge)
+	FlatMap< MAC,Address > _remoteBridgeRoutes; // remote addresses where given MACs are reachable (for tracking devices behind remote bridges)
 
 
 	NetworkConfig _config;
 	NetworkConfig _config;
 	std::atomic<int64_t> _lastConfigUpdate;
 	std::atomic<int64_t> _lastConfigUpdate;
@@ -380,7 +377,7 @@ private:
 		NETCONF_FAILURE_INIT_FAILED
 		NETCONF_FAILURE_INIT_FAILED
 	} _netconfFailure;
 	} _netconfFailure;
 
 
-	Hashtable<Address,Membership> _memberships;
+	FlatMap<Address,Membership> _memberships;
 
 
 	Mutex _myMulticastGroups_l;
 	Mutex _myMulticastGroups_l;
 	Mutex _remoteBridgeRoutes_l;
 	Mutex _remoteBridgeRoutes_l;

+ 0 - 1
node/NetworkConfig.hpp

@@ -30,7 +30,6 @@
 #include "Capability.hpp"
 #include "Capability.hpp"
 #include "Tag.hpp"
 #include "Tag.hpp"
 #include "Dictionary.hpp"
 #include "Dictionary.hpp"
-#include "Hashtable.hpp"
 #include "Identity.hpp"
 #include "Identity.hpp"
 #include "Utils.hpp"
 #include "Utils.hpp"
 #include "Trace.hpp"
 #include "Trace.hpp"

+ 38 - 94
node/Node.cpp

@@ -82,7 +82,6 @@ Node::Node(void *uPtr,void *tPtr,const struct ZT_Node_Callbacks *callbacks,int64
 	_cb(*callbacks),
 	_cb(*callbacks),
 	_uPtr(uPtr),
 	_uPtr(uPtr),
 	_networks(),
 	_networks(),
-	_networksMask(15),
 	_now(now),
 	_now(now),
 	_lastPing(0),
 	_lastPing(0),
 	_lastHousekeepingRun(0),
 	_lastHousekeepingRun(0),
@@ -91,8 +90,6 @@ Node::Node(void *uPtr,void *tPtr,const struct ZT_Node_Callbacks *callbacks,int64
 	_natMustDie(true),
 	_natMustDie(true),
 	_online(false)
 	_online(false)
 {
 {
-	_networks.resize(16); // _networksMask + 1, must be power of two
-
 	uint64_t idtmp[2]; idtmp[0] = 0; idtmp[1] = 0;
 	uint64_t idtmp[2]; idtmp[0] = 0; idtmp[1] = 0;
 	std::vector<uint8_t> data(stateObjectGet(tPtr,ZT_STATE_OBJECT_IDENTITY_SECRET,idtmp));
 	std::vector<uint8_t> data(stateObjectGet(tPtr,ZT_STATE_OBJECT_IDENTITY_SECRET,idtmp));
 	bool haveIdentity = false;
 	bool haveIdentity = false;
@@ -133,15 +130,9 @@ Node::Node(void *uPtr,void *tPtr,const struct ZT_Node_Callbacks *callbacks,int64
 
 
 Node::~Node()
 Node::~Node()
 {
 {
-	// Let go of all networks to leave them. Do it this way in case Network wants to
-	// do anything in its destructor that locks the _networks lock to avoid a deadlock.
-	std::vector< SharedPtr<Network> > networks;
-	{
-		RWMutex::Lock _l(_networks_m);
-		networks.swap(_networks);
-	}
-	networks.clear();
-
+	_networks_m.lock();
+	_networks_m.unlock();
+	_networks.clear();
 	_networks_m.lock();
 	_networks_m.lock();
 	_networks_m.unlock();
 	_networks_m.unlock();
 
 
@@ -277,10 +268,8 @@ ZT_ResultCode Node::processBackgroundTasks(void *tPtr, int64_t now, volatile int
 		_lastHousekeepingRun = now;
 		_lastHousekeepingRun = now;
 		{
 		{
 			RWMutex::RLock l(_networks_m);
 			RWMutex::RLock l(_networks_m);
-			for(std::vector< SharedPtr<Network> >::const_iterator i(_networks.begin());i!=_networks.end();++i) {
-				if ((*i))
-					(*i)->doPeriodicTasks(tPtr,now);
-			}
+			for(FlatMap< uint64_t,SharedPtr<Network> >::const_iterator i(_networks.begin());i!=_networks.end();++i)
+				i->second->doPeriodicTasks(tPtr,now);
 		}
 		}
 	}
 	}
 
 
@@ -292,13 +281,10 @@ ZT_ResultCode Node::processBackgroundTasks(void *tPtr, int64_t now, volatile int
 			// or trust nodes without doing an extra cert check.
 			// or trust nodes without doing an extra cert check.
 			{
 			{
 				_localControllerAuthorizations_m.lock();
 				_localControllerAuthorizations_m.lock();
-				Hashtable< _LocalControllerAuth,int64_t >::Iterator i(_localControllerAuthorizations);
-				_LocalControllerAuth *k = (_LocalControllerAuth *)0;
-				int64_t *v = (int64_t *)0;
-				while (i.next(k,v)) {
-					if ((*v - now) > (ZT_NETWORK_AUTOCONF_DELAY * 3)) {
-						_localControllerAuthorizations.erase(*k);
-					}
+				for(FlatMap<_LocalControllerAuth,int64_t>::iterator i(_localControllerAuthorizations.begin());i!=_localControllerAuthorizations.end();) {
+					if ((i->second - now) > (ZT_NETWORK_AUTOCONF_DELAY * 3))
+						_localControllerAuthorizations.erase(i++);
+					else ++i;
 				}
 				}
 				_localControllerAuthorizations_m.unlock();
 				_localControllerAuthorizations_m.unlock();
 			}
 			}
@@ -349,72 +335,40 @@ ZT_ResultCode Node::processBackgroundTasks(void *tPtr, int64_t now, volatile int
 
 
 ZT_ResultCode Node::join(uint64_t nwid,const ZT_Fingerprint *controllerFingerprint,void *uptr,void *tptr)
 ZT_ResultCode Node::join(uint64_t nwid,const ZT_Fingerprint *controllerFingerprint,void *uptr,void *tptr)
 {
 {
-	RWMutex::Lock l(_networks_m);
-
-	const uint64_t nwidHashed = nwid + (nwid >> 32U);
-	SharedPtr<Network> *nw = &(_networks[(unsigned long)(nwidHashed & _networksMask)]);
-
-	// Enlarge flat hash table of networks until all networks fit without collisions.
-	if (*nw) {
-		unsigned long newNetworksSize = (unsigned long)_networks.size();
-		std::vector< SharedPtr<Network> > newNetworks;
-		uint64_t newNetworksMask,id;
-		std::vector< SharedPtr<Network> >::const_iterator i;
-
-try_larger_network_hashtable:
-		newNetworksSize <<= 1U; // must remain a power of two
-		newNetworks.clear();
-		newNetworks.resize(newNetworksSize);
-		newNetworksMask = (uint64_t)(newNetworksSize - 1);
-
-		for(i=_networks.begin();i!=_networks.end();++i) {
-			id = (*i)->id();
-			nw = &(newNetworks[(unsigned long)((id + (id >> 32U)) & newNetworksMask)]);
-			if (*nw)
-				goto try_larger_network_hashtable;
-			*nw = *i;
-		}
-		if (newNetworks[(unsigned long)(nwidHashed & newNetworksMask)])
-			goto try_larger_network_hashtable;
-
-		_networks.swap(newNetworks);
-		_networksMask = newNetworksMask;
-		nw = &(_networks[(unsigned long)(nwidHashed & newNetworksMask)]);
-	}
-
 	Fingerprint fp;
 	Fingerprint fp;
 	if (controllerFingerprint)
 	if (controllerFingerprint)
 		Utils::copy<sizeof(ZT_Fingerprint)>(fp.apiFingerprint(),controllerFingerprint);
 		Utils::copy<sizeof(ZT_Fingerprint)>(fp.apiFingerprint(),controllerFingerprint);
-	nw->set(new Network(RR,tptr,nwid,fp,uptr,(const NetworkConfig *)0));
+
+	RWMutex::Lock l(_networks_m);
+	SharedPtr<Network> &nw = _networks[nwid];
+	if (nw)
+		return ZT_RESULT_OK;
+	nw.set(new Network(RR,tptr,nwid,fp,uptr,nullptr));
 
 
 	return ZT_RESULT_OK;
 	return ZT_RESULT_OK;
 }
 }
 
 
 ZT_ResultCode Node::leave(uint64_t nwid,void **uptr,void *tptr)
 ZT_ResultCode Node::leave(uint64_t nwid,void **uptr,void *tptr)
 {
 {
-	const uint64_t nwidHashed = nwid + (nwid >> 32U);
-
 	ZT_VirtualNetworkConfig ctmp;
 	ZT_VirtualNetworkConfig ctmp;
-	void **nUserPtr = (void **)0;
-	{
-		RWMutex::RLock l(_networks_m);
-		SharedPtr<Network> &nw = _networks[(unsigned long)(nwidHashed & _networksMask)];
-		if (!nw)
-			return ZT_RESULT_OK;
-		if (uptr)
-			*uptr = nw->userPtr();
-		nw->externalConfig(&ctmp);
-		nw->destroy();
-		nUserPtr = nw->userPtr();
+
+	_networks_m.lock();
+	FlatMap< uint64_t,SharedPtr<Network> >::iterator nwi(_networks.find(nwid));
+	if (nwi == _networks.end()) {
+		_networks_m.unlock();
+		return ZT_RESULT_OK;
 	}
 	}
+	SharedPtr<Network> nw(nwi->second);
+	_networks.erase(nwi);
+	_networks_m.unlock();
 
 
-	if (nUserPtr)
-		RR->node->configureVirtualNetworkPort(tptr,nwid,nUserPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY,&ctmp);
+	if (uptr)
+		*uptr = *nw->userPtr();
+	nw->externalConfig(&ctmp);
+	nw->destroy();
+	nw.zero();
 
 
-	{
-		RWMutex::Lock _l(_networks_m);
-		_networks[(unsigned long)(nwidHashed & _networksMask)].zero();
-	}
+	RR->node->configureVirtualNetworkPort(tptr,nwid,uptr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY,&ctmp);
 
 
 	uint64_t tmp[2];
 	uint64_t tmp[2];
 	tmp[0] = nwid; tmp[1] = 0;
 	tmp[0] = nwid; tmp[1] = 0;
@@ -539,30 +493,22 @@ ZT_VirtualNetworkConfig *Node::networkConfig(uint64_t nwid) const
 		nw->externalConfig(nc);
 		nw->externalConfig(nc);
 		return nc;
 		return nc;
 	}
 	}
-	return (ZT_VirtualNetworkConfig *)0;
+	return nullptr;
 }
 }
 
 
 ZT_VirtualNetworkList *Node::networks() const
 ZT_VirtualNetworkList *Node::networks() const
 {
 {
 	RWMutex::RLock l(_networks_m);
 	RWMutex::RLock l(_networks_m);
 
 
-	unsigned long networkCount = 0;
-	for(std::vector< SharedPtr<Network> >::const_iterator i(_networks.begin());i!=_networks.end();++i) {
-		if ((*i))
-			++networkCount;
-	}
-
-	char *const buf = (char *)::malloc(sizeof(ZT_VirtualNetworkList) + (sizeof(ZT_VirtualNetworkConfig) * networkCount));
+	char *const buf = (char *)::malloc(sizeof(ZT_VirtualNetworkList) + (sizeof(ZT_VirtualNetworkConfig) * _networks.size()));
 	if (!buf)
 	if (!buf)
 		return (ZT_VirtualNetworkList *)0;
 		return (ZT_VirtualNetworkList *)0;
 	ZT_VirtualNetworkList *nl = (ZT_VirtualNetworkList *)buf;
 	ZT_VirtualNetworkList *nl = (ZT_VirtualNetworkList *)buf;
 	nl->networks = (ZT_VirtualNetworkConfig *)(buf + sizeof(ZT_VirtualNetworkList));
 	nl->networks = (ZT_VirtualNetworkConfig *)(buf + sizeof(ZT_VirtualNetworkList));
 
 
 	nl->networkCount = 0;
 	nl->networkCount = 0;
-	for(std::vector< SharedPtr<Network> >::const_iterator i(_networks.begin());i!=_networks.end();++i) {
-		if ((*i))
-			(*i)->externalConfig(&(nl->networks[nl->networkCount++]));
-	}
+	for(FlatMap< uint64_t,SharedPtr<Network> >::const_iterator i(_networks.begin());i!=_networks.end();++i)
+		i->second->externalConfig(&(nl->networks[nl->networkCount++]));
 
 
 	return nl;
 	return nl;
 }
 }
@@ -650,12 +596,10 @@ bool Node::shouldUsePathForZeroTierTraffic(void *tPtr,const Identity &id,const i
 {
 {
 	{
 	{
 		RWMutex::RLock l(_networks_m);
 		RWMutex::RLock l(_networks_m);
-		for (std::vector<SharedPtr<Network> >::iterator i(_networks.begin()); i != _networks.end(); ++i) {
-			if ((*i)) {
-				for (unsigned int k = 0,j = (*i)->config().staticIpCount; k < j; ++k) {
-					if ((*i)->config().staticIps[k].containsAddress(remoteAddress))
-						return false;
-				}
+		for (FlatMap< uint64_t,SharedPtr<Network> >::iterator i(_networks.begin()); i != _networks.end(); ++i) {
+			for (unsigned int k = 0,j = i->second->config().staticIpCount; k < j; ++k) {
+				if (i->second->config().staticIps[k].containsAddress(remoteAddress))
+					return false;
 			}
 			}
 		}
 		}
 	}
 	}

+ 9 - 7
node/Node.hpp

@@ -23,8 +23,8 @@
 #include "Path.hpp"
 #include "Path.hpp"
 #include "Salsa20.hpp"
 #include "Salsa20.hpp"
 #include "NetworkController.hpp"
 #include "NetworkController.hpp"
-#include "Hashtable.hpp"
 #include "Buf.hpp"
 #include "Buf.hpp"
+#include "FlatMap.hpp"
 
 
 #include <cstdio>
 #include <cstdio>
 #include <cstdlib>
 #include <cstdlib>
@@ -172,7 +172,10 @@ public:
 	ZT_INLINE SharedPtr<Network> network(uint64_t nwid) const noexcept
 	ZT_INLINE SharedPtr<Network> network(uint64_t nwid) const noexcept
 	{
 	{
 		RWMutex::RLock l(_networks_m);
 		RWMutex::RLock l(_networks_m);
-		return _networks[(unsigned long)((nwid + (nwid >> 32U)) & _networksMask)];
+		const SharedPtr<Network> *const n = _networks.get(nwid);
+		if (n)
+			return *n;
+		return SharedPtr<Network>();
 	}
 	}
 
 
 	/**
 	/**
@@ -342,7 +345,7 @@ private:
 	// in each peer if that peer object is still held in memory. Calling alarm() unnecessarily on a peer
 	// in each peer if that peer object is still held in memory. Calling alarm() unnecessarily on a peer
 	// is harmless. This just exists as an optimization to prevent having to iterate through all peers
 	// is harmless. This just exists as an optimization to prevent having to iterate through all peers
 	// on every processBackgroundTasks call. A simple map<> is used here because there are usually only
 	// on every processBackgroundTasks call. A simple map<> is used here because there are usually only
-	// a few of these, if any, and it's slightly faster and lower memory in that case than a Hashtable.
+	// a few of these, if any.
 	std::map<Address,int64_t> _peerAlarms;
 	std::map<Address,int64_t> _peerAlarms;
 	RWMutex _peerAlarms_l;
 	RWMutex _peerAlarms_l;
 
 
@@ -355,17 +358,16 @@ private:
 	{
 	{
 		uint64_t nwid,address;
 		uint64_t nwid,address;
 		ZT_INLINE _LocalControllerAuth(const uint64_t nwid_,const Address &address_)  noexcept: nwid(nwid_),address(address_.toInt()) {}
 		ZT_INLINE _LocalControllerAuth(const uint64_t nwid_,const Address &address_)  noexcept: nwid(nwid_),address(address_.toInt()) {}
-		ZT_INLINE unsigned long hashCode() const noexcept { return (unsigned long)(nwid ^ address); }
+		ZT_INLINE unsigned long hashCode() const noexcept { return (unsigned long)(nwid + address); }
 		ZT_INLINE bool operator==(const _LocalControllerAuth &a) const noexcept { return ((a.nwid == nwid) && (a.address == address)); }
 		ZT_INLINE bool operator==(const _LocalControllerAuth &a) const noexcept { return ((a.nwid == nwid) && (a.address == address)); }
 		ZT_INLINE bool operator!=(const _LocalControllerAuth &a) const noexcept { return ((a.nwid != nwid) || (a.address != address)); }
 		ZT_INLINE bool operator!=(const _LocalControllerAuth &a) const noexcept { return ((a.nwid != nwid) || (a.address != address)); }
 	};
 	};
-	Hashtable< _LocalControllerAuth,int64_t > _localControllerAuthorizations;
+	FlatMap<_LocalControllerAuth,int64_t> _localControllerAuthorizations;
 	Mutex _localControllerAuthorizations_m;
 	Mutex _localControllerAuthorizations_m;
 
 
 	// Networks are stored in a flat hash table that is resized on any network ID collision. This makes
 	// Networks are stored in a flat hash table that is resized on any network ID collision. This makes
 	// network lookup by network ID a few bitwise ops and an array index.
 	// network lookup by network ID a few bitwise ops and an array index.
-	std::vector< SharedPtr<Network> > _networks;
-	uint64_t _networksMask;
+	FlatMap< uint64_t,SharedPtr<Network> > _networks;
 	RWMutex _networks_m;
 	RWMutex _networks_m;
 
 
 	// These are local interface addresses that have been configured via the API
 	// These are local interface addresses that have been configured via the API

+ 0 - 1
node/Peer.hpp

@@ -23,7 +23,6 @@
 #include "Identity.hpp"
 #include "Identity.hpp"
 #include "InetAddress.hpp"
 #include "InetAddress.hpp"
 #include "SharedPtr.hpp"
 #include "SharedPtr.hpp"
-#include "Hashtable.hpp"
 #include "Mutex.hpp"
 #include "Mutex.hpp"
 #include "Endpoint.hpp"
 #include "Endpoint.hpp"
 #include "Locator.hpp"
 #include "Locator.hpp"

+ 1 - 1
node/Protocol.cpp

@@ -77,7 +77,7 @@ void armor(Buf &pkt,int packetSize,const uint8_t key[ZT_PEER_SECRET_KEY_LENGTH],
 			ph.mac = mac[0];
 			ph.mac = mac[0];
 		} break;
 		} break;
 
 
-		case ZT_PROTO_CIPHER_SUITE__AES_GCM_NRH: {
+		case ZT_PROTO_CIPHER_SUITE__AES_GMAC_SIV: {
 		} break;
 		} break;
 	}
 	}
 }
 }

+ 15 - 28
node/SelfAwareness.cpp

@@ -46,8 +46,7 @@ private:
 };
 };
 
 
 SelfAwareness::SelfAwareness(const RuntimeEnvironment *renv) :
 SelfAwareness::SelfAwareness(const RuntimeEnvironment *renv) :
-	RR(renv),
-	_phy(256)
+	RR(renv)
 {
 {
 }
 }
 
 
@@ -70,14 +69,10 @@ void SelfAwareness::iam(void *tPtr,const Identity &reporter,const int64_t receiv
 		// Erase all entries in this scope that were not reported from this remote address to prevent 'thrashing'
 		// Erase all entries in this scope that were not reported from this remote address to prevent 'thrashing'
 		// due to multiple reports of endpoint change.
 		// due to multiple reports of endpoint change.
 		// Don't use 'entry' after this since hash table gets modified.
 		// Don't use 'entry' after this since hash table gets modified.
-		{
-			Hashtable< PhySurfaceKey,PhySurfaceEntry >::Iterator i(_phy);
-			PhySurfaceKey *k = nullptr;
-			PhySurfaceEntry *e = nullptr;
-			while (i.next(k,e)) {
-				if ((k->scope == scope)&&(k->reporterPhysicalAddress != reporterPhysicalAddress))
-					_phy.erase(*k);
-			}
+		for(FlatMap<PhySurfaceKey,PhySurfaceEntry>::iterator i(_phy.begin());i!=_phy.end();) {
+			if ((i->first.scope == scope)&&(i->first.reporterPhysicalAddress != reporterPhysicalAddress))
+				_phy.erase(i++);
+			else ++i;
 		}
 		}
 
 
 		// Reset all paths within this scope and address family
 		// Reset all paths within this scope and address family
@@ -96,36 +91,28 @@ void SelfAwareness::iam(void *tPtr,const Identity &reporter,const int64_t receiv
 void SelfAwareness::clean(int64_t now)
 void SelfAwareness::clean(int64_t now)
 {
 {
 	Mutex::Lock l(_phy_l);
 	Mutex::Lock l(_phy_l);
-	Hashtable< PhySurfaceKey,PhySurfaceEntry >::Iterator i(_phy);
-	PhySurfaceKey *k = nullptr;
-	PhySurfaceEntry *e = nullptr;
-	while (i.next(k,e)) {
-		if ((now - e->ts) >= ZT_SELFAWARENESS_ENTRY_TIMEOUT)
-			_phy.erase(*k);
+	for(FlatMap<PhySurfaceKey,PhySurfaceEntry>::iterator i(_phy.begin());i!=_phy.end();) {
+		if ((now - i->second.ts) >= ZT_SELFAWARENESS_ENTRY_TIMEOUT)
+			_phy.erase(i++);
+		else ++i;
 	}
 	}
 }
 }
 
 
 std::multimap<unsigned long,InetAddress> SelfAwareness::externalAddresses(const int64_t now) const
 std::multimap<unsigned long,InetAddress> SelfAwareness::externalAddresses(const int64_t now) const
 {
 {
 	std::multimap<unsigned long,InetAddress> r;
 	std::multimap<unsigned long,InetAddress> r;
-	Hashtable<InetAddress,unsigned long> counts(16);
+	FlatMap<InetAddress,unsigned long,256> counts;
 
 
 	{
 	{
 		Mutex::Lock l(_phy_l);
 		Mutex::Lock l(_phy_l);
-		Hashtable<PhySurfaceKey,PhySurfaceEntry>::Iterator i(const_cast<SelfAwareness *>(this)->_phy);
-		PhySurfaceKey *k = nullptr;
-		PhySurfaceEntry *e = nullptr;
-		while (i.next(k,e)) {
-			if ((now - e->ts) < ZT_SELFAWARENESS_ENTRY_TIMEOUT)
-				++counts[e->mySurface];
+		for(FlatMap<PhySurfaceKey,PhySurfaceEntry>::const_iterator i(_phy.begin());i!=_phy.end();++i) {
+			if ((now - i->second.ts) < ZT_SELFAWARENESS_ENTRY_TIMEOUT)
+				++counts[i->second.mySurface];
 		}
 		}
 	}
 	}
 
 
-	Hashtable<InetAddress,unsigned long>::Iterator i(counts);
-	InetAddress *k = nullptr;
-	unsigned long *c = nullptr;
-	while (i.next(k,c))
-		r.insert(std::pair<unsigned long,InetAddress>(*c,*k));
+	for(FlatMap<InetAddress,unsigned long,256>::iterator i(counts.begin());i!=counts.end();++i)
+		r.insert(std::pair<unsigned long,InetAddress>(i->second,i->first));
 
 
 	return r;
 	return r;
 }
 }

+ 2 - 2
node/SelfAwareness.hpp

@@ -16,7 +16,7 @@
 
 
 #include "Constants.hpp"
 #include "Constants.hpp"
 #include "InetAddress.hpp"
 #include "InetAddress.hpp"
-#include "Hashtable.hpp"
+#include "FlatMap.hpp"
 #include "Address.hpp"
 #include "Address.hpp"
 #include "Mutex.hpp"
 #include "Mutex.hpp"
 
 
@@ -92,7 +92,7 @@ private:
 	};
 	};
 
 
 	const RuntimeEnvironment *RR;
 	const RuntimeEnvironment *RR;
-	Hashtable< PhySurfaceKey,PhySurfaceEntry > _phy;
+	FlatMap< PhySurfaceKey,PhySurfaceEntry > _phy;
 	Mutex _phy_l;
 	Mutex _phy_l;
 };
 };
 
 

+ 24 - 45
node/Tests.cpp

@@ -34,12 +34,11 @@
 #include "Tag.hpp"
 #include "Tag.hpp"
 #include "Capability.hpp"
 #include "Capability.hpp"
 #include "NetworkConfig.hpp"
 #include "NetworkConfig.hpp"
-#include "LZ4.hpp"
-#include "Hashtable.hpp"
 #include "FCV.hpp"
 #include "FCV.hpp"
 #include "SHA512.hpp"
 #include "SHA512.hpp"
 #include "Defragmenter.hpp"
 #include "Defragmenter.hpp"
 #include "Fingerprint.hpp"
 #include "Fingerprint.hpp"
+#include "FlatMap.hpp"
 
 
 #include <cstdint>
 #include <cstdint>
 #include <cstring>
 #include <cstring>
@@ -467,54 +466,34 @@ extern "C" const char *ZTT_general()
 		}
 		}
 
 
 		{
 		{
-			ZT_T_PRINTF("[general] Testing Hashtable... ");
-
-			long cnt = 0;
-			Hashtable< unsigned int,LifeCycleTracker > test,test2;
-			for(unsigned int i=0;i<512;++i)
-				test[i] = LifeCycleTracker(cnt);
-			if (cnt != 512) {
-				ZT_T_PRINTF("FAILED (expected 512 objects, got %lu (1))" ZT_EOL_S,cnt);
-				return "Hashtable test failed (1)";
-			}
-			test2 = test;
-			if (cnt != 1024) {
-				ZT_T_PRINTF("FAILED (expected 1024 objects, got %lu (2))" ZT_EOL_S,cnt);
-				return "Hashtable test failed (2)";
-			}
-			test.clear();
-			if (cnt != 512) {
-				ZT_T_PRINTF("FAILED (expected 512 objects, got %lu (3))" ZT_EOL_S,cnt);
-				return "Hashtable test failed (3)";
-			}
-			for(unsigned int i=0;i<512;++i)
-				test[i] = LifeCycleTracker(cnt);
-			if (cnt != 1024) {
-				ZT_T_PRINTF("FAILED (expected 1024 objects, got %lu (4))" ZT_EOL_S,cnt);
-				return "Hashtable test failed (4)";
+			ZT_T_PRINTF("[general] Testing FlatMap... ");
+			FlatMap<uint64_t,uint64_t> tm;
+			for(uint64_t i=0;i<10000;++i)
+				tm.set(i,i);
+			for(uint64_t i=0;i<10000;++i) {
+				uint64_t *v = tm.get(i);
+				if ((!v)||(*v != i)) {
+					ZT_T_PRINTF("FAILED (get() failed)" ZT_EOL_S);
+					return "FlatMap::get() failed";
+				}
 			}
 			}
-			test.clear();
-			test2.clear();
-			if (cnt != 0) {
-				ZT_T_PRINTF("FAILED (expected 0 objects, got %lu (5))" ZT_EOL_S,cnt);
-				return "Hashtable test failed (5)";
+			for(FlatMap<uint64_t,uint64_t>::iterator i(tm.begin());i!=tm.end();) {
+				if ((i->first & 1U) == 0)
+					tm.erase(i++);
+				else ++i;
 			}
 			}
-
-			Hashtable< unsigned int,unsigned int > test3;
-			for(unsigned int i=0;i<1111;++i)
-				test3[i] = i;
-			if (test3.size() != 1111) {
-				ZT_T_PRINTF("FAILED (size() incorrect)" ZT_EOL_S);
-				return "Hashtable test failed (size() incorrect)";
+			if (tm.size() != 5000) {
+				ZT_T_PRINTF("FAILED (erase() failed (1))" ZT_EOL_S);
+				return "FlatMap::erase() failed (1)";
 			}
 			}
-			for(unsigned int i=0;i<1111;++i) {
-				if (test3[i] != i) {
-					ZT_T_PRINTF("FAILED (lookup test)" ZT_EOL_S);
-					return "Hashtable test failed (lookup)";
+			for(uint64_t i=0;i<10000;++i) {
+				uint64_t *v = tm.get(i);
+				if (((i & 1U) == 0)&&(v)) {
+					ZT_T_PRINTF("FAILED (erase() failed (2))" ZT_EOL_S);
+					return "FlatMap::erase() failed (2)";
 				}
 				}
 			}
 			}
-
-			ZT_T_PRINTF("OK" ZT_EOL_S);
+			ZT_T_PRINTF("OK (hash size: %lu)" ZT_EOL_S,tm.hashSize());
 		}
 		}
 
 
 		{
 		{

+ 16 - 31
node/Topology.cpp

@@ -36,11 +36,7 @@ struct _RootSortComparisonOperator
 
 
 Topology::Topology(const RuntimeEnvironment *renv,void *tPtr) :
 Topology::Topology(const RuntimeEnvironment *renv,void *tPtr) :
 	RR(renv),
 	RR(renv),
-	_numConfiguredPhysicalPaths(0),
-	_peers(256),
-	_peersByIncomingProbe(256),
-	_peersByIdentityHash(256),
-	_paths(1024)
+	_numConfiguredPhysicalPaths(0)
 {
 {
 	uint64_t idtmp[2]; idtmp[0] = 0; idtmp[1] = 0;
 	uint64_t idtmp[2]; idtmp[0] = 0; idtmp[1] = 0;
 	std::vector<uint8_t> data(RR->node->stateObjectGet(tPtr,ZT_STATE_OBJECT_ROOTS,idtmp));
 	std::vector<uint8_t> data(RR->node->stateObjectGet(tPtr,ZT_STATE_OBJECT_ROOTS,idtmp));
@@ -103,11 +99,8 @@ void Topology::getAllPeers(std::vector< SharedPtr<Peer> > &allPeers) const
 	RWMutex::RLock l(_peers_l);
 	RWMutex::RLock l(_peers_l);
 	allPeers.clear();
 	allPeers.clear();
 	allPeers.reserve(_peers.size());
 	allPeers.reserve(_peers.size());
-	Hashtable< Address,SharedPtr<Peer> >::Iterator i(*(const_cast<Hashtable< Address,SharedPtr<Peer> > *>(&_peers)));
-	Address *a = nullptr;
-	SharedPtr<Peer> *p = nullptr;
-	while (i.next(a,p))
-		allPeers.push_back(*p);
+	for(FlatMap< Address,SharedPtr<Peer> >::const_iterator i(_peers.begin());i!=_peers.end();++i)
+		allPeers.push_back(i->second);
 }
 }
 
 
 void Topology::setPhysicalPathConfiguration(const struct sockaddr_storage *pathNetwork,const ZT_PhysicalPathConfiguration *pathConfig)
 void Topology::setPhysicalPathConfiguration(const struct sockaddr_storage *pathNetwork,const ZT_PhysicalPathConfiguration *pathConfig)
@@ -205,26 +198,21 @@ void Topology::doPeriodicTasks(void *tPtr,const int64_t now)
 {
 {
 	{
 	{
 		RWMutex::Lock l1(_peers_l);
 		RWMutex::Lock l1(_peers_l);
-		Hashtable< Address,SharedPtr<Peer> >::Iterator i(_peers);
-		Address *a = nullptr;
-		SharedPtr<Peer> *p = nullptr;
-		while (i.next(a,p)) {
-			if ( (!(*p)->alive(now)) && (_roots.count((*p)->identity()) == 0) ) {
-				(*p)->save(tPtr);
-				_peersByIncomingProbe.erase((*p)->incomingProbe());
-				_peersByIdentityHash.erase((*p)->identity().fingerprint());
-				_peers.erase(*a);
-			}
+		for(FlatMap< Address,SharedPtr<Peer> >::iterator i(_peers.begin());i!=_peers.end();) {
+			if ( (!i->second->alive(now)) && (_roots.count(i->second->identity()) == 0) ) {
+				i->second->save(tPtr);
+				_peersByIncomingProbe.erase(i->second->incomingProbe());
+				_peersByIdentityHash.erase(i->second->identity().fingerprint());
+				_peers.erase(i++);
+			} else ++i;
 		}
 		}
 	}
 	}
 	{
 	{
 		RWMutex::Lock l1(_paths_l);
 		RWMutex::Lock l1(_paths_l);
-		Hashtable< uint64_t,SharedPtr<Path> >::Iterator i(_paths);
-		uint64_t *k = nullptr;
-		SharedPtr<Path> *p = nullptr;
-		while (i.next(k,p)) {
-			if ((p->references() <= 1)&&(!(*p)->alive(now)))
-				_paths.erase(*k);
+		for(FlatMap< uint64_t,SharedPtr<Path> >::iterator i(_paths.begin());i!=_paths.end();) {
+			if ((i->second.references() <= 1)&&(!i->second->alive(now)))
+				_paths.erase(i++);
+			else ++i;
 		}
 		}
 	}
 	}
 }
 }
@@ -232,11 +220,8 @@ void Topology::doPeriodicTasks(void *tPtr,const int64_t now)
 void Topology::saveAll(void *tPtr)
 void Topology::saveAll(void *tPtr)
 {
 {
 	RWMutex::RLock l(_peers_l);
 	RWMutex::RLock l(_peers_l);
-	Hashtable< Address,SharedPtr<Peer> >::Iterator i(_peers);
-	Address *a = nullptr;
-	SharedPtr<Peer> *p = nullptr;
-	while (i.next(a,p))
-		(*p)->save(tPtr);
+	for(FlatMap< Address,SharedPtr<Peer> >::iterator i(_peers.begin());i!=_peers.end();++i)
+		i->second->save(tPtr);
 }
 }
 
 
 void Topology::_loadCached(void *tPtr,const Address &zta,SharedPtr<Peer> &peer)
 void Topology::_loadCached(void *tPtr,const Address &zta,SharedPtr<Peer> &peer)

+ 11 - 20
node/Topology.hpp

@@ -27,10 +27,10 @@
 #include "Path.hpp"
 #include "Path.hpp"
 #include "Mutex.hpp"
 #include "Mutex.hpp"
 #include "InetAddress.hpp"
 #include "InetAddress.hpp"
-#include "Hashtable.hpp"
 #include "SharedPtr.hpp"
 #include "SharedPtr.hpp"
 #include "ScopedPtr.hpp"
 #include "ScopedPtr.hpp"
 #include "Fingerprint.hpp"
 #include "Fingerprint.hpp"
+#include "FlatMap.hpp"
 
 
 namespace ZeroTier {
 namespace ZeroTier {
 
 
@@ -179,11 +179,8 @@ public:
 	ZT_INLINE void eachPeer(F f) const
 	ZT_INLINE void eachPeer(F f) const
 	{
 	{
 		RWMutex::RLock l(_peers_l);
 		RWMutex::RLock l(_peers_l);
-		Hashtable< Address,SharedPtr<Peer> >::Iterator i(const_cast<Topology *>(this)->_peers);
-		Address *a = nullptr;
-		SharedPtr<Peer> *p = nullptr;
-		while (i.next(a,p))
-			f(*((const SharedPtr<Peer> *)p));
+		for(FlatMap< Address,SharedPtr<Peer> >::const_iterator i(_peers.begin());i!=_peers.end();++i)
+			f(i->second);
 	}
 	}
 
 
 	/**
 	/**
@@ -207,11 +204,8 @@ public:
 		std::sort(rootPeerPtrs.begin(),rootPeerPtrs.end());
 		std::sort(rootPeerPtrs.begin(),rootPeerPtrs.end());
 
 
 		try {
 		try {
-			Hashtable< Address,SharedPtr<Peer> >::Iterator i(const_cast<Topology *>(this)->_peers);
-			Address *a = nullptr;
-			SharedPtr<Peer> *p = nullptr;
-			while (i.next(a,p))
-				f(*((const SharedPtr<Peer> *)p),std::binary_search(rootPeerPtrs.begin(),rootPeerPtrs.end(),(uintptr_t)p->ptr()));
+			for(FlatMap< Address,SharedPtr<Peer> >::const_iterator i(_peers.begin());i!=_peers.end();++i)
+				f(i->second,std::binary_search(rootPeerPtrs.begin(),rootPeerPtrs.end(),(uintptr_t)i->second.ptr()));
 		} catch ( ... ) {} // should not throw
 		} catch ( ... ) {} // should not throw
 	}
 	}
 
 
@@ -225,11 +219,8 @@ public:
 	ZT_INLINE void eachPath(F f) const
 	ZT_INLINE void eachPath(F f) const
 	{
 	{
 		RWMutex::RLock l(_paths_l);
 		RWMutex::RLock l(_paths_l);
-		Hashtable< uint64_t,SharedPtr<Path> >::Iterator i(const_cast<Topology *>(this)->_paths);
-		uint64_t *k = nullptr;
-		SharedPtr<Path> *p = nullptr;
-		while (i.next(k,p))
-			f(*((const SharedPtr<Path> *)p));
+		for(FlatMap< uint64_t,SharedPtr<Path> >::const_iterator i(_paths.begin());i!=_paths.end();++i)
+			f(i->second);
 	}
 	}
 
 
 	/**
 	/**
@@ -363,10 +354,10 @@ private:
 	std::pair< InetAddress,ZT_PhysicalPathConfiguration > _physicalPathConfig[ZT_MAX_CONFIGURABLE_PATHS];
 	std::pair< InetAddress,ZT_PhysicalPathConfiguration > _physicalPathConfig[ZT_MAX_CONFIGURABLE_PATHS];
 	unsigned int _numConfiguredPhysicalPaths;
 	unsigned int _numConfiguredPhysicalPaths;
 
 
-	Hashtable< Address,SharedPtr<Peer> > _peers;
-	Hashtable< uint64_t,SharedPtr<Peer> > _peersByIncomingProbe;
-	Hashtable< Fingerprint,SharedPtr<Peer> > _peersByIdentityHash;
-	Hashtable< uint64_t,SharedPtr<Path> > _paths;
+	FlatMap< Address,SharedPtr<Peer> > _peers;
+	FlatMap< uint64_t,SharedPtr<Peer> > _peersByIncomingProbe;
+	FlatMap< Fingerprint,SharedPtr<Peer> > _peersByIdentityHash;
+	FlatMap< uint64_t,SharedPtr<Path> > _paths;
 	std::set< Identity > _roots; // locked by _peers_l
 	std::set< Identity > _roots; // locked by _peers_l
 	std::vector< SharedPtr<Peer> > _rootPeers; // locked by _peers_l
 	std::vector< SharedPtr<Peer> > _rootPeers; // locked by _peers_l
 };
 };

+ 14 - 0
node/Utils.hpp

@@ -263,6 +263,20 @@ static ZT_INLINE uint64_t hash64(uint64_t x) noexcept
 	return x;
 	return x;
 }
 }
 
 
+/**
+ * Mix bits in a 32-bit integer
+ *
+ * @param x Integer to mix
+ * @return Hashed value
+ */
+static ZT_INLINE uint32_t hash32(uint32_t x) noexcept
+{
+	x = ((x >> 16U) ^ x) * 0x45d9f3b;
+	x = ((x >> 16U) ^ x) * 0x45d9f3b;
+	x = (x >> 16U) ^ x;
+	return x;
+}
+
 /**
 /**
  * Check if a buffer's contents are all zero
  * Check if a buffer's contents are all zero
  */
  */

+ 5 - 8
node/VL1.cpp

@@ -440,14 +440,11 @@ void VL1::_sendPendingWhois(void *const tPtr,const int64_t now)
 	std::vector<Address> toSend;
 	std::vector<Address> toSend;
 	{
 	{
 		Mutex::Lock wl(_whoisQueue_l);
 		Mutex::Lock wl(_whoisQueue_l);
-		Hashtable<Address,_WhoisQueueItem>::Iterator wi(_whoisQueue);
-		Address *a = nullptr;
-		_WhoisQueueItem *wq = nullptr;
-		while (wi.next(a,wq)) {
-			if ((now - wq->lastRetry) >= ZT_WHOIS_RETRY_DELAY) {
-				wq->lastRetry = now;
-				++wq->retries;
-				toSend.push_back(*a);
+		for(std::map<Address,_WhoisQueueItem>::iterator wi(_whoisQueue.begin());wi!=_whoisQueue.end();++wi) {
+			if ((now - wi->second.lastRetry) >= ZT_WHOIS_RETRY_DELAY) {
+				wi->second.lastRetry = now;
+				++wi->second.retries;
+				toSend.push_back(wi->first);
 			}
 			}
 		}
 		}
 	}
 	}

+ 2 - 2
node/VL1.hpp

@@ -19,10 +19,10 @@
 #include "Buf.hpp"
 #include "Buf.hpp"
 #include "Address.hpp"
 #include "Address.hpp"
 #include "Protocol.hpp"
 #include "Protocol.hpp"
-#include "Hashtable.hpp"
 #include "Mutex.hpp"
 #include "Mutex.hpp"
 #include "FCV.hpp"
 #include "FCV.hpp"
 
 
+#include <map>
 #include <vector>
 #include <vector>
 
 
 namespace ZeroTier {
 namespace ZeroTier {
@@ -89,7 +89,7 @@ private:
 
 
 	Defragmenter<ZT_MAX_PACKET_FRAGMENTS> _inputPacketAssembler;
 	Defragmenter<ZT_MAX_PACKET_FRAGMENTS> _inputPacketAssembler;
 
 
-	Hashtable<Address,_WhoisQueueItem> _whoisQueue;
+	std::map<Address,_WhoisQueueItem> _whoisQueue;
 	Mutex _whoisQueue_l;
 	Mutex _whoisQueue_l;
 };
 };
 
 

+ 0 - 1
node/VL2.hpp

@@ -18,7 +18,6 @@
 #include "Buf.hpp"
 #include "Buf.hpp"
 #include "Address.hpp"
 #include "Address.hpp"
 #include "Protocol.hpp"
 #include "Protocol.hpp"
-#include "Hashtable.hpp"
 #include "Mutex.hpp"
 #include "Mutex.hpp"
 #include "FCV.hpp"
 #include "FCV.hpp"