aboutsummaryrefslogtreecommitdiffstats
path: root/metis/ccnx/forwarder/metis/processor
diff options
context:
space:
mode:
authormichele papalini <micpapal+fdio@cisco.com>2017-02-23 17:01:34 +0100
committerMichele Papalini <micpapal+fdio@cisco.com>2017-02-23 17:23:19 +0000
commitc580a00aac271a524e5a75b35f4b91c174ed227b (patch)
treefeddc15a9f1a4eb319d950f8d6330ac2b91e3d99 /metis/ccnx/forwarder/metis/processor
parent9b30fc10fb1cbebe651e5a107e8ca5b24de54675 (diff)
Initial commit: sb-forwarder, metis.
Change-Id: I65ee3c851a6901929ef4417ad80d34bca0dce445 Signed-off-by: michele papalini <micpapal+fdio@cisco.com>
Diffstat (limited to 'metis/ccnx/forwarder/metis/processor')
-rw-r--r--metis/ccnx/forwarder/metis/processor/metis_ContentStore.c242
-rw-r--r--metis/ccnx/forwarder/metis/processor/metis_ContentStore.h52
-rw-r--r--metis/ccnx/forwarder/metis/processor/metis_ContentStoreEntry.c91
-rw-r--r--metis/ccnx/forwarder/metis/processor/metis_ContentStoreEntry.h86
-rw-r--r--metis/ccnx/forwarder/metis/processor/metis_FIB.c290
-rw-r--r--metis/ccnx/forwarder/metis/processor/metis_FIB.h159
-rw-r--r--metis/ccnx/forwarder/metis/processor/metis_FibEntry.c204
-rw-r--r--metis/ccnx/forwarder/metis/processor/metis_FibEntry.h122
-rw-r--r--metis/ccnx/forwarder/metis/processor/metis_FibEntryList.c82
-rw-r--r--metis/ccnx/forwarder/metis/processor/metis_FibEntryList.h96
-rw-r--r--metis/ccnx/forwarder/metis/processor/metis_HashTableFunction.c150
-rw-r--r--metis/ccnx/forwarder/metis/processor/metis_HashTableFunction.h268
-rw-r--r--metis/ccnx/forwarder/metis/processor/metis_MatchingRulesTable.c203
-rw-r--r--metis/ccnx/forwarder/metis/processor/metis_MatchingRulesTable.h144
-rw-r--r--metis/ccnx/forwarder/metis/processor/metis_MessageProcessor.c860
-rw-r--r--metis/ccnx/forwarder/metis/processor/metis_MessageProcessor.h220
-rw-r--r--metis/ccnx/forwarder/metis/processor/metis_PIT.c61
-rw-r--r--metis/ccnx/forwarder/metis/processor/metis_PIT.h119
-rw-r--r--metis/ccnx/forwarder/metis/processor/metis_PITVerdict.h38
-rw-r--r--metis/ccnx/forwarder/metis/processor/metis_PitEntry.c164
-rw-r--r--metis/ccnx/forwarder/metis/processor/metis_PitEntry.h177
-rw-r--r--metis/ccnx/forwarder/metis/processor/metis_StandardPIT.c326
-rw-r--r--metis/ccnx/forwarder/metis/processor/metis_StandardPIT.h46
-rw-r--r--metis/ccnx/forwarder/metis/processor/metis_Tap.h140
-rw-r--r--metis/ccnx/forwarder/metis/processor/test/CMakeLists.txt21
-rw-r--r--metis/ccnx/forwarder/metis/processor/test/test_metis_ContentStore.c437
-rw-r--r--metis/ccnx/forwarder/metis/processor/test/test_metis_FIB.c549
-rw-r--r--metis/ccnx/forwarder/metis/processor/test/test_metis_FibEntry.c136
-rw-r--r--metis/ccnx/forwarder/metis/processor/test/test_metis_FibEntryList.c115
-rw-r--r--metis/ccnx/forwarder/metis/processor/test/test_metis_HashTableFunction.c133
-rw-r--r--metis/ccnx/forwarder/metis/processor/test/test_metis_MatchingRulesTable.c666
-rw-r--r--metis/ccnx/forwarder/metis/processor/test/test_metis_MessageProcessor.c1533
-rw-r--r--metis/ccnx/forwarder/metis/processor/test/test_metis_PIT.c223
-rw-r--r--metis/ccnx/forwarder/metis/processor/test/test_metis_PitEntry.c294
-rw-r--r--metis/ccnx/forwarder/metis/processor/test/test_metis_StandardPIT.c498
-rw-r--r--metis/ccnx/forwarder/metis/processor/test/testrig_MockTap.h104
36 files changed, 9049 insertions, 0 deletions
diff --git a/metis/ccnx/forwarder/metis/processor/metis_ContentStore.c b/metis/ccnx/forwarder/metis/processor/metis_ContentStore.c
new file mode 100644
index 00000000..5d07c8e4
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/metis_ContentStore.c
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Has one hash table indexed by the ContentObjectHash which stores the objects.
+ *
+ * Has a MetisMatchingRulesTable used for index lookups. The stored data points to the
+ * object in the storage table.
+ *
+ * LRU used to manage evictions.
+ *
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <LongBow/runtime.h>
+
+#include <parc/algol/parc_HashCodeTable.h>
+#include <parc/algol/parc_Memory.h>
+
+#include <ccnx/forwarder/metis/processor/metis_ContentStore.h>
+#include <ccnx/forwarder/metis/processor/metis_ContentStoreEntry.h>
+#include <ccnx/forwarder/metis/processor/metis_MatchingRulesTable.h>
+#include <ccnx/forwarder/metis/processor/metis_HashTableFunction.h>
+#include <ccnx/forwarder/metis/processor/metis_LruList.h>
+
+typedef struct metis_contentstore_stats {
+ uint64_t countLruEvictions;
+ uint64_t countAdds;
+ uint64_t countHits;
+ uint64_t countMisses;
+} _MetisContentStoreStats;
+
+struct metis_contentstore {
+ PARCHashCodeTable *storageByObjectHash;
+ MetisMatchingRulesTable *indexTable;
+
+ size_t objectCapacity;
+ size_t objectCount;
+ MetisLruList *lruList;
+
+ _MetisContentStoreStats stats;
+ MetisLogger *logger;
+};
+
+
+// ========================================================================================
+
+static void
+_hashTableFunction_ContentStoreEntryDestroyer(void **dataPtr)
+{
+ metisContentStoreEntry_Release((MetisContentStoreEntry **) dataPtr);
+}
+
+static void
+_metisContentStore_EvictIfNecessary(MetisContentStore *store)
+{
+ if (store->objectCount >= store->objectCapacity) {
+ MetisLruListEntry *lruEntry = metisLruList_PopTail(store->lruList);
+ MetisContentStoreEntry *storeEntry = (MetisContentStoreEntry *) metisLruList_EntryGetData(lruEntry);
+ MetisMessage *evictedMessage = metisContentStoreEntry_GetMessage(storeEntry);
+
+ metisMatchingRulesTable_RemoveFromAll(store->indexTable, evictedMessage);
+
+ // This calls the destroyer on storeEntry, which only has a refcount 1 between the LRU and the
+ // storageByObjectHash table. if there's a higher refcount, its someone else holding a copy.
+ parcHashCodeTable_Del(store->storageByObjectHash, evictedMessage);
+
+ store->stats.countLruEvictions++;
+ store->objectCount--;
+
+ if (metisLogger_IsLoggable(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "ContentStore %p evict message %p (evictions %" PRIu64 ")",
+ (void *) store, (void *) evictedMessage, store->stats.countLruEvictions);
+ }
+
+ metisMessage_Release(&evictedMessage);
+ }
+}
+
+// ==========================================================================================
+
+MetisContentStore *
+metisContentStore_Create(size_t objectCapacity, MetisLogger *logger)
+{
+ size_t initialSize = objectCapacity * 2;
+ MetisContentStore *store = parcMemory_AllocateAndClear(sizeof(MetisContentStore));
+ assertNotNull(store, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisContentStore));
+ memset(&store->stats, 0, sizeof(_MetisContentStoreStats));
+
+ store->logger = metisLogger_Acquire(logger);
+ store->lruList = metisLruList_Create();
+ store->objectCapacity = objectCapacity;
+ store->objectCount = 0;
+
+ // initial size must be at least 1 or else the data structures break.
+ initialSize = (initialSize == 0) ? 1 : initialSize;
+
+ store->storageByObjectHash = parcHashCodeTable_Create_Size(metisHashTableFunction_MessageNameAndObjectHashEquals,
+ metisHashTableFunction_MessageNameAndObjectHashHashCode,
+ NULL,
+ _hashTableFunction_ContentStoreEntryDestroyer,
+ initialSize);
+
+ // no destroyer on the Rules Table. They objects are stored in the storage table.
+ store->indexTable = metisMatchingRulesTable_Create(NULL);
+
+ if (metisLogger_IsLoggable(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "ContentStore %p created with capacity %zu",
+ (void *) store, objectCapacity);
+ }
+ return store;
+}
+
+void
+metisContentStore_Destroy(MetisContentStore **storePtr)
+{
+ assertNotNull(storePtr, "Parameter must be non-null double pointer");
+ assertNotNull(*storePtr, "Parameter must dereference to non-null pointer");
+
+ MetisContentStore *store = *storePtr;
+
+ if (metisLogger_IsLoggable(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "ContentStore %p destroyed",
+ (void *) store);
+ }
+
+ metisLogger_Release(&store->logger);
+ metisMatchingRulesTable_Destroy(&store->indexTable);
+ parcHashCodeTable_Destroy(&store->storageByObjectHash);
+ metisLruList_Destroy(&store->lruList);
+ parcMemory_Deallocate((void **) &store);
+ *storePtr = NULL;
+}
+
+bool
+metisContentStore_Save(MetisContentStore *store, MetisMessage *objectMessage)
+{
+ bool result = false;
+
+ assertNotNull(store, "Parameter store must be non-null");
+ assertNotNull(objectMessage, "Parameter objectMessage must be non-null");
+ assertTrue(metisMessage_GetType(objectMessage) == MetisMessagePacketType_ContentObject,
+ "Parameter objectMessage must be a Content Object");
+
+ if (store->objectCapacity == 0) {
+ return false;
+ }
+
+ // if we're at capacity, this will pop the tail off the list and call metisContentStoreEntry_Destroy() on it.
+ _metisContentStore_EvictIfNecessary(store);
+
+ // This will add it to the LRU list at the head
+ MetisContentStoreEntry *entry = metisContentStoreEntry_Create(objectMessage, store->lruList);
+
+ // adds it to the canonical storage table. There is only a "1" refcount on the MetisContentStoreEntry, but it
+ // is stored in both the LRU and in the storageByObjectHash table
+
+ if (parcHashCodeTable_Add(store->storageByObjectHash, objectMessage, entry)) {
+ // index in all the lookup tables the content object ByName, ByNameAndKeyId, and ByNameAndObjectHash
+ metisMatchingRulesTable_AddToAllTables(store->indexTable, objectMessage, entry);
+
+ store->objectCount++;
+ store->stats.countAdds++;
+ result = true;
+
+ if (metisLogger_IsLoggable(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "ContentStore %p saved message %p (object count %" PRIu64 ")",
+ (void *) store, (void *) objectMessage, store->objectCount);
+ }
+ } else {
+ if (metisLogger_IsLoggable(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Warning)) {
+ metisLogger_Log(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Warning, __func__,
+ "ContentStore %p failed to add message %p to hash table",
+ (void *) store, (void *) objectMessage);
+ }
+
+
+ // Free what we just created, but did not add. 'entry' has ownership of 'copy', and so will
+ // call _Release() on it.
+ metisContentStoreEntry_Release(&entry);
+ }
+
+ return result;
+}
+
+MetisMessage *
+metisContentStore_Fetch(MetisContentStore *store, MetisMessage *interestMessage)
+{
+ assertNotNull(store, "Parameter store must be non-null");
+ assertNotNull(interestMessage, "Parameter interestMessage must be non-null");
+ assertTrue(metisMessage_GetType(interestMessage) == MetisMessagePacketType_Interest,
+ "Parameter interestMessage must be an Interest");
+
+ // This will do the most restrictive lookup.
+ // a) If the interest has a ContentObjectHash restriction, it will look only in the ByNameAndObjectHash table.
+ // b) If it has a KeyId, it will look only in the ByNameAndKeyId table.
+ // c) otherwise, it looks only in the ByName table.
+
+ MetisContentStoreEntry *storeEntry = metisMatchingRulesTable_Get(store->indexTable, interestMessage);
+ MetisMessage *copy = NULL;
+
+ if (storeEntry) {
+ metisContentStoreEntry_MoveToHead(storeEntry);
+ copy = metisContentStoreEntry_GetMessage(storeEntry);
+ store->stats.countHits++;
+
+ if (metisLogger_IsLoggable(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "ContentStore %p matched interest %p (hits %" PRIu64 ", misses %" PRIu64 ")",
+ (void *) store, (void *) interestMessage, store->stats.countHits, store->stats.countMisses);
+ }
+ } else {
+ store->stats.countMisses++;
+ if (metisLogger_IsLoggable(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "ContentStore %p missed interest %p (hits %" PRIu64 ", misses %" PRIu64 ")",
+ (void *) store, (void *) interestMessage, store->stats.countHits, store->stats.countMisses);
+ }
+ }
+
+ return copy;
+}
+
diff --git a/metis/ccnx/forwarder/metis/processor/metis_ContentStore.h b/metis/ccnx/forwarder/metis/processor/metis_ContentStore.h
new file mode 100644
index 00000000..5c485825
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/metis_ContentStore.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef Metis_metis_ContentStore_h
+#define Metis_metis_ContentStore_h
+
+#include <ccnx/forwarder/metis/core/metis_Message.h>
+#include <ccnx/forwarder/metis/core/metis_Logger.h>
+
+struct metis_contentstore;
+typedef struct metis_contentstore MetisContentStore;
+
+MetisContentStore *metisContentStore_Create(size_t objectCapacity, MetisLogger *logger);
+void metisContentStore_Destroy(MetisContentStore **storePtr);
+
+/**
+ * @function metisContentStore_Save
+ * @abstract Saves content object in the store
+ * @discussion
+ * Will make a reference counted copy of the message, so caller retains ownership of original message.
+ *
+ * @param <#param1#>
+ * @return True if saved, false othewise
+ */
+
+bool metisContentStore_Save(MetisContentStore *store, MetisMessage *objectMessage);
+
+/**
+ * @function metisContentStore_Fetch
+ * @abstract Fetch a content object from the store that matches the interest message
+ * @discussion
+ * Returns a reference counted copy, caller must Destroy it.
+ *
+ * @param <#param1#>
+ * @return May be NULL if no match
+ */
+MetisMessage *metisContentStore_Fetch(MetisContentStore *store, MetisMessage *interestMessage);
+
+#endif // Metis_metis_ContentStore_h
diff --git a/metis/ccnx/forwarder/metis/processor/metis_ContentStoreEntry.c b/metis/ccnx/forwarder/metis/processor/metis_ContentStoreEntry.c
new file mode 100644
index 00000000..cefc547a
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/metis_ContentStoreEntry.c
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <stdio.h>
+
+#include <parc/algol/parc_Memory.h>
+#include <ccnx/forwarder/metis/processor/metis_ContentStoreEntry.h>
+
+#include <LongBow/runtime.h>
+
+struct metis_content_store_entry {
+ MetisMessage *message;
+ MetisLruListEntry *lruEntry;
+ unsigned refcount;
+};
+
+MetisContentStoreEntry *
+metisContentStoreEntry_Create(MetisMessage *objectMessage, MetisLruList *lruList)
+{
+ assertNotNull(objectMessage, "Parameter objectMessage must be non-null");
+
+ MetisContentStoreEntry *entry = parcMemory_AllocateAndClear(sizeof(MetisContentStoreEntry));
+ assertNotNull(entry, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisContentStoreEntry));
+ entry->message = metisMessage_Acquire(objectMessage);
+ entry->refcount = 1;
+ entry->lruEntry = metisLruList_NewHeadEntry(lruList, entry);
+
+ return entry;
+}
+
+MetisContentStoreEntry *
+metisContentStoreEntry_Acquire(MetisContentStoreEntry *original)
+{
+ assertNotNull(original, "Parameter must be non-null");
+ original->refcount++;
+ return original;
+}
+
+void
+metisContentStoreEntry_Release(MetisContentStoreEntry **entryPtr)
+{
+ assertNotNull(entryPtr, "Parameter must be non-null double pointer");
+ assertNotNull(*entryPtr, "Parameter must dereference to non-null pointer");
+
+ MetisContentStoreEntry *entry = *entryPtr;
+ assertTrue(entry->refcount > 0, "Illegal state: has refcount of 0");
+
+ entry->refcount--;
+ if (entry->refcount == 0) {
+ metisLruList_EntryDestroy(&entry->lruEntry);
+ metisMessage_Release(&entry->message);
+ parcMemory_Deallocate((void **) &entry);
+ }
+ *entryPtr = NULL;
+}
+
+/**
+ * @function metisContentStoreEntry_GetMessage
+ * @abstract Returns a reference counted copy of the message.
+ * @discussion
+ * Caller must use <code>metisMessage_Release()</code> on the returned message
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+MetisMessage *
+metisContentStoreEntry_GetMessage(MetisContentStoreEntry *storeEntry)
+{
+ assertNotNull(storeEntry, "Parameter must be non-null");
+ return metisMessage_Acquire(storeEntry->message);
+}
+
+void
+metisContentStoreEntry_MoveToHead(MetisContentStoreEntry *storeEntry)
+{
+ assertNotNull(storeEntry, "Parameter must be non-null");
+ metisLruList_EntryMoveToHead(storeEntry->lruEntry);
+}
diff --git a/metis/ccnx/forwarder/metis/processor/metis_ContentStoreEntry.h b/metis/ccnx/forwarder/metis/processor/metis_ContentStoreEntry.h
new file mode 100644
index 00000000..d7059a4b
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/metis_ContentStoreEntry.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef Metis_metis_ContentStoreEntry_h
+#define Metis_metis_ContentStoreEntry_h
+
+#include <ccnx/forwarder/metis/core/metis_Message.h>
+#include <ccnx/forwarder/metis/processor/metis_LruList.h>
+
+struct metis_content_store_entry;
+typedef struct metis_content_store_entry MetisContentStoreEntry;
+
+/**
+ * @function metisContentStoreEntry_Create
+ * @abstract Creates a content store entry, saving a reference to the message
+ * @discussion
+ * When <code>metisContentStoreEntry_Destroy()</code> is called, will release its reference
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+MetisContentStoreEntry *metisContentStoreEntry_Create(MetisMessage *objectMessage, MetisLruList *lruList);
+
+/**
+ * @function metisContentStoreEntry_Copy
+ * @abstract Returns a reference counted copy
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return Reference counted copy, must call <code>metisContentStoreEntry_Destroy()</code> on it.
+ */
+MetisContentStoreEntry *metisContentStoreEntry_Acquire(MetisContentStoreEntry *original);
+
+/**
+ * Releases one reference count and destroys object when reaches zero
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [in,out] entryPtr A pointer to an allocated MetisContentStoreEntry
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+void metisContentStoreEntry_Release(MetisContentStoreEntry **entryPtr);
+
+/**
+ * @function metisContentStoreEntry_GetMessage
+ * @abstract Returns a reference counted copy of the message.
+ * @discussion
+ * Caller must use <code>metisMessage_Release()</code> on the returned message
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+MetisMessage *metisContentStoreEntry_GetMessage(MetisContentStoreEntry *storeEntry);
+
+/**
+ * Move this entry to the head of the LRU list
+ *
+ * Moves the entry to the head of the LRU list it was created with
+ *
+ * @param [in] storeEntry An allocated MetisContenstoreEntry
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+void metisContentStoreEntry_MoveToHead(MetisContentStoreEntry *storeEntry);
+#endif // Metis_metis_ContentStoreEntry_h
diff --git a/metis/ccnx/forwarder/metis/processor/metis_FIB.c b/metis/ccnx/forwarder/metis/processor/metis_FIB.c
new file mode 100644
index 00000000..3c76a072
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/metis_FIB.c
@@ -0,0 +1,290 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Right now, the FIB table is sparse. There can be an entry for /a and for /a/b/c, but
+ * not for /a/b. This means we need to exhastively lookup all the components to make sure
+ * there's not a route for it.
+ *
+ */
+
+#include <config.h>
+#include <stdio.h>
+
+#include <ccnx/forwarder/metis/processor/metis_FIB.h>
+#include <ccnx/forwarder/metis/processor/metis_FibEntry.h>
+#include <ccnx/forwarder/metis/processor/metis_HashTableFunction.h>
+#include <parc/algol/parc_Memory.h>
+#include <parc/algol/parc_TreeRedBlack.h>
+
+#include <LongBow/runtime.h>
+
+// =====================================================
+
+/**
+ * @function hashTableFunction_FibEntryDestroyer
+ * @abstract Used in the hash table to destroy the data pointer when an item's removed
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+static void
+_hashTableFunction_FibEntryDestroyer(void **dataPtr)
+{
+ metisFibEntry_Release((MetisFibEntry **) dataPtr);
+}
+
+/**
+ * @function hashTableFunction_TlvNameDestroyer
+ * @abstract Used in the hash table to destroy the key pointer when an item's removed
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+static void
+_hashTableFunction_TlvNameDestroyer(void **dataPtr)
+{
+ metisTlvName_Release((MetisTlvName **) dataPtr);
+}
+
+// =====================================================
+
+struct metis_fib {
+ // KEY = tlvName, VALUE = FibEntry
+ PARCHashCodeTable *tableByName;
+
+ // KEY = tlvName. We use a tree for the keys because that
+ // has the same average insert and remove time. The tree
+ // is only used by GetEntries, which in turn is used by things
+ // that want to enumerate the FIB
+ PARCTreeRedBlack *tableOfKeys;
+
+ MetisLogger *logger;
+
+ // If there are no forward paths, we return an emtpy set. Allocate this
+ // once and return a reference to it whenever we need an empty set.
+ MetisNumberSet *emptySet;
+};
+
+static MetisFibEntry *_metisFIB_CreateFibEntry(MetisFIB *fib, MetisTlvName *tlvName, const char *fwdStrategy);
+
+// =====================================================
+// Public API
+
+MetisFIB *
+metisFIB_Create(MetisLogger *logger)
+{
+ unsigned initialSize = 1024;
+
+ MetisFIB *fib = parcMemory_AllocateAndClear(sizeof(MetisFIB));
+ assertNotNull(fib, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisFIB));
+ fib->emptySet = metisNumberSet_Create();
+ fib->logger = metisLogger_Acquire(logger);
+ fib->tableByName = parcHashCodeTable_Create_Size(metisHashTableFunction_TlvNameEquals,
+ metisHashTableFunction_TlvNameHashCode,
+ _hashTableFunction_TlvNameDestroyer,
+ _hashTableFunction_FibEntryDestroyer,
+ initialSize);
+
+ fib->tableOfKeys =
+ parcTreeRedBlack_Create(metisHashTableFunction_TlvNameCompare, NULL, NULL, NULL, NULL, NULL);
+
+ if (metisLogger_IsLoggable(fib->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(fib->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "FIB %p created with initialSize %u",
+ (void *) fib, initialSize);
+ }
+
+ return fib;
+}
+
+void
+metisFIB_Destroy(MetisFIB **fibPtr)
+{
+ assertNotNull(fibPtr, "Parameter must be non-null double pointer");
+ assertNotNull(*fibPtr, "Parameter must dereference to non-null pointer");
+
+ MetisFIB *fib = *fibPtr;
+
+ if (metisLogger_IsLoggable(fib->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(fib->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "FIB %p destroyed",
+ (void *) fib);
+ }
+
+ metisNumberSet_Release(&fib->emptySet);
+ metisLogger_Release(&fib->logger);
+ parcTreeRedBlack_Destroy(&fib->tableOfKeys);
+ parcHashCodeTable_Destroy(&fib->tableByName);
+ parcMemory_Deallocate((void **) &fib);
+ *fibPtr = NULL;
+}
+
+MetisFibEntry *
+metisFIB_Match(MetisFIB *fib, const MetisMessage *interestMessage)
+{
+ assertNotNull(fib, "Parameter fib must be non-null");
+ assertNotNull(interestMessage, "Parameter interestMessage must be non-null");
+
+ if (metisMessage_HasName(interestMessage)) {
+ // this is NOT reference counted, don't destroy it
+ MetisTlvName *tlvName = metisMessage_GetName(interestMessage);
+ MetisFibEntry *longestMatchingFibEntry = NULL;
+
+ // because the FIB table is sparse, we need to scan all the name segments in order.
+ for (size_t i = 0; i < metisTlvName_SegmentCount(tlvName); i++) {
+ MetisTlvName *prefixName = metisTlvName_Slice(tlvName, i + 1);
+ MetisFibEntry *fibEntry = parcHashCodeTable_Get(fib->tableByName, prefixName);
+ if (fibEntry != NULL) {
+
+ // we can accept the FIB entry if it does not contain the ingress connection id or if
+ // there is more than one forward path besides the ingress connection id.
+ const MetisNumberSet *nexthops = metisFibEntry_GetNexthops(fibEntry);
+ bool containsIngressConnectionId = metisNumberSet_Contains(nexthops, metisMessage_GetIngressConnectionId(interestMessage));
+ size_t nextHopsCount = metisNumberSet_Length(nexthops);
+ // Further control on the nextHopCount, because if the first condition is true (no ingress connection among the next hops), the number of next hops could still be 0.
+ if ((!containsIngressConnectionId && nextHopsCount > 0) || nextHopsCount > 1) {
+ longestMatchingFibEntry = fibEntry;
+ }
+ }
+ metisTlvName_Release(&prefixName);
+ }
+ return longestMatchingFibEntry;
+ }
+
+ return NULL;
+}
+
+bool
+metisFIB_AddOrUpdate(MetisFIB *fib, CPIRouteEntry *route, char const * fwdStrategy)
+{
+ assertNotNull(fib, "Parameter fib must be non-null");
+ assertNotNull(route, "Parameter route must be non-null");
+
+ const CCNxName *ccnxName = cpiRouteEntry_GetPrefix(route);
+ MetisTlvName *tlvName = metisTlvName_CreateFromCCNxName(ccnxName);
+
+ MetisFibEntry *fibEntry = parcHashCodeTable_Get(fib->tableByName, tlvName);
+ if (fibEntry == NULL) {
+ if(fwdStrategy == NULL){
+ fwdStrategy = "random"; //default strategy for now
+ }
+ fibEntry = _metisFIB_CreateFibEntry(fib, tlvName, fwdStrategy);
+ }
+
+ metisFibEntry_AddNexthop(fibEntry, route);
+
+ // if anyone saved the name in a table, they copied it.
+ metisTlvName_Release(&tlvName);
+
+ return true;
+}
+
+bool
+metisFIB_Remove(MetisFIB *fib, CPIRouteEntry *route)
+{
+ assertNotNull(fib, "Parameter fib must be non-null");
+ assertNotNull(route, "Parameter route must be non-null");
+
+ bool routeRemoved = false;
+
+ const CCNxName *ccnxName = cpiRouteEntry_GetPrefix(route);
+ MetisTlvName *tlvName = metisTlvName_CreateFromCCNxName(ccnxName);
+
+ MetisFibEntry *fibEntry = parcHashCodeTable_Get(fib->tableByName, tlvName);
+ if (fibEntry != NULL) {
+ metisFibEntry_RemoveNexthopByRoute(fibEntry, route);
+ if (metisFibEntry_NexthopCount(fibEntry) == 0) {
+ parcTreeRedBlack_Remove(fib->tableOfKeys, tlvName);
+
+ // this will de-allocate the key, so must be done last
+ parcHashCodeTable_Del(fib->tableByName, tlvName);
+
+ routeRemoved = true;
+ }
+ }
+
+ metisTlvName_Release(&tlvName);
+ return routeRemoved;
+}
+
+size_t
+metisFIB_Length(const MetisFIB *fib)
+{
+ assertNotNull(fib, "Parameter fib must be non-null");
+ return parcHashCodeTable_Length(fib->tableByName);
+}
+
+MetisFibEntryList *
+metisFIB_GetEntries(const MetisFIB *fib)
+{
+ assertNotNull(fib, "Parameter fib must be non-null");
+ MetisFibEntryList *list = metisFibEntryList_Create();
+
+ PARCArrayList *values = parcTreeRedBlack_Values(fib->tableOfKeys);
+ for (size_t i = 0; i < parcArrayList_Size(values); i++) {
+ MetisFibEntry *original = (MetisFibEntry *) parcArrayList_Get(values, i);
+ metisFibEntryList_Append(list, original);
+ }
+ parcArrayList_Destroy(&values);
+ return list;
+}
+
+void
+metisFIB_RemoveConnectionIdFromRoutes(MetisFIB *fib, unsigned connectionId)
+{
+ assertNotNull(fib, "Parameter fib must be non-null");
+
+ // Walk the entire tree and remove the connection id from every entry.
+ PARCArrayList *values = parcTreeRedBlack_Values(fib->tableOfKeys);
+ for (size_t i = 0; i < parcArrayList_Size(values); i++) {
+ MetisFibEntry *original = (MetisFibEntry *) parcArrayList_Get(values, i);
+ metisFibEntry_RemoveNexthopByConnectionId(original, connectionId);
+ }
+ parcArrayList_Destroy(&values);
+}
+
+// =========================================================================
+// Private API
+
+/**
+ * @function metisFib_CreateFibEntry
+ * @abstract Create the given FIB entry
+ * @discussion
+ * PRECONDITION: You know that the FIB entry does not exist already
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+static MetisFibEntry *
+_metisFIB_CreateFibEntry(MetisFIB *fib, MetisTlvName *tlvName, const char *fwdStrategy)
+{
+ MetisFibEntry *entry = metisFibEntry_Create(tlvName, fwdStrategy);
+
+ // add a reference counted name, as we specified a key destroyer when we
+ // created the table.
+ MetisTlvName *copy = metisTlvName_Acquire(tlvName);
+ parcHashCodeTable_Add(fib->tableByName, copy, entry);
+
+ // this is an index structure. It does not have its own destroyer functions in
+ // the data structure. The data in this table is the same pointer as in the hash table.
+ parcTreeRedBlack_Insert(fib->tableOfKeys, copy, entry);
+
+ return entry;
+}
diff --git a/metis/ccnx/forwarder/metis/processor/metis_FIB.h b/metis/ccnx/forwarder/metis/processor/metis_FIB.h
new file mode 100644
index 00000000..97e20fd6
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/metis_FIB.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * The Forwarding Information Base (FIB) table is a map from a name to a MetisFibEntry.
+ *
+ * Each MetisFibEntry has a set of nexthops and a MetisStrategy to pick a nexthop.
+ *
+ * The strategy may be changed. It will wipe out all the previous state for the last
+ * strategy and the new strategy will need to start from scratch. changing the strategy does
+ * not change the nexthops, but it does wipe any stragegy-specific state in each nexthop.
+ *
+ * So, the FIB table is make up of rows like this:
+ * name -> { strategy, { {nexthop_1, strategyState_1}, {nexthop_2, strategyState_2}, ... } }
+ *
+ * The "strategy" is a MetisStrategyImpl function structure (see strategies/metis_Strategy.h).
+ * Some strategies might allocate an implementation per row, others might use one implementation
+ * for the whole table. Its up to the strategy implementation.
+ *
+ *
+ */
+
+#ifndef Metis_metis_FIB_h
+#define Metis_metis_FIB_h
+
+#include <ccnx/common/ccnx_Name.h>
+#include <ccnx/api/control/cpi_RouteEntry.h>
+
+#include <ccnx/forwarder/metis/core/metis_NumberSet.h>
+#include <ccnx/forwarder/metis/core/metis_Message.h>
+#include <ccnx/forwarder/metis/processor/metis_FibEntryList.h>
+#include <ccnx/forwarder/metis/processor/metis_FibEntry.h>
+#include <ccnx/forwarder/metis/core/metis_Logger.h>
+
+struct metis_fib;
+typedef struct metis_fib MetisFIB;
+
+/**
+ * <#One Line Description#>
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @return <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisFIB *metisFIB_Create(MetisLogger *logger);
+
+/**
+ * <#One Line Description#>
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @return <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+void metisFIB_Destroy(MetisFIB **fibPtr);
+
+/**
+ * @function metisFib_AddOrUpdate
+ * @abstract Adds or updates a route
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return true if added/updated, false if a problem.
+ */
+bool metisFIB_AddOrUpdate(MetisFIB *fib, CPIRouteEntry *route, const char * fwdStrategy);
+
+/**
+ * Removes a route
+ *
+ * Removes a specific nexthop for a route. If there are no nexthops left after the
+ * removal, the entire route is deleted from the FIB.
+ *
+ * @param [in] fib The FIB to modify
+ * @param [in] route The route to remove
+ *
+ * @retval true Route completely removed
+ * @retval false There are still other nexthops for the route
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+bool metisFIB_Remove(MetisFIB *fib, CPIRouteEntry *route);
+
+/**
+ * Removes the given connection ID from all routes
+ *
+ * Removes the given connection ID from all routes. If that leaves a route
+ * with no nexthops, the route remains in the table with an empty nexthop set.
+ *
+ * @param [in] fib The forwarding table
+ * @param [in] connectionId The connection to remove.
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+void metisFIB_RemoveConnectionIdFromRoutes(MetisFIB *fib, unsigned connectionId);
+
+/**
+ * @function metisFib_Match
+ * @abstract Lookup the interest in the FIB, returns set of connection ids to forward over
+ * @discussion
+ * This is the internal state of the FIB entry. If you will store a copy you must acquire a reference.
+ *
+ * @param <#param1#>
+ * @return May be empty, should not be null
+ */
+MetisFibEntry *metisFIB_Match(MetisFIB *fib, const MetisMessage *interestMessage);
+/**
+ * @function metisFib_Length
+ * @abstract The number of entries in the forwarding table
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+size_t metisFIB_Length(const MetisFIB *fib);
+
+/**
+ * @function metisFib_GetEntries
+ * @abstract Returns a list of the current FIB entries.
+ * @discussion
+ * Caller must destroy the list
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+MetisFibEntryList *metisFIB_GetEntries(const MetisFIB *fib);
+#endif // Metis_metis_FIB_h
diff --git a/metis/ccnx/forwarder/metis/processor/metis_FibEntry.c b/metis/ccnx/forwarder/metis/processor/metis_FibEntry.c
new file mode 100644
index 00000000..3bcca8b0
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/metis_FibEntry.c
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include <config.h>
+#include <stdio.h>
+
+#include <ccnx/forwarder/metis/processor/metis_FibEntry.h>
+#include <ccnx/forwarder/metis/core/metis_NumberSet.h>
+
+#include <ccnx/forwarder/metis/strategies/metis_StrategyImpl.h>
+#include <ccnx/forwarder/metis/strategies/strategy_Rnd.h>
+#include <ccnx/forwarder/metis/strategies/strategy_LoadBalancer.h>
+#include <ccnx/forwarder/metis/strategies/strategy_RndSegment.h>
+#include <ccnx/forwarder/metis/strategies/strategy_LoadBalancerWithPD.h>
+
+#include <parc/algol/parc_Memory.h>
+#include <LongBow/runtime.h>
+
+struct metis_fib_entry {
+ MetisTlvName *name;
+ unsigned refcount;
+ MetisStrategyImpl *fwdStrategy;
+};
+
+
+MetisFibEntry *
+metisFibEntry_Create(MetisTlvName *name, const char *fwdStrategy)
+{
+ MetisFibEntry *fibEntry = parcMemory_AllocateAndClear(sizeof(MetisFibEntry));
+ assertNotNull(fibEntry, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisFibEntry));
+ fibEntry->name = metisTlvName_Acquire(name);
+ CCNxName *ccnxName = metisTlvName_ToCCNxName(name);
+ char *strname = ccnxName_ToString(ccnxName);
+ if (strcmp(fwdStrategy, FWD_STRATEGY_LOADBALANCER) == 0) {
+ printf("[Metis Forwarding Strategy] --- set \"laodbalancer\" for %s\n", strname);
+ fibEntry->fwdStrategy = strategyLoadBalancer_Create();
+ } else if (strcmp(fwdStrategy, FWD_STRATEGY_RANDOM_PER_DASH_SEGMENT) == 0) {
+ printf("[Metis Forwarding Strategy] --- set \"random_per_dash_segment\" for %s\n", strname);
+ fibEntry->fwdStrategy = strategyRndSegment_Create();
+ } else if (strcmp(fwdStrategy, FWD_STRATEGY_LOADBALANCER_WITH_DELAY) == 0) {
+ printf("[Metis Forwarding Strategy] --- set \"laodbalancer with dealy\" for %s\n", strname);
+ fibEntry->fwdStrategy = strategyLoadBalancerWithPD_Create();
+ } else {
+ //random is the defualt strategy
+ printf("[Metis Forwarding Strategy] --- set \"random\" for %s\n", strname);
+ fibEntry->fwdStrategy = strategyRnd_Create(); //the Random strategy is the default one
+ //other strategies can be set using the appropiate function
+ }
+
+ ccnxName_Release(&ccnxName);
+ parcMemory_Deallocate((void **) &strname);
+ fibEntry->refcount = 1;
+ return fibEntry;
+}
+
+MetisFibEntry *
+metisFibEntry_Acquire(const MetisFibEntry *fibEntry)
+{
+ assertNotNull(fibEntry, "Parameter fibEntry must be non-null");
+ MetisFibEntry *copy = (MetisFibEntry *) fibEntry;
+ copy->refcount++;
+ return copy;
+}
+
+void
+metisFibEntry_Release(MetisFibEntry **fibEntryPtr)
+{
+ MetisFibEntry *fibEntry = *fibEntryPtr;
+ assertTrue(fibEntry->refcount > 0, "Illegal state: refcount is 0");
+ fibEntry->refcount--;
+ if (fibEntry->refcount == 0) {
+ metisTlvName_Release(&fibEntry->name);
+ fibEntry->fwdStrategy->destroy(&(fibEntry->fwdStrategy));
+ parcMemory_Deallocate((void **) &fibEntry);
+ }
+ *fibEntryPtr = NULL;
+}
+
+void
+metisFibEntry_SetStrategy(MetisFibEntry *fibEntry, const char *strategy)
+{
+ MetisStrategyImpl *fwdStrategyImpl;
+ char *strname = ccnxName_ToString(metisTlvName_ToCCNxName(fibEntry->name));
+ if (strcmp(strategy, FWD_STRATEGY_LOADBALANCER) == 0) {
+ printf("[Metis Forwarding Strategy] --- change to \"laodbalancer\" for %s\n", strname);
+ fwdStrategyImpl = strategyLoadBalancer_Create();
+ } else if (strcmp(strategy, FWD_STRATEGY_RANDOM_PER_DASH_SEGMENT) == 0) {
+ printf("[Metis Forwarding Strategy] --- change to \"random_per_dash_segment\" for %s\n", strname);
+ fwdStrategyImpl = strategyRndSegment_Create();
+ } else if (strcmp(strategy, FWD_STRATEGY_LOADBALANCER_WITH_DELAY) == 0) {
+ printf("[Metis Forwarding Strategy] --- change to \"loadbalancer_with_delay\" for %s\n", strname);
+ fwdStrategyImpl = strategyLoadBalancerWithPD_Create();
+ } else {
+ //random is the defualt strategy
+ printf("[Metis Forwarding Strategy] --- change to \"random\" for %s\n", strname);
+ fwdStrategyImpl = strategyRnd_Create(); //the Random strategy is the default one
+ //other strategies can be set using the appropiate function
+ }
+ parcMemory_Deallocate((void **) &strname);
+
+ const MetisNumberSet *nexthops = metisFibEntry_GetNexthops(fibEntry);
+ unsigned size = metisFibEntry_NexthopCount(fibEntry);
+ for (unsigned i = 0; i < size; i++) {
+ CCNxName *ccnxName = metisTlvName_ToCCNxName(fibEntry->name);
+ CPIRouteEntry *cpiRouteEntry = cpiRouteEntry_Create(ccnxName, metisNumberSet_GetItem(nexthops, i), NULL, 0, 0, NULL, 0);
+ fwdStrategyImpl->addNexthop(fwdStrategyImpl, cpiRouteEntry);
+ cpiRouteEntry_Destroy(&cpiRouteEntry);
+ }
+ fibEntry->fwdStrategy->destroy(&(fibEntry->fwdStrategy));
+ fibEntry->fwdStrategy = fwdStrategyImpl;
+}
+
+void
+metisFibEntry_AddNexthop(MetisFibEntry *fibEntry, CPIRouteEntry *route)
+{
+ assertNotNull(fibEntry, "Parameter fibEntry must be non-null");
+ fibEntry->fwdStrategy->addNexthop(fibEntry->fwdStrategy, route);
+}
+
+void
+metisFibEntry_RemoveNexthopByRoute(MetisFibEntry *fibEntry, CPIRouteEntry *route)
+{
+ assertNotNull(fibEntry, "Parameter fibEntry must be non-null");
+ fibEntry->fwdStrategy->removeNexthop(fibEntry->fwdStrategy, route);
+}
+
+void
+metisFibEntry_RemoveNexthopByConnectionId(MetisFibEntry *fibEntry, unsigned connectionId)
+{
+ assertNotNull(fibEntry, "Parameter fibEntry must be non-null");
+ CCNxName *ccnxName = metisTlvName_ToCCNxName(fibEntry->name);
+ //this is a fake route, create only to deal with the strategyImpl interface
+ CPIRouteEntry *cpiRouteEntry = cpiRouteEntry_Create(ccnxName, connectionId, NULL, 0, 0, NULL, 1);
+ metisFibEntry_RemoveNexthopByRoute(fibEntry, cpiRouteEntry);
+ cpiRouteEntry_Destroy(&cpiRouteEntry);
+}
+
+
+size_t
+metisFibEntry_NexthopCount(const MetisFibEntry *fibEntry)
+{
+ assertNotNull(fibEntry, "Parameter fibEntry must be non-null");
+ return fibEntry->fwdStrategy->countNexthops(fibEntry->fwdStrategy);
+}
+
+const MetisNumberSet *
+metisFibEntry_GetNexthops(const MetisFibEntry *fibEntry)
+{
+ assertNotNull(fibEntry, "Parameter fibEntry must be non-null");
+ return fibEntry->fwdStrategy->returnNexthops(fibEntry->fwdStrategy);
+}
+
+const MetisNumberSet *
+metisFibEntry_GetNexthopsFromForwardingStrategy(const MetisFibEntry *fibEntry, const MetisMessage *interestMessage)
+{
+ assertNotNull(fibEntry, "Parameter fibEntry must be non-null");
+ return fibEntry->fwdStrategy->lookupNexthop(fibEntry->fwdStrategy, interestMessage);
+}
+
+void
+metisFibEntry_ReceiveObjectMessage(const MetisFibEntry *fibEntry, const MetisNumberSet *egressId, const MetisMessage *objectMessage, MetisTicks rtt)
+{
+ assertNotNull(fibEntry, "Parameter fibEntry must be non-null");
+ fibEntry->fwdStrategy->receiveObject(fibEntry->fwdStrategy, egressId, objectMessage, rtt);
+}
+
+void
+metisFibEntry_OnTimeout(const MetisFibEntry *fibEntry, const MetisNumberSet *egressId)
+{
+ assertNotNull(fibEntry, "Parameter fibEntry must be non-null");
+ fibEntry->fwdStrategy->onTimeout(fibEntry->fwdStrategy, egressId);
+}
+
+MetisTlvName *
+metisFibEntry_GetPrefix(const MetisFibEntry *fibEntry)
+{
+ assertNotNull(fibEntry, "Parameter fibEntry must be non-null");
+ return metisTlvName_Acquire(fibEntry->name);
+}
+
+const char *
+metisFibEntry_GetFwdStrategyType(const MetisFibEntry *fibEntry)
+{
+ return fibEntry->fwdStrategy->getStrategy(fibEntry->fwdStrategy);
+}
+
+MetisStrategyImpl *
+metisFibEntry_GetFwdStrategy(const MetisFibEntry *fibEntry)
+{
+ return fibEntry->fwdStrategy;
+}
diff --git a/metis/ccnx/forwarder/metis/processor/metis_FibEntry.h b/metis/ccnx/forwarder/metis/processor/metis_FibEntry.h
new file mode 100644
index 00000000..ec848c05
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/metis_FibEntry.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file metis_FibEntry.h
+ * @brief A forwarding entry in the FIB table
+ *
+ * A Forwarding Information Base (FIB) entry (MetisFibEntry) is a
+ * set of nexthops for a name. It also indicates the forwarding strategy.
+ *
+ * Each nexthop contains the ConnectionId assocaited with it. This could be
+ * something specific like a MAC address or point-to-point tunnel. Or, it
+ * could be something general like a MAC group address or ip multicast overlay.
+ *
+ * See metis/strategies/metis_Strategy.h for a description of forwarding strategies.
+ * In short, a strategy is the algorithm used to select one or more nexthops from
+ * the set of available nexthops.
+ *
+ * Each nexthop also contains a void* to a forwarding strategy data container.
+ * This allows a strategy to keep proprietary information about each nexthop.
+ *
+ *
+ */
+
+#ifndef Metis_metis_FibEntry_h
+#define Metis_metis_FibEntry_h
+
+#include <ccnx/forwarder/metis/tlv/metis_TlvName.h>
+#include <ccnx/forwarder/metis/strategies/metis_StrategyImpl.h>
+
+struct metis_fib_entry;
+typedef struct metis_fib_entry MetisFibEntry;
+
+MetisFibEntry *metisFibEntry_Create(MetisTlvName *name, const char *fwdStrategy);
+
+/**
+ * Decrements the reference count by one, and destroys the memory after last release
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [in,out] fibEntryPtr A pointer to a MetisFibEntry, will be NULL'd
+ *
+ * Example:
+ * @code
+ * {
+ * MetisFibEntry *fibEntry = metisFibEntry(name);
+ * metisFibEntry_Release(&fibEntry);
+ * }
+ * @endcode
+ */
+void metisFibEntry_Release(MetisFibEntry **fibEntryPtr);
+
+/**
+ * Returns a reference counted copy of the fib entry
+ *
+ * The reference count is increased by one. The returned value must be
+ * released via metisFibEnty_Release().
+ *
+ * @param [in] fibEntry An allocated MetisFibEntry
+ *
+ * @return non-null A reference counted copy of the fibEntry
+ *
+ * Example:
+ * @code
+ * {
+ * MetisFibEntry *fibEntry = metisFibEntry(name);
+ * MetisFibEntry *copy = metisFibEntry_Acquire(fibEntry);
+ * metisFibEntry_Release(&copy);
+ * metisFibEntry_Release(&fibEntry);
+ * }
+ * @endcode
+ */
+MetisFibEntry *metisFibEntry_Acquire(const MetisFibEntry *fibEntry);
+
+void metisFibEntry_SetStrategy(MetisFibEntry *fibEntry, const char *strategy);
+void metisFibEntry_AddNexthop(MetisFibEntry *fibEntry, CPIRouteEntry *route);
+void metisFibEntry_RemoveNexthopByRoute(MetisFibEntry *fibEntry, CPIRouteEntry *route);
+void metisFibEntry_RemoveNexthopByConnectionId(MetisFibEntry *fibEntry, unsigned connectionId);
+
+
+size_t metisFibEntry_NexthopCount(const MetisFibEntry *fibEntry);
+
+/**
+ * @function metisFibEntry_GetNexthops
+ * @abstract Returns the nexthop set of the FIB entry. You must Acquire if it will be saved.
+ * @discussion
+ * Returns the next hop set for the FIB entry.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+const MetisNumberSet *metisFibEntry_GetNexthops(const MetisFibEntry *fibEntry);
+const MetisNumberSet *metisFibEntry_GetNexthopsFromForwardingStrategy(const MetisFibEntry *fibEntry, const MetisMessage *interestMessage);
+
+void metisFibEntry_ReceiveObjectMessage(const MetisFibEntry *fibEntry, const MetisNumberSet *egressId, const MetisMessage *objectMessage, MetisTicks rtt);
+void metisFibEntry_OnTimeout(const MetisFibEntry *fibEntry, const MetisNumberSet *egressId);
+const char *metisFibEntry_GetFwdStrategyType(const MetisFibEntry *fibEntry);
+MetisStrategyImpl *metisFibEntry_GetFwdStrategy(const MetisFibEntry *fibEntry);
+
+/**
+ * @function metisFibEntry_GetPrefix
+ * @abstract Returns a copy of the prefix.
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return A reference counted copy that you must destroy
+ */
+MetisTlvName *metisFibEntry_GetPrefix(const MetisFibEntry *fibEntry);
+#endif // Metis_metis_FibEntry_h
diff --git a/metis/ccnx/forwarder/metis/processor/metis_FibEntryList.c b/metis/ccnx/forwarder/metis/processor/metis_FibEntryList.c
new file mode 100644
index 00000000..3ec5e1d5
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/metis_FibEntryList.c
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <parc/algol/parc_ArrayList.h>
+#include <parc/algol/parc_Memory.h>
+#include <ccnx/forwarder/metis/processor/metis_FibEntryList.h>
+#include <LongBow/runtime.h>
+
+struct metis_fib_entry_list {
+ PARCArrayList *listOfFibEntries;
+};
+
+static void
+metisFibEntryList_ListDestroyer(void **voidPtr)
+{
+ MetisFibEntry **entryPtr = (MetisFibEntry **) voidPtr;
+ metisFibEntry_Release(entryPtr);
+}
+
+MetisFibEntryList *
+metisFibEntryList_Create()
+{
+ MetisFibEntryList *fibEntryList = parcMemory_AllocateAndClear(sizeof(MetisFibEntryList));
+ assertNotNull(fibEntryList, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisFibEntryList));
+ fibEntryList->listOfFibEntries = parcArrayList_Create(metisFibEntryList_ListDestroyer);
+ return fibEntryList;
+}
+
+void
+metisFibEntryList_Destroy(MetisFibEntryList **listPtr)
+{
+ assertNotNull(listPtr, "Parameter must be non-null double pointer");
+ assertNotNull(*listPtr, "Parameter must dereference to non-null pointer");
+
+ MetisFibEntryList *list = *listPtr;
+ parcArrayList_Destroy(&list->listOfFibEntries);
+ parcMemory_Deallocate((void **) &list);
+ listPtr = NULL;
+}
+
+void
+metisFibEntryList_Append(MetisFibEntryList *list, MetisFibEntry *fibEntry)
+{
+ assertNotNull(list, "Parameter list must be non-null pointer");
+ assertNotNull(fibEntry, "Parameter fibEntry must be non-null pointer");
+
+ MetisFibEntry *copy = metisFibEntry_Acquire(fibEntry);
+ parcArrayList_Add(list->listOfFibEntries, copy);
+}
+
+size_t
+metisFibEntryList_Length(const MetisFibEntryList *list)
+{
+ assertNotNull(list, "Parameter list must be non-null pointer");
+ return parcArrayList_Size(list->listOfFibEntries);
+}
+
+
+const MetisFibEntry *
+metisFibEntryList_Get(const MetisFibEntryList *list, size_t index)
+{
+ assertNotNull(list, "Parameter list must be non-null pointer");
+ MetisFibEntry *entry = parcArrayList_Get(list->listOfFibEntries, index);
+ return entry;
+}
diff --git a/metis/ccnx/forwarder/metis/processor/metis_FibEntryList.h b/metis/ccnx/forwarder/metis/processor/metis_FibEntryList.h
new file mode 100644
index 00000000..67051958
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/metis_FibEntryList.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file metis_FibEntryList.h
+ * @brief A typesafe list of MetisFibEntry
+ *
+ * <#Detailed Description#>
+ *
+ */
+
+#ifndef Metis_metis_FibEntryList_h
+#define Metis_metis_FibEntryList_h
+
+#include <ccnx/forwarder/metis/processor/metis_FibEntry.h>
+
+struct metis_fib_entry_list;
+typedef struct metis_fib_entry_list MetisFibEntryList;
+
+/**
+ * Creates an emtpy FIB entry list
+ *
+ * Must be destroyed with metisFibEntryList_Destroy.
+ *
+ * @retval non-null An allocated MetisFibEntryList
+ * @retval null An error
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisFibEntryList *metisFibEntryList_Create(void);
+
+/**
+ * @function MetisFibEntryList_Detroy
+ * @abstract Destroys the list and all entries.
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ */
+void metisFibEntryList_Destroy(MetisFibEntryList **listPtr);
+
+/**
+ * @function metisFibEntryList_Append
+ * @abstract Will store a reference counted copy of the entry.
+ * @discussion
+ * Will create and store a reference counted copy. You keep ownership
+ * of the parameter <code>fibEntry</code>.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+void metisFibEntryList_Append(MetisFibEntryList *list, MetisFibEntry *fibEntry);
+
+/**
+ * Returns the number of entries in the list
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [in] list An allocated MetisFibEntryList
+ *
+ * @retval number The number of entries in the list
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+size_t metisFibEntryList_Length(const MetisFibEntryList *list);
+
+/**
+ * @function metisFibEntryList_Get
+ * @abstract Gets an element. This is the internal reference, do not destroy.
+ * @discussion
+ * Returns an internal reference from the list. You must not destroy it.
+ * Will assert if you go off the end of the list.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+const MetisFibEntry *metisFibEntryList_Get(const MetisFibEntryList *list, size_t index);
+#endif // Metis_metis_FibEntryList_h
diff --git a/metis/ccnx/forwarder/metis/processor/metis_HashTableFunction.c b/metis/ccnx/forwarder/metis/processor/metis_HashTableFunction.c
new file mode 100644
index 00000000..7ed0231c
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/metis_HashTableFunction.c
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include <config.h>
+#include <stdio.h>
+
+#include <parc/algol/parc_Memory.h>
+#include <parc/algol/parc_Hash.h>
+
+#include <ccnx/forwarder/metis/processor/metis_HashTableFunction.h>
+#include <ccnx/forwarder/metis/core/metis_Message.h>
+
+#include <LongBow/runtime.h>
+
+// ======================================================================
+// Hash table key functions
+// We use a MetisMessage as the key data type
+
+bool
+metisHashTableFunction_MessageNameEquals(const void *keyA, const void *keyB)
+{
+ const MetisMessage *a = (const MetisMessage *) keyA;
+ const MetisMessage *b = (const MetisMessage *) keyB;
+
+ return metisTlvName_Equals(metisMessage_GetName(a), metisMessage_GetName(b));
+}
+
+HashCodeType
+metisHashTableFunction_MessageNameHashCode(const void *keyA)
+{
+ const MetisMessage *message = (const MetisMessage *) keyA;
+ MetisTlvName *name = metisMessage_GetName(message);
+
+ // we want the cumulative hash for the whole name
+ uint32_t hash = metisTlvName_HashCode(name);
+
+ return hash;
+}
+
+bool
+metisHashTableFunction_MessageNameAndKeyIdEquals(const void *keyA, const void *keyB)
+{
+ const MetisMessage *a = (const MetisMessage *) keyA;
+ const MetisMessage *b = (const MetisMessage *) keyB;
+
+ if (metisMessage_KeyIdEquals(a, b)) {
+ if (metisTlvName_Equals(metisMessage_GetName(a), metisMessage_GetName(b))) {
+ return true;
+ }
+ }
+ return false;
+}
+
+HashCodeType
+metisHashTableFunction_MessageNameAndKeyIdHashCode(const void *keyA)
+{
+ const MetisMessage *message = (const MetisMessage *) keyA;
+
+ uint32_t keyIdHash;
+
+ bool hasKeyId = metisMessage_GetKeyIdHash(message, &keyIdHash);
+ assertTrue(hasKeyId, "Called NameAndKeyIdHashCode for a message without a keyid");
+
+ // we want the cumulative hash for the whole name
+ MetisTlvName *name = metisMessage_GetName(message);
+ uint32_t nameHash = metisTlvName_HashCode(name);
+
+ // now combine the two hashes. The KeyId hash is mixed in to the name hash.
+ uint32_t hash = parcHash32_Data_Cumulative(&keyIdHash, sizeof(keyIdHash), nameHash);
+ return hash;
+}
+
+bool
+metisHashTableFunction_MessageNameAndObjectHashEquals(const void *keyA, const void *keyB)
+{
+ const MetisMessage *a = (const MetisMessage *) keyA;
+ const MetisMessage *b = (const MetisMessage *) keyB;
+
+ // due to lazy calculation of hash in content objects, need non-const
+ if (metisMessage_ObjectHashEquals((MetisMessage *) a, (MetisMessage *) b)) {
+ if (metisTlvName_Equals(metisMessage_GetName(a), metisMessage_GetName(b))) {
+ return true;
+ }
+ }
+ return false;
+}
+
+HashCodeType
+metisHashTableFunction_MessageNameAndObjectHashHashCode(const void *keyA)
+{
+ const MetisMessage *message = (const MetisMessage *) keyA;
+
+ uint32_t contentObjectHashHash;
+
+ bool hasObjectHash = metisMessage_GetContentObjectHashHash((MetisMessage *) message, &contentObjectHashHash);
+ assertTrue(hasObjectHash, "Called metisPit_NameAndObjectHashHashCode for an interest without a ContentObjectHash restriction");
+
+ // we want the cumulative hash for the whole name
+ MetisTlvName *name = metisMessage_GetName(message);
+ uint32_t nameHash = metisTlvName_HashCode(name);
+
+ // now combine the two hashes
+ uint32_t hash = parcHash32_Data_Cumulative(&contentObjectHashHash, sizeof(contentObjectHashHash), nameHash);
+ return hash;
+}
+
+// ======================================================================
+// TlvName variety
+
+bool
+metisHashTableFunction_TlvNameEquals(const void *keyA, const void *keyB)
+{
+ const MetisTlvName *a = (const MetisTlvName *) keyA;
+ const MetisTlvName *b = (const MetisTlvName *) keyB;
+
+ return metisTlvName_Equals(a, b);
+}
+
+int
+metisHashTableFunction_TlvNameCompare(const void *keyA, const void *keyB)
+{
+ const MetisTlvName *a = (const MetisTlvName *) keyA;
+ const MetisTlvName *b = (const MetisTlvName *) keyB;
+
+ return metisTlvName_Compare(a, b);
+}
+
+HashCodeType
+metisHashTableFunction_TlvNameHashCode(const void *keyA)
+{
+ MetisTlvName *name = (MetisTlvName *) keyA;
+
+ // we want the cumulative hash for the whole name
+ uint32_t hash = metisTlvName_HashCode(name);
+
+ return hash;
+}
diff --git a/metis/ccnx/forwarder/metis/processor/metis_HashTableFunction.h b/metis/ccnx/forwarder/metis/processor/metis_HashTableFunction.h
new file mode 100644
index 00000000..0051527e
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/metis_HashTableFunction.h
@@ -0,0 +1,268 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file metis_HashTableFunction.h
+ * @brief These functions are used in PARCHashCodeTables by the
+ * MatchingRulesTable and ContentStore and PIT. They perform the equality
+ * and has generation needed by the PARCHashCodeTable.
+ *
+ */
+#ifndef Metis_metis_HashTableFunction_h
+#define Metis_metis_HashTableFunction_h
+
+#include <parc/algol/parc_HashCodeTable.h>
+
+// ==========================================================
+// These functions operate on a MetisMessage as the key in the HashTable.
+// The functions use void * rather than MetisMessage instances in the function
+// signature because it is using generic has code tables from PARC Library
+
+/**
+ * Determine if the Names of two `MetisMessage` instances are equal.
+ *
+ * The following equivalence relations on non-null `MetisMessage` instances are maintained:
+ *
+ * * It is reflexive: for any non-null reference value x, `metisHashTableFunction_MessageNameEquals(x, x)`
+ * must return true.
+ *
+ * * It is symmetric: for any non-null reference values x and y,
+ * `MetisMessage_Equals(x, y)` must return true if and only if
+ * `metisHashTableFunction_MessageNameEquals(y, x)` returns true.
+ *
+ * * It is transitive: for any non-null reference values x, y, and z, if
+ * `metisHashTableFunction_MessageNameEquals(x, y)` returns true and
+ * `metisHashTableFunction_MessageNameEquals(y, z)` returns true,
+ * then `metisHashTableFunction_MessageNameEquals(x, z)` must return true.
+ *
+ * * It is consistent: for any non-null reference values x and y, multiple
+ * invocations of `metisHashTableFunction_MessageNameEquals(x, y)` consistently return true or
+ * consistently return false.
+ *
+ * * For any non-null reference value x, `metisHashTableFunction_MessageNameEquals(x, NULL)` must
+ * return false.
+ *
+ * @param a A pointer to a `MetisMessage` instance.
+ * @param b A pointer to a `MetisMessage` instance.
+ * @return true if the names of the two `MetisMessage` instances are equal.
+ *
+ * Example:
+ * @code
+ * {
+ * MetisMessage *a = MetisMessage_Create();
+ * MetisMessage *b = MetisMessage_Create();
+ *
+ * if (metisHashTableFunction_MessageNameEquals(a, b)) {
+ * // true
+ * } else {
+ * // false
+ * }
+ * }
+ * @endcode
+ */
+bool metisHashTableFunction_MessageNameEquals(const void *metisMessageA, const void *metisMessageB);
+
+/**
+ * @function hashTableFunction_NameHashCode
+ * @abstract Computes the hash of the entire name in a MetisMessage
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param metisMessageA is a MetisMessage
+ * @return A non-cryptographic hash of Name
+ */
+HashCodeType metisHashTableFunction_MessageNameHashCode(const void *metisMessageA);
+
+/**
+ * Determine if the Names and KeyIds of two MetisMessage instances are equal.
+ *
+ *
+ * The following equivalence relations on non-null `MetisMessage` instances are maintained:
+ *
+ * * It is reflexive: for any non-null reference value x, `metisHashTableFunction_MessageNameAndKeyIdEquals(x, x)`
+ * must return true.
+ *
+ * * It is symmetric: for any non-null reference values x and y,
+ * `metisHashTableFunction_MessageNameAndKeyIdEquals(x, y)` must return true if and only if
+ * `metisHashTableFunction_MessageNameAndKeyIdEquals(y, x)` returns true.
+ *
+ * * It is transitive: for any non-null reference values x, y, and z, if
+ * `metisHashTableFunction_MessageNameAndKeyIdEquals(x, y)` returns true and
+ * `metisHashTableFunction_MessageNameAndKeyIdEquals(y, z)` returns true,
+ * then `metisHashTableFunction_MessageNameAndKeyIdEquals(x, z)` must return true.
+ *
+ * * It is consistent: for any non-null reference values x and y, multiple
+ * invocations of `metisHashTableFunction_MessageNameAndKeyIdEquals(x, y)` consistently
+ * return true or consistently return false.
+ *
+ * * For any non-null reference value x, `metisHashTableFunction_MessageNameAndKeyIdEquals(x, NULL)`
+ * must return false.
+ *
+ * @param a A pointer to a `MetisMessage` instance.
+ * @param b A pointer to a `MetisMessage` instance.
+ * @return true if the Name and KeyId tuple of the two `MetisMessage` instances are equal.
+ *
+ * Example:
+ * @code
+ * {
+ * MetisMessage *a = MetisMessage_Create();
+ * MetisMessage *b = MetisMessage_Create();
+ *
+ * if (metisHashTableFunction_MessageNameAndKeyIdEquals(a, b)) {
+ * // true
+ * } else {
+ * // false
+ * }
+ * }
+ * @endcode
+ */
+
+bool metisHashTableFunction_MessageNameAndKeyIdEquals(const void *metisMessageA, const void *metisMessageB);
+
+/**
+ * @function hashTableFunction_NameAndKeyIdHashCode
+ * @abstract Generates a hash code on the tuple (Name, KeyId)
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param metisMessageA is a MetisMessage
+ * @return A non-cryptographic hash of (Name, KeyId)
+ */
+HashCodeType metisHashTableFunction_MessageNameAndKeyIdHashCode(const void *metisMessageA);
+
+/**
+ * Determine if the (Name, ContentObjectHash) tuple of two `MetisMessage` instances are equal.
+ *
+ * The following equivalence relations on non-null `MetisMessage` instances are maintained:
+ *
+ * * It is reflexive: for any non-null reference value x, `metisHashTableFunction_MessageNameAndObjectHashEquals(x, x)`
+ * must return true.
+ *
+ * * It is symmetric: for any non-null reference values x and y,
+ * `metisHashTableFunction_MessageNameAndObjectHashEquals(x, y)` must return true if and only if
+ * `metisHashTableFunction_MessageNameAndObjectHashEquals(y, x)` returns true.
+ *
+ * * It is transitive: for any non-null reference values x, y, and z, if
+ * `metisHashTableFunction_MessageNameAndObjectHashEquals(x, y)` returns true and
+ * `metisHashTableFunction_MessageNameAndObjectHashEquals(y, z)` returns true,
+ * then `metisHashTableFunction_MessageNameAndObjectHashEquals(x, z)` must return true.
+ *
+ * * It is consistent: for any non-null reference values x and y, multiple
+ * invocations of `metisHashTableFunction_MessageNameAndObjectHashEquals(x, y)` consistently
+ * return true or consistently return false.
+ *
+ * * For any non-null reference value x, `metisHashTableFunction_MessageNameAndObjectHashEquals(x, NULL)`
+ * must return false.
+ *
+ * @param a A pointer to a `MetisMessage` instance.
+ * @param b A pointer to a `MetisMessage` instance.
+ * @return true if the (Name, ContentObjectHash)tuple of the two `MetisMessage` instances are equal.
+ *
+ * Example:
+ * @code
+ * {
+ * MetisMessage *a = MetisMessage_Create();
+ * MetisMessage *b = MetisMessage_Create();
+ *
+ * if (metisHashTableFunction_MessageNameAndObjectHashEquals(a, b)) {
+ * // true
+ * } else {
+ * // false
+ * }
+ * }
+ * @endcode
+ */
+bool metisHashTableFunction_MessageNameAndObjectHashEquals(const void *metisMessageA, const void *metisMessageB);
+
+/**
+ * @function hashTableFunction_NameAndObjectHashHashCode
+ * @abstract <#OneLineDescription#>
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param metisMessageA is a MetisMessage
+ * @return A non-cryptographic hash of (Name, ContentObjectHash)
+ */
+HashCodeType metisHashTableFunction_MessageNameAndObjectHashHashCode(const void *metisMessageA);
+
+// ==========================================================
+// These functions operate on a MetisTlvName as the key of the hash table
+
+/**
+ * Determine if two `MetisTlvName` instances in the keys of the hash table are equal.
+ *
+ * The following equivalence relations on non-null `MetisTlvName` instances are maintained:
+ *
+ * * It is reflexive: for any non-null reference value x, `metisHashTableFunction_TlvNameEquals(x, x)`
+ * must return true.
+ *
+ * * It is symmetric: for any non-null reference values x and y,
+ * `metisHashTableFunction_TlvNameEquals(x, y)` must return true if and only if
+ * `metisHashTableFunction_TlvNameEquals(y, x)` returns true.
+ *
+ * * It is transitive: for any non-null reference values x, y, and z, if
+ * `metisHashTableFunction_TlvNameEquals(x, y)` returns true and
+ * `metisHashTableFunction_TlvNameEquals(y, z)` returns true,
+ * then `metisHashTableFunction_TlvNameEquals(x, z)` must return true.
+ *
+ * * It is consistent: for any non-null reference values x and y, multiple
+ * invocations of `metisHashTableFunction_TlvNameEquals(x, y)` consistently
+ * return true or consistently return false.
+ *
+ * * For any non-null reference value x, `metisHashTableFunction_TlvNameEquals(x, NULL)`
+ * must return false.
+ *
+ * @param a A pointer to a `MetisTlvName` instance.
+ * @param b A pointer to a `MetisTlvName` instance.
+ * @return true if the two `MetisTlvName` instances are equal.
+ *
+ * Example:
+ * @code
+ * {
+ * MetisTlvName *a = metisTlvName_Create();
+ * MetisTlvName *b = metisTlvName_Create();
+ *
+ * if (metisHashTableFunction_TlvNameEquals(a, b)) {
+ * // true
+ * } else {
+ * // false
+ * }
+ * }
+ * @endcode
+ */
+bool metisHashTableFunction_TlvNameEquals(const void *metisTlvNameA, const void *metisTlvNameB);
+
+/**
+ * @function hashTableFunction_TlvNameCompare
+ * @abstract The key is a MetisTlvName. Returns the order comparison of two names.
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return A < B -> -1, A = B -> 0, A > B -> +1
+ */
+int metisHashTableFunction_TlvNameCompare(const void *keyA, const void *keyB);
+
+/**
+ * @function hashTableFunction_TlvNameHashCode
+ * @abstract Computes the hash of the entire name in a MetisTlvName
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param keyA is a MetisTlvName
+ * @return A non-cryptographic hash of Name
+ */
+HashCodeType metisHashTableFunction_TlvNameHashCode(const void *keyA);
+#endif // Metis_metis_HashTableFunction_h
diff --git a/metis/ccnx/forwarder/metis/processor/metis_MatchingRulesTable.c b/metis/ccnx/forwarder/metis/processor/metis_MatchingRulesTable.c
new file mode 100644
index 00000000..f1de2232
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/metis_MatchingRulesTable.c
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+#include <config.h>
+#include <stdio.h>
+
+#include <parc/algol/parc_Memory.h>
+#include <parc/algol/parc_Hash.h>
+
+#include <ccnx/forwarder/metis/processor/metis_HashTableFunction.h>
+#include <ccnx/forwarder/metis/processor/metis_MatchingRulesTable.h>
+#include <LongBow/runtime.h>
+
+struct metis_matching_rules_table {
+ // we maintain three hash tables indexed by the different ways
+ // one could ask for something. THis means a content object needs
+ // to do three lookups. We can optimize this later.
+
+ PARCHashCodeTable *tableByName;
+ PARCHashCodeTable *tableByNameAndKeyId;
+ PARCHashCodeTable *tableByNameAndObjectHash;
+
+ PARCHashCodeTable_Destroyer dataDestroyer;
+};
+
+static PARCHashCodeTable *metisMatchingRulesTable_GetTableForMessage(const MetisMatchingRulesTable *pit, const MetisMessage *interestMessage);
+
+// ======================================================================
+
+MetisMatchingRulesTable *
+metisMatchingRulesTable_Create(PARCHashCodeTable_Destroyer dataDestroyer)
+{
+ size_t initialSize = 65535;
+
+ MetisMatchingRulesTable *table = parcMemory_AllocateAndClear(sizeof(MetisMatchingRulesTable));
+ assertNotNull(table, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisMatchingRulesTable));
+ table->dataDestroyer = dataDestroyer;
+
+ // There is not a Key destroyer because we use the message from the MetisPitEntry as the key
+
+ table->tableByName = parcHashCodeTable_Create_Size(metisHashTableFunction_MessageNameEquals,
+ metisHashTableFunction_MessageNameHashCode,
+ NULL,
+ dataDestroyer,
+ initialSize);
+
+ table->tableByNameAndKeyId = parcHashCodeTable_Create_Size(metisHashTableFunction_MessageNameAndKeyIdEquals,
+ metisHashTableFunction_MessageNameAndKeyIdHashCode,
+ NULL,
+ dataDestroyer,
+ initialSize);
+
+ table->tableByNameAndObjectHash = parcHashCodeTable_Create_Size(metisHashTableFunction_MessageNameAndObjectHashEquals,
+ metisHashTableFunction_MessageNameAndObjectHashHashCode,
+ NULL,
+ dataDestroyer,
+ initialSize);
+ return table;
+}
+
+void
+metisMatchingRulesTable_Destroy(MetisMatchingRulesTable **tablePtr)
+{
+ assertNotNull(tablePtr, "Parameter must be non-null double pointer");
+ assertNotNull(*tablePtr, "Parameter must dereference to non-null pointer");
+
+ MetisMatchingRulesTable *table = *tablePtr;
+
+ parcHashCodeTable_Destroy(&table->tableByNameAndObjectHash);
+ parcHashCodeTable_Destroy(&table->tableByNameAndKeyId);
+ parcHashCodeTable_Destroy(&table->tableByName);
+
+ parcMemory_Deallocate((void **) &table);
+ *tablePtr = NULL;
+}
+
+void *
+metisMatchingRulesTable_Get(const MetisMatchingRulesTable *rulesTable, const MetisMessage *message)
+{
+ assertNotNull(rulesTable, "Parameter rulesTable must be non-null");
+ assertNotNull(message, "Parameter message must be non-null");
+
+ PARCHashCodeTable *hashTable = metisMatchingRulesTable_GetTableForMessage(rulesTable, message);
+ return parcHashCodeTable_Get(hashTable, message);
+}
+
+PARCArrayList *
+metisMatchingRulesTable_GetUnion(const MetisMatchingRulesTable *table, const MetisMessage *message)
+{
+ // we can have at most 3 results, so create with that capacity
+ PARCArrayList *list = parcArrayList_Create_Capacity(NULL, NULL, 3);
+
+ void *dataByName = parcHashCodeTable_Get(table->tableByName, message);
+ if (dataByName) {
+ parcArrayList_Add(list, dataByName);
+ }
+
+ if (metisMessage_HasKeyId(message)) {
+ void *dataByNameAndKeyId = parcHashCodeTable_Get(table->tableByNameAndKeyId, message);
+ if (dataByNameAndKeyId) {
+ parcArrayList_Add(list, dataByNameAndKeyId);
+ }
+ }
+
+ if (metisMessage_HasContentObjectHash(message)) {
+ void *dataByNameAndObjectHash = parcHashCodeTable_Get(table->tableByNameAndObjectHash, message);
+ if (dataByNameAndObjectHash) {
+ parcArrayList_Add(list, dataByNameAndObjectHash);
+ }
+ }
+
+ return list;
+}
+
+void
+metisMatchingRulesTable_RemoveFromBest(MetisMatchingRulesTable *rulesTable, const MetisMessage *message)
+{
+ assertNotNull(rulesTable, "Parameter rulesTable must be non-null");
+ assertNotNull(message, "Parameter message must be non-null");
+
+ PARCHashCodeTable *hashTable = metisMatchingRulesTable_GetTableForMessage(rulesTable, message);
+ parcHashCodeTable_Del(hashTable, message);
+}
+
+void
+metisMatchingRulesTable_RemoveFromAll(MetisMatchingRulesTable *rulesTable, const MetisMessage *message)
+{
+ assertNotNull(rulesTable, "Parameter rulesTable must be non-null");
+ assertNotNull(message, "Parameter message must be non-null");
+
+ parcHashCodeTable_Del(rulesTable->tableByName, message);
+
+ // not all messages have a keyid any more
+ if (metisMessage_HasKeyId(message)) {
+ parcHashCodeTable_Del(rulesTable->tableByNameAndKeyId, message);
+ }
+
+ if (metisMessage_HasContentObjectHash(message)) {
+ parcHashCodeTable_Del(rulesTable->tableByNameAndObjectHash, message);
+ }
+}
+
+bool
+metisMatchingRulesTable_AddToBestTable(MetisMatchingRulesTable *rulesTable, MetisMessage *key, void *data)
+{
+ assertNotNull(rulesTable, "Parameter rulesTable must be non-null");
+ assertNotNull(key, "Parameter key must be non-null");
+ assertNotNull(data, "Parameter data must be non-null");
+
+ PARCHashCodeTable *hashTable = metisMatchingRulesTable_GetTableForMessage(rulesTable, key);
+
+ bool success = parcHashCodeTable_Add(hashTable, key, data);
+
+ return success;
+}
+
+void
+metisMatchingRulesTable_AddToAllTables(MetisMatchingRulesTable *rulesTable, MetisMessage *key, void *data)
+{
+ assertNotNull(rulesTable, "Parameter rulesTable must be non-null");
+ assertNotNull(key, "Parameter key must be non-null");
+ assertNotNull(data, "Parameter data must be non-null");
+
+ parcHashCodeTable_Add(rulesTable->tableByName, key, data);
+
+ // not all messages have a keyid any more
+ if (metisMessage_HasKeyId(key)) {
+ parcHashCodeTable_Add(rulesTable->tableByNameAndKeyId, key, data);
+ }
+
+ parcHashCodeTable_Add(rulesTable->tableByNameAndObjectHash, key, data);
+}
+
+// ========================================================================================
+
+static PARCHashCodeTable *
+metisMatchingRulesTable_GetTableForMessage(const MetisMatchingRulesTable *pit, const MetisMessage *interestMessage)
+{
+ PARCHashCodeTable *table;
+ if (metisMessage_HasContentObjectHash(interestMessage)) {
+ table = pit->tableByNameAndObjectHash;
+ } else if (metisMessage_HasKeyId(interestMessage)) {
+ table = pit->tableByNameAndKeyId;
+ } else {
+ table = pit->tableByName;
+ }
+
+ return table;
+}
diff --git a/metis/ccnx/forwarder/metis/processor/metis_MatchingRulesTable.h b/metis/ccnx/forwarder/metis/processor/metis_MatchingRulesTable.h
new file mode 100644
index 00000000..f46ed96b
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/metis_MatchingRulesTable.h
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @header metis_MatchingRulesTable
+ * @abstract A generic table (void *) that matches a MetisMessage according to the CCNx 1.0 rules
+ * @discussion
+ * Matching is done based on Name, Name + KeyId, or Name + ContentObjectHash.
+ * The table key is always a MetisMessage.
+ *
+ * When used in the PIT, one calls <code>metisMatchingRulesTable_AddToBestTable()</code> to
+ * add an interest to the "best" (i.e. most restrictive match) table, then calls
+ * <code>metisMatchingRulesTable_GetUnion()</code> on a content object to match against
+ * all of them.
+ *
+ * When used in a ContentStore, one calls <code>metisMatchingRulesTable_AddToAllTables()</code>
+ * to index a Content Object in all the tables. one then calls <code>metisMatchingRulesTable_Get()</code>
+ * with an Interest to do the "best" matching (i.e by hash first, then keyid, then just by name).
+ *
+ */
+
+#ifndef Metis_metis_MatchingRulesTable_h
+#define Metis_metis_MatchingRulesTable_h
+
+#include <parc/algol/parc_HashCodeTable.h>
+#include <ccnx/forwarder/metis/core/metis_Message.h>
+#include <parc/algol/parc_ArrayList.h>
+
+struct metis_matching_rules_table;
+typedef struct metis_matching_rules_table MetisMatchingRulesTable;
+
+/**
+ * Creates a MetisMatchigRulesTable and specifies the function to call to de-allocate an entry
+ *
+ * The datadestroyer will be called when an entry is removed from a table. It may be NULL.
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @retval <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisMatchingRulesTable *metisMatchingRulesTable_Create(PARCHashCodeTable_Destroyer dataDestroyer);
+
+/**
+ * Destroys the table and removes all stored elements.
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @retval <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+void metisMatchingRulesTable_Destroy(MetisMatchingRulesTable **tablePtr);
+
+/**
+ * @function metisMatchingRulesTable_Get
+ * @abstract Returns the data item that best matches the message.
+ * @discussion
+ * Indexed by NameAndContentObjectHash, NameAndKeyId, and Name, in that order.
+ *
+ * @param <#param1#>
+ * @return NULL if nothing matches, otherwise the stored value
+ */
+void *metisMatchingRulesTable_Get(const MetisMatchingRulesTable *table, const MetisMessage *message);
+
+/**
+ * @function metisMatchingRulesTable_GetUnion
+ * @abstract Returns matching data items from all index tables.
+ * @discussion
+ * The PARCArrayList does not have an item destructor, so destroying it will not affect
+ * the underlying data.
+ *
+ *
+ * @param <#param1#>
+ * @return Will not be NULL, but may be empty
+ */
+PARCArrayList *metisMatchingRulesTable_GetUnion(const MetisMatchingRulesTable *table, const MetisMessage *message);
+
+/**
+ * @function metisMatchingRulesTable_Add
+ * @abstract Adds the data to the best table
+ * @discussion
+ * The key must be derived from the data and destroyed when the data is destroyed. Only the data
+ * destroyer is called.
+ *
+ * No duplicates are allowed, will return false if not added.
+ *
+ * @param <#param1#>
+ * @return true if unique key and added, false if duplicate and no action taken.
+ */
+bool metisMatchingRulesTable_AddToBestTable(MetisMatchingRulesTable *rulesTable, MetisMessage *key, void *data);
+
+/**
+ * @function metisMatchingRulesTable_AddToAllTables
+ * @abstract Adds the key and data to all tables
+ * @discussion
+ * duplicates are not added
+ *
+ * @param <#param1#>
+ */
+void metisMatchingRulesTable_AddToAllTables(MetisMatchingRulesTable *rulesTable, MetisMessage *key, void *data);
+
+/**
+ * @function metisMatchingRulesTable_Remove
+ * @abstract Removes the matching entry from the best match table, calling the destroyer on the data.
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ */
+void metisMatchingRulesTable_RemoveFromBest(MetisMatchingRulesTable *rulesTable, const MetisMessage *message);
+
+/**
+ * @function metisMatchingRulesTable_RemoveFromAll
+ * @abstract Removes the message from all tables
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+void metisMatchingRulesTable_RemoveFromAll(MetisMatchingRulesTable *rulesTable, const MetisMessage *message);
+#endif // Metis_metis_MatchingRulesTable_h
diff --git a/metis/ccnx/forwarder/metis/processor/metis_MessageProcessor.c b/metis/ccnx/forwarder/metis/processor/metis_MessageProcessor.c
new file mode 100644
index 00000000..26e67761
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/metis_MessageProcessor.c
@@ -0,0 +1,860 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <ccnx/forwarder/metis/processor/metis_MessageProcessor.h>
+#include <parc/algol/parc_Memory.h>
+#include <parc/algol/parc_ArrayList.h>
+
+#include <ccnx/forwarder/metis/processor/metis_StandardPIT.h>
+#include <ccnx/forwarder/metis/processor/metis_FIB.h>
+
+#include <ccnx/forwarder/metis/content_store/metis_ContentStoreInterface.h>
+#include <ccnx/forwarder/metis/content_store/metis_LRUContentStore.h>
+
+#include <ccnx/forwarder/metis/strategies/metis_StrategyImpl.h>
+#include <ccnx/forwarder/metis/strategies/strategy_Rnd.h>
+#include <ccnx/forwarder/metis/strategies/strategy_LoadBalancer.h>
+#include <ccnx/forwarder/metis/strategies/strategy_RndSegment.h>
+#include <ccnx/forwarder/metis/strategies/strategy_LoadBalancerWithPD.h>
+
+
+#include <LongBow/runtime.h>
+
+/**
+ * @typedef MetisProcessorStats
+ * @abstract MessageProcessor event counters
+ *
+ * @constant countReceived All received messages, the good, the bad, the ugly
+ * @constant countInterestsReceived Count of received interests
+ * @constant countObjectsReceived Count of received content objects
+ *
+ * @constant countInterestsAggregated Number of Interests suppressed via PIT table aggregation
+ * @constant countInterestForwarded Number of Interests forwarded, for each outbound interface
+ * @constant countObjectsForwarded Number of Content Objects forwarded, for each outbound interface
+ * @constant countInterestsSatisfiedFromStore Number of Interests satisfied from the Content Store
+ *
+ * @constant countDropped Number of messages dropped, for any reason
+ * @constant countInterestsDropped Number of Interests dropped, for any reason
+ * @constant countDroppedNoRoute Number of Interests dropped because no FIB entry
+ * @constant countDroppedNoReversePath Number of Content Objects dropped because no PIT entry
+ * @constant countDroppedNoHopLimit Number of Interests without a HopLimit
+ * @constant countDroppedZeroHopLimitFromRemote Number of Interest from a remote node with a 0 hoplimit
+ *
+ * @constant countDroppedZeroHopLimitToRemote Number of Interest not forwarded to a FIB entry because hoplimit is 0 and its remote
+ * @constant countSendFailures Number of send failures (problems using MetisIoOperations)
+ *
+ * @discussion <#Discussion#>
+ */
+typedef struct metis_processor_stats {
+ uint32_t countReceived;
+ uint32_t countInterestsReceived;
+ uint32_t countObjectsReceived;
+
+ uint32_t countInterestsAggregated;
+
+ uint32_t countDropped;
+ uint32_t countInterestsDropped;
+ uint32_t countDroppedNoRoute;
+ uint32_t countDroppedNoReversePath;
+
+ uint32_t countDroppedConnectionNotFound;
+ uint32_t countObjectsDropped;
+
+ uint32_t countSendFailures;
+ uint32_t countInterestForwarded;
+ uint32_t countObjectsForwarded;
+ uint32_t countInterestsSatisfiedFromStore;
+
+ uint32_t countDroppedNoHopLimit;
+ uint32_t countDroppedZeroHopLimitFromRemote;
+ uint32_t countDroppedZeroHopLimitToRemote;
+} _MetisProcessorStats;
+
+struct metis_message_processor {
+ MetisForwarder *metis;
+ MetisLogger *logger;
+ MetisTap *tap;
+
+ MetisPIT *pit;
+ MetisContentStoreInterface *contentStore;
+ MetisFIB *fib;
+
+ bool store_in_cache;
+ bool serve_from_cache;
+
+ _MetisProcessorStats stats;
+};
+
+static void metisMessageProcessor_Drop(MetisMessageProcessor *processor, MetisMessage *message);
+static void metisMessageProcessor_ReceiveInterest(MetisMessageProcessor *processor, MetisMessage *interestMessage);
+static void metisMessageProcessor_ReceiveContentObject(MetisMessageProcessor *processor, MetisMessage *objectMessage);
+static unsigned metisMessageProcessor_ForwardToNexthops(MetisMessageProcessor *processor, MetisMessage *message, const MetisNumberSet *nexthops);
+
+static void metisMessageProcessor_ForwardToInterfaceId(MetisMessageProcessor *processor, MetisMessage *message, unsigned interfaceId);
+
+// ============================================================
+// Public API
+
+MetisMessageProcessor *
+metisMessageProcessor_Create(MetisForwarder *metis)
+{
+ size_t objectStoreSize = metisConfiguration_GetObjectStoreSize(metisForwarder_GetConfiguration(metis));
+
+ MetisMessageProcessor *processor = parcMemory_AllocateAndClear(sizeof(MetisMessageProcessor));
+ assertNotNull(processor, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisMessageProcessor));
+ memset(processor, 0, sizeof(MetisMessageProcessor));
+
+ processor->metis = metis;
+ processor->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis));
+ processor->pit = metisStandardPIT_Create(metis);
+
+ processor->fib = metisFIB_Create(processor->logger);
+
+ if (metisLogger_IsLoggable(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "MessageProcessor %p created",
+ (void *) processor);
+ }
+
+ MetisContentStoreConfig contentStoreConfig = {
+ .objectCapacity = objectStoreSize,
+ };
+
+ // Currently, this will instantiate an LRUContentStore. Perhaps someday it'll switch stores
+ // based on the MetisContentStoreConfig passed to it.
+ processor->contentStore = metisLRUContentStore_Create(&contentStoreConfig, processor->logger);
+
+ //the two flags for the cache are set to true by default. If the cache
+ //is active it always work as expected unless the use modifies this
+ //values using metis_control
+ processor->store_in_cache = true;
+ processor->serve_from_cache = true;
+
+ return processor;
+}
+
+void
+metisMessageProcessor_SetContentObjectStoreSize(MetisMessageProcessor *processor, size_t maximumContentStoreSize)
+{
+ assertNotNull(processor, "Parameter processor must be non-null");
+ metisContentStoreInterface_Release(&processor->contentStore);
+
+ MetisContentStoreConfig contentStoreConfig = {
+ .objectCapacity = maximumContentStoreSize
+ };
+
+ processor->contentStore = metisLRUContentStore_Create(&contentStoreConfig, processor->logger);
+}
+
+void
+metisMessageProcessor_ClearCache(MetisMessageProcessor *processor)
+{
+ assertNotNull(processor, "Parameter processor must be non-null");
+ size_t objectStoreSize = metisConfiguration_GetObjectStoreSize(metisForwarder_GetConfiguration(processor->metis));
+
+ metisContentStoreInterface_Release(&processor->contentStore);
+
+ MetisContentStoreConfig contentStoreConfig = {
+ .objectCapacity = objectStoreSize,
+ };
+
+ processor->contentStore = metisLRUContentStore_Create(&contentStoreConfig, processor->logger);
+}
+
+MetisContentStoreInterface *
+metisMessageProcessor_GetContentObjectStore(const MetisMessageProcessor *processor)
+{
+ assertNotNull(processor, "Parameter processor must be non-null");
+ return processor->contentStore;
+}
+
+void
+metisMessageProcessor_Destroy(MetisMessageProcessor **processorPtr)
+{
+ assertNotNull(processorPtr, "Parameter must be non-null double pointer");
+ assertNotNull(*processorPtr, "Parameter dereference to non-null pointer");
+
+ MetisMessageProcessor *processor = *processorPtr;
+
+ if (metisLogger_IsLoggable(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "MessageProcessor %p destroyed",
+ (void *) processor);
+ }
+
+ metisLogger_Release(&processor->logger);
+ metisFIB_Destroy(&processor->fib);
+ metisContentStoreInterface_Release(&processor->contentStore);
+ metisPIT_Release(&processor->pit);
+
+ parcMemory_Deallocate((void **) &processor);
+ *processorPtr = NULL;
+}
+
+void
+metisMessageProcessor_Receive(MetisMessageProcessor *processor, MetisMessage *message)
+{
+ assertNotNull(processor, "Parameter processor must be non-null");
+ assertNotNull(message, "Parameter message must be non-null");
+
+ processor->stats.countReceived++;
+
+ if (processor->tap != NULL && processor->tap->isTapOnReceive(processor->tap)) {
+ processor->tap->tapOnReceive(processor->tap, message);
+ }
+
+ if (metisLogger_IsLoggable(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ char *nameString = "NONAME";
+ if (metisMessage_HasName(message)) {
+ CCNxName *name = metisTlvName_ToCCNxName(metisMessage_GetName(message));
+ nameString = ccnxName_ToString(name);
+
+ metisLogger_Log(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "Message %p ingress %3u length %5u received name %s",
+ (void *) message,
+ metisMessage_GetIngressConnectionId(message),
+ metisMessage_Length(message),
+ nameString);
+
+ parcMemory_Deallocate((void **) &nameString);
+ ccnxName_Release(&name);
+ }
+ }
+
+ switch (metisMessage_GetType(message)) {
+ case MetisMessagePacketType_Interest:
+ metisMessageProcessor_ReceiveInterest(processor, message);
+ break;
+
+ case MetisMessagePacketType_ContentObject:
+ metisMessageProcessor_ReceiveContentObject(processor, message);
+ break;
+
+ default:
+ metisMessageProcessor_Drop(processor, message);
+ break;
+ }
+
+ // if someone wanted to save it, they made a copy
+ metisMessage_Release(&message);
+}
+
+void
+metisMessageProcessor_AddTap(MetisMessageProcessor *processor, MetisTap *tap)
+{
+ assertNotNull(processor, "Parameter processor must be non-null");
+ assertNotNull(tap, "Parameter tap must be non-null");
+
+ processor->tap = tap;
+}
+
+void
+metisMessageProcessor_RemoveTap(MetisMessageProcessor *processor, const MetisTap *tap)
+{
+ assertNotNull(processor, "Parameter processor must be non-null");
+ assertNotNull(tap, "Parameter tap must be non-null");
+
+ if (processor->tap == tap) {
+ processor->tap = NULL;
+ }
+}
+
+static void
+_metisMessageProcess_CheckForwardingStrategies(MetisMessageProcessor *processor)
+{
+ MetisFibEntryList *fib_entries = metisMessageProcessor_GetFibEntries(processor);
+ size_t size = metisFibEntryList_Length(fib_entries);
+ for (unsigned i = 0; i < size; i++) {
+ MetisFibEntry *entry = (MetisFibEntry *) metisFibEntryList_Get(fib_entries, i);
+ const char *strategy = metisFibEntry_GetFwdStrategyType(entry);
+ if (strcmp(strategy, FWD_STRATEGY_LOADBALANCER_WITH_DELAY) == 0) {
+ strategyLoadBalancerWithPD_SetConnectionTable(metisFibEntry_GetFwdStrategy(entry),
+ metisForwarder_GetConnectionTable(processor->metis));
+ }
+ }
+ metisFibEntryList_Destroy(&fib_entries);
+}
+
+bool
+metisMessageProcessor_AddOrUpdateRoute(MetisMessageProcessor *processor, CPIRouteEntry *route)
+{
+ MetisConfiguration *config = metisForwarder_GetConfiguration(processor->metis);
+ const char *fwdStrategy = metisConfiguration_GetForwarginStrategy(config, cpiRouteEntry_GetPrefix(route));
+ bool res = metisFIB_AddOrUpdate(processor->fib, route, fwdStrategy);
+ _metisMessageProcess_CheckForwardingStrategies(processor);
+ return res;
+}
+
+bool
+metisMessageProcessor_RemoveRoute(MetisMessageProcessor *processor, CPIRouteEntry *route)
+{
+ return metisFIB_Remove(processor->fib, route);
+}
+
+void
+metisMessageProcessor_RemoveConnectionIdFromRoutes(MetisMessageProcessor *processor, unsigned connectionId)
+{
+ metisFIB_RemoveConnectionIdFromRoutes(processor->fib, connectionId);
+}
+
+void
+metisProcessor_SetStrategy(MetisMessageProcessor *processor, CCNxName *prefix, const char *strategy)
+{
+ MetisFibEntryList *fib_entries = metisMessageProcessor_GetFibEntries(processor);
+ MetisTlvName *strategyPrefix = metisTlvName_CreateFromCCNxName(prefix);
+ size_t size = metisFibEntryList_Length(fib_entries);
+ for (unsigned i = 0; i < size; i++) {
+ MetisFibEntry *entry = (MetisFibEntry *) metisFibEntryList_Get(fib_entries, i);
+ MetisTlvName *entryPrefix = metisFibEntry_GetPrefix(entry);
+ if (metisTlvName_Equals(entryPrefix, strategyPrefix)) {
+ metisFibEntry_SetStrategy(entry, strategy);
+ }
+ }
+ metisTlvName_Release(&strategyPrefix);
+ metisFibEntryList_Destroy(&fib_entries);
+ _metisMessageProcess_CheckForwardingStrategies(processor);
+}
+
+MetisFibEntryList *
+metisMessageProcessor_GetFibEntries(MetisMessageProcessor *processor)
+{
+ assertNotNull(processor, "Parameter processor must be non-null");
+ return metisFIB_GetEntries(processor->fib);
+}
+
+// ============================================================
+// Internal API
+
+/**
+ * @function metisMessageProcessor_Drop
+ * @abstract Whenever we "drop" a message, notify the OnDrop tap and increment countes
+ * @discussion
+ * This is a bookkeeping function. It notifies the tap, if its an onDrop tap, and
+ * it increments the appropriate counters.
+ *
+ * The default action for a message is to destroy it in <code>metisMessageProcessor_Receive()</code>,
+ * so this function does not need to do that.
+ *
+ * @param <#param1#>
+ */
+static void
+metisMessageProcessor_Drop(MetisMessageProcessor *processor, MetisMessage *message)
+{
+ if (processor->tap != NULL && processor->tap->isTapOnDrop && processor->tap->isTapOnDrop(processor->tap)) {
+ processor->tap->tapOnDrop(processor->tap, message);
+ }
+
+ processor->stats.countDropped++;
+
+ switch (metisMessage_GetType(message)) {
+ case MetisMessagePacketType_Interest:
+ processor->stats.countInterestsDropped++;
+ break;
+
+ case MetisMessagePacketType_ContentObject:
+ processor->stats.countObjectsDropped++;
+ break;
+
+ default:
+ break;
+ }
+
+ // dont destroy message here, its done at end of receive
+}
+
+/**
+ * @function metisMessageProcessor_AggregateInterestInPit
+ * @abstract Try to aggregate the interest in the PIT
+ * @discussion
+ * Tries to aggregate the interest with another interest.
+ *
+ * @param <#param1#>
+ * @return true if interest aggregagted (no more forwarding needed), false if need to keep processing it.
+ */
+static bool
+metisMessageProcessor_AggregateInterestInPit(MetisMessageProcessor *processor, MetisMessage *interestMessage)
+{
+ MetisPITVerdict verdict = metisPIT_ReceiveInterest(processor->pit, interestMessage);
+
+ if (verdict == MetisPITVerdict_Aggregate) {
+ // PIT has it, we're done
+ processor->stats.countInterestsAggregated++;
+
+ if (metisLogger_IsLoggable(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "Message %p aggregated in PIT (aggregated count %u)",
+ (void *) interestMessage,
+ processor->stats.countInterestsAggregated);
+ }
+
+ return true;
+ }
+
+ if (metisLogger_IsLoggable(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "Message %p not aggregated in PIT (aggregated count %u)",
+ (void *) interestMessage,
+ processor->stats.countInterestsAggregated);
+ }
+
+ return false;
+}
+
+static bool
+_satisfyFromContentStore(MetisMessageProcessor *processor, MetisMessage *interestMessage)
+{
+ bool result = false;
+
+ if (!processor->serve_from_cache) {
+ return result;
+ }
+
+ // See if there's a match in the store.
+ MetisMessage *objectMessage = metisContentStoreInterface_MatchInterest(processor->contentStore, interestMessage);
+
+ if (objectMessage) {
+ // If the Interest specified a KeyId restriction and we had a match, check to see if the ContentObject's KeyId
+ // has been verified. If not, we don't respond with it.
+ if (metisMessage_HasKeyId(interestMessage) && !metisMessage_IsKeyIdVerified(objectMessage)) {
+ // We don't match if they specified a KeyId restriction and we haven't yet verified it.
+ objectMessage = NULL;
+ }
+ }
+
+ if (objectMessage != NULL) {
+ bool hasExpired = false;
+ bool hasExceededRCT = false;
+
+ uint64_t currentTimeTicks = metisForwarder_GetTicks(processor->metis);
+
+ // Check for ExpiryTime exceeded.
+ if (metisMessage_HasExpiryTime(objectMessage)
+ && (currentTimeTicks > metisMessage_GetExpiryTimeTicks(objectMessage))) {
+ hasExpired = true;
+ }
+
+ // Check for RCT exceeded.
+ if (metisMessage_HasRecommendedCacheTime(objectMessage)
+ && (currentTimeTicks > metisMessage_GetRecommendedCacheTimeTicks(objectMessage))) {
+ hasExceededRCT = true;
+ }
+
+ if (!hasExpired) { // && !hasExceededRCT ? It's up to us.
+ // Remove it from the PIT. nexthops is allocated, so need to destroy
+ MetisNumberSet *nexthops = metisPIT_SatisfyInterest(processor->pit, objectMessage);
+ assertNotNull(nexthops, "Illegal state: got a null nexthops for an interest we just inserted.");
+
+ // send message in reply, then done
+ processor->stats.countInterestsSatisfiedFromStore++;
+
+ if (metisLogger_IsLoggable(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "Message %p satisfied from content store (satisfied count %u)",
+ (void *) interestMessage,
+ processor->stats.countInterestsSatisfiedFromStore);
+ }
+
+ metisMessage_ResetPathLabel(objectMessage);
+
+ metisMessageProcessor_ForwardToNexthops(processor, objectMessage, nexthops);
+ metisNumberSet_Release(&nexthops);
+
+ result = true;
+ }
+
+ // Remove the retrieved ContentObject from the ContentStore if it has expired, or exceeded its RCT.
+ if (hasExpired || hasExceededRCT) {
+ metisContentStoreInterface_RemoveContent(processor->contentStore, objectMessage);
+ }
+ }
+
+ return result;
+}
+
+/**
+ * @function metisMessageProcessor_ForwardViaFib
+ * @abstract Try to forward the interest via the FIB
+ * @discussion
+ * This calls <code>metisMessageProcessor_ForwardToNexthops()</code>, so if we find any nexthops,
+ * the interest will be sent on its way. Depending on the MetisIoOperations of each nexthop,
+ * it may be a deferred write and bump up the <code>interestMessage</code> refernce count, or it
+ * may copy the data out.
+ *
+ * A TRUE return means we did our best to forward it via the routes. If those routes are actually
+ * down or have errors, we still return TRUE. A FALSE return means there were no routes to try.
+ *
+ * @param <#param1#>
+ * @return true if we found a route and tried to forward it, false if no route
+ */
+static bool
+metisMessageProcessor_ForwardViaFib(MetisMessageProcessor *processor, MetisMessage *interestMessage)
+{
+ MetisFibEntry *fibEntry = metisFIB_Match(processor->fib, interestMessage);
+ if (fibEntry == NULL) {
+ return false;
+ }
+
+ MetisPitEntry *pitEntry = metisPIT_GetPitEntry(processor->pit, interestMessage);
+ if (pitEntry == NULL) {
+ return false;
+ }
+
+ metisPitEntry_AddFibEntry(pitEntry, fibEntry);
+
+ MetisNumberSet *nexthops = (MetisNumberSet *) metisFibEntry_GetNexthopsFromForwardingStrategy(fibEntry, interestMessage);
+ //this requires some additional checks. It may happen that some of the output faces selected by the forwarding strategy are not
+ //usable. So far all the forwarding strategy return only valid faces (or an empty list)
+ for (unsigned i = 0; i < metisNumberSet_Length(nexthops); i++) {
+ metisPitEntry_AddEgressId(pitEntry, metisNumberSet_GetItem(nexthops, i));
+ }
+
+ //The function GetPitEntry encreases the ref counter in the pit entry
+ //we need to decrease it
+ metisPitEntry_Release(&pitEntry);
+
+ if (metisMessageProcessor_ForwardToNexthops(processor, interestMessage, nexthops) > 0) {
+ metisNumberSet_Release(&nexthops);
+ return true;
+ } else {
+ if (metisLogger_IsLoggable(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "Message %p returned an emtpy next hop set", (void *) interestMessage);
+ }
+ }
+
+ return false;
+}
+
+static bool
+metisMessageProcessor_IsIngressConnectionLocal(MetisMessageProcessor *processor, MetisMessage *interestMessage)
+{
+ MetisConnectionTable *connTable = metisForwarder_GetConnectionTable(processor->metis);
+ unsigned ingressConnId = metisMessage_GetIngressConnectionId(interestMessage);
+ const MetisConnection *ingressConn = metisConnectionTable_FindById(connTable, ingressConnId);
+
+ bool isLocal = false;
+ if (ingressConn) {
+ isLocal = metisConnection_IsLocal(ingressConn);
+ }
+ return isLocal;
+}
+
+/**
+ * On ingress, a remote connection must have hop limit > 0. All interests must have a hop limit.
+ *
+ * This function will log the error, if any, but it does not drop the message.
+ *
+ * If Interest is from a local application, the hop limit is not decremented and may be 0.
+ *
+ * If Interest is from a remote connection, the hop limit must be greater than 0 and will be decremented.
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @retval true The interest passes the hop limit check
+ * @retval false The interest fails the hop limit check, should be dropped
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+static bool
+metisMessageProcessor_CheckAndDecrementHopLimitOnIngress(MetisMessageProcessor *processor, MetisMessage *interestMessage)
+{
+ bool success = true;
+ if (!metisMessage_HasHopLimit(interestMessage)) {
+ processor->stats.countDroppedNoHopLimit++;
+
+ if (metisLogger_IsLoggable(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "Message %p did not have a hop limit (count %u)",
+ (void *) interestMessage,
+ processor->stats.countDroppedNoHopLimit);
+ }
+
+ success = false;
+ } else {
+ // Is the ingress connection remote? If so check for non-zero and decrement
+ if (!metisMessageProcessor_IsIngressConnectionLocal(processor, interestMessage)) {
+ uint8_t hoplimit = metisMessage_GetHopLimit(interestMessage);
+ if (hoplimit == 0) {
+ processor->stats.countDroppedZeroHopLimitFromRemote++;
+
+ if (metisLogger_IsLoggable(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "Message %p from remote host has 0 hop limit (count %u)",
+ (void *) interestMessage,
+ processor->stats.countDroppedZeroHopLimitFromRemote);
+ }
+
+ success = false;
+ } else {
+ hoplimit--;
+ metisMessage_SetHopLimit(interestMessage, hoplimit);
+ }
+ }
+ }
+ return success;
+}
+
+/**
+ * @function metisMessageProcessor_ReceiveInterest
+ * @abstract Receive an interest from the network
+ * @discussion
+ * (0) It must have a HopLimit and pass the hoplimit checks
+ * (1) if interest in the PIT, aggregate in PIT
+ * (2) if interest in the ContentStore, reply
+ * (3) if in the FIB, forward
+ * (4) drop
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+static void
+metisMessageProcessor_ReceiveInterest(MetisMessageProcessor *processor, MetisMessage *interestMessage)
+{
+ processor->stats.countInterestsReceived++;
+
+ if (!metisMessageProcessor_CheckAndDecrementHopLimitOnIngress(processor, interestMessage)) {
+ metisMessageProcessor_Drop(processor, interestMessage);
+ return;
+ }
+
+ // (1) Try to aggregate in PIT
+ if (metisMessageProcessor_AggregateInterestInPit(processor, interestMessage)) {
+ // done
+ return;
+ }
+
+ // At this point, we just created a PIT entry. If we don't forward the interest, we need
+ // to remove the PIT entry.
+
+ // (2) Try to satisfy from content store
+ if (_satisfyFromContentStore(processor, interestMessage)) {
+ // done
+ // If we found a content object in the CS, metisMessageProcess_SatisfyFromContentStore already
+ // cleared the PIT state
+ return;
+ }
+
+ // (3) Try to forward it
+ if (metisMessageProcessor_ForwardViaFib(processor, interestMessage)) {
+ // done
+ return;
+ }
+
+ // Remove the PIT entry?
+ processor->stats.countDroppedNoRoute++;
+
+ if (metisLogger_IsLoggable(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "Message %p did not match FIB, no route (count %u)",
+ (void *) interestMessage,
+ processor->stats.countDroppedNoRoute);
+ }
+
+ metisMessageProcessor_Drop(processor, interestMessage);
+}
+
+/**
+ * @function metisMessageProcessor_ReceiveContentObject
+ * @abstract Process an in-bound content object
+ * @discussion
+ * (1) If it does not match anything in the PIT, drop it
+ * (2) Add to Content Store
+ * (3) Reverse path forward via PIT entries
+ *
+ * @param <#param1#>
+ */
+static void
+metisMessageProcessor_ReceiveContentObject(MetisMessageProcessor *processor, MetisMessage *message)
+{
+ processor->stats.countObjectsReceived++;
+
+ MetisNumberSet *ingressSetUnion = metisPIT_SatisfyInterest(processor->pit, message);
+
+ if (metisNumberSet_Length(ingressSetUnion) == 0) {
+ // (1) If it does not match anything in the PIT, drop it
+ processor->stats.countDroppedNoReversePath++;
+
+ if (metisLogger_IsLoggable(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "Message %p did not match PIT, no reverse path (count %u)",
+ (void *) message,
+ processor->stats.countDroppedNoReversePath);
+ }
+
+ metisMessageProcessor_Drop(processor, message);
+ } else {
+ // (2) Add to Content Store. Store may remove expired content, if necessary, depending on store policy.
+ if (processor->store_in_cache) {
+ uint64_t currentTimeTicks = metisForwarder_GetTicks(processor->metis);
+ metisContentStoreInterface_PutContent(processor->contentStore, message, currentTimeTicks);
+ }
+ // (3) Reverse path forward via PIT entries
+ metisMessageProcessor_ForwardToNexthops(processor, message, ingressSetUnion);
+ }
+
+ metisNumberSet_Release(&ingressSetUnion);
+}
+
+/**
+ * @function metisMessageProcessor_ForwardToNexthops
+ * @abstract Try to forward to each nexthop listed in the MetisNumberSet
+ * @discussion
+ * Will not forward to the ingress connection.
+ *
+ * @param <#param1#>
+ * @return The number of nexthops tried
+ */
+static unsigned
+metisMessageProcessor_ForwardToNexthops(MetisMessageProcessor *processor, MetisMessage *message, const MetisNumberSet *nexthops)
+{
+ unsigned forwardedCopies = 0;
+
+ size_t length = metisNumberSet_Length(nexthops);
+
+ unsigned ingressId = metisMessage_GetIngressConnectionId(message);
+ for (size_t i = 0; i < length; i++) {
+ unsigned egressId = metisNumberSet_GetItem(nexthops, i);
+ if (egressId != ingressId) {
+ forwardedCopies++;
+ metisMessageProcessor_ForwardToInterfaceId(processor, message, egressId);
+ }
+ }
+ return forwardedCopies;
+}
+
+/**
+ * caller has checked that the hop limit is ok. Try to send out the connection.
+ */
+static void
+metisMessageProcessor_SendWithGoodHopLimit(MetisMessageProcessor *processor, MetisMessage *message, unsigned interfaceId, const MetisConnection *conn)
+{
+ bool success = metisConnection_Send(conn, message);
+ if (success) {
+ switch (metisMessage_GetType(message)) {
+ case MetisMessagePacketType_Interest:
+ processor->stats.countInterestForwarded++;
+ break;
+
+ case MetisMessagePacketType_ContentObject:
+ processor->stats.countObjectsForwarded++;
+ break;
+
+ default:
+ break;
+ }
+
+ if (metisLogger_IsLoggable(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "forward message %p to interface %u (int %u, obj %u)",
+ (void *) message,
+ interfaceId,
+ processor->stats.countInterestForwarded,
+ processor->stats.countObjectsForwarded);
+ }
+ } else {
+ processor->stats.countSendFailures++;
+
+ if (metisLogger_IsLoggable(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "forward message %p to interface %u send failure (count %u)",
+ (void *) message,
+ interfaceId,
+ processor->stats.countSendFailures);
+ }
+ metisMessageProcessor_Drop(processor, message);
+ }
+}
+
+/*
+ * If the hoplimit is equal to 0, then we may only forward it to local applications. Otherwise,
+ * we may forward it off the system.
+ *
+ */
+static void
+metisMessageProcessor_ForwardToInterfaceId(MetisMessageProcessor *processor, MetisMessage *message, unsigned interfaceId)
+{
+ MetisConnectionTable *connectionTable = metisForwarder_GetConnectionTable(processor->metis);
+ const MetisConnection *conn = metisConnectionTable_FindById(connectionTable, interfaceId);
+
+
+ if (conn != NULL) {
+ /*
+ * We can send the message if:
+ * a) If the message does not carry a hop limit (e.g. content object)
+ * b) It has a hoplimit and it is positive
+ * c) Or if the egress connection is local (i.e. it has a hoplimit and it's 0, but this is ok for a local app)
+ */
+ if ((!metisMessage_HasHopLimit(message)) || (metisMessage_GetHopLimit(message) > 0) || metisConnection_IsLocal(conn)) {
+ metisMessageProcessor_SendWithGoodHopLimit(processor, message, interfaceId, conn);
+ } else {
+ // To reach here, the message has to have a hop limit, it has to be 0 and and going to a remote target
+ processor->stats.countDroppedZeroHopLimitToRemote++;
+
+ if (metisLogger_IsLoggable(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "forward message %p to interface %u hop limit 0 and not local (count %u)",
+ (void *) message,
+ interfaceId,
+ processor->stats.countDroppedZeroHopLimitToRemote);
+ }
+ }
+ } else {
+ processor->stats.countDroppedConnectionNotFound++;
+
+ if (metisLogger_IsLoggable(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(processor->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "forward message %p to interface %u not found (count %u)",
+ (void *) message,
+ interfaceId,
+ processor->stats.countDroppedConnectionNotFound);
+ }
+
+ metisMessageProcessor_Drop(processor, message);
+ }
+}
+
+void
+metisMessageProcessor_SetCacheStoreFlag(MetisMessageProcessor *processor, bool val)
+{
+ processor->store_in_cache = val;
+}
+
+bool
+metisMessageProcessor_GetCacheStoreFlag(MetisMessageProcessor *processor)
+{
+ return processor->store_in_cache;
+}
+
+void
+metisMessageProcessor_SetCacheServeFlag(MetisMessageProcessor *processor, bool val)
+{
+ processor->serve_from_cache = val;
+}
+
+bool
+metisMessageProcessor_GetCacheServeFlag(MetisMessageProcessor *processor)
+{
+ return processor->serve_from_cache;
+}
diff --git a/metis/ccnx/forwarder/metis/processor/metis_MessageProcessor.h b/metis/ccnx/forwarder/metis/processor/metis_MessageProcessor.h
new file mode 100644
index 00000000..19c88f07
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/metis_MessageProcessor.h
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file metis_MessageProcessor.h
+ * @brief Executes the set of rules dictated by the PacketType
+ *
+ * This is a "run-to-completion" handling of a message based on the PacketType.
+ *
+ * The MessageProcessor also owns the PIT and FIB tables.
+ *
+ */
+
+#ifndef Metis_metis_MessageProcessor_h
+#define Metis_metis_MessageProcessor_h
+
+#include <ccnx/api/control/cpi_RouteEntry.h>
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+#include <ccnx/forwarder/metis/core/metis_Message.h>
+#include <ccnx/forwarder/metis/processor/metis_Tap.h>
+#include <ccnx/forwarder/metis/content_store/metis_ContentStoreInterface.h>
+
+struct metis_message_processor;
+typedef struct metis_message_processor MetisMessageProcessor;
+
+/**
+ * Allocates a MessageProcessor along with PIT, FIB and ContentStore tables
+ *
+ * The metis pointer is primarily used for logging (metisForwarder_Log), getting the
+ * configuration, and accessing the connection table.
+ *
+ * @param [in] metis Pointer to owning Metis process
+ *
+ * @retval non-null An allocated message processor
+ * @retval null An error
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisMessageProcessor *metisMessageProcessor_Create(MetisForwarder *metis);
+
+/**
+ * Deallocates a message processor an all internal tables
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [in,out] processorPtr Pointer to message processor to de-allocate, will be NULL'd.
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+void metisMessageProcessor_Destroy(MetisMessageProcessor **processorPtr);
+
+/**
+ * @function metisMessageProcessor_Receive
+ * @abstract Process the message, takes ownership of the memory.
+ * @discussion
+ * Will call destroy on the memory when done with it, so if the caller wants to
+ * keep it, make a reference counted copy.
+ *
+ * Receive may modify some fields in the message, such as the HopLimit field.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+void metisMessageProcessor_Receive(MetisMessageProcessor *procesor, MetisMessage *message);
+
+/**
+ * @function metisMessageProcessor_AddTap
+ * @abstract Add a tap to see messages. Only one allowed. caller must remove and free it.
+ * @discussion
+ * The tap will see messages on Receive, Drop, or Send, based on the properties of the Tap.
+ * The caller owns the memory and must remove and free it.
+ *
+ * Currently only supports one tap. If one is already set, its replaced.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+void metisMessageProcessor_AddTap(MetisMessageProcessor *procesor, MetisTap *tap);
+
+/**
+ * @function metisMessageProcessor_RemoveTap
+ * @abstract Removes the tap from the message path.
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+void metisMessageProcessor_RemoveTap(MetisMessageProcessor *procesor, const MetisTap *tap);
+
+/**
+ * Adds or updates a route in the FIB
+ *
+ * If the route already exists, it is replaced
+ *
+ * @param [in] procesor An allocated message processor
+ * @param [in] route The route to update
+ *
+ * @retval true added or updated
+ * @retval false An error
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+bool metisMessageProcessor_AddOrUpdateRoute(MetisMessageProcessor *procesor, CPIRouteEntry *route);
+
+/**
+ * Removes a route from the FIB
+ *
+ * Removes a specific nexthop for a route. If there are no nexthops left after the
+ * removal, the entire route is deleted from the FIB.
+ *
+ * @param [in] procesor An allocated message processor
+ * @param [in] route The route to remove
+ *
+ * @retval true Route completely removed
+ * @retval false There is still a nexthop for the route
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+bool metisMessageProcessor_RemoveRoute(MetisMessageProcessor *procesor, CPIRouteEntry *route);
+
+/**
+ * Removes a given connection id from all FIB entries
+ *
+ * Iterates the FIB and removes the given connection ID from every route.
+ * If a route is left with no nexthops, it stays in the FIB, but packets that match it will
+ * not be forwarded. IS THIS THE RIGHT BEHAVIOR?
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+void metisMessageProcessor_RemoveConnectionIdFromRoutes(MetisMessageProcessor *processor, unsigned connectionId);
+
+/**
+ * Returns a list of all FIB entries
+ *
+ * You must destroy the list.
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @retval non-null The list of FIB entries
+ * @retval null An error
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisFibEntryList *metisMessageProcessor_GetFibEntries(MetisMessageProcessor *processor);
+
+/**
+ * Adjusts the ContentStore to the given size.
+ *
+ * This will destroy and re-create the content store, so any cached objects will be lost.
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+void metisMessageProcessor_SetContentObjectStoreSize(MetisMessageProcessor *processor, size_t maximumContentStoreSize);
+
+/**
+ * Return the interface to the currently instantiated ContentStore, if any.
+ *
+ * @param [in] processor the `MetisMessageProcessor` from which to return the ContentStoreInterface.
+ *
+ * Example:
+ * @code
+ * {
+ * MetisContentStoreInterface *storeImpl = metisMessageProcessor_GetContentObjectStore(processor);
+ * size_t capacity = metisContentStoreInterface_GetObjectCapacity(storeImpl);
+ * }
+ * @endcode
+ */
+MetisContentStoreInterface *metisMessageProcessor_GetContentObjectStore(const MetisMessageProcessor *processor);
+
+void metisMessageProcessor_SetCacheStoreFlag(MetisMessageProcessor *processor, bool val);
+
+bool metisMessageProcessor_GetCacheStoreFlag(MetisMessageProcessor *processor);
+
+void metisMessageProcessor_SetCacheServeFlag(MetisMessageProcessor *processor, bool val);
+
+bool metisMessageProcessor_GetCacheServeFlag(MetisMessageProcessor *processor);
+
+void metisMessageProcessor_ClearCache(MetisMessageProcessor *processor);
+
+void metisProcessor_SetStrategy(MetisMessageProcessor *processor, CCNxName *prefix, const char *strategy);
+
+#endif // Metis_metis_MessageProcessor_h
diff --git a/metis/ccnx/forwarder/metis/processor/metis_PIT.c b/metis/ccnx/forwarder/metis/processor/metis_PIT.c
new file mode 100644
index 00000000..d250f47b
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/metis_PIT.c
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Generic interface to PIT table
+ *
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <LongBow/runtime.h>
+
+#include <ccnx/forwarder/metis/processor/metis_PIT.h>
+
+void *
+metisPIT_Closure(const MetisPIT *pit)
+{
+ return pit->closure;
+}
+
+void
+metisPIT_Release(MetisPIT **pitPtr)
+{
+ (*pitPtr)->release(pitPtr);
+}
+
+MetisPITVerdict
+metisPIT_ReceiveInterest(MetisPIT *pit, MetisMessage *interestMessage)
+{
+ return pit->receiveInterest(pit, interestMessage);
+}
+
+MetisNumberSet *
+metisPIT_SatisfyInterest(MetisPIT *pit, const MetisMessage *objectMessage)
+{
+ return pit->satisfyInterest(pit, objectMessage);
+}
+
+void
+metisPIT_RemoveInterest(MetisPIT *pit, const MetisMessage *interestMessage)
+{
+ pit->removeInterest(pit, interestMessage);
+}
+
+MetisPitEntry *
+metisPIT_GetPitEntry(const MetisPIT *pit, const MetisMessage *interestMessage)
+{
+ return pit->getPitEntry(pit, interestMessage);
+}
diff --git a/metis/ccnx/forwarder/metis/processor/metis_PIT.h b/metis/ccnx/forwarder/metis/processor/metis_PIT.h
new file mode 100644
index 00000000..be7badc0
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/metis_PIT.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file metis_PIT.h
+ * @brief The Pending Interest Table interface
+ *
+ * Interface for implementing a PIT table
+ *
+ */
+
+#ifndef Metis_metis_PIT_h
+#define Metis_metis_PIT_h
+
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+#include <ccnx/forwarder/metis/core/metis_Message.h>
+#include <ccnx/forwarder/metis/core/metis_NumberSet.h>
+#include <ccnx/forwarder/metis/processor/metis_PitEntry.h>
+#include <ccnx/forwarder/metis/processor/metis_PITVerdict.h>
+
+struct metis_pit;
+typedef struct metis_pit MetisPIT;
+
+struct metis_pit {
+ void (*release)(MetisPIT **pitPtr);
+ MetisPITVerdict (*receiveInterest)(MetisPIT *pit, MetisMessage *interestMessage);
+ MetisNumberSet * (*satisfyInterest)(MetisPIT * pit, const MetisMessage * objectMessage);
+ void (*removeInterest)(MetisPIT *pit, const MetisMessage *interestMessage);
+ MetisPitEntry * (*getPitEntry)(const MetisPIT * pit, const MetisMessage * interestMessage);
+ void *closure;
+};
+
+void *metisPIT_Closure(const MetisPIT *pit);
+
+/**
+ * Destroys the PIT table and all entries contained in it.
+ *
+ * PIT entries are reference counted, so if the user has stored one outside the PIT table
+ * it will still be valid.
+ *
+ * @param [in,out] pitPtr Double pointer to PIT table, will be NULLed
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+void metisPIT_Release(MetisPIT **pitPtr);
+
+/**
+ * @function metisPit_ReceiveInterest
+ * @abstract Receives an interest and adds to PIT table
+ * @discussion
+ * If not present, adds entry to the PIT table and returns PIT_VERDICT_NEW_ENTRY.
+ * If present and aggregated, returns PIT_VERDICT_EXISTING_ENTRY.
+ *
+ * Some aggregated interests may return PIT_VERDICT_NEW_ENTRY if the interest needs
+ * to be forwarded again (e.g. the lifetime is extended).
+ *
+ * If the PIT stores the message in its table, it will store a reference counted copy.
+ *
+ * @param <#param1#>
+ * @return Verdict of receiving the interest
+ */
+MetisPITVerdict metisPIT_ReceiveInterest(MetisPIT *pit, MetisMessage *interestMessage);
+
+/**
+ * @function metisPit_SatisfyInterest
+ * @abstract Tries to satisfy PIT entries based on the message, returning where to send message
+ * @discussion
+ * If matching interests are in the PIT, will return the set of reverse paths to use
+ * to forward the content object.
+ *
+ * The return value is allocated and must be destroyed.
+ *
+ * @param <#param1#>
+ * @return Set of ConnectionTable id's to forward the message, may be empty or NULL. Must be destroyed.
+ */
+MetisNumberSet *metisPIT_SatisfyInterest(MetisPIT *pit, const MetisMessage *objectMessage);
+
+/**
+ * @function metisPit_RemoveInterest
+ * @abstract Unconditionally remove the interest from the PIT
+ * @discussion
+ * The PIT may store a specific name in several tables. This function will
+ * remove the interest from the specific table it lives it. It will not
+ * remove PIT entries in different tables with the same name.
+ *
+ * The different tables index interests based on their matching criteria,
+ * such as by name, by name and keyid, etc.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+void metisPIT_RemoveInterest(MetisPIT *pit, const MetisMessage *interestMessage);
+
+/**
+ * @function metisPit_GetPitEntry
+ * @abstract Retrieve the best matching PIT entry for the message.
+ * @discussion
+ * Returns a reference counted copy of the entry, must call <code>metisPitEntry_Destory()</code> on it.
+ *
+ * @param <#param1#>
+ * @return NULL if not in table, otherwise a reference counted copy of the entry
+ */
+MetisPitEntry *metisPIT_GetPitEntry(const MetisPIT *pit, const MetisMessage *interestMessage);
+#endif // Metis_metis_PIT_h
diff --git a/metis/ccnx/forwarder/metis/processor/metis_PITVerdict.h b/metis/ccnx/forwarder/metis/processor/metis_PITVerdict.h
new file mode 100644
index 00000000..88827229
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/metis_PITVerdict.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file metis_PITVerdict.h
+ * @brief Adding an entry to the PIT will return NEW or EXISTING
+ *
+ * Adding an entry to the PIT will return NEW or EXISTING
+ *
+ */
+
+#ifndef Metis_metis_PITVerdict_h
+#define Metis_metis_PITVerdict_h
+
+/**
+ * @typedef PitVerdict
+ * @abstract The verdit of the PIT for receiving a message
+ * @constant MetisPITVerdict_Forward The message made a new PIT entry, the interest should be forwarded
+ * @constant MetisPITVerdict_Aggregate The Interest was aggregated in the PIT, does not need to be forwarded
+ * @discussion <#Discussion#>
+ */
+typedef enum {
+ MetisPITVerdict_Forward,
+ MetisPITVerdict_Aggregate
+} MetisPITVerdict;
+#endif // Metis_metis_PITVerdict_h
diff --git a/metis/ccnx/forwarder/metis/processor/metis_PitEntry.c b/metis/ccnx/forwarder/metis/processor/metis_PitEntry.c
new file mode 100644
index 00000000..4b65960b
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/metis_PitEntry.c
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include <config.h>
+#include <stdio.h>
+
+#include <parc/algol/parc_Memory.h>
+#include <ccnx/forwarder/metis/processor/metis_PitEntry.h>
+#include <ccnx/forwarder/metis/core/metis_NumberSet.h>
+
+#include <LongBow/runtime.h>
+
+struct metis_pit_entry {
+ MetisMessage *message;
+ MetisNumberSet *ingressIdSet;
+ MetisNumberSet *egressIdSet;
+
+ MetisFibEntry *fibEntry;
+
+ MetisTicks creationTime;
+ MetisTicks expiryTime;
+
+ unsigned refcount;
+};
+
+MetisPitEntry *
+metisPitEntry_Create(MetisMessage *message, MetisTicks expiryTime, MetisTicks creationTime)
+{
+ MetisPitEntry *pitEntry = parcMemory_AllocateAndClear(sizeof(MetisPitEntry));
+ assertNotNull(pitEntry, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisPitEntry));
+ pitEntry->message = message;
+ pitEntry->ingressIdSet = metisNumberSet_Create();
+ pitEntry->egressIdSet = metisNumberSet_Create();
+ pitEntry->refcount = 1;
+
+ // add the message to the reverse path set
+ metisNumberSet_Add(pitEntry->ingressIdSet, metisMessage_GetIngressConnectionId(message));
+
+ // hack in a 4-second timeout
+ pitEntry->expiryTime = expiryTime;
+ pitEntry->fibEntry = NULL;
+
+ pitEntry->creationTime = creationTime;
+ return pitEntry;
+}
+
+void
+metisPitEntry_Release(MetisPitEntry **pitEntryPtr)
+{
+ assertNotNull(pitEntryPtr, "Parameter must be non-null double pointer");
+ assertNotNull(*pitEntryPtr, "Parameter must dereference to non-null pointer");
+
+ MetisPitEntry *pitEntry = *pitEntryPtr;
+ trapIllegalValueIf(pitEntry->refcount == 0, "Illegal state: has refcount of 0");
+
+ pitEntry->refcount--;
+ if (pitEntry->refcount == 0) {
+ if(pitEntry->fibEntry != NULL){
+ metisFibEntry_Release(&pitEntry->fibEntry);
+ }
+ metisNumberSet_Release(&pitEntry->ingressIdSet);
+ metisNumberSet_Release(&pitEntry->egressIdSet);
+ metisMessage_Release(&pitEntry->message);
+ parcMemory_Deallocate((void **) &pitEntry);
+ }
+ *pitEntryPtr = NULL;
+}
+
+MetisPitEntry *
+metisPitEntry_Acquire(MetisPitEntry *original)
+{
+ assertNotNull(original, "Parameter original must be non-null");
+ original->refcount++;
+ return original;
+}
+
+void
+metisPitEntry_AddIngressId(MetisPitEntry *pitEntry, unsigned ingressId)
+{
+ assertNotNull(pitEntry, "Parameter pitEntry must be non-null");
+ metisNumberSet_Add(pitEntry->ingressIdSet, ingressId);
+}
+
+void
+metisPitEntry_AddEgressId(MetisPitEntry *pitEntry, unsigned egressId)
+{
+ assertNotNull(pitEntry, "Parameter pitEntry must be non-null");
+ metisNumberSet_Add(pitEntry->egressIdSet, egressId);
+}
+
+void
+metisPitEntry_AddFibEntry(MetisPitEntry *pitEntry, MetisFibEntry *fibEntry)
+{
+ assertNotNull(pitEntry, "Parameter pitEntry must be non-null");
+ assertNotNull(fibEntry, "Parameter fibEntry must be non-null");
+ //the fibEntry should be always the same for all the interests in the same pitEntry
+ if(pitEntry->fibEntry == NULL){
+ metisFibEntry_Acquire(fibEntry);
+ pitEntry->fibEntry = fibEntry;
+ }
+}
+
+MetisFibEntry *
+metisPitEntry_GetFibEntry(MetisPitEntry *pitEntry)
+{
+ assertNotNull(pitEntry, "Parameter pitEntry must be non-null");
+ return pitEntry->fibEntry;
+}
+
+MetisTicks
+metisPitEntry_GetExpiryTime(const MetisPitEntry *pitEntry)
+{
+ assertNotNull(pitEntry, "Parameter pitEntry must be non-null");
+ return pitEntry->expiryTime;
+}
+
+MetisTicks
+metisPitEntry_GetCreationTime(const MetisPitEntry *pitEntry)
+{
+ assertNotNull(pitEntry, "Parameter pitEntry must be non-null");
+ return pitEntry->creationTime;
+}
+
+void
+metisPitEntry_SetExpiryTime(MetisPitEntry *pitEntry, MetisTicks expiryTime)
+{
+ assertNotNull(pitEntry, "Parameter pitEntry must be non-null");
+ pitEntry->expiryTime = expiryTime;
+}
+
+
+const MetisNumberSet *
+metisPitEntry_GetIngressSet(const MetisPitEntry *pitEntry)
+{
+ assertNotNull(pitEntry, "Parameter pitEntry must be non-null");
+ return pitEntry->ingressIdSet;
+}
+
+const MetisNumberSet *
+metisPitEntry_GetEgressSet(const MetisPitEntry *pitEntry)
+{
+ assertNotNull(pitEntry, "Parameter pitEntry must be non-null");
+ return pitEntry->egressIdSet;
+}
+
+MetisMessage *
+metisPitEntry_GetMessage(const MetisPitEntry *pitEntry)
+{
+ assertNotNull(pitEntry, "Parameter pitEntry must be non-null");
+ return metisMessage_Acquire(pitEntry->message);
+}
diff --git a/metis/ccnx/forwarder/metis/processor/metis_PitEntry.h b/metis/ccnx/forwarder/metis/processor/metis_PitEntry.h
new file mode 100644
index 00000000..49ec214e
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/metis_PitEntry.h
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file metis_PitEntry.h
+ * @brief The embodiment of a PIT entry
+ *
+ * Embodies a PIT entry
+ *
+ */
+
+#ifndef Metis_metis_PitEntry_h
+#define Metis_metis_PitEntry_h
+
+#include <ccnx/forwarder/metis/core/metis_Ticks.h>
+#include <ccnx/forwarder/metis/core/metis_Message.h>
+#include <ccnx/forwarder/metis/core/metis_NumberSet.h>
+#include <ccnx/forwarder/metis/processor/metis_FibEntry.h>
+
+struct metis_pit_entry;
+typedef struct metis_pit_entry MetisPitEntry;
+
+/**
+ * @function metisPitEntry_Create
+ * @abstract Takes ownership of the message inside the PitEntry
+ * @discussion
+ * When the PIT entry is destroyed, will call <code>metisMessage_Release()</code> on the message.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+MetisPitEntry *metisPitEntry_Create(MetisMessage *message, MetisTicks expiryTime, MetisTicks CreationTime);
+
+/**
+ * Release a previously acquired reference to the specified instance,
+ * decrementing the reference count for the instance.
+ *
+ * The pointer to the instance is set to NULL as a side-effect of this function.
+ *
+ * If the invocation causes the last reference to the instance to be released,
+ * the instance is deallocated and the instance's implementation will perform
+ * additional cleanup and release other privately held references.
+ *
+ * @param [in,out] pitEntryPtr A pointer to a MetisPitEntry instance pointer, which will be set to zero on return.
+ *
+ * Example:
+ * @code
+ * {
+ * }
+ * @endcode
+ */
+void metisPitEntry_Release(MetisPitEntry **pitEntryPtr);
+
+/**
+ * @function metisPitEntry_Acquire
+ * @abstract Returns a reference counted copy
+ * @discussion
+ * A reference counted copy that shares the same state as the original.
+ * Caller must use <code>metisPitEntry_Release()</code> on it when done.
+ *
+ * @return A reference counted copy, use Destroy on it.
+ */
+MetisPitEntry *metisPitEntry_Acquire(MetisPitEntry *original);
+
+/**
+ * @function metisPitEntry_AddIngressId
+ * @abstract Add an ingress connection id to the list of reverse paths
+ * @discussion
+ * A PitEntry has two NumberSets. The first is the set of ingress ports, which
+ * make up the reverse path. The second is the set of egress ports, which make up
+ * its forward path.
+ *
+ * This function tracks which reverse paths have sent us the interest.
+ *
+ * @param ingressId the reverse path
+ */
+void metisPitEntry_AddIngressId(MetisPitEntry *pitEntry, unsigned ingressId);
+
+/**
+ * @function metisPitEntry_AddEgressId
+ * @abstract Add an egress connection id to the list of attempted paths
+ * @discussion
+ * A PitEntry has two NumberSets. The first is the set of ingress ports, which
+ * make up the reverse path. The second is the set of egress ports, which make up
+ * its forward path.
+ *
+ * This function tracks which forward paths we've tried for the interest.
+ *
+ * @param egressId the forwarded path
+ */
+void metisPitEntry_AddEgressId(MetisPitEntry *pitEntry, unsigned egressId);
+
+void metisPitEntry_AddFibEntry(MetisPitEntry *pitEntry, MetisFibEntry *fibEntry);
+MetisFibEntry *metisPitEntry_GetFibEntry(MetisPitEntry *pitEntry);
+
+/**
+ * @function metisPitEntry_GetIngressSet
+ * @abstract The Ingress connection id set
+ * @discussion
+ * You must acquire a copy of the number set if you will store the result. This is
+ * the internal reference.
+ *
+ * @param <#param1#>
+ * @return May be empty, will not be null. Must be destroyed.
+ */
+const MetisNumberSet *metisPitEntry_GetIngressSet(const MetisPitEntry *pitEntry);
+
+/**
+ * @function metisPitEntry_GetEgressSet
+ * @abstract The Egress connection id set
+ * @discussion
+ * You must acquire a copy of the number set if you will store the result. This is
+ * the internal reference.
+ *
+ * @param <#param1#>
+ * @return May be empty, will not be null. Must be destroyed.
+ */
+const MetisNumberSet *metisPitEntry_GetEgressSet(const MetisPitEntry *pitEntry);
+
+/**
+ * @function metisPitEntry_GetMessage
+ * @abstract Gets the interest underpinning the PIT entry
+ * @discussion
+ * A reference counted copy, call <code>MetisMessage_Release()</code> on it.
+ *
+ * @param <#param1#>
+ * @return A reference counted copy, call <code>MetisMessage_Release()</code> on it.
+ */
+MetisMessage *metisPitEntry_GetMessage(const MetisPitEntry *pitEntry);
+
+/**
+ * Returns the time (in ticks) at which the PIT entry is no longer valid
+ *
+ * The ExpiryTime is computed when the PIT entry is added (or via metisPitEntry_SetExpiryTime).
+ * It is the aboslute time (in Ticks) at which the Pit entry is no longer valid.
+ *
+ * @param [in] MetisPitEntry An allocated PIT entry
+ *
+ * @retval number The abosolute time (in Ticks) of the Expiry
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisTicks metisPitEntry_GetExpiryTime(const MetisPitEntry *pitEntry);
+
+MetisTicks metisPitEntry_GetCreationTime(const MetisPitEntry *pitEntry);
+/**
+ * Sets the ExpriyTime of the PIT entry to the given value
+ *
+ * It is probalby an error to set the expiryTime to a smaller value than currently set to, but
+ * this is not enforced. PIT entries use lazy delete.
+ *
+ * @param [in] pitEntry The allocated PIT entry to modify
+ * @param [in] expiryTime The new expiryTime (UTC in forwarder Ticks)
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+void metisPitEntry_SetExpiryTime(MetisPitEntry *pitEntry, MetisTicks expiryTime);
+
+#endif // Metis_metis_PitEntry_h
diff --git a/metis/ccnx/forwarder/metis/processor/metis_StandardPIT.c b/metis/ccnx/forwarder/metis/processor/metis_StandardPIT.c
new file mode 100644
index 00000000..4c981f23
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/metis_StandardPIT.c
@@ -0,0 +1,326 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * The pending interest table.
+ *
+ * Interest aggregation strategy:
+ * - The first Interest for a name is forwarded
+ * - A second Interest for a name from a different reverse path may be aggregated
+ * - A second Interest for a name from an existing Interest is forwarded
+ * - The Interest Lifetime is like a subscription time. A reverse path entry is removed once the lifetime
+ * is exceeded.
+ * - Whan an Interest arrives or is aggregated, the Lifetime for that reverse hop is extended. As a simplification,
+ * we only keep a single lifetime not per reverse hop.
+ *
+ */
+
+#include <config.h>
+#include <stdio.h>
+
+#define __STDC_FORMAT_MACROS
+#include <inttypes.h>
+
+#include <ccnx/forwarder/metis/processor/metis_PIT.h>
+#include <ccnx/forwarder/metis/processor/metis_MatchingRulesTable.h>
+
+#include <ccnx/forwarder/metis/core/metis_Ticks.h>
+
+#include <parc/algol/parc_Memory.h>
+#include <parc/algol/parc_HashCodeTable.h>
+#include <parc/algol/parc_Hash.h>
+
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+
+#include <LongBow/runtime.h>
+
+struct metis_standard_pit;
+typedef struct metis_standard_pit MetisStandardPIT;
+
+struct metis_standard_pit {
+ MetisForwarder *metis;
+ MetisLogger *logger;
+
+ MetisMatchingRulesTable *table;
+
+ // counters to track how many of each type of Interest we get
+ unsigned insertCounterByName;
+ unsigned insertCounterByKeyId;
+ unsigned insertCounterByObjectHash;
+};
+
+static void _metisPIT_StoreInTable(MetisStandardPIT *pit, MetisMessage *interestMessage);
+
+static void
+_metisPIT_PitEntryDestroyer(void **dataPtr)
+{
+ metisPitEntry_Release((MetisPitEntry **) dataPtr);
+}
+
+static bool
+_metisPIT_IngressSetContains(MetisPitEntry *pitEntry, unsigned connectionId)
+{
+ const MetisNumberSet *set = metisPitEntry_GetIngressSet(pitEntry);
+ bool numberInSet = metisNumberSet_Contains(set, connectionId);
+ return numberInSet;
+}
+
+static MetisTicks
+_metisPIT_CalculateLifetime(MetisStandardPIT *pit, MetisMessage *interestMessage)
+{
+ uint64_t interestLifetimeTicks = 0;
+
+ if (metisMessage_HasInterestLifetime(interestMessage)) {
+ interestLifetimeTicks = metisMessage_GetInterestLifetimeTicks(interestMessage);
+ } else {
+ interestLifetimeTicks = metisForwarder_NanosToTicks(4000000000ULL);
+ }
+
+ MetisTicks expiryTime = metisForwarder_GetTicks(pit->metis) + interestLifetimeTicks;
+ return expiryTime;
+}
+
+static void
+_metisPIT_StoreInTable(MetisStandardPIT *pit, MetisMessage *interestMessage)
+{
+ MetisMessage *key = metisMessage_Acquire(interestMessage);
+
+ MetisTicks expiryTime = _metisPIT_CalculateLifetime(pit, interestMessage);
+
+ MetisPitEntry *pitEntry = metisPitEntry_Create(key, expiryTime, metisForwarder_GetTicks(pit->metis));
+ // this is done in metisPitEntry_Create
+ // metisPitEntry_AddIngressId(pitEntry, metisMessage_GetIngressConnectionId(interestMessage));
+
+ metisMatchingRulesTable_AddToBestTable(pit->table, key, pitEntry);
+
+ if (metisLogger_IsLoggable(pit->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(pit->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "Message %p added to PIT (expiry %" PRIu64 ") ingress %u",
+ (void *) interestMessage,
+ metisPitEntry_GetExpiryTime(pitEntry),
+ metisMessage_GetIngressConnectionId(interestMessage));
+ }
+}
+
+static void
+_metisPIT_ExtendLifetime(MetisStandardPIT *pit, MetisPitEntry *pitEntry, MetisMessage *interestMessage)
+{
+ MetisTicks expiryTime = _metisPIT_CalculateLifetime(pit, interestMessage);
+ metisPitEntry_SetExpiryTime(pitEntry, expiryTime);
+}
+
+// this appears to only be used in some unit tests
+__attribute__((unused))
+static void
+_metisPIT_AddEgressConnectionId(MetisPIT *generic, const MetisMessage *interestMessage, unsigned connectionId)
+{
+ assertNotNull(generic, "Parameter pit must be non-null");
+ assertNotNull(interestMessage, "Parameter interestMessage must be non-null");
+
+ MetisStandardPIT *pit = metisPIT_Closure(generic);
+
+ MetisPitEntry *entry = metisMatchingRulesTable_Get(pit->table, interestMessage);
+ if (entry) {
+ metisPitEntry_AddEgressId(entry, connectionId);
+ }
+}
+
+
+// ======================================================================
+// Interface API
+
+static void
+_metisStandardPIT_Destroy(MetisPIT **pitPtr)
+{
+ assertNotNull(pitPtr, "Parameter must be non-null double pointer");
+ assertNotNull(*pitPtr, "Parameter must dereference to non-null pointer");
+
+ MetisStandardPIT *pit = metisPIT_Closure(*pitPtr);
+
+ if (metisLogger_IsLoggable(pit->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(pit->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "PIT %p destroyed",
+ (void *) pit);
+ }
+
+ metisMatchingRulesTable_Destroy(&pit->table);
+ metisLogger_Release(&pit->logger);
+ parcMemory_Deallocate(pitPtr);
+}
+
+// There's a bit too much going on in this function, need to break it
+// apart for testability and style.
+static MetisPITVerdict
+_metisStandardPIT_ReceiveInterest(MetisPIT *generic, MetisMessage *interestMessage)
+{
+ assertNotNull(generic, "Parameter pit must be non-null");
+ assertNotNull(interestMessage, "Parameter interestMessage must be non-null");
+
+ MetisStandardPIT *pit = metisPIT_Closure(generic);
+
+ MetisPitEntry *pitEntry = metisMatchingRulesTable_Get(pit->table, interestMessage);
+
+ if (pitEntry) {
+ // has it expired?
+ MetisTicks now = metisForwarder_GetTicks(pit->metis);
+ if (now < metisPitEntry_GetExpiryTime(pitEntry)) {
+ _metisPIT_ExtendLifetime(pit, pitEntry, interestMessage);
+
+ // Is the reverse path already in the PIT entry?
+ if (_metisPIT_IngressSetContains(pitEntry, metisMessage_GetIngressConnectionId(interestMessage))) {
+ // It is already in the PIT entry, so this is a retransmission, so forward it.
+
+ if (metisLogger_IsLoggable(pit->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(pit->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "Message %p existing entry (expiry %" PRIu64 ") and reverse path, forwarding",
+ (void *) interestMessage,
+ metisPitEntry_GetExpiryTime(pitEntry));
+ }
+
+ return MetisPITVerdict_Forward;
+ }
+
+ // It is in the PIT but this is the first interest for the reverse path
+ metisPitEntry_AddIngressId(pitEntry, metisMessage_GetIngressConnectionId(interestMessage));
+
+ if (metisLogger_IsLoggable(pit->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(pit->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "Message %p existing entry (expiry %" PRIu64 ") and reverse path is new, aggregate",
+ (void *) interestMessage,
+ metisPitEntry_GetExpiryTime(pitEntry));
+ }
+
+ return MetisPITVerdict_Aggregate;
+ }
+ //this is a timeout....
+ MetisFibEntry *fibEntry = metisPitEntry_GetFibEntry(pitEntry);
+ if (fibEntry != NULL) {
+ metisFibEntry_OnTimeout(fibEntry, metisPitEntry_GetEgressSet(pitEntry));
+ }
+
+ // it's an old entry, remove it
+ metisMatchingRulesTable_RemoveFromBest(pit->table, interestMessage);
+ }
+
+ _metisPIT_StoreInTable(pit, interestMessage);
+
+ return MetisPITVerdict_Forward;
+}
+
+static MetisNumberSet *
+_metisStandardPIT_SatisfyInterest(MetisPIT *generic, const MetisMessage *objectMessage)
+{
+ assertNotNull(generic, "Parameter pit must be non-null");
+ assertNotNull(objectMessage, "Parameter objectMessage must be non-null");
+
+ MetisStandardPIT *pit = metisPIT_Closure(generic);
+
+ // we need to look in all three tables to see if there's anything
+ // to satisy in each of them and take the union of the reverse path sets.
+
+ MetisNumberSet *ingressSetUnion = metisNumberSet_Create();
+
+ PARCArrayList *list = metisMatchingRulesTable_GetUnion(pit->table, objectMessage);
+ for (size_t i = 0; i < parcArrayList_Size(list); i++) {
+ MetisPitEntry *pitEntry = (MetisPitEntry *) parcArrayList_Get(list, i);
+
+ MetisFibEntry *fibEntry = metisPitEntry_GetFibEntry(pitEntry);
+ if (fibEntry != NULL) {
+ //this is a rough estimation of the residual RTT
+ MetisTicks rtt = metisForwarder_GetTicks(pit->metis) - metisPitEntry_GetCreationTime(pitEntry);
+ metisFibEntry_ReceiveObjectMessage(fibEntry, metisPitEntry_GetEgressSet(pitEntry), objectMessage, rtt); //need to implement RTT
+ }
+
+ // this is a reference counted return
+ const MetisNumberSet *ingressSet = metisPitEntry_GetIngressSet(pitEntry);
+ metisNumberSet_AddSet(ingressSetUnion, ingressSet);
+
+ // and remove it from the PIT. Key is a reference counted copy of the pit entry message
+ MetisMessage *key = metisPitEntry_GetMessage(pitEntry);
+ metisMatchingRulesTable_RemoveFromBest(pit->table, key);
+ metisMessage_Release(&key);
+ }
+ parcArrayList_Destroy(&list);
+
+ return ingressSetUnion;
+}
+
+static void
+_metisStandardPIT_RemoveInterest(MetisPIT *generic, const MetisMessage *interestMessage)
+{
+ assertNotNull(generic, "Parameter pit must be non-null");
+ assertNotNull(interestMessage, "Parameter interestMessage must be non-null");
+
+ MetisStandardPIT *pit = metisPIT_Closure(generic);
+
+ if (metisLogger_IsLoggable(pit->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(pit->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "Message %p removed from PIT",
+ (void *) interestMessage);
+ }
+
+ metisMatchingRulesTable_RemoveFromBest(pit->table, interestMessage);
+}
+
+static MetisPitEntry *
+_metisStandardPIT_GetPitEntry(const MetisPIT *generic, const MetisMessage *interestMessage)
+{
+ assertNotNull(generic, "Parameter pit must be non-null");
+ assertNotNull(interestMessage, "Parameter interestMessage must be non-null");
+
+ MetisStandardPIT *pit = metisPIT_Closure(generic);
+
+ MetisPitEntry *entry = metisMatchingRulesTable_Get(pit->table, interestMessage);
+ if (entry) {
+ return metisPitEntry_Acquire(entry);
+ }
+ return NULL;
+}
+
+
+// ======================================================================
+// Public API
+
+MetisPIT *
+metisStandardPIT_Create(MetisForwarder *metis)
+{
+ assertNotNull(metis, "Parameter must be non-null");
+
+ size_t allocation = sizeof(MetisPIT) + sizeof(MetisStandardPIT);
+
+ MetisPIT *generic = parcMemory_AllocateAndClear(allocation);
+ assertNotNull(generic, "parcMemory_AllocateAndClear(%zu) returned NULL", allocation);
+ generic->closure = (uint8_t *) generic + sizeof(MetisPIT);
+
+ MetisStandardPIT *pit = metisPIT_Closure(generic);
+ pit->metis = metis;
+ pit->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis));
+ pit->table = metisMatchingRulesTable_Create(_metisPIT_PitEntryDestroyer);
+
+ if (metisLogger_IsLoggable(pit->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) {
+ metisLogger_Log(pit->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__,
+ "PIT %p created",
+ (void *) pit);
+ }
+
+ generic->getPitEntry = _metisStandardPIT_GetPitEntry;
+ generic->receiveInterest = _metisStandardPIT_ReceiveInterest;
+ generic->release = _metisStandardPIT_Destroy;
+ generic->removeInterest = _metisStandardPIT_RemoveInterest;
+ generic->satisfyInterest = _metisStandardPIT_SatisfyInterest;
+
+ return generic;
+}
+
diff --git a/metis/ccnx/forwarder/metis/processor/metis_StandardPIT.h b/metis/ccnx/forwarder/metis/processor/metis_StandardPIT.h
new file mode 100644
index 00000000..5fe37326
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/metis_StandardPIT.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file metis_StandardPIT.h
+ * @brief The Pending Interest Table
+ *
+ * Implements the standard Pending Interest Table.
+ *
+ */
+
+#ifndef Metis_metis_StandardPIT_h
+#define Metis_metis_StandardPIT_h
+
+#include <ccnx/forwarder/metis/processor/metis_PIT.h>
+
+/**
+ * Creates a PIT table
+ *
+ * Creates and allocates an emtpy PIT table. The MetisForwarder reference is
+ * used for logging and for time functions.
+ *
+ * @param [in] metis The releated MetisForwarder
+ *
+ * @return non-null a PIT table
+ * @return null An error
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisPIT *metisStandardPIT_Create(MetisForwarder *metis);
+#endif // Metis_metis_PIT_h
diff --git a/metis/ccnx/forwarder/metis/processor/metis_Tap.h b/metis/ccnx/forwarder/metis/processor/metis_Tap.h
new file mode 100644
index 00000000..052f9556
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/metis_Tap.h
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * The MetisForwarder supports a Tap that will inspect all messages passing through the
+ * forwarder. See metisForwarder_AddTap() and metisForwarder_RemoveTap().
+ *
+ *
+ * Example:
+ * @code
+ * {
+ * struct testTap_s {
+ * bool callOnReceive;
+ * unsigned onReceiveCount;
+ * } testTap;
+ *
+ * static bool
+ * testTap_IsTapOnReceive(const MetisTap *tap)
+ * {
+ * struct testTap_s *mytap = (struct testTap_s *) tap->context;
+ * return mytap->callOnReceive;
+ * }
+ *
+ * static void
+ * testTap_TapOnReceive(MetisTap *tap, const MetisMessage *message)
+ * {
+ * struct testTap_s *mytap = (struct testTap_s *) tap->context;
+ * mytap->onReceiveCount++;
+ * mytap->lastMessage = message;
+ * }
+ *
+ * MetisTap testTapTemplate = {
+ * .context = &testTap,
+ * .isTapOnReceive = &testTap_IsTapOnReceive,
+ * .isTapOnSend = NULL,
+ * .isTapOnDrop = NULL,
+ * .tapOnReceive = &testTap_TapOnReceive,
+ * .tapOnSend = NULL,
+ * .tapOnDrop = NULL
+ * };
+ *
+ * }
+ * @endcode
+ *
+ */
+
+#ifndef Metis_metis_Tap_h
+#define Metis_metis_Tap_h
+
+struct metis_tap;
+
+
+typedef struct metis_tap MetisTap;
+
+/**
+ * Defines callbacks for message taps
+ *
+ * Each of the taps (tapOnReceive, tapOnSend, tapOnDrop) may be NULL.
+ * if a tap is not null, then the correspnoding isX function must be non-null. The isX functions
+ * allow turning on/off particular calls depending on user preference.
+ */
+struct metis_tap {
+
+ /**
+ * A user-defined parameter
+ */
+ void *context;
+
+ /**
+ * Determines if the tapOnReceive() function should be called
+ *
+ * If *tapOnReceive is non-null, this function must be defined too.
+ *
+ * @param [in] MetisTap The tap structure
+ *
+ * @return true call the tap function
+ * @return false Do not call the tap function.
+ */
+ bool (*isTapOnReceive)(const MetisTap *tap);
+
+ /**
+ * Determines if the tapOnSend() function should be called
+ *
+ * If *tapOnSend is non-null, this function must be defined too.
+ *
+ * @param [in] MetisTap The tap structure
+ *
+ * @return true call the tap function
+ * @return false Do not call the tap function.
+ */
+ bool (*isTapOnSend)(const MetisTap *tap);
+
+ /**
+ * Determines if the tapOnDrop() function should be called
+ *
+ * If *tapOnDrop is non-null, this function must be defined too.
+ *
+ * @param [in] MetisTap The tap structure
+ *
+ * @return true call the tap function
+ * @return false Do not call the tap function.
+ */
+ bool (*isTapOnDrop)(const MetisTap *tap);
+
+ /**
+ * Called for each message entering the message processor. May be NULL.
+ *
+ * @param [in] MetisTap The tap structure
+ */
+ void (*tapOnReceive)(MetisTap *tap, const MetisMessage *message);
+
+ /**
+ * Called for each message forwarded by the message processor. May be NULL.
+ *
+ * @param [in] MetisTap The tap structure
+ */
+ void (*tapOnSend)(MetisTap *tap, const MetisMessage *message);
+
+ /**
+ * Called for each message dropped by the message processor. May be NULL.
+ *
+ * @param [in] MetisTap The tap structure
+ */
+ void (*tapOnDrop)(MetisTap *tap, const MetisMessage *message);
+};
+
+
+#endif // Metis_metis_Tap_h
diff --git a/metis/ccnx/forwarder/metis/processor/test/CMakeLists.txt b/metis/ccnx/forwarder/metis/processor/test/CMakeLists.txt
new file mode 100644
index 00000000..aae53353
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/test/CMakeLists.txt
@@ -0,0 +1,21 @@
+# Enable gcov output for the tests
+add_definitions(--coverage)
+set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} " --coverage")
+
+set(TestsExpectedToPass
+ test_metis_FIB
+ test_metis_FibEntryList
+ test_metis_HashTableFunction
+ test_metis_FibEntry
+ test_metis_MatchingRulesTable
+ test_metis_MessageProcessor
+ test_metis_PIT
+ test_metis_PitEntry
+ test_metis_StandardPIT
+)
+
+
+foreach(test ${TestsExpectedToPass})
+ AddTest(${test})
+endforeach()
+
diff --git a/metis/ccnx/forwarder/metis/processor/test/test_metis_ContentStore.c b/metis/ccnx/forwarder/metis/processor/test/test_metis_ContentStore.c
new file mode 100644
index 00000000..65c2db74
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/test/test_metis_ContentStore.c
@@ -0,0 +1,437 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+// Include the file(s) containing the functions to be tested.
+// This permits internal static functions to be visible to this Test Framework.
+#include "../metis_ContentStore.c"
+#include <LongBow/unit-test.h>
+#include <parc/algol/parc_SafeMemory.h>
+#include <parc/logging/parc_LogReporterTextStdout.h>
+
+#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h>
+#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h>
+
+LONGBOW_TEST_RUNNER(metis_ContentStore)
+{
+ parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory);
+
+ LONGBOW_RUN_TEST_FIXTURE(Global);
+ LONGBOW_RUN_TEST_FIXTURE(Local);
+}
+
+// The Test Runner calls this function once before any Test Fixtures are run.
+LONGBOW_TEST_RUNNER_SETUP(metis_ContentStore)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// The Test Runner calls this function once after all the Test Fixtures are run.
+LONGBOW_TEST_RUNNER_TEARDOWN(metis_ContentStore)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// ============================================================================
+
+LONGBOW_TEST_FIXTURE(Global)
+{
+ LONGBOW_RUN_TEST_CASE(Global, metisContentStore_Create_Destroy);
+ LONGBOW_RUN_TEST_CASE(Global, metisContentStore_Create_ZeroCapacity);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisContentStore_Fetch_ByName);
+ LONGBOW_RUN_TEST_CASE(Global, metisContentStore_Fetch_ByNameAndKeyId);
+ LONGBOW_RUN_TEST_CASE(Global, metisContentStore_Fetch_ByNameAndObjectHash);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisContentStore_Fetch_Lru);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisContentStore_Save_ZeroCapacity);
+ LONGBOW_RUN_TEST_CASE(Global, metisContentStore_Save_CapacityLimit);
+ LONGBOW_RUN_TEST_CASE(Global, metisContentStore_Save_WithoutEviction);
+ LONGBOW_RUN_TEST_CASE(Global, metisContentStore_Save_WithEviction);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisContentStore_Save_DuplicateHash);
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(Global)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(Global)
+{
+ uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO);
+ if (outstandingAllocations != 0) {
+ printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations);
+ return LONGBOW_STATUS_MEMORYLEAK;
+ }
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_CASE(Global, metisContentStore_Create_Destroy)
+{
+ size_t objectCapacity = 10;
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisContentStore *store = metisContentStore_Create(objectCapacity, logger);
+ metisLogger_Release(&logger);
+
+ assertTrue(store->objectCapacity == objectCapacity, "Wrong capacity, expected %zu got %zu", objectCapacity, store->objectCapacity);
+ assertTrue(store->objectCount == 0, "Wrong initial count, expected %u got %zu", 0, store->objectCount);
+ metisContentStore_Destroy(&store);
+
+ assertTrue(parcSafeMemory_ReportAllocation(STDOUT_FILENO) == 0, "Memory imbalance after create/destroy, expected %u got %u", 0, parcMemory_Outstanding());
+}
+
+LONGBOW_TEST_CASE(Global, metisContentStore_Create_ZeroCapacity)
+{
+ size_t objectCapacity = 0;
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisContentStore *store = metisContentStore_Create(objectCapacity, logger);
+ assertTrue(store->objectCapacity == objectCapacity, "Wrong capacity, expected %zu got %zu", objectCapacity, store->objectCapacity);
+ assertTrue(store->objectCount == 0, "Wrong initial count, expected %u got %zu", 0, store->objectCount);
+ metisLogger_Release(&logger);
+ metisContentStore_Destroy(&store);
+}
+
+LONGBOW_TEST_CASE(Global, metisContentStore_Fetch_ByName)
+{
+ size_t objectCapacity = 10;
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisContentStore *store = metisContentStore_Create(objectCapacity, logger);
+ MetisMessage *object_1 = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+ MetisMessage *object_2 = metisMessage_CreateFromArray(metisTestDataV0_SecondObject, sizeof(metisTestDataV0_SecondObject), 1, 2, logger);
+
+ metisContentStore_Save(store, object_1);
+ metisContentStore_Save(store, object_2);
+
+ MetisMessage *interestByName = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 3, 5, logger);
+ MetisMessage *testObject = metisContentStore_Fetch(store, interestByName);
+ assertNotNull(testObject, "Fetch did not find match when it should have");
+
+ // two objects with same name, 1st one will win
+ assertTrue(testObject == object_1, "Fetch returned wrong object, expecting %p got %p", (void *) object_1, (void *) testObject);
+
+ metisLogger_Release(&logger);
+ metisContentStore_Destroy(&store);
+ metisMessage_Release(&object_1);
+ metisMessage_Release(&object_2);
+ metisMessage_Release(&testObject);
+ metisMessage_Release(&interestByName);
+}
+
+LONGBOW_TEST_CASE(Global, metisContentStore_Fetch_ByNameAndKeyId)
+{
+ size_t objectCapacity = 10;
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisContentStore *store = metisContentStore_Create(objectCapacity, logger);
+ MetisMessage *object_1 = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+ MetisMessage *object_2 = metisMessage_CreateFromArray(metisTestDataV0_SecondObject, sizeof(metisTestDataV0_SecondObject), 1, 2, logger);
+
+ metisContentStore_Save(store, object_1);
+ metisContentStore_Save(store, object_2);
+
+ MetisMessage *interestByNameKeyId = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName_keyid, sizeof(metisTestDataV0_InterestWithName_keyid), 3, 5, logger);
+ MetisMessage *testObject = metisContentStore_Fetch(store, interestByNameKeyId);
+ assertNotNull(testObject, "Fetch did not find match when it should have");
+
+ // two objects with same name, 1st one will win
+ assertTrue(testObject == object_1, "Fetch returned wrong object, expecting %p got %p", (void *) object_1, (void *) testObject);
+
+ metisLogger_Release(&logger);
+ metisContentStore_Destroy(&store);
+ metisMessage_Release(&object_1);
+ metisMessage_Release(&object_2);
+ metisMessage_Release(&testObject);
+ metisMessage_Release(&interestByNameKeyId);
+}
+
+LONGBOW_TEST_CASE(Global, metisContentStore_Fetch_ByNameAndObjectHash)
+{
+ size_t objectCapacity = 10;
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisContentStore *store = metisContentStore_Create(objectCapacity, logger);
+ MetisMessage *object_1 = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+ MetisMessage *object_2 = metisMessage_CreateFromArray(metisTestDataV0_SecondObject, sizeof(metisTestDataV0_SecondObject), 1, 2, logger);
+
+ metisContentStore_Save(store, object_1);
+ metisContentStore_Save(store, object_2);
+
+ MetisMessage *interestByNameObjectHash = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName_objecthash, sizeof(metisTestDataV0_InterestWithName_objecthash), 3, 5, logger);
+
+ // this should retrieve object_1 because that is the one whose
+ // content object hash matches the interest
+ MetisMessage *testObject = metisContentStore_Fetch(store, interestByNameObjectHash);
+ assertNotNull(testObject, "Fetch did not find match when it should have");
+
+ // two objects with same name, 1st one will win
+ assertTrue(testObject == object_1, "Fetch returned wrong object, expecting %p got %p", (void *) object_1, (void *) testObject);
+
+ metisLogger_Release(&logger);
+ metisContentStore_Destroy(&store);
+ metisMessage_Release(&object_1);
+ metisMessage_Release(&object_2);
+ metisMessage_Release(&testObject);
+ metisMessage_Release(&interestByNameObjectHash);
+}
+
+/*
+ * Create an cache and access objects to make sure the LRU is evicting the right way
+ */
+LONGBOW_TEST_CASE(Global, metisContentStore_Fetch_Lru)
+{
+ const size_t objectCapacity = 2;
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+
+ MetisContentStore *store = metisContentStore_Create(objectCapacity, logger);
+
+ MetisMessage *object1 = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+ MetisMessage *object2 = metisMessage_CreateFromArray(metisTestDataV0_object_with_othername, sizeof(metisTestDataV0_object_with_othername), 2, 2, logger);
+
+ metisContentStore_Save(store, object1);
+ metisContentStore_Save(store, object2);
+
+ // object 2 sould be at top of LRU (was saved last). Fetch object 1, then evict object 2.
+
+ // interest_with_name will match the name in object1.
+ MetisMessage *interestByName = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 3, 5, logger);
+ MetisMessage *testObject = metisContentStore_Fetch(store, interestByName);
+
+ assertTrue(testObject == object1, "Fetch returned wrong object, expecting %p got %p", (void *) object1, (void *) testObject);
+
+ // objectcapacity = 2, so object 3 will evict bottom of LRU
+ MetisMessage *object3 = metisMessage_CreateFromArray(metisTestDataV0_SecondObject, sizeof(metisTestDataV0_SecondObject), 4, 2, logger);
+ metisContentStore_Save(store, object3);
+
+ // object 2 should be evicted
+ MetisMessage *interestOtherName = metisMessage_CreateFromArray(metisTestDataV0_InterestWithOtherName, sizeof(metisTestDataV0_InterestWithOtherName), 5, 5, logger);
+ MetisMessage *testEvictedObject = metisContentStore_Fetch(store, interestOtherName);
+ assertNull(testEvictedObject, "object with othername should have been evicted");
+
+ // as final sanity check, make sure object 1 is still in the list
+ MetisMessage *testObject1Again = metisContentStore_Fetch(store, interestByName);
+ assertNotNull(testObject1Again, "Did not retrieve object1 from the content store");
+
+ metisLogger_Release(&logger);
+ metisContentStore_Destroy(&store);
+ metisMessage_Release(&testObject1Again);
+ metisMessage_Release(&object1);
+ metisMessage_Release(&object2);
+ metisMessage_Release(&object3);
+ metisMessage_Release(&testObject);
+ metisMessage_Release(&interestByName);
+ metisMessage_Release(&interestOtherName);
+}
+
+
+LONGBOW_TEST_CASE(Global, metisContentStore_Save_WithoutEviction)
+{
+ size_t objectCapacity = 10;
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisContentStore *store = metisContentStore_Create(objectCapacity, logger);
+ MetisMessage *object_1 = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+ MetisMessage *object_2 = metisMessage_CreateFromArray(metisTestDataV0_SecondObject, sizeof(metisTestDataV0_SecondObject), 1, 2, logger);
+
+ metisContentStore_Save(store, object_1);
+ metisContentStore_Save(store, object_2);
+
+ assertTrue(store->stats.countAdds == 2, "Wrong countAdds, expected %u got %" PRIu64, 2, store->stats.countAdds);
+ assertTrue(store->stats.countLruEvictions == 0, "Wrong countLruEvictions, expected %u got %" PRIu64, 0, store->stats.countLruEvictions);
+ assertTrue(metisLruList_Length(store->lruList) == 2, "Wrong metisLruList_Length, expected %u got %zu", 2, metisLruList_Length(store->lruList));
+
+ metisLogger_Release(&logger);
+ metisContentStore_Destroy(&store);
+ metisMessage_Release(&object_1);
+ metisMessage_Release(&object_2);
+}
+
+LONGBOW_TEST_CASE(Global, metisContentStore_Save_WithEviction)
+{
+ size_t objectCapacity = 1;
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisContentStore *store = metisContentStore_Create(objectCapacity, logger);
+ MetisMessage *object_1 = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+ MetisMessage *object_2 = metisMessage_CreateFromArray(metisTestDataV0_SecondObject, sizeof(metisTestDataV0_SecondObject), 1, 2, logger);
+
+ metisContentStore_Save(store, object_1);
+
+ assertTrue(store->objectCount == 1, "Wrong objectCount. Expected %u, got %zu", 1, store->objectCount);
+
+ metisContentStore_Save(store, object_2);
+
+ // Capacity is 1, so we should never grow bigger than that.
+ assertTrue(store->objectCount == 1, "Wrong objectCount. Expected %u, got %zu", 1, store->objectCount);
+
+ assertTrue(store->stats.countAdds == 2, "Wrong countAdds, expected %u got %" PRIu64, 2, store->stats.countAdds);
+ assertTrue(store->stats.countLruEvictions == 1, "Wrong countLruEvictions, expected %u got %" PRIu64, 1, store->stats.countLruEvictions);
+ assertTrue(metisLruList_Length(store->lruList) == 1, "Wrong metisLruList_Length, expected %u got %zu", 1, metisLruList_Length(store->lruList));
+
+ metisLogger_Release(&logger);
+ metisContentStore_Destroy(&store);
+ metisMessage_Release(&object_1);
+ metisMessage_Release(&object_2);
+}
+
+LONGBOW_TEST_CASE(Global, metisContentStore_Save_ZeroCapacity)
+{
+ size_t objectCapacity = 0;
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisContentStore *store = metisContentStore_Create(objectCapacity, logger);
+
+ MetisMessage *object_1 = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+ bool success = metisContentStore_Save(store, object_1);
+ assertFalse(success, "Should have returned failure with 0 capacity object store saving something");
+
+ metisLogger_Release(&logger);
+ metisMessage_Release(&object_1);
+ metisContentStore_Destroy(&store);
+}
+
+static MetisMessage *
+_createUniqueMetisMessage(int tweakNumber, uint8_t *template, size_t templateSize, int nameOffset)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ PARCBuffer *buffer = parcBuffer_Allocate(templateSize);
+ memcpy(parcBuffer_Overlay(buffer, 0), template, templateSize); // Copy the template to new memory
+
+ // Tweak the encoded object's name so the name hash varies each time.
+ uint8_t *bufPtr = parcBuffer_Overlay(buffer, 0);
+ bufPtr[nameOffset] = 'a' + tweakNumber;
+
+ MetisMessage *result = metisMessage_CreateFromArray(bufPtr, templateSize, 1, 2, logger);
+ metisLogger_Release(&logger);
+ parcBuffer_Release(&buffer);
+
+ return result;
+}
+
+LONGBOW_TEST_CASE(Global, metisContentStore_Save_CapacityLimit)
+{
+ size_t storeCapacity = 5;
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisContentStore *store = metisContentStore_Create(storeCapacity, logger);
+
+ for (int i = 1; i < storeCapacity * 2; i++) {
+
+ int offsetOfNameInEncodedObject = metisTestDataV0_EncodedObject.offset + 4;
+ MetisMessage *object = _createUniqueMetisMessage(i, metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), offsetOfNameInEncodedObject);
+
+ bool success = metisContentStore_Save(store, object);
+
+ assertTrue(success, "Unexpectedly failed to add entry to ContentStore");
+
+ if (i < store->objectCapacity) {
+ assertTrue(store->objectCount == i, "Unexpected value for store->objectCount");
+ } else {
+ assertTrue(store->objectCount == store->objectCapacity, "Unexpected value (%zu) for store->objectCount (%zu)",
+ store->objectCount, store->objectCapacity);
+ }
+
+ if (success) {
+ metisMessage_Release(&object);
+ }
+ }
+ metisLogger_Release(&logger);
+ metisContentStore_Destroy(&store);
+}
+
+LONGBOW_TEST_CASE(Global, metisContentStore_Save_DuplicateHash)
+{
+ size_t storeCapacity = 5;
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisContentStore *store = metisContentStore_Create(storeCapacity, logger);
+
+ MetisMessage *object_1 = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+
+ bool success = metisContentStore_Save(store, object_1);
+
+ for (int i = 0; i < 10; i++) {
+ MetisMessage *object_1_dup = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+
+ success = metisContentStore_Save(store, object_1_dup);
+
+ assertFalse(success, "Unexpectedly added duplicated entry to ContentStore");
+
+ assertTrue(store->objectCount == 1, "ObjectCount should be 1");
+
+ metisMessage_Release(&object_1_dup);
+ }
+
+ metisLogger_Release(&logger);
+ metisMessage_Release(&object_1);
+ metisContentStore_Destroy(&store);
+}
+
+// ============================================================================
+
+LONGBOW_TEST_FIXTURE(Local)
+{
+ LONGBOW_RUN_TEST_CASE(Local, hashTableFunction_ContentStoreEntryDestroyer);
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(Local)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(Local)
+{
+ uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO);
+ if (outstandingAllocations != 0) {
+ printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations);
+ return LONGBOW_STATUS_MEMORYLEAK;
+ }
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_CASE(Local, hashTableFunction_ContentStoreEntryDestroyer)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+// ============================================================================
+
+int
+main(int argc, char *argv[])
+{
+ LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_ContentStore);
+ int exitStatus = longBowMain(argc, argv, testRunner, NULL);
+ longBowTestRunner_Destroy(&testRunner);
+ exit(exitStatus);
+}
diff --git a/metis/ccnx/forwarder/metis/processor/test/test_metis_FIB.c b/metis/ccnx/forwarder/metis/processor/test/test_metis_FIB.c
new file mode 100644
index 00000000..0d02501f
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/test/test_metis_FIB.c
@@ -0,0 +1,549 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+// Include the file(s) containing the functions to be tested.
+// This permits internal static functions to be visible to this Test Framework.
+#include "../metis_FIB.c"
+#include <LongBow/unit-test.h>
+#include <parc/algol/parc_SafeMemory.h>
+#include <parc/logging/parc_LogReporterTextStdout.h>
+
+#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h>
+
+LONGBOW_TEST_RUNNER(metis_FIB)
+{
+ // The following Test Fixtures will run their corresponding Test Cases.
+ // Test Fixtures are run in the order specified, but all tests should be idempotent.
+ // Never rely on the execution order of tests or share state between them.
+ LONGBOW_RUN_TEST_FIXTURE(Global);
+ LONGBOW_RUN_TEST_FIXTURE(Local);
+}
+
+// The Test Runner calls this function once before any Test Fixtures are run.
+LONGBOW_TEST_RUNNER_SETUP(metis_FIB)
+{
+ parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory);
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// The Test Runner calls this function once after all the Test Fixtures are run.
+LONGBOW_TEST_RUNNER_TEARDOWN(metis_FIB)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE(Global)
+{
+ LONGBOW_RUN_TEST_CASE(Global, metisFib_AddOrUpdate_Add);
+ LONGBOW_RUN_TEST_CASE(Global, metisFib_AddOrUpdate_Update);
+ LONGBOW_RUN_TEST_CASE(Global, metisFib_Create_Destroy);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisFib_Match_Exists);
+ LONGBOW_RUN_TEST_CASE(Global, metisFib_Match_NotExists);
+ LONGBOW_RUN_TEST_CASE(Global, metisFib_Match_ExcludeIngress);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisFib_Remove_NoEntry);
+ LONGBOW_RUN_TEST_CASE(Global, metisFib_Remove_ExistsNotLast);
+ LONGBOW_RUN_TEST_CASE(Global, metisFib_Remove_ExistsIsLast);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisFIB_Length);
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(Global)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(Global)
+{
+ uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO);
+ if (outstandingAllocations != 0) {
+ printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations);
+ return LONGBOW_STATUS_MEMORYLEAK;
+ }
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_CASE(Global, metisFib_AddOrUpdate_Add)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisFIB *fib = metisFIB_Create(logger);
+ metisLogger_Release(&logger);
+
+ CCNxName *ccnxName = ccnxName_CreateFromCString("lci:/foo/bar");
+ MetisTlvName *tlvName = metisTlvName_CreateFromCCNxName(ccnxName);
+ unsigned interfaceIndex = 22;
+ CPIAddress *nexthop = NULL;
+ struct timeval *lifetime = NULL;
+ unsigned cost = 12;
+
+ CPIRouteEntry *route = cpiRouteEntry_Create(ccnxName, interfaceIndex, nexthop, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, lifetime, cost);
+
+ metisFIB_AddOrUpdate(fib, route, "random");
+ size_t hashCodeTableLength = parcHashCodeTable_Length(fib->tableByName);
+
+ MetisFibEntry *fibEntry = parcHashCodeTable_Get(fib->tableByName, tlvName);
+ size_t nexthopCount = metisFibEntry_NexthopCount(fibEntry);
+
+ cpiRouteEntry_Destroy(&route);
+ metisTlvName_Release(&tlvName);
+ metisFIB_Destroy(&fib);
+
+ assertTrue(hashCodeTableLength == 1, "Wrong hash table length, expected %u got %zu", 1, hashCodeTableLength);
+ assertTrue(nexthopCount == 1, "Wrong hash table length, expected %u got %zu", 1, nexthopCount);
+}
+
+LONGBOW_TEST_CASE(Global, metisFib_AddOrUpdate_Update)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisFIB *fib = metisFIB_Create(logger);
+ metisLogger_Release(&logger);
+
+ CCNxName *ccnxName = ccnxName_CreateFromCString("lci:/foo/bar");
+ MetisTlvName *tlvName = metisTlvName_CreateFromCCNxName(ccnxName);
+ unsigned interfaceIndex_1 = 22;
+ CPIAddress *nexthop = NULL;
+ struct timeval *lifetime = NULL;
+ unsigned cost = 12;
+
+ // ----- Add
+ CPIRouteEntry *route_1 = cpiRouteEntry_Create(ccnxName_Copy(ccnxName), interfaceIndex_1, nexthop, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, lifetime, cost);
+ metisFIB_AddOrUpdate(fib, route_1, "random");
+
+ // ----- Update
+ unsigned interfaceIndex_2 = 33;
+ CPIRouteEntry *route_2 = cpiRouteEntry_Create(ccnxName_Copy(ccnxName), interfaceIndex_2, nexthop, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, lifetime, cost);
+ metisFIB_AddOrUpdate(fib, route_2, "random");
+
+ // ----- Measure
+ size_t hashCodeTableLength = parcHashCodeTable_Length(fib->tableByName);
+ MetisFibEntry *fibEntry = parcHashCodeTable_Get(fib->tableByName, tlvName);
+ size_t nexthopCount = metisFibEntry_NexthopCount(fibEntry);
+
+
+ cpiRouteEntry_Destroy(&route_1);
+ cpiRouteEntry_Destroy(&route_2);
+ ccnxName_Release(&ccnxName);
+ metisTlvName_Release(&tlvName);
+ metisFIB_Destroy(&fib);
+
+ assertTrue(hashCodeTableLength == 1, "Wrong hash table length, expected %u got %zu", 1, hashCodeTableLength);
+ assertTrue(nexthopCount == 2, "Wrong hash table length, expected %u got %zu", 2, nexthopCount);
+}
+
+LONGBOW_TEST_CASE(Global, metisFib_Create_Destroy)
+{
+ size_t beforeMemory = parcMemory_Outstanding();
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisFIB *fib = metisFIB_Create(logger);
+ metisLogger_Release(&logger);
+
+ metisFIB_Destroy(&fib);
+ size_t afterMemory = parcMemory_Outstanding();
+
+ assertTrue(beforeMemory == afterMemory, "Memory imbalance on create/destroy: expected %zu got %zu", beforeMemory, afterMemory);
+}
+
+/**
+ * Add /hello/ouch and lookup that name
+ */
+LONGBOW_TEST_CASE(Global, metisFib_Match_Exists)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisFIB *fib = metisFIB_Create(logger);
+ CCNxName *ccnxNameToAdd = ccnxName_CreateFromCString("lci:/2=hello/0xF000=ouch");
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ unsigned interfaceIndex_1 = 22;
+ CPIAddress *nexthop = NULL;
+ struct timeval *lifetime = NULL;
+ unsigned cost = 12;
+
+ // ----- Add
+ CPIRouteEntry *routeAdd = cpiRouteEntry_Create(ccnxNameToAdd, interfaceIndex_1, nexthop, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, lifetime, cost);
+ metisFIB_AddOrUpdate(fib, routeAdd, "random");
+
+ // ----- Match
+ MetisFibEntry *entry = metisFIB_Match(fib, interest);
+
+ // ----- Measure
+ size_t nexthopsLength = metisNumberSet_Length(metisFibEntry_GetNexthops(entry));
+
+ // ----- Cleanup
+ cpiRouteEntry_Destroy(&routeAdd);
+ metisMessage_Release(&interest);
+ metisFIB_Destroy(&fib);
+
+ // ----- Validate
+ assertTrue(nexthopsLength == 1, "Wrong nexthops length, expected %u got %zu", 1, nexthopsLength);
+}
+
+/**
+ * Add /foo/bar to connection 10
+ * Add /foo to connection 11
+ * Forward an Interest /foo/bar/cat from connection 10. Should select 11.
+ */
+LONGBOW_TEST_CASE(Global, metisFib_Match_ExcludeIngress)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisFIB *fib = metisFIB_Create(logger);
+
+ CCNxName *nameFoo = ccnxName_CreateFromCString("lci:/foo");
+ CCNxName *nameFooBar = ccnxName_CreateFromCString("lci:/foo/bar");
+
+ uint8_t encodedInterest[] = {
+ 0x01, 0x00, 0x00, 37, // ver = 1, type = interest, length = 37
+ 0xFF, 0x00, 0x00, 8, // hoplimit = 255, header length = 8
+ // ------------------------
+ 0x00, 0x01, 0x00, 25, // type = interest, length = 25
+ // ------------------------
+ 0x00, 0x00, 0x00, 21, // type = name, length = 21
+ 0x00, 0x01, 0x00, 3, // type = name, length = 3
+ 'f', 'o', 'o',
+ 0x00, 0x01, 0x00, 3, // type = name, length = 3
+ 'b', 'a', 'r',
+ 0x00, 0x01, 0x00, 3, // type = name, length = 3
+ 'c', 'a', 't',
+ };
+
+ MetisMessage *interest = metisMessage_CreateFromArray(encodedInterest, sizeof(encodedInterest), 10, 2, logger);
+ metisLogger_Release(&logger);
+
+ CPIAddress *nexthop = NULL;
+ struct timeval *lifetime = NULL;
+ unsigned cost = 12;
+
+ CPIRouteEntry *routeAdd;
+
+ // ----- Add long route to Interface 10
+ routeAdd = cpiRouteEntry_Create(nameFooBar, 10, nexthop, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, lifetime, cost);
+ metisFIB_AddOrUpdate(fib, routeAdd, "random");
+ cpiRouteEntry_Destroy(&routeAdd);
+
+ // ----- Add short route to Interface 11
+ routeAdd = cpiRouteEntry_Create(nameFoo, 11, nexthop, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, lifetime, cost);
+ metisFIB_AddOrUpdate(fib, routeAdd, "random");
+ cpiRouteEntry_Destroy(&routeAdd);
+
+ // ----- Match
+ MetisFibEntry *entry = metisFIB_Match(fib, interest);
+
+ // ----- Measure
+ size_t nexthopsLength = metisNumberSet_Length(metisFibEntry_GetNexthops(entry));
+
+ // ----- Validate
+ assertTrue(nexthopsLength == 1, "Wrong nexthops length, expected %u got %zu", 1, nexthopsLength);
+ bool hasEgress = metisNumberSet_Contains(metisFibEntry_GetNexthops(entry), 11);
+ assertTrue(hasEgress, "Egress interface 11 not in nexthop set");
+
+ // ----- Cleanup
+ metisMessage_Release(&interest);
+ metisFIB_Destroy(&fib);
+}
+
+
+/**
+ * Add /hello/ouch and lookup /party/ouch
+ */
+LONGBOW_TEST_CASE(Global, metisFib_Match_NotExists)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisFIB *fib = metisFIB_Create(logger);
+ CCNxName *ccnxNameToAdd = ccnxName_CreateFromCString("lci:/2=hello/0xF000=ouch");
+
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithOtherName, sizeof(metisTestDataV0_InterestWithOtherName), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ unsigned interfaceIndex_1 = 22;
+ CPIAddress *nexthop = NULL;
+ struct timeval *lifetime = NULL;
+ unsigned cost = 12;
+
+ // ----- Add
+ CPIRouteEntry *routeAdd = cpiRouteEntry_Create(ccnxNameToAdd, interfaceIndex_1, nexthop, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, lifetime, cost);
+ metisFIB_AddOrUpdate(fib, routeAdd, "random");
+
+ // ----- Match
+ MetisFibEntry *entry = metisFIB_Match(fib, interest);
+
+ // ----- Measure
+ assertTrue(entry == NULL, "expected null");
+ cpiRouteEntry_Destroy(&routeAdd);
+ metisMessage_Release(&interest);
+ metisFIB_Destroy(&fib);
+
+ //size_t nexthopsLength = metisNumberSet_Length(metisFibEntry_GetNexthops(entry));
+
+ // ----- Cleanup
+ //cpiRouteEntry_Destroy(&routeAdd);
+ //metisMessage_Release(&interest);
+ //metisFibEntry_Release(&entry);
+ //metisFIB_Destroy(&fib);
+
+ // ----- Validate
+ //assertTrue(nexthopsLength == 0, "Wrong nexthops length, expected %u got %zu", 0, nexthopsLength);
+}
+
+/**
+ * Add /foo/bar and try to remove /baz
+ */
+LONGBOW_TEST_CASE(Global, metisFib_Remove_NoEntry)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisFIB *fib = metisFIB_Create(logger);
+ metisLogger_Release(&logger);
+
+ CCNxName *ccnxNameToAdd = ccnxName_CreateFromCString("lci:/foo/bar");
+ CCNxName *ccnxNameToRemove = ccnxName_CreateFromCString("lci:/baz");
+ MetisTlvName *tlvNameToCheck = metisTlvName_CreateFromCCNxName(ccnxNameToAdd);
+ unsigned interfaceIndex_1 = 22;
+ CPIAddress *nexthop = NULL;
+ struct timeval *lifetime = NULL;
+ unsigned cost = 12;
+
+ // ----- Add
+ CPIRouteEntry *routeAdd = cpiRouteEntry_Create(ccnxNameToAdd, interfaceIndex_1, nexthop, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, lifetime, cost);
+ metisFIB_AddOrUpdate(fib, routeAdd, "random");
+
+ // ----- Remove
+ CPIRouteEntry *routeRemove = cpiRouteEntry_Create(ccnxNameToRemove, interfaceIndex_1, nexthop, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, lifetime, cost);
+ metisFIB_Remove(fib, routeRemove);
+
+ // ----- Measure
+ size_t hashCodeTableLength = parcHashCodeTable_Length(fib->tableByName);
+ MetisFibEntry *fibEntry = parcHashCodeTable_Get(fib->tableByName, tlvNameToCheck);
+ size_t nexthopCount = metisFibEntry_NexthopCount(fibEntry);
+
+ // ----- Cleanup
+ cpiRouteEntry_Destroy(&routeAdd);
+ cpiRouteEntry_Destroy(&routeRemove);
+ metisTlvName_Release(&tlvNameToCheck);
+ metisFIB_Destroy(&fib);
+
+ // ----- Validate
+ assertTrue(hashCodeTableLength == 1, "Wrong hash table length, expected %u got %zu", 1, hashCodeTableLength);
+ assertTrue(nexthopCount == 1, "Wrong hash table length, expected %u got %zu", 1, nexthopCount);
+}
+
+LONGBOW_TEST_CASE(Global, metisFib_Remove_ExistsNotLast)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisFIB *fib = metisFIB_Create(logger);
+ metisLogger_Release(&logger);
+
+ CCNxName *ccnxNameToAdd = ccnxName_CreateFromCString("lci:/foo/bar");
+ CCNxName *ccnxNameToRemove = ccnxName_CreateFromCString("lci:/foo/bar");
+ MetisTlvName *tlvNameToCheck = metisTlvName_CreateFromCCNxName(ccnxNameToAdd);
+ unsigned interfaceIndex_1 = 11;
+ unsigned interfaceIndex_2 = 22;
+ CPIAddress *nexthop = NULL;
+ struct timeval *lifetime = NULL;
+ unsigned cost = 12;
+
+ // ----- Add two next hops
+ CPIRouteEntry *routeAdd1 = cpiRouteEntry_Create(ccnxName_Copy(ccnxNameToAdd), interfaceIndex_1, nexthop, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, lifetime, cost);
+ metisFIB_AddOrUpdate(fib, routeAdd1, "random");
+
+ CPIRouteEntry *routeAdd2 = cpiRouteEntry_Create(ccnxNameToAdd, interfaceIndex_2, nexthop, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, lifetime, cost);
+ metisFIB_AddOrUpdate(fib, routeAdd2, "random");
+
+ // ----- Remove
+ CPIRouteEntry *routeRemove = cpiRouteEntry_Create(ccnxNameToRemove, interfaceIndex_1, nexthop, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, lifetime, cost);
+ metisFIB_Remove(fib, routeRemove);
+
+ // ----- Measure
+ size_t hashCodeTableLength = parcHashCodeTable_Length(fib->tableByName);
+ MetisFibEntry *fibEntry = parcHashCodeTable_Get(fib->tableByName, tlvNameToCheck);
+ size_t nexthopCount = metisFibEntry_NexthopCount(fibEntry);
+
+ // ----- Cleanup
+ cpiRouteEntry_Destroy(&routeAdd1);
+ cpiRouteEntry_Destroy(&routeAdd2);
+ cpiRouteEntry_Destroy(&routeRemove);
+ metisTlvName_Release(&tlvNameToCheck);
+ metisFIB_Destroy(&fib);
+
+ // ----- Validate
+ assertTrue(hashCodeTableLength == 1, "Wrong hash table length, expected %u got %zu", 1, hashCodeTableLength);
+ assertTrue(nexthopCount == 1, "Wrong hash table length, expected %u got %zu", 1, nexthopCount);
+}
+
+/**
+ * Remove the last nexthop for a route. should remove the route
+ */
+LONGBOW_TEST_CASE(Global, metisFib_Remove_ExistsIsLast)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisFIB *fib = metisFIB_Create(logger);
+ metisLogger_Release(&logger);
+
+ CCNxName *ccnxNameToAdd = ccnxName_CreateFromCString("lci:/foo/bar");
+ CCNxName *ccnxNameToRemove = ccnxName_CreateFromCString("lci:/foo/bar");
+ MetisTlvName *tlvNameToCheck = metisTlvName_CreateFromCCNxName(ccnxNameToAdd);
+ unsigned interfaceIndex_1 = 22;
+ CPIAddress *nexthop = NULL;
+ struct timeval *lifetime = NULL;
+ unsigned cost = 12;
+
+ // ----- Add
+ CPIRouteEntry *routeAdd = cpiRouteEntry_Create(ccnxNameToAdd, interfaceIndex_1, nexthop, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, lifetime, cost);
+ metisFIB_AddOrUpdate(fib, routeAdd, "random");
+
+ // ----- Remove
+ CPIRouteEntry *routeRemove = cpiRouteEntry_Create(ccnxNameToRemove, interfaceIndex_1, nexthop, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, lifetime, cost);
+ metisFIB_Remove(fib, routeRemove);
+
+ // ----- Measure
+ size_t hashCodeTableLength = parcHashCodeTable_Length(fib->tableByName);
+
+ // ----- Cleanup
+ cpiRouteEntry_Destroy(&routeAdd);
+ cpiRouteEntry_Destroy(&routeRemove);
+ metisTlvName_Release(&tlvNameToCheck);
+ metisFIB_Destroy(&fib);
+
+ // ----- Validate
+ assertTrue(hashCodeTableLength == 0, "Wrong hash table length, expected %u got %zu", 0, hashCodeTableLength);
+}
+
+LONGBOW_TEST_CASE(Global, metisFIB_Length)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisFIB *fib = metisFIB_Create(logger);
+
+ // CCNxName *ccnxNameToAdd = ccnxName_CreateFromCString("lci:/%02=hello/%F0%00=ouch");
+ CCNxName *ccnxNameToAdd = ccnxName_CreateFromCString("lci:/2=hello/0xF000=ouch");
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ unsigned interfaceIndex_1 = 22;
+ CPIAddress *nexthop = NULL;
+ struct timeval *lifetime = NULL;
+ unsigned cost = 12;
+
+ // ----- Add
+ CPIRouteEntry *routeAdd = cpiRouteEntry_Create(ccnxNameToAdd, interfaceIndex_1, nexthop, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, lifetime, cost);
+ metisFIB_AddOrUpdate(fib, routeAdd, "random");
+
+ // ----- Measure
+ size_t tableLength = metisFIB_Length(fib);
+
+ // ----- Cleanup
+ cpiRouteEntry_Destroy(&routeAdd);
+ metisMessage_Release(&interest);
+ metisFIB_Destroy(&fib);
+
+ // ----- Validate
+ assertTrue(tableLength == 1, "Wrong table length, expected %u got %zu", 1, tableLength);
+}
+
+// ====================================================================
+
+LONGBOW_TEST_FIXTURE(Local)
+{
+ LONGBOW_RUN_TEST_CASE(Local, _hashTableFunction_FibEntryDestroyer);
+ LONGBOW_RUN_TEST_CASE(Local, _hashTableFunction_TlvNameDestroyer);
+ LONGBOW_RUN_TEST_CASE(Local, _metisFIB_CreateFibEntry);
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(Local)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(Local)
+{
+ uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO);
+ if (outstandingAllocations != 0) {
+ printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations);
+ return LONGBOW_STATUS_MEMORYLEAK;
+ }
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_CASE(Local, _hashTableFunction_FibEntryDestroyer)
+{
+ CCNxName *ccnxName = ccnxName_CreateFromCString("lci:/foo/bar");
+ MetisTlvName *tlvName = metisTlvName_CreateFromCCNxName(ccnxName);
+ MetisFibEntry *fibEntry = metisFibEntry_Create(tlvName, "random");
+
+ _hashTableFunction_FibEntryDestroyer((void **) &fibEntry);
+
+ metisTlvName_Release(&tlvName);
+ ccnxName_Release(&ccnxName);
+ assertTrue(parcMemory_Outstanding() == 0, "Memory imbalance after hashTableFunction_TlvNameDestroyer: %u", parcMemory_Outstanding());
+}
+
+LONGBOW_TEST_CASE(Local, _hashTableFunction_TlvNameDestroyer)
+{
+ CCNxName *ccnxName = ccnxName_CreateFromCString("lci:/foo/bar");
+ MetisTlvName *tlvName = metisTlvName_CreateFromCCNxName(ccnxName);
+
+ _hashTableFunction_TlvNameDestroyer((void **) &tlvName);
+ ccnxName_Release(&ccnxName);
+
+ assertTrue(parcMemory_Outstanding() == 0, "Memory imbalance after hashTableFunction_TlvNameDestroyer: %u", parcMemory_Outstanding());
+}
+
+LONGBOW_TEST_CASE(Local, _metisFIB_CreateFibEntry)
+{
+ CCNxName *ccnxName = ccnxName_CreateFromCString("lci:/foo/bar");
+ MetisTlvName *tlvName = metisTlvName_CreateFromCCNxName(ccnxName);
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisFIB *fib = metisFIB_Create(logger);
+ metisLogger_Release(&logger);
+
+ _metisFIB_CreateFibEntry(fib, tlvName, "random");
+ size_t hashCodeTableLength = parcHashCodeTable_Length(fib->tableByName);
+
+ metisFIB_Destroy(&fib);
+ metisTlvName_Release(&tlvName);
+ ccnxName_Release(&ccnxName);
+
+ assertTrue(hashCodeTableLength == 1, "Wrong hash table size, expected %u got %zu", 1, hashCodeTableLength);
+}
+
+int
+main(int argc, char *argv[])
+{
+ LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_FIB);
+ int exitStatus = longBowMain(argc, argv, testRunner, NULL);
+ longBowTestRunner_Destroy(&testRunner);
+ exit(exitStatus);
+}
diff --git a/metis/ccnx/forwarder/metis/processor/test/test_metis_FibEntry.c b/metis/ccnx/forwarder/metis/processor/test/test_metis_FibEntry.c
new file mode 100644
index 00000000..19b46d47
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/test/test_metis_FibEntry.c
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+// Include the file(s) containing the functions to be tested.
+// This permits internal static functions to be visible to this Test Framework.
+#include "../metis_FibEntry.c"
+#include <LongBow/unit-test.h>
+#include <parc/algol/parc_SafeMemory.h>
+
+LONGBOW_TEST_RUNNER(metis_FibEntry)
+{
+ // The following Test Fixtures will run their corresponding Test Cases.
+ // Test Fixtures are run in the order specified, but all tests should be idempotent.
+ // Never rely on the execution order of tests or share state between them.
+ LONGBOW_RUN_TEST_FIXTURE(Global);
+ LONGBOW_RUN_TEST_FIXTURE(Local);
+}
+
+// The Test Runner calls this function once before any Test Fixtures are run.
+LONGBOW_TEST_RUNNER_SETUP(metis_FibEntry)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// The Test Runner calls this function once after all the Test Fixtures are run.
+LONGBOW_TEST_RUNNER_TEARDOWN(metis_FibEntry)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE(Global)
+{
+ LONGBOW_RUN_TEST_CASE(Global, metisFibEntry_AddNexthop);
+ LONGBOW_RUN_TEST_CASE(Global, metisFibEntry_Create_Destroy);
+ LONGBOW_RUN_TEST_CASE(Global, metisFibEntry_SetStrategy);
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(Global)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(Global)
+{
+ uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO);
+ if (outstandingAllocations != 0) {
+ printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations);
+ return LONGBOW_STATUS_MEMORYLEAK;
+ }
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_CASE(Global, metisFibEntry_AddNexthop)
+{
+ CCNxName *ccnxName = ccnxName_CreateFromCString("lci:/foo/bar");
+ MetisTlvName *tlvName = metisTlvName_CreateFromCCNxName(ccnxName);
+ MetisFibEntry *fibEntry = metisFibEntry_Create(tlvName,"random");
+
+ CCNxName *ccnxName1 = ccnxName_CreateFromCString("lci:/foo/bar");
+ CPIRouteEntry *cpiRouteEntry1 = cpiRouteEntry_Create(ccnxName1, 1, NULL, 0, 0, NULL, 1);
+ CCNxName *ccnxName2 = ccnxName_CreateFromCString("lci:/foo/bar");
+ CPIRouteEntry *cpiRouteEntry2 = cpiRouteEntry_Create(ccnxName2, 2, NULL, 0, 0, NULL, 1);
+ metisFibEntry_AddNexthop(fibEntry, cpiRouteEntry1);
+ metisFibEntry_AddNexthop(fibEntry, cpiRouteEntry2);
+
+ assertTrue(metisFibEntry_NexthopCount(fibEntry) == 2, "wrong nexthop length, expected %u got %zu", 2, metisFibEntry_NexthopCount(fibEntry));
+
+ cpiRouteEntry_Destroy(&cpiRouteEntry1);
+ cpiRouteEntry_Destroy(&cpiRouteEntry2);
+ metisFibEntry_Release(&fibEntry);
+ metisTlvName_Release(&tlvName);
+ ccnxName_Release(&ccnxName);
+}
+
+LONGBOW_TEST_CASE(Global, metisFibEntry_Create_Destroy)
+{
+ CCNxName *ccnxName = ccnxName_CreateFromCString("lci:/foo/bar");
+ MetisTlvName *tlvName = metisTlvName_CreateFromCCNxName(ccnxName);
+
+ size_t beforeMemory = parcMemory_Outstanding();
+ MetisFibEntry *fibEntry = metisFibEntry_Create(tlvName, "random");
+ metisFibEntry_Release(&fibEntry);
+ size_t afterMemory = parcMemory_Outstanding();
+
+ metisTlvName_Release(&tlvName);
+ ccnxName_Release(&ccnxName);
+
+ assertTrue(beforeMemory == afterMemory, "Memory imbalance on create/destroy: expected %zu got %zu", beforeMemory, afterMemory);
+}
+
+
+LONGBOW_TEST_CASE(Global, metisFibEntry_SetStrategy)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+LONGBOW_TEST_FIXTURE(Local)
+{
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(Local)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(Local)
+{
+ uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO);
+ if (outstandingAllocations != 0) {
+ printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations);
+ return LONGBOW_STATUS_MEMORYLEAK;
+ }
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+int
+main(int argc, char *argv[])
+{
+ LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_FibEntry);
+ int exitStatus = longBowMain(argc, argv, testRunner, NULL);
+ longBowTestRunner_Destroy(&testRunner);
+ exit(exitStatus);
+}
diff --git a/metis/ccnx/forwarder/metis/processor/test/test_metis_FibEntryList.c b/metis/ccnx/forwarder/metis/processor/test/test_metis_FibEntryList.c
new file mode 100644
index 00000000..25de216b
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/test/test_metis_FibEntryList.c
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+// Include the file(s) containing the functions to be tested.
+// This permits internal static functions to be visible to this Test Framework.
+#include "../metis_FibEntryList.c"
+
+#include <LongBow/unit-test.h>
+
+LONGBOW_TEST_RUNNER(metis_FibEntryList)
+{
+ // The following Test Fixtures will run their corresponding Test Cases.
+ // Test Fixtures are run in the order specified, but all tests should be idempotent.
+ // Never rely on the execution order of tests or share state between them.
+ LONGBOW_RUN_TEST_FIXTURE(Global);
+ LONGBOW_RUN_TEST_FIXTURE(Local);
+}
+
+// The Test Runner calls this function once before any Test Fixtures are run.
+LONGBOW_TEST_RUNNER_SETUP(metis_FibEntryList)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// The Test Runner calls this function once after all the Test Fixtures are run.
+LONGBOW_TEST_RUNNER_TEARDOWN(metis_FibEntryList)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE(Global)
+{
+ LONGBOW_RUN_TEST_CASE(Global, metisFibEntryList_Append);
+ LONGBOW_RUN_TEST_CASE(Global, metisFibEntryList_Create);
+ LONGBOW_RUN_TEST_CASE(Global, metisFibEntryList_Destroy);
+ LONGBOW_RUN_TEST_CASE(Global, metisFibEntryList_Get);
+ LONGBOW_RUN_TEST_CASE(Global, metisFibEntryList_Length);
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(Global)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(Global)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_CASE(Global, metisFibEntryList_Append)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Global, metisFibEntryList_Create)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Global, metisFibEntryList_Destroy)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Global, metisFibEntryList_Get)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Global, metisFibEntryList_Length)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_FIXTURE(Local)
+{
+ LONGBOW_RUN_TEST_CASE(Local, metisFibEntryList_ListDestroyer);
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(Local)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(Local)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_CASE(Local, metisFibEntryList_ListDestroyer)
+{
+ testUnimplemented("");
+}
+
+int
+main(int argc, char *argv[])
+{
+ LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_FibEntryList);
+ int exitStatus = longBowMain(argc, argv, testRunner, NULL);
+ longBowTestRunner_Destroy(&testRunner);
+ exit(exitStatus);
+}
diff --git a/metis/ccnx/forwarder/metis/processor/test/test_metis_HashTableFunction.c b/metis/ccnx/forwarder/metis/processor/test/test_metis_HashTableFunction.c
new file mode 100644
index 00000000..8f61cfb0
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/test/test_metis_HashTableFunction.c
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+// Include the file(s) containing the functions to be tested.
+// This permits internal static functions to be visible to this Test Framework.
+#include "../metis_HashTableFunction.c"
+
+#include <LongBow/unit-test.h>
+
+LONGBOW_TEST_RUNNER(metis_HashTableFunction)
+{
+ // The following Test Fixtures will run their corresponding Test Cases.
+ // Test Fixtures are run in the order specified, but all tests should be idempotent.
+ // Never rely on the execution order of tests or share state between them.
+ LONGBOW_RUN_TEST_FIXTURE(Global);
+ LONGBOW_RUN_TEST_FIXTURE(Local);
+}
+
+// The Test Runner calls this function once before any Test Fixtures are run.
+LONGBOW_TEST_RUNNER_SETUP(metis_HashTableFunction)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// The Test Runner calls this function once after all the Test Fixtures are run.
+LONGBOW_TEST_RUNNER_TEARDOWN(metis_HashTableFunction)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE(Global)
+{
+ LONGBOW_RUN_TEST_CASE(Global, metisHashTableFunction_MessageNameAndKeyIdEquals);
+ LONGBOW_RUN_TEST_CASE(Global, metisHashTableFunction_MessageNameAndKeyIdHashCode);
+ LONGBOW_RUN_TEST_CASE(Global, metisHashTableFunction_MessageNameAndObjectHashEquals);
+ LONGBOW_RUN_TEST_CASE(Global, metisHashTableFunction_MessageNameAndObjectHashHashCode);
+ LONGBOW_RUN_TEST_CASE(Global, metisHashTableFunction_MessageNameEquals);
+ LONGBOW_RUN_TEST_CASE(Global, metisHashTableFunction_MessageNameHashCode);
+ LONGBOW_RUN_TEST_CASE(Global, metisHashTableFunction_TlvNameCompare);
+ LONGBOW_RUN_TEST_CASE(Global, metisHashTableFunction_TlvNameEquals);
+ LONGBOW_RUN_TEST_CASE(Global, metisHashTableFunction_TlvNameHashCode);
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(Global)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(Global)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_CASE(Global, metisHashTableFunction_MessageNameAndKeyIdEquals)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Global, metisHashTableFunction_MessageNameAndKeyIdHashCode)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Global, metisHashTableFunction_MessageNameAndObjectHashEquals)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Global, metisHashTableFunction_MessageNameAndObjectHashHashCode)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Global, metisHashTableFunction_MessageNameEquals)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Global, metisHashTableFunction_MessageNameHashCode)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Global, metisHashTableFunction_TlvNameCompare)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Global, metisHashTableFunction_TlvNameEquals)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Global, metisHashTableFunction_TlvNameHashCode)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_FIXTURE(Local)
+{
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(Local)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(Local)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+int
+main(int argc, char *argv[])
+{
+ LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_HashTableFunction);
+ int exitStatus = longBowMain(argc, argv, testRunner, NULL);
+ longBowTestRunner_Destroy(&testRunner);
+ exit(exitStatus);
+}
diff --git a/metis/ccnx/forwarder/metis/processor/test/test_metis_MatchingRulesTable.c b/metis/ccnx/forwarder/metis/processor/test/test_metis_MatchingRulesTable.c
new file mode 100644
index 00000000..63e31dea
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/test/test_metis_MatchingRulesTable.c
@@ -0,0 +1,666 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+// Include the file(s) containing the functions to be tested.
+// This permits internal static functions to be visible to this Test Framework.
+#include "../metis_MatchingRulesTable.c"
+#include <LongBow/unit-test.h>
+
+#include <parc/algol/parc_SafeMemory.h>
+#include <parc/logging/parc_LogReporterTextStdout.h>
+
+#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h>
+
+LONGBOW_TEST_RUNNER(metis_MatchingRulesTable)
+{
+ // The following Test Fixtures will run their corresponding Test Cases.
+ // Test Fixtures are run in the order specified, but all tests should be idempotent.
+ // Never rely on the execution order of tests or share state between them.
+ LONGBOW_RUN_TEST_FIXTURE(Global);
+ LONGBOW_RUN_TEST_FIXTURE(Local);
+ LONGBOW_RUN_TEST_FIXTURE(HashFunctions);
+}
+
+// The Test Runner calls this function once before any Test Fixtures are run.
+LONGBOW_TEST_RUNNER_SETUP(metis_MatchingRulesTable)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// The Test Runner calls this function once after all the Test Fixtures are run.
+LONGBOW_TEST_RUNNER_TEARDOWN(metis_MatchingRulesTable)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// ============================================================================
+
+LONGBOW_TEST_FIXTURE(Global)
+{
+ LONGBOW_RUN_TEST_CASE(Global, metisMatchingRulesTable_Create_Destroy);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisMatchingRulesTable_Add_ByName);
+ LONGBOW_RUN_TEST_CASE(Global, metisMatchingRulesTable_Add_ByNameAndKeyId);
+ LONGBOW_RUN_TEST_CASE(Global, metisMatchingRulesTable_Add_ByNameAndObjectHash);
+ LONGBOW_RUN_TEST_CASE(Global, metisMatchingRulesTable_AddToAllTables);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisMatchingRulesTable_Get);
+ LONGBOW_RUN_TEST_CASE(Global, metisMatchingRulesTable_RemoveFromBest);
+ LONGBOW_RUN_TEST_CASE(Global, metisMatchingRulesTable_RemoveFromAll);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisMatchingRulesTable_GetUnion_NoMatch);
+ LONGBOW_RUN_TEST_CASE(Global, metisMatchingRulesTable_GetUnion_1Table);
+ LONGBOW_RUN_TEST_CASE(Global, metisMatchingRulesTable_GetUnion_2Tables);
+ LONGBOW_RUN_TEST_CASE(Global, metisMatchingRulesTable_GetUnion_3Tables);
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(Global)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(Global)
+{
+ if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) {
+ return LONGBOW_STATUS_MEMORYLEAK;
+ }
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_CASE(Global, metisMatchingRulesTable_Add_ByName)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 1, logger);
+ metisLogger_Release(&logger);
+
+ MetisMatchingRulesTable *rulesTable = metisMatchingRulesTable_Create(NULL);
+ void *data = (void *) 0x01;
+
+ metisMatchingRulesTable_AddToBestTable(rulesTable, interest, data);
+ size_t tableLength = parcHashCodeTable_Length(rulesTable->tableByName);
+
+ metisMatchingRulesTable_Destroy(&rulesTable);
+ metisMessage_Release(&interest);
+
+ assertTrue(tableLength == 1, "tableByName wrong length, expected %u got %zu", 1, tableLength);
+}
+
+LONGBOW_TEST_CASE(Global, metisMatchingRulesTable_Add_ByNameAndKeyId)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName_keyid, sizeof(metisTestDataV0_InterestWithName_keyid), 1, 1, logger);
+ metisLogger_Release(&logger);
+ MetisMatchingRulesTable *rulesTable = metisMatchingRulesTable_Create(NULL);
+ void *data = (void *) 0x01;
+
+ metisMatchingRulesTable_AddToBestTable(rulesTable, interest, data);
+ size_t tableLength = parcHashCodeTable_Length(rulesTable->tableByNameAndKeyId);
+
+ metisMatchingRulesTable_Destroy(&rulesTable);
+ metisMessage_Release(&interest);
+
+ assertTrue(tableLength == 1, "tableByNameAndKeyId wrong length, expected %u got %zu", 1, tableLength);
+}
+
+LONGBOW_TEST_CASE(Global, metisMatchingRulesTable_Add_ByNameAndObjectHash)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName_objecthash, sizeof(metisTestDataV0_InterestWithName_objecthash), 1, 1, logger);
+ metisLogger_Release(&logger);
+ MetisMatchingRulesTable *rulesTable = metisMatchingRulesTable_Create(NULL);
+ void *data = (void *) 0x01;
+
+ metisMatchingRulesTable_AddToBestTable(rulesTable, interest, data);
+ size_t tableLength = parcHashCodeTable_Length(rulesTable->tableByNameAndObjectHash);
+
+ metisMatchingRulesTable_Destroy(&rulesTable);
+ metisMessage_Release(&interest);
+
+ assertTrue(tableLength == 1, "tableByNameAndObjectHash wrong length, expected %u got %zu", 1, tableLength);
+}
+
+LONGBOW_TEST_CASE(Global, metisMatchingRulesTable_AddToAllTables)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName_objecthash, sizeof(metisTestDataV0_InterestWithName_objecthash), 1, 1, logger);
+ metisLogger_Release(&logger);
+ MetisMatchingRulesTable *rulesTable = metisMatchingRulesTable_Create(NULL);
+ void *data = (void *) 0x01;
+
+ metisMatchingRulesTable_AddToAllTables(rulesTable, interest, data);
+ size_t tableLength = parcHashCodeTable_Length(rulesTable->tableByNameAndObjectHash);
+ assertTrue(tableLength == 1, "tableToAllTables wrong length, expected %u got %zu", 1, tableLength);
+
+ metisMatchingRulesTable_Destroy(&rulesTable);
+ metisMessage_Release(&interest);
+
+}
+
+LONGBOW_TEST_CASE(Global, metisMatchingRulesTable_Create_Destroy)
+{
+ size_t baselineMemory = parcMemory_Outstanding();
+
+ MetisMatchingRulesTable *rulesTable = metisMatchingRulesTable_Create(NULL);
+ metisMatchingRulesTable_Destroy(&rulesTable);
+
+ assertTrue(parcMemory_Outstanding() == baselineMemory, "Memory imbalance on create/destroy: %u", parcMemory_Outstanding());
+}
+
+LONGBOW_TEST_CASE(Global, metisMatchingRulesTable_Get)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName_objecthash, sizeof(metisTestDataV0_InterestWithName_objecthash), 1, 1, logger);
+ metisLogger_Release(&logger);
+ MetisMatchingRulesTable *rulesTable = metisMatchingRulesTable_Create(NULL);
+ void *data = (void *) 0x01;
+
+ metisMatchingRulesTable_AddToBestTable(rulesTable, interest, data);
+ void *test = metisMatchingRulesTable_Get(rulesTable, interest);
+
+ metisMatchingRulesTable_Destroy(&rulesTable);
+ metisMessage_Release(&interest);
+
+ assertTrue(data == test, "metisMatchingRulesTable_Get returned wrong result, expected %p got %p", data, test);
+}
+
+LONGBOW_TEST_CASE(Global, metisMatchingRulesTable_RemoveFromAll)
+{
+ MetisMatchingRulesTable *rulesTable = metisMatchingRulesTable_Create(NULL);
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 1, logger);
+ metisLogger_Release(&logger);
+ void *data = (void *) 0x01;
+
+ size_t before = parcHashCodeTable_Length(rulesTable->tableByName);
+ parcHashCodeTable_Add(rulesTable->tableByName, interest, data);
+ metisMatchingRulesTable_RemoveFromAll(rulesTable, interest);
+ size_t after = parcHashCodeTable_Length(rulesTable->tableByName);
+
+ metisMessage_Release(&interest);
+ metisMatchingRulesTable_Destroy(&rulesTable);
+
+ assertTrue(after == before, "Did not remove interest in HashCodeTable: before %zu after %zu", before, after);
+}
+
+LONGBOW_TEST_CASE(Global, metisMatchingRulesTable_RemoveFromBest)
+{
+ MetisMatchingRulesTable *rulesTable = metisMatchingRulesTable_Create(NULL);
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 1, logger);
+ metisLogger_Release(&logger);
+ void *data = (void *) 0x01;
+
+ size_t before = parcHashCodeTable_Length(rulesTable->tableByName);
+ parcHashCodeTable_Add(rulesTable->tableByName, interest, data);
+ metisMatchingRulesTable_RemoveFromBest(rulesTable, interest);
+ size_t after = parcHashCodeTable_Length(rulesTable->tableByName);
+
+ metisMessage_Release(&interest);
+ metisMatchingRulesTable_Destroy(&rulesTable);
+
+ assertTrue(after == before, "Did not remove interest in HashCodeTable: before %zu after %zu", before, after);
+}
+
+LONGBOW_TEST_CASE(Global, metisMatchingRulesTable_GetUnion_NoMatch)
+{
+ MetisMatchingRulesTable *rulesTable = metisMatchingRulesTable_Create(NULL);
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 1, logger);
+ metisLogger_Release(&logger);
+
+ PARCArrayList *list = metisMatchingRulesTable_GetUnion(rulesTable, object);
+ assertTrue(parcArrayList_Size(list) == 0, "Incorrect result length, expected %u got %zu", 0, parcArrayList_Size(list));
+
+ parcArrayList_Destroy(&list);
+ metisMessage_Release(&object);
+ metisMatchingRulesTable_Destroy(&rulesTable);
+}
+
+LONGBOW_TEST_CASE(Global, metisMatchingRulesTable_GetUnion_1Table)
+{
+ MetisMatchingRulesTable *rulesTable = metisMatchingRulesTable_Create(NULL);
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interestByName = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+ void *data = (void *) 0x01;
+
+ // add the interest to the table
+ metisMatchingRulesTable_AddToBestTable(rulesTable, interestByName, data);
+
+ // now retrieve it with a matching content object
+ MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 4, logger);
+
+ PARCArrayList *list = metisMatchingRulesTable_GetUnion(rulesTable, object);
+ assertTrue(parcArrayList_Size(list) == 1, "Incorrect result length, expected %u got %zu", 1, parcArrayList_Size(list));
+
+ metisLogger_Release(&logger);
+ parcArrayList_Destroy(&list);
+ metisMessage_Release(&object);
+ metisMessage_Release(&interestByName);
+ metisMatchingRulesTable_Destroy(&rulesTable);
+}
+
+LONGBOW_TEST_CASE(Global, metisMatchingRulesTable_GetUnion_2Tables)
+{
+ MetisMatchingRulesTable *rulesTable = metisMatchingRulesTable_Create(NULL);
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interestByName = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+ MetisMessage *interestByNameAndKeyId = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName_keyid, sizeof(metisTestDataV0_InterestWithName_keyid), 1, 2, logger);
+ void *data = (void *) 0x01;
+
+ // add the interest to the tables
+ metisMatchingRulesTable_AddToBestTable(rulesTable, interestByName, data);
+ metisMatchingRulesTable_AddToBestTable(rulesTable, interestByNameAndKeyId, data);
+
+ // now retrieve it with a matching content object
+ MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 4, logger);
+
+ PARCArrayList *list = metisMatchingRulesTable_GetUnion(rulesTable, object);
+ assertTrue(parcArrayList_Size(list) == 2, "Incorrect result length, expected %u got %zu", 2, parcArrayList_Size(list));
+
+ metisLogger_Release(&logger);
+ parcArrayList_Destroy(&list);
+ metisMessage_Release(&object);
+ metisMessage_Release(&interestByName);
+ metisMessage_Release(&interestByNameAndKeyId);
+ metisMatchingRulesTable_Destroy(&rulesTable);
+}
+
+LONGBOW_TEST_CASE(Global, metisMatchingRulesTable_GetUnion_3Tables)
+{
+ MetisMatchingRulesTable *rulesTable = metisMatchingRulesTable_Create(NULL);
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interestByName = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+ MetisMessage *interestByNameAndKeyId = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName_keyid, sizeof(metisTestDataV0_InterestWithName_keyid), 1, 2, logger);
+ MetisMessage *interestByNameAndObjectHash = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName_objecthash, sizeof(metisTestDataV0_InterestWithName_objecthash), 1, 2, logger);
+ void *data = (void *) 0x01;
+
+ // add the interest to the tables
+ assertTrue(metisMatchingRulesTable_AddToBestTable(rulesTable, interestByName, data), "Cannot add interestByName");
+ assertTrue(metisMatchingRulesTable_AddToBestTable(rulesTable, interestByNameAndKeyId, data), "Cannot add interestByNameAndKeyId");
+ assertTrue(metisMatchingRulesTable_AddToBestTable(rulesTable, interestByNameAndObjectHash, data), "Cannot add interestByNameAndObjectHash");
+
+ // now retrieve it with a matching content object
+ MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 4, logger);
+
+ PARCArrayList *list = metisMatchingRulesTable_GetUnion(rulesTable, object);
+ assertTrue(parcArrayList_Size(list) == 3, "Incorrect result length, expected %u got %zu", 3, parcArrayList_Size(list));
+
+ metisLogger_Release(&logger);
+ parcArrayList_Destroy(&list);
+ metisMessage_Release(&object);
+ metisMessage_Release(&interestByName);
+ metisMessage_Release(&interestByNameAndKeyId);
+ metisMessage_Release(&interestByNameAndObjectHash);
+ metisMatchingRulesTable_Destroy(&rulesTable);
+}
+
+// ============================================================================
+
+LONGBOW_TEST_FIXTURE(Local)
+{
+ LONGBOW_RUN_TEST_CASE(Local, metisMatchingRulesTable_GetTableForMessage_TableByNameAndObjectHash);
+ LONGBOW_RUN_TEST_CASE(Local, metisMatchingRulesTable_GetTableForMessage_TableByNameAndKeyId);
+ LONGBOW_RUN_TEST_CASE(Local, metisMatchingRulesTable_GetTableForMessage_TableByName);
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(Local)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(Local)
+{
+ if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) {
+ return LONGBOW_STATUS_MEMORYLEAK;
+ }
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+/**
+ * Use an interest with only a name, should select tableByName
+ */
+LONGBOW_TEST_CASE(Local, metisMatchingRulesTable_GetTableForMessage_TableByName)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 1, logger);
+ metisLogger_Release(&logger);
+
+ MetisMatchingRulesTable *rulesTable = metisMatchingRulesTable_Create(NULL);
+ PARCHashCodeTable *table = metisMatchingRulesTable_GetTableForMessage(rulesTable, interest);
+
+ assertTrue(table == rulesTable->tableByName,
+ "Chose wrong table, expected TableByName, got %s",
+ (table == rulesTable->tableByNameAndKeyId) ? "tableByNameAndKeyId" :
+ (table == rulesTable->tableByNameAndObjectHash) ? "tableByNameAndObjectHash" : "unknown");
+
+ metisMatchingRulesTable_Destroy(&rulesTable);
+ metisMessage_Release(&interest);
+}
+
+/**
+ * Use an interest with a name and keyid, should select tableByNameAndKeyId
+ */
+LONGBOW_TEST_CASE(Local, metisMatchingRulesTable_GetTableForMessage_TableByNameAndKeyId)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName_keyid, sizeof(metisTestDataV0_InterestWithName_keyid), 1, 1, logger);
+ metisLogger_Release(&logger);
+
+ MetisMatchingRulesTable *rulesTable = metisMatchingRulesTable_Create(NULL);
+ PARCHashCodeTable *table = metisMatchingRulesTable_GetTableForMessage(rulesTable, interest);
+
+ assertTrue(table == rulesTable->tableByNameAndKeyId,
+ "Chose wrong table, expected TableByNameAndKeyId, got %s",
+ (table == rulesTable->tableByName) ? "tableByName" :
+ (table == rulesTable->tableByNameAndObjectHash) ? "tableByNameAndObjectHash" : "unknown");
+
+ metisMatchingRulesTable_Destroy(&rulesTable);
+ metisMessage_Release(&interest);
+}
+
+/**
+ * Use an interest with a name and objecthash, should select tableByNameAndObjectHash
+ */
+LONGBOW_TEST_CASE(Local, metisMatchingRulesTable_GetTableForMessage_TableByNameAndObjectHash)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName_objecthash, sizeof(metisTestDataV0_InterestWithName_objecthash), 1, 1, logger);
+ metisLogger_Release(&logger);
+
+ MetisMatchingRulesTable *rulesTable = metisMatchingRulesTable_Create(NULL);
+ PARCHashCodeTable *table = metisMatchingRulesTable_GetTableForMessage(rulesTable, interest);
+
+ assertTrue(table == rulesTable->tableByNameAndObjectHash,
+ "Chose wrong table, expected TableByName, got %s",
+ (table == rulesTable->tableByNameAndKeyId) ? "tableByNameAndKeyId" :
+ (table == rulesTable->tableByName) ? "tableByName" : "unknown");
+
+ metisMatchingRulesTable_Destroy(&rulesTable);
+ metisMessage_Release(&interest);
+}
+
+// ============================================================================
+
+LONGBOW_TEST_FIXTURE(HashFunctions)
+{
+ LONGBOW_RUN_TEST_CASE(HashFunctions, hashTableFunction_NameAndKeyIdEquals_IsEqual);
+ LONGBOW_RUN_TEST_CASE(HashFunctions, hashTableFunction_NameAndKeyIdEquals_IsNotEqual);
+ LONGBOW_RUN_TEST_CASE(HashFunctions, hashTableFunction_NameAndKeyIdHashCode);
+
+ LONGBOW_RUN_TEST_CASE(HashFunctions, hashTableFunction_NameAndObjectHashEquals_IsEqual);
+ LONGBOW_RUN_TEST_CASE(HashFunctions, hashTableFunction_NameAndObjectHashEquals_IsNotEqual);
+ LONGBOW_RUN_TEST_CASE(HashFunctions, hashTableFunction_NameAndObjectHashHashCode);
+
+ LONGBOW_RUN_TEST_CASE(HashFunctions, hashTableFunction_NameEquals_IsEqual);
+ LONGBOW_RUN_TEST_CASE(HashFunctions, hashTableFunction_NameEquals_IsNotEqual);
+ LONGBOW_RUN_TEST_CASE(HashFunctions, hashTableFunction_NameHashCode);
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(HashFunctions)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(HashFunctions)
+{
+ if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) {
+ return LONGBOW_STATUS_MEMORYLEAK;
+ }
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+/**
+ * Test an interest and content object that match on (Name, KeyId)
+ */
+LONGBOW_TEST_CASE(HashFunctions, hashTableFunction_NameAndKeyIdEquals_IsEqual)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *a = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest), 1, 1, logger);
+ MetisMessage *b = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 1, logger);
+ metisLogger_Release(&logger);
+
+ bool success = metisHashTableFunction_MessageNameAndKeyIdEquals(a, b);
+
+ metisMessage_Release(&a);
+ metisMessage_Release(&b);
+
+ assertTrue(success, "Two equal names and keyids did not compare equal");
+}
+
+/**
+ * Test two interests that do not match on (Name, KeyId)
+ */
+LONGBOW_TEST_CASE(HashFunctions, hashTableFunction_NameAndKeyIdEquals_IsNotEqual)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *a = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName_keyid, sizeof(metisTestDataV0_InterestWithName_keyid), 1, 1, logger);
+ MetisMessage *b = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName_keyid2, sizeof(metisTestDataV0_InterestWithName_keyid2), 1, 1, logger);
+ metisLogger_Release(&logger);
+
+ bool success = metisHashTableFunction_MessageNameAndKeyIdEquals(a, b);
+
+ metisMessage_Release(&a);
+ metisMessage_Release(&b);
+
+ assertFalse(success, "Two unequal names compared equal");
+}
+
+LONGBOW_TEST_CASE(HashFunctions, hashTableFunction_NameAndKeyIdHashCode)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName_keyid, sizeof(metisTestDataV0_InterestWithName_keyid), 1, 1, logger);
+ metisLogger_Release(&logger);
+
+ MetisTlvName *name = metisMessage_GetName(interest);
+ uint32_t name_hash = metisTlvName_HashCode(name);
+ uint32_t keyid_hash;
+ metisMessage_GetKeyIdHash(interest, &keyid_hash);
+
+ HashCodeType truth_hash = parcHash32_Data_Cumulative(&keyid_hash, sizeof(keyid_hash), name_hash);
+
+ // the function to test
+ HashCodeType test_hash = metisHashTableFunction_MessageNameAndKeyIdHashCode(interest);
+
+ metisMessage_Release(&interest);
+
+ assertTrue(test_hash == truth_hash, "Got wrong hash, expected %08"PRIX64 " got %08"PRIX64, truth_hash, test_hash);
+}
+
+LONGBOW_TEST_CASE(HashFunctions, hashTableFunction_NameAndObjectHashEquals_IsEqual)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *a = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName_objecthash, sizeof(metisTestDataV0_InterestWithName_objecthash), 1, 1, logger);
+ MetisMessage *b = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 1, logger);
+ metisLogger_Release(&logger);
+
+ bool success = metisHashTableFunction_MessageNameAndObjectHashEquals(a, b);
+
+ metisMessage_Release(&a);
+ metisMessage_Release(&b);
+
+ assertTrue(success, "Two equal names and hashes did not compare equal");
+}
+
+LONGBOW_TEST_CASE(HashFunctions, hashTableFunction_NameAndObjectHashEquals_IsNotEqual)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *a = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName_objecthash, sizeof(metisTestDataV0_InterestWithName_objecthash), 1, 1, logger);
+ MetisMessage *b = metisMessage_CreateFromArray(metisTestDataV0_SecondObject, sizeof(metisTestDataV0_SecondObject), 1, 1, logger);
+ metisLogger_Release(&logger);
+
+ bool success = metisHashTableFunction_MessageNameAndObjectHashEquals(a, b);
+
+ metisMessage_Release(&a);
+ metisMessage_Release(&b);
+
+ assertFalse(success, "Two unequal names and hashes compared equal");
+}
+
+LONGBOW_TEST_CASE(HashFunctions, hashTableFunction_NameAndObjectHashHashCode)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName_objecthash, sizeof(metisTestDataV0_InterestWithName_objecthash), 1, 1, logger);
+ metisLogger_Release(&logger);
+
+ MetisTlvName *name = metisMessage_GetName(interest);
+ uint32_t name_hash = metisTlvName_HashCode(name);
+ uint32_t object_hash;
+ metisMessage_GetContentObjectHashHash(interest, &object_hash);
+
+ HashCodeType truth_hash = parcHash32_Data_Cumulative(&object_hash, sizeof(object_hash), name_hash);
+
+ // the function we actually want to test
+ HashCodeType test_hash = (HashCodeType)metisHashTableFunction_MessageNameAndObjectHashHashCode(interest);
+
+ metisMessage_Release(&interest);
+
+ assertTrue(test_hash == truth_hash, "Got wrong hash, expected %08"PRIX64 " got %08"PRIX64 , truth_hash, test_hash);
+}
+
+/**
+ * Takes two MetisMessage and compares their names for equality
+ */
+LONGBOW_TEST_CASE(HashFunctions, hashTableFunction_NameEquals_IsEqual)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *a = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 1, logger);
+ MetisMessage *b = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName_keyid, sizeof(metisTestDataV0_InterestWithName_keyid), 1, 1, logger);
+ metisLogger_Release(&logger);
+
+ bool success = metisHashTableFunction_MessageNameEquals(a, b);
+
+ metisMessage_Release(&a);
+ metisMessage_Release(&b);
+
+ assertTrue(success, "Two equal names did not compare equal");
+}
+
+/**
+ * test two interests with different names
+ */
+LONGBOW_TEST_CASE(HashFunctions, hashTableFunction_NameEquals_IsNotEqual)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *a = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 1, logger);
+ MetisMessage *b = metisMessage_CreateFromArray(metisTestDataV0_InterestWithOtherName, sizeof(metisTestDataV0_InterestWithOtherName), 1, 1, logger);
+ metisLogger_Release(&logger);
+
+ bool success = metisHashTableFunction_MessageNameEquals(a, b);
+
+ metisMessage_Release(&a);
+ metisMessage_Release(&b);
+
+ assertFalse(success, "Two unequal names compared equal");
+}
+
+/**
+ * Used on a MetisMessage key type, should return the HashCode
+ * of the message's name
+ */
+LONGBOW_TEST_CASE(HashFunctions, hashTableFunction_NameHashCode)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 1, logger);
+ metisLogger_Release(&logger);
+
+ MetisTlvName *name = metisMessage_GetName(interest);
+ HashCodeType truth_hash = (HashCodeType)metisTlvName_HashCode(name);
+ HashCodeType test_hash = metisHashTableFunction_MessageNameHashCode(interest);
+
+ metisMessage_Release(&interest);
+
+ assertTrue(test_hash == truth_hash, "Got wrong hash, expected %08"PRIX64 " got %08"PRIX64, truth_hash, test_hash);
+}
+
+// ============================================================================
+
+int
+main(int argc, char *argv[])
+{
+ LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_MatchingRulesTable);
+ int exitStatus = longBowMain(argc, argv, testRunner, NULL);
+ longBowTestRunner_Destroy(&testRunner);
+ exit(exitStatus);
+}
diff --git a/metis/ccnx/forwarder/metis/processor/test/test_metis_MessageProcessor.c b/metis/ccnx/forwarder/metis/processor/test/test_metis_MessageProcessor.c
new file mode 100644
index 00000000..1da3ca94
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/test/test_metis_MessageProcessor.c
@@ -0,0 +1,1533 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+// Include the file(s) containing the functions to be tested.
+// This permits internal static functions to be visible to this Test Framework.
+#include "../metis_MessageProcessor.c"
+#include <LongBow/unit-test.h>
+#include <parc/algol/parc_SafeMemory.h>
+
+#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h>
+#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h>
+#include "../../core/test/testrig_MetisIoOperations.h"
+
+// Include this so we can step the clock forward without waiting real time
+#include "../../core/metis_Forwarder.c"
+
+#include "testrig_MockTap.h"
+
+// =========================================================================
+
+LONGBOW_TEST_RUNNER(metis_MessageProcessor)
+{
+ parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory);
+
+ LONGBOW_RUN_TEST_FIXTURE(Global);
+ LONGBOW_RUN_TEST_FIXTURE(Local);
+}
+
+// The Test Runner calls this function once before any Test Fixtures are run.
+LONGBOW_TEST_RUNNER_SETUP(metis_MessageProcessor)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// The Test Runner calls this function once after all the Test Fixtures are run.
+LONGBOW_TEST_RUNNER_TEARDOWN(metis_MessageProcessor)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE(Global)
+{
+ LONGBOW_RUN_TEST_CASE(Global, metisMessageProcessor_Create_Destroy);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessageProcessor_AddTap);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisMessageProcessor_Receive_WithTap);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessageProcessor_Receive_Interest_WithoutTap);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessageProcessor_Receive_Object_WithoutTap);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisMessageProcessor_RemoveTap_RemoveCurrentTap);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessageProcessor_RemoveTap_RemoveOtherTap);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisMessageProcessor_AddOrUpdateRoute);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessageProcessor_RemoveRoute);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisMessageProcessor_SetContentStoreSize);
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(Global)
+{
+ memset(&testTap, 0, sizeof(testTap));
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(Global)
+{
+ uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO);
+ if (outstandingAllocations != 0) {
+ printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations);
+ return LONGBOW_STATUS_MEMORYLEAK;
+ }
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_CASE(Global, metisMessageProcessor_Create_Destroy)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+
+ uint32_t beforeBalance = parcMemory_Outstanding();
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ metisMessageProcessor_Destroy(&processor);
+ uint32_t afterBalance = parcMemory_Outstanding();
+
+ metisForwarder_Destroy(&metis);
+ assertTrue(beforeBalance == afterBalance, "Memory imbalance on create/destroy: before %u after %u", beforeBalance, afterBalance);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessageProcessor_AddTap)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+
+ metisMessageProcessor_AddTap(processor, &testTapTemplate);
+ void *currentTap = processor->tap;
+
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ assertTrue(currentTap == &testTapTemplate, "tap did not get set correctly, expected %p got %p", (void *) &testTapTemplate, currentTap);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessageProcessor_Receive_WithTap)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+
+ testTap.callOnReceive = true;
+ metisMessageProcessor_AddTap(processor, &testTapTemplate);
+
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 4, 5, logger);
+
+ // now test the function and measure results
+ metisMessageProcessor_Receive(processor, interest);
+
+ // cleanup
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ // validate
+ assertTrue(testTap.onReceiveCount == 1, "Incorrect testTap.onReceiveCount, expected %u got %u", 1, testTap.onReceiveCount);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessageProcessor_Receive_Interest_WithoutTap)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 4, 5, logger);
+
+ // now test the function and measure results
+ uint32_t beforeCountReceived = processor->stats.countReceived;
+ uint32_t beforeCountInterestsReceived = processor->stats.countInterestsReceived;
+ metisMessageProcessor_Receive(processor, interest);
+ uint32_t afterCountInterestsReceived = processor->stats.countInterestsReceived;
+ uint32_t afterCountReceived = processor->stats.countReceived;
+
+ // cleanup
+ // do not cleanup interest, metisMessageProcessor_Receive() takes ownership
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ // validate
+ assertTrue(afterCountReceived == beforeCountReceived + 1,
+ "Incorrect afterCountReceived, expected %u got %u",
+ beforeCountReceived + 1,
+ afterCountReceived);
+
+ assertTrue(afterCountInterestsReceived == beforeCountInterestsReceived + 1,
+ "Incorrect afterCountInterestsReceived, expected %u got %u",
+ beforeCountInterestsReceived + 1,
+ afterCountInterestsReceived);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessageProcessor_Receive_Object_WithoutTap)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 4, 5, logger);
+
+
+ // now test the function and measure results
+ uint32_t beforeCountReceived = processor->stats.countReceived;
+ uint32_t beforeCountObjectsReceived = processor->stats.countObjectsReceived;
+ metisMessageProcessor_Receive(processor, object);
+ uint32_t afterCountObjectsReceived = processor->stats.countObjectsReceived;
+ uint32_t afterCountReceived = processor->stats.countReceived;
+
+ // cleanup
+ // do not cleanup object, metisMessageProcessor_Receive() takes ownership
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ // validate
+ assertTrue(afterCountReceived == beforeCountReceived + 1,
+ "Incorrect afterCountReceived, expected %u got %u",
+ beforeCountReceived + 1,
+ afterCountReceived);
+
+ assertTrue(afterCountObjectsReceived == beforeCountObjectsReceived + 1,
+ "Incorrect afterCountInterestsReceived, expected %u got %u",
+ afterCountObjectsReceived,
+ beforeCountObjectsReceived + 1);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessageProcessor_RemoveTap_RemoveCurrentTap)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+
+ metisMessageProcessor_AddTap(processor, &testTapTemplate);
+ metisMessageProcessor_RemoveTap(processor, &testTapTemplate);
+ void *currentTap = processor->tap;
+
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ assertTrue(currentTap == NULL, "tap did not get removed correctly, expected %p got %p", NULL, currentTap);
+}
+
+/**
+ * If we remove a tap that is not currently set, should have no effect.
+ */
+LONGBOW_TEST_CASE(Global, metisMessageProcessor_RemoveTap_RemoveOtherTap)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+
+ MetisTap otherTap;
+
+ metisMessageProcessor_AddTap(processor, &testTapTemplate);
+ metisMessageProcessor_RemoveTap(processor, &otherTap);
+ void *currentTap = processor->tap;
+
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ assertTrue(currentTap == &testTapTemplate, "tap incorrectly removed, expected %p got %p", (void *) &testTapTemplate, currentTap);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessageProcessor_AddOrUpdateRoute)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+
+ CCNxName *ccnxName = ccnxName_CreateFromCString("lci:/foo/bar");
+ MetisTlvName *tlvName = metisTlvName_CreateFromCCNxName(ccnxName);
+ unsigned interfaceIndex = 22;
+ CPIAddress *nexthop = NULL;
+ struct timeval *lifetime = NULL;
+ unsigned cost = 12;
+
+ CPIRouteEntry *route = cpiRouteEntry_Create(ccnxName, interfaceIndex, nexthop, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, lifetime, cost);
+
+ metisMessageProcessor_AddOrUpdateRoute(processor, route);
+
+ size_t hashCodeTableLength = metisFIB_Length(processor->fib);
+
+ cpiRouteEntry_Destroy(&route);
+ metisTlvName_Release(&tlvName);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ assertTrue(hashCodeTableLength == 1, "Wrong hash table length, expected %u got %zu", 1, hashCodeTableLength);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessageProcessor_RemoveRoute)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+
+ CCNxName *ccnxName = ccnxName_CreateFromCString("lci:/foo/bar");
+ MetisTlvName *tlvName = metisTlvName_CreateFromCCNxName(ccnxName);
+ unsigned interfaceIndex = 22;
+ CPIAddress *nexthop = NULL;
+ struct timeval *lifetime = NULL;
+ unsigned cost = 12;
+
+ CPIRouteEntry *route = cpiRouteEntry_Create(ccnxName, interfaceIndex, nexthop, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, lifetime, cost);
+
+ metisMessageProcessor_AddOrUpdateRoute(processor, route);
+ metisMessageProcessor_RemoveRoute(processor, route);
+
+ size_t hashCodeTableLength = metisFIB_Length(processor->fib);
+
+ cpiRouteEntry_Destroy(&route);
+ metisTlvName_Release(&tlvName);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ assertTrue(hashCodeTableLength == 0, "Wrong hash table length, expected %u got %zu", 0, hashCodeTableLength);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessageProcessor_SetContentStoreSize)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+
+ size_t newCapacity = 1234;
+ metisForwarder_SetContentObjectStoreSize(metis, newCapacity);
+
+ MetisContentStoreInterface *storeImpl = metisMessageProcessor_GetContentObjectStore(metis->processor);
+
+ size_t testCapacity = metisContentStoreInterface_GetObjectCapacity(storeImpl);
+ assertTrue(testCapacity == newCapacity, "Expected the new store capacity");
+
+ metisForwarder_Destroy(&metis);
+}
+
+// ===================================================================================
+
+LONGBOW_TEST_FIXTURE(Local)
+{
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_Drop_TestTapNoDrop);
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_Drop_TestTapWithDrop);
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_Drop_Interest);
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_Drop_Object);
+
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_ForwardToInterfaceId_NoConnection);
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_ForwardToInterfaceId_SendFails);
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_ForwardToInterfaceId_SendInterest);
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_ForwardToInterfaceId_SendObject);
+
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_ForwardToInterfaceId_ZeroHopLimit_Remote);
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_ForwardToInterfaceId_ZeroHopLimit_Local);
+
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_ForwardToNexthops);
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_ForwardToNexthops_DontForwardToIngress);
+
+
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_ReceiveContentObjectV0_InPit);
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_ReceiveContentObjectV0_NotInPit);
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_ReceiveContentObjectV1_InPit);
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_ReceiveContentObjectV1_NotInPit);
+
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_ReceiveInterest_InPit);
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_ReceiveInterest_NotInPit);
+
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_ReceiveInterest_InCache);
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_ReceiveInterest_InCacheButExpired);
+
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_ReceiveInterest_NotInCache);
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_ReceiveInterest_InFib);
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_ReceiveInterest_NotInFib);
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_ReceiveInterest_NoHopLimit);
+
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_AggregateInterestInPit_NewEntry);
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_AggregateInterestInPit_ExistingEntry);
+
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_SatisfyFromContentStore_IsInStore);
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_SatisfyFromContentStore_IsNotInStore);
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_SatisfyFromContentStore_WithKeyIdNotVerified_WithoutVerification);
+
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_ForwardViaFib_IsNotInFib);
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_ForwardViaFib_IsInFib_EmptyEgressSet);
+
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_CheckAndDecrementHopLimitOnIngress_NoHopLimit);
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_CheckAndDecrementHopLimitOnIngress_Local_Zero);
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_CheckAndDecrementHopLimitOnIngress_Local_NonZero);
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_CheckAndDecrementHopLimitOnIngress_Remote_Zero);
+ LONGBOW_RUN_TEST_CASE(Local, metisMessageProcessor_CheckAndDecrementHopLimitOnIngress_Remote_NonZero);
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(Local)
+{
+ memset(&testTap, 0, sizeof(testTap));
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(Local)
+{
+ uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO);
+ if (outstandingAllocations != 0) {
+ printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations);
+ return LONGBOW_STATUS_MEMORYLEAK;
+ }
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+/**
+ * Test that the tap does not fire if testTap.callOnDrop is false
+ */
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_Drop_TestTapNoDrop)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+
+
+ testTap.callOnDrop = false;
+ metisMessageProcessor_AddTap(processor, &testTapTemplate);
+
+ // should not increment a counter
+ metisMessageProcessor_Drop(processor, interest);
+
+ metisMessage_Release(&interest);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ assertTrue(testTap.onDropCount == 0, "Incorrect onDropCount, expecting %u, got %u", 0, testTap.onDropCount);
+}
+
+/**
+ * Test that the tap does fire if testTap.callOnDrop is true
+ */
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_Drop_TestTapWithDrop)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+
+
+ testTap.callOnDrop = true;
+ metisMessageProcessor_AddTap(processor, &testTapTemplate);
+
+ // should increment a counter
+ metisMessageProcessor_Drop(processor, interest);
+
+ metisMessage_Release(&interest);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ assertTrue(testTap.onDropCount == 1, "Incorrect onDropCount, expecting %u, got %u", 1, testTap.onDropCount);
+}
+
+/**
+ * Test that when we drop an interest it is counted
+ */
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_Drop_Interest)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+
+
+ // should increment a counter
+ metisMessageProcessor_Drop(processor, interest);
+
+ unsigned countDropped = processor->stats.countDropped;
+ unsigned countInterestsDropped = processor->stats.countInterestsDropped;
+
+ metisMessage_Release(&interest);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ assertTrue(countInterestsDropped == 1, "Incorrect countInterestsDropped, expecting %u, got %u", 1, countInterestsDropped);
+ assertTrue(countDropped == 1, "Incorrect countDropped, expecting %u, got %u", 1, countDropped);
+}
+
+/**
+ * Test that when we drop an object it is counted
+ */
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_Drop_Object)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+
+
+ // should increment a counter
+ metisMessageProcessor_Drop(processor, object);
+
+ unsigned countDropped = processor->stats.countDropped;
+ unsigned countObjectsDropped = processor->stats.countObjectsDropped;
+
+ metisMessage_Release(&object);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ assertTrue(countObjectsDropped == 1, "Incorrect countInterestsDropped, expecting %u, got %u", 1, countObjectsDropped);
+ assertTrue(countDropped == 1, "Incorrect countDropped, expecting %u, got %u", 1, countDropped);
+}
+
+/**
+ * Send a message to a connection that does not exist in the connection table
+ */
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_ForwardToInterfaceId_NoConnection)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+
+
+ metisMessageProcessor_ForwardToInterfaceId(processor, object, 99);
+
+ uint32_t countDroppedConnectionNotFound = processor->stats.countDroppedConnectionNotFound;
+ uint32_t countObjectsDropped = processor->stats.countObjectsDropped;
+
+ metisMessage_Release(&object);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ assertTrue(countDroppedConnectionNotFound == 1, "Incorrect countDroppedConnectionNotFound, expecting %u, got %u", 1, countDroppedConnectionNotFound);
+ assertTrue(countObjectsDropped == 1, "Incorrect countDropped, expecting %u, got %u", 1, countObjectsDropped);
+}
+
+/**
+ * Send to a connection that is down
+ */
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_ForwardToInterfaceId_SendFails)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+
+ MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 99, false, false, false);
+ MetisConnection *conn = metisConnection_Create(ops);
+
+ metisConnectionTable_Add(metisForwarder_GetConnectionTable(metis), conn);
+ metisMessageProcessor_ForwardToInterfaceId(processor, object, 99);
+
+ uint32_t countSendFailures = processor->stats.countSendFailures;
+ uint32_t countObjectsDropped = processor->stats.countObjectsDropped;
+
+ metisMessage_Release(&object);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+ mockIoOperationsData_Destroy(&ops);
+
+ assertTrue(countSendFailures == 1, "Incorrect countSendFailures, expecting %u, got %u", 1, countSendFailures);
+ assertTrue(countObjectsDropped == 1, "Incorrect countDropped, expecting %u, got %u", 1, countObjectsDropped);
+}
+
+/**
+ * Send an interest out a good connection
+ */
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_ForwardToInterfaceId_SendInterest)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest), 1, 2, logger);
+
+ MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 99, true, true, false);
+ MockIoOperationsData *data = metisIoOperations_GetClosure(ops);
+ MetisConnection *conn = metisConnection_Create(ops);
+
+ metisConnectionTable_Add(metisForwarder_GetConnectionTable(metis), conn);
+ metisMessageProcessor_ForwardToInterfaceId(processor, object, 99);
+
+ uint32_t countInterestForwarded = processor->stats.countInterestForwarded;
+ uint32_t sendCount = data->sendCount;
+
+ metisMessage_Release(&object);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+ mockIoOperationsData_Destroy(&ops);
+
+ assertTrue(countInterestForwarded == 1, "Incorrect countInterestForwarded, expecting %u, got %u", 1, countInterestForwarded);
+ assertTrue(sendCount == 1, "Incorrect sendCount, expecting %u, got %u", 1, sendCount);
+}
+
+/**
+ * Send a content object out a good connection
+ */
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_ForwardToInterfaceId_SendObject)
+{
+ // setup
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+
+ MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 99, true, true, false);
+ MockIoOperationsData *data = metisIoOperations_GetClosure(ops);
+ MetisConnection *conn = metisConnection_Create(ops);
+
+ // test
+ metisConnectionTable_Add(metisForwarder_GetConnectionTable(metis), conn);
+ metisMessageProcessor_ForwardToInterfaceId(processor, object, 99);
+
+ // measure
+ uint32_t countObjectsForwarded = processor->stats.countObjectsForwarded;
+ uint32_t sendCount = data->sendCount;
+
+ // cleanup
+ metisMessage_Release(&object);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+ mockIoOperationsData_Destroy(&ops);
+
+ // validate
+ assertTrue(countObjectsForwarded == 1, "Incorrect countObjectsForwarded, expecting %u, got %u", 1, countObjectsForwarded);
+ assertTrue(sendCount == 1, "Incorrect sendCount, expecting %u, got %u", 1, sendCount);
+}
+
+/*
+ * Try to forward an interest with a 0 hop limit to a remote. Should fail
+ */
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_ForwardToInterfaceId_ZeroHopLimit_Remote)
+{
+ // setup
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest_zero_hoplimit, sizeof(metisTestDataV0_EncodedInterest_zero_hoplimit), 1, 2, logger);
+
+
+ unsigned connId = 99;
+ bool isLocal = false;
+ MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, connId, true, true, isLocal);
+ MockIoOperationsData *data = metisIoOperations_GetClosure(ops);
+ MetisConnection *conn = metisConnection_Create(ops);
+
+ metisConnectionTable_Add(metisForwarder_GetConnectionTable(metis), conn);
+ metisMessageProcessor_ForwardToInterfaceId(processor, object, connId);
+
+ // measure
+ uint32_t countDropZeroToRemote = processor->stats.countDroppedZeroHopLimitToRemote;
+ uint32_t sendCount = data->sendCount;
+
+ // cleanup
+ metisMessage_Release(&object);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+ mockIoOperationsData_Destroy(&ops);
+
+ // validate
+ assertTrue(countDropZeroToRemote == 1, "Incorrect countDropZeroToRemote, expecting %u, got %u", 1, countDropZeroToRemote);
+ assertTrue(sendCount == 0, "Incorrect sendCount, expecting %u, got %u", 0, sendCount);
+}
+
+/*
+ * Try to forward an interest with a 0 hop limit to a local. Should succeed.
+ */
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_ForwardToInterfaceId_ZeroHopLimit_Local)
+{
+ // setup
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest_zero_hoplimit, sizeof(metisTestDataV0_EncodedInterest_zero_hoplimit), 1, 2, logger);
+
+
+ unsigned connId = 99;
+ bool isLocal = true;
+ MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, connId, true, true, isLocal);
+ MockIoOperationsData *data = metisIoOperations_GetClosure(ops);
+ MetisConnection *conn = metisConnection_Create(ops);
+
+ metisConnectionTable_Add(metisForwarder_GetConnectionTable(metis), conn);
+ metisMessageProcessor_ForwardToInterfaceId(processor, object, connId);
+
+ // measure
+ uint32_t countDropZeroToRemote = processor->stats.countDroppedZeroHopLimitToRemote;
+ uint32_t sendCount = data->sendCount;
+
+ // cleanup
+ metisMessage_Release(&object);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+ mockIoOperationsData_Destroy(&ops);
+
+ // validate
+ assertTrue(countDropZeroToRemote == 0, "Incorrect countDropZeroToRemote, expecting %u, got %u", 0, countDropZeroToRemote);
+ assertTrue(sendCount == 1, "Incorrect sendCount, expecting %u, got %u", 1, sendCount);
+}
+
+
+/**
+ * Create 2 connections, and try to forwarder to both of them
+ */
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_ForwardToNexthops)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+
+
+ // 2 connections
+ MetisIoOperations *ops_42 = mockIoOperationsData_CreateSimple(1, 2, 42, true, true, false);
+ MockIoOperationsData *data_42 = metisIoOperations_GetClosure(ops_42);
+ MetisConnection *conn_42 = metisConnection_Create(ops_42);
+
+ MetisIoOperations *ops_43 = mockIoOperationsData_CreateSimple(1, 2, 43, true, true, false);
+ MockIoOperationsData *data_43 = metisIoOperations_GetClosure(ops_43);
+ MetisConnection *conn_43 = metisConnection_Create(ops_43);
+
+ // Add the connections to the connection table
+ metisConnectionTable_Add(metisForwarder_GetConnectionTable(metis), conn_42);
+ metisConnectionTable_Add(metisForwarder_GetConnectionTable(metis), conn_43);
+
+ // Setup the next hops
+ MetisNumberSet *nexthops = metisNumberSet_Create();
+ metisNumberSet_Add(nexthops, 42);
+ metisNumberSet_Add(nexthops, 43);
+
+ // forward the content object to both of them
+ metisMessageProcessor_ForwardToNexthops(processor, object, nexthops);
+
+ // there should be 2 object forwards and each IoOps should have gotten 1 send
+ uint32_t countObjectsForwarded = processor->stats.countObjectsForwarded;
+ uint32_t sendCount_42 = data_42->sendCount;
+ uint32_t sendCount_43 = data_43->sendCount;
+
+ // cleanup
+ metisNumberSet_Release(&nexthops);
+ metisMessage_Release(&object);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+ mockIoOperationsData_Destroy(&ops_42);
+ mockIoOperationsData_Destroy(&ops_43);
+
+ // validate
+ assertTrue(countObjectsForwarded == 2, "Incorrect countObjectsForwarded, expecting %u, got %u", 2, countObjectsForwarded);
+ assertTrue(sendCount_42 == 1, "Incorrect sendCount_42, expecting %u, got %u", 1, sendCount_42);
+ assertTrue(sendCount_43 == 1, "Incorrect sendCount_43, expecting %u, got %u", 1, sendCount_43);
+}
+
+/**
+ * There is a route in the FIB that points to the ingress interface of an interest.
+ * Ensure that we don't forward to that interface
+ */
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_ForwardToNexthops_DontForwardToIngress)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+
+ // ingress interface is #42, so it should not get forwarded out there
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 42, 1, logger);
+
+
+ // 2 connections
+ MetisIoOperations *ops_42 = mockIoOperationsData_CreateSimple(1, 2, 42, true, true, false);
+ MockIoOperationsData *data_42 = metisIoOperations_GetClosure(ops_42);
+ MetisConnection *conn_42 = metisConnection_Create(ops_42);
+
+ MetisIoOperations *ops_43 = mockIoOperationsData_CreateSimple(1, 2, 43, true, true, false);
+ MockIoOperationsData *data_43 = metisIoOperations_GetClosure(ops_43);
+ MetisConnection *conn_43 = metisConnection_Create(ops_43);
+
+ // Add the connections to the connection table
+ metisConnectionTable_Add(metisForwarder_GetConnectionTable(metis), conn_42);
+ metisConnectionTable_Add(metisForwarder_GetConnectionTable(metis), conn_43);
+
+ // Setup the next hops
+ MetisNumberSet *nexthops = metisNumberSet_Create();
+ metisNumberSet_Add(nexthops, 42);
+ metisNumberSet_Add(nexthops, 43);
+
+ // forward the content object to both of them
+ metisMessageProcessor_ForwardToNexthops(processor, object, nexthops);
+
+ // there should be 2 object forwards and each IoOps should have gotten 1 send
+ uint32_t countObjectsForwarded = processor->stats.countObjectsForwarded;
+ uint32_t sendCount_42 = data_42->sendCount;
+ uint32_t sendCount_43 = data_43->sendCount;
+
+ // cleanup
+ metisNumberSet_Release(&nexthops);
+ metisMessage_Release(&object);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+ mockIoOperationsData_Destroy(&ops_42);
+ mockIoOperationsData_Destroy(&ops_43);
+
+ // validate
+ assertTrue(countObjectsForwarded == 1, "Incorrect countObjectsForwarded, expecting %u, got %u", 1, countObjectsForwarded);
+ assertTrue(sendCount_42 == 0, "Incorrect sendCount_42, expecting %u, got %u", 0, sendCount_42);
+ assertTrue(sendCount_43 == 1, "Incorrect sendCount_43, expecting %u, got %u", 1, sendCount_43);
+}
+
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_ReceiveContentObjectV0_InPit)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+ MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 3, 4, logger);
+
+
+ // receive the interst to add it to PIT
+ metisMessageProcessor_ReceiveInterest(processor, interest);
+
+ // now test the function and measure results
+ // There is no actual connection "1" (the interest ingress port), so the forwarding
+ // will show up as a countDroppedConnectionNotFound.
+
+ uint32_t beforeCountDroppedConnectionNotFound = processor->stats.countDroppedConnectionNotFound;
+ metisMessageProcessor_ReceiveContentObject(processor, object);
+ uint32_t afterCountDroppedConnectionNotFound = processor->stats.countDroppedConnectionNotFound;
+
+ // cleanup
+ metisMessage_Release(&interest);
+ metisMessage_Release(&object);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ // validate
+ assertTrue(afterCountDroppedConnectionNotFound == beforeCountDroppedConnectionNotFound + 1,
+ "Incorrect afterCountDroppedConnectionNotFound, expected %u got %u",
+ beforeCountDroppedConnectionNotFound + 1,
+ afterCountDroppedConnectionNotFound);
+}
+
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_ReceiveContentObjectV0_NotInPit)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+
+
+ // now test the function and measure results
+ uint32_t beforeCountDroppedNoReversePath = processor->stats.countDroppedNoReversePath;
+ metisMessageProcessor_ReceiveContentObject(processor, object);
+ uint32_t afterCountDroppedNoReversePath = processor->stats.countDroppedNoReversePath;
+
+ // cleanup
+ metisMessage_Release(&object);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ // validate
+ assertTrue(afterCountDroppedNoReversePath == beforeCountDroppedNoReversePath + 1,
+ "Incorrect afterCountDroppedNoReversePath, expected %u got %u",
+ beforeCountDroppedNoReversePath + 1,
+ afterCountDroppedNoReversePath);
+}
+
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_ReceiveContentObjectV1_InPit)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+// metisLogger_SetLogLevel(logger, MetisLoggerFacility_Message, PARCLogLevel_Debug);
+// metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV1_Interest_NameA_Crc32c, sizeof(metisTestDataV1_Interest_NameA_Crc32c), 1, 2, logger);
+ MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV1_ContentObject_NameA_Crc32c, sizeof(metisTestDataV1_ContentObject_NameA_Crc32c), 3, 4, logger);
+
+ // receive the interst to add it to PIT
+ metisMessageProcessor_ReceiveInterest(processor, interest);
+
+ // now test the function and measure results
+ // There is no actual connection "1" (the interest ingress port), so the forwarding
+ // will show up as a countDroppedConnectionNotFound.
+
+ uint32_t beforeCountDroppedConnectionNotFound = processor->stats.countDroppedConnectionNotFound;
+ metisMessageProcessor_ReceiveContentObject(processor, object);
+ uint32_t afterCountDroppedConnectionNotFound = processor->stats.countDroppedConnectionNotFound;
+
+ // cleanup
+ metisMessage_Release(&interest);
+ metisMessage_Release(&object);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ // validate
+ assertTrue(afterCountDroppedConnectionNotFound == beforeCountDroppedConnectionNotFound + 1,
+ "Incorrect afterCountDroppedConnectionNotFound, expected %u got %u",
+ beforeCountDroppedConnectionNotFound + 1,
+ afterCountDroppedConnectionNotFound);
+}
+
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_ReceiveContentObjectV1_NotInPit)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+
+
+ // now test the function and measure results
+ uint32_t beforeCountDroppedNoReversePath = processor->stats.countDroppedNoReversePath;
+ metisMessageProcessor_ReceiveContentObject(processor, object);
+ uint32_t afterCountDroppedNoReversePath = processor->stats.countDroppedNoReversePath;
+
+ // cleanup
+ metisMessage_Release(&object);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ // validate
+ assertTrue(afterCountDroppedNoReversePath == beforeCountDroppedNoReversePath + 1,
+ "Incorrect afterCountDroppedNoReversePath, expected %u got %u",
+ beforeCountDroppedNoReversePath + 1,
+ afterCountDroppedNoReversePath);
+}
+
+
+/**
+ * There's already a detailed test for this, we just check the stats counter
+ * to make sure the right logic flow is executed. The second interest must come from
+ * a different reverse path to be aggregated.
+ */
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_ReceiveInterest_InPit)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *interest1 = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+ MetisMessage *interest2 = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 2, 2, logger);
+
+
+ // add it once
+ metisMessageProcessor_AggregateInterestInPit(processor, interest1);
+
+ // now test the function and measure results
+ uint32_t beforeCountInterestsAggregated = processor->stats.countInterestsAggregated;
+ metisMessageProcessor_ReceiveInterest(processor, interest2);
+ uint32_t afterCountInterestsAggregated = processor->stats.countInterestsAggregated;
+
+ // cleanup
+ metisMessage_Release(&interest1);
+ metisMessage_Release(&interest2);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ // validate
+ assertTrue(afterCountInterestsAggregated == beforeCountInterestsAggregated + 1,
+ "Incorrect afterCountInterestsAggregated, expected %u got %u",
+ beforeCountInterestsAggregated + 1,
+ afterCountInterestsAggregated);
+}
+
+/**
+ * There's already a detailed test for this, we just check the stats counter
+ * to make sure the right logic flow is executed
+ */
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_ReceiveInterest_NotInPit)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+
+
+ // now test the function and measure results
+ uint32_t beforeCountInterestsAggregated = processor->stats.countInterestsAggregated;
+ metisMessageProcessor_ReceiveInterest(processor, interest);
+ uint32_t afterCountInterestsAggregated = processor->stats.countInterestsAggregated;
+
+ // also check its in the PIT now
+ MetisPitEntry *pitEntry = metisPIT_GetPitEntry(processor->pit, interest);
+ bool foundInPit = false;
+ if (pitEntry) {
+ foundInPit = true;
+ }
+
+ // cleanup
+ metisPitEntry_Release(&pitEntry);
+ metisMessage_Release(&interest);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ // validate
+ assertTrue(afterCountInterestsAggregated == beforeCountInterestsAggregated,
+ "Incorrect afterCountInterestsAggregated, expected %u got %u",
+ beforeCountInterestsAggregated,
+ afterCountInterestsAggregated);
+
+ assertTrue(foundInPit, "Did not find interest in the PIT");
+}
+
+/**
+ * There's already a detailed test for this, we just check the stats counter
+ * to make sure the right logic flow is executed
+ */
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_ReceiveInterest_InCache)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+ MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 4, 5, logger);
+
+ // add it to the cache
+ metisContentStoreInterface_PutContent(processor->contentStore, object, 0l);
+
+ // now test the function and measure results
+ uint32_t beforeCountObjectsForwardedFromStore = processor->stats.countInterestsSatisfiedFromStore;
+ metisMessageProcessor_ReceiveInterest(processor, interest);
+ uint32_t afterCountObjectsForwardedFromStore = processor->stats.countInterestsSatisfiedFromStore;
+
+ // cleanup
+ metisMessage_Release(&object);
+ metisMessage_Release(&interest);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ // validate
+ assertTrue(afterCountObjectsForwardedFromStore == beforeCountObjectsForwardedFromStore + 1,
+ "Incorrect afterCountObjectsForwardedFromStore, expected %u got %u",
+ beforeCountObjectsForwardedFromStore + 1,
+ afterCountObjectsForwardedFromStore);
+}
+
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_ReceiveInterest_InCacheButExpired)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+
+ uint64_t currentTimeInTicks = metisForwarder_GetTicks(processor->metis);
+
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName,
+ sizeof(metisTestDataV0_InterestWithName), 1, currentTimeInTicks, logger);
+ MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject,
+ sizeof(metisTestDataV0_EncodedObject), 4, currentTimeInTicks, logger);
+
+ // add it to the cache. It's already expired, so should not be forwarded.
+ metisMessage_SetExpiryTimeTicks(object, currentTimeInTicks + 1000ULL);
+ metisContentStoreInterface_PutContent(processor->contentStore, object, currentTimeInTicks);
+
+ // Crank metis clock.
+ metis->clockOffset = metisForwarder_NanosToTicks(5000000000ULL); // Add 5 seconds. Content is now expired.
+
+ // now test the function and measure results.
+ uint32_t beforeCountObjectsForwardedFromStore = processor->stats.countInterestsSatisfiedFromStore;
+ metisMessageProcessor_ReceiveInterest(processor, interest);
+ uint32_t afterCountObjectsForwardedFromStore = processor->stats.countInterestsSatisfiedFromStore;
+
+ // cleanup
+ metisMessage_Release(&object);
+ metisMessage_Release(&interest);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ // validate. Nothing should have been forwarded.
+ assertTrue(afterCountObjectsForwardedFromStore == beforeCountObjectsForwardedFromStore,
+ "Incorrect afterCountObjectsForwardedFromStore, expected %u got %u",
+ beforeCountObjectsForwardedFromStore,
+ afterCountObjectsForwardedFromStore);
+}
+
+
+/**
+ * There's already a detailed test for this, we just check the stats counter
+ * to make sure the right logic flow is executed
+ */
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_ReceiveInterest_NotInCache)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+ MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 4, 5, logger);
+
+
+ // now test the function and measure results
+ uint32_t beforeCountObjectsForwardedFromStore = processor->stats.countInterestsSatisfiedFromStore;
+ metisMessageProcessor_ReceiveInterest(processor, interest);
+ uint32_t afterCountObjectsForwardedFromStore = processor->stats.countInterestsSatisfiedFromStore;
+
+ // cleanup
+ metisMessage_Release(&object);
+ metisMessage_Release(&interest);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ // validate
+ assertTrue(afterCountObjectsForwardedFromStore == beforeCountObjectsForwardedFromStore,
+ "Incorrect afterCountObjectsForwardedFromStore, expected %u got %u",
+ beforeCountObjectsForwardedFromStore,
+ afterCountObjectsForwardedFromStore);
+}
+
+/**
+ * There's already a detailed test for this, we just check the stats counter
+ * to make sure the right logic flow is executed
+ */
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_ReceiveInterest_InFib)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+
+
+ // ----- Add Route
+ CCNxName *ccnxNameToAdd =
+ ccnxName_CreateFromCString("lci:/2=hello/0xF000=ouch");
+ unsigned interfaceIndex_1 = 22;
+ CPIAddress *nexthop = NULL;
+ struct timeval *lifetime = NULL;
+ unsigned cost = 12;
+ CPIRouteEntry *routeAdd = cpiRouteEntry_Create(ccnxNameToAdd,
+ interfaceIndex_1,
+ nexthop,
+ cpiNameRouteProtocolType_STATIC,
+ cpiNameRouteType_LONGEST_MATCH,
+ lifetime,
+ cost);
+ metisFIB_AddOrUpdate(processor->fib, routeAdd, "random");
+
+ // now test the function and measure results
+ // We will see it in countDroppedConnectionNotFound, because we didnt mock up the interface 22 connection
+ uint32_t beforeCountDroppedConnectionNotFound = processor->stats.countDroppedConnectionNotFound;
+ metisMessageProcessor_ReceiveInterest(processor, interest);
+ uint32_t afterCountDroppedConnectionNotFound = processor->stats.countDroppedConnectionNotFound;
+
+ // cleanup
+ cpiRouteEntry_Destroy(&routeAdd);
+ metisMessage_Release(&interest);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ // validate
+ assertTrue(afterCountDroppedConnectionNotFound == beforeCountDroppedConnectionNotFound + 1,
+ "Incorrect afterCountDroppedConnectionNotFound, expected %u got %u",
+ beforeCountDroppedConnectionNotFound + 1,
+ afterCountDroppedConnectionNotFound);
+}
+
+/**
+ * There's already a detailed test for this, we just check the stats counter
+ * to make sure the right logic flow is executed
+ */
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_ReceiveInterest_NotInFib)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_ReceiveInterest_NoHopLimit)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest_no_hoplimit,
+ sizeof(metisTestDataV0_EncodedInterest_no_hoplimit), 1, 2, logger);
+
+
+ metisMessageProcessor_ReceiveInterest(processor, interest);
+ uint32_t dropCount = processor->stats.countDroppedNoHopLimit;
+
+ // cleanup
+ metisMessage_Release(&interest);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ // validate
+ assertTrue(dropCount == 1,
+ "Incorrect countDroppedNoHopLimit, expected %u got %u",
+ 1,
+ dropCount);
+}
+
+/**
+ * Add an interest to the PIT when it does not exist. Should not increment the "stats.countInterestsAggregated' counter
+ * and should return FALSE, meaning not aggregated
+ */
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_AggregateInterestInPit_NewEntry)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+
+
+ uint32_t beforeCountInterestsAggregated = processor->stats.countInterestsAggregated;
+ bool aggregated = metisMessageProcessor_AggregateInterestInPit(processor, interest);
+ uint32_t afterCountInterestsAggregated = processor->stats.countInterestsAggregated;
+
+ metisMessage_Release(&interest);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ assertTrue(afterCountInterestsAggregated == beforeCountInterestsAggregated,
+ "Incorrect afterCountInterestsAggregated, expected %u got %u",
+ beforeCountInterestsAggregated,
+ afterCountInterestsAggregated);
+ assertFalse(aggregated, "Interest aggregated when no interests in table!");
+}
+
+/**
+ * Add an interest to the PIT, then add it again. SHould increment the "stats.countInterestsAggregated" counter and
+ * should return TRUE meaning, it was aggregated. The second interest needs to come from a different interface.
+ */
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_AggregateInterestInPit_ExistingEntry)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *interest1 = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+ MetisMessage *interest2 = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 2, 2, logger);
+
+
+ // add it once
+ metisMessageProcessor_AggregateInterestInPit(processor, interest1);
+
+ // now add it again
+ uint32_t beforeCountInterestsAggregated = processor->stats.countInterestsAggregated;
+ bool aggregated = metisMessageProcessor_AggregateInterestInPit(processor, interest2);
+ uint32_t afterCountInterestsAggregated = processor->stats.countInterestsAggregated;
+
+ metisMessage_Release(&interest1);
+ metisMessage_Release(&interest2);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ assertTrue(afterCountInterestsAggregated == beforeCountInterestsAggregated + 1,
+ "Incorrect afterCountInterestsAggregated, expected %u got %u",
+ beforeCountInterestsAggregated + 1,
+ afterCountInterestsAggregated);
+ assertTrue(aggregated, "Interest not aggregated with self!");
+}
+
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_SatisfyFromContentStore_WithKeyIdNotVerified_WithoutVerification)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+
+ MetisMessage *contentObjectWithKeyId =
+ metisMessage_CreateFromArray(metisTestDataV1_ContentObject_NameA_KeyId1_RsaSha256,
+ sizeof(metisTestDataV1_ContentObject_NameA_KeyId1_RsaSha256), 4, 5, logger);
+
+ // add it to the cache
+ metisContentStoreInterface_PutContent(processor->contentStore, contentObjectWithKeyId, 1l);
+
+ // Now create an Interest with the same name and a KeyId.
+ MetisMessage *interestWithKeyIdRestriction =
+ metisMessage_CreateFromArray(metisTestDataV1_Interest_NameAAndKeyId,
+ sizeof(metisTestDataV1_Interest_NameAAndKeyId), 4, 5, logger);
+
+ // Now test the code. We should NOT match it, due to the content store not currently verifying keyIds.
+ bool success = _satisfyFromContentStore(processor, interestWithKeyIdRestriction);
+ unsigned countObjectsForwardedFromStore = processor->stats.countInterestsSatisfiedFromStore;
+
+ // cleanup
+ metisMessage_Release(&interestWithKeyIdRestriction);
+ metisMessage_Release(&contentObjectWithKeyId);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ // validate
+ assertFalse(success, "Expected Interest to not be satisfied from cache!");
+ assertTrue(countObjectsForwardedFromStore == 0,
+ "Incorrect countObjectsForwardedFromStore, expected %u got %u",
+ 0,
+ countObjectsForwardedFromStore);
+}
+
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_SatisfyFromContentStore_IsInStore)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+ MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 4, 5, logger);
+
+ // add it to the cache
+ metisContentStoreInterface_PutContent(processor->contentStore, object, 1l);
+
+ // now test the code
+ bool success = _satisfyFromContentStore(processor, interest);
+ unsigned countObjectsForwardedFromStore = processor->stats.countInterestsSatisfiedFromStore;
+
+ // cleanup
+ metisMessage_Release(&interest);
+ metisMessage_Release(&object);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ // validate
+ assertTrue(success, "Interest not satisfied from cache!");
+ assertTrue(countObjectsForwardedFromStore == 1,
+ "Incorrect countObjectsForwardedFromStore, expected %u got %u",
+ 1,
+ countObjectsForwardedFromStore);
+}
+
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_SatisfyFromContentStore_IsNotInStore)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+ MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 4, 5, logger);
+
+
+ // don't add it to the cache
+
+ // now test the code
+ bool success = _satisfyFromContentStore(processor, interest);
+ unsigned countObjectsForwardedFromStore = processor->stats.countInterestsSatisfiedFromStore;
+
+ // cleanup
+ metisMessage_Release(&interest);
+ metisMessage_Release(&object);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ // validate
+ assertFalse(success, "Interest satisfied from cache, when we didn't put it there!");
+ assertTrue(countObjectsForwardedFromStore == 0,
+ "Incorrect countObjectsForwardedFromStore, expected %u got %u",
+ 0,
+ countObjectsForwardedFromStore);
+}
+
+/**
+ * Add fib entry /hello/ouch and ask for /party/ouch
+ */
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_ForwardViaFib_IsNotInFib)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+
+ // ----- Add
+ CCNxName *ccnxNameToAdd =
+ ccnxName_CreateFromCString("lci:/2=hello/0xF000=ouch");
+
+ unsigned interfaceIndex_1 = 22;
+ CPIAddress *nexthop = NULL;
+ struct timeval *lifetime = NULL;
+ unsigned cost = 12;
+ CPIRouteEntry *routeAdd = cpiRouteEntry_Create(ccnxNameToAdd, interfaceIndex_1, nexthop,
+ cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, lifetime, cost);
+ metisFIB_AddOrUpdate(processor->fib, routeAdd,"random" );
+
+ // ----- Measure
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithOtherName, sizeof(metisTestDataV0_InterestWithOtherName), 1, 2, logger);
+
+
+ bool success = metisMessageProcessor_ForwardViaFib(processor, interest);
+
+ // ----- Cleanup
+ cpiRouteEntry_Destroy(&routeAdd);
+ metisMessage_Release(&interest);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ // ----- Validate
+ assertFalse(success, "Returned true even though no route");
+}
+
+/**
+ * Forward to an existing FIB entry. The PIT entry has an empty egress set.
+ */
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_ForwardViaFib_IsInFib_EmptyEgressSet)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+
+ // ----- Add Route
+ CCNxName *ccnxNameToAdd =
+ ccnxName_CreateFromCString("lci:/2=hello/0xF000=ouch");
+ unsigned interfaceIndex_1 = 22;
+ CPIAddress *nexthop = NULL;
+ struct timeval *lifetime = NULL;
+ unsigned cost = 12;
+ CPIRouteEntry *routeAdd = cpiRouteEntry_Create(ccnxNameToAdd, interfaceIndex_1, nexthop,
+ cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, lifetime, cost);
+ metisFIB_AddOrUpdate(processor->fib, routeAdd, "random");
+
+ // ----- Add PIT entry
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+
+
+ metisPIT_ReceiveInterest(processor->pit, interest);
+
+ // ----- Measure
+ bool success = metisMessageProcessor_ForwardViaFib(processor, interest);
+
+ // ----- Cleanup
+ cpiRouteEntry_Destroy(&routeAdd);
+ metisMessage_Release(&interest);
+ metisMessageProcessor_Destroy(&processor);
+ metisForwarder_Destroy(&metis);
+
+ // ----- Validate
+ assertTrue(success, "Returned false with existing PIT entry");
+}
+
+MetisConnection *
+setupMockConnection(MetisForwarder *metis, bool isLocal)
+{
+ MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 99, false, false, isLocal);
+ MetisConnection *conn = metisConnection_Create(ops);
+ metisConnectionTable_Add(metisForwarder_GetConnectionTable(metis), conn);
+
+ return conn;
+}
+
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_CheckAndDecrementHopLimitOnIngress_NoHopLimit)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+
+ bool isLocal = false;
+ MetisConnection *conn = setupMockConnection(metis, isLocal);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *interest =
+ metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest_no_hoplimit,
+ sizeof(metisTestDataV0_EncodedInterest_no_hoplimit), metisConnection_GetConnectionId(conn), 2, logger);
+
+
+ bool success = metisMessageProcessor_CheckAndDecrementHopLimitOnIngress(processor, interest);
+ assertFalse(success, "Should have failed for an interest without hoplimit");
+
+ assertTrue(processor->stats.countDroppedNoHopLimit == 1,
+ "Wrong countDroppedNoHopLimit, got %u expected %u", processor->stats.countDroppedNoHopLimit, 1);
+ assertTrue(processor->stats.countDroppedZeroHopLimitFromRemote == 0,
+ "Wrong countDroppedZeroHopLimitFromRemote, got %u expected %u", processor->stats.countDroppedZeroHopLimitFromRemote, 0);
+
+ metisMessage_Release(&interest);
+ metisMessageProcessor_Destroy(&processor);
+ MetisIoOperations *ioOps = metisConnection_GetIoOperations(conn);
+ metisForwarder_Destroy(&metis);
+ mockIoOperationsData_Destroy(&ioOps);
+}
+
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_CheckAndDecrementHopLimitOnIngress_Local_Zero)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+
+ bool isLocal = true;
+ MetisConnection *conn = setupMockConnection(metis, isLocal);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *interest =
+ metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest_zero_hoplimit,
+ sizeof(metisTestDataV0_EncodedInterest_zero_hoplimit), metisConnection_GetConnectionId(conn), 2, logger);
+
+
+ bool success = metisMessageProcessor_CheckAndDecrementHopLimitOnIngress(processor, interest);
+ assertTrue(success, "Local with 0 hoplimit should have been ok");
+ assertTrue(processor->stats.countDroppedNoHopLimit == 0,
+ "Wrong countDroppedNoHopLimit, got %u expected %u", processor->stats.countDroppedNoHopLimit, 0);
+ assertTrue(processor->stats.countDroppedZeroHopLimitFromRemote == 0,
+ "Wrong countDroppedZeroHopLimitFromRemote, got %u expected %u", processor->stats.countDroppedZeroHopLimitFromRemote, 0);
+
+ metisMessage_Release(&interest);
+ metisMessageProcessor_Destroy(&processor);
+ MetisIoOperations *ioOps = metisConnection_GetIoOperations(conn);
+ metisForwarder_Destroy(&metis);
+ mockIoOperationsData_Destroy(&ioOps);
+}
+
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_CheckAndDecrementHopLimitOnIngress_Local_NonZero)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+
+ bool isLocal = true;
+ MetisConnection *conn = setupMockConnection(metis, isLocal);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *interest =
+ metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest,
+ sizeof(metisTestDataV0_EncodedInterest), metisConnection_GetConnectionId(conn), 2, logger);
+
+
+ bool success = metisMessageProcessor_CheckAndDecrementHopLimitOnIngress(processor, interest);
+ assertTrue(success, "Local with non-0 hoplimit should have been ok");
+ assertTrue(processor->stats.countDroppedNoHopLimit == 0,
+ "Wrong countDroppedNoHopLimit, got %u expected %u", processor->stats.countDroppedNoHopLimit, 0);
+ assertTrue(processor->stats.countDroppedZeroHopLimitFromRemote == 0,
+ "Wrong countDroppedZeroHopLimitFromRemote, got %u expected %u", processor->stats.countDroppedZeroHopLimitFromRemote, 0);
+
+ metisMessage_Release(&interest);
+ metisMessageProcessor_Destroy(&processor);
+ MetisIoOperations *ioOps = metisConnection_GetIoOperations(conn);
+ metisForwarder_Destroy(&metis);
+ mockIoOperationsData_Destroy(&ioOps);
+}
+
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_CheckAndDecrementHopLimitOnIngress_Remote_Zero)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+
+ bool isLocal = false;
+ MetisConnection *conn = setupMockConnection(metis, isLocal);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *interest =
+ metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest_zero_hoplimit,
+ sizeof(metisTestDataV0_EncodedInterest_zero_hoplimit), metisConnection_GetConnectionId(conn), 2, logger);
+
+
+ bool success = metisMessageProcessor_CheckAndDecrementHopLimitOnIngress(processor, interest);
+ assertFalse(success, "Remote with 0 hoplimit should have been failure");
+ assertTrue(processor->stats.countDroppedNoHopLimit == 0,
+ "Wrong countDroppedNoHopLimit, got %u expected %u", processor->stats.countDroppedNoHopLimit, 0);
+ assertTrue(processor->stats.countDroppedZeroHopLimitFromRemote == 1,
+ "Wrong countDroppedZeroHopLimitFromRemote, got %u expected %u", processor->stats.countDroppedZeroHopLimitFromRemote, 1);
+
+ metisMessage_Release(&interest);
+ metisMessageProcessor_Destroy(&processor);
+ MetisIoOperations *ioOps = metisConnection_GetIoOperations(conn);
+ metisForwarder_Destroy(&metis);
+ mockIoOperationsData_Destroy(&ioOps);
+}
+
+LONGBOW_TEST_CASE(Local, metisMessageProcessor_CheckAndDecrementHopLimitOnIngress_Remote_NonZero)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisMessageProcessor *processor = metisMessageProcessor_Create(metis);
+
+ bool isLocal = false;
+ MetisConnection *conn = setupMockConnection(metis, isLocal);
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ MetisMessage *interest =
+ metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest), metisConnection_GetConnectionId(conn), 2, logger);
+
+
+ bool success = metisMessageProcessor_CheckAndDecrementHopLimitOnIngress(processor, interest);
+ assertTrue(success, "Remote with non-0 hoplimit should have been ok");
+ assertTrue(processor->stats.countDroppedNoHopLimit == 0,
+ "Wrong countDroppedNoHopLimit, got %u expected %u", processor->stats.countDroppedNoHopLimit, 0);
+ assertTrue(processor->stats.countDroppedZeroHopLimitFromRemote == 0,
+ "Wrong countDroppedZeroHopLimitFromRemote, got %u expected %u", processor->stats.countDroppedZeroHopLimitFromRemote, 0);
+
+ metisMessage_Release(&interest);
+ metisMessageProcessor_Destroy(&processor);
+ MetisIoOperations *ioOps = metisConnection_GetIoOperations(conn);
+ metisForwarder_Destroy(&metis);
+ mockIoOperationsData_Destroy(&ioOps);
+}
+
+
+// ========================================================
+
+int
+main(int argc, char *argv[])
+{
+ LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_MessageProcessor);
+ int exitStatus = longBowMain(argc, argv, testRunner, NULL);
+ longBowTestRunner_Destroy(&testRunner);
+ exit(exitStatus);
+}
diff --git a/metis/ccnx/forwarder/metis/processor/test/test_metis_PIT.c b/metis/ccnx/forwarder/metis/processor/test/test_metis_PIT.c
new file mode 100644
index 00000000..90403402
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/test/test_metis_PIT.c
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/*
+ * These tests were written before MetisMatchRulesTable was broken out of the PIT.
+ * So, many of the tests "cheat" by looking directly in a constiuent table in MetisMatchingRulesTable.
+ * They should be re-written to use the MetisMatchingRulesTable API.
+ */
+
+// Include the file(s) containing the functions to be tested.
+// This permits internal static functions to be visible to this Test Framework.
+#include "../metis_PIT.c"
+
+
+
+#include <LongBow/unit-test.h>
+#include <parc/algol/parc_SafeMemory.h>
+#include <parc/logging/parc_LogReporterTextStdout.h>
+
+#include <parc/logging/parc_LogReporterTextStdout.h>
+
+// ===============================================================================================
+// Mock PIT
+// These functions just count calls. The Destroy interface does not actually release memeory, you
+// need to call _metisPIT_Release() yourself -- note that this is a static function with leading "_".
+
+typedef struct mock_pit {
+ unsigned countRelease;
+ unsigned countReceiveInterest;
+ unsigned countSatisfyInterest;
+ unsigned countRemoveInterest;
+ unsigned countGetPitEntry;
+} _MockPIT;
+
+static void
+_mockPITInterface_Release(MetisPIT **pitPtr)
+{
+ _MockPIT *mock = metisPIT_Closure(*pitPtr);
+ mock->countRelease++;
+ *pitPtr = NULL;
+}
+
+static MetisPITVerdict
+_mockPITInterface_ReceiveInterest(MetisPIT *pit, MetisMessage *interestMessage)
+{
+ _MockPIT *mock = metisPIT_Closure(pit);
+ mock->countReceiveInterest++;
+ return MetisPITVerdict_Aggregate;
+}
+
+static MetisNumberSet *
+_mockPITInterface_SatisfyInterest(MetisPIT *pit, const MetisMessage *objectMessage)
+{
+ _MockPIT *mock = metisPIT_Closure(pit);
+ mock->countSatisfyInterest++;
+ return NULL;
+}
+
+static void
+_mockPITInterface_RemoveInterest(MetisPIT *pit, const MetisMessage *interestMessage)
+{
+ _MockPIT *mock = metisPIT_Closure(pit);
+ mock->countRemoveInterest++;
+}
+
+static MetisPitEntry *
+_mockPITInterface_GetPitEntry(const MetisPIT *pit, const MetisMessage *interestMessage)
+{
+ _MockPIT *mock = metisPIT_Closure(pit);
+ mock->countGetPitEntry++;
+ return NULL;
+}
+
+static MetisPIT *
+_mockPIT_Create(void)
+{
+ size_t allocation = sizeof(MetisPIT) + sizeof(_MockPIT);
+ MetisPIT *pit = parcMemory_AllocateAndClear(allocation);
+
+ pit->getPitEntry = _mockPITInterface_GetPitEntry;
+ pit->receiveInterest = _mockPITInterface_ReceiveInterest;
+ pit->release = _mockPITInterface_Release;
+ pit->removeInterest = _mockPITInterface_RemoveInterest;
+ pit->satisfyInterest = _mockPITInterface_SatisfyInterest;
+
+ pit->closure = (uint8_t *) pit + sizeof(MetisPIT);
+ return pit;
+}
+
+static void
+_metisPIT_Release(MetisPIT **pitPtr)
+{
+ parcMemory_Deallocate(pitPtr);
+}
+
+
+// ===============================================================================================
+
+LONGBOW_TEST_RUNNER(metis_PIT)
+{
+ LONGBOW_RUN_TEST_FIXTURE(Global);
+}
+
+// The Test Runner calls this function once before any Test Fixtures are run.
+LONGBOW_TEST_RUNNER_SETUP(metis_PIT)
+{
+ parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory);
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// The Test Runner calls this function once after all the Test Fixtures are run.
+LONGBOW_TEST_RUNNER_TEARDOWN(metis_PIT)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// ===============================================================================================
+
+LONGBOW_TEST_FIXTURE(Global)
+{
+ LONGBOW_RUN_TEST_CASE(Global, metisPIT_Closure);
+ LONGBOW_RUN_TEST_CASE(Global, metisPIT_Release);
+ LONGBOW_RUN_TEST_CASE(Global, metisPIT_ReceiveInterest);
+ LONGBOW_RUN_TEST_CASE(Global, metisPIT_SatisfyInterest);
+ LONGBOW_RUN_TEST_CASE(Global, metisPIT_RemoveInterest);
+ LONGBOW_RUN_TEST_CASE(Global, metisPIT_GetPitEntry);
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(Global)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(Global)
+{
+ if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) {
+ return LONGBOW_STATUS_MEMORYLEAK;
+ }
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_CASE(Global, metisPIT_Closure)
+{
+ MetisPIT *pit = _mockPIT_Create();
+ _MockPIT *mock = metisPIT_Closure(pit);
+ assertTrue(mock == pit->closure, "Wrong pointer expected %p got %p", pit->closure, mock);
+ _metisPIT_Release(&pit);
+}
+
+LONGBOW_TEST_CASE(Global, metisPIT_Release)
+{
+ MetisPIT *pit = _mockPIT_Create();
+ MetisPIT *original = pit;
+ _MockPIT *mock = metisPIT_Closure(pit);
+ metisPIT_Release(&pit);
+
+ assertTrue(mock->countRelease == 1, "Wrong count expected 1 got %u", mock->countRelease);
+ _metisPIT_Release(&original);
+}
+
+LONGBOW_TEST_CASE(Global, metisPIT_ReceiveInterest)
+{
+ MetisPIT *pit = _mockPIT_Create();
+ _MockPIT *mock = metisPIT_Closure(pit);
+ metisPIT_ReceiveInterest(pit, NULL);
+
+ assertTrue(mock->countReceiveInterest == 1, "Wrong count expected 1 got %u", mock->countReceiveInterest);
+ _metisPIT_Release(&pit);
+}
+
+LONGBOW_TEST_CASE(Global, metisPIT_SatisfyInterest)
+{
+ MetisPIT *pit = _mockPIT_Create();
+ _MockPIT *mock = metisPIT_Closure(pit);
+ metisPIT_SatisfyInterest(pit, NULL);
+
+ assertTrue(mock->countSatisfyInterest == 1, "Wrong count expected 1 got %u", mock->countSatisfyInterest);
+ _metisPIT_Release(&pit);
+}
+
+LONGBOW_TEST_CASE(Global, metisPIT_RemoveInterest)
+{
+ MetisPIT *pit = _mockPIT_Create();
+ _MockPIT *mock = metisPIT_Closure(pit);
+ metisPIT_RemoveInterest(pit, NULL);
+
+ assertTrue(mock->countRemoveInterest == 1, "Wrong count expected 1 got %u", mock->countRemoveInterest);
+ _metisPIT_Release(&pit);
+}
+
+LONGBOW_TEST_CASE(Global, metisPIT_GetPitEntry)
+{
+ MetisPIT *pit = _mockPIT_Create();
+ _MockPIT *mock = metisPIT_Closure(pit);
+ metisPIT_GetPitEntry(pit, NULL);
+
+ assertTrue(mock->countGetPitEntry == 1, "Wrong count expected 1 got %u", mock->countGetPitEntry);
+ _metisPIT_Release(&pit);
+}
+
+// ===============================================================================================
+
+int
+main(int argc, char *argv[])
+{
+ LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_PIT);
+ int exitStatus = longBowMain(argc, argv, testRunner, NULL);
+ longBowTestRunner_Destroy(&testRunner);
+ exit(exitStatus);
+}
diff --git a/metis/ccnx/forwarder/metis/processor/test/test_metis_PitEntry.c b/metis/ccnx/forwarder/metis/processor/test/test_metis_PitEntry.c
new file mode 100644
index 00000000..2ad4b3da
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/test/test_metis_PitEntry.c
@@ -0,0 +1,294 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+// Include the file(s) containing the functions to be tested.
+// This permits internal static functions to be visible to this Test Framework.
+#include "../metis_PitEntry.c"
+#include <LongBow/unit-test.h>
+
+#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h>
+
+#define __STDC_FORMAT_MACROS
+#include <stdint.h>
+
+#include <parc/algol/parc_SafeMemory.h>
+#include <parc/logging/parc_LogReporterTextStdout.h>
+
+LONGBOW_TEST_RUNNER(metis_PitEntry)
+{
+ LONGBOW_RUN_TEST_FIXTURE(Global);
+}
+
+// The Test Runner calls this function once before any Test Fixtures are run.
+LONGBOW_TEST_RUNNER_SETUP(metis_PitEntry)
+{
+ parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory);
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// The Test Runner calls this function once after all the Test Fixtures are run.
+LONGBOW_TEST_RUNNER_TEARDOWN(metis_PitEntry)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// =====================================================================
+
+LONGBOW_TEST_FIXTURE(Global)
+{
+ LONGBOW_RUN_TEST_CASE(Global, metisPitEntry_AddEgressId);
+ LONGBOW_RUN_TEST_CASE(Global, metisPitEntry_AddIngressId);
+ LONGBOW_RUN_TEST_CASE(Global, metisPitEntry_Copy);
+ LONGBOW_RUN_TEST_CASE(Global, metisPitEntry_Create_Destroy);
+ LONGBOW_RUN_TEST_CASE(Global, metisPitEntry_GetExpiryTime);
+ LONGBOW_RUN_TEST_CASE(Global, metisPitEntry_SetExpiryTime);
+ LONGBOW_RUN_TEST_CASE(Global, metisPitEntry_GetIngressSet);
+ LONGBOW_RUN_TEST_CASE(Global, metisPitEntry_GetEgressSet);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisPitEntry_GetMessage);
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(Global)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(Global)
+{
+ if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) {
+ return LONGBOW_STATUS_MEMORYLEAK;
+ }
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_CASE(Global, metisPitEntry_AddEgressId)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ MetisPitEntry *entry = metisPitEntry_Create(metisMessage_Acquire(interest), 10000, 0);
+
+ metisPitEntry_AddEgressId(entry, 10);
+ metisPitEntry_AddEgressId(entry, 11);
+
+ size_t set_length = metisNumberSet_Length(entry->egressIdSet);
+ bool contains_10 = metisNumberSet_Contains(entry->egressIdSet, 10);
+ bool contains_11 = metisNumberSet_Contains(entry->egressIdSet, 11);
+
+ metisPitEntry_Release(&entry);
+ metisMessage_Release(&interest);
+
+ assertTrue(set_length == 2, "Wrong set length, expected %u got %zu", 2, set_length);
+ assertTrue(contains_10, "Set did not contain 10");
+ assertTrue(contains_11, "Set did not contain 11");
+}
+
+LONGBOW_TEST_CASE(Global, metisPitEntry_AddIngressId)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ MetisPitEntry *entry = metisPitEntry_Create(metisMessage_Acquire(interest), 10000, 0);
+
+ metisPitEntry_AddIngressId(entry, 10);
+ metisPitEntry_AddIngressId(entry, 11);
+
+ size_t set_length = metisNumberSet_Length(entry->ingressIdSet);
+
+ // #1 is from the original interest
+ bool contains_1 = metisNumberSet_Contains(entry->ingressIdSet, 1);
+ bool contains_10 = metisNumberSet_Contains(entry->ingressIdSet, 10);
+ bool contains_11 = metisNumberSet_Contains(entry->ingressIdSet, 11);
+
+ metisPitEntry_Release(&entry);
+ metisMessage_Release(&interest);
+
+ assertTrue(set_length == 3, "Wrong set length, expected %u got %zu", 2, set_length);
+ assertTrue(contains_1, "Set did not contain 1");
+ assertTrue(contains_10, "Set did not contain 10");
+ assertTrue(contains_11, "Set did not contain 11");
+}
+
+LONGBOW_TEST_CASE(Global, metisPitEntry_Copy)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ MetisPitEntry *entry = metisPitEntry_Create(metisMessage_Acquire(interest), 40000, 0);
+ unsigned refCountBeforeCopy = entry->refcount;
+
+ MetisPitEntry *copy = metisPitEntry_Acquire(entry);
+ unsigned refCountAfterCopy = entry->refcount;
+
+ metisPitEntry_Release(&entry);
+ unsigned refCountAfterDestroy = copy->refcount;
+ metisPitEntry_Release(&copy);
+ metisMessage_Release(&interest);
+
+ assertTrue(refCountAfterCopy == refCountBeforeCopy + 1, "Refcount after copy not 1 larger: expected %u got %u", refCountBeforeCopy + 1, refCountAfterCopy);
+ assertTrue(refCountAfterDestroy == refCountBeforeCopy, "Refcount after destroy not same as before copy: expected %u got %u", refCountBeforeCopy, refCountAfterDestroy);
+}
+
+LONGBOW_TEST_CASE(Global, metisPitEntry_Create_Destroy)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+ metisLogger_Release(&logger);
+ size_t baselineMemory = parcMemory_Outstanding();
+ MetisPitEntry *entry = metisPitEntry_Create(metisMessage_Acquire(interest), 40000, 0);
+ metisPitEntry_Release(&entry);
+ size_t testMemory = parcMemory_Outstanding();
+
+ metisMessage_Release(&interest);
+
+ assertTrue(testMemory == baselineMemory, "Memory imbalance on create/destroy: expected %zu got %zu", baselineMemory, testMemory);
+}
+
+LONGBOW_TEST_CASE(Global, metisPitEntry_GetExpiryTime)
+{
+ MetisTicks expiry = 40000;
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+ metisLogger_Release(&logger);
+ MetisPitEntry *entry = metisPitEntry_Create(metisMessage_Acquire(interest), expiry, 0);
+
+ MetisTicks test = metisPitEntry_GetExpiryTime(entry);
+
+ metisPitEntry_Release(&entry);
+ metisMessage_Release(&interest);
+
+ assertTrue(expiry == test, "Got wrong expiry time, expected %" PRIu64 ", got %" PRIu64, expiry, test);
+}
+
+LONGBOW_TEST_CASE(Global, metisPitEntry_SetExpiryTime)
+{
+ MetisTicks expiry = 40000;
+ MetisTicks expiry2 = 80000;
+
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+ metisLogger_Release(&logger);
+ MetisPitEntry *entry = metisPitEntry_Create(metisMessage_Acquire(interest), expiry, 0);
+
+ metisPitEntry_SetExpiryTime(entry, expiry2);
+
+ MetisTicks test = metisPitEntry_GetExpiryTime(entry);
+
+ metisPitEntry_Release(&entry);
+ metisMessage_Release(&interest);
+
+ assertTrue(expiry2 == test, "Got wrong expiry time, expected %" PRIu64 ", got %" PRIu64, expiry2, test);
+}
+
+LONGBOW_TEST_CASE(Global, metisPitEntry_GetIngressSet)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+ metisLogger_Release(&logger);
+ MetisPitEntry *entry = metisPitEntry_Create(metisMessage_Acquire(interest), 10000, 0);
+
+ unsigned truth_set[] = { 1, 2, 3, 4, 0 };
+
+ MetisNumberSet *truth = metisNumberSet_Create();
+
+ for (int i = 0; truth_set[i] != 0; i++) {
+ metisNumberSet_Add(truth, truth_set[i]);
+ metisPitEntry_AddIngressId(entry, truth_set[i]);
+ }
+
+ const MetisNumberSet *ingressSet = metisPitEntry_GetIngressSet(entry);
+ bool equals = metisNumberSet_Equals(truth, ingressSet);
+
+ metisPitEntry_Release(&entry);
+ metisMessage_Release(&interest);
+ metisNumberSet_Release(&truth);
+
+ assertTrue(equals, "Number set returned by metisPitEntry_GetIngressSet did not equal truth set");
+}
+
+LONGBOW_TEST_CASE(Global, metisPitEntry_GetEgressSet)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+ metisLogger_Release(&logger);
+ MetisPitEntry *entry = metisPitEntry_Create(metisMessage_Acquire(interest), 10000, 0);
+
+ unsigned truth_set[] = { 1, 2, 3, 4, 0 };
+
+ MetisNumberSet *truth = metisNumberSet_Create();
+
+ for (int i = 0; truth_set[i] != 0; i++) {
+ metisNumberSet_Add(truth, truth_set[i]);
+ metisPitEntry_AddEgressId(entry, truth_set[i]);
+ }
+
+ const MetisNumberSet *egressSet = metisPitEntry_GetEgressSet(entry);
+ bool equals = metisNumberSet_Equals(truth, egressSet);
+
+ metisPitEntry_Release(&entry);
+ metisMessage_Release(&interest);
+ metisNumberSet_Release(&truth);
+
+ assertTrue(equals, "Number set returned by metisPitEntry_GetIngressSet did not equal truth set");
+}
+
+LONGBOW_TEST_CASE(Global, metisPitEntry_GetMessage)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ MetisPitEntry *entry = metisPitEntry_Create(metisMessage_Acquire(interest), 10000, 0);
+ MetisMessage *copy = metisPitEntry_GetMessage(entry);
+
+ assertTrue(copy == interest, "Returned message not equal, expected %p got %p", (void *) interest, (void *) entry);
+
+ metisPitEntry_Release(&entry);
+ metisMessage_Release(&copy);
+ metisMessage_Release(&interest);
+}
+
+// =====================================================================
+
+int
+main(int argc, char *argv[])
+{
+ LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_PitEntry);
+ int exitStatus = longBowMain(argc, argv, testRunner, NULL);
+ longBowTestRunner_Destroy(&testRunner);
+ exit(exitStatus);
+}
diff --git a/metis/ccnx/forwarder/metis/processor/test/test_metis_StandardPIT.c b/metis/ccnx/forwarder/metis/processor/test/test_metis_StandardPIT.c
new file mode 100644
index 00000000..b04a3964
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/test/test_metis_StandardPIT.c
@@ -0,0 +1,498 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/*
+ * These tests were written before MetisMatchRulesTable was broken out of the PIT.
+ * So, many of the tests "cheat" by looking directly in a constiuent table in MetisMatchingRulesTable.
+ * They should be re-written to use the MetisMatchingRulesTable API.
+ */
+
+// Include this so we can step the clock forward without waiting real time
+#include "../../core/metis_Forwarder.c"
+
+// Include the file(s) containing the functions to be tested.
+// This permits internal static functions to be visible to this Test Framework.
+#include "../metis_StandardPIT.c"
+
+// so we can directly test the underlying tables
+#include "../metis_MatchingRulesTable.c"
+
+// test data set
+#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h>
+
+#include <LongBow/unit-test.h>
+#include <parc/algol/parc_SafeMemory.h>
+#include <parc/logging/parc_LogReporterTextStdout.h>
+
+#include <parc/logging/parc_LogReporterTextStdout.h>
+
+MetisForwarder *metis;
+
+LONGBOW_TEST_RUNNER(metis_PIT)
+{
+ // The following Test Fixtures will run their corresponding Test Cases.
+ // Test Fixtures are run in the order specified, but all tests should be idempotent.
+ // Never rely on the execution order of tests or share state between them.
+ LONGBOW_RUN_TEST_FIXTURE(Global);
+ LONGBOW_RUN_TEST_FIXTURE(Local);
+}
+
+// The Test Runner calls this function once before any Test Fixtures are run.
+LONGBOW_TEST_RUNNER_SETUP(metis_PIT)
+{
+ parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory);
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// The Test Runner calls this function once after all the Test Fixtures are run.
+LONGBOW_TEST_RUNNER_TEARDOWN(metis_PIT)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// ===============================================================================================
+
+LONGBOW_TEST_FIXTURE(Global)
+{
+ LONGBOW_RUN_TEST_CASE(Global, metisPit_Create_Destroy);
+ LONGBOW_RUN_TEST_CASE(Global, metisPit_ReceiveInterest_NewEntry);
+ LONGBOW_RUN_TEST_CASE(Global, metisPit_ReceiveInterest_ExistingExpired);
+ LONGBOW_RUN_TEST_CASE(Global, metisPit_ReceiveInterest_ExistingExpired_VerifyTable);
+ LONGBOW_RUN_TEST_CASE(Global, metisPit_ReceiveInterest_ExistingCurrentSameReversePath);
+ LONGBOW_RUN_TEST_CASE(Global, metisPit_ReceiveInterest_ExistingCurrentNewReversePath);
+ LONGBOW_RUN_TEST_CASE(Global, metisPit_SatisfyInterest);
+ LONGBOW_RUN_TEST_CASE(Global, metisPIT_RemoveInterest);
+ LONGBOW_RUN_TEST_CASE(Global, metisPIT_AddEgressConnectionId);
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(Global)
+{
+ metis = metisForwarder_Create(NULL);
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(Global)
+{
+ metisForwarder_Destroy(&metis);
+ if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) {
+ return LONGBOW_STATUS_MEMORYLEAK;
+ }
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_CASE(Global, metisPit_Create_Destroy)
+{
+ metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ size_t baselineMemory = parcMemory_Outstanding();
+
+ MetisPIT *pit = metisStandardPIT_Create(metis);
+ metisPIT_Release(&pit);
+
+ assertTrue(parcMemory_Outstanding() == baselineMemory, "Memory imbalance on create/destroy: %u", parcMemory_Outstanding());
+}
+
+/**
+ * Receive an interest that is not already in the table
+ */
+LONGBOW_TEST_CASE(Global, metisPit_ReceiveInterest_NewEntry)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisPIT *generic = metisStandardPIT_Create(metis);
+ MetisStandardPIT *pit = metisPIT_Closure(generic);
+
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 1, logger);
+ metisLogger_Release(&logger);
+
+ MetisPITVerdict verdict = metisPIT_ReceiveInterest(generic, interest);
+ size_t table_length = parcHashCodeTable_Length(pit->table->tableByName);
+
+ metisMessage_Release(&interest);
+
+ metisPIT_Release(&generic);
+ metisForwarder_Destroy(&metis);
+
+ assertTrue(table_length == 1, "tableByName wrong length, expected %u got %zu", 1, table_length);
+ assertTrue(verdict == MetisPITVerdict_Forward, "New entry did not return PIT_VERDICT_NEW_ENTRY, got %d", verdict);
+}
+
+/**
+ * Receive an interest that is in the table, but expired
+ */
+LONGBOW_TEST_CASE(Global, metisPit_ReceiveInterest_ExistingExpired)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisPIT *generic = metisStandardPIT_Create(metis);
+ MetisStandardPIT *pit = metisPIT_Closure(generic);
+
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ MetisMessage *interest_1 = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 1, logger);
+ MetisMessage *interest_2 = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 2, 2, logger);
+
+ // stuff in the first interest
+ _metisPIT_StoreInTable(pit, interest_1);
+
+ // we need to crank the clock forward over 4 seconds, so add 5 seconds to the clock
+ metis->clockOffset = metisForwarder_NanosToTicks(5000000000ULL);
+
+ // now do the operation we're testing. The previous entry should show as expired
+ MetisPITVerdict verdict_2 = metisPIT_ReceiveInterest(generic, interest_2);
+
+ size_t table_length = parcHashCodeTable_Length(pit->table->tableByName);
+
+ metisMessage_Release(&interest_1);
+ metisMessage_Release(&interest_2);
+
+ metisPIT_Release(&generic);
+ metisForwarder_Destroy(&metis);
+
+ assertTrue(table_length == 1, "tableByName wrong length, expected %u got %zu", 1, table_length);
+ assertTrue(verdict_2 == MetisPITVerdict_Forward, "New entry did not return PIT_VERDICT_NEW_ENTRY, got %d", verdict_2);
+}
+
+/**
+ * Receive an interest that is in the table, but expired.
+ * In this test, retrieve the interest from the table and make sure its the 2nd one.
+ */
+LONGBOW_TEST_CASE(Global, metisPit_ReceiveInterest_ExistingExpired_VerifyTable)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisPIT *generic = metisStandardPIT_Create(metis);
+ MetisStandardPIT *pit = metisPIT_Closure(generic);
+
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ MetisMessage *interest_1 = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 1, logger);
+ MetisMessage *interest_2 = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 2, 2, logger);
+
+ // stuff in the first interest
+ _metisPIT_StoreInTable(pit, interest_1);
+
+ // we need to crank the clock forward over 4 seconds, so add 5 seconds to the clock
+ metis->clockOffset = metisForwarder_NanosToTicks(5000000000ULL);
+
+ // now do the operation we're testing. The previous entry should show as expired
+ metisPIT_ReceiveInterest(generic, interest_2);
+
+ MetisPitEntry *entry = parcHashCodeTable_Get(pit->table->tableByName, interest_2);
+ const MetisNumberSet *ingressSet = metisPitEntry_GetIngressSet(entry);
+ bool containsTwo = metisNumberSet_Contains(ingressSet, 2);
+
+ metisMessage_Release(&interest_1);
+ metisMessage_Release(&interest_2);
+ metisPIT_Release(&generic);
+ metisForwarder_Destroy(&metis);
+
+ assertTrue(containsTwo, "Got wrong ingressId, does not contain %u", 2);
+}
+
+/**
+ * Receive an interest that is in the table, and not expired, and from an existing reverse path.
+ * This should cause the interest to be forwarded.
+ */
+LONGBOW_TEST_CASE(Global, metisPit_ReceiveInterest_ExistingCurrentSameReversePath)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisPIT *generic = metisStandardPIT_Create(metis);
+ MetisStandardPIT *pit = metisPIT_Closure(generic);
+
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ MetisMessage *interest_1 = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 1, logger);
+ MetisMessage *interest_2 = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+
+ // stuff in the first interest
+ _metisPIT_StoreInTable(pit, interest_1);
+
+ // now do the operation we're testing
+ MetisPITVerdict verdict_2 = metisPIT_ReceiveInterest(generic, interest_2);
+ size_t table_length = parcHashCodeTable_Length(pit->table->tableByName);
+
+ metisMessage_Release(&interest_1);
+ metisMessage_Release(&interest_2);
+ metisPIT_Release(&generic);
+ metisForwarder_Destroy(&metis);
+
+ assertTrue(table_length == 1, "tableByName wrong length, expected %u got %zu", 1, table_length);
+ assertTrue(verdict_2 == MetisPITVerdict_Forward, "New entry did not return MetisPITVerdict_Forward, got %d", verdict_2);
+}
+
+/*
+ * Receive an interest that exists in the PIT but from a new reverse path. this should be
+ * aggregated as an existing entry.
+ */
+LONGBOW_TEST_CASE(Global, metisPit_ReceiveInterest_ExistingCurrentNewReversePath)
+{
+ printf("THE TEST\n");
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisPIT *generic = metisStandardPIT_Create(metis);
+ MetisStandardPIT *pit = metisPIT_Closure(generic);
+
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ MetisMessage *interest_1 = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 1, logger);
+ MetisMessage *interest_2 = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 2, 2, logger);
+
+ // stuff in the first interest
+ _metisPIT_StoreInTable(pit, interest_1);
+
+ // now do the operation we're testing
+ MetisPITVerdict verdict_2 = metisPIT_ReceiveInterest(generic, interest_2);
+ size_t table_length = parcHashCodeTable_Length(pit->table->tableByName);
+
+ metisMessage_Release(&interest_1);
+ metisMessage_Release(&interest_2);
+ metisPIT_Release(&generic);
+ metisForwarder_Destroy(&metis);
+
+ assertTrue(table_length == 1, "tableByName wrong length, expected %u got %zu", 1, table_length);
+ assertTrue(verdict_2 == MetisPITVerdict_Aggregate, "New entry did not return MetisPITVerdict_Aggregate, got %d", verdict_2);
+}
+
+
+LONGBOW_TEST_CASE(Global, metisPit_SatisfyInterest)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisPIT *generic = metisStandardPIT_Create(metis);
+ MetisStandardPIT *pit = metisPIT_Closure(generic);
+
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName_objecthash, sizeof(metisTestDataV0_InterestWithName_objecthash), 1, 1, logger);
+ MetisMessage *contentObjectMessage = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 1, logger);
+
+ // we manually stuff it in to the proper table, then call the public API, which will
+ // figure out the right table then remove it.
+ size_t before = parcHashCodeTable_Length(pit->table->tableByName);
+ _metisPIT_StoreInTable(pit, interest);
+ MetisNumberSet *ingressSetUnion = metisPIT_SatisfyInterest(generic, contentObjectMessage);
+ metisPIT_RemoveInterest(generic, interest);
+ assertTrue(metisNumberSet_Length(ingressSetUnion) == 1, "Unexpected satisfy interest return set size (%zu)",
+ metisNumberSet_Length(ingressSetUnion));
+ size_t after = parcHashCodeTable_Length(pit->table->tableByName);
+
+ metisNumberSet_Release(&ingressSetUnion);
+ metisMessage_Release(&interest);
+ metisMessage_Release(&contentObjectMessage);
+ metisPIT_Release(&generic);
+ metisForwarder_Destroy(&metis);
+
+ assertTrue(after == before, "Did not remove interest in HashCodeTable: before %zu after %zu", before, after);
+}
+
+LONGBOW_TEST_CASE(Global, metisPIT_RemoveInterest)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisPIT *generic = metisStandardPIT_Create(metis);
+ MetisStandardPIT *pit = metisPIT_Closure(generic);
+
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 1, logger);
+
+ // we manually stuff it in to the proper table, then call the public API, which will
+ // figure out the right table then remove it.
+ size_t before = parcHashCodeTable_Length(pit->table->tableByName);
+ _metisPIT_StoreInTable(pit, interest);
+ metisPIT_RemoveInterest(generic, interest);
+ size_t after = parcHashCodeTable_Length(pit->table->tableByName);
+
+ metisMessage_Release(&interest);
+ metisPIT_Release(&generic);
+ metisForwarder_Destroy(&metis);
+
+ assertTrue(after == before, "Did not remove interest in HashCodeTable: before %zu after %zu", before, after);
+}
+
+LONGBOW_TEST_CASE(Global, metisPIT_AddEgressConnectionId)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisPIT *generic = metisStandardPIT_Create(metis);
+ MetisStandardPIT *pit = metisPIT_Closure(generic);
+
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 1, logger);
+
+ _metisPIT_StoreInTable(pit, interest);
+ _metisPIT_AddEgressConnectionId(generic, interest, 6);
+
+ MetisPitEntry *entry = metisPIT_GetPitEntry(generic, interest);
+ const MetisNumberSet *egressSet = metisPitEntry_GetEgressSet(entry);
+
+ size_t egress_length = metisNumberSet_Length(egressSet);
+ bool contains_6 = metisNumberSet_Contains(egressSet, 6);
+
+ metisPitEntry_Release(&entry);
+ metisMessage_Release(&interest);
+ metisPIT_Release(&generic);
+ metisForwarder_Destroy(&metis);
+
+ assertTrue(egress_length == 1, "Wrong egress_set length, expected %u got %zu", 1, egress_length);
+ assertTrue(contains_6, "Wrong egress_set match, did not contain %u", 6);
+}
+
+// ===============================================================================================
+
+LONGBOW_TEST_FIXTURE(Local)
+{
+ LONGBOW_RUN_TEST_CASE(Local, metisPit_PitEntryDestroyer);
+ LONGBOW_RUN_TEST_CASE(Local, _metisPIT_StoreInTable);
+ LONGBOW_RUN_TEST_CASE(Local, metisPit_StoreInTable_IngressSetCheck);
+ LONGBOW_RUN_TEST_CASE(Local, _metisPIT_CalculateLifetime_WithLifetime);
+ LONGBOW_RUN_TEST_CASE(Local, _metisPIT_CalculateLifetime_DefaultLifetime);
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(Local)
+{
+ metis = metisForwarder_Create(NULL);
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(Local)
+{
+ metisForwarder_Destroy(&metis);
+ if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) {
+ return LONGBOW_STATUS_MEMORYLEAK;
+ }
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_CASE(Local, metisPit_PitEntryDestroyer)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+LONGBOW_TEST_CASE(Local, _metisPIT_StoreInTable)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisPIT *generic = metisStandardPIT_Create(metis);
+ MetisStandardPIT *pit = metisPIT_Closure(generic);
+
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 1, logger);
+ metisLogger_Release(&logger);
+
+ size_t before = parcHashCodeTable_Length(pit->table->tableByName);
+ _metisPIT_StoreInTable(pit, interest);
+ size_t after = parcHashCodeTable_Length(pit->table->tableByName);
+
+ metisMessage_Release(&interest);
+ metisPIT_Release(&generic);
+ metisForwarder_Destroy(&metis);
+
+ assertTrue(after == before + 1, "Did not store interest in HashCodeTable: before %zu after %zu", before, after);
+}
+
+LONGBOW_TEST_CASE(Local, metisPit_StoreInTable_IngressSetCheck)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisPIT *generic = metisStandardPIT_Create(metis);
+ MetisStandardPIT *pit = metisPIT_Closure(generic);
+
+ unsigned connid = 99;
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), connid, 1, logger);
+ metisLogger_Release(&logger);
+
+ _metisPIT_StoreInTable(pit, interest);
+ MetisPitEntry *entry = parcHashCodeTable_Get(pit->table->tableByName, interest);
+ const MetisNumberSet *ingressSet = metisPitEntry_GetIngressSet(entry);
+ bool containsIngressId = metisNumberSet_Contains(ingressSet, connid);
+
+ metisMessage_Release(&interest);
+ metisPIT_Release(&generic);
+ metisForwarder_Destroy(&metis);
+
+ assertTrue(containsIngressId, "PIT entry did not have the ingress id in its ingress set");
+}
+
+/*
+ * Use an interest with a lifetime
+ */
+LONGBOW_TEST_CASE(Local, _metisPIT_CalculateLifetime_WithLifetime)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisPIT *generic = metisStandardPIT_Create(metis);
+ MetisStandardPIT *pit = metisPIT_Closure(generic);
+
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_SecondInterest, sizeof(metisTestDataV0_SecondInterest), 1, 1, logger);
+ metisLogger_Release(&logger);
+
+ MetisTicks now = metisForwarder_GetTicks(metis);
+ MetisTicks lifetime = _metisPIT_CalculateLifetime(pit, interest);
+
+ metisMessage_Release(&interest);
+ metisPIT_Release(&generic);
+ metisForwarder_Destroy(&metis);
+
+ uint64_t value = 32000;
+ assertTrue(lifetime >= value + now, "Wrong lifetime, should be at least %" PRIu64 ", got %" PRIu64, now + value, lifetime);
+}
+
+/*
+ * Use an interest without a Lifetime, should return with the default 4s lifetime
+ */
+LONGBOW_TEST_CASE(Local, _metisPIT_CalculateLifetime_DefaultLifetime)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisPIT *generic = metisStandardPIT_Create(metis);
+ MetisStandardPIT *pit = metisPIT_Closure(generic);
+
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug);
+ parcLogReporter_Release(&reporter);
+ MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 1, logger);
+ metisLogger_Release(&logger);
+
+ MetisTicks now = metisForwarder_GetTicks(metis);
+ MetisTicks lifetime = _metisPIT_CalculateLifetime(pit, interest);
+
+ metisMessage_Release(&interest);
+ metisPIT_Release(&generic);
+ metisForwarder_Destroy(&metis);
+
+ assertTrue(lifetime >= 4000 + now, "Wrong lifetime, should be at least %" PRIu64 ", got %" PRIu64, now + 4000, lifetime);
+}
+
+
+
+// ===============================================================================================
+
+int
+main(int argc, char *argv[])
+{
+ LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_PIT);
+ int exitStatus = longBowMain(argc, argv, testRunner, NULL);
+ longBowTestRunner_Destroy(&testRunner);
+ exit(exitStatus);
+}
diff --git a/metis/ccnx/forwarder/metis/processor/test/testrig_MockTap.h b/metis/ccnx/forwarder/metis/processor/test/testrig_MockTap.h
new file mode 100644
index 00000000..e6c7e967
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/processor/test/testrig_MockTap.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef Metis_testrig_MockTap_h
+#define Metis_testrig_MockTap_h
+
+// =========================================================================
+// Mock for tap testing
+// Allows the test to set the IsTapOnX return values.
+// Counts the number of calls to each TapOnX.
+// Records the last message pointer
+// The user sets and examines values in the static "testTap" variable and
+// passes "testTapTemplate" to the tap setup.
+
+static bool testTap_IsTapOnReceive(const MetisTap *tap);
+static bool testTap_IsTapOnSend(const MetisTap *tap);
+static bool testTap_IsTapOnDrop(const MetisTap *tap);
+static void testTap_TapOnReceive(MetisTap *tap, const MetisMessage *message);
+static void testTap_TapOnSend(MetisTap *tap, const MetisMessage *message);
+static void testTap_TapOnDrop(MetisTap *tap, const MetisMessage *message);
+
+// this test variable is zeroed in each FIXTURE_SETUP.
+// To test tap functionality, set the various callOnX flags, run your test,
+// then check the onXCounts to make sure they are right.
+struct testTap_s {
+ bool callOnReceive;
+ bool callOnSend;
+ bool callOnDrop;
+ unsigned onReceiveCount;
+ unsigned onSendCount;
+ unsigned onDropCount;
+
+ const MetisMessage *lastMessage;
+} testTap;
+
+// you should not need tochange this template
+MetisTap testTapTemplate = {
+ .context = &testTap,
+ .isTapOnReceive = &testTap_IsTapOnReceive,
+ .isTapOnSend = &testTap_IsTapOnSend,
+ .isTapOnDrop = &testTap_IsTapOnDrop,
+ .tapOnReceive = &testTap_TapOnReceive,
+ .tapOnSend = &testTap_TapOnSend,
+ .tapOnDrop = &testTap_TapOnDrop
+};
+
+static bool
+testTap_IsTapOnReceive(const MetisTap *tap)
+{
+ struct testTap_s *mytap = (struct testTap_s *) tap->context;
+ return mytap->callOnReceive;
+}
+
+static bool
+testTap_IsTapOnSend(const MetisTap *tap)
+{
+ struct testTap_s *mytap = (struct testTap_s *) tap->context;
+ return mytap->callOnSend;
+}
+
+static bool
+testTap_IsTapOnDrop(const MetisTap *tap)
+{
+ struct testTap_s *mytap = (struct testTap_s *) tap->context;
+ return mytap->callOnDrop;
+}
+
+static void
+testTap_TapOnReceive(MetisTap *tap, const MetisMessage *message)
+{
+ struct testTap_s *mytap = (struct testTap_s *) tap->context;
+ mytap->onReceiveCount++;
+ mytap->lastMessage = message;
+}
+
+static void
+testTap_TapOnSend(MetisTap *tap, const MetisMessage *message)
+{
+ struct testTap_s *mytap = (struct testTap_s *) tap->context;
+ mytap->onSendCount++;
+ mytap->lastMessage = message;
+}
+
+static void
+testTap_TapOnDrop(MetisTap *tap, const MetisMessage *message)
+{
+ struct testTap_s *mytap = (struct testTap_s *) tap->context;
+ mytap->onDropCount++;
+ mytap->lastMessage = message;
+}
+#endif // Metis_testrig_MockTap_h