diff options
Diffstat (limited to 'metis/ccnx/forwarder/metis/processor')
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(©); + * 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(©); + 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(©); + 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 |