Browse Source

Take Dictionary behind the barn...

Adam Ierymenko 5 years ago
parent
commit
cdc6c42375
11 changed files with 701 additions and 634 deletions
  1. 26 83
      attic/Str.hpp
  2. 90 2
      node/Buf.cpp
  3. 24 94
      node/Buf.hpp
  4. 0 1
      node/CMakeLists.txt
  5. 0 3
      node/Constants.hpp
  6. 228 174
      node/Dictionary.cpp
  7. 94 118
      node/Dictionary.hpp
  8. 3 3
      node/Hashtable.hpp
  9. 145 145
      node/NetworkConfig.cpp
  10. 79 0
      node/Utils.cpp
  11. 12 11
      node/Utils.hpp

+ 26 - 83
node/Str.hpp → attic/Str.hpp

@@ -34,50 +34,30 @@ public:
 	typedef char * iterator;
 	typedef char * iterator;
 	typedef const char * const_iterator;
 	typedef const char * const_iterator;
 
 
-	ZT_ALWAYS_INLINE Str() { _l = 0; _s[0] = 0; }
-	ZT_ALWAYS_INLINE Str(const Str &s)
-	{
-		_l = s._l;
-		memcpy(_s,s._s,_l+1);
-	}
-	ZT_ALWAYS_INLINE Str(const char *s)
-	{
-		_l = 0;
-		_s[0] = 0;
-		(*this) << s;
-	}
-	ZT_ALWAYS_INLINE Str(const std::string &s) { *this = s; }
+	ZT_ALWAYS_INLINE Str() { memset(reinterpret_cast<void *>(this),0,sizeof(Str)); }
+	explicit ZT_ALWAYS_INLINE Str(const char *s) { *this = s; }
 
 
-	ZT_ALWAYS_INLINE Str &operator=(const Str &s)
-	{
-		_l = s._l;
-		memcpy(_s,s._s,_l+1);
-		return *this;
-	}
 	ZT_ALWAYS_INLINE Str &operator=(const char *s)
 	ZT_ALWAYS_INLINE Str &operator=(const char *s)
 	{
 	{
-		_l = 0;
-		_s[0] = 0;
-		return ((*this) << s);
-	}
-	ZT_ALWAYS_INLINE Str &operator=(const std::string &s)
-	{
-		if (s.length() > C) {
+		if (s) {
+			unsigned int l = 0;
+			while (l < C) {
+				char c = s[l];
+				if (!c) break;
+				_s[l++] = c;
+			}
+			_s[l] = 0;
+			_l = (uint16_t)l;
+		} else {
 			_l = 0;
 			_l = 0;
 			_s[0] = 0;
 			_s[0] = 0;
-			throw ZT_EXCEPTION_OUT_OF_BOUNDS;
-		} else {
-			_l = (uint8_t)s.length();
-			memcpy(_s,s.data(),s.length());
-			_s[s.length()] = 0;
 		}
 		}
-		return *this;
 	}
 	}
 
 
 	ZT_ALWAYS_INLINE char operator[](const unsigned int i) const
 	ZT_ALWAYS_INLINE char operator[](const unsigned int i) const
 	{
 	{
-		if (unlikely(i >= (unsigned int)_l))
-			throw ZT_EXCEPTION_OUT_OF_BOUNDS;
+		if (i >= (unsigned int)_l)
+			return 0;
 		return _s[i];
 		return _s[i];
 	}
 	}
 
 
@@ -85,6 +65,7 @@ public:
 	ZT_ALWAYS_INLINE const char *c_str() const { return _s; }
 	ZT_ALWAYS_INLINE const char *c_str() const { return _s; }
 	ZT_ALWAYS_INLINE unsigned int length() const { return (unsigned int)_l; }
 	ZT_ALWAYS_INLINE unsigned int length() const { return (unsigned int)_l; }
 	ZT_ALWAYS_INLINE bool empty() const { return (_l == 0); }
 	ZT_ALWAYS_INLINE bool empty() const { return (_l == 0); }
+
 	ZT_ALWAYS_INLINE iterator begin() { return (iterator)_s; }
 	ZT_ALWAYS_INLINE iterator begin() { return (iterator)_s; }
 	ZT_ALWAYS_INLINE iterator end() { return (iterator)(_s + (unsigned long)_l); }
 	ZT_ALWAYS_INLINE iterator end() { return (iterator)(_s + (unsigned long)_l); }
 	ZT_ALWAYS_INLINE const_iterator begin() const { return (const_iterator)_s; }
 	ZT_ALWAYS_INLINE const_iterator begin() const { return (const_iterator)_s; }
@@ -92,44 +73,27 @@ public:
 
 
 	ZT_ALWAYS_INLINE Str &operator<<(const char *s)
 	ZT_ALWAYS_INLINE Str &operator<<(const char *s)
 	{
 	{
-		if (likely(s != (const char *)0)) {
-			unsigned long l = _l;
-			while (*s) {
-				if (unlikely(l >= C)) {
-					_s[C] = 0;
-					_l = C;
-					throw ZT_EXCEPTION_OUT_OF_BOUNDS;
-				}
-				_s[l++] = *(s++);
+		if (s) {
+			unsigned int l = _l;
+			while (l < C) {
+				char c = s[l];
+				if (!c) break;
+				_s[l++] = c;
 			}
 			}
 			_s[l] = 0;
 			_s[l] = 0;
-			_l = (uint8_t)l;
+			_l = (uint16_t)l;
 		}
 		}
-		return *this;
 	}
 	}
 	ZT_ALWAYS_INLINE Str &operator<<(const Str &s) { return ((*this) << s._s); }
 	ZT_ALWAYS_INLINE Str &operator<<(const Str &s) { return ((*this) << s._s); }
 	ZT_ALWAYS_INLINE Str &operator<<(const char c)
 	ZT_ALWAYS_INLINE Str &operator<<(const char c)
 	{
 	{
-		if (unlikely(_l >= C)) {
-			_s[C] = 0;
-			throw ZT_EXCEPTION_OUT_OF_BOUNDS;
+		if (_l < C) {
+			_s[_l++] = c;
+			_s[_l] = 0;
 		}
 		}
-		_s[(unsigned long)(_l++)] = c;
-		_s[(unsigned long)_l] = 0;
 		return *this;
 		return *this;
 	}
 	}
-	ZT_ALWAYS_INLINE Str &operator<<(const unsigned long n)
-	{
-		char tmp[32];
-		Utils::decimal(n,tmp);
-		return ((*this) << tmp);
-	}
-	ZT_ALWAYS_INLINE Str &operator<<(const unsigned int n)
-	{
-		char tmp[32];
-		Utils::decimal((unsigned long)n,tmp);
-		return ((*this) << tmp);
-	}
+
 	ZT_ALWAYS_INLINE Str &operator<<(const Address &a)
 	ZT_ALWAYS_INLINE Str &operator<<(const Address &a)
 	{
 	{
 		char tmp[32];
 		char tmp[32];
@@ -146,27 +110,6 @@ public:
 		return ((*this) << a.toString(tmp));
 		return ((*this) << a.toString(tmp));
 	}
 	}
 
 
-	inline Str &append(const char *s,const unsigned int max)
-	{
-		if (likely(s != (const char *)0)) {
-			unsigned long l = _l;
-			unsigned int c = 0;
-			while (*s) {
-				if (c++ >= max) break;
-				if (unlikely(l >= C)) {
-					_s[C] = 0;
-					_l = C;
-					throw ZT_EXCEPTION_OUT_OF_BOUNDS;
-				}
-				_s[l++] = *s;
-				++s;
-			}
-			_s[l] = 0;
-			_l = (uint8_t)l;
-		}
-		return *this;
-	}
-
 	ZT_ALWAYS_INLINE operator bool() const { return (_l != 0); }
 	ZT_ALWAYS_INLINE operator bool() const { return (_l != 0); }
 
 
 	ZT_ALWAYS_INLINE bool operator==(const Str &s) const { return ((_l == s._l)&&(memcmp(_s,s._s,_l) == 0)); }
 	ZT_ALWAYS_INLINE bool operator==(const Str &s) const { return ((_l == s._l)&&(memcmp(_s,s._s,_l) == 0)); }

+ 90 - 2
node/Buf.cpp

@@ -16,9 +16,97 @@
 namespace ZeroTier {
 namespace ZeroTier {
 
 
 #ifdef __GNUC__
 #ifdef __GNUC__
-uintptr_t Buf_pool = 0;
+uintptr_t _Buf_pool = 0;
 #else
 #else
-std::atomic<uintptr_t> Buf_pool(0);
+std::atomic<uintptr_t> _Buf_pool(0);
 #endif
 #endif
 
 
+void _Buf_release(void *ptr,std::size_t sz)
+{
+	if (ptr) {
+		uintptr_t bb;
+		const uintptr_t locked = ~((uintptr_t)0);
+		for (;;) {
+#ifdef __GNUC__
+			bb = __sync_fetch_and_or(&_Buf_pool,locked); // get value of s_pool and "lock" by filling with all 1's
+#else
+			bb = s_pool.fetch_or(locked);
+#endif
+			if (bb != locked)
+				break;
+		}
+
+		((Buf<> *)ptr)->__nextInPool = bb;
+#ifdef __GNUC__
+		__sync_fetch_and_and(&_Buf_pool,(uintptr_t)ptr);
+#else
+		s_pool.store((uintptr_t)ptr);
+#endif
+	}
+}
+
+void *_Buf_get()
+{
+	uintptr_t bb;
+	const uintptr_t locked = ~((uintptr_t)0);
+	for (;;) {
+#ifdef __GNUC__
+		bb = __sync_fetch_and_or(&_Buf_pool,locked); // get value of s_pool and "lock" by filling with all 1's
+#else
+		bb = s_pool.fetch_or(locked);
+#endif
+		if (bb != locked)
+			break;
+	}
+
+	Buf<> *b;
+	if (bb == 0) {
+#ifdef __GNUC__
+		__sync_fetch_and_and(&_Buf_pool,bb);
+#else
+		s_pool.store(bb);
+#endif
+		b = (Buf<> *)malloc(sizeof(Buf<>));
+		if (!b)
+			return nullptr;
+	} else {
+		b = (Buf<> *)bb;
+#ifdef __GNUC__
+		__sync_fetch_and_and(&_Buf_pool,b->__nextInPool);
+#else
+		s_pool.store(b->__nextInPool);
+#endif
+	}
+
+	b->__refCount.zero();
+	return (void *)b;
+}
+
+void freeBufPool()
+{
+	uintptr_t bb;
+	const uintptr_t locked = ~((uintptr_t)0);
+	for (;;) {
+#ifdef __GNUC__
+		bb = __sync_fetch_and_or(&_Buf_pool,locked); // get value of s_pool and "lock" by filling with all 1's
+#else
+		bb = s_pool.fetch_or(locked);
+#endif
+		if (bb != locked)
+			break;
+	}
+
+#ifdef __GNUC__
+	__sync_fetch_and_and(&_Buf_pool,(uintptr_t)0);
+#else
+	s_pool.store((uintptr_t)0);
+#endif
+
+	while (bb != 0) {
+		uintptr_t next = ((Buf<> *)bb)->__nextInPool;
+		free((void *)bb);
+		bb = next;
+	}
+}
+
 } // namespace ZeroTier
 } // namespace ZeroTier

+ 24 - 94
node/Buf.hpp

@@ -23,6 +23,7 @@
 #include <cstdint>
 #include <cstdint>
 #include <cstring>
 #include <cstring>
 #include <cstdlib>
 #include <cstdlib>
+#include <stdexcept>
 
 
 #ifndef __GNUC__
 #ifndef __GNUC__
 #include <atomic>
 #include <atomic>
@@ -37,10 +38,21 @@
 namespace ZeroTier {
 namespace ZeroTier {
 
 
 #ifdef __GNUC__
 #ifdef __GNUC__
-extern uintptr_t Buf_pool;
+extern uintptr_t _Buf_pool;
 #else
 #else
-extern std::atomic<uintptr_t> Buf_pool;
+extern std::atomic<uintptr_t> _Buf_pool;
 #endif
 #endif
+void _Buf_release(void *ptr,std::size_t sz);
+void *_Buf_get();
+
+/**
+ * Free buffers in the pool
+ *
+ * New buffers will be created and the pool repopulated if get() is called
+ * and outstanding buffers will still be returned to the pool. This just
+ * frees buffers currently held in reserve.
+ */
+void freeBufPool();
 
 
 /**
 /**
  * Buffer and methods for branch-free bounds-checked data assembly and parsing
  * Buffer and methods for branch-free bounds-checked data assembly and parsing
@@ -82,10 +94,13 @@ extern std::atomic<uintptr_t> Buf_pool;
  *
  *
  * @tparam U Type to overlap with data bytes in data union (can't be larger than ZT_BUF_MEM_SIZE)
  * @tparam U Type to overlap with data bytes in data union (can't be larger than ZT_BUF_MEM_SIZE)
  */
  */
-template<typename U = void>
+template<typename U = int>
 class Buf
 class Buf
 {
 {
 	friend class SharedPtr< Buf<U> >;
 	friend class SharedPtr< Buf<U> >;
+	friend void _Buf_release(void *,std::size_t);
+	friend void *_Buf_get();
+	friend void freeBufPool();
 
 
 private:
 private:
 	// Direct construction isn't allowed; use get().
 	// Direct construction isn't allowed; use get().
@@ -95,104 +110,19 @@ private:
 	ZT_ALWAYS_INLINE Buf(const Buf<X> &b) { memcpy(data.bytes,b.data.bytes,ZT_BUF_MEM_SIZE); }
 	ZT_ALWAYS_INLINE Buf(const Buf<X> &b) { memcpy(data.bytes,b.data.bytes,ZT_BUF_MEM_SIZE); }
 
 
 public:
 public:
-	static void operator delete(void *ptr,std::size_t sz)
-	{
-		if (ptr) {
-			uintptr_t bb;
-			const uintptr_t locked = ~((uintptr_t)0);
-			for (;;) {
-#ifdef __GNUC__
-				bb = __sync_fetch_and_or(&Buf_pool,locked); // get value of s_pool and "lock" by filling with all 1's
-#else
-				bb = s_pool.fetch_or(locked);
-#endif
-				if (bb != locked)
-					break;
-			}
-
-			((Buf *)ptr)->__nextInPool = bb;
-#ifdef __GNUC__
-			__sync_fetch_and_and(&Buf_pool,(uintptr_t)ptr);
-#else
-			s_pool.store((uintptr_t)ptr);
-#endif
-		}
-	}
+	static void operator delete(void *ptr,std::size_t sz) { _Buf_release(ptr,sz); }
 
 
 	/**
 	/**
 	 * Get obtains a buffer from the pool or allocates a new buffer if the pool is empty
 	 * Get obtains a buffer from the pool or allocates a new buffer if the pool is empty
 	 *
 	 *
-	 * @return Buffer
+	 * @return Buffer instance
 	 */
 	 */
 	static ZT_ALWAYS_INLINE SharedPtr< Buf<U> > get()
 	static ZT_ALWAYS_INLINE SharedPtr< Buf<U> > get()
 	{
 	{
-		uintptr_t bb;
-		const uintptr_t locked = ~((uintptr_t)0);
-		for (;;) {
-#ifdef __GNUC__
-			bb = __sync_fetch_and_or(&Buf_pool,locked); // get value of s_pool and "lock" by filling with all 1's
-#else
-			bb = s_pool.fetch_or(locked);
-#endif
-			if (bb != locked)
-				break;
-		}
-
-		Buf *b;
-		if (bb == 0) {
-#ifdef __GNUC__
-			__sync_fetch_and_and(&Buf_pool,bb);
-#else
-			s_pool.store(bb);
-#endif
-			b = (Buf *)malloc(sizeof(Buf));
-			if (!b)
-				return SharedPtr<Buf>();
-		} else {
-			b = (Buf *)bb;
-#ifdef __GNUC__
-			__sync_fetch_and_and(&Buf_pool,b->__nextInPool);
-#else
-			s_pool.store(b->__nextInPool);
-#endif
-		}
-
-		b->__refCount.zero();
-		return SharedPtr<Buf>(b);
-	}
-
-	/**
-	 * Free buffers in the pool
-	 *
-	 * New buffers will be created and the pool repopulated if get() is called
-	 * and outstanding buffers will still be returned to the pool. This just
-	 * frees buffers currently held in reserve.
-	 */
-	static inline void freePool()
-	{
-		uintptr_t bb;
-		const uintptr_t locked = ~((uintptr_t)0);
-		for (;;) {
-#ifdef __GNUC__
-			bb = __sync_fetch_and_or(&Buf_pool,locked); // get value of s_pool and "lock" by filling with all 1's
-#else
-			bb = s_pool.fetch_or(locked);
-#endif
-			if (bb != locked)
-				break;
-		}
-
-#ifdef __GNUC__
-		__sync_fetch_and_and(&Buf_pool,(uintptr_t)0);
-#else
-		s_pool.store((uintptr_t)0);
-#endif
-
-		while (bb != 0) {
-			uintptr_t next = ((Buf *)bb)->__nextInPool;
-			free((void *)bb);
-			bb = next;
-		}
+		void *const b = _Buf_get();
+		if (b)
+			return SharedPtr<Buf>((Buf *)b);
+		throw std::bad_alloc();
 	}
 	}
 
 
 	/**
 	/**

+ 0 - 1
node/CMakeLists.txt

@@ -38,7 +38,6 @@ set(core_headers
 	SelfAwareness.hpp
 	SelfAwareness.hpp
 	SHA512.hpp
 	SHA512.hpp
 	SharedPtr.hpp
 	SharedPtr.hpp
-	Str.hpp
 	Switch.hpp
 	Switch.hpp
 	Tag.hpp
 	Tag.hpp
 	Topology.hpp
 	Topology.hpp

+ 0 - 3
node/Constants.hpp

@@ -231,9 +231,6 @@
 #define ZT_CRYPTO_ALG_C25519 0
 #define ZT_CRYPTO_ALG_C25519 0
 #define ZT_CRYPTO_ALG_P384 1
 #define ZT_CRYPTO_ALG_P384 1
 
 
-// Exceptions thrown in core ZT code
-#define ZT_EXCEPTION_OUT_OF_BOUNDS 100
-
 /* Ethernet frame types that might be relevant to us */
 /* Ethernet frame types that might be relevant to us */
 #define ZT_ETHERTYPE_IPV4 0x0800
 #define ZT_ETHERTYPE_IPV4 0x0800
 #define ZT_ETHERTYPE_ARP 0x0806
 #define ZT_ETHERTYPE_ARP 0x0806

+ 228 - 174
node/Dictionary.cpp

@@ -15,210 +15,264 @@
 
 
 namespace ZeroTier {
 namespace ZeroTier {
 
 
-Dictionary::Dictionary(const char *s,unsigned int len)
-{
-	for(unsigned int i=0;i<ZT_DICTIONARY_MAX_CAPACITY;++i) {
-		if ((s)&&(i < len)) {
-			if (!(_d[i] = *s))
-				s = (const char *)0;
-			else ++s;
-		} else _d[i] = (char)0;
+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;
 	}
 	}
-	_d[ZT_DICTIONARY_MAX_CAPACITY-1] = (char)0;
+	return n;
 }
 }
 
 
-bool Dictionary::load(const char *s)
+Dictionary::Dictionary()
 {
 {
-	for(unsigned int i=0;i<ZT_DICTIONARY_MAX_CAPACITY;++i) {
-		if (s) {
-			if (!(_d[i] = *s))
-				s = (const char *)0;
-			else ++s;
-		} else _d[i] = (char)0;
-	}
-	_d[ZT_DICTIONARY_MAX_CAPACITY - 1] = (char)0;
-	return (!s);
-}
-
-int Dictionary::get(const char *key,char *dest,unsigned int destlen) const
-{
-	const char *p = _d;
-	const char *const eof = p + ZT_DICTIONARY_MAX_CAPACITY;
-	const char *k;
-	bool esc;
-	int j;
-
-	if (!destlen) // sanity check
-		return -1;
-
-	while (*p) {
-		k = key;
-		while ((*k)&&(*p)) {
-			if (*p != *k)
-				break;
-			++k;
-			if (++p == eof) {
-				dest[0] = (char)0;
-				return -1;
-			}
-		}
+}
 
 
-		if ((!*k)&&(*p == '=')) {
-			j = 0;
-			esc = false;
-			++p;
-			while ((*p != 0)&&(*p != 13)&&(*p != 10)) {
-				if (esc) {
-					esc = false;
-					switch(*p) {
-						case 'r': dest[j++] = 13; break;
-						case 'n': dest[j++] = 10; break;
-						case '0': dest[j++] = (char)0; break;
-						case 'e': dest[j++] = '='; break;
-						default: dest[j++] = *p; break;
-					}
-					if (j == (int)destlen) {
-						dest[j-1] = (char)0;
-						return j-1;
-					}
-				} else if (*p == '\\') {
-					esc = true;
-				} else {
-					dest[j++] = *p;
-					if (j == (int)destlen) {
-						dest[j-1] = (char)0;
-						return j-1;
-					}
-				}
-				if (++p == eof) {
-					dest[0] = (char)0;
-					return -1;
-				}
-			}
-			dest[j] = (char)0;
-			return j;
-		} else {
-			while ((*p)&&(*p != 13)&&(*p != 10)) {
-				if (++p == eof) {
-					dest[0] = (char)0;
-					return -1;
-				}
-			}
-			if (*p) {
-				if (++p == eof) {
-					dest[0] = (char)0;
-					return -1;
-				}
-			}
-			else break;
-		}
-	}
+Dictionary::~Dictionary()
+{
+}
 
 
-	dest[0] = (char)0;
-	return -1;
+std::vector<uint8_t> &Dictionary::operator[](const char *k)
+{
+	return _t[_toKey(k)];
 }
 }
 
 
-bool Dictionary::add(const char *key,const char *value,int vlen)
+const std::vector<uint8_t> &Dictionary::operator[](const char *k) const
 {
 {
-	for(unsigned int i=0;i<ZT_DICTIONARY_MAX_CAPACITY;++i) {
-		if (!_d[i]) {
-			unsigned int j = i;
+	static const std::vector<uint8_t> emptyEntry;
+	const std::vector<uint8_t> *const e = _t.get(_toKey(k));
+	return (e) ? *e : emptyEntry;
+}
 
 
-			if (j > 0) {
-				_d[j++] = (char)10;
-				if (j == ZT_DICTIONARY_MAX_CAPACITY) {
-					_d[i] = (char)0;
-					return false;
-				}
-			}
+void Dictionary::add(const char *k,bool v)
+{
+	std::vector<uint8_t> &e = (*this)[k];
+	e.resize(2);
+	e[0] = (uint8_t)(v ? '1' : '0');
+	e[1] = 0;
+}
 
 
-			const char *p = key;
-			while (*p) {
-				_d[j++] = *(p++);
-				if (j == ZT_DICTIONARY_MAX_CAPACITY) {
-					_d[i] = (char)0;
-					return false;
-				}
-			}
+void Dictionary::add(const char *k,uint16_t v)
+{
+	std::vector<uint8_t> &e = (*this)[k];
+	e.resize(5);
+	Utils::hex(v,(char *)e.data());
+}
 
 
-			_d[j++] = '=';
-			if (j == ZT_DICTIONARY_MAX_CAPACITY) {
-				_d[i] = (char)0;
-				return false;
-			}
+void Dictionary::add(const char *k,uint32_t v)
+{
+	std::vector<uint8_t> &e = (*this)[k];
+	e.resize(9);
+	Utils::hex(v,(char *)e.data());
+}
 
 
-			p = value;
-			int k = 0;
-			while ( ((vlen < 0)&&(*p)) || (k < vlen) ) {
-				switch(*p) {
-					case 0:
-					case 13:
-					case 10:
-					case '\\':
-					case '=':
-						_d[j++] = '\\';
-						if (j == ZT_DICTIONARY_MAX_CAPACITY) {
-							_d[i] = (char)0;
-							return false;
-						}
-						switch(*p) {
-							case 0: _d[j++] = '0'; break;
-							case 13: _d[j++] = 'r'; break;
-							case 10: _d[j++] = 'n'; break;
-							case '\\': _d[j++] = '\\'; break;
-							case '=': _d[j++] = 'e'; break;
-						}
-						if (j == ZT_DICTIONARY_MAX_CAPACITY) {
-							_d[i] = (char)0;
-							return false;
-						}
-						break;
-					default:
-						_d[j++] = *p;
-						if (j == ZT_DICTIONARY_MAX_CAPACITY) {
-							_d[i] = (char)0;
-							return false;
-						}
-						break;
-				}
-				++p;
-				++k;
-			}
+void Dictionary::add(const char *k,uint64_t v)
+{
+	std::vector<uint8_t> &e = (*this)[k];
+	e.resize(17);
+	Utils::hex(v,(char *)e.data());
+}
+
+void Dictionary::add(const char *k,const Address &v)
+{
+	std::vector<uint8_t> &e = (*this)[k];
+	e.resize(11);
+	v.toString((char *)e.data());
+}
+
+void Dictionary::add(const char *k,const char *v)
+{
+	std::vector<uint8_t> &e = (*this)[k];
+	e.clear();
+	if (v) {
+		for(;;) {
+			const uint8_t c = (uint8_t)*(v++);
+			e.push_back(c);
+			if (!c) break;
+		}
+	}
+}
 
 
-			_d[j] = (char)0;
+void Dictionary::add(const char *k,const void *data,unsigned int len)
+{
+	std::vector<uint8_t> &e = (*this)[k];
+	if (len != 0) {
+		e.assign((const uint8_t *)data,(const uint8_t *)data + len);
+	} else {
+		e.clear();
+	}
+}
+
+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]) {
+			case '1':
+			case 't':
+			case 'T':
+			case 'y':
+			case 'Y':
+				return true;
+			default:
+				return false;
+		}
+	}
+	return v;
+}
 
 
-			return true;
+uint64_t Dictionary::getUI(const char *k,uint64_t dfl) const
+{
+	uint8_t tmp[18];
+	uint64_t v = dfl;
+	const std::vector<uint8_t> &e = (*this)[k];
+	if (!e.empty()) {
+		if (e.back() != 0) {
+			const unsigned long sl = e.size();
+			memcpy(tmp,e.data(),(sl > 17) ? 17 : sl);
+			tmp[17] = 0;
+			return Utils::unhex((const char *)tmp);
 		}
 		}
+		return Utils::unhex((const char *)e.data());
 	}
 	}
-	return false;
+	return v;
 }
 }
 
 
-bool Dictionary::add(const char *key,bool value)
+void Dictionary::getS(const char *k,char *v,unsigned int cap) const
 {
 {
-	return this->add(key,(value) ? "1" : "0",1);
+	if (cap == 0) // sanity check
+		return;
+	const std::vector<uint8_t> &e = (*this)[k];
+	unsigned int i = 0;
+	const unsigned int last = cap - 1;
+	for(;;) {
+		if ((i == last)||(i >= (unsigned int)e.size()))
+			break;
+		v[i] = (char)e[i];
+		++i;
+	}
+	v[i] = 0;
 }
 }
 
 
-bool Dictionary::add(const char *key,uint64_t value)
+void Dictionary::clear()
 {
 {
-	char tmp[32];
-	return this->add(key,Utils::hex(value,tmp),-1);
+	_t.clear();
 }
 }
 
 
-bool Dictionary::add(const char *key,int64_t value)
+void Dictionary::encode(std::vector<uint8_t> &out) const
 {
 {
-	char tmp[32];
-	if (value >= 0) {
-		return this->add(key,Utils::hex((uint64_t)value,tmp),-1);
-	} else {
-		tmp[0] = '-';
-		return this->add(key,Utils::hex((uint64_t)(value * -1),tmp+1),-1);
+	uint64_t str[2] = { 0,0 }; // second entry causes all strings to be null-terminated even if 8 chars in length
+
+	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;
+		const char *k = (const char *)str;
+
+		for(;;) {
+			char kc = *(k++);
+			if (!kc) break;
+			if ((kc >= 33)&&(kc <= 126)&&(kc != 61)&&(kc != 92)) // printable ASCII with no spaces, equals, or backslash
+				out.push_back((uint8_t)kc);
+		}
+
+		out.push_back(61); // =
+
+		for(std::vector<uint8_t>::const_iterator i(vv->begin());i!=vv->end();++i) {
+			uint8_t c = *i;
+			switch(c) {
+				case 0:
+					out.push_back(92);
+					out.push_back(48);
+					break;
+				case 10:
+					out.push_back(92);
+					out.push_back(110);
+					break;
+				case 13:
+					out.push_back(92);
+					out.push_back(114);
+					break;
+				case 61:
+					out.push_back(92);
+					out.push_back(101);
+					break;
+				case 92:
+					out.push_back(92);
+					out.push_back(92);
+					break;
+				default:
+					out.push_back(c);
+					break;
+			}
+		}
+
+		out.push_back(10);
 	}
 	}
 }
 }
 
 
-bool Dictionary::add(const char *key,const Address &a)
+bool Dictionary::decode(const void *data,unsigned int len)
 {
 {
-	char tmp[32];
-	return this->add(key,Utils::hex(a.toInt(),tmp),-1);
+	clear();
+
+	uint64_t k = 0;
+	unsigned int ki = 0;
+	std::vector<uint8_t> *v = nullptr;
+	bool escape = false;
+	for(unsigned int di=0;di<len;++di) {
+		uint8_t c = reinterpret_cast<const uint8_t *>(data)[di];
+		if (!c) break;
+		if (v) {
+			if (escape) {
+				escape = false;
+				switch(c) {
+					case 48:
+						v->push_back(0);
+						break;
+					case 101:
+						v->push_back(61);
+						break;
+					case 110:
+						v->push_back(10);
+						break;
+					case 114:
+						v->push_back(13);
+						break;
+					default:
+						v->push_back(c);
+						break;
+				}
+			} else {
+				if (c == 10) {
+					k = 0;
+					ki = 0;
+					v = nullptr;
+				} else if (c == 92) {
+					escape = true;
+				} else {
+					v->push_back(c);
+				}
+			}
+		} else {
+			if ((c < 33)||(c > 126)||(c == 92)) {
+				return false;
+			} else if (c == 61) {
+				v = &_t[k];
+			} else {
+				reinterpret_cast<uint8_t *>(&k)[ki & 7U] ^= c;
+			}
+		}
+	}
+
+	return true;
 }
 }
 
 
 } // namespace ZeroTier
 } // namespace ZeroTier

+ 94 - 118
node/Dictionary.hpp

@@ -18,181 +18,157 @@
 #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>
-
-#define ZT_DICTIONARY_MAX_CAPACITY 65536
+#include <vector>
 
 
 namespace ZeroTier {
 namespace ZeroTier {
 
 
 /**
 /**
- * A small (in code and data) packed key=value store
- *
- * This stores data in the form of a compact blob that is sort of human
- * readable (depending on whether you put binary data in it) and is backward
- * compatible with older versions. Binary data is escaped such that the
- * serialized form of a Dictionary is always a valid null-terminated C string.
- *
- * Keys are restricted: no binary data, no CR/LF, and no equals (=). If a key
- * contains these characters it may not be retrievable. This is not checked.
- *
- * Lookup is via linear search and will be slow with a lot of keys. It's
- * designed for small things.
- *
- * There is code to test and fuzz this in selftest.cpp. Fuzzing a blob of
- * pointer tricks like this is important after any modifications.
+ * A simple key-value store for short keys
  *
  *
- * This is used for network configurations and for saving some things on disk
- * in the ZeroTier One service code.
+ * 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,
+ * or integers with the latter being by convention in hex format.
  *
  *
- * @tparam C Dictionary max capacity in bytes
+ * If this seems a little odd, it is. It dates back to the very first alpha
+ * versions of ZeroTier and if it were redesigned today we'd use some kind
+ * of simple or standardized binary encoding. Nevertheless it is efficient
+ * and it works so there is no need to change it and break backward
+ * compatibility.
  */
  */
 class Dictionary
 class Dictionary
 {
 {
 public:
 public:
-	ZT_ALWAYS_INLINE Dictionary() { _d[0] = 0; }
-	explicit ZT_ALWAYS_INLINE Dictionary(const char *s) { this->load(s); }
-	Dictionary(const char *s,unsigned int len);
+	Dictionary();
+	~Dictionary();
 
 
-	ZT_ALWAYS_INLINE operator bool() const { return (_d[0] != 0); }
+	/**
+	 * Get a reference to a value
+	 *
+	 * @param k Key to look up
+	 * @return Reference to value
+	 */
+	std::vector<uint8_t> &operator[](const char *k);
 
 
 	/**
 	/**
-	 * Load a dictionary from a C-string
+	 * Get a const reference to a value
 	 *
 	 *
-	 * @param s Dictionary in string form
-	 * @return False if 's' was longer than our capacity
+	 * @param k Key to look up
+	 * @return Reference to value or to empty vector if not found
 	 */
 	 */
-	bool load(const char *s);
+	const std::vector<uint8_t> &operator[](const char *k) const;
 
 
 	/**
 	/**
-	 * Delete all entries
+	 * Add a boolean as '1' or '0'
 	 */
 	 */
-	ZT_ALWAYS_INLINE void clear() { memset(_d,0,sizeof(_d)); }
+	void add(const char *k,bool v);
 
 
 	/**
 	/**
-	 * Get an entry
-	 *
-	 * Note that to get binary values, dest[] should be at least one more than
-	 * the maximum size of the value being retrieved. That's because even if
-	 * the data is binary a terminating 0 is still appended to dest[] after it.
-	 *
-	 * If the key is not found, dest[0] is set to 0 to make dest[] an empty
-	 * C string in that case. The dest[] array will *never* be unterminated
-	 * after this call.
-	 *
-	 * Security note: if 'key' is ever directly based on anything that is not
-	 * a hard-code or internally-generated name, it must be checked to ensure
-	 * that the buffer is NULL-terminated since key[] does not take a secondary
-	 * size parameter. In NetworkConfig all keys are hard-coded strings so this
-	 * isn't a problem in the core.
-	 *
-	 * @param key Key to look up
-	 * @param dest Destination buffer
-	 * @param destlen Size of destination buffer
-	 * @return -1 if not found, or actual number of bytes stored in dest[] minus trailing 0
+	 * Add an integer as a hexadecimal string value
 	 */
 	 */
-	int get(const char *key,char *dest,unsigned int destlen) const;
+	void add(const char *k,uint16_t v);
 
 
 	/**
 	/**
-	 * Get a boolean value
-	 *
-	 * @param key Key to look up
-	 * @param dfl Default value if not found in dictionary
-	 * @return Boolean value of key or 'dfl' if not found
+	 * Add an integer as a hexadecimal string value
 	 */
 	 */
-	ZT_ALWAYS_INLINE bool getB(const char *key,bool dfl = false) const
-	{
-		char tmp[4];
-		if (this->get(key,tmp,sizeof(tmp)) >= 0)
-			return ((*tmp == '1')||(*tmp == 't')||(*tmp == 'T'));
-		return dfl;
-	}
+	void add(const char *k,uint32_t v);
 
 
 	/**
 	/**
-	 * Get an unsigned int64 stored as hex in the dictionary
-	 *
-	 * @param key Key to look up
-	 * @param dfl Default value or 0 if unspecified
-	 * @return Decoded hex UInt value or 'dfl' if not found
+	 * Add an integer as a hexadecimal string value
 	 */
 	 */
-	ZT_ALWAYS_INLINE uint64_t getUI(const char *key,uint64_t dfl = 0) const
-	{
-		char tmp[128];
-		if (this->get(key,tmp,sizeof(tmp)) >= 1)
-			return Utils::hexStrToU64(tmp);
-		return dfl;
-	}
+	void add(const char *k,uint64_t v);
+
+	ZT_ALWAYS_INLINE void add(const char *k,int16_t v) { add(k,(uint16_t)v); }
+	ZT_ALWAYS_INLINE void add(const char *k,int32_t v) { add(k,(uint32_t)v); }
+	ZT_ALWAYS_INLINE void add(const char *k,int64_t v) { add(k,(uint64_t)v); }
 
 
 	/**
 	/**
-	 * Get an unsigned int64 stored as hex in the dictionary
-	 *
-	 * @param key Key to look up
-	 * @param dfl Default value or 0 if unspecified
-	 * @return Decoded hex UInt value or 'dfl' if not found
+	 * Add an address in 10-digit hex string format
 	 */
 	 */
-	ZT_ALWAYS_INLINE int64_t getI(const char *key,int64_t dfl = 0) const
-	{
-		char tmp[128];
-		if (this->get(key,tmp,sizeof(tmp)) >= 1)
-			return Utils::hexStrTo64(tmp);
-		return dfl;
-	}
+	void add(const char *k,const Address &v);
 
 
 	/**
 	/**
-	 * Add a new key=value pair
-	 *
-	 * If the key is already present this will append another, but the first
-	 * will always be returned by get(). This is not checked. If you want to
-	 * ensure a key is not present use erase() first.
-	 *
-	 * Use the vlen parameter to add binary values. Nulls will be escaped.
+	 * Add a C string as a value
+	 */
+	void add(const char *k,const char *v);
+
+	/**
+	 * Add a binary blob as a value
+	 */
+	void add(const char *k,const void *data,unsigned int len);
+
+	/**
+	 * Get a boolean
 	 *
 	 *
-	 * @param key Key -- nulls, CR/LF, and equals (=) are illegal characters
-	 * @param value Value to set
-	 * @param vlen Length of value in bytes or -1 to treat value[] as a C-string and look for terminating 0
-	 * @return True if there was enough room to add this key=value pair
+	 * @param k Key to look up
+	 * @param dfl Default value (default: false)
+	 * @return Value of key or default if not found
 	 */
 	 */
-	bool add(const char *key,const char *value,int vlen = -1);
+	bool getB(const char *k,bool dfl = false) const;
 
 
 	/**
 	/**
-	 * Add a boolean as a '1' or a '0'
+	 * Get an integer
+	 *
+	 * @param k Key to look up
+	 * @param dfl Default value (default: 0)
+	 * @return Value of key or default if not found
 	 */
 	 */
-	bool add(const char *key,bool value);
+	uint64_t getUI(const char *k,uint64_t dfl = 0) const;
 
 
 	/**
 	/**
-	 * Add a 64-bit integer (unsigned) as a hex value
+	 * Get a C string
+	 *
+	 * If the buffer is too small the string will be truncated, but the
+	 * buffer will always end in a terminating null no matter what.
+	 *
+	 * @param k Key to look up
+	 * @param v Buffer to hold string
+	 * @param cap Maximum size of string (including terminating null)
 	 */
 	 */
-	bool add(const char *key,uint64_t value);
+	void getS(const char *k,char *v,unsigned int cap) const;
 
 
 	/**
 	/**
-	 * Add a 64-bit integer (unsigned) as a hex value
+	 * Erase all entries in dictionary
 	 */
 	 */
-	bool add(const char *key,int64_t value);
+	void clear();
 
 
 	/**
 	/**
-	 * Add a 64-bit integer (unsigned) as a hex value
+	 * @return Number of entries
 	 */
 	 */
-	bool add(const char *key,const Address &a);
+	ZT_ALWAYS_INLINE unsigned int size() const { return _t.size(); }
 
 
 	/**
 	/**
-	 * @param key Key to check
-	 * @return True if key is present
+	 * @return True if dictionary is not empty
 	 */
 	 */
-	ZT_ALWAYS_INLINE bool contains(const char *key) const
-	{
-		char tmp[2];
-		return (this->get(key,tmp,2) >= 0);
-	}
+	ZT_ALWAYS_INLINE bool empty() const { return _t.empty(); }
 
 
 	/**
 	/**
-	 * @return Value of C template parameter
+	 * Encode to a string in the supplied vector
+	 *
+	 * This does not add a terminating zero. This must be pushed afterwords
+	 * if the result is to be handled as a C string.
+	 *
+	 * @param out String encoded dictionary
 	 */
 	 */
-	ZT_ALWAYS_INLINE unsigned int capacity() const { return sizeof(_d); }
+	void encode(std::vector<uint8_t> &out) const;
 
 
-	ZT_ALWAYS_INLINE const char *data() const { return _d; }
+	/**
+	 * Decode a string encoded dictionary
+	 *
+	 * This will decode up to 'len' but will also abort if it finds a
+	 * null/zero as this could be a C string.
+	 *
+	 * @param data Data to decode
+	 * @param len Length of data
+	 * @return True if dictionary was formatted correctly and valid, false on error
+	 */
+	bool decode(const void *data,unsigned int len);
 
 
 private:
 private:
-	char _d[ZT_DICTIONARY_MAX_CAPACITY];
+	Hashtable< uint64_t,std::vector<uint8_t> > _t;
 };
 };
 
 
 } // namespace ZeroTier
 } // namespace ZeroTier

+ 3 - 3
node/Hashtable.hpp

@@ -35,7 +35,7 @@ private:
 	struct _Bucket
 	struct _Bucket
 	{
 	{
 		ZT_ALWAYS_INLINE _Bucket(const K &k,const V &v) : k(k),v(v) {}
 		ZT_ALWAYS_INLINE _Bucket(const K &k,const V &v) : k(k),v(v) {}
-		ZT_ALWAYS_INLINE _Bucket(const K &k) : k(k),v() {}
+		explicit ZT_ALWAYS_INLINE _Bucket(const K &k) : k(k),v() {}
 		ZT_ALWAYS_INLINE _Bucket(const _Bucket &b) : k(b.k),v(b.v) {}
 		ZT_ALWAYS_INLINE _Bucket(const _Bucket &b) : k(b.k),v(b.v) {}
 		ZT_ALWAYS_INLINE _Bucket &operator=(const _Bucket &b) { k = b.k; v = b.v; return *this; }
 		ZT_ALWAYS_INLINE _Bucket &operator=(const _Bucket &b) { k = b.k; v = b.v; return *this; }
 		_Bucket *next; // must be set manually for each _Bucket
 		_Bucket *next; // must be set manually for each _Bucket
@@ -57,7 +57,7 @@ public:
 		/**
 		/**
 		 * @param ht Hash table to iterate over
 		 * @param ht Hash table to iterate over
 		 */
 		 */
-		ZT_ALWAYS_INLINE Iterator(Hashtable &ht) :
+		explicit ZT_ALWAYS_INLINE Iterator(Hashtable &ht) :
 			_idx(0),
 			_idx(0),
 			_ht(&ht),
 			_ht(&ht),
 			_b(ht._t[0])
 			_b(ht._t[0])
@@ -95,7 +95,7 @@ public:
 	/**
 	/**
 	 * @param bc Initial capacity in buckets (default: 32, must be nonzero)
 	 * @param bc Initial capacity in buckets (default: 32, must be nonzero)
 	 */
 	 */
-	ZT_ALWAYS_INLINE Hashtable(unsigned long bc = 32) :
+	explicit ZT_ALWAYS_INLINE Hashtable(unsigned long bc = 32) :
 		_t(reinterpret_cast<_Bucket **>(::malloc(sizeof(_Bucket *) * bc))),
 		_t(reinterpret_cast<_Bucket **>(::malloc(sizeof(_Bucket *) * bc))),
 		_bc(bc),
 		_bc(bc),
 		_s(0)
 		_s(0)

+ 145 - 145
node/NetworkConfig.cpp

@@ -44,138 +44,105 @@ NetworkConfig::NetworkConfig() :
 bool NetworkConfig::toDictionary(Dictionary &d,bool includeLegacy) const
 bool NetworkConfig::toDictionary(Dictionary &d,bool includeLegacy) const
 {
 {
 	uint8_t tmp[16384];
 	uint8_t tmp[16384];
-	std::vector<uint8_t> buf;
-	char tmp2[128];
-
-	d.clear();
-
-	// Try to put the more human-readable fields first
-
-	if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID,this->networkId)) return false;
-	if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP,this->timestamp)) return false;
-	if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CREDENTIAL_TIME_MAX_DELTA,this->credentialTimeMaxDelta)) return false;
-	if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_REVISION,this->revision)) return false;
-	if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO,this->issuedTo.toString(tmp2))) return false;
-	if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_FLAGS,this->flags)) return false;
-	if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT,(uint64_t)this->multicastLimit)) return false;
-	if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_TYPE,(uint64_t)this->type)) return false;
-	if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_NAME,this->name)) return false;
-	if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_MTU,(uint64_t)this->mtu)) return false;
-
-	// Then add binary blobs
-
-	if (this->com) {
-		if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_COM,(const char *)tmp,this->com.marshal(tmp)))
-			return false;
-	}
-
-	buf.clear();
-	for(unsigned int i=0;i<this->capabilityCount;++i) {
-		int l = this->capabilities[i].marshal(tmp);
-		if (l < 0)
-			return false;
-		buf.insert(buf.end(),tmp,tmp + l);
-	}
-	if (!buf.empty()) {
-		if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CAPABILITIES,(const char *)buf.data(),(int)buf.size()))
-			return false;
-	}
-
-	buf.clear();
-	for(unsigned int i=0;i<this->tagCount;++i) {
-		int l = this->tags[i].marshal(tmp);
-		if (l < 0)
-			return false;
-		buf.insert(buf.end(),tmp,tmp + l);
-	}
-	if (!buf.empty()) {
-		if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_TAGS,(const char *)buf.data(),(int)buf.size()))
-			return false;
-	}
+	try {
+		d.clear();
+
+		d.add(ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID,this->networkId);
+		d.add(ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP,this->timestamp);
+		d.add(ZT_NETWORKCONFIG_DICT_KEY_CREDENTIAL_TIME_MAX_DELTA,this->credentialTimeMaxDelta);
+		d.add(ZT_NETWORKCONFIG_DICT_KEY_REVISION,this->revision);
+		d.add(ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO,this->issuedTo.toString((char *)tmp));
+		d.add(ZT_NETWORKCONFIG_DICT_KEY_FLAGS,this->flags);
+		d.add(ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT,(uint64_t)this->multicastLimit);
+		d.add(ZT_NETWORKCONFIG_DICT_KEY_TYPE,(uint16_t)this->type);
+		d.add(ZT_NETWORKCONFIG_DICT_KEY_NAME,this->name);
+		d.add(ZT_NETWORKCONFIG_DICT_KEY_MTU,this->mtu);
+
+		if (this->com)
+			d.add(ZT_NETWORKCONFIG_DICT_KEY_COM,tmp,this->com.marshal(tmp));
+
+		std::vector<uint8_t> *blob = &(d[ZT_NETWORKCONFIG_DICT_KEY_CAPABILITIES]);
+		for (unsigned int i = 0; i < this->capabilityCount; ++i) {
+			int l = this->capabilities[i].marshal(tmp);
+			if (l < 0)
+				return false;
+			blob->insert(blob->end(),tmp,tmp + l);
+		}
 
 
-	buf.clear();
-	for(unsigned int i=0;i<this->certificateOfOwnershipCount;++i) {
-		int l = this->certificatesOfOwnership[i].marshal(tmp);
-		if (l < 0)
-			return false;
-		buf.insert(buf.end(),tmp,tmp + l);
-	}
-	if (!buf.empty()) {
-		if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATES_OF_OWNERSHIP,(const char *)buf.data(),(int)buf.size()))
-			return false;
-	}
+		blob = &(d[ZT_NETWORKCONFIG_DICT_KEY_TAGS]);
+		for (unsigned int i = 0; i < this->tagCount; ++i) {
+			int l = this->tags[i].marshal(tmp);
+			if (l < 0)
+				return false;
+			blob->insert(blob->end(),tmp,tmp + l);
+		}
 
 
-	buf.clear();
-	for(unsigned int i=0;i<this->specialistCount;++i) {
-		Utils::storeBigEndian<uint64_t>(tmp,this->specialists[i]);
-		buf.insert(buf.end(),tmp,tmp + 8);
-	}
-	if (!buf.empty()) {
-		if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SPECIALISTS,(const char *)buf.data(),(int)buf.size()))
-			return false;
-	}
+		blob = &(d[ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATES_OF_OWNERSHIP]);
+		for (unsigned int i = 0; i < this->certificateOfOwnershipCount; ++i) {
+			int l = this->certificatesOfOwnership[i].marshal(tmp);
+			if (l < 0)
+				return false;
+			blob->insert(blob->end(),tmp,tmp + l);
+		}
 
 
-	buf.clear();
-	for(unsigned int i=0;i<this->routeCount;++i) {
-		int l = asInetAddress(this->routes[i].target).marshal(tmp);
-		if (l < 0)
-			return false;
-		buf.insert(buf.end(),tmp,tmp + l);
-		l = asInetAddress(this->routes[i].via).marshal(tmp);
-		if (l < 0)
-			return false;
-		buf.insert(buf.end(),tmp,tmp + l);
-	}
-	if (!buf.empty()) {
-		if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ROUTES,(const char *)buf.data(),(int)buf.size()))
-			return false;
-	}
+		blob = &(d[ZT_NETWORKCONFIG_DICT_KEY_SPECIALISTS]);
+		for (unsigned int i = 0; i < this->specialistCount; ++i) {
+			Utils::storeBigEndian<uint64_t>(tmp,this->specialists[i]);
+			blob->insert(blob->end(),tmp,tmp + 8);
+		}
 
 
-	buf.clear();
-	for(unsigned int i=0;i<this->staticIpCount;++i) {
-		int l = this->staticIps[i].marshal(tmp);
-		if (l < 0)
-			return false;
-		buf.insert(buf.end(),tmp,tmp + l);
-	}
-	if (!buf.empty()) {
-		if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_STATIC_IPS,(const char *)buf.data(),(int)buf.size()))
-			return false;
-	}
+		blob = &(d[ZT_NETWORKCONFIG_DICT_KEY_ROUTES]);
+		for (unsigned int i = 0; i < this->routeCount; ++i) {
+			int l = asInetAddress(this->routes[i].target).marshal(tmp);
+			if (l < 0)
+				return false;
+			blob->insert(blob->end(),tmp,tmp + l);
+			l = asInetAddress(this->routes[i].via).marshal(tmp);
+			if (l < 0)
+				return false;
+			blob->insert(blob->end(),tmp,tmp + l);
+		}
 
 
-	if (this->ruleCount) {
-		buf.resize(ruleCount * ZT_VIRTUALNETWORKRULE_MARSHAL_SIZE_MAX);
-		int l = Capability::marshalVirtualNetworkRules(buf.data(),rules,ruleCount);
-		if (l > 0) {
-			if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_RULES,(const char *)buf.data(),l))
+		blob = &(d[ZT_NETWORKCONFIG_DICT_KEY_STATIC_IPS]);
+		for (unsigned int i = 0; i < this->staticIpCount; ++i) {
+			int l = this->staticIps[i].marshal(tmp);
+			if (l < 0)
 				return false;
 				return false;
+			blob->insert(blob->end(),tmp,tmp + l);
+		}
+
+		blob = &(d[ZT_NETWORKCONFIG_DICT_KEY_RULES]);
+		if (this->ruleCount) {
+			blob->resize(ruleCount * ZT_VIRTUALNETWORKRULE_MARSHAL_SIZE_MAX);
+			int l = Capability::marshalVirtualNetworkRules(blob->data(),rules,ruleCount);
+			if (l > 0)
+				blob->resize(l);
 		}
 		}
-	}
 
 
-	return true;
+		return true;
+	} catch ( ... ) {}
+	return false;
 }
 }
 
 
 bool NetworkConfig::fromDictionary(const Dictionary &d)
 bool NetworkConfig::fromDictionary(const Dictionary &d)
 {
 {
 	static const NetworkConfig NIL_NC;
 	static const NetworkConfig NIL_NC;
-	ScopedPtr< Buffer<ZT_NETWORKCONFIG_DICT_CAPACITY> > tmp(new Buffer<ZT_NETWORKCONFIG_DICT_CAPACITY>());
-
 	try {
 	try {
 		*this = NIL_NC;
 		*this = NIL_NC;
 
 
-		// Fields that are always present, new or old
 		this->networkId = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID,0);
 		this->networkId = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID,0);
 		if (!this->networkId)
 		if (!this->networkId)
 			return false;
 			return false;
 		this->timestamp = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP,0);
 		this->timestamp = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP,0);
+		if (this->timestamp <= 0)
+			return false;
 		this->credentialTimeMaxDelta = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_CREDENTIAL_TIME_MAX_DELTA,0);
 		this->credentialTimeMaxDelta = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_CREDENTIAL_TIME_MAX_DELTA,0);
 		this->revision = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_REVISION,0);
 		this->revision = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_REVISION,0);
 		this->issuedTo = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO,0);
 		this->issuedTo = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO,0);
 		if (!this->issuedTo)
 		if (!this->issuedTo)
 			return false;
 			return false;
 		this->multicastLimit = (unsigned int)d.getUI(ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT,0);
 		this->multicastLimit = (unsigned int)d.getUI(ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT,0);
-		d.get(ZT_NETWORKCONFIG_DICT_KEY_NAME,this->name,sizeof(this->name));
-
+		d.getS(ZT_NETWORKCONFIG_DICT_KEY_NAME,this->name,sizeof(this->name));
 		this->mtu = (unsigned int)d.getUI(ZT_NETWORKCONFIG_DICT_KEY_MTU,ZT_DEFAULT_MTU);
 		this->mtu = (unsigned int)d.getUI(ZT_NETWORKCONFIG_DICT_KEY_MTU,ZT_DEFAULT_MTU);
 		if (this->mtu < 1280)
 		if (this->mtu < 1280)
 			this->mtu = 1280; // minimum MTU allowed by IPv6 standard and others
 			this->mtu = 1280; // minimum MTU allowed by IPv6 standard and others
@@ -185,87 +152,120 @@ bool NetworkConfig::fromDictionary(const Dictionary &d)
 		if (d.getUI(ZT_NETWORKCONFIG_DICT_KEY_VERSION,0) < 6) {
 		if (d.getUI(ZT_NETWORKCONFIG_DICT_KEY_VERSION,0) < 6) {
 			return false;
 			return false;
 		} else {
 		} else {
-			// Otherwise we can use the new fields
 			this->flags = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_FLAGS,0);
 			this->flags = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_FLAGS,0);
 			this->type = (ZT_VirtualNetworkType)d.getUI(ZT_NETWORKCONFIG_DICT_KEY_TYPE,(uint64_t)ZT_NETWORK_TYPE_PRIVATE);
 			this->type = (ZT_VirtualNetworkType)d.getUI(ZT_NETWORKCONFIG_DICT_KEY_TYPE,(uint64_t)ZT_NETWORK_TYPE_PRIVATE);
 
 
-			if (d.get(ZT_NETWORKCONFIG_DICT_KEY_COM,*tmp))
-				this->com.deserialize(*tmp,0);
+			const std::vector<uint8_t> *blob = &(d[ZT_NETWORKCONFIG_DICT_KEY_COM]);
+			if (!blob->empty()) {
+				if (this->com.unmarshal(blob->data(),(int)(blob->size()) < 0))
+					return false;
+			}
 
 
-			if (d.get(ZT_NETWORKCONFIG_DICT_KEY_CAPABILITIES,*tmp)) {
+			blob = &(d[ZT_NETWORKCONFIG_DICT_KEY_CAPABILITIES]);
+			if (!blob->empty()) {
 				try {
 				try {
 					unsigned int p = 0;
 					unsigned int p = 0;
-					while (p < tmp->size()) {
+					while (p < blob->size()) {
 						Capability cap;
 						Capability cap;
-						p += cap.deserialize(*tmp,p);
-						this->capabilities[this->capabilityCount++] = cap;
+						int l = cap.unmarshal(blob->data() + p,(int)(blob->size() - p));
+						if (l < 0)
+							return false;
+						p += l;
+						if (this->capabilityCount < ZT_MAX_NETWORK_CAPABILITIES)
+							this->capabilities[this->capabilityCount++] = cap;
 					}
 					}
 				} catch ( ... ) {}
 				} catch ( ... ) {}
 				std::sort(&(this->capabilities[0]),&(this->capabilities[this->capabilityCount]));
 				std::sort(&(this->capabilities[0]),&(this->capabilities[this->capabilityCount]));
 			}
 			}
 
 
-			if (d.get(ZT_NETWORKCONFIG_DICT_KEY_TAGS,*tmp)) {
+			blob = &(d[ZT_NETWORKCONFIG_DICT_KEY_TAGS]);
+			if (!blob->empty()) {
 				try {
 				try {
 					unsigned int p = 0;
 					unsigned int p = 0;
-					while (p < tmp->size()) {
+					while (p < blob->size()) {
 						Tag tag;
 						Tag tag;
-						p += tag.deserialize(*tmp,p);
-						this->tags[this->tagCount++] = tag;
+						int l = tag.unmarshal(blob->data() + p,(int)(blob->size() - p));
+						if (l < 0)
+							return false;
+						p += l;
+						if (this->tagCount < ZT_MAX_NETWORK_TAGS)
+							this->tags[this->tagCount++] = tag;
 					}
 					}
 				} catch ( ... ) {}
 				} catch ( ... ) {}
 				std::sort(&(this->tags[0]),&(this->tags[this->tagCount]));
 				std::sort(&(this->tags[0]),&(this->tags[this->tagCount]));
 			}
 			}
 
 
-			if (d.get(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATES_OF_OWNERSHIP,*tmp)) {
-				unsigned int p = 0;
-				while (p < tmp->size()) {
-					if (certificateOfOwnershipCount < ZT_MAX_CERTIFICATES_OF_OWNERSHIP)
-						p += certificatesOfOwnership[certificateOfOwnershipCount++].deserialize(*tmp,p);
-					else {
-						CertificateOfOwnership foo;
-						p += foo.deserialize(*tmp,p);
+			blob = &(d[ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATES_OF_OWNERSHIP]);
+			if (!blob->empty()) {
+				try {
+					unsigned int p = 0;
+					while (p < blob->size()) {
+						CertificateOfOwnership coo;
+						int l = coo.unmarshal(blob->data() + p,(int)(blob->size() - p));
+						if (l < 0)
+							return false;
+						p += l;
+						if (this->certificateOfOwnershipCount < ZT_MAX_CERTIFICATES_OF_OWNERSHIP)
+							this->certificatesOfOwnership[certificateOfOwnershipCount++] = coo;
 					}
 					}
-				}
+				} catch ( ... ) {}
+				std::sort(&(this->certificatesOfOwnership[0]),&(this->certificatesOfOwnership[this->certificateOfOwnershipCount]));
 			}
 			}
 
 
-			if (d.get(ZT_NETWORKCONFIG_DICT_KEY_SPECIALISTS,*tmp)) {
+			blob = &(d[ZT_NETWORKCONFIG_DICT_KEY_SPECIALISTS]);
+			if (!blob->empty()) {
 				unsigned int p = 0;
 				unsigned int p = 0;
-				while ((p + 8) <= tmp->size()) {
-					if (specialistCount < ZT_MAX_NETWORK_SPECIALISTS)
-						this->specialists[this->specialistCount++] = tmp->at<uint64_t>(p);
+				while (((p + 8) <= blob->size())&&(specialistCount < ZT_MAX_NETWORK_SPECIALISTS)) {
+					this->specialists[this->specialistCount++] = Utils::loadBigEndian<uint64_t>(blob->data() + p);
 					p += 8;
 					p += 8;
 				}
 				}
 			}
 			}
 
 
-			if (d.get(ZT_NETWORKCONFIG_DICT_KEY_ROUTES,*tmp)) {
+			blob = &(d[ZT_NETWORKCONFIG_DICT_KEY_ROUTES]);
+			if (!blob->empty()) {
 				unsigned int p = 0;
 				unsigned int p = 0;
-				while ((p < tmp->size())&&(routeCount < ZT_MAX_NETWORK_ROUTES)) {
-					p += reinterpret_cast<InetAddress *>(&(this->routes[this->routeCount].target))->deserialize(*tmp,p);
-					p += reinterpret_cast<InetAddress *>(&(this->routes[this->routeCount].via))->deserialize(*tmp,p);
-					this->routes[this->routeCount].flags = tmp->at<uint16_t>(p); p += 2;
-					this->routes[this->routeCount].metric = tmp->at<uint16_t>(p); p += 2;
+				while ((p < blob->size())&&(routeCount < ZT_MAX_NETWORK_ROUTES)) {
+					int l = asInetAddress(this->routes[this->routeCount].target).unmarshal(blob->data(),(int)(blob->size() - p));
+					if (l < 0)
+						return false;
+					p += l;
+					if (p >= blob->size())
+						return false;
+					l = asInetAddress(this->routes[this->routeCount].via).unmarshal(blob->data(),(int)(blob->size() - p));
+					if (l < 0)
+						return false;
+					p += l;
+					if ((p + 4) > blob->size())
+						return false;
+					this->routes[this->routeCount].flags = Utils::loadBigEndian<uint16_t>(blob->data() + p); p += 2;
+					this->routes[this->routeCount].metric = Utils::loadBigEndian<uint16_t>(blob->data() + p); p += 2;
 					++this->routeCount;
 					++this->routeCount;
 				}
 				}
 			}
 			}
 
 
-			if (d.get(ZT_NETWORKCONFIG_DICT_KEY_STATIC_IPS,*tmp)) {
+			blob = &(d[ZT_NETWORKCONFIG_DICT_KEY_STATIC_IPS]);
+			if (!blob->empty()) {
 				unsigned int p = 0;
 				unsigned int p = 0;
-				while ((p < tmp->size())&&(staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) {
-					p += this->staticIps[this->staticIpCount++].deserialize(*tmp,p);
+				while ((p < blob->size())&&(staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) {
+					int l = this->staticIps[this->staticIpCount].unmarshal(blob->data() + p,(int)(blob->size() - p));
+					if (l < 0)
+						return false;
+					p += l;
+					++this->staticIpCount;
 				}
 				}
 			}
 			}
 
 
-			if (d.get(ZT_NETWORKCONFIG_DICT_KEY_RULES,*tmp)) {
+			blob = &(d[ZT_NETWORKCONFIG_DICT_KEY_RULES]);
+			if (!blob->empty()) {
 				this->ruleCount = 0;
 				this->ruleCount = 0;
-				unsigned int p = 0;
-				Capability::deserializeRules(*tmp,p,this->rules,this->ruleCount,ZT_MAX_NETWORK_RULES);
+				if (Capability::unmarshalVirtualNetworkRules(blob->data(),(int)blob->size(),this->rules,this->ruleCount,ZT_MAX_NETWORK_RULES) < 0)
+					return false;
 			}
 			}
 		}
 		}
 
 
 		return true;
 		return true;
-	} catch ( ... ) {
-		return false;
-	}
+	} catch ( ... ) {}
+	return false;
 }
 }
 
 
 bool NetworkConfig::addSpecialist(const Address &a,const uint64_t f)
 bool NetworkConfig::addSpecialist(const Address &a,const uint64_t f)

+ 79 - 0
node/Utils.cpp

@@ -97,6 +97,85 @@ char *decimal(unsigned long n,char s[24])
 	return s;
 	return s;
 }
 }
 
 
+char *hex(uint8_t i,char s[3])
+{
+	s[0] = HEXCHARS[(i >> 4U) & 0xfU];
+	s[1] = HEXCHARS[i & 0xfU];
+	s[2] = 0;
+	return s;
+}
+
+char *hex(uint16_t i,char s[5])
+{
+	s[0] = HEXCHARS[(i >> 12U) & 0xfU];
+	s[1] = HEXCHARS[(i >> 8U) & 0xfU];
+	s[2] = HEXCHARS[(i >> 4U) & 0xfU];
+	s[3] = HEXCHARS[i & 0xfU];
+	s[4] = 0;
+	return s;
+}
+
+char *hex(uint32_t i,char s[9])
+{
+	s[0] = HEXCHARS[(i >> 28U) & 0xfU];
+	s[1] = HEXCHARS[(i >> 24U) & 0xfU];
+	s[2] = HEXCHARS[(i >> 20U) & 0xfU];
+	s[3] = HEXCHARS[(i >> 16U) & 0xfU];
+	s[4] = HEXCHARS[(i >> 12U) & 0xfU];
+	s[5] = HEXCHARS[(i >> 8U) & 0xfU];
+	s[6] = HEXCHARS[(i >> 4U) & 0xfU];
+	s[7] = HEXCHARS[i & 0xfU];
+	s[8] = 0;
+	return s;
+}
+
+char *hex(uint64_t i,char s[17])
+{
+	s[0] = HEXCHARS[(i >> 60U) & 0xfU];
+	s[1] = HEXCHARS[(i >> 56U) & 0xfU];
+	s[2] = HEXCHARS[(i >> 52U) & 0xfU];
+	s[3] = HEXCHARS[(i >> 48U) & 0xfU];
+	s[4] = HEXCHARS[(i >> 44U) & 0xfU];
+	s[5] = HEXCHARS[(i >> 40U) & 0xfU];
+	s[6] = HEXCHARS[(i >> 36U) & 0xfU];
+	s[7] = HEXCHARS[(i >> 32U) & 0xfU];
+	s[8] = HEXCHARS[(i >> 28U) & 0xfU];
+	s[9] = HEXCHARS[(i >> 24U) & 0xfU];
+	s[10] = HEXCHARS[(i >> 20U) & 0xfU];
+	s[11] = HEXCHARS[(i >> 16U) & 0xfU];
+	s[12] = HEXCHARS[(i >> 12U) & 0xfU];
+	s[13] = HEXCHARS[(i >> 8U) & 0xfU];
+	s[14] = HEXCHARS[(i >> 4U) & 0xfU];
+	s[15] = HEXCHARS[i & 0xfU];
+	s[16] = 0;
+	return s;
+}
+
+uint64_t unhex(const char *s)
+{
+	uint64_t n = 0;
+	if (s) {
+		int k = 0;
+		while (k < 16) {
+			char hc = *(s++);
+			if (!hc) break;
+
+			uint8_t c = 0;
+			if ((hc >= 48)&&(hc <= 57))
+				c = hc - 48;
+			else if ((hc >= 97)&&(hc <= 102))
+				c = hc - 87;
+			else if ((hc >= 65)&&(hc <= 70))
+				c = hc - 55;
+
+			n <<= 4U;
+			n |= (uint64_t)c;
+			++k;
+		}
+	}
+	return n;
+}
+
 char *hex10(uint64_t i,char s[11])
 char *hex10(uint64_t i,char s[11])
 {
 {
 	s[0] = HEXCHARS[(i >> 36U) & 0xfU];
 	s[0] = HEXCHARS[(i >> 36U) & 0xfU];

+ 12 - 11
node/Utils.hpp

@@ -85,17 +85,18 @@ char *decimal(unsigned long n,char s[24]);
  * @param s Buffer to receive hex, must be at least (2*sizeof(i))+1 in size or overflow will occur.
  * @param s Buffer to receive hex, must be at least (2*sizeof(i))+1 in size or overflow will occur.
  * @return Pointer to s containing hex string with trailing zero byte
  * @return Pointer to s containing hex string with trailing zero byte
  */
  */
-template<typename I>
-static ZT_ALWAYS_INLINE char *hex(I x,char *s)
-{
-	char *const r = s;
-	for(unsigned int i=0,b=(sizeof(x)*8);i<sizeof(x);++i) {
-		*(s++) = HEXCHARS[(x >> (b -= 4)) & 0xf];
-		*(s++) = HEXCHARS[(x >> (b -= 4)) & 0xf];
-	}
-	*s = (char)0;
-	return r;
-}
+char *hex(uint8_t i,char s[3]);
+char *hex(uint16_t i,char s[5]);
+char *hex(uint32_t i,char s[9]);
+char *hex(uint64_t i,char s[17]);
+
+/**
+ * Decode an unsigned integer in hex format
+ *
+ * @param s String to decode, non-hex chars are ignored
+ * @return Unsigned integer
+ */
+uint64_t unhex(const char *s);
 
 
 /**
 /**
  * Convert the least significant 40 bits of a uint64_t to hex
  * Convert the least significant 40 bits of a uint64_t to hex