Forráskód Böngészése

A bunch of cleanup, bug fix, just use unordered_map, etc.

Adam Ierymenko 5 éve
szülő
commit
9428fc53f6

+ 1 - 1
controller/EmbeddedNetworkController.cpp

@@ -1370,7 +1370,7 @@ void EmbeddedNetworkController::_request(
 								++caprc;
 						}
 					}
-					nc->capabilities[nc->capabilityCount] = Capability((uint32_t)capId,nwid,now,1,capr,caprc);
+					nc->capabilities[nc->capabilityCount] = Capability((uint32_t)capId,nwid,now,capr,caprc);
 					if (nc->capabilities[nc->capabilityCount].sign(_signingId,identity.address()))
 						++nc->capabilityCount;
 					if (nc->capabilityCount >= ZT_MAX_NETWORK_CAPABILITIES)

+ 1 - 1
node/AES.cpp

@@ -51,7 +51,7 @@ ZT_INLINE void s_bmul64(const uint64_t x,const uint64_t y,uint64_t &r_high,uint6
 	r |= z & m4;
 	z = (x1 * y5) ^ (x2 * y4) ^ (x3 * y3) ^ (x4 * y2) ^ (x5 * y1);
 	r |= z & m5;
-	r_high = (uint64_t)(r >> 64);
+	r_high = (uint64_t)(r >> 64U);
 	r_low = (uint64_t)r;
 }
 

+ 99 - 1
node/AES.hpp

@@ -130,6 +130,7 @@ public:
 	}
 
 	class GMACSIVEncryptor;
+	class GMACSIVDecryptor;
 
 	/**
 	 * Streaming GMAC calculator
@@ -137,6 +138,7 @@ public:
 	class GMAC
 	{
 		friend class GMACSIVEncryptor;
+		friend class GMACSIVDecryptor;
 
 	public:
 		/**
@@ -209,6 +211,7 @@ public:
 	class CTR
 	{
 		friend class GMACSIVEncryptor;
+		friend class GMACSIVDecryptor;
 
 	public:
 		ZT_INLINE CTR(const AES &aes) noexcept : _aes(aes) {}
@@ -250,7 +253,7 @@ public:
 	};
 
 	/**
-	 * Encrypt with AES-GMAC-SIV
+	 * Encryptor for GMAC-SIV
 	 */
 	class GMACSIVEncryptor
 	{
@@ -374,6 +377,101 @@ public:
 		AES::CTR _ctr;
 	};
 
+	/**
+	 * Decryptor for GMAC-SIV
+	 */
+	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
+		{
+			// Init CTR with the most significant 96 bits of the tag (as in encryption).
+			uint64_t tmp[2];
+			tmp[0] = tag[0];
+#if __BYTE_ORDER == __BIG_ENDIAN
+			tmp[1] = tag[1] & 0xffffffff00000000ULL;
+#else
+			tmp[1] = tag[1] & 0x00000000ffffffffULL;
+#endif
+			_ctr.init(reinterpret_cast<const uint8_t *>(tmp),output);
+
+			// Decrypt the opaque tag to yield the original IV and 64-bit truncated MAC.
+			_ctr._aes.decrypt(tag,_ivMac);
+
+			// Initialize GMAC with the original IV.
+			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
+		{
+			// Flush any remaining bytes from CTR.
+			_ctr.finish();
+
+			// Feed plaintext through GMAC.
+			_gmac.update(_output,_decryptedLen);
+			uint64_t gmacTag[2];
+			_gmac.finish(reinterpret_cast<uint8_t *>(gmacTag));
+
+			// MAC passes if its first 64 bits equals the MAC we got by decrypting the tag.
+			return gmacTag[0] == _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 Te1[256];

+ 5 - 5
node/Buf.hpp

@@ -329,7 +329,7 @@ public:
 	template<typename T>
 	ZT_INLINE int rO(int &ii,T &obj) const noexcept
 	{
-		if (ii < ZT_BUF_MEM_SIZE) {
+		if (likely(ii < ZT_BUF_MEM_SIZE)) {
 			int ms = obj.unmarshal(unsafeData + ii,ZT_BUF_MEM_SIZE - ii);
 			if (ms > 0)
 				ii += ms;
@@ -353,7 +353,7 @@ public:
 	{
 		const char *const s = (const char *)(unsafeData + ii);
 		const int sii = ii;
-		while (ii < ZT_BUF_MEM_SIZE) {
+		while (likely(ii < ZT_BUF_MEM_SIZE)) {
 			if (unsafeData[ii++] == 0) {
 				Utils::copy(buf,s,ii - sii);
 				return buf;
@@ -398,7 +398,7 @@ public:
 	 */
 	ZT_INLINE uint8_t *rB(int &ii,void *const bytes,const unsigned int len) const noexcept
 	{
-		if ((ii += (int)len) <= ZT_BUF_MEM_SIZE) {
+		if (likely(((ii += (int)len) <= ZT_BUF_MEM_SIZE))) {
 			Utils::copy(bytes,unsafeData + ii,len);
 			return reinterpret_cast<uint8_t *>(bytes);
 		}
@@ -586,7 +586,7 @@ public:
 	ZT_INLINE void wO(int &ii,T &t) noexcept
 	{
 		const int s = ii;
-		if ((s + T::marshalSizeMax()) <= ZT_BUF_MEM_SIZE) {
+		if (likely((s + T::marshalSizeMax()) <= ZT_BUF_MEM_SIZE)) {
 			int ms = t.marshal(unsafeData + s);
 			if (ms > 0)
 				ii += ms;
@@ -624,7 +624,7 @@ public:
 	ZT_INLINE void wB(int &ii,const void *const bytes,const unsigned int len) noexcept
 	{
 		const int s = ii;
-		if ((ii += (int)len) <= ZT_BUF_MEM_SIZE)
+		if (likely((ii += (int)len) <= ZT_BUF_MEM_SIZE))
 			Utils::copy(unsafeData + s,bytes,len);
 	}
 

+ 14 - 10
node/C25519.cpp

@@ -2474,24 +2474,28 @@ void C25519::_calcPubDH(uint8_t pub[ZT_C25519_PUBLIC_KEY_LEN],const uint8_t priv
 
 void C25519::_calcPubED(uint8_t pub[ZT_C25519_PUBLIC_KEY_LEN],const uint8_t priv[ZT_C25519_PRIVATE_KEY_LEN])
 {
-	unsigned char extsk[64];
-	sc25519 scsk;
-	ge25519 gepk;
+	struct {
+		uint8_t extsk[64];
+		sc25519 scsk;
+		ge25519 gepk;
+	} s;
 
 	// Second 32 bytes of pub and priv are the keys for ed25519
 	// signing and verification.
-	SHA512(extsk,priv + 32,32);
-	extsk[0] &= 248;
-	extsk[31] &= 127;
-	extsk[31] |= 64;
-	sc25519_from32bytes(&scsk,extsk);
-	ge25519_scalarmult_base(&gepk,&scsk);
-	ge25519_pack(pub + 32,&gepk);
+	SHA512(s.extsk,priv + 32,32);
+	s.extsk[0] &= 248U;
+	s.extsk[31] &= 127U;
+	s.extsk[31] |= 64U;
+	sc25519_from32bytes(&s.scsk,s.extsk);
+	ge25519_scalarmult_base(&s.gepk,&s.scsk);
+	ge25519_pack(pub + 32,&s.gepk);
 
 	// In NaCl, the public key is crammed into the next 32 bytes
 	// of the private key for signing since both keys are required
 	// to sign. In this version we just get it from kp.pub, so we
 	// leave that out of private.
+
+	Utils::burn(&s,sizeof(s));
 }
 
 } // namespace ZeroTier

+ 1 - 1
node/CMakeLists.txt

@@ -17,12 +17,12 @@ set(core_headers
 	Expect.hpp
 	FCV.hpp
 	Fingerprint.hpp
-	FlatMap.hpp
 	Identity.hpp
 	InetAddress.hpp
 	Locator.hpp
 	LZ4.hpp
 	MAC.hpp
+	Map.hpp
 	Membership.hpp
 	MulticastGroup.hpp
 	Mutex.hpp

+ 17 - 3
node/Defragmenter.hpp

@@ -20,7 +20,7 @@
 #include "Mutex.hpp"
 #include "Path.hpp"
 #include "FCV.hpp"
-#include "FlatMap.hpp"
+#include "Map.hpp"
 
 #include <cstring>
 #include <cstdlib>
@@ -164,7 +164,7 @@ public:
 				std::vector<std::pair<int64_t,uint64_t> > messagesByLastUsedTime;
 				messagesByLastUsedTime.reserve(_messages.size());
 
-				for(typename FlatMap< uint64_t,_E >::const_iterator i(_messages.begin());i!=_messages.end();++i)
+				for(typename Map< 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());
 
@@ -286,6 +286,7 @@ public:
 	}
 
 private:
+	// _E is an entry in the message queue.
 	struct _E
 	{
 		ZT_INLINE _E() noexcept :
@@ -312,6 +313,19 @@ private:
 			}
 		}
 
+		ZT_INLINE _E &operator=(const _E &e)
+		{
+			if (this != &e) {
+				id = e.id;
+				lastUsed = e.lastUsed;
+				totalFragmentsExpected = e.totalFragmentsExpected;
+				fragmentsReceived = e.fragmentsReceived;
+				via = e.via;
+				message = e.message;
+			}
+			return *this;
+		}
+
 		uint64_t id;
 		volatile int64_t lastUsed;
 		unsigned int totalFragmentsExpected;
@@ -321,7 +335,7 @@ private:
 		Mutex lock;
 	};
 
-	FlatMap< uint64_t,_E > _messages;
+	Map< uint64_t,_E > _messages;
 	RWMutex _messages_l;
 };
 

+ 2 - 17
node/Dictionary.cpp

@@ -15,19 +15,6 @@
 
 namespace ZeroTier {
 
-static uint64_t _toKey(const char *k)
-{
-	uint64_t n = 0;
-	unsigned int i = 0;
-	for(;;) {
-		char c = k[i];
-		if (!c) break;
-		reinterpret_cast<uint8_t *>(&n)[i & 7U] ^= (uint8_t)c;
-		++i;
-	}
-	return n;
-}
-
 Dictionary::Dictionary()
 {
 }
@@ -80,7 +67,7 @@ void Dictionary::add(const char *k,uint64_t v)
 void Dictionary::add(const char *k,const Address &v)
 {
 	std::vector<uint8_t> &e = (*this)[k];
-	e.resize(11);
+	e.resize(ZT_ADDRESS_STRING_SIZE_MAX);
 	v.toString((char *)e.data());
 }
 
@@ -109,7 +96,6 @@ void Dictionary::add(const char *k,const void *data,unsigned int len)
 
 bool Dictionary::getB(const char *k,bool dfl) const
 {
-	bool v = dfl;
 	const std::vector<uint8_t> &e = (*this)[k];
 	if (!e.empty()) {
 		switch ((char)e[0]) {
@@ -123,7 +109,7 @@ bool Dictionary::getB(const char *k,bool dfl) const
 				return false;
 		}
 	}
-	return v;
+	return dfl;
 }
 
 uint64_t Dictionary::getUI(const char *k,uint64_t dfl) const
@@ -173,7 +159,6 @@ void Dictionary::encode(std::vector<uint8_t> &out) const
 	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;
-
 		for(;;) {
 			char kc = *(k++);
 			if (!kc) break;

+ 13 - 1
node/Dictionary.hpp

@@ -30,7 +30,7 @@ namespace ZeroTier {
  *
  * This data structure is used for network configurations, node meta-data,
  * and other open-definition protocol objects. It consists of a key-value
- * store with short (under 8 characters) keys that map to strings, blobs,
+ * store with short (max: 8 characters) keys that map to strings, blobs,
  * or integers with the latter being by convention in hex format.
  *
  * If this seems a little odd, it is. It dates back to the very first alpha
@@ -168,6 +168,18 @@ public:
 	bool decode(const void *data,unsigned int len);
 
 private:
+	// This just packs up to 8 character bytes into a 64-bit word. There is no need
+	// for this to be portable in terms of endian-ness. It's just for fast key lookup.
+	static ZT_INLINE uint64_t _toKey(const char *k)
+	{
+		uint64_t key = 0;
+		for(int i=0;i<8;++i) {
+			if ((reinterpret_cast<uint8_t *>(&key)[i] = *(k++)) == 0)
+				break;
+		}
+		return key;
+	}
+
 	std::map< uint64_t,std::vector<uint8_t> > _t;
 };
 

+ 1 - 1
node/Expect.hpp

@@ -80,7 +80,7 @@ private:
 	const uint64_t _salt;
 
 	// Each bucket contains a timestamp in units of the expect duration.
-	std::atomic<int32_t> _packetIdSent[ZT_EXPECT_TTL];
+	std::atomic<int32_t> _packetIdSent[ZT_EXPECT_BUCKETS];
 };
 
 } // namespace ZeroTier

+ 0 - 245
node/FlatMap.hpp

@@ -1,245 +0,0 @@
-/*
- * 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

+ 1 - 10
node/Identity.cpp

@@ -85,16 +85,9 @@ struct identityV0ProofOfWorkCriteria
 bool identityV1ProofOfWorkCriteria(const void *in,const unsigned int len)
 {
 	uint64_t b[98304]; // 768 KiB of working memory
-	uint64_t polykey[4];
 
 	SHA512(b,in,len);
 
-	// Poly1305 key, used in final hash at the end.
-	polykey[0] = b[0];
-	polykey[1] = b[1];
-	polykey[2] = b[2];
-	polykey[3] = b[3];
-
 #if __BYTE_ORDER == __BIG_ENDIAN
 	b[0] = Utils::swapBytes(b[0]);
 	b[1] = Utils::swapBytes(b[1]);
@@ -152,9 +145,7 @@ bool identityV1ProofOfWorkCriteria(const void *in,const unsigned int len)
 	}
 #endif
 
-	// Use poly1305 to compute a very fast digest of 'b'. This doesn't have to be
-	// cryptographic per se, just have good hashing properties.
-	poly1305(b,b,sizeof(b),polykey);
+	SHA384(b,b,sizeof(b),in,len);
 
 	// Criterion: add two 64-bit components of poly1305 hash, must be zero mod 180.
 	// As with the rest of this bits are used in little-endian byte order. The value

+ 94 - 0
node/Map.hpp

@@ -0,0 +1,94 @@
+/*
+ * 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_MAP_HPP
+#define ZT_MAP_HPP
+
+#include "Constants.hpp"
+#include "Utils.hpp"
+
+#ifdef __CPP11__
+#include <unordered_map>
+#else
+#include <map>
+#endif
+
+namespace ZeroTier {
+
+#ifdef __CPP11__
+struct _MapHasher
+{
+	template<typename O>
+	std::size_t operator()(const O &obj) const noexcept { return (std::size_t)obj.hashCode() ^ (std::size_t)Utils::s_mapNonce; }
+
+	std::size_t operator()(const uint64_t i) const noexcept { return (std::size_t)Utils::hash64(i ^ Utils::s_mapNonce); }
+	std::size_t operator()(const int64_t i) const noexcept { return (std::size_t)Utils::hash64((uint64_t)i ^ Utils::s_mapNonce); }
+	std::size_t operator()(const uint32_t i) const noexcept { return (std::size_t)Utils::hash32(i ^ (uint32_t)Utils::s_mapNonce); }
+	std::size_t operator()(const int32_t i) const noexcept { return (std::size_t)Utils::hash32((uint32_t)i ^ (uint32_t)Utils::s_mapNonce); }
+};
+template<typename K,typename V>
+class Map : public std::unordered_map<K,V,_MapHasher>
+{
+public:
+	ZT_INLINE V *get(const K &key) noexcept
+	{
+		typename Map::iterator i(this->find(key));
+		if (i == this->end())
+			return nullptr;
+		return &(i->second);
+	}
+
+	ZT_INLINE const V *get(const K &key) const noexcept
+	{
+		typename Map::const_iterator i(this->find(key));
+		if (i == this->end())
+			return nullptr;
+		return &(i->second);
+	}
+
+	ZT_INLINE void set(const K &key,const V &value)
+	{
+		this->emplace(key,value);
+	}
+};
+#else
+template<typename K,typename V>
+class Map : public std::map<K,V>
+{
+public:
+	ZT_INLINE V *get(const K &key) noexcept
+	{
+		typename Map::iterator i(this->find(key));
+		if (i == this->end())
+			return nullptr;
+		return &(i->second);
+	}
+
+	ZT_INLINE const V *get(const K &key) const noexcept
+	{
+		typename Map::const_iterator i(this->find(key));
+		if (i == this->end())
+			return nullptr;
+		return &(i->second);
+	}
+
+	ZT_INLINE void set(const K &key,const V &value)
+	{
+		this->emplace(key,value);
+	}
+};
+#endif
+
+} // ZeroTier
+
+#endif

+ 2 - 2
node/Membership.cpp

@@ -159,8 +159,8 @@ Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironme
 // 3/5 of the credential types have identical addCredential() code
 template<typename C>
 static ZT_INLINE Membership::AddCredentialResult _addCredImpl(
-	FlatMap<uint32_t,C> &remoteCreds,
-	const FlatMap<uint64_t,int64_t> &revocations,
+	Map<uint32_t,C> &remoteCreds,
+	const Map<uint64_t,int64_t> &revocations,
 	const RuntimeEnvironment *const RR,
 	void *const tPtr,
 	const Identity &sourcePeerIdentity,

+ 10 - 10
node/Membership.hpp

@@ -18,7 +18,7 @@
 
 #include "Constants.hpp"
 #include "Credential.hpp"
-#include "FlatMap.hpp"
+#include "Map.hpp"
 #include "CertificateOfMembership.hpp"
 #include "Capability.hpp"
 #include "Tag.hpp"
@@ -106,7 +106,7 @@ public:
 	{
 		if (_isUnspoofableAddress(nconf,r))
 			return true;
-		for(FlatMap< uint32_t,CertificateOfOwnership >::const_iterator i(_remoteCoos.begin());i!=_remoteCoos.end();++i) {
+		for(Map< uint32_t,CertificateOfOwnership >::const_iterator i(_remoteCoos.begin());i!=_remoteCoos.end();++i) {
 			if (_isCredentialTimestampValid(nconf,i->second)&&(i->second.owns(r)))
 				return true;
 		}
@@ -176,9 +176,9 @@ private:
 	}
 
 	template<typename C>
-	ZT_INLINE void _cleanCredImpl(const NetworkConfig &nconf,FlatMap<uint32_t,C> &remoteCreds)
+	ZT_INLINE void _cleanCredImpl(const NetworkConfig &nconf,Map<uint32_t,C> &remoteCreds)
 	{
-		for(typename FlatMap<uint32_t,C>::iterator i(remoteCreds.begin());i!=remoteCreds.end();) {
+		for(typename Map<uint32_t,C>::iterator i(remoteCreds.begin());i!=remoteCreds.end();) {
 			if (!_isCredentialTimestampValid(nconf,i->second))
 				remoteCreds.erase(i++);
 			else ++i;
@@ -198,12 +198,12 @@ private:
 	CertificateOfMembership _com;
 
 	// Revocations by credentialKey()
-	FlatMap< uint64_t,int64_t > _revocations;
+	Map< uint64_t,int64_t > _revocations;
 
 	// Remote credentials that we have received from this member (and that are valid)
-	FlatMap< uint32_t,Tag > _remoteTags;
-	FlatMap< uint32_t,Capability > _remoteCaps;
-	FlatMap< uint32_t,CertificateOfOwnership > _remoteCoos;
+	Map< uint32_t,Tag > _remoteTags;
+	Map< uint32_t,Capability > _remoteCaps;
+	Map< uint32_t,CertificateOfOwnership > _remoteCoos;
 
 public:
 	class CapabilityIterator
@@ -219,7 +219,7 @@ public:
 		ZT_INLINE Capability *next() noexcept
 		{
 			while (_hti != _m._remoteCaps.end()) {
-				FlatMap< uint32_t,Capability >::iterator i(_hti++);
+				Map< uint32_t,Capability >::iterator i(_hti++);
 				if (_m._isCredentialTimestampValid(_nconf,i->second))
 					return &(i->second);
 			}
@@ -227,7 +227,7 @@ public:
 		}
 
 	private:
-		FlatMap< uint32_t,Capability >::iterator _hti;
+		Map< uint32_t,Capability >::iterator _hti;
 		Membership &_m;
 		const NetworkConfig &_nconf;
 	};

+ 5 - 5
node/Network.cpp

@@ -1099,7 +1099,7 @@ void Network::doPeriodicTasks(void *tPtr,const int64_t now)
 	{
 		Mutex::Lock l1(_memberships_l);
 
-		for(FlatMap<Address,Membership>::iterator i(_memberships.begin());i!=_memberships.end();++i)
+		for(Map<Address,Membership>::iterator i(_memberships.begin());i!=_memberships.end();++i)
 			i->second.clean(now,_config);
 
 		{
@@ -1128,12 +1128,12 @@ 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
 	while (_remoteBridgeRoutes.size() > ZT_MAX_BRIDGE_ROUTES) {
-		FlatMap< Address,unsigned long > counts;
+		Map< Address,unsigned long > counts;
 		Address maxAddr;
 		unsigned long maxCount = 0;
 
 		// Find the address responsible for the most entries
-		for(FlatMap<MAC,Address>::iterator i(_remoteBridgeRoutes.begin());i!=_remoteBridgeRoutes.end();++i) {
+		for(Map<MAC,Address>::iterator i(_remoteBridgeRoutes.begin());i!=_remoteBridgeRoutes.end();++i) {
 			const unsigned long c = ++counts[i->second];
 			if (c > maxCount) {
 				maxCount = c;
@@ -1142,7 +1142,7 @@ void Network::learnBridgeRoute(const MAC &mac,const Address &addr)
 		}
 
 		// Kill this address from our table, since it's most likely spamming us
-		for(FlatMap<MAC,Address>::iterator i(_remoteBridgeRoutes.begin());i!=_remoteBridgeRoutes.end();) {
+		for(Map<MAC,Address>::iterator i(_remoteBridgeRoutes.begin());i!=_remoteBridgeRoutes.end();) {
 			if (i->second == maxAddr)
 				_remoteBridgeRoutes.erase(i++);
 			else ++i;
@@ -1529,7 +1529,7 @@ std::vector<MulticastGroup> Network::_allMulticastGroups() const
 	std::vector<MulticastGroup> mgs;
 	mgs.reserve(_myMulticastGroups.size() + _multicastGroupsBehindMe.size() + 1);
 	mgs.insert(mgs.end(),_myMulticastGroups.begin(),_myMulticastGroups.end());
-	for(FlatMap< MulticastGroup,uint64_t >::const_iterator i(_multicastGroupsBehindMe.begin());i!=_multicastGroupsBehindMe.end();++i)
+	for(Map< MulticastGroup,uint64_t >::const_iterator i(_multicastGroupsBehindMe.begin());i!=_multicastGroupsBehindMe.end();++i)
 		mgs.push_back(i->first);
 	if ((_config)&&(_config.enableBroadcast()))
 		mgs.push_back(Network::BROADCAST);

+ 5 - 5
node/Network.hpp

@@ -25,7 +25,7 @@
 #include "Membership.hpp"
 #include "NetworkConfig.hpp"
 #include "CertificateOfMembership.hpp"
-#include "FlatMap.hpp"
+#include "Map.hpp"
 
 #include <cstdint>
 #include <string>
@@ -326,7 +326,7 @@ public:
 	ZT_INLINE void eachMember(F f)
 	{
 		Mutex::Lock ml(_memberships_l);
-		for(FlatMap<Address,Membership>::iterator i(_memberships.begin());i!=_memberships.end();++i) {
+		for(Map<Address,Membership>::iterator i(_memberships.begin());i!=_memberships.end();++i) {
 			if (!f(i->first,i->second))
 				break;
 		}
@@ -353,8 +353,8 @@ private:
 	bool _portInitialized;
 
 	std::vector< MulticastGroup > _myMulticastGroups; // multicast groups that we belong to (according to tap)
-	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)
+	Map< MulticastGroup,uint64_t > _multicastGroupsBehindMe; // multicast groups that seem to be behind us and when we last saw them (if we are a bridge)
+	Map< MAC,Address > _remoteBridgeRoutes; // remote addresses where given MACs are reachable (for tracking devices behind remote bridges)
 
 	NetworkConfig _config;
 	std::atomic<int64_t> _lastConfigUpdate;
@@ -377,7 +377,7 @@ private:
 		NETCONF_FAILURE_INIT_FAILED
 	} _netconfFailure;
 
-	FlatMap<Address,Membership> _memberships;
+	Map<Address,Membership> _memberships;
 
 	Mutex _myMulticastGroups_l;
 	Mutex _remoteBridgeRoutes_l;

+ 5 - 5
node/Node.cpp

@@ -268,7 +268,7 @@ ZT_ResultCode Node::processBackgroundTasks(void *tPtr, int64_t now, volatile int
 		_lastHousekeepingRun = now;
 		{
 			RWMutex::RLock l(_networks_m);
-			for(FlatMap< uint64_t,SharedPtr<Network> >::const_iterator i(_networks.begin());i!=_networks.end();++i)
+			for(Map< uint64_t,SharedPtr<Network> >::const_iterator i(_networks.begin());i!=_networks.end();++i)
 				i->second->doPeriodicTasks(tPtr,now);
 		}
 	}
@@ -281,7 +281,7 @@ ZT_ResultCode Node::processBackgroundTasks(void *tPtr, int64_t now, volatile int
 			// or trust nodes without doing an extra cert check.
 			{
 				_localControllerAuthorizations_m.lock();
-				for(FlatMap<_LocalControllerAuth,int64_t>::iterator i(_localControllerAuthorizations.begin());i!=_localControllerAuthorizations.end();) {
+				for(Map<_LocalControllerAuth,int64_t>::iterator i(_localControllerAuthorizations.begin());i!=_localControllerAuthorizations.end();) {
 					if ((i->second - now) > (ZT_NETWORK_AUTOCONF_DELAY * 3))
 						_localControllerAuthorizations.erase(i++);
 					else ++i;
@@ -353,7 +353,7 @@ ZT_ResultCode Node::leave(uint64_t nwid,void **uptr,void *tptr)
 	ZT_VirtualNetworkConfig ctmp;
 
 	_networks_m.lock();
-	FlatMap< uint64_t,SharedPtr<Network> >::iterator nwi(_networks.find(nwid));
+	Map< uint64_t,SharedPtr<Network> >::iterator nwi(_networks.find(nwid));
 	if (nwi == _networks.end()) {
 		_networks_m.unlock();
 		return ZT_RESULT_OK;
@@ -507,7 +507,7 @@ ZT_VirtualNetworkList *Node::networks() const
 	nl->networks = (ZT_VirtualNetworkConfig *)(buf + sizeof(ZT_VirtualNetworkList));
 
 	nl->networkCount = 0;
-	for(FlatMap< uint64_t,SharedPtr<Network> >::const_iterator i(_networks.begin());i!=_networks.end();++i)
+	for(Map< uint64_t,SharedPtr<Network> >::const_iterator i(_networks.begin());i!=_networks.end();++i)
 		i->second->externalConfig(&(nl->networks[nl->networkCount++]));
 
 	return nl;
@@ -596,7 +596,7 @@ bool Node::shouldUsePathForZeroTierTraffic(void *tPtr,const Identity &id,const i
 {
 	{
 		RWMutex::RLock l(_networks_m);
-		for (FlatMap< uint64_t,SharedPtr<Network> >::iterator i(_networks.begin()); i != _networks.end(); ++i) {
+		for (Map< 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;

+ 4 - 3
node/Node.hpp

@@ -24,7 +24,7 @@
 #include "Salsa20.hpp"
 #include "NetworkController.hpp"
 #include "Buf.hpp"
-#include "FlatMap.hpp"
+#include "Map.hpp"
 
 #include <cstdio>
 #include <cstdlib>
@@ -361,13 +361,14 @@ private:
 		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.nwid == nwid)&&(a.address < address))); }
 	};
-	FlatMap<_LocalControllerAuth,int64_t> _localControllerAuthorizations;
+	Map<_LocalControllerAuth,int64_t> _localControllerAuthorizations;
 	Mutex _localControllerAuthorizations_m;
 
 	// 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.
-	FlatMap< uint64_t,SharedPtr<Network> > _networks;
+	Map< uint64_t,SharedPtr<Network> > _networks;
 	RWMutex _networks_m;
 
 	// These are local interface addresses that have been configured via the API

+ 5 - 5
node/SelfAwareness.cpp

@@ -69,7 +69,7 @@ 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'
 		// due to multiple reports of endpoint change.
 		// Don't use 'entry' after this since hash table gets modified.
-		for(FlatMap<PhySurfaceKey,PhySurfaceEntry>::iterator i(_phy.begin());i!=_phy.end();) {
+		for(Map<PhySurfaceKey,PhySurfaceEntry>::iterator i(_phy.begin());i!=_phy.end();) {
 			if ((i->first.scope == scope)&&(i->first.reporterPhysicalAddress != reporterPhysicalAddress))
 				_phy.erase(i++);
 			else ++i;
@@ -91,7 +91,7 @@ void SelfAwareness::iam(void *tPtr,const Identity &reporter,const int64_t receiv
 void SelfAwareness::clean(int64_t now)
 {
 	Mutex::Lock l(_phy_l);
-	for(FlatMap<PhySurfaceKey,PhySurfaceEntry>::iterator i(_phy.begin());i!=_phy.end();) {
+	for(Map<PhySurfaceKey,PhySurfaceEntry>::iterator i(_phy.begin());i!=_phy.end();) {
 		if ((now - i->second.ts) >= ZT_SELFAWARENESS_ENTRY_TIMEOUT)
 			_phy.erase(i++);
 		else ++i;
@@ -101,17 +101,17 @@ void SelfAwareness::clean(int64_t now)
 std::multimap<unsigned long,InetAddress> SelfAwareness::externalAddresses(const int64_t now) const
 {
 	std::multimap<unsigned long,InetAddress> r;
-	FlatMap<InetAddress,unsigned long,256> counts;
+	Map<InetAddress,unsigned long> counts;
 
 	{
 		Mutex::Lock l(_phy_l);
-		for(FlatMap<PhySurfaceKey,PhySurfaceEntry>::const_iterator i(_phy.begin());i!=_phy.end();++i) {
+		for(Map<PhySurfaceKey,PhySurfaceEntry>::const_iterator i(_phy.begin());i!=_phy.end();++i) {
 			if ((now - i->second.ts) < ZT_SELFAWARENESS_ENTRY_TIMEOUT)
 				++counts[i->second.mySurface];
 		}
 	}
 
-	for(FlatMap<InetAddress,unsigned long,256>::iterator i(counts.begin());i!=counts.end();++i)
+	for(Map<InetAddress,unsigned long>::iterator i(counts.begin());i!=counts.end();++i)
 		r.insert(std::pair<unsigned long,InetAddress>(i->second,i->first));
 
 	return r;

+ 28 - 11
node/SelfAwareness.hpp

@@ -16,7 +16,7 @@
 
 #include "Constants.hpp"
 #include "InetAddress.hpp"
-#include "FlatMap.hpp"
+#include "Map.hpp"
 #include "Address.hpp"
 #include "Mutex.hpp"
 
@@ -72,13 +72,30 @@ private:
 		InetAddress reporterPhysicalAddress;
 		InetAddress::IpScope scope;
 
-		ZT_INLINE PhySurfaceKey() {}
-		ZT_INLINE PhySurfaceKey(const Address &r,const int64_t rol,const InetAddress &ra,InetAddress::IpScope s) : reporter(r),receivedOnLocalSocket(rol),reporterPhysicalAddress(ra),scope(s) {}
-
-		ZT_INLINE unsigned long hashCode() const { return ((unsigned long)reporter.toInt() + (unsigned long)receivedOnLocalSocket + (unsigned long)scope); }
-
-		ZT_INLINE bool operator==(const PhySurfaceKey &k) const { return ((reporter == k.reporter) && (receivedOnLocalSocket == k.receivedOnLocalSocket) && (reporterPhysicalAddress == k.reporterPhysicalAddress) && (scope == k.scope)); }
-		ZT_INLINE bool operator!=(const PhySurfaceKey &k) const { return (!(*this == k)); }
+		ZT_INLINE PhySurfaceKey() noexcept {}
+		ZT_INLINE PhySurfaceKey(const Address &r,const int64_t rol,const InetAddress &ra,InetAddress::IpScope s) noexcept : reporter(r),receivedOnLocalSocket(rol),reporterPhysicalAddress(ra),scope(s) {}
+
+		ZT_INLINE unsigned long hashCode() const noexcept { return ((unsigned long)reporter.toInt() + (unsigned long)receivedOnLocalSocket + (unsigned long)scope); }
+
+		ZT_INLINE bool operator==(const PhySurfaceKey &k) const noexcept { return ((reporter == k.reporter) && (receivedOnLocalSocket == k.receivedOnLocalSocket) && (reporterPhysicalAddress == k.reporterPhysicalAddress) && (scope == k.scope)); }
+		ZT_INLINE bool operator!=(const PhySurfaceKey &k) const noexcept { return (!(*this == k)); }
+		ZT_INLINE bool operator<(const PhySurfaceKey &k) const noexcept
+		{
+			if (reporter < k.reporter) {
+				return true;
+			} else if (reporter == k.reporter) {
+				if (receivedOnLocalSocket < k.receivedOnLocalSocket) {
+					return true;
+				} else if (receivedOnLocalSocket == k.receivedOnLocalSocket) {
+					if (reporterPhysicalAddress < k.reporterPhysicalAddress) {
+						return true;
+					} else if (reporterPhysicalAddress == k.reporterPhysicalAddress) {
+						return scope < k.scope;
+					}
+				}
+			}
+			return false;
+		}
 	};
 
 	struct PhySurfaceEntry
@@ -87,12 +104,12 @@ private:
 		uint64_t ts;
 		bool trusted;
 
-		ZT_INLINE PhySurfaceEntry() : mySurface(),ts(0),trusted(false) {}
-		ZT_INLINE PhySurfaceEntry(const InetAddress &a,const uint64_t t) : mySurface(a),ts(t),trusted(false) {}
+		ZT_INLINE PhySurfaceEntry() noexcept : mySurface(),ts(0),trusted(false) {}
+		ZT_INLINE PhySurfaceEntry(const InetAddress &a,const uint64_t t) noexcept : mySurface(a),ts(t),trusted(false) {}
 	};
 
 	const RuntimeEnvironment *RR;
-	FlatMap< PhySurfaceKey,PhySurfaceEntry > _phy;
+	Map< PhySurfaceKey,PhySurfaceEntry > _phy;
 	Mutex _phy_l;
 };
 

+ 8 - 1
node/Speck128.hpp

@@ -25,6 +25,10 @@ namespace ZeroTier {
  * Speck does not specify a mandatory endian-ness. This implementation is
  * little-endian for higher performance on the majority of platforms.
  *
+ * This is only used as part of the work function for V1 identity generation
+ * and for the built-in secure random source on systems that lack AES
+ * hardware acceleration.
+ *
  * @tparam R Number of rounds (default: 32)
  */
 template<int R = 32>
@@ -96,7 +100,10 @@ public:
 	}
 
 	/**
-	 * Encrypt 512 bits in parallel with the same key
+	 * Encrypt 512 bits in parallel with the same key.
+	 *
+	 * Parallel in this case assumes instruction level parallelism, but even without that
+	 * it may be faster due to cache/memory effects.
 	 */
 	ZT_INLINE void encryptXYXYXYXY(uint64_t &x0,uint64_t &y0,uint64_t &x1,uint64_t &y1,uint64_t &x2,uint64_t &y2,uint64_t &x3,uint64_t &y3) const noexcept
 	{

+ 22 - 15
node/Tests.cpp

@@ -38,7 +38,7 @@
 #include "SHA512.hpp"
 #include "Defragmenter.hpp"
 #include "Fingerprint.hpp"
-#include "FlatMap.hpp"
+#include "Map.hpp"
 
 #include <cstdint>
 #include <cstring>
@@ -176,7 +176,7 @@ static const C25519TestVector C25519_TEST_VECTORS[ZT_NUM_C25519_TEST_VECTORS] =
 };
 
 #define IDENTITY_V0_KNOWN_GOOD_0 "8e4df28b72:0:ac3d46abe0c21f3cfe7a6c8d6a85cfcffcb82fbd55af6a4d6350657c68200843fa2e16f9418bbd9702cae365f2af5fb4c420908b803a681d4daef6114d78a2d7:bd8dd6e4ce7022d2f812797a80c6ee8ad180dc4ebf301dec8b06d1be08832bddd63a2f1cfa7b2c504474c75bdc8898ba476ef92e8e2d0509f8441985171ff16e"
-#define IDENTITY_V1_KNOWN_GOOD_0 "8013cb9738:1:avsyitc474r2ylspwjkjvlermiiouaxy3bzzlqtcvafo2gv3abpeyov2zjfyx4vbkxghcoidpjidszt2ibsn4nmmvtpj42clzsxkyt5dajljk2i3x56picaeapayrv5xvd6x6ucgzvei7773xnoj6tyxwcjvqwbhz3joxeb74kdrfq2247hm7ly:54cckqx5fmd54zkmxt6lz2bxn2elws2ju2kv44cqpy7b22n5dlz256iz7j44jc4wyqj4mppyhknbayf2iulu7g4mysgeee5zrtp4zfwqtjnqkn2xpemap5cfm33kldc5kpukg33nqaizcgvekobj6omt3uwro5xboopvgiwdejwmgmpfevja"
+#define IDENTITY_V1_KNOWN_GOOD_0 "2d48f7a238:1:gltupn4yrt226o3vebl7m7m5hpndhvfz66nzx6gwgtgbsgs5xr7dpz5aiv636zijrxayuu2ydpff4zgho7o6gpvx62njwkavqordxcceajs2fif4y2ytofpyr25mmxmanbf4fmdiitiq2b53nmx4ckjcmtyqrkqye2jkdainmkqbtil3dhyuiwa:xg73bkrxptymo7kyyd6efu2o7ziemyu3lpgtip53ejsqukt6l2gebq5uofzt6cd2455st5iwrdgc2ft3twkdzrkunu6x5imdz6jt27qopsvqpdijx5cqgukpjxrtyx73j42socym5pi5hy2ir5yma7by4gmtjgvvu3sxbb3qv2yuicykyz2q"
 
 // --------------------------------------------------------------------------------------------------------------------
 
@@ -466,34 +466,41 @@ extern "C" const char *ZTT_general()
 		}
 
 		{
-			ZT_T_PRINTF("[general] Testing FlatMap... ");
-			FlatMap<uint64_t,uint64_t> tm;
-			for(uint64_t i=0;i<10000;++i)
+			ZT_T_PRINTF("[general] Testing Map... ");
+			Map<uint64_t,uint64_t> tm;
+			for(uint64_t i=0;i<100000;++i)
 				tm.set(i,i);
-			for(uint64_t i=0;i<10000;++i) {
+			for(uint64_t i=0;i<100000;++i) {
 				uint64_t *v = tm.get(i);
 				if ((!v)||(*v != i)) {
 					ZT_T_PRINTF("FAILED (get() failed)" ZT_EOL_S);
-					return "FlatMap::get() failed";
+					return "Map::get() failed";
 				}
 			}
-			for(FlatMap<uint64_t,uint64_t>::iterator i(tm.begin());i!=tm.end();) {
+			for(Map<uint64_t,uint64_t>::iterator i(tm.begin());i!=tm.end();) {
 				if ((i->first & 1U) == 0)
 					tm.erase(i++);
 				else ++i;
 			}
-			if (tm.size() != 5000) {
+			if (tm.size() != 50000) {
 				ZT_T_PRINTF("FAILED (erase() failed (1))" ZT_EOL_S);
-				return "FlatMap::erase() failed (1)";
+				return "Map::erase() failed (1)";
 			}
-			for(uint64_t i=0;i<10000;++i) {
+			for(uint64_t i=0;i<100000;++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)";
+				if ((i & 1U) == 0) {
+					if (v) {
+						ZT_T_PRINTF("FAILED (erase() failed (2))" ZT_EOL_S);
+						return "Map::erase() failed (2)";
+					}
+				} else {
+					if (!v) {
+						ZT_T_PRINTF("FAILED (erase() failed (3))" ZT_EOL_S);
+						return "Map::erase() failed (3)";
+					}
 				}
 			}
-			ZT_T_PRINTF("OK (hash size: %lu)" ZT_EOL_S,tm.hashSize());
+			ZT_T_PRINTF("OK" ZT_EOL_S);
 		}
 
 		{

+ 4 - 4
node/Topology.cpp

@@ -99,7 +99,7 @@ void Topology::getAllPeers(std::vector< SharedPtr<Peer> > &allPeers) const
 	RWMutex::RLock l(_peers_l);
 	allPeers.clear();
 	allPeers.reserve(_peers.size());
-	for(FlatMap< Address,SharedPtr<Peer> >::const_iterator i(_peers.begin());i!=_peers.end();++i)
+	for(Map< Address,SharedPtr<Peer> >::const_iterator i(_peers.begin());i!=_peers.end();++i)
 		allPeers.push_back(i->second);
 }
 
@@ -198,7 +198,7 @@ void Topology::doPeriodicTasks(void *tPtr,const int64_t now)
 {
 	{
 		RWMutex::Lock l1(_peers_l);
-		for(FlatMap< Address,SharedPtr<Peer> >::iterator i(_peers.begin());i!=_peers.end();) {
+		for(Map< 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());
@@ -209,7 +209,7 @@ void Topology::doPeriodicTasks(void *tPtr,const int64_t now)
 	}
 	{
 		RWMutex::Lock l1(_paths_l);
-		for(FlatMap< uint64_t,SharedPtr<Path> >::iterator i(_paths.begin());i!=_paths.end();) {
+		for(Map< 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;
@@ -220,7 +220,7 @@ void Topology::doPeriodicTasks(void *tPtr,const int64_t now)
 void Topology::saveAll(void *tPtr)
 {
 	RWMutex::RLock l(_peers_l);
-	for(FlatMap< Address,SharedPtr<Peer> >::iterator i(_peers.begin());i!=_peers.end();++i)
+	for(Map< Address,SharedPtr<Peer> >::iterator i(_peers.begin());i!=_peers.end();++i)
 		i->second->save(tPtr);
 }
 

+ 13 - 13
node/Topology.hpp

@@ -30,7 +30,7 @@
 #include "SharedPtr.hpp"
 #include "ScopedPtr.hpp"
 #include "Fingerprint.hpp"
-#include "FlatMap.hpp"
+#include "Map.hpp"
 
 namespace ZeroTier {
 
@@ -127,7 +127,7 @@ public:
 	 */
 	ZT_INLINE SharedPtr<Path> path(const int64_t l,const InetAddress &r)
 	{
-		const uint64_t k = _pathHash(l,r);
+		const uint64_t k = _getPathKey(l,r);
 		{
 			RWMutex::RLock lck(_paths_l);
 			SharedPtr<Path> *const p = _paths.get(k);
@@ -179,7 +179,7 @@ public:
 	ZT_INLINE void eachPeer(F f) const
 	{
 		RWMutex::RLock l(_peers_l);
-		for(FlatMap< Address,SharedPtr<Peer> >::const_iterator i(_peers.begin());i!=_peers.end();++i)
+		for(Map< Address,SharedPtr<Peer> >::const_iterator i(_peers.begin());i!=_peers.end();++i)
 			f(i->second);
 	}
 
@@ -204,7 +204,7 @@ public:
 		std::sort(rootPeerPtrs.begin(),rootPeerPtrs.end());
 
 		try {
-			for(FlatMap< Address,SharedPtr<Peer> >::const_iterator i(_peers.begin());i!=_peers.end();++i)
+			for(Map< 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
 	}
@@ -219,7 +219,7 @@ public:
 	ZT_INLINE void eachPath(F f) const
 	{
 		RWMutex::RLock l(_paths_l);
-		for(FlatMap< uint64_t,SharedPtr<Path> >::const_iterator i(_paths.begin());i!=_paths.end();++i)
+		for(Map< uint64_t,SharedPtr<Path> >::const_iterator i(_paths.begin());i!=_paths.end();++i)
 			f(i->second);
 	}
 
@@ -324,11 +324,11 @@ private:
 	// This is a secure random integer created at startup to salt the calculation of path hash map keys
 	static const uint64_t s_pathHashSalt;
 
-	// Get a hash key for looking up paths by their local port and destination address
-	ZT_INLINE uint64_t _pathHash(int64_t l,const InetAddress &r) const
+	// This gets an integer key from an InetAddress for looking up paths.
+	ZT_INLINE uint64_t _getPathKey(int64_t l,const InetAddress &r) const
 	{
 		if (r.family() == AF_INET) {
-			return Utils::hash64(s_pathHashSalt ^ (uint64_t)(reinterpret_cast<const struct sockaddr_in *>(&r)->sin_addr.s_addr)) + (uint64_t)Utils::ntoh(reinterpret_cast<const struct sockaddr_in *>(&r)->sin_port) + (uint64_t)l;
+			return s_pathHashSalt + (uint64_t)(reinterpret_cast<const struct sockaddr_in *>(&r)->sin_addr.s_addr) + (uint64_t)Utils::ntoh(reinterpret_cast<const struct sockaddr_in *>(&r)->sin_port) + (uint64_t)l;
 		} else if (r.family() == AF_INET6) {
 #ifdef ZT_NO_UNALIGNED_ACCESS
 			uint64_t h = s_pathHashSalt;
@@ -338,7 +338,7 @@ private:
 				h ^= (h >> 6U);
 			}
 #else
-			uint64_t h = Utils::hash64(s_pathHashSalt ^ (reinterpret_cast<const uint64_t *>(reinterpret_cast<const struct sockaddr_in6 *>(&r)->sin6_addr.s6_addr)[0] + reinterpret_cast<const uint64_t *>(reinterpret_cast<const struct sockaddr_in6 *>(&r)->sin6_addr.s6_addr)[1]));
+			uint64_t h = s_pathHashSalt + (reinterpret_cast<const uint64_t *>(reinterpret_cast<const struct sockaddr_in6 *>(&r)->sin6_addr.s6_addr)[0] + reinterpret_cast<const uint64_t *>(reinterpret_cast<const struct sockaddr_in6 *>(&r)->sin6_addr.s6_addr)[1]);
 #endif
 			return h + (uint64_t)Utils::ntoh(reinterpret_cast<const struct sockaddr_in6 *>(&r)->sin6_port) + (uint64_t)l;
 		} else {
@@ -354,10 +354,10 @@ private:
 	std::pair< InetAddress,ZT_PhysicalPathConfiguration > _physicalPathConfig[ZT_MAX_CONFIGURABLE_PATHS];
 	unsigned int _numConfiguredPhysicalPaths;
 
-	FlatMap< Address,SharedPtr<Peer> > _peers;
-	FlatMap< uint64_t,SharedPtr<Peer> > _peersByIncomingProbe;
-	FlatMap< Fingerprint,SharedPtr<Peer> > _peersByIdentityHash;
-	FlatMap< uint64_t,SharedPtr<Path> > _paths;
+	Map< Address,SharedPtr<Peer> > _peers;
+	Map< uint64_t,SharedPtr<Peer> > _peersByIncomingProbe;
+	Map< Fingerprint,SharedPtr<Peer> > _peersByIdentityHash;
+	Map< uint64_t,SharedPtr<Path> > _paths;
 	std::set< Identity > _roots; // locked by _peers_l
 	std::vector< SharedPtr<Peer> > _rootPeers; // locked by _peers_l
 };

+ 56 - 48
node/Utils.cpp

@@ -19,6 +19,7 @@
 #include "Mutex.hpp"
 #include "AES.hpp"
 #include "SHA512.hpp"
+#include "Speck128.hpp"
 
 #ifdef __UNIX_LIKE__
 #include <unistd.h>
@@ -59,6 +60,7 @@ const CPUIDRegisters CPUID;
 
 const uint64_t ZERO256[4] = { 0,0,0,0 };
 const char HEXCHARS[16] = { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' };
+const uint64_t s_mapNonce = getSecureRandomU64();
 
 bool secureEq(const void *a,const void *b,unsigned int len) noexcept
 {
@@ -223,32 +225,40 @@ unsigned int unhex(const char *h,unsigned int hlen,void *buf,unsigned int buflen
 	return l;
 }
 
-void getSecureRandom(void *buf,unsigned int bytes) noexcept
+#define ZT_GETSECURERANDOM_STATE_SIZE 64
+#define ZT_GETSECURERANDOM_BUF_SIZE 4096
+
+void getSecureRandom(void *const buf,const unsigned int bytes) noexcept
 {
 	static Mutex globalLock;
 	static bool initialized = false;
-	static uint64_t randomState[16]; // secret state
-	static uint64_t randomBuf[8192]; // next batch of random bytes
+	static uint64_t randomState[ZT_GETSECURERANDOM_STATE_SIZE]; // secret state
+	static uint64_t randomBuf[ZT_GETSECURERANDOM_BUF_SIZE]; // next batch of random bytes
 	static unsigned long randomPtr = sizeof(randomBuf); // refresh on first iteration
 
-	// This secure random function gets entropy from the system random source (e.g. /dev/urandom),
-	// CPU random instructions if present, and other sources and uses them to initialize a SHA/AES
-	// based CSPRNG with a large state. System random sources are not used directly to mitigate
-	// against cases where the system random source is broken in some way, which does happen from
-	// time to time.
-
 	Mutex::Lock gl(globalLock);
 
+	// This could be a lot faster if we're not going to need a new block.
+	if ((randomPtr + (unsigned long)bytes) <= sizeof(randomBuf)) {
+		Utils::copy(buf,reinterpret_cast<uint8_t *>(randomBuf) + randomPtr,bytes);
+		randomPtr += bytes;
+		return;
+	}
+
 	for(unsigned int i=0;i<bytes;++i) {
-		if (randomPtr >= sizeof(randomBuf)) {
+		// Generate a new block of random data if we're at the end of the current block.
+		// Note that randomPtr is a byte pointer not a word pointer so we compare with sizeof.
+		if (randomPtr >= (unsigned long)sizeof(randomBuf)) {
 			randomPtr = 0;
 
 			if (!initialized) {
 				initialized = true;
 
-				// Fill both randomState and randomBuf from system random source. Failure here
-				// is fatal to the running application and indicates a serious system problem.
-				// This is some of the only OS-specific code in the core.
+				Utils::memoryLock(randomState,sizeof(randomState));
+				Utils::memoryLock(randomBuf,sizeof(randomBuf));
+
+				// Fill randomState with entropy from the system. If this doesn't work this is a hard fail.
+				Utils::zero<sizeof(randomState)>(randomState);
 #ifdef __WINDOWS__
 				HCRYPTPROV cryptProvider = NULL;
 				if (!CryptAcquireContextA(&cryptProvider,NULL,NULL,PROV_RSA_FULL,CRYPT_VERIFYCONTEXT|CRYPT_SILENT)) {
@@ -259,10 +269,6 @@ void getSecureRandom(void *buf,unsigned int bytes) noexcept
 					fprintf(stderr,"FATAL: Utils::getSecureRandom() CryptGenRandom failed!\r\n");
 					exit(1);
 				}
-				if (!CryptGenRandom(cryptProvider,(DWORD)sizeof(randomBuf),(BYTE *)randomBuf)) {
-					fprintf(stderr,"FATAL: Utils::getSecureRandom() CryptGenRandom failed!\r\n");
-					exit(1);
-				}
 				CryptReleaseContext(cryptProvider,0);
 #else
 				int devURandomFd = ::open("/dev/urandom",O_RDONLY);
@@ -275,25 +281,20 @@ void getSecureRandom(void *buf,unsigned int bytes) noexcept
 					fprintf(stderr,"FATAL: Utils::getSecureRandom() unable to read from /dev/urandom\n");
 					exit(1);
 				}
-				if ((long)::read(devURandomFd,randomBuf,sizeof(randomBuf)) != (long)sizeof(randomBuf)) {
-					::close(devURandomFd);
-					fprintf(stderr,"FATAL: Utils::getSecureRandom() unable to read from /dev/urandom\n");
-					exit(1);
-				}
 				close(devURandomFd);
 #endif
 
-				// Mix in additional entropy from time, the address of 'buf', rdrand if present, etc.
-				randomState[0] ^= (uint64_t)time(nullptr);
-				randomState[1] ^= (uint64_t)((uintptr_t)buf);
+				// Mix in additional entropy from time, the address of 'buf', CPU RDRAND if present, etc.
+				randomState[0] += (uint64_t)time(nullptr);
+				randomState[1] += (uint64_t)((uintptr_t)buf);
 #ifdef __UNIX_LIKE__
-				randomState[2] ^= (uint64_t)getpid();
-				randomState[3] ^= (uint64_t)getppid();
+				randomState[2] += (uint64_t)getpid();
+				randomState[3] += (uint64_t)getppid();
 #endif
 #ifdef ZT_ARCH_X64
 				if (CPUID.rdrand) {
 					uint64_t tmp = 0;
-					for(int k=0;k<16;++k) {
+					for(int k=0;k<ZT_GETSECURERANDOM_STATE_SIZE;++k) {
 						_rdrand64_step((unsigned long long *)&tmp);
 						randomState[k] ^= tmp;
 					}
@@ -301,25 +302,31 @@ void getSecureRandom(void *buf,unsigned int bytes) noexcept
 #endif
 			}
 
-			// Generate a new randomBuf:
-			//
-			// (1) Generate next randomState by perturbing, hashing, and replacing the first 384 bits with the hash.
-			// (2) Initialize AES using the first 256 bits of the new randomState as its key.
-			// (3) Initialize a 128-bit counter field using the following 128 bits of randomState.
-			// (4) Encrypt randomBuf with AES-CTR (machine-endian counter since spec conformance doesn't matter).
-
-			++randomState[15];
-			SHA384(randomState,randomState,sizeof(randomState));
-
-			AES aes(randomState);
-			uint64_t ctr[2],tmp[2];
-			ctr[0] = randomState[4];
-			ctr[1] = randomState[5]; // AES key + CTR/nonce = part replaced each time by SHA384
-			for(int k=0;k<8192;k+=2) {
-				++ctr[0];
-				aes.encrypt(ctr,tmp);
-				randomBuf[k] ^= tmp[0];
-				randomBuf[k+1] ^= tmp[1];
+			// Perturb state, hash, and overwrite the first 64 bytes with this hash.
+			++randomState[ZT_GETSECURERANDOM_STATE_SIZE-1];
+			SHA512(randomState,randomState,sizeof(randomState));
+
+			// Use the part of the state that was overwritten with new state to key a
+			// stream cipher and re-fill the buffer. Use AES if we're HW accel or use
+			// Speck if not since it's way faster on tiny chips without AES units.
+			if (AES::accelerated()) {
+				AES aes(randomState);
+				uint64_t ctr[2];
+				ctr[0] = randomState[4];
+				ctr[1] = randomState[5];
+				for (int k = 0;k < ZT_GETSECURERANDOM_BUF_SIZE;k += 2) {
+					++ctr[0];
+					aes.encrypt(ctr,randomBuf + k);
+				}
+			} else {
+				Speck128<> speck(randomState);
+				uint64_t ctr[2];
+				ctr[0] = randomState[4];
+				ctr[1] = randomState[5];
+				for (int k = 0;k < ZT_GETSECURERANDOM_BUF_SIZE;k += 2) {
+					++ctr[0];
+					speck.encrypt(ctr,randomBuf + k);
+				}
 			}
 		}
 
@@ -336,7 +343,7 @@ uint64_t getSecureRandomU64() noexcept
 
 int b32e(const uint8_t *data,int length,char *result,int bufSize) noexcept
 {
-  if (length < 0 || length > (1 << 28)) {
+  if (length < 0 || length > (1 << 28U)) {
 		result[0] = (char)0;
     return -1;
 	}
@@ -413,6 +420,7 @@ int b32d(const char *encoded,uint8_t *result,int bufSize) noexcept
 uint64_t random() noexcept
 {
 	// https://en.wikipedia.org/wiki/Xorshift#xoshiro256**
+
 	static volatile uint64_t s_s0 = getSecureRandomU64();
 	static volatile uint64_t s_s1 = getSecureRandomU64();
 	static volatile uint64_t s_s2 = getSecureRandomU64();

+ 20 - 7
node/Utils.hpp

@@ -68,6 +68,11 @@ extern const uint64_t ZERO256[4];
  */
 extern const char HEXCHARS[16];
 
+/**
+ * A random integer generated at startup for Map's hash bucket calculation.
+ */
+extern const uint64_t s_mapNonce;
+
 /**
  * Lock memory to prevent swapping out to secondary storage (if possible)
  *
@@ -228,7 +233,11 @@ int b32e(const uint8_t *data,int length,char *result,int bufSize) noexcept;
 int b32d(const char *encoded, uint8_t *result, int bufSize) noexcept;
 
 /**
- * Get a non-cryptographic random integer
+ * Get a non-cryptographic random integer.
+ *
+ * This should never be used for cryptographic use cases, not even for choosing
+ * message nonce/IV values if they should not repeat. It should only be used when
+ * a fast and potentially "dirty" random source is needed.
  */
 uint64_t random() noexcept;
 
@@ -246,7 +255,7 @@ uint64_t random() noexcept;
 bool scopy(char *dest,unsigned int len,const char *src) noexcept;
 
 /**
- * Mix bits in a 64-bit integer (non-cryptographic)
+ * Mix bits in a 64-bit integer (non-cryptographic, for hash tables)
  *
  * https://nullprogram.com/blog/2018/07/31/
  *
@@ -264,16 +273,20 @@ static ZT_INLINE uint64_t hash64(uint64_t x) noexcept
 }
 
 /**
- * Mix bits in a 32-bit integer
+ * Mix bits in a 32-bit integer (non-cryptographic, for hash tables)
+ *
+ * https://nullprogram.com/blog/2018/07/31/
  *
  * @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;
+	x ^= x >> 16U;
+	x *= 0x7feb352dU;
+	x ^= x >> 15U;
+	x *= 0x846ca68bU;
+	x ^= x >> 16U;
 	return x;
 }
 
@@ -598,7 +611,7 @@ static ZT_INLINE void storeLittleEndian(void *const p,const I i) noexcept
 }
 
 /**
- * Copy memory block whose size is known at compile time
+ * Copy memory block whose size is known at compile time.
  *
  * @tparam L Size of memory
  * @param dest Destination memory

+ 5 - 8
osdep/Arp.cpp

@@ -11,7 +11,6 @@
  */
 /****/
 
-#include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
 
@@ -24,7 +23,7 @@ static const uint8_t ARP_REQUEST_HEADER[8] = { 0x00,0x01,0x08,0x00,0x06,0x04,0x0
 static const uint8_t ARP_RESPONSE_HEADER[8] = { 0x00,0x01,0x08,0x00,0x06,0x04,0x00,0x02 };
 
 Arp::Arp() :
-	_cache(256),
+	_cache(),
 	_lastCleaned(OSUtils::now())
 {
 }
@@ -78,12 +77,10 @@ uint32_t Arp::processIncomingArp(const void *arp,unsigned int len,void *response
 
 	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);
+		for(Map< uint32_t,_ArpEntry >::iterator i(_cache.begin());i!=_cache.end();) {
+			if ((!i->second.local)&&((now - i->second.lastResponseReceived) >= ZT_ARP_EXPIRE))
+				_cache.erase(i++);
+			else ++i;
 		}
 	}
 

+ 2 - 2
osdep/Arp.hpp

@@ -20,7 +20,7 @@
 #include <vector>
 
 #include "../node/Constants.hpp"
-#include "../node/Hashtable.hpp"
+#include "../node/Map.hpp"
 #include "../node/MAC.hpp"
 
 /**
@@ -135,7 +135,7 @@ private:
 		bool local; // True if this is a local ARP entry
 	};
 
-	Hashtable< uint32_t,_ArpEntry > _cache;
+	Map< uint32_t,_ArpEntry > _cache;
 	uint64_t _lastCleaned;
 };
 

+ 7 - 8
osdep/NeighborDiscovery.cpp

@@ -156,7 +156,7 @@ struct _neighbor_advertisement {
 };
 
 NeighborDiscovery::NeighborDiscovery()
-    : _cache(256)
+    : _cache()
     , _lastCleaned(OSUtils::now())
 {}
 
@@ -211,13 +211,12 @@ sockaddr_storage NeighborDiscovery::processIncomingND(const uint8_t *nd, unsigne
 
     if ((now - _lastCleaned) >= ZT_ND_EXPIRE) {
         _lastCleaned = now;
-        Hashtable<InetAddress, _NDEntry>::Iterator i(_cache);
-        InetAddress *k = nullptr;
-        _NDEntry *v = nullptr;
-        while (i.next(k, v)) {
-            if(!v->local && (now - v->lastResponseReceived) >= ZT_ND_EXPIRE) {
-                _cache.erase(*k);
-            }
+        for(Map<InetAddress,_NDEntry>::iterator i(_cache.begin());i!=_cache.end();) {
+	        if(!i->second.local && (now - i->second.lastResponseReceived) >= ZT_ND_EXPIRE) {
+	          _cache.erase(i++);
+	        } else {
+	          ++i;
+	        }
         }
     }
 

+ 2 - 2
osdep/NeighborDiscovery.hpp

@@ -14,7 +14,7 @@
 #ifndef ZT_NEIGHBORDISCOVERY_HPP
 #define ZT_NEIGHBORDISCOVERY_HPP
 
-#include "../node/Hashtable.hpp"
+#include "../node/Map.hpp"
 #include "../node/MAC.hpp"
 #include "../node/InetAddress.hpp"
 
@@ -58,7 +58,7 @@ private:
         bool local;
     };
 
-    Hashtable<InetAddress, _NDEntry> _cache;
+    Map< InetAddress,_NDEntry > _cache;
     uint64_t _lastCleaned;
 };