From bcd05fbdfa7e340ef4df962773bb7c32cf5013c2 Mon Sep 17 00:00:00 2001
From: Adam Ierymenko <adam.ierymenko@gmail.com>
Date: Tue, 9 Aug 2016 09:34:13 -0700
Subject: [PATCH] Chunking of network config replies.

---
 node/Dictionary.hpp     | 34 +++++++++++++++++++++++++++++
 node/IncomingPacket.cpp | 48 +++++++++++++++++++++++++----------------
 node/NetworkConfig.hpp  |  2 ++
 node/Packet.hpp         | 14 +++++-------
 4 files changed, 70 insertions(+), 28 deletions(-)

diff --git a/node/Dictionary.hpp b/node/Dictionary.hpp
index 59fc4bbf5..5d453fd95 100644
--- a/node/Dictionary.hpp
+++ b/node/Dictionary.hpp
@@ -23,6 +23,7 @@
 #include "Utils.hpp"
 #include "Buffer.hpp"
 #include "Address.hpp"
+#include "C25519.hpp"
 
 #include <stdint.h>
 
@@ -443,6 +444,39 @@ public:
 		return found;
 	}
 
+	/**
+	 * Sign this Dictionary, replacing any previous signature
+	 *
+	 * @param sigKey Key to use for signature in dictionary
+	 * @param kp Key pair to sign with
+	 */
+	inline void wrapWithSignature(const char *sigKey,const C25519::Pair &kp)
+	{
+		this->erase(sigKey);
+		C25519::Signature sig(C25519::sign(kp,this->data(),this->sizeBytes()));
+		this->add(sigKey,sig.data,ZT_C25519_SIGNATURE_LEN);
+	}
+
+	/**
+	 * Verify signature (and erase signature key)
+	 *
+	 * This erases this Dictionary's signature key (if present) and verifies
+	 * the signature. The key is erased to render the Dictionary into the
+	 * original unsigned form it was signed in for verification purposes.
+	 *
+	 * @param sigKey Key to use for signature in dictionary
+	 * @param pk Public key to check against
+	 * @return True if signature was present and valid
+	 */
+	inline bool unwrapAndVerify(const char *sigKey,const C25519::Public &pk)
+	{
+		char sig[ZT_C25519_SIGNATURE_LEN+1];
+		if (this->get(sigKey,sig,sizeof(sig)) != ZT_C25519_SIGNATURE_LEN)
+			return false;
+		this->erase(sigKey);
+		return C25519::verify(pk,this->data(),this->sizeBytes(),sig);
+	}
+
 	/**
 	 * @return Dictionary data as a 0-terminated C-string
 	 */
diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp
index fae689d18..147f54da1 100644
--- a/node/IncomingPacket.cpp
+++ b/node/IncomingPacket.cpp
@@ -719,35 +719,46 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons
 		const char *metaDataBytes = (const char *)field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT,metaDataLength);
 		const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> metaData(metaDataBytes,metaDataLength);
 
-		//const uint64_t haveRevision = ((ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT + metaDataLength + 8) <= size()) ? at<uint64_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT + metaDataLength) : 0ULL;
-
 		const unsigned int h = hops();
-		const uint64_t pid = packetId();
-		peer->received(_localAddress,_remoteAddress,h,pid,Packet::VERB_NETWORK_CONFIG_REQUEST,0,Packet::VERB_NOP);
+		const uint64_t requestPacketId = packetId();
+		peer->received(_localAddress,_remoteAddress,h,requestPacketId,Packet::VERB_NETWORK_CONFIG_REQUEST,0,Packet::VERB_NOP);
 
 		if (RR->localNetworkController) {
 			NetworkConfig netconf;
 			switch(RR->localNetworkController->doNetworkConfigRequest((h > 0) ? InetAddress() : _remoteAddress,RR->identity,peer->identity(),nwid,metaData,netconf)) {
 
 				case NetworkController::NETCONF_QUERY_OK: {
-					Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> dconf;
-					if (netconf.toDictionary(dconf,metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION,0) < 6)) {
-						Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK);
-						outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST);
-						outp.append(pid);
-						outp.append(nwid);
-						const unsigned int dlen = dconf.sizeBytes();
-						outp.append((uint16_t)dlen);
-						outp.append((const void *)dconf.data(),dlen);
-						outp.compress();
-						RR->sw->send(outp,true,0);
+					Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> *dconf = new Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>();
+					try {
+						if (netconf.toDictionary(*dconf,metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION,0) < 6)) {
+							const unsigned int totalSize = dconf->sizeBytes();
+							unsigned int chunkPtr = 0;
+							while (chunkPtr < totalSize) {
+								const unsigned int chunkLen = std::min(totalSize - chunkPtr,(unsigned int)(ZT_PROTO_MAX_PACKET_LENGTH - (ZT_PROTO_MIN_PACKET_LENGTH + 32)));
+								Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK);
+								outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST);
+								outp.append(requestPacketId);
+								outp.append(nwid);
+								outp.append((uint16_t)chunkLen);
+								outp.append((const void *)(dconf->data() + chunkPtr),chunkLen);
+								outp.append((uint32_t)totalSize);
+								outp.append((uint32_t)chunkPtr);
+								outp.compress();
+								RR->sw->send(outp,true,0);
+								chunkPtr += chunkLen;
+							}
+						}
+						delete dconf;
+					} catch ( ... ) {
+						delete dconf;
+						throw;
 					}
 				}	break;
 
 				case NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND: {
 					Packet outp(peer->address(),RR->identity.address(),Packet::VERB_ERROR);
 					outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST);
-					outp.append(pid);
+					outp.append(requestPacketId);
 					outp.append((unsigned char)Packet::ERROR_OBJ_NOT_FOUND);
 					outp.append(nwid);
 					outp.armor(peer->key(),true);
@@ -757,7 +768,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons
 				case NetworkController::NETCONF_QUERY_ACCESS_DENIED: {
 					Packet outp(peer->address(),RR->identity.address(),Packet::VERB_ERROR);
 					outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST);
-					outp.append(pid);
+					outp.append(requestPacketId);
 					outp.append((unsigned char)Packet::ERROR_NETWORK_ACCESS_DENIED_);
 					outp.append(nwid);
 					outp.armor(peer->key(),true);
@@ -765,7 +776,6 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons
 				} break;
 
 				case NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR:
-					// TRACE("NETWORK_CONFIG_REQUEST failed: internal error: %s",netconf.get("error","(unknown)").c_str());
 					break;
 
 				case NetworkController::NETCONF_QUERY_IGNORE:
@@ -779,7 +789,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons
 		} else {
 			Packet outp(peer->address(),RR->identity.address(),Packet::VERB_ERROR);
 			outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST);
-			outp.append(pid);
+			outp.append(requestPacketId);
 			outp.append((unsigned char)Packet::ERROR_UNSUPPORTED_OPERATION);
 			outp.append(nwid);
 			outp.armor(peer->key(),true);
diff --git a/node/NetworkConfig.hpp b/node/NetworkConfig.hpp
index 907da936e..3682c466b 100644
--- a/node/NetworkConfig.hpp
+++ b/node/NetworkConfig.hpp
@@ -135,6 +135,8 @@ namespace ZeroTier {
 #define ZT_NETWORKCONFIG_DICT_KEY_CAPABILITIES "CAP"
 // tags (binary blobs)
 #define ZT_NETWORKCONFIG_DICT_KEY_TAGS "TAG"
+// curve25519 signature
+#define ZT_NETWORKCONFIG_DICT_KEY_SIGNATURE "C25519"
 
 // Legacy fields -- these are obsoleted but are included when older clients query
 
diff --git a/node/Packet.hpp b/node/Packet.hpp
index dce9f208f..9d4c82894 100644
--- a/node/Packet.hpp
+++ b/node/Packet.hpp
@@ -724,7 +724,7 @@ public:
 		 *   <[8] 64-bit network ID>
 		 *   <[2] 16-bit length of request meta-data dictionary>
 		 *   <[...] string-serialized request meta-data>
-		 *  [<[8] 64-bit timestamp of netconf we currently have>]
+		 *   <[8] 64-bit timestamp of netconf we currently have>
 		 *
 		 * This message requests network configuration from a node capable of
 		 * providing it. If the optional revision is included, a response is
@@ -732,14 +732,10 @@ public:
 		 *
 		 * OK response payload:
 		 *   <[8] 64-bit network ID>
-		 *   <[2] 16-bit length of network configuration dictionary field>
-		 *   <[...] network configuration dictionary (or fragment)>
-		 *   [<[4] 32-bit total length of assembled dictionary>]
-		 *   [<[4] 32-bit index of fragment in this reply>]
-		 *
-		 * Fields after the dictionary are extensions to support multipart
-		 * sending of large network configs. If they are not present the
-		 * sent config must be assumed to be whole.
+		 *   <[2] 16-bit length of network configuration dictionary chunk>
+		 *   <[...] network configuration dictionary (may be incomplete)>
+		 *   <[4] 32-bit total length of assembled dictionary>
+		 *   <[4] 32-bit index of chunk in this reply>
 		 *
 		 * ERROR response payload:
 		 *   <[8] 64-bit network ID>