Browse Source

Starting to (re)write unit tests.

Adam Ierymenko 5 years ago
parent
commit
de1b54821e
9 changed files with 216 additions and 68 deletions
  1. 28 11
      node/Buf.cpp
  2. 5 0
      node/Buf.hpp
  3. 2 0
      node/CMakeLists.txt
  4. 6 4
      node/Expect.hpp
  5. 2 2
      node/Identity.hpp
  6. 2 47
      node/Protocol.cpp
  7. 91 0
      node/Tests.cpp
  8. 78 0
      node/Tests.h
  9. 2 4
      node/TriviallyCopyable.hpp

+ 28 - 11
node/Buf.cpp

@@ -17,10 +17,15 @@
 #define sched_yield() Sleep(0)
 #define sched_yield() Sleep(0)
 #endif
 #endif
 
 
+// Sanity limit on maximum buffer pool size
+#define ZT_BUF_MAX_POOL_SIZE 1024
+
 namespace ZeroTier {
 namespace ZeroTier {
 
 
 static std::atomic<uintptr_t> s_pool(0);
 static std::atomic<uintptr_t> s_pool(0);
+static std::atomic<long> s_allocated(0);
 
 
+// uintptr_max can never be a valid pointer, so use it to indicate that s_pool is locked (very short duration spinlock)
 #define ZT_ATOMIC_PTR_LOCKED (~((uintptr_t)0))
 #define ZT_ATOMIC_PTR_LOCKED (~((uintptr_t)0))
 
 
 void *Buf::operator new(std::size_t sz)
 void *Buf::operator new(std::size_t sz)
@@ -35,13 +40,14 @@ void *Buf::operator new(std::size_t sz)
 
 
 	Buf *b;
 	Buf *b;
 	if (bb) {
 	if (bb) {
+		s_pool.store(((Buf *)bb)->__nextInPool);
 		b = (Buf *)bb;
 		b = (Buf *)bb;
-		s_pool.store(b->__nextInPool);
 	} else {
 	} else {
 		s_pool.store(0);
 		s_pool.store(0);
 		b = (Buf *)malloc(sz);
 		b = (Buf *)malloc(sz);
 		if (!b)
 		if (!b)
 			throw std::bad_alloc();
 			throw std::bad_alloc();
+		++s_allocated;
 	}
 	}
 
 
 	b->__refCount.store(0);
 	b->__refCount.store(0);
@@ -51,16 +57,21 @@ void *Buf::operator new(std::size_t sz)
 void Buf::operator delete(void *ptr)
 void Buf::operator delete(void *ptr)
 {
 {
 	if (ptr) {
 	if (ptr) {
-		uintptr_t bb;
-		for (;;) {
-			bb = s_pool.exchange(ZT_ATOMIC_PTR_LOCKED);
-			if (bb != ZT_ATOMIC_PTR_LOCKED)
-				break;
-			sched_yield();
-		}
+		if (s_allocated.load() > ZT_BUF_MAX_POOL_SIZE) {
+			--s_allocated;
+			free(ptr);
+		} else {
+			uintptr_t bb;
+			for (;;) {
+				bb = s_pool.exchange(ZT_ATOMIC_PTR_LOCKED);
+				if (bb != ZT_ATOMIC_PTR_LOCKED)
+					break;
+				sched_yield();
+			}
 
 
-		((Buf *)ptr)->__nextInPool = bb;
-		s_pool.store((uintptr_t)ptr);
+			((Buf *)ptr)->__nextInPool.store(bb);
+			s_pool.store((uintptr_t)ptr);
+		}
 	}
 	}
 }
 }
 
 
@@ -73,13 +84,19 @@ void Buf::freePool() noexcept
 			break;
 			break;
 		sched_yield();
 		sched_yield();
 	}
 	}
+	s_allocated.store(0);
 	s_pool.store(0);
 	s_pool.store(0);
 
 
 	while (bb != 0) {
 	while (bb != 0) {
-		uintptr_t next = ((Buf *)bb)->__nextInPool;
+		const uintptr_t next = ((Buf *)bb)->__nextInPool;
 		free((void *)bb);
 		free((void *)bb);
 		bb = next;
 		bb = next;
 	}
 	}
 }
 }
 
 
+long Buf::poolAllocated() noexcept
+{
+	return s_allocated.load();
+}
+
 } // namespace ZeroTier
 } // namespace ZeroTier

+ 5 - 0
node/Buf.hpp

@@ -95,6 +95,11 @@ public:
 	 */
 	 */
 	static void freePool() noexcept;
 	static void freePool() noexcept;
 
 
+	/**
+	 * @return Number of Buf objects currently allocated via pool mechanism
+	 */
+	static long poolAllocated() noexcept;
+
 	/**
 	/**
 	 * Slice is almost exactly like the built-in slice data structure in Go
 	 * Slice is almost exactly like the built-in slice data structure in Go
 	 */
 	 */

+ 2 - 0
node/CMakeLists.txt

@@ -41,6 +41,7 @@ set(core_headers
 	SHA512.hpp
 	SHA512.hpp
 	SharedPtr.hpp
 	SharedPtr.hpp
 	Tag.hpp
 	Tag.hpp
+	Tests.h
 	Topology.hpp
 	Topology.hpp
 	Trace.hpp
 	Trace.hpp
 	TriviallyCopyable.hpp
 	TriviallyCopyable.hpp
@@ -77,6 +78,7 @@ set(core_src
 	SelfAwareness.cpp
 	SelfAwareness.cpp
 	SHA512.cpp
 	SHA512.cpp
 	Tag.cpp
 	Tag.cpp
+	Tests.cpp
 	Topology.cpp
 	Topology.cpp
 	Trace.cpp
 	Trace.cpp
 	Utils.cpp
 	Utils.cpp

+ 6 - 4
node/Expect.hpp

@@ -37,7 +37,7 @@
  * Making this a power of two improves efficiency a little by allowing bit
  * Making this a power of two improves efficiency a little by allowing bit
  * shift division.
  * shift division.
  */
  */
-#define ZT_EXPECT_TTL 4096
+#define ZT_EXPECT_TTL 4096LL
 
 
 namespace ZeroTier {
 namespace ZeroTier {
 
 
@@ -57,7 +57,7 @@ public:
 	 */
 	 */
 	ZT_ALWAYS_INLINE void sending(const uint64_t packetId,const int64_t now) noexcept
 	ZT_ALWAYS_INLINE void sending(const uint64_t packetId,const int64_t now) noexcept
 	{
 	{
-		_packetIdSent[Utils::hash64(packetId ^ _salt) % ZT_EXPECT_BUCKETS] = (int32_t)(now / ZT_EXPECT_TTL);
+		_packetIdSent[Utils::hash64(packetId ^ _salt) % ZT_EXPECT_BUCKETS].store((int32_t)(now / ZT_EXPECT_TTL));
 	}
 	}
 
 
 	/**
 	/**
@@ -69,12 +69,14 @@ public:
 	 */
 	 */
 	ZT_ALWAYS_INLINE bool expecting(const uint64_t inRePacketId,const int64_t now) const noexcept
 	ZT_ALWAYS_INLINE bool expecting(const uint64_t inRePacketId,const int64_t now) const noexcept
 	{
 	{
-		return ((((int32_t)(now / ZT_EXPECT_TTL)) - _packetIdSent[Utils::hash64(inRePacketId ^ _salt) % ZT_EXPECT_BUCKETS]) <= 1);
+		return (((now / ZT_EXPECT_TTL) - (int64_t)_packetIdSent[Utils::hash64(inRePacketId ^ _salt) % ZT_EXPECT_BUCKETS].load()) <= 1);
 	}
 	}
 
 
 private:
 private:
-	// This is a static per-object salt that's XORed and mixed with the packet ID
+	// This is a static per-runtime salt that's XORed and mixed with the packet ID
 	// to make it difficult for a third party to predict expected-reply buckets.
 	// to make it difficult for a third party to predict expected-reply buckets.
+	// Such prediction would not be catastrophic but it's easy and good to harden
+	// against it.
 	const uint64_t _salt;
 	const uint64_t _salt;
 
 
 	// Each bucket contains a timestamp in units of the expect duration.
 	// Each bucket contains a timestamp in units of the expect duration.

+ 2 - 2
node/Identity.hpp

@@ -205,6 +205,8 @@ public:
 	 */
 	 */
 	explicit ZT_ALWAYS_INLINE operator bool() const noexcept { return (_address); }
 	explicit ZT_ALWAYS_INLINE operator bool() const noexcept { return (_address); }
 
 
+	ZT_ALWAYS_INLINE unsigned long hashCode() const noexcept { return _hash.hashCode(); }
+
 	ZT_ALWAYS_INLINE bool operator==(const Identity &id) const noexcept
 	ZT_ALWAYS_INLINE bool operator==(const Identity &id) const noexcept
 	{
 	{
 		if ((_address == id._address)&&(_type == id._type)) {
 		if ((_address == id._address)&&(_type == id._type)) {
@@ -238,8 +240,6 @@ public:
 	ZT_ALWAYS_INLINE bool operator<=(const Identity &id) const noexcept { return !(id < *this); }
 	ZT_ALWAYS_INLINE bool operator<=(const Identity &id) const noexcept { return !(id < *this); }
 	ZT_ALWAYS_INLINE bool operator>=(const Identity &id) const noexcept { return !(*this < id); }
 	ZT_ALWAYS_INLINE bool operator>=(const Identity &id) const noexcept { return !(*this < id); }
 
 
-	ZT_ALWAYS_INLINE unsigned long hashCode() const noexcept { return ((unsigned long)_address.toInt() + (unsigned long)_pub.c25519[0] + (unsigned long)_pub.c25519[1] + (unsigned long)_pub.c25519[2]); }
-
 	static constexpr int marshalSizeMax() noexcept { return ZT_IDENTITY_MARSHAL_SIZE_MAX; }
 	static constexpr int marshalSizeMax() noexcept { return ZT_IDENTITY_MARSHAL_SIZE_MAX; }
 	int marshal(uint8_t data[ZT_IDENTITY_MARSHAL_SIZE_MAX],bool includePrivate = false) const noexcept;
 	int marshal(uint8_t data[ZT_IDENTITY_MARSHAL_SIZE_MAX],bool includePrivate = false) const noexcept;
 	int unmarshal(const uint8_t *data,int len) noexcept;
 	int unmarshal(const uint8_t *data,int len) noexcept;

+ 2 - 47
node/Protocol.cpp

@@ -16,52 +16,10 @@
 #include "Utils.hpp"
 #include "Utils.hpp"
 
 
 #include <cstdlib>
 #include <cstdlib>
-#include <stdexcept>
-
-#if defined(__GCC__) && (defined(__amd64) || defined(__amd64__) || defined(__x86_64) || defined(__x86_64__) || defined(__AMD64) || defined(__AMD64__) || defined(_M_X64))
-#define ZT_PACKET_USE_ATOMIC_INTRINSICS
-#endif
-#ifndef ZT_PACKET_USE_ATOMIC_INTRINSICS
-#include <atomic>
-#endif
 
 
 namespace ZeroTier {
 namespace ZeroTier {
 namespace Protocol {
 namespace Protocol {
 
 
-namespace {
-
-unsigned long long _initPacketID()
-{
-	unsigned long long tmp = 0;
-	Utils::getSecureRandom(&tmp,sizeof(tmp));
-	tmp >>= 31U;
-	tmp |= (((uint64_t)time(nullptr)) & 0xffffffffULL) << 33U;
-	return tmp;
-}
-#ifdef ZT_PACKET_USE_ATOMIC_INTRINSICS
-unsigned long long _packetIdCtr = _initPacketID();
-#else
-static std::atomic<unsigned long long> _packetIdCtr(_initPacketID());
-#endif
-
-uintptr_t _checkSizes()
-{
-	// These are compiled time checked assertions that make sure our platform/compiler is sane
-	// and that packed structures are working properly.
-	if (ZT_PROTO_MAX_PACKET_LENGTH > ZT_BUF_MEM_SIZE)
-		throw std::runtime_error("ZT_PROTO_MAX_PACKET_LENGTH > ZT_BUF_MEM_SIZE");
-	if (sizeof(Header) != ZT_PROTO_MIN_PACKET_LENGTH)
-		throw std::runtime_error("sizeof(Header) != ZT_PROTO_MIN_PACKET_LENGTH");
-	if (sizeof(FragmentHeader) != ZT_PROTO_MIN_FRAGMENT_LENGTH)
-		throw std::runtime_error("sizeof(FragmentHeader) != ZT_PROTO_MIN_FRAGMENT_LENGTH");
-	return (uintptr_t)Utils::getSecureRandomU64(); // also prevents compiler from optimizing out
-}
-
-} // anonymous namespace
-
-// Make compiler compile and "run" _checkSizes()
-volatile uintptr_t _checkSizesIMeanIt = _checkSizes();
-
 uint64_t createProbe(const Identity &sender,const Identity &recipient,const uint8_t key[ZT_PEER_SECRET_KEY_LENGTH]) noexcept
 uint64_t createProbe(const Identity &sender,const Identity &recipient,const uint8_t key[ZT_PEER_SECRET_KEY_LENGTH]) noexcept
 {
 {
 	uint8_t tmp[ZT_IDENTITY_HASH_SIZE + ZT_IDENTITY_HASH_SIZE];
 	uint8_t tmp[ZT_IDENTITY_HASH_SIZE + ZT_IDENTITY_HASH_SIZE];
@@ -74,11 +32,8 @@ uint64_t createProbe(const Identity &sender,const Identity &recipient,const uint
 
 
 uint64_t getPacketId() noexcept
 uint64_t getPacketId() noexcept
 {
 {
-#ifdef ZT_PACKET_USE_ATOMIC_INTRINSICS
-	return __sync_add_and_fetch(&_packetIdCtr,1ULL);
-#else
-	return ++_packetIdCtr;
-#endif
+	static std::atomic<uint64_t> s_packetIdCtr(Utils::getSecureRandomU64());
+	return ++s_packetIdCtr;
 }
 }
 
 
 void armor(Buf &pkt,int packetSize,const uint8_t key[ZT_PEER_SECRET_KEY_LENGTH],uint8_t cipherSuite) noexcept
 void armor(Buf &pkt,int packetSize,const uint8_t key[ZT_PEER_SECRET_KEY_LENGTH],uint8_t cipherSuite) noexcept

File diff suppressed because it is too large
+ 91 - 0
node/Tests.cpp


+ 78 - 0
node/Tests.h

@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+/****/
+
+/*
+ * This header and its implementation in Tests.cpp contain assertion tests,
+ * self-tests, cryptographic tests, and fuzzing for the ZeroTier core.
+ *
+ * To build these ensure that ZT_ENABLE_TESTS is defined during build time.
+ * Otherwise they are omitted.
+ *
+ * These symbols are defined extern "C" so tests can be called from regular
+ * C code, which is important for use via CGo or in plain C projects.
+ *
+ * The ZT_T_PRINTF macro defaults to printf() but if it's defined at compile
+ * time (also must be set while building Tests.cpp) it can specify another
+ * function to use for output. Defining it to a no-op can be used to disable
+ * output.
+ *
+ * Each test function returns NULL if the tests succeeds or an error message
+ * on test failure.
+ *
+ * Only the fuzzing test functions are thread-safe. Other test functions
+ * should not be called concurrently. It's okay to call different tests from
+ * different threads, but not the same test.
+ */
+
+#ifndef ZT_TESTS_HPP
+#define ZT_TESTS_HPP
+
+#define ZT_ENABLE_TESTS
+
+#ifdef ZT_ENABLE_TESTS
+
+#ifdef __cplusplus
+#include <cstdint>
+#include <cstdio>
+extern "C" {
+#else
+#include <stdint.h>
+#include <stdio.h>
+#endif
+
+#ifndef ZT_T_PRINTF
+#define ZT_T_PRINTF(fmt,...) printf((fmt),##__VA_ARGS__),fflush(stdout)
+#endif
+
+// Basic self-tests ---------------------------------------------------------------------------------------------------
+
+const char *ZTT_general();
+const char *ZTT_crypto();
+const char *ZTT_defragmenter();
+
+// Benchmarks ---------------------------------------------------------------------------------------------------------
+
+const char *ZTT_benchmarkCrypto();
+
+// Fuzzing ------------------------------------------------------------------------------------------------------------
+
+
+// --------------------------------------------------------------------------------------------------------------------
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ZT_ENABLE_TESTS
+
+#endif

+ 2 - 4
node/TriviallyCopyable.hpp

@@ -23,11 +23,9 @@
 namespace ZeroTier {
 namespace ZeroTier {
 
 
 /**
 /**
- * This is a class that others can inherit from to tag themselves as safe to abuse in C-like ways with memcpy, etc.
+ * Classes inheriting from this base class are safe to abuse in C-like ways: memcpy, memset, etc.
  *
  *
- * Later versions of C++ have a built-in auto-detected notion like this, but
- * this is more explicit and its use will make audits for memory safety
- * a lot easier.
+ * It also includes some static methods to do this conveniently.
  */
  */
 class TriviallyCopyable
 class TriviallyCopyable
 {
 {

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