Browse Source

Added versioning to the protocol, still not implemented version 1 yet.

Caleb James DeLisle 11 years ago
parent
commit
ccb4220bec

+ 9 - 22
CMakeLists.txt

@@ -163,14 +163,14 @@ endif (${Log_LEVEL} STREQUAL "KEYS")
 add_definitions("-D Log_${Log_LEVEL}")
 
 # vrooooooom
-add_definitions(-funroll-loops)
+add_definitions(-funroll-loops -O3)
 
-IF(NOT CMAKE_BUILD_TYPE)
+#IF(NOT CMAKE_BUILD_TYPE)
 # default to RelWithDebInfo (-g -O2)
-    SET(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING
-        "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel."
-        FORCE)
-ENDIF(NOT CMAKE_BUILD_TYPE)
+#    SET(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING
+#        "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel."
+#        FORCE)
+#ENDIF(NOT CMAKE_BUILD_TYPE)
 
 OPTION(WITH_LTO "Building with link time optimization.")
 if(WITH_LTO)
@@ -183,25 +183,12 @@ endif()
 SET(CJDNS_MAX_PEERS 256 CACHE STRING "maximum number of peers")
 add_definitions("-D CJDNS_MAX_PEERS=${CJDNS_MAX_PEERS}")
 
+# make sure casts are done properly.
+add_definitions("-D Identity_CHECK")
+
 
-if(NOT "$ENV{GIT_VERSION}" STREQUAL "")
-    SET(GIT_VERSION "$ENV{GIT_VERSION}")
-elseif(EXISTS .git_commit)
-    FILE(READ .git_commit GIT_VERSION)
-else()
-    find_package(Git)
-    execute_process(
-        COMMAND ${GIT_EXECUTABLE} log -n 1
-        OUTPUT_VARIABLE GIT_LOG
-        OUTPUT_STRIP_TRAILING_WHITESPACE
-    )
-    string(REGEX MATCH "[0-9a-f]*\n" GIT_VERSION "${GIT_LOG}")
-    string(REPLACE "\n" "" GIT_VERSION "${GIT_VERSION}")
-endif()
-# GIT_VERSION is used in dht/dhtcore/CMakeLists.txt
 
 include_directories(${CMAKE_SOURCE_DIR})
-include_directories(${CMAKE_SOURCE_DIR}/src)
 
 find_package(Libevent2 REQUIRED)
 include_directories(${LIBEVENT2_INCLUDE_DIRS})

+ 1 - 1
dht/DHTMessage.h

@@ -53,7 +53,7 @@ struct DHTMessage
     struct DHTMessage* replyTo;
 
     /** A memory allocator which will be freed after this message is sent. */
-    const struct Allocator* allocator;
+    struct Allocator* allocator;
 };
 
 #endif

+ 9 - 3
dht/dhtcore/CMakeLists.txt

@@ -11,17 +11,23 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 find_package(Libevent2 REQUIRED)
 
-add_definitions(-DGIT_VERSION=\"${GIT_VERSION}\")
-
 add_library(dhtcore
     NodeStore.c
     RouterModule.c
     RouterModule_admin.c
     SearchStore.c
     Janitor.c
+    VersionList.c
 )
 
-target_link_libraries(dhtcore cjdbenc util crypto switch ${LIBEVENT2_LIBRARIES})
+target_link_libraries(dhtcore
+    cjdbenc
+    util
+    crypto
+    switch
+    ${LIBEVENT2_LIBRARIES}
+    cjdns-util-version-version
+)
 
 # Everything must be tested.
 enable_testing()

+ 3 - 0
dht/dhtcore/Node.h

@@ -29,6 +29,9 @@ struct Node
      */
     uint32_t reach;
 
+    /** The version of the node, must be synchronized with NodeHeader */
+    uint32_t version;
+
     /** The address of the node. */
     struct Address address;
 };

+ 3 - 2
dht/dhtcore/NodeHeader.h

@@ -21,7 +21,6 @@
 
 /**
  * Information about a given node.
- * Takes 8 bytes, 512 headers per 4096 byte page of memory.
  */
 struct NodeHeader
 {
@@ -36,7 +35,9 @@ struct NodeHeader
 
     /** The number interface of the next hop to get to this node. */
     uint32_t switchIndex;
+
+    /** The protocol version of the node. */
+    uint32_t version;
 };
-Assert_compileTime(sizeof(struct NodeHeader) == 12);
 
 #endif

+ 50 - 18
dht/dhtcore/NodeStore.c

@@ -30,6 +30,7 @@
 #include "util/Assert.h"
 #include "util/Bits.h"
 #include "util/log/Log.h"
+#include "util/version/Version.h"
 #include "switch/NumberCompress.h"
 #include "switch/LabelSplicer.h"
 
@@ -64,8 +65,16 @@ struct NodeStore* NodeStore_new(struct Address* myAddress,
     return out;
 }
 
+static struct Node* nodeForIndex(struct NodeStore* store, uint32_t index)
+{
+    struct Node* out = &store->nodes[index];
+    out->reach = store->headers[index].reach;
+    out->version = store->headers[index].version;
+    return out;
+}
+
 /** See: NodeStore.h */
-struct Node* NodeStore_getNode(const struct NodeStore* store, struct Address* addr)
+struct Node* NodeStore_getNode(struct NodeStore* store, struct Address* addr)
 {
     uint32_t pfx = Address_getPrefix(addr);
 
@@ -87,8 +96,7 @@ struct Node* NodeStore_getNode(const struct NodeStore* store, struct Address* ad
     }
 
     // Synchronize the reach values.
-    store->nodes[bestIndex].reach = store->headers[bestIndex].reach;
-    return &store->nodes[bestIndex];
+    return nodeForIndex(store, bestIndex);
 }
 
 static inline uint32_t getSwitchIndex(struct Address* addr)
@@ -97,13 +105,14 @@ static inline uint32_t getSwitchIndex(struct Address* addr)
     return NumberCompress_getDecompressed(addr->path, bits);
 }
 
-static inline void replaceNode(struct Node* const nodeToReplace,
-                               struct NodeHeader* const headerToReplace,
+static inline void replaceNode(struct Node* nodeToReplace,
+                               struct NodeHeader* headerToReplace,
                                struct Address* addr,
                                struct NodeStore* store)
 {
     headerToReplace->addressPrefix = Address_getPrefix(addr);
     headerToReplace->reach = 0;
+    headerToReplace->version = 0;
     headerToReplace->switchIndex = getSwitchIndex(addr);
     store->labelSum -= Bits_log2x64(nodeToReplace->address.path);
     store->labelSum += Bits_log2x64(addr->path);
@@ -129,8 +138,13 @@ static inline void adjustReach(struct NodeHeader* header,
 
 struct Node* NodeStore_addNode(struct NodeStore* store,
                                struct Address* addr,
-                               const int64_t reachDifference)
+                               int64_t reachDifference,
+                               uint32_t version)
 {
+    if (!Version_isCompatible(Version_CURRENT_PROTOCOL, version)) {
+        return NULL;
+    }
+
     Address_getPrefix(addr);
     if (memcmp(addr->ip6.bytes, store->thisNodeAddress, 16) == 0) {
         printf("got introduced to ourselves\n");
@@ -177,6 +191,10 @@ struct Node* NodeStore_addNode(struct NodeStore* store,
 
                 adjustReach(&store->headers[i], reachDifference);
 
+                if (version) {
+                    store->headers[i].version = version;
+                }
+
                 /*#ifdef Log_DEBUG
                     if (oldReach != store->headers[i].reach) {
                         uint8_t nodeAddr[60];
@@ -192,7 +210,7 @@ struct Node* NodeStore_addNode(struct NodeStore* store,
                     }
                 #endif*/
 
-                return &store->nodes[i];
+                return nodeForIndex(store, i);
             }
             #ifdef Log_DEBUG
                 else if (store->headers[i].addressPrefix == pfx) {
@@ -215,7 +233,9 @@ struct Node* NodeStore_addNode(struct NodeStore* store,
         // Free space, regular insert.
         replaceNode(&store->nodes[store->size], &store->headers[store->size], addr, store);
         adjustReach(&store->headers[store->size], reachDifference);
-        return &store->nodes[store->size++];
+        store->headers[store->size].version = version;
+
+        return nodeForIndex(store, store->size++);
     }
 
     // The node whose reach OR distance is the least.
@@ -247,14 +267,14 @@ struct Node* NodeStore_addNode(struct NodeStore* store,
 
     adjustReach(&store->headers[indexOfNodeToReplace], reachDifference);
 
-    return &store->nodes[indexOfNodeToReplace];
+    store->headers[indexOfNodeToReplace].version = version;
+
+    return nodeForIndex(store, indexOfNodeToReplace);
 }
 
 static struct Node* nodeForHeader(struct NodeHeader* header, struct NodeStore* store)
 {
-    struct Node* n = &store->nodes[header - store->headers];
-    n->reach = header->reach;
-    return n;
+    return nodeForIndex(store, header - store->headers);
 }
 
 struct Node* NodeStore_getBest(struct Address* targetAddress, struct NodeStore* store)
@@ -324,6 +344,7 @@ struct NodeList* NodeStore_getClosestNodes(struct NodeStore* store,
                                            struct Address* requestorsAddress,
                                            const uint32_t count,
                                            bool allowNodesFartherThanUs,
+                                           uint32_t versionOfRequestingNode,
                                            const struct Allocator* allocator)
 {
     // LinkStateNodeCollector strictly requires that allowNodesFartherThanUs be true.
@@ -340,6 +361,11 @@ struct NodeList* NodeStore_getClosestNodes(struct NodeStore* store,
     // naive implementation, todo make this faster
     for (uint32_t i = 0; i < store->size; i++) {
         if (requestorsAddress && store->headers[i].switchIndex == index) {
+            // Nodes which are down the same interface as the node who asked.
+            continue;
+        }
+        if (!Version_isCompatible(store->headers[i].version, versionOfRequestingNode)) {
+            // Known not to be compatable.
             continue;
         }
         LinkStateNodeCollector_addNode(store->headers + i, store->nodes + i, collector);
@@ -394,7 +420,7 @@ struct Node* NodeStore_getNodeByNetworkAddr(uint64_t path, struct NodeStore* sto
 
     for (uint32_t i = 0; i < store->size; i++) {
         if (path == store->nodes[i].address.path) {
-            return &store->nodes[i];
+            return nodeForIndex(store, i);
         }
     }
     return NULL;
@@ -456,11 +482,16 @@ static void addRoutingTableEntries(struct NodeStore* store,
 {
     uint8_t path[20];
     uint8_t ip[40];
+    String* pathStr = &(String) { .len = 19, .bytes = (char*)path };
+    String* ipStr = &(String) { .len = 39, .bytes = (char*)ip };
+    Object* link = Int_OBJ(0xFFFFFFFF);
+    Object* version = Int_OBJ(Version_CURRENT_PROTOCOL);
     Dict entry = Dict_CONST(
-        String_CONST("ip"), String_OBJ((&(String) { .len = 39, .bytes = (char*)ip })), Dict_CONST(
-        String_CONST("link"), Int_OBJ(0xFFFFFFFF), Dict_CONST(
-        String_CONST("path"), String_OBJ((&(String) { .len = 19, .bytes = (char*)path })), NULL
-    )));
+        String_CONST("ip"), String_OBJ(ipStr), Dict_CONST(
+        String_CONST("link"), link, Dict_CONST(
+        String_CONST("path"), String_OBJ(pathStr), Dict_CONST(
+        String_CONST("version"), version, NULL
+    ))));
 
     struct List_Item next = { .next = last, .elem = Dict_OBJ(&entry) };
 
@@ -476,7 +507,8 @@ static void addRoutingTableEntries(struct NodeStore* store,
         return;
     }
 
-    entry->next->val->as.number = store->headers[i].reach;
+    link->as.number = store->headers[i].reach;
+    version->as.number = store->headers[i].version;
     Address_printIp(ip, &store->nodes[i].address);
     AddrTools_printPath(path, store->nodes[i].address.path);
 

+ 7 - 2
dht/dhtcore/NodeStore.h

@@ -49,12 +49,14 @@ struct NodeStore* NodeStore_new(struct Address* myAddress,
  *                  amount causes the reach to become negative or nolonger fit in a uint32
  *                  type, it will be set to 0 or UINT32_MAX, respectively.
  *                  Undefined behavior will result if this input exceeds UINT32_MAX.
+ * @param version the protocol version of the node to add.
  * @return the node in the node store which was added or NULL if the node is "us".
  *         NOTE: The reach in this node will be *wrong* because it is not synced with the header.
  */
 struct Node* NodeStore_addNode(struct NodeStore* store,
                                struct Address* addr,
-                               const int64_t reachDiff);
+                               int64_t reachDiff,
+                               uint32_t version);
 
 struct Node* NodeStore_getBest(struct Address* targetAddress, struct NodeStore* store);
 
@@ -75,13 +77,16 @@ struct NodeList* NodeStore_getNodesByAddr(struct Address* address,
  * @param allowNodesFartherThanUs if true then return nodes which are farther than the target
  *                                then we are. this is required for searches but unallowable
  *                                for answering queries.
+ * @param versionOfRequestingNode the version of the node who asked for the list, no nodes will
+ *                                be returned which are known to be incompatible with this version.
  * @param allocator the memory allocator to use for getting the memory to store the output.
  */
 struct NodeList* NodeStore_getClosestNodes(struct NodeStore* store,
                                            struct Address* targetAddress,
                                            struct Address* requestorsAddress,
                                            const uint32_t count,
-                                           const bool allowNodesFartherThanUs,
+                                           bool allowNodesFartherThanUs,
+                                           uint32_t versionOfRequestingNode,
                                            const struct Allocator* allocator);
 
 /**

+ 32 - 15
dht/dhtcore/RouterModule.c

@@ -21,6 +21,7 @@
 #include "dht/dhtcore/NodeList.h"
 #include "dht/dhtcore/NodeStore.h"
 #include "dht/dhtcore/SearchStore.h"
+#include "dht/dhtcore/VersionList.h"
 #include "dht/CJDHTConstants.h"
 #include "dht/DHTMessage.h"
 #include "dht/DHTModule.h"
@@ -35,6 +36,7 @@
 #include "util/Pinger.h"
 #include "util/Time.h"
 #include "util/Timeout.h"
+#include "util/version/Version.h"
 
 #include <string.h>
 #include <stdint.h>
@@ -186,9 +188,6 @@
 
 #define LINK_STATE_MULTIPLIER 536870
 
-/** hex git version (null terminated string with 40 chars) */
-static const uint8_t gitVersion[41] = GIT_VERSION;
-
 /*--------------------Structures--------------------*/
 /**
  * A structure to give the user when performing a search so they can cancel it.
@@ -233,7 +232,7 @@ struct RouterModule* RouterModule_register(struct DHTModuleRegistry* registry,
         .handleOutgoing = handleOutgoing
     }), registry);
 
-    Hex_decode(out->gitVersionBytes, 20, gitVersion, 40);
+    Hex_decode(out->gitVersionBytes, 20, Version_gitVersion(), 40);
     out->gitVersion.len = 20;
     out->gitVersion.bytes = (char*) out->gitVersionBytes;
 
@@ -418,7 +417,7 @@ static inline void sendRequest(struct Address* address,
     memset(&message, 0, sizeof(struct DHTMessage));
 
     char buffer[4096];
-    const struct Allocator* allocator = BufferAllocator_new(buffer, 4096);
+    struct Allocator* allocator = BufferAllocator_new(buffer, 4096);
 
     message.allocator = allocator;
     message.asDict = Dict_new(allocator);
@@ -678,8 +677,11 @@ static inline void responseFromNode(struct Node* node,
  */
 static inline int handleReply(struct DHTMessage* message, struct RouterModule* module)
 {
+    int64_t* version = Dict_getInt(message->asDict, String_CONST("p"));
+
     // this implementation only pings to get the address of a node, so lets add the node.
-    struct Node* node = NodeStore_addNode(module->nodeStore, message->address, 0);
+    struct Node* node =
+        NodeStore_addNode(module->nodeStore, message->address, 0, ((version) ? *version : 0) );
 
     String* tid = Dict_getString(message->asDict, CJDHTConstants_TXID);
 
@@ -760,6 +762,12 @@ static inline int handleReply(struct DHTMessage* message, struct RouterModule* m
         return 0;
     }
 
+    struct VersionList* versions = NULL;
+    String* versionsStr = Dict_getString(message->asDict, String_CONST("np"));
+    if (versionsStr) {
+        versions = VersionList_parse(versionsStr, message->allocator);
+    }
+
     // If this node has sent us any entries which are further from the target than it is,
     // garbage the whole response.
     const uint32_t targetPrefix = Address_getPrefix(&scc->targetAddress);
@@ -813,7 +821,8 @@ static inline int handleReply(struct DHTMessage* message, struct RouterModule* m
         }
 
         // Nodes we are told about are inserted with 0 reach.
-        NodeStore_addNode(module->nodeStore, &addr, 0);
+        uint32_t version = (versions) ? versions->versions[i / Address_SERIALIZED_SIZE] : 0;
+        NodeStore_addNode(module->nodeStore, &addr, 0, version);
 
         if ((newNodePrefix ^ targetPrefix) >= parentDistance
             && xorCompare(&scc->targetAddress, &addr, parent->address) >= 0)
@@ -866,8 +875,11 @@ static inline int handleQuery(struct DHTMessage* message,
 {
     struct DHTMessage* query = message->replyTo;
 
+    int64_t* versionPtr = Dict_getInt(query->asDict, String_CONST("p"));
+    uint32_t version = (versionPtr && *versionPtr <= UINT32_MAX) ? *versionPtr : 0;
+
     // We got a query, the reach should be set to 1 in the new node.
-    NodeStore_addNode(module->nodeStore, query->address, 1);
+    NodeStore_addNode(module->nodeStore, query->address, 1, version);
 
     // get the target
     String* target = Dict_getString(query->asDict, CJDHTConstants_TARGET);
@@ -889,6 +901,7 @@ static inline int handleQuery(struct DHTMessage* message,
                                                           query->address,
                                                           RouterModule_K + 5,
                                                           false,
+                                                          version,
                                                           message->allocator);
 
     String* nodes = message->allocator->malloc(sizeof(String), message->allocator);
@@ -896,6 +909,8 @@ static inline int handleQuery(struct DHTMessage* message,
     nodes->bytes = message->allocator->malloc(nodeList->size * Address_SERIALIZED_SIZE,
                                               message->allocator);
 
+    struct VersionList* versions = VersionList_new(nodeList->size, message->allocator);
+
     uint32_t i;
     for (i = 0; i < nodeList->size; i++) {
 
@@ -908,9 +923,13 @@ static inline int handleQuery(struct DHTMessage* message,
         addr.path = LabelSplicer_getLabelFor(addr.path, query->address->path);
 
         Address_serialize((uint8_t*) &nodes->bytes[i * Address_SERIALIZED_SIZE], &addr);
+
+        versions->versions[i] = nodeList->nodes[i]->version;
     }
     if (i > 0) {
         Dict_putString(message->asDict, CJDHTConstants_NODES, nodes, message->allocator);
+        String* versionsStr = VersionList_stringify(versions, message->allocator);
+        Dict_putString(message->asDict, String_CONST("np"), versionsStr, message->allocator);
     }
 
     return 0;
@@ -927,6 +946,8 @@ static int handleOutgoing(struct DHTMessage* message, void* vcontext)
 {
     struct RouterModule* module = (struct RouterModule*) vcontext;
 
+    Dict_putInt(message->asDict, String_CONST("p"), Version_CURRENT_PROTOCOL, message->allocator);
+
     if (message->replyTo != NULL) {
         return handleQuery(message, module);
     }
@@ -957,6 +978,7 @@ struct RouterModule_Search* RouterModule_beginSearch(
                                   NULL,
                                   RouterModule_K,
                                   true,
+                                  Version_CURRENT_PROTOCOL,
                                   searchAllocator);
 
     if (nodes->size == 0) {
@@ -1139,10 +1161,10 @@ int RouterModule_pingNode(struct Node* node,
 }
 
 /** See: RouterModule.h */
-void RouterModule_addNode(struct Address* address, struct RouterModule* module)
+void RouterModule_addNode(struct RouterModule* module, struct Address* address, uint32_t version)
 {
     Address_getPrefix(address);
-    NodeStore_addNode(module->nodeStore, address, 0);
+    NodeStore_addNode(module->nodeStore, address, 0, version);
     struct Node* best = RouterModule_lookup(address->ip6.bytes, module);
     if (best && best->address.path != address->path) {
         RouterModule_pingNode(best, module, 0, NULL);
@@ -1163,8 +1185,3 @@ struct Node* RouterModule_getNode(uint64_t path, struct RouterModule* module)
 {
     return NodeStore_getNodeByNetworkAddr(path, module->nodeStore);
 }
-
-const char* RouterModule_gitVersion()
-{
-    return (const char*) gitVersion;
-}

+ 3 - 8
dht/dhtcore/RouterModule.h

@@ -91,10 +91,11 @@ void RouterModule_cancelSearch(struct RouterModule_Search* toCancel);
  * This injects a node directly into the routing table, it's much safer to ping the node and let the
  * routing engine pick up the ping response and insert the node then.
  *
- * @param address the address of the node.
  * @param module the router module to add the node to.
+ * @param address the address of the node.
+ * @param version the protocol version of the node which we are adding.
  */
-void RouterModule_addNode(struct Address* address, struct RouterModule* module);
+void RouterModule_addNode(struct RouterModule* module, struct Address* address, uint32_t version);
 
 /**
  * Send a ping to a node, when it responds it will be added to the routing table.
@@ -123,10 +124,4 @@ struct Node* RouterModule_getNode(uint64_t path, struct RouterModule* module);
 struct Node* RouterModule_lookup(uint8_t targetAddr[Address_SEARCH_TARGET_SIZE],
                                  struct RouterModule* module);
 
-/**
- * return git commit id as hex (null terminated string with 40 chars)
- */
-const char* RouterModule_gitVersion();
-
-
 #endif

+ 78 - 0
dht/dhtcore/VersionList.c

@@ -0,0 +1,78 @@
+/* vim: set expandtab ts=4 sw=4: */
+/*
+ * You may redistribute this program and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#include "dht/dhtcore/VersionList.h"
+#include "memory/Allocator.h"
+#include "io/Reader.h"
+#include "io/Writer.h"
+#include "io/ArrayWriter.h"
+#include "io/ArrayReader.h"
+#include "util/Endian.h"
+
+struct VersionList* VersionList_parse(String* str, struct Allocator* alloc)
+{
+    const uint8_t numberSize = str->bytes[0];
+    if (str->len < 1 || numberSize == 0 || numberSize > 4) {
+        return NULL;
+    }
+
+    uint32_t length = (str->len - 1) / numberSize;
+
+    if ((length * numberSize) != (str->len - 1)) {
+        return NULL;
+    }
+
+    struct VersionList* list = VersionList_new(length, alloc);
+    struct Reader* r = ArrayReader_new(str->bytes + 1, str->len - 1, alloc);
+    for (int i = 0; i < (int)list->length; i++) {
+        uint32_t ver = 0;
+        r->read((uint8_t*) &ver, numberSize, r);
+        list->versions[i] = Endian_bigEndianToHost32(ver);
+    }
+    return list;
+}
+
+String* VersionList_stringify(struct VersionList* list, struct Allocator* alloc)
+{
+    uint8_t numberSize = 0;
+    uint32_t max = 0;
+    for (int i = 0; i < (int)list->length; i++) {
+        if (list->versions[i] >= max) {
+            numberSize++;
+            max = UINT32_MAX >> (32 - (numberSize * 8));
+        }
+    }
+
+    String* out = String_newBinary(NULL, (numberSize * list->length + 1), alloc);
+    out->bytes[0] = numberSize;
+
+    struct Writer* w = ArrayWriter_new(out->bytes + 1, out->len - 1, alloc);
+    for (int i = 0; i < (int)list->length; i++) {
+        uint32_t ver = Endian_hostToBigEndian32(list->versions[i]);
+        w->write((uint8_t*) &ver, numberSize, w);
+    }
+
+    return out;
+}
+
+struct VersionList* VersionList_new(uint32_t length, struct Allocator* alloc)
+{
+    struct VersionList* out = alloc->clone(sizeof(struct VersionList), alloc,
+        &(struct VersionList) {
+            .length = length,
+            .alloc = alloc
+        });
+    out->versions = alloc->calloc(4, length, alloc);
+    return out;
+}

+ 37 - 0
dht/dhtcore/VersionList.h

@@ -0,0 +1,37 @@
+/* vim: set expandtab ts=4 sw=4: */
+/*
+ * You may redistribute this program and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef VersionList_H
+#define VersionList_H
+
+#include "benc/String.h"
+#include "memory/Allocator.h"
+
+#include <stdint.h>
+
+struct VersionList
+{
+    uint32_t length;
+    struct Allocator* alloc;
+    uint32_t* versions;
+};
+
+struct VersionList* VersionList_parse(String* str, struct Allocator* alloc);
+
+String* VersionList_stringify(struct VersionList* list, struct Allocator* alloc);
+
+struct VersionList* VersionList_new(uint32_t count, struct Allocator* alloc);
+
+
+#endif

+ 4 - 3
net/DefaultInterfaceController.c

@@ -143,6 +143,7 @@ static void onPingResponse(enum SwitchPinger_Result result,
                            uint64_t label,
                            String* data,
                            uint32_t millisecondsLag,
+                           uint32_t version,
                            void* onResponseContext)
 {
     if (SwitchPinger_Result_OK != result) {
@@ -154,7 +155,7 @@ static void onPingResponse(enum SwitchPinger_Result result,
     memset(&addr, 0, sizeof(struct Address));
     Bits_memcpyConst(addr.key, CryptoAuth_getHerPublicKey(ep->cryptoAuthIf), 32);
     addr.path = ep->switchLabel;
-    RouterModule_addNode(&addr, ic->routerModule);
+    RouterModule_addNode(ic->routerModule, &addr, version);
 
     #ifdef Log_DEBUG
         // This will be false if it times out.
@@ -451,8 +452,6 @@ static struct Endpoint* insertEndpoint(uint8_t key[InterfaceController_KEY_SIZE]
     authedIf->receiverContext = ep;
 
     if (herPublicKey) {
-        Bits_memcpyConst(addr.key, herPublicKey, 32);
-        RouterModule_addNode(&addr, ic->routerModule);
         #ifdef Log_INFO
             uint8_t printAddr[60];
             Address_print(printAddr, &addr);
@@ -463,6 +462,8 @@ static struct Endpoint* insertEndpoint(uint8_t key[InterfaceController_KEY_SIZE]
             Hex_encode(keyHex, sizeof(keyHex), key, InterfaceController_KEY_SIZE);
             Log_keys(ic->logger, "With connection identifier [%s]", keyHex);
         #endif
+        // Kick the ping callback so that the node will be pinged ASAP.
+        pingCallback(ic);
     }
 
     return ep;

+ 1 - 3
net/Ducttape.c

@@ -560,9 +560,7 @@ static uint8_t incomingFromSwitch(struct Message* message, struct Interface* swi
         }
         struct Control* ctrl = (struct Control*) message->bytes;
 
-        uint16_t checksum = ctrl->checksum_be;
-        ctrl->checksum_be = 0;
-        if (checksum != Checksum_engine(message->bytes, message->length)) {
+        if (Checksum_engine(message->bytes, message->length)) {
             Log_info(context->logger, "ctrl packet from [%s] with invalid checksum.", labelStr);
             // TODO: return Error_NONE;
             // This will break error responses since they were

+ 24 - 3
net/SwitchPinger.c

@@ -19,6 +19,7 @@
 #include "util/checksum/Checksum.h"
 #include "util/Endian.h"
 #include "util/Pinger.h"
+#include "util/version/Version.h"
 #include "wire/Headers.h"
 #include "wire/Control.h"
 #include "wire/Error.h"
@@ -42,6 +43,9 @@ struct SwitchPinger
      */
     uint64_t incomingLabel;
 
+    /** The version of the node which sent the message. */
+    uint32_t incomingVersion;
+
     bool isError;
 };
 
@@ -51,7 +55,7 @@ struct Ping
     uint64_t label;
     String* data;
     struct SwitchPinger* context;
-    SwitchPinger_ON_RESPONSE(onResponse);
+    SwitchPinger_ResponseCallback onResponse;
     void* onResponseContext;
 };
 
@@ -61,12 +65,22 @@ static uint8_t receiveMessage(struct Message* msg, struct Interface* iface)
     struct SwitchPinger* ctx = iface->receiverContext;
     struct Headers_SwitchHeader* switchHeader = (struct Headers_SwitchHeader*) msg->bytes;
     ctx->incomingLabel = Endian_bigEndianToHost64(switchHeader->label_be);
+    ctx->incomingVersion = 0;
     Assert_true(Headers_getMessageType(switchHeader) == Headers_SwitchHeader_TYPE_CONTROL);
     Message_shift(msg, -Headers_SwitchHeader_SIZE);
     struct Control* ctrl = (struct Control*) msg->bytes;
     if (ctrl->type_be == Control_PONG_be) {
         Message_shift(msg, -Control_HEADER_SIZE);
         ctx->isError = false;
+        struct Control_Ping* pongHeader = (struct Control_Ping*) msg->bytes;
+        if (msg->length >= Control_Ping_MIN_SIZE) {
+             // TODO: Make this a failure (protocol version 2)
+             if (pongHeader->magic == Control_Pong_MAGIC) {
+                 ctx->incomingVersion = Endian_bigEndianToHost32(pongHeader->version_be);
+             }
+             Message_shift(msg, -Control_Ping_HEADER_SIZE);
+        }
+
     } else if (ctrl->type_be == Control_ERROR_be) {
         Log_debug(ctx->logger, "error was caused by our ping.");
         Message_shift(msg, -(
@@ -77,6 +91,7 @@ static uint8_t receiveMessage(struct Message* msg, struct Interface* iface)
         ));
         ctx->isError = true;
     }
+
     String* msgStr = &(String) { .bytes = (char*) msg->bytes, .len = msg->length };
     Pinger_pongReceived(msgStr, ctx->pinger);
     return Error_NONE;
@@ -99,7 +114,8 @@ static void onPingResponse(String* data, uint32_t milliseconds, void* vping)
         err = SwitchPinger_Result_TIMEOUT;
     }
 
-    p->onResponse(err, label, data, milliseconds, p->public.onResponseContext);
+    uint32_t version = p->context->incomingVersion;
+    p->onResponse(err, label, data, milliseconds, version, p->public.onResponseContext);
 }
 
 static void sendPing(String* data, void* sendPingContext)
@@ -116,6 +132,11 @@ static void sendPing(String* data, void* sendPingContext)
     Assert_true(data->len < (BUFFER_SZ / 2));
     Bits_memcpy(msg.bytes, data->bytes, data->len);
 
+    Message_shift(&msg, Control_Ping_HEADER_SIZE);
+    struct Control_Ping* pingHeader = (struct Control_Ping*) msg.bytes;
+    pingHeader->magic = Control_Ping_MAGIC;
+    pingHeader->version_be = Endian_hostToBigEndian32(Version_CURRENT_PROTOCOL);
+
     Message_shift(&msg, Control_HEADER_SIZE);
     struct Control* ctrl = (struct Control*) msg.bytes;
     ctrl->checksum_be = 0;
@@ -164,7 +185,7 @@ String* SwitchPinger_resultString(enum SwitchPinger_Result result)
 struct SwitchPinger_Ping* SwitchPinger_ping(uint64_t label,
                                             String* data,
                                             uint32_t timeoutMilliseconds,
-                                            SwitchPinger_ON_RESPONSE(onResponse),
+                                            SwitchPinger_ResponseCallback onResponse,
                                             struct SwitchPinger* ctx)
 {
     if (data && data->len > Control_Ping_MAX_SIZE) {

+ 8 - 7
net/SwitchPinger.h

@@ -49,14 +49,15 @@ enum SwitchPinger_Result
  * @param label the label as of the responding node in host order.
  * @param data the content of the ping response.
  * @param millisecondsLag the number of milliseconds since the original ping was sent.
+ * @param nodeVersion the version of the node which was pinged.
  * @param onResponseContext a context which was provided to SwitchPinger_ping().
  */
-#define SwitchPinger_ON_RESPONSE(x) \
-    void (* x)(enum SwitchPinger_Result result,           \
-               uint64_t label,                            \
-               String* data,                              \
-               uint32_t millisecondsLag,                  \
-               void* onResponseContext)
+typedef void (* SwitchPinger_ResponseCallback)(enum SwitchPinger_Result result,
+                                               uint64_t label,
+                                               String* data,
+                                               uint32_t millisecondsLag,
+                                               uint32_t nodeVersion,
+                                               void* onResponseContext);
 
 struct SwitchPinger_Ping
 {
@@ -87,7 +88,7 @@ String* SwitchPinger_resultString(enum SwitchPinger_Result result);
 struct SwitchPinger_Ping* SwitchPinger_ping(uint64_t label,
                                             String* data,
                                             uint32_t timeoutMilliseconds,
-                                            SwitchPinger_ON_RESPONSE(onResponse),
+                                            SwitchPinger_ResponseCallback onResponse,
                                             struct SwitchPinger* sp);
 
 struct SwitchPinger* SwitchPinger_new(struct Interface* iface,

+ 14 - 9
net/SwitchPinger_admin.c

@@ -38,6 +38,7 @@ static void adminPingOnResponse(enum SwitchPinger_Result result,
                                 uint64_t label,
                                 String* data,
                                 uint32_t millisecondsLag,
+                                uint32_t version,
                                 void* vping)
 {
     struct Ping* ping = vping;
@@ -47,16 +48,20 @@ static void adminPingOnResponse(enum SwitchPinger_Result result,
     String* pathStr = &(String) { .bytes = (char*) path, .len = 19 };
 
     Dict response = Dict_CONST(
+        String_CONST("version"), Int_OBJ(version), Dict_CONST(
         String_CONST("result"), String_OBJ(resultStr), NULL
-    );
+    ));
+
+    Dict d = Dict_CONST(String_CONST("path"), String_OBJ(pathStr), response);
     if (result != SwitchPinger_Result_TIMEOUT) {
-        response = Dict_CONST(String_CONST("path"), String_OBJ(pathStr), response);
+        response = d;
     }
 
     response = Dict_CONST(String_CONST("ms"), Int_OBJ(millisecondsLag), response);
 
+    d = Dict_CONST(String_CONST("data"), String_OBJ(data), response);
     if (data) {
-        response = Dict_CONST(String_CONST("data"), String_OBJ(data), response);
+        response = d;
     }
 
     Admin_sendMessage(&response, ping->txid, ping->context->admin);
@@ -102,10 +107,10 @@ void SwitchPinger_admin_register(struct SwitchPinger* sp,
         .admin = admin
     });
 
-    struct Admin_FunctionArg adma[] = {
-        { .name = "path", .required = 1, .type = "String" },
-        { .name = "timeout", .required = 0, .type = "Int" },
-        { .name = "data", .required = 0, .type = "String" }
-    };
-    Admin_registerFunction("SwitchPinger_ping", adminPing, ctx, true, adma, admin);
+    Admin_registerFunction("SwitchPinger_ping", adminPing, ctx, true,
+        ((struct Admin_FunctionArg[]) {
+            { .name = "path", .required = 1, .type = "String" },
+            { .name = "timeout", .required = 0, .type = "Int" },
+            { .name = "data", .required = 0, .type = "String" }
+        }), admin);
 }

+ 1 - 0
util/CMakeLists.txt

@@ -14,6 +14,7 @@ cmake_minimum_required(VERSION 2.4)
 
 add_subdirectory(checksum)
 add_subdirectory(log)
+add_subdirectory(version)
 
 find_package(Libevent2 REQUIRED)
 include_directories(${LIBEVENT2_INCLUDE_DIRS})

+ 43 - 0
util/Identity.h

@@ -0,0 +1,43 @@
+/* vim: set expandtab ts=4 sw=4: */
+/*
+ * You may redistribute this program and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef Identity_H
+#define Identity_H
+
+#include "util/Assert.h"
+
+#ifdef Identity_CHECK
+
+    /** This is unique to each file. */
+    static char Identity_ID;
+
+    /** This goes in each structure which will be checked. */
+    #define Identity \
+        char* Identity_verifier;
+
+    #define Identity_set(pointer) \
+        pointer->Identity_verifier = &Identity_ID
+
+    #define Identity_check(pointer) \
+        (pointer); Assert_always(pointer->Identity_verifier == &Identity_ID)
+
+#else
+
+    #define Identity
+    #define Identity_set(pointer)
+    #define Identity_check(pointer) (pointer)
+
+#endif
+
+#endif

+ 0 - 11
util/test/CMakeLists.txt

@@ -29,14 +29,3 @@ foreach(test_source_fullpath ${tests})
 endforeach()
 # Add an empty line after tests. 
 message("")
-
-# Experimental benchmark target.
-file(GLOB tests "*_benchmark.c")
-foreach(test_source_fullpath ${tests})
-  string(REGEX REPLACE "^.*/" "" test_source ${test_source_fullpath})
-  string(REPLACE "benchmark.c" "benchmark" test_bin ${test_source})
-  message("     " ${test_source})
-  add_executable(${test_bin} ${test_source})
-  target_link_libraries(${test_bin} ${LIBEVENT2_LIBRARIES})
-  add_test(${test_bin} ${test_bin})
-endforeach()

+ 31 - 0
util/version/CMakeLists.txt

@@ -0,0 +1,31 @@
+# You may redistribute this program and/or modify it under the terms of
+# the GNU General Public License as published by the Free Software Foundation,
+# either version 3 of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+if(NOT "$ENV{GIT_VERSION}" STREQUAL "")
+    SET(GIT_VERSION "$ENV{GIT_VERSION}")
+elseif(EXISTS ${CMAKE_SOURCE_DIR}.git_commit)
+    FILE(READ .git_commit GIT_VERSION)
+else()
+    find_package(Git)
+    execute_process(
+        COMMAND ${GIT_EXECUTABLE} log -n 1
+        OUTPUT_VARIABLE GIT_LOG
+        OUTPUT_STRIP_TRAILING_WHITESPACE
+    )
+    string(REGEX MATCH "[0-9a-f]*\n" GIT_VERSION "${GIT_LOG}")
+    string(REPLACE "\n" "" GIT_VERSION "${GIT_VERSION}")
+endif()
+
+add_definitions("-D GIT_VERSION=\\\"${GIT_VERSION}\\\"")
+add_library(cjdns-util-version-version
+    Version.c
+)

+ 20 - 0
util/version/Version.c

@@ -0,0 +1,20 @@
+/* vim: set expandtab ts=4 sw=4: */
+/*
+ * You may redistribute this program and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#include "util/version/Version.h"
+
+const uint8_t* Version_gitVersion()
+{
+    return (uint8_t*) GIT_VERSION;
+}

+ 185 - 0
util/version/Version.h

@@ -0,0 +1,185 @@
+/* vim: set expandtab ts=4 sw=4: */
+/*
+ * You may redistribute this program and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef Version_H
+#define Version_H
+
+#include <stdint.h>
+
+/**
+ * Cjdns Protocol Versions
+ *
+ *
+ * Version 0:
+ * January 2012
+ * First version.
+ */
+#define Version_isCompat0(x, y) \
+    (x == y)
+/*
+ * ----------------------------------
+ *
+ * Version 1:
+ * October 2012
+ */
+#define Version_isCompat1(x, y) \
+    (Version_isCompat0(x, y) || (x == 1))
+/*
+ * When you send someone a message through cjdns, it's encrypted.
+ * When you send the first message to a router, it has a nice header on it which tells them your key
+ * and allows them to establish a cryptographic session with you. For every message after that, they
+ * need to remember that session and use it to decrypt the message.
+ *
+ * The way they remember which session a message is associated with is to look at the switching
+ * label from that message and compare that with the label which was used when the first message was
+ * sent (with the header).
+ *
+ * What I didn't think about at the time is that labels change. Nodes find a better path to a
+ * destination and expect the same session to work. It would work if the other end knew which
+ * session to use but it can't know, the label is different.
+ *
+ * This is a protocol bug.
+ *
+ * In my opinion, the best way to fix it is to send an additional header before the crypto nonce
+ * which tells the foreign node who it came from. When the handshake message is sent, the node will
+ * send a 4 byte integer which the other end will store. Every time the other end sends a
+ * non-handshake message to this end, it will prepend that same integer to the encrypted message and
+ * on recieving a message, a node will use that number to do a lookup of the correct session to use.
+ * The number can be a pointer or index offset so this can be quite fast.
+ * Integer 0xFFFFFFFF shall be reserved and handshake messages which contain this value must be
+ * ignored or treated as protocol 0 messages.
+ *
+ * But this is a protocol break.
+ *
+ * If a node gets handed a number and doesn't know what to do with it, it will think it's a
+ * CryptoAuth header and it will fail. New nodes would be able to try a message as the old form if
+ * the new form doesn't work but old nodes will just fail if they are ever sent a message in the new
+ * form.
+ *
+ * Proposed Solution:
+ * A key will be sent in all findNodes responses in the router, this key will be "np" for nodes'
+ * protocol (version).
+ * It will contain a string representation of a list of the protocol version numbers for the nodes
+ * which it is introducing. The numbers will all be the same number of bytes and the first byte will
+ * give that number. The length of the string will always be equal to one, plus the number of nodes
+ * times the value of the first byte.
+ *
+ * This example shows the p which would accompany a findNodes response containing 1 node with
+ * protocol version zero and one node with protocol version 1.
+ *
+ * "np": "\x01\x00\x01"
+ *
+ * 2:np3:\x01\x00\x01
+ *
+ * This example shows the p which would accompany a findNodes response containing 1 node with
+ * protocol version 300 and two nodes with protocol version 5.
+ *
+ * "np": "\x02\x01\x2c\x00\x05\x00\x05"
+ *
+ * 2:np7:\x02\x01\x2c\x00\x05\x00\x05
+ *
+ * All multi-byte numbers shall be encoded in big endian encoding.
+ *
+ * Each node will have an internal compatibility matrix giving protocol version numbers which
+ * can communicate, a node shall not respond to a findNodes message with a response containing any
+ * nodes which are known to be incompatible with the protocol version of the requesting node.
+ * Versions which are beyond the highest version number in the compatability matrix will be assumed
+ * to have the same compatability as the highest number in the table.
+ *
+ * All messages shall contain shall contain an "p" key as well but these will have a benc integer
+ * representing the protocol version of the sending node.
+ *
+ * Change to the Ping switch control message:
+ * New switch ping messages will all be 8 or more bytes long, they will begin with a magic field
+ * and then a version number which is the version of the sending node.
+ * The magic will be set to 0x09f91102 for all ping messages and to 0x9d74e35b in the response
+ * messages (these numbers shall be big endian encoded). Following this number will be a 4 byte
+ * field containing the protocol version. Nodes which only speak protocol version 0 will be
+ * identifyable because they echo back 0x09f91102 rather than replacing it and they will be unlikely
+ * to send a ping request whose content begins with 0x09f91102.
+ *
+ *
+ * ----------------------------------
+ *
+ * Version 2:
+ * Future
+ */
+#define Version_isCompat2(x, y) \
+    (Version_isCompat1(x, y) || (x == 2 && y > 0))
+/*
+ * Remove compatibility layer for communicating with version 0 nodes.
+ */
+
+
+
+/**
+ * Determine if two versions are compatible inefficiently.
+ * Used to populate lookup table.
+ *
+ * When adding a new version, this must be redirected to the highest
+ * numbered isCompat macro.
+ */
+#define Version_isCompatConst(x, y) \
+    (x > y ? Version_isCompat2(x, y) : Version_isCompat2(y, x))
+
+
+/**
+ * The current protocol version.
+ */
+#define Version_CURRENT_PROTOCOL 0
+
+
+/**
+ * The git commit version of the code.
+ */
+const uint8_t* Version_gitVersion();
+
+
+/**
+ * Check the compatibility matrix and return whether two versions are compatible.
+ * If a version is not listed on the table, the highest version on the table is
+ * substituted for it but if the return value is yes, it is changed to maybe.
+ *
+ * @param version1 the first version
+ * @param version2 the second version
+ * @return 1 meaning compatible or 0 meaning incompatible.
+ */
+static inline int Version_isCompatible(uint32_t version1, uint32_t version2)
+{
+    // When adding a new version, a new column and row must be added.
+    #define Version_TABLE_ROW(col) { \
+        Version_isCompatConst(col,0), \
+        Version_isCompatConst(col,1), \
+        Version_isCompatConst(col,2)  \
+    }
+    static const uint8_t table[3][3] = {
+        Version_TABLE_ROW(0),
+        Version_TABLE_ROW(1),
+        Version_TABLE_ROW(2)
+    };
+
+    #define Version_TABLE_HEIGHT (sizeof(table) / sizeof(table[0]))
+    #define Version_TABLE_WIDTH  sizeof(table[0])
+
+    if (version2 >= Version_TABLE_WIDTH) {
+        version2 = Version_TABLE_WIDTH - 1;
+    }
+    if (version1 >= Version_TABLE_HEIGHT) {
+        version1 = Version_TABLE_HEIGHT - 1;
+    }
+
+    return table[version1][version2];
+}
+
+#endif

+ 15 - 4
wire/Control.h

@@ -16,6 +16,7 @@
 #define Control_H
 
 #include "wire/Headers.h"
+#include "util/Endian.h"
 
 /**
  * Type one, error.
@@ -39,27 +40,37 @@ Assert_compileTime(sizeof(struct Control_Error) == Control_Error_MIN_SIZE);
  * Type two, ping.
  */
 #define Control_PING_be Endian_hostToBigEndian16(3)
-#define Control_Ping_MIN_SIZE 4
+#define Control_Ping_HEADER_SIZE 8
+#define Control_Ping_MIN_SIZE 12
 #define Control_Ping_MAX_SIZE 256
+#define Control_Ping_MAGIC Endian_hostToBigEndian32(0x09f91102)
 struct Control_Ping
 {
+    /** Magic: equal to Control_Ping_MAGIC in a ping and Control_Pong_MAGIC in a pong. */
+    uint32_t magic;
+
+    /** The version of the sending node. */
+    uint32_t version_be;
+
     /**
-     * Between 4 and 256 bytes of opaque data.
+     * Between 0 and 256 bytes of opaque data.
      * Since a ping is inherently a message to one's self,
      * the format is only of interest to the sender and thus undefined.
      */
-    uint8_t data[Control_Ping_MIN_SIZE];
+    uint8_t data[4];
 };
 Assert_compileTime(sizeof(struct Control_Ping) == Control_Ping_MIN_SIZE);
 
-
 /**
  * Type three, pong.
  * A pong is identical to a ping.
  */
 #define Control_PONG_be Endian_hostToBigEndian16(4)
+#define Control_Pong_HEADER_SIZE Control_Ping_HEADER_SIZE
 #define Control_Pong_MIN_SIZE Control_Ping_MIN_SIZE
 #define Control_Pong_MAX_SIZE Control_Ping_MAX_SIZE
+#define Control_Pong_MAGIC Endian_hostToBigEndian32(0x9d74e35b)
+
 
 static inline char* Control_typeString(uint16_t type_be)
 {