#include "STDInclude.hpp"

namespace Components
{
	Utils::Cryptography::ECC::Key Node::SignatureKey;
	std::vector<Node::NodeEntry> Node::Nodes;
	std::vector<Node::ClientSession> Node::Sessions;

	void Node::LoadNodePreset()
	{
		FileSystem::File defaultNodes("nodes_default.dat");
		if (!defaultNodes.Exists()) return;

		auto buffer = defaultNodes.GetBuffer();
		Utils::String::Replace(buffer, "\r", "");

		auto nodes = Utils::String::Explode(buffer, '\n');
		for (auto node : nodes)
		{
			if (!node.empty())
			{
				Node::AddNode(node);
			}
		}
	}

	void Node::LoadNodes()
	{
		Proto::Node::List list;
		std::string nodes = Utils::IO::ReadFile("players/nodes.dat");
		if (nodes.empty() || !list.ParseFromString(nodes)) return;

		for (int i = 0; i < list.address_size(); ++i)
		{
			Node::AddNode(list.address(i));
		}
	}
	void Node::StoreNodes(bool force)
	{
		if (Dedicated::IsEnabled() && Dvar::Var("sv_lanOnly").Get<bool>()) return;

		static int lastStorage = 0;

		// Don't store nodes if the delta is too small and were not forcing it
		if (((Game::Sys_Milliseconds() - lastStorage) < NODE_STORE_INTERVAL && !force) || !Node::GetValidNodeCount()) return;
		lastStorage = Game::Sys_Milliseconds();

		Proto::Node::List list;

		// This is obsolete when storing to file.
		// However, defining another proto message due to this would be redundant.
		//list.set_is_dedi(Dedicated::IsDedicated());

		for (auto node : Node::Nodes)
		{
			if (node.state == Node::STATE_VALID && node.registered)
			{
				node.address.Serialize(list.add_address());
			}
		}

		CreateDirectoryW(L"players", NULL);
		Utils::IO::WriteFile("players/nodes.dat", list.SerializeAsString());
	}

	Node::NodeEntry* Node::FindNode(Network::Address address)
	{
		for (auto i = Node::Nodes.begin(); i != Node::Nodes.end(); ++i)
		{
			if (i->address == address)
			{
				return &(*i);
			}
		}

		return nullptr;
	}
	Node::ClientSession* Node::FindSession(Network::Address address)
	{
		for (auto i = Node::Sessions.begin(); i != Node::Sessions.end(); ++i)
		{
			if (i->address == address)
			{
				return &(*i);
			}
		}

		return nullptr;
	}

	unsigned int Node::GetValidNodeCount()
	{
		unsigned int count = 0;

		for (auto node : Node::Nodes)
		{
			if (node.state == Node::STATE_VALID)
			{
				++count;
			}
		}

		return count;
	}

	void Node::AddNode(Network::Address address)
	{
#ifdef DEBUG
		if (!address.IsValid() || address.IsSelf()) return;
#else
		if (!address.IsValid() || address.IsLocal() || address.IsSelf()) return;
#endif

		Node::NodeEntry* existingEntry = Node::FindNode(address);
		if (existingEntry)
		{
			existingEntry->lastHeard = Game::Sys_Milliseconds();
		}
		else
		{
			Node::NodeEntry entry;

			entry.lastHeard = Game::Sys_Milliseconds();
			entry.lastTime = 0;
			entry.lastListQuery = 0;
			entry.registered = false;
			entry.state = Node::STATE_UNKNOWN;
			entry.address = address;
			entry.challenge.clear();

			Node::Nodes.push_back(entry);

#ifdef DEBUG
			Logger::Print("Adding node %s...\n", address.GetCString());
#endif
		}
	}

	void Node::SendNodeList(Network::Address address)
	{
		if (address.IsSelf()) return;

		Proto::Node::List list;
		list.set_is_dedi(Dedicated::IsEnabled());
		list.set_protocol(PROTOCOL);
		list.set_version(NODE_VERSION);

		for (auto node : Node::Nodes)
		{
			if (node.state == Node::STATE_VALID && node.registered)
			{
				node.address.Serialize(list.add_address());
			}

			if (list.address_size() >= NODE_PACKET_LIMIT)
			{
				Network::SendCommand(address, "nodeListResponse", list.SerializeAsString());
				list.clear_address();
			}
		}

		// Even if we send an empty list, we have to tell the client about our dedi-status
		// If the amount of servers we have modulo the NODE_PACKET_LIMIT equals 0, we will send this request without any servers, so it's obsolete, but meh...
		Network::SendCommand(address, "nodeListResponse", list.SerializeAsString());
	}

	void Node::DeleteInvalidSessions()
	{
		for (auto i = Node::Sessions.begin(); i != Node::Sessions.end();)
		{
			if (i->lastTime <= 0 || (Game::Sys_Milliseconds() - i->lastTime) > SESSION_TIMEOUT)
			{
				i = Node::Sessions.erase(i);
			}
			else
			{
				++i;
			}
		}
	}

	void Node::DeleteInvalidNodes()
	{
		std::vector<Node::NodeEntry> cleanNodes;

		for (auto node : Node::Nodes)
		{
			if (node.state == Node::STATE_INVALID && (Game::Sys_Milliseconds() - node.lastHeard) > NODE_INVALID_DELETE)
			{
#ifdef DEBUG
				Logger::Print("Removing invalid node %s\n", node.address.GetCString());
#endif
			}
			else
			{
				cleanNodes.push_back(node);
			}
		}

		if (cleanNodes.size() != Node::Nodes.size())
		{
			//Node::Nodes.clear();
			//Utils::Merge(&Node::Nodes, cleanNodes);
			Node::Nodes = cleanNodes;
		}
	}

	void Node::SyncNodeList()
	{
		for (auto& node : Node::Nodes)
		{
			if (node.state == Node::STATE_VALID && node.registered)
			{
				node.state = Node::STATE_UNKNOWN;
				node.registered = false;
			}
		}
	}

	void Node::PerformRegistration(Network::Address address)
	{
		Node::NodeEntry* entry = Node::FindNode(address);
		if (!entry) return;

		entry->lastTime = Game::Sys_Milliseconds();

		if (Dedicated::IsEnabled())
		{
			entry->challenge = fmt::sprintf("%X", Utils::Cryptography::Rand::GenerateInt());

			Proto::Node::Packet packet;
			packet.set_challenge(entry->challenge);

#ifdef DEBUG
			Logger::Print("Sending registration request to %s\n", entry->address.GetCString());
#endif
			Network::SendCommand(entry->address, "nodeRegisterRequest", packet.SerializeAsString());
		}
		else
		{
#ifdef DEBUG
			Logger::Print("Sending session request to %s\n", entry->address.GetCString());
#endif
			Network::SendCommand(entry->address, "sessionRequest");
		}
	}

	void Node::FrameHandler()
	{
		if (Dedicated::IsEnabled() && Dvar::Var("sv_lanOnly").Get<bool>()) return;

		// Frame limit
		static int lastFrame = 0;
		if ((Game::Sys_Milliseconds() - lastFrame) < (1000 / NODE_FRAME_LOCK) || Game::Sys_Milliseconds() < 5000) return;
		lastFrame = Game::Sys_Milliseconds();

		int registerCount = 0;
		int listQueryCount = 0;

		for (auto &node : Node::Nodes)
		{
			// TODO: Decide how to handle nodes that were already registered, but timed out re-registering.
			if (node.state == STATE_NEGOTIATING && (Game::Sys_Milliseconds() - node.lastTime) > (NODE_QUERY_TIMEOUT))
			{
				node.registered = false; // Definitely unregister here!
				node.state = Node::STATE_INVALID;
				node.lastHeard = Game::Sys_Milliseconds();
				node.lastTime = Game::Sys_Milliseconds();

#ifdef DEBUG
				Logger::Print("Node negotiation timed out. Invalidating %s\n", node.address.GetCString());
#endif
			}

			if (registerCount < NODE_FRAME_QUERY_LIMIT)
			{
				// Register when unregistered and in UNKNOWN state (I doubt it's possible to be unregistered and in VALID state)
				if (!node.registered && (node.state != Node::STATE_NEGOTIATING && node.state != Node::STATE_INVALID))
				{
					++registerCount;
					node.state = Node::STATE_NEGOTIATING;
					Node::PerformRegistration(node.address);
				}
				// Requery invalid nodes within the NODE_QUERY_INTERVAL
				// This is required, as a node might crash, which causes it to be invalid.
				// If it's restarted though, we wouldn't query it again.

				// But wouldn't it send a registration request to us?
				// Not sure if the code below is necessary...
				// Well, it might be possible that this node doesn't know use anymore. Anyways, just keep that code here...

				// Nvm, this is required for clients, as nodes don't send registration requests to clients.
				else if (node.state == STATE_INVALID && (Game::Sys_Milliseconds() - node.lastTime) > NODE_QUERY_INTERVAL)
				{
					++registerCount;
					Node::PerformRegistration(node.address);
				}
			}

			if (listQueryCount < NODE_FRAME_QUERY_LIMIT)
			{
				if (node.registered && node.state == Node::STATE_VALID && (!node.lastListQuery || (Game::Sys_Milliseconds() - node.lastListQuery) > NODE_QUERY_INTERVAL))
				{
					++listQueryCount;
					node.state = Node::STATE_NEGOTIATING;
					node.lastTime = Game::Sys_Milliseconds();
					node.lastListQuery = Game::Sys_Milliseconds();

					if (Dedicated::IsEnabled())
					{
						Network::SendCommand(node.address, "nodeListRequest");
					}
					else
					{
						Network::SendCommand(node.address, "sessionRequest");
					}
				}
			}
		}

		static int lastCheck = 0;
		if ((Game::Sys_Milliseconds() - lastCheck) < 1000) return;
		lastCheck = Game::Sys_Milliseconds();

		Node::DeleteInvalidSessions();
		Node::DeleteInvalidNodes();
		Node::StoreNodes(false);
	}

	const char* Node::GetStateName(EntryState state)
	{
		switch (state)
		{
		case Node::STATE_UNKNOWN:
			return "Unknown";

		case Node::STATE_NEGOTIATING:
			return "Negotiating";

		case Node::STATE_INVALID:
			return "Invalid";

		case Node::STATE_VALID:
			return "Valid";
		}

		return "";
	}

	Node::Node()
	{
		Node::Nodes.clear();

		// ZoneBuilder doesn't require node stuff
		if (ZoneBuilder::IsEnabled()) return;

		// Generate our ECDSA key
		Node::SignatureKey = Utils::Cryptography::ECC::GenerateKey(512);

		// Load stored nodes
		Dvar::OnInit([] ()
		{
			Node::LoadNodePreset();
			Node::LoadNodes();
		});

		// Send deadline when shutting down
		if (Dedicated::IsEnabled())
		{
			QuickPatch::OnShutdown([] ()
			{
				if (Dvar::Var("sv_lanOnly").Get<bool>()) return;

				std::string challenge = fmt::sprintf("%X", Utils::Cryptography::Rand::GenerateInt());

				Proto::Node::Packet packet;
				packet.set_challenge(challenge);
				packet.set_signature(Utils::Cryptography::ECC::SignMessage(Node::SignatureKey, challenge));

				for (auto node : Node::Nodes)
				{
					Network::SendCommand(node.address, "nodeDeregister", packet.SerializeAsString());
				}
			});

			// This is the handler that accepts registration requests from other nodes
			// If you want to get accepted as node, you have to send a request to this handler
			Network::Handle("nodeRegisterRequest", [] (Network::Address address, std::string data)
			{
				if (Dvar::Var("sv_lanOnly").Get<bool>()) return;

				Node::NodeEntry* entry = Node::FindNode(address);

				// Create a new entry, if we don't already know it
				if (!entry)
				{
					Node::AddNode(address);
					entry = Node::FindNode(address);
					if (!entry) return;
				}

#ifdef DEBUG
				Logger::Print("Received registration request from %s\n", address.GetCString());
#endif

				Proto::Node::Packet packet;
				if (!packet.ParseFromString(data)) return;
				if (packet.challenge().empty()) return;

				std::string signature = Utils::Cryptography::ECC::SignMessage(Node::SignatureKey, packet.challenge());
				std::string challenge = fmt::sprintf("%X", Utils::Cryptography::Rand::GenerateInt());

				// The challenge this client sent is exactly the challenge we stored for this client
				// That means this is us, so we're going to ignore us :P
				if (packet.challenge() == entry->challenge)
				{
					entry->lastHeard = Game::Sys_Milliseconds();
					entry->lastTime = Game::Sys_Milliseconds();
					entry->registered = false;
					entry->state = Node::STATE_INVALID;
					return;
				}

				packet.Clear();
				packet.set_challenge(challenge);
				packet.set_signature(signature);
				packet.set_publickey(Node::SignatureKey.GetPublicKey());

				entry->lastTime = Game::Sys_Milliseconds();
				entry->challenge = challenge;
				entry->state = Node::STATE_NEGOTIATING;

				Network::SendCommand(address, "nodeRegisterSynchronize", packet.SerializeAsString());
			});

			Network::Handle("nodeRegisterSynchronize", [] (Network::Address address, std::string data)
			{
				if (Dvar::Var("sv_lanOnly").Get<bool>()) return;

				Node::NodeEntry* entry = Node::FindNode(address);
				if (!entry || entry->state != Node::STATE_NEGOTIATING) return;

#ifdef DEBUG
				Logger::Print("Received synchronization data for registration from %s!\n", address.GetCString());
#endif

				Proto::Node::Packet packet;
				if (!packet.ParseFromString(data)) return;
				if (packet.challenge().empty()) return;
				if (packet.publickey().empty()) return;
				if (packet.signature().empty()) return;

				std::string challenge = packet.challenge();
				std::string publicKey = packet.publickey();
				std::string signature = packet.signature();

				// Verify signature
				entry->publicKey.Set(publicKey);
				if (!Utils::Cryptography::ECC::VerifyMessage(entry->publicKey, entry->challenge, signature))
				{
					Logger::Print("Signature from %s for challenge '%s' is invalid!\n", address.GetCString(), entry->challenge.data());
					return;
				}

				// Mark as registered
				entry->lastTime = Game::Sys_Milliseconds();
				entry->state = Node::STATE_VALID;
				entry->registered = true;

#ifdef DEBUG
				Logger::Print("Signature from %s for challenge '%s' is valid!\n", address.GetCString(), entry->challenge.data());
				Logger::Print("Node %s registered\n", address.GetCString());
#endif

				// Build response
				publicKey = Node::SignatureKey.GetPublicKey();
				signature = Utils::Cryptography::ECC::SignMessage(Node::SignatureKey, challenge);

				packet.Clear();
				packet.set_signature(signature);
				packet.set_publickey(publicKey);

				Network::SendCommand(address, "nodeRegisterAcknowledge", packet.SerializeAsString());
			});

			Network::Handle("nodeRegisterAcknowledge", [] (Network::Address address, std::string data)
			{
				if (Dvar::Var("sv_lanOnly").Get<bool>()) return;

				// Ignore requests from nodes we don't know
				Node::NodeEntry* entry = Node::FindNode(address);
				if (!entry || entry->state != Node::STATE_NEGOTIATING) return;

#ifdef DEBUG
				Logger::Print("Received acknowledgment from %s\n", address.GetCString());
#endif

				Proto::Node::Packet packet;
				if (!packet.ParseFromString(data)) return;
				if (packet.signature().empty()) return;
				if (packet.publickey().empty()) return;

				std::string publicKey = packet.publickey();
				std::string signature = packet.signature();

				entry->publicKey.Set(publicKey);

				if (Utils::Cryptography::ECC::VerifyMessage(entry->publicKey, entry->challenge, signature))
				{
					entry->lastTime = Game::Sys_Milliseconds();
					entry->state = Node::STATE_VALID;
					entry->registered = true;

#ifdef DEBUG
					Logger::Print("Signature from %s for challenge '%s' is valid!\n", address.GetCString(), entry->challenge.data());
					Logger::Print("Node %s registered\n", address.GetCString());
#endif
				}
				else
				{
#ifdef DEBUG
					Logger::Print("Signature from %s for challenge '%s' is invalid!\n", address.GetCString(), entry->challenge.data());
#endif
				}
			});

			Network::Handle("nodeListRequest", [] (Network::Address address, std::string data)
			{
				if (Dvar::Var("sv_lanOnly").Get<bool>()) return;

				// Check if this is a registered node
				bool allowed = false;
				Node::NodeEntry* entry = Node::FindNode(address);
				if (entry && entry->registered)
				{
					entry->lastTime = Game::Sys_Milliseconds();
					allowed = true;
				}

				// Check if there is any open session
				if (!allowed) 
				{
					Node::ClientSession* session = Node::FindSession(address);
					if (session)
					{
						session->lastTime = Game::Sys_Milliseconds();
						allowed = session->valid;
					}
				}

				if (allowed)
				{
					Node::SendNodeList(address);
				}
				else
				{
#ifdef DEBUG
					// Unallowed connection
					Logger::Print("Node list requested by %s, but no valid session was present!\n", address.GetCString());
#endif
					Network::SendCommand(address, "nodeListError");
				}
			});

			Network::Handle("nodeDeregister", [] (Network::Address address, std::string data)
			{
				if (Dvar::Var("sv_lanOnly").Get<bool>()) return;

				Node::NodeEntry* entry = Node::FindNode(address);
				if (!entry || !entry->registered) return;

				Proto::Node::Packet packet;
				if (!packet.ParseFromString(data)) return;
				if (packet.challenge().empty()) return;
				if (packet.signature().empty()) return;

				std::string challenge = packet.challenge();
				std::string signature = packet.signature();

				if (Utils::Cryptography::ECC::VerifyMessage(entry->publicKey, challenge, signature))
				{
					entry->lastHeard = Game::Sys_Milliseconds();
					entry->lastTime = Game::Sys_Milliseconds();
					entry->registered = false;
					entry->state = Node::STATE_INVALID;

#ifdef DEBUG
					Logger::Print("Node %s unregistered\n", address.GetCString());
#endif
				}
				else
				{
#ifdef DEBUG
					Logger::Print("Node %s tried to unregister using an invalid signature!\n", address.GetCString());
#endif
				}
			});

			Network::Handle("sessionRequest", [] (Network::Address address, std::string data)
			{
				if (Dvar::Var("sv_lanOnly").Get<bool>()) return;

				// Search an active session, if we haven't found one, register a template
				if (!Node::FindSession(address))
				{
					Node::ClientSession templateSession;
					templateSession.address = address;
					Node::Sessions.push_back(templateSession);
				}

				// Search our target session (this should not fail!)
				Node::ClientSession* session = Node::FindSession(address);
				if (!session) return; // Registering template session failed, odd...

#ifdef DEBUG
				Logger::Print("Client %s is requesting a new session\n", address.GetCString());
#endif

				// Initialize session data
				session->challenge = fmt::sprintf("%X", Utils::Cryptography::Rand::GenerateInt());
				session->lastTime = Game::Sys_Milliseconds();
				session->valid = false;

				Network::SendCommand(address, "sessionInitialize", session->challenge);
			});

			Network::Handle("sessionSynchronize", [] (Network::Address address, std::string data)
			{
				if (Dvar::Var("sv_lanOnly").Get<bool>()) return;

				// Return if we don't have a session for this address
				Node::ClientSession* session = Node::FindSession(address);
				if (!session || session->valid) return;

				if (session->challenge == data)
				{
#ifdef DEBUG
					Logger::Print("Session for %s validated.\n", address.GetCString());
#endif
					session->valid = true;
					Network::SendCommand(address, "sessionAcknowledge");
				}
				else
				{
					session->lastTime = -1;
#ifdef DEBUG
					Logger::Print("Challenge mismatch. Validating session for %s failed.\n", address.GetCString());
#endif
				}
			});
		}
		else
		{
			Network::Handle("sessionInitialize", [] (Network::Address address, std::string data)
			{
				Node::NodeEntry* entry = Node::FindNode(address);
				if (!entry) return;

#ifdef DEBUG
				Logger::Print("Session initialization received from %s. Synchronizing...\n", address.GetCString());
#endif

				entry->lastTime = Game::Sys_Milliseconds();
				Network::SendCommand(address, "sessionSynchronize", data);
			});

			Network::Handle("sessionAcknowledge", [] (Network::Address address, std::string data)
			{
				Node::NodeEntry* entry = Node::FindNode(address);
				if (!entry) return;

				entry->state = Node::STATE_VALID;
				entry->registered = true;
				entry->lastTime = Game::Sys_Milliseconds();

#ifdef DEBUG
				Logger::Print("Session acknowledged by %s, synchronizing node list...\n", address.GetCString());
#endif
				Network::SendCommand(address, "nodeListRequest");
				Node::SendNodeList(address);
			});
		}

		Network::Handle("nodeListResponse", [] (Network::Address address, std::string data)
		{
			Proto::Node::List list;

			if (data.empty() || !list.ParseFromString(data)) 
			{
#ifdef DEBUG
				Logger::Print("Received invalid node list from %s!\n", address.GetCString());
#endif
				return;
			}

			Node::NodeEntry* entry = Node::FindNode(address);
			if (entry)
			{
				if (entry->registered)
				{
#ifdef DEBUG
					Logger::Print("Received valid node list with %i entries from %s\n", list.address_size(), address.GetCString());
#endif

					entry->isDedi = list.is_dedi();
					entry->protocol = list.protocol();
					entry->version = list.version();
					entry->state = Node::STATE_VALID;
					entry->lastTime = Game::Sys_Milliseconds();

					if (!Dedicated::IsEnabled() && entry->isDedi && ServerList::IsOnlineList() && entry->protocol == PROTOCOL)
					{
						ServerList::InsertRequest(entry->address, true);
					}

					for (int i = 0; i < list.address_size(); ++i)
					{
						Network::Address _addr(list.address(i));

						// Version 0 sends port in the wrong byte order!
						if (list.version() == 0)
						{
							_addr.SetPort(ntohs(_addr.GetPort()));
						}

// 						if (!Node::FindNode(_addr) && _addr.GetPort() >= 1024 && _addr.GetPort() - 20 < 1024)
// 						{
// 							std::string a1 = _addr.GetString();
// 							std::string a2 = address.GetString();
// 							Logger::Print("Received weird address %s from %s\n", a1.data(), a2.data());
// 						}

						Node::AddNode(_addr);
					}
				}
			}
			else
			{
				//Node::AddNode(address);

				Node::ClientSession* session = Node::FindSession(address);
				if (session && session->valid)
				{
					session->lastTime = Game::Sys_Milliseconds();

					for (int i = 0; i < list.address_size(); ++i)
					{
						Node::AddNode(list.address(i));
					}
				}
			}
		});

		// If we receive that response, our request was not permitted
		// So we either have to register as node, or register a remote session
		Network::Handle("nodeListError", [] (Network::Address address, std::string data)
		{
			if (Dedicated::IsEnabled())
			{
				Node::NodeEntry* entry = Node::FindNode(address);
				if (entry)
				{
					// Set to unregistered to perform registration later on
					entry->lastTime = Game::Sys_Milliseconds();
					entry->registered = false;
					entry->state = Node::STATE_UNKNOWN;
				}
				else
				{
					// Add as new entry to perform registration
					Node::AddNode(address);
				}
			}
			else
			{
				// TODO: Implement client handshake stuff
				// Nvm, clients can simply ignore that i guess
			}
		});

		Command::Add("listnodes", [] (Command::Params params)
		{
			Logger::Print("Nodes: %d (%d)\n", Node::Nodes.size(), Node::GetValidNodeCount());

			for (auto node : Node::Nodes)
			{
				Logger::Print("%s\t(%s)\n", node.address.GetCString(), Node::GetStateName(node.state));
			}
		});

		Command::Add("addnode", [] (Command::Params params)
		{
			if (params.Length() < 2) return;

			Network::Address address(params[1]);
			Node::AddNode(address);

			Node::NodeEntry* entry = Node::FindNode(address);
			if (entry)
			{
				entry->state = Node::STATE_UNKNOWN;
				entry->registered = false;
			}
		});

		Command::Add("syncnodes", [] (Command::Params params)
		{
			Logger::Print("Re-Synchronizing nodes...\n");
			
			for (auto& node : Node::Nodes)
			{
				node.state = Node::STATE_UNKNOWN;
				node.registered = false;
				node.lastTime = 0;
				node.lastListQuery = 0;
			}
		});

		// Install frame handlers
		QuickPatch::OnFrame(Node::FrameHandler);
	}

	Node::~Node()
	{
		Node::SignatureKey.Free();

		Node::StoreNodes(true);
		Node::Nodes.clear();
		Node::Sessions.clear();
	}

	bool Node::UnitTest()
	{
		printf("Testing ECDSA key...");

		if (!Node::SignatureKey.IsValid())
		{
			printf("Error\n");
			printf("ECDSA key seems invalid!\n");
			return false;
		}
		
		printf("Success\n");
		printf("Testing 10 valid signatures...");

		for (int i = 0; i < 10; ++i)
		{
			std::string message = fmt::sprintf("%X", Utils::Cryptography::Rand::GenerateInt());
			std::string signature = Utils::Cryptography::ECC::SignMessage(Node::SignatureKey, message);

			if (!Utils::Cryptography::ECC::VerifyMessage(Node::SignatureKey, message, signature))
			{
				printf("Error\n");
				printf("Signature for '%s' (%d) was invalid!\n", message.data(), i);
				return false;
			}
		}

		printf("Success\n");
		printf("Testing 10 invalid signatures...");

		for (int i = 0; i < 10; ++i)
		{
			std::string message = fmt::sprintf("%X", Utils::Cryptography::Rand::GenerateInt());
			std::string signature = Utils::Cryptography::ECC::SignMessage(Node::SignatureKey, message);

			// Invalidate the message...
			++message[Utils::Cryptography::Rand::GenerateInt() % message.size()];

			if (Utils::Cryptography::ECC::VerifyMessage(Node::SignatureKey, message, signature))
			{
				printf("Error\n");
				printf("Signature for '%s' (%d) was valid? What the fuck? That is absolutely impossible...\n", message.data(), i);
				return false;
			}
		}

		printf("Success\n");
		printf("Testing ECDSA key import...");

		std::string pubKey = Node::SignatureKey.GetPublicKey();
		std::string message = fmt::sprintf("%X", Utils::Cryptography::Rand::GenerateInt());
		std::string signature = Utils::Cryptography::ECC::SignMessage(Node::SignatureKey, message);

		Utils::Cryptography::ECC::Key testKey;
		testKey.Set(pubKey);

		if (!Utils::Cryptography::ECC::VerifyMessage(Node::SignatureKey, message, signature))
		{
			printf("Error\n");
			printf("Verifying signature for message '%s' using imported keys failed!\n", message.data());
			return false;
		}

		printf("Success\n");

		uint32_t randIntCount = 4'000'000;
		printf("Generating %d random integers...", randIntCount);

		auto startTime = std::chrono::high_resolution_clock::now();

		for (uint32_t i = 0; i < randIntCount; ++i)
		{
			Utils::Cryptography::Rand::GenerateInt();
		}

		auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - startTime).count();
		Logger::Print("took %llims\n", duration);

		return true;
	}
}
