diff options
Diffstat (limited to 'metis/ccnx')
330 files changed, 68952 insertions, 0 deletions
diff --git a/metis/ccnx/forwarder/metis/.gitignore b/metis/ccnx/forwarder/metis/.gitignore new file mode 100644 index 00000000..f6b1142c --- /dev/null +++ b/metis/ccnx/forwarder/metis/.gitignore @@ -0,0 +1,91 @@ +libmetis.a + +command-line/metis_control/metis_control +command-line/metis_daemon/metis_daemon + +config/test/test_metis_CommandLineInterface +config/test/test_metis_Configuration +config/test/test_metis_ConfigurationFile +config/test/test_metis_ConfigurationListeners +config/test/test_metis_CommandOps +config/test/test_metis_CommandParser +config/test/test_metis_SymbolicNameTable +config/test/test_metisControl_AddListener + +content_store/test/test_metis_LRUContentStore +content_store/test/test_metis_ContentStoreFactory +content_store/test/test_metis_ContentStoreInterface +content_store/test/test_metis_ContentStoreEntry +content_store/test/test_metis_LruList +content_store/test/test_metis_TimeOrderedList + +core/test/test_metis_Clock +core/test/test_metis_Connection +core/test/test_metis_ConnectionTable +core/test/test_metis_Dispatcher +core/test/test_metis_Forwarder +core/test/test_metis_Interface +core/test/test_metis_Logger +core/test/test_metis_Message +core/test/test_metis_NumberSet +core/test/test_metis_StreamBuffer +core/test/test_metis_System +core/test/test_metis_ConnectionList +core/test/test_metis_ThreadedForwarder + +io/test/test_metis_AddressPair +io/test/test_metis_EtherConnection +io/test/test_metis_EtherListener +io/test/test_metis_IPMulticastListener +io/test/test_metis_ListenerSet +io/test/test_metis_LocalListener +io/test/test_metis_StreamConnection +io/test/test_metis_TcpListener +io/test/test_metis_TcpTunnel +io/test/test_metis_UdpConnection +io/test/test_metis_UdpListener +io/test/test_metis_UdpTunnel +io/test/test_metis_HopByHopFragmenter + + +messenger/test/test_metis_Messenger +messenger/test/test_metis_MessengerRecipient +messenger/test/test_metis_Missive +messenger/test/test_metis_MissiveDeque + +processor/test/test_metis_FIB +processor/test/test_metis_FibEntry +processor/test/test_metis_FibEntryList +processor/test/test_metis_HashTableFunction +processor/test/test_metis_MatchingRulesTable +processor/test/test_metis_MessageProcessor +processor/test/test_metis_PIT +processor/test/test_metis_PitEntry + +strategies/test/test_strategy_All + +test/test_sys_Errors +test/test_sys_EtherEndToEnd +test/test_sys_TcpEndToEnd +test/test_sys_UdpEndToEnd +test/test_sys_TcpTunnel + + +config/test/test_metisControl_Add +config/test/test_metisControl_AddConnection +config/test/test_metisControl_AddRoute +config/test/test_metisControl_List +config/test/test_metisControl_ListConnections +config/test/test_metisControl_ListInterfaces +config/test/test_metisControl_ListRoutes +config/test/test_metisControl_Quit +config/test/test_metisControl_Remove +config/test/test_metisControl_RemoveConnection +config/test/test_metisControl_RemoveRoute +config/test/test_metisControl_Root +config/test/test_metisControl_Set +config/test/test_metisControl_Unset +config/test/test_metisControl_SetDebug +config/test/test_metisControl_UnsetDebug +config/test/test_metis_ControlState + diff --git a/metis/ccnx/forwarder/metis/CMakeLists.txt b/metis/ccnx/forwarder/metis/CMakeLists.txt new file mode 100644 index 00000000..3e73c456 --- /dev/null +++ b/metis/ccnx/forwarder/metis/CMakeLists.txt @@ -0,0 +1,360 @@ +# Define a few configuration variables that we want accessible in the software + +configure_file(config.h.in config.h @ONLY) + +set(METIS_BASE_HEADERS + metis_About.h + ) + +set(METIS_BASE_SOURCE + metis_About.c + ) + +source_group(core FILES ${METIS_BASE_HEADERS} ${METIS_BASE_SOURCE}) + +set(METIS_CONFIG_HEADERS + config/metis_CommandOps.h + config/metis_CommandParser.h + config/metis_Configuration.h + config/metis_CommandLineInterface.h + config/metis_CommandReturn.h + config/metis_SymbolicNameTable.h + config/metis_ControlState.h + config/metisControl_Root.h + config/metisControl_AddConnection.h + config/metisControl_Add.h + config/metis_ConfigurationFile.h + config/metis_ConfigurationListeners.h + config/metisControl_AddRoute.h + config/metisControl_AddListener.h + config/metisControl_ListConnections.h + config/metisControl_List.h + config/metisControl_ListInterfaces.h + config/metisControl_ListRoutes.h + config/metisControl_Quit.h + config/metisControl_Remove.h + config/metisControl_RemoveConnection.h + config/metisControl_RemoveRoute.h + config/metisControl_Set.h + config/metisControl_Unset.h + config/metisControl_SetDebug.h + config/metisControl_UnsetDebug.h + config/metis_WebInterface.h + config/metisControl_CacheServe.h + config/metisControl_CacheStore.h + config/metisControl_CacheClear.h + config/metisControl_Cache.h + config/metisControl_SetStrategy.h + config/metisControl_SetWldr.h + ) + +source_group(config FILES ${METIS_CONFIG_HEADERS}) + +set(METIS_CONFIG_SOURCE + config/metis_CommandLineInterface.c + config/metis_CommandOps.c + config/metis_CommandParser.c + config/metis_Configuration.c + config/metis_ConfigurationFile.c + config/metis_ConfigurationListeners.c + config/metis_ControlState.c + config/metis_SymbolicNameTable.c + config/metisControl_Add.c + config/metisControl_AddConnection.c + config/metisControl_AddRoute.c + config/metisControl_AddListener.c + config/metisControl_List.c + config/metisControl_ListConnections.c + config/metisControl_ListInterfaces.c + config/metisControl_ListRoutes.c + config/metisControl_Quit.c + config/metisControl_Remove.c + config/metisControl_RemoveConnection.c + config/metisControl_RemoveRoute.c + config/metisControl_Root.c + config/metisControl_Set.c + config/metisControl_SetDebug.c + config/metisControl_Unset.c + config/metisControl_UnsetDebug.c + config/metisControl_CacheServe.c + config/metisControl_CacheStore.c + config/metisControl_CacheClear.c + config/metisControl_Cache.c + config/metisControl_SetStrategy.c + config/metisControl_SetWldr.c + ) + +source_group(config FILES ${METIS_CONFIG_SOURCE}) + +set(METIS_CONTENT_STORE_HEADERS + content_store/metis_ContentStoreEntry.h + content_store/metis_ContentStoreInterface.h + content_store/metis_LRUContentStore.h + content_store/metis_TimeOrderedList.h + content_store/metis_LruList.h + ) + +source_group(content_store FILES ${METIS_CONTENT_STORE_HEADERS}) + +set(METIS_CONTENT_STORE_SOURCE + content_store/metis_ContentStoreInterface.c + content_store/metis_LRUContentStore.c + content_store/metis_LruList.c + content_store/metis_TimeOrderedList.c + content_store/metis_ContentStoreEntry.c + ) + +source_group(content_store FILES ${METIS_CONTENT_STORE_SOURCE}) + +set(METIS_CORE_HEADERS + core/metis_ConnectionManager.h + core/metis_Ticks.h + core/metis_ConnectionList.h + core/metis_ConnectionTable.h + core/metis_Connection.h + core/metis_Forwarder.h + core/metis_Logger.h + core/metis_Dispatcher.h + core/metis_Message.h + core/metis_MessagePacketType.h + core/metis_NumberSet.h + core/metis_StreamBuffer.h + core/metis_ThreadedForwarder.h + core/metis_System.h + core/metis_Wldr.h + ) + +source_group(core FILES ${METIS_CORE_HEADERS}) + +set(METIS_CORE_SOURCE + core/metis_Connection.c + core/metis_ConnectionList.c + core/metis_ConnectionManager.c + core/metis_ConnectionTable.c + core/metis_Dispatcher.c + core/metis_Forwarder.c + core/metis_Logger.c + core/metis_Message.c + core/metis_NumberSet.c + core/metis_StreamBuffer.c + core/metis_ThreadedForwarder.c + core/metis_Wldr.c + ) + +source_group(core FILES ${METIS_CORE_SOURCE}) + +set(METIS_IO_HEADERS + io/metis_HopByHopFragmenter.h + io/metis_EtherConnection.h + io/metis_TcpTunnel.h + io/metis_AddressPair.h + io/metis_IoOperations.h + io/metis_Listener.h + io/metis_ListenerSet.h + io/metis_TcpListener.h + io/metis_UdpTunnel.h + io/metis_UdpConnection.h + io/metis_UdpListener.h + io/metis_EtherListener.h + io/metis_GenericEther.h + io/metis_LocalListener.h + io/metis_StreamConnection.h + io/metis_IPMulticastListener.h + io/metis_Ethernet.h + ) + +source_group(io FILES ${METIS_IO_HEADERS}) + +set(METIS_IO_SOURCE + io/metis_AddressPair.c + io/metis_EtherConnection.c + io/metis_EtherListener.c + io/metis_HopByHopFragmenter.c + io/metis_IoOperations.c + io/metis_IPMulticastListener.c + io/metis_ListenerSet.c + io/metis_LocalListener.c + io/metis_StreamConnection.c + io/metis_TcpListener.c + io/metis_TcpTunnel.c + io/metis_UdpConnection.c + io/metis_UdpListener.c + io/metis_UdpTunnel.c + ) + +source_group(io FILES ${METIS_IO_SOURCE}) + +set(METIS_MESSENGER_HEADERS + messenger/metis_MissiveDeque.h + messenger/metis_Missive.h + messenger/metis_MissiveType.h + messenger/metis_Messenger.h + messenger/metis_MessengerRecipient.h + ) + +source_group(messenger FILES ${METIS_MESSENGER_HEADERS}) + +set(METIS_MESSENGER_SOURCE + messenger/metis_Messenger.c + messenger/metis_MessengerRecipient.c + messenger/metis_Missive.c + messenger/metis_MissiveDeque.c + ) + +source_group(messenger FILES ${METIS_MESSENGER_SOURCE}) + +set(METIS_PROCESSOR_HEADERS + processor/metis_FibEntry.h + processor/metis_FibEntryList.h + processor/metis_MessageProcessor.h + processor/metis_Tap.h + processor/metis_HashTableFunction.h + processor/metis_PIT.h + processor/metis_FIB.h + processor/metis_PitEntry.h + processor/metis_MatchingRulesTable.h + processor/metis_PITVerdict.h + processor/metis_StandardPIT.h + ) + +source_group(processor FILES ${METIS_PROCESSOR_HEADERS}) + +set(METIS_PROCESSOR_SOURCE + processor/metis_HashTableFunction.c + processor/metis_FIB.c + processor/metis_FibEntry.c + processor/metis_FibEntryList.c + processor/metis_MatchingRulesTable.c + processor/metis_MessageProcessor.c + processor/metis_PIT.c + processor/metis_PitEntry.c + processor/metis_StandardPIT.c + ) + +source_group(processor FILES ${METIS_PROCESSOR_SOURCE}) + +set(METIS_STRATEGIES_HEADERS + strategies/metis_Strategy.h + strategies/metis_StrategyImpl.h + strategies/strategy_LoadBalancer.h + strategies/strategy_LoadBalancerWithPD.h + strategies/strategy_NexthopState.h + strategies/strategy_NexthopStateWithPD.h + strategies/strategy_Rnd.h + strategies/strategy_RndSegment.h + ) + +source_group(strategies FILES ${METIS_STRATEGIES_HEADERS}) + +set(METIS_STRATEGIES_SOURCE + strategies/metis_StrategyImpl.c + strategies/strategy_LoadBalancer.c + strategies/strategy_LoadBalancerWithPD.c + strategies/strategy_NexthopState.c + strategies/strategy_NexthopStateWithPD.c + strategies/strategy_Rnd.c + strategies/strategy_RndSegment.c + ) + +source_group(strategies FILES ${METIS_STRATEGIES_SOURCE}) + +set(METIS_TLV_HEADERS + tlv/metis_TlvOps.h + tlv/metis_Tlv.h + tlv/metis_TlvName.h + tlv/metis_TlvExtent.h + tlv/metis_TlvNameCodec.h + tlv/metis_TlvSchemaV0.h + tlv/metis_TlvSchemaV1.h + tlv/metis_TlvSkeleton.h + ) + +source_group(tlv FILES ${METIS_TLV_HEADERS}) + +set(METIS_TLV_SOURCE + tlv/metis_Tlv.c + tlv/metis_TlvExtent.c + tlv/metis_TlvName.c + tlv/metis_TlvSchemaV0.c + tlv/metis_TlvSchemaV1.c + tlv/metis_TlvSkeleton.c + tlv/metis_TlvNameCodec.c + ) + +source_group(tlv FILES ${METIS_TLV_SOURCE}) + +if(${CMAKE_SYSTEM_NAME} STREQUAL "Android") + set(METIS_PLATFORM_MODULES + platforms/android/metis_GenericEther.c + platforms/android/metis_System.c + platforms/android/ifaddrs.c + ) + source_group(platforms FILES ${METIS_PLATFORM_MODULES}) +else() + +if ( APPLE ) + set(METIS_PLATFORM_MODULES + platforms/darwin/metis_GenericEther.c + platforms/darwin/metis_System.c + ) + source_group(platforms FILES ${METIS_PLATFORM_MODULES}) +endif() + + +if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) + set(METIS_PLATFORM_MODULES + platforms/linux/metis_GenericEther.c + platforms/linux/metis_System.c + ) +endif() +endif() + +set(METIS_SOURCE_FILES + metis_About.c + ${METIS_CONFIG_SOURCE} + ${METIS_CORE_SOURCE} + ${METIS_TLV_SOURCE} + ${METIS_CONTENT_STORE_SOURCE} + ${METIS_IO_SOURCE} + ${METIS_MESSENGER_SOURCE} + ${METIS_PLATFORM_MODULES} + ${METIS_PROCESSOR_SOURCE} + ${METIS_STRATEGIES_SOURCE} + ) + +add_library(metis STATIC ${METIS_SOURCE_FILES}) + +set(metis_libraries + metis + ) + +foreach(lib ${metis_libraries}) + install(TARGETS ${lib} LIBRARY DESTINATION lib ARCHIVE DESTINATION lib) + set_property(TARGET ${lib} PROPERTY C_STANDARD 99) +endforeach() + +if(NOT ANDROID_API) + add_subdirectory(command-line) +else () + install(FILES ${METIS_BASE_HEADERS} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/ccnx/forwarder/metis) + install(FILES ${METIS_CONFIG_HEADERS} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/ccnx/forwarder/metis/config) + install(FILES ${METIS_CONTENT_STORE_HEADERS} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/ccnx/forwarder/metis/content_store) + install(FILES ${METIS_CORE_HEADERS} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/ccnx/forwarder/metis/core) + install(FILES ${METIS_IO_HEADERS} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/ccnx/forwarder/metis/io) + install(FILES ${METIS_MESSENGER_HEADERS} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/ccnx/forwarder/metis/messenger) + install(FILES ${METIS_PROCESSOR_HEADERS} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/ccnx/forwarder/metis/processor) + install(FILES ${METIS_STRATEGIES_HEADERS} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/ccnx/forwarder/metis/strategies) + install(FILES ${METIS_TLV_HEADERS} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/ccnx/forwarder/metis/tlv) + install(FILES ${CMAKE_INSTALL_PREFIX}/../build/Metis/ccnx/forwarder/metis/config.h DESTINATION ${CMAKE_INSTALL_PREFIX}/include) + +endif() + +add_subdirectory(test) +add_subdirectory(tlv/test) +add_subdirectory(strategies/test) +add_subdirectory(processor/test) +add_subdirectory(messenger/test) +add_subdirectory(io/test) +add_subdirectory(core/test) +add_subdirectory(content_store/test) +add_subdirectory(config/test) diff --git a/metis/ccnx/forwarder/metis/README.txt b/metis/ccnx/forwarder/metis/README.txt new file mode 100644 index 00000000..37c6306a --- /dev/null +++ b/metis/ccnx/forwarder/metis/README.txt @@ -0,0 +1,90 @@ +/* + * 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. + */ + +================= +Metis (mythology) +Metis was the the Titaness of wisdom and deep thought. The Greek word +"metis" meant a quality that combined wisdom and cunning. +================= + +Metis is the CCNx 1.0 forwarder. It uses a modular design so adding new +encapsulations, forwarding behavior, or event dispatching is +well-defined and has minimum (or no) side effects. + +================= +Building + +"make" will create "libmetis.a" and "metis". The library is used by other +parts of Libccnx, such as Transport testing and the mobile builds. The +executable "metis" is for command-line invocation of a forwarding daemon. + +Metis uses a few things from parc/algol and ccnx/common. + +Metis uses the LongBow XUnit test framework. "make check" will run the tests. + +Metis is built with Libevent 2.0 for the dispatcher. Libevent is visible in the +code in the following modules. To use a different event dispatcher, these modules +would need to be updated: metisDispatcher.c, metisStreamBuffer.c, metisMessage.c, +metis_StreamConnection.c + +================= +Code Layout + +Each directory has its code and headers along with a test/ subdirectory with +LongBow unit tests. + +config/ +config/test +- Forwarder configuration, from a file or CLI or web + The default telnet port is 2001. + The default web port is 2002. + +core/ +core/test +- The forwarder (metisForwarder) that bundles the whole thing and the event + dispatcher (metisDispatcher). Also includes various utility classes, such as + MetisMessage that wraps all messages inside the forwarder. + +io/ +io/test/ +- I/O related primatives. These include "listeners" that monitor for specific types + of incoming packets. io/ includes tunnels, ip overlays, and direct L2 encapsulation. + +messenger/ +messenger/test/ +- event messages within the router, such as notificaitons when an interface + comes up or goes down. + +platforms/ +- platform specific functions, such as dealing with interfaces and + direct Ethernet encapsulation. The files in here implement function + prototypes (usually in core/ or io/), and are tested with unit tests of those + prototypes. So, there is no platforms/test/ directory. + +processor/ +processor/test/ +- The message processor. Forwards Interests and objects. + +strategies/ +strategies/test/ +- Forwarding strategies. Each FIB entry indicates the strategy that should + be used to forward an interest among its entries. + +libmetis.a +- The output library + +test/ +- These are system-level tests + diff --git a/metis/ccnx/forwarder/metis/command-line/CMakeLists.txt b/metis/ccnx/forwarder/metis/command-line/CMakeLists.txt new file mode 100644 index 00000000..ac383008 --- /dev/null +++ b/metis/ccnx/forwarder/metis/command-line/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(metis_control) +add_subdirectory(metis_daemon) +#add_subdirectory(test) diff --git a/metis/ccnx/forwarder/metis/command-line/metis_control/CMakeLists.txt b/metis/ccnx/forwarder/metis/command-line/metis_control/CMakeLists.txt new file mode 100644 index 00000000..e07d5f40 --- /dev/null +++ b/metis/ccnx/forwarder/metis/command-line/metis_control/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(metis_control metisControl_main.c) +target_link_libraries(metis_control ${METIS_LINK_LIBRARIES}) + +install(TARGETS metis_control RUNTIME DESTINATION bin) diff --git a/metis/ccnx/forwarder/metis/command-line/metis_control/metisControl_main.c b/metis/ccnx/forwarder/metis/command-line/metis_control/metisControl_main.c new file mode 100644 index 00000000..067ed9c9 --- /dev/null +++ b/metis/ccnx/forwarder/metis/command-line/metis_control/metisControl_main.c @@ -0,0 +1,251 @@ +/* + * 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 <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> +#include <getopt.h> + +#include <LongBow/runtime.h> +#include <string.h> + +#include <parc/security/parc_Security.h> +#include <parc/security/parc_IdentityFile.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_SafeMemory.h> +#include <parc/algol/parc_List.h> +#include <parc/algol/parc_ArrayList.h> + +#include <ccnx/api/control/cpi_ManageLinks.h> + +#include <ccnx/api/control/cpi_Forwarding.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_Dispatcher.h> + +#include <ccnx/api/ccnx_Portal/ccnx_Portal.h> +#include <ccnx/api/ccnx_Portal/ccnx_PortalRTA.h> + +#include <ccnx/common/ccnx_KeystoreUtilities.h> +#include <ccnx/transport/common/transport_MetaMessage.h> + +#include <ccnx/forwarder/metis/metis_About.h> +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +#include <ccnx/forwarder/metis/config/metisControl_Root.h> + +#include <errno.h> + +typedef struct metis_control_main_state { + KeystoreParams *keystoreParams; + CCNxPortal *controlPortal; + MetisControlState *controlState; +} MetisControlMainState; + +static void +_displayForwarderLogo(void) +{ + printf("%s\n", metisAbout_About()); + + printf(" __ __ _ _\n"); + printf(" | \\/ | ___ | |_ (_) ___\n"); + printf(" | |\\/| | / _ \\| __|| |/ __|\n"); + printf(" | | | || __/| |_ | |\\__ \\\n"); + printf(" |_| |_| \\___| \\__||_||___/\n"); + + printf("\n"); +} + +static void +_displayUsage(char *programName) +{ + printf("Usage: %s -h\n", programName); + printf(" %s [--k|--keystore <keystore file name>] [--p|--password <keystore password>] [commands]\n", programName); + printf("\n"); + printf("Metis is the CCNx 1.0 forwarder, which runs on each end system and as a software forwarder\n"); + printf("on intermediate systems. metis_control is the program to configure the forwarder, metis_daemon.\n"); + printf("\n"); + printf("Options:\n"); + printf("-h = This help screen\n"); + printf("-k | --keystore = Specify the path of the PKCS12 keystore (default ~/.ccnx/.ccnx_keystore.p12)\n"); + printf("-p | --password = keystore password (default to prompt user)\n"); + printf("commands = configuration line to send to metis (use 'help' for list)\n"); + printf("\n"); +} + +static int +_parseArgs(int argc, char *argv[], char **keystorePath, char **keystorePassword, PARCList *commandList) +{ + static struct option longFormOptions[] = { + { "help", no_argument, 0, 'h' }, + { "keystore", required_argument, 0, 'k' }, + { "password", required_argument, 0, 'p' }, + { 0, 0, 0, 0 } + }; + + int c; + + while (1) { + // getopt_long stores the option index here. + int optionIndex = 0; + + c = getopt_long(argc, argv, "hk:p:", longFormOptions, &optionIndex); + + // Detect the end of the options. + if (c == -1) { + break; + } + + switch (c) { + case 'k': + *keystorePath = optarg; + break; + + case 'p': + *keystorePassword = optarg; + break; + + case 'h': + default: + _displayUsage(argv[0]); + return 0; + } + } + + // Any remaining parameters get put in the command list. + if (optind < argc) { + while (optind < argc) { + parcList_Add(commandList, argv[optind]); + optind++; + } + } + + return 1; +} + +static CCNxMetaMessage * +_writeAndReadMessage(void *mainStatePtr, CCNxMetaMessage *msg) +{ + assertNotNull(mainStatePtr, "Parameter mainStatePtr must be non-null"); + assertNotNull(msg, "Parameter msg must be non-null"); + + MetisControlMainState *mainState = mainStatePtr; + + if (ccnxPortal_Send(mainState->controlPortal, msg, CCNxStackTimeout_Never)) { + CCNxMetaMessage *result = ccnxPortal_Receive(mainState->controlPortal, CCNxStackTimeout_Never); + assertTrue(result != NULL, "Error reading response from Portal: (%d) %s\nb", errno, strerror(errno)); + + return result; + } + + return NULL; +} + +static CCNxPortal * +_createPortalWithKeystore(const char *keystoreName, const char *keystorePassword) +{ + PARCIdentityFile *identityFile = parcIdentityFile_Create(keystoreName, keystorePassword); + PARCIdentity *identity = parcIdentity_Create(identityFile, PARCIdentityFileAsPARCIdentity); + CCNxPortalFactory *portalFactory = ccnxPortalFactory_Create(identity); + + CCNxPortal *result = ccnxPortalFactory_CreatePortal(portalFactory, ccnxPortalRTA_Message); + + ccnxPortalFactory_Release(&portalFactory); + parcIdentity_Release(&identity); + parcIdentityFile_Release(&identityFile); + + return result; +} + +static MetisControlMainState +_openKeyStore(char *keystorePath, char *keystorePassword) +{ + MetisControlMainState mainState; + + if (keystorePassword == NULL) { + keystorePassword = ccnxKeystoreUtilities_ReadPassword(); + + mainState.keystoreParams = ccnxKeystoreUtilities_OpenFile(keystorePath, keystorePassword); + parcMemory_Deallocate((void *) &keystorePassword); + } else { + mainState.keystoreParams = ccnxKeystoreUtilities_OpenFile(keystorePath, keystorePassword); + } + + return mainState; +} + +int +main(int argc, char *argv[]) +{ + _displayForwarderLogo(); + + if (argc == 2 && strcmp("-h", argv[1]) == 0) { + _displayUsage(argv[0]); + exit(EXIT_SUCCESS); + } + + char *keystorePath = NULL; + char *keystorePassword = NULL; + + PARCList *commands = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + + // Extract optional keystore and password, and optional commands. + if (!_parseArgs(argc, argv, &keystorePath, &keystorePassword, commands)) { + parcList_Release(&commands); + exit(EXIT_FAILURE); + } + + if (keystorePath == NULL) { + printf("No keystore specified. Will try default.\n"); + } else { + printf("Using keystore: %s\n", keystorePath); + } + + parcSecurity_Init(); + + MetisControlMainState mainState = _openKeyStore(keystorePath, keystorePassword); + if (mainState.keystoreParams == NULL) { + printf("Could not open keystore '%s'\n", keystorePath == NULL ? "~/.ccnx/.ccnx_keystore.p12" : keystorePath); + exit(EXIT_FAILURE); + } + + mainState.controlPortal = _createPortalWithKeystore(ccnxKeystoreUtilities_GetFileName(mainState.keystoreParams), + ccnxKeystoreUtilities_GetPassword(mainState.keystoreParams)); + parcSecurity_Fini(); + + mainState.controlState = metisControlState_Create(&mainState, _writeAndReadMessage); + + metisControlState_RegisterCommand(mainState.controlState, metisControlRoot_HelpCreate(mainState.controlState)); + metisControlState_RegisterCommand(mainState.controlState, metisControlRoot_Create(mainState.controlState)); + + if (parcList_Size(commands) > 0) { + metisControlState_DispatchCommand(mainState.controlState, commands); + } else { + metisControlState_Interactive(mainState.controlState); + } + + parcList_Release(&commands); + + metisControlState_Destroy(&mainState.controlState); + + keystoreParams_Destroy(&mainState.keystoreParams); + + ccnxPortal_Release(&mainState.controlPortal); + + return EXIT_SUCCESS; +} diff --git a/metis/ccnx/forwarder/metis/command-line/metis_daemon/CMakeLists.txt b/metis/ccnx/forwarder/metis/command-line/metis_daemon/CMakeLists.txt new file mode 100644 index 00000000..957cab17 --- /dev/null +++ b/metis/ccnx/forwarder/metis/command-line/metis_daemon/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(metis_daemon metis_daemon.c) +target_link_libraries(metis_daemon ${METIS_LINK_LIBRARIES}) + +install(TARGETS metis_daemon RUNTIME DESTINATION bin) diff --git a/metis/ccnx/forwarder/metis/command-line/metis_daemon/metis_daemon.c b/metis/ccnx/forwarder/metis/command-line/metis_daemon/metis_daemon.c new file mode 100644 index 00000000..b6fcfcf7 --- /dev/null +++ b/metis/ccnx/forwarder/metis/command-line/metis_daemon/metis_daemon.c @@ -0,0 +1,319 @@ +/* + * 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 <stdbool.h> +#include <stdint.h> +#include <string.h> +#include <strings.h> +#include <stdlib.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/stat.h> +#include <stdio.h> +#include <unistd.h> + +#include <parc/algol/parc_FileOutputStream.h> +#include <parc/logging/parc_LogLevel.h> +#include <parc/logging/parc_LogReporterTextStdout.h> +#include <parc/logging/parc_LogReporterFile.h> + +#include <LongBow/runtime.h> + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_Dispatcher.h> +#include <ccnx/forwarder/metis/metis_About.h> + +static void +header(void) +{ + printf("%s\n", metisAbout_About()); + + printf(" __ __ _ _\n"); + printf(" | \\/ | ___ | |_ (_) ___\n"); + printf(" | |\\/| | / _ \\| __|| |/ __|\n"); + printf(" | | | || __/| |_ | |\\__ \\\n"); + printf(" |_| |_| \\___| \\__||_||___/\n"); + + printf("\n"); +} + +static void +_usage(int exitCode) +{ + printf("Usage: metis_daemon [--port port] [--daemon] [--capacity objectStoreSize] [--log facility=level] [--log-file filename] [--config file]\n"); + printf("\n"); + printf("Metis is the CCNx 1.0 forwarder, which runs on each end system and as a software forwarder\n"); + printf("on intermediate systems. metis_daemon is the program to launch Metis, either as a console program\n"); + printf("or a background daemon (detatched from console). Once running, use the program metis_control to\n"); + printf("configure Metis.\n"); + printf("\n"); + printf("The configuration file contains configuration lines as per metis_control\n"); + printf("If logging level or content store capacity is set in the configuraiton file, it overrides the command-line\n"); + printf("When a configuration file is specified, no default listeners on 'port' are setup. Only 'add listener' lines\n"); + printf("in the configuration file matter.\n"); + printf("\n"); + printf("If no configuration file is specified, metis_daemon will listen on TCP and UDP ports specified by\n"); + printf("the --port flag (or default port). It will listen on both IPv4 and IPv6 if available.\n"); + printf("\n"); + printf("Options:\n"); + printf("--port = tcp port for in-bound connections\n"); + printf("--daemon = start as daemon process\n"); + printf("--objectStoreSize = maximum number of content objects to cache\n"); + printf("--log = sets a facility to a given log level. You can have multiple of these.\n"); + printf(" facilities: all, config, core, io, message, processor\n"); + printf(" levels: debug, info, notice, warning, error, critical, alert, off\n"); + printf(" example: metis_daemon --log io=debug --log core=off\n"); + printf("--log-file = file to write log messages to (required in daemon mode)\n"); + printf("--config = configuration filename\n"); + printf("\n"); + exit(exitCode); +} + +static void +_setLogLevelToLevel(int logLevelArray[MetisLoggerFacility_END], MetisLoggerFacility facility, const char *levelString) +{ + PARCLogLevel level = parcLogLevel_FromString(levelString); + + if (level < PARCLogLevel_All) { + // we have a good facility and level + logLevelArray[facility] = level; + } else { + printf("Invalid log level string %s\n", levelString); + _usage(EXIT_FAILURE); + } +} + +/** + * string: "facility=level" + * Set the right thing in the logger + */ +static void +_setLogLevel(int logLevelArray[MetisLoggerFacility_END], const char *string) +{ + char *tofree = parcMemory_StringDuplicate(string, strlen(string)); + char *p = tofree; + + char *facilityString = strsep(&p, "="); + if (facilityString) { + char *levelString = p; + + if (strcasecmp(facilityString, "all") == 0) { + for (MetisLoggerFacility facility = 0; facility < MetisLoggerFacility_END; facility++) { + _setLogLevelToLevel(logLevelArray, facility, levelString); + } + } else { + MetisLoggerFacility facility; + for (facility = 0; facility < MetisLoggerFacility_END; facility++) { + if (strcasecmp(facilityString, metisLogger_FacilityString(facility)) == 0) { + break; + } + } + + if (facility < MetisLoggerFacility_END) { + _setLogLevelToLevel(logLevelArray, facility, levelString); + } else { + printf("Invalid facility string %s\n", facilityString); + _usage(EXIT_FAILURE); + } + } + } + + parcMemory_Deallocate((void **) &tofree); +} + +static void +_daemonize(void) +{ + if (getppid() == 1) { + // already a daemon + return; + } + + int forkReturn = fork(); + trapUnexpectedStateIf(forkReturn < 0, "Fork error"); + + if (forkReturn > 0) { + // parent exits + exit(EXIT_SUCCESS); + } + + // Child daemon detaches + printf("child continuing, pid = %u\n", getpid()); + + // get a new process group independent from old parent + setsid(); + + /* close all descriptors */ + for (int i = getdtablesize(); i >= 0; --i) { + close(i); + } + + // reset errno because it might be seg to EBADF from the close calls above + errno = 0; + + // Redirect stdin and stdout and stderr to /dev/null + const char *devnull = "/dev/null"; + int nullfile = open(devnull, O_RDWR); + assertTrue(nullfile >= 0, "Error opening file '%s': (%d) %s", devnull, errno, strerror(errno)); + + int ret; + ret = dup(nullfile); + assertTrue(ret == 1, "Error duping fd 1 got %d file: (%d) %s", ret, errno, strerror(errno)); + ret = dup(nullfile); + assertTrue(ret == 2, "Error duping fd 2, got %d file: (%d) %s", ret, errno, strerror(errno)); + + // metisForwarder will capture signals +} + +static MetisLogger * +_createLogfile(const char *logfile) +{ + int logfd = open(logfile, O_WRONLY | O_APPEND | O_CREAT, S_IWUSR | S_IRUSR); + if (logfd < 0) { + fprintf(stderr, "Error opening %s for writing: (%d) %s\n", logfile, errno, strerror(errno)); + exit(EXIT_FAILURE); + } + + chmod(logfile, S_IRWXU); + + PARCFileOutputStream *fos = parcFileOutputStream_Create(logfd); + PARCOutputStream *pos = parcFileOutputStream_AsOutputStream(fos); + PARCLogReporter *reporter = parcLogReporterFile_Create(pos); + + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + + parcOutputStream_Release(&pos); + parcLogReporter_Release(&reporter); + + return logger; +} + +int +main(int argc, const char *argv[]) +{ + header(); + + uint16_t port = PORT_NUMBER; + uint16_t configurationPort = 2001; + bool daemon = false; + int capacity = -1; + const char *configFileName = NULL; + + char *logfile = NULL; + + if (argc == 2 && strcasecmp(argv[1], "-h") == 0) { + _usage(EXIT_SUCCESS); + } + + int logLevelArray[MetisLoggerFacility_END]; + for (int i = 0; i < MetisLoggerFacility_END; i++) { + logLevelArray[i] = -1; + } + + for (int i = 0; i < argc; i++) { + if (argv[i][0] == '-') { + if (strcmp(argv[i], "--config") == 0) { + configFileName = argv[i + 1]; + i++; + } else if (strcmp(argv[i], "--port") == 0) { + port = atoi(argv[i + 1]); + i++; + } else if (strcmp(argv[i], "--daemon") == 0) { + daemon = true; + } else if (strcmp(argv[i], "--capacity") == 0 || strcmp(argv[i], "-c") == 0) { + capacity = atoi(argv[i + 1]); + i++; + } else if (strcmp(argv[i], "--log") == 0) { + _setLogLevel(logLevelArray, argv[i + 1]); + i++; + } else if (strcmp(argv[i], "--log-file") == 0) { + if (logfile) { + // error cannot repeat + fprintf(stderr, "Cannot specify --log-file more than once\n"); + _usage(EXIT_FAILURE); + } + + logfile = parcMemory_StringDuplicate(argv[i + 1], strlen(argv[i + 1])); + i++; + } else { + _usage(EXIT_FAILURE); + } + } + } + + // set restrictive umask, in case we create any files + umask(027); + + if (daemon && (logfile == NULL)) { + fprintf(stderr, "Must specify a logfile when running in daemon mode\n"); + _usage(EXIT_FAILURE); + } + + if (daemon) { + // inside this call, parent will EXIT_SUCCESS and child will continue + _daemonize(); + } + + MetisLogger *logger = NULL; + if (logfile) { + logger = _createLogfile(logfile); + parcMemory_Deallocate((void **) &logfile); + } else { + PARCLogReporter *stdoutReporter = parcLogReporterTextStdout_Create(); + logger = metisLogger_Create(stdoutReporter, parcClock_Wallclock()); + parcLogReporter_Release(&stdoutReporter); + } + + for (int i = 0; i < MetisLoggerFacility_END; i++) { + if (logLevelArray[i] > -1) { + metisLogger_SetLogLevel(logger, i, logLevelArray[i]); + } + } + + + // this will update the clock to the tick clock + MetisForwarder *metis = metisForwarder_Create(logger); + + MetisConfiguration *configuration = metisForwarder_GetConfiguration(metis); + + if (capacity > -1) { + metisConfiguration_SetObjectStoreSize(configuration, capacity); + } + + metisConfiguration_StartCLI(configuration, configurationPort); + + if (configFileName) { + metisForwarder_SetupFromConfigFile(metis, configFileName); + } else { + // NULL to not setup AF_UNIX + metisForwarder_SetupAllListeners(metis, port, NULL); + } + + MetisDispatcher *dispatcher = metisForwarder_GetDispatcher(metis); + + metisLogger_Log(logger, MetisLoggerFacility_Core, PARCLogLevel_Alert, "daemon", "metis running port %d configuration-port %d", port, configurationPort); + + metisDispatcher_Run(dispatcher); + + metisLogger_Log(logger, MetisLoggerFacility_Core, PARCLogLevel_Alert, "daemon", "metis exiting port %d", port); + + metisForwarder_Destroy(&metis); + + sleep(2); + + metisLogger_Release(&logger); + return 0; +} diff --git a/metis/ccnx/forwarder/metis/command-line/test/test_metis-control.c b/metis/ccnx/forwarder/metis/command-line/test/test_metis-control.c new file mode 100644 index 00000000..fd1b4110 --- /dev/null +++ b/metis/ccnx/forwarder/metis/command-line/test/test_metis-control.c @@ -0,0 +1,68 @@ +/* + * 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 <LongBow/unit-test.h> +#include <LongBow/debugging.h> + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis-control/metisControl_main.c" + +LONGBOW_TEST_RUNNER(test_metis_control) +{ + // 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); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(test_metis_control) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(test_metis_control) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, myTest); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, myTest) +{ +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(test_metis_control); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config.h.in b/metis/ccnx/forwarder/metis/config.h.in new file mode 100644 index 00000000..16ec1ab3 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config.h.in @@ -0,0 +1,5 @@ +/* CPU Cache line size */ +#define LEVEL1_DCACHE_LINESIZE @LEVEL1_DCACHE_LINESIZE@ + +#define _GNU_SOURCE + diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Add.c b/metis/ccnx/forwarder/metis/config/metisControl_Add.c new file mode 100644 index 00000000..3b11bbe1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Add.c @@ -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. + */ + + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/config/metisControl_Add.h> +#include <ccnx/forwarder/metis/config/metisControl_AddConnection.h> +#include <ccnx/forwarder/metis/config/metisControl_AddRoute.h> +#include <ccnx/forwarder/metis/config/metisControl_AddListener.h> + +// =================================================== + +static void _metisControlAdd_Init(MetisCommandParser *parser, MetisCommandOps *ops); +static MetisCommandReturn _metisControlAdd_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlAdd_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +// =================================================== + +static const char *command_add = "add"; +static const char *help_command_add = "help add"; + +MetisCommandOps * +metisControlAdd_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, command_add, _metisControlAdd_Init, _metisControlAdd_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlAdd_CreateHelp(MetisControlState *state) +{ + return metisCommandOps_Create(state, help_command_add, NULL, _metisControlAdd_HelpExecute, metisCommandOps_Destroy); +} + +// =================================================== + +static MetisCommandReturn +_metisControlAdd_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + + printf("Available commands:\n"); + printf(" %s\n", command_add); + printf(" %s\n", help_command_add); + printf("\n"); + return MetisCommandReturn_Success; +} + +static void +_metisControlAdd_Init(MetisCommandParser *parser, MetisCommandOps *ops) +{ + MetisControlState *state = ops->closure; + metisControlState_RegisterCommand(state, metisControlAddListener_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlAddListener_Create(state)); + metisControlState_RegisterCommand(state, metisControlAddConnection_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlAddRoute_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlAddConnection_Create(state)); + metisControlState_RegisterCommand(state, metisControlAddRoute_Create(state)); +} + +static MetisCommandReturn +_metisControlAdd_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + return _metisControlAdd_HelpExecute(parser, ops, args); +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Add.h b/metis/ccnx/forwarder/metis/config/metisControl_Add.h new file mode 100644 index 00000000..eb0ac8db --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Add.h @@ -0,0 +1,32 @@ +/* + * 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 metisControl_Add.h + * @brief Command-line "add" node + * + * Implements the "add" node of the CLI tree + * + * + */ + +#ifndef Metis_metis_Control_Add_h +#define Metis_metis_Control_Add_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> + +MetisCommandOps *metisControlAdd_Create(MetisControlState *state); +MetisCommandOps *metisControlAdd_CreateHelp(MetisControlState *state); +#endif // Metis_metis_Control_Add_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_AddConnection.c b/metis/ccnx/forwarder/metis/config/metisControl_AddConnection.c new file mode 100644 index 00000000..f275f492 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_AddConnection.c @@ -0,0 +1,622 @@ +/* + * 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 <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_Network.h> +#include <ccnx/api/control/cpi_Address.h> +#include <ccnx/api/control/cpi_InterfaceIPTunnel.h> +#include <ccnx/api/control/cpi_ManageLinks.h> +#include <ccnx/api/control/cpi_ConnectionEthernet.h> +#include <ccnx/forwarder/metis/config/metisControl_AddConnection.h> + +// =================================================== + +static void _metisControlAddConnection_Init(MetisCommandParser *parser, MetisCommandOps *ops); +static MetisCommandReturn _metisControlAddConnection_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlAddConnection_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +// =================================================== + +static MetisCommandReturn _metisControlAddConnection_TcpHelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlAddConnection_TcpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static MetisCommandReturn _metisControlAddConnection_UdpHelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlAddConnection_UdpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static MetisCommandReturn _metisControlAddConnection_McastHelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlAddConnection_McastExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static MetisCommandReturn _metisControlAddConnection_EtherHelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlAddConnection_EtherExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +// =================================================== + +static const char *_commandAddConnection = "add connection"; +static const char *_commandAddConnectionTcp = "add connection tcp"; +static const char *_commandAddConnectionUdp = "add connection udp"; +static const char *_commandAddConnectionMcast = "add connection mcast"; +static const char *_commandAddConnectionEther = "add connection ether"; +static const char *_commandAddConnectionHelp = "help add connection"; +static const char *_commandAddConnectionTcpHelp = "help add connection tcp"; +static const char *_commandAddConnectionUdpHelp = "help add connection udp"; +static const char *_commandAddConnectionMcastHelp = "help add connection mcast"; +static const char *_commandAddConnectionEtherHelp = "help add connection ether"; + +// =================================================== + +MetisCommandOps * +metisControlAddConnection_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandAddConnection, _metisControlAddConnection_Init, + _metisControlAddConnection_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlAddConnection_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandAddConnectionHelp, NULL, + _metisControlAddConnection_HelpExecute, metisCommandOps_Destroy); +} + +// =================================================== + +static MetisCommandOps * +_metisControlAddConnection_TcpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandAddConnectionTcp, NULL, + _metisControlAddConnection_TcpExecute, metisCommandOps_Destroy); +} + +static MetisCommandOps * +_metisControlAddConnection_UdpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandAddConnectionUdp, NULL, + _metisControlAddConnection_UdpExecute, metisCommandOps_Destroy); +} + +static MetisCommandOps * +_metisControlAddConnection_McastCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandAddConnectionMcast, NULL, + _metisControlAddConnection_McastExecute, metisCommandOps_Destroy); +} + +static MetisCommandOps * +_metisControlAddConnection_EtherCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandAddConnectionEther, NULL, + _metisControlAddConnection_EtherExecute, metisCommandOps_Destroy); +} + +// =================================================== + +static MetisCommandOps * +_metisControlAddConnection_TcpHelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandAddConnectionTcpHelp, NULL, + _metisControlAddConnection_TcpHelpExecute, metisCommandOps_Destroy); +} + +static MetisCommandOps * +_metisControlAddConnection_UdpHelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandAddConnectionUdpHelp, NULL, + _metisControlAddConnection_UdpHelpExecute, metisCommandOps_Destroy); +} + +static MetisCommandOps * +_metisControlAddConnection_McastHelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandAddConnectionMcastHelp, NULL, + _metisControlAddConnection_McastHelpExecute, metisCommandOps_Destroy); +} + +static MetisCommandOps * +_metisControlAddConnection_EtherHelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandAddConnectionEtherHelp, NULL, + _metisControlAddConnection_EtherHelpExecute, metisCommandOps_Destroy); +} + +/** + * A symbolic name must be at least 1 character and must begin with an alpha. + * The remainder must be an alphanum. + */ +static bool +_validateSymbolicName(const char *symbolic) +{ + bool success = false; + size_t len = strlen(symbolic); + if (len > 0) { + if (isalpha(symbolic[0])) { + success = true; + for (size_t i = 1; i < len; i++) { + if (!isalnum(symbolic[i])) { + success = false; + break; + } + } + } + } + return success; +} + +// =================================================== + +static MetisCommandReturn +_metisControlAddConnection_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("Available commands:\n"); + printf(" %s\n", _commandAddConnectionTcp); + printf(" %s\n", _commandAddConnectionUdp); + printf(" %s\n", _commandAddConnectionMcast); + printf(" %s\n", _commandAddConnectionEther); + printf("\n"); + return MetisCommandReturn_Success; +} + +static void +_metisControlAddConnection_Init(MetisCommandParser *parser, MetisCommandOps *ops) +{ + MetisControlState *state = ops->closure; + metisControlState_RegisterCommand(state, _metisControlAddConnection_TcpHelpCreate(state)); + metisControlState_RegisterCommand(state, _metisControlAddConnection_UdpHelpCreate(state)); + metisControlState_RegisterCommand(state, _metisControlAddConnection_McastHelpCreate(state)); + metisControlState_RegisterCommand(state, _metisControlAddConnection_EtherHelpCreate(state)); + + metisControlState_RegisterCommand(state, _metisControlAddConnection_TcpCreate(state)); + metisControlState_RegisterCommand(state, _metisControlAddConnection_UdpCreate(state)); + metisControlState_RegisterCommand(state, _metisControlAddConnection_McastCreate(state)); + metisControlState_RegisterCommand(state, _metisControlAddConnection_EtherCreate(state)); +} + +static MetisCommandReturn +_metisControlAddConnection_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + return _metisControlAddConnection_HelpExecute(parser, ops, args); +} + +// =================================================== +// functions general to all connection types + +/** + * Create a tunnel in the forwarder based on the CPI addresses + * + * Caller retains ownership of memory. + * The symbolic name will be used to refer to this connection. It must be unqiue otherwise + * the forwarder will reject this commend. + * + * @param [in] parser An allocated MetisCommandParser + * @param [in] ops Allocated MetisCommandOps (needed to extract MetisControlState) + * @param [in] localAddress the local IP and port. The port may be the wildcard value. + * @param [in] remoteAddress The remote IP and port (both must be specified) + * @param [in] tunnelType The tunneling protocol + * @param [in] symbolic The symbolic name for the connection (must be unique) + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * { + * struct sockaddr_in *anyAddress = parcNetwork_SockInet4AddressAny(); + * struct sockaddr_in *remote = parcNetwork_SockInet4Address("192.168.1.2", 9695); + * + * CPIAddress *localAddress = cpiAddress_CreateFromInet(anyAddress); + * CPIAddress *remoteAddress = cpiAddress_CreateFromInet(remote); + * + * metisControl_CreateTunnel(state, localAddress, remoteAddress, IPTUN_TCP, "conn7"); + * + * cpiAddress_Destroy(&localAddress); + * cpiAddress_Destroy(&remoteAddress); + * parcMemory_Deallocate((void **)&remote); + * parcMemory_Deallocate((void **)&anyAddress); + * } + * @endcode + */ +static void +_metisControlAddConnection_CreateTunnel(MetisCommandParser *parser, MetisCommandOps *ops, CPIAddress *localAddress, CPIAddress *remoteAddress, CPIInterfaceIPTunnelType tunnelType, const char *symbolic) +{ + MetisControlState *state = ops->closure; + CPIAddress *remoteAddressCopy = cpiAddress_Copy(remoteAddress); + CPIAddress *localAddressCopy = cpiAddress_Copy(localAddress); + + // a request like this always has an interface index of 0 + unsigned int interfaceIndex = 0; + CPIInterfaceIPTunnel *ipTunnel = cpiInterfaceIPTunnel_Create(interfaceIndex, localAddressCopy, remoteAddressCopy, tunnelType, symbolic); + PARCJSON *cpiMessage = cpiLinks_CreateIPTunnel(ipTunnel); + CCNxControl *controlMessage = ccnxControl_CreateCPIRequest(cpiMessage); + parcJSON_Release(&cpiMessage); + + CCNxMetaMessage *message = ccnxMetaMessage_CreateFromControl(controlMessage); + + // Write it, and get the response. + CCNxMetaMessage *rawResponse = metisControlState_WriteRead(state, message); + ccnxMetaMessage_Release(&message); + + CCNxControl *response = ccnxMetaMessage_GetControl(rawResponse); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(response)); + printf("reponse:\n%s\n", str); + parcMemory_Deallocate((void **) &str); + } + + ccnxControl_Release(&controlMessage); + ccnxMetaMessage_Release(&rawResponse); + cpiInterfaceIPTunnel_Release(&ipTunnel); +} + +static CPIAddress * +_metisControlAddConnection_ConvertStringsToCpiAddress(const char *ip_string, const char *port_string) +{ + int port = atoi(port_string); + struct sockaddr *addr = parcNetwork_SockAddress(ip_string, port); + + if (addr == NULL) { + printf("Error converting address '%s' port '%s' to socket address\n", ip_string, port_string); + return NULL; + } + + CPIAddress *remote_cpi_address = NULL; + switch (addr->sa_family) { + case PF_INET: + { + remote_cpi_address = cpiAddress_CreateFromInet((struct sockaddr_in *) addr); + break; + } + + case PF_INET6: + { + remote_cpi_address = cpiAddress_CreateFromInet6((struct sockaddr_in6 *) addr); + break; + } + default: + { + printf("Error converting address '%s' port '%s' to socket address, unsupported address family %d\n", + ip_string, port_string, addr->sa_family); + break; + } + } + parcMemory_Deallocate((void **) &addr); + return remote_cpi_address; +} + +/** + * Parse a standard format command-line to a remote address and a local adress + * + * Command-line format: + * aaa bbb ccc <symbolic> <remote_ip|hostname> <remote_port> [<local_ip|hostname> [<local_port>]] + * + * where aaa, bbb, and ccc are don't care. + * + * @param [in] args The command line + * @param [out] remoteAddressPtr The remote address, use `parcMemory_Deallocate()` on it, or null if error + * @param [out] localAddressPtr The remote address, use `parcMemory_Deallocate()` on it, or null if error + * @param [out] symbolicPtr The symbolic name (points to string in args) + * + * @return MetisCommandReturn_Success if valid IP address command-line + * @return MetisCommandReturn_Failure if an error in the IP address command-line + * + * Example: + * @code + * <#example#> + * @endcode + */ +static MetisCommandReturn +_metisControlAddConnection_ParseIPCommandLine(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args, + CPIAddress **remoteAddressPtr, CPIAddress **localAddressPtr, char **symbolicPtr) +{ + *remoteAddressPtr = NULL; + *localAddressPtr = NULL; + + if (parcList_Size(args) < 6 || parcList_Size(args) > 8) { + _metisControlAddConnection_TcpHelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + char *symbolic = parcList_GetAtIndex(args, 3); + + if (_validateSymbolicName(symbolic)) { + char *remote_ip = parcList_GetAtIndex(args, 4); + char *remote_port = parcList_GetAtIndex(args, 5); + + CPIAddress *remote_addr = _metisControlAddConnection_ConvertStringsToCpiAddress(remote_ip, remote_port); + if (remote_addr == NULL) { + return MetisCommandReturn_Failure; + } + + char *local_ip = "0.0.0.0"; + char *local_port = "0"; + + if (parcList_Size(args) > 6) { + local_ip = parcList_GetAtIndex(args, 6); + } + + if (parcList_Size(args) > 7) { + local_port = parcList_GetAtIndex(args, 7); + } + + CPIAddress *local_addr = _metisControlAddConnection_ConvertStringsToCpiAddress(local_ip, local_port); + if (local_addr == NULL) { + cpiAddress_Destroy(&remote_addr); + return MetisCommandReturn_Failure; + } + + if (cpiAddress_GetType(local_addr) != cpiAddress_GetType(remote_addr)) { + char *local_str = cpiAddress_ToString(local_addr); + char *remote_str = cpiAddress_ToString(remote_addr); + printf("Error: local address %s not same type as remote address %s\n", + local_str, remote_str); + parcMemory_Deallocate((void **) &local_str); + parcMemory_Deallocate((void **) &remote_str); + cpiAddress_Destroy(&remote_addr); + cpiAddress_Destroy(&local_addr); + return MetisCommandReturn_Failure; + } + + *symbolicPtr = symbolic; + *remoteAddressPtr = remote_addr; + *localAddressPtr = local_addr; + return MetisCommandReturn_Success; + } else { + printf("Invalid symbolic name. Must begin with alpha and contain only alphanum.\n"); + return MetisCommandReturn_Failure; + } +} + +static MetisCommandReturn +_metisControlAddConnection_IpHelp(MetisCommandParser*parser, + MetisCommandOps*ops, PARCList*args, const char*protocol) +{ + printf("add connection %s <symbolic> <remote_ip|hostname> <remote_port> [<local_ip|hostname> [<local_port>]]\n", + protocol); + printf(" <symbolic> : symbolic name, e.g. 'conn1' (must be unique, start with alpha)\n"); + printf(" <remote_ip | hostname> : the IPv4 or IPv6 or hostname of the remote system\n"); + printf(" <remote_port> : the remote TCP port\n"); + printf(" <local_ip> : optional local IP address to bind to\n"); + printf(" <local_port> : optional local TCP port, random if not specified\n"); + printf("\n"); + printf("Examples:\n"); + printf(" add connection %s conn1 1.1.1.1 1200\n", protocol); + printf(" opens a connection to IP address 1.1.1.1 port 1200 using the best local\n"); + printf(" interface and random local port."); + printf("\n"); + printf(" add connection %s barney2 fe80::aa20:66ff:fe00:314a 1300\n", protocol); + printf(" opens connection to IPv6 address on port 1300.\n"); + printf("\n"); + printf(" add connection %s conn0 1.1.1.1 1200 2.2.2.2 1300\n", protocol); + printf(" opens a connection to 1.1.1.1 on port 1200 from the local address 2.2.2.2 port 1300\n"); + printf("\n"); + printf(" add connection %s conn3 ccn.parc.com 9695\n", protocol); + printf(" opens a connection to the host 'ccn.parc.com' on port 9695.\n"); + printf(" Maybe an IPv4 or IPv6 connection as the name is resolved and connectivity permits.\n"); + printf("\n"); + return MetisCommandReturn_Success; +} + +// =================================================== + +static MetisCommandReturn +_metisControlAddConnection_TcpHelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + _metisControlAddConnection_IpHelp(parser, ops, args, "tcp"); + printf("A TCP connection will not be usable until the remote peer accepts the connection.\n"); + printf("\n"); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlAddConnection_TcpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + char *symbolic = NULL; + CPIAddress *remote_addr; + CPIAddress *local_addr; + if (_metisControlAddConnection_ParseIPCommandLine(parser, ops, args, &remote_addr, &local_addr, &symbolic) == MetisCommandReturn_Success) { + _metisControlAddConnection_CreateTunnel(parser, ops, local_addr, remote_addr, IPTUN_TCP, symbolic); + + cpiAddress_Destroy(&remote_addr); + cpiAddress_Destroy(&local_addr); + return MetisCommandReturn_Success; + } + + return MetisCommandReturn_Failure; +} + +// =================================================== + +static MetisCommandReturn +_metisControlAddConnection_UdpHelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + _metisControlAddConnection_IpHelp(parser, ops, args, "udp"); + printf("A UDP connection will be usable immediately, even if the remote side has not accepted.\n"); + printf("\n"); + + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlAddConnection_UdpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + CPIAddress *remote_addr; + CPIAddress *local_addr; + char *symbolic = NULL; + if (_metisControlAddConnection_ParseIPCommandLine(parser, ops, args, &remote_addr, &local_addr, &symbolic) == MetisCommandReturn_Success) { + _metisControlAddConnection_CreateTunnel(parser, ops, local_addr, remote_addr, IPTUN_UDP, symbolic); + + cpiAddress_Destroy(&remote_addr); + cpiAddress_Destroy(&local_addr); + return MetisCommandReturn_Success; + } + + return MetisCommandReturn_Failure; +} + +// =================================================== + +static MetisCommandReturn +_metisControlAddConnection_McastHelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("%s help", ops->command); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlAddConnection_McastExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("ERROR: command not implemented\n\n"); + return MetisCommandReturn_Failure; +} + +// =================================================== + +/** + * Parse a standard format command-line to a remote address and a local adress + * + * Command-line format: + * aaa bbb ccc <symbolic> <destination_mac> <local_interface> + * + * where aaa, bbb, and ccc are don't care. + * + * @param [in] args The command line + * @param [out] remoteAddressPtr The remote address, or null if error + * @param [out] localAddressPtr The local interface name as a LINK address, or null if error + * @param [out] etherEncapType The ethertype (host byte order) + * @param [out] symbolic The symbolic name (points to string in args) + * + * @retval MetisCommandReturn_Success if valid IP address command-line + * @retval MetisCommandReturn_Failure if an error in the IP address command-line + * + * Example: + * @code + * <#example#> + * @endcode + */ +static MetisCommandReturn +_metisControl_ParseEtherCommandLine(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args, + CPIAddress **remoteAddressPtr, char **localAddressPtr, uint16_t *etherEncapType, char **symbolicPtr) +{ + // MetisControlState *state = ops->closure; + *remoteAddressPtr = NULL; + + if (parcList_Size(args) < 5) { + _metisControlAddConnection_EtherHelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + char *symbolic = parcList_GetAtIndex(args, 3); + + if (_validateSymbolicName(symbolic)) { + char *remoteMacString = parcList_GetAtIndex(args, 4); + char *localInterface = parcList_GetAtIndex(args, 5); + + if (parcList_Size(args) > 6) { + // TODO : Parse for ether_type = [ value ] + *etherEncapType = 0x801; + } else { + *etherEncapType = 0x801; + } + // This will over-allocate the buffer + PARCBuffer *remoteMacBuffer = parcBuffer_Allocate(strnlen(remoteMacString, 24)); + + bool success = false; + if (strlen(localInterface) > 0) { + if (parcNetwork_ParseMAC48Address(remoteMacString, remoteMacBuffer)) { + parcBuffer_Flip(remoteMacBuffer); + *remoteAddressPtr = cpiAddress_CreateFromLink(parcBuffer_Overlay(remoteMacBuffer, 0), parcBuffer_Remaining(remoteMacBuffer)); + *localAddressPtr = localInterface; + *symbolicPtr = symbolic; + success = true; + } + } + + parcBuffer_Release(&remoteMacBuffer); + + return (success ? MetisCommandReturn_Success : MetisCommandReturn_Failure); + } else { + printf("Invalid symbolic name. Must begin with alpha and contain only alphanum.\n"); + return MetisCommandReturn_Failure; + } +} + +static MetisCommandReturn +_metisControlAddConnection_EtherHelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + // ethertype not currently supported + + printf("add connection ether <symbolic> <destination_mac> <local_interface>\n"); + printf(" <symbolic> : symbolic name, e.g. 'conn1' (must be unique, start with alpha)\n"); + printf(" <destination_mac> : destination MAC address in hex (optional \":\" or \"-\" separators)\n"); + printf(" <local_interface> : the name of the local interface (e.g. \"en0\")\n"); + printf("\n"); + printf("Examples:\n"); + printf(" add connection ether conn7 e8-06-88-cd-28-de em3\n"); + printf(" Creates a connection to e8-06-88-cd-28-de on interface em3, ethertype = 0x0801\n"); + printf("\n"); + printf(" add connection ether hal2 00:1c:42:00:00:08 eth0\n"); + printf(" Creates a connection to 00:1c:42:00:00:08 on interface eth0, ethertype = 0x0801\n"); + printf("\n"); + printf(" add connection ether bcast0 FFFFFFFFFFFF eth0\n"); + printf(" Creates a broadcast connection on eth0 with ethertype = 0x0801\n"); + printf("\n"); + + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlAddConnection_EtherExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + CPIAddress *remote_addr; + char *local_addr = NULL; + char *symbolic = NULL; + uint16_t ether_EncapType; + MetisControlState *metis_State = ops->closure; + + + if (_metisControl_ParseEtherCommandLine(parser, ops, args, &remote_addr, &local_addr, ðer_EncapType, &symbolic) == MetisCommandReturn_Success) { + CPIConnectionEthernet *ether_Conn = cpiConnectionEthernet_Create(local_addr, remote_addr, ether_EncapType, symbolic); + CCNxControl *control_Message = cpiConnectionEthernet_CreateAddMessage(ether_Conn); + + CCNxMetaMessage *msg = ccnxMetaMessage_CreateFromControl(control_Message); + CCNxMetaMessage *control_Response = metisControlState_WriteRead(metis_State, msg); + ccnxMetaMessage_Release(&msg); + + if (metisControlState_GetDebug(metis_State)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(control_Message)); + printf("reponse:\n%s\n", str); + parcMemory_Deallocate((void **) &str); + } + + ccnxMetaMessage_Release(&control_Response); + ccnxControl_Release(&control_Message); + cpiConnectionEthernet_Release(ðer_Conn); + cpiAddress_Destroy(&remote_addr); + return MetisCommandReturn_Success; + } + + return MetisCommandReturn_Failure; +} + +// =================================================== diff --git a/metis/ccnx/forwarder/metis/config/metisControl_AddConnection.h b/metis/ccnx/forwarder/metis/config/metisControl_AddConnection.h new file mode 100644 index 00000000..dee6e461 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_AddConnection.h @@ -0,0 +1,31 @@ +/* + * 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 metisControl_AddConnection.h + * @brief Command-line "add connection" node + * + * Implements the "add connection" node of the CLI tree + * + * + */ + +#ifndef Metis_metisControl_AddConnection_h +#define Metis_metisControl_AddConnection_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlAddConnection_Create(MetisControlState *state); +MetisCommandOps *metisControlAddConnection_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_AddConnection_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_AddListener.c b/metis/ccnx/forwarder/metis/config/metisControl_AddListener.c new file mode 100644 index 00000000..9268c76f --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_AddListener.c @@ -0,0 +1,280 @@ +/* + * 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 <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> +#include <inttypes.h> +#include <ctype.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Network.h> +#include <parc/algol/parc_Memory.h> +#include <ccnx/api/control/cpi_Listener.h> + +#include <ccnx/forwarder/metis/config/metisControl_AddListener.h> +#include <ccnx/api/control/controlPlaneInterface.h> +#include <ccnx/api/control/cpi_Acks.h> + +static MetisCommandReturn _metisControlAddListener_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlAddListener_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *command_add_listener = "add listener"; +static const char *command_help_add_listener = "help add listener"; + +MetisCommandOps * +metisControlAddListener_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, command_add_listener, NULL, _metisControlAddListener_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlAddListener_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, command_help_add_listener, NULL, _metisControlAddListener_HelpExecute, metisCommandOps_Destroy); +} + +/** + * A symbolic name must be at least 1 character and must begin with an alpha. + * The remainder must be an alphanum. + */ +static bool +_validateSymbolicName(const char *symbolic) +{ + bool success = false; + size_t len = strlen(symbolic); + if (len > 0) { + if (isalpha(symbolic[0])) { + success = true; + for (size_t i = 1; i < len; i++) { + if (!isalnum(symbolic[i])) { + success = false; + break; + } + } + } + } + return success; +} + +// ==================================================== + +static const int _indexProtocol = 2; +static const int _indexSymbolic = 3; +static const int _indexAddress = 4; +static const int _indexPort = 5; + +static MetisCommandReturn +_metisControlAddListener_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("commands:\n"); + printf(" add listener <protocol> <symbolic> <localAddress> <PortOrEtherType>\n"); + printf("\n"); + printf(" symbolic: User defined name for listener, must start with alpha and be alphanum\n"); + printf(" protocol: tcp | udp | ether\n"); + printf(" localAddress: IPv4 or IPv6 or hostname or interface name (see examples)\n"); + printf(" PortOrEtherType: TCP/UDP port or EtherType (base 10 or use 0x for base 16)\n"); + printf("\n"); + printf("Notes:\n"); + printf(" The local address must be on the system (see 'help list interfaces' command).\n"); + printf(" For Ethernet, the broadcast and CCNx group address will also be added.\n"); + printf(" The symblic name must be unique or the forwarder will reject it.\n"); + printf("\n"); + printf("Examples:\n"); + printf(" Listens to 192.168.1.7 on tcp port 9695\n"); + printf(" add listener tcp homenet 192.168.1.7 9695\n"); + printf("\n"); + printf(" Listens to IPv6 localhost on udp port 9695\n"); + printf(" add listener udp localhost6 ::1 9695\n"); + printf("\n"); + printf(" Listens to interface 'en0' on ethertype 0x0801\n"); + printf(" add listener ether nic0 en0 0x0801\n"); + printf("\n"); + return MetisCommandReturn_Success; +} + +static CPIAddress * +_convertStringsToCpiAddress(const char *ip_string, const char *port_string) +{ + int port = atoi(port_string); + struct sockaddr *addr = parcNetwork_SockAddress(ip_string, port); + + if (addr == NULL) { + printf("Error converting address '%s' port '%s' to socket address\n", ip_string, port_string); + return NULL; + } + + CPIAddress *remote_cpi_address = NULL; + switch (addr->sa_family) { + case PF_INET: { + remote_cpi_address = cpiAddress_CreateFromInet((struct sockaddr_in *) addr); + break; + } + + case PF_INET6: { + remote_cpi_address = cpiAddress_CreateFromInet6((struct sockaddr_in6 *) addr); + break; + } + + default: { + printf("Error converting address '%s' port '%s' to socket address, unsupported address family %d\n", + ip_string, port_string, addr->sa_family); + break; + } + } + parcMemory_Deallocate((void **) &addr); + return remote_cpi_address; +} + +static MetisCommandReturn +_sendAndVerify(MetisControlState *metis_State, CCNxControl *control) +{ + MetisCommandReturn result = MetisCommandReturn_Failure; + uint64_t seqnum = cpi_GetSequenceNumber(control); + + CCNxMetaMessage *requestMessage = ccnxMetaMessage_CreateFromControl(control); + CCNxMetaMessage *responseMessage = metisControlState_WriteRead(metis_State, requestMessage); + ccnxMetaMessage_Release(&requestMessage); + + if (metisControlState_GetDebug(metis_State)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(responseMessage)); + printf("reponse:\n%s\n", str); + parcMemory_Deallocate((void **) &str); + } + + if (ccnxMetaMessage_IsControl(responseMessage)) { + CCNxControl *responseControl = ccnxMetaMessage_GetControl(responseMessage); + if (ccnxControl_IsACK(responseControl)) { + uint64_t ackedSeqnum = cpiAcks_GetAckOriginalSequenceNumber(ccnxControl_GetJson(responseControl)); + if (ackedSeqnum == seqnum) { + result = MetisCommandReturn_Success; + } else { + printf("Error: received wrong seqnum expected %" PRIu64 " got %" PRIu64 "\n", seqnum, ackedSeqnum); + } + } + } + + ccnxMetaMessage_Release(&responseMessage); + return result; +} + +static MetisCommandReturn +_createTcpListener(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + MetisCommandReturn result = MetisCommandReturn_Failure; + + const char *symbolic = parcList_GetAtIndex(args, _indexSymbolic); + const char *host = parcList_GetAtIndex(args, _indexAddress); + const char *port = parcList_GetAtIndex(args, _indexPort); + + CPIAddress *socket = _convertStringsToCpiAddress(host, port); + if (socket) { + CPIListener *listener = cpiListener_CreateIP(IPTUN_TCP, socket, symbolic); + CCNxControl *control = cpiListener_CreateAddMessage(listener); + + MetisControlState *metis_State = ops->closure; + result = _sendAndVerify(metis_State, control); + ccnxControl_Release(&control); + cpiListener_Release(&listener); + cpiAddress_Destroy(&socket); + } + + return result; +} + +static MetisCommandReturn +_createUdpListener(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + MetisCommandReturn result = MetisCommandReturn_Failure; + + const char *symbolic = parcList_GetAtIndex(args, _indexSymbolic); + const char *host = parcList_GetAtIndex(args, _indexAddress); + const char *port = parcList_GetAtIndex(args, _indexPort); + + CPIAddress *socket = _convertStringsToCpiAddress(host, port); + if (socket) { + CPIListener *listener = cpiListener_CreateIP(IPTUN_UDP, socket, symbolic); + CCNxControl *control = cpiListener_CreateAddMessage(listener); + + MetisControlState *metis_State = ops->closure; + result = _sendAndVerify(metis_State, control); + ccnxControl_Release(&control); + cpiListener_Release(&listener); + cpiAddress_Destroy(&socket); + } + + return result; +} + +static MetisCommandReturn +_createEtherListener(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + MetisCommandReturn result = MetisCommandReturn_Failure; + + const char *symbolic = parcList_GetAtIndex(args, _indexSymbolic); + const char *ifname = parcList_GetAtIndex(args, _indexAddress); + uint16_t ethertype = (uint16_t) strtoul(parcList_GetAtIndex(args, _indexPort), NULL, 0); + + { + CPIListener *listener = cpiListener_CreateEther(ifname, (uint16_t) ethertype, symbolic); + CCNxControl *control = cpiListener_CreateAddMessage(listener); + + MetisControlState *metis_State = ops->closure; + result = _sendAndVerify(metis_State, control); + ccnxControl_Release(&control); + cpiListener_Release(&listener); + } + + return result; +} + + +static MetisCommandReturn +_metisControlAddListener_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + if (parcList_Size(args) != 6) { + _metisControlAddListener_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + MetisCommandReturn result = MetisCommandReturn_Failure; + + const char *symbolic = parcList_GetAtIndex(args, _indexSymbolic); + if (_validateSymbolicName(symbolic)) { + const char *protocol = parcList_GetAtIndex(args, _indexProtocol); + if (strcasecmp("tcp", protocol) == 0) { + result = _createTcpListener(parser, ops, args); + } else if (strcasecmp("udp", protocol) == 0) { + result = _createUdpListener(parser, ops, args); + } else if (strcasecmp("ether", protocol) == 0) { + result = _createEtherListener(parser, ops, args); + } else { + printf("Error: unrecognized protocol '%s'\n", protocol); + } + } else { + printf("Error: symbolic name must begin with an alpha and be alphanum after\n"); + } + + + return result; +} + + + diff --git a/metis/ccnx/forwarder/metis/config/metisControl_AddListener.h b/metis/ccnx/forwarder/metis/config/metisControl_AddListener.h new file mode 100644 index 00000000..847dfe34 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_AddListener.h @@ -0,0 +1,30 @@ +/* + * 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 metisControl_AddListener.h + * @brief Add a listener to an interface + * + * <#Detailed Description#> + * + */ + +#ifndef Metis_metisControl_AddListener_h +#define Metis_metisControl_AddListener_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlAddListener_Create(MetisControlState *state); +MetisCommandOps *metisControlAddListener_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_AddListener_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_AddRoute.c b/metis/ccnx/forwarder/metis/config/metisControl_AddRoute.c new file mode 100644 index 00000000..215bcee1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_AddRoute.c @@ -0,0 +1,189 @@ +/* + * 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 <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> +#include <ccnx/api/control/cpi_NameRouteProtocolType.h> +#include <ccnx/api/control/cpi_RouteEntry.h> +#include <ccnx/api/control/cpi_Forwarding.h> + +#include <ccnx/forwarder/metis/config/metisControl_AddRoute.h> + +static MetisCommandReturn _metisControlAddRoute_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlAddRoute_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandAddRoute = "add route"; +static const char *_commandAddRouteHelp = "help add route"; + +MetisCommandOps * +metisControlAddRoute_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandAddRoute, NULL, _metisControlAddRoute_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlAddRoute_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandAddRouteHelp, NULL, _metisControlAddRoute_HelpExecute, metisCommandOps_Destroy); +} + +/** + * Return true if string is purely an integer + */ +static bool +_isNumber(const char *string) +{ + size_t len = strlen(string); + for (size_t i = 0; i < len; i++) { + if (!isdigit(string[i])) { + return false; + } + } + return true; +} + +/** + * A symbolic name must be at least 1 character and must begin with an alpha. + * The remainder must be an alphanum. + */ +static bool +_validateSymbolicName(const char *symbolic) +{ + bool success = false; + size_t len = strlen(symbolic); + if (len > 0) { + if (isalpha(symbolic[0])) { + success = true; + for (size_t i = 1; i < len; i++) { + if (!isalnum(symbolic[i])) { + success = false; + break; + } + } + } + } + return success; +} + +// ==================================================== + +static MetisCommandReturn +_metisControlAddRoute_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("commands:\n"); + printf(" add route <symbolic | connid> <prefix> <cost>\n"); + printf("\n"); + printf(" symbolic: The symbolic name for an exgress\n"); + printf(" connid: The egress connection id (see 'help list connections')\n"); + printf(" prefix: The CCNx name as a URI (e.g. lci:/foo/bar)\n"); + printf(" cost: positive integer representing cost\n"); + printf(" nexthop: Optional network endpoint on the connection\n"); + printf(" seconds: Create a route that will expire if not refresed within the lifetime\n"); + printf("\n"); + printf("Examples:\n"); + printf(" add route 7 lci:/foo/bar 1\n"); + printf(" adds route to prefix '/foo/bar' on egress connection 7 with cost 1\n"); + printf(" add route tun3 lci:/foo/bar 1\n"); + printf(" adds route to prefix '/foo/bar' on egress connection 'tun3' with cost 1\n"); + printf("\n"); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlAddRoute_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + MetisControlState *state = ops->closure; + + if (parcList_Size(args) != 5) { + _metisControlAddRoute_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + const char *symbolicOrConnid = parcList_GetAtIndex(args, 2); + + if (_validateSymbolicName(symbolicOrConnid) || _isNumber(symbolicOrConnid)) { + const char *prefixString = parcList_GetAtIndex(args, 3); + unsigned cost = atoi(parcList_GetAtIndex(args, 4)); + + if (cost == 0) { + printf("ERROR: cost must be positive integer, got %u from '%s'\n", cost, (char *) parcList_GetAtIndex(args, 4)); + return MetisCommandReturn_Failure; + } + + CCNxName *prefix = ccnxName_CreateFromCString(prefixString); + if (prefix == NULL) { + printf("ERROR: could not parse prefix '%s'\n", prefixString); + return MetisCommandReturn_Failure; + } + + char *protocolTypeAsString = "static"; + + CPINameRouteProtocolType protocolType = cpiNameRouteProtocolType_FromString(protocolTypeAsString); + CPINameRouteType routeType = cpiNameRouteType_LONGEST_MATCH; + CPIAddress *nexthop = NULL; + + struct timeval *lifetime = NULL; + + CPIRouteEntry *route = NULL; + + if (_isNumber(symbolicOrConnid)) { + unsigned connid = (unsigned) strtold(symbolicOrConnid, NULL); + route = cpiRouteEntry_Create(prefix, connid, nexthop, protocolType, routeType, lifetime, cost); + } else { + route = cpiRouteEntry_CreateSymbolic(prefix, symbolicOrConnid, protocolType, routeType, lifetime, cost); + } + + CCNxControl *addRouteRequest = ccnxControl_CreateAddRouteRequest(route); + + cpiRouteEntry_Destroy(&route); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(addRouteRequest)); + printf("request: %s\n", str); + parcMemory_Deallocate((void **) &str); + } + + CCNxMetaMessage *message = ccnxMetaMessage_CreateFromControl(addRouteRequest); + CCNxMetaMessage *rawResponse = metisControlState_WriteRead(state, message); + ccnxMetaMessage_Release(&message); + + ccnxControl_Release(&addRouteRequest); + + CCNxControl *response = ccnxMetaMessage_GetControl(rawResponse); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(response)); + printf("response: %s\n", str); + parcMemory_Deallocate((void **) &str); + } + + ccnxMetaMessage_Release(&rawResponse); + + return MetisCommandReturn_Success; + } else { + printf("ERROR: Invalid symbolic or connid. Symbolic name must begin with an alpha followed by alphanum. connid must be an integer\n"); + return MetisCommandReturn_Failure; + } +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_AddRoute.h b/metis/ccnx/forwarder/metis/config/metisControl_AddRoute.h new file mode 100644 index 00000000..7ffda24c --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_AddRoute.h @@ -0,0 +1,30 @@ +/* + * 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 metisControl_AddRoute.h + * @brief Add a static route + * + * Implements the "add route" node of the CLI tree + * + */ + +#ifndef Metis_metisControl_AddRoute_h +#define Metis_metisControl_AddRoute_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlAddRoute_Create(MetisControlState *state); +MetisCommandOps *metisControlAddRoute_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_AddRoute_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Cache.c b/metis/ccnx/forwarder/metis/config/metisControl_Cache.c new file mode 100644 index 00000000..c2fde132 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Cache.c @@ -0,0 +1,95 @@ +/* + * 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 <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/security/parc_Security.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/config/metisControl_Cache.h> +#include <ccnx/forwarder/metis/config/metisControl_CacheServe.h> +#include <ccnx/forwarder/metis/config/metisControl_CacheStore.h> +#include <ccnx/forwarder/metis/config/metisControl_CacheClear.h> + +static void _metisControlCache_Init(MetisCommandParser *parser, MetisCommandOps *ops); +static MetisCommandReturn _metisControlCache_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlCache_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandCache = "cache"; +static const char *_commandCacheHelp = "help cache"; + +MetisCommandOps * +metisControlCache_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandCache, _metisControlCache_Init, _metisControlCache_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlCache_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandCacheHelp, NULL, _metisControlCache_HelpExecute, metisCommandOps_Destroy); +} + +// ===================================================== + +static MetisCommandReturn +_metisControlCache_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + MetisCommandOps *ops_cache_serve = metisControlCacheServe_HelpCreate(NULL); + MetisCommandOps *ops_cache_store = metisControlCacheStore_HelpCreate(NULL); + MetisCommandOps *ops_cache_clear = metisControlCacheClear_HelpCreate(NULL); + + printf("Available commands:\n"); + printf(" %s\n", ops_cache_serve->command); + printf(" %s\n", ops_cache_store->command); + printf(" %s\n", ops_cache_clear->command); + printf("\n"); + + metisCommandOps_Destroy(&ops_cache_serve); + metisCommandOps_Destroy(&ops_cache_store); + metisCommandOps_Destroy(&ops_cache_clear); + + return MetisCommandReturn_Success; +} + +static void +_metisControlCache_Init(MetisCommandParser *parser, MetisCommandOps *ops) +{ + MetisControlState *state = ops->closure; + metisControlState_RegisterCommand(state, metisControlCacheServe_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlCacheStore_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlCacheClear_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlCacheServe_Create(state)); + metisControlState_RegisterCommand(state, metisControlCacheStore_Create(state)); + metisControlState_RegisterCommand(state, metisControlCacheClear_Create(state)); +} + +static MetisCommandReturn +_metisControlCache_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + return _metisControlCache_HelpExecute(parser, ops, args); +} + +// ====================================================================== diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Cache.h b/metis/ccnx/forwarder/metis/config/metisControl_Cache.h new file mode 100644 index 00000000..94f614f2 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Cache.h @@ -0,0 +1,22 @@ +/* + * 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_ControlCache_h +#define Metis_metis_ControlCache_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlCache_Create(MetisControlState *state); +MetisCommandOps *metisControlCache_HelpCreate(MetisControlState *state); +#endif // Metis_metis_ControlCache_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_CacheClear.c b/metis/ccnx/forwarder/metis/config/metisControl_CacheClear.c new file mode 100644 index 00000000..1b2c2e8f --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_CacheClear.c @@ -0,0 +1,97 @@ +/* + * 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 <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/config/metisControl_CacheClear.h> + +#include <ccnx/api/control/cpi_ManageLinks.h> +#include <ccnx/api/control/cpi_Acks.h> +#include <ccnx/api/control/cpi_Forwarding.h> + +static MetisCommandReturn _metisControlCacheClear_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlCacheClear_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandCacheClear = "cache clear"; +static const char *_commandCacheClearHelp = "help cache clear"; + +// ==================================================== + +MetisCommandOps * +metisControlCacheClear_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandCacheClear, NULL, _metisControlCacheClear_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlCacheClear_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandCacheClearHelp, NULL, _metisControlCacheClear_HelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + +static MetisCommandReturn +_metisControlCacheClear_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("cache clear\n"); + printf("\n"); + + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlCacheClear_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + if (parcList_Size(args) != 2) { + _metisControlCacheClear_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + CCNxControl * cacheRequest = ccnxControl_CreateCacheClearRequest(); + + MetisControlState *state = ops->closure; + CCNxMetaMessage *message = ccnxMetaMessage_CreateFromControl(cacheRequest); + CCNxMetaMessage *rawResponse = metisControlState_WriteRead(state, message); + ccnxMetaMessage_Release(&message); + + CCNxControl *response = ccnxMetaMessage_GetControl(rawResponse); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(response)); + printf("reponse:\n%s\n", str); + parcMemory_Deallocate((void **) &str); + } + + if(!cpiAcks_IsAck(ccnxControl_GetJson(response))){ + printf("command failed\n"); + } + + ccnxMetaMessage_Release(&rawResponse); + + return MetisCommandReturn_Success; + +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_CacheClear.h b/metis/ccnx/forwarder/metis/config/metisControl_CacheClear.h new file mode 100644 index 00000000..126345be --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_CacheClear.h @@ -0,0 +1,30 @@ +/* + * 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 metisControl_ListInterfaces.h + * @brief List the metis interfaces + * + * Implements the "list interfaces" and "help list interfaces" nodes of the command tree + * + */ + +#ifndef Metis_metisControl_CacheClear_h +#define Metis_metisControl_CacheClear_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlCacheClear_Create(MetisControlState *state); +MetisCommandOps *metisControlCacheClear_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_CacheClear_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_CacheServe.c b/metis/ccnx/forwarder/metis/config/metisControl_CacheServe.c new file mode 100644 index 00000000..6fb7bf4d --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_CacheServe.c @@ -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. + */ + + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/config/metisControl_CacheServe.h> + +#include <ccnx/api/control/cpi_ManageLinks.h> +#include <ccnx/api/control/cpi_Acks.h> +#include <ccnx/api/control/cpi_Forwarding.h> + +static MetisCommandReturn _metisControlCacheServe_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlCacheServe_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandCacheServe = "cache serve"; +static const char *_commandCacheServeHelp = "help cache serve"; + +// ==================================================== + +MetisCommandOps * +metisControlCacheServe_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandCacheServe, NULL, _metisControlCacheServe_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlCacheServe_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandCacheServeHelp, NULL, _metisControlCacheServe_HelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + +static MetisCommandReturn +_metisControlCacheServe_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("cache serve [on|off]\n"); + printf("\n"); + + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlCacheServe_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + if (parcList_Size(args) != 3) { + _metisControlCacheServe_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + CCNxControl *cacheRequest; + if (strcmp(parcList_GetAtIndex(args, 2), "on") == 0) { + cacheRequest = ccnxControl_CreateCacheServeRequest(true); + } else if (strcmp(parcList_GetAtIndex(args, 2), "off") == 0) { + cacheRequest = ccnxControl_CreateCacheServeRequest(false); + } else { + _metisControlCacheServe_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + MetisControlState *state = ops->closure; + CCNxMetaMessage *message = ccnxMetaMessage_CreateFromControl(cacheRequest); + CCNxMetaMessage *rawResponse = metisControlState_WriteRead(state, message); + ccnxMetaMessage_Release(&message); + + CCNxControl *response = ccnxMetaMessage_GetControl(rawResponse); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(response)); + printf("reponse:\n%s\n", str); + parcMemory_Deallocate((void **) &str); + } + + if (!cpiAcks_IsAck(ccnxControl_GetJson(response))) { + printf("command failed\n"); + } + + ccnxMetaMessage_Release(&rawResponse); + + return MetisCommandReturn_Success; +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_CacheServe.h b/metis/ccnx/forwarder/metis/config/metisControl_CacheServe.h new file mode 100644 index 00000000..a0e45a1c --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_CacheServe.h @@ -0,0 +1,22 @@ +/* + * 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_metisControl_CacheServe_h +#define Metis_metisControl_CacheServe_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlCacheServe_Create(MetisControlState *state); +MetisCommandOps *metisControlCacheServe_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_CacheServe_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_CacheStore.c b/metis/ccnx/forwarder/metis/config/metisControl_CacheStore.c new file mode 100644 index 00000000..1a620b06 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_CacheStore.c @@ -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. + */ + + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/config/metisControl_CacheStore.h> + +#include <ccnx/api/control/cpi_ManageLinks.h> +#include <ccnx/api/control/cpi_Acks.h> +#include <ccnx/api/control/cpi_Forwarding.h> + +static MetisCommandReturn _metisControlCacheStore_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlCacheStore_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandCacheStore = "cache store"; +static const char *_commandCacheStoreHelp = "help cache store"; + +// ==================================================== + +MetisCommandOps * +metisControlCacheStore_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandCacheStore, NULL, _metisControlCacheStore_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlCacheStore_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandCacheStoreHelp, NULL, _metisControlCacheStore_HelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + +static MetisCommandReturn +_metisControlCacheStore_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("cache store [on|off]\n"); + printf("\n"); + + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlCacheStore_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + if (parcList_Size(args) != 3) { + _metisControlCacheStore_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + CCNxControl *cacheRequest; + if (strcmp(parcList_GetAtIndex(args, 2), "on") == 0) { + cacheRequest = ccnxControl_CreateCacheStoreRequest(true); + } else if (strcmp(parcList_GetAtIndex(args, 2), "off") == 0) { + cacheRequest = ccnxControl_CreateCacheStoreRequest(false); + } else { + _metisControlCacheStore_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + MetisControlState *state = ops->closure; + CCNxMetaMessage *message = ccnxMetaMessage_CreateFromControl(cacheRequest); + CCNxMetaMessage *rawResponse = metisControlState_WriteRead(state, message); + ccnxMetaMessage_Release(&message); + + CCNxControl *response = ccnxMetaMessage_GetControl(rawResponse); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(response)); + printf("reponse:\n%s\n", str); + parcMemory_Deallocate((void **) &str); + } + + if (!cpiAcks_IsAck(ccnxControl_GetJson(response))) { + printf("command failed:\n"); + } + + ccnxMetaMessage_Release(&rawResponse); + + return MetisCommandReturn_Success; +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_CacheStore.h b/metis/ccnx/forwarder/metis/config/metisControl_CacheStore.h new file mode 100644 index 00000000..02511ea3 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_CacheStore.h @@ -0,0 +1,22 @@ +/* + * 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_metisControl_CacheStore_h +#define Metis_metisControl_CacheStore_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlCacheStore_Create(MetisControlState *state); +MetisCommandOps *metisControlCacheStore_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_CacheStore_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_List.c b/metis/ccnx/forwarder/metis/config/metisControl_List.c new file mode 100644 index 00000000..76995a31 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_List.c @@ -0,0 +1,95 @@ +/* + * 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 <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/security/parc_Security.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/config/metisControl_List.h> +#include <ccnx/forwarder/metis/config/metisControl_ListConnections.h> +#include <ccnx/forwarder/metis/config/metisControl_ListInterfaces.h> +#include <ccnx/forwarder/metis/config/metisControl_ListRoutes.h> + +static void _metisControlList_Init(MetisCommandParser *parser, MetisCommandOps *ops); +static MetisCommandReturn _metisControlList_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlList_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandList = "list"; +static const char *_commandListHelp = "help list"; + +MetisCommandOps * +metisControlList_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandList, _metisControlList_Init, _metisControlList_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlList_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandListHelp, NULL, _metisControlList_HelpExecute, metisCommandOps_Destroy); +} + +// ===================================================== + +static MetisCommandReturn +_metisControlList_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + MetisCommandOps *ops_list_connections = metisControlListConnections_HelpCreate(NULL); + MetisCommandOps *ops_list_interfaces = metisControlListInterfaces_HelpCreate(NULL); + MetisCommandOps *ops_list_routes = metisControlListRoutes_HelpCreate(NULL); + + printf("Available commands:\n"); + printf(" %s\n", ops_list_connections->command); + printf(" %s\n", ops_list_interfaces->command); + printf(" %s\n", ops_list_routes->command); + printf("\n"); + + metisCommandOps_Destroy(&ops_list_connections); + metisCommandOps_Destroy(&ops_list_interfaces); + metisCommandOps_Destroy(&ops_list_routes); + + return MetisCommandReturn_Success; +} + +static void +_metisControlList_Init(MetisCommandParser *parser, MetisCommandOps *ops) +{ + MetisControlState *state = ops->closure; + metisControlState_RegisterCommand(state, metisControlListConnections_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlListInterfaces_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlListRoutes_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlListConnections_Create(state)); + metisControlState_RegisterCommand(state, metisControlListInterfaces_Create(state)); + metisControlState_RegisterCommand(state, metisControlListRoutes_Create(state)); +} + +static MetisCommandReturn +_metisControlList_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + return _metisControlList_HelpExecute(parser, ops, args); +} + +// ====================================================================== diff --git a/metis/ccnx/forwarder/metis/config/metisControl_List.h b/metis/ccnx/forwarder/metis/config/metisControl_List.h new file mode 100644 index 00000000..95ed7f82 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_List.h @@ -0,0 +1,30 @@ +/* + * 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 metisControl_List.h + * @brief Root node for the "list" commands + * + * Implements the "list" node of the CLI tree. + * + */ + +#ifndef Metis_metis_ControlList_h +#define Metis_metis_ControlList_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlList_Create(MetisControlState *state); +MetisCommandOps *metisControlList_HelpCreate(MetisControlState *state); +#endif // Metis_metis_ControlList_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_ListConnections.c b/metis/ccnx/forwarder/metis/config/metisControl_ListConnections.c new file mode 100644 index 00000000..a6a645dd --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_ListConnections.c @@ -0,0 +1,108 @@ +/* + * 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 <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/config/metisControl_ListConnections.h> + +#include <ccnx/api/control/cpi_ManageLinks.h> +#include <ccnx/api/control/cpi_Forwarding.h> + +static MetisCommandReturn _metisControlListConnections_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlListConnections_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandListConnections = "list connections"; +static const char *_commandListConnectionsHelp = "help list connections"; + +MetisCommandOps * +metisControlListConnections_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandListConnections, NULL, _metisControlListConnections_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlListConnections_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandListConnectionsHelp, NULL, _metisControlListConnections_HelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + +static MetisCommandReturn +_metisControlListConnections_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("list connections: displays a 1-line summary of each connection\n"); + printf("\n"); + printf("The columns are:\n"); + printf(" connection id : an integer index for the connection\n"); + printf(" state : UP or DOWN\n"); + printf(" local address : the local network address associated with the connection\n"); + printf(" remote address: the remote network address associated with the connection\n"); + printf(" protocol : the network protocol (tcp, udp, gre, mcast, ether)\n"); + printf("\n"); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlListConnections_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + if (parcList_Size(args) != 2) { + _metisControlListConnections_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + MetisControlState *state = ops->closure; + + CCNxControl *connectionListRequest = ccnxControl_CreateConnectionListRequest(); + + CCNxMetaMessage *message = ccnxMetaMessage_CreateFromControl(connectionListRequest); + CCNxMetaMessage *rawResponse = metisControlState_WriteRead(state, message); + ccnxMetaMessage_Release(&message); + + CCNxControl *response = ccnxMetaMessage_GetControl(rawResponse); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(response)); + printf("reponse:\n%s\n", str); + parcMemory_Deallocate((void **) &str); + } + + CPIConnectionList *list = cpiLinks_ConnectionListFromControlMessage(response); + // + // //"%3u %10s %1s%1s %8u " + // // printf("%3.3s %10.10s %1.1s%1.1s %8.8s \n", "interface", "name", "loopback", "multicast", "MTU"); + for (size_t i = 0; i < cpiConnectionList_Length(list); i++) { + CPIConnection *connection = cpiConnectionList_Get(list, i); + char *string = cpiConnection_ToString(connection); + puts(string); + parcMemory_Deallocate((void **) &string); + cpiConnection_Release(&connection); + } + cpiConnectionList_Destroy(&list); + ccnxMetaMessage_Release(&rawResponse); + + return MetisCommandReturn_Success; +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_ListConnections.h b/metis/ccnx/forwarder/metis/config/metisControl_ListConnections.h new file mode 100644 index 00000000..4c9d5146 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_ListConnections.h @@ -0,0 +1,30 @@ +/* + * 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 metisControl_ListConnections.h + * @brief List the current connections of metis + * + * Implements the "list connections" node of the CLI tree + * + */ + +#ifndef Metis_metisControl_ListConnections_h +#define Metis_metisControl_ListConnections_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlListConnections_Create(MetisControlState *state); +MetisCommandOps *metisControlListConnections_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_ListConnections_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_ListInterfaces.c b/metis/ccnx/forwarder/metis/config/metisControl_ListInterfaces.c new file mode 100644 index 00000000..d08db325 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_ListInterfaces.c @@ -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. + */ + + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/config/metisControl_ListInterfaces.h> + +#include <ccnx/api/control/cpi_ManageLinks.h> +#include <ccnx/api/control/cpi_Forwarding.h> + +static MetisCommandReturn _metisControlListInterfaces_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlListInterfaces_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandListInterfaces = "list interfaces"; +static const char *_commandListInterfacesHelp = "help list interfaces"; + +// ==================================================== + +MetisCommandOps * +metisControlListInterfaces_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandListInterfaces, NULL, _metisControlListInterfaces_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlListInterfaces_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandListInterfacesHelp, NULL, _metisControlListInterfaces_HelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + +static MetisCommandReturn +_metisControlListInterfaces_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("list interfaces\n"); + printf("\n"); + + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlListInterfaces_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + if (parcList_Size(args) != 2) { + _metisControlListInterfaces_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + MetisControlState *state = ops->closure; + CCNxControl *listRequest = ccnxControl_CreateInterfaceListRequest(); + + CCNxMetaMessage *message = ccnxMetaMessage_CreateFromControl(listRequest); + CCNxMetaMessage *rawResponse = metisControlState_WriteRead(state, message); + ccnxMetaMessage_Release(&message); + + CCNxControl *response = ccnxMetaMessage_GetControl(rawResponse); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(response)); + printf("reponse:\n%s\n", str); + parcMemory_Deallocate((void **) &str); + } + + CPIInterfaceSet *set = cpiLinks_InterfacesFromControlMessage(response); + + //"%3u %10s %1s%1s %8u " + printf("%3.3s %10.10s %1.1s%1.1s %8.8s \n", "interface", "name", "loopback", "multicast", "MTU"); + for (size_t i = 0; i < cpiInterfaceSet_Length(set); i++) { + CPIInterface *interface = cpiInterfaceSet_GetByOrdinalIndex(set, i); + char *string = cpiInterface_ToString(interface); + puts(string); + parcMemory_Deallocate((void **) &string); + } + + cpiInterfaceSet_Destroy(&set); + + ccnxMetaMessage_Release(&rawResponse); + + return MetisCommandReturn_Success; +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_ListInterfaces.h b/metis/ccnx/forwarder/metis/config/metisControl_ListInterfaces.h new file mode 100644 index 00000000..9d874ab1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_ListInterfaces.h @@ -0,0 +1,30 @@ +/* + * 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 metisControl_ListInterfaces.h + * @brief List the metis interfaces + * + * Implements the "list interfaces" and "help list interfaces" nodes of the command tree + * + */ + +#ifndef Metis_metisControl_ListInterfaces_h +#define Metis_metisControl_ListInterfaces_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlListInterfaces_Create(MetisControlState *state); +MetisCommandOps *metisControlListInterfaces_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_ListInterfaces_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_ListRoutes.c b/metis/ccnx/forwarder/metis/config/metisControl_ListRoutes.c new file mode 100644 index 00000000..0f84fa3a --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_ListRoutes.c @@ -0,0 +1,146 @@ +/* + * 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 <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_Time.h> + +#include <ccnx/forwarder/metis/config/metisControl_ListRoutes.h> + +#include <ccnx/api/control/cpi_ManageLinks.h> +#include <ccnx/api/control/cpi_Forwarding.h> + +static MetisCommandReturn _metisControlListRoutes_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlListRoutes_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandListRoutes = "list routes"; +static const char *_commandListRoutesHelp = "help list routes"; + +// ==================================================== + +MetisCommandOps * +metisControlListRoutes_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandListRoutes, NULL, _metisControlListRoutes_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlListRoutes_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandListRoutesHelp, NULL, _metisControlListRoutes_HelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + +static MetisCommandReturn +_metisControlListRoutes_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("command: list routes\n"); + printf("\n"); + printf("This command will fetch the prefix routing table. For each route, it will list:\n"); + printf(" iface: interface\n"); + printf(" protocol: the routing protocol, such as STATIC, CONNECTED, etc.\n"); + printf(" type: LMP or EXACT (longest matching prefix or exact match)\n"); + printf(" cost: The route cost, lower being preferred\n"); + printf(" next: List of next hops by interface id\n"); + printf(" prefix: The CCNx name prefix\n"); + printf("\n"); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlListRoutes_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + if (parcList_Size(args) != 2) { + _metisControlListRoutes_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + MetisControlState *state = ops->closure; + + CCNxControl *routeListRequest = ccnxControl_CreateRouteListRequest(); + + CCNxMetaMessage *message = ccnxMetaMessage_CreateFromControl(routeListRequest); + + CCNxMetaMessage *rawResponse = metisControlState_WriteRead(state, message); + + CCNxControl *response = ccnxMetaMessage_GetControl(rawResponse); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(response)); + printf("reponse:\n%s\n", str); + parcMemory_Deallocate((void **) &str); + } + + CPIRouteEntryList *list = cpiForwarding_RouteListFromControlMessage(response); + + printf("%6.6s %9.9s %7.7s %8.8s %20.20s %s\n", "iface", "protocol", "route", "cost", "next", "prefix"); + + for (size_t i = 0; i < cpiRouteEntryList_Length(list); i++) { + CPIRouteEntry *route = cpiRouteEntryList_Get(list, i); + + PARCBufferComposer *composer = parcBufferComposer_Create(); + + parcBufferComposer_Format(composer, "%6d %9.9s %7.7s %8u ", + cpiRouteEntry_GetInterfaceIndex(route), + cpiNameRouteProtocolType_ToString(cpiRouteEntry_GetRouteProtocolType(route)), + cpiNameRouteType_ToString(cpiRouteEntry_GetRouteType(route)), + cpiRouteEntry_GetCost(route)); + + if (cpiRouteEntry_GetNexthop(route) != NULL) { + cpiAddress_BuildString(cpiRouteEntry_GetNexthop(route), composer); + } else { + parcBufferComposer_PutString(composer, "---.---.---.---/...."); + } + + if (cpiRouteEntry_HasLifetime(route)) { + char *timeString = parcTime_TimevalAsString(cpiRouteEntry_GetLifetime(route)); + parcBufferComposer_PutString(composer, timeString); + parcMemory_Deallocate((void **) &timeString); + } else { + parcBufferComposer_PutString(composer, " "); + } + + char *ccnxName = ccnxName_ToString(cpiRouteEntry_GetPrefix(route)); + parcBufferComposer_PutString(composer, ccnxName); + parcMemory_Deallocate((void **) &ccnxName); + + PARCBuffer *tempBuffer = parcBufferComposer_ProduceBuffer(composer); + char *result = parcBuffer_ToString(tempBuffer); + parcBuffer_Release(&tempBuffer); + + puts(result); + parcMemory_Deallocate((void **) &result); + parcBufferComposer_Release(&composer); + cpiRouteEntry_Destroy(&route); + } + + cpiRouteEntryList_Destroy(&list); + ccnxMetaMessage_Release(&rawResponse); + ccnxControl_Release(&routeListRequest); + + printf("Done\n\n"); + + return MetisCommandReturn_Success; +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_ListRoutes.h b/metis/ccnx/forwarder/metis/config/metisControl_ListRoutes.h new file mode 100644 index 00000000..f7611259 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_ListRoutes.h @@ -0,0 +1,29 @@ +/* + * 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 metisControl_ListRoutes.h + * @brief List the metis routes + * + * Implements the "list routes" and "help list routes" nodes of the command tree + * + */ +#ifndef Metis_metisControl_ListRoutes_h +#define Metis_metisControl_ListRoutes_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlListRoutes_Create(MetisControlState *state); +MetisCommandOps *metisControlListRoutes_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_ListRoutes_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Quit.c b/metis/ccnx/forwarder/metis/config/metisControl_Quit.c new file mode 100644 index 00000000..c8b0e783 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Quit.c @@ -0,0 +1,66 @@ +/* + * 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 <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/security/parc_Security.h> +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/config/metisControl_Quit.h> + +static MetisCommandReturn _metisControlQuit_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlQuit_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandQuit = "quit"; +static const char *_commandQuitHelp = "help quit"; + +// ==================================================== + +MetisCommandOps * +metisControlQuit_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandQuit, NULL, _metisControlQuit_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlQuit_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandQuitHelp, NULL, _metisControlQuit_HelpExecute, metisCommandOps_Destroy); +} + +// ============================================== + +static MetisCommandReturn +_metisControlQuit_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("Exits the interactive control program\n\n"); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlQuit_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("exiting interactive shell\n"); + return MetisCommandReturn_Exit; +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Quit.h b/metis/ccnx/forwarder/metis/config/metisControl_Quit.h new file mode 100644 index 00000000..a3c278f8 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Quit.h @@ -0,0 +1,29 @@ +/* + * 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 metisControl_Quit.h + * @brief The quit command + * + * Implements the "quit" and "help quit" nodes of the command tree + * + */ +#ifndef Metis_metisControl_Quit_h +#define Metis_metisControl_Quit_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlQuit_Create(MetisControlState *state); +MetisCommandOps *metisControlQuit_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_Quit_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Remove.c b/metis/ccnx/forwarder/metis/config/metisControl_Remove.c new file mode 100644 index 00000000..2b494bf3 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Remove.c @@ -0,0 +1,87 @@ +/* + * 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 <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/security/parc_Security.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/config/metisControl_Remove.h> +#include <ccnx/forwarder/metis/config/metisControl_RemoveConnection.h> +#include <ccnx/forwarder/metis/config/metisControl_RemoveRoute.h> + +static void _metisControlRemove_Init(MetisCommandParser *parser, MetisCommandOps *ops); +static MetisCommandReturn _metisControlRemove_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlRemove_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandRemove = "remove"; +static const char *_commandRemoveHelp = "help remove"; + +// ==================================================== + +MetisCommandOps * +metisControlRemove_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandRemove, _metisControlRemove_Init, _metisControlRemove_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlRemove_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandRemoveHelp, NULL, _metisControlRemove_HelpExecute, metisCommandOps_Destroy); +} + +// ============================================== + +static MetisCommandReturn +_metisControlRemove_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + MetisCommandOps *ops_remove_connection = metisControlRemoveConnection_Create(NULL); + MetisCommandOps *ops_remove_route = metisControlRemoveRoute_Create(NULL); + + printf("Available commands:\n"); + printf(" %s\n", ops_remove_connection->command); + printf(" %s\n", ops_remove_route->command); + printf("\n"); + + metisCommandOps_Destroy(&ops_remove_connection); + metisCommandOps_Destroy(&ops_remove_route); + return MetisCommandReturn_Success; +} + +static void +_metisControlRemove_Init(MetisCommandParser *parser, MetisCommandOps *ops) +{ + MetisControlState *state = ops->closure; + metisControlState_RegisterCommand(state, metisControlRemoveConnection_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlRemoveRoute_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlRemoveConnection_Create(state)); + metisControlState_RegisterCommand(state, metisControlRemoveRoute_Create(state)); +} + +static MetisCommandReturn +_metisControlRemove_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + return _metisControlRemove_HelpExecute(parser, ops, args); +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Remove.h b/metis/ccnx/forwarder/metis/config/metisControl_Remove.h new file mode 100644 index 00000000..b6ddc229 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Remove.h @@ -0,0 +1,29 @@ +/* + * 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 metisControl_Remove.h + * @brief Implements the remove node of the CLI tree + * + * Implements the "remove" and "help remove" nodes of the command tree + * + */ +#ifndef Metis_metis_ControlRemove_h +#define Metis_metis_ControlRemove_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlRemove_Create(MetisControlState *state); +MetisCommandOps *metisControlRemove_HelpCreate(MetisControlState *state); +#endif // Metis_metis_ControlRemove_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_RemoveConnection.c b/metis/ccnx/forwarder/metis/config/metisControl_RemoveConnection.c new file mode 100644 index 00000000..12c796c1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_RemoveConnection.c @@ -0,0 +1,224 @@ +/* + * 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 <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_Network.h> +#include <ccnx/api/control/cpi_Address.h> +#include <ccnx/api/control/cpi_InterfaceIPTunnel.h> +#include <ccnx/api/control/cpi_ManageLinks.h> +#include <ccnx/api/control/cpi_ConnectionEthernet.h> + +#include <ccnx/forwarder/metis/config/metisControl_RemoveConnection.h> + +static void _metisControlRemoveConnection_Init(MetisCommandParser *parser, MetisCommandOps *ops); +static MetisCommandReturn _metisControlRemoveConnection_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlRemoveConnection_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +// =================================================== + +//TODO: implement this also for TCP and ethernet + +static MetisCommandReturn _metisControlRemoveConnection_UdpHelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlRemoveConnection_UdpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +// =================================================== + + +static const char *_commandRemoveConnection = "remove connection"; +static const char *_commandRemoveConnectionUdp = "remove connection udp"; +static const char *_commandRemoveConnectionHelp = "help remove connection"; +static const char *_commandRemoveConnectionUdpHelp = "help remove connection udp"; + +// ==================================================== + +MetisCommandOps * +metisControlRemoveConnection_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandRemoveConnection, _metisControlRemoveConnection_Init, _metisControlRemoveConnection_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlRemoveConnection_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandRemoveConnectionHelp, NULL, _metisControlRemoveConnection_HelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + +static MetisCommandOps * +_metisControlRemoveConnection_UdpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandRemoveConnectionUdp, NULL, + _metisControlRemoveConnection_UdpExecute, metisCommandOps_Destroy); +} + +static MetisCommandOps * +_metisControlRemoveConnection_UdpHelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandRemoveConnectionUdpHelp, NULL, + _metisControlRemoveConnection_UdpHelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + +/** + * A symbolic name must be at least 1 character and must begin with an alpha. + * The remainder must be an alphanum. + */ +static bool +_validateSymbolicName(const char *symbolic) +{ + bool success = false; + size_t len = strlen(symbolic); + if (len > 0) { + if (isalpha(symbolic[0])) { + success = true; + for (size_t i = 1; i < len; i++) { + if (!isalnum(symbolic[i])) { + success = false; + break; + } + } + } + } + return success; +} + +// ==================================================== + +static MetisCommandReturn +_metisControlRemoveConnection_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("Available commands:\n"); + printf(" %s\n", _commandRemoveConnectionUdp); + return MetisCommandReturn_Success; +} + +static void +_metisControlRemoveConnection_Init(MetisCommandParser *parser, MetisCommandOps *ops) +{ + MetisControlState *state = ops->closure; + metisControlState_RegisterCommand(state, _metisControlRemoveConnection_UdpHelpCreate(state)); + + metisControlState_RegisterCommand(state, _metisControlRemoveConnection_UdpCreate(state)); +} + + +static MetisCommandReturn +_metisControlRemoveConnection_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + return _metisControlRemoveConnection_HelpExecute(parser, ops, args); +} + +// ================================================== + +static bool +_parseMessage(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args, char **symbolicPtr) +{ + if (parcList_Size(args) != 4) { + _metisControlRemoveConnection_UdpHelpExecute(parser, ops, args); + return false; + } + + if ((strcmp(parcList_GetAtIndex(args, 0), "remove") != 0) || + (strcmp(parcList_GetAtIndex(args, 1), "connection") != 0) || + (strcmp(parcList_GetAtIndex(args, 2), "udp") != 0)) { + _metisControlRemoveConnection_UdpHelpExecute(parser, ops, args); + return false; + } + + char *symbolic = parcList_GetAtIndex(args, 3); + if (_validateSymbolicName(symbolic)) { + *symbolicPtr = symbolic; + return true; + } + return false; +} + +static void +_removeUdpConnection(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args, char *symbolic) +{ + MetisControlState *state = ops->closure; + struct sockaddr_in *local = parcNetwork_SockInet4AddressAny(); //parcNetwork_SockInet4Address("192.168.56.27", 12346); + struct sockaddr_in *remote = parcNetwork_SockInet4AddressAny(); //parcNetwork_SockInet4Address("192.168.62.10", 19695); + CPIAddress *localAddress = cpiAddress_CreateFromInet(local); + CPIAddress *remoteAddress = cpiAddress_CreateFromInet(remote); + + CPIInterfaceIPTunnel *ipTunnel = cpiInterfaceIPTunnel_Create(0, localAddress, remoteAddress, IPTUN_UDP, symbolic); + + PARCJSON *cpiMessage = cpiLinks_RemoveIPTunnel(ipTunnel); + CCNxControl *controlMessage = ccnxControl_CreateCPIRequest(cpiMessage); + parcJSON_Release(&cpiMessage); + + CCNxMetaMessage *message = ccnxMetaMessage_CreateFromControl(controlMessage); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(message)); + printf("request: %s\n", str); + parcMemory_Deallocate((void **) &str); + } + + + CCNxMetaMessage *rawResponse = metisControlState_WriteRead(state, message); + ccnxMetaMessage_Release(&message); + + CCNxControl *response = ccnxMetaMessage_GetControl(rawResponse); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(response)); + printf("reponse:\n%s\n", str); + parcMemory_Deallocate((void **) &str); + } + + ccnxControl_Release(&controlMessage); + ccnxMetaMessage_Release(&rawResponse); + cpiInterfaceIPTunnel_Release(&ipTunnel); + parcMemory_Deallocate((void **) &remote); + parcMemory_Deallocate((void **) &local); +} + +static MetisCommandReturn +_metisControlRemoveConnection_UdpHelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("command:\n"); + printf(" remove connection upd <symbolic>\n"); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlRemoveConnection_UdpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + char *symbolic = NULL; + if (!_parseMessage(parser, ops, args, &symbolic)) { + return MetisCommandReturn_Success; + } + + _removeUdpConnection(parser, ops, args, symbolic); + + return MetisCommandReturn_Success; +} + + diff --git a/metis/ccnx/forwarder/metis/config/metisControl_RemoveConnection.h b/metis/ccnx/forwarder/metis/config/metisControl_RemoveConnection.h new file mode 100644 index 00000000..ffaa98f0 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_RemoveConnection.h @@ -0,0 +1,30 @@ +/* + * 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 metisControl_RemoveConnection.h + * @brief Remove a connection from the connection table + * + * Implements the "remove connection" and "help remove connection" nodes of the CLI tree + * + */ + +#ifndef Metis_metisControl_RemoveConnection_h +#define Metis_metisControl_RemoveConnection_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlRemoveConnection_Create(MetisControlState *state); +MetisCommandOps *metisControlRemoveConnection_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_RemoveConnection_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_RemoveRoute.c b/metis/ccnx/forwarder/metis/config/metisControl_RemoveRoute.c new file mode 100644 index 00000000..fa51a268 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_RemoveRoute.c @@ -0,0 +1,178 @@ +/* + * 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 <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_List.h> + +#include <ccnx/api/control/cpi_NameRouteProtocolType.h> +#include <ccnx/api/control/cpi_RouteEntry.h> +#include <ccnx/api/control/cpi_Forwarding.h> + +#include <ccnx/forwarder/metis/config/metisControl_RemoveRoute.h> + +static MetisCommandReturn _metisControlRemoveRoute_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlRemoveRoute_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandRemoveRoute = "remove route"; +static const char *_commandRemoveRouteHelp = "help remove route"; + +// ==================================================== + +MetisCommandOps * +metisControlRemoveRoute_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandRemoveRoute, NULL, _metisControlRemoveRoute_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlRemoveRoute_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandRemoveRouteHelp, NULL, _metisControlRemoveRoute_HelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + + +/** + * Return true if string is purely an integer + */ +static bool +_isNumber(const char *string) +{ + size_t len = strlen(string); + for (size_t i = 0; i < len; i++) { + if (!isdigit(string[i])) { + return false; + } + } + return true; +} + +/** + * A symbolic name must be at least 1 character and must begin with an alpha. + * The remainder must be an alphanum. + */ +static bool +_validateSymbolicName(const char *symbolic) +{ + bool success = false; + size_t len = strlen(symbolic); + if (len > 0) { + if (isalpha(symbolic[0])) { + success = true; + for (size_t i = 1; i < len; i++) { + if (!isalnum(symbolic[i])) { + success = false; + break; + } + } + } + } + return success; +} + +static MetisCommandReturn +_metisControlRemoveRoute_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("commands:\n"); + printf(" remove route <symbolic | connid> <prefix>\n"); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlRemoveRoute_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + + MetisControlState *state = ops->closure; + + if (parcList_Size(args) != 4) { + _metisControlRemoveRoute_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + const char *symbolicOrConnid = parcList_GetAtIndex(args, 2); + if (_validateSymbolicName(symbolicOrConnid) || _isNumber(symbolicOrConnid)) { + const char *prefixString = parcList_GetAtIndex(args, 3); + + CCNxName *prefix = ccnxName_CreateFromCString(prefixString); + if (prefix == NULL) { + printf("ERROR: could not parse prefix '%s'\n", prefixString); + return MetisCommandReturn_Failure; + } + + char *protocolTypeAsString = "static"; + + CPINameRouteProtocolType protocolType = cpiNameRouteProtocolType_FromString(protocolTypeAsString); + CPINameRouteType routeType = cpiNameRouteType_LONGEST_MATCH; + CPIAddress *nexthop = NULL; + + struct timeval *lifetime = NULL; + + CPIRouteEntry *route = NULL; + + unsigned cost = 1; + + if (_isNumber(symbolicOrConnid)) { + unsigned connid = (unsigned) strtold(symbolicOrConnid, NULL); + route = cpiRouteEntry_Create(prefix, connid, nexthop, protocolType, routeType, lifetime, cost); + } else { + route = cpiRouteEntry_CreateSymbolic(prefix, symbolicOrConnid, protocolType, routeType, lifetime, cost); + } + + CCNxControl *removeRouteRequest = ccnxControl_CreateRemoveRouteRequest(route); + + cpiRouteEntry_Destroy(&route); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(removeRouteRequest)); + printf("request: %s\n", str); + parcMemory_Deallocate((void **) &str); + } + + CCNxMetaMessage *message = ccnxMetaMessage_CreateFromControl(removeRouteRequest); + CCNxMetaMessage *rawResponse = metisControlState_WriteRead(state, message); + ccnxMetaMessage_Release(&message); + + ccnxControl_Release(&removeRouteRequest); + + CCNxControl *response = ccnxMetaMessage_GetControl(rawResponse); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(response)); + printf("response: %s\n", str); + parcMemory_Deallocate((void **) &str); + } + + ccnxMetaMessage_Release(&rawResponse); + + return MetisCommandReturn_Success; + + }else{ + printf("ERROR: Invalid symbolic or connid. Symbolic name must begin with an alpha followed by alphanum. connid must be an integer\n"); + return MetisCommandReturn_Failure; + } + +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_RemoveRoute.h b/metis/ccnx/forwarder/metis/config/metisControl_RemoveRoute.h new file mode 100644 index 00000000..ef438680 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_RemoveRoute.h @@ -0,0 +1,30 @@ +/* + * 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 metisControl_RemoveRoute.h + * @brief Remove a route from the FIB + * + * Implements the "remove route" and "help remove route" nodes of the command tree + * + */ + +#ifndef Metis_metisControl_RemoveRoute_h +#define Metis_metisControl_RemoveRoute_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlRemoveRoute_Create(MetisControlState *state); +MetisCommandOps *metisControlRemoveRoute_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_RemoveRoute_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Root.c b/metis/ccnx/forwarder/metis/config/metisControl_Root.c new file mode 100644 index 00000000..61e9ba50 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Root.c @@ -0,0 +1,129 @@ +/* + * 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 <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <ccnx/forwarder/metis/config/metisControl_Root.h> +#include <ccnx/forwarder/metis/config/metisControl_Add.h> +#include <ccnx/forwarder/metis/config/metisControl_List.h> +#include <ccnx/forwarder/metis/config/metisControl_Quit.h> +#include <ccnx/forwarder/metis/config/metisControl_Remove.h> +#include <ccnx/forwarder/metis/config/metisControl_Set.h> +#include <ccnx/forwarder/metis/config/metisControl_Unset.h> +#include <ccnx/forwarder/metis/config/metisControl_Cache.h> + +static void _metisControlRoot_Init(MetisCommandParser *parser, MetisCommandOps *ops); +static MetisCommandReturn _metisControlRoot_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlRoot_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandRoot = ""; +static const char *_commandRootHelp = "help"; + +// ==================================================== + +MetisCommandOps * +metisControlRoot_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandRoot, _metisControlRoot_Init, _metisControlRoot_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlRoot_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandRootHelp, NULL, _metisControlRoot_HelpExecute, metisCommandOps_Destroy); +} + +// =================================================== + +static MetisCommandReturn +_metisControlRoot_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("Command-line execution:\n"); + printf(" metis_control [--keystore <keystorepath>] [--password <password>] command\n"); + printf("\n"); + printf("Interactive execution:\n"); + printf(" metis_control [--keystore <keystorepath>] [--password <password>]\n"); + printf("\n"); + printf("If the keystore is not specified, the default path is used. Keystore must exist prior to running program.\n"); + printf("If the password is not specified, the user will be prompted.\n"); + printf("\n"); + + MetisCommandOps *ops_help_add = metisControlAdd_CreateHelp(NULL); + MetisCommandOps *ops_help_list = metisControlList_HelpCreate(NULL); + MetisCommandOps *ops_help_quit = metisControlQuit_HelpCreate(NULL); + MetisCommandOps *ops_help_remove = metisControlRemove_HelpCreate(NULL); + MetisCommandOps *ops_help_set = metisControlSet_HelpCreate(NULL); + MetisCommandOps *ops_help_unset = metisControlUnset_HelpCreate(NULL); + MetisCommandOps *ops_help_cache = metisControlCache_HelpCreate(NULL); + + printf("Available commands:\n"); + printf(" %s\n", ops_help_add->command); + printf(" %s\n", ops_help_list->command); + printf(" %s\n", ops_help_quit->command); + printf(" %s\n", ops_help_remove->command); + printf(" %s\n", ops_help_set->command); + printf(" %s\n", ops_help_unset->command); + printf(" %s\n", ops_help_cache->command); + printf("\n"); + + metisCommandOps_Destroy(&ops_help_add); + metisCommandOps_Destroy(&ops_help_list); + metisCommandOps_Destroy(&ops_help_quit); + metisCommandOps_Destroy(&ops_help_remove); + metisCommandOps_Destroy(&ops_help_set); + metisCommandOps_Destroy(&ops_help_unset); + metisCommandOps_Destroy(&ops_help_cache); + + return MetisCommandReturn_Success; +} + +static void +_metisControlRoot_Init(MetisCommandParser *parser, MetisCommandOps *ops) +{ + MetisControlState *state = ops->closure; + + metisControlState_RegisterCommand(state, metisControlAdd_CreateHelp(state)); + metisControlState_RegisterCommand(state, metisControlList_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlQuit_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlRemove_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlSet_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlUnset_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlCache_HelpCreate(state)); + + metisControlState_RegisterCommand(state, metisControlAdd_Create(state)); + metisControlState_RegisterCommand(state, metisControlList_Create(state)); + metisControlState_RegisterCommand(state, metisControlQuit_Create(state)); + metisControlState_RegisterCommand(state, metisControlRemove_Create(state)); + metisControlState_RegisterCommand(state, metisControlSet_Create(state)); + metisControlState_RegisterCommand(state, metisControlUnset_Create(state)); + metisControlState_RegisterCommand(state, metisControlCache_Create(state)); +} + +static MetisCommandReturn +_metisControlRoot_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + return MetisCommandReturn_Success; +} + +// ====================================================================== diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Root.h b/metis/ccnx/forwarder/metis/config/metisControl_Root.h new file mode 100644 index 00000000..8ab37359 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Root.h @@ -0,0 +1,31 @@ +/* + * 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 metisControl_Root.h + * @brief Root of the command tree + * + * Implements the root of the command tree. This is the one module that + * needs to be seeded to the control state to build the whole tree. + * + */ + +#ifndef Metis_metisControl_Root_h +#define Metis_metisControl_Root_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlRoot_Create(MetisControlState *state); +MetisCommandOps *metisControlRoot_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_Root_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Set.c b/metis/ccnx/forwarder/metis/config/metisControl_Set.c new file mode 100644 index 00000000..c18fc297 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Set.c @@ -0,0 +1,92 @@ +/* + * 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 <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/security/parc_Security.h> +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/config/metisControl_Set.h> +#include <ccnx/forwarder/metis/config/metisControl_SetDebug.h> +#include <ccnx/forwarder/metis/config/metisControl_SetStrategy.h> +#include <ccnx/forwarder/metis/config/metisControl_SetWldr.h> + +static void _metisControlSet_Init(MetisCommandParser *parser, MetisCommandOps *ops); +static MetisCommandReturn _metisControlSet_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlSet_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandSet = "set"; +static const char *_commandSetHelp = "help set"; + +// =========================================================== + +MetisCommandOps * +metisControlSet_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandSet, _metisControlSet_Init, _metisControlSet_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlSet_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandSetHelp, NULL, _metisControlSet_HelpExecute, metisCommandOps_Destroy); +} + +// =========================================================== + +static void +_metisControlSet_Init(MetisCommandParser *parser, MetisCommandOps *ops) +{ + MetisControlState *state = ops->closure; + metisControlState_RegisterCommand(state, metisControlSetDebug_Create(state)); + metisControlState_RegisterCommand(state, metisControlSetDebug_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlSetStrategy_Create(state)); + metisControlState_RegisterCommand(state, metisControlSetStrategy_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlSetWldr_Create(state)); + metisControlState_RegisterCommand(state, metisControlSetWldr_HelpCreate(state)); +} + +static MetisCommandReturn +_metisControlSet_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + MetisCommandOps *ops_help_set_debug = metisControlSetDebug_HelpCreate(NULL); + MetisCommandOps *ops_help_set_strategy = metisControlSetStrategy_HelpCreate(NULL); + MetisCommandOps *ops_help_set_wldr = metisControlSetWldr_HelpCreate(NULL); + + printf("Available commands:\n"); + printf(" %s\n", ops_help_set_debug->command); + printf(" %s\n", ops_help_set_strategy->command); + printf(" %s\n", ops_help_set_wldr->command); + printf("\n"); + + metisCommandOps_Destroy(&ops_help_set_debug); + metisCommandOps_Destroy(&ops_help_set_strategy); + metisCommandOps_Destroy(&ops_help_set_wldr); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlSet_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + return _metisControlSet_HelpExecute(parser, ops, args); +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Set.h b/metis/ccnx/forwarder/metis/config/metisControl_Set.h new file mode 100644 index 00000000..a7d10549 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Set.h @@ -0,0 +1,29 @@ +/* + * 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 metisControl_Set.h + * @brief Implements the set node of the CLI tree + * + * Implements the "set" and "help set" nodes of the command tree + * + */ +#ifndef Metis_metisControl_Set_h +#define Metis_metisControl_Set_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlSet_Create(MetisControlState *state); +MetisCommandOps *metisControlSet_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_Set_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_SetDebug.c b/metis/ccnx/forwarder/metis/config/metisControl_SetDebug.c new file mode 100644 index 00000000..30510230 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_SetDebug.c @@ -0,0 +1,79 @@ +/* + * 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 <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/api/control/cpi_ManageLinks.h> +#include <ccnx/api/control/cpi_Forwarding.h> + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_Dispatcher.h> +#include <ccnx/forwarder/metis/config/metisControl_SetDebug.h> + + +static MetisCommandReturn _metisControlSetDebug_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlSetDebug_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandSetDebug = "set debug"; +static const char *_commandSetDebugHelp = "help set debug"; + +// ==================================================== + +MetisCommandOps * +metisControlSetDebug_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandSetDebug, NULL, _metisControlSetDebug_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlSetDebug_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandSetDebugHelp, NULL, _metisControlSetDebug_HelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + +static MetisCommandReturn +_metisControlSetDebug_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("set debug: will enable the debug flag for more verbose output\n"); + printf("\n"); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlSetDebug_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + if (parcList_Size(args) != 2) { + _metisControlSetDebug_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + MetisControlState *state = ops->closure; + metisControlState_SetDebug(state, true); + printf("Debug flag set\n\n"); + return MetisCommandReturn_Success; +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_SetDebug.h b/metis/ccnx/forwarder/metis/config/metisControl_SetDebug.h new file mode 100644 index 00000000..56b085e3 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_SetDebug.h @@ -0,0 +1,30 @@ +/* + * 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 metisControl_SetDebug.h + * @brief Sets the debug flag for more verbose output + * + * Implements the "set debug" and "help set debug" nodes of the command tree + * + */ + +#ifndef Metis_metisControl_SetDebug_h +#define Metis_metisControl_SetDebug_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlSetDebug_Create(MetisControlState *state); +MetisCommandOps *metisControlSetDebug_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_SetDebug_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_SetStrategy.c b/metis/ccnx/forwarder/metis/config/metisControl_SetStrategy.c new file mode 100644 index 00000000..f7b805ca --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_SetStrategy.c @@ -0,0 +1,123 @@ +/* + * 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 <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/api/control/cpi_ManageLinks.h> +#include <ccnx/api/control/cpi_Forwarding.h> + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_Dispatcher.h> +#include <ccnx/forwarder/metis/config/metisControl_SetDebug.h> + +static MetisCommandReturn _metisControlSetStrategy_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlSetStrategy_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandSetStrategy = "set strategy"; +static const char *_commandSetStrategyHelp = "help set strategy"; + +// ==================================================== + +MetisCommandOps * +metisControlSetStrategy_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandSetStrategy, NULL, _metisControlSetStrategy_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlSetStrategy_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandSetStrategyHelp, NULL, _metisControlSetStrategy_HelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + +static MetisCommandReturn +_metisControlSetStrategy_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("set strategy <prefix> <strategy>\n"); + printf("available strateies:\n"); + printf(" random\n"); + printf(" loadbalancer\n"); + printf(" random_per_dash_segment\n"); + printf(" loadbalancer_with_delay\n"); + printf("\n"); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlSetStrategy_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + MetisControlState *state = ops->closure; + + if (parcList_Size(args) != 4) { + _metisControlSetStrategy_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + if (((strcmp(parcList_GetAtIndex(args, 0), "set") != 0) || (strcmp(parcList_GetAtIndex(args, 1), "strategy") != 0))) { + _metisControlSetStrategy_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + const char *prefixString = parcList_GetAtIndex(args, 2); + const char *strategy = parcList_GetAtIndex(args, 3); + CCNxName *prefix = ccnxName_CreateFromCString(prefixString); + if (prefix == NULL) { + printf("ERROR: could not parse prefix '%s'\n", prefixString); + return MetisCommandReturn_Failure; + } + + CPIForwardingStrategy *fwdStrategy = cpiForwardingStrategy_Create(prefix, (char *) strategy); + + CCNxControl *setFwdStrategyRequest = ccnxControl_CreateSetStrategyRequest(fwdStrategy); + + cpiForwardingStrategy_Destroy(&fwdStrategy); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(setFwdStrategyRequest)); + printf("request: %s\n", str); + parcMemory_Deallocate((void **) &str); + } + + CCNxMetaMessage *message = ccnxMetaMessage_CreateFromControl(setFwdStrategyRequest); + CCNxMetaMessage *rawResponse = metisControlState_WriteRead(state, message); + ccnxMetaMessage_Release(&message); + + ccnxControl_Release(&setFwdStrategyRequest); + + CCNxControl *response = ccnxMetaMessage_GetControl(rawResponse); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(response)); + printf("response: %s\n", str); + parcMemory_Deallocate((void **) &str); + } + + ccnxMetaMessage_Release(&rawResponse); + + return MetisCommandReturn_Success; +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_SetStrategy.h b/metis/ccnx/forwarder/metis/config/metisControl_SetStrategy.h new file mode 100644 index 00000000..e25b2c29 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_SetStrategy.h @@ -0,0 +1,22 @@ +/* + * 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_metisControl_SetStrategy_h +#define Metis_metisControl_SetStrategy_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlSetStrategy_Create(MetisControlState *state); +MetisCommandOps *metisControlSetStrategy_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_SetStrategy_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_SetWldr.c b/metis/ccnx/forwarder/metis/config/metisControl_SetWldr.c new file mode 100644 index 00000000..6cc9c951 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_SetWldr.c @@ -0,0 +1,130 @@ +/* + * 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 <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/api/control/cpi_ManageWldr.h> +#include <ccnx/api/control/cpi_Forwarding.h> + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_Dispatcher.h> +#include <ccnx/forwarder/metis/config/metisControl_SetDebug.h> + + +static MetisCommandReturn _metisControlSetWldr_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlSetWldr_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandSetWldr = "set wldr"; +static const char *_commandSetWldrHelp = "help set wldr"; + +// ==================================================== + +MetisCommandOps * +metisControlSetWldr_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandSetWldr, NULL, _metisControlSetWldr_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlSetWldr_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandSetWldrHelp, NULL, _metisControlSetWldr_HelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + +static MetisCommandReturn +_metisControlSetWldr_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("set wldr <on|off> <connection_id>\n"); + printf("\n"); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlSetWldr_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + MetisControlState *state = ops->closure; + + if (parcList_Size(args) != 4) { + _metisControlSetWldr_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + if (((strcmp(parcList_GetAtIndex(args, 0), "set") != 0) || (strcmp(parcList_GetAtIndex(args, 1), "wldr") != 0))) { + _metisControlSetWldr_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + const char *activeStr = parcList_GetAtIndex(args, 2); + bool active; + if(strcmp(activeStr, "on") == 0){ + active = true; + }else if(strcmp(activeStr, "off") == 0){ + active = false; + }else{ + _metisControlSetWldr_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + const char *connId = parcList_GetAtIndex(args, 3); + + CPIManageWldr *cpiWldr = cpiManageWldr_Create(active, (char *) connId); + + CCNxControl *setWldrRequest = ccnxControl_CreateSetWldrRequest(cpiWldr); + + cpiManageWldr_Destroy(&cpiWldr); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(setWldrRequest)); + printf("request: %s\n", str); + parcMemory_Deallocate((void **) &str); + } + + CCNxMetaMessage *message = ccnxMetaMessage_CreateFromControl(setWldrRequest); + CCNxMetaMessage *rawResponse = metisControlState_WriteRead(state, message); + ccnxMetaMessage_Release(&message); + + ccnxControl_Release(&setWldrRequest); + + CCNxControl *response = ccnxMetaMessage_GetControl(rawResponse); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(response)); + printf("response: %s\n", str); + parcMemory_Deallocate((void **) &str); + } + + if(ccnxControl_IsNACK(response)){ + printf("command set wldr failed"); + ccnxMetaMessage_Release(&rawResponse); + return MetisCommandReturn_Failure; + } + + ccnxMetaMessage_Release(&rawResponse); + + return MetisCommandReturn_Success; +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_SetWldr.h b/metis/ccnx/forwarder/metis/config/metisControl_SetWldr.h new file mode 100644 index 00000000..492d0865 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_SetWldr.h @@ -0,0 +1,22 @@ +/* + * 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_metisControl_SetWldr_h +#define Metis_metisControl_SetWldr_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlSetWldr_Create(MetisControlState *state); +MetisCommandOps *metisControlSetWldr_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_SetWldr_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Unset.c b/metis/ccnx/forwarder/metis/config/metisControl_Unset.c new file mode 100644 index 00000000..852b8a9a --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Unset.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 <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/security/parc_Security.h> +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/config/metisControl_Unset.h> +#include <ccnx/forwarder/metis/config/metisControl_UnsetDebug.h> + +static void _metisControlUnset_Init(MetisCommandParser *parser, MetisCommandOps *ops); + +static MetisCommandReturn _metisControlUnset_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlUnset_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandUnset = "unset"; +static const char *_commandUnsetHelp = "help unset"; + +// =========================================================== + +MetisCommandOps * +metisControlUnset_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandUnset, _metisControlUnset_Init, _metisControlUnset_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlUnset_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandUnsetHelp, NULL, _metisControlUnset_HelpExecute, metisCommandOps_Destroy); +} + +// =========================================================== + +static void +_metisControlUnset_Init(MetisCommandParser *parser, MetisCommandOps *ops) +{ + MetisControlState *state = ops->closure; + metisControlState_RegisterCommand(state, metisControlUnsetDebug_Create(state)); + metisControlState_RegisterCommand(state, metisControlUnsetDebug_HelpCreate(state)); +} + +static MetisCommandReturn +_metisControlUnset_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + MetisCommandOps *ops_help_unset_debug = metisControlUnsetDebug_HelpCreate(NULL); + + printf("Available commands:\n"); + printf(" %s\n", ops_help_unset_debug->command); + printf("\n"); + + metisCommandOps_Destroy(&ops_help_unset_debug); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlUnset_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + return _metisControlUnset_HelpExecute(parser, ops, args); +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Unset.h b/metis/ccnx/forwarder/metis/config/metisControl_Unset.h new file mode 100644 index 00000000..d8ca6c16 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Unset.h @@ -0,0 +1,29 @@ +/* + * 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 metisControl_Unset.h + * @brief Implements the unset node of the CLI tree + * + * Implements the "unset" and "help unset" nodes of the command tree + * + */ +#ifndef Metis_metisControl_Unset_h +#define Metis_metisControl_Unset_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlUnset_Create(MetisControlState *state); +MetisCommandOps *metisControlUnset_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_Unset_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_UnsetDebug.c b/metis/ccnx/forwarder/metis/config/metisControl_UnsetDebug.c new file mode 100644 index 00000000..acc877d6 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_UnsetDebug.c @@ -0,0 +1,79 @@ +/* + * 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 <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/api/control/cpi_ManageLinks.h> +#include <ccnx/api/control/cpi_Forwarding.h> + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_Dispatcher.h> +#include <ccnx/forwarder/metis/config/metisControl_UnsetDebug.h> + +static MetisCommandReturn _metisControlUnsetDebug_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlUnsetDebug_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandUnsetDebug = "unset debug"; +static const char *_commandUnsetDebugHelp = "help unset debug"; + +// ==================================================== + +MetisCommandOps * +metisControlUnsetDebug_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandUnsetDebug, NULL, _metisControlUnsetDebug_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlUnsetDebug_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandUnsetDebugHelp, NULL, _metisControlUnsetDebug_HelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + +static MetisCommandReturn +_metisControlUnsetDebug_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("unset debug: will disable the debug flag\n"); + printf("\n"); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlUnsetDebug_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + if (parcList_Size(args) != 2) { + _metisControlUnsetDebug_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + MetisControlState *state = ops->closure; + metisControlState_SetDebug(state, false); + printf("Debug flag cleared\n\n"); + + return MetisCommandReturn_Success; +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_UnsetDebug.h b/metis/ccnx/forwarder/metis/config/metisControl_UnsetDebug.h new file mode 100644 index 00000000..c3a02b05 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_UnsetDebug.h @@ -0,0 +1,30 @@ +/* + * 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 metisControl_UnsetDebug.h + * @brief Unsets the debug flag for more verbose output + * + * Implements the "unset debug" and "help unset debug" nodes of the CLI tree + * + */ + +#ifndef Metis_metisControl_UnsetDebug_h +#define Metis_metisControl_UnsetDebug_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlUnsetDebug_Create(MetisControlState *state); +MetisCommandOps *metisControlUnsetDebug_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_UnsetDebug_h diff --git a/metis/ccnx/forwarder/metis/config/metis_CommandLineInterface.c b/metis/ccnx/forwarder/metis/config/metis_CommandLineInterface.c new file mode 100644 index 00000000..10857a29 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_CommandLineInterface.c @@ -0,0 +1,405 @@ +/* + * 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. + */ + +/* + * NB: binds to all interfaces on the listen port, which might be a security issue. + * + * The CLI runs as an event managed listener. The Api here creates, starts, stops, and destroys it. + * + * The CLI is a user interface to the programmatic interface in <code>metis_Configuration.h</code> + * + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <arpa/inet.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_ArrayList.h> + +#include "metis_CommandLineInterface.h" + +struct metis_command_line_interface { + MetisForwarder *metis; + PARCEventSocket *listener; + PARCArrayList *openSessions; + + uint16_t port; +}; + +typedef struct metis_cli_session { + MetisCommandLineInterface *parentCli; + MetisSocketType clientSocket; + struct sockaddr *clientAddress; + int clientAddressLength; + PARCEventQueue *streamBuffer; + bool doingTheRightThing; +} _MetisCommandLineInterface_Session; + +struct metis_cli_command; +typedef struct metis_cli_command _MetisCommandLineInterface_Command; + +struct metis_cli_command { + char *text; + char *helpDescription; + void (*func)(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params); +}; + +static void _metisCommandLineInterface_ListenerCallback(MetisSocketType client_socket, + struct sockaddr *client_addr, int socklen, void *user_data); + +static _MetisCommandLineInterface_Session *metisCliSession_Create(MetisCommandLineInterface *cli, MetisSocketType client_socket, struct sockaddr *client_addr, int socklen); +static void _metisCliSession_Destory(_MetisCommandLineInterface_Session **cliSessionPtr); +static void _metisCliSession_ReadCallback(PARCEventQueue *event, PARCEventType type, void *cliSessionVoid); +static void _metisCliSession_EventCallback(PARCEventQueue *event, PARCEventQueueEventType what, void *cliSessionVoid); +static bool _metisCliSession_ProcessCommand(_MetisCommandLineInterface_Session *session, char *cmdline); +static void _metisCliSession_DisplayMotd(_MetisCommandLineInterface_Session *session); +static void _metisCliSession_DisplayPrompt(_MetisCommandLineInterface_Session *session); + +// used by PARCArrayList +static void +_session_VoidDestroyer(void **cliSessionVoidPtr) +{ + _MetisCommandLineInterface_Session **cliSessionPtr = (_MetisCommandLineInterface_Session **) cliSessionVoidPtr; + (*cliSessionPtr)->doingTheRightThing = true; + _metisCliSession_Destory(cliSessionPtr); +} + +// ==================================================================================== + +static void _cmd_Help(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params); +static void _cmd_Show(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params); +static void _cmd_Exit(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params); +static void _cmd_Tunnel(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params); +static void _cmd_Version(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params); + +/** + * @typedef _cliCommands + * The commands, their short help, and their function pointer + * @constant <#name#> <#description#> + * List must be terminated with a NULL entry + * + * Example: + * @code + * <#example#> + * @endcode + */ +static _MetisCommandLineInterface_Command _cliCommands[] = { + { "exit", "Ends the session", _cmd_Exit }, + { "help", "Displays the help menu", _cmd_Help }, + { "show", "Displays state", _cmd_Show }, + { "tunnel", "manage tunnels", _cmd_Tunnel }, + { "ver", "Forwarder version", _cmd_Version }, + { NULL, NULL, NULL } +}; + +// ==================================================================================== + +MetisCommandLineInterface * +metisCommandLineInterface_Create(MetisForwarder *metis, uint16_t port) +{ + MetisCommandLineInterface *cli = parcMemory_AllocateAndClear(sizeof(MetisCommandLineInterface)); + assertNotNull(cli, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisCommandLineInterface)); + cli->port = port; + cli->listener = NULL; + cli->metis = metis; + cli->openSessions = parcArrayList_Create(_session_VoidDestroyer); + + return cli; +} + +void +metisCommandLineInterface_Start(MetisCommandLineInterface *cli) +{ + // listen address + struct sockaddr_in6 addr6; + memset(&addr6, 0, sizeof(addr6)); + addr6.sin6_family = PF_INET6; + addr6.sin6_port = htons(cli->port); + + MetisDispatcher *dispatcher = metisForwarder_GetDispatcher(cli->metis); + PARCEventSocket *listener = metisDispatcher_CreateListener(dispatcher, _metisCommandLineInterface_ListenerCallback, cli, -1, (struct sockaddr *) &addr6, sizeof(addr6)); + assertNotNull(listener, "Got null listener"); + + cli->listener = listener; +} + +void +metisCommandLineInterface_Destroy(MetisCommandLineInterface **cliPtr) +{ + assertNotNull(cliPtr, "Parameter must be non-null double pointer"); + assertNotNull(*cliPtr, "Parameter must dereference to non-null pointer"); + + MetisCommandLineInterface *cli = *cliPtr; + + parcArrayList_Destroy(&cli->openSessions); + + if (cli->listener) { + MetisDispatcher *dispatcher = metisForwarder_GetDispatcher(cli->metis); + metisDispatcher_DestroyListener(dispatcher, &(cli->listener)); + } + + parcMemory_Deallocate((void **) &cli); + *cliPtr = NULL; +} + +/** + * Creates a client-specific session + * + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +static _MetisCommandLineInterface_Session * +metisCliSession_Create(MetisCommandLineInterface *cli, MetisSocketType clientSocket, struct sockaddr *clientAddress, int clientAddressLength) +{ + _MetisCommandLineInterface_Session *session = parcMemory_AllocateAndClear(sizeof(_MetisCommandLineInterface_Session)); + assertNotNull(session, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisCommandLineInterface_Session)); + session->parentCli = cli; + session->clientAddress = parcMemory_Allocate(clientAddressLength); + assertNotNull(session->clientAddress, "parcMemory_Allocate(%d) returned NULL", clientAddressLength); + session->clientAddressLength = clientAddressLength; + session->clientSocket = clientSocket; + + memcpy(session->clientAddress, clientAddress, clientAddressLength); + + MetisDispatcher *dispatcher = metisForwarder_GetDispatcher(cli->metis); + PARCEventScheduler *eventBase = metisDispatcher_GetEventScheduler(dispatcher); + session->streamBuffer = parcEventQueue_Create(eventBase, clientSocket, PARCEventQueueOption_CloseOnFree | PARCEventQueueOption_DeferCallbacks); + + parcEventQueue_SetCallbacks(session->streamBuffer, _metisCliSession_ReadCallback, NULL, _metisCliSession_EventCallback, session); + parcEventQueue_Enable(session->streamBuffer, PARCEventType_Read); + + return session; +} + +/** + * SHOULD ONLY BE CALLED FROM ARRAYLIST + * + * Do not call this on your own!! It should only be called when an + * item is removed from the cli->openSessions array list. + * + * Will close the tcp session and free memory. + * + * @param <#param1#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +static void +_metisCliSession_Destory(_MetisCommandLineInterface_Session **cliSessionPtr) +{ + assertNotNull(cliSessionPtr, "Parameter must be non-null double pointer"); + assertNotNull(*cliSessionPtr, "Parameter must dereference to non-null pointer"); + _MetisCommandLineInterface_Session *session = *cliSessionPtr; + + assertTrue(session->doingTheRightThing, "Ha! caught you! You called Destroy outside the PARCArrayList"); + + parcEventQueue_Destroy(&(session->streamBuffer)); + parcMemory_Deallocate((void **) &(session->clientAddress)); + parcMemory_Deallocate((void **) &session); + *cliSessionPtr = NULL; +} + +/** + * Called on a new connection to the server socket + * + * Will allocate a new _MetisCommandLineInterface_Session and put it in the + * server's PARCArrayList + * + * @param <#param1#> + * @return <#return#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +static void +_metisCommandLineInterface_ListenerCallback(MetisSocketType client_socket, + struct sockaddr *client_addr, int socklen, void *user_data) +{ + MetisCommandLineInterface *cli = (MetisCommandLineInterface *) user_data; + _MetisCommandLineInterface_Session *session = metisCliSession_Create(cli, client_socket, client_addr, socklen); + parcArrayList_Add(cli->openSessions, session); + + _metisCliSession_DisplayMotd(session); + _metisCliSession_DisplayPrompt(session); +} + +static void +_metisCliSession_ReadCallback(PARCEventQueue *event, PARCEventType type, void *cliSessionVoid) +{ + assertTrue(type == PARCEventType_Read, "Illegal type: expected read event, got %d\n", type); + _MetisCommandLineInterface_Session *session = (_MetisCommandLineInterface_Session *) cliSessionVoid; + PARCEventBuffer *input = parcEventBuffer_GetQueueBufferInput(event); + + while (parcEventBuffer_GetLength(input) > 0) { + size_t readLength = 0; + char *cmdline = parcEventBuffer_ReadLine(input, &readLength); + if (cmdline == NULL) { + // we have run out of input, we're done + parcEventBuffer_Destroy(&input); + return; + } + + // we have a whole command line + bool success = _metisCliSession_ProcessCommand(session, cmdline); + parcEventBuffer_FreeLine(input, &cmdline); + + if (!success) { + // the session is dead + parcEventBuffer_Destroy(&input); + return; + } + + _metisCliSession_DisplayPrompt(session); + } + parcEventBuffer_Destroy(&input); +} + +static void +_metisCommandLineInterface_RemoveSession(MetisCommandLineInterface *cli, _MetisCommandLineInterface_Session *session) +{ + size_t length = parcArrayList_Size(cli->openSessions); + for (size_t i = 0; i < length; i++) { + _MetisCommandLineInterface_Session *x = parcArrayList_Get(cli->openSessions, i); + if (x == session) { + // removing from list will call the session destroyer + parcArrayList_RemoveAndDestroyAtIndex(cli->openSessions, i); + return; + } + } + assertTrue(0, "Illegal state: did not find a session in openSessions %p", (void *) session); +} + +static void +_metisCliSession_EventCallback(PARCEventQueue *event, PARCEventQueueEventType what, void *cliSessionVoid) +{ + _MetisCommandLineInterface_Session *session = (_MetisCommandLineInterface_Session *) cliSessionVoid; + if (what & PARCEventQueueEventType_Error) { + MetisCommandLineInterface *cli = session->parentCli; + _metisCommandLineInterface_RemoveSession(cli, session); + } +} + +static void +_metisCliSession_DisplayMotd(_MetisCommandLineInterface_Session *session) +{ + parcEventQueue_Printf(session->streamBuffer, "Metis Forwarder CLI\n"); + parcEventQueue_Printf(session->streamBuffer, "Copyright (c) 2017 Cisco and/or its affiliates.\n\n"); + + parcEventQueue_Flush(session->streamBuffer, PARCEventType_Write); +} + +static void +_metisCliSession_DisplayPrompt(_MetisCommandLineInterface_Session *session) +{ + parcEventQueue_Printf(session->streamBuffer, "metis> "); + parcEventQueue_Flush(session->streamBuffer, PARCEventType_Write); +} + +/** + * Process commands until there's not a whole line (upto CRLF) + * + * <#Discussion#> + * + * @param <#param1#> + * @return false if the session died, true if its still going + * + * Example: + * @code + * <#example#> + * @endcode + */ +static bool +_metisCliSession_ProcessCommand(_MetisCommandLineInterface_Session *session, char *cmdline) +{ + // parse out the first word. The NULL goes in after a space or tab. + // "cmd" will be the string prior to the NULL, and "cmdline" will be what's after the NULL. + char *cmd = strsep(&cmdline, " \t"); + + // there's a secret command for unit testing + if (strcasecmp(cmd, "~~") == 0) { + parcEventQueue_Printf(session->streamBuffer, "success: %s\n", cmdline); + return true; + } + + for (int i = 0; _cliCommands[i].text != NULL; i++) { + if (strcasecmp(_cliCommands[i].text, cmd) == 0) { + _cliCommands[i].func(session, &_cliCommands[i], cmdline); + if (_cliCommands[i].func == _cmd_Exit) { + return false; + } + return true; + } + } + + // could not find command, print the help menu + parcEventQueue_Printf(session->streamBuffer, "Unrecognized command: %s, displaying help menu\n", cmdline); + _cmd_Help(session, NULL, NULL); + return true; +} + +static void +_cmd_Help(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params) +{ + for (int i = 0; _cliCommands[i].text != NULL; i++) { + parcEventQueue_Printf(session->streamBuffer, "%-8s %s\n", _cliCommands[i].text, _cliCommands[i].helpDescription); + } +} + +static void +_cmd_Show(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params) +{ + parcEventQueue_Printf(session->streamBuffer, "not implemented\n"); +} + +static void +_cmd_Tunnel(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params) +{ + parcEventQueue_Printf(session->streamBuffer, "not implemented\n"); +} + +static void +_cmd_Exit(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params) +{ + parcEventQueue_Printf(session->streamBuffer, "Exiting session, goodby\n\n"); + parcEventQueue_Flush(session->streamBuffer, PARCEventType_Write); + _metisCommandLineInterface_RemoveSession(session->parentCli, session); +} + +static void +_cmd_Version(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params) +{ + PARCJSON *versionJson = metisConfiguration_GetVersion(metisForwarder_GetConfiguration(session->parentCli->metis)); + char *str = parcJSON_ToString(versionJson); + parcEventQueue_Printf(session->streamBuffer, "%s", str); + parcMemory_Deallocate((void **) &str); + parcJSON_Release(&versionJson); +} diff --git a/metis/ccnx/forwarder/metis/config/metis_CommandLineInterface.h b/metis/ccnx/forwarder/metis/config/metis_CommandLineInterface.h new file mode 100644 index 00000000..f4ab85dc --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_CommandLineInterface.h @@ -0,0 +1,80 @@ +/* + * 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_CommandLineInterface.h + * @brief A telnet-like server for management interface + * + * + * We do not start the CLI until metisCommandLineInterface_Start() is called. This allows us to always create it in + * metisForwarder_Create(), but not bind the port until needed. Binding the port in metisForwarder_Create() + * causes severe issues in rapid execution of unit tests. + * + * + */ + +#ifndef Metis_metis_CommandLineInterface_h +#define Metis_metis_CommandLineInterface_h + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +struct metis_command_line_interface; +typedef struct metis_command_line_interface MetisCommandLineInterface; + +/** + * Creates a CLI on the given port. + * + * A telnet-style interface. Creating it will not bind the port or start + * the service. You need to call <code>metisCli_Start()</code> + * + * @param port the command port, in host byte order + * + * @return NULL if cannot be created on the port + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisCommandLineInterface *metisCommandLineInterface_Create(MetisForwarder *metis, uint16_t port); + +/** + * Stops and destroys the CLI. Existing sessions are destroyed. + * + * <#Discussion#> + * + * @param cliPtr + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisCommandLineInterface_Destroy(MetisCommandLineInterface **cliPtr); + +/* + * Binds the port and starts the CLI service + * + * <#Discussion#> + * + * @param cli + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisCommandLineInterface_Start(MetisCommandLineInterface *cli); +#endif // Metis_metis_CommandLineInterface_h diff --git a/metis/ccnx/forwarder/metis/config/metis_CommandOps.c b/metis/ccnx/forwarder/metis/config/metis_CommandOps.c new file mode 100644 index 00000000..3b2f49ca --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_CommandOps.c @@ -0,0 +1,69 @@ +/* + * 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 <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> +#include <string.h> + +#ifndef _ANDROID_ +# ifdef HAVE_ERRNO_H +# include <errno.h> +# else +extern int errno; +# endif +#endif + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/config/metis_CommandOps.h> +#include <ccnx/forwarder/metis/config/metis_CommandParser.h> + +MetisCommandOps * +metisCommandOps_Create(void *closure, const char *command, void (*init)(MetisCommandParser *parser, MetisCommandOps *ops), + MetisCommandReturn (*execute)(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args), + void (*destroyer)(MetisCommandOps **opsPtr)) +{ + assertNotNull(command, "Parameter command must be non-null"); + assertNotNull(execute, "Parameter execute must be non-null"); + MetisCommandOps *ops = parcMemory_AllocateAndClear(sizeof(MetisCommandOps)); + assertNotNull(ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisCommandOps)); + ops->closure = closure; + ops->command = parcMemory_StringDuplicate(command, strlen(command) + 1); + ops->init = init; + ops->execute = execute; + ops->destroyer = destroyer; + return ops; +} + +void +metisCommandOps_Destroy(MetisCommandOps **opsPtr) +{ + assertNotNull(opsPtr, "Parameter opsPtr must be non-null"); + assertNotNull(*opsPtr, "Parameter opsPtr must dereference to non-null pointer"); + + MetisCommandOps *ops = *opsPtr; + parcMemory_Deallocate((void **) &(ops->command)); + // DO NOT call ops->destroyer, we are one! + parcMemory_Deallocate((void **) &ops); + + *opsPtr = NULL; +} diff --git a/metis/ccnx/forwarder/metis/config/metis_CommandOps.h b/metis/ccnx/forwarder/metis/config/metis_CommandOps.h new file mode 100644 index 00000000..e37024aa --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_CommandOps.h @@ -0,0 +1,121 @@ +/* + * 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_CommandOps.h + * @brief The function structure defining a CLI command + * + * The function structure that defines a CLI command. Each command will return one + * of these which defines how to run the command. + * + */ + +#ifndef Metis_metis_CommandOps_h +#define Metis_metis_CommandOps_h + +#include <parc/algol/parc_List.h> + +#include <ccnx/forwarder/metis/config/metis_CommandReturn.h> + +// forward reference +struct metis_command_parser; + +struct metis_command_ops; +typedef struct metis_command_ops MetisCommandOps; + +/** + * @typedef MetisCommandOps + * @abstract Each command implements a MetisCommandOps + * @constant closure is a user-specified pointer for any state the user needs + * @constant command The text string of the command, must be the spelled out string, e.g. "help list routes" + * @constant init A function to call to initialize the command at program startup + * @constant execute A function to call to execute the command + * @constant destroyer A function to call to release the command + * @discussion + * Typically, the root of the thee has an Init function that then initilizes the + * rest of the tree. For example: + * + * @code + * const MetisCommandOps metisControl_Root = { + * .closure = NULL, + * .command = "", // empty string for root + * .init = metisControl_Root_Init, + * .execute = metisControl_Root_Execute + * .destroyer = NULL + * }; + * @endcode + * + * The metisControl_Root_Init function will then begin adding the subtree under root. For example: + * + * @code + * const MetisCommandOps metisControl_Add = { + * .closure = NULL, + * .command = "add", + * .init = metisControl_Add_Init, + * .execute = metisControl_Add_Execute, + * .destroyer = NULL + * }; + * + * static void + * metisControl_Root_Init(MetisControlState *state, MetisCommandOps *ops) + * { + * metisControlState_RegisterCommand(state, &metisControl_Add); + * } + * @endcode + */ +struct metis_command_ops { + void *closure; + char *command; + void (*init)(struct metis_command_parser *parser, MetisCommandOps *ops); + MetisCommandReturn (*execute)(struct metis_command_parser *parser, MetisCommandOps *ops, PARCList *args); + void (*destroyer)(MetisCommandOps **opsPtr); +}; + +/** + * A helper function to create the pubically defined MetisCommandOps. + * + * Retruns allocated memory of the command + * + * @param [in] command The string is copied + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisCommandOps *metisCommandOps_Create(void *closure, + const char *command, + void (*init)(struct metis_command_parser *parser, MetisCommandOps *ops), + MetisCommandReturn (*execute)(struct metis_command_parser *parser, MetisCommandOps *ops, PARCList *args), + void (*destroyer)(MetisCommandOps **opsPtr)); + +/** + * De-allocates the memory of the MetisCommandOps and the copied command string + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisCommandOps_Destroy(MetisCommandOps **opsPtr); +#endif // Metis_metis_CommandOps_h diff --git a/metis/ccnx/forwarder/metis/config/metis_CommandParser.c b/metis/ccnx/forwarder/metis/config/metis_CommandParser.c new file mode 100644 index 00000000..6cc19311 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_CommandParser.c @@ -0,0 +1,227 @@ +/* + * 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 <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> +#include <string.h> + +#include <parc/security/parc_Security.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_List.h> +#include <parc/algol/parc_TreeRedBlack.h> +#include <parc/algol/parc_Time.h> + +#include <ccnx/common/ccnx_KeystoreUtilities.h> + +#include <ccnx/forwarder/metis/config/metis_CommandParser.h> + +#ifndef _ANDROID_ +# ifdef HAVE_ERRNO_H +# include <errno.h> +# else +extern int errno; +# endif +#endif + +struct metis_command_parser { + // key = command, value = MetisCommandOps + PARCTreeRedBlack *commandTree; + bool debugFlag; +}; + +static int +_stringCompare(const void *key1, const void *key2) +{ + return strcasecmp((const char *) key1, (const char *) key2); +} + +MetisCommandParser * +metisCommandParser_Create(void) +{ + MetisCommandParser *state = parcMemory_AllocateAndClear(sizeof(MetisCommandParser)); + assertNotNull(state, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisCommandParser)); + state->commandTree = parcTreeRedBlack_Create( + _stringCompare, // key compare + NULL, // key free + NULL, // key copy + NULL, // value equals + NULL, // value free + NULL // value copy + ); + state->debugFlag = false; + return state; +} + +void +metisCommandParser_Destroy(MetisCommandParser **parserPtr) +{ + MetisCommandParser *parser = *parserPtr; + + // destroy every element if it has a destroyer + PARCArrayList *values = parcTreeRedBlack_Values(parser->commandTree); + if (values) { + for (int i = 0; i < parcArrayList_Size(values); i++) { + MetisCommandOps *ops = parcArrayList_Get(values, i); + parcTreeRedBlack_Remove(parser->commandTree, ops->command); + if (ops->destroyer) { + ops->destroyer(&ops); + } + } + parcArrayList_Destroy(&values); + } + + parcTreeRedBlack_Destroy(&parser->commandTree); + + parcMemory_Deallocate((void **) &parser); + *parserPtr = NULL; +} + +void +metisCommandParser_SetDebug(MetisCommandParser *state, bool debugFlag) +{ + state->debugFlag = debugFlag; +} + +bool +metisCommandParser_GetDebug(MetisCommandParser *state) +{ + return state->debugFlag; +} + +void +metisCommandParser_RegisterCommand(MetisCommandParser *state, MetisCommandOps *ops) +{ + assertNotNull(state, "Parameter state must be non-null"); + assertNotNull(ops, "Parameter ops must be non-null"); + assertNotNull(ops->command, "Operation command string must be non-null"); + + void *exists = parcTreeRedBlack_Get(state->commandTree, ops->command); + assertNull(exists, "Command '%s' already exists in the tree %p\n", ops->command, (void *) exists); + + parcTreeRedBlack_Insert(state->commandTree, (void *) ops->command, (void *) ops); + + // if the command being registered asked for an init function to be called, call it + if (ops->init != NULL) { + ops->init(state, ops); + } +} + +static PARCList * +parseStringIntoTokens(const char *originalString) +{ + PARCList *list = parcList(parcArrayList_Create(parcArrayList_StdlibFreeFunction), PARCArrayListAsPARCList); + + char *token; + + char *tofree = parcMemory_StringDuplicate(originalString, strlen(originalString) + 1); + char *string = tofree; + + while ((token = strsep(&string, " \t\n")) != NULL) { + if (strlen(token) > 0) { + parcList_Add(list, strdup(token)); + } + } + + parcMemory_Deallocate((void **) &tofree); + + return list; +} + +/** + * Matches the user arguments to available commands, returning the command or NULL if not found + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +static MetisCommandOps * +metisCommandParser_MatchCommand(MetisCommandParser *state, PARCList *args) +{ + // Find the longest matching prefix command. + // Pretty wildly inefficient + + size_t longest_token_count = 0; + char *longest_command = NULL; + + PARCArrayList *commands = parcTreeRedBlack_Keys(state->commandTree); + for (int i = 0; i < parcArrayList_Size(commands); i++) { + char *command = parcArrayList_Get(commands, i); + PARCList *command_tokens = parseStringIntoTokens(command); + + // is it a prefix match? + if (parcList_Size(args) >= parcList_Size(command_tokens)) { + bool possible_match = true; + for (int i = 0; i < parcList_Size(command_tokens) && possible_match; i++) { + const char *a = parcList_GetAtIndex(command_tokens, i); + const char *b = parcList_GetAtIndex(args, i); + if (strncasecmp(a, b, strlen(a) + 1) != 0) { + possible_match = false; + } + } + + if (possible_match && parcList_Size(command_tokens) > longest_token_count) { + longest_token_count = parcList_Size(command_tokens); + longest_command = command; + } + } + + parcList_Release(&command_tokens); + } + + parcArrayList_Destroy(&commands); + + if (longest_token_count == 0) { + return NULL; + } else { + MetisCommandOps *ops = parcTreeRedBlack_Get(state->commandTree, longest_command); + assertNotNull(ops, "Got null operations for command '%s'\n", longest_command); + return ops; + } +} + +MetisCommandReturn +metisCommandParser_DispatchCommand(MetisCommandParser *state, PARCList *args) +{ + MetisCommandOps *ops = metisCommandParser_MatchCommand(state, args); + + if (ops == NULL) { + printf("Command not found.\n"); + return MetisCommandReturn_Failure; + } else { + return ops->execute(state, ops, args); + } +} + +bool +metisCommandParser_ContainsCommand(MetisCommandParser *parser, const char *command) +{ + MetisCommandOps *ops = parcTreeRedBlack_Get(parser->commandTree, command); + return (ops != NULL); +} diff --git a/metis/ccnx/forwarder/metis/config/metis_CommandParser.h b/metis/ccnx/forwarder/metis/config/metis_CommandParser.h new file mode 100644 index 00000000..dc274291 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_CommandParser.h @@ -0,0 +1,190 @@ +/* + * 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_CommandParser.h + * @brief Creates a dictionary of commands and parses a command-line to match against them + * + * A user creates individual CommandParserEntry that map a command-line to a function + * to execute. The CommandParser then does a longest-matching prefix match of a command-line + * to the dictionary of commands and executes the appropriate command. + * + */ + +#ifndef Metis_metis_command_parser_h +#define Metis_metis_command_parser_h + +#include <ccnx/transport/common/transport_MetaMessage.h> + +#include <ccnx/forwarder/metis/config/metis_CommandReturn.h> +#include <ccnx/forwarder/metis/config/metis_CommandOps.h> + +struct metis_command_parser; +typedef struct metis_command_parser MetisCommandParser; + +/** + * metisControlState_Create + * + * Creates the global state for the MetisControl program + * + * @return non-null A command parser + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisCommandParser *metisCommandParser_Create(void); + +/** + * Destroys the control state, closing all network connections + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisCommandParser_Destroy(MetisCommandParser **statePtr); + +/** + * Registers a MetisCommandOps with the system. + * + * Each command has its complete command prefix in the "command" field. RegisterCommand + * will put these command prefixes in to a tree and then match what a user types against + * the longest-matching prefix in the tree. If there's a match, it will call the "execute" + * function. + * + * When the parser is destroyed, each command's destroyer function will be called. + * + * @param [in] state An allocated MetisControlState + * @param [in] command The command to register with the system + * + * Example: + * @code + * static MetisControlReturn + * metisControl_Root_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) + * { + * printf("Root Command\n"); + * return MetisCommandReturn_Success; + * } + * + * static MetisControlReturn + * metisControl_FooBar_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) + * { + * printf("Foo Bar Command\n"); + * return MetisCommandReturn_Success; + * } + * + * const MetisCommandOps metisControl_Root = { + * .closure = NULL, + * .command = "", // empty string for root + * .init = NULL, + * .execute = metisControl_Root_Execute + * }; + * + * const MetisCommandOps metisControl_FooBar = { + * .closure = NULL, + * .command = "foo bar", // empty string for root + * .init = NULL, + * .execute = metisControl_FooBar_Execute + * }; + * + * void startup(void) + * { + * MetisControlState *state = metisControlState_Create("happy", "day"); + * metisControlState_RegisterCommand(state, metisControl_FooBar); + * metisControlState_RegisterCommand(state, metisControl_Root); + * + * // this executes "root" + * metisControlState_DispatchCommand(state, "foo"); + * metisControlState_Destroy(&state); + * } + * @endcode + */ +void metisCommandParser_RegisterCommand(MetisCommandParser *state, MetisCommandOps *command); + +/** + * Performs a longest-matching prefix of the args to the command tree + * + * The command tree is created with metisControlState_RegisterCommand. + * + * @param [in] state The allocated MetisControlState + * @param [in] args Each command-line word parsed to the ordered list + * + * @return MetisCommandReturn_Success the command was successful + * @return MetisCommandReturn_Failure the command failed or was not found + * @return MetisCommandReturn_Exit the command indicates that the interactive mode should exit + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisCommandReturn metisCommandParser_DispatchCommand(MetisCommandParser *state, PARCList *args); + +/** + * Sets the Debug mode, which will print out much more information. + * + * Prints out much more diagnostic information about what metis-control is doing. + * yes, you would make a MetisCommandOps to set and unset this :) + * + * @param [in] debugFlag true means to print debug info, false means to turn it off + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisCommandParser_SetDebug(MetisCommandParser *state, bool debugFlag); + +/** + * Returns the debug state of MetisControlState + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisCommandParser_GetDebug(MetisCommandParser *state); + +/** + * Checks if the command is registered + * + * Checks if the exact command given is registered. This is not a prefix match. + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return true The command is registered + * @return false The command is not registered + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisCommandParser_ContainsCommand(MetisCommandParser *parser, const char *command); +#endif // Metis_metis_command_parser_h diff --git a/metis/ccnx/forwarder/metis/config/metis_CommandReturn.h b/metis/ccnx/forwarder/metis/config/metis_CommandReturn.h new file mode 100644 index 00000000..f12215d3 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_CommandReturn.h @@ -0,0 +1,42 @@ +/* + * 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_CommandReturn.h + * @brief The return code used by CLI commands + * + * This return code is used throughout the command parser and command implementations + * to indicate success, failure, or if the program should exit. + * + */ + +#ifndef Metis_metis_command_return_h +#define Metis_metis_command_return_h + +/** + * @typedef MetisControlReturn + * @abstract A command returns one of (SUCCESS, FAILURE, EXIT) + * @constant SUCCESS means the command succeeded + * @constant FAILURE indicates failure + * @constant EXIT means the command indicated that metis-control should exit. + * @discussion <#Discussion#> + */ +typedef enum metis_command_return { + MetisCommandReturn_Success, // command returned success + MetisCommandReturn_Failure, // command failure + MetisCommandReturn_Exit // command indicates program should exit +} MetisCommandReturn; + +#endif // Metis_metis_command_return_h diff --git a/metis/ccnx/forwarder/metis/config/metis_Configuration.c b/metis/ccnx/forwarder/metis/config/metis_Configuration.c new file mode 100644 index 00000000..3703c6ef --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_Configuration.c @@ -0,0 +1,936 @@ +/* + * 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. + */ + +/** + * + * Example: + * @code + * <#example#> + * @endcode + */ +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <arpa/inet.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_HashMap.h> +#include <parc/algol/parc_String.h> + +#include <ccnx/api/control/cpi_InterfaceSet.h> +#include <ccnx/api/control/cpi_ConnectionEthernet.h> +#include <ccnx/api/control/cpi_Listener.h> +#include <ccnx/api/control/controlPlaneInterface.h> +#include <ccnx/api/control/cpi_InterfaceIPTunnel.h> +#include <ccnx/api/control/cpi_InterfaceIPTunnelList.h> +#include <ccnx/api/control/cpi_ForwardingStrategy.h> + +#include <ccnx/forwarder/metis/config/metis_CommandLineInterface.h> +#include <ccnx/forwarder/metis/config/metis_SymbolicNameTable.h> +#include <ccnx/forwarder/metis/config/metis_ConfigurationListeners.h> + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_System.h> +#include <ccnx/forwarder/metis/core/metis_ConnectionTable.h> +#include <ccnx/forwarder/metis/core/metis_Connection.h> + +#include <ccnx/forwarder/metis/io/metis_TcpTunnel.h> +#include <ccnx/forwarder/metis/io/metis_UdpTunnel.h> +#include <ccnx/forwarder/metis/io/metis_UdpConnection.h> +#include <ccnx/forwarder/metis/io/metis_EtherListener.h> +#include <ccnx/forwarder/metis/io/metis_EtherConnection.h> + +#include <ccnx/forwarder/metis/metis_About.h> + +#define ETHERTYPE 0x0801 + +struct metis_configuration { + MetisForwarder *metis; + MetisLogger *logger; + MetisCommandLineInterface *cli; + + size_t maximumContentObjectStoreSize; + + // map from prefix to strategy + PARCHashMap *strategy_map; + + // translates between a symblic name and a connection id + MetisSymbolicNameTable *symbolicNameTable; +}; + + +// ======================================================================================== + +MetisConfiguration * +metisConfiguration_Create(MetisForwarder *metis) +{ + assertNotNull(metis, "Parameter metis must be non-null"); + MetisConfiguration *config = parcMemory_AllocateAndClear(sizeof(MetisConfiguration)); + assertNotNull(config, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisConfiguration)); + config->metis = metis; + config->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + config->cli = NULL; + config->maximumContentObjectStoreSize = 100000; + config->strategy_map = parcHashMap_Create(); + config->symbolicNameTable = metisSymbolicNameTable_Create(); + + return config; +} + +void +metisConfiguration_Destroy(MetisConfiguration **configPtr) +{ + assertNotNull(configPtr, "Parameter must be non-null double poitner"); + assertNotNull(*configPtr, "Parameter must dereference to non-null pointer"); + + MetisConfiguration *config = *configPtr; + metisLogger_Release(&config->logger); + if (config->cli != NULL) { + metisCommandLineInterface_Destroy(&config->cli); + } + parcHashMap_Release(&(config->strategy_map)); + metisSymbolicNameTable_Destroy(&config->symbolicNameTable); + parcMemory_Deallocate((void **) &config); + *configPtr = NULL; +} + +void +metisConfiguration_StartCLI(MetisConfiguration *config, uint16_t port) +{ + assertNull(config->cli, "You cannot start more than one CLI"); + config->cli = metisCommandLineInterface_Create(config->metis, port); + metisCommandLineInterface_Start(config->cli); +} + +PARCJSON * +metisConfiguration_GetVersion(MetisConfiguration *config) +{ + PARCJSON *fwd = parcJSON_Create(); + parcJSON_AddString(fwd, "NAME", metisAbout_Name()); + parcJSON_AddString(fwd, "COPYRIGHT", metisAbout_MiniNotice()); + parcJSON_AddString(fwd, "VERSION", metisAbout_Version()); + return fwd; +} + +static void +metisConfiguration_SendResponse(MetisConfiguration *config, CCNxControl *response, unsigned egressId) +{ + PARCBuffer *responseBuffer = metisTlv_EncodeControlPlaneInformation(response); + MetisMessage *tlvEncodedResponse = metisMessage_CreateFromParcBuffer(responseBuffer, 0, metisForwarder_GetTicks(config->metis), metisForwarder_GetLogger(config->metis)); + + parcBuffer_Release(&responseBuffer); + + MetisConnectionTable *connectionTable = metisForwarder_GetConnectionTable(config->metis); + const MetisConnection *conn = metisConnectionTable_FindById(connectionTable, egressId); + assertNotNull(conn, "Got null connection for control message we just received"); + + metisConnection_Send(conn, tlvEncodedResponse); + metisMessage_Release(&tlvEncodedResponse); +} + +static CCNxControl * +_createNack(MetisConfiguration *config, CCNxControl *control, unsigned ingressId) +{ + PARCJSON *json = ccnxControl_GetJson(control); + + PARCJSON *jsonNack = cpiAcks_CreateNack(json); + + CCNxControl *response = ccnxControl_CreateCPIRequest(jsonNack); + parcJSON_Release(&jsonNack); + return response; +} + +static CCNxControl * +_createAck(MetisConfiguration *config, CCNxControl *control, unsigned ingressId) +{ + PARCJSON *json = ccnxControl_GetJson(control); + PARCJSON *jsonAck = cpiAcks_CreateAck(json); + + CCNxControl *response = ccnxControl_CreateCPIRequest(jsonAck); + parcJSON_Release(&jsonAck); + return response; +} + + +static CCNxControl * +metisConfiguration_ProcessForwarderVersion(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + PARCJSON *fwd = metisConfiguration_GetVersion(config); + + // this process of getting to a MetisMesage needs streamlining + + CCNxControl *response = cpi_CreateResponse(request, fwd); + return response; +} + +static CCNxControl * +metisConfiguration_ProcessInterfaceList(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + CPIInterfaceSet *set = metisSystem_Interfaces(config->metis); + PARCJSON *setJson = cpiInterfaceSet_ToJson(set); + + CCNxControl *response = cpi_CreateResponse(request, setJson); + parcJSON_Release(&setJson); + cpiInterfaceSet_Destroy(&set); + return response; +} + +static bool +_symbolicRegisterPrefix(MetisConfiguration *config, CPIRouteEntry *route) +{ + bool success = false; + + const char *symbolic = cpiRouteEntry_GetSymbolicName(route); + unsigned ifidx = metisSymbolicNameTable_Get(config->symbolicNameTable, symbolic); + if (ifidx != UINT32_MAX) { + cpiRouteEntry_SetInterfaceIndex(route, ifidx); + if (metisLogger_IsLoggable(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Debug)) { + metisLogger_Log(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Debug, __func__, + "Add route resolve name '%s' to connid %u", + symbolic, ifidx); + } + + success = metisForwarder_AddOrUpdateRoute(config->metis, route); + } else { + if (metisLogger_IsLoggable(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Warning)) { + metisLogger_Log(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Warning, __func__, + "Add route symbolic name '%s' could not be resolved", symbolic); + } + // this is a failure + } + return success; +} + +static CCNxControl * +metisConfiguration_ProcessRegisterPrefix(MetisConfiguration *config, CCNxControl *control, unsigned ingressId) +{ + CPIRouteEntry *route = cpiForwarding_RouteFromControlMessage(control); + + bool success = false; + + // if it has a symbolic name set the interface index + if (cpiRouteEntry_GetSymbolicName(route) != NULL) { + success = _symbolicRegisterPrefix(config, route); + } else { + if (cpiRouteEntry_GetInterfaceIndex(route) == CPI_CURRENT_INTERFACE) { + // We want to use the ingress interface + cpiRouteEntry_SetInterfaceIndex(route, ingressId); + } + success = metisForwarder_AddOrUpdateRoute(config->metis, route); + } + + CCNxControl *response; + if (success) { + response = _createAck(config, control, ingressId); + } else { + response = _createNack(config, control, ingressId); + } + + cpiRouteEntry_Destroy(&route); + return response; +} + +static CCNxControl * +metisConfiguration_ProcessUnregisterPrefix(MetisConfiguration *config, CCNxControl *control, unsigned ingressId) +{ + CPIRouteEntry *route = cpiForwarding_RouteFromControlMessage(control); + + bool success = false; + + if (cpiRouteEntry_GetSymbolicName(route) != NULL) { + const char *symbolic = cpiRouteEntry_GetSymbolicName(route); + unsigned ifidx = metisSymbolicNameTable_Get(config->symbolicNameTable, symbolic); + if (ifidx != UINT32_MAX) { + cpiRouteEntry_SetInterfaceIndex(route, ifidx); + success = metisForwarder_RemoveRoute(config->metis, route); + } else { + // this is a failure + success = false; + } + } else { + if (cpiRouteEntry_GetInterfaceIndex(route) == CPI_CURRENT_INTERFACE) { + // We want to use the ingress interface + cpiRouteEntry_SetInterfaceIndex(route, ingressId); + } + success = metisForwarder_RemoveRoute(config->metis, route); + } + + CCNxControl *response; + if (success) { + response = _createAck(config, control, ingressId); + } else { + response = _createNack(config, control, ingressId); + } + + cpiRouteEntry_Destroy(&route); + return response; +} + +static CCNxControl * +metisConfiguration_ProcessRegistrationList(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + MetisFibEntryList *fibList = metisForwarder_GetFibEntries(config->metis); + + CPIRouteEntryList *routeEntryList = cpiRouteEntryList_Create(); + for (size_t i = 0; i < metisFibEntryList_Length(fibList); i++) { + const MetisFibEntry *fibEntry = metisFibEntryList_Get(fibList, i); + MetisTlvName *prefix = metisFibEntry_GetPrefix(fibEntry); + const MetisNumberSet *nexthops = metisFibEntry_GetNexthops(fibEntry); + + for (int j = 0; j < metisNumberSet_Length(nexthops); j++) { + CPIRouteEntry *route = cpiRouteEntry_Create(metisTlvName_ToCCNxName(prefix), + metisNumberSet_GetItem(nexthops, j), + NULL, + cpiNameRouteProtocolType_STATIC, + cpiNameRouteType_LONGEST_MATCH, + NULL, // lifetime + 1); // cost + cpiRouteEntryList_Append(routeEntryList, route); + } + + metisTlvName_Release(&prefix); + } + PARCJSON *entryListJson = cpiRouteEntryList_ToJson(routeEntryList); + CCNxControl *response = cpi_CreateResponse(request, entryListJson); + parcJSON_Release(&entryListJson); + cpiRouteEntryList_Destroy(&routeEntryList); + metisFibEntryList_Destroy(&fibList); + return response; +} + +static void +_logProcessCreateTunnelMessage(MetisConfiguration *config, CPIInterfaceIPTunnel *iptun, PARCLogLevel logLevel, const char *message) +{ + if (metisLogger_IsLoggable(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Info)) { + const CPIAddress *source = cpiInterfaceIPTunnel_GetSourceAddress(iptun); + const CPIAddress *destination = cpiInterfaceIPTunnel_GetDestinationAddress(iptun); + + const char *symbolicName = cpiInterfaceIPTunnel_GetSymbolicName(iptun); + + char *sourceString = cpiAddress_ToString(source); + char *remoteString = cpiAddress_ToString(destination); + + metisLogger_Log(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Info, __func__, + "Add connection %s on %s to %s: %s", + symbolicName, + sourceString, + remoteString, + message); + + parcMemory_Deallocate((void **) &sourceString); + parcMemory_Deallocate((void **) &remoteString); + } +} + +/** + * Add an IP-based tunnel. + * + * The call cal fail if the symbolic name is a duplicate. It could also fail if there's an problem creating + * the local side of the tunnel (i.e. the local socket address is not usable). + * + * @return true Tunnel added + * @return false Tunnel not added (an error) + */ +static CCNxControl * +metisConfiguration_ProcessCreateTunnel(MetisConfiguration *config, CCNxControl *control, unsigned ingressId) +{ + bool success = false; + + CPIInterfaceIPTunnel *iptun = cpiLinks_CreateIPTunnelFromControlMessage(control); + + const char *symbolicName = cpiInterfaceIPTunnel_GetSymbolicName(iptun); + + if (!metisSymbolicNameTable_Exists(config->symbolicNameTable, symbolicName)) { + const CPIAddress *source = cpiInterfaceIPTunnel_GetSourceAddress(iptun); + const CPIAddress *destination = cpiInterfaceIPTunnel_GetDestinationAddress(iptun); + + MetisIoOperations *ops = NULL; + switch (cpiInterfaceIPTunnel_GetTunnelType(iptun)) { + case IPTUN_TCP: + ops = metisTcpTunnel_Create(config->metis, source, destination); + break; + case IPTUN_UDP: + ops = metisUdpTunnel_Create(config->metis, source, destination); + break; + case IPTUN_GRE: + metisLogger_Log(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Error, __func__, + "Unsupported tunnel protocol: GRE"); + break; + default: + metisLogger_Log(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Error, __func__, + "Unsupported tunnel protocol: %d", + cpiInterfaceIPTunnel_GetTunnelType(iptun)); + break; + } + + + if (ops != NULL) { + MetisConnection *conn = metisConnection_Create(ops); + + const char *suffix = "wldr"; + size_t suffix_len = 4; + size_t name_len = strlen(symbolicName); + if (suffix_len < name_len && (strncmp(symbolicName + name_len - suffix_len, suffix, suffix_len) == 0)) { + printf("WARNING: WLDR initialized with legacy code. please use the command set wldr <on|off> <connId>\n"); + printf("see metis_control help for more information\n"); + metisConnection_EnableWldr(conn); + } + + metisConnectionTable_Add(metisForwarder_GetConnectionTable(config->metis), conn); + metisSymbolicNameTable_Add(config->symbolicNameTable, symbolicName, metisConnection_GetConnectionId(conn)); + + success = true; + + _logProcessCreateTunnelMessage(config, iptun, PARCLogLevel_Info, "success"); + } else { + _logProcessCreateTunnelMessage(config, iptun, PARCLogLevel_Warning, "failed, could not create IoOperations"); + } + } else { + _logProcessCreateTunnelMessage(config, iptun, PARCLogLevel_Warning, "failed, symbolic name exists"); + } + + // send the ACK or NACK + CCNxControl *response; + if (success) { + response = _createAck(config, control, ingressId); + } else { + response = _createNack(config, control, ingressId); + } + + cpiInterfaceIPTunnel_Release(&iptun); + + return response; +} + +static bool +_metisConfiguration_AddConnectionEthernet(MetisConfiguration *config, CPIConnectionEthernet *etherConn, CPIAddress *linkAddress, MetisListenerOps *listenerOps) +{ + bool success = false; + + const char *symbolic = cpiConnectionEthernet_GetSymbolicName(etherConn); + if (!metisSymbolicNameTable_Exists(config->symbolicNameTable, symbolic)) { + const CPIAddress *remote = cpiConnectionEthernet_GetPeerLinkAddress(etherConn); + MetisAddressPair *pair = metisAddressPair_Create(linkAddress, remote); + + MetisGenericEther *ether = metisEtherListener_GetGenericEtherFromListener(listenerOps); + + if (ether) { + MetisIoOperations *ops = metisEtherConnection_Create(config->metis, ether, pair); + + if (ops) { + MetisConnection *conn = metisConnection_Create(ops); + assertNotNull(conn, "Failed to create connection"); + + metisConnectionTable_Add(metisForwarder_GetConnectionTable(config->metis), conn); + metisSymbolicNameTable_Add(config->symbolicNameTable, symbolic, metisConnection_GetConnectionId(conn)); + + success = true; + + if (metisLogger_IsLoggable(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Debug)) { + char *peerAddressString = cpiAddress_ToString(remote); + metisLogger_Log(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Debug, __func__, + "Add connection %s on %s to %s, connid %u", + symbolic, + cpiConnectionEthernet_GetInterfaceName(etherConn), + peerAddressString, + metisConnection_GetConnectionId(conn)); + parcMemory_Deallocate((void **) &peerAddressString); + } + } + } else { + metisLogger_Log(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Error, __func__, + "Could not get MetisGenericEther for listener %p", + listenerOps); + } + + metisAddressPair_Release(&pair); + } else { + if (metisLogger_IsLoggable(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Warning)) { + const CPIAddress *remote = cpiConnectionEthernet_GetPeerLinkAddress(etherConn); + char *peerAddressString = cpiAddress_ToString(remote); + metisLogger_Log(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Warning, __func__, + "Add connection %s on %s to %s failed, symbolic name exists", + symbolic, + cpiConnectionEthernet_GetInterfaceName(etherConn), + peerAddressString); + parcMemory_Deallocate((void **) &peerAddressString); + } + } + + return success; +} + + +static CCNxControl * +metisConfiguration_ProcessAddConnectionEthernet(MetisConfiguration *config, CCNxControl *control, unsigned ingressId) +{ + bool success = false; + + CPIConnectionEthernet *etherConn = cpiConnectionEthernet_FromControl(control); + assertNotNull(etherConn, "Control message did not parse to CPIConnectionEthernet"); + + if (cpiConnectionEthernet_GetEthertype(etherConn) == ETHERTYPE) { + CPIAddress *linkAddress = metisSystem_GetMacAddressByName(config->metis, cpiConnectionEthernet_GetInterfaceName(etherConn)); + if (linkAddress != NULL) { + MetisListenerSet *listenerSet = metisForwarder_GetListenerSet(config->metis); + MetisListenerOps *listenerOps = metisListenerSet_Find(listenerSet, METIS_ENCAP_ETHER, linkAddress); + + if (listenerOps) { + // Now add the connection + success = _metisConfiguration_AddConnectionEthernet(config, etherConn, linkAddress, listenerOps); + } else { + char *str = cpiAddress_ToString(linkAddress); + metisLogger_Log(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Error, __func__, + "Could not find listener for interface '%s' addr %s ethertype 0x%04x", + cpiConnectionEthernet_GetInterfaceName(etherConn), + str, + cpiConnectionEthernet_GetEthertype(etherConn)); + parcMemory_Deallocate((void **) &str); + } + + + cpiAddress_Destroy(&linkAddress); + } else { + metisLogger_Log(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Error, __func__, + "Could not resolve interface '%s' to a MAC address", + cpiConnectionEthernet_GetInterfaceName(etherConn)); + } + } else { + metisLogger_Log(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Error, __func__, + "Control message asked for ethertype %04x, only %04x supported", + cpiConnectionEthernet_GetEthertype(etherConn), ETHERTYPE); + } + + CCNxControl *response; + if (success) { + response = _createAck(config, control, ingressId); + } else { + response = _createNack(config, control, ingressId); + } + + cpiConnectionEthernet_Release(ðerConn); + return response; +} + +static CCNxControl * +metisConfiguration_ProcessRemoveConnectionEthernet(MetisConfiguration *config, CCNxControl *control, unsigned ingressId) +{ + printf("%s not implemented\n", __func__); + return _createNack(config, control, ingressId); +} + + +static CCNxControl * +metisConfiguration_ProcessRemoveTunnel(MetisConfiguration *config, CCNxControl *control, unsigned ingressId) +{ + CPIInterfaceIPTunnel *iptun = cpiLinks_CreateIPTunnelFromControlMessage(control); + const char *symbolic = cpiInterfaceIPTunnel_GetSymbolicName(iptun); + unsigned ifidx = metisSymbolicNameTable_Get(config->symbolicNameTable, symbolic); + if (ifidx != UINT32_MAX) { + //remove connection from the FIB + metisForwarder_RemoveConnectionIdFromRoutes(config->metis, ifidx); + MetisConnectionTable *table = metisForwarder_GetConnectionTable(config->metis); + //remove connection from connection table + metisConnectionTable_RemoveById(table, ifidx); + //remove connection from symbolicNameTable + metisSymbolicNameTable_Remove(config->symbolicNameTable, symbolic); + //and ... this is it! + CCNxControl *response = _createAck(config, control, ingressId); + cpiInterfaceIPTunnel_Release(&iptun); + return response; + } else { + CCNxControl *response = _createNack(config, control, ingressId); + cpiInterfaceIPTunnel_Release(&iptun); + return response; + } +} + +static CCNxControl * +metisConfiguration_ProcessConnectionList(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + CPIConnectionList *tunnelList = cpiConnectionList_Create(); + + MetisConnectionTable *table = metisForwarder_GetConnectionTable(config->metis); + MetisConnectionList *connList = metisConnectionTable_GetEntries(table); + + for (size_t i = 0; i < metisConnectionList_Length(connList); i++) { + // Don't release original, we're not storing it + MetisConnection *original = metisConnectionList_Get(connList, i); + const MetisAddressPair *addressPair = metisConnection_GetAddressPair(original); + CPIAddress *localAddress = cpiAddress_Copy(metisAddressPair_GetLocal(addressPair)); + CPIAddress *remoteAddress = cpiAddress_Copy(metisAddressPair_GetRemote(addressPair)); + + CPIConnectionType type = metisIoOperations_GetConnectionType(metisConnection_GetIoOperations(original)); + + CPIConnection *cpiConn = cpiConnection_Create(metisConnection_GetConnectionId(original), + localAddress, + remoteAddress, + type); + + cpiConnection_SetState(cpiConn, metisConnection_IsUp(original) ? CPI_IFACE_UP : CPI_IFACE_DOWN); + cpiConnectionList_Append(tunnelList, cpiConn); + } + + PARCJSON *connectListJson = cpiConnectionList_ToJson(tunnelList); + CCNxControl *response = cpi_CreateResponse(request, connectListJson); + parcJSON_Release(&connectListJson); + cpiConnectionList_Destroy(&tunnelList); + metisConnectionList_Destroy(&connList); + + return response; +} + +static CCNxControl * +metisConfiguration_ProcessCacheStoreOn(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + bool success = false; + + metisForwarder_SetChacheStoreFlag(config->metis, true); + if (metisForwarder_GetChacheStoreFlag(config->metis)) { + success = true; + } + + CCNxControl *response; + if (success) { + response = _createAck(config, request, ingressId); + } else { + response = _createNack(config, request, ingressId); + } + return response; +} + +static CCNxControl * +metisConfiguration_ProcessCacheStoreOff(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + bool success = false; + + metisForwarder_SetChacheStoreFlag(config->metis, false); + if (!metisForwarder_GetChacheStoreFlag(config->metis)) { + success = true; + } + + CCNxControl *response; + if (success) { + response = _createAck(config, request, ingressId); + } else { + response = _createNack(config, request, ingressId); + } + return response; +} + +static CCNxControl * +metisConfiguration_ProcessCacheServeOn(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + bool success = true; + + metisForwarder_SetChacheServeFlag(config->metis, true); + if (metisForwarder_GetChacheServeFlag(config->metis)) { + success = true; + } + + CCNxControl *response; + if (success) { + response = _createAck(config, request, ingressId); + } else { + response = _createNack(config, request, ingressId); + } + return response; +} + +static CCNxControl * +metisConfiguration_ProcessCacheServeOff(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + bool success = true; + + metisForwarder_SetChacheServeFlag(config->metis, false); + if (!metisForwarder_GetChacheServeFlag(config->metis)) { + success = true; + } + + + CCNxControl *response; + if (success) { + response = _createAck(config, request, ingressId); + } else { + response = _createNack(config, request, ingressId); + } + return response; +} + +static CCNxControl * +metisConfiguration_ProcessCacheClear(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + metisForwarder_ClearCache(config->metis); + + CCNxControl *response = _createAck(config, request, ingressId); + + return response; +} + + +size_t +metisConfiguration_GetObjectStoreSize(MetisConfiguration *config) +{ + return config->maximumContentObjectStoreSize; +} + +static CCNxControl * +metisConfiguration_SetForwarginStrategy(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + CPIForwardingStrategy *fwdStrategy = cpiForwarding_ForwardingStrategyFromControlMessage(request); + + const CCNxName *prefix = cpiForwardingStrategy_GetPrefix(fwdStrategy); + const char *strategy = cpiForwardingStrategy_GetStrategy(fwdStrategy); + const char *existingFwdStrategy = metisConfiguration_GetForwarginStrategy(config, prefix); + + if ((existingFwdStrategy == NULL) || (strcmp(strategy, existingFwdStrategy) != 0)) { + PARCString *fwdStrategy = parcString_Create(strategy); + parcHashMap_Put(config->strategy_map, prefix, fwdStrategy); + metisForwarder_SetStrategy(config->metis, (CCNxName *) prefix, (char *) strategy); + } + + cpiForwardingStrategy_Destroy(&fwdStrategy); + CCNxControl *response = _createAck(config, request, ingressId); + return response; +} + +static CCNxControl * +metisConfiguration_SetWldr(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + CPIManageWldr *cpiWldr = cpiLinks_ManageWldrFromControlMessage(request); + + const char * connId = cpiManageWldr_GetConnection(cpiWldr); + bool active = cpiManageWldr_IsActive(cpiWldr); + + bool success = false; + unsigned ifidx = metisSymbolicNameTable_Get(config->symbolicNameTable, connId); + if (ifidx != UINT32_MAX) { + MetisConnectionTable *table = metisForwarder_GetConnectionTable(config->metis); + MetisConnection *conn = (MetisConnection *) metisConnectionTable_FindById(table, ifidx); + if(conn == NULL){ + success = false; + } else { + if(active){ + metisConnection_EnableWldr(conn); + } else { + metisConnection_DisableWldr(conn); + } + success = true; + } + } else { + success = false; + } + + CCNxControl *response; + if(success){ + response = _createAck(config, request, ingressId); + } else { + response = _createNack(config, request, ingressId); + } + + cpiManageWldr_Destroy(&cpiWldr); + return response; +} + +const char * +metisConfiguration_GetForwarginStrategy(MetisConfiguration *config, const CCNxName *prefix) +{ + const PARCString *val = parcHashMap_Get(config->strategy_map, prefix); + if (val == NULL) { + return NULL; + } else { + return (const char *) parcString_ToString(val); + } +} + +void +metisConfiguration_SetObjectStoreSize(MetisConfiguration *config, size_t maximumObjectCount) +{ + config->maximumContentObjectStoreSize = maximumObjectCount; + + metisForwarder_SetContentObjectStoreSize(config->metis, config->maximumContentObjectStoreSize); +} + +MetisForwarder * +metisConfiguration_GetForwarder(const MetisConfiguration *config) +{ + return config->metis; +} + +MetisLogger * +metisConfiguration_GetLogger(const MetisConfiguration *config) +{ + return config->logger; +} + +// =========================== +// Main functions that deal with receiving commands, executing them, and sending ACK/NACK + +static CCNxControl * +metisConfiguration_DispatchCommandOldStyle(MetisConfiguration *config, CCNxControl *control, unsigned ingressId) +{ + CCNxControl *response = NULL; + switch (cpi_GetMessageOperation(control)) { + case CPI_FORWARDER_VERSION: + response = metisConfiguration_ProcessForwarderVersion(config, control, ingressId); + break; + + case CPI_INTERFACE_LIST: + response = metisConfiguration_ProcessInterfaceList(config, control, ingressId); + break; + + case CPI_PREFIX_REGISTRATION_LIST: + response = metisConfiguration_ProcessRegistrationList(config, control, ingressId); + break; + + case CPI_REGISTER_PREFIX: + response = metisConfiguration_ProcessRegisterPrefix(config, control, ingressId); + break; + + case CPI_UNREGISTER_PREFIX: + response = metisConfiguration_ProcessUnregisterPrefix(config, control, ingressId); + break; + + case CPI_CREATE_TUNNEL: + response = metisConfiguration_ProcessCreateTunnel(config, control, ingressId); + break; + + case CPI_REMOVE_TUNNEL: + response = metisConfiguration_ProcessRemoveTunnel(config, control, ingressId); + break; + + case CPI_CONNECTION_LIST: + response = metisConfiguration_ProcessConnectionList(config, control, ingressId); + break; + + case CPI_PAUSE: + break; + + case CPI_CACHE_STORE_ON: + response = metisConfiguration_ProcessCacheStoreOn(config, control, ingressId); + break; + + case CPI_CACHE_STORE_OFF: + response = metisConfiguration_ProcessCacheStoreOff(config, control, ingressId); + break; + + case CPI_CACHE_SERVE_ON: + response = metisConfiguration_ProcessCacheServeOn(config, control, ingressId); + break; + + case CPI_CACHE_SERVE_OFF: + response = metisConfiguration_ProcessCacheServeOff(config, control, ingressId); + break; + + case CPI_CACHE_CLEAR: + response = metisConfiguration_ProcessCacheClear(config, control, ingressId); + break; + + case CPI_SET_FORWARDING_STRATEGY: + response = metisConfiguration_SetForwarginStrategy(config, control, ingressId); + break; + + case CPI_SET_WLDR: + response = metisConfiguration_SetWldr(config, control, ingressId); + break; + + default: + break; + } + + return response; +} + +static CCNxControl * +_processControl(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + CCNxControl *response = NULL; + + switch (cpi_GetMessageType(request)) { + case CPI_REQUEST: { + if (cpiConnectionEthernet_IsAddMessage(request)) { + response = metisConfiguration_ProcessAddConnectionEthernet(config, request, ingressId); + } else if (cpiConnectionEthernet_IsRemoveMessage(request)) { + response = metisConfiguration_ProcessRemoveConnectionEthernet(config, request, ingressId); + } else if (cpiListener_IsAddMessage(request)) { + bool success = metisConfigurationListeners_Add(config, request, ingressId); + if (success) { + response = _createAck(config, request, ingressId); + } else { + response = _createNack(config, request, ingressId); + } + } else if (cpiListener_IsRemoveMessage(request)) { + bool success = metisConfigurationListeners_Remove(config, request, ingressId); + if (success) { + response = _createAck(config, request, ingressId); + } else { + response = _createNack(config, request, ingressId); + } + } else { + response = metisConfiguration_DispatchCommandOldStyle(config, request, ingressId); + } + break; + } + + default: + break; + } + + assertNotNull(response, "Got null CCNxControl response"); + return response; +} + +CCNxControl * +metisConfiguration_ReceiveControl(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + assertNotNull(config, "Parameter config must be non-null"); + assertNotNull(request, "Parameter request must be non-null"); + + CCNxControl *response = _processControl(config, request, ingressId); + return response; +} + +void +metisConfiguration_Receive(MetisConfiguration *config, MetisMessage *message) +{ + assertNotNull(config, "Parameter config must be non-null"); + assertNotNull(message, "Parameter message must be non-null"); + assertTrue(metisMessage_GetType(message) == MetisMessagePacketType_Control, + "Message must be type CPI, expected %02x got %02x", + MetisMessagePacketType_Control, metisMessage_GetType(message)); + + CCNxControl *control = metisMessage_CreateControlMessage(message); + unsigned ingressId = metisMessage_GetIngressConnectionId(message); + + if (metisLogger_IsLoggable(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Debug)) { + char *str = parcJSON_ToCompactString(ccnxControl_GetJson(control)); + metisLogger_Log(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Debug, __func__, + "%s received %s\n", __func__, str); + parcMemory_Deallocate((void **) &str); + } + + CCNxControl *response = _processControl(config, control, ingressId); + metisConfiguration_SendResponse(config, response, ingressId); + ccnxControl_Release(&response); + + ccnxControl_Release(&control); + metisMessage_Release(&message); +} + + diff --git a/metis/ccnx/forwarder/metis/config/metis_Configuration.h b/metis/ccnx/forwarder/metis/config/metis_Configuration.h new file mode 100644 index 00000000..59b18821 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_Configuration.h @@ -0,0 +1,200 @@ +/* + * 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_Configuration.h + * @brief Metis configuration, such as in-band commands or CLI + * + * Manages all user configuration of the system, such as from the CLI or web interface + * It remembers the user commands and will be able to write out a config file. + * + */ + +#ifndef Metis_metis_Configuration_h +#define Metis_metis_Configuration_h + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +struct metis_configuration; +typedef struct metis_configuration MetisConfiguration; + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisConfiguration *metisConfiguration_Create(MetisForwarder *metis); + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisConfiguration_Destroy(MetisConfiguration **configPtr); + +void metisConfiguration_SetupAllListeners(MetisConfiguration *config, uint16_t port, const char *localPath); + +/** + * Receive a CPI control message from the user encapsulated in a MetisMessage + * + * Takes ownership of the message, and will destroy it as needed. + * + * @param message is of type CCNX_MSG_CPI. + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisConfiguration_Receive(MetisConfiguration *config, MetisMessage *message); + +/** + * Receives a CPI control message from the user + * + * Processes the message and generates the CPI control response. The response should always + * be non-null and must be released by the caller. + * + * @param [in] config Allocated MetisConfiguration + * @param [in] request The CPI Request to process + * @param [in] ingressId The ingress connection ID, used to track in logging messages + * + * @retval CCNxControl The response control message (an ACK, NACK, or Response). + * + * Example: + * @code + * <#example#> + * @endcode + */ +CCNxControl *metisConfiguration_ReceiveControl(MetisConfiguration *config, CCNxControl *request, unsigned ingressId); + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +PARCJSON *metisConfiguration_GetVersion(MetisConfiguration *config); + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisConfiguration_StartCLI(MetisConfiguration *config, uint16_t port); + +/** + * Returns the configured size of the content store + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +size_t metisConfiguration_GetObjectStoreSize(MetisConfiguration *config); + +/** + * Sets the size of the content store (in objects, not bytes) + * + * Must be set before starting the forwarder + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisConfiguration_SetObjectStoreSize(MetisConfiguration *config, size_t maximumContentObjectCount); + +const char * metisConfiguration_GetForwarginStrategy(MetisConfiguration *config, const CCNxName * prefix); + +/** + * Returns the MetisForwarder that owns the MetisConfiguration + * + * Returns the Metis Forwarder. Used primarily by associated classes in the + * configuration group. + * + * @param [in] config An allocated MetisConfiguration + * + * @return non-null The owning MetisForwarder + * @return null An error + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +MetisForwarder *metisConfiguration_GetForwarder(const MetisConfiguration *config); + +/** + * Returns the logger used by the Configuration subsystem + * + * Returns the logger specified when the MetisConfiguration was created. + * + * @param [in] config An allocated MetisConfiguration + * + * @retval non-null The logger + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisLogger *metisConfiguration_GetLogger(const MetisConfiguration *config); +#endif // Metis_metis_Configuration_h diff --git a/metis/ccnx/forwarder/metis/config/metis_ConfigurationFile.c b/metis/ccnx/forwarder/metis/config/metis_ConfigurationFile.c new file mode 100644 index 00000000..59e4c5a8 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_ConfigurationFile.c @@ -0,0 +1,313 @@ +/* + * 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 <errno.h> +#include <string.h> +#include <ctype.h> +#include <stdio.h> + +#include <LongBow/longBow_Runtime.h> +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_Object.h> +#include <parc/algol/parc_ArrayList.h> +#include <parc/algol/parc_List.h> +#include <ccnx/forwarder/metis/config/metis_ConfigurationFile.h> +#include <ccnx/forwarder/metis/config/metis_Configuration.h> +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +#include <ccnx/forwarder/metis/config/metisControl_Root.h> + +struct metis_configuration_file { + MetisForwarder *metis; + const char *filename; + FILE *fh; + + size_t linesRead; + + // our custom state machine. + MetisControlState *controlState; +}; + + +/** + * Called by the command parser for each command. + * + * The command parser will make a CCNxControl message inside the CCNxMetaMessage and send it here. + * This function must return a ACK or NACK in a CCNxControl in a CCNxMetaMessage. + * + * @param [in] userdata A void * to MetisConfigurationFile + * @param [in] msg The CCNxControl message to process + * + * @retval CCNxMetaMessage A CPI ACK or NACK in a CCNxControl in a CCNxMetaMessage + * + * Example: + * @code + * <#example#> + * @endcode + */ +static CCNxMetaMessage * +_writeRead(void *userdata, CCNxMetaMessage *msgin) +{ + MetisConfigurationFile *configFile = (MetisConfigurationFile *) userdata; + + CCNxControl *request = ccnxMetaMessage_GetControl(msgin); + CCNxControl *response = metisConfiguration_ReceiveControl(metisForwarder_GetConfiguration(configFile->metis), request, 0); + + CCNxMetaMessage *msgout = ccnxMetaMessage_CreateFromControl(response); + ccnxControl_Release(&response); + + return msgout; +} + +/** + * Removes leading whitespace (space + tab). + * + * If the string is all whitespace, the return value will point to the terminating '\0'. + * + * @param [in] str A null-terminated c-string + * + * @retval non-null A pointer in to string of the first non-whitespace + * + * + * Example: + * @code + * <#example#> + * @endcode + */ +static char * +_stripLeadingWhitespace(char *str) +{ + while (isspace(*str)) { + str++; + } + return str; +} + +/** + * Removes trailing whitespace + * + * Inserts a NULL after the last non-whitespace character, modiyfing the input string. + * + * @param [in] str A null-terminated c-string + * + * @return non-null A pointer to the input string + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +static char * +_stripTrailingWhitespace(char *str) +{ + char *p = str + strlen(str) - 1; + while (p > str && isspace(*p)) { + p--; + } + + // cap it. If no whitespace, p+1 == str + strlen(str), so will overwrite the + // current null. If all whitespace p+1 == str+1. For an empty string, p+1 = str. + *(p + 1) = 0; + + // this does not catch the case where the entire string is whitespace + if (p == str && isspace(*p)) { + *p = 0; + } + + return str; +} + +/** + * Removed leading and trailing whitespace + * + * Modifies the input string (may add a NULL at the end). Will return + * a pointer to the first non-whitespace character or the terminating NULL. + * + * @param [in] str A null-terminated c-string + * + * @return non-null A pointer in to the input string + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +static char * +_trim(char *str) +{ + return _stripTrailingWhitespace(_stripLeadingWhitespace(str)); +} + +/** + * Parse a string in to a PARCList with one word per element + * + * The string passed will be modified by inserting NULLs after each token. + * + * @param [in] str A c-string (will be modified) + * + * @retval non-null A PARCList where each item is a single word + * + * Example: + * @code + * <#example#> + * @endcode + */ +static PARCList * +_parseArgs(char *str) +{ + PARCList *list = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + + const char delimiters[] = " \t"; + + char *token; + while ((token = strsep(&str, delimiters)) != NULL) { + parcList_Add(list, token); + } + + return list; +} + +// ============================================================= + +static void +_destroy(MetisConfigurationFile **configFilePtr) +{ + MetisConfigurationFile *configFile = *configFilePtr; + parcMemory_Deallocate((void **) &configFile->filename); + + if (configFile->fh != NULL) { + fclose(configFile->fh); + } + + metisControlState_Destroy(&configFile->controlState); +} + +parcObject_ExtendPARCObject(MetisConfigurationFile, _destroy, NULL, NULL, NULL, NULL, NULL, NULL); + +parcObject_ImplementRelease(metisConfigurationFile, MetisConfigurationFile); + +MetisConfigurationFile * +metisConfigurationFile_Create(MetisForwarder *metis, const char *filename) +{ + assertNotNull(metis, "Parameter metis must be non-null"); + assertNotNull(filename, "Parameter filename must be non-null"); + + MetisConfigurationFile *configFile = parcObject_CreateInstance(MetisConfigurationFile); + + if (configFile) { + configFile->linesRead = 0; + configFile->metis = metis; + configFile->filename = parcMemory_StringDuplicate(filename, strlen(filename)); + assertNotNull(configFile->filename, "Could not copy string '%s'", filename); + + // setup the control state for the command parser + configFile->controlState = metisControlState_Create(configFile, _writeRead); + + // we do not register Help commands + metisControlState_RegisterCommand(configFile->controlState, metisControlRoot_Create(configFile->controlState)); + + // open the file and make sure we can read it + configFile->fh = fopen(configFile->filename, "r"); + + if (configFile->fh) { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug)) { + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug, __func__, + "Open config file %s", + configFile->filename); + } + } else { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Error)) { + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Error, __func__, + "Could not open config file %s: (%d) %s", + configFile->filename, + errno, + strerror(errno)); + } + + // failure cleanup the object -- this nulls it so final return null be NULL + metisConfigurationFile_Release(&configFile); + } + } + return configFile; +} + + +bool +metisConfigurationFile_Process(MetisConfigurationFile *configFile) +{ + assertNotNull(configFile, "Parameter configFile must be non-null"); + + // default to a "true" return value and only set to false if we encounter an error. + bool success = true; + + #define BUFFERLEN 2048 + char buffer[BUFFERLEN]; + + configFile->linesRead = 0; + + // always clear errors and fseek to start of file in case we get called multiple times. + clearerr(configFile->fh); + rewind(configFile->fh); + + while (success && fgets(buffer, BUFFERLEN, configFile->fh) != NULL) { + configFile->linesRead++; + + char *stripedBuffer = _trim(buffer); + if (strlen(stripedBuffer) > 0) { + if (stripedBuffer[0] != '#') { + // not empty and not a comment + + // _parseArgs will modify the string + char *copy = parcMemory_StringDuplicate(stripedBuffer, strlen(stripedBuffer)); + PARCList *args = _parseArgs(copy); + MetisCommandReturn result = metisControlState_DispatchCommand(configFile->controlState, args); + + // we ignore EXIT from the configuration file + if (result == MetisCommandReturn_Failure) { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(configFile->metis), MetisLoggerFacility_Config, PARCLogLevel_Error)) { + metisLogger_Log(metisForwarder_GetLogger(configFile->metis), MetisLoggerFacility_Config, PARCLogLevel_Error, __func__, + "Error on input file %s line %d: %s", + configFile->filename, + configFile->linesRead, + stripedBuffer); + } + success = false; + } + parcList_Release(&args); + parcMemory_Deallocate((void **) ©); + } + } + } + + if (ferror(configFile->fh)) { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(configFile->metis), MetisLoggerFacility_Config, PARCLogLevel_Error)) { + metisLogger_Log(metisForwarder_GetLogger(configFile->metis), MetisLoggerFacility_Config, PARCLogLevel_Error, __func__, + "Error on input file %s line %d: (%d) %s", + configFile->filename, + configFile->linesRead, + errno, + strerror(errno)); + } + success = false; + } + + return success; +} + diff --git a/metis/ccnx/forwarder/metis/config/metis_ConfigurationFile.h b/metis/ccnx/forwarder/metis/config/metis_ConfigurationFile.h new file mode 100644 index 00000000..e2f07085 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_ConfigurationFile.h @@ -0,0 +1,90 @@ +/* + * 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_ConfigurationFile.h + * @brief Accepts a filename and provides a means to read it into MetisConfiguration + * + * Reads a configuration file and converts the lines in to configuration commands for use + * in MetisConfiguration. + * + * Accepts '#' lines as comments. Skips blank and whitespace-only lines. + * + */ + +#ifndef Metis__metis_ConfigurationFile_h +#define Metis__metis_ConfigurationFile_h + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +struct metis_configuration_file; +typedef struct metis_configuration_file MetisConfigurationFile; + +/** + * Creates a MetisConfigurationFile to prepare to process the file + * + * Prepares the object and opens the file. Makes sure we can read the file. + * Does not read the file or process any commands from the file. + * + * @param [in] metis An allocated MetisForwarder to configure with the file + * @param [in] filename The file to use + * + * @retval non-null An allocated MetisConfigurationFile that is readable + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisConfigurationFile *metisConfigurationFile_Create(MetisForwarder *metis, const char *filename); + +/** + * Reads the configuration file line-by-line and issues commands to MetisConfiguration + * + * Reads the file line by line. Skips '#' and blank lines. Creates CPI objects from the + * lines and feeds them to MetisConfiguration. + * + * Will stop on the first error. Lines already processed will not be un-done. + * + * @param [in] configFile An allocated MetisConfigurationFile + * + * @retval true The entire files was processed without error. + * @retval false There was an error in the file. + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisConfigurationFile_Process(MetisConfigurationFile *configFile); + +//void metisConfigurationFile_ProcessForwardingStrategies(MetisConfiguration * config, MetisConfigurationFile * configFile); + +/** + * Closes the underlying file and releases memory + * + * <#Paragraphs Of Explanation#> + * + * @param [in,out] configFilePtr An allocated MetisConfigurationFile that will be NULL'd as output + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisConfigurationFile_Release(MetisConfigurationFile **configFilePtr); + +#endif /* defined(Metis__metis_ConfigurationFile_h) */ diff --git a/metis/ccnx/forwarder/metis/config/metis_ConfigurationListeners.c b/metis/ccnx/forwarder/metis/config/metis_ConfigurationListeners.c new file mode 100644 index 00000000..5d744ac1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_ConfigurationListeners.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. + */ + + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <arpa/inet.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/api/control/cpi_InterfaceSet.h> +#include <ccnx/api/control/cpi_Listener.h> +#include <ccnx/forwarder/metis/core/metis_System.h> + +#include <ccnx/forwarder/metis/config/metis_ConfigurationListeners.h> +#include <ccnx/forwarder/metis/io/metis_TcpListener.h> +#include <ccnx/forwarder/metis/io/metis_UdpListener.h> +#include <ccnx/forwarder/metis/io/metis_LocalListener.h> +#include <ccnx/forwarder/metis/io/metis_EtherListener.h> + +static bool +_setupTcpListenerOnInet(MetisForwarder *metis, const CPIAddress *address, uint16_t port) +{ + bool success = false; + struct sockaddr_in addr_sin; + cpiAddress_GetInet(address, &addr_sin); + addr_sin.sin_port = htons(port); + + MetisListenerOps *ops = metisTcpListener_CreateInet(metis, addr_sin); + if (ops) { + success = metisListenerSet_Add(metisForwarder_GetListenerSet(metis), ops); + assertTrue(success, "Failed to add TCP listener on %s to ListenerSet", cpiAddress_ToString(ops->getListenAddress(ops))); + } + return success; +} + +static bool +_setupUdpListenerOnInet(MetisForwarder *metis, const CPIAddress *address, uint16_t port) +{ + bool success = false; + struct sockaddr_in addr_sin; + cpiAddress_GetInet(address, &addr_sin); + addr_sin.sin_port = htons(port); + + MetisListenerOps *ops = metisUdpListener_CreateInet(metis, addr_sin); + if (ops) { + success = metisListenerSet_Add(metisForwarder_GetListenerSet(metis), ops); + assertTrue(success, "Failed to add UDP listener on %s to ListenerSet", cpiAddress_ToString(ops->getListenAddress(ops))); + } + return success; +} + +static bool +_setupTcpListenerOnInet6(MetisForwarder *metis, const CPIAddress *address, uint16_t port) +{ + bool success = false; + struct sockaddr_in6 addr_sin6; + cpiAddress_GetInet6(address, &addr_sin6); + addr_sin6.sin6_port = htons(port); + + MetisListenerOps *ops = metisTcpListener_CreateInet6(metis, addr_sin6); + if (ops) { + success = metisListenerSet_Add(metisForwarder_GetListenerSet(metis), ops); + assertTrue(success, "Failed to add TCP6 listener on %s to ListenerSet", cpiAddress_ToString(ops->getListenAddress(ops))); + } + return success; +} + +static bool +_setupUdpListenerOnInet6(MetisForwarder *metis, const CPIAddress *address, uint16_t port) +{ + bool success = false; + struct sockaddr_in6 addr_sin6; + cpiAddress_GetInet6(address, &addr_sin6); + addr_sin6.sin6_port = htons(port); + + MetisListenerOps *ops = metisUdpListener_CreateInet6(metis, addr_sin6); + if (ops) { + success = metisListenerSet_Add(metisForwarder_GetListenerSet(metis), ops); + assertTrue(success, "Failed to add UDP6 listener on %s to ListenerSet", cpiAddress_ToString(ops->getListenAddress(ops))); + } + return success; +} + +static bool +_setupLocalListener(MetisForwarder *metis, const char *path) +{ + bool success = false; + MetisListenerOps *ops = metisLocalListener_Create(metis, path); + if (ops) { + success = metisListenerSet_Add(metisForwarder_GetListenerSet(metis), ops); + assertTrue(success, "Failed to add Local listener on %s to ListenerSet", path); + } + return success; +} + +static MetisListenerOps * +_setupEthernetListenerOnLink(MetisForwarder *metis, const CPIAddress *address, const char *interfaceName, uint16_t ethertype) +{ + MetisListenerOps *ops = metisEtherListener_Create(metis, interfaceName, ethertype); + if (ops) { + bool success = metisListenerSet_Add(metisForwarder_GetListenerSet(metis), ops); + if (!success) { + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Error, __func__, + "Failed to add Ethernet listener on %s ethertype 0x%04x to ListenerSet (likely already one on interface)", interfaceName, ethertype); + + // this will null ops for the return value + ops->destroy(&ops); + } + } else { + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Error, __func__, + "Could not start Ethernet listener on interface %s\n", interfaceName); + } + return ops; +} + +static void +_setupListenersOnInet(MetisForwarder *metis, const CPIAddress *address, uint16_t port) +{ + _setupTcpListenerOnInet(metis, address, port); + _setupUdpListenerOnInet(metis, address, port); +} + +static void +_setupListenersOnInet6(MetisForwarder *metis, const CPIAddress *address, uint16_t port) +{ + _setupTcpListenerOnInet6(metis, address, port); + _setupUdpListenerOnInet6(metis, address, port); +} + +static void +_setupListenersOnAddress(MetisForwarder *metis, const CPIAddress *address, uint16_t port, const char *interfaceName) +{ + CPIAddressType type = cpiAddress_GetType(address); + switch (type) { + case cpiAddressType_INET: + _setupListenersOnInet(metis, address, port); + break; + + case cpiAddressType_INET6: + _setupListenersOnInet6(metis, address, port); + break; + + case cpiAddressType_LINK: + // not used + break; + + default: + // dont' know how to handle this, so no listeners + break; + } +} + +void +metisConfigurationListeners_SetupAll(const MetisConfiguration *config, uint16_t port, const char *localPath) +{ + MetisForwarder *metis = metisConfiguration_GetForwarder(config); + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + + size_t interfaceSetLength = cpiInterfaceSet_Length(set); + for (size_t i = 0; i < interfaceSetLength; i++) { + CPIInterface *iface = cpiInterfaceSet_GetByOrdinalIndex(set, i); + + const CPIAddressList *addresses = cpiInterface_GetAddresses(iface); + size_t addressListLength = cpiAddressList_Length(addresses); + + for (size_t j = 0; j < addressListLength; j++) { + const CPIAddress *address = cpiAddressList_GetItem(addresses, j); + + // Do not start on link address + if (cpiAddress_GetType(address) != cpiAddressType_LINK) { + _setupListenersOnAddress(metis, address, port, cpiInterface_GetName(iface)); + } + } + } + + if (localPath != NULL) { + _setupLocalListener(metis, localPath); + } + + cpiInterfaceSet_Destroy(&set); +} + + +static bool +_addEther(const MetisConfiguration *config, const CPIListener *cpiListener, unsigned ingressId) +{ + bool success = false; + CPIAddress *mac = metisSystem_GetMacAddressByName(metisConfiguration_GetForwarder(config), cpiListener_GetInterfaceName(cpiListener)); + if (mac) { + MetisListenerOps *listener = _setupEthernetListenerOnLink(metisConfiguration_GetForwarder(config), mac, cpiListener_GetInterfaceName(cpiListener), cpiListener_GetEtherType(cpiListener)); + success = (listener != NULL); + cpiAddress_Destroy(&mac); + } + return success; +} + +static bool +_addIP(const MetisConfiguration *config, const CPIListener *cpiListener, unsigned ingressId) +{ + bool success = false; + CPIAddress *localAddress = cpiListener_GetAddress(cpiListener); + + switch (cpiAddress_GetType(localAddress)) { + case cpiAddressType_INET: { + // The CPI address, in this case, has the port inside it, so use that + struct sockaddr_in sin; + cpiAddress_GetInet(localAddress, &sin); + if (cpiListener_IsProtocolUdp(cpiListener)) { + success = _setupUdpListenerOnInet(metisConfiguration_GetForwarder(config), localAddress, htons(sin.sin_port)); + } else if (cpiListener_IsProtocolTcp(cpiListener)) { + success = _setupTcpListenerOnInet(metisConfiguration_GetForwarder(config), localAddress, htons(sin.sin_port)); + } + break; + } + + case cpiAddressType_INET6: { + // The CPI address, in this case, has the port inside it, so use that + struct sockaddr_in6 sin6; + cpiAddress_GetInet6(localAddress, &sin6); + if (cpiListener_IsProtocolUdp(cpiListener)) { + success = _setupUdpListenerOnInet6(metisConfiguration_GetForwarder(config), localAddress, htons(sin6.sin6_port)); + } else if (cpiListener_IsProtocolTcp(cpiListener)) { + success = _setupTcpListenerOnInet6(metisConfiguration_GetForwarder(config), localAddress, htons(sin6.sin6_port)); + } + break; + } + + default: + if (metisLogger_IsLoggable(metisConfiguration_GetLogger(config), MetisLoggerFacility_Config, PARCLogLevel_Warning)) { + char *str = cpiAddress_ToString(localAddress); + metisLogger_Log(metisConfiguration_GetLogger(config), MetisLoggerFacility_Config, PARCLogLevel_Warning, __func__, + "Unsupported address type for IP encapsulation ingress id %u: %s", + ingressId, + str); + parcMemory_Deallocate((void **) &str); + } + break; + } + + if (success) { + if (metisLogger_IsLoggable(metisConfiguration_GetLogger(config), MetisLoggerFacility_Config, PARCLogLevel_Info)) { + char *str = cpiAddress_ToString(localAddress); + metisLogger_Log(metisConfiguration_GetLogger(config), MetisLoggerFacility_Config, PARCLogLevel_Info, __func__, + "Setup listener on address %s", + str); + parcMemory_Deallocate((void **) &str); + } + } + + return success; +} + +bool +metisConfigurationListeners_Add(const MetisConfiguration *config, const CCNxControl *control, unsigned ingressId) +{ + bool success = false; + CPIListener *cpiListener = cpiListener_FromControl(control); + if (cpiListener) { + if (cpiListener_IsEtherEncap(cpiListener)) { + success = _addEther(config, cpiListener, ingressId); + } else if (cpiListener_IsIPEncap(cpiListener)) { + success = _addIP(config, cpiListener, ingressId); + } else { + MetisLogger *logger = metisConfiguration_GetLogger(config); + if (metisLogger_IsLoggable(logger, MetisLoggerFacility_Config, PARCLogLevel_Warning)) { + PARCJSON *json = ccnxControl_GetJson(control); + char *str = parcJSON_ToCompactString(json); + metisLogger_Log(logger, MetisLoggerFacility_Config, PARCLogLevel_Warning, __func__, + "Unsupported encapsulation ingress %u control %s", + ingressId, + str); + parcMemory_Deallocate((void **) &str); + } + } + + cpiListener_Release(&cpiListener); + } else { + MetisLogger *logger = metisConfiguration_GetLogger(config); + if (metisLogger_IsLoggable(logger, MetisLoggerFacility_Config, PARCLogLevel_Warning)) { + PARCJSON *json = ccnxControl_GetJson(control); + char *str = parcJSON_ToCompactString(json); + metisLogger_Log(logger, MetisLoggerFacility_Config, PARCLogLevel_Warning, __func__, + "Could not parse control message ingress %u control %s", + ingressId, + str); + parcMemory_Deallocate((void **) &str); + } + } + return success; +} + +bool +metisConfigurationListeners_Remove(const MetisConfiguration *config, const CCNxControl *control, unsigned ingressId) +{ + MetisLogger *logger = metisConfiguration_GetLogger(config); + if (metisLogger_IsLoggable(logger, MetisLoggerFacility_Config, PARCLogLevel_Warning)) { + PARCJSON *json = ccnxControl_GetJson(control); + char *str = parcJSON_ToCompactString(json); + metisLogger_Log(logger, MetisLoggerFacility_Config, PARCLogLevel_Warning, __func__, + "Removing a listener not supported: ingress %u control %s", + ingressId, + str); + parcMemory_Deallocate((void **) &str); + } + + return false; +} + diff --git a/metis/ccnx/forwarder/metis/config/metis_ConfigurationListeners.h b/metis/ccnx/forwarder/metis/config/metis_ConfigurationListeners.h new file mode 100644 index 00000000..c39865b0 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_ConfigurationListeners.h @@ -0,0 +1,53 @@ +/* + * 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_ConfigurationListeners.h + * @brief Configuration routines related to Listeners + * + * Adding and removing listeners. + * + */ + +#ifndef Metis__metis_ConfigurationListeners_h +#define Metis__metis_ConfigurationListeners_h + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/config/metis_Configuration.h> + +#include <ccnx/api/control/cpi_Address.h> +#include <ccnx/api/control/cpi_ControlMessage.h> + +/** + * Setup udp, tcp, and local listeners + * + * Will bind to all available IP protocols on the given port. + * Does not add Ethernet listeners. + * + * @param port is the UPD and TCP port to use + * @param localPath is the AF_UNIX path to use, if NULL no AF_UNIX listener is setup + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisConfigurationListeners_SetupAll(const MetisConfiguration *config, uint16_t port, const char *localPath); + +bool metisConfigurationListeners_Add(const MetisConfiguration *config, const CCNxControl *control, unsigned ingressId); +bool metisConfigurationListeners_Remove(const MetisConfiguration *config, const CCNxControl *control, unsigned ingressId); + + +#endif /* defined(Metis__metis_ConfigurationListeners_h) */ diff --git a/metis/ccnx/forwarder/metis/config/metis_ControlState.c b/metis/ccnx/forwarder/metis/config/metis_ControlState.c new file mode 100644 index 00000000..bf95afce --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_ControlState.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 <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> +#include <string.h> + +#include <parc/security/parc_Security.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_Network.h> +#include <parc/algol/parc_List.h> +#include <parc/algol/parc_TreeRedBlack.h> +#include <parc/algol/parc_Time.h> + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +#include <ccnx/forwarder/metis/config/metisControl_Root.h> +#include <ccnx/forwarder/metis/config/metis_CommandParser.h> + +struct metis_control_state { + MetisCommandParser *parser; + bool debugFlag; + + void *userdata; + CCNxMetaMessage * (*writeRead)(void *userdata, CCNxMetaMessage *msg); +}; + +MetisControlState * +metisControlState_Create(void *userdata, CCNxMetaMessage * (*writeRead)(void *userdata, CCNxMetaMessage * msg)) +{ + MetisControlState *state = parcMemory_AllocateAndClear(sizeof(MetisControlState)); + assertNotNull(state, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisControlState)); + state->parser = metisCommandParser_Create(); + + state->userdata = userdata; + state->writeRead = writeRead; + state->debugFlag = false; + + return state; +} + +void +metisControlState_Destroy(MetisControlState **statePtr) +{ + assertNotNull(statePtr, "Parameter statePtr must be non-null"); + assertNotNull(*statePtr, "Parameter statePtr must dereference t non-null"); + MetisControlState *state = *statePtr; + metisCommandParser_Destroy(&state->parser); + + parcMemory_Deallocate((void **) &state); + *statePtr = NULL; +} + +void +metisControlState_SetDebug(MetisControlState *state, bool debugFlag) +{ + assertNotNull(state, "Parameter state must be non-null"); + state->debugFlag = debugFlag; + metisCommandParser_SetDebug(state->parser, debugFlag); +} + +bool +metisControlState_GetDebug(MetisControlState *state) +{ + assertNotNull(state, "Parameter state must be non-null"); + return state->debugFlag; +} + +void +metisControlState_RegisterCommand(MetisControlState *state, MetisCommandOps *ops) +{ + assertNotNull(state, "Parameter state must be non-null"); + metisCommandParser_RegisterCommand(state->parser, ops); +} + +CCNxMetaMessage * +metisControlState_WriteRead(MetisControlState *state, CCNxMetaMessage *msg) +{ + assertNotNull(state, "Parameter state must be non-null"); + assertNotNull(msg, "Parameter msg must be non-null"); + + return state->writeRead(state->userdata, msg); +} + +static PARCList * +_metisControlState_ParseStringIntoTokens(const char *originalString) +{ + PARCList *list = parcList(parcArrayList_Create(parcArrayList_StdlibFreeFunction), PARCArrayListAsPARCList); + + char *token; + + char *tofree = parcMemory_StringDuplicate(originalString, strlen(originalString) + 1); + char *string = tofree; + + while ((token = strsep(&string, " \t\n")) != NULL) { + if (strlen(token) > 0) { + parcList_Add(list, strdup(token)); + } + } + + parcMemory_Deallocate((void **) &tofree); + + return list; +} + +MetisCommandReturn +metisControlState_DispatchCommand(MetisControlState *state, PARCList *args) +{ + assertNotNull(state, "Parameter state must be non-null"); + return metisCommandParser_DispatchCommand(state->parser, args); +} + +int +metisControlState_Interactive(MetisControlState *state) +{ + assertNotNull(state, "Parameter state must be non-null"); + char *line = NULL; + size_t linecap = 0; + MetisCommandReturn controlReturn = MetisCommandReturn_Success; + + while (controlReturn != MetisCommandReturn_Exit && !feof(stdin)) { + fputs("> ", stdout); fflush(stdout); + ssize_t failure = getline(&line, &linecap, stdin); + assertTrue(failure > -1, "Error getline"); + + PARCList *args = _metisControlState_ParseStringIntoTokens(line); + controlReturn = metisControlState_DispatchCommand(state, args); + parcList_Release(&args); + } + return 0; +} diff --git a/metis/ccnx/forwarder/metis/config/metis_ControlState.h b/metis/ccnx/forwarder/metis/config/metis_ControlState.h new file mode 100644 index 00000000..ed8fc52e --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_ControlState.h @@ -0,0 +1,205 @@ +/* + * 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_ControlState.h + * @brief A control program for Metis using CLI commands + * + * Implements the state machine for the control program. It takes a "writeRead" function + * as part of the constructor. This abstracts out the backend. It could be a Portal from + * metis_control program down to the forwarder or it could be an internal function within + * metis. + * + */ + +#ifndef Metis_metis_control_h +#define Metis_metis_control_h + +#include <parc/algol/parc_List.h> +#include <ccnx/transport/common/transport_MetaMessage.h> +#include <ccnx/forwarder/metis/config/metis_CommandParser.h> + +struct metis_control_state; +typedef struct metis_control_state MetisControlState; + +/** + * metisControlState_Create + * + * Creates the global state for the MetisControl program. The user provides the writeRead function + * for sending and receiving the CCNxMetaMessage wrapping a CPIControlMessage. For a CLI program, this + * function would work over a CCNxSocket or CCNxComms. For the baked-in CLI or configuration file + * inside metis, it would make direct calls to MetisConfiguration. + * + * @param [in] userdata A closure passed back to the user when calling writeRead. + * @param [in] writeRead The function to write then read configuration messages to Metis + * + * @return non-null The control state + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisControlState *metisControlState_Create(void *userdata, CCNxMetaMessage * (*writeRead)(void *userdata, CCNxMetaMessage * msg)); + +/** + * Destroys the control state, closing all network connections + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisControlState_Destroy(MetisControlState **statePtr); + +/** + * Registers a MetisCommandOps with the system. + * + * Each command has its complete command prefix in the "command" field. RegisterCommand + * will put these command prefixes in to a tree and then match what a user types against + * the longest-matching prefix in the tree. If there's a match, it will call the "execute" + * function. + * + * @param [in] state An allocated MetisControlState + * @param [in] command The command to register with the system + * + * Example: + * @code + * static MetisCommandReturn + * metisControl_Root_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) + * { + * printf("Root Command\n"); + * return MetisCommandReturn_Success; + * } + * + * static MetisCommandReturn + * metisControl_FooBar_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) + * { + * printf("Foo Bar Command\n"); + * return MetisCommandReturn_Success; + * } + * + * const MetisCommandOps metisControl_Root = { + * .command = "", // empty string for root + * .init = NULL, + * .execute = metisControl_Root_Execute + * }; + * + * const MetisCommandOps metisControl_FooBar = { + * .command = "foo bar", // empty string for root + * .init = NULL, + * .execute = metisControl_FooBar_Execute + * }; + * + * void startup(void) + * { + * MetisControlState *state = metisControlState_Create("happy", "day"); + * metisControlState_RegisterCommand(state, metisControl_FooBar); + * metisControlState_RegisterCommand(state, metisControl_Root); + * + * // this executes "root" + * metisControlState_DispatchCommand(state, "foo"); + * metisControlState_Destroy(&state); + * } + * @endcode + */ +void metisControlState_RegisterCommand(MetisControlState *state, MetisCommandOps *command); + +/** + * Performs a longest-matching prefix of the args to the command tree + * + * The command tree is created with metisControlState_RegisterCommand. + * + * @param [in] state The allocated MetisControlState + * @param [in] args Each command-line word parsed to the ordered list + * + * @return MetisCommandReturn_Success the command was successful + * @return MetisCommandReturn_Failure the command failed or was not found + * @return MetisCommandReturn_Exit the command indicates that the interactive mode should exit + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisCommandReturn metisControlState_DispatchCommand(MetisControlState *state, PARCList *args); + +/** + * Begin an interactive shell + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +int metisControlState_Interactive(MetisControlState *state); + +/** + * Write then Read a CPI command + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +CCNxMetaMessage *metisControlState_WriteRead(MetisControlState *state, CCNxMetaMessage *msg); + +/** + * Sets the Debug mode, which will print out much more information. + * + * Prints out much more diagnostic information about what metis-control is doing. + * yes, you would make a MetisCommandOps to set and unset this :) + * + * @param [in] debugFlag true means to print debug info, false means to turn it off + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisControlState_SetDebug(MetisControlState *state, bool debugFlag); + +/** + * Returns the debug state of MetisControlState + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisControlState_GetDebug(MetisControlState *state); +#endif // Metis_metis_control_h diff --git a/metis/ccnx/forwarder/metis/config/metis_SymbolicNameTable.c b/metis/ccnx/forwarder/metis/config/metis_SymbolicNameTable.c new file mode 100644 index 00000000..f6c50688 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_SymbolicNameTable.c @@ -0,0 +1,151 @@ +/* + * 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 <ctype.h> + +#include <LongBow/runtime.h> +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_HashCodeTable.h> +#include <parc/algol/parc_Hash.h> + +#include <ccnx/forwarder/metis/config/metis_SymbolicNameTable.h> + +struct metis_symblic_name_table { + PARCHashCodeTable *symbolicNameTable; +}; + +// ======================================================================================== +// symbolic name table functions + +static bool +_symbolicNameEquals(const void *keyA, const void *keyB) +{ + return (strcasecmp((const char *) keyA, (const char *) keyB) == 0); +} + +static HashCodeType +_symbolicNameHash(const void *keyA) +{ + const char *str = (const char *) keyA; + size_t length = strlen(str); + return parcHash32_Data(str, length); +} + +// ======================================================================================== + +MetisSymbolicNameTable * +metisSymbolicNameTable_Create(void) +{ + MetisSymbolicNameTable *table = parcMemory_Allocate(sizeof(MetisSymbolicNameTable)); + + if (table) { + // key = char * + // value = uint32_t * + table->symbolicNameTable = parcHashCodeTable_Create(_symbolicNameEquals, _symbolicNameHash, parcMemory_DeallocateImpl, parcMemory_DeallocateImpl); + } + + return table; +} + +void +metisSymbolicNameTable_Destroy(MetisSymbolicNameTable **tablePtr) +{ + MetisSymbolicNameTable *table = *tablePtr; + parcHashCodeTable_Destroy(&table->symbolicNameTable); + parcMemory_Deallocate((void **) &table); + *tablePtr = NULL; +} + +static char * +_createKey(const char *symbolicName) +{ + char *key = parcMemory_StringDuplicate(symbolicName, strlen(symbolicName)); + + // convert key to upper case + char *p = key; + + // keeps looping until the first null + while ((*p = toupper(*p))) { + p++; + } + return key; +} + +bool +metisSymbolicNameTable_Exists(MetisSymbolicNameTable *table, const char *symbolicName) +{ + assertNotNull(table, "Parameter table must be non-null"); + assertNotNull(symbolicName, "Parameter symbolicName must be non-null"); + + char *key = _createKey(symbolicName); + bool found = (parcHashCodeTable_Get(table->symbolicNameTable, key) != NULL); + parcMemory_Deallocate((void **) &key); + return found; +} + +void +metisSymbolicNameTable_Remove(MetisSymbolicNameTable *table, const char *symbolicName) +{ + assertNotNull(table, "Parameter table must be non-null"); + assertNotNull(symbolicName, "Parameter symbolicName must be non-null"); + + char *key = _createKey(symbolicName); + parcHashCodeTable_Del(table->symbolicNameTable, key); + parcMemory_Deallocate((void **) &key); + +} + +bool +metisSymbolicNameTable_Add(MetisSymbolicNameTable *table, const char *symbolicName, unsigned connid) +{ + assertNotNull(table, "Parameter table must be non-null"); + assertNotNull(symbolicName, "Parameter symbolicName must be non-null"); + assertTrue(connid < UINT32_MAX, "Parameter connid must be less than %u", UINT32_MAX); + + char *key = _createKey(symbolicName); + + uint32_t *value = parcMemory_Allocate(sizeof(uint32_t)); + *value = connid; + + bool success = parcHashCodeTable_Add(table->symbolicNameTable, key, value); + if (!success) { + parcMemory_Deallocate((void **) &key); + parcMemory_Deallocate((void **) &value); + } + + return success; +} + +unsigned +metisSymbolicNameTable_Get(MetisSymbolicNameTable *table, const char *symbolicName) +{ + assertNotNull(table, "Parameter table must be non-null"); + assertNotNull(symbolicName, "Parameter symbolicName must be non-null"); + + unsigned connid = UINT32_MAX; + + char *key = _createKey(symbolicName); + + uint32_t *value = parcHashCodeTable_Get(table->symbolicNameTable, key); + if (value) { + connid = *value; + } + + parcMemory_Deallocate((void **) &key); + return connid; +} + diff --git a/metis/ccnx/forwarder/metis/config/metis_SymbolicNameTable.h b/metis/ccnx/forwarder/metis/config/metis_SymbolicNameTable.h new file mode 100644 index 00000000..cff341c8 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_SymbolicNameTable.h @@ -0,0 +1,123 @@ +/* + * 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_SymbolicNameTable.h + * @brief The symbolic name table maps a string name to a connection id + * + * When configuring tunnels/connections, the user provides a string name (symbolic name) that they + * will use to refer to that connection. The symblic name table translates that symbolic name + * to a connection id. + * + */ + +#ifndef __Metis__metis_SymbolicNameTable__ +#define __Metis__metis_SymbolicNameTable__ + +struct metis_symblic_name_table; +typedef struct metis_symblic_name_table MetisSymbolicNameTable; + +#include <stdbool.h> + +/** + * Creates a symbolic name table + * + * Allocates a MetisSymbolicNameTable, which will store the symbolic names + * in a hash table. + * + * @retval non-null An allocated MetisSymbolicNameTable + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisSymbolicNameTable *metisSymbolicNameTable_Create(void); + +/** + * Destroys a name table + * + * All keys and data are released. + * + * @param [in,out] tablePtr A pointer to a MetisSymbolicNameTable, which will be NULL'd + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisSymbolicNameTable_Destroy(MetisSymbolicNameTable **tablePtr); + +/** + * Tests if the name (case insensitive) is in the table + * + * Does a case-insensitive match to see if the name is in the table + * + * @param [in] table An allocated MetisSymbolicNameTable + * @param [in] symbolicName The name to check for + * + * @retval true The name is in the table + * @retval false The name is not in the talbe + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisSymbolicNameTable_Exists(MetisSymbolicNameTable *table, const char *symbolicName); + +/** + * Adds a (name, connid) pair to the table. + * + * The name is stored case insensitive. The value UINT_MAX is used to indicate a + * non-existent key, so it should not be stored as a value in the table. + * + * @param [in] table An allocated MetisSymbolicNameTable + * @param [in] symbolicName The name to save (will make a copy) + * @param [in] connid The connection id to associate with the name + * + * @retval true The pair was added + * @retval false The pair was not added (likely duplicate key) + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisSymbolicNameTable_Add(MetisSymbolicNameTable *table, const char *symbolicName, unsigned connid); + +/** + * Returns the connection id associated with the symbolic name + * + * This function will look for the given name (case insensitive) and return the + * corresponding connid. If the name is not in the table, the function will return UINT_MAX. + * + * @param [in] table An allocated MetisSymbolicNameTable + * @param [in] symbolicName The name to retrieve + * + * @retval UINT_MAX symbolicName not found + * @retval number the corresponding connid. + * + * Example: + * @code + * <#example#> + * @endcode + */ +unsigned metisSymbolicNameTable_Get(MetisSymbolicNameTable *table, const char *symbolicName); + +void metisSymbolicNameTable_Remove(MetisSymbolicNameTable *table, const char *symbolicName); + +#endif /* defined(__Metis__metis_SymbolicNameTable__) */ diff --git a/metis/ccnx/forwarder/metis/config/metis_WebInterface.h b/metis/ccnx/forwarder/metis/config/metis_WebInterface.h new file mode 100644 index 00000000..4813c98e --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_WebInterface.h @@ -0,0 +1,85 @@ +/* + * 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_WebInterface_h +#define Metis_metis_WebInterface_h + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +struct metis_web; +typedef struct metis_web MetisWeb; + +/** + * Creates a Web on the given port. + * + * A http interface. The Web interface is created in the STOPPED mode, so + * you need to start it for it to be usable. + * + * Create will bind the port, but callbacks in the dispatcher will not be + * enabled until it is started. + * + * @param port the command port, in host byte order + * @return NULL if cannot be created on the port + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisWeb *metisWeb_Create(MetisForwarder *metis, uint16_t port); + +/** + * Stops and destroys the web interface. Existing sessions are destroyed. + * + * <#Discussion#> + * + * @param <#param1#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisWeb_Destroy(MetisWeb **WebPtr); + +/** + * Enables the web interface in the event dispatcher. + * + * <#Discussion#> + * + * @param <#param1#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisWeb_Start(MetisWeb *web); + +/** + * Disables callback in the event dispatcher. Existing connections unaffected. + * + * Stopping it only disable accepting new connections. + * + * @param <#param1#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisWeb_Stop(MetisWeb *web); +#endif // Metis_metis_WebInterface_h diff --git a/metis/ccnx/forwarder/metis/config/test/CMakeLists.txt b/metis/ccnx/forwarder/metis/config/test/CMakeLists.txt new file mode 100644 index 00000000..7e14e1d5 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/CMakeLists.txt @@ -0,0 +1,37 @@ +# Enable gcov output for the tests +add_definitions(--coverage) +set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} " --coverage") + +set(TestsExpectedToPass + test_metis_ControlState + #test_metis_CommandLineInterface + test_metis_CommandOps + test_metis_CommandParser + test_metis_Configuration + test_metis_ConfigurationFile + test_metis_ConfigurationListeners + test_metis_SymbolicNameTable + test_metisControl_Add + test_metisControl_AddConnection + test_metisControl_AddListener + test_metisControl_AddRoute + test_metisControl_List + test_metisControl_ListConnections + test_metisControl_ListInterfaces + test_metisControl_ListRoutes + test_metisControl_Quit + test_metisControl_Remove + test_metisControl_RemoveConnection + test_metisControl_RemoveRoute + test_metisControl_Root + test_metisControl_Set + test_metisControl_SetDebug + test_metisControl_Unset + test_metisControl_UnsetDebug +) + + +foreach(test ${TestsExpectedToPass}) + AddTest(${test}) +endforeach() + diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_Add.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_Add.c new file mode 100644 index 00000000..c4001621 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_Add.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 "../metisControl_Add.c" + +#include <LongBow/unit-test.h> + +#include "testrig_MetisControl.c" + +LONGBOW_TEST_RUNNER(metisControl_Add) +{ + // 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(metisControl_Add) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_Add) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlAdd_Create); + LONGBOW_RUN_TEST_CASE(Global, metisControlAdd_CreateHelp); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControlAdd_Create) +{ + testCommandCreate(testCase, metisControlAdd_Create, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlAdd_CreateHelp) +{ + testCommandCreate(testCase, metisControlAdd_CreateHelp, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _metisControlAdd_Execute); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAdd_Init); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAdd_HelpExecute); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, _metisControlAdd_Execute) +{ + testHelpExecute(testCase, metisControlAdd_Create, __func__, MetisCommandReturn_Success); +} + +/** + * Init should add 4 commands to the command parser + */ +LONGBOW_TEST_CASE(Local, _metisControlAdd_Init) +{ + testInit(testCase, metisControlAdd_Create, __func__, + (const char *[]) { + "add connection", "add route", + "help add connection", "help add route", + NULL + }); +} + +LONGBOW_TEST_CASE(Local, _metisControlAdd_HelpExecute) +{ + testHelpExecute(testCase, metisControlAdd_CreateHelp, __func__, MetisCommandReturn_Success); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_Add); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_AddConnection.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_AddConnection.c new file mode 100644 index 00000000..c98ab268 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_AddConnection.c @@ -0,0 +1,473 @@ +/* + * 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 "../metisControl_AddConnection.c" +#include "testrig_MetisControl.c" + +LONGBOW_TEST_RUNNER(metisControl_AddConnection) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + // 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(metisControl_AddConnection) +{ + 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(metisControl_AddConnection) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlAddConnection_Create); + LONGBOW_RUN_TEST_CASE(Global, metisControlAddConnection_HelpCreate); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControlAddConnection_Create) +{ + testCommandCreate(testCase, &metisControlAddConnection_Create, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlAddConnection_HelpCreate) +{ + testCommandCreate(testCase, &metisControlAddConnection_HelpCreate, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_EtherCreate); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_EtherExecute); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_McastCreate); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_McastExecute); + + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_TcpCreate); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_TcpExecute); + + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_UdpCreate); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_UdpExecute); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_Execute); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_Init); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_ConvertStringsToCpiAddress); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_CreateTunnel); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_EtherHelpCreate); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_EtherHelpExecute); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_McastHelpCreate); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_McastHelpExecute); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_TcpHelpCreate); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_TcpHelpExecute); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_UdpHelpCreate); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_UdpHelpExecute); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_HelpExecute); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_IpHelp); + + LONGBOW_RUN_TEST_CASE(Local, metisControl_ParseIPCommandLine_TooFewArgs); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ParseIPCommandLine_TooManyArgs); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ParseIPCommandLine_BadRemoteIp); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ParseIPCommandLine_GoodRemoteIp); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ParseIPCommandLine_WithLocalIp); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ParseIPCommandLine_WithLocalIpAndPort); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ParseIPCommandLine_BadLocalIp); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ParseIPCommandLine_MismatchLocalAndRemote); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, _metisControlAddConnection_EtherCreate) +{ + testCommandCreate(testCase, &_metisControlAddConnection_EtherCreate, __func__); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_EtherExecute) +{ + const char *argv[] = { "add", "connection", "ether", "conn3", "e8-06-88-cd-28-de", "em3" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = _metisControlAddConnection_EtherCreate(data->state); + MetisCommandReturn result = ops->execute(data->state->parser, ops, args); + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + assertTrue(result == MetisCommandReturn_Success, "Valid command line should succeed"); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_McastCreate) +{ + testCommandCreate(testCase, &_metisControlAddConnection_McastCreate, __func__); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_McastExecute) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = _metisControlAddConnection_McastCreate(data->state); + MetisCommandReturn result = ops->execute(data->state->parser, ops, NULL); + metisCommandOps_Destroy(&ops); + assertTrue(result == MetisCommandReturn_Failure, "Unimplemented execute should have failed"); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_TcpCreate) +{ + testCommandCreate(testCase, &_metisControlAddConnection_TcpCreate, __func__); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_TcpExecute) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + const char *argv[] = { "add", "connection", "tcp", "conn3", "1.2.3.4", "123" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandOps *ops = _metisControlAddConnection_TcpCreate(data->state); + MetisCommandReturn result = ops->execute(data->state->parser, ops, args); + + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + + assertTrue(result == MetisCommandReturn_Success, "Unimplemented execute should have failed"); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_UdpCreate) +{ + testCommandCreate(testCase, &_metisControlAddConnection_UdpCreate, __func__); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_UdpExecute) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + const char *argv[] = { "add", "connection", "tcp", "conn3", "1.2.3.4", "123" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandOps *ops = _metisControlAddConnection_UdpCreate(data->state); + MetisCommandReturn result = ops->execute(data->state->parser, ops, args); + + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + + assertTrue(result == MetisCommandReturn_Success, "Unimplemented execute should have failed"); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_Execute) +{ + // this just prints a Help message + testHelpExecute(testCase, metisControlAddConnection_Create, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_Init) +{ + testInit(testCase, metisControlAddConnection_Create, __func__, + (const char *[]) { + _commandAddConnectionTcp, _commandAddConnectionUdp, _commandAddConnectionEther, _commandAddConnectionMcast, + _commandAddConnectionTcpHelp, _commandAddConnectionUdpHelp, _commandAddConnectionEtherHelp, _commandAddConnectionMcastHelp, + NULL + }); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_ConvertStringsToCpiAddress) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_CreateTunnel) +{ + // this is actully testred in the Tcp_Execute and Udp_Execute + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_EtherHelpCreate) +{ + testCommandCreate(testCase, &_metisControlAddConnection_EtherHelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_EtherHelpExecute) +{ + testHelpExecute(testCase, _metisControlAddConnection_EtherHelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_McastHelpCreate) +{ + testCommandCreate(testCase, &_metisControlAddConnection_McastHelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_McastHelpExecute) +{ + testHelpExecute(testCase, _metisControlAddConnection_McastHelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_TcpHelpCreate) +{ + testCommandCreate(testCase, &_metisControlAddConnection_TcpHelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_TcpHelpExecute) +{ + testHelpExecute(testCase, _metisControlAddConnection_TcpHelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_UdpHelpCreate) +{ + testCommandCreate(testCase, &_metisControlAddConnection_UdpHelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_UdpHelpExecute) +{ + testHelpExecute(testCase, _metisControlAddConnection_UdpHelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_HelpExecute) +{ + testHelpExecute(testCase, metisControlAddConnection_HelpCreate, __func__, MetisCommandReturn_Success); +} + +/** + * Expectes 5 to 7 options + */ +LONGBOW_TEST_CASE(Local, metisControl_ParseIPCommandLine_TooFewArgs) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + const char *argv[] = { "a", "b", "c" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 3, (void **) &argv[0]); + + MetisCommandOps *ops = _metisControlAddConnection_TcpCreate(data->state); + + CPIAddress *remote; + CPIAddress *local; + char *symbolic = NULL; + + MetisCommandReturn result = _metisControlAddConnection_ParseIPCommandLine(data->state->parser, ops, args, &remote, &local, &symbolic); + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + + assertTrue(result == MetisCommandReturn_Failure, "ParseIPCommandLine with 3 args should have returned %d, got %d", MetisCommandReturn_Failure, result); +} + +/** + * Expects 5 to 7 options + */ +LONGBOW_TEST_CASE(Local, metisControl_ParseIPCommandLine_TooManyArgs) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + const char *argv[] = { "a", "b", "c", "d", "e", "f", "g", "h", "i" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 9, (void **) &argv[0]); + + MetisCommandOps *ops = _metisControlAddConnection_TcpCreate(data->state); + + CPIAddress *remote; + CPIAddress *local; + char *symbolic = NULL; + + MetisCommandReturn result = _metisControlAddConnection_ParseIPCommandLine(data->state->parser, ops, args, &remote, &local, &symbolic); + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + + assertTrue(result == MetisCommandReturn_Failure, "ParseIPCommandLine with 3 args should have returned %d, got %d", MetisCommandReturn_Failure, result); +} + +LONGBOW_TEST_CASE(Local, metisControl_ParseIPCommandLine_BadRemoteIp) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + const char *argv[] = { "a", "b", "c", "tun0", "555.555.555.555", "123", }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandOps *ops = _metisControlAddConnection_TcpCreate(data->state); + + CPIAddress *remote; + CPIAddress *local; + char *symbolic = NULL; + + MetisCommandReturn result = _metisControlAddConnection_ParseIPCommandLine(data->state->parser, ops, args, &remote, &local, &symbolic); + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + + assertTrue(result == MetisCommandReturn_Failure, "ParseIPCommandLine with invalid IP address should have returned %d, got %d", MetisCommandReturn_Failure, result); +} + +/** + * Pass a set of args to metisControl_ParseIPCommandLine, then verify: + * Successful + * remote_ip is what we gave it + * remote_port is what we gave it + * local_ip is 0.0.0.0 or what we gave it + * local_pot is 0 or what we gave it. + */ +static void +verifyParseIpWithGoodAddress(TestData *data, int argc, const char *remote_ip, const char *remote_port, const char *local_ip, const char *local_port) +{ + const char *argv[] = { "a", "b", "c", "tun0", remote_ip, remote_port, local_ip, local_port }; + + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, argc, (void **) &argv[0]); + + MetisCommandOps *ops = _metisControlAddConnection_TcpCreate(data->state); + + CPIAddress *remote; + CPIAddress *local; + char *symbolic = NULL; + + MetisCommandReturn result = _metisControlAddConnection_ParseIPCommandLine(data->state->parser, ops, args, &remote, &local, &symbolic); + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + + assertTrue(result == MetisCommandReturn_Success, "ParseIPCommandLine with invalid IP address should have returned %d, got %d", MetisCommandReturn_Failure, result); + + struct sockaddr *sockaddr_remote = parcNetwork_SockAddress(remote_ip, atoi(remote_port)); + struct sockaddr *sockaddr_local = parcNetwork_SockAddress(local_ip, atoi(local_port)); + CPIAddress *truth_remote = cpiAddress_CreateFromInet((struct sockaddr_in *) sockaddr_remote); + CPIAddress *truth_local = cpiAddress_CreateFromInet((struct sockaddr_in *) sockaddr_local); + parcMemory_Deallocate((void **) &sockaddr_local); + parcMemory_Deallocate((void **) &sockaddr_remote); + + assertTrue(cpiAddress_Equals(truth_remote, remote), "Got wrong remote address"); + assertTrue(cpiAddress_Equals(truth_local, local), "Got wrong local address"); + cpiAddress_Destroy(&truth_remote); + cpiAddress_Destroy(&truth_local); + cpiAddress_Destroy(&remote); + cpiAddress_Destroy(&local); +} + +LONGBOW_TEST_CASE(Local, metisControl_ParseIPCommandLine_GoodRemoteIp) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + verifyParseIpWithGoodAddress(data, 6, "1.2.3.4", "123", "0.0.0.0", "0"); +} + +LONGBOW_TEST_CASE(Local, metisControl_ParseIPCommandLine_WithLocalIp) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + verifyParseIpWithGoodAddress(data, 7, "1.2.3.4", "123", "10.11.12.13", "0"); +} + +LONGBOW_TEST_CASE(Local, metisControl_ParseIPCommandLine_WithLocalIpAndPort) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + verifyParseIpWithGoodAddress(data, 8, "1.2.3.4", "123", "10.11.12.13", "456"); +} + +LONGBOW_TEST_CASE(Local, metisControl_ParseIPCommandLine_BadLocalIp) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + const char *argv[] = { "a", "b", "c", "tun0", "1.2.3.4", "123", "666.666.666.666", "123", }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 8, (void **) &argv[0]); + + MetisCommandOps *ops = _metisControlAddConnection_TcpCreate(data->state); + + CPIAddress *remote; + CPIAddress *local; + char *symbolic = NULL; + + MetisCommandReturn result = _metisControlAddConnection_ParseIPCommandLine(data->state->parser, ops, args, &remote, &local, &symbolic); + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + + assertTrue(result == MetisCommandReturn_Failure, "ParseIPCommandLine with invalid local IP address should have returned %d, got %d", MetisCommandReturn_Failure, result); +} + +/** + * One's an IPv4 and one's an IPv6. + */ +LONGBOW_TEST_CASE(Local, metisControl_ParseIPCommandLine_MismatchLocalAndRemote) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + const char *argv[] = { "a", "b", "c", "tun0", "1.2.3.4", "123", "2001:720:1500:1::a100", "123", }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 8, (void **) &argv[0]); + + MetisCommandOps *ops = _metisControlAddConnection_TcpCreate(data->state); + + CPIAddress *remote; + CPIAddress *local; + char *symbolic = NULL; + + MetisCommandReturn result = _metisControlAddConnection_ParseIPCommandLine(data->state->parser, ops, args, &remote, &local, &symbolic); + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + + assertTrue(result == MetisCommandReturn_Failure, "ParseIPCommandLine with invalid local IP address should have returned %d, got %d", MetisCommandReturn_Failure, result); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_IpHelp) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = _metisControlAddConnection_McastHelpCreate(data->state); + MetisCommandReturn result = _metisControlAddConnection_IpHelp(NULL, ops, NULL, "WIZARD"); + assertTrue(result == MetisCommandReturn_Success, "Wrong return, got %d expected %d", result, MetisCommandReturn_Success); + metisCommandOps_Destroy(&ops); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_AddConnection); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_AddListener.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_AddListener.c new file mode 100644 index 00000000..b5319aa4 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_AddListener.c @@ -0,0 +1,327 @@ +/* + * 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 "../metisControl_AddListener.c" +#include "testrig_MetisControl.c" + +LONGBOW_TEST_RUNNER(metisControl_AddListener) +{ + 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(metisControl_AddListener) +{ + 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(metisControl_AddListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlAddListener_Create); + LONGBOW_RUN_TEST_CASE(Global, metisControlAddListener_HelpCreate); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControlAddListener_Create) +{ + testCommandCreate(testCase, &metisControlAddListener_Create, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlAddListener_HelpCreate) +{ + testCommandCreate(testCase, &metisControlAddListener_HelpCreate, __func__); +} + +// =========================================================== + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddListener_Execute_WrongArgCount); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddListener_Execute_Tcp); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddListener_Execute_Udp); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddListener_Execute_Udp6); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddListener_Execute_Ether); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddListener_Execute_UnknownProtocol); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddListener_Execute_BadSymbolic); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddListener_Execute_BadSymbolic_NotAlphaNum); + + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddListener_HelpExecute); + LONGBOW_RUN_TEST_CASE(Local, _createTcpListener); + LONGBOW_RUN_TEST_CASE(Local, _createUdpListener); + LONGBOW_RUN_TEST_CASE(Local, _createEtherListener); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, _createTcpListener) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = metisControlAddListener_Create(data->state); + + const char *argv[] = { "add", "listener", "tcp", "public0", "13.14.15.16", "9596", }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandReturn test = _createTcpListener(data->state->parser, ops, args); + + assertTrue(test == MetisCommandReturn_Success, "Command did not return success: %d", test); + assertTrue(data->writeread_count == 1, "Wrong write/read count, expected %d got %u", 1, data->writeread_count); + + parcList_Release(&args); + ops->destroyer(&ops); +} + +LONGBOW_TEST_CASE(Local, _createUdpListener) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = metisControlAddListener_Create(data->state); + + const char *argv[] = { "add", "listener", "udp", "public0", "13.14.15.16", "9596", }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandReturn test = _createUdpListener(data->state->parser, ops, args); + + assertTrue(test == MetisCommandReturn_Success, "Command did not return success: %d", test); + assertTrue(data->writeread_count == 1, "Wrong write/read count, expected %d got %u", 1, data->writeread_count); + + parcList_Release(&args); + ops->destroyer(&ops); +} + +LONGBOW_TEST_CASE(Local, _createEtherListener) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = metisControlAddListener_Create(data->state); + + const char *argv[] = { "add", "listener", "ether", "nic3", "eth3", "0x0801", }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandReturn test = _createEtherListener(data->state->parser, ops, args); + + assertTrue(test == MetisCommandReturn_Success, "Command did not return success: %d", test); + assertTrue(data->writeread_count == 1, "Wrong write/read count, expected %d got %u", 1, data->writeread_count); + + parcList_Release(&args); + ops->destroyer(&ops); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddListener_Execute_WrongArgCount) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = metisControlAddListener_Create(data->state); + + const char *argv[] = { "add", "listener", "ether" "nic3", "eth3", "0x0801", "foobar" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 7, (void **) &argv[0]); + + MetisCommandReturn test = _metisControlAddListener_Execute(data->state->parser, ops, args); + + assertTrue(test == MetisCommandReturn_Failure, "Command did not return failure: %d", test); + assertTrue(data->writeread_count == 0, "Wrong write/read count, expected %d got %u", 0, data->writeread_count); + + parcList_Release(&args); + ops->destroyer(&ops); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddListener_Execute_Tcp) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = metisControlAddListener_Create(data->state); + metisControlState_SetDebug(data->state, true); + + const char *argv[] = { "add", "listener", "tcp", "public0", "13.14.15.16", "9596", }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandReturn test = _metisControlAddListener_Execute(data->state->parser, ops, args); + + assertTrue(test == MetisCommandReturn_Success, "Command did not return success: %d", test); + assertTrue(data->writeread_count == 1, "Wrong write/read count, expected %d got %u", 1, data->writeread_count); + + parcList_Release(&args); + ops->destroyer(&ops); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddListener_Execute_Udp) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = metisControlAddListener_Create(data->state); + metisControlState_SetDebug(data->state, true); + + const char *argv[] = { "add", "listener", "udp", "public0", "13.14.15.16", "9596", }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandReturn test = _metisControlAddListener_Execute(data->state->parser, ops, args); + + assertTrue(test == MetisCommandReturn_Success, "Command did not return success: %d", test); + assertTrue(data->writeread_count == 1, "Wrong write/read count, expected %d got %u", 1, data->writeread_count); + + parcList_Release(&args); + ops->destroyer(&ops); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddListener_Execute_Udp6) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = metisControlAddListener_Create(data->state); + metisControlState_SetDebug(data->state, true); + + // INET6 address + const char *argv[] = { "add", "listener", "udp", "public0", "::1", "9596", }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandReturn test = _metisControlAddListener_Execute(data->state->parser, ops, args); + + assertTrue(test == MetisCommandReturn_Success, "Command did not return success: %d", test); + assertTrue(data->writeread_count == 1, "Wrong write/read count, expected %d got %u", 1, data->writeread_count); + + parcList_Release(&args); + ops->destroyer(&ops); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddListener_Execute_Ether) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = metisControlAddListener_Create(data->state); + + const char *argv[] = { "add", "listener", "ether", "nic3", "eth3", "0x0801", }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandReturn test = _metisControlAddListener_Execute(data->state->parser, ops, args); + + assertTrue(test == MetisCommandReturn_Success, "Command did not return success: %d", test); + assertTrue(data->writeread_count == 1, "Wrong write/read count, expected %d got %u", 1, data->writeread_count); + + parcList_Release(&args); + ops->destroyer(&ops); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddListener_Execute_UnknownProtocol) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = metisControlAddListener_Create(data->state); + + const char *argv[] = { "add", "listener", "pup", "nic3", "eth3", "0x0801" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandReturn test = _metisControlAddListener_Execute(data->state->parser, ops, args); + + assertTrue(test == MetisCommandReturn_Failure, "Command did not return failure: %d", test); + assertTrue(data->writeread_count == 0, "Wrong write/read count, expected %d got %u", 0, data->writeread_count); + + parcList_Release(&args); + ops->destroyer(&ops); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddListener_Execute_BadSymbolic) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = metisControlAddListener_Create(data->state); + + const char *argv[] = { "add", "listener", "ether" "111", "eth3", "0x0801" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandReturn test = _metisControlAddListener_Execute(data->state->parser, ops, args); + + assertTrue(test == MetisCommandReturn_Failure, "Command did not return failure: %d", test); + assertTrue(data->writeread_count == 0, "Wrong write/read count, expected %d got %u", 0, data->writeread_count); + + parcList_Release(&args); + ops->destroyer(&ops); +} + + +LONGBOW_TEST_CASE(Local, _metisControlAddListener_Execute_BadSymbolic_NotAlphaNum) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = metisControlAddListener_Create(data->state); + + const char *argv[] = { "add", "listener", "ether", "n()t", "eth3", "0x0801" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandReturn test = _metisControlAddListener_Execute(data->state->parser, ops, args); + + assertTrue(test == MetisCommandReturn_Failure, "Command did not return failure: %d", test); + assertTrue(data->writeread_count == 0, "Wrong write/read count, expected %d got %u", 0, data->writeread_count); + + parcList_Release(&args); + ops->destroyer(&ops); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddListener_HelpExecute) +{ + _metisControlAddListener_HelpExecute(NULL, NULL, NULL); +} + +// =========================================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_AddListener); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_AddRoute.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_AddRoute.c new file mode 100644 index 00000000..4a57dd64 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_AddRoute.c @@ -0,0 +1,170 @@ +/* + * 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 "../metisControl_AddRoute.c" +#include "testrig_MetisControl.c" + +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_AddRoute) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + + // 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(metisControl_AddRoute) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_AddRoute) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlAddRoute_Create); + LONGBOW_RUN_TEST_CASE(Global, metisControlAddRoute_HelpCreate); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControlAddRoute_Create) +{ + testCommandCreate(testCase, &metisControlAddRoute_Create, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlAddRoute_HelpCreate) +{ + testCommandCreate(testCase, &metisControlAddRoute_HelpCreate, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_AddRoute_Execute_WrongArgCount); + LONGBOW_RUN_TEST_CASE(Local, metisControl_AddRoute_Execute_ZeroCost); + LONGBOW_RUN_TEST_CASE(Local, metisControl_AddRoute_Execute_BadPrefix); + LONGBOW_RUN_TEST_CASE(Local, metisControl_AddRoute_Execute_Good); + + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_AddRoute_Execute); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + 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; +} + +static MetisCommandReturn +testAddRoute(const LongBowTestCase *testCase, int argc, const char *prefix, const char *nexthop, const char *cost) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + metisControlState_SetDebug(data->state, true); + + const char *argv[] = { "add", "route", nexthop, prefix, cost }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, argc, (void **) &argv[0]); + + MetisCommandOps *ops = metisControlAddRoute_Create(data->state); + + MetisCommandReturn result = ops->execute(data->state->parser, ops, args); + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + return result; +} + +LONGBOW_TEST_CASE(Local, metisControl_AddRoute_Execute_WrongArgCount) +{ + // argc is wrong, needs to be 5. + MetisCommandReturn result = testAddRoute(testCase, 2, "lci:/foo", "703", "1"); + + assertTrue(result == MetisCommandReturn_Failure, + "metisControl_AddRoute with wrong argc should return %d, got %d", MetisCommandReturn_Failure, result); +} + +LONGBOW_TEST_CASE(Local, metisControl_AddRoute_Execute_ZeroCost) +{ + MetisCommandReturn result = testAddRoute(testCase, 5, "lci:/foo", "702", "0"); + + assertTrue(result == MetisCommandReturn_Failure, + "metisControl_AddRoute with zero cost should return %d, got %d", MetisCommandReturn_Failure, result); +} + +LONGBOW_TEST_CASE(Local, metisControl_AddRoute_Execute_BadPrefix) +{ + MetisCommandReturn result = testAddRoute(testCase, 5, "blah", "701", "1"); + + assertTrue(result == MetisCommandReturn_Failure, + "metisControl_AddRoute with zero cost should return %d, got %d", MetisCommandReturn_Failure, result); +} + +LONGBOW_TEST_CASE(Local, metisControl_AddRoute_Execute_Good) +{ + MetisCommandReturn result = testAddRoute(testCase, 5, "lci:/foo", "700", "1"); + + assertTrue(result == MetisCommandReturn_Success, + "metisControl_AddRoute should return %d, got %d", MetisCommandReturn_Success, result); +} + +LONGBOW_TEST_CASE(Local, metisControl_Help_AddRoute_Execute) +{ + testHelpExecute(testCase, metisControlAddRoute_HelpCreate, __func__, MetisCommandReturn_Success); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_AddRoute); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_List.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_List.c new file mode 100644 index 00000000..1d8f347f --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_List.c @@ -0,0 +1,130 @@ +/* + * 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 "../metisControl_List.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_List) +{ + // 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(metisControl_List) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_List) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlList_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlList_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControlList_HelpCreate) +{ + testCommandCreate(testCase, &metisControlList_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlList_Create) +{ + testCommandCreate(testCase, &metisControlList_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_List_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_List_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_List_Init); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControl_Help_List_Execute) +{ + testHelpExecute(testCase, metisControlList_HelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_List_Execute) +{ + // this just prints the Help function + testHelpExecute(testCase, metisControlList_Create, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_List_Init) +{ + testInit(testCase, metisControlList_Create, __func__, + (const char *[]) { + "list connections", "list interfaces", "list routes", + "help list connections", "help list interfaces", "help list routes", + NULL + }); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_List); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_ListConnections.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_ListConnections.c new file mode 100644 index 00000000..56d691e1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_ListConnections.c @@ -0,0 +1,172 @@ +/* + * 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 "../metisControl_ListConnections.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_ListConnections) +{ + // 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(metisControl_ListConnections) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_ListConnections) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlListConnections_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlListConnections_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControlListConnections_HelpCreate) +{ + testCommandCreate(testCase, &metisControlListConnections_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlListConnections_Create) +{ + testCommandCreate(testCase, &metisControlListConnections_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_ListConnections_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ListConnections_Execute_WrongArgCount); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ListConnections_Execute_Good); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControl_Help_ListConnections_Execute) +{ + testHelpExecute(testCase, &metisControlListConnections_HelpCreate, __func__, MetisCommandReturn_Success); +} + +static CCNxControl * +customWriteReadResponse(void *userdata, CCNxMetaMessage *messageToWrite) +{ + CPIConnectionList *connlist = cpiConnectionList_Create(); + CPIConnection *conn = cpiConnection_Create(1, cpiAddress_CreateFromInterface(1), cpiAddress_CreateFromInterface(2), cpiConnection_L2); + cpiConnectionList_Append(connlist, conn); + + PARCJSON *connectionListAsJson = cpiConnectionList_ToJson(connlist); + + CCNxControl *inboundControlMessage = ccnxMetaMessage_GetControl(messageToWrite); + + // Create a response to the inbound Control message. + CCNxControl *outboundControlMessage = cpi_CreateResponse(inboundControlMessage, connectionListAsJson); + parcJSON_Release(&connectionListAsJson); + + ccnxControl_Release(&inboundControlMessage); + + cpiConnectionList_Destroy(&connlist); + + return outboundControlMessage; +} + +static MetisCommandReturn +testListConnections(const LongBowTestCase *testCase, int argc) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + metisControlState_SetDebug(data->state, true); + data->customWriteReadReply = &customWriteReadResponse; + + const char *argv[] = { "list", "interfaces" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, argc, (void **) &argv[0]); + + MetisCommandOps *ops = metisControlListConnections_Create(data->state); + + MetisCommandReturn result = ops->execute(data->state->parser, ops, args); + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + return result; +} + +LONGBOW_TEST_CASE(Local, metisControl_ListConnections_Execute_WrongArgCount) +{ + // argc is wrong, needs to be 2. + MetisCommandReturn result = testListConnections(testCase, 3); + + assertTrue(result == MetisCommandReturn_Failure, + "metisControl_ListConnections with wrong argc should return %d, got %d", MetisCommandReturn_Failure, result); +} + +LONGBOW_TEST_CASE(Local, metisControl_ListConnections_Execute_Good) +{ + MetisCommandReturn result = testListConnections(testCase, 2); + + assertTrue(result == MetisCommandReturn_Success, + "metisControl_ListConnections should return %d, got %d", MetisCommandReturn_Success, result); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_ListConnections); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_ListInterfaces.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_ListInterfaces.c new file mode 100644 index 00000000..0e1e13d2 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_ListInterfaces.c @@ -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. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metisControl_ListInterfaces.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_ListInterfaces) +{ + // 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(metisControl_ListInterfaces) +{ + 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(metisControl_ListInterfaces) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlListInterfaces_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlListInterfaces_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControlListInterfaces_HelpCreate) +{ + testCommandCreate(testCase, &metisControlListInterfaces_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlListInterfaces_Create) +{ + testCommandCreate(testCase, &metisControlListInterfaces_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_ListInterfaces_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ListInterfaces_Execute_WrongArgCount); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ListInterfaces_Execute_Good); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControl_Help_ListInterfaces_Execute) +{ + testHelpExecute(testCase, &metisControlListInterfaces_HelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_ListInterfaces_Execute) +{ + testUnimplemented(""); +} + +static CCNxControl * +customWriteReadResponse(void *userdata, CCNxMetaMessage *messageToWrite) +{ + CPIInterfaceSet *set = cpiInterfaceSet_Create(); + cpiInterfaceSet_Add(set, cpiInterface_Create("abc0", 1, false, true, 1500)); + cpiInterfaceSet_Add(set, cpiInterface_Create("abc1", 2, false, true, 1500)); + PARCJSON *setJson = cpiInterfaceSet_ToJson(set); + + CCNxControl *inboundControlMessage = ccnxMetaMessage_GetControl(messageToWrite); + + // Create a response to the inbound Control message. + CCNxControl *outboundControlMessage = cpi_CreateResponse(inboundControlMessage, setJson); + parcJSON_Release(&setJson); + + ccnxControl_Release(&inboundControlMessage); + cpiInterfaceSet_Destroy(&set); + + return outboundControlMessage; +} + +static MetisCommandReturn +testListInterfaces(const LongBowTestCase *testCase, int argc) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + metisControlState_SetDebug(data->state, true); + data->customWriteReadReply = &customWriteReadResponse; + + const char *argv[] = { "list", "connections" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, argc, (void **) &argv[0]); + + MetisCommandOps *ops = metisControlListInterfaces_Create(data->state); + + MetisCommandReturn result = ops->execute(data->state->parser, ops, args); + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + return result; +} + +LONGBOW_TEST_CASE(Local, metisControl_ListInterfaces_Execute_WrongArgCount) +{ + // argc is wrong, needs to be 2. + MetisCommandReturn result = testListInterfaces(testCase, 3); + + assertTrue(result == MetisCommandReturn_Failure, + "metisControl_ListInterfaces with wrong argc should return %d, got %d", MetisCommandReturn_Failure, result); +} + +LONGBOW_TEST_CASE(Local, metisControl_ListInterfaces_Execute_Good) +{ + MetisCommandReturn result = testListInterfaces(testCase, 2); + + assertTrue(result == MetisCommandReturn_Success, + "metisControl_ListInterfaces should return %d, got %d", MetisCommandReturn_Success, result); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_ListInterfaces); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_ListRoutes.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_ListRoutes.c new file mode 100644 index 00000000..ca71f93f --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_ListRoutes.c @@ -0,0 +1,187 @@ +/* + * 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 "../metisControl_ListRoutes.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_ListRoutes) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + + // 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(metisControl_ListRoutes) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_ListRoutes) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlListRoutes_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlListRoutes_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControlListRoutes_HelpCreate) +{ + testCommandCreate(testCase, &metisControlListRoutes_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlListRoutes_Create) +{ + testCommandCreate(testCase, &metisControlListRoutes_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_ListRoutes_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ListRoutes_Execute_WrongArgCount); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ListRoutes_Execute_Good); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControl_Help_ListRoutes_Execute) +{ + testHelpExecute(testCase, &metisControlListRoutes_HelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_ListRoutes_Execute) +{ + testUnimplemented(""); +} + +static CCNxControl * +customWriteReadResponse(void *userdata, CCNxMetaMessage *messageToWrite) +{ + CPIRouteEntryList *routeEntryList = cpiRouteEntryList_Create(); + CPIAddress *nexthop = cpiAddress_CreateFromInterface(10); + CPIRouteEntry *route = cpiRouteEntry_Create(ccnxName_CreateFromCString("lci:/foo"), + 1, + nexthop, + cpiNameRouteProtocolType_STATIC, + cpiNameRouteType_LONGEST_MATCH, + &((struct timeval) { 100, 0 }), // lifetime + 1); // cost + + cpiRouteEntryList_Append(routeEntryList, route); + PARCJSON *setJson = cpiRouteEntryList_ToJson(routeEntryList); + + CCNxControl *inboundControlMessage = ccnxMetaMessage_GetControl(messageToWrite); + + // Create a response to the inbound Control message. + CCNxControl *outboundControlMessage = cpi_CreateResponse(inboundControlMessage, setJson); + parcJSON_Release(&setJson); + + ccnxControl_Release(&inboundControlMessage); + + cpiAddress_Destroy(&nexthop); + cpiRouteEntryList_Destroy(&routeEntryList); + + return outboundControlMessage; +} + +static MetisCommandReturn +testListRoutes(const LongBowTestCase *testCase, int argc) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + metisControlState_SetDebug(data->state, true); + data->customWriteReadReply = &customWriteReadResponse; + + const char *argv[] = { "list", "connections" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, argc, (void **) &argv[0]); + + MetisCommandOps *ops = metisControlListRoutes_Create(data->state); + + MetisCommandReturn result = ops->execute(data->state->parser, ops, args); + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + return result; +} + +LONGBOW_TEST_CASE(Local, metisControl_ListRoutes_Execute_WrongArgCount) +{ + // argc is wrong, needs to be 2. + MetisCommandReturn result = testListRoutes(testCase, 3); + + assertTrue(result == MetisCommandReturn_Failure, + "metisControl_ListRoutes with wrong argc should return %d, got %d", MetisCommandReturn_Failure, result); +} + +LONGBOW_TEST_CASE(Local, metisControl_ListRoutes_Execute_Good) +{ + MetisCommandReturn result = testListRoutes(testCase, 2); + + assertTrue(result == MetisCommandReturn_Success, + "metisControl_ListRoutes should return %d, got %d", MetisCommandReturn_Success, result); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_ListRoutes); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_Quit.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_Quit.c new file mode 100644 index 00000000..192bdf86 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_Quit.c @@ -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. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metisControl_Quit.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_Quit) +{ + // 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(metisControl_Quit) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_Quit) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlQuit_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlQuit_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControlQuit_HelpCreate) +{ + testCommandCreate(testCase, metisControlQuit_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlQuit_Create) +{ + testCommandCreate(testCase, metisControlQuit_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_Quit_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_Quit_Execute); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControl_Help_Quit_Execute) +{ + testHelpExecute(testCase, metisControlQuit_HelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_Quit_Execute) +{ + // This only displays the Help message + testHelpExecute(testCase, metisControlQuit_Create, __func__, MetisCommandReturn_Exit); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_Quit); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_Remove.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_Remove.c new file mode 100644 index 00000000..b5878797 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_Remove.c @@ -0,0 +1,130 @@ +/* + * 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 "../metisControl_Remove.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_Remove) +{ + // 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(metisControl_Remove) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_Remove) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlRemove_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlRemove_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControlRemove_HelpCreate) +{ + testCommandCreate(testCase, metisControlRemove_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlRemove_Create) +{ + testCommandCreate(testCase, metisControlRemove_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_Remove_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_Remove_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_Remove_Init); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControl_Help_Remove_Execute) +{ + testHelpExecute(testCase, metisControlRemove_HelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_Remove_Execute) +{ + // this only displays the help menu + testHelpExecute(testCase, metisControlRemove_Create, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_Remove_Init) +{ + testInit(testCase, metisControlRemove_Create, __func__, + (const char *[]) { + "remove connection", "remove route", + "help remove connection", "help remove route", + NULL + }); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_Remove); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_RemoveConnection.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_RemoveConnection.c new file mode 100644 index 00000000..76340b77 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_RemoveConnection.c @@ -0,0 +1,118 @@ +/* + * 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 "../metisControl_RemoveConnection.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_RemoveConnection) +{ + // 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(metisControl_RemoveConnection) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_RemoveConnection) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlRemoveConnection_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlRemoveConnection_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControlRemoveConnection_HelpCreate) +{ + testCommandCreate(testCase, &metisControlRemoveConnection_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlRemoveConnection_Create) +{ + testCommandCreate(testCase, &metisControlRemoveConnection_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_RemoveConnection_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_RemoveConnection_Execute); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControl_Help_RemoveConnection_Execute) +{ + testHelpExecute(testCase, &metisControlRemoveConnection_HelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_RemoveConnection_Execute) +{ + testHelpExecute(testCase, &metisControlRemoveConnection_Create, __func__, MetisCommandReturn_Success); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_RemoveConnection); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_RemoveRoute.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_RemoveRoute.c new file mode 100644 index 00000000..636d04b5 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_RemoveRoute.c @@ -0,0 +1,118 @@ +/* + * 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 "../metisControl_RemoveRoute.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_RemoveRoute) +{ + // 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(metisControl_RemoveRoute) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_RemoveRoute) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlRemoveRoute_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlRemoveRoute_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControlRemoveRoute_HelpCreate) +{ + testCommandCreate(testCase, &metisControlRemoveRoute_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlRemoveRoute_Create) +{ + testCommandCreate(testCase, &metisControlRemoveRoute_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_RemoveRoute_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_RemoveRoute_Execute); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControl_Help_RemoveRoute_Execute) +{ + testHelpExecute(testCase, &metisControlRemoveRoute_HelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_RemoveRoute_Execute) +{ + testUnimplemented(""); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_RemoveRoute); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_Root.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_Root.c new file mode 100644 index 00000000..3adf736c --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_Root.c @@ -0,0 +1,130 @@ +/* + * 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 "../metisControl_Root.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_Root) +{ + // 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(metisControl_Root) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_Root) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlRoot_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlRoot_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControlRoot_HelpCreate) +{ + testCommandCreate(testCase, &metisControlRoot_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlRoot_Create) +{ + testCommandCreate(testCase, &metisControlRoot_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_Root_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_Root_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_Root_Init); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControl_Help_Root_Execute) +{ + testHelpExecute(testCase, &metisControlRoot_HelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_Root_Execute) +{ + // this command only displays Help + testHelpExecute(testCase, &metisControlRoot_Create, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_Root_Init) +{ + testInit(testCase, &metisControlRoot_Create, __func__, + (const char *[]) { + "add", "list", "quit", "remove", "set", "unset", + "help add", "help list", "help quit", "help remove", "help set", "help unset", + NULL + }); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_Root); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_Set.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_Set.c new file mode 100644 index 00000000..b224e49f --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_Set.c @@ -0,0 +1,130 @@ +/* + * 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 "../metisControl_Set.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_Set) +{ + // 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(metisControl_Set) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_Set) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlSet_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlSet_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControlSet_HelpCreate) +{ + testCommandCreate(testCase, &metisControlSet_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlSet_Create) +{ + testCommandCreate(testCase, &metisControlSet_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_Set_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_Set_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_Set_Init); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControl_Help_Set_Execute) +{ + testHelpExecute(testCase, &metisControlSet_HelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_Set_Execute) +{ + // Only prints a help menu + testHelpExecute(testCase, &metisControlSet_Create, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_Set_Init) +{ + testInit(testCase, &metisControlSet_Create, __func__, + (const char *[]) { + "set debug", + "help set debug", + NULL + }); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_Set); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_SetDebug.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_SetDebug.c new file mode 100644 index 00000000..5f7f4805 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_SetDebug.c @@ -0,0 +1,153 @@ +/* + * 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 "../metisControl_SetDebug.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_SetDebug) +{ + // 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(metisControl_SetDebug) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_SetDebug) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlSetDebug_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlSetDebug_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControlSetDebug_HelpCreate) +{ + testCommandCreate(testCase, &metisControlSetDebug_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlSetDebug_Create) +{ + testCommandCreate(testCase, &metisControlSetDebug_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_SetDebug_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_SetDebug_Execute_WrongArgCount); + LONGBOW_RUN_TEST_CASE(Local, metisControl_SetDebug_Execute_Good); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControl_Help_SetDebug_Execute) +{ + testHelpExecute(testCase, &metisControlSetDebug_HelpCreate, __func__, MetisCommandReturn_Success); +} + +static MetisCommandReturn +testDebug(const LongBowTestCase *testCase, MetisCommandOps * (*create)(MetisControlState * state), int argc, bool initialDebugSetting, bool expectedDebugSetting) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + const char *argv[] = { "blah", "blah" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, argc, (void **) &argv[0]); + + metisControlState_SetDebug(data->state, initialDebugSetting); + MetisCommandOps *ops = create(data->state); + MetisCommandReturn result = ops->execute(data->state->parser, ops, args); + if (result == MetisCommandReturn_Success) { + assertTrue(data->state->debugFlag == expectedDebugSetting, + "Debug flag wrong, expected %d got %d", + expectedDebugSetting, + data->state->debugFlag); + } + + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + return result; +} + +LONGBOW_TEST_CASE(Local, metisControl_SetDebug_Execute_WrongArgCount) +{ + MetisCommandReturn result = testDebug(testCase, metisControlSetDebug_Create, 3, false, true); + assertTrue(result == MetisCommandReturn_Failure, + "metisControl_SetDebug_Execute should return %d, got %d", MetisCommandReturn_Failure, result); +} + + +LONGBOW_TEST_CASE(Local, metisControl_SetDebug_Execute_Good) +{ + MetisCommandReturn result = testDebug(testCase, metisControlSetDebug_Create, 2, false, true); + assertTrue(result == MetisCommandReturn_Success, + "metisControl_SetDebug_Execute should return %d, got %d", MetisCommandReturn_Success, result); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_SetDebug); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_Unset.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_Unset.c new file mode 100644 index 00000000..f7e27daf --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_Unset.c @@ -0,0 +1,130 @@ +/* + * 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 "../metisControl_Unset.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_Unset) +{ + // 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(metisControl_Unset) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_Unset) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlUnset_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlUnset_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControlUnset_HelpCreate) +{ + testCommandCreate(testCase, &metisControlUnset_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlUnset_Create) +{ + testCommandCreate(testCase, &metisControlUnset_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_Unset_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_Unset_Init); + LONGBOW_RUN_TEST_CASE(Local, metisControl_Unset_Execute); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControl_Help_Unset_Execute) +{ + testHelpExecute(testCase, &metisControlUnset_HelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_Unset_Init) +{ + testInit(testCase, &metisControlUnset_Create, __func__, + (const char *[]) { + "unset debug", + "help unset debug", + NULL + }); +} + +LONGBOW_TEST_CASE(Local, metisControl_Unset_Execute) +{ + // Only prints a help menu + testHelpExecute(testCase, &metisControlUnset_Create, __func__, MetisCommandReturn_Success); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_Unset); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_UnsetDebug.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_UnsetDebug.c new file mode 100644 index 00000000..bb31b917 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_UnsetDebug.c @@ -0,0 +1,153 @@ +/* + * 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 "../metisControl_UnsetDebug.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_UnsetDebug) +{ + // 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(metisControl_UnsetDebug) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_UnsetDebug) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlUnsetDebug_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlUnsetDebug_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControlUnsetDebug_HelpCreate) +{ + testCommandCreate(testCase, &metisControlUnsetDebug_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlUnsetDebug_Create) +{ + testCommandCreate(testCase, &metisControlUnsetDebug_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_UnsetDebug_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_UnsetDebug_Execute_WrongArgCount); + LONGBOW_RUN_TEST_CASE(Local, metisControl_UnsetDebug_Execute_Good); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + 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, metisControl_Help_UnsetDebug_Execute) +{ + testHelpExecute(testCase, &metisControlUnsetDebug_HelpCreate, __func__, MetisCommandReturn_Success); +} + +static MetisCommandReturn +testDebug(const LongBowTestCase *testCase, MetisCommandOps * (*create)(MetisControlState * state), int argc, bool initialDebugSetting, bool expectedDebugSetting) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + const char *argv[] = { "blah", "blah" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, argc, (void **) &argv[0]); + + metisControlState_SetDebug(data->state, initialDebugSetting); + MetisCommandOps *ops = create(data->state); + MetisCommandReturn result = ops->execute(data->state->parser, ops, args); + if (result == MetisCommandReturn_Success) { + assertTrue(data->state->debugFlag == expectedDebugSetting, + "Debug flag wrong, expected %d got %d", + expectedDebugSetting, + data->state->debugFlag); + } + + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + return result; +} + +LONGBOW_TEST_CASE(Local, metisControl_UnsetDebug_Execute_WrongArgCount) +{ + MetisCommandReturn result = testDebug(testCase, metisControlUnsetDebug_Create, 3, true, false); + assertTrue(result == MetisCommandReturn_Failure, + "metisControl_UnsetDebug_Execute should return %d, got %d", MetisCommandReturn_Failure, result); +} + + +LONGBOW_TEST_CASE(Local, metisControl_UnsetDebug_Execute_Good) +{ + MetisCommandReturn result = testDebug(testCase, metisControlUnsetDebug_Create, 2, true, false); + assertTrue(result == MetisCommandReturn_Success, + "metisControl_UnsetDebug_Execute should return %d, got %d", MetisCommandReturn_Success, result); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_UnsetDebug); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metis_CommandLineInterface.c b/metis/ccnx/forwarder/metis/config/test/test_metis_CommandLineInterface.c new file mode 100644 index 00000000..fe8e911c --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metis_CommandLineInterface.c @@ -0,0 +1,181 @@ +/* + * 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 "../metis_CommandLineInterface.c" + +#include <errno.h> +#include <string.h> + +#include <LongBow/unit-test.h> +#include <LongBow/debugging.h> + +#include <parc/algol/parc_SafeMemory.h> + +LONGBOW_TEST_RUNNER(test_metis_CommandLineInterface) +{ +// 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); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(test_metis_CommandLineInterface) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(test_metis_CommandLineInterface) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + //LONGBOW_RUN_TEST_CASE(Global, myTest); + LONGBOW_RUN_TEST_CASE(Global, Version); +} + +typedef struct test_state { + MetisForwarder *metis; + MetisDispatcher *dispatcher; + MetisCommandLineInterface *cli; + + int clientFd; +} TestState; + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + TestState *state = malloc(sizeof(TestState)); + + state->metis = metisForwarder_Create(NULL); + state->dispatcher = metisForwarder_GetDispatcher(state->metis); + +// we create our own CLI, because the one built in to metisForwarder is not started +// until the forwarder is running. + + state->cli = metisCommandLineInterface_Create(state->metis, 2001); + metisCommandLineInterface_Start(state->cli); + + metisDispatcher_RunCount(state->dispatcher, 1); + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = PF_INET; + addr.sin_port = htons(2001); + inet_pton(AF_INET, "127.0.0.1", &(addr.sin_addr)); + + state->clientFd = socket(PF_INET, SOCK_STREAM, 0); + assertFalse(state->clientFd < 0, "Error on socket: (%d) %s", errno, strerror(errno)); + + int failure = connect(state->clientFd, (struct sockaddr *) &addr, sizeof(addr)); + assertFalse(failure, "Error on connect: (%d) %s", errno, strerror(errno)); + +// crank the handle once + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(state->metis), &((struct timeval) { 0, 1000 })); + + longBowTestCase_SetClipBoardData(testCase, state); + + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + TestState *state = longBowTestCase_GetClipBoardData(testCase); + + close(state->clientFd); + metisCommandLineInterface_Destroy(&state->cli); + metisForwarder_Destroy(&state->metis); + free(state); + + 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; +} + +/** + * The CLI has a secret command "~~" (two of them) that will echo back whatever the next + * words are. The string "~~ hello world" would echo back "success: hello world" followed by + * the next command prompt. This lets us test that the 1st level of parsing is working. It + * differentiates "~~" as the command and the rest of the string as parameters. + */ +LONGBOW_TEST_CASE(Global, myTest) +{ + TestState *state = longBowTestCase_GetClipBoardData(testCase); + + + char readbuffer[1024]; + +// Skipover the MOTD + ssize_t nread = read(state->clientFd, readbuffer, 1024); + assertTrue(nread > -1, "Error read"); + printf("read:\n%s\n", readbuffer); + + // send special command "~~" followed by a string. It should be repeated back + // as "success: see no hands\nmetis> ", where the stuff after the \n is the next command prompt + char magic[] = "~~ see no hands\r\n"; + ssize_t nwritten = write(state->clientFd, magic, sizeof(magic)); + assertTrue(nwritten == sizeof(magic), "Error write, expected %zu got %zd", sizeof(magic), nwritten); + + metisDispatcher_RunDuration(state->dispatcher, &((struct timeval) { 0, 1000 })); + + memset(readbuffer, 0, 1024); + nread = read(state->clientFd, readbuffer, 1024); + assertTrue(nread > -1, "Error read"); + + // we look for the answer without the "\nmetis> " part. + char answer[] = "success: see no hands"; + assertTrue(strncasecmp(readbuffer, answer, sizeof(answer) - 1) == 0, "Got wrong string: %s", readbuffer); +} + +LONGBOW_TEST_CASE(Global, Version) +{ + TestState *state = longBowTestCase_GetClipBoardData(testCase); + + char readbuffer[1024]; + + // Skipover the MOTD + ssize_t nread = read(state->clientFd, readbuffer, 1024); + assertTrue(nread > -1, "Error read"); + + printf("read:\n%s\n", readbuffer); + + // send special command "~~" followed by a string. It should be repeated back + // as "success: see no hands\nmetis> ", where the stuff after the \n is the next command prompt + char magic[] = "ver\r\n"; + ssize_t nwritten = write(state->clientFd, magic, sizeof(magic)); + assertTrue(nwritten == sizeof(magic), "Error write, expected %zu got %zd", sizeof(magic), nwritten); + + metisDispatcher_RunDuration(state->dispatcher, &((struct timeval) { 0, 1000 })); + + memset(readbuffer, 0, 1024); + nread = read(state->clientFd, readbuffer, 1024); + assertTrue(nread > -1, "Error read"); + + printf("%s", readbuffer); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(test_metis_CommandLineInterface); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metis_CommandOps.c b/metis/ccnx/forwarder/metis/config/test/test_metis_CommandOps.c new file mode 100644 index 00000000..4d6a5c13 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metis_CommandOps.c @@ -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. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_CommandOps.c" + +#include <inttypes.h> +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +LONGBOW_TEST_RUNNER(metis_CommandOps) +{ + // 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); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_CommandOps) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_CommandOps) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisCommandOps_Create); +} + +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; +} + +static void +_init(struct metis_command_parser *parser, MetisCommandOps *ops) +{ +} + +static MetisCommandReturn +_execute(struct metis_command_parser *parser, MetisCommandOps *ops, PARCList *args) +{ + return MetisCommandReturn_Success; +} + +static void +_destroyer(MetisCommandOps **opsPtr) +{ +} + +LONGBOW_TEST_CASE(Global, metisCommandOps_Create) +{ + char hello[] = "hello"; + char command[] = "test"; + + MetisCommandOps *ops = metisCommandOps_Create(hello, command, _init, _execute, _destroyer); + + assertTrue(ops->closure == hello, "closure wrong expected %p got %p", (void *) hello, (void *) ops->closure); + assertTrue(strcmp(ops->command, command) == 0, "command wrong expected '%s' got '%s'", command, ops->command); + assertTrue(ops->init == _init, "Wrong init, expected %" PRIXPTR " got %" PRIXPTR, (uintptr_t) _init, (uintptr_t) ops->init); + assertTrue(ops->execute == _execute, "Wrong execute, expected %" PRIXPTR " got %" PRIXPTR, (uintptr_t) _execute, (uintptr_t) ops->execute); + assertTrue(ops->destroyer == _destroyer, "Wrong destroyer, expected %" PRIXPTR " got %" PRIXPTR, (uintptr_t) _destroyer, (uintptr_t) ops->destroyer); + + metisCommandOps_Destroy(&ops); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_CommandOps); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metis_CommandParser.c b/metis/ccnx/forwarder/metis/config/test/test_metis_CommandParser.c new file mode 100644 index 00000000..b7e6edae --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metis_CommandParser.c @@ -0,0 +1,259 @@ +/* + * 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_CommandParser.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metis_CommandParser) +{ + // 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_CommandParser) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_CommandParser) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisCommandParser_Create_Destroy); + + LONGBOW_RUN_TEST_CASE(Global, metisCommandParser_DispatchCommand_Exact); + LONGBOW_RUN_TEST_CASE(Global, metisCommandParser_DispatchCommand_Longer); + LONGBOW_RUN_TEST_CASE(Global, metisCommandParser_DispatchCommand_Shorter); + LONGBOW_RUN_TEST_CASE(Global, metisCommandParser_DispatchCommand_Sibling); + LONGBOW_RUN_TEST_CASE(Global, metisCommandParser_GetDebug); + LONGBOW_RUN_TEST_CASE(Global, metisCommandParser_Interactive); + LONGBOW_RUN_TEST_CASE(Global, metisCommandParser_RegisterCommand_NullInit); + LONGBOW_RUN_TEST_CASE(Global, metisCommandParser_RegisterCommand_WithInit); + LONGBOW_RUN_TEST_CASE(Global, metisCommandParser_SetDebug); +} + +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, metisCommandParser_Create_Destroy) +{ + MetisCommandParser *parser = metisCommandParser_Create(); + assertNotNull(parser, "Got null parser from metisCommandParser_Create"); + metisCommandParser_Destroy(&parser); + assertTrue(parcSafeMemory_ReportAllocation(STDOUT_FILENO) == 0, "Memory imbalance!"); + assertNull(parser, "metisCommandParser_Destroy did not null pointer"); +} + +static MetisCommandReturn +test_execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + bool *execute_called_ptr = (bool *) ops->closure; + *execute_called_ptr = true; + return MetisCommandReturn_Success; +} + +/** + * argc = the exact number of args, don't include the command name + * example: argc = 2, argv = {"Hello", "World"} + * + * expectedResult true means the execute function is called + */ +static void +dispatchCommand(const char *command_string, int argc, char **argv, bool expectedResult) +{ + MetisCommandParser *parser = metisCommandParser_Create(); + + bool execute_called = false; + + MetisCommandOps *ops = metisCommandOps_Create(&execute_called, command_string, NULL, test_execute, metisCommandOps_Destroy); + + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, argc, (void **) &argv[0]); + + execute_called = false; + metisCommandParser_RegisterCommand(parser, ops); + metisCommandParser_DispatchCommand(parser, args); + if (expectedResult) { + assertTrue(execute_called, "Did not call the execute function"); + } else { + assertFalse(execute_called, "The execute function should not have been called but was"); + } + + metisCommandParser_Destroy(&parser); + parcList_Release(&args); +} + +LONGBOW_TEST_CASE(Global, metisCommandParser_DispatchCommand_Exact) +{ + // note that it is not case sensitive + dispatchCommand("hello world", 2, (char *[]) { "Hello", "World" }, true); +} + +LONGBOW_TEST_CASE(Global, metisCommandParser_DispatchCommand_Sibling) +{ + // note that it is not case sensitive + dispatchCommand("hello world", 2, (char *[]) { "Hello", "Universe" }, false); +} + + +LONGBOW_TEST_CASE(Global, metisCommandParser_DispatchCommand_Longer) +{ + // note that it is not case sensitive + dispatchCommand("hello world", 3, (char *[]) { "Hello", "World", "Again" }, true); +} + +LONGBOW_TEST_CASE(Global, metisCommandParser_DispatchCommand_Shorter) +{ + // note that it is not case sensitive + dispatchCommand("hello world", 1, (char *[]) { "Hello" }, false); +} + +LONGBOW_TEST_CASE(Global, metisCommandParser_GetDebug) +{ + MetisCommandParser *parser = metisCommandParser_Create(); + bool test = metisCommandParser_GetDebug(parser); + assertTrue(test == parser->debugFlag, "Got %d expected %d", test, parser->debugFlag); + metisCommandParser_Destroy(&parser); +} + +LONGBOW_TEST_CASE(Global, metisCommandParser_Interactive) +{ + testUnimplemented(""); +} + +static bool called_init = false; +static void +test_init_command(MetisCommandParser *parser, MetisCommandOps *ops) +{ + called_init = true; +} + +LONGBOW_TEST_CASE(Global, metisCommandParser_RegisterCommand_WithInit) +{ + MetisCommandParser *parser = metisCommandParser_Create(); + + MetisCommandOps *ops = metisCommandOps_Create(NULL, "hello world", test_init_command, test_execute, metisCommandOps_Destroy); + + called_init = false; + metisCommandParser_RegisterCommand(parser, ops); + + MetisCommandOps *test = parcTreeRedBlack_Get(parser->commandTree, ops->command); + assertNotNull(test, "Got null looking up command in tree"); + assertTrue(test == ops, "Wrong pointer, got %p expected %p", (void *) test, (void *) ops); + assertTrue(called_init, "Did not call the init function"); + + metisCommandParser_Destroy(&parser); +} + +LONGBOW_TEST_CASE(Global, metisCommandParser_RegisterCommand_NullInit) +{ + MetisCommandParser *parser = metisCommandParser_Create(); + + MetisCommandOps command = { + .command = "hello world", + .init = NULL, + .execute = NULL + }; + + called_init = false; + metisCommandParser_RegisterCommand(parser, &command); + + MetisCommandOps *test = parcTreeRedBlack_Get(parser->commandTree, command.command); + assertNotNull(test, "Got null looking up command in tree"); + assertTrue(test == &command, "Wrong pointer, got %p expected %p", (void *) test, (void *) &command); + assertFalse(called_init, "Somehow called the init function"); + + metisCommandParser_Destroy(&parser); +} + +LONGBOW_TEST_CASE(Global, metisCommandParser_SetDebug) +{ + MetisCommandParser *parser = metisCommandParser_Create(); + // flip the setting + bool truth = ~parser->debugFlag; + metisCommandParser_SetDebug(parser, truth); + assertTrue(truth == parser->debugFlag, "Got %d expected %d", parser->debugFlag, truth); + metisCommandParser_Destroy(&parser); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisCommandParser_MatchCommand); + LONGBOW_RUN_TEST_CASE(Local, parseStringIntoTokens); + LONGBOW_RUN_TEST_CASE(Local, stringCompare); +} + +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, metisCommandParser_MatchCommand) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, parseStringIntoTokens) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, stringCompare) +{ + testUnimplemented(""); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_CommandParser); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metis_Configuration.c b/metis/ccnx/forwarder/metis/config/test/test_metis_Configuration.c new file mode 100644 index 00000000..09b9a576 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metis_Configuration.c @@ -0,0 +1,779 @@ +/* + * 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. + */ + +/* + * Some of these tests might not execute on certain systems, as they + * depend on having INET and INET6 addresses available. If you system + * does not have one or both of those, the corresponding tests will not + * execute. + */ + +// We need to specifically include the Ethernet mockup and set the proper define so +// we do not need an actual Ethernet listener + +#define METIS_MOCK_ETHERNET 1 +#include "../../io/test/testrig_GenericEther.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_Configuration.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +#include <signal.h> + +// so we can mock up an interface +#include "../../core/test/testrig_MetisIoOperations.h" + +// so we can test content store size +#include "../../core/metis_Forwarder.c" +#include "../../processor/metis_MessageProcessor.c" +#include "../../content_store/metis_ContentStoreInterface.h" + +struct sigaction save_sigchld; +struct sigaction save_sigpipe; + +/** + * Add a connection to the connection table to mock the "ingress" port of a control message + * + * You must release the return value + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval non-null A mockup of a connection + * @retval null An error + * + * Example: + * @code + * { + * unsigned mockConnectionId = 7; + * MetisIoOperations *ops = _addIngressMockConnection(metis, mockConnectionId); + * MockIoOperationsData *data = ops->context; + * mockIoOperationsData_Destroy(&ops); + * } + * @endcode + */ +static MetisIoOperations * +_addIngressMockConnection(MetisForwarder *metis, unsigned mockup_id) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, mockup_id, true, true, true); + + MetisConnection *conn = metisConnection_Create(ops); + MetisConnectionTable *connTable = metisForwarder_GetConnectionTable(metis); + metisConnectionTable_Add(connTable, conn); + return ops; +} + +// returns a strdup() of the interface name, use free(3) +static char * +_pickInterfaceName(MetisForwarder *metis) +{ + char *ifname = NULL; + + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + size_t length = cpiInterfaceSet_Length(set); + assertTrue(length > 0, "metisSystem_Interfaces returned no interfaces"); + + for (size_t i = 0; i < length; i++) { + CPIInterface *iface = cpiInterfaceSet_GetByOrdinalIndex(set, i); + const CPIAddressList *addressList = cpiInterface_GetAddresses(iface); + + size_t length = cpiAddressList_Length(addressList); + for (size_t i = 0; i < length && !ifname; i++) { + const CPIAddress *a = cpiAddressList_GetItem(addressList, i); + if (cpiAddress_GetType(a) == cpiAddressType_LINK) { + ifname = strdup(cpiInterface_GetName(iface)); + } + } + } + + cpiInterfaceSet_Destroy(&set); + return ifname; +} + +/** + * Adds a mock ethernet connection to the given peer address with a symbolic name. + * You must have previously added an Ethernet listener. + * + * @return true Added + * @return false An error + */ +static bool +_addEthernetConnection(MetisForwarder *metis, unsigned connid, const char *symbolicName, MetisListenerOps *listener, uint8_t peerEther[6]) +{ + // Create a CPIConnectionEthernet Add control message + char *ifname = _pickInterfaceName(metis); + + uint16_t etherType = 0x0801; + + CPIAddress *peerAddress = cpiAddress_CreateFromLink(peerEther, 6); + CPIConnectionEthernet *etherConn = cpiConnectionEthernet_Create(ifname, peerAddress, etherType, symbolicName); + bool success = _metisConfiguration_AddConnectionEthernet(metisForwarder_GetConfiguration(metis), etherConn, peerAddress, listener); + + + cpiAddress_Destroy(&peerAddress); + free(ifname); + cpiConnectionEthernet_Release(ðerConn); + + return success; +} + +// ========================================================================= + +LONGBOW_TEST_RUNNER(metis_Configuration) +{ + // 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. + printf("line 140\n"); + 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_Configuration) +{ + printf("line 148\n"); + 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_Configuration) +{ + printf("line 156\n"); + return LONGBOW_STATUS_SUCCEEDED; +} + +// ============================================================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisConfiguration_SetupAllListeners); + LONGBOW_RUN_TEST_CASE(Global, metisConfiguration_Receive); + LONGBOW_RUN_TEST_CASE(Global, metisConfiguration_SetObjectStoreSize); +} + +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, metisConfiguration_SetupAllListeners) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Global, metisConfiguration_Receive) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + // Add a connection to apply the route to + unsigned mockConnectionId = 7000; + MetisIoOperations *ops = _addIngressMockConnection(metis, mockConnectionId); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + CCNxName *prefix = ccnxName_CreateFromCString("lci:/foo"); + CPIRouteEntry *routeEntry = cpiRouteEntry_Create(prefix, mockConnectionId, NULL, + cpiNameRouteProtocolType_STATIC, + cpiNameRouteType_LONGEST_MATCH, NULL, 4); + CCNxControl *request = ccnxControl_CreateAddRouteRequest(routeEntry); + cpiRouteEntry_Destroy(&routeEntry); + + PARCBuffer *buffer = metisTlv_EncodeControlPlaneInformation(request); + + MetisMessage *message = metisMessage_CreateFromArray(parcBuffer_Overlay(buffer, 0), parcBuffer_Limit(buffer), mockConnectionId, 2, metisForwarder_GetLogger(metis)); + parcBuffer_Release(&buffer); + + // this takes ownership of message and disposes of it + metisConfiguration_Receive(metisForwarder_GetConfiguration(metis), message); + + // crank the handle to lets the ACKs or NACKs move + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + + assertTrue(data->sendCount == 1, "Did not send a response message, expected 1 got %u", data->sendCount); + CCNxControl *response = metisMessage_CreateControlMessage(data->lastMessage); + + assertTrue(cpi_GetMessageType(response) == CPI_ACK, + "CPI message not a response: %s", + parcJSON_ToString(ccnxControl_GetJson(response))); + + ccnxControl_Release(&response); + ccnxControl_Release(&request); + metisForwarder_Destroy(&metis); + + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisConfiguration_SetObjectStoreSize) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + MetisContentStoreInterface *store = metis->processor->contentStore; + MetisConfiguration *config = metisForwarder_GetConfiguration(metis); + size_t current_capacity = metisContentStoreInterface_GetObjectCapacity(store); + size_t new_capacity = current_capacity + 5; + + metisConfiguration_SetObjectStoreSize(config, new_capacity); + + // Get the store pointer again, as it may have changed. + store = metis->processor->contentStore; + assertTrue(new_capacity == metisContentStoreInterface_GetObjectCapacity(store), + "Object Store is wrong capacity, got %zu expected %zu", + metisContentStoreInterface_GetObjectCapacity(store), new_capacity); + + metisForwarder_Destroy(&metis); +} + +// ============================================================================== + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_Receive_AddConnectionEthernet); + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_Receive_AddConnectionEthernet_Dup); + + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_ProcessUnregisterPrefix); + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_ProcessRegisterPrefix); + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_ProcessRegisterPrefix_Symbolic); + + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_ProcessInterfaceList); + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_ProcessRegistrationList); + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_ProcessCreateTunnel_Dup); + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_ProcessCreateTunnel_TCP); + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_ProcessCreateTunnel_UDP); + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_ProcessConnectionList); + + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_ProcessAddConnectionEthernet); + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_ProcessRemoveConnectionEthernet); + + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_Receive_AddConnectionEthernet); + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_Receive_RemoveConnectionEthernet); +} + +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, metisConfiguration_ProcessInterfaceList) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + CCNxControl *request = ccnxControl_CreateInterfaceListRequest(); + + unsigned mockConnectionId = 7; + + CCNxControl *response = metisConfiguration_ProcessInterfaceList(metisForwarder_GetConfiguration(metis), request, mockConnectionId); + + assertNotNull(response, "Got null response"); + + assertTrue(cpi_GetMessageType(response) == CPI_RESPONSE, + "CPI message not a response: %s", + parcJSON_ToString(ccnxControl_GetJson(response))); + + assertTrue(cpi_GetMessageOperation(response) == CPI_INTERFACE_LIST, + "CPI message not an interface list: %s", + parcJSON_ToString(ccnxControl_GetJson(response))); + + ccnxControl_Release(&response); + ccnxControl_Release(&request); + metisForwarder_Destroy(&metis); +} + +LONGBOW_TEST_CASE(Local, metisConfiguration_ProcessUnregisterPrefix) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, metisConfiguration_ProcessRegisterPrefix) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + // Add a connection to apply the route to + unsigned mockConnectionId = 7000; + + CCNxName *prefix = ccnxName_CreateFromCString("lci:/foo"); + CPIRouteEntry *routeEntry = cpiRouteEntry_Create(prefix, mockConnectionId, NULL, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, NULL, 4); + CCNxControl *request = ccnxControl_CreateAddRouteRequest(routeEntry); + cpiRouteEntry_Destroy(&routeEntry); + + CCNxControl *response = metisConfiguration_ProcessRegisterPrefix(metisForwarder_GetConfiguration(metis), request, mockConnectionId); + + // crank the handle to lets the ACKs or NACKs move + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + + assertNotNull(response, "LastMessage is not set in the test rig"); + + assertTrue(cpi_GetMessageType(response) == CPI_ACK, + "CPI message not a response: %s", + parcJSON_ToString(ccnxControl_GetJson(response))); + + ccnxControl_Release(&response); + ccnxControl_Release(&request); + metisForwarder_Destroy(&metis); +} + +LONGBOW_TEST_CASE(Local, metisConfiguration_ProcessRegisterPrefix_Symbolic) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + // Add a connection to apply the route to + unsigned mockConnectionId = 7000; + + // hack in the symbolic name because _addIngressMockConnection does not do that + metisSymbolicNameTable_Add(metisForwarder_GetConfiguration(metis)->symbolicNameTable, "foo0", mockConnectionId); + + CCNxName *prefix = ccnxName_CreateFromCString("lci:/foo"); + CPIRouteEntry *routeEntry = cpiRouteEntry_CreateSymbolic(prefix, "foo0", cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, NULL, 4); + CCNxControl *request = ccnxControl_CreateAddRouteRequest(routeEntry); + cpiRouteEntry_Destroy(&routeEntry); + + CCNxControl *response = metisConfiguration_ProcessRegisterPrefix(metisForwarder_GetConfiguration(metis), request, mockConnectionId); + + // crank the handle to lets the ACKs or NACKs move + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + + assertNotNull(response, "Response is NULL"); + + assertTrue(cpi_GetMessageType(response) == CPI_ACK, + "CPI message not a response: %s", + parcJSON_ToString(ccnxControl_GetJson(response))); + + ccnxControl_Release(&response); + ccnxControl_Release(&request); + metisForwarder_Destroy(&metis); +} + +/** + * Add a route, then verify the route shows up in a list + */ +LONGBOW_TEST_CASE(Local, metisConfiguration_ProcessRegistrationList) +{ + printf("\n%s starting\n", __func__); + + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 7; + + // Add a route to the forwarding table + CCNxName *prefix = ccnxName_CreateFromCString("lci:/pancakes/for/all"); + CPIRouteEntry *route = cpiRouteEntry_Create(prefix, 3, NULL, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, NULL, 2); + metisForwarder_AddOrUpdateRoute(metis, route); + cpiRouteEntry_Destroy(&route); + + // Create a request and send it in to MetisConfiguration. The response will be + // sent out the "mockup_id" interface + + CCNxControl *request = ccnxControl_CreateRouteListRequest(); + MetisConfiguration *config = metisForwarder_GetConfiguration(metis); + CCNxControl *response = metisConfiguration_ProcessRegistrationList(config, request, mockup_id); + + assertNotNull(response, "Got null response"); + + assertTrue(cpi_GetMessageType(response) == CPI_RESPONSE, + "CPI message not a response: %s", + parcJSON_ToString(ccnxControl_GetJson(response))); + + assertTrue(cpi_GetMessageOperation(response) == CPI_PREFIX_REGISTRATION_LIST, + "CPI message not an interface list: %s", + parcJSON_ToString(ccnxControl_GetJson(response))); + + ccnxControl_Release(&response); + ccnxControl_Release(&request); + metisForwarder_Destroy(&metis); +} + +LONGBOW_TEST_CASE(Local, metisConfiguration_ProcessCreateTunnel_TCP) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 7; + + // ----- + // Issue a command to create a TCP tunnel. We should be able to verify that it's in + // the connection table and we'll see the ACK come back to our mock interface. + + // --------------------------- + // Tunnel addresses + struct sockaddr_in sockaddr_any; + memset(&sockaddr_any, 0, sizeof(sockaddr_any)); + sockaddr_any.sin_family = PF_INET; + sockaddr_any.sin_addr.s_addr = INADDR_ANY; + + CPIAddress *source = cpiAddress_CreateFromInet(&sockaddr_any); + + struct sockaddr_in sockaddr_dst; + memset(&sockaddr_dst, 0, sizeof(sockaddr_dst)); + sockaddr_dst.sin_family = PF_INET; + sockaddr_dst.sin_port = htons(PORT_NUMBER); + inet_pton(AF_INET, "127.0.0.1", &(sockaddr_dst.sin_addr)); + + CPIAddress *destination = cpiAddress_CreateFromInet(&sockaddr_dst); + + // --------------------------- + + CPIInterfaceIPTunnel *iptun = cpiInterfaceIPTunnel_Create(0, source, destination, IPTUN_TCP, "tun0"); + CCNxControl *request = ccnxControl_CreateIPTunnelRequest(iptun); + + MetisConfiguration *config = metisForwarder_GetConfiguration(metis); + + CCNxControl *response = metisConfiguration_ProcessCreateTunnel(config, request, mockup_id); + + // crank the handle to lets the ACKs or NACKs move + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + + // Validate the ACK + assertNotNull(response, "Got null response"); + + assertTrue(cpi_GetMessageType(response) == CPI_ACK, + "CPI message not an ACK: %s", + parcJSON_ToString(ccnxControl_GetJson(response))); + + ccnxControl_Release(&response); + ccnxControl_Release(&request); + cpiInterfaceIPTunnel_Release(&iptun); + metisForwarder_Destroy(&metis); +} + +LONGBOW_TEST_CASE(Local, metisConfiguration_ProcessCreateTunnel_Dup) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 7000; + MetisIoOperations *ops = _addIngressMockConnection(metis, mockup_id); + + // --------------------------- + // Tunnel addresses + struct sockaddr_in sockaddr_any; + memset(&sockaddr_any, 0, sizeof(sockaddr_any)); + sockaddr_any.sin_family = PF_INET; + sockaddr_any.sin_addr.s_addr = INADDR_ANY; + + CPIAddress *source = cpiAddress_CreateFromInet(&sockaddr_any); + + struct sockaddr_in sockaddr_dst; + memset(&sockaddr_dst, 0, sizeof(sockaddr_dst)); + sockaddr_dst.sin_family = PF_INET; + sockaddr_dst.sin_port = htons(PORT_NUMBER); + inet_pton(AF_INET, "127.0.0.1", &(sockaddr_dst.sin_addr)); + + CPIAddress *destination = cpiAddress_CreateFromInet(&sockaddr_dst); + + // --------------------------- + + CPIInterfaceIPTunnel *iptun = cpiInterfaceIPTunnel_Create(0, source, destination, IPTUN_TCP, "tun0"); + CCNxControl *request = ccnxControl_CreateIPTunnelRequest(iptun); + + MetisConfiguration *config = metisForwarder_GetConfiguration(metis); + + CCNxControl *response_1 = metisConfiguration_ProcessCreateTunnel(config, request, mockup_id); + assertNotNull(response_1, "got null response"); + assertTrue(ccnxControl_IsACK(response_1), "Did not get ACK response for first tunnel"); + ccnxControl_Release(&response_1); + + CCNxControl *response_2 = metisConfiguration_ProcessCreateTunnel(config, request, mockup_id); + assertNotNull(response_2, "got null response"); + assertTrue(ccnxControl_IsNACK(response_2), "Did not get NACK response for second tunnel"); + + ccnxControl_Release(&response_2); + + ccnxControl_Release(&request); + cpiInterfaceIPTunnel_Release(&iptun); + metisForwarder_Destroy(&metis); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Local, metisConfiguration_ProcessCreateTunnel_UDP) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 7; + + // ----- + // Issue a command to create a UDP tunnel. We should be able to verify that it's in + // the connection table and we'll see the ACK come back to our mock interface. + + // --------------------------- + // Tunnel addresses + struct sockaddr_in sockaddr_any; + memset(&sockaddr_any, 0, sizeof(sockaddr_any)); + sockaddr_any.sin_family = PF_INET; + sockaddr_any.sin_addr.s_addr = INADDR_ANY; + + CPIAddress *source = cpiAddress_CreateFromInet(&sockaddr_any); + + struct sockaddr_in sockaddr_dst; + memset(&sockaddr_dst, 0, sizeof(sockaddr_dst)); + sockaddr_dst.sin_family = PF_INET; + sockaddr_dst.sin_port = htons(PORT_NUMBER); + inet_pton(AF_INET, "127.0.0.1", &(sockaddr_dst.sin_addr)); + + CPIAddress *destination = cpiAddress_CreateFromInet(&sockaddr_dst); + + // --------------------------- + + CPIInterfaceIPTunnel *iptun = cpiInterfaceIPTunnel_Create(0, source, destination, IPTUN_UDP, "conn0"); + CCNxControl *request = ccnxControl_CreateIPTunnelRequest(iptun); + + MetisConfiguration *config = metisForwarder_GetConfiguration(metis); + + CCNxControl *response = metisConfiguration_ProcessCreateTunnel(config, request, mockup_id); + + // Validate the ACK + assertNotNull(response, "Got null response"); + + assertTrue(cpi_GetMessageType(response) == CPI_ACK, + "CPI message not an ACK: %s", + parcJSON_ToString(ccnxControl_GetJson(response))); + + ccnxControl_Release(&response); + ccnxControl_Release(&request); + cpiInterfaceIPTunnel_Release(&iptun); + metisForwarder_Destroy(&metis); +} + + +LONGBOW_TEST_CASE(Local, metisConfiguration_ProcessConnectionList) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 7; + MetisIoOperations *ops = _addIngressMockConnection(metis, mockup_id); + + CCNxControl *request = ccnxControl_CreateConnectionListRequest(); + + MetisConfiguration *config = metisForwarder_GetConfiguration(metis); + CCNxControl *response = metisConfiguration_ProcessConnectionList(config, request, mockup_id); + + // Validate the response + assertNotNull(response, "Got null response"); + + // Get the CPI response out of the test mock up + CPIConnectionList *list = cpiLinks_ConnectionListFromControlMessage(response); + assertTrue(cpiConnectionList_Length(list) == 1, "Wrong list size, expected %u got %zu", 1, cpiConnectionList_Length(list)); + + ccnxControl_Release(&response); + ccnxControl_Release(&request); + cpiConnectionList_Destroy(&list); + metisForwarder_Destroy(&metis); + mockIoOperationsData_Destroy(&ops); +} + + +LONGBOW_TEST_CASE(Local, metisConfiguration_ProcessAddConnectionEthernet) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 77; + + // create the listener + char *ifname = _pickInterfaceName(metis); + CPIListener *cpiListener = cpiListener_CreateEther(ifname, 0x0801, "fake0"); + CCNxControl *control = cpiListener_CreateAddMessage(cpiListener); + bool listenerOk = metisConfigurationListeners_Add(metisForwarder_GetConfiguration(metis), control, mockup_id); + assertTrue(listenerOk, "Failed to setup ether listener."); + + ccnxControl_Release(&control); + cpiListener_Release(&cpiListener); + + // ======== + uint8_t peerEther[6] = { 0x02, 0x33, 0x44, 0x55, 0x66, 0x77 }; + CPIAddress *peerAddress = cpiAddress_CreateFromLink(peerEther, sizeof(peerEther)); + CPIConnectionEthernet *etherconn = cpiConnectionEthernet_Create(ifname, peerAddress, 0x0801, "conn3"); + CCNxControl *addRequest = cpiConnectionEthernet_CreateAddMessage(etherconn); + + MetisConfiguration *config = metisForwarder_GetConfiguration(metis); + + CCNxControl *response = metisConfiguration_ProcessAddConnectionEthernet(config, addRequest, mockup_id); + + // crank the handle to lets the ACKs or NACKs move + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + + // Get the CPI response out of the test mock up + assertNotNull(response, "Got null response"); + + assertTrue(ccnxControl_IsACK(response), "Response is not an ACK") + { + ccnxControl_Display(response, 3); + } + + // we must manually destroy a Mock connection + ccnxControl_Release(&response); + free(ifname); + cpiConnectionEthernet_Release(ðerconn); + cpiAddress_Destroy(&peerAddress); + ccnxControl_Release(&addRequest); + metisForwarder_Destroy(&metis); +} + +LONGBOW_TEST_CASE(Local, metisConfiguration_ProcessRemoveConnectionEthernet) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, metisConfiguration_Receive_AddConnectionEthernet) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 7; + MetisIoOperations *ops = _addIngressMockConnection(metis, mockup_id); + + // create the listener + char *ifname = _pickInterfaceName(metis); + CPIListener *cpiListener = cpiListener_CreateEther(ifname, 0x0801, "fake0"); + CCNxControl *control = cpiListener_CreateAddMessage(cpiListener); + bool listenerOk = metisConfigurationListeners_Add(metisForwarder_GetConfiguration(metis), control, mockup_id); + assertTrue(listenerOk, "Failed to setup ether listener."); + + ccnxControl_Release(&control); + cpiListener_Release(&cpiListener); + + // create the connection + uint8_t linkAddrArray[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 }; + uint16_t etherType = 0x0801; + + CPIAddress *peerAddress = cpiAddress_CreateFromLink(linkAddrArray, sizeof(linkAddrArray)); + CPIConnectionEthernet *etherconn = cpiConnectionEthernet_Create(ifname, peerAddress, etherType, "conn3"); + CCNxControl *addRequest = cpiConnectionEthernet_CreateAddMessage(etherconn); + + // Translate the control message to a MetisMessage + PARCBuffer *buffer = metisTlv_EncodeControlPlaneInformation(addRequest); + + MetisMessage *message = metisMessage_CreateFromArray(parcBuffer_Overlay(buffer, 0), parcBuffer_Limit(buffer), mockup_id, 2, metisForwarder_GetLogger(metis)); + + MetisConfiguration *config = metisForwarder_GetConfiguration(metis); + + // this will release the message + metisConfiguration_Receive(config, message); + + // ==== Verify it's in the connection table + + MetisConnectionList *connList = metisConnectionTable_GetEntries(metisForwarder_GetConnectionTable(metis)); + assertNotNull(connList, "Got null connection list"); + + bool found = false; + for (size_t i = 0; i < metisConnectionList_Length(connList) && !found; i++) { + MetisConnection *conn = metisConnectionList_Get(connList, i); + const MetisAddressPair *pair = metisConnection_GetAddressPair(conn); + const CPIAddress *remote = metisAddressPair_GetRemote(pair); + if (cpiAddress_Equals(remote, peerAddress)) { + found = true; + } + } + + assertTrue(found, "Could not find peer address in the connection table as a remote"); + + // ==== Cleanup + + parcBuffer_Release(&buffer); + ccnxControl_Release(&addRequest); + cpiConnectionEthernet_Release(ðerconn); + cpiAddress_Destroy(&peerAddress); + free(ifname); + metisConnectionList_Destroy(&connList); + metisForwarder_Destroy(&metis); + mockIoOperationsData_Destroy(&ops); +} + +/* + * Try to add a second connection with same symbolic name + */ +LONGBOW_TEST_CASE(Local, metisConfiguration_Receive_AddConnectionEthernet_Dup) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + uint8_t peerEther[6] = { 7, 8, 9, 10, 11, 12 }; + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 7000; + MetisIoOperations *ops = _addIngressMockConnection(metis, mockup_id); + + char *ifname = _pickInterfaceName(metis); + CPIListener *cpiListener = cpiListener_CreateEther(ifname, 0x0801, "fake0"); + CCNxControl *control = cpiListener_CreateAddMessage(cpiListener); + metisConfigurationListeners_Add(metisForwarder_GetConfiguration(metis), control, mockup_id); + ccnxControl_Release(&control); + cpiListener_Release(&cpiListener); + free(ifname); + + MetisListenerOps *listener = metisListenerSet_Get(metisForwarder_GetListenerSet(metis), 0); + + // Create a mock up of an interface so we can see the response + bool success = _addEthernetConnection(metis, 1000, "conn3", listener, peerEther); + assertTrue(success, "Failed to add first instance of connection"); + + // now add again, should fail + bool failure = _addEthernetConnection(metis, 1001, "conn3", listener, peerEther); + assertFalse(failure, "Should have failed to add it a second time"); + + metisForwarder_Destroy(&metis); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Local, metisConfiguration_Receive_RemoveConnectionEthernet) +{ +} + +// ====================================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_Configuration); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metis_ConfigurationFile.c b/metis/ccnx/forwarder/metis/config/test/test_metis_ConfigurationFile.c new file mode 100644 index 00000000..6c7e19df --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metis_ConfigurationFile.c @@ -0,0 +1,403 @@ +/* + * 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 "../metis_ConfigurationFile.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <errno.h> +#include <string.h> + +#include <sys/types.h> +#include <sys/stat.h> + +// ========================================================================= + +LONGBOW_TEST_RUNNER(metis_ConfigurationFile) +{ + LONGBOW_RUN_TEST_FIXTURE(Create); + LONGBOW_RUN_TEST_FIXTURE(Process); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_ConfigurationFile) +{ + 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_ConfigurationFile) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ============================================================================== + +LONGBOW_TEST_FIXTURE(Create) +{ + LONGBOW_RUN_TEST_CASE(Create, metisConfigurationFile_Create); + LONGBOW_RUN_TEST_CASE(Create, metisConfigurationFile_Create_CantRead); + LONGBOW_RUN_TEST_CASE(Create, metisConfigurationFile_Create_Missing); +} + +LONGBOW_TEST_FIXTURE_SETUP(Create) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Create) +{ + 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; +} + +static void +_writeConfigFile(FILE *fh) +{ + ssize_t nwritten = fprintf(fh, "add listener udp conn0 127.0.0.1 9696\n"); + assertTrue(nwritten > 0, "Bad fprintf"); + fflush(fh); +} + +LONGBOW_TEST_CASE(Create, metisConfigurationFile_Create) +{ + char template[] = "/tmp/test_metis_ConfigurationFile.XXXXXX"; + int fd = mkstemp(template); + assertTrue(fd > -1, "Error creating temp file: (%d) %s", errno, strerror(errno)); + + FILE *fh = fdopen(fd, "w"); + _writeConfigFile(fh); + + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + MetisConfigurationFile *cf = metisConfigurationFile_Create(metis, template); + + assertNotNull(cf, "Should have returned non-null for good configuration file"); + + metisConfigurationFile_Release(&cf); + metisForwarder_Destroy(&metis); + fclose(fh); + unlink(template); +} + +LONGBOW_TEST_CASE(Create, metisConfigurationFile_Create_CantRead) +{ + char template[] = "/tmp/test_metis_ConfigurationFile.XXXXXX"; + int fd = mkstemp(template); + assertTrue(fd > -1, "Error creating temp file: (%d) %s", errno, strerror(errno)); + + chmod(template, 0); + + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + MetisConfigurationFile *cf = metisConfigurationFile_Create(metis, template); + + chmod(template, 0600); + unlink(template); + + uid_t uid = getuid(), euid = geteuid(); + if (uid <= 0 || uid != euid) { + metisConfigurationFile_Release(&cf); + } else { + assertNull(cf, "Should have returned null configuration file for non-readable file"); + } + + metisForwarder_Destroy(&metis); + close(fd); +} + +LONGBOW_TEST_CASE(Create, metisConfigurationFile_Create_Missing) +{ + char template[] = "/tmp/test_metis_ConfigurationFile.ZZZZZZZZZ"; + + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + MetisConfigurationFile *cf = metisConfigurationFile_Create(metis, template); + + assertNull(cf, "Should have returned null configuration file for missing file"); + + metisForwarder_Destroy(&metis); +} + +// ====================================================== + +typedef struct test_data { + MetisForwarder *metis; + char template[1024]; + int fd; + FILE *fh; +} TestData; + +LONGBOW_TEST_FIXTURE(Process) +{ + LONGBOW_RUN_TEST_CASE(Process, metisConfigurationFile_Process_NoErrors); + LONGBOW_RUN_TEST_CASE(Process, metisConfigurationFile_Process_WithErrors); + LONGBOW_RUN_TEST_CASE(Process, metisConfigurationFile_Process_WithComments); + LONGBOW_RUN_TEST_CASE(Process, metisConfigurationFile_Process_Whitespace); +} + +LONGBOW_TEST_FIXTURE_SETUP(Process) +{ + TestData *data = parcMemory_Allocate(sizeof(TestData)); + data->metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(data->metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + metisLogger_SetLogLevel(metisForwarder_GetLogger(data->metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + sprintf(data->template, "/tmp/test_metis_ConfigurationFile.XXXXXX"); + + data->fd = mkstemp(data->template); + assertTrue(data->fd > -1, "Error creating temp file: (%d) %s", errno, strerror(errno)); + + data->fh = fdopen(data->fd, "w"); + + longBowTestCase_SetClipBoardData(testCase, data); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Process) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + fclose(data->fh); + unlink(data->template); + metisForwarder_Destroy(&data->metis); + parcMemory_Deallocate((void **) &data); + 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(Process, metisConfigurationFile_Process_NoErrors) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _writeConfigFile(data->fh); + + MetisConfigurationFile *cf = metisConfigurationFile_Create(data->metis, data->template); + + bool success = metisConfigurationFile_Process(cf); + assertTrue(success, "Failed to execute configuration file."); + assertTrue(cf->linesRead == 1, "Should have read 1 line, got %zu", cf->linesRead); + + metisConfigurationFile_Release(&cf); +} + +LONGBOW_TEST_CASE(Process, metisConfigurationFile_Process_WithErrors) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _writeConfigFile(data->fh); + + ssize_t nwritten = fprintf(data->fh, "blah blah\n"); + assertTrue(nwritten > 0, "Bad write"); + + // this should not be executed + nwritten = fprintf(data->fh, "add listener conn3 tcp 127.0.0.1 9696\n"); + assertTrue(nwritten > 0, "Bad write"); + + fflush(data->fh); + + MetisConfigurationFile *cf = metisConfigurationFile_Create(data->metis, data->template); + + bool success = metisConfigurationFile_Process(cf); + assertFalse(success, "Should have failed to execute configuration file.") { + int res; + res = system("netstat -an -p tcp"); + assertTrue(res != -1, "Error on system call"); + res = system("ps -el"); + assertTrue(res != -1, "Error on system call"); + } + assertTrue(cf->linesRead == 2, "Should have read 2 lines, got %zu", cf->linesRead); + + metisConfigurationFile_Release(&cf); +} + +LONGBOW_TEST_CASE(Process, metisConfigurationFile_Process_WithComments) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _writeConfigFile(data->fh); + + ssize_t nwritten = fprintf(data->fh, "# ignore this\n"); + assertTrue(nwritten > 0, "Bad write"); + + nwritten = fprintf(data->fh, "add listener tcp conn3 127.0.0.1 9696\n"); + assertTrue(nwritten > 0, "Bad write"); + + fflush(data->fh); + + MetisConfigurationFile *cf = metisConfigurationFile_Create(data->metis, data->template); + + bool success = metisConfigurationFile_Process(cf); + assertTrue(success, "Should have failed to execute configuration file.") { + int res; + res = system("netstat -an -p tcp"); + assertTrue(res != -1, "Error on system call"); + res = system("ps -el"); + assertTrue(res != -1, "Error on system call"); + } + assertTrue(cf->linesRead == 3, "Should have read 3 lines, got %zu", cf->linesRead); + + metisConfigurationFile_Release(&cf); +} + +LONGBOW_TEST_CASE(Process, metisConfigurationFile_Process_Whitespace) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _writeConfigFile(data->fh); + + ssize_t nwritten = fprintf(data->fh, "add listener tcp conn3 127.0.0.1 9696\n"); + assertTrue(nwritten > 0, "Bad write"); + + fflush(data->fh); + + MetisConfigurationFile *cf = metisConfigurationFile_Create(data->metis, data->template); + + bool success = metisConfigurationFile_Process(cf); + assertTrue(success, "Should have failed to execute configuration file.") { + int res; + res = system("netstat -an -p tcp"); + assertTrue(res != -1, "Error on system call"); + res = system("ps -el"); + assertTrue(res != -1, "Error on system call"); + } + assertTrue(cf->linesRead == 2, "Should have read 2 lines, got %zu", cf->linesRead); + + metisConfigurationFile_Release(&cf); +} + + +// ============================================================================== + + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _stripLeadingWhitespace); + LONGBOW_RUN_TEST_CASE(Local, _stripTrailingWhitespace); + LONGBOW_RUN_TEST_CASE(Local, _trim); +} + +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; +} + +typedef struct test_vector { + const char *input; + const char *output; + bool sentinel; +} TestVector; + +LONGBOW_TEST_CASE(Local, _stripLeadingWhitespace) +{ + TestVector vectors[] = { + { .input = "", .output = "" }, + { .input = " ", .output = "" }, + { .input = "\t", .output = "" }, + { .input = "a", .output = "a" }, + { .input = "abc", .output = "abc" }, + { .input = " a c ", .output = "a c " }, + { .input = " bc", .output = "bc" }, + { .input = "\tbc", .output = "bc" }, + { .input = " \tbc", .output = "bc" }, + { .input = "\t\tbc ", .output = "bc " }, + { .input = NULL, .output = NULL }, + }; + + for (int i = 0; vectors[i].input != NULL; i++) { + char *copy = parcMemory_StringDuplicate(vectors[i].input, strlen(vectors[i].input)); + char *test = _stripLeadingWhitespace(copy); + assertTrue(strcmp(test, vectors[i].output) == 0, "Bad output index %d. input = '%s' expected = '%s' actual = '%s'", i, vectors[i].input, vectors[i].output, test); + parcMemory_Deallocate((void **) ©); + } +} + +LONGBOW_TEST_CASE(Local, _stripTrailingWhitespace) +{ + TestVector vectors[] = { + { .input = "", .output = "" }, + { .input = " ", .output = "" }, + { .input = "\t", .output = "" }, + { .input = "a", .output = "a" }, + { .input = "abc", .output = "abc" }, + { .input = " a c ", .output = " a c" }, + { .input = "bc ", .output = "bc" }, + { .input = "bc\t", .output = "bc" }, + { .input = "bc \t", .output = "bc" }, + { .input = " bc\t\t", .output = " bc" }, + { .input = NULL, .output = NULL }, + }; + + for (int i = 0; vectors[i].input != NULL; i++) { + char *copy = parcMemory_StringDuplicate(vectors[i].input, strlen(vectors[i].input)); + char *test = _stripTrailingWhitespace(copy); + assertTrue(strcmp(test, vectors[i].output) == 0, "Bad output index %d. input = '%s' expected = '%s' actual = '%s'", i, vectors[i].input, vectors[i].output, test); + parcMemory_Deallocate((void **) ©); + } +} + +LONGBOW_TEST_CASE(Local, _trim) +{ + TestVector vectors[] = { + { .input = "", .output = "" }, + { .input = " ", .output = "" }, + { .input = "\t", .output = "" }, + { .input = "a", .output = "a" }, + { .input = "abc", .output = "abc" }, + { .input = " a c ", .output = "a c" }, + { .input = "bc ", .output = "bc" }, + { .input = "bc\t", .output = "bc" }, + { .input = "bc \t", .output = "bc" }, + { .input = " bc\t\t", .output = "bc" }, + { .input = NULL, .output = NULL }, + }; + + for (int i = 0; vectors[i].input != NULL; i++) { + char *copy = parcMemory_StringDuplicate(vectors[i].input, strlen(vectors[i].input)); + char *test = _trim(copy); + assertTrue(strcmp(test, vectors[i].output) == 0, "Bad output index %d. input = '%s' expected = '%s' actual = '%s'", i, vectors[i].input, vectors[i].output, test); + parcMemory_Deallocate((void **) ©); + } +} + +// ====================================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_ConfigurationFile); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metis_ConfigurationListeners.c b/metis/ccnx/forwarder/metis/config/test/test_metis_ConfigurationListeners.c new file mode 100644 index 00000000..8ac2c82d --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metis_ConfigurationListeners.c @@ -0,0 +1,644 @@ +/* + * 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. + */ + +/* + * Some of these tests might not execute on certain systems, as they + * depend on having INET and INET6 addresses available. If you system + * does not have one or both of those, the corresponding tests will not + * execute. + */ + +// We need to specifically include the Ethernet mockup and set the proper define so +// we do not need an actual Ethernet listener + +#define METIS_MOCK_ETHERNET 1 +#include "../../io/test/testrig_GenericEther.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_ConfigurationListeners.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +#include <signal.h> +#include <net/ethernet.h> + +#define TEST_PORT 9697 +static const CPIAddress * +getFirstAddressOfType(CPIInterfaceSet *set, CPIAddressType type) +{ + for (int i = 0; i < cpiInterfaceSet_Length(set); i++) { + CPIInterface *iface = cpiInterfaceSet_GetByOrdinalIndex(set, i); + const CPIAddressList *list = cpiInterface_GetAddresses(iface); + for (int j = 0; j < cpiAddressList_Length(list); j++) { + const CPIAddress *address = cpiAddressList_GetItem(list, j); + if (cpiAddress_GetType(address) == type) { + return address; + } + } + } + return NULL; +} + +struct sigaction save_sigchld; +struct sigaction save_sigpipe; + +static void +blockSigChild() +{ + struct sigaction ignore_action; + ignore_action.sa_handler = SIG_IGN; + sigemptyset(&ignore_action.sa_mask); + ignore_action.sa_flags = 0; + + sigaction(SIGCHLD, NULL, &save_sigchld); + sigaction(SIGPIPE, NULL, &save_sigpipe); + + sigaction(SIGCHLD, &ignore_action, NULL); + sigaction(SIGPIPE, &ignore_action, NULL); +} + +static void +unblockSigChild() +{ + sigaction(SIGCHLD, &save_sigchld, NULL); + sigaction(SIGPIPE, &save_sigpipe, NULL); +} + +static bool +verifyInNetstat(const char *addressString, int port) +{ + // now verify that we are listening + // tcp4 0 0 127.0.0.1.49009 *.* LISTEN + + FILE *fp = popen("netstat -an", "r"); + assertNotNull(fp, "Got null opening netstat for reading"); + + char buffer[4][1024]; + + sprintf(buffer[0], "%s.%d", addressString, port); + sprintf(buffer[1], "%s:%d", addressString, port); + sprintf(buffer[2], "%s%%lo0.%d", addressString, port); + sprintf(buffer[3], "%s%%lo0:%d", addressString, port); + + char str[1035]; + bool found = false; + while (!found && (fgets(str, sizeof(str) - 1, fp) != NULL)) { + for (int i = 0; i < 4; i++) { + if (strstr(str, buffer[i]) != NULL) { + found = true; + } + } + } + + blockSigChild(); + pclose(fp); + unblockSigChild(); + + return found; +} + +// returns a strdup() of the interface name, use free(3) +static char * +_pickInterfaceName(MetisForwarder *metis) +{ + char *ifname = NULL; + + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + size_t length = cpiInterfaceSet_Length(set); + assertTrue(length > 0, "metisSystem_Interfaces returned no interfaces"); + + for (size_t i = 0; i < length; i++) { + CPIInterface *iface = cpiInterfaceSet_GetByOrdinalIndex(set, i); + const CPIAddressList *addressList = cpiInterface_GetAddresses(iface); + + size_t length = cpiAddressList_Length(addressList); + for (size_t i = 0; i < length && !ifname; i++) { + const CPIAddress *a = cpiAddressList_GetItem(addressList, i); + if (cpiAddress_GetType(a) == cpiAddressType_LINK) { + ifname = strdup(cpiInterface_GetName(iface)); + } + } + } + + cpiInterfaceSet_Destroy(&set); + return ifname; +} + +// ========================================================================= + +LONGBOW_TEST_RUNNER(metis_Configuration) +{ + 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_Configuration) +{ + 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_Configuration) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ============================================================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisConfigurationListeners_SetupAll); + LONGBOW_RUN_TEST_CASE(Global, metisConfigurationListeners_Add_Ether); + LONGBOW_RUN_TEST_CASE(Global, metisConfigurationListeners_Add_IP_UDP4); + LONGBOW_RUN_TEST_CASE(Global, metisConfigurationListeners_Add_IP_UDP6); + LONGBOW_RUN_TEST_CASE(Global, metisConfigurationListeners_Add_IP_TCP4); + LONGBOW_RUN_TEST_CASE(Global, metisConfigurationListeners_Add_IP_TCP6); + LONGBOW_RUN_TEST_CASE(Global, metisConfigurationListeners_Remove); +} + +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, metisConfigurationListeners_SetupAll) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + metisConfigurationListeners_SetupAll(metisForwarder_GetConfiguration(metis), TEST_PORT, NULL); + + MetisListenerSet *set = metisForwarder_GetListenerSet(metis); + size_t len = metisListenerSet_Length(set); + assertTrue(len > 0, "Bad listener set size, expected positive, got %zu", len); + + metisForwarder_Destroy(&metis); +} + +LONGBOW_TEST_CASE(Global, metisConfigurationListeners_Add_Ether) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 77; + + // create the listener + char *ifname = _pickInterfaceName(metis); + CPIListener *cpiListener = cpiListener_CreateEther(ifname, 0x0801, "fake0"); + CCNxControl *control = cpiListener_CreateAddMessage(cpiListener); + bool listenerOk = metisConfigurationListeners_Add(metisForwarder_GetConfiguration(metis), control, mockup_id); + assertTrue(listenerOk, "Failed to setup ether listener."); + free(ifname); + + ccnxControl_Release(&control); + cpiListener_Release(&cpiListener); + + MetisListenerSet *set = metisForwarder_GetListenerSet(metis); + size_t len = metisListenerSet_Length(set); + assertTrue(len == 1, "Bad listener set size, expected 1, got %zu", len); + + metisForwarder_Destroy(&metis); +} + +LONGBOW_TEST_CASE(Global, metisConfigurationListeners_Add_IP_UDP4) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 77; + + // create the listener + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(TEST_PORT); + int result = inet_aton("127.0.0.1", &sin.sin_addr); + assertTrue(result == 1, "failed inet_aton: (%d) %s", errno, strerror(errno)); + + CPIAddress *address = cpiAddress_CreateFromInet(&sin); + CPIListener *listener = cpiListener_CreateIP(IPTUN_UDP, address, "conn1"); + cpiAddress_Destroy(&address); + + + CCNxControl *control = cpiListener_CreateAddMessage(listener); + bool listenerOk = metisConfigurationListeners_Add(metisForwarder_GetConfiguration(metis), control, mockup_id); + assertTrue(listenerOk, "Failed to setup ether listener.") { + int res; + res = system("netstat -an -p udp"); + assertTrue(res != -1, "Error on system call"); + res = system("ps -el"); + assertTrue(res != -1, "Error on system call"); + } + + ccnxControl_Release(&control); + cpiListener_Release(&listener); + + MetisListenerSet *set = metisForwarder_GetListenerSet(metis); + size_t len = metisListenerSet_Length(set); + assertTrue(len == 1, "Bad listener set size, expected 1, got %zu", len); + + metisForwarder_Destroy(&metis); +} + +LONGBOW_TEST_CASE(Global, metisConfigurationListeners_Add_IP_UDP6) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 77; + + // create the listener + struct sockaddr_in6 sin6; + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(TEST_PORT); + int result = inet_pton(AF_INET6, "::1", &(sin6.sin6_addr)); + if (result == 1) { + CPIAddress *address = cpiAddress_CreateFromInet6(&sin6); + CPIListener *listener = cpiListener_CreateIP(IPTUN_UDP, address, "conn1"); + cpiAddress_Destroy(&address); + + + CCNxControl *control = cpiListener_CreateAddMessage(listener); + bool listenerOk = metisConfigurationListeners_Add(metisForwarder_GetConfiguration(metis), control, mockup_id); + assertTrue(listenerOk, "Failed to setup ether listener.") { + int res; + res = system("netstat -an -p udp"); + assertTrue(res != -1, "Error on system call"); + res = system("ps -el"); + assertTrue(res != -1, "Error on system call"); + } + + ccnxControl_Release(&control); + cpiListener_Release(&listener); + + MetisListenerSet *set = metisForwarder_GetListenerSet(metis); + size_t len = metisListenerSet_Length(set); + assertTrue(len == 1, "Bad listener set size, expected 1, got %zu", len); + metisForwarder_Destroy(&metis); + } else { + metisForwarder_Destroy(&metis); + testSkip("IPv6 not supported"); + } +} + +LONGBOW_TEST_CASE(Global, metisConfigurationListeners_Add_IP_TCP4) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 77; + + // create the listener + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(TEST_PORT); + int result = inet_aton("127.0.0.1", &sin.sin_addr); + assertTrue(result == 1, "failed inet_aton: (%d) %s", errno, strerror(errno)); + + CPIAddress *address = cpiAddress_CreateFromInet(&sin); + CPIListener *listener = cpiListener_CreateIP(IPTUN_TCP, address, "conn1"); + cpiAddress_Destroy(&address); + + + CCNxControl *control = cpiListener_CreateAddMessage(listener); + bool listenerOk = metisConfigurationListeners_Add(metisForwarder_GetConfiguration(metis), control, mockup_id); + assertTrue(listenerOk, "Failed to setup ether listener.") { + int res; + res = system("netstat -an -p tcp"); + assertTrue(res != -1, "Error on system call"); + res = system("ps -el"); + assertTrue(res != -1, "Error on system call"); + } + + ccnxControl_Release(&control); + cpiListener_Release(&listener); + + MetisListenerSet *set = metisForwarder_GetListenerSet(metis); + size_t len = metisListenerSet_Length(set); + assertTrue(len == 1, "Bad listener set size, expected 1, got %zu", len); + + metisForwarder_Destroy(&metis); +} + +LONGBOW_TEST_CASE(Global, metisConfigurationListeners_Add_IP_TCP6) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 77; + + // create the listener + struct sockaddr_in6 sin6; + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(TEST_PORT); + int result = inet_pton(AF_INET6, "::1", &(sin6.sin6_addr)); + if (result == 1) { + CPIAddress *address = cpiAddress_CreateFromInet6(&sin6); + CPIListener *listener = cpiListener_CreateIP(IPTUN_TCP, address, "conn1"); + cpiAddress_Destroy(&address); + + + CCNxControl *control = cpiListener_CreateAddMessage(listener); + bool listenerOk = metisConfigurationListeners_Add(metisForwarder_GetConfiguration(metis), control, mockup_id); + assertTrue(listenerOk, "Failed to setup ether listener.") { + int res; + res = system("netstat -an -p tcp"); + assertTrue(res != -1, "Error on system call"); + res = system("ps -el"); + assertTrue(res != -1, "Error on system call"); + } + + ccnxControl_Release(&control); + cpiListener_Release(&listener); + + MetisListenerSet *set = metisForwarder_GetListenerSet(metis); + size_t len = metisListenerSet_Length(set); + assertTrue(len == 1, "Bad listener set size, expected 1, got %zu", len); + metisForwarder_Destroy(&metis); + } else { + metisForwarder_Destroy(&metis); + testSkip("IPv6 not supported"); + } +} + +LONGBOW_TEST_CASE(Global, metisConfigurationListeners_Remove) +{ + testUnimplemented("This test is unimplemented"); +} + +// ============================================================================== + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, setupEthernetListenerOnLink); + LONGBOW_RUN_TEST_CASE(Local, setupEthernetListenerOnLink_SecondEthertype); + LONGBOW_RUN_TEST_CASE(Local, setupIPMulticastListenerOnInet); + LONGBOW_RUN_TEST_CASE(Local, setupListenersOnAddress); + LONGBOW_RUN_TEST_CASE(Local, setupListenersOnInet); + LONGBOW_RUN_TEST_CASE(Local, setupListenersOnInet6); + LONGBOW_RUN_TEST_CASE(Local, setupListenersOnLink); + LONGBOW_RUN_TEST_CASE(Local, setupLocalListener); + LONGBOW_RUN_TEST_CASE(Local, setupTcpListenerOnInet); + LONGBOW_RUN_TEST_CASE(Local, setupTcpListenerOnInet6); + LONGBOW_RUN_TEST_CASE(Local, setupUdpListenerOnInet); + LONGBOW_RUN_TEST_CASE(Local, setupUdpListenerOnInet6); +} + +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, setupEthernetListenerOnLink) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + uint8_t addr[ETHER_ADDR_LEN] = { 1, 2, 3, 4, 5, 6 }; + CPIAddress *localAddress = cpiAddress_CreateFromLink(addr, ETHER_ADDR_LEN); + + char *ifname = _pickInterfaceName(metis); + MetisListenerOps *listenerops = _setupEthernetListenerOnLink(metis, localAddress, ifname, 0x0801); + assertNotNull(listenerops, "Got null result from _setupEthernetListenerOnLink"); + + free(ifname); + cpiAddress_Destroy(&localAddress); + metisForwarder_Destroy(&metis); +} + +/* + * The current system does not allow multiple ether listeners on a single interface. + * even if they are different ethertypes + */ +LONGBOW_TEST_CASE(Local, setupEthernetListenerOnLink_SecondEthertype) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + uint8_t addr[ETHER_ADDR_LEN] = { 1, 2, 3, 4, 5, 6 }; + CPIAddress *localAddress = cpiAddress_CreateFromLink(addr, ETHER_ADDR_LEN); + + char *ifname = _pickInterfaceName(metis); + MetisListenerOps *listenerops = _setupEthernetListenerOnLink(metis, localAddress, ifname, 0x0801); + assertNotNull(listenerops, "Got null result from _setupEthernetListenerOnLink"); + + // now try to add again with different ethertype + MetisListenerOps *second = _setupEthernetListenerOnLink(metis, localAddress, ifname, 0x0802); + assertNull(second, "Should have gotten null for second listener"); + + free(ifname); + cpiAddress_Destroy(&localAddress); + metisForwarder_Destroy(&metis); +} + + +LONGBOW_TEST_CASE(Local, setupIPMulticastListenerOnInet) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, setupListenersOnAddress) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, setupListenersOnInet) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, setupListenersOnInet6) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, setupListenersOnLink) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, setupLocalListener) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, setupTcpListenerOnInet) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + const CPIAddress *address = getFirstAddressOfType(set, cpiAddressType_INET); + if (address != NULL) { + char valueToFind[1024]; + struct sockaddr_in sin; + cpiAddress_GetInet(address, &sin); + _setupTcpListenerOnInet(metis, address, PORT_NUMBER); + bool found = verifyInNetstat(inet_ntoa(sin.sin_addr), PORT_NUMBER); + if (!found) { + // extra diagnostics + int ret = system("netstat -an -p tcp"); + assertTrue(ret > -1, "Error on system call"); + } + assertTrue(found, "Did not find value %s in netstat", valueToFind); + } + + cpiInterfaceSet_Destroy(&set); + metisForwarder_Destroy(&metis); + + if (address == NULL) { + testSkip("No network interfaces of type INET found"); + } +} + +LONGBOW_TEST_CASE(Local, setupTcpListenerOnInet6) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + const CPIAddress *address = getFirstAddressOfType(set, cpiAddressType_INET6); + if (address != NULL) { + char valueToFind[1024]; + char inet6str[INET6_ADDRSTRLEN]; + struct sockaddr_in6 sin6; + cpiAddress_GetInet6(address, &sin6); + inet_ntop(AF_INET6, &sin6.sin6_addr, inet6str, INET6_ADDRSTRLEN); + _setupTcpListenerOnInet6(metis, address, PORT_NUMBER); + bool found = verifyInNetstat(inet6str, PORT_NUMBER); + if (!found) { + // extra diagnostics + int ret = system("netstat -an -p tcp"); + assertTrue(ret > -1, "Error on system call"); + } + assertTrue(found, "Did not find value %s in netstat", valueToFind); + } + + cpiInterfaceSet_Destroy(&set); + metisForwarder_Destroy(&metis); + + if (address == NULL) { + testSkip("No network interfaces of type INET found"); + } +} + +LONGBOW_TEST_CASE(Local, setupUdpListenerOnInet) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + const CPIAddress *address = getFirstAddressOfType(set, cpiAddressType_INET); + if (address != NULL) { + char valueToFind[1024]; + struct sockaddr_in sin; + cpiAddress_GetInet(address, &sin); + _setupUdpListenerOnInet(metis, address, PORT_NUMBER); + bool found = verifyInNetstat(inet_ntoa(sin.sin_addr), PORT_NUMBER); + if (!found) { + // extra diagnostics + int ret = system("netstat -an -p udp"); + assertTrue(ret > -1, "Error on system call"); + } + assertTrue(found, "Did not find value %s in netstat", valueToFind); + } + + cpiInterfaceSet_Destroy(&set); + metisForwarder_Destroy(&metis); + + if (address == NULL) { + testSkip("No network interfaces of type INET found"); + } +} + +LONGBOW_TEST_CASE(Local, setupUdpListenerOnInet6) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + const CPIAddress *address = getFirstAddressOfType(set, cpiAddressType_INET6); + if (address != NULL) { + char valueToFind[1024]; + char inet6str[INET6_ADDRSTRLEN]; + struct sockaddr_in6 sin6; + cpiAddress_GetInet6(address, &sin6); + inet_ntop(AF_INET6, &sin6.sin6_addr, inet6str, INET6_ADDRSTRLEN); + _setupUdpListenerOnInet6(metis, address, PORT_NUMBER); + bool found = verifyInNetstat(inet6str, PORT_NUMBER); + if (!found) { + // extra diagnostics + int ret = system("netstat -an -p udp"); + assertTrue(ret > -1, "Error on system call"); + } + assertTrue(found, "Did not find value %s in netstat", valueToFind); + } + + cpiInterfaceSet_Destroy(&set); + metisForwarder_Destroy(&metis); + + if (address == NULL) { + testSkip("No network interfaces of type INET found"); + } +} + + +// ====================================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_Configuration); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metis_ControlState.c b/metis/ccnx/forwarder/metis/config/test/test_metis_ControlState.c new file mode 100644 index 00000000..088118d4 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metis_ControlState.c @@ -0,0 +1,253 @@ +/* + * 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_ControlState.c" + +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +LONGBOW_TEST_RUNNER(metis_Control) +{ + // 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_Control) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_Control) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ================================================== + +static CCNxMetaMessage *_testWriteMessage = NULL; +static CCNxMetaMessage *_testReadMessage = NULL; + +/** + * For testing purposes writes a message to a local buffer and reads response from local buffer + * + * _testWriteMessage will be an allocated reference to what is written + * _testReadMessage will be sent back (returend). You must put an allocated message there + * before calling this test function. + */ +static CCNxMetaMessage * +_testWriteRead(void *userdata, CCNxMetaMessage *msg) +{ + _testWriteMessage = ccnxMetaMessage_Acquire(msg); + return ccnxMetaMessage_Acquire(_testReadMessage); +} + +static unsigned _testCommandExecuteCount = 0; + +static MetisCommandReturn +_testCommand(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + _testCommandExecuteCount++; + return MetisCommandReturn_Success; +} + +static MetisCommandOps _testCommandOps = { + .command = "test", // empty string for root + .init = NULL, + .execute = _testCommand +}; + +// ================================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlState_Create); + LONGBOW_RUN_TEST_CASE(Global, metisControlState_DispatchCommand); + LONGBOW_RUN_TEST_CASE(Global, metisControlState_GetDebug); + LONGBOW_RUN_TEST_CASE(Global, metisControlState_Interactive); + LONGBOW_RUN_TEST_CASE(Global, metisControlState_RegisterCommand); + LONGBOW_RUN_TEST_CASE(Global, metisControlState_SetDebug); + LONGBOW_RUN_TEST_CASE(Global, metisControlState_WriteRead); + LONGBOW_RUN_TEST_CASE(Global, _metisControlState_ParseStringIntoTokens); +} + +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, metisControlState_Create) +{ + char hello[] = "hello"; + MetisControlState *state = metisControlState_Create(hello, _testWriteRead); + metisControlState_Destroy(&state); +} + +LONGBOW_TEST_CASE(Global, metisControlState_DispatchCommand) +{ + char hello[] = "hello"; + MetisControlState *state = metisControlState_Create(hello, _testWriteRead); + + metisControlState_RegisterCommand(state, &_testCommandOps); + + const char *argv[] = { "test", "foobar" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 2, (void **) &argv[0]); + + _testCommandExecuteCount = 0; + + metisControlState_DispatchCommand(state, args); + + assertTrue(_testCommandExecuteCount == 1, "Incorrect execution count, expected 1 got %u", _testCommandExecuteCount); + parcList_Release(&args); + metisControlState_Destroy(&state); +} + +LONGBOW_TEST_CASE(Global, metisControlState_GetDebug) +{ + char hello[] = "hello"; + MetisControlState *state = metisControlState_Create(hello, _testWriteRead); + + bool test = metisControlState_GetDebug(state); + assertTrue(test == state->debugFlag, "debug flag in unexpected state"); + + metisControlState_Destroy(&state); +} + +LONGBOW_TEST_CASE(Global, metisControlState_Interactive) +{ + // this reads commands from stdin. not sure how to test this. + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Global, metisControlState_RegisterCommand) +{ + char hello[] = "hello"; + MetisControlState *state = metisControlState_Create(hello, _testWriteRead); + + metisControlState_RegisterCommand(state, &_testCommandOps); + + bool match = metisCommandParser_ContainsCommand(state->parser, "test"); + assertTrue(match, "Command not found in parser"); + + metisControlState_Destroy(&state); +} + +LONGBOW_TEST_CASE(Global, metisControlState_SetDebug) +{ + char hello[] = "hello"; + MetisControlState *state = metisControlState_Create(hello, _testWriteRead); + + assertFalse(state->debugFlag, "debug flag in unexpected true state"); + metisControlState_SetDebug(state, true); + assertTrue(state->debugFlag, "debug flag in unexpected false state"); + + metisControlState_Destroy(&state); +} + +LONGBOW_TEST_CASE(Global, metisControlState_WriteRead) +{ + char hello[] = "hello"; + MetisControlState *state = metisControlState_Create(hello, _testWriteRead); + + CCNxName *appleName = ccnxName_CreateFromCString("lci:/apple"); + CCNxInterest *appleInterest = ccnxInterest_CreateSimple(appleName); + _testReadMessage = ccnxMetaMessage_CreateFromInterest(appleInterest); + ccnxInterest_Release(&appleInterest); + ccnxName_Release(&appleName); + + CCNxName *pieName = ccnxName_CreateFromCString("lci:/pie"); + CCNxInterest *pieInterest = ccnxInterest_CreateSimple(pieName); + CCNxMetaMessage *writeMessage = ccnxMetaMessage_CreateFromInterest(pieInterest);; + ccnxInterest_Release(&pieInterest); + ccnxName_Release(&pieName); + + CCNxMetaMessage *test = metisControlState_WriteRead(state, writeMessage); + + assertTrue(_testWriteMessage == writeMessage, "write message incorrect, expected %p got %p", (void *) writeMessage, (void *) _testWriteMessage); + assertTrue(_testReadMessage == test, "read message incorrect, expected %p got %p", (void *) _testReadMessage, (void *) test); + + ccnxMetaMessage_Release(&test); + ccnxMetaMessage_Release(&writeMessage); + + ccnxMetaMessage_Release(&_testReadMessage); + ccnxMetaMessage_Release(&_testWriteMessage); + + metisControlState_Destroy(&state); +} + +LONGBOW_TEST_CASE(Global, _metisControlState_ParseStringIntoTokens) +{ + const char *string = "the quick brown fox"; + + const char *argv[] = { "the", "quick", "brown", "fox" }; + PARCList *truth = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(truth, 4, (void **) &argv[0]); + + PARCList *test = _metisControlState_ParseStringIntoTokens(string); + + assertTrue(parcList_Size(test) == parcList_Size(truth), "list wrong size, expected %zu got %zu", parcList_Size(truth), parcList_Size(test)); + + for (int i = 0; i < parcList_Size(truth); i++) { + const char *testString = parcList_GetAtIndex(test, i); + const char *truthString = parcList_GetAtIndex(truth, i); + assertTrue(strcmp(testString, truthString) == 0, "index %d not equal, expected '%s' got '%s'", i, truthString, testString); + } + + parcList_Release(&test); + parcList_Release(&truth); +} + +// ======================================================================== + +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_Control); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metis_SymbolicNameTable.c b/metis/ccnx/forwarder/metis/config/test/test_metis_SymbolicNameTable.c new file mode 100644 index 00000000..811f5b0f --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metis_SymbolicNameTable.c @@ -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. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_SymbolicNameTable.c" + +#include <stdio.h> +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metis_SymbolicNameTable) +{ + LONGBOW_RUN_TEST_FIXTURE(Global); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_SymbolicNameTable) +{ + 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_SymbolicNameTable) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ============================================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisSymbolicNameTable_Create); + LONGBOW_RUN_TEST_CASE(Global, metisSymbolicNameTable_Exists_True); + LONGBOW_RUN_TEST_CASE(Global, metisSymbolicNameTable_Exists_False); + LONGBOW_RUN_TEST_CASE(Global, metisSymbolicNameTable_Add_Unique); + LONGBOW_RUN_TEST_CASE(Global, metisSymbolicNameTable_Add_Duplicate); + LONGBOW_RUN_TEST_CASE(Global, metisSymbolicNameTable_Get_Exists); + LONGBOW_RUN_TEST_CASE(Global, metisSymbolicNameTable_Get_Missing); +} + +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, metisSymbolicNameTable_Create) +{ + MetisSymbolicNameTable *table = metisSymbolicNameTable_Create(); + assertNotNull(table, "Got null table"); + assertNotNull(table->symbolicNameTable, "Table did not have an inner hash table allocated"); + metisSymbolicNameTable_Destroy(&table); +} + +LONGBOW_TEST_CASE(Global, metisSymbolicNameTable_Exists_True) +{ + MetisSymbolicNameTable *table = metisSymbolicNameTable_Create(); + metisSymbolicNameTable_Add(table, "foo", 3); + bool exists = metisSymbolicNameTable_Exists(table, "foo"); + assertTrue(exists, "Failed to find existing key"); + metisSymbolicNameTable_Destroy(&table); +} + +LONGBOW_TEST_CASE(Global, metisSymbolicNameTable_Exists_False) +{ + MetisSymbolicNameTable *table = metisSymbolicNameTable_Create(); + bool exists = metisSymbolicNameTable_Exists(table, "foo"); + assertFalse(exists, "Found non-existent key!"); + metisSymbolicNameTable_Destroy(&table); +} + +LONGBOW_TEST_CASE(Global, metisSymbolicNameTable_Add_Unique) +{ + MetisSymbolicNameTable *table = metisSymbolicNameTable_Create(); + bool success = metisSymbolicNameTable_Add(table, "foo", 3); + assertTrue(success, "Failed to add a unique key"); + metisSymbolicNameTable_Destroy(&table); +} + +LONGBOW_TEST_CASE(Global, metisSymbolicNameTable_Add_Duplicate) +{ + MetisSymbolicNameTable *table = metisSymbolicNameTable_Create(); + metisSymbolicNameTable_Add(table, "foo", 3); + bool failure = metisSymbolicNameTable_Add(table, "foo", 4); + assertFalse(failure, "Should have failed to add a duplicate key"); + metisSymbolicNameTable_Destroy(&table); +} + +LONGBOW_TEST_CASE(Global, metisSymbolicNameTable_Get_Exists) +{ + MetisSymbolicNameTable *table = metisSymbolicNameTable_Create(); + metisSymbolicNameTable_Add(table, "foo", 3); + unsigned value = metisSymbolicNameTable_Get(table, "foo"); + assertTrue(value == 3, "Wrong value, expected %u got %u", 3, value); + metisSymbolicNameTable_Destroy(&table); +} + +LONGBOW_TEST_CASE(Global, metisSymbolicNameTable_Get_Missing) +{ + MetisSymbolicNameTable *table = metisSymbolicNameTable_Create(); + unsigned value = metisSymbolicNameTable_Get(table, "foo"); + assertTrue(value == UINT32_MAX, "Wrong value, expected %u got %u", UINT32_MAX, value); + metisSymbolicNameTable_Destroy(&table); +} + + +// ============================================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_SymbolicNameTable); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/testrig_MetisControl.c b/metis/ccnx/forwarder/metis/config/test/testrig_MetisControl.c new file mode 100644 index 00000000..f3f18000 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/testrig_MetisControl.c @@ -0,0 +1,191 @@ +/* + * 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. + */ + +/** + * Common operations for the metisControl tests. This C module + * is intended to be #include'd in to each test. + * + */ + +#include <LongBow/unit-test.h> + +#include "../metis_ControlState.c" +#include <parc/algol/parc_SafeMemory.h> +#include <ccnx/forwarder/metis/config/metis_CommandParser.h> +#include <ccnx/api/control/controlPlaneInterface.h> + +typedef struct test_data { + MetisControlState *state; + unsigned writeread_count; + + // If the user specifies this, it will be used as the reply to all test_WriteRead calls + CCNxControl * (*customWriteReadReply)(void *userdata, CCNxMetaMessage * messageToWrite); +} TestData; + +/** + * As part of the testrig, we simply create a CPIAck of the request message. + * We also increment the call count in TestData. + * + * If the user specified a customWriteReadReply function, we will call that to get + * the specific response to send. + */ +static CCNxMetaMessage * +test_WriteRead(void *userdata, CCNxMetaMessage *messageToWrite) +{ + TestData *data = (TestData *) userdata; + data->writeread_count++; + + assertTrue(ccnxMetaMessage_IsControl(messageToWrite), "messageToWrite is not a control message"); + + CCNxControl *response; + CCNxMetaMessage *result; + + if (data->customWriteReadReply == NULL) { + CCNxControl *request = ccnxMetaMessage_GetControl(messageToWrite); + PARCJSON *json = ccnxControl_GetJson(request); + PARCJSON *jsonAck = cpiAcks_CreateAck(json); + + response = ccnxControl_CreateCPIRequest(jsonAck); + result = ccnxMetaMessage_CreateFromControl(response); + + parcJSON_Release(&jsonAck); + ccnxControl_Release(&response); + } else { + response = data->customWriteReadReply(userdata, messageToWrite); + assertTrue(ccnxMetaMessage_IsControl(response), "response is not a control message"); + result = ccnxMetaMessage_CreateFromControl(response); + ccnxControl_Release(&response); + } + + return result; +} + +static void +testrigMetisControl_commonSetup(const LongBowTestCase *testCase) +{ + TestData *data = parcMemory_AllocateAndClear(sizeof(TestData)); + assertNotNull(data, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(TestData)); + memset(data, 0, sizeof(TestData)); + + data->state = metisControlState_Create(data, test_WriteRead); + longBowTestCase_SetClipBoardData(testCase, data); +} + +static void +testrigMetisControl_CommonTeardown(const LongBowTestCase *testCase) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + metisControlState_Destroy(&data->state); + parcMemory_Deallocate((void **) &data); +} + +/** + * Verify that a Command Create operated correctly + * + * We verify the basic properties of what a Create returns. Will assert if a failure. + * + * @param [in] testCase The LongBow test case (used for the clipboard) + * @param [in] create The command create function pointer to test + * @param [in] title The descriptive title to display in case of error + * + * Example: + * @code + * <#example#> + * @endcode + */ +void +testCommandCreate(const LongBowTestCase *testCase, MetisCommandOps * (*create)(MetisControlState * state), const char *title) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = create(data->state); + assertNotNull(ops, "%s: Got null ops", title); + assertNotNull(ops->execute, "%s: Ops execute must not be null", title); + assertNotNull(ops->command, "%s: Ops command must not be null", title); + assertTrue(ops->closure == data->state, "%s: ops closure should be data->state, got wrong pointer", title); + + metisCommandOps_Destroy(&ops); + assertNull(ops, "Ops not nulled by Destroy"); +} + +/** + * Test a Help command's execution. + * + * A Help execution will display text (which we don't test). We make sure there + * is no memory leak and that it returns successfully. We will call the passed create method + * to create the Help command then execute its execute. + * + * @param [in] testCase The LongBow test case (used for the clipboard) + * @param [in] create The command create function pointer to test + * @param [in] title The descriptive title to display in case of error + * @param [in] expected A MetisCommandReturn to use as the expected result + * + * Example: + * @code + * { + * // expectes MetisCommandReturn_Success + * testHelpExecute(testCase, metisControl_Add_Create, __func__, MetisCommandReturn_Success); + * + * // expectes MetisCommandReturn_Exit + * testHelpExecute(testCase, metisControl_Quit_Create, __func__, MetisCommandReturn_Exit); + * } + * @endcode + */ +void +testHelpExecute(const LongBowTestCase *testCase, MetisCommandOps * (*create)(MetisControlState * state), const char *title, MetisCommandReturn expected) +{ + uint32_t beforeMemory = parcMemory_Outstanding(); + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = create(data->state); + MetisCommandReturn result = ops->execute(NULL, ops, NULL); + assertTrue(result == expected, "Wrong return, got %d expected %d", result, expected); + metisCommandOps_Destroy(&ops); + uint32_t afterMemory = parcMemory_Outstanding(); + + assertTrue(beforeMemory == afterMemory, "Memory leak by %d\n", (int) (afterMemory - beforeMemory)); +} + +/** + * Verify that a list of commands is added by the Init function + * + * <#Paragraphs Of Explanation#> + * + * @param [in] testCase The LongBow test case (used for the clipboard) + * @param [in] create We will create one of these and call it's init() function + * @param [in] title The descriptive title to display in case of error + * @param [in] commandList Null terminated list of commands + * + * Example: + * @code + * <#example#> + * @endcode + */ +void +testInit(const LongBowTestCase *testCase, MetisCommandOps * (*create)(MetisControlState * state), const char *title, const char **commandList) +{ + // this will register 8 commands, so check they exist + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = create(data->state); + assertNotNull(ops, "%s got null ops from the create function", title); + assertNotNull(ops->init, "%s got null ops->init from the create function", title); + + ops->init(data->state->parser, ops); + + for (int i = 0; commandList[i] != NULL; i++) { + bool success = metisCommandParser_ContainsCommand(data->state->parser, commandList[i]); + assertTrue(success, "%s: Missing: %s", title, commandList[i]); + } + + metisCommandOps_Destroy(&ops); +} diff --git a/metis/ccnx/forwarder/metis/content_store/metis_ContentStoreEntry.c b/metis/ccnx/forwarder/metis/content_store/metis_ContentStoreEntry.c new file mode 100644 index 00000000..95fee23e --- /dev/null +++ b/metis/ccnx/forwarder/metis/content_store/metis_ContentStoreEntry.c @@ -0,0 +1,192 @@ +/* + * 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/content_store/metis_ContentStoreEntry.h> + +#include <LongBow/runtime.h> + +const uint64_t metisContentStoreEntry_MaxExpiryTime = UINT64_MAX; +const uint64_t metisContentStoreEntry_MaxRecommendedCacheTime = UINT64_MAX; + +struct metis_contentstore_entry { + MetisMessage *message; + MetisLruListEntry *lruEntry; + unsigned refcount; + + bool hasRecommendedCacheTimeTicks; + uint64_t recommendedCacheTimeTicks; + + bool hasExpiryTimeTicks; + uint64_t expiryTimeTicks; +}; + +MetisContentStoreEntry * +metisContentStoreEntry_Create(MetisMessage *contentMessage, MetisLruList *lruList) +{ + assertNotNull(contentMessage, "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(contentMessage); + entry->refcount = 1; + if (lruList != NULL) { + entry->lruEntry = metisLruList_NewHeadEntry(lruList, entry); + } + + entry->hasExpiryTimeTicks = metisMessage_HasExpiryTime(contentMessage); + entry->hasRecommendedCacheTimeTicks = metisMessage_HasRecommendedCacheTime(contentMessage); + + if (entry->hasExpiryTimeTicks) { + entry->expiryTimeTicks = metisMessage_GetExpiryTimeTicks(contentMessage); + } + + if (entry->hasRecommendedCacheTimeTicks) { + entry->recommendedCacheTimeTicks = metisMessage_GetRecommendedCacheTimeTicks(contentMessage); + } + + return entry; +} + +MetisContentStoreEntry * +metisContentStoreEntry_Acquire(const MetisContentStoreEntry *original) +{ + assertNotNull(original, "Parameter must be non-null"); + ((MetisContentStoreEntry *) original)->refcount++; // cast to break the const. + return (MetisContentStoreEntry *) 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) { + if (entry->lruEntry) { + metisLruList_EntryDestroy(&entry->lruEntry); + } + metisMessage_Release(&entry->message); + parcMemory_Deallocate((void **) &entry); + } + *entryPtr = NULL; +} + +MetisMessage * +metisContentStoreEntry_GetMessage(const MetisContentStoreEntry *storeEntry) +{ + assertNotNull(storeEntry, "Parameter must be non-null"); + return storeEntry->message; +} + +bool +metisContentStoreEntry_HasExpiryTimeTicks(const MetisContentStoreEntry *storeEntry) +{ + assertNotNull(storeEntry, "Parameter must be non-null"); + return storeEntry->hasExpiryTimeTicks; +} + +uint64_t +metisContentStoreEntry_GetExpiryTimeTicks(const MetisContentStoreEntry *storeEntry) +{ + assertNotNull(storeEntry, "Parameter must be non-null"); + assertTrue(storeEntry->hasExpiryTimeTicks, + "storeEntry has no ExpiryTimeTicks. Did you call metisContentStoreEntry_HasExpiryTimeTicks() first?"); + return storeEntry->expiryTimeTicks; +} + +bool +metisContentStoreEntry_HasRecommendedCacheTimeTicks(const MetisContentStoreEntry *storeEntry) +{ + assertNotNull(storeEntry, "Parameter must be non-null"); + return storeEntry->hasRecommendedCacheTimeTicks; +} + +uint64_t +metisContentStoreEntry_GetRecommendedCacheTimeTicks(const MetisContentStoreEntry *storeEntry) +{ + assertNotNull(storeEntry, "Parameter must be non-null"); + assertTrue(storeEntry->hasRecommendedCacheTimeTicks, + "storeEntry has no RecommendedCacheTimeTicks. Did you call metisContentStoreEntry_HasRecommendedCacheTimeTicks() first?"); + return storeEntry->recommendedCacheTimeTicks; +} + +int +metisContentStoreEntry_CompareRecommendedCacheTime(const MetisContentStoreEntry *value1, const MetisContentStoreEntry *value2) +{ + // A signum comparison. negative if key 1 is smaller, 0 if key1 == key2, greater than 0 if key1 is bigger. + + MetisContentStoreEntry *v1 = (MetisContentStoreEntry *) value1; + MetisContentStoreEntry *v2 = (MetisContentStoreEntry *) value2; + + if (v1->recommendedCacheTimeTicks < v2->recommendedCacheTimeTicks) { + return -1; + } else if (v1->recommendedCacheTimeTicks > v2->recommendedCacheTimeTicks) { + return +1; + } else { + // At this point, the times are the same. Use the address of the MetisMessage as the decider. + // This allows us to store multiple MetisMessages with the same expiry/cache time. + if (v1->message < v2->message) { + return -1; + } else if (v1->message > v2->message) { + return +1; + } + } + + return 0; // The same MetisMessage has been encountered. +} + +int +metisContentStoreEntry_CompareExpiryTime(const MetisContentStoreEntry *value1, const MetisContentStoreEntry *value2) +{ + // A signum comparison. negative if key 1 is smaller, 0 if key1 == key2, greater than 0 if key1 is bigger. + + MetisContentStoreEntry *v1 = (MetisContentStoreEntry *) value1; + MetisContentStoreEntry *v2 = (MetisContentStoreEntry *) value2; + + if (v1->expiryTimeTicks < v2->expiryTimeTicks) { + return -1; + } else if (v1->expiryTimeTicks > v2->expiryTimeTicks) { + return +1; + } else { + // At this point, the times are the same. Use the address of the MetisMessage as the decider. + // This allows us to store multiple MetisMessages with the same expiry/cache time. + if (v1->message < v2->message) { + return -1; + } else if (v1->message > v2->message) { + return +1; + } + } + + return 0; // The same MetisMessage has been encountered. +} + +void +metisContentStoreEntry_MoveToHead(MetisContentStoreEntry *storeEntry) +{ + assertNotNull(storeEntry, "Parameter must be non-null"); + assertNotNull(storeEntry->lruEntry, "MetisContentStoreEntry is not attached to an LRUList"); + if (storeEntry->lruEntry) { + metisLruList_EntryMoveToHead(storeEntry->lruEntry); + } +} diff --git a/metis/ccnx/forwarder/metis/content_store/metis_ContentStoreEntry.h b/metis/ccnx/forwarder/metis/content_store/metis_ContentStoreEntry.h new file mode 100644 index 00000000..bf8cd9f8 --- /dev/null +++ b/metis/ccnx/forwarder/metis/content_store/metis_ContentStoreEntry.h @@ -0,0 +1,213 @@ +/* + * 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/content_store/metis_LruList.h> + +struct metis_contentstore_entry; +typedef struct metis_contentstore_entry MetisContentStoreEntry; + +/** + * The max time allowed for an ExpiryTime. Will never be exceeded. + */ +extern const uint64_t metisContentStoreEntry_MaxExpiryTime; + +/** + * The max time allowed for an RecommendedCacheTime. Will never be exceeded. + */ +extern const uint64_t metisContentStoreEntry_MaxRecommendedCacheTime; + + +/** + * Creates a new `MetisContentStoreEntry` instance, acquiring a reference to the supplied `MetisMessage`. + * + * @param message the message to store + * @param lruList the LRU list that this entry will be stored in. + * @return A newly created `MetisContentStoreEntry` instance that must eventually be released by calling + * {@link metisContentStoreEntry_Release}. + * + * @see metisContentStoreEntry_Release + */ +MetisContentStoreEntry *metisContentStoreEntry_Create(MetisMessage *objectMessage, MetisLruList *lruList); + +/** + * Returns a reference counted copy of the supplied `MetisContentStoreEntry`. + * + * @param original the MetisContentStoreEntry to return a reference to. + * @return Reference counted copy, must call <code>metisContentStoreEntry_Destroy()</code> on it. + */ +MetisContentStoreEntry *metisContentStoreEntry_Acquire(const 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); + +/** + * Returns a pointer to the contained {@link MetisMessage}. + * The caller must called {@link metisMessage_Acquire()} if they want to keep a reference to the returned + * message. + * + * @param storeEntry the MetisContentStoreEntry from which to retrieve the `MetisMessage` pointer. + * @return the address of the `MetisMessage` contained in the storeEntry. + * @see metisMessage_Acquire + */ +MetisMessage *metisContentStoreEntry_GetMessage(const MetisContentStoreEntry *storeEntry); + +/** + * Return true if the message stored in this `MetisContentStoreEntry` has an ExpiryTime. + * + * @param storeEntry the MetisContentStoreEntry containing the message. + * @return true if the referenced message has an ExpiryTime. False, otherwise. + */ +bool metisContentStoreEntry_HasExpiryTimeTicks(const MetisContentStoreEntry *storeEntry); + +/** + * Return the ExpiryTime stored in this `MetisContentStoreEntry`. + * + * @param storeEntry the MetisContentStoreEntry from which to retrieve the `MetisMessage` pointer. + * @return the address of the `MetisMessage` contained in the storeEntry. + */ +uint64_t metisContentStoreEntry_GetExpiryTimeTicks(const MetisContentStoreEntry *storeEntry); + +/** + * Return true if the message stored in this `MetisContentStoreEntry` has a RecommendedCacheTime. + * + * @param storeEntry the MetisContentStoreEntry containing the message. + * @return true if the referenced message has a RecommendedCacheTime. False, otherwise. + */ +bool metisContentStoreEntry_HasRecommendedCacheTimeTicks(const MetisContentStoreEntry *storeEntry); + +/** + * Return the RecommendedCacheTime stored in this `MetisContentStoreEntry`. + * + * @param storeEntry the MetisContentStoreEntry from which to retrieve the `MetisMessage` pointer. + * @return the address of the `MetisMessage` contained in the storeEntry. + */ +uint64_t metisContentStoreEntry_GetRecommendedCacheTimeTicks(const MetisContentStoreEntry *storeEntry); + +/** + * A signum function comparing two `MetisContentStoreEntry` instances, using their + * RecommendedCacheTime and, if necessary, the addresses of the referenced MetisMessage. In other words, if + * two ContentStoreEntries have the same RecommendedCacheTime, the comparison will then be made on the + * memory addresses of the MetisMessages referenced by the MetisContentStoreEntrys. So, the only way two + * MetisContentStoreEntrys will compare equally (0) is if they both have the same RecommendedCacheTime and reference + * the same MetisMessage. + * + * Used to determine the ordering relationship of two `MetisContentStoreEntry` instances. + * This is used by the {@link MetisTimeOrderedList} to keep a list of MetisContentStoreEntrys, sorted by + * RecommendedCacheTime. + * + * @param [in] storeEntry1 A pointer to a `MetisContentStoreEntry` instance. + * @param [in] storeEntry2 A pointer to a `MetisContentStoreEntry` instance to be compared to `storeEntry1`. + * + * @return 0 if `storeEntry1` and `storeEntry2` are equivalent + * @return < 0 if `storeEntry1` < `storeEntry2` + * @return > 0 if `storeEntry1` > `storeEntry2` + * + * Example: + * @code + * { + * MetisContentStoreEntry *entry1 = metisContentStoreEntry_Create(...); + * MetisContentStoreEntry *entry2 = metisContentStoreEntry_Create(...); + * + * int val = metisContentStoreEntry_CompareRecommendedCacheTime(entry1, entry2); + * if (val < 0) { + * // entry1 has a lower RecommendedCacheTime, or the same RecommendedCacheTime as entry2 and a different message. + * } else if (val > 0) { + * // entry2 has a lower RecommendedCacheTime, or the same RecommendedCacheTime as entry1 and a different message. + * } else { + * // entry1 and entry2 have the same RecommendedCacheTime AND the same message. + * } + * + * metisContentStoreEntry_Release(&entry1); + * metisContentStoreEntry_Release(&entry2); + * + * } + * @endcode + * @see `metisContentStoreEntry_CompareExpiryTime` + */ +int metisContentStoreEntry_CompareRecommendedCacheTime(const MetisContentStoreEntry *storeEntry1, const MetisContentStoreEntry *storeEntry2); + +/** + * A signum function comparing two `MetisContentStoreEntry` instances, using their + * ExpiryTime and, if necessary, the addresses of the referenced MetisMessage. In other words, if + * two ContentStoreEntries have the same ExpiryTime, the comparison will then be made on the + * memory addresses of the MetisMessages referenced by the MetisContentStoreEntrys. So, the only way two + * MetisContentStoreEntrys will compare equally (0) is if they both have the same ExpiryTime and reference + * the same MetisMessage. + * + * Used to determine the ordering relationship of two `MetisContentStoreEntry` instances. + * This is used by the {@link MetisTimeOrderedList} to keep a list of MetisContentStoreEntrys, sorted by + * ExpiryTime. + * + * @param [in] storeEntry1 A pointer to a `MetisContentStoreEntry` instance. + * @param [in] storeEntry2 A pointer to a `MetisContentStoreEntry` instance to be compared to `storeEntry1`. + * + * @return 0 if `storeEntry1` and `storeEntry2` are equivalent + * @return < 0 if `storeEntry1` < `storeEntry2` + * @return > 0 if `storeEntry1` > `storeEntry2` + * + * Example: + * @code + * { + * MetisContentStoreEntry *entry1 = metisContentStoreEntry_Create(...); + * MetisContentStoreEntry *entry2 = metisContentStoreEntry_Create(...); + * + * int val = metisContentStoreEntry_CompareExpiryTime(entry1, entry2); + * if (val < 0) { + * // entry1 has a lower ExpiryTime, or the same ExpiryTime as entry2 and a different message. + * } else if (val > 0) { + * // entry2 has a lower ExpiryTime, or the same ExpiryTime as entry1 and a different message. + * } else { + * // entry1 and entry2 have the same ExpiryTime AND the same message. + * } + * + * metisContentStoreEntry_Release(&entry1); + * metisContentStoreEntry_Release(&entry2); + * + * } + * @endcode + * @see `metisContentStoreEntry_CompareRecommendedCacheTime` + */ +int metisContentStoreEntry_CompareExpiryTime(const MetisContentStoreEntry *storeEntry1, const MetisContentStoreEntry *storeEntry2); + +/** + * 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/content_store/metis_ContentStoreInterface.c b/metis/ccnx/forwarder/metis/content_store/metis_ContentStoreInterface.c new file mode 100644 index 00000000..d846487f --- /dev/null +++ b/metis/ccnx/forwarder/metis/content_store/metis_ContentStoreInterface.c @@ -0,0 +1,68 @@ +/* + * 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/content_store/metis_ContentStoreInterface.h> + +void +metisContentStoreInterface_Release(MetisContentStoreInterface **storeImplPtr) +{ + (*storeImplPtr)->release(storeImplPtr); +} + +bool +metisContentStoreInterface_PutContent(MetisContentStoreInterface *storeImpl, MetisMessage *content, uint64_t currentTimeTicks) +{ + return storeImpl->putContent(storeImpl, content, currentTimeTicks); +} + +bool +metisContentStoreInterface_RemoveContent(MetisContentStoreInterface *storeImpl, MetisMessage *content) +{ + return storeImpl->removeContent(storeImpl, content); +} + +MetisMessage * +metisContentStoreInterface_MatchInterest(MetisContentStoreInterface *storeImpl, MetisMessage *interest) +{ + return storeImpl->matchInterest(storeImpl, interest); +} + +size_t +metisContentStoreInterface_GetObjectCapacity(MetisContentStoreInterface *storeImpl) +{ + return storeImpl->getObjectCapacity(storeImpl); +} + +size_t +metisContentStoreInterface_GetObjectCount(MetisContentStoreInterface *storeImpl) +{ + return storeImpl->getObjectCount(storeImpl); +} + +void +metisContentStoreInterface_Log(MetisContentStoreInterface *storeImpl) +{ + storeImpl->log(storeImpl); +} + +void * +metisContentStoreInterface_GetPrivateData(MetisContentStoreInterface *storeImpl) +{ + return storeImpl->_privateData; +} diff --git a/metis/ccnx/forwarder/metis/content_store/metis_ContentStoreInterface.h b/metis/ccnx/forwarder/metis/content_store/metis_ContentStoreInterface.h new file mode 100644 index 00000000..fde8cf66 --- /dev/null +++ b/metis/ccnx/forwarder/metis/content_store/metis_ContentStoreInterface.h @@ -0,0 +1,197 @@ +/* + * 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_ContentStoreInterface_h +#define Metis_metis_ContentStoreInterface_h + +#include <stdio.h> + +#include <ccnx/forwarder/metis/core/metis_Message.h> + +typedef struct metis_contentstore_config { + size_t objectCapacity; +} MetisContentStoreConfig; + +typedef struct metis_contentstore_interface MetisContentStoreInterface; + +struct metis_contentstore_interface { + /** + * Place a MetisMessage representing a ContentObject into the ContentStore. If necessary to make room, + * remove expired content or content that has exceeded the Recommended Cache Time. + * + * @param storeImpl - a pointer to this MetisContentStoreInterface instance. + * @param content - a pointer to a `MetisMessage` to place in the store. + * @param currentTimeTicks - the current time, in metis ticks, since the UTC epoch. + */ + bool (*putContent)(MetisContentStoreInterface *storeImpl, MetisMessage *content, uint64_t currentTimeTicks); + + /** + * The function to call to remove content from the ContentStore. + * It will Release any references that were created when the content was placed into the ContentStore. + * + * @param storeImpl - a pointer to this MetisContentStoreInterface instance. + * @param content - a pointer to a `MetisMessage` to remove from the store. + */ + bool (*removeContent)(MetisContentStoreInterface *storeImpl, MetisMessage *content); + + /** + * Given a MetisMessage that represents and Interest, try to find a matching ContentObject. Matching is + * done on a most-restrictive basis. + * + * a) If the interest has a ContentObjectHash restriction, it will match on the Name and the Object Hash. + * b) If it has a KeyId, it will match on the Name and the KeyId + * c) otherwise, it matches by Name + * + * @param storeImpl - a pointer to this MetisContentStoreInterface instance. + * @param interest - a pointer to a `MetisMessage` representing the Interest to match. + * + * @return a pointer to a MetisMessage containing the matching ContentObject + * @return NULL if no matching ContentObject was found + */ + MetisMessage * (*matchInterest)(MetisContentStoreInterface*storeImpl, MetisMessage *interest); + + /** + * Return the maximum number of ContentObjects that can be stored in this ContentStore. This is a raw + * count, not based on memory size. + * + * @param storeImpl - a pointer to this MetisContentStoreInterface instance. + * + * @return the maximum number of ContentObjects that can be stored + */ + size_t (*getObjectCapacity)(MetisContentStoreInterface *storeImpl); + + /** + * Return the number of ContentObjects currently stored in the ContentStore. + * + * @param storeImpl - a pointer to this MetisContentStoreInterface instance. + * + * @return the current number of ContentObjects in the ContentStore + */ + size_t (*getObjectCount)(MetisContentStoreInterface *storeImpl); + + /** + * Loga ContentStore implementation specific version of store-related information. + * + * @param storeImpl - a pointer to this MetisContentStoreInterface instance. + */ + void (*log)(MetisContentStoreInterface *storeImpl); + + /** + * Acquire a new reference to the specified ContentStore instance. This reference will eventually need + * to be released by calling {@link metisContentStoreInterface_Release}. + * + * @param storeImpl - a pointer to this MetisContentStoreInterface instance. + */ + MetisContentStoreInterface *(*acquire)(const MetisContentStoreInterface *storeImpl); + + /** + * Release the ContentStore, which will also Release any references held by it. + * + * @param storeImpl - a pointer to this MetisContentStoreInterface instance. + */ + void (*release)(MetisContentStoreInterface **storeImpl); + + /** + * A pointer to opaque private data used by the ContentStore instance represented by this instance of + * MetisContentStoreInterface. + */ + void *_privateData; +}; + +/** + * Place a MetisMessage representing a ContentObject into the ContentStore. If necessary to make room, + * remove expired content or content that has exceeded the Recommended Cache Time. + * + * @param storeImpl - a pointer to this MetisContentStoreInterface instance. + * @param content - a pointer to a `MetisMessage` to place in the store. + * + * @param currentTimeTicks - the current time, in metis ticks, since the UTC epoch. + */ +bool metisContentStoreInterface_PutContent(MetisContentStoreInterface *storeImpl, MetisMessage *content, uint64_t currentTimeTicks); + +/** + * The function to call to remove content from the ContentStore. + * It will Release any references that were created when the content was placed into the ContentStore. + * + * @param storeImpl - a pointer to this MetisContentStoreInterface instance. + * @param content - a pointer to a `MetisMessage` to remove from the store. + */ +bool metisContentStoreInterface_RemoveContent(MetisContentStoreInterface *storeImpl, MetisMessage *content); + +/** + * Given a MetisMessage that represents and Interest, try to find a matching ContentObject. Matching is + * done on a most-restrictive basis. + * + * a) If the interest has a ContentObjectHash restriction, it will match on the Name and the Object Hash. + * b) If it has a KeyId, it will match on the Name and the KeyId + * c) otherwise, it matches by Name + * + * @param storeImpl - a pointer to this MetisContentStoreInterface instance. + * @param interest - a pointer to a `MetisMessage` representing the Interest to match. + * + * @return a pointer to a MetisMessage containing the matching ContentObject + * @return NULL if no matching ContentObject was found + */ +MetisMessage *metisContentStoreInterface_MatchInterest(MetisContentStoreInterface*storeImpl, MetisMessage *interest); + +/** + * Return the maximum number of ContentObjects that can be stored in this ContentStore. This is a raw + * count, not based on memory size. + * + * @param storeImpl - a pointer to this MetisContentStoreInterface instance. + * + * @return the maximum number of ContentObjects that can be stored + */ +size_t metisContentStoreInterface_GetObjectCapacity(MetisContentStoreInterface *storeImpl); + +/** + * Return the number of ContentObjects currently stored in the ContentStore. + * + * @param storeImpl - a pointer to this MetisContentStoreInterface instance. + * + * @return the current number of ContentObjects in the ContentStore + */ +size_t metisContentStoreInterface_GetObjectCount(MetisContentStoreInterface *storeImpl); + +/** + * Loga ContentStore implementation specific version of store-related information. + * + * @param storeImpl - a pointer to this MetisContentStoreInterface instance. + */ +void metisContentStoreInterface_Log(MetisContentStoreInterface *storeImpl); + +/** + * Acquire a new reference to the specified ContentStore instance. This reference will eventually need + * to be released by calling {@link metisContentStoreInterface_Release}. + * + * @param storeImpl - a pointer to this MetisContentStoreInterface instance. + */ +MetisContentStoreInterface *metisContentStoreInterface_Aquire(const MetisContentStoreInterface *storeImpl); + +/** + * Release the ContentStore, which will also Release any references held by it. + * + * @param storeImpl - a pointer to this MetisContentStoreInterface instance. + */ +void metisContentStoreInterface_Release(MetisContentStoreInterface **storeImplPtr); + +/** + * Return a pointer to the data private to this implementation of the ContentStore interface. + * + * @param storeImpl - a pointer to this MetisContentStoreInterface instance. + */ +void *metisContentStoreInterface_GetPrivateData(MetisContentStoreInterface *storeImpl); +#endif // Metis_metis_ContentStoreInterface_h diff --git a/metis/ccnx/forwarder/metis/content_store/metis_LRUContentStore.c b/metis/ccnx/forwarder/metis/content_store/metis_LRUContentStore.c new file mode 100644 index 00000000..82095882 --- /dev/null +++ b/metis/ccnx/forwarder/metis/content_store/metis_LRUContentStore.c @@ -0,0 +1,530 @@ +/* + * 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 <sys/queue.h> + +#include <parc/algol/parc_Object.h> +#include <parc/algol/parc_HashCodeTable.h> +#include <parc/algol/parc_DisplayIndented.h> + +#include <ccnx/forwarder/metis/core/metis_Logger.h> + +#include <ccnx/forwarder/metis/content_store/metis_LRUContentStore.h> + +#include <ccnx/forwarder/metis/content_store/metis_ContentStoreInterface.h> +#include <ccnx/forwarder/metis/content_store/metis_ContentStoreEntry.h> +#include <ccnx/forwarder/metis/content_store/metis_LruList.h> +#include <ccnx/forwarder/metis/content_store/metis_TimeOrderedList.h> + +#include <ccnx/forwarder/metis/processor/metis_HashTableFunction.h> + +typedef struct metis_lru_contentstore_stats { + uint64_t countExpiryEvictions; + uint64_t countRCTEvictions; + uint64_t countLruEvictions; + uint64_t countAdds; + uint64_t countHits; + uint64_t countMisses; +} _MetisLRUContentStoreStats; + + +typedef struct metis_lru_contentstore_data { + size_t objectCapacity; + size_t objectCount; + + MetisLogger *logger; + + // This LRU is just for keeping track of insertion and access order. + MetisLruList *lru; + + // These are indexes by name and key ID hash + PARCHashCodeTable *indexByNameHash; + PARCHashCodeTable *indexByNameAndKeyIdHash; + + // These are indexes by time. + MetisTimeOrderedList *indexByRecommendedCacheTime; + MetisTimeOrderedList *indexByExpirationTime; + + // This table is responsible for Releasing our ContentStoreEntries. + PARCHashCodeTable *storageByNameAndObjectHashHash; + + _MetisLRUContentStoreStats stats; +} _MetisLRUContentStore; + + + +static void +_destroyIndexes(_MetisLRUContentStore *store) +{ + if (store->indexByNameHash != NULL) { + parcHashCodeTable_Destroy(&(store->indexByNameHash)); + } + + if (store->indexByNameAndKeyIdHash != NULL) { + parcHashCodeTable_Destroy(&(store->indexByNameAndKeyIdHash)); + } + + if (store->indexByRecommendedCacheTime != NULL) { + metisTimeOrderedList_Release(&(store->indexByRecommendedCacheTime)); + } + + if (store->indexByExpirationTime != NULL) { + metisTimeOrderedList_Release(&(store->indexByExpirationTime)); + } + + // This tables must go last. It holds the references to the MetisMessage. + if (store->storageByNameAndObjectHashHash != NULL) { + parcHashCodeTable_Destroy(&(store->storageByNameAndObjectHashHash)); + } + + if (store->lru != NULL) { + metisLruList_Destroy(&(store->lru)); + } +} + +static void +_MetisContentStoreInterface_Destroy(MetisContentStoreInterface **storeImplPtr) +{ + _MetisLRUContentStore *store = metisContentStoreInterface_GetPrivateData(*storeImplPtr); + + parcObject_Release((PARCObject **) &store); +} + +static bool +_MetisLRUContentStore_Destructor(_MetisLRUContentStore **storePtr) +{ + _MetisLRUContentStore *store = *storePtr; + + _destroyIndexes(store); + metisLogger_Release(&store->logger); + + return true; +} + +parcObject_Override(_MetisLRUContentStore, PARCObject, + .destructor = (PARCObjectDestructor *) _MetisLRUContentStore_Destructor + ); + +parcObject_ExtendPARCObject(MetisContentStoreInterface, + _MetisContentStoreInterface_Destroy, NULL, NULL, NULL, NULL, NULL, NULL); + +static parcObject_ImplementAcquire(_metisLRUContentStore, MetisContentStoreInterface); +static parcObject_ImplementRelease(_metisLRUContentStore, MetisContentStoreInterface); + +static void +_hashTableFunction_ContentStoreEntryDestroyer(void **dataPtr) +{ + metisContentStoreEntry_Release((MetisContentStoreEntry **) dataPtr); +} + +static bool +_metisLRUContentStore_Init(_MetisLRUContentStore *store, MetisContentStoreConfig *config, MetisLogger *logger) +{ + bool result = false; + + store->logger = metisLogger_Acquire(logger); + + size_t initialSize = config->objectCapacity * 2; + memset(&store->stats, 0, sizeof(_MetisLRUContentStoreStats)); + + store->objectCapacity = config->objectCapacity; + store->objectCount = 0; + + // initial size must be at least 1 or else the data structures break. + initialSize = (initialSize == 0) ? 1 : initialSize; + + store->indexByExpirationTime = + metisTimeOrderedList_Create((MetisTimeOrderList_KeyCompare *) metisContentStoreEntry_CompareExpiryTime); + + store->indexByRecommendedCacheTime = + metisTimeOrderedList_Create((MetisTimeOrderList_KeyCompare *) metisContentStoreEntry_CompareRecommendedCacheTime); + + store->indexByNameHash = parcHashCodeTable_Create_Size(metisHashTableFunction_MessageNameEquals, + metisHashTableFunction_MessageNameHashCode, + NULL, + NULL, + initialSize); + + store->indexByNameAndKeyIdHash = parcHashCodeTable_Create_Size(metisHashTableFunction_MessageNameAndKeyIdEquals, + metisHashTableFunction_MessageNameAndKeyIdHashCode, + NULL, + NULL, + initialSize); + + store->storageByNameAndObjectHashHash = parcHashCodeTable_Create_Size(metisHashTableFunction_MessageNameAndObjectHashEquals, + metisHashTableFunction_MessageNameAndObjectHashHashCode, + NULL, + _hashTableFunction_ContentStoreEntryDestroyer, + initialSize); + + store->lru = metisLruList_Create(); + + // If any of the index tables couldn't be allocated, we can't continue. + if ((store->indexByExpirationTime == NULL) + || (store->indexByNameAndKeyIdHash == NULL) + || (store->indexByNameHash == NULL) + || (store->indexByRecommendedCacheTime == NULL) + || (store->storageByNameAndObjectHashHash == NULL) + || (store->lru == NULL)) { + if (metisLogger_IsLoggable(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Error)) { + metisLogger_Log(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Error, __func__, + "LRUContentStore could not be created. Could not allocate all index tables.", + (void *) store, store->objectCapacity); + } + + _destroyIndexes(store); + result = false; + } else { + result = true; + } + return result; +} + +/** + * Remove a MetisContentStoreEntry from all tables and indices. + */ +static void +_metisLRUContentStore_PurgeStoreEntry(_MetisLRUContentStore *store, MetisContentStoreEntry *entryToPurge) +{ + if (metisContentStoreEntry_HasExpiryTimeTicks(entryToPurge)) { + metisTimeOrderedList_Remove(store->indexByExpirationTime, entryToPurge); + } + + if (metisContentStoreEntry_HasRecommendedCacheTimeTicks(entryToPurge)) { + metisTimeOrderedList_Remove(store->indexByRecommendedCacheTime, entryToPurge); + } + + MetisMessage *content = metisContentStoreEntry_GetMessage(entryToPurge); + parcHashCodeTable_Del(store->indexByNameHash, content); + + if (metisMessage_HasKeyId(content)) { + parcHashCodeTable_Del(store->indexByNameAndKeyIdHash, content); + } + + // This _Del call will call the Release/Destroy on the ContentStoreEntry, + // which will remove it from the LRU as well. + parcHashCodeTable_Del(store->storageByNameAndObjectHashHash, content); + + store->objectCount--; +} + +static bool +_metisLRUContentStore_RemoveLeastUsed(_MetisLRUContentStore *store) +{ + bool result = false; + + if (store->objectCount > 0) { + MetisLruListEntry *lruEntry = metisLruList_PopTail(store->lru); + MetisContentStoreEntry *storeEntry = + (MetisContentStoreEntry *) metisLruList_EntryGetData(lruEntry); + + if (metisLogger_IsLoggable(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) { + metisLogger_Log(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__, + "ContentStore %p evict message %p by LRU (LRU evictions %" PRIu64 ")", + (void *) store, (void *) metisContentStoreEntry_GetMessage(storeEntry), + store->stats.countLruEvictions); + } + + _metisLRUContentStore_PurgeStoreEntry(store, storeEntry); + + result = true; + } + return result; +} + +static void +_evictByStorePolicy(_MetisLRUContentStore *store, uint64_t currentTimeInMetisTicks) +{ + // We need to make room. Here's the plan: + // 1) Check to see if anything has expired. If so, remove it and we're done. If not, + // 2) Check to see if anything has exceeded it's recommended cache time. If so, remove it and we're done. If not, + // 3) Remove the least recently used item. + + MetisContentStoreEntry *entry = metisTimeOrderedList_GetOldest(store->indexByExpirationTime); + if (entry + && metisContentStoreEntry_HasExpiryTimeTicks(entry) + && (currentTimeInMetisTicks > metisContentStoreEntry_GetExpiryTimeTicks(entry))) { + // Found an expired entry. Remove it, and we're done. + + store->stats.countExpiryEvictions++; + if (metisLogger_IsLoggable(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) { + metisLogger_Log(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__, + "ContentStore %p evict message %p by ExpiryTime (ExpiryTime evictions %" PRIu64 ")", + (void *) store, (void *) metisContentStoreEntry_GetMessage(entry), + store->stats.countExpiryEvictions); + } + + _metisLRUContentStore_PurgeStoreEntry(store, entry); + } else { + // Check for entries that have exceeded RCT + entry = metisTimeOrderedList_GetOldest(store->indexByRecommendedCacheTime); + if (entry + && metisContentStoreEntry_HasRecommendedCacheTimeTicks(entry) + && (currentTimeInMetisTicks > metisContentStoreEntry_GetRecommendedCacheTimeTicks(entry))) { + // Found an entry passed it's RCT. Remove it, and we're done. + + store->stats.countRCTEvictions++; + if (metisLogger_IsLoggable(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) { + metisLogger_Log(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__, + "ContentStore %p evict message %p by RCT (RCT evictions %" PRIu64 ")", + (void *) store, (void *) metisContentStoreEntry_GetMessage(entry), + store->stats.countRCTEvictions); + } + + _metisLRUContentStore_PurgeStoreEntry(store, entry); + } else { + store->stats.countLruEvictions++; + _metisLRUContentStore_RemoveLeastUsed(store); + } + } +} + +static bool +_metisLRUContentStore_PutContent(MetisContentStoreInterface *storeImpl, MetisMessage *content, uint64_t currentTimeTicks) + +{ + bool result = false; + _MetisLRUContentStore *store = (_MetisLRUContentStore *) metisContentStoreInterface_GetPrivateData(storeImpl); + assertNotNull(store, "Parameter store must be non-null"); + assertNotNull(content, "Parameter objectMessage must be non-null"); + + assertTrue(metisMessage_GetType(content) == MetisMessagePacketType_ContentObject, + "Parameter objectMessage must be a Content Object"); + + if (store->objectCapacity == 0) { + return false; + } + + uint64_t expiryTimeTicks = metisContentStoreEntry_MaxExpiryTime; + uint64_t recommendedCacheTimeTicks = metisContentStoreEntry_MaxRecommendedCacheTime; + + if (metisMessage_HasExpiryTime(content)) { + expiryTimeTicks = metisMessage_GetExpiryTimeTicks(content); + } + + if (metisMessage_HasRecommendedCacheTime(content)) { + recommendedCacheTimeTicks = metisMessage_GetRecommendedCacheTimeTicks(content); + } + + // Don't add anything that's already expired or has exceeded RCT. + if (currentTimeTicks >= expiryTimeTicks || currentTimeTicks >= recommendedCacheTimeTicks) { + return false; + } + + if (store->objectCount >= store->objectCapacity) { + // Store is full. Need to make room. + _evictByStorePolicy(store, currentTimeTicks); + } + + // And now add a new entry to the head of the LRU. + + MetisContentStoreEntry *entry = metisContentStoreEntry_Create(content, store->lru); + + if (entry != NULL) { + if (parcHashCodeTable_Add(store->storageByNameAndObjectHashHash, content, entry)) { + parcHashCodeTable_Add(store->indexByNameHash, content, entry); + + if (metisMessage_HasKeyId(content)) { + parcHashCodeTable_Add(store->indexByNameAndKeyIdHash, content, entry); + } + + if (metisContentStoreEntry_HasExpiryTimeTicks(entry)) { + metisTimeOrderedList_Add(store->indexByExpirationTime, entry); + } + + if (metisContentStoreEntry_HasRecommendedCacheTimeTicks(entry)) { + metisTimeOrderedList_Add(store->indexByRecommendedCacheTime, entry); + } + + store->objectCount++; + store->stats.countAdds++; + + if (metisLogger_IsLoggable(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) { + metisLogger_Log(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__, + "LRUContentStore %p saved message %p (object count %" PRIu64 ")", + (void *) store, (void *) content, store->objectCount); + } + + result = true; + } else { + // Free what we just created, but did not add. 'entry' has ownership of 'copy', and so will + // call _Release() on it + metisContentStoreEntry_Release(&entry); + + if (metisLogger_IsLoggable(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Warning)) { + metisLogger_Log(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Warning, __func__, + "LRUContentStore %p failed to add message %p to hash table", + (void *) store, (void *) content); + } + } + } + + return result; +} + +static MetisMessage * +_metisLRUContentStore_MatchInterest(MetisContentStoreInterface *storeImpl, MetisMessage *interest) +{ + MetisMessage *result = NULL; + + _MetisLRUContentStore *store = (_MetisLRUContentStore *) metisContentStoreInterface_GetPrivateData(storeImpl); + + assertNotNull(store, "Parameter store must be non-null"); + assertNotNull(interest, "Parameter interestMessage must be non-null"); + assertTrue(metisMessage_GetType(interest) == 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. + + PARCHashCodeTable *table; + if (metisMessage_HasContentObjectHash(interest)) { + table = store->storageByNameAndObjectHashHash; + } else if (metisMessage_HasKeyId(interest)) { + table = store->indexByNameAndKeyIdHash; + } else { + table = store->indexByNameHash; + } + + MetisContentStoreEntry *storeEntry = parcHashCodeTable_Get(table, interest); + + if (storeEntry) { + metisContentStoreEntry_MoveToHead(storeEntry); + result = metisContentStoreEntry_GetMessage(storeEntry); + + store->stats.countHits++; + + if (metisLogger_IsLoggable(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug)) { + metisLogger_Log(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug, __func__, + "LRUContentStore %p matched interest %p (hits %" PRIu64 ", misses %" PRIu64 ")", + (void *) store, (void *) interest, 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__, + "LRUContentStore %p missed interest %p (hits %" PRIu64 ", misses %" PRIu64 ")", + (void *) store, (void *) interest, store->stats.countHits, store->stats.countMisses); + } + } + + return result; +} + +static bool +_metisLRUContentStore_RemoveContent(MetisContentStoreInterface *storeImpl, MetisMessage *content) +{ + bool result = false; + _MetisLRUContentStore *store = (_MetisLRUContentStore *) metisContentStoreInterface_GetPrivateData(storeImpl); + + MetisContentStoreEntry *storeEntry = parcHashCodeTable_Get(store->storageByNameAndObjectHashHash, content); + + if (storeEntry != NULL) { + _metisLRUContentStore_PurgeStoreEntry(store, storeEntry); + result = true; + } + + return result; +} + +static void +_metisLRUContentStore_Log(MetisContentStoreInterface *storeImpl) +{ + _MetisLRUContentStore *store = (_MetisLRUContentStore *) metisContentStoreInterface_GetPrivateData(storeImpl); + + metisLogger_Log(store->logger, MetisLoggerFacility_Processor, PARCLogLevel_All, __func__, + "MetisLRUContentStore @%p {count = %zu, capacity = %zu {" + "stats = @%p {adds = %" PRIu64 ", hits = %" PRIu64 ", misses = %" PRIu64 ", LRUEvictons = %" PRIu64 + ", ExpiryEvictions = %" PRIu64 ", RCTEvictions = %" PRIu64 "} }", + store, + store->objectCount, + store->objectCapacity, + &store->stats, + store->stats.countAdds, + store->stats.countHits, + store->stats.countMisses, + store->stats.countLruEvictions, + store->stats.countExpiryEvictions, + store->stats.countRCTEvictions); +} + +static size_t +_metisLRUContentStore_GetObjectCapacity(MetisContentStoreInterface *storeImpl) +{ + _MetisLRUContentStore *store = (_MetisLRUContentStore *) metisContentStoreInterface_GetPrivateData(storeImpl); + return store->objectCapacity; +} + +static size_t +_metisLRUContentStore_GetObjectCount(MetisContentStoreInterface *storeImpl) +{ + _MetisLRUContentStore *store = (_MetisLRUContentStore *) metisContentStoreInterface_GetPrivateData(storeImpl); + return store->objectCount; +} + +static size_t +_metisLRUContentStore_SetObjectCapacity(MetisContentStoreInterface *storeImpl, size_t newCapacity) +{ + _MetisLRUContentStore *store = (_MetisLRUContentStore *) metisContentStoreInterface_GetPrivateData(storeImpl); + return store->objectCapacity = newCapacity; +} + +MetisContentStoreInterface * +metisLRUContentStore_Create(MetisContentStoreConfig *config, MetisLogger *logger) +{ + MetisContentStoreInterface *storeImpl = NULL; + + assertNotNull(logger, "MetisLRUContentStore requires a non-NULL logger"); + + storeImpl = parcObject_CreateAndClearInstance(MetisContentStoreInterface); + + if (storeImpl != NULL) { + storeImpl->_privateData = parcObject_CreateAndClearInstance(_MetisLRUContentStore); + + if (_metisLRUContentStore_Init(storeImpl->_privateData, config, logger)) { + storeImpl->putContent = &_metisLRUContentStore_PutContent; + storeImpl->removeContent = &_metisLRUContentStore_RemoveContent; + + storeImpl->matchInterest = &_metisLRUContentStore_MatchInterest; + + storeImpl->getObjectCount = &_metisLRUContentStore_GetObjectCount; + storeImpl->getObjectCapacity = &_metisLRUContentStore_GetObjectCapacity; + + storeImpl->log = &_metisLRUContentStore_Log; + + storeImpl->acquire = &_metisLRUContentStore_Acquire; + storeImpl->release = &_metisLRUContentStore_Release; + + // Initialize from the config passed to us. + _metisLRUContentStore_SetObjectCapacity(storeImpl, config->objectCapacity); + + if (metisLogger_IsLoggable(logger, MetisLoggerFacility_Processor, PARCLogLevel_Info)) { + metisLogger_Log(logger, MetisLoggerFacility_Processor, PARCLogLevel_Info, __func__, + "LRUContentStore %p created with capacity %zu", + (void *) storeImpl, metisContentStoreInterface_GetObjectCapacity(storeImpl)); + } + } + } else { + parcObject_Release((void **) &storeImpl); + } + + return storeImpl; +} + diff --git a/metis/ccnx/forwarder/metis/content_store/metis_LRUContentStore.h b/metis/ccnx/forwarder/metis/content_store/metis_LRUContentStore.h new file mode 100644 index 00000000..36738352 --- /dev/null +++ b/metis/ccnx/forwarder/metis/content_store/metis_LRUContentStore.h @@ -0,0 +1,53 @@ +/* + * 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_LRUContentStore_h +#define Metis_metis_LRUContentStore_h + +#include <stdio.h> +#include <ccnx/forwarder/metis/content_store/metis_ContentStoreInterface.h> +#include <ccnx/forwarder/metis/core/metis_Logger.h> + +/** + * Create and Initialize an instance of MetisLRUContentStore. A newly allocated {@link MetisContentStoreInterface} + * object is initialized and returned. It must eventually be released by calling {@link metisContentStoreInterface_Release}. + * + * + * @param config An instance of `MetisContentStoreConfig`, specifying options to be applied + * by the underlying MetisLRUContentStore instance. + * @param logger An instance of a {@link MetisLogger} to use for logging content store events. + * + * @return a newly created MetisLRUContentStore instance. + * + * Example: + * @code + * { + * MetisContentStoreConfig config = { + * .objectCapacity = 10 + * }; + * + * MetisContentStoreInterface *store = metisLRUContentStore_Create(&config, logger); + * assertTrue(status, "Expected to init a content store"); + * + * store->Display(&store); + * metisContentStoreInterface_Release(&store); + * } + * @endcode + * @see MetisContentStoreInterface + * @see metisContentStoreInterface_Release + */ +MetisContentStoreInterface *metisLRUContentStore_Create(MetisContentStoreConfig *config, MetisLogger *logger); +#endif // Metis_metis_LRUContentStore_h diff --git a/metis/ccnx/forwarder/metis/content_store/metis_LruList.c b/metis/ccnx/forwarder/metis/content_store/metis_LruList.c new file mode 100644 index 00000000..6cbd72eb --- /dev/null +++ b/metis/ccnx/forwarder/metis/content_store/metis_LruList.c @@ -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. + */ + + +#include <config.h> +#include <stdio.h> +#include <sys/queue.h> + +#include <ccnx/forwarder/metis/content_store/metis_LruList.h> +#include <parc/algol/parc_Memory.h> +#include <LongBow/runtime.h> + +struct metis_lru_list_entry { + void *userData; + + // always set to the list + MetisLruList *parentList; + + // indicates if the Entry is currently in the list + bool inList; + + TAILQ_ENTRY(metis_lru_list_entry) list; +}; + +// this defines the TAILQ structure so we can access the tail pointer +TAILQ_HEAD(metis_lru_s, metis_lru_list_entry); + +struct metis_lru_list { + struct metis_lru_s head; + size_t itemsInList; +}; + +void +metisLruList_EntryDestroy(MetisLruListEntry **entryPtr) +{ + assertNotNull(entryPtr, "Parameter entryPtr must be non-null double pointer"); + + MetisLruListEntry *entry = *entryPtr; + if (entry->inList) { + TAILQ_REMOVE(&entry->parentList->head, entry, list); + assertTrue(entry->parentList->itemsInList > 0, "Invalid state, removed entry from list, but itemsInList is 0"); + entry->parentList->itemsInList--; + } + + parcMemory_Deallocate((void **) &entry); + *entryPtr = NULL; +} + +void +metisLruList_EntryMoveToHead(MetisLruListEntry *entry) +{ + assertNotNull(entry, "Parameter entry must be non-null"); + + TAILQ_REMOVE(&entry->parentList->head, entry, list); + TAILQ_INSERT_HEAD(&entry->parentList->head, entry, list); +} + +void * +metisLruList_EntryGetData(MetisLruListEntry *entry) +{ + return entry->userData; +} + +MetisLruList * +metisLruList_Create() +{ + MetisLruList *list = parcMemory_AllocateAndClear(sizeof(MetisLruList)); + assertNotNull(list, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisLruList)); + list->itemsInList = 0; + TAILQ_INIT(&list->head); + return list; +} + +void +metisLruList_Destroy(MetisLruList **lruPtr) +{ + assertNotNull(lruPtr, "Parameter lruPtr must be non-null double pointer"); + + MetisLruList *lru = *lruPtr; + + MetisLruListEntry *entry = TAILQ_FIRST(&lru->head); + while (entry != NULL) { + MetisLruListEntry *next = TAILQ_NEXT(entry, list); + metisLruList_EntryDestroy(&entry); + entry = next; + } + + parcMemory_Deallocate((void **) &lru); + *lruPtr = NULL; +} + +MetisLruListEntry * +metisLruList_NewHeadEntry(MetisLruList *lru, void *data) +{ + assertNotNull(lru, "Parameter lru must be non-null"); + assertNotNull(data, "Parameter data must be non-null"); + + MetisLruListEntry *entry = parcMemory_AllocateAndClear(sizeof(MetisLruListEntry)); + assertNotNull(entry, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisLruListEntry)); + entry->userData = data; + entry->parentList = lru; + entry->inList = true; + + TAILQ_INSERT_HEAD(&lru->head, entry, list); + lru->itemsInList++; + + return entry; +} + +MetisLruListEntry * +metisLruList_PopTail(MetisLruList *lru) +{ + assertNotNull(lru, "Parameter lru must be non-null"); + + MetisLruListEntry *entry = TAILQ_LAST(&lru->head, metis_lru_s); + + if (entry) { + assertTrue(lru->itemsInList > 0, "Invalid state, removed entry from list, but itemsInList is 0"); + lru->itemsInList--; + TAILQ_REMOVE(&lru->head, entry, list); + entry->inList = false; + } + + return entry; +} + +size_t +metisLruList_Length(const MetisLruList *lru) +{ + assertNotNull(lru, "Parameter lru must be non-null"); + return lru->itemsInList; +} diff --git a/metis/ccnx/forwarder/metis/content_store/metis_LruList.h b/metis/ccnx/forwarder/metis/content_store/metis_LruList.h new file mode 100644 index 00000000..906f8c50 --- /dev/null +++ b/metis/ccnx/forwarder/metis/content_store/metis_LruList.h @@ -0,0 +1,180 @@ +/* + * 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_LruList.h + * @brief Maintains an LRU for the content store + * + * An LRU list is make up of LRU entries. The entries are bound to the list. + * The user of the list is reponsible for knowing when there's too many things and + * wants to remove one. The LRU list will grow without bound otherwise. + * + * The LRU list is meant to be used as an auxiliary data structure, not the primary + * storage of data elements. + * + * Example Usage: + * + * <code> + * myClass_Create() { + * ... + * me->lru = metisLruList_Create(); + * ... + * } + * + * myClass_AddAnItem(MyClass *me, ...) { + * size_t length = metisLruList_Length(me->lru); + * if( lenth == me->limit ) + * myClass_Overflow(me); + * + * item_Create(me, ...); + * } + * + * myClass_Overflow(MyClass *me) { + * MetisLruEntry *entry = metisLruList_PopTail(me->lru); + * + * // entry could be NULL, if the list is empty, but you probalby wouldnt have overflow then... + * Item *item = (Item *) metisLruEntry_GetData(entry); + * item_Destroy(&item); + * } + * + * myClass_Destroy(MyClass *me) { + * // this does not destroy the void * data in the entries, just the entries. + * metisLruList_Destroy(&me->lru); + * // destroy all the items from some other knowledge + * } + * + * item_Create(MyClass *me, ...) { + * item = malloc(sizeof(Item)); + * MetisLruEntry *entry = metisLruList_NewHeadEntry(me->lru, item); + * item->lruEntry = entry; + * ... + * } + * + * item_Use(Item *item) { + * metisLruEntry_MoveToHead(item->lruEntry); + * ... + * } + * + * item_Destroy(Item *item) { + * // removes the entry from the list, if its in a list + * metisLruEntry_Destroy(&item->lruEntry); + * free(item); + * } + * + */ + +#ifndef Metis_metis_LruList_h +#define Metis_metis_LruList_h + +struct metis_lru_list_entry; +typedef struct metis_lru_list_entry MetisLruListEntry; + +struct metis_lru_list; +typedef struct metis_lru_list MetisLruList; + +/** + * @function metisLruEntry_Destroy + * @abstract Destroys and element. This will also remove it from the list. + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +void metisLruList_EntryDestroy(MetisLruListEntry **entryPtr); + +/** + * @function <#FunctionName#> + * @abstract <#OneLineDescription#> + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +void metisLruList_EntryMoveToHead(MetisLruListEntry *entry); + +/** + * @function metisLruEntry_GetData + * @abstract Returns the user-supplied opaque data when the entry was created + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +void *metisLruList_EntryGetData(MetisLruListEntry *entry); + +/** + * @function metisLruList_Create + * @abstract Creates a new Least-Recently-Used list + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +MetisLruList *metisLruList_Create(); + +/** + * @function metisLruList_Destroy + * @abstract Destroys a list and frees all the elements in it + * @discussion + * <#Discussion#> + * + * @param <#param1#> + */ +void metisLruList_Destroy(MetisLruList **listPtr); + +/** + * Returns the number of items in the list + * + * <#Paragraphs Of Explanation#> + * + * @param [in] lru An allocated MetisLruList + * + * @retval number The number of items in the LRU list + * + * Example: + * @code + * <#example#> + * @endcode + */ +size_t metisLruList_Length(const MetisLruList *lru); + +/** + * @function metisLruList_NewHeadEntry + * @abstract Creates a new entry for the list. It is inserted at the head of the list. + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +MetisLruListEntry *metisLruList_NewHeadEntry(MetisLruList *lru, void *data); + +/** + * @function metisLruList_PopTail + * @abstract Removes the tail element from the list and returns it to the user + * @discussion + * Pops the tail element. The user should examine its data to destroy their + * tail object, then call <code>MetisLruEntry_Destroy()</code> to free the + * LRU entry. + * + * @param <#param1#> + * @return The tail element, or NULL for an empty list + */ +MetisLruListEntry *metisLruList_PopTail(MetisLruList *list); +#endif // Metis_metis_LruList_h diff --git a/metis/ccnx/forwarder/metis/content_store/metis_TimeOrderedList.c b/metis/ccnx/forwarder/metis/content_store/metis_TimeOrderedList.c new file mode 100644 index 00000000..55054c59 --- /dev/null +++ b/metis/ccnx/forwarder/metis/content_store/metis_TimeOrderedList.c @@ -0,0 +1,103 @@ +/* + * 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 <LongBow/runtime.h> + +#include <ccnx/forwarder/metis/content_store/metis_TimeOrderedList.h> + +#include <parc/algol/parc_Object.h> +#include <parc/algol/parc_TreeRedBlack.h> + +/** + * A list of MetisContentStoreEntrys, kept in sorted order by time. The ordering is calculated by a + * key compare function (e.g. {@link MetisTimeOrderList_KeyCompare}), passed in. + * + * This container does not hold references to the objects that it contains. In other words, it does not Acquire() + * the MetisMessages that are placed in it. That reference count is managed by the owning ContentStore. This is + * purely an index, and provides an easy to way index MetisMessages based on a specified time value. Typically, + * that would be their Recommended Cache Time, or Expiration Time. + * + * It maintains a tree, sorted by the time values passed in to the Add() function. It does not manage capacity, + * and can grow uncontrollably if the owning ContentStore does not manage it. Items are indexed first by time, then + * address of the MetisMessage (just as a distringuishing attribute). This allows us to store multiple items with + * the same expiration time. + */ + +struct metis_timeordered_list { + PARCTreeRedBlack *timeOrderedTree; +}; + +static void +_finalRelease(MetisTimeOrderedList **listP) +{ + MetisTimeOrderedList *list = *listP; + parcTreeRedBlack_Destroy(&list->timeOrderedTree); +} + +parcObject_ExtendPARCObject(MetisTimeOrderedList, _finalRelease, NULL, NULL, NULL, NULL, NULL, NULL); + +parcObject_ImplementAcquire(metisTimeOrderedList, MetisTimeOrderedList); + +parcObject_ImplementRelease(metisTimeOrderedList, MetisTimeOrderedList); + + +MetisTimeOrderedList * +metisTimeOrderedList_Create(MetisTimeOrderList_KeyCompare *keyCompareFunction) +{ + MetisTimeOrderedList *result = parcObject_CreateInstance(MetisTimeOrderedList); + if (NULL != result) { + result->timeOrderedTree = parcTreeRedBlack_Create(keyCompareFunction, // keyCompare + NULL, // keyFree + NULL, // keyCopy + NULL, // valueEquals + NULL, // valueFree + NULL); // valueCopy + } + return result; +} + +void +metisTimeOrderedList_Add(MetisTimeOrderedList *list, MetisContentStoreEntry *entry) +{ + parcTreeRedBlack_Insert(list->timeOrderedTree, entry, entry); +} + +MetisContentStoreEntry * +metisTimeOrderedList_GetOldest(MetisTimeOrderedList *list) +{ + return parcTreeRedBlack_FirstKey(list->timeOrderedTree); +} + +bool +metisTimeOrderedList_Remove(MetisTimeOrderedList *list, MetisContentStoreEntry *storeEntry) +{ + bool result = false; + + MetisContentStoreEntry *entry = (MetisContentStoreEntry *) parcTreeRedBlack_Remove(list->timeOrderedTree, storeEntry); + if (entry != NULL) { + result = true; + } + return result; +} + +size_t +metisTimeOrderedList_Length(MetisTimeOrderedList *list) +{ + return (size_t) parcTreeRedBlack_Size(list->timeOrderedTree); +} + + diff --git a/metis/ccnx/forwarder/metis/content_store/metis_TimeOrderedList.h b/metis/ccnx/forwarder/metis/content_store/metis_TimeOrderedList.h new file mode 100644 index 00000000..2f854e12 --- /dev/null +++ b/metis/ccnx/forwarder/metis/content_store/metis_TimeOrderedList.h @@ -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. + */ + +#ifndef __Metis__metis_TimeOrderedList__ +#define __Metis__metis_TimeOrderedList__ + +#include <stdio.h> +#include <ccnx/forwarder/metis/core/metis_Message.h> +#include <ccnx/forwarder/metis/content_store/metis_ContentStoreEntry.h> +#include <parc/algol/parc_TreeRedBlack.h> + +struct metis_timeordered_list; +typedef struct metis_timeordered_list MetisTimeOrderedList; + +/** + * A signum function that takes two instances of MetisContentStoreEntrys and + * returns a value based on their relative values. + */ +typedef PARCTreeRedBlack_KeyCompare MetisTimeOrderList_KeyCompare; + +/** + * Create a new instance of `MetisTimeOrderedList` that will maintain the order of its + * list items using the supplied `keyCompareFunction`. + * + * The newly created `MetisTimeOrderedList` must eventually be released by calling + * {@link metisTimeOrderedList_Release}. + * + * @param keyCompareFunction the signum comparison function to use to sort stored items. + * @return a new instance of `MetisTimeOrderList`. + * @return NULL if the new instance couldn't be created. + * + * Example: + * @code + * { + * MetisTimeOrderedList *list = + * metisTimeOrderedList_Create((MetisTimeOrderList_KeyCompare *) metisContentStoreEntry_CompareExpiryTime); + * + * ... + * + * metisTimeOrderedList_Release(&list); + * } + * @endcode + * + * @see metisTimeOrderedList_Release + * @see metisContentStoreEntry_CompareExpiryTime + * @see metisContentStoreEntry_CompareRecommendedCacheTime + */ +MetisTimeOrderedList *metisTimeOrderedList_Create(MetisTimeOrderList_KeyCompare *keyCompareFunction); + +/** + * 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] listP A pointer to a pointer to the instance to release. + * Example: + * @code + * { + * MetisTimeOrderedList *list = + * metisTimeOrderedList_Create((MetisTimeOrderList_KeyCompare *) metisContentStoreEntry_CompareExpiryTime); + * + * ... + * + * metisTimeOrderedList_Release(&list); + * } + * @endcode + */ +void metisTimeOrderedList_Release(MetisTimeOrderedList **listP); + +/** + * Add a {@link MetisContentStoreEntry} instance to the specified list. Note that a new refernece to + * the specified `storeEntry` is not acquired. + * + * @param list the list instance into which to add the specified storeEntry. + * @param storeEntry the storeEntry instance to add. + * + * Example: + * @code + * { + * MetisLogger *logger = metisLogger_Create(...); + * MetisTimeOrderedList *list = + * metisTimeOrderedList_Create((MetisTimeOrderList_KeyCompare *) metisContentStoreEntry_CompareExpiryTime); + * MetisLruList *lruList = metisLruList_Create(); + * MetisMessage *message = metisMessage_CreateFromArray((uint8_t *) "\x00" "ehlo", 5, 111, 2, logger); + * + * MetisContentStoreEntry *entry = metisContentStoreEntry_Create(message, lruList, 2l, 1l); + * + * metisTimeOrderedList_Add(list, entry); + * + * metisTimeOrderedList_Release(&list); + * + * metisContentStoreEntry_Release(&entry); + * metisMessage_Release(&message); + * metisLruList_Destroy(&lruList); + * metisLogger_Release(&logger); + * } + * @endcode + * @see metisTimeOrderedList_Remove + */ +void metisTimeOrderedList_Add(MetisTimeOrderedList *list, MetisContentStoreEntry *storeEntry); + +/** + * Remove a {@link MetisContentStoreEntry} instance from the specified list. + * + * @param list the list instance from which to remove the specified storeEntry. + * @param storeEntry the storeEntry instance to remove. + * @return true if the removal was succesful. + * @return false if the removal was not succesful. + * + * Example: + * @code + * { + * MetisLogger *logger = metisLogger_Create(...); + * MetisTimeOrderedList *list = + * metisTimeOrderedList_Create((MetisTimeOrderList_KeyCompare *) metisContentStoreEntry_CompareExpiryTime); + * MetisMessage *message = metisMessage_CreateFromArray((uint8_t *) "\x00" "ehlo", 5, 111, 2, logger); + * + * MetisContentStoreEntry *entry = metisContentStoreEntry_Create(message, NULL, 2l, 1l); + * + * metisTimeOrderedList_Add(list, entry); + * + * ... + * + * metisTimeOrderedList_Remove(list, entry); + * metisTimeOrderedList_Release(&list); + * + * metisContentStoreEntry_Release(&entry); + * metisMessage_Release(&message); + * metisLogger_Release(&logger); + * } + * @endcode + * @see metisTimeOrderedList_Add + */ +bool metisTimeOrderedList_Remove(MetisTimeOrderedList *list, MetisContentStoreEntry *storeEntry); + +/** + * Return the oldest {@link MetisContentStoreEntry} instance in this list. That is, the one with the smallest + * time value. + * + * @param list the list instance from which to retrieve the oldest storeEntry. + * @param the oldest `MetisContentStoreEntry` in the list + * @param NULL if no `MetisContentStoreEntry` was available. + * + * Example: + * @code + * { + * MetisLogger *logger = metisLogger_Create(...); + * MetisTimeOrderedList *list = + * metisTimeOrderedList_Create((MetisTimeOrderList_KeyCompare *) metisContentStoreEntry_CompareExpiryTime); + * MetisLruList *lruList = metisLruList_Create(); + * MetisMessage *message = metisMessage_CreateFromArray((uint8_t *) "\x00" "ehlo", 5, 111, 2, logger); + * + * MetisContentStoreEntry *entry = metisContentStoreEntry_Create(message, lruList, 2l, 1l); + * + * metisTimeOrderedList_Add(list, entry); + * + * MetisContentStoreEntry *oldest = metisTimeOrderedList_GetOldest(list); + * + * ... + * + * metisTimeOrderedList_Release(&list); + * + * metisContentStoreEntry_Release(&entry); + * metisMessage_Release(&message); + * metisLruList_Destroy(&lruList); + * metisLogger_Release(&logger); + * } + * @endcode + * @see metisTimeOrderedList_Remove + */ +MetisContentStoreEntry *metisTimeOrderedList_GetOldest(MetisTimeOrderedList *list); + +/** + * Return the number of items currently stored in the list. + * + * @param list the `MetisTimeOrderedList` instance from which to retrieve the count. + * @return the number of items in the list. + * + * Example: + * @code + * { + * MetisLogger *logger = metisLogger_Create(...); + * MetisTimeOrderedList *list = + * metisTimeOrderedList_Create((MetisTimeOrderList_KeyCompare *) metisContentStoreEntry_CompareExpiryTime); + * MetisLruList *lruList = metisLruList_Create(); + * MetisMessage *message = metisMessage_CreateFromArray((uint8_t *) "\x00" "ehlo", 5, 111, 2, logger); + * + * MetisContentStoreEntry *entry = metisContentStoreEntry_Create(message, lruList, 2l, 1l); + * + * metisTimeOrderedList_Add(list, entry); + * + * ... + * size_t numItemsInList = metisTimeOrderedList_Length(list); + * ... + * + * metisTimeOrderedList_Release(&list); + * + * metisContentStoreEntry_Release(&entry); + * metisMessage_Release(&message); + * metisLruList_Destroy(&lruList); + * metisLogger_Release(&logger); + * } + * @endcode + */ +size_t metisTimeOrderedList_Length(MetisTimeOrderedList *list); +#endif /* defined(__Metis__metis_TimeOrderedList__) */ diff --git a/metis/ccnx/forwarder/metis/content_store/test/CMakeLists.txt b/metis/ccnx/forwarder/metis/content_store/test/CMakeLists.txt new file mode 100644 index 00000000..0542732d --- /dev/null +++ b/metis/ccnx/forwarder/metis/content_store/test/CMakeLists.txt @@ -0,0 +1,17 @@ +# Enable gcov output for the tests +add_definitions(--coverage) +set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} " --coverage") + +set(TestsExpectedToPass + test_metis_LruList + test_metis_ContentStoreEntry + test_metis_ContentStoreInterface + test_metis_TimeOrderedList + test_metis_LRUContentStore +) + + +foreach(test ${TestsExpectedToPass}) + AddTest(${test}) +endforeach() + diff --git a/metis/ccnx/forwarder/metis/content_store/test/test_metis_ContentStoreEntry.c b/metis/ccnx/forwarder/metis/content_store/test/test_metis_ContentStoreEntry.c new file mode 100644 index 00000000..87d154b7 --- /dev/null +++ b/metis/ccnx/forwarder/metis/content_store/test/test_metis_ContentStoreEntry.c @@ -0,0 +1,354 @@ +/* + * 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 "../metis_ContentStoreEntry.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_ContentStoreEntry) +{ + // 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_ContentStoreEntry) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_ContentStoreEntry) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ============================================================================ + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisContentStoreEntry_Create_Destroy_Memory); + LONGBOW_RUN_TEST_CASE(Global, metisContentStoreEntry_Create_Destroy_State); + LONGBOW_RUN_TEST_CASE(Global, metisContentStoreEntry_Acquire); + LONGBOW_RUN_TEST_CASE(Global, metisContentStoreEntry_GetMessage); + LONGBOW_RUN_TEST_CASE(Global, metisContentStoreEntry_MoveToHead); + + LONGBOW_RUN_TEST_CASE(Global, metisContentStoreEntry_GetExpiryTimeInTicks); + LONGBOW_RUN_TEST_CASE(Global, metisContentStoreEntry_GetRecommendedCacheTimeInTicks); + + LONGBOW_RUN_TEST_CASE(Global, metisContentStoreEntry_CompareRecommendedCacheTime); + LONGBOW_RUN_TEST_CASE(Global, metisContentStoreEntry_CompareExpiryTime); +} + +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; +} +static MetisLogger * +_createLogger() +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug); + parcLogReporter_Release(&reporter); + + return logger; +} +LONGBOW_TEST_CASE(Global, metisContentStoreEntry_Create_Destroy_Memory) +{ + MetisLruList *lruList = metisLruList_Create(); + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + metisLogger_Release(&logger); + + size_t beforeMemory = parcMemory_Outstanding(); + MetisContentStoreEntry *storeEntry = metisContentStoreEntry_Create(object, lruList); + metisContentStoreEntry_Release(&storeEntry); + size_t afterMemory = parcMemory_Outstanding(); + + metisLruList_Destroy(&lruList); + metisMessage_Release(&object); + + assertTrue(afterMemory == beforeMemory, "Imbalance on create/destroy, expected %zu got %zu", beforeMemory, afterMemory); +} + +LONGBOW_TEST_CASE(Global, metisContentStoreEntry_Create_Destroy_State) +{ + MetisLogger *logger = _createLogger(); + + MetisLruList *lruList = metisLruList_Create(); + MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + + MetisContentStoreEntry *storeEntry = metisContentStoreEntry_Create(object, lruList); + + assertTrue(storeEntry->refcount == 1, "lruEntry has wrong refcount, expected %u got %u", 1, storeEntry->refcount); + + metisContentStoreEntry_Release(&storeEntry); + metisLruList_Destroy(&lruList); + metisMessage_Release(&object); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Global, metisContentStoreEntry_Acquire) +{ + MetisLogger *logger = _createLogger(); + MetisLruList *lruList = metisLruList_Create(); + MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + + MetisContentStoreEntry *storeEntry = metisContentStoreEntry_Create(object, lruList); + MetisContentStoreEntry *copy = metisContentStoreEntry_Acquire(storeEntry); + + assertTrue(copy->refcount == 2, "incorrect refcount after copy, expected %u got %u", 2, copy->refcount); + metisContentStoreEntry_Release(&storeEntry); + assertTrue(copy->refcount == 1, "incorrect refcount after destroy, expected %u got %u", 1, copy->refcount); + + metisContentStoreEntry_Release(©); + metisLruList_Destroy(&lruList); + metisMessage_Release(&object); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Global, metisContentStoreEntry_GetMessage) +{ + MetisLogger *logger = _createLogger(); + + MetisLruList *lruList = metisLruList_Create(); + MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + + MetisContentStoreEntry *storeEntry = metisContentStoreEntry_Create(object, lruList); + + MetisMessage *copy = metisContentStoreEntry_GetMessage(storeEntry); + assertTrue(copy == object, "Incorrect mesage, expected %p got %p", (void *) object, (void *) copy); + + metisContentStoreEntry_Release(&storeEntry); + + metisLruList_Destroy(&lruList); + metisMessage_Release(&object); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Global, metisContentStoreEntry_MoveToHead) +{ + MetisLogger *logger = _createLogger(); + MetisLruList *lruList = metisLruList_Create(); + + MetisMessage *object[3]; + MetisContentStoreEntry *storeEntry[3]; + + for (int i = 0; i < 3; i++) { + object[i] = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), i, 2, logger); + storeEntry[i] = metisContentStoreEntry_Create(object[i], lruList); + } + + // object 2 is at top of list and object 0 is at bottom. move 0 to top and 1 should be at bottom + metisContentStoreEntry_MoveToHead(storeEntry[0]); + + MetisLruListEntry *bottom = metisLruList_PopTail(lruList); + + assertTrue(bottom == storeEntry[1]->lruEntry, "Incorrect mesage, expected %p got %p", (void *) storeEntry[1]->lruEntry, (void *) bottom); + for (int i = 0; i < 3; i++) { + metisContentStoreEntry_Release(&storeEntry[i]); + metisMessage_Release(&object[i]); + } + + metisLogger_Release(&logger); + metisLruList_Destroy(&lruList); +} + +LONGBOW_TEST_CASE(Global, metisContentStoreEntry_GetExpiryTimeInTicks) +{ + MetisLogger *logger = _createLogger(); + + MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, + sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + + uint64_t expiryTime = 101l; + metisMessage_SetExpiryTimeTicks(object, expiryTime); + + MetisContentStoreEntry *storeEntry = metisContentStoreEntry_Create(object, NULL); + + assertTrue(metisContentStoreEntry_HasExpiryTimeTicks(storeEntry), "Expected entry to have expiry time"); + assertTrue(metisContentStoreEntry_GetExpiryTimeTicks(storeEntry) == expiryTime, "Got unexpected expiry time"); + + metisContentStoreEntry_Release(&storeEntry); + metisMessage_Release(&object); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Global, metisContentStoreEntry_GetRecommendedCacheTimeInTicks) +{ + MetisLogger *logger = _createLogger(); + + MetisMessage *object = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, + sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + + uint64_t rct = 202l; + metisMessage_SetRecommendedCacheTimeTicks(object, rct); + + MetisContentStoreEntry *storeEntry = metisContentStoreEntry_Create(object, NULL); + + assertTrue(metisContentStoreEntry_HasRecommendedCacheTimeTicks(storeEntry), "Expected entry to have expiry time"); + assertTrue(metisContentStoreEntry_GetRecommendedCacheTimeTicks(storeEntry) == rct, "Got unexpected cache time"); + + metisContentStoreEntry_Release(&storeEntry); + metisMessage_Release(&object); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Global, metisContentStoreEntry_CompareExpiryTime) +{ + MetisLogger *logger = _createLogger(); + + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + MetisMessage *message2 = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 3, 4, logger); + + metisMessage_SetExpiryTimeTicks(message, 100); + MetisContentStoreEntry *entry1 = metisContentStoreEntry_Create(message, NULL); + + metisMessage_SetExpiryTimeTicks(message, 200); + MetisContentStoreEntry *entry2 = metisContentStoreEntry_Create(message, NULL); + + // Different message, same times as entry1 + metisMessage_SetExpiryTimeTicks(message2, 100); + MetisContentStoreEntry *entry3 = metisContentStoreEntry_Create(message2, NULL); + + // Same message, same times as entry2 + metisMessage_SetExpiryTimeTicks(message, 200); + MetisContentStoreEntry *entry4 = metisContentStoreEntry_Create(message, NULL); + + assertTrue(metisContentStoreEntry_CompareExpiryTime(entry1, entry2) == -1, "Expected -1"); + assertTrue(metisContentStoreEntry_CompareExpiryTime(entry2, entry1) == 1, "Expected +1"); + assertTrue(metisContentStoreEntry_CompareExpiryTime(entry1, entry1) == 0, "Expected 0"); + + // Compare same expiry time, but different Message addressed. + if (message < message2) { + assertTrue(metisContentStoreEntry_CompareExpiryTime(entry1, entry3) == -1, "Expected -1"); + assertTrue(metisContentStoreEntry_CompareExpiryTime(entry3, entry1) == 1, "Expected +1"); + } else { + assertTrue(metisContentStoreEntry_CompareExpiryTime(entry1, entry3) == 1, "Expected 1"); + assertTrue(metisContentStoreEntry_CompareExpiryTime(entry3, entry1) == -1, "Expected -1"); + } + + assertTrue(metisContentStoreEntry_CompareExpiryTime(entry2, entry4) == 0, "Expected 0"); + + metisContentStoreEntry_Release(&entry1); + metisContentStoreEntry_Release(&entry2); + metisContentStoreEntry_Release(&entry3); + metisContentStoreEntry_Release(&entry4); + + metisMessage_Release(&message); + metisMessage_Release(&message2); + + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Global, metisContentStoreEntry_CompareRecommendedCacheTime) +{ + MetisLogger *logger = _createLogger(); + + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + MetisMessage *message2 = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 3, 4, logger); + + metisMessage_SetRecommendedCacheTimeTicks(message, 200); + MetisContentStoreEntry *entry1 = metisContentStoreEntry_Create(message, NULL); + + metisMessage_SetRecommendedCacheTimeTicks(message, 100); + MetisContentStoreEntry *entry2 = metisContentStoreEntry_Create(message, NULL); + + // Different message, same times as entry1 + metisMessage_SetRecommendedCacheTimeTicks(message2, 200); + MetisContentStoreEntry *entry3 = metisContentStoreEntry_Create(message2, NULL); + + // Same message, same times as entry2 + metisMessage_SetRecommendedCacheTimeTicks(message, 100); + MetisContentStoreEntry *entry4 = metisContentStoreEntry_Create(message, NULL); + + assertTrue(metisContentStoreEntry_CompareRecommendedCacheTime(entry1, entry2) == 1, "Expected 1"); + assertTrue(metisContentStoreEntry_CompareRecommendedCacheTime(entry2, entry1) == -1, "Expected -1"); + assertTrue(metisContentStoreEntry_CompareRecommendedCacheTime(entry1, entry1) == 0, "Expected 0"); + + // Compare same RCT, but different Message addressed. + if (message < message2) { + assertTrue(metisContentStoreEntry_CompareRecommendedCacheTime(entry1, entry3) == -1, "Expected -1"); + assertTrue(metisContentStoreEntry_CompareRecommendedCacheTime(entry3, entry1) == 1, "Expected +1"); + } else { + assertTrue(metisContentStoreEntry_CompareRecommendedCacheTime(entry1, entry3) == 1, "Expected 1"); + assertTrue(metisContentStoreEntry_CompareRecommendedCacheTime(entry3, entry1) == -1, "Expected -1"); + } + + assertTrue(metisContentStoreEntry_CompareRecommendedCacheTime(entry2, entry4) == 0, "Expected 0"); + + metisContentStoreEntry_Release(&entry1); + metisContentStoreEntry_Release(&entry2); + metisContentStoreEntry_Release(&entry3); + metisContentStoreEntry_Release(&entry4); + + metisMessage_Release(&message); + metisMessage_Release(&message2); + + metisLogger_Release(&logger); +} + +// ============================================================================ + +LONGBOW_TEST_FIXTURE(Local) +{ +} + +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; +} + +// ============================================================================ + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_ContentStoreEntry); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/content_store/test/test_metis_ContentStoreInterface.c b/metis/ccnx/forwarder/metis/content_store/test/test_metis_ContentStoreInterface.c new file mode 100644 index 00000000..1a32308a --- /dev/null +++ b/metis/ccnx/forwarder/metis/content_store/test/test_metis_ContentStoreInterface.c @@ -0,0 +1,222 @@ +/* + * 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 "../metis_ContentStoreInterface.c" + +#include <LongBow/unit-test.h> + +#include <parc/logging/parc_LogReporterTextStdout.h> + +#include <parc/algol/parc_SafeMemory.h> + +#include <ccnx/forwarder/metis/content_store/metis_LRUContentStore.h> +#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h> + +LONGBOW_TEST_RUNNER(metis_ContentStoreInterface) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + + LONGBOW_RUN_TEST_FIXTURE(Global); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_ContentStoreInterface) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_ContentStoreInterface) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisContentStoreInterface_CreateRelease); + LONGBOW_RUN_TEST_CASE(Global, metisContentStoreInterface_PutContent); + LONGBOW_RUN_TEST_CASE(Global, metisContentStoreInterface_RemoveContent); + LONGBOW_RUN_TEST_CASE(Global, metisContentStoreInterface_MatchInterest); + LONGBOW_RUN_TEST_CASE(Global, metisContentStoreInterface_GetObjectCount); + LONGBOW_RUN_TEST_CASE(Global, metisContentStoreInterface_GetObjectCapacity); + LONGBOW_RUN_TEST_CASE(Global, metisContentStoreInterface_Log); +} + +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; +} + +static MetisLogger * +_createLogger(void) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *result = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + metisLogger_SetLogLevel(result, MetisLoggerFacility_Processor, PARCLogLevel_Debug); + + return result; +} +static MetisContentStoreInterface * +_createContentStore(MetisLogger *logger) +{ + MetisContentStoreConfig config = { + .objectCapacity = 1000, + }; + + MetisContentStoreInterface *result = metisLRUContentStore_Create(&config, logger); + assertNotNull(result, "Expected to allocate a MetisContentStoreInterface"); + + return result; +} + +LONGBOW_TEST_CASE(Global, metisContentStoreInterface_CreateRelease) +{ + MetisLogger *logger = _createLogger(); + MetisContentStoreInterface *store = _createContentStore(logger); + + assertNotNull(store, "Expected to allocate a MetisContentStoreInterface"); + + metisContentStoreInterface_Release(&store); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Global, metisContentStoreInterface_PutContent) +{ + MetisLogger *logger = _createLogger(); + MetisContentStoreInterface *store = _createContentStore(logger); + + MetisMessage *content = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + metisContentStoreInterface_PutContent(store, content, 1000); + + metisMessage_Release(&content); + metisContentStoreInterface_Release(&store); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Global, metisContentStoreInterface_RemoveContent) +{ + MetisLogger *logger = _createLogger(); + MetisContentStoreInterface *store = _createContentStore(logger); + + MetisMessage *content = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + metisContentStoreInterface_PutContent(store, content, 1000); + + bool wasRemoved = metisContentStoreInterface_RemoveContent(store, content); + assertTrue(wasRemoved, "Expected to remove the previously stored MetisMessage"); + + metisMessage_Release(&content); + metisContentStoreInterface_Release(&store); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Global, metisContentStoreInterface_MatchInterest) +{ + MetisLogger *logger = _createLogger(); + MetisContentStoreInterface *store = _createContentStore(logger); + + MetisMessage *content = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + metisContentStoreInterface_PutContent(store, content, 1000); + + MetisMessage *interest = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger); + MetisMessage *match = metisContentStoreInterface_MatchInterest(store, interest); + + assertTrue(match == content, "Expected to retrieve the stored MetisMessage"); + + metisMessage_Release(&interest); + metisMessage_Release(&content); + metisContentStoreInterface_Release(&store); + metisLogger_Release(&logger); +} + +static MetisMessage * +_createUniqueMetisMessage(MetisLogger *logger, int tweakNumber, uint8_t *template, size_t templateSize, int nameOffset) +{ + 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); + parcBuffer_Release(&buffer); + + return result; +} + +LONGBOW_TEST_CASE(Global, metisContentStoreInterface_GetObjectCount) +{ + MetisLogger *logger = _createLogger(); + MetisContentStoreInterface *store = _createContentStore(logger); + + for (int i = 1; i < 100; i++) { + MetisMessage *content = _createUniqueMetisMessage(logger, i, metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), + metisTestDataV0_EncodedObject_name.offset + 4); + metisContentStoreInterface_PutContent(store, content, 1000 + i); + metisMessage_Release(&content); + + size_t count = metisContentStoreInterface_GetObjectCount(store); + assertTrue(count == i, "Expected a count of %d items, got %zu", i, count); + } + + metisContentStoreInterface_Release(&store); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Global, metisContentStoreInterface_GetObjectCapacity) +{ + MetisLogger *logger = _createLogger(); + MetisContentStoreConfig config = { + .objectCapacity = 1000, + }; + + MetisContentStoreInterface *store = metisLRUContentStore_Create(&config, logger); + assertTrue(metisContentStoreInterface_GetObjectCapacity(store) == config.objectCapacity, "Expected to get back the capacity we set"); + metisContentStoreInterface_Release(&store); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Global, metisContentStoreInterface_Log) +{ + MetisLogger *logger = _createLogger(); + MetisContentStoreInterface *store = _createContentStore(logger); + + metisContentStoreInterface_Log(store); + + metisContentStoreInterface_Release(&store); + metisLogger_Release(&logger); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_ContentStoreInterface); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/content_store/test/test_metis_LRUContentStore.c b/metis/ccnx/forwarder/metis/content_store/test/test_metis_LRUContentStore.c new file mode 100644 index 00000000..ea55b361 --- /dev/null +++ b/metis/ccnx/forwarder/metis/content_store/test/test_metis_LRUContentStore.c @@ -0,0 +1,788 @@ +/* + * 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 "../metis_LRUContentStore.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_LRUContentStore) +{ + 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_LRUContentStore) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_LRUContentStore) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ============================================================================ + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisLRUContentStore_Create_Destroy); + LONGBOW_RUN_TEST_CASE(Global, metisLRUContentStore_Log); + + LONGBOW_RUN_TEST_CASE(Global, metisLRUContentStore_Create_ZeroCapacity); + LONGBOW_RUN_TEST_CASE(Global, metisLRUContentStore_Fetch_ByName); + LONGBOW_RUN_TEST_CASE(Global, metisLRUContentStore_Fetch_ByNameAndKeyId); + LONGBOW_RUN_TEST_CASE(Global, metisLRUContentStore_Fetch_ByNameAndObjectHash); + + LONGBOW_RUN_TEST_CASE(Global, metisLRUContentStore_Remove_Content); + LONGBOW_RUN_TEST_CASE(Global, metisLRUContentStore_Remove_NonExistentContent); + + LONGBOW_RUN_TEST_CASE(Global, metisLRUContentStore_Fetch_Lru); + + LONGBOW_RUN_TEST_CASE(Global, metisLRUContentStore_Save_ZeroCapacity); + LONGBOW_RUN_TEST_CASE(Global, metisLRUContentStore_Save_CapacityLimit); + LONGBOW_RUN_TEST_CASE(Global, metisLRUContentStore_Save_WithoutEviction); + LONGBOW_RUN_TEST_CASE(Global, metisLRUContentStore_Save_WithEviction); + + LONGBOW_RUN_TEST_CASE(Global, metisLRUContentStore_Save_WithEvictionByExpiryTime); + LONGBOW_RUN_TEST_CASE(Global, metisLRUContentStore_Save_WithEvictionByRCT); + + LONGBOW_RUN_TEST_CASE(Global, metisLRUContentStore_Save_ExpiredContent); + + LONGBOW_RUN_TEST_CASE(Global, metisLRUContentStore_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; +} + +static MetisContentStoreInterface * +_createLRUContentStore(size_t capacity) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug); + + MetisContentStoreConfig config = { + .objectCapacity = capacity, + }; + + MetisContentStoreInterface *store = metisLRUContentStore_Create(&config, logger); + + metisLogger_Release(&logger); + + return store; +} + +static MetisMessage * +_createUniqueMetisMessage(MetisLogger *logger, int tweakNumber, uint8_t *template, size_t templateSize, int nameOffset) +{ + 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); + parcBuffer_Release(&buffer); + + return result; +} + +LONGBOW_TEST_CASE(Global, metisLRUContentStore_Create_Destroy) +{ + MetisContentStoreInterface *store = _createLRUContentStore(10); + assertNotNull(store, "Expected to init a content store"); + metisContentStoreInterface_Release(&store); +} + +LONGBOW_TEST_CASE(Global, metisLRUContentStore_Log) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + size_t capacity = 20; + MetisContentStoreInterface *store = _createLRUContentStore(capacity); + + for (int i = 1; i <= capacity; i++) { + int offsetOfNameInEncodedObject = metisTestDataV0_EncodedObject_name.offset + 4; + + MetisMessage *object = _createUniqueMetisMessage(logger, i, + metisTestDataV0_EncodedObject, + sizeof(metisTestDataV0_EncodedObject), + offsetOfNameInEncodedObject); + + bool success = metisContentStoreInterface_PutContent(store, object, 1); + metisMessage_Release(&object); + + assertTrue(success, "Unexpectedly failed to add entry to ContentStore"); + } + + metisContentStoreInterface_Log(store); + + metisLogger_Release(&logger); + metisContentStoreInterface_Release(&store); +} + +LONGBOW_TEST_CASE(Global, metisLRUContentStore_Create_ZeroCapacity) +{ + size_t capacity = 10; + MetisContentStoreInterface *store = _createLRUContentStore(capacity); + + assertTrue(metisContentStoreInterface_GetObjectCapacity(store) == capacity, "Wrong capacity, expected %zu got %zu", + capacity, metisContentStoreInterface_GetObjectCapacity(store)); + + assertTrue(metisContentStoreInterface_GetObjectCount(store) == 0, "Wrong initial count, expected %u got %zu", 0, + metisContentStoreInterface_GetObjectCount(store)); + metisContentStoreInterface_Release(&store); +} + +LONGBOW_TEST_CASE(Global, metisLRUContentStore_Fetch_ByName) +{ + size_t capacity = 10; + MetisContentStoreInterface *store = _createLRUContentStore(capacity); + + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + uint64_t expiryTime = 300l; + uint64_t rct = 200; + uint64_t now = 100; + + MetisMessage *object_1 = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, + sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + + metisMessage_SetExpiryTimeTicks(object_1, expiryTime); + metisMessage_SetRecommendedCacheTimeTicks(object_1, rct); + + MetisMessage *object_2 = metisMessage_CreateFromArray(metisTestDataV0_SecondObject, + sizeof(metisTestDataV0_SecondObject), 1, 2, logger); + metisMessage_SetExpiryTimeTicks(object_2, expiryTime); + metisMessage_SetRecommendedCacheTimeTicks(object_2, rct); + + metisMessage_SetExpiryTimeTicks(object_1, expiryTime); + metisMessage_SetRecommendedCacheTimeTicks(object_1, rct); + + metisContentStoreInterface_PutContent(store, object_1, now); + metisContentStoreInterface_PutContent(store, object_2, now); + + MetisMessage *interestByName = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, + sizeof(metisTestDataV0_InterestWithName), 3, 5, logger); + + + MetisMessage *testObject = metisContentStoreInterface_MatchInterest(store, interestByName); + assertNotNull(testObject, "Fetch did not find match when it should have"); + + uint64_t expiryTimeOut = metisMessage_GetExpiryTimeTicks(testObject); + uint64_t rctOut = metisMessage_GetRecommendedCacheTimeTicks(testObject); + bool hasExpiryTime = metisMessage_HasExpiryTime(testObject); + bool hasRCT = metisMessage_HasRecommendedCacheTime(testObject); + + assertTrue(hasRCT, "Expected object to have an RCT"); + assertTrue(hasExpiryTime, "Expected object to have an ExpiryTime"); + assertTrue(expiryTime == expiryTimeOut, "Expected the same expiryTime to be retrieved"); + assertTrue(rct == rctOut, "Expected the same RCT to be retrieved"); + + // 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); + + metisContentStoreInterface_Release(&store); + metisLogger_Release(&logger); + metisMessage_Release(&object_1); + metisMessage_Release(&object_2); + metisMessage_Release(&interestByName); +} + + +LONGBOW_TEST_CASE(Global, metisLRUContentStore_Fetch_ByNameAndKeyId) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + size_t capacity = 10; + MetisContentStoreInterface *store = _createLRUContentStore(capacity); + + 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); + + metisContentStoreInterface_PutContent(store, object_1, 1); + metisContentStoreInterface_PutContent(store, object_2, 1); + + MetisMessage *interestByNameKeyId = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName_keyid, + sizeof(metisTestDataV0_InterestWithName_keyid), + 3, 5, logger); + + MetisMessage *testObject = metisContentStoreInterface_MatchInterest(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); + + metisContentStoreInterface_Release(&store); + metisLogger_Release(&logger); + metisMessage_Release(&object_1); + metisMessage_Release(&object_2); + metisMessage_Release(&interestByNameKeyId); +} + +LONGBOW_TEST_CASE(Global, metisLRUContentStore_Fetch_ByNameAndObjectHash) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + size_t capacity = 10; + MetisContentStoreInterface *store = _createLRUContentStore(capacity); + + 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); + + uint64_t expiryTime = 300l; + uint64_t rct = 200l; + uint64_t now = 100l; + + metisMessage_SetExpiryTimeTicks(object_1, expiryTime); + metisMessage_SetRecommendedCacheTimeTicks(object_1, rct); + + metisContentStoreInterface_PutContent(store, object_1, now); + metisContentStoreInterface_PutContent(store, object_2, now); + + 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 = metisContentStoreInterface_MatchInterest(store, interestByNameObjectHash); + + assertTrue(expiryTime == metisMessage_GetExpiryTimeTicks(testObject), "Expected the same expiryTime to be retrieved"); + assertTrue(rct == metisMessage_GetRecommendedCacheTimeTicks(testObject), "Expected the same RCT to be retrieved"); + + 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); + + metisContentStoreInterface_Release(&store); + metisLogger_Release(&logger); + metisMessage_Release(&object_1); + metisMessage_Release(&object_2); + metisMessage_Release(&interestByNameObjectHash); +} + +LONGBOW_TEST_CASE(Global, metisLRUContentStore_Remove_Content) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + size_t capacity = 10; + MetisContentStoreInterface *store = _createLRUContentStore(capacity); + + 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); + + metisMessage_SetExpiryTimeTicks(object_1, 200); + metisMessage_SetRecommendedCacheTimeTicks(object_1, 100); + + metisContentStoreInterface_PutContent(store, object_1, 10); + metisContentStoreInterface_PutContent(store, object_2, 10); + + assertTrue(metisContentStoreInterface_GetObjectCount(store) == 2, "Expected 2 objects in the metisContentStoreInterface_"); + + 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 = metisContentStoreInterface_MatchInterest(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); + + // Now remove it. + metisContentStoreInterface_RemoveContent(store, object_1); // Releases the contained Message + + assertTrue(metisContentStoreInterface_GetObjectCount(store) == 1, "Expected 1 object in the store, got %zu", + metisContentStoreInterface_GetObjectCount(store)); + + MetisMessage *nullObject = metisContentStoreInterface_MatchInterest(store, interestByNameObjectHash); + + assertNull(nullObject, "Fetch found a match when it should not have"); + + metisContentStoreInterface_Release(&store); + metisLogger_Release(&logger); + metisMessage_Release(&object_1); + metisMessage_Release(&object_2); + metisMessage_Release(&interestByNameObjectHash); +} + +LONGBOW_TEST_CASE(Global, metisLRUContentStore_Remove_NonExistentContent) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + size_t capacity = 10; + MetisContentStoreInterface *store = _createLRUContentStore(capacity); + + 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); + + metisContentStoreInterface_PutContent(store, object_1, 1); + + assertTrue(metisContentStoreInterface_GetObjectCount(store) == 1, "Expected 1 object in the metisContentStoreInterface_"); + + // Try to remove one that is not in the store. + bool result = metisContentStoreInterface_RemoveContent(store, object_2); // Releases the contained Message + assertFalse(result, "Expected to NOT remove object_2"); + + assertTrue(metisContentStoreInterface_GetObjectCount(store) == 1, "Expected 1 object in the metisContentStoreInterface_"); + + result = metisContentStoreInterface_RemoveContent(store, object_1); // Releases the contained Message + assertTrue(result, "Expected to remove object_1"); + + assertTrue(metisContentStoreInterface_GetObjectCount(store) == 0, "Expected 0 objects in the metisContentStoreInterface_"); + + metisContentStoreInterface_Release(&store); + metisLogger_Release(&logger); + metisMessage_Release(&object_1); + metisMessage_Release(&object_2); +} + +/* + * Create an cache and access objects to make sure the LRU is evicting the right way + */ +LONGBOW_TEST_CASE(Global, metisLRUContentStore_Fetch_Lru) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + metisLogger_SetLogLevel(logger, MetisLoggerFacility_Processor, PARCLogLevel_Debug); + parcLogReporter_Release(&reporter); + + size_t capacity = 2; + MetisContentStoreInterface *store = _createLRUContentStore(capacity); + + 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); + + metisContentStoreInterface_PutContent(store, object1, 1); + metisContentStoreInterface_PutContent(store, object2, 1); + + // 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 = metisContentStoreInterface_MatchInterest(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); + metisContentStoreInterface_PutContent(store, object3, 1); + + // object 2 should be evicted + MetisMessage *interestOtherName = metisMessage_CreateFromArray(metisTestDataV0_InterestWithOtherName, + sizeof(metisTestDataV0_InterestWithOtherName), 5, 5, logger); + MetisMessage *testEvictedObject = metisContentStoreInterface_MatchInterest(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 = metisContentStoreInterface_MatchInterest(store, interestByName); + + assertNotNull(testObject1Again, "Did not retrieve object1 from the content store"); + + metisContentStoreInterface_Release(&store); + metisLogger_Release(&logger); + metisMessage_Release(&object1); + metisMessage_Release(&object2); + metisMessage_Release(&object3); + metisMessage_Release(&interestByName); + metisMessage_Release(&interestOtherName); +} + + +LONGBOW_TEST_CASE(Global, metisLRUContentStore_Save_WithoutEviction) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + size_t capacity = 10; + MetisContentStoreInterface *store = _createLRUContentStore(capacity); + + 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); + + metisContentStoreInterface_PutContent(store, object_1, 10); + metisContentStoreInterface_PutContent(store, object_2, 10); + + _MetisLRUContentStore *internalStore = ( _MetisLRUContentStore *) metisContentStoreInterface_GetPrivateData(store); + _MetisLRUContentStoreStats *stats = &internalStore->stats; + + assertTrue(stats->countAdds == 2, "Wrong countAdds, expected %u got %" PRIu64, 2, stats->countAdds); + assertTrue(stats->countLruEvictions == 0, "Wrong countLruEvictions, expected %u got %" PRIu64, 0, stats->countLruEvictions); + assertTrue(metisLruList_Length(internalStore->lru) == 2, "Wrong metisLruList_Length, expected %u got %zu", 2, + metisLruList_Length(internalStore->lru)); + + metisContentStoreInterface_Release(&store); + metisLogger_Release(&logger); + metisMessage_Release(&object_1); + metisMessage_Release(&object_2); +} + +LONGBOW_TEST_CASE(Global, metisLRUContentStore_Save_WithEviction) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + size_t capacity = 1; + MetisContentStoreInterface *store = _createLRUContentStore(capacity); + + MetisMessage *content_1 = _createUniqueMetisMessage(logger, 1, metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), + metisTestDataV0_EncodedObject_name.offset + 4); + + MetisMessage *content_2 = _createUniqueMetisMessage(logger, 2, metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), + metisTestDataV0_EncodedObject_name.offset + 4); + + MetisMessage *content_3 = _createUniqueMetisMessage(logger, 3, metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), + metisTestDataV0_EncodedObject_name.offset + 4); + + + metisContentStoreInterface_PutContent(store, content_1, 1); + + assertTrue(metisContentStoreInterface_GetObjectCount(store) == 1, "Wrong objectCount. Expected %u, got %zu", 1, metisContentStoreInterface_GetObjectCount(store)); + + metisContentStoreInterface_PutContent(store, content_2, 1); + metisContentStoreInterface_PutContent(store, content_3, 1); + + _MetisLRUContentStore *internalStore = (_MetisLRUContentStore *) metisContentStoreInterface_GetPrivateData(store); + _MetisLRUContentStoreStats *stats = &internalStore->stats; + + // Capacity is 1, so we should never grow bigger than that. + assertTrue(metisContentStoreInterface_GetObjectCount(store) == 1, "Wrong objectCount. Expected %u, got %zu", 1, metisContentStoreInterface_GetObjectCount(store)); + + assertTrue(stats->countAdds == 3, "Wrong countAdds, expected %u got %" PRIu64, 2, + stats->countAdds); + assertTrue(stats->countLruEvictions == 2, "Wrong countLruEvictions, expected %u got %" PRIu64, 1, stats->countLruEvictions); + assertTrue(metisLruList_Length(internalStore->lru) == 1, "Wrong metisLruList_Length, expected %u got %zu", 1, + metisLruList_Length(internalStore->lru)); + + metisContentStoreInterface_Release(&store); + metisLogger_Release(&logger); + metisMessage_Release(&content_1); + metisMessage_Release(&content_2); + metisMessage_Release(&content_3); +} + +LONGBOW_TEST_CASE(Global, metisLRUContentStore_Save_ExpiredContent) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + size_t capacity = 1; + MetisContentStoreInterface *store = _createLRUContentStore(capacity); + + 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); + + metisMessage_SetRecommendedCacheTimeTicks(object_1, 50); + + assertFalse(metisContentStoreInterface_PutContent(store, object_1, 51), "Should not be able to insert content past its recommended cache time"); + assertTrue(metisContentStoreInterface_GetObjectCount(store) == 0, "Wrong objectCount. Expected 0, got %zu", metisContentStoreInterface_GetObjectCount(store)); + + metisMessage_SetExpiryTimeTicks(object_2, 100); + + assertFalse(metisContentStoreInterface_PutContent(store, object_2, 101), "Should not be able to insert content past its expiry time"); + assertTrue(metisContentStoreInterface_GetObjectCount(store) == 0, "Wrong objectCount. Expected 0, got %zu", metisContentStoreInterface_GetObjectCount(store)); + + metisContentStoreInterface_Release(&store); + metisLogger_Release(&logger); + metisMessage_Release(&object_1); + metisMessage_Release(&object_2); +} + + +LONGBOW_TEST_CASE(Global, metisLRUContentStore_Save_WithEvictionByExpiryTime) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + size_t capacity = 1; + MetisContentStoreInterface *store = _createLRUContentStore(capacity); + + 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); + + uint64_t currentMetisTime = 150; + uint64_t expiryTime = 200; + + metisMessage_SetExpiryTimeTicks(object_1, expiryTime); + + // This should add the object, as currentMetisTime is less than expiry or RCT. + metisContentStoreInterface_PutContent(store, object_1, currentMetisTime); + assertTrue(metisContentStoreInterface_GetObjectCount(store) == 1, "Wrong objectCount. Expected 1, got %zu", metisContentStoreInterface_GetObjectCount(store)); + + // No expiry time. + metisContentStoreInterface_PutContent(store, object_2, expiryTime + 10); // Add this one after expiration of first one. + + _MetisLRUContentStore *internalStore = (_MetisLRUContentStore *) metisContentStoreInterface_GetPrivateData(store); + _MetisLRUContentStoreStats *stats = &internalStore->stats; + + // Capacity is 1, so we should never grow bigger than that. + assertTrue(metisContentStoreInterface_GetObjectCount(store) == 1, "Wrong objectCount. Expected 1, got %zu", metisContentStoreInterface_GetObjectCount(store)); + + assertTrue(stats->countAdds == 2, "Wrong countAdds, expected %u got %" PRIu64, 2, + stats->countAdds); + assertTrue(stats->countExpiryEvictions == 1, "Wrong countLruEvictions, expected %u got %" PRIu64, 1, stats->countLruEvictions); + assertTrue(metisLruList_Length(internalStore->lru) == 1, "Wrong metisLruList_Length, expected 1 got %zu", + metisLruList_Length(internalStore->lru)); + assertTrue(metisContentStoreInterface_GetObjectCount(store) == 1, "Expected a store count of 1"); + + metisContentStoreInterface_Release(&store); + metisLogger_Release(&logger); + metisMessage_Release(&object_1); + metisMessage_Release(&object_2); +} + +LONGBOW_TEST_CASE(Global, metisLRUContentStore_Save_WithEvictionByRCT) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + size_t capacity = 1; + MetisContentStoreInterface *store = _createLRUContentStore(capacity); + + 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); + + + uint64_t recommendedCacheTime = 1000; + + metisMessage_SetRecommendedCacheTimeTicks(object_1, recommendedCacheTime); + metisContentStoreInterface_PutContent(store, object_1, recommendedCacheTime - 100); // Add it. + + assertTrue(metisContentStoreInterface_GetObjectCount(store) == 1, "Wrong objectCount. Expected 1, got %zu", + metisContentStoreInterface_GetObjectCount(store)); + + metisContentStoreInterface_PutContent(store, object_2, recommendedCacheTime + 1); // Add this one after the first one's RCT. + + _MetisLRUContentStore *internalStore = (_MetisLRUContentStore *) metisContentStoreInterface_GetPrivateData(store); + _MetisLRUContentStoreStats *stats = &internalStore->stats; + + // Capacity is 1, so we should never grow bigger than that. + assertTrue(metisContentStoreInterface_GetObjectCount(store) == 1, "Wrong objectCount. Expected 1, got %zu", + metisContentStoreInterface_GetObjectCount(store)); + + assertTrue(stats->countAdds == 2, "Wrong countAdds, expected %u got %" PRIu64, 2, + stats->countAdds); + assertTrue(stats->countExpiryEvictions == 0, "Wrong countLruEvictions, expected 1got %" PRIu64, stats->countLruEvictions); + assertTrue(stats->countRCTEvictions == 1, "Wrong countLruEvictions, expected 1 got %" PRIu64, stats->countLruEvictions); + + assertTrue(metisLruList_Length(internalStore->lru) == 1, "Wrong metisLruList_Length, expected 1 got %zu", metisLruList_Length(internalStore->lru)); + assertTrue(metisContentStoreInterface_GetObjectCount(store) == 1, "Expected a store count of 1"); + + metisContentStoreInterface_Release(&store); + metisLogger_Release(&logger); + metisMessage_Release(&object_1); + metisMessage_Release(&object_2); +} + +LONGBOW_TEST_CASE(Global, metisLRUContentStore_Save_ZeroCapacity) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + size_t capacity = 0; + MetisContentStoreInterface *store = _createLRUContentStore(capacity); + + MetisMessage *object_1 = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, + sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + bool success = metisContentStoreInterface_PutContent(store, object_1, 1); + assertFalse(success, "Should have returned failure with 0 capacity object store saving something"); + + metisMessage_Release(&object_1); + metisContentStoreInterface_Release(&store); + metisLogger_Release(&logger); +} + + +LONGBOW_TEST_CASE(Global, metisLRUContentStore_Save_CapacityLimit) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + size_t capacity = 5; + MetisContentStoreInterface *store = _createLRUContentStore(capacity); + + for (int i = 1; i < capacity * 2; i++) { + int offsetOfNameInEncodedObject = metisTestDataV0_EncodedObject_name.offset + 4; + + MetisMessage *object = _createUniqueMetisMessage(logger, i, + metisTestDataV0_EncodedObject, + sizeof(metisTestDataV0_EncodedObject), + offsetOfNameInEncodedObject); + + bool success = metisContentStoreInterface_PutContent(store, object, 1); + + assertTrue(success, "Unexpectedly failed to add entry to ContentStore"); + + if (i < metisContentStoreInterface_GetObjectCapacity(store)) { + assertTrue(metisContentStoreInterface_GetObjectCount(store) == i, "Unexpected value for metisContentStoreInterface_objectCount"); + } else { + assertTrue(metisContentStoreInterface_GetObjectCount(store) == metisContentStoreInterface_GetObjectCapacity(store), + "Unexpected value (%zu) for metisContentStoreInterface_objectCount (%zu)", + metisContentStoreInterface_GetObjectCount(store), metisContentStoreInterface_GetObjectCapacity(store)); + } + + if (success) { + metisMessage_Release(&object); + } + } + metisLogger_Release(&logger); + metisContentStoreInterface_Release(&store); +} + +LONGBOW_TEST_CASE(Global, metisLRUContentStore_Save_DuplicateHash) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + size_t capacity = 5; + MetisContentStoreInterface *store = _createLRUContentStore(capacity); + + MetisMessage *object_1 = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, + sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + + + bool success = metisContentStoreInterface_PutContent(store, object_1, 1); + assertTrue(success, "Expected to add object_1 to store"); + + for (int i = 0; i < 10; i++) { + MetisMessage *object_1_dup = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, + sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + + success = metisContentStoreInterface_PutContent(store, object_1_dup, 1l); + + assertFalse(success, "Unexpectedly added duplicated entry to ContentStore"); + + assertTrue(metisContentStoreInterface_GetObjectCount(store) == 1, "ObjectCount should be 1"); + + metisMessage_Release(&object_1_dup); + } + + metisMessage_Release(&object_1); + metisContentStoreInterface_Release(&store); + metisLogger_Release(&logger); +} + +// ============================================================================ + +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_LRUContentStore); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/content_store/test/test_metis_LruList.c b/metis/ccnx/forwarder/metis/content_store/test/test_metis_LruList.c new file mode 100644 index 00000000..ef01b532 --- /dev/null +++ b/metis/ccnx/forwarder/metis/content_store/test/test_metis_LruList.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 "../metis_LruList.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +LONGBOW_TEST_RUNNER(metis_LruList) +{ + // 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); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_LruList) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_LruList) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ============================================================================ + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisLruList_Create_Destroy); + + LONGBOW_RUN_TEST_CASE(Global, MetisLruListEntry_Destroy); + LONGBOW_RUN_TEST_CASE(Global, metisLruEntry_GetData); + LONGBOW_RUN_TEST_CASE(Global, metisLruEntry_MoveToHead); + + LONGBOW_RUN_TEST_CASE(Global, metisLruList_NewHeadEntry); + LONGBOW_RUN_TEST_CASE(Global, metisLruList_PopTail); + LONGBOW_RUN_TEST_CASE(Global, MetisLruList_Length); +} + +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, MetisLruListEntry_Destroy) +{ + MetisLruList *lru = metisLruList_Create(); + + size_t beforeMemory = parcMemory_Outstanding(); + MetisLruListEntry *entry = (MetisLruListEntry *) metisLruList_NewHeadEntry(lru, (void *) 0x02); + metisLruList_PopTail(lru); + metisLruList_EntryDestroy(&entry); + size_t afterMemory = parcMemory_Outstanding(); + + metisLruList_Destroy(&lru); + + assertTrue(afterMemory == beforeMemory, "Memory imbalance for LruEntry_Destroy, expected %zu got %zu", beforeMemory, afterMemory); +} + +LONGBOW_TEST_CASE(Global, metisLruEntry_GetData) +{ + void *data = (void *) 99; + MetisLruList *lru = metisLruList_Create(); + MetisLruListEntry *entry = (MetisLruListEntry *) metisLruList_NewHeadEntry(lru, data); + void *p = metisLruList_EntryGetData(entry); + + assertTrue(p == data, "Data did not match, expected %p, got %p", data, p); + metisLruList_Destroy(&lru); +} + +LONGBOW_TEST_CASE(Global, metisLruEntry_MoveToHead) +{ + int loops = 10; + + MetisLruList *lru = metisLruList_Create(); + + for (size_t i = 1; i <= loops; i++) { + metisLruList_NewHeadEntry(lru, (void *) i); + } + + MetisLruListEntry *tail = metisLruList_PopTail(lru); + metisLruList_EntryMoveToHead(tail); + + MetisLruListEntry *test = TAILQ_FIRST(&lru->head); + assertTrue(test == tail, "Head element not moved, expected %p got %p", (void *) tail, (void *) test); + + metisLruList_Destroy(&lru); +} + +LONGBOW_TEST_CASE(Global, metisLruList_Create_Destroy) +{ + size_t baselineMemory = parcMemory_Outstanding(); + + MetisLruList *lru = metisLruList_Create(); + metisLruList_Destroy(&lru); + + assertTrue(parcMemory_Outstanding() == baselineMemory, "Memory imbalance on create/destroy: %u", parcMemory_Outstanding()); +} + +LONGBOW_TEST_CASE(Global, metisLruList_Length) +{ + int loops = 10; + + MetisLruList *lru = metisLruList_Create(); + + for (size_t i = 1; i <= loops; i++) { + MetisLruListEntry *entry = (MetisLruListEntry *) metisLruList_NewHeadEntry(lru, (void *) i); + assertTrue(lru->itemsInList == i, "Incorrect list length element %zu, expected %zu got %zu", i, i, lru->itemsInList); + assertTrue(metisLruList_Length(lru) == i, "Incorrect length encountered"); + + MetisLruListEntry *test = TAILQ_FIRST(&lru->head); + assertTrue(test == entry, "Head element not new entry, expected %p got %p", (void *) entry, (void *) test); + } + metisLruList_Destroy(&lru); +} + +LONGBOW_TEST_CASE(Global, metisLruList_NewHeadEntry) +{ + int loops = 10; + + MetisLruList *lru = metisLruList_Create(); + + for (size_t i = 1; i <= loops; i++) { + MetisLruListEntry *entry = (MetisLruListEntry *) metisLruList_NewHeadEntry(lru, (void *) i); + assertTrue(lru->itemsInList == i, "Incorrect list length element %zu, expected %zu got %zu", i, i, lru->itemsInList); + + MetisLruListEntry *test = TAILQ_FIRST(&lru->head); + assertTrue(test == entry, "Head element not new entry, expected %p got %p", (void *) entry, (void *) test); + } + metisLruList_Destroy(&lru); +} + +LONGBOW_TEST_CASE(Global, metisLruList_PopTail) +{ + int loops = 10; + + MetisLruList *lru = metisLruList_Create(); + + for (size_t i = 1; i <= loops; i++) { + metisLruList_NewHeadEntry(lru, (void *) i); + } + + for (size_t i = 1; i <= loops; i++) { + MetisLruListEntry *entry = metisLruList_PopTail(lru); + void *data = metisLruList_EntryGetData(entry); + assertTrue(data == (void *) i, "Got wrong data, expected %p got %p", (void *) i, (void *) data); + + metisLruList_EntryDestroy(&entry); + } + + metisLruList_Destroy(&lru); +} + + +LONGBOW_TEST_CASE(Global, MetisLruList_Length) +{ + int loops = 10; + + MetisLruList *lru = metisLruList_Create(); + + for (size_t i = 1; i <= loops; i++) { + metisLruList_NewHeadEntry(lru, (void *) i); + assertTrue(metisLruList_Length(lru) == i, "Unexpected LruList length"); + } + + metisLruList_Destroy(&lru); +} + +// ============================================================================ + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_LruList); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/content_store/test/test_metis_TimeOrderedList.c b/metis/ccnx/forwarder/metis/content_store/test/test_metis_TimeOrderedList.c new file mode 100644 index 00000000..c3a7a530 --- /dev/null +++ b/metis/ccnx/forwarder/metis/content_store/test/test_metis_TimeOrderedList.c @@ -0,0 +1,259 @@ +/* + * 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 Runner. + +#include <config.h> +#include <stdlib.h> // for rand() + +#include "../metis_TimeOrderedList.c" + +#include <LongBow/unit-test.h> + +#include <parc/algol/parc_SafeMemory.h> +#include <parc/logging/parc_LogReporterTextStdout.h> + +LONGBOW_TEST_RUNNER(metis_TimeOrderedList) +{ + srand(5150); // A fixed seed for the RNG for consistency. + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Static); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_TimeOrderedList) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_TimeOrderedList) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisTimeOrderedList_CreateRelease); + LONGBOW_RUN_TEST_CASE(Global, metisTimeOrderedList_AcquireRelease); + LONGBOW_RUN_TEST_CASE(Global, metisTimeOrderedList_AddRemove); + LONGBOW_RUN_TEST_CASE(Global, metisTimeOrderedList_GetOldest); + LONGBOW_RUN_TEST_CASE(Global, metisTimeOrderedList_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; +} + +static MetisLogger * +_createLogger() +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + return logger; +} + +LONGBOW_TEST_CASE(Global, metisTimeOrderedList_CreateRelease) +{ + MetisLogger *logger = _createLogger(); + + MetisTimeOrderedList *list = metisTimeOrderedList_Create((MetisTimeOrderList_KeyCompare *) metisContentStoreEntry_CompareExpiryTime); + MetisLruList *lruList = metisLruList_Create(); + MetisMessage *message = metisMessage_CreateFromArray((uint8_t *) "\x00" "ehlo", 5, 111, 2, logger); + + metisMessage_SetRecommendedCacheTimeTicks(message, 100); + metisMessage_SetExpiryTimeTicks(message, 200); + MetisContentStoreEntry *entry = metisContentStoreEntry_Create(message, lruList); + + metisTimeOrderedList_Add(list, entry); + + metisTimeOrderedList_Release(&list); + + metisContentStoreEntry_Release(&entry); + metisMessage_Release(&message); // We must release the message, as it's not acquired by the TimeOrderedList. + metisLruList_Destroy(&lruList); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Global, metisTimeOrderedList_AcquireRelease) +{ + MetisTimeOrderedList *list = metisTimeOrderedList_Create((MetisTimeOrderList_KeyCompare *) metisContentStoreEntry_CompareExpiryTime); + MetisTimeOrderedList *ref = metisTimeOrderedList_Acquire(list); + + assertTrue(ref == list, "Expected ref and original to be the same"); + + metisTimeOrderedList_Release(&list); + metisTimeOrderedList_Release(&ref); +} + +LONGBOW_TEST_CASE(Global, metisTimeOrderedList_AddRemove) +{ + MetisLogger *logger = _createLogger(); + MetisTimeOrderedList *list = metisTimeOrderedList_Create((MetisTimeOrderList_KeyCompare *) metisContentStoreEntry_CompareExpiryTime); + MetisLruList *lruList = metisLruList_Create(); + + size_t numEntries = 100; + MetisContentStoreEntry **contentEntryList = parcMemory_AllocateAndClear(numEntries * sizeof(MetisContentStoreEntry *)); + + for (int i = 1; i <= numEntries; i++) { + MetisMessage *message = metisMessage_CreateFromArray((uint8_t *) "\x00" "ehlo", 5, 111, i, logger); + + metisMessage_SetRecommendedCacheTimeTicks(message, i % 10); // i % 10 will ensure that there are duplicate time entries. + metisMessage_SetExpiryTimeTicks(message, i % 10); + + contentEntryList[i - 1] = metisContentStoreEntry_Create(message, lruList); + metisTimeOrderedList_Add(list, contentEntryList[i - 1]); + + assertTrue(metisTimeOrderedList_Length(list) == i, "Got wrong TimeOrderedList object count"); + } + + for (int i = 1; i <= numEntries; i++) { + MetisContentStoreEntry *contentEntry = contentEntryList[i - 1]; + metisTimeOrderedList_Remove(list, contentEntry); + MetisMessage *message = metisContentStoreEntry_GetMessage(contentEntry); + metisMessage_Release(&message); + + size_t count = metisTimeOrderedList_Length(list); + assertTrue(count == numEntries - i, "Got wrong TimeOrderedList object count"); + metisContentStoreEntry_Release(&contentEntry); + } + + parcMemory_Deallocate((void **) &contentEntryList); + metisTimeOrderedList_Release(&list); + metisLruList_Destroy(&lruList); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Global, metisTimeOrderedList_GetOldest) +{ + MetisLogger *logger = _createLogger(); + + // We're going to use the ExpiryTime as the sorting comparison for GetOldest(). + MetisTimeOrderedList *list = metisTimeOrderedList_Create((MetisTimeOrderList_KeyCompare *) metisContentStoreEntry_CompareExpiryTime); + + // Add some entries, with randomly ordered ExpiryTimes. + for (int i = 0; i < 100; i++) { + uint64_t time = (uint64_t) rand() + 1; + + MetisMessage *message = metisMessage_CreateFromArray((uint8_t *) "\x00" "ehlo", 5, 111, i, logger); + + metisMessage_SetRecommendedCacheTimeTicks(message, 100); // constant RCT + metisMessage_SetExpiryTimeTicks(message, time); // random expiry time. + + MetisContentStoreEntry *entry = metisContentStoreEntry_Create(message, NULL); + + metisTimeOrderedList_Add(list, entry); + + assertTrue(metisTimeOrderedList_Length(list) == i + 1, "Got wrong TimeOrderedList object count"); + } + + // Ensure that GetOldest() always returns the oldest. + + uint64_t lastTime = 0; + MetisContentStoreEntry *entry = NULL; + while ((entry = metisTimeOrderedList_GetOldest(list)) != NULL) { + MetisMessage *message = metisContentStoreEntry_GetMessage(entry); + uint64_t messageTime = metisMessage_GetExpiryTimeTicks(message); + + // We should always retrieve a time later than the previous time we retrieved. + assertTrue(messageTime > lastTime, "Received out of order message"); + + lastTime = messageTime; + metisTimeOrderedList_Remove(list, entry); + metisMessage_Release(&message); + metisContentStoreEntry_Release(&entry); + } + + metisTimeOrderedList_Release(&list); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Global, metisTimeOrderedList_Length) +{ + MetisLogger *logger = _createLogger(); + + MetisTimeOrderedList *list = metisTimeOrderedList_Create((MetisTimeOrderList_KeyCompare *) metisContentStoreEntry_CompareExpiryTime); + + // Add some entries with duplicate times to make sure that duplicate time stamps work. + uint64_t times[] = { 1, 2, 3, 100, 100, 100, 4, 4, 3, 2, 1, 5, 6, 7, 8, 9 }; + size_t numEntriesToMake = sizeof(times) / sizeof(uint64_t); + + for (int i = 0; i < numEntriesToMake; i++) { + MetisMessage *message = metisMessage_CreateFromArray((uint8_t *) "\x00" "ehlo", 5, 111, times[i], logger); + metisMessage_SetExpiryTimeTicks(message, times[i]); + MetisContentStoreEntry *entry = metisContentStoreEntry_Create(message, NULL); + metisTimeOrderedList_Add(list, entry); + assertTrue(metisTimeOrderedList_Length(list) == i + 1, "Got wrong TimeOrderedList object count"); + } + + // Clean up the messages we allocated. + int i = 0; + MetisContentStoreEntry *entry = NULL; + while ((entry = metisTimeOrderedList_GetOldest(list)) != NULL) { + assertTrue(metisTimeOrderedList_Length(list) == numEntriesToMake - i, "Got wrong TimeOrderedList object count"); + + MetisMessage *message = metisContentStoreEntry_GetMessage(entry); + metisTimeOrderedList_Remove(list, entry); + metisMessage_Release(&message); + metisContentStoreEntry_Release(&entry); + i++; + } + + metisTimeOrderedList_Release(&list); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_FIXTURE(Static) +{ +} + +LONGBOW_TEST_FIXTURE_SETUP(Static) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Static) +{ + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_TimeOrderedList); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} + diff --git a/metis/ccnx/forwarder/metis/core/metis_Connection.c b/metis/ccnx/forwarder/metis/core/metis_Connection.c new file mode 100644 index 00000000..c3f28e14 --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_Connection.c @@ -0,0 +1,257 @@ +/* + * 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 <limits.h> + +#include <ccnx/forwarder/metis/io/metis_IoOperations.h> +#include <ccnx/forwarder/metis/core/metis_Connection.h> +#include <ccnx/forwarder/metis/io/metis_AddressPair.h> +#include <ccnx/forwarder/metis/core/metis_Wldr.h> +#include <ccnx/forwarder/metis/core/metis_Ticks.h> +#include <parc/algol/parc_Memory.h> +#include <LongBow/runtime.h> + +struct metis_connection { + const MetisAddressPair *addressPair; + MetisIoOperations *ops; + + unsigned refCount; + + bool probing_active; + unsigned probing_interval; + unsigned counter; + MetisTicks last_sent; + MetisTicks delay; + + MetisWldr *wldr; +}; + +MetisConnection * +metisConnection_Create(MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter ops must be non-null"); + MetisConnection *conn = parcMemory_AllocateAndClear(sizeof(MetisConnection)); + assertNotNull(conn, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisConnection)); + conn->addressPair = metisIoOperations_GetAddressPair(ops); + conn->ops = ops; + conn->refCount = 1; + conn->wldr = NULL; + conn->probing_active = false; + conn->probing_interval = 0; + conn->counter = 0; + conn->last_sent = 0; + conn->delay = INT_MAX; + return conn; +} + +MetisConnection * +metisConnection_Acquire(MetisConnection *connection) +{ + assertNotNull(connection, "Parameter conn must be non-null"); + connection->refCount++; + return connection; +} + +void +metisConnection_Release(MetisConnection **connectionPtr) +{ + assertNotNull(connectionPtr, "Parameter must be non-null double pointer"); + assertNotNull(*connectionPtr, "Parameter must dereference to non-null pointer"); + MetisConnection *conn = *connectionPtr; + + assertTrue(conn->refCount > 0, "Invalid state, connection reference count should be positive, got 0."); + conn->refCount--; + if (conn->refCount == 0) { + // don't destroy addressPair, its part of ops. + metisIoOperations_Release(&conn->ops); + if (conn->wldr != NULL) { + metisWldr_Destroy(&(conn->wldr)); + } + parcMemory_Deallocate((void **) &conn); + } + *connectionPtr = NULL; +} + +bool +metisConnection_Send(const MetisConnection *conn, MetisMessage *message) +{ + assertNotNull(conn, "Parameter conn must be non-null"); + assertNotNull(message, "Parameter message must be non-null"); + + if (metisIoOperations_IsUp(conn->ops)) { + uint8_t connectionId = (uint8_t) metisConnection_GetConnectionId(conn); + metisMessage_UpdatePathLabel(message, connectionId); + if (conn->wldr != NULL) { + metisWldr_SetLabel(conn->wldr, message); + } + return metisIoOperations_Send(conn->ops, NULL, message); + } + return false; +} + + +static void +_sendProbe(MetisConnection *conn, unsigned probeType) +{ + assertNotNull(conn, "Parameter conn must be non-null"); + + if (probeType == METIS_PACKET_TYPE_PROBE_REQUEST) { + MetisTicks now = metisIoOperations_SendProbe(conn->ops, probeType); + if (now != 0) { + conn->last_sent = now; + } + } else { + metisIoOperations_SendProbe(conn->ops, probeType); + } +} + + +void +metisConnection_Probe(MetisConnection *conn) +{ + _sendProbe(conn, METIS_PACKET_TYPE_PROBE_REQUEST); +} + +void +metisConnection_HandleProbe(MetisConnection *conn, uint8_t *pkt, MetisTicks actualTime) +{ + assertNotNull(conn, "Parameter conn must be non-null"); + assertNotNull(pkt, "Parameter pkt must be non-null"); + + if (pkt[1] == METIS_PACKET_TYPE_PROBE_REQUEST) { + _sendProbe(conn, METIS_PACKET_TYPE_PROBE_REPLY); + } else if (pkt[1] == METIS_PACKET_TYPE_PROBE_REPLY) { + MetisTicks delay = actualTime - conn->last_sent; + if (delay == 0) { + delay = 1; + } + if (delay < conn->delay) { + conn->delay = delay; + } + } else { + printf("receivde unkwon probe type\n"); + } +} + +uint64_t +metisConnection_GetDelay(MetisConnection *conn) +{ + return (uint64_t) conn->delay; +} + + +MetisIoOperations * +metisConnection_GetIoOperations(const MetisConnection *conn) +{ + return conn->ops; +} + +unsigned +metisConnection_GetConnectionId(const MetisConnection *conn) +{ + assertNotNull(conn, "Parameter conn must be non-null"); + return metisIoOperations_GetConnectionId(conn->ops); +} + +const MetisAddressPair * +metisConnection_GetAddressPair(const MetisConnection *conn) +{ + assertNotNull(conn, "Parameter conn must be non-null"); + return metisIoOperations_GetAddressPair(conn->ops); +} + +bool +metisConnection_IsUp(const MetisConnection *conn) +{ + assertNotNull(conn, "Parameter conn must be non-null"); + return metisIoOperations_IsUp(conn->ops); +} + +bool +metisConnection_IsLocal(const MetisConnection *conn) +{ + assertNotNull(conn, "Parameter conn must be non-null"); + return metisIoOperations_IsLocal(conn->ops); +} + +const void * +metisConnection_Class(const MetisConnection *conn) +{ + assertNotNull(conn, "Parameter conn must be non-null"); + return metisIoOperations_Class(conn->ops); +} + +bool +metisConnection_ReSend(const MetisConnection *conn, MetisMessage *message) +{ + assertNotNull(conn, "Parameter conn must be non-null"); + assertNotNull(message, "Parameter message must be non-null"); + + if (metisConnection_IsUp(conn)) { + //here the wldr header is alreay set: this message is a retransmission or a notification + + //we don't need to update the path label. In fact the path label was already set in the first + //transmission of this packet (in metisConnection_Send). Since we are using pointers this + //message has the same path label. However it could be a good idea to remove the path label + //so that raaqm will discard this packet for the RTT estimation. + + return metisIoOperations_Send(conn->ops, NULL, message); + } + return false; +} + +void +metisConnection_EnableWldr(MetisConnection *conn) +{ + if (!metisConnection_IsLocal(conn)) { + if (conn->wldr == NULL) { + printf("----------------- enable wldr\n"); + conn->wldr = metisWldr_Init(); + } + } +} + +void +metisConnection_DisableWldr(MetisConnection *conn) +{ + if (!metisConnection_IsLocal(conn)) { + if (conn->wldr != NULL) { + printf("----------------- disable wldr\n"); + metisWldr_Destroy(&(conn->wldr)); + conn->wldr = NULL; + } + } +} + + +bool +metisConnection_HasWldr(const MetisConnection *conn) +{ + if (conn->wldr == NULL) { + return false; + } else { + return true; + } +} + +void +metisConnection_DetectLosses(MetisConnection *conn, MetisMessage *message) +{ + metisWldr_DetectLosses(conn->wldr, conn, message); +} + diff --git a/metis/ccnx/forwarder/metis/core/metis_Connection.h b/metis/ccnx/forwarder/metis/core/metis_Connection.h new file mode 100644 index 00000000..8dfa119a --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_Connection.h @@ -0,0 +1,224 @@ +/* + * 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_Connection.h + * @brief Wrapper for different types of connections + * + * A connection wraps a specific set of {@link MetisIoOperations}. Those operations + * allow for input and output. Connections get stored in the Connection Table. + * + */ + +#ifndef Metis_metis_Connection_h +#define Metis_metis_Connection_h +#include <config.h> +#include <ccnx/api/control/cpi_Address.h> +#include <ccnx/forwarder/metis/core/metis_Message.h> +#include <ccnx/forwarder/metis/io/metis_IoOperations.h> + +//packet types for probing +#define METIS_PACKET_TYPE_PROBE_REQUEST 5 +#define METIS_PACKET_TYPE_PROBE_REPLY 6 + +struct metis_connection; +typedef struct metis_connection MetisConnection; + + +/** + * Creates a connection object. + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisConnection *metisConnection_Create(MetisIoOperations *ops); + +/** + * @function metisConnection_Release + * @abstract Releases a reference count, destroying on last release + * @discussion + * Only frees the memory on the final reference count. The pointer will + * always be NULL'd. + * + * @param <#param1#> + * @return <#return#> + */ +void metisConnection_Release(MetisConnection **connectionPtr); + +/** + * @function metisConnection_Acquire + * @abstract A reference counted copy. + * @discussion + * A shallow copy, they share the same memory. + * + * @param <#param1#> + * @return <#return#> + */ +MetisConnection *metisConnection_Acquire(MetisConnection *connection); + +/** + * @function metisConnection_Send + * @abstract Sends the ccnx message on the connection + * @discussion + * + * @param <#param1#> + * @return true if message sent, false if connection not up + */ +bool metisConnection_Send(const MetisConnection *conn, MetisMessage *message); + +/** + * Return the `MetisIoOperations` instance associated with the specified `MetisConnection` instance. + * + * @param [in] connection The allocated connection + * + * @return a pointer to the MetisIoOperations instance associated by th specified connection. + * + * Example: + * @code + * { + * MetisIoOperations *ioOps = metisConnection_GetIoOperations(conn); + * } + * @endcode + */ +MetisIoOperations *metisConnection_GetIoOperations(const MetisConnection *conn); + +/** + * Returns the unique identifier of the connection + * + * Calls the underlying MetisIoOperations to fetch the connection id + * + * @param [in] connection The allocated connection + * + * @return unsigned The unique connection id + * + * Example: + * @code + * <#example#> + * @endcode + */ +unsigned metisConnection_GetConnectionId(const MetisConnection *conn); + +/** + * Returns the (remote, local) address pair that describes the connection + * + * <#Paragraphs Of Explanation#> + * + * @param [in] connection The allocated connection + * + * @return non-null The connection's remote and local address + * @return null Should never return NULL + * + * Example: + * @code + * <#example#> + * @endcode + */ +const MetisAddressPair *metisConnection_GetAddressPair(const MetisConnection *conn); + +/** + * Tests if the connection is in the "up" state + * + * <#Paragraphs Of Explanation#> + * + * @param [in] connection The allocated connection + * + * @return true The connection is in the "up" state + * @return false The connection is not in the "up" state + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisConnection_IsUp(const MetisConnection *conn); + +/** + * Tests if the connection is to a Local/Loopback address + * + * A local connection is PF_LOCAL (PF_UNIX) and a loopback connection is + * 127.0.0.0/8 or ::1 for IPv6. + * + * @param [in] connection The allocated connection + * + * @retval true The connection is local or loopback + * @retval false The connection is not local or loopback + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisConnection_IsLocal(const MetisConnection *conn); + +/** + * Returns an opaque pointer representing the class of the Io Operations + * + * Returns an opaque pointer that an implementation can use to detect if + * the connection is based on that class. + * + * @param [in] conn The MetisConnection to test + * + * @return non-null An opaque pointer for each concrete implementation + * + * Example: + * @code + * { + * bool + * metisEtherConnection_IsClass(const MetisConnection *conn) + * { + * bool result = false; + * const void *class = metisConnection_Class(conn); + * if (class == _metisIoOperationsGuid) { + * result = true; + * } + * return result; + * } + * + * MetisHopByHopFragmenter * + * metisEtherConnection_GetFragmenter(const MetisConnection *conn) + * { + * MetisHopByHopFragmenter *fragmenter = NULL; + * + * if (metisEtherConnection_IsClass(conn)) { + * MetisIoOperations *ops = metisConnection_GetIoOperations(conn); + * _MetisEtherState *state = (_MetisEtherState *) ops->context; + * fragmenter = state->fragmenter; + * } + * return fragmenter; + * } + * } + * @endcode + */ +const void *metisConnection_Class(const MetisConnection *conn); + +bool metisConnection_ReSend(const MetisConnection *conn, MetisMessage *message); + +void metisConnection_Probe(MetisConnection *conn); +void metisConnection_HandleProbe(MetisConnection *conn, uint8_t *message, MetisTicks actualTime); +uint64_t metisConnection_GetDelay(MetisConnection *conn); +void metisConnection_EnableWldr(MetisConnection *conn); +void metisConnection_DisableWldr(MetisConnection *conn); +bool metisConnection_HasWldr(const MetisConnection *conn); +void metisConnection_DetectLosses(MetisConnection *conn, MetisMessage *message); + +#endif // Metis_metis_Connection_h diff --git a/metis/ccnx/forwarder/metis/core/metis_ConnectionList.c b/metis/ccnx/forwarder/metis/core/metis_ConnectionList.c new file mode 100644 index 00000000..021a6a7d --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_ConnectionList.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 <parc/algol/parc_Memory.h> +#include <parc/algol/parc_ArrayList.h> + +#include <ccnx/forwarder/metis/core/metis_ConnectionList.h> +#include <LongBow/runtime.h> + +struct metis_connection_list { + PARCArrayList *listOfConnections; +}; + +/** + * PARCArrayList entry destroyer + */ + +static void +metisConnectionList_ArrayDestroyer(void **voidPtr) +{ + MetisConnection **entryPtr = (MetisConnection **) voidPtr; + metisConnection_Release(entryPtr); +} + +MetisConnectionList * +metisConnectionList_Create() +{ + MetisConnectionList *list = parcMemory_AllocateAndClear(sizeof(MetisConnectionList)); + assertNotNull(list, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisConnectionList)); + list->listOfConnections = parcArrayList_Create(metisConnectionList_ArrayDestroyer); + return list; +} + +void +metisConnectionList_Destroy(MetisConnectionList **listPtr) +{ + assertNotNull(listPtr, "Parameter must be non-null double pointer"); + assertNotNull(*listPtr, "Parameter must dereference to non-null pointer"); + MetisConnectionList *list = *listPtr; + parcArrayList_Destroy(&list->listOfConnections); + parcMemory_Deallocate((void **) &list); + *listPtr = NULL; +} + +void +metisConnectionList_Append(MetisConnectionList *list, MetisConnection *entry) +{ + assertNotNull(list, "Parameter list must be non-null"); + assertNotNull(entry, "Parameter entry must be non-null"); + + parcArrayList_Add(list->listOfConnections, metisConnection_Acquire(entry)); +} + +size_t +metisConnectionList_Length(const MetisConnectionList *list) +{ + assertNotNull(list, "Parameter list must be non-null"); + return parcArrayList_Size(list->listOfConnections); +} + +MetisConnection * +metisConnectionList_Get(MetisConnectionList *list, size_t index) +{ + assertNotNull(list, "Parameter list must be non-null"); + MetisConnection *original = (MetisConnection *) parcArrayList_Get(list->listOfConnections, index); + return original; +} diff --git a/metis/ccnx/forwarder/metis/core/metis_ConnectionList.h b/metis/ccnx/forwarder/metis/core/metis_ConnectionList.h new file mode 100644 index 00000000..d84b654a --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_ConnectionList.h @@ -0,0 +1,102 @@ +/* + * 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_ConnectionList.h + * @brief A typesafe list of MetisConnection objects + * + * <#Detailed Description#> + * + */ + +#ifndef Metis_metis_ConnectionList_h +#define Metis_metis_ConnectionList_h + +struct metis_connection_list; +typedef struct metis_connection_list MetisConnectionList; + +#include <ccnx/forwarder/metis/core/metis_Connection.h> + +/** + * Creates a lis of MetisConnection + * + * <#Paragraphs Of Explanation#> + * + * @return non-null An allocated list + * @return null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisConnectionList *metisConnectionList_Create(void); + +/** + * Destroys the list and all objects inside it + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisConnectionList_Destroy(MetisConnectionList **listPtr); + +/** + * @function metisConnectionList_Append + * @abstract Adds a connection entry to the list. + * @discussion + * Acquires a reference to the passed entry and stores it in the list. + * + * @param <#param1#> + * @return <#return#> + */ +void metisConnectionList_Append(MetisConnectionList *list, MetisConnection *entry); + +/** + * Returns the number of items on the list + * + * <#Paragraphs Of Explanation#> + * + * @param [in] list The allocated list to check + * + * @return number The number of items on the list + * + * Example: + * @code + * <#example#> + * @endcode + */ +size_t metisConnectionList_Length(const MetisConnectionList *list); + +/** + * @function metisConnectionList_Get + * @abstract Returns the connection entry. + * @discussion + * Caller must not destroy the returned value. If you will store the + * entry in your own data structure, you should acquire your own reference. + * Will assert if you go beyond the end of the list. + * + * @param <#param1#> + * @return <#return#> + */ +MetisConnection *metisConnectionList_Get(MetisConnectionList *list, size_t index); +#endif // Metis_metis_ConnectionList_h diff --git a/metis/ccnx/forwarder/metis/core/metis_ConnectionManager.c b/metis/ccnx/forwarder/metis/core/metis_ConnectionManager.c new file mode 100644 index 00000000..778aff01 --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_ConnectionManager.c @@ -0,0 +1,293 @@ +/* + * 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 Connection Manager sets itself up as a listener to the Messenger so it can take + * action based on system events. + * + * The Connection Manager queues and then processes in a later time slice the messages. + * + */ + +#include <config.h> +#include <stdio.h> +#include <ccnx/forwarder/metis/core/metis_ConnectionManager.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/messenger/metis_Messenger.h> +#include <ccnx/forwarder/metis/messenger/metis_MessengerRecipient.h> +#include <ccnx/forwarder/metis/messenger/metis_MissiveDeque.h> + +#include <parc/algol/parc_Memory.h> + +#include <LongBow/runtime.h> + +struct metis_connection_manager { + MetisForwarder *metis; + MetisLogger *logger; + + MetisMessengerRecipient *messengerRecipient; + + // we queue missives as they come in to process in our own + // event timeslice + MetisMissiveDeque *missiveQueue; + + // for deferred queue processing + PARCEventTimer *timerEvent; +}; + +/** + * Receives missives from the messenger, queues them, and schedules our execution + * + * We defer processing of missives to a later time slice + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +static void metisConnectionManager_MessengerCallback(MetisMessengerRecipient *recipient, MetisMissive *missive); + +/** + * Event callback + * + * This is our main run loop to process our queue of messages. It is scheduled + * in {@link metisConnectionManager_MessengerCallback} when the queue becomes non-empty. + * + * When we are called here, we have exclusive use of the system, so we will not create any message loops + * + * @param [in] fd unused, required for compliance with function prototype + * @param [in] which_event unused, required for compliance with function prototype + * @param [in] connManagerVoidPtr A void* to MetisConnectionManager + * + * Example: + * @code + * <#example#> + * @endcode + */ +static void metisConnectionManager_ProcessQueue(int fd, PARCEventType which_event, void *connManagerVoidPtr); + +/** + * Process a missive for a connection DOWN + * + * We've dequeued a missive and are now ready to take action on it + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +static void metisConnectionManager_ProcessDownMissive(MetisConnectionManager *connManager, const MetisMissive *missive); + +/** + * Process a missive for a connection UP + * + * We've dequeued a missive and are now ready to take action on it + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +static void metisConnectionManager_ProcessUpMissive(MetisConnectionManager *connManager, const MetisMissive *missive); + +static void metisConnectionManager_ProcessCreateMissive(MetisConnectionManager *connManager, const MetisMissive *missive); +static void metisConnectionManager_ProcessClosedMissive(MetisConnectionManager *connManager, const MetisMissive *missive); +static void metisConnectionManager_ProcessDestroyedMissive(MetisConnectionManager *connManager, const MetisMissive *missive); + +/** + * Send a notification up to local applications about connection state changes. + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +static void metisConnectionManager_NotifyApplications(MetisConnectionManager *connManager, const MetisMissive *missive); + +// ======================================================== +// Public API + +MetisConnectionManager * +metisConnectionManager_Create(MetisForwarder *metis) +{ + MetisConnectionManager *connManager = parcMemory_AllocateAndClear(sizeof(MetisConnectionManager)); + assertNotNull(connManager, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisConnectionManager)); + connManager->metis = metis; + connManager->missiveQueue = metisMissiveDeque_Create(); + connManager->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + + MetisMessenger *messenger = metisForwarder_GetMessenger(connManager->metis); + + // creates the timer, but does not start it + PARCEventScheduler *base = metisDispatcher_GetEventScheduler(metisForwarder_GetDispatcher(metis)); + connManager->timerEvent = parcEventTimer_Create(base, 0, metisConnectionManager_ProcessQueue, connManager); + + connManager->messengerRecipient = metisMessengerRecipient_Create(connManager, metisConnectionManager_MessengerCallback); + metisMessenger_Register(messenger, connManager->messengerRecipient); + return connManager; +} + +void +metisConnectionManager_Destroy(MetisConnectionManager **managerPtr) +{ + assertNotNull(managerPtr, "Double pointer must be non-null"); + assertNotNull(*managerPtr, "Double pointer must dereference to non-null"); + + MetisConnectionManager *connManager = *managerPtr; + + MetisMessenger *messenger = metisForwarder_GetMessenger(connManager->metis); + parcEventTimer_Destroy(&(connManager->timerEvent)); + metisMessenger_Unregister(messenger, connManager->messengerRecipient); + metisMessengerRecipient_Destroy(&connManager->messengerRecipient); + metisMissiveDeque_Release(&connManager->missiveQueue); + metisLogger_Release(&connManager->logger); + + parcMemory_Deallocate((void **) &connManager); + *managerPtr = NULL; +} + +// ======================================================== +// Internal Functions + +static void +metisConnectionManager_MessengerCallback(MetisMessengerRecipient *recipient, MetisMissive *missive) +{ + MetisConnectionManager *connManager = metisMessengerRecipient_GetRecipientContext(recipient); + + // we do not release our reference count, we store it until later + // We are called with our own reference, so we do not need to acquire the missive here. + metisMissiveDeque_Append(connManager->missiveQueue, missive); + + if (metisMissiveDeque_Size(connManager->missiveQueue) == 1) { + // When it becomes non-empty, schedule {@link metisConnectionManager_ProcessQueue} + struct timeval immediateTimeout = { 0, 0 }; + parcEventTimer_Start(connManager->timerEvent, &immediateTimeout); + } +} + +static void +metisConnectionManager_ProcessQueue(int fd, PARCEventType which_event, void *connManagerVoidPtr) +{ + MetisConnectionManager *connManager = (MetisConnectionManager *) connManagerVoidPtr; + + MetisMissive *missive; + while ((missive = metisMissiveDeque_RemoveFirst(connManager->missiveQueue)) != NULL) { + switch (metisMissive_GetType(missive)) { + case MetisMissiveType_ConnectionCreate: + metisConnectionManager_ProcessCreateMissive(connManager, missive); + break; + case MetisMissiveType_ConnectionUp: + metisConnectionManager_ProcessUpMissive(connManager, missive); + break; + case MetisMissiveType_ConnectionDown: + metisConnectionManager_ProcessDownMissive(connManager, missive); + break; + case MetisMissiveType_ConnectionClosed: + metisConnectionManager_ProcessClosedMissive(connManager, missive); + break; + case MetisMissiveType_ConnectionDestroyed: + metisConnectionManager_ProcessDestroyedMissive(connManager, missive); + break; + default: + trapUnexpectedState("Missive %p of unknown type: %d", (void *) missive, metisMissive_GetType(missive)); + } + metisMissive_Release(&missive); + } +} + +static void +metisConnectionManager_ProcessUpMissive(MetisConnectionManager *connManager, const MetisMissive *missive) +{ + metisLogger_Log(connManager->logger, MetisLoggerFacility_Core, PARCLogLevel_Debug, __func__, + "Processing UP message for connid %u", + metisMissive_GetConnectionId(missive)); + + metisConnectionManager_NotifyApplications(connManager, missive); +} + +static void +metisConnectionManager_ProcessDownMissive(MetisConnectionManager *connManager, const MetisMissive *missive) +{ + metisLogger_Log(connManager->logger, MetisLoggerFacility_Core, PARCLogLevel_Debug, __func__, + "Processing DOWN message for connid %u", + metisMissive_GetConnectionId(missive)); + + metisConnectionManager_NotifyApplications(connManager, missive); +} + +static void +metisConnectionManager_ProcessCreateMissive(MetisConnectionManager *connManager, const MetisMissive *missive) +{ + metisLogger_Log(connManager->logger, MetisLoggerFacility_Core, PARCLogLevel_Debug, __func__, + "Processing CREATE message for connid %u", + metisMissive_GetConnectionId(missive)); + + metisConnectionManager_NotifyApplications(connManager, missive); +} + +static void +metisConnectionManager_ProcessClosedMissive(MetisConnectionManager *connManager, const MetisMissive *missive) +{ + metisLogger_Log(connManager->logger, MetisLoggerFacility_Core, PARCLogLevel_Debug, __func__, + "Processing CLOSED message for connid %u", + metisMissive_GetConnectionId(missive)); + + MetisConnectionTable *table = metisForwarder_GetConnectionTable(connManager->metis); + const MetisConnection *conn = metisConnectionTable_FindById(table, metisMissive_GetConnectionId(missive)); + + if (conn) { + // this will destroy the connection if its the last reference count + metisConnectionTable_Remove(table, conn); + + // remove from FIB + metisForwarder_RemoveConnectionIdFromRoutes(connManager->metis, metisMissive_GetConnectionId(missive)); + + // finally tell apps + metisConnectionManager_NotifyApplications(connManager, missive); + } +} + +static void +metisConnectionManager_ProcessDestroyedMissive(MetisConnectionManager *connManager, const MetisMissive *missive) +{ + metisLogger_Log(connManager->logger, MetisLoggerFacility_Core, PARCLogLevel_Debug, __func__, + "Processing DESTROYED message for connid %u", + metisMissive_GetConnectionId(missive)); + + metisConnectionManager_NotifyApplications(connManager, missive); +} + + +static void +metisConnectionManager_NotifyApplications(MetisConnectionManager *connManager, const MetisMissive *missive) +{ + //TODO +} diff --git a/metis/ccnx/forwarder/metis/core/metis_ConnectionManager.h b/metis/ccnx/forwarder/metis/core/metis_ConnectionManager.h new file mode 100644 index 00000000..d4cd1f3a --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_ConnectionManager.h @@ -0,0 +1,45 @@ +/* + * 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_ConnectionManager.h + * @brief The connection manager handles connection events, such as going down + * + * The connection manager listens to the event notification system. Based on those + * events, the connection manager will take specific actions. This is expected + * to be a singleton instantiated by metis_Forwarder. + * + * METIS_CONN_UP: + * - send a notification to appropriate local applications that want to + * known when connections come up. + * + * METIS_CONN_DOWN: + * - Tear down the connection + * - Send a notification to local applications + * + */ + +#ifndef Metis_metis_ConnectionManager_h +#define Metis_metis_ConnectionManager_h + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +struct metis_connection_manager; +typedef struct metis_connection_manager MetisConnectionManager; + +MetisConnectionManager *metisConnectionManager_Create(MetisForwarder *metis); + +void metisConnectionManager_Destroy(MetisConnectionManager **managerPtr); +#endif // Metis_metis_ConnectionManager_h diff --git a/metis/ccnx/forwarder/metis/core/metis_ConnectionTable.c b/metis/ccnx/forwarder/metis/core/metis_ConnectionTable.c new file mode 100644 index 00000000..9e15b30e --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_ConnectionTable.c @@ -0,0 +1,252 @@ +/* + * 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 MetisConnectionTable + * @abstract Records all the current connections and references to them + * @discussion + * + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <LongBow/runtime.h> + +#include <ccnx/forwarder/metis/core/metis_ConnectionTable.h> +#include <ccnx/forwarder/metis/io/metis_AddressPair.h> +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_ArrayList.h> +#include <parc/algol/parc_HashCodeTable.h> +#include <parc/algol/parc_Hash.h> +#include <parc/algol/parc_TreeRedBlack.h> + +struct metis_connection_table { + // The main storage table that has a Destroy method. + // The key is an unsigned int pointer. We use an unsigned int pointer + // because we want to be able to lookup by the id alone, and not have to + // have the MetisIoOperations everywhere. + PARCHashCodeTable *storageTableById; + + // The key is a MetisAddressPair + // It does not have a destroy method for the data or key, + // as they are derived from the storage table. + PARCHashCodeTable *indexByAddressPair; + + // An iterable stucture organized by connection id. The keys and + // values are the same pointers as in storageTableById, so there + // are no destructors in the tree. + // The only reason to keep this tree is so we have an iterable list + // of connections, which the hash table does not give us. + PARCTreeRedBlack *listById; +}; + +static bool +metisConnectionTable_ConnectionIdEquals(const void *keyA, const void *keyB) +{ + unsigned idA = *((unsigned *) keyA); + unsigned idB = *((unsigned *) keyB); + return (idA == idB); +} + +static int +metisConnectionTable_ConnectionIdCompare(const void *keyA, const void *keyB) +{ + unsigned idA = *((unsigned *) keyA); + unsigned idB = *((unsigned *) keyB); + if (idA < idB) { + return -1; + } + if (idA > idB) { + return +1; + } + return 0; +} + +static bool +metisConnectionTable_AddressPairEquals(const void *keyA, const void *keyB) +{ + const MetisAddressPair *pairA = (const MetisAddressPair *) keyA; + const MetisAddressPair *pairB = (const MetisAddressPair *) keyB; + + return metisAddressPair_Equals(pairA, pairB); +} + +static HashCodeType +metisConnectionTable_ConnectionIdHashCode(const void *keyA) +{ + unsigned idA = *((unsigned *) keyA); + return parcHash32_Int32(idA); +} + +static HashCodeType +metisConnectionTable_AddressPairHashCode(const void *keyA) +{ + const MetisAddressPair *pairA = (const MetisAddressPair *) keyA; + return metisAddressPair_HashCode(pairA); +} + + +static void +metisConnectionTable_ConnectionIdDestroyer(void **dataPtr) +{ + unsigned *idA = (unsigned *) *dataPtr; + parcMemory_Deallocate((void **) &idA); + *dataPtr = NULL; +} + +static void +metisConnectionTable_ConnectionDestroyer(void **dataPtr) +{ + metisConnection_Release((MetisConnection **) dataPtr); +} + +MetisConnectionTable * +metisConnectionTable_Create() +{ + size_t initialSize = 16384; + + MetisConnectionTable *conntable = parcMemory_AllocateAndClear(sizeof(MetisConnectionTable)); + assertNotNull(conntable, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisConnectionTable)); + + conntable->storageTableById = parcHashCodeTable_Create_Size(metisConnectionTable_ConnectionIdEquals, + metisConnectionTable_ConnectionIdHashCode, + metisConnectionTable_ConnectionIdDestroyer, + metisConnectionTable_ConnectionDestroyer, + initialSize); + + // no key or data destroyer, this is an index into storageByid. + conntable->indexByAddressPair = parcHashCodeTable_Create_Size(metisConnectionTable_AddressPairEquals, + metisConnectionTable_AddressPairHashCode, + NULL, + NULL, + initialSize); + + conntable->listById = parcTreeRedBlack_Create(metisConnectionTable_ConnectionIdCompare, + NULL, // key free + NULL, // key copy + NULL, // value equals + NULL, // value free + NULL); // value copy + + return conntable; +} + + +void +metisConnectionTable_Destroy(MetisConnectionTable **conntablePtr) +{ + assertNotNull(conntablePtr, "Parameter must be non-null double pointer"); + assertNotNull(*conntablePtr, "Parameter must dereference to non-null pointer"); + + MetisConnectionTable *conntable = *conntablePtr; + + parcTreeRedBlack_Destroy(&conntable->listById); + parcHashCodeTable_Destroy(&conntable->indexByAddressPair); + parcHashCodeTable_Destroy(&conntable->storageTableById); + parcMemory_Deallocate((void **) &conntable); + *conntablePtr = NULL; +} + +/** + * @function metisConnectionTable_Add + * @abstract Add a connection, takes ownership of memory + * @discussion + * <#Discussion#> + * + * @param <#param1#> + */ +void +metisConnectionTable_Add(MetisConnectionTable *table, MetisConnection *connection) +{ + assertNotNull(table, "Parameter table must be non-null"); + assertNotNull(connection, "Parameter connection must be non-null"); + + unsigned *connectionIdKey = parcMemory_Allocate(sizeof(unsigned)); + assertNotNull(connectionIdKey, "parcMemory_Allocate(%zu) returned NULL", sizeof(unsigned)); + *connectionIdKey = metisConnection_GetConnectionId(connection); + + if (parcHashCodeTable_Add(table->storageTableById, connectionIdKey, connection)) { + parcHashCodeTable_Add(table->indexByAddressPair, (void *) metisConnection_GetAddressPair(connection), connection); + parcTreeRedBlack_Insert(table->listById, connectionIdKey, connection); + } else { + trapUnexpectedState("Could not add connection id %u -- is it a duplicate?", *connectionIdKey); + } +} + +/** + * @function metisConnectionTable_Remove + * @abstract Removes the connection, calling Destroy on our copy + * @discussion + * <#Discussion#> + * + * @param <#param1#> + */ +void +metisConnectionTable_Remove(MetisConnectionTable *table, const MetisConnection *connection) +{ + assertNotNull(table, "Parameter table must be non-null"); + assertNotNull(connection, "Parameter connection must be non-null"); + + unsigned connid = metisConnection_GetConnectionId(connection); + + parcTreeRedBlack_Remove(table->listById, &connid); + parcHashCodeTable_Del(table->indexByAddressPair, metisConnection_GetAddressPair(connection)); + parcHashCodeTable_Del(table->storageTableById, &connid); +} + +void +metisConnectionTable_RemoveById(MetisConnectionTable *table, unsigned id) +{ + assertNotNull(table, "Parameter table must be non-null"); + const MetisConnection *connection = metisConnectionTable_FindById(table, id); + if (connection) { + metisConnectionTable_Remove(table, connection); + } +} + +const MetisConnection * +metisConnectionTable_FindByAddressPair(MetisConnectionTable *table, const MetisAddressPair *pair) +{ + assertNotNull(table, "Parameter table must be non-null"); + return (MetisConnection *) parcHashCodeTable_Get(table->indexByAddressPair, pair); +} + +const MetisConnection * +metisConnectionTable_FindById(MetisConnectionTable *table, unsigned id) +{ + assertNotNull(table, "Parameter table must be non-null"); + return (MetisConnection *) parcHashCodeTable_Get(table->storageTableById, &id); +} + + +MetisConnectionList * +metisConnectionTable_GetEntries(const MetisConnectionTable *table) +{ + assertNotNull(table, "Parameter table must be non-null"); + MetisConnectionList *list = metisConnectionList_Create(); + + PARCArrayList *values = parcTreeRedBlack_Values(table->listById); + for (size_t i = 0; i < parcArrayList_Size(values); i++) { + MetisConnection *original = parcArrayList_Get(values, i); + metisConnectionList_Append(list, original); + } + parcArrayList_Destroy(&values); + return list; +} diff --git a/metis/ccnx/forwarder/metis/core/metis_ConnectionTable.h b/metis/ccnx/forwarder/metis/core/metis_ConnectionTable.h new file mode 100644 index 00000000..1f47dafd --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_ConnectionTable.h @@ -0,0 +1,134 @@ +/* + * 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_ConnectionTable_h +#define Metis_metis_ConnectionTable_h + +#include <ccnx/forwarder/metis/core/metis_Connection.h> +#include <ccnx/forwarder/metis/core/metis_ConnectionList.h> +#include <ccnx/forwarder/metis/io/metis_IoOperations.h> +#include <ccnx/forwarder/metis/io/metis_AddressPair.h> + +struct metis_connection_table; +typedef struct metis_connection_table MetisConnectionTable; + +/** + * Creates an empty connection table + * + * <#Paragraphs Of Explanation#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisConnectionTable *metisConnectionTable_Create(void); + +/** + * Destroys the connection table + * + * This will release the reference to all connections stored in the connection table. + * + * @param [in,out] conntablePtr Pointer to the allocated connection table, will be NULL'd + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisConnectionTable_Destroy(MetisConnectionTable **conntablePtr); + +/** + * @function metisConnectionTable_Add + * @abstract Add a connection, takes ownership of memory + * @discussion + * <#Discussion#> + * + * @param <#param1#> + */ +void metisConnectionTable_Add(MetisConnectionTable *table, MetisConnection *connection); + +/** + * @function metisConnectionTable_Remove + * @abstract Removes the connection, calling Destroy on our copy + * @discussion + * <#Discussion#> + * + * @param <#param1#> + */ +void metisConnectionTable_Remove(MetisConnectionTable *table, const MetisConnection *connection); + +/** + * Removes a connection from the connection table + * + * Looks up a connection by its connection ID and removes it from the connection table. + * Removing the connection will call metisConnection_Release() on the connection object. + * + * @param [in] table The allocated connection table + * @param [in] id The connection ID + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisConnectionTable_RemoveById(MetisConnectionTable *table, unsigned id); + +/** + * Lookup a connection by the (local, remote) addres pair + * + * <#Paragraphs Of Explanation#> + * + * @param [in] table The allocated connection table + * @param [in] pair The address pair to match, based on the inner values of the local and remote addresses + * + * @retval non-null The matched conneciton + * @retval null No match found or error + * + * Example: + * @code + * <#example#> + * @endcode + */ +const MetisConnection *metisConnectionTable_FindByAddressPair(MetisConnectionTable *table, const MetisAddressPair *pair); + +/** + * @function metisConnectionTable_FindById + * @abstract Find a connection by its numeric id. + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return NULL if not found + */ +const MetisConnection *metisConnectionTable_FindById(MetisConnectionTable *table, unsigned id); + +/** + * @function metisConnectionTable_GetEntries + * @abstract Returns a list of connections. They are reference counted copies from the table. + * @discussion + * An allocated list of connections in the table. Each list entry is a reference counted + * copy of the connection in the table, thus they are "live" objects. + * + * @param <#param1#> + * @return An allocated list, which you must destroy + */ +MetisConnectionList *metisConnectionTable_GetEntries(const MetisConnectionTable *table); +#endif // Metis_metis_ConnectionTable_h diff --git a/metis/ccnx/forwarder/metis/core/metis_Dispatcher.c b/metis/ccnx/forwarder/metis/core/metis_Dispatcher.c new file mode 100644 index 00000000..cf7283da --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_Dispatcher.c @@ -0,0 +1,425 @@ +/* + * 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_Dispatch.c + * @abstract Event dispatcher for Metis. Uses parcEvent + * @discussion + * Wraps the functions we use in parcEvent, along with mets_StreamBuffer and metis_Message. + * The dispatcher is the event loop, so it manages things like signals, timers, and network events. + * + */ + +#include <config.h> +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <time.h> +#include <signal.h> +#include <string.h> +#include <errno.h> +#include <pthread.h> + +#include <arpa/inet.h> +#include <sys/socket.h> + +#include <parc/algol/parc_EventTimer.h> +#include <parc/algol/parc_EventQueue.h> + +#include <LongBow/runtime.h> + +#include <ccnx/forwarder/metis/core/metis_Dispatcher.h> + +#ifndef INPORT_ANY +#define INPORT_ANY 0 +#endif + +struct metis_dispatcher { + PARCEventScheduler *Base; + MetisLogger *logger; +}; + +// ========================================== +// Public API + +PARCEventScheduler * +metisDispatcher_GetEventScheduler(MetisDispatcher *dispatcher) +{ + return dispatcher->Base; +} + +MetisDispatcher * +metisDispatcher_Create(MetisLogger *logger) +{ + MetisDispatcher *dispatcher = parcMemory_AllocateAndClear(sizeof(MetisDispatcher)); + assertNotNull(dispatcher, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisDispatcher)); + + dispatcher->Base = parcEventScheduler_Create(); + dispatcher->logger = metisLogger_Acquire(logger); + + assertNotNull(dispatcher->Base, "Got NULL from parcEventScheduler_Create()"); + + return dispatcher; +} + +void +metisDispatcher_Destroy(MetisDispatcher **dispatcherPtr) +{ + assertNotNull(dispatcherPtr, "Parameter must be non-null double pointer"); + assertNotNull(*dispatcherPtr, "Parameter must dereference to non-null pointer"); + MetisDispatcher *dispatcher = *dispatcherPtr; + + metisLogger_Release(&dispatcher->logger); + parcEventScheduler_Destroy(&(dispatcher->Base)); + parcMemory_Deallocate((void **) &dispatcher); + *dispatcherPtr = NULL; +} + +void +metisDispatcher_Stop(MetisDispatcher *dispatcher) +{ + struct timeval delay = { 0, 1000 }; + + parcEventScheduler_Stop(dispatcher->Base, &delay); +} + +void +metisDispatcher_Run(MetisDispatcher *dispatcher) +{ + assertNotNull(dispatcher, "Parameter must be non-null"); + + parcEventScheduler_Start(dispatcher->Base, 0); +} + + +void +metisDispatcher_RunDuration(MetisDispatcher *dispatcher, struct timeval *duration) +{ + assertNotNull(dispatcher, "Parameter dispatcher must be non-null"); + assertNotNull(duration, "Parameter duration must be non-null"); + + parcEventScheduler_Stop(dispatcher->Base, duration); + parcEventScheduler_Start(dispatcher->Base, 0); +} + + +void +metisDispatcher_RunCount(MetisDispatcher *dispatcher, unsigned count) +{ + assertNotNull(dispatcher, "Parameter must be non-null"); + + for (unsigned i = 0; i < count; i++) { + parcEventScheduler_Start(dispatcher->Base, PARCEventSchedulerDispatchType_LoopOnce); + } +} + +PARCEventSocket * +metisDispatcher_CreateListener(MetisDispatcher *dispatcher, PARCEventSocket_Callback *callback, void *user_data, int backlog, const struct sockaddr *sa, int socklen) +{ + PARCEventSocket *listener = parcEventSocket_Create(dispatcher->Base, callback, NULL, user_data, sa, socklen); + if (listener == NULL) { + perror("Problem creating listener"); + } + return listener; +} + +void +metisDispatcher_DestroyListener(MetisDispatcher *dispatcher, PARCEventSocket **listenerPtr) +{ + assertNotNull(listenerPtr, "Parameter must be non-null double pointer"); + assertNotNull(*listenerPtr, "Parameter must dereference to non-null pointer"); + parcEventSocket_Destroy(listenerPtr); +} + +PARCEventQueue * +metisDispatcher_CreateStreamBufferFromSocket(MetisDispatcher *dispatcher, MetisSocketType fd) +{ + assertNotNull(dispatcher, "Parameter dispatcher must be non-null"); + PARCEventQueue *buffer = parcEventQueue_Create(dispatcher->Base, fd, PARCEventQueueOption_CloseOnFree | PARCEventQueueOption_DeferCallbacks); + assertNotNull(buffer, "Got null from parcEventBufver_Create for socket %d", fd); + return buffer; +} + +PARCEventTimer * +metisDispatcher_CreateTimer(MetisDispatcher *dispatcher, bool isPeriodic, PARCEvent_Callback *callback, void *userData) +{ + assertNotNull(dispatcher, "Parameter dispatcher must be non-null"); + assertNotNull(callback, "Parameter callback must be non-null"); + + PARCEventType flags = 0; + if (isPeriodic) { + flags |= PARCEventType_Persist; + } + PARCEventTimer *event = parcEventTimer_Create(dispatcher->Base, flags, callback, userData); + return event; +} + +void +metisDispatcher_StartTimer(MetisDispatcher *dispatcher, PARCEventTimer *timerEvent, struct timeval *delay) +{ + assertNotNull(dispatcher, "Parameter dispatcher must be non-null"); + assertNotNull(timerEvent, "Parameter timerEvent must be non-null"); + int failure = parcEventTimer_Start(timerEvent, delay); + assertFalse(failure < 0, "Error starting timer event %p: (%d) %s", (void *) timerEvent, errno, strerror(errno)); +} + +void +metisDispatcher_StopTimer(MetisDispatcher *dispatcher, PARCEventTimer *event) +{ + assertNotNull(dispatcher, "Parameter dispatcher must be non-null"); + assertNotNull(event, "Parameter event must be non-null"); + + int failure = parcEventTimer_Stop(event); + assertFalse(failure < 0, "Error stopping signal event %p: (%d) %s", (void *) event, errno, strerror(errno)); +} + +void +metisDispatcher_DestroyTimerEvent(MetisDispatcher *dispatcher, PARCEventTimer **eventPtr) +{ + assertNotNull(dispatcher, "Parameter dispatcher must be non-null"); + assertNotNull(eventPtr, "Parameter eventPtr must be non-null double pointer"); + assertNotNull(*eventPtr, "Paramter eventPtr must dereference to non-null pointer"); + + parcEventTimer_Destroy(eventPtr); + eventPtr = NULL; +} + +PARCEvent * +metisDispatcher_CreateNetworkEvent(MetisDispatcher *dispatcher, bool isPersistent, PARCEvent_Callback *callback, void *userData, int fd) +{ + short flags = PARCEventType_Timeout | PARCEventType_Read; + if (isPersistent) { + flags |= PARCEventType_Persist; + } + + PARCEvent *event = parcEvent_Create(dispatcher->Base, fd, flags, callback, userData); + assertNotNull(event, "Got null from parcEvent_Create for socket %d", fd); + return event; +} + +void +metisDispatcher_DestroyNetworkEvent(MetisDispatcher *dispatcher, PARCEvent **eventPtr) +{ + assertNotNull(dispatcher, "Parameter dispatcher must be non-null"); + assertNotNull(eventPtr, "Parameter eventPtr must be non-null double pointer"); + assertNotNull(*eventPtr, "Paramter eventPtr must dereference to non-null pointer"); + + parcEvent_Destroy(eventPtr); + eventPtr = NULL; +} + +void +metisDispatcher_StartNetworkEvent(MetisDispatcher *dispatcher, PARCEvent *event) +{ + assertNotNull(dispatcher, "Parameter dispatcher must be non-null"); + assertNotNull(event, "Parameter event must be non-null"); + + int failure = parcEvent_Start(event); + assertFalse(failure < 0, "Error starting signal event %p: (%d) %s", (void *) event, errno, strerror(errno)); +} + +void +metisDispatcher_StopNetworkEvent(MetisDispatcher *dispatcher, PARCEvent *event) +{ + assertNotNull(dispatcher, "Parameter dispatcher must be non-null"); + assertNotNull(event, "Parameter event must be non-null"); + + int failure = parcEvent_Stop(event); + assertFalse(failure < 0, "Error stopping signal event %p: (%d) %s", (void *) event, errno, strerror(errno)); +} + +PARCEventSignal * +metisDispatcher_CreateSignalEvent(MetisDispatcher *dispatcher, PARCEventSignal_Callback *callback, void *userData, int signal) +{ + assertNotNull(dispatcher, "Parameter dispatcher must be non-null"); + assertNotNull(callback, "Parameter callback must be non-null"); + + PARCEventSignal *event = parcEventSignal_Create(dispatcher->Base, signal, PARCEventType_Signal | PARCEventType_Persist, callback, userData); + assertNotNull(event, "Got null event when creating signal catcher for signal %d", signal); + + return event; +} + +void +metisDispatcher_DestroySignalEvent(MetisDispatcher *dispatcher, PARCEventSignal **eventPtr) +{ + assertNotNull(dispatcher, "Parameter dispatcher must be non-null"); + assertNotNull(eventPtr, "Parameter eventPtr must be non-null double pointer"); + assertNotNull(*eventPtr, "Paramter eventPtr must dereference to non-null pointer"); + + parcEventSignal_Destroy(eventPtr); + eventPtr = NULL; +} + +void +metisDispatcher_StartSignalEvent(MetisDispatcher *dispatcher, PARCEventSignal *event) +{ + assertNotNull(dispatcher, "Parameter dispatcher must be non-null"); + assertNotNull(event, "Parameter event must be non-null"); + + int failure = parcEventSignal_Start(event); + assertFalse(failure < 0, "Error starting signal event %p: (%d) %s", (void *) event, errno, strerror(errno)); +} + +void +metisDispatcher_StopSignalEvent(MetisDispatcher *dispatcher, PARCEventSignal *event) +{ + assertNotNull(dispatcher, "Parameter dispatcher must be non-null"); + assertNotNull(event, "Parameter event must be non-null"); + + int failure = parcEventSignal_Stop(event); + assertFalse(failure < 0, "Error stopping signal event %p: (%d) %s", (void *) event, errno, strerror(errno)); +} + +/** + * Bind to a local address/port then connect to peer. + */ +static bool +metisDispatcher_StreamBufferBindAndConnect(MetisDispatcher *dispatcher, PARCEventQueue *buffer, + struct sockaddr *localSock, socklen_t localSockLength, + struct sockaddr *remoteSock, socklen_t remoteSockLength) +{ + // we need to bind, then connect. Special operation, so we make our + // own fd then pass it off to the buffer event + + int fd = socket(localSock->sa_family, SOCK_STREAM, 0); + if (fd < 0) { + perror("socket"); + return -1; + } + + // Set non-blocking flag + int flags = fcntl(fd, F_GETFL, NULL); + if (flags < 0) { + perror("F_GETFL"); + close(fd); + return -1; + } + int failure = fcntl(fd, F_SETFL, flags | O_NONBLOCK); + if (failure) { + perror("F_SETFL"); + close(fd); + return -1; + } + + failure = bind(fd, localSock, localSockLength); + if (failure) { + perror("bind"); + close(fd); + return false; + } + + parcEventQueue_SetFileDescriptor(buffer, fd); + + failure = parcEventQueue_ConnectSocket(buffer, remoteSock, remoteSockLength); + if (failure && (errno != EINPROGRESS)) { + perror("connect"); + close(fd); + return false; + } + return true; +} + +/** + * Connect to an INET peer + * @return NULL on error, otherwise a streambuffer + */ +static PARCEventQueue * +metisDispatcher_StreamBufferConnect_INET(MetisDispatcher *dispatcher, const CPIAddress *localAddress, const CPIAddress *remoteAddress) +{ + struct sockaddr_in localSock, remoteSock; + cpiAddress_GetInet(localAddress, &localSock); + cpiAddress_GetInet(remoteAddress, &remoteSock); + + PARCEventQueue *buffer = parcEventQueue_Create(dispatcher->Base, -1, PARCEventQueueOption_CloseOnFree); + assertNotNull(buffer, "got null buffer from parcEventQueue_Create()"); + + bool success = metisDispatcher_StreamBufferBindAndConnect(dispatcher, buffer, + (struct sockaddr *) &localSock, sizeof(localSock), + (struct sockaddr *) &remoteSock, sizeof(remoteSock)); + if (!success) { + parcEventQueue_Destroy(&buffer); + buffer = NULL; + } + + return buffer; +} + +/** + * Connect to an INET peer + * @return NULL on error, otherwise a streambuffer + */ +static PARCEventQueue * +//static MetisStreamBuffer * +metisDispatcher_StreamBufferConnect_INET6(MetisDispatcher *dispatcher, const CPIAddress *localAddress, const CPIAddress *remoteAddress) +{ + struct sockaddr_in6 localSock, remoteSock; + cpiAddress_GetInet6(localAddress, &localSock); + cpiAddress_GetInet6(remoteAddress, &remoteSock); + + PARCEventQueue *buffer = parcEventQueue_Create(dispatcher->Base, -1, PARCEventQueueOption_CloseOnFree); + assertNotNull(buffer, "got null buffer from parcEventQueue_Create()"); + + bool success = metisDispatcher_StreamBufferBindAndConnect(dispatcher, buffer, + (struct sockaddr *) &localSock, sizeof(localSock), + (struct sockaddr *) &remoteSock, sizeof(remoteSock)); + if (!success) { + parcEventQueue_Destroy(&buffer); + buffer = NULL; + } + + return buffer; +} + +PARCEventQueue * +metisDispatcher_StreamBufferConnect(MetisDispatcher *dispatcher, const MetisAddressPair *pair) +{ + const CPIAddress *localAddress = metisAddressPair_GetLocal(pair); + const CPIAddress *remoteAddress = metisAddressPair_GetRemote(pair); + + + // they must be of the same address family + if (cpiAddress_GetType(localAddress) != cpiAddress_GetType(remoteAddress)) { + char message[2048]; + char *localAddressString = cpiAddress_ToString(localAddress); + char *remoteAddressString = cpiAddress_ToString(remoteAddress); + snprintf(message, + 2048, + "Remote address not same type as local address, expected %d got %d\nlocal %s remote %s", + cpiAddress_GetType(localAddress), + cpiAddress_GetType(remoteAddress), + localAddressString, + remoteAddressString); + + parcMemory_Deallocate((void **) &localAddressString); + parcMemory_Deallocate((void **) &remoteAddressString); + + assertTrue(cpiAddress_GetType(localAddress) == cpiAddress_GetType(remoteAddress), "%s", message); + } + + switch (cpiAddress_GetType(localAddress)) { + case cpiAddressType_INET: + return metisDispatcher_StreamBufferConnect_INET(dispatcher, localAddress, remoteAddress); + break; + case cpiAddressType_INET6: + return metisDispatcher_StreamBufferConnect_INET6(dispatcher, localAddress, remoteAddress); + break; + default: + trapIllegalValue(pair, "local address unsupported CPI address type: %d", cpiAddress_GetType(localAddress)); + } +} diff --git a/metis/ccnx/forwarder/metis/core/metis_Dispatcher.h b/metis/ccnx/forwarder/metis/core/metis_Dispatcher.h new file mode 100644 index 00000000..e936df22 --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_Dispatcher.h @@ -0,0 +1,303 @@ +/* + * 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 Dispatcher + * @abstract The dispatcher is the event loop run by MetisForwarder. + * @discussion + * These functions manage listeners, timers, and network events inside + * the event loop. + * + * Curently, it is a thin wrapper around an event so we don't have to + * expose that implementation detail to other modules. + * + */ + +#ifndef Metis_metis_Dispatcher_h +#define Metis_metis_Dispatcher_h + +#include <sys/socket.h> +#include <stdbool.h> + +struct metis_dispatcher; +typedef struct metis_dispatcher MetisDispatcher; + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_EventScheduler.h> +#include <parc/algol/parc_Event.h> +#include <parc/algol/parc_EventTimer.h> +#include <parc/algol/parc_EventSignal.h> +#include <parc/algol/parc_EventQueue.h> +#include <parc/algol/parc_EventSocket.h> + +#include <ccnx/forwarder/metis/core/metis_Logger.h> + +PARCEventScheduler *metisDispatcher_GetEventScheduler(MetisDispatcher *dispatcher); +/** + * Creates an event dispatcher + * + * Event dispatcher based on PARCEvent + * + * @return non-null Allocated event dispatcher + * @return null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisDispatcher *metisDispatcher_Create(MetisLogger *logger); + +/** + * Destroys event dispatcher + * + * Caller is responsible for destroying call events before destroying + * the event dispatcher. + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisDispatcher_Destroy(MetisDispatcher **dispatcherPtr); + +/** + * @function metisDispatcher_Stop + * @abstract Called from a different thread, tells the dispatcher to stop + * @discussion + * Called from a user thread or from an interrupt handler. + * Does not block. Use <code>metisDispatcher_WaitForStopped()</code> to + * block until stopped after calling this. + * + * @param <#param1#> + * @return <#return#> + */ +void metisDispatcher_Stop(MetisDispatcher *dispatcher); + +/** + * @function metisDispatcher_WaitForStopped + * @abstract Blocks until dispatcher in stopped state + * @discussion + * Used after <code>metisDispatcher_Stop()</code> to wait for stop. + * + * @param <#param1#> + * @return <#return#> + */ +void metisDispatcher_WaitForStopped(MetisDispatcher *dispatcher); + +/** + * @function metisDispatcher_Run + * @abstract Runs the forwarder, blocks. + * @discussion + * <#Discussion#> + * + * @param <#param1#> + */ +void metisDispatcher_Run(MetisDispatcher *dispatcher); + +/** + * @function metisDispatcher_RunDuration + * @abstract Runs forwarder for at most duration, blocks. + * @discussion + * Blocks running the forwarder for a duration. May be called + * iteratively to keep running. Duration is a minimum, actual + * runtime may be slightly longer. + * + * @param <#param1#> + */ +void metisDispatcher_RunDuration(MetisDispatcher *dispatcher, struct timeval *duration); + +/** + * @header metisDispatcher_RunCount + * @abstract Run the event loop for the given count cycles + * @discussion + * Runs the event loop for the given number of cycles, blocking + * until done. May be called sequentially over and over. + * + */ +void metisDispatcher_RunCount(MetisDispatcher *dispatcher, unsigned count); + +typedef int MetisSocketType; + +typedef struct evconnlistener MetisListener; + +/** + * @typedef MetisListenerCallback + * @abstract Callback function typedef for a stream listener + * + * @constant listener is the object created by <code>metisForwarder_NewBind()</code> that received the client connection + * @constant client_socket is the client socket + * @constant user_data is the user_data passed to <code>metisForwarder_NewBind()</code> + * @constant client_addr is the client address + * @constant socklen is the length of client_addr + * @discussion <#Discussion#> + */ +typedef void (MetisListenerCallback)(MetisListener *listener, MetisSocketType client_socket, + struct sockaddr *client_addr, int socklen, void *user_data); + +/** + * @header metisForwarder_NewBind + * @abstract Allocate a new stream listener + * @discussion + * The server socket will be freed when closed and will be reusable. + * + * @param metis the forwarder that owns the event loop + * @param cb is the callback for a new connection + * @param user_data is opaque user data passed to the callback + * @param backlog is the listen() depth, may use -1 for a default value + * @param sa is the socket address to bind to (INET, INET6, LOCAL) + * @param socklen is the sizeof the actual sockaddr (e.g. sizeof(sockaddr_un)) + */ +PARCEventSocket *metisDispatcher_CreateListener(MetisDispatcher *dispatcher, + PARCEventSocket_Callback *callback, void *user_data, + int backlog, const struct sockaddr *sa, int socklen); + +void metisDispatcher_DestroyListener(MetisDispatcher *dispatcher, PARCEventSocket **listenerPtr); + +typedef struct event MetisTimerEvent; +typedef struct event MetisNetworkEvent; +typedef struct event MetisSignalEvent; + +/** + * @typedef MetisEventCallback + * @abstract A network event or a timer callback + * @constant fd The file descriptor associated with the event, may be -1 for timers + * @constant which_event is a bitmap of the MetisEventType + * @constant user_data is the user_data passed to <code>MetisForwarder_CreateEvent()</code> + * @discussion <#Discussion#> + */ +typedef void (MetisEventCallback)(MetisSocketType fd, short which_event, void *user_data); + +/** + * @function metisDispatcher_CreateTimer + * @abstract Creates a MetisEvent for use as a timer. + * @discussion + * + * When created, the timer is idle and you need to call <code>metisForwarder_StartTimer()</code> + * + * @param isPeriodic means the timer will fire repeatidly, otherwise it is a one-shot and + * needs to be set again with <code>metisDispatcher_StartTimer()</code> + * @return <#return#> + */ +PARCEventTimer *metisDispatcher_CreateTimer(MetisDispatcher *dispatcher, bool isPeriodic, PARCEvent_Callback *callback, void *userData); + +/** + * @function metisDispatcher_StartTimer + * @abstract Starts the timer with the given delay. + * @discussion + * If the timer is periodic, it will keep firing with the given delay + * + * @param <#param1#> + * @return <#return#> + */ +void metisDispatcher_StartTimer(MetisDispatcher *dispatcher, PARCEventTimer *timerEvent, struct timeval *delay); + +void metisDispatcher_StopTimer(MetisDispatcher *dispatcher, PARCEventTimer *timerEvent); + +/** + * @function metisDispatcher_DestroyTimerEvent + * @abstract Cancels the timer and frees the event + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +void metisDispatcher_DestroyTimerEvent(MetisDispatcher *dispatcher, PARCEventTimer **eventPtr); + +/** + * @function metisDispatcher_CreateNetworkEvent + * @abstract Creates a network event callback on the socket + * @discussion + * May be used on any sort of file descriptor or socket. The event is edge triggered and non-reentrent. + * This means you need to drain the events off the socket, as the callback will not be called again + * until a new event arrives. + * + * When created, the event is idle and you need to call <code>metisForwarder_StartNetworkEvent()</code> + * + * @param isPersistent means the callback will keep firing with new events, otherwise its a one-shot + * @param fd is the socket to monitor + * @return <#return#> + */ +//MetisNetworkEvent *metisDispatcher_CreateNetworkEvent(MetisDispatcher *dispatcher, bool isPersistent, MetisEventCallback *callback, void *userData, MetisSocketType fd); +PARCEvent *metisDispatcher_CreateNetworkEvent(MetisDispatcher *dispatcher, bool isPersistent, PARCEvent_Callback *callback, void *userData, int fd); + +//void metisDispatcher_StartNetworkEvent(MetisDispatcher *dispatcher, MetisNetworkEvent *event); +//void metisDispatcher_StopNetworkEvent(MetisDispatcher *dispatcher, MetisNetworkEvent *event); +void metisDispatcher_StartNetworkEvent(MetisDispatcher *dispatcher, PARCEvent *event); +void metisDispatcher_StopNetworkEvent(MetisDispatcher *dispatcher, PARCEvent *event); + +//void metisDispatcher_DestroyNetworkEvent(MetisDispatcher *dispatcher, MetisNetworkEvent **eventPtr); +void metisDispatcher_DestroyNetworkEvent(MetisDispatcher *dispatcher, PARCEvent **eventPtr); + +/** + * @function metisDispatcher_CreateSignalEvent + * @abstract Creates a signal trap + * @discussion + * May be used on catchable signals. The event is edge triggered and non-reentrent. Signal events are persistent. + * + * When created, the signal trap is idle and you need to call <code>metisForwarder_StartSignalEvent()</code> + * + * @param signal is the system signal to monitor (e.g. SIGINT). + * @return <#return#> + */ +PARCEventSignal *metisDispatcher_CreateSignalEvent(MetisDispatcher *dispatcher, PARCEventSignal_Callback *callback, void *userData, int signal); + +void metisDispatcher_DestroySignalEvent(MetisDispatcher *dispatcher, PARCEventSignal **eventPtr); + +void metisDispatcher_StartSignalEvent(MetisDispatcher *dispatcher, PARCEventSignal *event); +void metisDispatcher_StopSignalEvent(MetisDispatcher *dispatcher, PARCEventSignal *event); + +// ============= +// stream buffers + +#include <ccnx/forwarder/metis/core/metis_StreamBuffer.h> +#include <ccnx/forwarder/metis/io/metis_AddressPair.h> + +/** + * @function metisDispatcher_CreateStreamBuffer + * @abstract Creates a high-function buffer around a stream socket + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +PARCEventQueue *metisDispatcher_CreateStreamBufferFromSocket(MetisDispatcher *dispatcher, MetisSocketType fd); + +/** + * @function metisDispatcher_StreamBufferConnect + * @abstract Create a TCP tunnel to a remote peer + * @discussion + * For TCP, both address pairs need to be the same address family: both INET or both INET6. The remote + * address must have the complete socket information (address, port). The local socket could be wildcarded or + * may specify down to the (address, port) pair. + * + * If the local address is IPADDR_ANY and the port is 0, then it is a normal call to "connect" that will use + * whatever local IP address and whatever local port for the connection. If either the address or port is + * set, the local socket will first be bound (via bind(2)), and then call connect(). + * + * It is unlikely that the buffer will be connected by the time the function returns. The eventCallback will + * fire once the remote system accepts the conneciton. + * + * @param <#param1#> + * @return NULL on error, otherwise a streambuffer. + */ +PARCEventQueue *metisDispatcher_StreamBufferConnect(MetisDispatcher *dispatcher, const MetisAddressPair *pair); +#endif // Metis_metis_Dispatcher_h diff --git a/metis/ccnx/forwarder/metis/core/metis_Forwarder.c b/metis/ccnx/forwarder/metis/core/metis_Forwarder.c new file mode 100644 index 00000000..7f3f9b8c --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_Forwarder.c @@ -0,0 +1,506 @@ +/* + * 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. + */ + +/** + * Event based router based on TLVs + * + * This module is the glue around the event scheduler. + * Its the packet i/o module. + * + * Packet processing is done in metis_Dispatcher.c, which is the actual wrapper around the event scheduler + * + * USAGE: + * + * MetisForwarder *forwarder = metisForwarder_Create(NULL); + * + * // do one of these + * metisForwarder_SetupAllListeners(forwarder); + * or + * metisForwarder_SetupFromConfigFile(forwarder, "metis.cfg"); + * + * // now run the event loop via the dispatcher + * MetisDispatcher *dispatcher = metisForwarder_GetDispatcher(); + * + * // you can call any of the Run method sequentially. + * // chose one of + * metisDispatcher_Run(dispatcher); + * metisDispatcher_RunCount(dispatcher, 100); + * metisDispatcher_RunDuration(dispatcher, &((struct timeval) {30, 0})); + * + * metisForwarder_Destroy(&forwarder); + */ + +#include <config.h> +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <time.h> +#include <signal.h> +#include <string.h> +#include <errno.h> + +#include <arpa/inet.h> +#include <sys/socket.h> + +#define __STDC_FORMAT_MACROS +#include <inttypes.h> + +#include <parc/algol/parc_ArrayList.h> +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_Object.h> +#include <parc/logging/parc_LogReporterTextStdout.h> + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_ConnectionManager.h> +#include <ccnx/forwarder/metis/core/metis_ConnectionTable.h> +#include <ccnx/forwarder/metis/core/metis_Dispatcher.h> +#include <ccnx/forwarder/metis/config/metis_Configuration.h> +#include <ccnx/forwarder/metis/config/metis_ConfigurationFile.h> +#include <ccnx/forwarder/metis/config/metis_ConfigurationListeners.h> +#include <ccnx/forwarder/metis/config/metis_CommandLineInterface.h> +#include <ccnx/forwarder/metis/config/metis_WebInterface.h> +#include <ccnx/forwarder/metis/processor/metis_MessageProcessor.h> + +#include <ccnx/forwarder/metis/core/metis_Wldr.h> + +#include <LongBow/runtime.h> + +// the router's clock frequency (we now use the monotonic clock) +#define METISHZ 1000 + +// these will all be a little off because its all integer division +#define METIS_MSEC_PER_TICK (1000 / METISHZ) +#define METIS_USEC_PER_TICK (1000000 / METISHZ) +#define METIS_NSEC_PER_TICK ((1000000000ULL) / METISHZ) +#define MSEC_TO_TICKS(msec) ((msec < FC_MSEC_PER_TICK) ? 1 : msec / FC_MSEC_PER_TICK) +#define NSEC_TO_TICKS(nsec) ((nsec < METIS_NSEC_PER_TICK) ? 1 : nsec / METIS_NSEC_PER_TICK) + + +struct metis_forwarder { + MetisDispatcher *dispatcher; + + uint16_t server_port; + + PARCEventSignal *signal_int; + PARCEventSignal *signal_term; + PARCEventSignal *signal_usr1; + PARCEventTimer *keepalive_event; + + // This is added to metisForwarder_GetTime(). Some unit tests + // will skew the virtual clock forward. In normal operaiton, it is 0. + MetisTicks clockOffset; + + unsigned nextConnectionid; + MetisMessenger *messenger; + MetisConnectionManager *connectionManager; + MetisConnectionTable *connectionTable; + MetisListenerSet *listenerSet; + MetisConfiguration *config; + + // we'll eventually want to setup a threadpool of these + MetisMessageProcessor *processor; + + MetisLogger *logger; + + PARCClock *clock; + + // used by seed48 and nrand48 + unsigned short seed[3]; +}; + +// signal traps through the event scheduler +static void _signal_cb(int, PARCEventType, void *); + +// A no-op keepalive to prevent Libevent from exiting the dispatch loop +static void _keepalive_cb(int, PARCEventType, void *); + +/** + * Reseed our pseudo-random number generator. + */ +static void +metisForwarder_Seed(MetisForwarder *metis) +{ + int fd; + ssize_t res; + + res = -1; + fd = open("/dev/urandom", O_RDONLY); + if (fd != -1) { + res = read(fd, metis->seed, sizeof(metis->seed)); + close(fd); + } + if (res != sizeof(metis->seed)) { + metis->seed[1] = (unsigned short) getpid(); /* better than no entropy */ + metis->seed[2] = (unsigned short) time(NULL); + } + /* + * The call to seed48 is needed by cygwin, and should be harmless + * on other platforms. + */ + seed48(metis->seed); +} + +MetisLogger * +metisForwarder_GetLogger(const MetisForwarder *metis) +{ + return metis->logger; +} + +// ============================================================================ +// Setup and destroy section + +MetisForwarder * +metisForwarder_Create(MetisLogger *logger) +{ + MetisForwarder *metis = parcMemory_AllocateAndClear(sizeof(MetisForwarder)); + assertNotNull(metis, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisForwarder)); + memset(metis, 0, sizeof(MetisForwarder)); + metisForwarder_Seed(metis); + + metis->clock = parcClock_Monotonic(); + metis->clockOffset = 0; + + if (logger) { + metis->logger = metisLogger_Acquire(logger); + metisLogger_SetClock(metis->logger, metis->clock); + } else { + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + metis->logger = metisLogger_Create(reporter, metis->clock); + parcLogReporter_Release(&reporter); + } + + metis->nextConnectionid = 1; + metis->dispatcher = metisDispatcher_Create(metis->logger); + metis->messenger = metisMessenger_Create(metis->dispatcher); + metis->connectionManager = metisConnectionManager_Create(metis); + metis->connectionTable = metisConnectionTable_Create(); + metis->listenerSet = metisListenerSet_Create(); + metis->config = metisConfiguration_Create(metis); + metis->processor = metisMessageProcessor_Create(metis); + + metis->signal_term = metisDispatcher_CreateSignalEvent(metis->dispatcher, _signal_cb, metis, SIGTERM); + metisDispatcher_StartSignalEvent(metis->dispatcher, metis->signal_term); + + metis->signal_int = metisDispatcher_CreateSignalEvent(metis->dispatcher, _signal_cb, metis, SIGINT); + metisDispatcher_StartSignalEvent(metis->dispatcher, metis->signal_int); + + metis->signal_usr1 = metisDispatcher_CreateSignalEvent(metis->dispatcher, _signal_cb, metis, SIGPIPE); + metisDispatcher_StartSignalEvent(metis->dispatcher, metis->signal_usr1); + + /* ignore child */ + signal(SIGCHLD, SIG_IGN); + + /* ignore tty signals */ + signal(SIGTSTP, SIG_IGN); + signal(SIGTTOU, SIG_IGN); + signal(SIGTTIN, SIG_IGN); + + // We no longer use this for ticks, but we need to have at least one event schedule + // to keep Libevent happy. + + struct timeval wtnow_timeout; + timerclear(&wtnow_timeout); + + wtnow_timeout.tv_sec = 0; + wtnow_timeout.tv_usec = 50000; // 20 Hz keepalive + + PARCEventScheduler *base = metisDispatcher_GetEventScheduler(metis->dispatcher); + metis->keepalive_event = parcEventTimer_Create(base, PARCEventType_Persist, _keepalive_cb, (void *) metis); + parcEventTimer_Start(metis->keepalive_event, &wtnow_timeout); + + return metis; +} + +void +metisForwarder_Destroy(MetisForwarder **metisPtr) +{ + assertNotNull(metisPtr, "Parameter must be non-null double pointer"); + assertNotNull(*metisPtr, "Parameter must dereference to non-null pointer"); + MetisForwarder *metis = *metisPtr; + + parcEventTimer_Destroy(&(metis->keepalive_event)); + + metisListenerSet_Destroy(&(metis->listenerSet)); + metisConnectionManager_Destroy(&(metis->connectionManager)); + metisConnectionTable_Destroy(&(metis->connectionTable)); + metisMessageProcessor_Destroy(&(metis->processor)); + metisConfiguration_Destroy(&(metis->config)); + + // the messenger is used by many of the other pieces, so destroy it last + metisMessenger_Destroy(&(metis->messenger)); + + metisDispatcher_DestroySignalEvent(metis->dispatcher, &(metis->signal_int)); + metisDispatcher_DestroySignalEvent(metis->dispatcher, &(metis->signal_term)); + metisDispatcher_DestroySignalEvent(metis->dispatcher, &(metis->signal_usr1)); + + parcClock_Release(&metis->clock); + metisLogger_Release(&metis->logger); + + // do the dispatcher last + metisDispatcher_Destroy(&(metis->dispatcher)); + + parcMemory_Deallocate((void **) &metis); + *metisPtr = NULL; +} + +void +metisForwarder_SetupAllListeners(MetisForwarder *metis, uint16_t port, const char *localPath) +{ + assertNotNull(metis, "Parameter must be non-null"); + + metisConfigurationListeners_SetupAll(metis->config, port, localPath); +} + +void +metisForwarder_SetupFromConfigFile(MetisForwarder *forwarder, const char *filename) +{ + MetisConfigurationFile *configFile = metisConfigurationFile_Create(forwarder, filename); + if (configFile) { + //metisConfigurationFile_ProcessForwardingStrategies(forwarder->config, configFile); + metisConfigurationFile_Process(configFile); + metisConfigurationFile_Release(&configFile); + } +} + +MetisConfiguration * +metisForwarder_GetConfiguration(MetisForwarder *metis) +{ + assertNotNull(metis, "Parameter must be non-null"); + return metis->config; +} + +// ============================================================================ + +unsigned +metisForwarder_GetNextConnectionId(MetisForwarder *metis) +{ + assertNotNull(metis, "Parameter must be non-null"); + return metis->nextConnectionid++; +} + +MetisMessenger * +metisForwarder_GetMessenger(MetisForwarder *metis) +{ + assertNotNull(metis, "Parameter must be non-null"); + return metis->messenger; +} + +MetisDispatcher * +metisForwarder_GetDispatcher(MetisForwarder *metis) +{ + assertNotNull(metis, "Parameter must be non-null"); + return metis->dispatcher; +} + +MetisConnectionTable * +metisForwarder_GetConnectionTable(MetisForwarder *metis) +{ + assertNotNull(metis, "Parameter must be non-null"); + return metis->connectionTable; +} + +MetisListenerSet * +metisForwarder_GetListenerSet(MetisForwarder *metis) +{ + assertNotNull(metis, "Parameter must be non-null"); + return metis->listenerSet; +} + +void +metisForwarder_SetChacheStoreFlag(MetisForwarder *metis, bool val) +{ + assertNotNull(metis, "Parameter must be non-null"); + metisMessageProcessor_SetCacheStoreFlag(metis->processor, val); +} + +bool +metisForwarder_GetChacheStoreFlag(MetisForwarder *metis) +{ + assertNotNull(metis, "Parameter must be non-null"); + return metisMessageProcessor_GetCacheStoreFlag(metis->processor); +} + +void +metisForwarder_SetChacheServeFlag(MetisForwarder *metis, bool val) +{ + assertNotNull(metis, "Parameter must be non-null"); + metisMessageProcessor_SetCacheServeFlag(metis->processor, val); +} + +bool +metisForwarder_GetChacheServeFlag(MetisForwarder *metis) +{ + assertNotNull(metis, "Parameter must be non-null"); + return metisMessageProcessor_GetCacheServeFlag(metis->processor); +} + +void +metisForwarder_Receive(MetisForwarder *metis, MetisMessage *message) +{ + assertNotNull(metis, "Parameter metis must be non-null"); + assertNotNull(message, "Parameter message must be non-null"); + + // this takes ownership of the message, so we're done here + if (metisMessage_GetType(message) == MetisMessagePacketType_Control) { + metisConfiguration_Receive(metis->config, message); + } else { + const MetisConnection *conn = metisConnectionTable_FindById(metis->connectionTable, metisMessage_GetIngressConnectionId(message)); + if (metisConnection_HasWldr(conn)) { + metisConnection_DetectLosses((MetisConnection *) conn, message); + } + if (metisMessage_HasWldr(message) && (metisMessage_GetWldrType(message) == WLDR_NOTIFICATION)) { + //this is a wldr notification packet. We can discard it + metisMessage_Release(&message); + return; + } + metisMessageProcessor_Receive(metis->processor, message); + } +} + +MetisTicks +metisForwarder_GetTicks(const MetisForwarder *metis) +{ + assertNotNull(metis, "Parameter must be non-null"); + return parcClock_GetTime(metis->clock) + metis->clockOffset; +} + +MetisTicks +metisForwarder_NanosToTicks(uint64_t nanos) +{ + return NSEC_TO_TICKS(nanos); +} + +uint64_t +metisForwarder_TicksToNanos(MetisTicks ticks) +{ + return (1000000000ULL) * ticks / METISHZ; +} + +bool +metisForwarder_AddOrUpdateRoute(MetisForwarder *metis, CPIRouteEntry *route) +{ + assertNotNull(metis, "Parameter metis must be non-null"); + assertNotNull(route, "Parameter route must be non-null"); + + // we only have one message processor + return metisMessageProcessor_AddOrUpdateRoute(metis->processor, route); +} + +bool +metisForwarder_RemoveRoute(MetisForwarder *metis, CPIRouteEntry *route) +{ + assertNotNull(metis, "Parameter metis must be non-null"); + assertNotNull(route, "Parameter route must be non-null"); + + // we only have one message processor + return metisMessageProcessor_RemoveRoute(metis->processor, route); +} + +void +metisForwarder_RemoveConnectionIdFromRoutes(MetisForwarder *metis, unsigned connectionId) +{ + assertNotNull(metis, "Parameter metis must be non-null"); + metisMessageProcessor_RemoveConnectionIdFromRoutes(metis->processor, connectionId); +} + +void +metisForwarder_SetStrategy(MetisForwarder *metis, CCNxName *prefix, const char *strategy) +{ + assertNotNull(metis, "Parameter metis must be non-null"); + assertNotNull(prefix, "Parameter route must be non-null"); + + if (strategy == NULL) { + strategy = "random"; + } + + metisProcessor_SetStrategy(metis->processor, prefix, strategy); +} + +void +metisForwarder_AddTap(MetisForwarder *metis, MetisTap *tap) +{ + metisMessageProcessor_AddTap(metis->processor, tap); +} + +void +metisForwarder_RemoveTap(MetisForwarder *metis, MetisTap *tap) +{ + metisMessageProcessor_RemoveTap(metis->processor, tap); +} + +MetisFibEntryList * +metisForwarder_GetFibEntries(MetisForwarder *metis) +{ + return metisMessageProcessor_GetFibEntries(metis->processor); +} + +void +metisForwarder_SetContentObjectStoreSize(MetisForwarder *metis, size_t maximumContentStoreSize) +{ + metisMessageProcessor_SetContentObjectStoreSize(metis->processor, maximumContentStoreSize); +} + +void +metisForwarder_ClearCache(MetisForwarder *metis) +{ + metisMessageProcessor_ClearCache(metis->processor); +} + +PARCClock * +metisForwarder_GetClock(const MetisForwarder *metis) +{ + return metis->clock; +} + +// ======================================================= + +static void +_signal_cb(int sig, PARCEventType events, void *user_data) +{ + MetisForwarder *metis = (MetisForwarder *) user_data; + + metisLogger_Log(metis->logger, MetisLoggerFacility_Core, PARCLogLevel_Warning, __func__, + "signal %d events %d", sig, events); + + switch ((int) sig) { + case SIGTERM: + metisLogger_Log(metis->logger, MetisLoggerFacility_Core, PARCLogLevel_Warning, __func__, + "Caught an terminate signal; exiting cleanly."); + metisDispatcher_Stop(metis->dispatcher); + break; + + case SIGINT: + metisLogger_Log(metis->logger, MetisLoggerFacility_Core, PARCLogLevel_Warning, __func__, + "Caught an interrupt signal; exiting cleanly."); + metisDispatcher_Stop(metis->dispatcher); + break; + + case SIGUSR1: + // dump stats + break; + + default: + break; + } +} + +static void +_keepalive_cb(int fd, PARCEventType what, void *user_data) +{ + assertTrue(what & PARCEventType_Timeout, "Got unexpected tick_cb: %d", what); + // function is just a keepalive for Metis, does not do anything +} diff --git a/metis/ccnx/forwarder/metis/core/metis_Forwarder.h b/metis/ccnx/forwarder/metis/core/metis_Forwarder.h new file mode 100644 index 00000000..b3240dc6 --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_Forwarder.h @@ -0,0 +1,340 @@ +/* + * 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 methods in this header are for the non-threaded forwarder. They should only be called + * within the forwarders thread of execution. + */ + +#ifndef Metis_metis_Forwarder_h +#define Metis_metis_Forwarder_h + +#include <sys/time.h> +#include <stdlib.h> + +#include <ccnx/api/control/cpi_RouteEntry.h> + +#include <ccnx/forwarder/metis/messenger/metis_Messenger.h> +#include <ccnx/forwarder/metis/core/metis_Dispatcher.h> +#include <ccnx/forwarder/metis/core/metis_ConnectionTable.h> + +#include <ccnx/forwarder/metis/core/metis_Message.h> +#include <ccnx/forwarder/metis/core/metis_Ticks.h> +#include <ccnx/forwarder/metis/core/metis_Logger.h> +#include <ccnx/forwarder/metis/io/metis_ListenerSet.h> + +#include <ccnx/forwarder/metis/processor/metis_FibEntryList.h> + +#include <parc/algol/parc_Clock.h> + + +#define PORT_NUMBER 9695 +#define PORT_NUMBER_AS_STRING "9695" + +// ============================================== + +struct metis_forwarder; +typedef struct metis_forwarder MetisForwarder; + +// needs to be after the definition of MetisForwarder +#include <ccnx/forwarder/metis/config/metis_Configuration.h> + +/** + * @function metisForwarder_Create + * @abstract Create the forwarder and use the provided logger for diagnostic output + * @discussion + * If the logger is null, Metis will create a STDOUT logger. + * + * @param logger may be NULL + * @return <#return#> + */ +MetisForwarder *metisForwarder_Create(MetisLogger *logger); + +/** + * @function metisForwarder_Destroy + * @abstract Destroys the forwarder, stopping all traffic and freeing all memory + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +void metisForwarder_Destroy(MetisForwarder **metisPtr); + +/** + * @function metisForwarder_SetupAllListeners + * @abstract Setup all listeners (tcp, udp, local, ether, ip multicast) on all interfaces + * @discussion + * Sets up all listeners on all running interfaces. This provides a quick and easy + * startup, rather than providing a configuration file or programmatic commands. + * + * @param port is used by TCP and UDP listeners, in host byte order + * @param localPath is the AF_UNIX path to use, if NULL no AF_UNIX listener is setup + */ +void metisForwarder_SetupAllListeners(MetisForwarder *forwarder, uint16_t port, const char *localPath); + +/** + * Configure Metis via a configuration file + * + * The configuration file is a set of lines, just like used in metis_control. + * You need to have "add listener" lines in the file to receive connections. No default + * listeners are configured. + * + * @param [in] forwarder An alloated MetisForwarder + * @param [in] filename The path to the configuration file + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisForwarder_SetupFromConfigFile(MetisForwarder *forwarder, const char *filename); + +/** + * Returns the logger used by this forwarder + * + * If you will store the logger, you should acquire a reference to it. + * + * @param [in] metis An allocated Metis forwarder + * + * @retval non-null The logger used by Metis + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisLogger *metisForwarder_GetLogger(const MetisForwarder *metis); + +/** + * @function metisForwarder_SetLogLevel + * @abstract Sets the minimum level to log + * @discussion + * <#Discussion#> + * + * @param <#param1#> + */ +void metisForwarder_SetLogLevel(MetisForwarder *metis, PARCLogLevel level); + +/** + * @function metisForwarder_GetNextConnectionId + * @abstract Get the next identifier for a new connection + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +unsigned metisForwarder_GetNextConnectionId(MetisForwarder *metis); + +MetisMessenger *metisForwarder_GetMessenger(MetisForwarder *metis); + +MetisDispatcher *metisForwarder_GetDispatcher(MetisForwarder *metis); + +/** + * Returns the set of currently active listeners + * + * <#Paragraphs Of Explanation#> + * + * @param [in] metis An allocated Metis forwarder + * + * @retval non-null The set of active listeners + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisListenerSet *metisForwarder_GetListenerSet(MetisForwarder *metis); + +/** + * Returns the forwrder's connection table + * + * <#Paragraphs Of Explanation#> + * + * @param [in] metis An allocated Metis forwarder + * + * @retval non-null The connection tabler + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisConnectionTable *metisForwarder_GetConnectionTable(MetisForwarder *metis); + +/** + * Returns a Tick-based clock + * + * Runs at approximately 1 msec per tick (see METISHZ in metis_Forwarder.c). + * Do not Release this clock. If you save a copy of it, create your own + * reference to it with parcClock_Acquire(). + * + * @param [in] metis An allocated Metis forwarder + * + * @retval non-null An allocated Metis Clock based on the Tick counter + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +PARCClock *metisForwarder_GetClock(const MetisForwarder *metis); + +/** + * Direct call to get the Tick clock + * + * Runs at approximately 1 msec per tick (see METISHZ in metis_Forwarder.c) + * + * @param [in] metis An allocated Metis forwarder + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisTicks metisForwarder_GetTicks(const MetisForwarder *metis); + +/** + * Convert nano seconds to Ticks + * + * Converts nano seconds to Ticks, based on METISHZ (in metis_Forwarder.c) + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisTicks metisForwarder_NanosToTicks(uint64_t nanos); + + +uint64_t metisForwarder_TicksToNanos(MetisTicks ticks); + +void metisForwarder_Receive(MetisForwarder *metis, MetisMessage *mesage); + +/** + * @function metisForwarder_AddOrUpdateRoute + * @abstract Adds or updates a route on all the message processors + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +bool metisForwarder_AddOrUpdateRoute(MetisForwarder *metis, CPIRouteEntry *route); + +/** + * @function metisForwarder_RemoveRoute + * @abstract Removes a route from all the message processors + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +bool metisForwarder_RemoveRoute(MetisForwarder *metis, CPIRouteEntry *route); + +/** + * Removes a connection id from all routes + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisForwarder_RemoveConnectionIdFromRoutes(MetisForwarder *metis, unsigned connectionId); + +/** + * @function metisForwarder_GetConfiguration + * @abstract The configuration object + * @discussion + * The configuration contains all user-issued commands. It does not include dynamic state. + * + * @param <#param1#> + * @return <#return#> + */ +MetisConfiguration *metisForwarder_GetConfiguration(MetisForwarder *metis); + +MetisFibEntryList *metisForwarder_GetFibEntries(MetisForwarder *metis); + +/** + * Sets the maximum number of content objects in the content store + * + * Implementation dependent - may wipe the cache. + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisForwarder_SetContentObjectStoreSize(MetisForwarder *metis, size_t maximumContentStoreSize); + +// ======================== +// Functions to manipulate the event dispatcher + +#include <ccnx/forwarder/metis/processor/metis_Tap.h> + +/** + * @function metisForwarder_AddTap + * @abstract Add a diagnostic tap to see message events. + * @discussion + * There can only be one tap at a time. The most recent add wins. + * + * @param <#param1#> + */ +void metisForwarder_AddTap(MetisForwarder *metis, MetisTap *tap); + +/** + * @function metisForwarder_RemoveTap + * @abstract Removes a message tap, no effect if it was not in effect + * @discussion + * <#Discussion#> + * + * @param <#param1#> + */ +void metisForwarder_RemoveTap(MetisForwarder *metis, MetisTap *tap); + +void metisForwarder_SetChacheStoreFlag(MetisForwarder *metis, bool val); + +bool metisForwarder_GetChacheStoreFlag(MetisForwarder *metis); + +void metisForwarder_SetChacheServeFlag(MetisForwarder *metis, bool val); + +bool metisForwarder_GetChacheServeFlag(MetisForwarder *metis); + +void metisForwarder_ClearCache(MetisForwarder *metis); + +void metisForwarder_SetStrategy(MetisForwarder *metis, CCNxName *prefix, const char *strategy); + +#endif // Metis_metis_Forwarder_h diff --git a/metis/ccnx/forwarder/metis/core/metis_Logger.c b/metis/ccnx/forwarder/metis/core/metis_Logger.c new file mode 100644 index 00000000..cb4d2beb --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_Logger.c @@ -0,0 +1,189 @@ +/* + * 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 <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <errno.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_Object.h> + +#include <parc/logging/parc_Log.h> + +#include <ccnx/forwarder/metis/core/metis_Logger.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +struct metis_logger { + PARCClock *clock; + + PARCLogReporter *reporter; + PARCLog *loggerArray[MetisLoggerFacility_END]; +}; + +static const struct facility_to_string { + MetisLoggerFacility facility; + const char *string; +} _facilityToString[] = { + { .facility = MetisLoggerFacility_Config, .string = "Config" }, + { .facility = MetisLoggerFacility_Core, .string = "Core" }, + { .facility = MetisLoggerFacility_IO, .string = "IO" }, + { .facility = MetisLoggerFacility_Message, .string = "Message" }, + { .facility = MetisLoggerFacility_Processor, .string = "Processor" }, + { .facility = 0, .string = NULL } +}; + +const char * +metisLogger_FacilityString(MetisLoggerFacility facility) +{ + for (int i = 0; _facilityToString[i].string != NULL; i++) { + if (_facilityToString[i].facility == facility) { + return _facilityToString[i].string; + } + } + return "Unknown"; +} + +static void +_allocateLoggers(MetisLogger *logger, PARCLogReporter *reporter) +{ + trapUnexpectedStateIf(logger->reporter != NULL, "Trying to allocate a reporter when the previous one is not null"); + logger->reporter = parcLogReporter_Acquire(reporter); + + char hostname[255]; + int gotHostName = gethostname(hostname, 255); + if (gotHostName < 0) { + snprintf(hostname, 255, "unknown"); + } + + for (int i = 0; i < MetisLoggerFacility_END; i++) { + logger->loggerArray[i] = parcLog_Create(hostname, metisLogger_FacilityString(i), "metis", logger->reporter); + parcLog_SetLevel(logger->loggerArray[i], PARCLogLevel_Error); + } +} + +static void +_releaseLoggers(MetisLogger *logger) +{ + for (int i = 0; i < MetisLoggerFacility_END; i++) { + parcLog_Release(&logger->loggerArray[i]); + } + parcLogReporter_Release(&logger->reporter); +} + +static void +_destroyer(MetisLogger **loggerPtr) +{ + MetisLogger *logger = *loggerPtr; + _releaseLoggers(logger); + parcClock_Release(&(*loggerPtr)->clock); +} + +parcObject_ExtendPARCObject(MetisLogger, _destroyer, NULL, NULL, NULL, NULL, NULL, NULL); + +parcObject_ImplementAcquire(metisLogger, MetisLogger); + +parcObject_ImplementRelease(metisLogger, MetisLogger); + +MetisLogger * +metisLogger_Create(PARCLogReporter *reporter, const PARCClock *clock) +{ + assertNotNull(reporter, "Parameter reporter must be non-null"); + assertNotNull(clock, "Parameter clock must be non-null"); + + MetisLogger *logger = parcObject_CreateAndClearInstance(MetisLogger); + if (logger) { + logger->clock = parcClock_Acquire(clock); + _allocateLoggers(logger, reporter); + } + + return logger; +} + +void +metisLogger_SetReporter(MetisLogger *logger, PARCLogReporter *reporter) +{ + assertNotNull(logger, "Parameter logger must be non-null"); + + // save the log level state + PARCLogLevel savedLevels[MetisLoggerFacility_END]; + for (int i = 0; i < MetisLoggerFacility_END; i++) { + savedLevels[i] = parcLog_GetLevel(logger->loggerArray[i]); + } + + _releaseLoggers(logger); + + _allocateLoggers(logger, reporter); + + // restore log level state + for (int i = 0; i < MetisLoggerFacility_END; i++) { + parcLog_SetLevel(logger->loggerArray[i], savedLevels[i]); + } +} + +void +metisLogger_SetClock(MetisLogger *logger, PARCClock *clock) +{ + assertNotNull(logger, "Parameter logger must be non-null"); + parcClock_Release(&logger->clock); + logger->clock = parcClock_Acquire(clock); +} + +static void +_assertInvariants(const MetisLogger *logger, MetisLoggerFacility facility) +{ + assertNotNull(logger, "Parameter logger must be non-null"); + trapOutOfBoundsIf(facility >= MetisLoggerFacility_END, "Invalid facility %d", facility); +} + +void +metisLogger_SetLogLevel(MetisLogger *logger, MetisLoggerFacility facility, PARCLogLevel minimumLevel) +{ + _assertInvariants(logger, facility); + PARCLog *log = logger->loggerArray[facility]; + parcLog_SetLevel(log, minimumLevel); +} + +bool +metisLogger_IsLoggable(const MetisLogger *logger, MetisLoggerFacility facility, PARCLogLevel level) +{ + _assertInvariants(logger, facility); + PARCLog *log = logger->loggerArray[facility]; + return parcLog_IsLoggable(log, level); +} + +void +metisLogger_Log(MetisLogger *logger, MetisLoggerFacility facility, PARCLogLevel level, const char *module, const char *format, ...) +{ + if (metisLogger_IsLoggable(logger, facility, level)) { + // this is logged as the messageid + uint64_t logtime = parcClock_GetTime(logger->clock); + + // metisLogger_IsLoggable asserted invariants so we know facility is in bounds + PARCLog *log = logger->loggerArray[facility]; + + va_list va; + va_start(va, format); + + parcLog_MessageVaList(log, level, logtime, format, va); + + va_end(va); + } +} + diff --git a/metis/ccnx/forwarder/metis/core/metis_Logger.h b/metis/ccnx/forwarder/metis/core/metis_Logger.h new file mode 100644 index 00000000..5b462e22 --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_Logger.h @@ -0,0 +1,228 @@ +/* + * 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_Logger.h + * @brief Logger for the Metis forwarder + * + * A facility based logger to allow selective logging from different parts of Metis + * + */ + +#ifndef Metis_metis_Logger_h +#define Metis_metis_Logger_h + +#include <sys/time.h> +#include <stdarg.h> +#include <parc/algol/parc_Buffer.h> +#include <parc/logging/parc_LogLevel.h> +#include <parc/logging/parc_LogReporter.h> + +#include <parc/algol/parc_Clock.h> + +struct metis_logger; +typedef struct metis_logger MetisLogger; + +/** + * CONFIG faciilty concerns anything in the /config directory + * CORE concerns anything in the /core directory + * IO concerns anything in the /io directory (listeners, connectors, tcp, ethernet, etc.) + * PROCESSOR concerns FIB, PIT, CS + * MESSAGE concerns message events, like parsing + */ +typedef enum { + MetisLoggerFacility_Config, + MetisLoggerFacility_Core, + MetisLoggerFacility_IO, + MetisLoggerFacility_Processor, + MetisLoggerFacility_Message, + MetisLoggerFacility_END // sentinel value +} MetisLoggerFacility; + +/** + * Returns a string representation of a facility + * + * Do not free the returned value. + * + * @param [in] facility The facility to change to a string + * + * @retval string A string representation of the facility + * + * Example: + * @code + * <#example#> + * @endcode + */ +const char * metisLogger_FacilityString(MetisLoggerFacility facility); + +/** + * Returns a string representation of a log level + * + * Do not free the returned value. + * + * @param [in] level The level to change to a string + * + * @retval string A string representation of the level + * + * Example: + * @code + * <#example#> + * @endcode + */ +const char * metisLogger_LevelString(PARCLogLevel level); + +/** + * Create a logger that uses a given writer and clock + * + * <#Paragraphs Of Explanation#> + * + * @param [in] writer The output writer + * @param [in] clock The clock to use for log messages + * + * @retval non-null An allocated logger + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisLogger * metisLogger_Create(PARCLogReporter *reporter, const PARCClock *clock); + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisLogger_Release(MetisLogger **loggerPtr); + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisLogger *metisLogger_Acquire(const MetisLogger *logger); + +/** + * Sets the minimum log level for a facility + * + * The default log level is ERROR. For a message to be logged, it must be of equal + * or higher log level. + * + * @param [in] logger An allocated logger + * @param [in] facility The facility to set the log level for + * @param [in] The minimum level to log + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * { + * PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + * MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + * parcLogReporter_Release(&reporter); + * metisLogger_SetLogLevel(logger, MetisLoggerFacility_IO, PARCLogLevel_Warning); + * } + * @endcode + */ +void metisLogger_SetLogLevel(MetisLogger *logger, MetisLoggerFacility facility, PARCLogLevel minimumLevel); + +/** + * Tests if the log level would be logged + * + * If the facility would log the given level, returns true. May be used as a + * guard around expensive logging functions. + * + * @param [in] logger An allocated logger + * @param [in] facility The facility to test + * @param [in] The level to test + * + * @retval true The given facility would log the given level + * @retval false A message of the given level would not be logged + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisLogger_IsLoggable(const MetisLogger *logger, MetisLoggerFacility facility, PARCLogLevel level); + +/** + * Log a message + * + * The message will only be logged if it is loggable (metisLogger_IsLoggable returns true). + * + * @param [in] logger An allocated MetisLogger + * @param [in] facility The facility to log under + * @param [in] level The log level of the message + * @param [in] module The specific module logging the message + * @param [in] format The message with varargs + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisLogger_Log(MetisLogger *logger, MetisLoggerFacility facility, PARCLogLevel level, const char *module, const char *format, ...); + +/** + * Switch the logger to a new reporter + * + * Will close the old reporter and re-setup the internal loggers to use the new reporter. + * All current log level settings are preserved. + * + * @param [in] logger An allocated MetisLogger + * @param [in] reporter An allocated PARCLogReporter + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisLogger_SetReporter(MetisLogger *logger, PARCLogReporter *reporter); + +/** + * Set a new clock to use with the logger + * + * The logger will start getting the time (logged as the messageid) from the specified clock + * + * @param [in] logger An allocated MetisLogger + * @param [in] clock An allocated PARCClock + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisLogger_SetClock(MetisLogger *logger, PARCClock *clock); +#endif // Metis_metis_Logger_h diff --git a/metis/ccnx/forwarder/metis/core/metis_Message.c b/metis/ccnx/forwarder/metis/core/metis_Message.c new file mode 100644 index 00000000..77295476 --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_Message.c @@ -0,0 +1,992 @@ +/* + * 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 implementation of metisMessage_Slice() copies data, it needs to do this by reference. + * + */ +#include <config.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> + +#include <ccnx/forwarder/metis/core/metis_Message.h> +#include <ccnx/forwarder/metis/core/metis_StreamBuffer.h> +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +#include <ccnx/forwarder/metis/core/metis_Wldr.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_Hash.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_EventBuffer.h> + +struct metis_message { + MetisLogger *logger; + + MetisTicks receiveTime; + unsigned ingressConnectionId; + + PARCEventBuffer *messageBytes; + uint8_t *messageHead; + + unsigned refcount; + + struct tlv_skeleton skeleton; + + bool hasKeyId; + uint32_t keyIdHash; + bool isKeyIdVerified; + + bool hasContentObjectHash; + // may be null, even if hasContentObjectHash true due to lazy calculation + PARCBuffer *contentObjectHash; + + PARCBuffer *certificate; + + PARCBuffer *publicKey; + + bool hasInterestLifetime; + uint64_t interestLifetimeTicks; + + bool hasExpiryTimeTicks; + uint64_t expiryTimeTicks; + + bool hasRecommendedCacheTimeTicks; + uint64_t recommendedCacheTimeTicks; + + bool hasName; + MetisTlvName *name; + + bool hasFragmentPayload; + + MetisMessagePacketType packetType; + + + bool hasPathLabel; + uint64_t pathLabel; + + bool hasWldr; + //the following fields are valid only if hasWldr is true + uint8_t wldrType; + uint16_t wldrLbl; //if wldrType == WLDR_LBL this indicates the message label + //if wldrType == WLDR_NOTIFICATION this indicates the expected message label + uint16_t wldrLastReceived; //this field is valid only when wldrType == WLDR_NOTIFICATION. In this case, + //all the messages between wldrLbl (included) and wldrLastReceived (excluded) + //are considered lost +}; + +static void +_setupWldr(MetisMessage *message) +{ + uint8_t wldr_header = 0; + parcEventBuffer_copyOut(message->messageBytes, &wldr_header, 1); + if (wldr_header == WLDR_HEADER) { + message->hasWldr = true; + parcEventBuffer_Read(message->messageBytes, NULL, 1); + parcEventBuffer_Read(message->messageBytes, &(message->wldrType), 1); + if (message->wldrType == WLDR_LBL) { + parcEventBuffer_Read(message->messageBytes, &(message->wldrLbl), 2); + parcEventBuffer_Read(message->messageBytes, NULL, 2); + } else if (message->wldrType == WLDR_NOTIFICATION) { + parcEventBuffer_Read(message->messageBytes, &(message->wldrLbl), 2); + parcEventBuffer_Read(message->messageBytes, &(message->wldrLastReceived), 2); + } else { + //find a better way to exit (look into longBow) + printf("Error, Unknown WLDR Type\n"); + exit(0); + } + message->messageHead = parcEventBuffer_Pullup(message->messageBytes, -1); + } else { + message->hasWldr = false; + } +} + +static void +_setupName(MetisMessage *message) +{ + MetisTlvExtent extent = metisTlvSkeleton_GetName(&message->skeleton); + if (extent.offset > 0) { + message->hasName = true; + message->name = metisTlvName_Create(&message->messageHead[extent.offset], extent.length); + } else { + message->hasName = false; + message->name = NULL; + } +} + +static void +_setupValidationParams(MetisMessage *message) +{ + MetisTlvExtent extent = metisTlvSkeleton_GetKeyId(&message->skeleton); + if (extent.offset > 0) { + message->hasKeyId = true; + message->keyIdHash = parcHash32_Data(&message->messageHead[extent.offset], extent.length); + } else { + message->hasKeyId = false; + message->keyIdHash = 0; + } + message->isKeyIdVerified = false; + + extent = metisTlvSkeleton_GetCertificate(&message->skeleton); + if (extent.offset > 0) { + message->certificate = parcBuffer_Flip(parcBuffer_CreateFromArray(&message->messageHead[extent.offset], extent.length)); + } else { + message->certificate = NULL; + } + + extent = metisTlvSkeleton_GetPublicKey(&message->skeleton); + if (extent.offset > 0) { + message->publicKey = parcBuffer_Flip(parcBuffer_CreateFromArray(&message->messageHead[extent.offset], extent.length)); + } else { + message->publicKey = NULL; + } +} + +static void +_setupContentObjectHash(MetisMessage *message) +{ + if (metisTlvSkeleton_IsPacketTypeInterest(&message->skeleton)) { + MetisTlvExtent extent = metisTlvSkeleton_GetObjectHash(&message->skeleton); + // pre-compute it for an interest + if (extent.offset > 0) { + message->hasContentObjectHash = true; + message->contentObjectHash = + parcBuffer_Flip(parcBuffer_CreateFromArray(&message->messageHead[extent.offset], extent.length)); + } else { + message->hasContentObjectHash = false; + } + } else if (metisTlvSkeleton_IsPacketTypeContentObject(&message->skeleton)) { + // we will compute this on demand + message->hasContentObjectHash = true; + message->contentObjectHash = NULL; + } else { + message->hasContentObjectHash = false; + } +} + +static void +_setupInterestLifetime(MetisMessage *message) +{ + MetisTlvExtent extent = metisTlvSkeleton_GetInterestLifetime(&message->skeleton); + message->hasInterestLifetime = false; + if (metisTlvSkeleton_IsPacketTypeInterest(&message->skeleton) && extent.offset > 0) { + message->hasInterestLifetime = true; + uint64_t lifetimeMilliseconds; + metisTlv_ExtentToVarInt(message->messageHead, &extent, &lifetimeMilliseconds); + message->interestLifetimeTicks = metisForwarder_NanosToTicks(lifetimeMilliseconds * 1000000); + } +} + +static void +_setupPathLabel(MetisMessage *message) +{ + MetisTlvExtent extent = metisTlvSkeleton_GetPathLabel(&message->skeleton); + message->hasPathLabel = false; + if (metisTlvSkeleton_IsPacketTypeContentObject(&message->skeleton) && extent.offset > 0) { + message->hasPathLabel = true; + uint64_t pathLabel; + metisTlv_ExtentToVarInt(message->messageHead, &extent, &pathLabel); + message->pathLabel = pathLabel; + } +} + + +static void +_setupFragmentPayload(MetisMessage *message) +{ + MetisTlvExtent extent = metisTlvSkeleton_GetFragmentPayload(&message->skeleton); + if (extent.offset > 0) { + message->hasFragmentPayload = true; + } else { + message->hasFragmentPayload = true; + } +} + +static void +_setupExpiryTime(MetisMessage *message) +{ + MetisTlvExtent expiryTimeExtent = metisTlvSkeleton_GetExpiryTime(&message->skeleton); + + message->hasExpiryTimeTicks = false; + + if (metisTlvSkeleton_IsPacketTypeContentObject(&message->skeleton)) { + if (!metisTlvExtent_Equals(&expiryTimeExtent, &metisTlvExtent_NotFound)) { + uint64_t expiryTimeUTC = 0; + if (metisTlv_ExtentToVarInt(metisTlvSkeleton_GetPacket(&message->skeleton), &expiryTimeExtent, &expiryTimeUTC)) { + message->hasExpiryTimeTicks = true; + + // Convert it to ticks that we can use for expiration checking. + uint64_t metisWallClockTime = parcClock_GetTime(parcClock_Wallclock()); + uint64_t currentTimeInTicks = parcClock_GetTime(parcClock_Monotonic()); + + message->expiryTimeTicks = expiryTimeUTC - metisWallClockTime + currentTimeInTicks; + } + } + } +} + +static void +_setupRecommendedCacheTime(MetisMessage *message) +{ + MetisTlvExtent cacheTimeExtent = metisTlvSkeleton_GetCacheTimeHeader(&message->skeleton); + + message->hasRecommendedCacheTimeTicks = false; + + if (metisTlvSkeleton_IsPacketTypeContentObject(&message->skeleton)) { + if (!metisTlvExtent_Equals(&cacheTimeExtent, &metisTlvExtent_NotFound)) { + uint64_t recommendedCacheTime = 0; + if (metisTlv_ExtentToVarInt(metisTlvSkeleton_GetPacket(&message->skeleton), &cacheTimeExtent, &recommendedCacheTime)) { + message->hasRecommendedCacheTimeTicks = true; + + // Convert it to ticks that we can use for expiration checking. + uint64_t metisWallClockTime = parcClock_GetTime(parcClock_Wallclock()); + uint64_t currentTimeInTicks = parcClock_GetTime(parcClock_Monotonic()); + + message->recommendedCacheTimeTicks = recommendedCacheTime - metisWallClockTime + currentTimeInTicks; + } + } + } +} + +/** + * Parse the TLV skeleton and setup message pointers + * + * <#Paragraphs Of Explanation#> + * + * @param [in] message An allocated messae with the message->messagaeHead pointer setup. + * + * @retval false Error parsing message + * @retval true Good parse + * + * Example: + * @code + * <#example#> + * @endcode + */ +static bool +_setupInternalData(MetisMessage *message) +{ + // -1 means linearize the whole buffer + message->messageHead = parcEventBuffer_Pullup(message->messageBytes, -1); + message->packetType = MetisMessagePacketType_Unknown; + + _setupWldr(message); + if (message->hasWldr == true && message->wldrType == WLDR_NOTIFICATION) { + //this is a WLDR notification message, all the other fields are meaningless because the + //packet will be dropped immmedialtly after the retransmissions. For this reason we can + //immediatly retrun and avoid the parsing of the message + return true; + } + //now the WLDR header is removed from the packet and the parsing shuould work as usual + + bool goodSkeleton = metisTlvSkeleton_Parse(&message->skeleton, message->messageHead, message->logger); + + if (goodSkeleton) { + _setupName(message); + _setupValidationParams(message); + _setupContentObjectHash(message); + _setupInterestLifetime(message); + _setupPathLabel(message); + _setupFragmentPayload(message); + _setupExpiryTime(message); + _setupRecommendedCacheTime(message); + + // set the packet type + bool requiresName = false; + if (metisTlvSkeleton_IsPacketTypeInterest(&message->skeleton)) { + message->packetType = MetisMessagePacketType_Interest; + requiresName = true; + } else if (metisTlvSkeleton_IsPacketTypeContentObject(&message->skeleton)) { + message->packetType = MetisMessagePacketType_ContentObject; + requiresName = true; + } else if (metisTlvSkeleton_IsPacketTypeHopByHopFragment(&message->skeleton)) { + message->packetType = MetisMessagePacketType_HopByHopFrag; + } else if (metisTlvSkeleton_IsPacketTypeControl(&message->skeleton)) { + message->packetType = MetisMessagePacketType_Control; + } else if (metisTlvSkeleton_IsPacketTypeInterestReturn(&message->skeleton)) { + message->packetType = MetisMessagePacketType_InterestReturn; + } + + if (requiresName && !metisMessage_HasName(message)) { + goodSkeleton = false; + } + } + + return goodSkeleton; +} + +MetisMessage * +metisMessage_Acquire(const MetisMessage *message) +{ + MetisMessage *copy = (MetisMessage *) message; + copy->refcount++; + return copy; +} + +MetisMessage * +metisMessage_CreateFromParcBuffer(PARCBuffer *buffer, unsigned ingressConnectionId, MetisTicks receiveTime, MetisLogger *logger) +{ + MetisMessage *message = parcMemory_AllocateAndClear(sizeof(MetisMessage)); + assertNotNull(message, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisMessage)); + message->receiveTime = receiveTime; + message->ingressConnectionId = ingressConnectionId; + message->messageBytes = parcEventBuffer_Create(); + message->refcount = 1; + message->logger = metisLogger_Acquire(logger); + + // this copies the data + int failure = parcEventBuffer_Append(message->messageBytes, parcBuffer_Overlay(buffer, 0), parcBuffer_Remaining(buffer)); + assertFalse(failure, "Got failure copying data into buffer: (%d) %s", errno, strerror(errno)); + + bool goodSkeleton = _setupInternalData(message); + if (goodSkeleton) { + if (metisLogger_IsLoggable(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) { + metisLogger_Log(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__, + "Message %p created ingress %u", + (void *) message, ingressConnectionId); + } + } else { + if (metisLogger_IsLoggable(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Warning)) { + metisLogger_Log(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Warning, __func__, + "Error setting up skeleton for buffer %p ingress %u", + (void *) buffer, ingressConnectionId); + } + + metisMessage_Release(&message); + } + return message; +} + +MetisMessage * +metisMessage_CreateFromArray(const uint8_t *data, size_t dataLength, unsigned ingressConnectionId, MetisTicks receiveTime, MetisLogger *logger) +{ + MetisMessage *message = parcMemory_AllocateAndClear(sizeof(MetisMessage)); + assertNotNull(message, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisMessage)); + message->receiveTime = receiveTime; + message->ingressConnectionId = ingressConnectionId; + message->messageBytes = parcEventBuffer_Create(); + message->refcount = 1; + message->logger = metisLogger_Acquire(logger); + + // this copies the data + int failure = parcEventBuffer_Append(message->messageBytes, (void *) data, dataLength); + assertFalse(failure, "Got failure copying data into PARCEventBuffer: (%d) %s", errno, strerror(errno)); + + bool goodSkeleton = _setupInternalData(message); + if (goodSkeleton) { + if (metisLogger_IsLoggable(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) { + metisLogger_Log(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__, + "Message %p created ingress %u", + (void *) message, ingressConnectionId); + } + } else { + if (metisLogger_IsLoggable(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Warning)) { + metisLogger_Log(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Warning, __func__, + "Error setting up skeleton for array %p ingress %u", + (void *) data, ingressConnectionId); + } + + metisMessage_Release(&message); + } + + return message; +} + +MetisMessage * +metisMessage_ReadFromBuffer(unsigned ingressConnectionId, MetisTicks receiveTime, PARCEventBuffer *input, size_t bytesToRead, MetisLogger *logger) +{ + MetisMessage *message = parcMemory_AllocateAndClear(sizeof(MetisMessage)); + assertNotNull(message, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisMessage)); + message->receiveTime = receiveTime; + message->ingressConnectionId = ingressConnectionId; + message->messageBytes = parcEventBuffer_Create(); + message->refcount = 1; + message->logger = metisLogger_Acquire(logger); + + // dequeue into packet buffer. This is a near-zero-copy operation from + // one buffer to another. It only copies if the message falls across iovec + // boundaries. + int bytesRead = parcEventBuffer_ReadIntoBuffer(input, message->messageBytes, bytesToRead); + assertTrue(bytesRead == bytesToRead, "Partial read, expected %zu got %d", bytesToRead, bytesRead); + + bool goodSkeleton = _setupInternalData(message); + if (goodSkeleton) { + if (metisLogger_IsLoggable(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) { + metisLogger_Log(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__, + "Message %p created ingress %u", + (void *) message, ingressConnectionId); + } + } else { + if (metisLogger_IsLoggable(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Warning)) { + metisLogger_Log(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Warning, __func__, + "Error setting up skeleton for buffer %p ingress %u", + (void *) input, ingressConnectionId); + } + + metisMessage_Release(&message); + } + return message; +} + +MetisMessage * +metisMessage_CreateFromBuffer(unsigned ingressConnectionId, MetisTicks receiveTime, PARCEventBuffer *input, MetisLogger *logger) +{ + assertNotNull(input, "Parameter input must be non-null"); + MetisMessage *message = parcMemory_AllocateAndClear(sizeof(MetisMessage)); + assertNotNull(message, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisMessage)); + message->receiveTime = receiveTime; + message->ingressConnectionId = ingressConnectionId; + message->messageBytes = input; + message->refcount = 1; + message->logger = metisLogger_Acquire(logger); + + bool goodSkeleton = _setupInternalData(message); + if (goodSkeleton) { + if (metisLogger_IsLoggable(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) { + metisLogger_Log(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__, + "Message %p created ingress %u", + (void *) message, ingressConnectionId); + } + } else { + if (metisLogger_IsLoggable(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Warning)) { + metisLogger_Log(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Warning, __func__, + "Error setting up skeleton for buffer %p ingress %u", + (void *) input, ingressConnectionId); + } + + metisMessage_Release(&message); + } + return message; +} + +void +metisMessage_Release(MetisMessage **messagePtr) +{ + assertNotNull(messagePtr, "Parameter must be non-null double pointer"); + assertNotNull(*messagePtr, "Parameter must dereference to non-null pointer"); + + MetisMessage *message = *messagePtr; + assertTrue(message->refcount > 0, "Invalid state: metisMessage_Release called on message with 0 references %p", (void *) message); + + message->refcount--; + if (message->refcount == 0) { + if (metisLogger_IsLoggable(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) { + metisLogger_Log(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__, + "Message %p destroyed", + (void *) message); + } + + if (message->contentObjectHash) { + parcBuffer_Release(&message->contentObjectHash); + } + + if (message->name) { + metisTlvName_Release(&message->name); + } + + if (message->publicKey) { + parcBuffer_Release(&message->publicKey); + } + + if (message->certificate) { + parcBuffer_Release(&message->certificate); + } + + metisLogger_Release(&message->logger); + parcEventBuffer_Destroy(&(message->messageBytes)); + parcMemory_Deallocate((void **) &message); + } + *messagePtr = NULL; +} + +bool +metisMessage_Write(PARCEventQueue *parcEventQueue, const MetisMessage *message) +{ + assertNotNull(message, "Message parameter must be non-null"); + assertNotNull(parcEventQueue, "Buffer parameter must be non-null"); + + return parcEventQueue_Write(parcEventQueue, message->messageHead, parcEventBuffer_GetLength(message->messageBytes)); +} + +bool +metisMessage_Append(PARCEventBuffer *writeBuffer, const MetisMessage *message) +{ + assertNotNull(message, "Message parameter must be non-null"); + assertNotNull(writeBuffer, "Buffer parameter must be non-null"); + + if (message->messageBytes == NULL) { + //this is an error that we need to handle (just dropping the packet?) + //right now we log the event, drop the packet and return false + //should I release the message as well? + if (message->logger != NULL && metisLogger_IsLoggable(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) { + metisLogger_Log(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__, + "Message %p has a null message buffer inside!", + (void *) message); + } + return false; + } + + return parcEventBuffer_Append(writeBuffer, message->messageHead, metisMessage_Length(message)); +} + +size_t +metisMessage_Length(const MetisMessage *message) +{ + assertNotNull(message, "Parameter must be non-null"); + return parcEventBuffer_GetLength(message->messageBytes); +} + +bool +metisMessage_HasWldr(const MetisMessage *message) +{ + assertNotNull(message, "Parameter must be non-null"); + return message->hasWldr; +} + +unsigned +metisMessage_GetWldrType(const MetisMessage *message) +{ + assertNotNull(message, "Parameter must be non-null"); + assertTrue(message->hasWldr == true, "This message does not contains a WLDR header"); + return message->wldrType; +} + +unsigned +metisMessage_GetWldrLabel(const MetisMessage *message) +{ + assertNotNull(message, "Parameter must be non-null"); + assertTrue(message->hasWldr == true, "This message does not contains a WLDR header"); + return message->wldrLbl; +} + +unsigned +metisMessage_GetWldrLastReceived(const MetisMessage *message) +{ + assertNotNull(message, "Parameter must be non-null"); + assertTrue(message->hasWldr == true, "This message does not contains a WLDR header"); + return message->wldrLastReceived; +} + +static void +_removeOldWldrHeader(MetisMessage *message) +{ + uint8_t wldr_header = 0; + parcEventBuffer_copyOut(message->messageBytes, &wldr_header, 1); + if (wldr_header == WLDR_HEADER) { + parcEventBuffer_Read(message->messageBytes, NULL, WLDR_HEADER_SIZE); + } +} + +void +metisMessage_SetWldrLabel(MetisMessage *message, uint16_t label) +{ + assertNotNull(message, "Parameter must be non-null"); + _removeOldWldrHeader(message); + message->hasWldr = true; + message->wldrType = WLDR_LBL; + message->wldrLbl = label; + uint8_t wldr_header[6]; + wldr_header[0] = WLDR_HEADER; + wldr_header[1] = WLDR_LBL; + wldr_header[2] = label & 0xFF; + wldr_header[3] = (label >> 8UL) & 0xFF; + wldr_header[4] = 0; + wldr_header[5] = 0; + parcEventBuffer_Prepend(message->messageBytes, &wldr_header, sizeof(wldr_header)); + uint8_t *newMessage = parcEventBuffer_Pullup(message->messageBytes, -1); + bool goodSkeleton = metisTlvSkeleton_Parse(&message->skeleton, newMessage + WLDR_HEADER_SIZE, message->logger); + if (goodSkeleton) { + message->messageHead = newMessage; + } else { + trapNotImplemented("[metis_Message.c] message parsing after WLDR header insertion failed"); + } +} + +void +metisMessage_SetWldrNotification(MetisMessage *message, uint16_t expected, uint16_t lastReceived) +{ + assertNotNull(message, "Parameter must be non-null"); + _removeOldWldrHeader(message); + message->hasWldr = true; + message->wldrType = WLDR_NOTIFICATION; + message->wldrLbl = expected; + message->wldrLastReceived = lastReceived; + uint8_t wldr_header[6]; + wldr_header[0] = WLDR_HEADER; + wldr_header[1] = WLDR_NOTIFICATION; + wldr_header[2] = expected & 0xFF; + wldr_header[3] = (expected >> 8U) & 0xFF; + wldr_header[4] = lastReceived & 0xFF; + wldr_header[5] = (lastReceived >> 8U) & 0xFF; + parcEventBuffer_Prepend(message->messageBytes, &wldr_header, sizeof(wldr_header)); + message->messageHead = parcEventBuffer_Pullup(message->messageBytes, -1); +} + +unsigned +metisMessage_GetIngressConnectionId(const MetisMessage *message) +{ + assertNotNull(message, "Parameter must be non-null"); + return message->ingressConnectionId; +} + +MetisTicks +metisMessage_GetReceiveTime(const MetisMessage *message) +{ + assertNotNull(message, "Parameter must be non-null"); + return message->receiveTime; +} + +bool +metisMessage_HasHopLimit(const MetisMessage *message) +{ + assertNotNull(message, "Parameter must be non-null"); + MetisTlvExtent extent = metisTlvSkeleton_GetHopLimit(&message->skeleton); + + if (extent.offset > 0) { + return true; + } + return false; +} + +uint8_t +metisMessage_GetHopLimit(const MetisMessage *message) +{ + bool hasHopLimit = metisMessage_HasHopLimit(message); + assertTrue(hasHopLimit, "Message does not have a HopLimit field"); + + MetisTlvExtent extent = metisTlvSkeleton_GetHopLimit(&message->skeleton); + uint8_t hopLimit = message->messageHead[extent.offset]; + return hopLimit; +} + +void +metisMessage_SetHopLimit(MetisMessage *message, uint8_t hoplimit) +{ + assertNotNull(message, "Parameter must be non-null"); + metisTlvSkeleton_UpdateHopLimit(&message->skeleton, hoplimit); +} + +void +metisMessage_UpdatePathLabel(MetisMessage *message, uint8_t outFace) +{ + assertNotNull(message, "Parameter must be non-null"); + metisTlvSkeleton_UpdatePathLabel(&message->skeleton, outFace); +} +void +metisMessage_ResetPathLabel(MetisMessage *message) +{ + assertNotNull(message, "Parameter must be non-null"); + metisTlvSkeleton_ResetPathLabel(&message->skeleton); +} + +MetisMessagePacketType +metisMessage_GetType(const MetisMessage *message) +{ + assertNotNull(message, "Parameter message must be non-null"); + return message->packetType; +} + +MetisTlvName * +metisMessage_GetName(const MetisMessage *message) +{ + assertNotNull(message, "Parameter message must be non-null"); + return message->name; +} + +bool +metisMessage_GetKeyIdHash(const MetisMessage *message, uint32_t *hashOutput) +{ + assertNotNull(message, "Parameter message must be non-null"); + if (message->hasKeyId) { + *hashOutput = message->keyIdHash; + return true; + } + return false; +} + +PARCBuffer * +metisMessage_GetCertificate(const MetisMessage *message) +{ + assertNotNull(message, "Parameter message must be non-null"); + return message->certificate; +} + +PARCBuffer * +metisMessage_GetPublicKey(const MetisMessage *message) +{ + assertNotNull(message, "Parameter message must be non-null"); + return message->publicKey; +} + +bool +metisMessage_KeyIdEquals(const MetisMessage *a, const MetisMessage *b) +{ + assertNotNull(a, "Parameter a must be non-null"); + assertNotNull(b, "Parameter b must be non-null"); + + if (a->hasKeyId && b->hasKeyId) { + MetisTlvExtent ae = metisTlvSkeleton_GetKeyId(&a->skeleton); + MetisTlvExtent be = metisTlvSkeleton_GetKeyId(&b->skeleton); + + if (ae.length == be.length) { + return memcmp(&a->messageHead[ae.offset], &b->messageHead[be.offset], ae.length) == 0; + } + } + return false; +} + +bool +metisMessage_ObjectHashEquals(MetisMessage *a, MetisMessage *b) +{ + assertNotNull(a, "Parameter a must be non-null"); + assertNotNull(b, "Parameter b must be non-null"); + + if (a->hasContentObjectHash && b->hasContentObjectHash) { + if (a->contentObjectHash == NULL) { + PARCCryptoHash *hash = metisTlvSkeleton_ComputeContentObjectHash(&a->skeleton); + a->contentObjectHash = parcBuffer_Acquire(parcCryptoHash_GetDigest(hash)); + parcCryptoHash_Release(&hash); + } + + if (b->contentObjectHash == NULL) { + PARCCryptoHash *hash = metisTlvSkeleton_ComputeContentObjectHash(&b->skeleton); + b->contentObjectHash = parcBuffer_Acquire(parcCryptoHash_GetDigest(hash)); + parcCryptoHash_Release(&hash); + } + + return parcBuffer_Equals(a->contentObjectHash, b->contentObjectHash); + } + + return false; +} + +bool +metisMessage_GetContentObjectHashHash(MetisMessage *message, uint32_t *hashOutput) +{ + assertNotNull(message, "Parameter message must be non-null"); + assertNotNull(hashOutput, "Parameter hashOutput must be non-null"); + + if (message->hasContentObjectHash) { + if (message->contentObjectHash == NULL) { + PARCCryptoHash *hash = metisTlvSkeleton_ComputeContentObjectHash(&message->skeleton); + message->contentObjectHash = parcBuffer_Acquire(parcCryptoHash_GetDigest(hash)); + parcCryptoHash_Release(&hash); + } + + *hashOutput = (uint32_t) parcBuffer_HashCode(message->contentObjectHash); + return true; + } + return false; +} + +bool +metisMessage_HasPublicKey(const MetisMessage *message) +{ + assertNotNull(message, "Parameter message must be non-null"); + return (message->publicKey != NULL); +} + +bool +metisMessage_HasCertificate(const MetisMessage *message) +{ + assertNotNull(message, "Parameter message must be non-null"); + return (message->certificate != NULL); +} + +bool +metisMessage_HasName(const MetisMessage *message) +{ + assertNotNull(message, "Parameter message must be non-null"); + return message->hasName; +} + +bool +metisMessage_HasKeyId(const MetisMessage *message) +{ + assertNotNull(message, "Parameter message must be non-null"); + return message->hasKeyId; +} + +bool +metisMessage_IsKeyIdVerified(const MetisMessage *message) +{ + assertNotNull(message, "Parameter message must be non-null"); + return message->isKeyIdVerified; +} + +bool +metisMessage_HasContentObjectHash(const MetisMessage *message) +{ + assertNotNull(message, "Parameter message must be non-null"); + return message->hasContentObjectHash; +} + +CCNxControl * +metisMessage_CreateControlMessage(const MetisMessage *message) +{ + assertNotNull(message, "Parameter message must be non-null"); + assertTrue(metisMessage_GetType(message) == MetisMessagePacketType_Control, + "Wrong type of message, expected %02X got %02X", + MetisMessagePacketType_Control, + metisMessage_GetType(message)); + + MetisTlvExtent extent = metisTlvSkeleton_GetCPI(&message->skeleton); + assertTrue(extent.offset > 0, "Message does not have a CPI TLV field!"); + + PARCJSON *json = parcJSON_ParseString((char *) &message->messageHead[ extent.offset ]); + CCNxControl *control = ccnxControl_CreateCPIRequest(json); + parcJSON_Release(&json); + return control; +} + +bool +metisMessage_HasInterestLifetime(const MetisMessage *message) +{ + assertNotNull(message, "Parameter message must be non-null"); + return message->hasInterestLifetime; +} + +uint64_t +metisMessage_GetInterestLifetimeTicks(const MetisMessage *message) +{ + assertNotNull(message, "Parameter message must be non-null"); + return message->interestLifetimeTicks; +} + +bool +metisMessage_HasFragmentPayload(const MetisMessage *message) +{ + assertNotNull(message, "Parameter message must be non-null"); + return message->hasFragmentPayload; +} + +size_t +metisMessage_AppendFragmentPayload(const MetisMessage *message, PARCEventBuffer *buffer) +{ + size_t bytesAppended = 0; + if (message->hasFragmentPayload) { + MetisTlvExtent extent = metisTlvSkeleton_GetFragmentPayload(&message->skeleton); + parcEventBuffer_Append(buffer, message->messageHead + extent.offset, extent.length); + bytesAppended = extent.length; + } + return bytesAppended; +} + +const uint8_t * +metisMessage_FixedHeader(const MetisMessage *message) +{ + assertNotNull(message, "Parameter message must be non-null"); + return message->messageHead; +} + +MetisMessage * +metisMessage_Slice(const MetisMessage *original, size_t offset, size_t length, size_t headerLength, const uint8_t header[headerLength]) +{ + assertNotNull(original, "Parameter original must be non-null"); + assertTrue(length > 0, "Parameter length must be positive"); + assertTrue(offset + length <= parcEventBuffer_GetLength(original->messageBytes), + "Slice extends beyond end, maximum %zu got %zu", + parcEventBuffer_GetLength(original->messageBytes), + offset + length); + + MetisMessage *message = parcMemory_AllocateAndClear(sizeof(MetisMessage)); + assertNotNull(message, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisMessage)); + message->receiveTime = original->receiveTime; + message->ingressConnectionId = original->ingressConnectionId; + message->messageBytes = parcEventBuffer_Create(); + message->refcount = 1; + message->logger = metisLogger_Acquire(original->logger); + + if (headerLength > 0) { + assertNotNull(header, "Cannot have a positive headerLength and NULL header"); + + // this copies the data + int failure = parcEventBuffer_Append(message->messageBytes, (void *) header, headerLength); + assertFalse(failure, "Got failure adding header data into PARCEventBuffer: (%d) %s", errno, strerror(errno)); + } + + // this copies the data + int failure = parcEventBuffer_Append(message->messageBytes, (uint8_t *) original->messageHead + offset, length); + assertFalse(failure, "Got failure adding slice data into PARCEventBuffer: (%d) %s", errno, strerror(errno)); + + bool goodSkeleton = _setupInternalData(message); + if (goodSkeleton) { + if (metisLogger_IsLoggable(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) { + metisLogger_Log(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__, + "Message %p created slice(%p, %zu, %zu)", + (void *) message, (void *) original, offset, length); + } + } else { + if (metisLogger_IsLoggable(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Warning)) { + metisLogger_Log(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Warning, __func__, + "Error setting up skeleton for original %p and header %p", + (void *) original, (void *) header); + } + + metisMessage_Release(&message); + } + + return message; +} + +bool +metisMessage_HasRecommendedCacheTime(const MetisMessage *message) +{ + assertNotNull(message, "Parameter message must be non-null"); + return message->hasRecommendedCacheTimeTicks; +} + +uint64_t +metisMessage_GetRecommendedCacheTimeTicks(const MetisMessage *message) +{ + assertNotNull(message, "Parameter message must be non-null"); + assertTrue(message->hasRecommendedCacheTimeTicks, "MetisMessage does not have a RecommendedCacheTime. Call metisMessage_HasRecommendedCacheTime() first."); + return message->recommendedCacheTimeTicks; +} + +void +metisMessage_SetRecommendedCacheTimeTicks(MetisMessage *message, uint64_t recommendedCacheTimeTicks) +{ + assertNotNull(message, "Parameter message must be non-null"); + message->recommendedCacheTimeTicks = recommendedCacheTimeTicks; + message->hasRecommendedCacheTimeTicks = true; +} + +bool +metisMessage_HasExpiryTime(const MetisMessage *message) +{ + assertNotNull(message, "Parameter message must be non-null"); + return message->hasExpiryTimeTicks; +} + +uint64_t +metisMessage_GetExpiryTimeTicks(const MetisMessage *message) +{ + assertNotNull(message, "Parameter message must be non-null"); + assertTrue(message->hasExpiryTimeTicks, "MetisMessage does not have an ExpiryTime. Call metisMessage_HasExpiryTime() first."); + return message->expiryTimeTicks; +} + +void +metisMessage_SetExpiryTimeTicks(MetisMessage *message, uint64_t expiryTimeTicks) +{ + assertNotNull(message, "Parameter message must be non-null"); + message->expiryTimeTicks = expiryTimeTicks; + message->hasExpiryTimeTicks = true; +} diff --git a/metis/ccnx/forwarder/metis/core/metis_Message.h b/metis/ccnx/forwarder/metis/core/metis_Message.h new file mode 100644 index 00000000..3b954a4e --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_Message.h @@ -0,0 +1,859 @@ +/* + * 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_Message.h + * @brief MetisMessage is the unit of forwarding, i.e. the packets being switched + * + */ +#ifndef Metis_metis_Message_h +#define Metis_metis_Message_h + +#include <config.h> +#include <ccnx/forwarder/metis/core/metis_MessagePacketType.h> +#include <ccnx/forwarder/metis/core/metis_StreamBuffer.h> +#include <ccnx/forwarder/metis/tlv/metis_TlvName.h> +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> + +#include <parc/algol/parc_EventQueue.h> +#include <parc/algol/parc_EventBuffer.h> + +#include <ccnx/api/control/cpi_Address.h> +#include <ccnx/api/control/cpi_ControlMessage.h> + +#include <ccnx/forwarder/metis/core/metis_Ticks.h> + +struct metis_message; +typedef struct metis_message MetisMessage; + +/** + * @function metisMessage_ReadFromBuffer + * @abstract Read bytes from the input buffer and create a MetisMessage + * @discussion + * There must be bytesToRead bytes available. + * + * @param <#param1#> + * @return <#return#> + */ +MetisMessage *metisMessage_ReadFromBuffer(unsigned ingressConnectionId, MetisTicks receiveTime, PARCEventBuffer *input, size_t bytesToRead, MetisLogger *logger); + +/** + * @function metisMessage_CreateFromBuffer + * @abstract Takes ownership of the input buffer, which comprises one complete message + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +MetisMessage *metisMessage_CreateFromBuffer(unsigned ingressConnectionId, MetisTicks receiveTime, PARCEventBuffer *input, MetisLogger *logger); + +/** + * @function metisMessage_CreateFromArray + * @abstract Copies the input buffer into the message. + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +MetisMessage *metisMessage_CreateFromArray(const uint8_t *data, size_t dataLength, unsigned ingressConnectionId, MetisTicks receiveTime, MetisLogger *logger); + +/** + * @function metisMessage_CreateFromParcBuffer + * @abstract Creates a message from the byte buffer + * @discussion + * Caller retains owership of the buffer + * + * @param <#param1#> + * @return <#return#> + */ +MetisMessage *metisMessage_CreateFromParcBuffer(PARCBuffer *buffer, unsigned ingressConnectionId, MetisTicks receiveTime, MetisLogger *logger); + +/** + * @function metisMessage_Copy + * @abstract Get a reference counted copy + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +MetisMessage *metisMessage_Acquire(const MetisMessage *message); + +/** + * Releases the message and frees the memory + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisMessage_Release(MetisMessage **messagePtr); + +/** + * Writes the message to the queue + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisMessage_Write(PARCEventQueue *parcEventQueue, const MetisMessage *message); + +/** + * Appends the message to the buffer + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisMessage_Append(PARCEventBuffer *parcEventBuffer, const MetisMessage *message); + +/** + * Returns the total byte length of the message + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +size_t metisMessage_Length(const MetisMessage *message); + + +bool metisMessage_HasWldr(const MetisMessage *message); + +unsigned metisMessage_GetWldrType(const MetisMessage *message); + +unsigned metisMessage_GetWldrLabel(const MetisMessage *message); + +unsigned metisMessage_GetWldrLastReceived(const MetisMessage *message); + +void metisMessage_SetWldrLabel(MetisMessage *message, uint16_t label); + +void metisMessage_SetWldrNotification(MetisMessage *message, uint16_t expected, uint16_t lastReceived); + +/** + * Returns the connection id of the packet input + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +unsigned metisMessage_GetIngressConnectionId(const MetisMessage *message); + +/** + * Returns the receive time (in router ticks) of the message + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisTicks metisMessage_GetReceiveTime(const MetisMessage *message); + +/** + * Returns the PacketType from the FixedHeader + * + * <#Paragraphs Of Explanation#> + * + * @param [in] message A parsed message + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisMessagePacketType metisMessage_GetType(const MetisMessage *message); + +/** + * Determines if the message has a hop limit + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval true A hop limit exists + * @retval false A hop limit does not exist + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisMessage_HasHopLimit(const MetisMessage *message); + +void metisMessage_UpdatePathLabel(MetisMessage *message, uint8_t outFace); +void metisMessage_ResetPathLabel(MetisMessage *message); + +/** + * Returns the hoplimit of the message + * + * Will assert if the message does not have a hoplimit. Use metisMessage_HasHopLimit() first + * to determine if there is a hop limit. + * + * @param [in] message An allocated MetisMessage + * + * @retval number The hop limit + * + * Example: + * @code + * { + * if (metisMessage_HasHopLimit(message)) { + * uint8_t hoplimit = metisMessage_GetHopLimit(message); + * if (hoplimit == 0) { + * // drop packet + * } else { + * metisMessage_SetHopLimit(message, hoplimit - 1); + * } + * } + * } + * @endcode + */ +uint8_t metisMessage_GetHopLimit(const MetisMessage *message); + +/** + * Sets the message hop limit to the specified value + * + * Will assert if the message does not already have a hop limit. Use metisMessage_HasHopLimit() first + * to determine if there is a hop limit. + * + * @param [in] message An allocated MetisMessage + * @param [in] hoplimit The value to set in the packet + * + * Example: + * @code + * { + * if (metisMessage_HasHopLimit(message)) { + * uint8_t hoplimit = metisMessage_GetHopLimit(message); + * if (hoplimit == 0) { + * // drop packet + * } else { + * metisMessage_SetHopLimit(message, hoplimit - 1); + * } + * } + * } + * @endcode + */ +void metisMessage_SetHopLimit(MetisMessage *message, uint8_t hoplimit); + +// =========================================================== +// Accessors used to index and compare messages + +/** + * @function metisMessage_GetName + * @abstract The name in the CCNx message + * @discussion + * The name of the Interest or Content Object. If the caller will store the + * name, he should make a reference counted copy. + * + * @param <#param1#> + * @return The name as stored in the message object. + */ +MetisTlvName *metisMessage_GetName(const MetisMessage *message); + +/** + * @function metisMessage_GetKeyIdHash + * @abstract Non-cryptographic hash of the KeyId + * @discussion + * If the message does not have a KeyId, the output pointer is left unchanged. + * + * @param hashOutput will be filled in with the hash, if a KeyId exists. + * @return true if object has a KeyId + */ +bool metisMessage_GetKeyIdHash(const MetisMessage *message, uint32_t *hashOutput); + + +/** + * Determine if the KeyIds of two Metis Messages are equal. + * + * The following equivalence relations on non-null KeyIds in `MetisMessage` instances are maintained: + * + * * It is reflexive: for any non-null reference value x, `metisMessage_KeyIdEquals(x, x)` + * must return true. + * + * * It is symmetric: for any non-null reference values x and y, + * `metisMessage_KeyIdEquals(x, y)` must return true if and only if + * `metisMessage_KeyIdEquals(y, x)` returns true. + * + * * It is transitive: for any non-null reference values x, y, and z, if + * `metisMessage_KeyIdEquals(x, y)` returns true and + * `metisMessage_KeyIdEquals(y, z)` returns true, + * then `metisMessage_KeyIdEquals(x, z)` must return true. + * + * * It is consistent: for any non-null reference values x and y, multiple + * invocations of `metisMessage_KeyIdEquals(x, y)` consistently return true or + * consistently return false. + * + * * For any non-null reference value x, `metisMessage_KeyIdEquals(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 KeyIds of the two `MetisMessage` instances are equal. + * + * Example: + * @code + * { + * MetisMessage *a = metisMessage_Create(); + * MetisMessage *b = metisMessage_Create(); + * + * if (metisMessage_KeyIdEquals(a, b)) { + * // true + * } else { + * // false + * } + * } + * @endcode + */ +bool metisMessage_KeyIdEquals(const MetisMessage *a, const MetisMessage *b); + +/** + * Determine if the ContentObjectHashes of two `Metis Messages` are equal. + * + * The ContentObjectHashes of two `MetisMessage` instances are equal if, and only if, + * a ContentObjectHash exists in both `MetisMessage` and are equal. + * + * + * The following equivalence relations on non-null KeyIds in `MetisMessage` instances are maintained: + * + * * It is reflexive: for any non-null reference value x, `metisMessage_ObjectHashEquals(x, x)` + * must return true. + * + * * It is symmetric: for any non-null reference values x and y, + * `metisMessage_ObjectHashEquals(x, y)` must return true if and only if + * `metisMessage_ObjectHashEquals(y, x)` returns true. + * + * * It is transitive: for any non-null reference values x, y, and z, if + * `metisMessage_ObjectHashEquals(x, y)` returns true and + * `metisMessage_ObjectHashEquals(y, z)` returns true, + * then `metisMessage_ObjectHashEquals(x, z)` must return true. + * + * * It is consistent: for any non-null reference values x and y, multiple + * invocations of `metisMessage_ObjectHashEquals(x, y)` consistently return true or + * consistently return false. + * + * * For any non-null reference value x, `metisMessage_ObjectHashEquals(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 KeyIds of the two `MetisMessage` instances are equal. + * + * Example: + * @code + * { + * MetisMessage *a = metisMessage_Create(); + * MetisMessage *b = metisMessage_Create(); + * + * if (metisMessage_ObjectHashEquals(a, b)) { + * // true + * } else { + * // false + * } + * } + * @endcode + */ +bool metisMessage_ObjectHashEquals(MetisMessage *a, MetisMessage *b); + +/** + * @function metisMessage_GetContentObjectHashHash + * @abstract Non-cryptographic hash of the ContentObjectHash + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return true if message has a contentobject hash and the output updated. + */ +bool metisMessage_GetContentObjectHashHash(MetisMessage *message, uint32_t *hashOutput); + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisMessage_HasName(const MetisMessage *message); + +/** + * Return true if there is a KeyId associated with this `MetisMessage`. * + * + * Note that this will return true if either the underlying message is an Interest that contains + * a KeyId restriction, or if the underlying message is a ContentObject that contains a KeyId. + * In the latter case, the KeyId might have been specified by the creator of the content, or + * it may have been calculated when we verified the public key specified by the ContentObject. + * + * + * @param [in] message the `MetisMessage` to query. + * + * @return true if there is a KeyId (or KeyId restriction) associated with this `MetisMessage`. + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisMessage_HasKeyId(const MetisMessage *message); + +/** + * Return true if there is a KeyId associated with this `MetisMessage`, and that KeyId + * has been verified. + * + * This KeyId may have been specified by the sender, or may have been calculated while + * verifying the public key associated with this `MetisMessage`s validation data, if any. + * + * @param [in] message the `MetisMessage` to query. + * + * @return true if there is a KeyId associated with this `MetisMessage`, and that KeyId has been verified. + * @return false if there is no KeyId associated with this `MetisMessage` or it has not yet been verified. + * Example: + * @code + * { + * if (metisMessage_IsKeyIdVerified(message)) { + * doSomethingWithMssage(message); + * } else { + * // KeyId was not verified + * } + * } + * @endcode + */ +bool metisMessage_IsKeyIdVerified(const MetisMessage *message); + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisMessage_HasContentObjectHash(const MetisMessage *message); + +/** + * @function metisMessage_GetControlMessage + * @abstract A TLV_MSG_CPI will return a control message + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +CCNxControl *metisMessage_CreateControlMessage(const MetisMessage *message); + +/** + * Determines if the message has an Interest Lifetime parameter + * + * <#Paragraphs Of Explanation#> + * + * @param [in] message An allocated and parsed Message + * + * @retval true If an Intrerest Lifetime field exists + * @retval false If no Interest Lifetime exists + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisMessage_HasInterestLifetime(const MetisMessage *message); + +/** + * Returns the Interest lifetime + * + * Will trap if the message does not contain an Interest lifetime + * + * @param [in] message An allocated and parsed Message + * + * @retval integer Lifetime in forwarder Ticks + * + * Example: + * @code + * <#example#> + * @endcode + */ +uint64_t metisMessage_GetInterestLifetimeTicks(const MetisMessage *message); + +/** + * Return true if the specified `MetisMessage` instance contains a RecommendedCacheTime. + * + * The Recommended Cache Time (a millisecond timestamp) is a network byte ordered unsigned integer of the number of + * milliseconds since the epoch in UTC of when the payload expires. It is a 64-bit field. + * + * @param message the `MetisMessage` instance to check for RecommendedCacheTime. + * @return true if the specified `MetisMessage` instance has a RecommendedCacheTime. + * @return false otherwise. + * Example: + * @code + * { + * MetisMessage *metisMessage = <...>; + * if (metisMessage_HasRecommendedCacheTime(metisMessage)) { + * uint64_t rct = metisMessage_GetRecommendedCacheTimeTicks(metisMessage); + * } + * } + * @endcode + * @see metisMessage_GetRecommendedCacheTimeTicks + */ +bool metisMessage_HasRecommendedCacheTime(const MetisMessage *message); + +/** + * Return the RecommendedCacheTime of the specified `MetisMessage`, if available, in Metis ticks. If not, it will trap. + * Before calling this function, call {@link metisMessage_HasRecommendedCacheTime} first. + * + * The Recommended Cache Time (a millisecond timestamp) is a network byte ordered unsigned integer of the number of + * milliseconds since the epoch in UTC of when the payload expires. It is a 64-bit field. + * + * The Recommended Cache Time is initially set from the referenced tlv_skeleton, but may be assigned by calling + * {@link metisMessage_SetRecommendedCacheTimeTicks}. + * + * @param message the `MetisMessage` instance to check for RecommendedCacheTime. + * @return the RecommendedCacheTime of the specified `MetisMessage`. + * + * Example: + * @code + * { + * MetisMessage *metisMessage = <...>; + * if (metisMessage_HasRecommendedCacheTime(metisMessage)) { + * uint64_t rct = metisMessage_GetRecommendedCacheTimeTicks(metisMessage); + * } + * } + * @endcode + * + * @see metisMessage_HasRecommendedCacheTime + * @see metisMessage_SetRecommendedCacheTimeTicks + */ +uint64_t metisMessage_GetRecommendedCacheTimeTicks(const MetisMessage *message); + +/** + * Return true if the specified `MetisMessage` instance contains an ExpiryTime. + * + * The ExpiryTime is the time at which the Payload expires, as expressed by a timestamp containing the number of milliseconds + * since the epoch in UTC. It is a network byte order unsigned integer in a 64-bit field. A cache or end system should not + * respond with a Content Object past its ExpiryTime. Routers forwarding a Content Object do not need to check the ExpiryTime. + * If the ExpiryTime field is missing, the Content Object has no expressed expiration and a cache or end system may use the + * Content Object for as long as desired. + * + * @param message the `MetisMessage` instance to check for ExpiryTime. + * @return true if the specified `MetisMessage` instance has an ExpiryTime. + * @return false otherwise. + * + * Example: + * @code + * { + * MetisMessage *metisMessage = <...>; + * if (metisMessage_HasExpiryTime(metisMessage)) { + * uint64_t expiryTime = metisMessage_GetExpiryTimeTicks(metisMessage); + * } + * } + * @endcode + * + * @see metisMessage_GetExpiryTimeTicks + * @see metisMessage_SetExpiryTimeTicks + */ +bool metisMessage_HasExpiryTime(const MetisMessage *message); + +/** + * Return the ExpiryTime of the specified `MetisMessage`, if available, in Metis ticks. If not, it will trap. + * Before calling this function, call {@link metisMessage_HasExpiryTime} first. + * + * The ExpiryTime is the time at which the Payload expires, as expressed by a timestamp containing the number of milliseconds + * since the epoch in UTC. It is a network byte order unsigned integer in a 64-bit field. A cache or end system should not + * respond with a Content Object past its ExpiryTime. Routers forwarding a Content Object do not need to check the ExpiryTime. + * If the ExpiryTime field is missing, the Content Object has no expressed expiration and a cache or end system may use the + * Content Object for as long as desired. + * + * The ExpiryTime is initially set from the referenced tlv_skeleton, but may be assigned by calling + * {@link metisMessage_SetExpiryTimeTicks}. + * + * @param message the `MetisMessage` instance to check for ExpiryTime. + * @return the ExpiryTime of the specified `MetisMessage`. + * + * Example: + * @code + * { + * MetisMessage *metisMessage = <...>; + * if (metisMessage_HasExpiryTime(metisMessage)) { + * uint64_t rct = metisMessage_GetExpiryTimeTicks(metisMessage); + * } + * } + * @endcode + * + * @see metisMessage_HasExpiryTime + * @see metisMessage_SetExpiryTimeTicks + */ +uint64_t metisMessage_GetExpiryTimeTicks(const MetisMessage *message); + +/** + * Assign the ExpiryTime of the specified `MetisMessage`, in Metis ticks. This will not update the + * referenced tlv_skeleton. + * + * The ExpiryTime is the time at which the Payload expires, as expressed by a timestamp containing the number of milliseconds + * since the epoch in UTC. It is a network byte order unsigned integer in a 64-bit field. A cache or end system should not + * respond with a Content Object past its ExpiryTime. Routers forwarding a Content Object do not need to check the ExpiryTime. + * If the ExpiryTime field is missing, the Content Object has no expressed expiration and a cache or end system may use the + * Content Object for as long as desired. + * + * @param message the `MetisMessage` instance to check for ExpiryTime. + * @param expiryTimeTicks the time, in ticks, that this message's payload will expire. + * + * Example: + * @code + * { + * MetisMessage *metisMessage = <...>; + * uint64_t timeInTicks = <...>; + * metisMessage_SetExpiryTimeTicks(metisMessage, timeInTicks); + * } + * @endcode + * + * @see metisMessage_HasExpiryTime + * @see metisMessage_GetExpiryTimeTicks + */ +void metisMessage_SetExpiryTimeTicks(MetisMessage *message, uint64_t expiryTimeTicks); + +/** + * Assign the RecommendedCacheTime of the specified `MetisMessage`, in Metis ticks. This will not update the + * referenced tlv_skeleton. + * + * The Recommended Cache Time (a millisecond timestamp) is a network byte ordered unsigned integer of the number of + * milliseconds since the epoch in UTC of when the payload expires. It is a 64-bit field. + * + * @param message the `MetisMessage` instance to check for ExpiryTime. + * @param expiryTimeTicks the time, in ticks, that this message's payload will expire. + * + * Example: + * @code + * { + * MetisMessage *metisMessage = <...>; + * uint64_t timeInTicks = <...>; + * metisMessage_SetRecommendedCacheTimeTicks(metisMessage, timeInTicks); + * } + * @endcode + * + * @see metisMessage_HasExpiryTime + */ +void metisMessage_SetRecommendedCacheTimeTicks(MetisMessage *message, uint64_t recommendedCacheTimeTicks); + +/** + * Return true if there is a public key associated with the specified `MetisMessage`. + * + * @param message the `MetisMessage` instance to check for a public key. + * + * @return true if there is a public key in this `MetisMessage`. + * @return false otherwise. + */ +bool metisMessage_HasPublicKey(const MetisMessage *message); + +/** + * Return true if there is a certificate associated with the specified `MetisMessage`. + * + * @param message the `MetisMessage` instance to check for certificate. + * + * @return true if there is a certificate in this `MetisMessage`. + * @return false otherwise. + */ +bool metisMessage_HasCertificate(const MetisMessage *message); + +/** + * Return the public key associated with the specified `MetisMessage`, if it exists. + * + * @param message the `MetisMessage` instance to check for a public key. + * + * @return a pointer to a PARCBuffer containing the public key of this `MetisMessage`. + * @return NULL if no public key exists. + */ +PARCBuffer *metisMessage_GetPublicKey(const MetisMessage *message); + +/** + * Return the certificate associated with the specified `MetisMessage`, if it exists. + * + * @param message the `MetisMessage` instance to check for a certificate. + * + * @return a pointer to a PARCBuffer containing the certificate of this `MetisMessage`. + * @return NULL if no certificate exists. + */ +PARCBuffer *metisMessage_GetCertificate(const MetisMessage *message); + +/** + * Tests if the packet has a fragment payload + * + * The fragment payload is part of a larger packet + * + * @param [in] message An allocated and parsed Message + * + * @return true The packet contains a portion of another packet + * @return false There is no fragment payload + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +bool metisMessage_HasFragmentPayload(const MetisMessage *message); + +/** + * Appends the fragment payload to the given buffer + * + * Will append the fragment payload from the message to the given buffer. + * This is a non-destructive copy. If there is no fragment payload in the + * message, 0 bytes will be copied and 0 returned. + * + * @param [in] message An allocated and parsed Message + * @param [in] buffer The buffer to append to + * + * @return number The number of bytes appended + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +size_t metisMessage_AppendFragmentPayload(const MetisMessage *message, PARCEventBuffer *buffer); + +/** + * Returns a pointer to the beginning of the FixedHeader + * + * <#Paragraphs Of Explanation#> + * + * @param [in] message An allocated and parsed Message + * + * @return non-null The fixed header memory + * @return null No fixed header or an error + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +const uint8_t *metisMessage_FixedHeader(const MetisMessage *message); + +/** + * Creates a new MetisMessage from a slice of a first message + * + * The new MetisMessage will be the header byte array prefix followed by the slice extent. + * The resulting MetisMessage must be freed by calling metisMessage_Release(). + * + * Depending on the implementation, this may make a reference to the first message and access the + * memory by reference. + * + * The offset + length must be less than or equal to metisMessage_Length(). It is an error + * to call with a 0 length. + * + * @param [in] message The original message to slice + * @param [in] offset The offset within the message to start the slice + * @param [in] length The length after the offset to include in the slice (must be positive) + * @param [in] headerLength The length of the header to prepend (may be 0) + * @param [in] header The header to prepend (may be NULL with 0 length) + * + * @return non-null A new MetisMessage that has the header plus slice + * @return null An error + * + * Example: + * @code + * { + * uint8_t interestToFragment[] = { + * 0x01, 0x00, 0x00, 24, // ver = 1, type = interest, length = 24 + * 0x20, 0x00, 0x11, 8, // HopLimit = 32, reserved = 0, flags = 0x11, header length = 8 + * 0x00, 0x01, 0x00, 12, // type = interest, length = 12 + * 0x00, 0x00, 0x00, 8, // type = name, length = 8 + * 0x00, 0x02, 0x00, 4, // type = binary, length = 4 + * 'c', 'o', 'o', 'l', // "cool" + * }; + * + * MetisMessage *firstMessage = metisMessage_CreateFromArray(interestToFragment, sizeof(interestToFragment), 1, 100, logger); + * + * uint8_t fragmentHeader[] = { + * 0x01, 0x05, 0x00, 12, // hop-by-hop fragment + * 0x40, 0x00, 0x01, 8, // B flag, seqnum = 1 + * 0x00, 0x05, 0x00, 8, // fragment data, length = 8 + * }; + * + * MetisMessage *secondMessage = metisMessage_Slice(firstMessage, 0, 8, sizeof(fragmentHeader), fragmentHeader,); + * // the secondMessage message contains the fragmentHeader followed by the first 8 bytes + * // of the first message, as showin in wireFormat below. + * + * uint8_t wireFormat[] = { + * 0x01, 0x05, 0x00, 12, // hop-by-hop fragment + * 0x40, 0x00, 0x01, 8, // B flag, seqnum = 1 + * 0x00, 0x05, 0x00, 8, // fragment data, length = 8 + * 0x01, 0x00, 0x00, 24, // ver = 1, type = interest, length = 24 + * 0x20, 0x00, 0x11, 8, // HopLimit = 32, reserved = 0, flags = 0x11, header length = 8 + * }; + * + * metisMessage_Release(&firstMessage); + * metisMessage_Release(&secondMessage); + * } + * @endcode + */ +MetisMessage *metisMessage_Slice(const MetisMessage *message, size_t offset, size_t length, size_t headerLength, const uint8_t header[headerLength]); +#endif // Metis_metis_Message_h diff --git a/metis/ccnx/forwarder/metis/core/metis_MessagePacketType.h b/metis/ccnx/forwarder/metis/core/metis_MessagePacketType.h new file mode 100644 index 00000000..c2f9b77a --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_MessagePacketType.h @@ -0,0 +1,36 @@ +/* + * 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_metis_MessagePacketType_h + * @brief Defines the packet type for a CCNx message + * + * Used by MetisMessage to define the packet type of a Fixed Header. + * + */ + +#ifndef Metis_metis_MessagePacketType_h +#define Metis_metis_MessagePacketType_h + +typedef enum metis_message_tlv_type { + MetisMessagePacketType_Unknown, + MetisMessagePacketType_Interest, + MetisMessagePacketType_ContentObject, + MetisMessagePacketType_Control, + MetisMessagePacketType_InterestReturn, + MetisMessagePacketType_HopByHopFrag +} MetisMessagePacketType; + +#endif // Metis_metis_MessagePacketType_h diff --git a/metis/ccnx/forwarder/metis/core/metis_NumberSet.c b/metis/ccnx/forwarder/metis/core/metis_NumberSet.c new file mode 100644 index 00000000..6828014a --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_NumberSet.c @@ -0,0 +1,225 @@ +/* + * 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. + */ + +/** + * Currently uses an unsorted array of numbers. + * + */ + +#include <config.h> +#include <stdio.h> +#include <parc/algol/parc_Memory.h> +#include <ccnx/forwarder/metis/core/metis_NumberSet.h> +#include <parc/algol/parc_ArrayList.h> + +#include <LongBow/runtime.h> + +struct metis_number_set { + MetisNumber *arrayOfNumbers; + size_t length; + size_t limit; + unsigned refcount; +}; + +static void metisNumberSet_Expand(MetisNumberSet *set); + +MetisNumberSet * +metisNumberSet_Create() +{ + MetisNumberSet *set = parcMemory_AllocateAndClear(sizeof(MetisNumberSet)); + assertNotNull(set, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisNumberSet)); + set->arrayOfNumbers = parcMemory_AllocateAndClear(sizeof(MetisNumber) * 16); + assertNotNull((set->arrayOfNumbers), "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisNumber) * 16); + set->length = 0; + set->limit = 16; + set->refcount = 1; + return set; +} + +MetisNumberSet * +metisNumberSet_Acquire(const MetisNumberSet *original) +{ + assertNotNull(original, "Parameter original must be non-null"); + MetisNumberSet *copy = (MetisNumberSet *) original; + copy->refcount++; + return copy; +} + +void +metisNumberSet_Release(MetisNumberSet **setPtr) +{ + assertNotNull(setPtr, "Parameter must be non-null double pointer"); + assertNotNull(*setPtr, "Parameter must dereference to non-null pointer"); + + MetisNumberSet *set = *setPtr; + assertTrue(set->refcount > 0, "Invalid state: calling destroy on an object with 0 reference count"); + set->refcount--; + + if (set->refcount == 0) { + parcMemory_Deallocate((void **) &(set->arrayOfNumbers)); + parcMemory_Deallocate((void **) &set); + *setPtr = NULL; + } +} + +/** + * @function metisNumberSet_AddNoChecks + * @abstract Add a number we know is not already in the set + * @discussion + * Used by other functions that already know the number is unique in the set, + * Does not do the expensive Contains check. + * + * @param <#param1#> + */ +static void +metisNumberSet_AddNoChecks(MetisNumberSet *set, MetisNumber number) +{ + if (set->length == set->limit) { + metisNumberSet_Expand(set); + } + + set->arrayOfNumbers[ set->length ] = number; + set->length++; +} + +bool +metisNumberSet_Add(MetisNumberSet *set, MetisNumber number) +{ + assertNotNull(set, "Parameter set must be non-null"); + if (metisNumberSet_Contains(set, number)) { + return false; + } + + metisNumberSet_AddNoChecks(set, number); + return true; +} + +size_t +metisNumberSet_Length(const MetisNumberSet *set) +{ + assertNotNull(set, "Parameter set must be non-null"); + return set->length; +} + +MetisNumber +metisNumberSet_GetItem(const MetisNumberSet *set, size_t ordinalIndex) +{ + assertNotNull(set, "Parameter set must be non-null"); + assertTrue(ordinalIndex < set->length, "Limit beyond end of set, length %zu got %zu", set->length, ordinalIndex); + + return set->arrayOfNumbers[ordinalIndex]; +} + +bool +metisNumberSet_Contains(const MetisNumberSet *set, MetisNumber number) +{ + assertNotNull(set, "Parameter set must be non-null"); + for (size_t i = 0; i < set->length; i++) { + if (set->arrayOfNumbers[i] == number) { + return true; + } + } + return false; +} + +void +metisNumberSet_AddSet(MetisNumberSet *destinationSet, const MetisNumberSet *setToAdd) +{ + assertNotNull(destinationSet, "Parameter destinationSet must be non-null"); + assertNotNull(setToAdd, "Parameter setToAdd must be non-null"); + + for (size_t i = 0; i < setToAdd->length; i++) { + metisNumberSet_Add(destinationSet, setToAdd->arrayOfNumbers[i]); + } +} + +MetisNumberSet * +metisNumberSet_Subtract(const MetisNumberSet *minuend, const MetisNumberSet *subtrahend) +{ + // because the underlying ADT is not sorted, this is pretty ineffient, could be O(n^2). + + MetisNumberSet *difference = metisNumberSet_Create(); + + for (size_t i = 0; i < minuend->length; i++) { + bool unique = true; + for (size_t j = 0; j < subtrahend->length && unique; j++) { + if (minuend->arrayOfNumbers[i] == subtrahend->arrayOfNumbers[j]) { + unique = false; + } + } + + if (unique) { + metisNumberSet_AddNoChecks(difference, minuend->arrayOfNumbers[i]); + } + } + return difference; +} + +bool +metisNumberSet_Equals(const MetisNumberSet *a, const MetisNumberSet *b) +{ + if (a == NULL && b == NULL) { + return true; + } + + if (a == NULL || b == NULL) { + return false; + } + + if (a->length == b->length) { + for (size_t i = 0; i < a->length; i++) { + bool found = false; + for (size_t j = 0; j < b->length && !found; j++) { + if (a->arrayOfNumbers[i] == b->arrayOfNumbers[j]) { + found = true; + } + } + if (!found) { + return false; + } + } + return true; + } + + return false; +} + +void +metisNumberSet_Remove(MetisNumberSet *set, MetisNumber number) +{ + assertNotNull(set, "Parameter set must be non-null"); + for (size_t i = 0; i < set->length; i++) { + if (set->arrayOfNumbers[i] == number) { + set->length--; + if (set->length > 0) { + // move the last element to the removed element to keep the array packed. + set->arrayOfNumbers[i] = set->arrayOfNumbers[set->length]; + } + return; + } + } +} + +// ===================================================== + +static void +metisNumberSet_Expand(MetisNumberSet *set) +{ + size_t newlimit = set->limit * 2; + size_t newbytes = newlimit * sizeof(MetisNumber); + + set->arrayOfNumbers = parcMemory_Reallocate(set->arrayOfNumbers, newbytes); + set->limit = newlimit; +} diff --git a/metis/ccnx/forwarder/metis/core/metis_NumberSet.h b/metis/ccnx/forwarder/metis/core/metis_NumberSet.h new file mode 100644 index 00000000..8ca5a9b3 --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_NumberSet.h @@ -0,0 +1,212 @@ +/* + * 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+NumberList.h + * @brief Stores a set of numbers. + * + * Useful for things like the reverse path of a PIT + * or the forward paths of a FIB. Does not allow duplicates. + * + */ + +#ifndef Metis_metis_NumberSet_h +#define Metis_metis_NumberSet_h + +#include <stdlib.h> +#include <stdbool.h> + +struct metis_number_set; +typedef struct metis_number_set MetisNumberSet; + +typedef uint32_t MetisNumber; + +/** + * @function metisNumberList_Create + * @abstract A new list of numbers + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +MetisNumberSet *metisNumberSet_Create(void); + +/** + * Obtains a reference counted copy of the original + * + * The reference count is increased by one. It must be released with MetisNumberSet_Release(). + * + * @param [in] original An allocated MetisNumberSet + * + * @return non-null The reference counted copy + * + * Example: + * @code + * { + * MetisNumberSet *set = metisNumberSet_Create(); + * MetisNumberSet *copy = metisNumberSet_Acquire(set); + * metisNumberSet_Release(©); + * metisNumberSet_Release(&set); + * } + * @endcode + */ +MetisNumberSet *metisNumberSet_Acquire(const MetisNumberSet *original); + +/** + * Releases one reference count and destroys the memory after last release + * + * The pointer will be NULLed after release regardless if the memory was destroyed. + * + * @param [in,out] setPtr A pointer to a MetisNumberSet. Will be NULL'd after release. + * + * Example: + * @code + * { + * MetisNumberSet *set = metisNumberSet_Create(); + * metisNumberSet_Release(&set); + * } + * @endcode + */ +void metisNumberSet_Release(MetisNumberSet **setPtr); + +/** + * @function metisNumberList_Append + * @abstract Add a number to the end of the list + * @discussion + * No check for duplicates is done + * + * @param <#param1#> + * @return true if added, false if a duplicate + */ +bool metisNumberSet_Add(MetisNumberSet *set, MetisNumber number); + +/** + * @function metisNumberList_Length + * @abstract The count of numbers in the list + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +size_t metisNumberSet_Length(const MetisNumberSet *set); + +/** + * @function metisNumberSet_GetItem + * @abstract Retrieves an item based on the ordinal index + * @discussion + * Will assert if the ordinalIndex is out of bounds. + * + * @param <#param1#> + * @return the number + */ +MetisNumber metisNumberSet_GetItem(const MetisNumberSet *set, size_t ordinalIndex); + +/** + * @function metisNumberSet_Contains + * @abstract Checks for set membership + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return true if the set contains the number, false otherwise + */ +bool metisNumberSet_Contains(const MetisNumberSet *set, MetisNumber number); + +/** + * @function metisNumberSet_AddSet + * @abstract Adds one set to another set + * @discussion + * Adds <code>setToAdd</code> to <code>destinationSet</code> + * + * @param <#param1#> + * @return true if the set contains the number, false otherwise + */ +void metisNumberSet_AddSet(MetisNumberSet *destinationSet, const MetisNumberSet *setToAdd); + +/** + * @function metisNumberSet_Subtract + * @abstract Computes set difference <code>difference = minuend - subtrahend</code>, returns a new number set. + * @discussion + * <code>minuend</code> and <code>subtrahend</code> are not modified. A new difference set is created. + * + * Returns the elements in <code>minuend</code> that are not in <code>subtrahend</code>. + * + * @param minuend The set from which to subtract + * @param subrahend The set begin removed from minuend + * @return The set difference. May be empty, but will not be NULL. + */ +MetisNumberSet *metisNumberSet_Subtract(const MetisNumberSet *minuend, const MetisNumberSet *subtrahend); + +/** + * Determine if two MetisNumberSet instances are equal. + * + * Two MetisNumberSet instances are equal if, and only if, + * they are the same size and contain the same elements. Empty sets are equal. + * NULL equals NULL, but does not equal non-NULL. + * + * The following equivalence relations on non-null `MetisNumberSet` instances are maintained: + * + * * It is reflexive: for any non-null reference value x, `MetisNumberSet_Equals(x, x)` + * must return true. + * + * * It is symmetric: for any non-null reference values x and y, + * `metisNumberSet_Equals(x, y)` must return true if and only if + * `metisNumberSet_Equals(y, x)` returns true. + * + * * It is transitive: for any non-null reference values x, y, and z, if + * `metisNumberSet_Equals(x, y)` returns true and + * `metisNumberSet_Equals(y, z)` returns true, + * then `metisNumberSet_Equals(x, z)` must return true. + * + * * It is consistent: for any non-null reference values x and y, multiple + * invocations of `metisNumberSet_Equals(x, y)` consistently return true or + * consistently return false. + * + * * For any non-null reference value x, `metisNumberSet_Equals(x, NULL)` must + * return false. + * + * @param a A pointer to a `MetisNumberSet` instance. + * @param b A pointer to a `MetisNumberSet` instance. + * @return true if the two `MetisNumberSet` instances are equal. + * + * Example: + * @code + * { + * MetisNumberSet *a = metisNumberSet_Create(); + * MetisNumberSet *b = metisNumberSet_Create(); + * + * if (metisNumberSet_Equals(a, b)) { + * // true + * } else { + * // false + * } + * } + * @endcode + */ +bool metisNumberSet_Equals(const MetisNumberSet *a, const MetisNumberSet *b); + +/** + * @function metisNumberSet_Remove + * @abstract Removes the number from the set + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +void metisNumberSet_Remove(MetisNumberSet *set, MetisNumber number); +#endif // Metis_metis_NumberSet_h diff --git a/metis/ccnx/forwarder/metis/core/metis_StreamBuffer.c b/metis/ccnx/forwarder/metis/core/metis_StreamBuffer.c new file mode 100644 index 00000000..12638095 --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_StreamBuffer.c @@ -0,0 +1,153 @@ +/* + * 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 <LongBow/runtime.h> + +#include <ccnx/forwarder/metis/core/metis_StreamBuffer.h> + +void +metisStreamBuffer_Destroy(PARCEventQueue **bufferPtr) +{ + assertNotNull(bufferPtr, "Parameter must be non-null double pointer"); + assertNotNull(*bufferPtr, "Parameter must dereference to non-null pointer"); + parcEventQueue_Destroy(bufferPtr); + *bufferPtr = NULL; +} + +void +metisStreamBuffer_SetWatermark(PARCEventQueue *buffer, bool setRead, bool setWrite, size_t low, size_t high) +{ + assertNotNull(buffer, "Parameter buffer must be non-null"); + + short flags = 0; + if (setRead) { + flags |= PARCEventType_Read; + } + + if (setWrite) { + flags |= PARCEventType_Write; + } + + parcEventQueue_SetWatermark(buffer, flags, low, high); +} + +int +metisStreamBuffer_Flush(PARCEventQueue *buffer, bool flushRead, bool flushWrite) +{ + assertNotNull(buffer, "Parameter buffer must be non-null"); + + short flags = 0; + if (flushRead) { + flags |= PARCEventType_Read; + } + + if (flushWrite) { + flags |= PARCEventType_Write; + } + + return parcEventQueue_Flush(buffer, flags); +} + +// NOT USED!! +int +metisStreamBuffer_FlushCheckpoint(PARCEventQueue *buffer, bool flushRead, bool flushWrite) +{ + assertNotNull(buffer, "Parameter buffer must be non-null"); + + short flags = 0; + if (flushRead) { + flags |= PARCEventType_Read; + } + + if (flushWrite) { + flags |= PARCEventType_Write; + } + + return parcEventQueue_Flush(buffer, flags); +} + +// NOT USED!! +int +metisStreamBuffer_FlushFinished(PARCEventQueue *buffer, bool flushRead, bool flushWrite) +{ + assertNotNull(buffer, "Parameter buffer must be non-null"); + + short flags = 0; + if (flushRead) { + flags |= PARCEventType_Read; + } + + if (flushWrite) { + flags |= PARCEventType_Write; + } + + return parcEventQueue_Flush(buffer, flags); +} + +void +metisStreamBuffer_SetCallbacks(PARCEventQueue *buffer, + PARCEventQueue_Callback *readCallback, + PARCEventQueue_Callback *writeCallback, + PARCEventQueue_EventCallback *eventCallback, + void *user_data) +{ + assertNotNull(buffer, "Parameter buffer must be non-null"); + + parcEventQueue_SetCallbacks(buffer, readCallback, writeCallback, eventCallback, user_data); +} + +void +metisStreamBuffer_EnableCallbacks(PARCEventQueue *buffer, bool enableRead, bool enableWrite) +{ + assertNotNull(buffer, "Parameter buffer must be non-null"); + short flags = 0; + if (enableRead) { + flags |= PARCEventType_Read; + } + if (enableWrite) { + flags |= PARCEventType_Write; + } + + parcEventQueue_Enable(buffer, flags); +} + +/** + * @function MetisStreamBuffer_DisableCallbacks + * @abstract Disables specified callbacks. Does not affect others. + * @discussion + * Disables enabled callbacks. If a callback is already disabled, has no effect. + * A "false" value does not enable it. + * + * @param <#param1#> + * @return <#return#> + */ +void +metisStreamBuffer_DisableCallbacks(PARCEventQueue *buffer, bool disableRead, bool disableWrite) +{ + assertNotNull(buffer, "Parameter buffer must be non-null"); + short flags = 0; + if (disableRead) { + flags |= PARCEventType_Read; + } + if (disableWrite) { + flags |= PARCEventType_Write; + } + + parcEventQueue_Disable(buffer, flags); +} diff --git a/metis/ccnx/forwarder/metis/core/metis_StreamBuffer.h b/metis/ccnx/forwarder/metis/core/metis_StreamBuffer.h new file mode 100644 index 00000000..bd0fc3c6 --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_StreamBuffer.h @@ -0,0 +1,141 @@ +/* + * 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. + */ + + +/** + * Wrapper around event scheduler + */ + +#ifndef Metis_metis_StreamBuffer_h +#define Metis_metis_StreamBuffer_h + +#include <parc/algol/parc_EventQueue.h> +#include <stdbool.h> + +void metisStreamBuffer_Destroy(PARCEventQueue **bufferPtr); + +/** + * @function metisStreamBuffer_SetWatermark + * @abstract Sets the read and/or write watermarks + * @discussion + * For a read watermark, when there is at least <code>low</code> bytes available to read, + * the read callback will be fired. If the bytes in the buffer exceed <code>high</code>, + * the stream buffer will stop reading from the network. + * + * For a write watermark, when the bytes in the buffer fall below <code>low</code>, the + * write callback is fired. The <code>high</code> watermark limits stream filters + * and shapers from exceeding that threashold on what they write to the buffer. + * + * @param <#param1#> + * @return <#return#> + */ +void metisStreamBuffer_SetWatermark(PARCEventQueue *buffer, bool setRead, bool setWrite, size_t low, size_t high); + +/** + * @function metisStreamBuffer_Flush + * @abstract The buffer will read/write more data if available + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return -1 error, 0 no more data, 1 more data + */ +int metisStreamBuffer_Flush(PARCEventQueue *buffer, bool flushRead, bool flushWrite); + +/** + * @function metisStreamBuffer_FlushCheckpoint + * @abstract Flushes the stream, checkpointing all data in the buffer + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +int metisStreamBuffer_FlushCheckpoint(PARCEventQueue *buffer, bool flushRead, bool flushWrite); + +/** + * @function metisStreamBuffer_FlushFinished + * @abstract Flush the stream and indicate the end of new data + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +int metisStreamBuffer_FlushFinished(PARCEventQueue *buffer, bool flushRead, bool flushWrite); + +/** + * @typedef MetisStreamBufferReadWriteCallback + * @abstract Callback when data is available or write space available + * @constant user_data opaque data passed to <code>MetisStreamBuffer_SetCallbacks()</code> + * @discussion <#Discussion#> + */ +typedef void (MetisStreamBufferReadWriteCallback)(PARCEventQueue *buffer, void *user_data); + +/** + * @typedef MetisStreamBufferEventCallback + * @abstract Callback on error or other event on the stream buffer + * @constant what logical or of METIS_STREAM events. METIS_STREAM_READING and METIS_STREAM_WRITING + * indicate if the error was on the read or write direction. The conditions + * may be METIS_STREAM_ERROR, METIS_STREAM_EOF, METIS_STREAM_TIMEOUT, or METIS_STREAM_CONNECTED. + * @constant user_data opaque data passed to <code>MetisStreamBuffer_SetCallbacks()</code> + * @discussion <#Discussion#> + */ +typedef void (MetisStreamBufferEventCallback)(PARCEventQueue *buffer, short what, void *user_data); + +/** + * Changes the callbacks for a buffer event. + * + * @param bufev the buffer event object for which to change callbacks + * @param readcb callback to invoke when there is data to be read, or NULL if + * no callback is desired + * @param writecb callback to invoke when the file descriptor is ready for + * writing, or NULL if no callback is desired + * @param eventcb callback to invoke when there is an event on the file + * descriptor + * @param cbarg an argument that will be supplied to each of the callbacks + * (readcb, writecb, and errorcb) + * @see parcEventQueue_Create() + */ +void metisStreamBuffer_SetCallbacks(PARCEventQueue *buffer, + PARCEventQueue_Callback *readCallback, + PARCEventQueue_Callback *writeCallback, + PARCEventQueue_EventCallback *eventCallback, + void *user_data); + +/** + * @function MetisStreamBuffer_EnableCallbacks + * @abstract Enables specified callbacks. Does not affect others. + * @discussion + * Enables disabled callbacks. If a callback is already enabled, has no effect. + * A "false" value does not disable it. + * + * @param <#param1#> + * @return <#return#> + */ +void metisStreamBuffer_EnableCallbacks(PARCEventQueue *buffer, bool enableRead, bool enableWrite); + +/** + * @function MetisStreamBuffer_DisableCallbacks + * @abstract Disables specified callbacks. Does not affect others. + * @discussion + * Disables enabled callbacks. If a callback is already disabled, has no effect. + * A "false" value does not enable it. + * + * @param <#param1#> + * @return <#return#> + */ +void metisStreamBuffer_DisableCallbacks(PARCEventQueue *buffer, bool disableRead, bool disableWrite); +#endif // Metis_metis_StreamBuffer_h diff --git a/metis/ccnx/forwarder/metis/core/metis_System.h b/metis/ccnx/forwarder/metis/core/metis_System.h new file mode 100644 index 00000000..5775e0fe --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_System.h @@ -0,0 +1,80 @@ +/* + * 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_System.h + * @abstract System-level properties + * @discussion + * <#Discussion#> + * + */ + +#ifndef Metis_metis_System_h +#define Metis_metis_System_h + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/api/control/cpi_InterfaceSet.h> + +/** + * @function metisSystem_Interfaces + * @abstract The system network interfaces + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +CPIInterfaceSet *metisSystem_Interfaces(MetisForwarder *metis); + +/** + * Returns the MTU of the named interface + * + * <#Paragraphs Of Explanation#> + * + * @param [in] metis An allocated forwarder + * @param [in] interfaceName The system interface name, e.g. "eth0" + * + * @return 0 Interface does not exist + * @return positive the MTU the kernel reports + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +unsigned metisSystem_InterfaceMtu(MetisForwarder *metis, const char *interfaceName); + +/** + * Returns the LINK address of the specified interface + * + * <#Paragraphs Of Explanation#> + * + * @param [in] metis An allocated forwarder + * @param [in] interfaceName The system interface name, e.g. "eth0" + * + * @retval non-null The MAC address of the interface + * @retval null The interface does not exist + * + * Example: + * @code + * { + * CPIAddress *linkAddress = metisSystem_GetMacAddressByName(metis, "en0"); + * } + * @endcode + */ +CPIAddress *metisSystem_GetMacAddressByName(MetisForwarder *metis, const char *interfaceName); +#endif diff --git a/metis/ccnx/forwarder/metis/core/metis_ThreadedForwarder.c b/metis/ccnx/forwarder/metis/core/metis_ThreadedForwarder.c new file mode 100644 index 00000000..db6c0626 --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_ThreadedForwarder.c @@ -0,0 +1,247 @@ +/* + * 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 Threaded Forwarder + * @abstract A thread wrapper around metis_Forwarder. + * @discussion + * Cannot restart a thread after its stopped. I think this should be ok, but + * have not had time to test it yet, so dont support it. + * + * This wrapper does not expose any of the metis_Forwarder calls, as those + * are all non-threaded calls. You can only create, start, stop, and destroy + * the forwarder. All configuration needs to be via the CLI or via CPI control messages. + * + * You may run multiple Metis forwarders as long as they are on different ports. + * + */ + +#include <config.h> +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <signal.h> +#include <string.h> +#include <errno.h> +#include <pthread.h> + +#define __STDC_FORMAT_MACROS +#include <inttypes.h> + +#include <parc/algol/parc_Memory.h> +#include <LongBow/runtime.h> + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_ThreadedForwarder.h> +#include <ccnx/forwarder/metis/core/metis_Dispatcher.h> +#include <ccnx/forwarder/metis/config/metis_Configuration.h> +#include <ccnx/forwarder/metis/config/metis_CommandLineInterface.h> + +struct metis_threaded_forwarder { + pthread_t thread; + pthread_mutex_t state_mutex; + pthread_cond_t state_cond; + + // indicates that the Start function was called + bool started; + + // indicates that the thread has entered the Run function and is running + bool running; + + MetisForwarder *forwarder; + MetisLogger *logger; + MetisCommandLineInterface *cli; +}; + +static void +metisThreadedForwarder_LockState(MetisThreadedForwarder *threadedMetis) +{ + int res = pthread_mutex_lock(&threadedMetis->state_mutex); + assertTrue(res == 0, "error from pthread_mutex_lock: %d", res); +} + +static void +metisThreadedForwarder_UnlockState(MetisThreadedForwarder *threadedMetis) +{ + int res = pthread_mutex_unlock(&threadedMetis->state_mutex); + assertTrue(res == 0, "error from pthread_mutex_unlock: %d", res); +} + +static void +metisThreadedForwarder_WaitStatus(MetisThreadedForwarder *threadedMetis) +{ + int res = pthread_cond_wait(&threadedMetis->state_cond, &threadedMetis->state_mutex); + assertTrue(res == 0, "error from pthread_mutex_unlock: %d", res); +} + +static void +metisThreadedForwarder_BroadcastStatus(MetisThreadedForwarder *threadedMetis) +{ + int res = pthread_cond_broadcast(&threadedMetis->state_cond); + assertTrue(res == 0, "error from pthread_mutex_unlock: %d", res); +} + +static void * +metisThreadedForwarder_Run(void *arg) +{ + MetisThreadedForwarder *threadedMetis = (MetisThreadedForwarder *) arg; + + metisThreadedForwarder_LockState(threadedMetis); + assertFalse(threadedMetis->running, "Invalid State: forwarder already in running state"); + threadedMetis->running = true; + metisThreadedForwarder_BroadcastStatus(threadedMetis); + metisThreadedForwarder_UnlockState(threadedMetis); + + // -------- + // Block in the dispatch loop + MetisDispatcher *dispatcher = metisForwarder_GetDispatcher(threadedMetis->forwarder); + metisDispatcher_Run(dispatcher); + // -------- + + metisThreadedForwarder_LockState(threadedMetis); + assertTrue(threadedMetis->running, "Invalid State: forwarder indicates its not running!"); + threadedMetis->running = false; + metisThreadedForwarder_BroadcastStatus(threadedMetis); + metisThreadedForwarder_UnlockState(threadedMetis); + + pthread_exit(NULL); +} + +// =========================== + +MetisThreadedForwarder * +metisThreadedForwarder_Create(MetisLogger *logger) +{ + struct sigaction ignore_action; + ignore_action.sa_handler = SIG_IGN; + sigemptyset(&ignore_action.sa_mask); + ignore_action.sa_flags = 0; + // sigaction(SIGPIPE, NULL, &save_sigpipe); + sigaction(SIGPIPE, &ignore_action, NULL); + + + MetisThreadedForwarder *threadedMetis = parcMemory_AllocateAndClear(sizeof(MetisThreadedForwarder)); + assertNotNull(threadedMetis, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisThreadedForwarder)); + threadedMetis->logger = metisLogger_Acquire(logger); + threadedMetis->forwarder = metisForwarder_Create(logger); + + pthread_mutex_init(&threadedMetis->state_mutex, NULL); + pthread_cond_init(&threadedMetis->state_cond, NULL); + + threadedMetis->thread = (pthread_t) { 0 }; + threadedMetis->cli = NULL; + threadedMetis->running = false; + return threadedMetis; +} + +void +metisThreadedForwarder_AddCLI(MetisThreadedForwarder *threadedMetis, uint16_t port) +{ + assertNotNull(threadedMetis, "Parameter must be non-null"); + assertFalse(threadedMetis->started, "Must be done prior to starting!"); + assertNull(threadedMetis->cli, "Can only define one CLI"); + + threadedMetis->cli = metisCommandLineInterface_Create(threadedMetis->forwarder, port); + + // this sets up all the network events in the dispatcher so when the thread is + // started, the CLI will be ready to go. + metisCommandLineInterface_Start(threadedMetis->cli); +} + +void +metisThreadedForwarder_SetupAllListeners(MetisThreadedForwarder *threadedMetis, uint16_t port, const char *localPath) +{ + assertNotNull(threadedMetis, "Parameter must be non-null"); + assertFalse(threadedMetis->started, "Must be done prior to starting!"); + + metisForwarder_SetupAllListeners(threadedMetis->forwarder, port, localPath); +} + +void +metisThreadedForwarder_Start(MetisThreadedForwarder *threadedMetis) +{ + assertNotNull(threadedMetis, "Parameter must be non-null"); + assertFalse(threadedMetis->started, "Must be done prior to starting!"); + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + int failure = pthread_create(&threadedMetis->thread, &attr, metisThreadedForwarder_Run, threadedMetis); + assertFalse(failure, "Eror creating thread: %d", failure); + + // block until running + metisThreadedForwarder_LockState(threadedMetis); + while (!threadedMetis->running) { + metisThreadedForwarder_WaitStatus(threadedMetis); + } + metisThreadedForwarder_UnlockState(threadedMetis); +} + +/** + * @function metisThreadedForwarder_Stop + * @abstract Blocks until stopped + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +void +metisThreadedForwarder_Stop(MetisThreadedForwarder *threadedMetis) +{ + assertNotNull(threadedMetis, "Parameter must be non-null"); + + // These are explicitly thread-safe operations inside Metis + MetisDispatcher *dispatcher = metisForwarder_GetDispatcher(threadedMetis->forwarder); + metisDispatcher_Stop(dispatcher); + + // Equivalently, we could block until joined + + // block until stopped + metisThreadedForwarder_LockState(threadedMetis); + while (threadedMetis->running) { + metisThreadedForwarder_WaitStatus(threadedMetis); + } + metisThreadedForwarder_UnlockState(threadedMetis); +} + +/** + * @function metisThreadedForwarder_Destroy + * @abstract Blocks until stopped and destoryed + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +void +metisThreadedForwarder_Destroy(MetisThreadedForwarder **threadedMetisPtr) +{ + assertNotNull(threadedMetisPtr, "Parameter must be non-null double pointer"); + assertNotNull(*threadedMetisPtr, "Parameter must dereference to non-null pointer"); + + MetisThreadedForwarder *threadedMetis = *threadedMetisPtr; + metisThreadedForwarder_Stop(threadedMetis); + + pthread_mutex_destroy(&threadedMetis->state_mutex); + pthread_cond_destroy(&threadedMetis->state_cond); + + metisLogger_Release(&threadedMetis->logger); + metisForwarder_Destroy(&threadedMetis->forwarder); + parcMemory_Deallocate((void **) &threadedMetis); + *threadedMetisPtr = NULL; +} diff --git a/metis/ccnx/forwarder/metis/core/metis_ThreadedForwarder.h b/metis/ccnx/forwarder/metis/core/metis_ThreadedForwarder.h new file mode 100644 index 00000000..162a6b78 --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_ThreadedForwarder.h @@ -0,0 +1,111 @@ +/* + * 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 Threaded Forwarder + * @abstract This is a wrapper around metis_Forwarder to run it as a thread + * @discussion + * <#Discussion#> + * + */ + +#ifndef Metis_metis_ThreadedForwarder_h +#define Metis_metis_ThreadedForwarder_h + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +struct metis_threaded_forwarder; +typedef struct metis_threaded_forwarder MetisThreadedForwarder; + +/** + * @function metisThreadedForwarder_Create + * @abstract Creates a threaded forwarder in the stopped state + * @discussion + * IMPORTANT: The logger is called from the Metis thread, so it is up to + * the user to implement any necessary thread saftey in the logger. There + * is only a single metis thread, so it does not need to be re-enterent. + * + * @param <#param1#> + * @return <#return#> + */ +MetisThreadedForwarder *metisThreadedForwarder_Create(MetisLogger *logger); + +/** + * @function metisThreadedForwarder_AddCLI + * @abstract Add a command line interface (CLI) on the given port + * @discussion + * MUST BE DONE PRIOR TO START. This function will add a CLI to the forwarder + * prior to starting it. Once started, will assert if you try to do this. + * + * @param <#param1#> + */ +void metisThreadedForwarder_AddCLI(MetisThreadedForwarder *metis, uint16_t port); + +/** + * @function metisThreadedForwarder_AddTcpListener + * @abstract Adds a TCP listenener + * @discussion + * MUST BE DONE PRIOR TO START. + * May be IPv4 or IPv6 + * + * @param <#param1#> + * @return <#return#> + */ +void metisThreadedForwarder_AddTcpListener(MetisThreadedForwarder *metis, struct sockaddr *address); + +/** + * @function metisThreadedForwarder_SetupAllListeners + * @abstract Setup all tcp/udp ipv4/ipv6 listeners on the given port + * @discussion + * MUST BE DONE PRIOR TO START. + * + * @param port is the UDP and TCP port + * @param localPath is the AF_UNIX path, may be NULL for no AF_UNIX socket. + * @return <#return#> + */ +void metisThreadedForwarder_SetupAllListeners(MetisThreadedForwarder *metis, uint16_t port, const char *localPath); + +/** + * @function metisThreadedForwarder_Start + * @abstract Blocks until started + * @discussion + * <#Discussion#> + * + * @param <#param1#> + */ +void metisThreadedForwarder_Start(MetisThreadedForwarder *metis); + +/** + * @function metisThreadedForwarder_Stop + * @abstract Blocks until stopped + * @discussion + * Currently we do not support re-starting a thread after it is stopped. + * + * @param <#param1#> + * @return <#return#> + */ +void metisThreadedForwarder_Stop(MetisThreadedForwarder *metis); + +/** + * @function metisThreadedForwarder_Destroy + * @abstract Blocks until stopped and destoryed + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +void metisThreadedForwarder_Destroy(MetisThreadedForwarder **metisPtr); +#endif // Metis_metis_ThreadedForwarder_h diff --git a/metis/ccnx/forwarder/metis/core/metis_Ticks.h b/metis/ccnx/forwarder/metis/core/metis_Ticks.h new file mode 100644 index 00000000..6e6bda00 --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_Ticks.h @@ -0,0 +1,32 @@ +/* + * 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_Ticks.h + * @brief The router periodically measures time in units of Ticks + * + * See metis_Forwarder.c METISHZ which specifies the tick rate. metis_Forwarder.h has functions + * to convert between ticks and milliseconds. + * + */ +#ifndef Metis_metis_Ticks_h +#define Metis_metis_Ticks_h + +#define __STDC_FORMAT_MACROS +#include <stdint.h> + +typedef uint64_t MetisTicks; + +#endif // Metis_metis_Ticks_h diff --git a/metis/ccnx/forwarder/metis/core/metis_Wldr.c b/metis/ccnx/forwarder/metis/core/metis_Wldr.c new file mode 100644 index 00000000..6249bf0a --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_Wldr.c @@ -0,0 +1,174 @@ +/* + * 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 <stdint.h> +#include <stdio.h> +#include <ccnx/forwarder/metis/core/metis_Connection.h> +#include <ccnx/forwarder/metis/core/metis_Wldr.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <parc/logging/parc_LogReporterTextStdout.h> + +struct metis_wldr_buffer { + MetisMessage *message; + uint8_t rtx_counter; +}; + +typedef struct metis_wldr_buffer MetisWldrBuffer; + +struct metis_wldr_state { + uint16_t expected_label; + uint16_t next_label; + MetisWldrBuffer *buffer[BUFFER_SIZE]; +}; + +MetisWldr * +metisWldr_Init() +{ + MetisWldr *wldr = parcMemory_AllocateAndClear(sizeof(MetisWldr)); + assertNotNull(wldr, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisWldr)); + wldr->expected_label = 1; + wldr->next_label = 1; + for (int i = 0; i < BUFFER_SIZE; i++) { + MetisWldrBuffer *entry = parcMemory_AllocateAndClear(sizeof(MetisWldrBuffer)); + assertNotNull(entry, "WldrBuffer init: parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisWldrBuffer)); + entry->message = NULL; + entry->rtx_counter = 0; + wldr->buffer[i] = entry; + } + return wldr; +} + +void +metisWldr_ResetState(MetisWldr *wldr) +{ + wldr->expected_label = 1; + wldr->next_label = 1; + for (int i = 0; i < BUFFER_SIZE; i++) { + wldr->buffer[i]->message = NULL; + wldr->buffer[i]->rtx_counter = 0; + } +} + +void +metisWldr_Destroy(MetisWldr **wldrPtr) +{ + MetisWldr *wldr = *wldrPtr; + for (unsigned i = 0; i < BUFFER_SIZE; i++) { + if (wldr->buffer[i]->message != NULL) { + metisMessage_Release(&(wldr->buffer[i]->message)); + parcMemory_Deallocate((void **) &(wldr->buffer[i])); + } + } + parcMemory_Deallocate((void **) &wldr); + *wldrPtr = NULL; +} + + +static void +_metisWldr_RetransmitPacket(MetisWldr *wldr, const MetisConnection *conn, uint16_t label) +{ + if (wldr->buffer[label % BUFFER_SIZE]->message == NULL) { + printf("the required message for retransmission is not in the buffer\n"); + return; + } + + if (wldr->buffer[label % BUFFER_SIZE]->rtx_counter < MAX_RTX) { + MetisMessage *msg = wldr->buffer[label % BUFFER_SIZE]->message; + + //printf("-----retransmit packet label = %d, new label = %d\n", label, wldr->next_label); + metisMessage_SetWldrLabel(msg, wldr->next_label); + + if (wldr->buffer[wldr->next_label % BUFFER_SIZE]->message != NULL) { + //printf("-------------release message in retransmit packet, %d %d\n",(wldr->next_label % BUFFER_SIZE),wldr->next_label); + metisMessage_Release(&(wldr->buffer[wldr->next_label % BUFFER_SIZE]->message)); + } + + wldr->buffer[wldr->next_label % BUFFER_SIZE]->message = msg; + wldr->buffer[wldr->next_label % BUFFER_SIZE]->rtx_counter = wldr->buffer[label % BUFFER_SIZE]->rtx_counter + 1; + metisMessage_Acquire(wldr->buffer[wldr->next_label % BUFFER_SIZE]->message); + wldr->next_label++; + metisConnection_ReSend(conn, msg); + } +} + +static void +_metisWldr_SendWldrNotificaiton(MetisWldr *wldr, const MetisConnection *conn, MetisMessage *message, uint16_t expected_lbl, uint16_t received_lbl) +{ + //here we create a copy of the last message received and we use it as a loss notification + //this can be made more efficient using a pre-encoded message with a small size + MetisMessage *notification = metisMessage_Slice(message, 0, metisMessage_Length(message), 0, NULL); + metisMessage_SetWldrNotification(notification, expected_lbl, received_lbl); + //printf("------------send notification %u, %u\n",expected_lbl, received_lbl); + assertNotNull(notification, "Got null from Slice"); + metisConnection_ReSend(conn, notification); +} + + +void +metisWldr_SetLabel(MetisWldr *wldr, MetisMessage *message) +{ + //we send the packet for the first time + metisMessage_SetWldrLabel(message, wldr->next_label); + if (wldr->buffer[wldr->next_label % BUFFER_SIZE]->message != NULL) { + //printf("-------------release message in set label packet, %d %d\n",(wldr->next_label % BUFFER_SIZE),wldr->next_label); + metisMessage_Release(&(wldr->buffer[wldr->next_label % BUFFER_SIZE]->message)); + } + metisMessage_Acquire(message); + wldr->buffer[wldr->next_label % BUFFER_SIZE]->message = message; + wldr->buffer[wldr->next_label % BUFFER_SIZE]->rtx_counter = 0; + wldr->next_label++; +} + +void +metisWldr_DetectLosses(MetisWldr *wldr, const MetisConnection *conn, MetisMessage *message) +{ + if (metisMessage_HasWldr(message)) { + uint8_t wldr_type = (uint8_t) metisMessage_GetWldrType(message); + if (wldr_type == WLDR_LBL) { + uint16_t pkt_lbl = (uint16_t) metisMessage_GetWldrLabel(message); + //printf("--------------received packet label %u\n", pkt_lbl); + if (pkt_lbl != wldr->expected_label) { + //if the received packet label is 1 and the expected packet label > pkt_lbl + //usually we are in the case where a remove note disconnected for a while + //and reconnected on this same connection, so the two nodes are out of synch + //for this reason we do not send any notification, we just synch the labels + + if ((pkt_lbl != 1) || (wldr->expected_label < pkt_lbl)) { + _metisWldr_SendWldrNotificaiton(wldr, conn, message, wldr->expected_label, pkt_lbl); + } + + //here we always synch + wldr->expected_label = (uint16_t) (pkt_lbl + 1); + } else { + wldr->expected_label++; + } + } else if (wldr_type == WLDR_NOTIFICATION) { + uint16_t expected_lbl = (uint16_t) metisMessage_GetWldrLabel(message); + uint16_t received_lbl = (uint16_t) metisMessage_GetWldrLastReceived(message); + //printf("------------received notification %u, %u\n",expected_lbl, received_lbl); + if ((wldr->next_label - expected_lbl) > BUFFER_SIZE) { + //printf("--------------the packets are not in the buffer anymore %u %u %u\n", wldr->next_label, + // expected_lbl, BUFFER_SIZE); + //the packets are not in the buffer anymore + return; + } + while (expected_lbl < received_lbl) { + _metisWldr_RetransmitPacket(wldr, conn, expected_lbl); + expected_lbl++; + } + } + } +} diff --git a/metis/ccnx/forwarder/metis/core/metis_Wldr.h b/metis/ccnx/forwarder/metis/core/metis_Wldr.h new file mode 100644 index 00000000..9332e9e0 --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/metis_Wldr.h @@ -0,0 +1,49 @@ +/* + * 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_Wldr_h +#define Metis_metis_Wldr_h + +#include <ccnx/forwarder/metis/core/metis_Message.h> +#include <ccnx/forwarder/metis/core/metis_Connection.h> +#include <config.h> + +#define BUFFER_SIZE 8192 +#define MAX_RTX 3 +#define WLDR_HEADER_SIZE 6 +#define WLDR_HEADER 12 +#define WLDR_LBL 13 +#define WLDR_NOTIFICATION 14 + +//WLDR HEADERS : +// NORMAL PACKET or RETRASMISSION +// | WLDR_HEADER | WLDR_LBL | label (1byte) | label (2bytes) | unused | unused | +// NOTIFICATION +// | WLDR_HEADER | WLDR_NOTIFICATION | expected_label (1byte) | expected_label (2bytes) | last_received_label (1byte) | last_received_label (2byte) | + +struct metis_wldr_state; +typedef struct metis_wldr_state MetisWldr; + +MetisWldr *metisWldr_Init(); + +void metisWldr_Destroy(MetisWldr **wldrPtr); + +void metisWldr_ResetState(MetisWldr *wldr); + +void metisWldr_SetLabel(MetisWldr *wldr, MetisMessage *message); + +void metisWldr_DetectLosses(MetisWldr *wldr, const MetisConnection *conn, MetisMessage *message); + +#endif //Metis_metis_Wldr_h diff --git a/metis/ccnx/forwarder/metis/core/test/CMakeLists.txt b/metis/ccnx/forwarder/metis/core/test/CMakeLists.txt new file mode 100644 index 00000000..a1209868 --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/test/CMakeLists.txt @@ -0,0 +1,22 @@ +# Enable gcov output for the tests +add_definitions(--coverage) +set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} " --coverage") + +set(TestsExpectedToPass + test_metis_Connection + test_metis_ConnectionTable + test_metis_Dispatcher + test_metis_Forwarder + test_metis_Logger + test_metis_Message + test_metis_NumberSet + test_metis_StreamBuffer + test_metis_ConnectionList + test_metis_ThreadedForwarder +) + + +foreach(test ${TestsExpectedToPass}) + AddTest(${test}) +endforeach() + diff --git a/metis/ccnx/forwarder/metis/core/test/test_metis_Connection.c b/metis/ccnx/forwarder/metis/core/test/test_metis_Connection.c new file mode 100644 index 00000000..1fe904d1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/test/test_metis_Connection.c @@ -0,0 +1,227 @@ +/* + * 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_Connection.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <parc/logging/parc_LogReporterTextStdout.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h> + +#include "testrig_MetisIoOperations.h" + +LONGBOW_TEST_RUNNER(metis_Connection) +{ + // 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_Connection) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_Connection) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisConnection_Acquire); + LONGBOW_RUN_TEST_CASE(Global, metisConnection_Create_Destroy); + LONGBOW_RUN_TEST_CASE(Global, metisConnection_Send); + + LONGBOW_RUN_TEST_CASE(Global, metisConnection_GetConnectionId); + LONGBOW_RUN_TEST_CASE(Global, metisConnection_GetAddressPair); + LONGBOW_RUN_TEST_CASE(Global, metisConnection_IsUp); + LONGBOW_RUN_TEST_CASE(Global, metisConnection_IsLocal); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + longBowTestCase_SetClipBoardData(testCase, ops); + + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + MetisIoOperations *ops = longBowTestCase_GetClipBoardData(testCase); + mockIoOperationsData_Destroy(&ops); + + 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, metisConnection_Acquire) +{ + MetisIoOperations *ops = longBowTestCase_GetClipBoardData(testCase); + MetisConnection *conn = metisConnection_Create(ops); + + assertTrue(conn->refCount == 1, "Wrong refcount, got %u expected %u", conn->refCount, 1); + + MetisConnection *copy = metisConnection_Acquire(conn); + assertTrue(conn->refCount == 2, "Wrong refcount, got %u expected %u", conn->refCount, 2); + + metisConnection_Release(©); + assertTrue(conn->refCount == 1, "Wrong refcount, got %u expected %u", conn->refCount, 1); + + metisConnection_Release(&conn); +} + +LONGBOW_TEST_CASE(Global, metisConnection_Create_Destroy) +{ + MetisIoOperations *ops = longBowTestCase_GetClipBoardData(testCase); + MetisConnection *conn = metisConnection_Create(ops); + assertNotNull(conn, "Got null connection"); + assertTrue(conn->refCount == 1, "Wrong refcount, got %u expected %u", conn->refCount, 1); + + metisConnection_Release(&conn); + assertNull(conn, "Release did not null pointer"); + + // the mock in testrig_MetisIoOperations does not destroy the IoOperations on destroy + // so we can still look at the counters + assertTrue(((MockIoOperationsData *) metisIoOperations_GetClosure(ops))->destroyCount == 1, + "Destroy count is wrong, got %u expected %u", + ((MockIoOperationsData *) metisIoOperations_GetClosure(ops))->destroyCount, + 1); +} + +LONGBOW_TEST_CASE(Global, metisConnection_Send) +{ + MetisIoOperations *ops = longBowTestCase_GetClipBoardData(testCase); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + MetisConnection *conn = metisConnection_Create(ops); + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + MetisMessage *message = metisMessage_CreateFromArray((uint8_t *) metisTestDataV1_Interest_AllFields, + sizeof(metisTestDataV1_Interest_AllFields), 111, 2, logger); + + metisConnection_Send(conn, message); + + assertTrue(data->sendCount == 1, "Send count wrong, got %u expected %u", data->sendCount, 1); + assertTrue(data->lastMessage == message, "Sent wrong message, got %p expected %p", (void *) data->lastMessage, (void *) message); + + metisMessage_Release(&message); + metisConnection_Release(&conn); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Global, metisConnection_GetConnectionId) +{ + MetisIoOperations *ops = longBowTestCase_GetClipBoardData(testCase); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + MetisConnection *conn = metisConnection_Create(ops); + unsigned testid = metisConnection_GetConnectionId(conn); + + assertTrue(testid == data->id, "Got wrong id, got %u expected %u", testid, data->id); + assertTrue(data->getConnectionIdCount == 1, "Wrong getConnectionIdCount, got %u expected %u", data->getConnectionIdCount, 1); + + metisConnection_Release(&conn); +} + +LONGBOW_TEST_CASE(Global, metisConnection_GetAddressPair) +{ + MetisIoOperations *ops = longBowTestCase_GetClipBoardData(testCase); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + MetisConnection *conn = metisConnection_Create(ops); + + unsigned beforeCount = data->getAddressPairCount; + const MetisAddressPair *pair = metisConnection_GetAddressPair(conn); + + assertTrue(metisAddressPair_Equals(pair, data->addressPair), "Got wrong address pair"); + assertTrue(data->getAddressPairCount == beforeCount + 1, "Wrong getAddressPairCount, got %u expected %u", data->getAddressPairCount, beforeCount + 1); + + metisConnection_Release(&conn); +} + +LONGBOW_TEST_CASE(Global, metisConnection_IsUp) +{ + MetisIoOperations *ops = longBowTestCase_GetClipBoardData(testCase); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + MetisConnection *conn = metisConnection_Create(ops); + + unsigned beforeCount = data->isUpCount; + bool isup = metisConnection_IsUp(conn); + + assertTrue(isup == data->isUp, "Got wrong isup, got %d expected %d", isup, data->isUp); + assertTrue(data->isUpCount == beforeCount + 1, "Wrong isUpCount, got %u expected %u", data->isUpCount, beforeCount + 1); + + metisConnection_Release(&conn); +} + +LONGBOW_TEST_CASE(Global, metisConnection_IsLocal) +{ + MetisIoOperations *ops = longBowTestCase_GetClipBoardData(testCase); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + MetisConnection *conn = metisConnection_Create(ops); + + unsigned beforeCount = data->isLocalCount; + bool islocal = metisConnection_IsLocal(conn); + + assertTrue(islocal == data->isLocal, "Got wrong islocal, got %d expected %d", islocal, data->isLocal); + assertTrue(data->isLocalCount == beforeCount + 1, "Wrong isLocalCount, got %u expected %u", data->isLocalCount, beforeCount + 1); + + metisConnection_Release(&conn); +} + + +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_Connection); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/core/test/test_metis_ConnectionList.c b/metis/ccnx/forwarder/metis/core/test/test_metis_ConnectionList.c new file mode 100644 index 00000000..94518a9e --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/test/test_metis_ConnectionList.c @@ -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. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_ConnectionList.c" +#include <parc/algol/parc_SafeMemory.h> +#include "testrig_MetisIoOperations.h" +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metis_ConnectionList) +{ + // 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_ConnectionList) +{ + 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_ConnectionList) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisConnectionList_Append); + LONGBOW_RUN_TEST_CASE(Global, metisConnectionList_Create_Destroy); + + LONGBOW_RUN_TEST_CASE(Global, metisConnectionList_Get); + LONGBOW_RUN_TEST_CASE(Global, metisConnectionList_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, metisConnectionList_Append) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MetisConnection *connection = metisConnection_Create(ops); + + MetisConnectionList *list = metisConnectionList_Create(); + metisConnectionList_Append(list, connection); + metisConnection_Release(&connection); + + assertTrue(parcArrayList_Size(list->listOfConnections) == 1, + "Got wrong list size, got %zu expected %u", + parcArrayList_Size(list->listOfConnections), 1); + + metisConnectionList_Destroy(&list); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisConnectionList_Create_Destroy) +{ + MetisConnectionList *list = metisConnectionList_Create(); + assertNotNull(list, "Got null from Create"); + + metisConnectionList_Destroy(&list); + assertNull(list, "Destroy did not null the parameter"); +} + +LONGBOW_TEST_CASE(Global, metisConnectionList_Get) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MetisConnection *connection = metisConnection_Create(ops); + + MetisConnectionList *list = metisConnectionList_Create(); + metisConnectionList_Append(list, connection); + + MetisConnection *test = metisConnectionList_Get(list, 0); + assertTrue(test == connection, + "Got wrong connection, got %p expected %p", + (void *) test, (void *) connection); + + metisConnection_Release(&connection); + metisConnectionList_Destroy(&list); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisConnectionList_Length) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MetisConnection *connection = metisConnection_Create(ops); + + MetisConnectionList *list = metisConnectionList_Create(); + metisConnectionList_Append(list, connection); + + size_t length = metisConnectionList_Length(list); + assertTrue(length == 1, + "Got wrong list size, got %zu expected %u", + length, 1); + + metisConnection_Release(&connection); + metisConnectionList_Destroy(&list); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisConnectionList_ArrayDestroyer); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, metisConnectionList_ArrayDestroyer) +{ + testUnimplemented(""); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_ConnectionList); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/core/test/test_metis_ConnectionManager.c b/metis/ccnx/forwarder/metis/core/test/test_metis_ConnectionManager.c new file mode 100644 index 00000000..b4550e44 --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/test/test_metis_ConnectionManager.c @@ -0,0 +1,261 @@ +/* + * 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_ConnectionManager.c" + +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metis_ConnectionManager) +{ + // 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_ConnectionManager) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_ConnectionManager) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisConnectionManager_Create); + LONGBOW_RUN_TEST_CASE(Global, metisConnectionManager_Destroy); +} + +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, metisConnectionManager_Create) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Global, metisConnectionManager_Destroy) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisConnectionManager_MessengerCallback); + LONGBOW_RUN_TEST_CASE(Local, metisConnectionManager_NotifyApplications); + LONGBOW_RUN_TEST_CASE(Local, metisConnectionManager_ProcessDownMissive); + LONGBOW_RUN_TEST_CASE(Local, metisConnectionManager_ProcessQueue); + LONGBOW_RUN_TEST_CASE(Local, metisConnectionManager_ProcessUpMissive); + LONGBOW_RUN_TEST_CASE(Local, metisConnectionManager_ProcessDestroyMissive); + + LONGBOW_RUN_TEST_CASE(Local, metisConnectionManager_ProcessCloseMissive_RemoveConnection); + LONGBOW_RUN_TEST_CASE(Local, metisConnectionManager_ProcessCloseMissive_RemoveRoutes); +} + +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, metisConnectionManager_MessengerCallback) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, metisConnectionManager_NotifyApplications) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, metisConnectionManager_ProcessDownMissive) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, metisConnectionManager_ProcessQueue) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, metisConnectionManager_ProcessUpMissive) +{ + testUnimplemented(""); +} + +typedef struct my_connection { + MetisAddressPair *addressPair; + unsigned connectionId; +} MyConnection; + +static const MetisAddressPair * +getAddressPair(const MetisIoOperations *ops) +{ + MyConnection *myconn = (MyConnection *) ops->context; + return myconn->addressPair; +} + +static unsigned +getConnectionId(const MetisIoOperations *ops) +{ + MyConnection *myconn = (MyConnection *) ops->context; + return myconn->connectionId; +} + +static void +myConnectionDestroy(MetisIoOperations **opsPtr) +{ + MetisIoOperations *ops = *opsPtr; + MyConnection *myconn = (MyConnection *) ops->context; + metisAddressPair_Release(&myconn->addressPair); + parcMemory_Deallocate((void **) &ops); + *opsPtr = NULL; +} + +static MetisConnection * +createConnection(unsigned connectionId) +{ + MyConnection *myconn = parcMemory_AllocateAndClear(sizeof(MyConnection)); + assertNotNull(myconn, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MyConnection)); + CPIAddress *a = cpiAddress_CreateFromInterface(1); + myconn->addressPair = metisAddressPair_Create(a, a); + myconn->connectionId = connectionId; + + cpiAddress_Destroy(&a); + + MetisIoOperations *ops = parcMemory_AllocateAndClear(sizeof(MetisIoOperations)); + assertNotNull(ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisIoOperations)); + memset(ops, 0, sizeof(MetisIoOperations)); + ops->context = myconn; + ops->destroy = myConnectionDestroy; + ops->getAddressPair = getAddressPair; + ops->getConnectionId = getConnectionId; + + MetisConnection *conn = metisConnection_Create(ops); + return conn; +} + +static void +addRoute(MetisForwarder *metis, const char *name, unsigned connectionId) +{ + CCNxName *uri = ccnxName_CreateFromURI(name); + CPIRouteEntry *route = cpiRouteEntry_Create(uri, connectionId, NULL, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, NULL, 1); + + metisForwarder_AddOrUpdateRoute(metis, route); + ccnxName_Release(&uri); +} + +/** + * We add a connection, then send a CLOSE message, make sure the connection + * is no longer in the connection table. + */ +LONGBOW_TEST_CASE(Local, metisConnectionManager_ProcessCloseMissive_RemoveConnection) +{ + unsigned connectionId = 1000; + + MetisForwarder *metis = metisForwarder_Create(NULL); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + + MetisConnection *conn = createConnection(connectionId); + metisConnectionTable_Add(metisForwarder_GetConnectionTable(metis), conn); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + + // send close message + metisMessenger_Send(metisForwarder_GetMessenger(metis), metisMissive_Create(MetisMissiveType_ConnectionClosed, connectionId)); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + + // now make sure its gone + const MetisConnection *test = metisConnectionTable_FindById(metisForwarder_GetConnectionTable(metis), connectionId); + assertNull(test, "Return from connection table should be null, got %p\n", (const void *) test); + + metisForwarder_Destroy(&metis); +} + +/** + * We add a connection and a route that uses that connection, then send a CLOSE message, + * then make sure the connection is no longer in the routing table. + */ +LONGBOW_TEST_CASE(Local, metisConnectionManager_ProcessCloseMissive_RemoveRoutes) +{ + unsigned connectionId = 1001; + + MetisForwarder *metis = metisForwarder_Create(NULL); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + + MetisConnection *conn = createConnection(connectionId); + metisConnectionTable_Add(metisForwarder_GetConnectionTable(metis), conn); + + addRoute(metis, "lci:/foo/bar", connectionId); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + + // send close message + metisMessenger_Send(metisForwarder_GetMessenger(metis), metisMissive_Create(MetisMissiveType_ConnectionClosed, connectionId)); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + + // now make sure its gone + MetisFibEntryList *fiblist = metisForwarder_GetFibEntries(metis); + for (size_t i = 0; i < metisFibEntryList_Length(fiblist); i++) { + MetisFibEntry *fibentry = metisFibEntryList_Get(fiblist, i); + assertTrue(metisFibEntry_NexthopCount(fibentry) == 0, "Wrong nexthop count, expected 0 got %zu", metisFibEntry_NexthopCount(fibentry)); + } + + metisForwarder_Destroy(&metis); +} + +LONGBOW_TEST_CASE(Local, metisConnectionManager_ProcessDestroyMissive) +{ + testUnimplemented(""); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_ConnectionManager); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/core/test/test_metis_ConnectionTable.c b/metis/ccnx/forwarder/metis/core/test/test_metis_ConnectionTable.c new file mode 100644 index 00000000..b55b6e67 --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/test/test_metis_ConnectionTable.c @@ -0,0 +1,490 @@ +/* + * 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_ConnectionTable.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +#include "testrig_MetisIoOperations.h" + +static struct test_set_s { + unsigned localAddr; + unsigned remoteAddr; + unsigned id; + MetisIoOperations *ops; + MetisConnection *conn; +} test_set [] = { + { .localAddr = 1, .remoteAddr = 2, .id = 3, .ops = NULL }, + { .localAddr = 2, .remoteAddr = 1, .id = 4, .ops = NULL }, + { .localAddr = 7, .remoteAddr = 2, .id = 22, .ops = NULL }, + { .localAddr = 13, .remoteAddr = 2, .id = 102332, .ops = NULL }, + { .localAddr = 99, .remoteAddr = 2, .id = 99, .ops = NULL }, + { .localAddr = 3, .remoteAddr = 5, .id = 0xFFFFFFFF, .ops = NULL }, + { .localAddr = 0, .remoteAddr = 0, .id = 0, .ops = NULL } +}; + +// ============================================ + +LONGBOW_TEST_RUNNER(metis_ConnectionTable) +{ + // 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(CreateDestroy); + 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_ConnectionTable) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_ConnectionTable) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ============================================ + +LONGBOW_TEST_FIXTURE(CreateDestroy) +{ + LONGBOW_RUN_TEST_CASE(CreateDestroy, metisConnectionTable_Create_Destroy); + LONGBOW_RUN_TEST_CASE(CreateDestroy, metisConnectionTable_Add); +} + +LONGBOW_TEST_FIXTURE_SETUP(CreateDestroy) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(CreateDestroy) +{ + 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(CreateDestroy, metisConnectionTable_Create_Destroy) +{ + MetisConnectionTable *table = metisConnectionTable_Create(); + metisConnectionTable_Destroy(&table); + + assertTrue(parcMemory_Outstanding() == 0, "Got memory imbalance on create/destroy: %u", parcMemory_Outstanding()); +} + +LONGBOW_TEST_CASE(CreateDestroy, metisConnectionTable_Add) +{ + MetisConnectionTable *table = metisConnectionTable_Create(); + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + MetisConnection *conn = metisConnection_Create(ops); + + assertTrue(parcHashCodeTable_Length(table->storageTableById) == 0, + "storageTableById not empty at start of test, length %zu", + parcHashCodeTable_Length(table->storageTableById)); + + assertTrue(parcHashCodeTable_Length(table->indexByAddressPair) == 0, + "indexByAddressPair not empty at start of test, length %zu", + parcHashCodeTable_Length(table->indexByAddressPair)); + + metisConnectionTable_Add(table, conn); + + assertTrue(parcHashCodeTable_Length(table->storageTableById) == 1, + "Incorrect storage table size, expected %u got %zu", + 1, + parcHashCodeTable_Length(table->storageTableById)); + + assertTrue(parcHashCodeTable_Length(table->indexByAddressPair) == 1, + "Incorrect storage table size, expected %u got %zu", + 1, + parcHashCodeTable_Length(table->indexByAddressPair)); + metisConnectionTable_Destroy(&table); + + assertTrue(data->destroyCount == 1, "metisConnectionTable_Destroy did not call entry's destroyer"); + + mockIoOperationsData_Destroy(&ops); + assertTrue(parcMemory_Outstanding() == 0, "Got memory imbalance on create/destroy: %u", parcMemory_Outstanding()); +} + +// ============================================ + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisConnectionTable_FindByAddressPair); + LONGBOW_RUN_TEST_CASE(Global, metisConnectionTable_FindById); + LONGBOW_RUN_TEST_CASE(Global, metisConnectionTable_Remove); + LONGBOW_RUN_TEST_CASE(Global, metisConnectionTable_RemoveById); + +// LONGBOW_RUN_TEST_CASE(Global, metisConnectionTable_GetEntries); +} + +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, metisConnectionTable_FindByAddressPair) +{ + MetisConnectionTable *table = metisConnectionTable_Create(); + + // add the test set to the table + for (int i = 0; test_set[i].localAddr != 0; i++) { + test_set[i].ops = mockIoOperationsData_CreateSimple(test_set[i].localAddr, test_set[i].remoteAddr, test_set[i].id, true, true, true); + assertNotNull(test_set[i].ops, "Got null from testdata_CreateSimple index %d", i); + test_set[i].conn = metisConnection_Create(test_set[i].ops); + assertNotNull(test_set[i].conn, "Got null from metisConnection_Create index %d", i); + metisConnectionTable_Add(table, test_set[i].conn); + } + + + // now make sure we can find them all by their address pair + for (int i = 0; test_set[i].localAddr != 0; i++) { + const MetisAddressPair *pair = test_set[i].ops->getAddressPair(test_set[i].ops); + const MetisConnection *conn = metisConnectionTable_FindByAddressPair(table, pair); + assertTrue(conn == test_set[i].conn, + "id %u returned wrong pointer, expected %p got %p", + test_set[i].id, + (void *) test_set[i].conn, + (void *) conn); + } + + // cleanup and verify destructions + metisConnectionTable_Destroy(&table); + + for (int i = 0; test_set[i].localAddr != 0; i++) { + MockIoOperationsData *data = metisIoOperations_GetClosure(test_set[i].ops); + assertTrue(data->destroyCount == 1, "Did not destroy data element %d, count %u", i, data->destroyCount); + mockIoOperationsData_Destroy(&test_set[i].ops); + } +} + +LONGBOW_TEST_CASE(Global, metisConnectionTable_FindById) +{ + MetisConnectionTable *table = metisConnectionTable_Create(); + + // add the test set to the table + for (int i = 0; test_set[i].localAddr != 0; i++) { + test_set[i].ops = mockIoOperationsData_CreateSimple(test_set[i].localAddr, test_set[i].remoteAddr, test_set[i].id, true, true, true); + assertNotNull(test_set[i].ops, "Got null from testdata_CreateSimple index %d", i); + test_set[i].conn = metisConnection_Create(test_set[i].ops); + assertNotNull(test_set[i].conn, "Got null from metisConnection_Create index %d", i); + metisConnectionTable_Add(table, test_set[i].conn); + } + + + // now make sure we can find them all by their id + for (int i = 0; test_set[i].localAddr != 0; i++) { + const MetisConnection *conn = metisConnectionTable_FindById(table, test_set[i].id); + assertTrue(conn == test_set[i].conn, + "id %u returned wrong pointer, expected %p got %p", + test_set[i].id, + (void *) test_set[i].conn, + (void *) conn); + } + + // cleanup and verify destructions + metisConnectionTable_Destroy(&table); + + for (int i = 0; test_set[i].localAddr != 0; i++) { + MockIoOperationsData *data = metisIoOperations_GetClosure(test_set[i].ops); + assertTrue(data->destroyCount == 1, "Did not destroy data element %d, count %u", i, data->destroyCount); + mockIoOperationsData_Destroy(&test_set[i].ops); + } +} + +LONGBOW_TEST_CASE(Global, metisConnectionTable_Remove) +{ + MetisConnectionTable *table = metisConnectionTable_Create(); + + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MetisConnection *conn = metisConnection_Create(ops); + metisConnectionTable_Add(table, conn); + + + // Check preconditions + assertTrue(parcHashCodeTable_Length(table->storageTableById) == 1, + "storageTableById wrong size, expected %u got %zu", + 1, + parcHashCodeTable_Length(table->storageTableById)); + + assertTrue(parcHashCodeTable_Length(table->indexByAddressPair) == 1, + "indexByAddressPair wrong size, expected %u got %zu", + 1, + parcHashCodeTable_Length(table->indexByAddressPair)); + + // test the operation + metisConnectionTable_Remove(table, conn); + + // check post conditions + assertTrue(parcHashCodeTable_Length(table->storageTableById) == 0, + "storageTableById wrong size, expected %u got %zu", + 0, + parcHashCodeTable_Length(table->storageTableById)); + + assertTrue(parcHashCodeTable_Length(table->indexByAddressPair) == 0, + "indexByAddressPair wrong size, expected %u got %zu", + 0, + parcHashCodeTable_Length(table->indexByAddressPair)); + + // cleanup + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + assertTrue(data->destroyCount == 1, "Remove did not destroy data, count %u", data->destroyCount); + mockIoOperationsData_Destroy(&ops); + metisConnectionTable_Destroy(&table); +} + +LONGBOW_TEST_CASE(Global, metisConnectionTable_RemoveById) +{ + MetisConnectionTable *table = metisConnectionTable_Create(); + + unsigned connid = 3; + + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, connid, true, true, true); + MetisConnection *conn = metisConnection_Create(ops); + metisConnectionTable_Add(table, conn); + + // Check preconditions + assertTrue(parcHashCodeTable_Length(table->storageTableById) == 1, + "storageTableById wrong size, expected %u got %zu", + 1, + parcHashCodeTable_Length(table->storageTableById)); + + assertTrue(parcHashCodeTable_Length(table->indexByAddressPair) == 1, + "indexByAddressPair wrong size, expected %u got %zu", + 1, + parcHashCodeTable_Length(table->indexByAddressPair)); + + // test the operation + metisConnectionTable_RemoveById(table, connid); + + + // check post conditions + assertTrue(parcHashCodeTable_Length(table->storageTableById) == 0, + "storageTableById wrong size, expected %u got %zu", + 0, + parcHashCodeTable_Length(table->storageTableById)); + + assertTrue(parcHashCodeTable_Length(table->indexByAddressPair) == 0, + "indexByAddressPair wrong size, expected %u got %zu", + 0, + parcHashCodeTable_Length(table->indexByAddressPair)); + + // cleanup + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + assertTrue(data->destroyCount == 1, "Remove did not destroy data, count %u", data->destroyCount); + mockIoOperationsData_Destroy(&ops); + metisConnectionTable_Destroy(&table); +} + +LONGBOW_TEST_CASE(Global, metisConnectionTable_GetEntries) +{ + MetisConnectionTable *table = metisConnectionTable_Create(); + + size_t count = 0; + // add the test set to the table + for (int i = 0; test_set[i].localAddr != 0; i++) { + test_set[i].ops = mockIoOperationsData_CreateSimple(test_set[i].localAddr, test_set[i].remoteAddr, test_set[i].id, true, true, true); + assertNotNull(test_set[i].ops, "Got null from testdata_CreateSimple index %d", i); + test_set[i].conn = metisConnection_Create(test_set[i].ops); + assertNotNull(test_set[i].conn, "Got null from metisConnection_Create index %d", i); + metisConnectionTable_Add(table, test_set[i].conn); + count++; + } + + MetisConnectionList *list = metisConnectionTable_GetEntries(table); + assertTrue(metisConnectionList_Length(list) == count, "List wrong size, expected %zu got %zu", count, metisConnectionList_Length(list)); + + // now verify each entry. The entries are not necessarily in the same order + for (int i = 0; i < count; i++) { + MetisConnection *test = metisConnectionList_Get(list, i); + const MetisAddressPair *test_pair = metisConnection_GetAddressPair(test); + const MetisAddressPair *truth_pair = test_set[i].ops->getAddressPair(test_set[i].ops); + assertTrue(metisAddressPair_Equals(test_pair, truth_pair), "Address pairs not equal, index %d", i); + } + + metisConnectionList_Destroy(&list); + for (int i = 0; test_set[i].localAddr != 0; i++) { + MockIoOperationsData *data = metisIoOperations_GetClosure(test_set[i].ops); + assertTrue(data->destroyCount == 1, "Did not destroy data element %d, count %u", i, data->destroyCount); + mockIoOperationsData_Destroy(&test_set[i].ops); + } +} + +// ============================================================================== + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisConnectionTable_AddressPairEquals_IsEqual); + LONGBOW_RUN_TEST_CASE(Local, metisConnectionTable_AddressPairEquals_IsNotEqual); + LONGBOW_RUN_TEST_CASE(Local, metisConnectionTable_AddressPairHashCode); + LONGBOW_RUN_TEST_CASE(Local, metisConnectionTable_ConnectionDestroyer); + LONGBOW_RUN_TEST_CASE(Local, metisConnectionTable_ConnectionIdDestroyer); + LONGBOW_RUN_TEST_CASE(Local, metisConnectionTable_ConnectionIdEquals_IsEqual); + LONGBOW_RUN_TEST_CASE(Local, metisConnectionTable_ConnectionIdEquals_IsNotEqual); + LONGBOW_RUN_TEST_CASE(Local, metisConnectionTable_ConnectionIdHashCode); +} + +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; +} + + +LONGBOW_TEST_CASE(Local, metisConnectionTable_AddressPairEquals_IsEqual) +{ + CPIAddress *a1 = cpiAddress_CreateFromInterface(1); + CPIAddress *a2 = cpiAddress_CreateFromInterface(2); + MetisAddressPair *pair_a = metisAddressPair_Create(a1, a2); + + CPIAddress *b1 = cpiAddress_CreateFromInterface(1); + CPIAddress *b2 = cpiAddress_CreateFromInterface(2); + MetisAddressPair *pair_b = metisAddressPair_Create(b1, b2); + + bool success = metisConnectionTable_AddressPairEquals((void *) pair_a, (void *) pair_b); + assertTrue(success, "Equal address pairs do not compare"); + + metisAddressPair_Release(&pair_a); + metisAddressPair_Release(&pair_b); + cpiAddress_Destroy(&a1); + cpiAddress_Destroy(&a2); + cpiAddress_Destroy(&b1); + cpiAddress_Destroy(&b2); +} + +LONGBOW_TEST_CASE(Local, metisConnectionTable_AddressPairEquals_IsNotEqual) +{ + CPIAddress *a1 = cpiAddress_CreateFromInterface(1); + CPIAddress *a2 = cpiAddress_CreateFromInterface(2); + MetisAddressPair *pair_a = metisAddressPair_Create(a1, a2); + + CPIAddress *b1 = cpiAddress_CreateFromInterface(1); + CPIAddress *b2 = cpiAddress_CreateFromInterface(2); + MetisAddressPair *pair_b = metisAddressPair_Create(b2, b1); + + bool success = metisConnectionTable_AddressPairEquals((void *) pair_a, (void *) pair_b); + assertFalse(success, "Unequal address pairs compare as equal"); + + metisAddressPair_Release(&pair_a); + metisAddressPair_Release(&pair_b); + cpiAddress_Destroy(&a1); + cpiAddress_Destroy(&a2); + cpiAddress_Destroy(&b1); + cpiAddress_Destroy(&b2); +} + +LONGBOW_TEST_CASE(Local, metisConnectionTable_AddressPairHashCode) +{ + CPIAddress *a1 = cpiAddress_CreateFromInterface(1); + CPIAddress *a2 = cpiAddress_CreateFromInterface(2); + MetisAddressPair *pair_a = metisAddressPair_Create(a1, a2); + + HashCodeType truth = metisAddressPair_HashCode(pair_a); + HashCodeType hash = metisConnectionTable_AddressPairHashCode((void *) pair_a); + + assertTrue(truth == hash, "Incorrect hash code, expected %04"PRIX64 "got %04"PRIX64, truth, hash); + + metisAddressPair_Release(&pair_a); + cpiAddress_Destroy(&a1); + cpiAddress_Destroy(&a2); +} + +LONGBOW_TEST_CASE(Local, metisConnectionTable_ConnectionDestroyer) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MetisConnection *conn = metisConnection_Create(ops); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + assertTrue(data->destroyCount == 0, "testdata_CreateSimple did not zero destroyCount"); + + metisConnectionTable_ConnectionDestroyer((void **) &conn); + + assertTrue(data->destroyCount == 1, "metisConnectionTable_ConnectionDestroyer did not call destroy on MetisIoOperations"); + mockIoOperationsData_Destroy(&ops); + + assertTrue(parcMemory_Outstanding() == 0, "Memory imbalance, expected 0 got %u", parcMemory_Outstanding()); +} + +LONGBOW_TEST_CASE(Local, metisConnectionTable_ConnectionIdDestroyer) +{ + unsigned *aptr = parcMemory_Allocate(sizeof(unsigned)); + assertNotNull(aptr, "parcMemory_Allocate(%zu) returned NULL", sizeof(unsigned)); + metisConnectionTable_ConnectionIdDestroyer((void **) &aptr); + assertNull(aptr, "destroyer did not null pointer"); + assertTrue(parcMemory_Outstanding() == 0, "Memory imbalance, expected 0 got %u", parcMemory_Outstanding()); +} + +LONGBOW_TEST_CASE(Local, metisConnectionTable_ConnectionIdEquals_IsEqual) +{ + unsigned a = 0x01020304; + unsigned b = 0x01020304; + + bool success = metisConnectionTable_ConnectionIdEquals(&a, &b); + assertTrue(success, "equal unsigned pointers do not compare"); +} + +LONGBOW_TEST_CASE(Local, metisConnectionTable_ConnectionIdEquals_IsNotEqual) +{ + unsigned a = 0x01020304; + unsigned b = 0x01020305; + + bool success = metisConnectionTable_ConnectionIdEquals(&a, &b); + assertFalse(success, "Unequal unsigned pointers compare as equal"); +} + +LONGBOW_TEST_CASE(Local, metisConnectionTable_ConnectionIdHashCode) +{ + unsigned a = 0x01020304; + + HashCodeType truth = parcHash32_Int32(a); + HashCodeType hash = metisConnectionTable_ConnectionIdHashCode(&a); + + assertTrue(truth == hash, "Incorrect hash code, expected %04"PRIX64 "got %04"PRIX64, truth, hash); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_ConnectionTable); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/core/test/test_metis_Dispatcher.c b/metis/ccnx/forwarder/metis/core/test/test_metis_Dispatcher.c new file mode 100644 index 00000000..3845d0d5 --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/test/test_metis_Dispatcher.c @@ -0,0 +1,733 @@ +/* + * 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_Dispatcher.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <parc/algol/parc_EventQueue.h> +#include <parc/logging/parc_LogReporterTextStdout.h> + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +LONGBOW_TEST_RUNNER(metis_Dispatcher) +{ + // 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. +#ifdef PARC_MEMORY + // The CreateDestroy fixture diagnoses issues with the debug memory allocator, + // which will fail if that allocator is not in use. + LONGBOW_RUN_TEST_FIXTURE(CreateDestroy); +#endif + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(StreamBufferConnect); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_Dispatcher) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_Dispatcher) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ================================================================================= +LONGBOW_TEST_FIXTURE(CreateDestroy) +{ + LONGBOW_RUN_TEST_CASE(CreateDestroy, metisDispatcher_Create_Destroy); + LONGBOW_RUN_TEST_CASE(CreateDestroy, metisDispatcher_Memory); +} + +LONGBOW_TEST_FIXTURE_SETUP(CreateDestroy) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(CreateDestroy) +{ + 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(CreateDestroy, metisDispatcher_Create_Destroy) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisDispatcher *dispatcher = metisDispatcher_Create(logger); + metisDispatcher_Destroy(&dispatcher); + metisLogger_Release(&logger); + assertTrue(parcSafeMemory_ReportAllocation(STDOUT_FILENO) == 0, "Got memory imbalance on create/destroy: %u", parcMemory_Outstanding()); +} + +/** + * Ensure that the dispatcher is using parc memory inside event scheduler + */ +LONGBOW_TEST_CASE(CreateDestroy, metisDispatcher_Memory) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisDispatcher *dispatcher = metisDispatcher_Create(logger); + size_t baseline = parcMemory_Outstanding(); + + PARCEventBuffer *buffer = parcEventBuffer_Create(); + + assertTrue(parcMemory_Outstanding() > baseline, + "parcEventBuffer_Create() did not increase parcMemory_Outstanding: baseline %zu now %u", + baseline, + parcMemory_Outstanding()); + + parcEventBuffer_Destroy(&buffer); + + assertTrue(parcMemory_Outstanding() == baseline, + "parcEventBuffer_Destroy() did reduce to baseline: baseline %zu now %u", + baseline, + parcMemory_Outstanding()); + + metisDispatcher_Destroy(&dispatcher); + metisLogger_Release(&logger); + + assertTrue(parcSafeMemory_ReportAllocation(STDOUT_FILENO) == 0, "Got memory imbalance on create/destroy: %u", parcMemory_Outstanding()); +} + +// ================================================================================= + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisDispatcher_CreateTimer_Oneshot); + LONGBOW_RUN_TEST_CASE(Global, metisDispatcher_CreateTimer_Periodic); + LONGBOW_RUN_TEST_CASE(Global, metisDispatcher_StopTimer); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisDispatcher *dispatcher = metisDispatcher_Create(logger); + metisLogger_Release(&logger); + + longBowTestCase_SetClipBoardData(testCase, dispatcher); + + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + MetisDispatcher *dispatcher = longBowTestCase_GetClipBoardData(testCase); + metisDispatcher_Destroy(&dispatcher); + + 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; +} + +static void +timerCallback(int fd, PARCEventType which_event, void *user_data) +{ + assertTrue(which_event & PARCEventType_Timeout, "Event incorrect, expecting %X set, got %X", PARCEventType_Timeout, which_event); + (*(unsigned *) user_data)++; +} + +LONGBOW_TEST_CASE(Global, metisDispatcher_CreateTimer_Oneshot) +{ + MetisDispatcher *dispatcher = longBowTestCase_GetClipBoardData(testCase); + + unsigned timerCallbackCount = 0; + PARCEventTimer *event = metisDispatcher_CreateTimer(dispatcher, false, timerCallback, &timerCallbackCount); + assertNotNull(event, "Got null event from metisDispatcher_CreateTimer"); + + // 10 msec + struct timeval timeout = { 0, 10000 }; + metisDispatcher_StartTimer(dispatcher, event, &timeout); + + // run for 250 msec; + struct timeval runtime = { 0, 250000 }; + metisDispatcher_RunDuration(dispatcher, &runtime); + + assertTrue(timerCallbackCount == 1, "Incorrect timer callbacks, expected %u got %u", 1, timerCallbackCount); + metisDispatcher_DestroyTimerEvent(dispatcher, &event); +} + +LONGBOW_TEST_CASE(Global, metisDispatcher_CreateTimer_Periodic) +{ +#ifdef __ARMEL__ + testUnimplemented("Test not implemented on ARMEL, timers too inaccurate"); +#else + MetisDispatcher *dispatcher = longBowTestCase_GetClipBoardData(testCase); + + unsigned timerCallbackCount = 0; + PARCEventTimer *event = metisDispatcher_CreateTimer(dispatcher, true, timerCallback, &timerCallbackCount); + assertNotNull(event, "Got null event from metisDispatcher_CreateTimer"); + + // 10 msec + struct timeval timeout = { 0, 10000 }; + metisDispatcher_StartTimer(dispatcher, event, &timeout); + + // run for 255 msec. Use an offset time to run so its clear what we should be after + // the 25th event and before the 26th event. + + struct timeval runtime = { 0, 255000 }; + metisDispatcher_RunDuration(dispatcher, &runtime); + + // make sure it runs at least twice (is periodic). Could run as many as 25. + assertTrue(timerCallbackCount >= 2, "Incorrect timer callbacks, expected at least %u got %u", 2, timerCallbackCount); + metisDispatcher_DestroyTimerEvent(dispatcher, &event); +#endif +} + +LONGBOW_TEST_CASE(Global, metisDispatcher_StopTimer) +{ + MetisDispatcher *dispatcher = longBowTestCase_GetClipBoardData(testCase); + + unsigned timerCallbackCount = 0; + PARCEventTimer *event = metisDispatcher_CreateTimer(dispatcher, true, timerCallback, &timerCallbackCount); + assertNotNull(event, "Got null event from metisDispatcher_CreateTimer"); + + // 10 msec + struct timeval timeout = { 0, 10000 }; + metisDispatcher_StartTimer(dispatcher, event, &timeout); + + // run for 55 msec (5 events), then stop the timer and run another 55 msec + + struct timeval runtime = { 0, 55000 }; + metisDispatcher_RunDuration(dispatcher, &runtime); + + metisDispatcher_StopTimer(dispatcher, event); + metisDispatcher_RunDuration(dispatcher, &runtime); + + // not sure how many times it will fire, but it should not fire more than 5 times + assertTrue(timerCallbackCount <= 5, "Incorrect timer callbacks, expected no more than %u got %u", 5, timerCallbackCount); + metisDispatcher_DestroyTimerEvent(dispatcher, &event); +} + +// ================================================================================= + +typedef struct open_connection_state { +// MetisForwarder * metis; + MetisDispatcher *dispatcher; + + CPIAddress *a; + CPIAddress *b; + MetisAddressPair *pair; + size_t baselineMemoryBalance; + + // if serverSocket > 0, then its allocated + int serverSocket; + struct sockaddr *serverAddr; + socklen_t serverAddrLength; +} OpenConnectionState; + +static void +listenToInet(OpenConnectionState *ocs) +{ + struct sockaddr_in server; + memset(&server, 0, sizeof(server)); + server.sin_family = PF_INET; + server.sin_addr.s_addr = INADDR_ANY; + server.sin_port = INPORT_ANY; + + int fd = socket(PF_INET, SOCK_STREAM, 0); + assertFalse(fd < 0, "error on bind: (%d) %s", errno, strerror(errno)); + + // Set non-blocking flag + int flags = fcntl(fd, F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)\n", errno); + int failure = fcntl(fd, F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)\n", errno); + + failure = bind(fd, (struct sockaddr *) &server, sizeof(server)); + assertFalse(failure, "error on bind: (%d) %s", errno, strerror(errno)); + + failure = listen(fd, 16); + assertFalse(failure, "error on listen: (%d) %s", errno, strerror(errno)); + + ocs->serverSocket = fd; + ocs->serverAddrLength = sizeof(struct sockaddr_in); + ocs->serverAddr = parcMemory_Allocate(ocs->serverAddrLength); + assertNotNull(ocs->serverAddr, "parcMemory_Allocate(%u) returned NULL", ocs->serverAddrLength); + + failure = getsockname(fd, ocs->serverAddr, &ocs->serverAddrLength); + assertFalse(failure, "error on getsockname: (%d) %s", errno, strerror(errno)); +} + +static void +listenToInet6(OpenConnectionState *ocs) +{ + struct sockaddr_in6 server; + memset(&server, 0, sizeof(server)); + server.sin6_family = PF_INET6; + server.sin6_addr = in6addr_any; + server.sin6_port = INPORT_ANY; + + int fd = socket(PF_INET6, SOCK_STREAM, 0); + assertFalse(fd < 0, "error on bind: (%d) %s", errno, strerror(errno)); + + // Set non-blocking flag + int flags = fcntl(fd, F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)\n", errno); + int failure = fcntl(fd, F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)\n", errno); + + failure = bind(fd, (struct sockaddr *) &server, sizeof(server)); + assertFalse(failure, "error on bind: (%d) %s", errno, strerror(errno)); + + failure = listen(fd, 16); + assertFalse(failure, "error on listen: (%d) %s", errno, strerror(errno)); + + ocs->serverSocket = fd; + ocs->serverAddrLength = sizeof(struct sockaddr_in6); + ocs->serverAddr = parcMemory_Allocate(ocs->serverAddrLength); + assertNotNull(ocs->serverAddr, "parcMemory_Allocate(%u) returned NULL", ocs->serverAddrLength); + + failure = getsockname(fd, ocs->serverAddr, &ocs->serverAddrLength); + assertFalse(failure, "error on getsockname: (%d) %s", errno, strerror(errno)); +} + +LONGBOW_TEST_FIXTURE(StreamBufferConnect) +{ + // -------- + // these two tests will cause assertions + LONGBOW_RUN_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_Invalid); + LONGBOW_RUN_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_DifferentTypes); + // -------- + + LONGBOW_RUN_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_INET); + LONGBOW_RUN_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_INET6); + + + LONGBOW_RUN_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_INET_Success); + LONGBOW_RUN_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_INET_Failure); + + LONGBOW_RUN_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_INET6_Success); + LONGBOW_RUN_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_INET6_Failure); + + LONGBOW_RUN_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferBindAndConnect_BindFail); + LONGBOW_RUN_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferBindAndConnect_BindOk_ConnectFail); + LONGBOW_RUN_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferBindAndConnect_BindOk_ConnectOk); +} + +LONGBOW_TEST_FIXTURE_SETUP(StreamBufferConnect) +{ + size_t baselineMemoryBalance = parcMemory_Outstanding(); + + OpenConnectionState *ocs = parcMemory_AllocateAndClear(sizeof(OpenConnectionState)); + assertNotNull(ocs, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(OpenConnectionState)); + memset(ocs, 0, sizeof(OpenConnectionState)); + + longBowTestCase_SetClipBoardData(testCase, ocs); + + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + ocs->dispatcher = metisDispatcher_Create(logger); + metisLogger_Release(&logger); + ocs->baselineMemoryBalance = baselineMemoryBalance; + + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(StreamBufferConnect) +{ + OpenConnectionState *ocs = longBowTestCase_GetClipBoardData(testCase); + metisDispatcher_Destroy(&ocs->dispatcher); + + if (ocs->a) { + cpiAddress_Destroy(&ocs->a); + } + + if (ocs->b) { + cpiAddress_Destroy(&ocs->b); + } + + if (ocs->pair) { + metisAddressPair_Release(&ocs->pair); + } + + if (ocs->serverSocket > 0) { + close(ocs->serverSocket); + } + + if (ocs->serverAddr) { + parcMemory_Deallocate((void **) &(ocs->serverAddr)); + } + + size_t baselineMemoryBalance = ocs->baselineMemoryBalance; + parcMemory_Deallocate((void **) &ocs); + + if (parcMemory_Outstanding() != baselineMemoryBalance) { + parcSafeMemory_ReportAllocation(STDOUT_FILENO); + assertTrue(parcMemory_Outstanding() == ocs->baselineMemoryBalance, + "memory imbalance in %s: exepcted %zu got %u", + longBowTestCase_GetName(testCase), + baselineMemoryBalance, + parcMemory_Outstanding()); + } + + + return LONGBOW_STATUS_SUCCEEDED; +} + +/** + * Tests invalid protocol family + */ +LONGBOW_TEST_CASE_EXPECTS(StreamBufferConnect, metisDispatcher_StreamBufferConnect_Invalid, .event = &LongBowTrapIllegalValue) +{ + OpenConnectionState *ocs = longBowTestCase_GetClipBoardData(testCase); + ocs->a = cpiAddress_CreateFromInterface(1); + ocs->b = cpiAddress_CreateFromInterface(2); + ocs->pair = metisAddressPair_Create(ocs->a, ocs->b); + + // this will throw a trap + fprintf(stderr, "\n\nTHIS IS NOT AN ERROR, EXPECTED TRAP: Assertion Illegal\n\n"); + metisDispatcher_StreamBufferConnect(ocs->dispatcher, ocs->pair); +} + +/** + * Tests different protocol families + */ +LONGBOW_TEST_CASE_EXPECTS(StreamBufferConnect, metisDispatcher_StreamBufferConnect_DifferentTypes, .event = &LongBowAssertEvent) +{ + OpenConnectionState *ocs = longBowTestCase_GetClipBoardData(testCase); + ocs->a = cpiAddress_CreateFromInet(&((struct sockaddr_in) { .sin_family = PF_INET })); + ocs->b = cpiAddress_CreateFromInet6(&((struct sockaddr_in6) { .sin6_family = PF_INET6 })); + ocs->pair = metisAddressPair_Create(ocs->a, ocs->b); + + // this will throw a trap + fprintf(stderr, "\n\nTHIS IS NOT AN ERROR, EXPECTED ASSERTION: Assertion cpiAddress_GetType...\n\n"); + metisDispatcher_StreamBufferConnect(ocs->dispatcher, ocs->pair); +} + +/** + * Use a port that is already in use + */ +LONGBOW_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferBindAndConnect_BindFail) +{ + OpenConnectionState *ocs = longBowTestCase_GetClipBoardData(testCase); + listenToInet(ocs); + + PARCEventQueue *buffer = parcEventQueue_Create(ocs->dispatcher->Base, -1, PARCEventQueueOption_CloseOnFree); + + // use the server address for our bind address. will cause a failure. + bool success = metisDispatcher_StreamBufferBindAndConnect(ocs->dispatcher, buffer, + ocs->serverAddr, ocs->serverAddrLength, + ocs->serverAddr, ocs->serverAddrLength); + parcEventQueue_Destroy(&buffer); + assertFalse(success, "metisDispatcher_StreamBufferBindAndConnect succedded with bind to in use address"); +} + +/** + * Good bind address, but bad connect to address + */ +LONGBOW_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferBindAndConnect_BindOk_ConnectFail) +{ + OpenConnectionState *ocs = longBowTestCase_GetClipBoardData(testCase); + + struct sockaddr_in goodAddress; + memset(&goodAddress, 0, sizeof(goodAddress)); + goodAddress.sin_family = PF_INET; + goodAddress.sin_addr.s_addr = INADDR_ANY; + goodAddress.sin_port = INPORT_ANY; + + struct sockaddr_in badAddress; + memset(&badAddress, 0xFF, sizeof(badAddress)); + badAddress.sin_family = PF_INET; + + PARCEventQueue *buffer = parcEventQueue_Create(ocs->dispatcher->Base, -1, PARCEventQueueOption_CloseOnFree); + + // use the server address for our bind address. will cause a failure. + bool success = metisDispatcher_StreamBufferBindAndConnect(ocs->dispatcher, buffer, + (struct sockaddr *) &goodAddress, sizeof(goodAddress), + (struct sockaddr *) &badAddress, sizeof(badAddress)); + + parcEventQueue_Destroy(&buffer); + assertFalse(success, "metisDispatcher_StreamBufferBindAndConnect succedded with bind to in use address"); +} + +/** + * Everything good, should succeed! + */ +LONGBOW_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferBindAndConnect_BindOk_ConnectOk) +{ + OpenConnectionState *ocs = longBowTestCase_GetClipBoardData(testCase); + listenToInet(ocs); + + struct sockaddr_in goodAddress; + memset(&goodAddress, 0, sizeof(goodAddress)); + goodAddress.sin_family = PF_INET; + goodAddress.sin_addr.s_addr = INADDR_ANY; + goodAddress.sin_port = INPORT_ANY; + + PARCEventQueue *buffer = parcEventQueue_Create(ocs->dispatcher->Base, -1, PARCEventQueueOption_CloseOnFree); + + // use the server address for our bind address. will cause a failure. + bool success = metisDispatcher_StreamBufferBindAndConnect(ocs->dispatcher, buffer, + (struct sockaddr *) &goodAddress, sizeof(goodAddress), + ocs->serverAddr, ocs->serverAddrLength); + + parcEventQueue_Destroy(&buffer); + assertTrue(success, "metisDispatcher_StreamBufferBindAndConnect did not succeed with good addresses"); +} + +LONGBOW_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_INET_Success) +{ + OpenConnectionState *ocs = longBowTestCase_GetClipBoardData(testCase); + listenToInet(ocs); + + uint16_t localPort = 9698; + printf("local port = %u\n", localPort); + + // Connection "from" will use localPort as the local port number + struct sockaddr_in goodLocalAddress; + memset(&goodLocalAddress, 0, sizeof(goodLocalAddress)); + goodLocalAddress.sin_family = PF_INET; + goodLocalAddress.sin_addr.s_addr = INADDR_ANY; + goodLocalAddress.sin_port = htons(localPort); + + ocs->a = cpiAddress_CreateFromInet(&goodLocalAddress); + + // ocs->serverAddr will have "0.0.0.0" as the address, so need to create + // something to 127.0.0.1 + struct sockaddr_in goodRemoteAddress; + memset(&goodRemoteAddress, 0, sizeof(goodRemoteAddress)); + goodRemoteAddress.sin_family = PF_INET; + goodRemoteAddress.sin_port = ((struct sockaddr_in *) ocs->serverAddr)->sin_port; + inet_pton(AF_INET, "127.0.0.1", &(goodRemoteAddress.sin_addr)); + + ocs->b = cpiAddress_CreateFromInet(&goodRemoteAddress); + + PARCEventQueue *result = metisDispatcher_StreamBufferConnect_INET(ocs->dispatcher, ocs->a, ocs->b); + + assertNotNull(result, "result buffer should be non-null for good local bind address 0.0.0.0 port %u", localPort) { + int res; + res = system("netstat -an -p tcp"); + assertTrue(res != -1, "Error on system call"); + res = system("ps -el"); + assertTrue(res != -1, "Error on system call"); + } + + // turn the crank a few times, then accept and make sure the bind address is correct + metisDispatcher_RunDuration(ocs->dispatcher, &((struct timeval) { 0, 1000 })); + + struct sockaddr_in clientAddr; + socklen_t clientAddrLen = sizeof(struct sockaddr_in); + int clientfd = accept(ocs->serverSocket, (struct sockaddr *) &clientAddr, &clientAddrLen); + assertFalse(clientfd < 0, "Error on accept: (%d) %s", errno, strerror(errno)); + + assertTrue(clientAddr.sin_port == goodLocalAddress.sin_port, + "Ports do not match, expecting %u got %u", + htons(goodLocalAddress.sin_port), + htons(clientAddr.sin_port)); + + close(clientfd); + metisDispatcher_RunCount(ocs->dispatcher, 1); + metisStreamBuffer_Destroy(&result); + metisDispatcher_RunCount(ocs->dispatcher, 1); +} + +/** + * Pass in a bad local address for the bind, will cause failure. + * should receive NULL back from call + */ +LONGBOW_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_INET_Failure) +{ + // This test only works on OSX, as linux will accept the 0xFFFFFFF address as localhost +#if !defined(__APPLE__) + testUnimplemented("Platform not supported"); +#else + OpenConnectionState *ocs = longBowTestCase_GetClipBoardData(testCase); + listenToInet(ocs); + + struct sockaddr_in badAddress; + memset(&badAddress, 0xFF, sizeof(badAddress)); + badAddress.sin_family = PF_INET; + + ocs->a = cpiAddress_CreateFromInet(&badAddress); + ocs->b = cpiAddress_CreateFromInet((struct sockaddr_in *) ocs->serverAddr); + + // use the server address for our bind address. will cause a failure. + PARCEventQueue *result = metisDispatcher_StreamBufferConnect_INET(ocs->dispatcher, ocs->a, ocs->b); + + assertNull(result, "result buffer should be null for bad local address"); +#endif +} + +LONGBOW_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_INET6_Success) +{ + OpenConnectionState *ocs = longBowTestCase_GetClipBoardData(testCase); + listenToInet6(ocs); + + struct sockaddr_in6 goodLocalAddress; + memset(&goodLocalAddress, 0, sizeof(goodLocalAddress)); + goodLocalAddress.sin6_family = PF_INET6; + goodLocalAddress.sin6_addr = in6addr_any; + goodLocalAddress.sin6_port = INPORT_ANY; + + ocs->a = cpiAddress_CreateFromInet6(&goodLocalAddress); + + // ocs->serverAddr will have "0.0.0.0" as the address, so need to create + // something to 127.0.0.1 + struct sockaddr_in6 goodRemoteAddress; + memset(&goodRemoteAddress, 0, sizeof(goodRemoteAddress)); + goodRemoteAddress.sin6_family = PF_INET6; + goodRemoteAddress.sin6_port = ((struct sockaddr_in6 *) ocs->serverAddr)->sin6_port; + inet_pton(AF_INET6, "::1", &(goodRemoteAddress.sin6_addr)); + + ocs->b = cpiAddress_CreateFromInet6(&goodRemoteAddress); + + PARCEventQueue *result = metisDispatcher_StreamBufferConnect_INET6(ocs->dispatcher, ocs->a, ocs->b); + + assertNotNull(result, "result buffer should be non-null for good local address"); + + + metisStreamBuffer_Destroy(&result); +} + +/** + * Pass in a bad local address for the bind, will cause failure. + * should receive NULL back from call + */ +LONGBOW_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_INET6_Failure) +{ + OpenConnectionState *ocs = longBowTestCase_GetClipBoardData(testCase); + listenToInet6(ocs); + + struct sockaddr_in6 badAddress; + memset(&badAddress, 0xFF, sizeof(badAddress)); + badAddress.sin6_family = PF_INET6; + + ocs->a = cpiAddress_CreateFromInet6(&badAddress); + ocs->b = cpiAddress_CreateFromInet6((struct sockaddr_in6 *) ocs->serverAddr); + + // use the server address for our bind address. will cause a failure. + PARCEventQueue *result = metisDispatcher_StreamBufferConnect_INET6(ocs->dispatcher, ocs->a, ocs->b); + + assertNull(result, "result buffer should be null for bad local address"); +} + +LONGBOW_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_INET) +{ + OpenConnectionState *ocs = longBowTestCase_GetClipBoardData(testCase); + listenToInet(ocs); + + struct sockaddr_in goodLocalAddress; + memset(&goodLocalAddress, 0, sizeof(goodLocalAddress)); + goodLocalAddress.sin_family = PF_INET; + goodLocalAddress.sin_addr.s_addr = INADDR_ANY; + goodLocalAddress.sin_port = INPORT_ANY; + + ocs->a = cpiAddress_CreateFromInet(&goodLocalAddress); + + // ocs->serverAddr will have "0.0.0.0" as the address, so need to create + // something to 127.0.0.1 + struct sockaddr_in goodRemoteAddress; + memset(&goodRemoteAddress, 0, sizeof(goodRemoteAddress)); + goodRemoteAddress.sin_family = PF_INET; + goodRemoteAddress.sin_port = ((struct sockaddr_in *) ocs->serverAddr)->sin_port; + inet_pton(AF_INET, "127.0.0.1", &(goodRemoteAddress.sin_addr)); + + ocs->b = cpiAddress_CreateFromInet(&goodRemoteAddress); + + MetisAddressPair *pair = metisAddressPair_Create(ocs->a, ocs->b); + PARCEventQueue *result = metisDispatcher_StreamBufferConnect(ocs->dispatcher, pair); + metisAddressPair_Release(&pair); + assertNotNull(result, "result buffer should be non-null for good local address"); + metisStreamBuffer_Destroy(&result); +} + +LONGBOW_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_INET6) +{ + OpenConnectionState *ocs = longBowTestCase_GetClipBoardData(testCase); + listenToInet6(ocs); + + struct sockaddr_in6 goodLocalAddress; + memset(&goodLocalAddress, 0, sizeof(goodLocalAddress)); + goodLocalAddress.sin6_family = PF_INET6; + goodLocalAddress.sin6_addr = in6addr_any; + goodLocalAddress.sin6_port = INPORT_ANY; + + ocs->a = cpiAddress_CreateFromInet6(&goodLocalAddress); + + // ocs->serverAddr will have "0.0.0.0" as the address, so need to create + // something to 127.0.0.1 + struct sockaddr_in6 goodRemoteAddress; + memset(&goodRemoteAddress, 0, sizeof(goodRemoteAddress)); + goodRemoteAddress.sin6_family = PF_INET6; + goodRemoteAddress.sin6_port = ((struct sockaddr_in6 *) ocs->serverAddr)->sin6_port; + inet_pton(AF_INET6, "::1", &(goodRemoteAddress.sin6_addr)); + + ocs->b = cpiAddress_CreateFromInet6(&goodRemoteAddress); + + MetisAddressPair *pair = metisAddressPair_Create(ocs->a, ocs->b); + PARCEventQueue *result = metisDispatcher_StreamBufferConnect(ocs->dispatcher, pair); + metisAddressPair_Release(&pair); + + assertNotNull(result, "result buffer should be non-null for good local address"); + + metisStreamBuffer_Destroy(&result); +} + +// ================================================================================= + +LONGBOW_TEST_FIXTURE(Local) +{ +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisDispatcher *dispatcher = metisDispatcher_Create(logger); + metisLogger_Release(&logger); + longBowTestCase_SetClipBoardData(testCase, dispatcher); + + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + MetisDispatcher *dispatcher = longBowTestCase_GetClipBoardData(testCase); + metisDispatcher_Destroy(&dispatcher); + + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_Dispatcher); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/core/test/test_metis_Forwarder.c b/metis/ccnx/forwarder/metis/core/test/test_metis_Forwarder.c new file mode 100644 index 00000000..b5f99594 --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/test/test_metis_Forwarder.c @@ -0,0 +1,241 @@ +/* + * 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_Forwarder.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +LONGBOW_TEST_RUNNER(metis_Forwarder) +{ + // 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_Forwarder) +{ + 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_Forwarder) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, byteArrayToUnsignedLong); + LONGBOW_RUN_TEST_CASE(Global, metisForwarder_Create); + LONGBOW_RUN_TEST_CASE(Global, metisForwarder_Destroy); + LONGBOW_RUN_TEST_CASE(Global, metisForwarder_GetDispatcher); + LONGBOW_RUN_TEST_CASE(Global, metisForwarder_GetMessenger); + LONGBOW_RUN_TEST_CASE(Global, metisForwarder_GetNextConnectionId); + LONGBOW_RUN_TEST_CASE(Global, metisForwarder_GetTicks); + LONGBOW_RUN_TEST_CASE(Global, metisForwarder_Log); + LONGBOW_RUN_TEST_CASE(Global, metisForwarder_Receive); + LONGBOW_RUN_TEST_CASE(Global, metis_run); + LONGBOW_RUN_TEST_CASE(Global, metis_stop); + + + LONGBOW_RUN_TEST_CASE(Global, metisForwarder_NanosToTicks_1sec); + LONGBOW_RUN_TEST_CASE(Global, metisForwarder_NanosToTicks_1msec); + LONGBOW_RUN_TEST_CASE(Global, metisForwarder_NanosToTicks_LessThanHz); + + LONGBOW_RUN_TEST_CASE(Global, metisForwarder_TicksToNanos_1sec); +} + +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, byteArrayToUnsignedLong) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Global, metisForwarder_Create) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Global, metisForwarder_Destroy) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Global, metisForwarder_GetDispatcher) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Global, metisForwarder_GetMessenger) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Global, metisForwarder_GetNextConnectionId) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Global, metisForwarder_GetTicks) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + MetisDispatcher *dispatcher = metisForwarder_GetDispatcher(metis); + + int msec = 50; + struct timeval duration = { 0, msec * 1000 }; + + // run for a bit to get things primed + metisDispatcher_RunDuration(dispatcher, &duration); + + MetisTicks t0 = metisForwarder_GetTicks(metis); + metisDispatcher_RunDuration(dispatcher, &duration); + MetisTicks t1 = metisForwarder_GetTicks(metis); + + int64_t tickDelta = (int64_t) (t1 - t0); + int64_t tickError = llabs(msec - tickDelta); + + metisForwarder_Destroy(&metis); + + printf("tickError = %" PRId64 "\n", tickError); + + // Test we're somewhere in the ballpark, betwen 40msec - 60msec + assertTrue(tickError <= 10, "tickError %" PRId64 + " (tickDelta %" PRId64 ")", + tickError, tickDelta); + +} + +LONGBOW_TEST_CASE(Global, metisForwarder_Log) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Global, metisForwarder_Receive) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Global, metis_run) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Global, metis_stop) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Global, metisForwarder_NanosToTicks_1sec) +{ + // 1 second + uint64_t nanos = 1000000000ULL; + MetisTicks t = metisForwarder_NanosToTicks(nanos); + + assertTrue(t == METISHZ, "1 second in nanos did not equal METISHZ, expected %d, got %" PRIu64, METISHZ, t); +} + +LONGBOW_TEST_CASE(Global, metisForwarder_NanosToTicks_1msec) +{ + // 1 second + uint64_t nanos = 1000000ULL; + MetisTicks t = metisForwarder_NanosToTicks(nanos); + MetisTicks expected = METISHZ / 1000; + if (expected == 0) { + expected = 1; + } + + assertTrue(t == expected, "1 msec in nanos did not equal METISHZ/1000, expected %" PRIu64 ", got %" PRIu64, expected, t); +} + +LONGBOW_TEST_CASE(Global, metisForwarder_NanosToTicks_LessThanHz) +{ + // 1 second + uint64_t nanos = 1ULL; + MetisTicks t = metisForwarder_NanosToTicks(nanos); + MetisTicks expected = 1; + + assertTrue(t == expected, "1 nsec in nanos did not equal 1, expected %" PRIu64 ", got %" PRIu64, expected, t); +} + +LONGBOW_TEST_CASE(Global, metisForwarder_TicksToNanos_1sec) +{ + MetisTicks t = METISHZ; + uint64_t expected = ((1000000000ULL) * t / METISHZ); + + uint64_t nanos = metisForwarder_TicksToNanos(t); + assertTrue(nanos == expected, "METISHZ ticks does not equal 1sec, expected %" PRIu64 ", got %" PRIu64, expected, nanos); +} + +// ====================================================================== + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisForwarder_Seed); + LONGBOW_RUN_TEST_CASE(Local, signal_cb); +} + +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, metisForwarder_Seed) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, signal_cb) +{ + testUnimplemented("This test is unimplemented"); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_Forwarder); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/core/test/test_metis_Logger.c b/metis/ccnx/forwarder/metis/core/test/test_metis_Logger.c new file mode 100644 index 00000000..4b62b1d6 --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/test/test_metis_Logger.c @@ -0,0 +1,222 @@ +/* + * 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_Logger.c" +#include <stdio.h> +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +LONGBOW_TEST_RUNNER(metis_Logger) +{ + LONGBOW_RUN_TEST_FIXTURE(Global); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_Logger) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_Logger) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ========================================================== + +/* + * _testWritter will vsprintf to this buffer + */ +#define _logLength 1024 +static char _lastLogMessage[_logLength]; + +static int +_testWriter(const char *message) +{ + int written = 0; + written = snprintf(_lastLogMessage, _logLength, "%s", message); + return written; +} + +static PARCLogReporter * +_testWriter_Acquire(const PARCLogReporter *reporter) +{ + return parcObject_Acquire(reporter); +} + +static void +_testWriter_Release(PARCLogReporter **reporterPtr) +{ + parcObject_Release((void **) reporterPtr); +} + +static void +_testWriter_Report(PARCLogReporter *reporter, const PARCLogEntry *entry) +{ + char *string = parcLogEntry_ToString(entry); + _testWriter(string); + parcMemory_Deallocate((void **) &string); +} + +static PARCLogReporter * +_testWriter_Create(void) +{ + return parcLogReporter_Create(_testWriter_Acquire, _testWriter_Release, _testWriter_Report, NULL); +} + +// ========================================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisLogger_FacilityString_Found); + LONGBOW_RUN_TEST_CASE(Global, metisLogger_FacilityString_NotFound); + LONGBOW_RUN_TEST_CASE(Global, metisLogger_Create); + LONGBOW_RUN_TEST_CASE(Global, metisLogger_Acquire); + LONGBOW_RUN_TEST_CASE(Global, metisLogger_SetLogLevel); + LONGBOW_RUN_TEST_CASE(Global, metisLogger_IsLoggable_True); + LONGBOW_RUN_TEST_CASE(Global, metisLogger_IsLoggable_False); + LONGBOW_RUN_TEST_CASE(Global, metisLogger_Log_IsLoggable); + LONGBOW_RUN_TEST_CASE(Global, metisLogger_Log_IsNotLoggable); +} + +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, metisLogger_FacilityString_Found) +{ + for (MetisLoggerFacility i = 0; i < MetisLoggerFacility_END; i++) { + const char *test = metisLogger_FacilityString(i); + assertNotNull(test, "Got null string for facility %d", i); + } +} + +LONGBOW_TEST_CASE(Global, metisLogger_FacilityString_NotFound) +{ + const char *test = metisLogger_FacilityString(1000); + assertTrue(strcmp(test, "Unknown") == 0, "Got wrong string for unknown facility"); +} + +LONGBOW_TEST_CASE(Global, metisLogger_Create) +{ + PARCLogReporter *reporter = _testWriter_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Global, metisLogger_Acquire) +{ + PARCLogReporter *reporter = _testWriter_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + MetisLogger *copy = metisLogger_Acquire(logger); + metisLogger_Release(&logger); + metisLogger_Release(©); +} + +LONGBOW_TEST_CASE(Global, metisLogger_SetLogLevel) +{ + PARCLogReporter *reporter = _testWriter_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + metisLogger_SetLogLevel(logger, MetisLoggerFacility_IO, PARCLogLevel_Off); + + PARCLogLevel test = parcLog_GetLevel(logger->loggerArray[MetisLoggerFacility_IO]); + assertTrue(test == PARCLogLevel_Off, "wrong log level, expected %d got %d", PARCLogLevel_Off, test); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Global, metisLogger_IsLoggable_True) +{ + PARCLogReporter *reporter = _testWriter_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + metisLogger_SetLogLevel(logger, MetisLoggerFacility_IO, PARCLogLevel_Warning); + bool isLoggable = metisLogger_IsLoggable(logger, MetisLoggerFacility_IO, PARCLogLevel_Warning); + assertTrue(isLoggable, "Did not get true for isLoggable when expecting true"); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Global, metisLogger_IsLoggable_False) +{ + PARCLogReporter *reporter = _testWriter_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + metisLogger_SetLogLevel(logger, MetisLoggerFacility_IO, PARCLogLevel_Warning); + bool isLoggable = metisLogger_IsLoggable(logger, MetisLoggerFacility_IO, PARCLogLevel_Debug); + assertFalse(isLoggable, "Logging debug to warning facility should have been false"); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Global, metisLogger_Log_IsLoggable) +{ + PARCLogReporter *reporter = _testWriter_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + metisLogger_SetLogLevel(logger, MetisLoggerFacility_IO, PARCLogLevel_Warning); + memset(_lastLogMessage, 0, _logLength); + + metisLogger_Log(logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, "hello"); + assertTrue(strlen(_lastLogMessage) > 0, "Did not write to log message"); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Global, metisLogger_Log_IsNotLoggable) +{ + PARCLogReporter *reporter = _testWriter_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + metisLogger_SetLogLevel(logger, MetisLoggerFacility_IO, PARCLogLevel_Warning); + memset(_lastLogMessage, 0, _logLength); + + metisLogger_Log(logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, "hello"); + assertTrue(strlen(_lastLogMessage) == 0, "Should not have written to log message"); + metisLogger_Release(&logger); +} + + +// ========================================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_Logger); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} + diff --git a/metis/ccnx/forwarder/metis/core/test/test_metis_Message.c b/metis/ccnx/forwarder/metis/core/test/test_metis_Message.c new file mode 100644 index 00000000..345a1440 --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/test/test_metis_Message.c @@ -0,0 +1,946 @@ +/* + * 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_Message.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <parc/logging/parc_LogReporterTextStdout.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_Message) +{ + // 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_Message) +{ + 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_Message) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisMessage_Create_InterestV0); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_Create_ObjectV0); + + LONGBOW_RUN_TEST_CASE(Global, metisMessage_Create_InterestV1); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_Create_ObjectV1); + + LONGBOW_RUN_TEST_CASE(Global, metisMessage_CreateFromBuffer); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_CreateFromArray); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_CreateFromElasticBuffer); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_CreateFromBuffer_BadMessage); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_CreateFromArray_BadMessage); + + LONGBOW_RUN_TEST_CASE(Global, metisMessage_Length); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_Append); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_Write); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_GetConnectionId); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_GetReceiveTime); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_ReadFromBuffer); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_Copy); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_GetMessageType); + + LONGBOW_RUN_TEST_CASE(Global, metisMessage_GetName); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasName_True); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasName_False); + + LONGBOW_RUN_TEST_CASE(Global, metisMessage_GetKeyIdHash); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasKeyId_True); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasKeyId_False); + + LONGBOW_RUN_TEST_CASE(Global, metisMessage_KeyIdEquals_IsEqual); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_KeyIdEquals_DifferentLength); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_KeyIdEquals_DifferentValue); + + LONGBOW_RUN_TEST_CASE(Global, metisMessage_ObjectHashEquals_IsEqual_Precomputed); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_ObjectHashEquals_IsEqual_Lazy); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_ObjectHashEquals_IsNotEqual); + + LONGBOW_RUN_TEST_CASE(Global, metisMessage_ObjectHashHashCode_Precomputed); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_ObjectHashHashCode_Lazy); + + LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasContentObjectHash_True); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasContentObjectHash_False); + + LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasHopLimit_True); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasHopLimit_False); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_GetHopLimit); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_SetHopLimit); + + LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasInterestLifetime); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_GetInterestLifetimeTicks); + + LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasExpirationTime); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasRecommendedCacheTime); + + LONGBOW_RUN_TEST_CASE(Global, metisMessage_SetGetExpirationTime); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_SetGetRecommendedCacheTime); + + LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasGetPublicKey); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_IsPublicKeyVerified_True); + LONGBOW_RUN_TEST_CASE(Global, metisMessage_IsPublicKeyVerified_False); + + LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasGetCertificate); +} + +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, metisMessage_Create_InterestV0) +{ + PARCEventBuffer *buff = parcEventBuffer_Create(); + parcEventBuffer_Append(buff, metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest)); + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger); + metisLogger_Release(&logger); + + assertNotNull(message, "Got null from metisMessage_CreateFromBuffer"); + assertTrue(message->ingressConnectionId == 1, "IngressConnectionId wrong, expected %d got %u", 1, message->ingressConnectionId); + assertTrue(message->receiveTime == 2, "receiveTime wrong, expected %u got %" PRIu64, 2, message->receiveTime); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_Create_ObjectV0) +{ + PARCEventBuffer *buff = parcEventBuffer_Create(); + parcEventBuffer_Append(buff, metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject)); + + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger); + metisLogger_Release(&logger); + + assertNotNull(message, "Got null from metisMessage_CreateFromBuffer"); + assertTrue(message->ingressConnectionId == 1, "IngressConnectionId wrong, expected %d got %u", 1, message->ingressConnectionId); + assertTrue(message->receiveTime == 2, "receiveTime wrong, expected %u got %" PRIu64, 2, message->receiveTime); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_Create_InterestV1) +{ + PARCEventBuffer *buff = parcEventBuffer_Create(); + parcEventBuffer_Append(buff, metisTestDataV1_Interest_AllFields, sizeof(metisTestDataV1_Interest_AllFields)); + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger); + metisLogger_Release(&logger); + + assertNotNull(message, "Got null from metisMessage_CreateFromBuffer"); + assertTrue(message->ingressConnectionId == 1, "IngressConnectionId wrong, expected %d got %u", 1, message->ingressConnectionId); + assertTrue(message->receiveTime == 2, "receiveTime wrong, expected %u got %" PRIu64, 2, message->receiveTime); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_Create_ObjectV1) +{ + PARCEventBuffer *buff = parcEventBuffer_Create(); + parcEventBuffer_Append(buff, metisTestDataV1_ContentObject_NameA_Crc32c, sizeof(metisTestDataV1_ContentObject_NameA_Crc32c)); + + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger); + metisLogger_Release(&logger); + + assertNotNull(message, "Got null from metisMessage_CreateFromBuffer"); + assertTrue(message->ingressConnectionId == 1, "IngressConnectionId wrong, expected %d got %u", 1, message->ingressConnectionId); + assertTrue(message->receiveTime == 2, "receiveTime wrong, expected %u got %" PRIu64, 2, message->receiveTime); + + metisMessage_Release(&message); +} + + +LONGBOW_TEST_CASE(Global, metisMessage_CreateFromArray) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + metisLogger_Release(&logger); + + + assertNotNull(message, "Got null from metisMessage_CreateFromArray"); + assertTrue(message->ingressConnectionId == 1, "IngressConnectionId wrong, expected %d got %u", 1, message->ingressConnectionId); + assertTrue(message->receiveTime == 2, "receiveTime wrong, expected %u got %" PRIu64, 2, message->receiveTime); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_CreateFromArray_BadMessage) +{ + // Invalid version + char message_str[] = "\xFFOnce upon a time, in a stack far away, a dangling pointer found its way to the top of the heap."; + printf("metisMessage_CreateFromArray_BadMessage attempting to process a bad message which should report an error:\n"); + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromArray((uint8_t *) message_str, sizeof(message_str), 1, 2, logger); + metisLogger_Release(&logger); + + assertNull(message, "Got null from metisMessage_CreateFromArray"); +} + +LONGBOW_TEST_CASE(Global, metisMessage_CreateFromBuffer) +{ + char message_str[] = "\x00Once upon a time, in a stack far away, a dangling pointer found its way to the top of the heap."; + + PARCEventBuffer *buff = parcEventBuffer_Create(); + parcEventBuffer_Append(buff, message_str, sizeof(message_str)); + + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger); + metisLogger_Release(&logger); + + assertNotNull(message, "Got null from metisMessage_CreateFromBuffer"); + assertTrue(message->ingressConnectionId == 1, "IngressConnectionId wrong, expected %d got %u", 1, message->ingressConnectionId); + assertTrue(message->receiveTime == 2, "receiveTime wrong, expected %u got %" PRIu64, 2, message->receiveTime); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_CreateFromElasticBuffer) +{ + char message_str[] = "\0x00Once upon a time, in a stack far away, a dangling pointer found its way to the top of the heap."; + + PARCBuffer *buff = parcBuffer_Wrap(message_str, sizeof(message_str), 0, sizeof(message_str)); + + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromParcBuffer(buff, 1, 2, logger); + metisLogger_Release(&logger); + + assertNotNull(message, "Got null from metisMessage_CreateFromBuffer"); + assertTrue(message->ingressConnectionId == 1, "IngressConnectionId wrong, expected %d got %u", 1, message->ingressConnectionId); + assertTrue(message->receiveTime == 2, "receiveTime wrong, expected %u got %" PRIu64, 2, message->receiveTime); + + metisMessage_Release(&message); + parcBuffer_Release(&buff); +} + +LONGBOW_TEST_CASE(Global, metisMessage_CreateFromBuffer_BadMessage) +{ + // Bad version + char message_str[] = "\xFFOnce upon a time, in a stack far away, a dangling pointer found its way to the top of the heap."; + + printf("metisMessage_CreateFromBuffer_BadMessage attempting to process a bad message which should report an error:\n"); + PARCEventBuffer *buff = parcEventBuffer_Create(); + parcEventBuffer_Append(buff, message_str, sizeof(message_str)); + + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger); + metisLogger_Release(&logger); + + assertNull(message, "Got null from metisMessage_CreateFromBuffer"); +} + +LONGBOW_TEST_CASE(Global, metisMessage_ReadFromBuffer) +{ + char message_str[] = "\x00Once upon a time, in a stack far away, a dangling pointer found its way to the top of the heap."; + + PARCEventBuffer *buff = parcEventBuffer_Create(); + parcEventBuffer_Append(buff, message_str, sizeof(message_str)); + + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_ReadFromBuffer(1, 2, buff, sizeof(message_str), logger); + metisLogger_Release(&logger); + + assertNotNull(message, "Got null from metisMessage_CreateFromBuffer"); + + assertTrue(parcEventBuffer_GetLength(message->messageBytes) == sizeof(message_str), + "Length of internal buffer wrong, expected %zu got %zu", + sizeof(message_str), + parcEventBuffer_GetLength(message->messageBytes)); + + uint8_t *p = parcEventBuffer_Pullup(message->messageBytes, sizeof(message_str)); + assertTrue(memcmp(p, message_str, sizeof(message_str)) == 0, "Internal buffer contents does not match test"); + assertTrue(message->ingressConnectionId == 1, "IngressConnectionId wrong, expected %d got %u", 1, message->ingressConnectionId); + assertTrue(message->receiveTime == 2, "receiveTime wrong, expected %u got %" PRIu64, 2, message->receiveTime); + assertTrue(parcEventBuffer_GetLength(buff) == 0, "Origin buffer not drained, expected 0, got %zu", parcEventBuffer_GetLength(buff)); + + metisMessage_Release(&message); + parcEventBuffer_Destroy(&buff); +} + +LONGBOW_TEST_CASE(Global, metisMessage_Append) +{ + char message_str[] = "\x00Once upon a time ..."; + + PARCEventBuffer *buffer = parcEventBuffer_Create(); + PARCEventBuffer *buff = parcEventBuffer_Create(); + parcEventBuffer_Append(buff, message_str, sizeof(message_str)); + + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger); + int result = metisMessage_Append(buffer, message); + + assertTrue(result == 0, "Got error from metisMessage_Append"); + metisLogger_Release(&logger); + metisMessage_Release(&message); + parcEventBuffer_Destroy(&buffer); +} + +LONGBOW_TEST_CASE(Global, metisMessage_Write) +{ + char message_str[] = "\x00Once upon a time ..."; + + PARCEventScheduler *scheduler = parcEventScheduler_Create(); + PARCEventQueue *queue = parcEventQueue_Create(scheduler, -1, PARCEventQueueOption_CloseOnFree); + + PARCEventBuffer *buff = parcEventBuffer_Create(); + parcEventBuffer_Append(buff, message_str, sizeof(message_str)); + + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger); + int result = metisMessage_Write(queue, message); + + assertTrue(result == 0, "Got error from metisMessage_Write"); + + // buff is deallocated by metisMessage_Release + metisLogger_Release(&logger); + metisMessage_Release(&message); + parcEventQueue_Destroy(&queue); + parcEventScheduler_Destroy(&scheduler); +} + +LONGBOW_TEST_CASE(Global, metisMessage_Length) +{ + char message_str[] = "\x00Once upon a time, in a stack far away, a dangling pointer found its way to the top of the heap."; + + PARCEventBuffer *buff = parcEventBuffer_Create(); + parcEventBuffer_Append(buff, message_str, sizeof(message_str)); + + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger); + metisLogger_Release(&logger); + + assertNotNull(message, "Got null from metisMessage_CreateFromBuffer"); + size_t length = metisMessage_Length(message); + assertTrue(length == sizeof(message_str), "Wrong length, expected %zu got %zu", sizeof(message_str), length); + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_GetConnectionId) +{ + char message_str[] = "\x00Once upon a time, in a stack far away, a dangling pointer found its way to the top of the heap."; + + PARCEventBuffer *buff = parcEventBuffer_Create(); + parcEventBuffer_Append(buff, message_str, sizeof(message_str)); + + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger); + metisLogger_Release(&logger); + + assertNotNull(message, "Got null from metisMessage_CreateFromBuffer"); + unsigned connid = metisMessage_GetIngressConnectionId(message); + + assertTrue(connid == 1, "Wrong length, expected %u got %u", 1, connid); + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_GetReceiveTime) +{ + char message_str[] = "\x00Once upon a time, in a stack far away, a dangling pointer found its way to the top of the heap."; + + PARCEventBuffer *buff = parcEventBuffer_Create(); + parcEventBuffer_Append(buff, message_str, sizeof(message_str)); + + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger); + metisLogger_Release(&logger); + + assertNotNull(message, "Got null from metisMessage_CreateFromBuffer"); + MetisTicks time = metisMessage_GetReceiveTime(message); + + assertTrue(time == 2, "Wrong receive time, expected %u got %" PRIu64, 2, time); + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_Copy) +{ + char message_str[] = "\x00Once upon a time, in a stack far away, a dangling pointer found its way to the top of the heap."; + + PARCEventBuffer *buff = parcEventBuffer_Create(); + parcEventBuffer_Append(buff, message_str, sizeof(message_str)); + + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger); + metisLogger_Release(&logger); + + assertNotNull(message, "Got null from metisMessage_CreateFromBuffer"); + assertTrue(message->refcount == 1, "Incorrect refcount, expected %u got %u", 1, message->refcount); + + MetisMessage *copy = metisMessage_Acquire(message); + assertTrue(message->refcount == 2, "Incorrect refcount, expected %u got %u", 2, message->refcount); + + metisMessage_Release(&message); + assertTrue(copy->refcount == 1, "Incorrect refcount, expected %u got %u", 1, message->refcount); + + metisMessage_Release(©); + + assertTrue(parcMemory_Outstanding() == 0, "Memory balance should be zero after destroying last copy, got %u", parcMemory_Outstanding()); +} + +LONGBOW_TEST_CASE(Global, metisMessage_GetMessageType) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + metisLogger_Release(&logger); + MetisMessagePacketType type = metisMessage_GetType(message); + + assertTrue(type == MetisMessagePacketType_ContentObject, "wrong type, expected %u got %u", MetisMessagePacketType_ContentObject, type); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_GetName) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + metisLogger_Release(&logger); + MetisTlvName *name = metisMessage_GetName(message); + MetisTlvName *truth = metisTlvName_Create(&metisTestDataV0_EncodedObject[metisTestDataV0_EncodedObject_name.offset], metisTestDataV0_EncodedObject_name.length); + + assertTrue(metisTlvName_Equals(truth, name), "Did not get back the right name"); + + metisTlvName_Release(&truth); + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_HasName_True) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + metisLogger_Release(&logger); + bool hasName = metisMessage_HasName(message); + assertTrue(hasName, "Message with a name says it does not"); + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_HasName_False) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_CPIMessage, sizeof(metisTestDataV0_CPIMessage), 1, 2, logger); + metisLogger_Release(&logger); + bool hasName = metisMessage_HasName(message); + assertFalse(hasName, "Message without a name says it does"); + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_HasContentObjectHash_True) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + metisLogger_Release(&logger); + bool hasHash = metisMessage_HasContentObjectHash(message); + assertTrue(hasHash, "Message with a content object hash says it does not"); + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_HasContentObjectHash_False) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest), 1, 2, logger); + metisLogger_Release(&logger); + bool hasHash = metisMessage_HasContentObjectHash(message); + assertTrue(hasHash, "Message without a content object hash says it does"); + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_GetKeyIdHash) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + metisLogger_Release(&logger); + + uint32_t truthhash = parcHash32_Data(&metisTestDataV0_EncodedObject[metisTestDataV0_EncodedObject_keyid.offset], metisTestDataV0_EncodedObject_keyid.length); + uint32_t testhash; + bool success = metisMessage_GetKeyIdHash(message, &testhash); + + assertTrue(success, "Failed metisMessage_GetKeyIdHash, returned false"); + assertTrue(truthhash == testhash, "Hash compared wrong, expected %08X, got %08X", truthhash, testhash); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_HasKeyId_True) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + metisLogger_Release(&logger); + bool hasKeyId = metisMessage_HasKeyId(message); + assertTrue(hasKeyId, "Message with a keyid says it does not"); + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_HasKeyId_False) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger); + metisLogger_Release(&logger); + bool hasKeyId = metisMessage_HasKeyId(message); + assertFalse(hasKeyId, "Message without a keyid says it does"); + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_KeyIdEquals_IsEqual) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *a = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + MetisMessage *b = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest), 1, 2, logger); + metisLogger_Release(&logger); + + assertTrue(metisMessage_KeyIdEquals(a, b), "Messages with equal keyids did not compare"); + metisMessage_Release(&a); + metisMessage_Release(&b); +} + +LONGBOW_TEST_CASE(Global, metisMessage_KeyIdEquals_DifferentLength) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *a = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + MetisMessage *b = metisMessage_CreateFromArray(metisTestDataV0_SecondObject, sizeof(metisTestDataV0_SecondObject), 1, 2, logger); + metisLogger_Release(&logger); + + assertFalse(metisMessage_KeyIdEquals(a, b), "Messages with differnt length keyids did compared equal"); + metisMessage_Release(&a); + metisMessage_Release(&b); +} + +LONGBOW_TEST_CASE(Global, metisMessage_KeyIdEquals_DifferentValue) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *a = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + MetisMessage *b = metisMessage_CreateFromArray(metisTestDataV0_SecondInterest, sizeof(metisTestDataV0_SecondInterest), 1, 2, logger); + metisLogger_Release(&logger); + + assertFalse(metisMessage_KeyIdEquals(a, b), "Messages with differnt keyids did compared equal"); + metisMessage_Release(&a); + metisMessage_Release(&b); +} + +LONGBOW_TEST_CASE(Global, metisMessage_ObjectHashEquals_IsEqual_Precomputed) +{ + // create messages from Interests, as those are precomputed + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *a = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest), 1, 2, logger); + MetisMessage *b = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest), 1, 2, logger); + metisLogger_Release(&logger); + + assertTrue(metisMessage_ObjectHashEquals(a, b), "Messages with equal ContentObjectHash did not compare"); + metisMessage_Release(&a); + metisMessage_Release(&b); +} + +LONGBOW_TEST_CASE(Global, metisMessage_ObjectHashEquals_IsEqual_Lazy) +{ + // create messages from content objects, as those are lazy computed + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *a = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + MetisMessage *b = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + metisLogger_Release(&logger); + + assertTrue(metisMessage_ObjectHashEquals(a, b), "Messages with equal ContentObjectHash did not compare"); + metisMessage_Release(&a); + metisMessage_Release(&b); +} + +LONGBOW_TEST_CASE(Global, metisMessage_ObjectHashEquals_IsNotEqual) +{ + // create messages from different content objects + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *a = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + MetisMessage *b = metisMessage_CreateFromArray(metisTestDataV0_SecondObject, sizeof(metisTestDataV0_SecondObject), 1, 2, logger); + metisLogger_Release(&logger); + + assertFalse(metisMessage_ObjectHashEquals(a, b), "Messages with unequal ContentObjectHash compared as equal"); + metisMessage_Release(&a); + metisMessage_Release(&b); +} + +LONGBOW_TEST_CASE(Global, metisMessage_ObjectHashHashCode_Precomputed) +{ + // create messages from Interests, as those are precomputed + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *a = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest), 1, 2, logger); + metisLogger_Release(&logger); + + uint32_t hashcode; + bool success = metisMessage_GetContentObjectHashHash(a, &hashcode); + assertTrue(success, "Returned false trying to get hash of contentobject hash"); + + metisMessage_Release(&a); +} + +LONGBOW_TEST_CASE(Global, metisMessage_ObjectHashHashCode_Lazy) +{ + // create messages from content object, as those are precomputed + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *a = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + metisLogger_Release(&logger); + + uint32_t hashcode; + bool success = metisMessage_GetContentObjectHashHash(a, &hashcode); + assertTrue(success, "Returned false trying to get hash of contentobject hash"); + + metisMessage_Release(&a); +} + +LONGBOW_TEST_CASE(Global, metisMessage_HasHopLimit_True) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest), 1, 2, logger); + metisLogger_Release(&logger); + + bool hasHopLimit = metisMessage_HasHopLimit(message); + assertTrue(hasHopLimit, "Message with a hop limit says it does not."); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_HasHopLimit_False) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest_no_hoplimit, sizeof(metisTestDataV0_EncodedInterest_no_hoplimit), 1, 2, logger); + metisLogger_Release(&logger); + + bool hasHopLimit = metisMessage_HasHopLimit(message); + assertFalse(hasHopLimit, "Message without a hop limit says it does."); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_GetHopLimit) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest), 1, 2, logger); + metisLogger_Release(&logger); + + uint8_t hoplimit = metisMessage_GetHopLimit(message); + assertTrue(hoplimit == 32, "Wrong hop limit, got %u expected %u.", hoplimit, 32); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_SetHopLimit) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest), 1, 2, logger); + metisLogger_Release(&logger); + + metisMessage_SetHopLimit(message, 99); + uint8_t hoplimit = metisMessage_GetHopLimit(message); + assertTrue(hoplimit == 99, "Wrong hop limit, got %u expected %u.", hoplimit, 99); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_HasInterestLifetime) +{ + PARCEventBuffer *buff = parcEventBuffer_Create(); + parcEventBuffer_Append(buff, metisTestDataV1_Interest_AllFields, sizeof(metisTestDataV1_Interest_AllFields)); + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger); + metisLogger_Release(&logger); + + assertTrue(metisMessage_HasInterestLifetime(message), "Should have returned true for interest lifetime"); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_GetInterestLifetimeTicks) +{ + PARCEventBuffer *buff = parcEventBuffer_Create(); + parcEventBuffer_Append(buff, metisTestDataV1_Interest_AllFields, sizeof(metisTestDataV1_Interest_AllFields)); + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger); + metisLogger_Release(&logger); + + // don't check actual value. It will vary based on METISHZ and rouding errors due to integer math + MetisTicks ticks = metisMessage_GetInterestLifetimeTicks(message); + assertTrue(ticks > 0, "Should have gotten positive value for interest lifetime ticks"); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_HasExpirationTime) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + // Note: Assumes metisTestDataV0_EncodedObject doesn't have ExpiryTime. + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + metisLogger_Release(&logger); + + bool hasExpiryTime = metisMessage_HasExpiryTime(message); + assertFalse(hasExpiryTime, "Message without ExpiryTime says it has one."); + + metisMessage_SetExpiryTimeTicks(message, 10000); + hasExpiryTime = metisMessage_HasExpiryTime(message); + assertTrue(hasExpiryTime, "Message with ExpiryTime says it doesn't have one."); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_HasRecommendedCacheTime) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + // Note: Assumes metisTestDataV0_EncodedObject doesn't have RCT. + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + metisLogger_Release(&logger); + + bool hasRCT = metisMessage_HasRecommendedCacheTime(message); + assertFalse(hasRCT, "Message without hasRCT says it has one."); + + metisMessage_SetRecommendedCacheTimeTicks(message, 10000); + hasRCT = metisMessage_HasRecommendedCacheTime(message); + assertTrue(hasRCT, "Message with hasRCT says it doesn't have one."); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_SetGetExpirationTime) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + // Note: Assumes metisTestDataV0_EncodedObject doesn't have RCT. + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + metisLogger_Release(&logger); + + uint64_t time = 12345; + metisMessage_SetExpiryTimeTicks(message, time); + assertTrue(time == metisMessage_GetExpiryTimeTicks(message), "Retrieved unexpected ExpiryTime"); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_SetGetRecommendedCacheTime) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + // Note: Assumes metisTestDataV0_EncodedObject doesn't have RCT. + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger); + metisLogger_Release(&logger); + + uint64_t time = 12345; + metisMessage_SetRecommendedCacheTimeTicks(message, time); + assertTrue(time == metisMessage_GetRecommendedCacheTimeTicks(message), "Retrieved unexpected RCT"); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisMessage_HasGetPublicKey) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + MetisMessage *contentWithKey = + metisMessage_CreateFromArray(metisTestDataV1_ContentObject_NameA_KeyId1_RsaSha256, + sizeof(metisTestDataV1_ContentObject_NameA_KeyId1_RsaSha256), 1, 2, logger); + + MetisMessage *interestWithKeyIdRestriction = + metisMessage_CreateFromArray(metisTestDataV1_Interest_NameAAndKeyId, + sizeof(metisTestDataV1_Interest_NameAAndKeyId), 1, 2, logger); + + metisLogger_Release(&logger); + + assertTrue(metisMessage_HasPublicKey(contentWithKey), "Expected to see a public key"); + assertFalse(metisMessage_HasPublicKey(interestWithKeyIdRestriction), "Expected to not see a public key"); + + PARCBuffer *key = metisMessage_GetPublicKey(contentWithKey); + + assertNotNull(key, "Expected to retrieve the public key"); + + metisMessage_Release(&contentWithKey); + metisMessage_Release(&interestWithKeyIdRestriction); +} + +LONGBOW_TEST_CASE(Global, metisMessage_IsPublicKeyVerified_False) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + MetisMessage *contentWithKey = + metisMessage_CreateFromArray(metisTestDataV1_ContentObject_NameA_KeyId1_RsaSha256, + sizeof(metisTestDataV1_ContentObject_NameA_KeyId1_RsaSha256), 1, 2, logger); + + MetisMessage *interestWithKeyIdRestriction = + metisMessage_CreateFromArray(metisTestDataV1_Interest_NameAAndKeyId, + sizeof(metisTestDataV1_Interest_NameAAndKeyId), 1, 2, logger); + + metisLogger_Release(&logger); + + assertFalse(metisMessage_IsKeyIdVerified(contentWithKey), "Expected key to not be verified."); + + // This is an interest. The keyId is actually a KeyId restriction, so will never be verified. + assertFalse(metisMessage_IsKeyIdVerified(interestWithKeyIdRestriction), "Expected key to not be verified."); + + PARCBuffer *key = metisMessage_GetPublicKey(contentWithKey); + + assertNotNull(key, "Expected to retrieve the public key"); + + metisMessage_Release(&contentWithKey); + metisMessage_Release(&interestWithKeyIdRestriction); +} + +LONGBOW_TEST_CASE(Global, metisMessage_IsPublicKeyVerified_True) +{ + testUnimplemented("Verification of KeyIds in ContentObjects is not yet implemented."); +} + +LONGBOW_TEST_CASE(Global, metisMessage_HasGetCertificate) +{ + testUnimplemented("Need test data with an encoded certificate."); +} + +// =================================================== + +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_Message); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/core/test/test_metis_NumberSet.c b/metis/ccnx/forwarder/metis/core/test/test_metis_NumberSet.c new file mode 100644 index 00000000..461e5e62 --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/test/test_metis_NumberSet.c @@ -0,0 +1,557 @@ +/* + * 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_NumberSet.c" +#include <LongBow/unit-test.h> + +#include <parc/algol/parc_SafeMemory.h> + +LONGBOW_TEST_RUNNER(metis_NumberSet) +{ + // 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_NumberSet) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_NumberSet) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Append_NoExpand); + LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Append_Expand); + LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Append_Duplicate); + LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Contains); + LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Copy); + LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Create_Destroy); + + LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Equals_IsEqual); + LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Equals_BothEmpty); + LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Equals_BothNull); + LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Equals_OneNull); + LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Equals_DifferentLengths); + LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Equals_IsNotEqual); + + LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_GetItem); + LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Length); + LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Subtract_Disjoint); + LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Subtract_Equivalent); + LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Subtract_Overlap); + + LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Remove_LastElement); + LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Remove_AllElements); + LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Remove_FirstElement); +} + +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, metisNumberSet_Append_NoExpand) +{ + MetisNumberSet *set = metisNumberSet_Create(); + + for (int i = 1; i <= set->limit; i++) { + bool result = metisNumberSet_Add(set, i); + assertTrue(result, "Got failure on append, i = %d", i); + assertTrue(set->length == i, "Set length wrong, expected %d got %zu", i, set->length); + } + + metisNumberSet_Release(&set); +} + +LONGBOW_TEST_CASE(Global, metisNumberSet_Append_Expand) +{ + MetisNumberSet *set = metisNumberSet_Create(); + + size_t limit = set->limit; + for (int i = 1; i <= limit + 5; i++) { + bool result = metisNumberSet_Add(set, i); + assertTrue(result, "Got failure on append, i = %d", i); + assertTrue(set->length == i, "Set length wrong, expected %d got %zu", i, set->length); + } + + metisNumberSet_Release(&set); +} + +LONGBOW_TEST_CASE(Global, metisNumberSet_Append_Duplicate) +{ + MetisNumberSet *set = metisNumberSet_Create(); + + for (int i = 1; i <= set->limit; i++) { + bool result = metisNumberSet_Add(set, i); + assertTrue(result, "Got failure on append, i = %d", i); + assertTrue(set->length == i, "Set length wrong, expected %d got %zu", i, set->length); + } + + for (int i = 1; i <= set->limit; i++) { + bool result = metisNumberSet_Add(set, i); + assertFalse(result, "Got success on duplicate append, i = %d", i); + assertTrue(set->length == set->limit, "Set length wrong, expected %zu got %zu", set->limit, set->length); + } + + metisNumberSet_Release(&set); +} + +LONGBOW_TEST_CASE(Global, metisNumberSet_Contains) +{ + MetisNumberSet *set = metisNumberSet_Create(); + + int limit = 10; + for (int i = 1; i <= limit; i++) { + bool result = metisNumberSet_Add(set, i); + assertTrue(result, "Got failure on append, i = %d", i); + assertTrue(set->length == i, "Set length wrong, expected %d got %zu", i, set->length); + } + + for (int i = 1; i <= limit; i++) { + bool result = metisNumberSet_Contains(set, i); + assertTrue(result, "Got missing member, i = %d", i); + } + + for (int i = limit + 1; i <= 2 * limit; i++) { + bool result = metisNumberSet_Contains(set, i); + assertFalse(result, "Got contains returned true for missing element, i = %d", i); + } + + metisNumberSet_Release(&set); +} + +LONGBOW_TEST_CASE(Global, metisNumberSet_Copy) +{ + MetisNumberSet *set = metisNumberSet_Create(); + + int limit = 10; + for (int i = 1; i <= limit; i++) { + bool result = metisNumberSet_Add(set, i); + assertTrue(result, "Got failure on append, i = %d", i); + assertTrue(set->length == i, "Set length wrong, expected %d got %zu", i, set->length); + } + + MetisNumberSet *copy = metisNumberSet_Acquire(set); + assertTrue(set->refcount == 2, "Set refcount not 2: %u", set->refcount); + + metisNumberSet_Release(©); + assertTrue(set->refcount == 1, "Set refcount not 1: %u", set->refcount); + + metisNumberSet_Release(&set); +} + +LONGBOW_TEST_CASE(Global, metisNumberSet_Create_Destroy) +{ + MetisNumberSet *set = metisNumberSet_Create(); + assertTrue(set->length == 0, "Set not 0 length on create: %zu", set->length); + assertTrue(set->refcount == 1, "Set refcount not 1: %u", set->refcount); + metisNumberSet_Release(&set); + + assertTrue(parcSafeMemory_ReportAllocation(STDOUT_FILENO) == 0, "Memory imbalance on create/destroy: %u", parcMemory_Outstanding()); +} + +LONGBOW_TEST_CASE(Global, metisNumberSet_Equals_IsEqual) +{ + // 0 is the terminator + unsigned a_set[] = { 1, 2, 3, 4, 5, 6, 7, 0 }; + unsigned b_set[] = { 1, 2, 3, 4, 5, 6, 7, 0 }; + + MetisNumberSet *a = metisNumberSet_Create(); + MetisNumberSet *b = metisNumberSet_Create(); + + for (int i = 0; a_set[i] != 0; i++) { + metisNumberSet_Add(a, a_set[i]); + } + + for (int i = 0; b_set[i] != 0; i++) { + metisNumberSet_Add(b, b_set[i]); + } + + bool equal = metisNumberSet_Equals(a, b); + + metisNumberSet_Release(&a); + metisNumberSet_Release(&b); + + assertTrue(equal, "Equal sets did not compare as equal"); +} + +LONGBOW_TEST_CASE(Global, metisNumberSet_Equals_BothEmpty) +{ + MetisNumberSet *a = metisNumberSet_Create(); + MetisNumberSet *b = metisNumberSet_Create(); + + bool equal = metisNumberSet_Equals(a, b); + + metisNumberSet_Release(&a); + metisNumberSet_Release(&b); + + assertTrue(equal, "Two empty sets did not compare as equal"); +} + +LONGBOW_TEST_CASE(Global, metisNumberSet_Equals_BothNull) +{ + MetisNumberSet *a = NULL; + MetisNumberSet *b = NULL; + + bool equal = metisNumberSet_Equals(a, b); + + assertTrue(equal, "Two NULL sets did not compare as equal"); +} + +LONGBOW_TEST_CASE(Global, metisNumberSet_Equals_OneNull) +{ + MetisNumberSet *a = NULL; + MetisNumberSet *b = metisNumberSet_Create(); + + bool equal = metisNumberSet_Equals(a, b); + + assertFalse(equal, "One null one allocated sets did compared as equal"); + metisNumberSet_Release(&b); +} + +LONGBOW_TEST_CASE(Global, metisNumberSet_Equals_DifferentLengths) +{ + // 0 is the terminator + unsigned a_set[] = { 1, 2, 3, 4, 5, 6, 7, 0 }; + unsigned b_set[] = { 1, 2, 3, 4, 5, 6, 0 }; + + MetisNumberSet *a = metisNumberSet_Create(); + MetisNumberSet *b = metisNumberSet_Create(); + + for (int i = 0; a_set[i] != 0; i++) { + metisNumberSet_Add(a, a_set[i]); + } + + for (int i = 0; b_set[i] != 0; i++) { + metisNumberSet_Add(b, b_set[i]); + } + + bool equal = metisNumberSet_Equals(a, b); + + metisNumberSet_Release(&a); + metisNumberSet_Release(&b); + + assertFalse(equal, "Sets of different lengths compared as equal"); +} + +LONGBOW_TEST_CASE(Global, metisNumberSet_Equals_IsNotEqual) +{ + // 0 is the terminator + unsigned a_set[] = { 1, 2, 3, 4, 5, 6, 7, 0 }; + unsigned b_set[] = { 1, 2, 3, 4, 5, 6, 8, 0 }; + + MetisNumberSet *a = metisNumberSet_Create(); + MetisNumberSet *b = metisNumberSet_Create(); + + for (int i = 0; a_set[i] != 0; i++) { + metisNumberSet_Add(a, a_set[i]); + } + + for (int i = 0; b_set[i] != 0; i++) { + metisNumberSet_Add(b, b_set[i]); + } + + bool equal = metisNumberSet_Equals(a, b); + + metisNumberSet_Release(&a); + metisNumberSet_Release(&b); + + assertFalse(equal, "Same length but unequal sets compared as equal"); +} + +LONGBOW_TEST_CASE(Global, metisNumberSet_GetItem) +{ + MetisNumberSet *set = metisNumberSet_Create(); + + int limit = 10; + for (int i = 1; i <= limit; i++) { + bool result = metisNumberSet_Add(set, i); + assertTrue(result, "Got failure on append, i = %d", i); + assertTrue(set->length == i, "Set length wrong, expected %d got %zu", i, set->length); + } + + for (int i = 0; i < limit; i++) { + MetisNumber n = metisNumberSet_GetItem(set, i); + assertTrue(n == i + 1, "Got wrong number, i = %d, n = %u", i, n); + } + + metisNumberSet_Release(&set); +} + +LONGBOW_TEST_CASE(Global, metisNumberSet_Length) +{ + MetisNumberSet *set = metisNumberSet_Create(); + + int limit = 10; + for (int i = 1; i <= limit; i++) { + bool result = metisNumberSet_Add(set, i); + assertTrue(result, "Got failure on append, i = %d", i); + assertTrue(metisNumberSet_Length(set) == i, "Set length wrong, expected %d got %zu", i, metisNumberSet_Length(set)); + } + + metisNumberSet_Release(&set); +} + +LONGBOW_TEST_CASE(Global, metisNumberSet_Subtract_Disjoint) +{ + // 0 is the terminator + unsigned a_set[] = { 1, 2, 3, 4, 5, 6, 7, 0 }; + unsigned b_set[] = { 11, 12, 13, 14, 15, 0 }; + unsigned truth_set[] = { 1, 2, 3, 4, 5, 6, 7, 0 }; + + MetisNumberSet *a = metisNumberSet_Create(); + MetisNumberSet *b = metisNumberSet_Create(); + MetisNumberSet *truth = metisNumberSet_Create(); + + for (int i = 0; a_set[i] != 0; i++) { + metisNumberSet_Add(a, a_set[i]); + } + + for (int i = 0; b_set[i] != 0; i++) { + metisNumberSet_Add(b, b_set[i]); + } + + for (int i = 0; truth_set[i] != 0; i++) { + metisNumberSet_Add(truth, truth_set[i]); + } + + MetisNumberSet *test = metisNumberSet_Subtract(a, b); + + bool equal = metisNumberSet_Equals(truth, test); + + metisNumberSet_Release(&a); + metisNumberSet_Release(&b); + metisNumberSet_Release(&truth); + metisNumberSet_Release(&test); + + assertTrue(equal, "subtraction result incorrect for disjoint sets"); +} + +LONGBOW_TEST_CASE(Global, metisNumberSet_Subtract_Equivalent) +{ + // 0 is the terminator + unsigned a_set[] = { 1, 2, 3, 4, 5, 6, 7, 0 }; + unsigned b_set[] = { 1, 2, 3, 4, 5, 6, 7, 0 }; + unsigned truth_set[] = { 0 }; + + MetisNumberSet *a = metisNumberSet_Create(); + MetisNumberSet *b = metisNumberSet_Create(); + MetisNumberSet *truth = metisNumberSet_Create(); + + for (int i = 0; a_set[i] != 0; i++) { + metisNumberSet_Add(a, a_set[i]); + } + + for (int i = 0; b_set[i] != 0; i++) { + metisNumberSet_Add(b, b_set[i]); + } + + for (int i = 0; truth_set[i] != 0; i++) { + metisNumberSet_Add(truth, truth_set[i]); + } + + MetisNumberSet *test = metisNumberSet_Subtract(a, b); + + bool equal = metisNumberSet_Equals(truth, test); + + metisNumberSet_Release(&a); + metisNumberSet_Release(&b); + metisNumberSet_Release(&truth); + metisNumberSet_Release(&test); + + assertTrue(equal, "subtraction result incorrect for disjoint sets"); +} + +LONGBOW_TEST_CASE(Global, metisNumberSet_Subtract_Overlap) +{ + // 0 is the terminator + unsigned a_set[] = { 1, 2, 3, 4, 5, 6, 7, 0 }; + unsigned b_set[] = { 1, 2, 3, 4, 5, 0 }; + unsigned truth_set[] = { 6, 7, 0 }; + + MetisNumberSet *a = metisNumberSet_Create(); + MetisNumberSet *b = metisNumberSet_Create(); + MetisNumberSet *truth = metisNumberSet_Create(); + + for (int i = 0; a_set[i] != 0; i++) { + metisNumberSet_Add(a, a_set[i]); + } + + for (int i = 0; b_set[i] != 0; i++) { + metisNumberSet_Add(b, b_set[i]); + } + + for (int i = 0; truth_set[i] != 0; i++) { + metisNumberSet_Add(truth, truth_set[i]); + } + + MetisNumberSet *test = metisNumberSet_Subtract(a, b); + + bool equal = metisNumberSet_Equals(truth, test); + + metisNumberSet_Release(&a); + metisNumberSet_Release(&b); + metisNumberSet_Release(&truth); + metisNumberSet_Release(&test); + + assertTrue(equal, "subtraction result incorrect for disjoint sets"); +} + +LONGBOW_TEST_CASE(Global, metisNumberSet_Remove_LastElement) +{ + // 0 is the terminator + unsigned a_set[] = { 1, 2, 3, 4, 5, 6, 7, 0 }; + unsigned to_remove = 7; + unsigned truth_set[] = { 1, 2, 3, 4, 5, 6, 0 }; + + MetisNumberSet *a = metisNumberSet_Create(); + MetisNumberSet *truth = metisNumberSet_Create(); + + for (int i = 0; a_set[i] != 0; i++) { + metisNumberSet_Add(a, a_set[i]); + } + + for (int i = 0; truth_set[i] != 0; i++) { + metisNumberSet_Add(truth, truth_set[i]); + } + + metisNumberSet_Remove(a, to_remove); + + bool equal = metisNumberSet_Equals(truth, a); + + metisNumberSet_Release(&a); + metisNumberSet_Release(&truth); + + assertTrue(equal, "Removing element gives incorrect set"); +} + +LONGBOW_TEST_CASE(Global, metisNumberSet_Remove_AllElements) +{ + // 0 is the terminator + unsigned a_set[] = { 1, 0 }; + unsigned to_remove = 1; + unsigned truth_set[] = { 0 }; + + MetisNumberSet *a = metisNumberSet_Create(); + MetisNumberSet *truth = metisNumberSet_Create(); + + for (int i = 0; a_set[i] != 0; i++) { + metisNumberSet_Add(a, a_set[i]); + } + + for (int i = 0; truth_set[i] != 0; i++) { + metisNumberSet_Add(truth, truth_set[i]); + } + + metisNumberSet_Remove(a, to_remove); + + bool equal = metisNumberSet_Equals(truth, a); + + metisNumberSet_Release(&a); + metisNumberSet_Release(&truth); + + assertTrue(equal, "Removing element gives incorrect set"); +} + +LONGBOW_TEST_CASE(Global, metisNumberSet_Remove_FirstElement) +{ + // 0 is the terminator + unsigned a_set[] = { 1, 2, 3, 4, 5, 6, 7, 0 }; + unsigned to_remove = 1; + unsigned truth_set[] = { 2, 3, 4, 5, 6, 7, 0 }; + + MetisNumberSet *a = metisNumberSet_Create(); + MetisNumberSet *truth = metisNumberSet_Create(); + + for (int i = 0; a_set[i] != 0; i++) { + metisNumberSet_Add(a, a_set[i]); + } + + for (int i = 0; truth_set[i] != 0; i++) { + metisNumberSet_Add(truth, truth_set[i]); + } + + metisNumberSet_Remove(a, to_remove); + + bool equal = metisNumberSet_Equals(truth, a); + + metisNumberSet_Release(&a); + metisNumberSet_Release(&truth); + + assertTrue(equal, "Removing element gives incorrect set"); +} + +// ====================================================================================== + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisNumberSet_Expand); + LONGBOW_RUN_TEST_CASE(Local, metisNumberSet_AddNoChecks); +} + +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, metisNumberSet_Expand) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, metisNumberSet_AddNoChecks) +{ + testUnimplemented("This test is unimplemented"); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_NumberSet); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/core/test/test_metis_StreamBuffer.c b/metis/ccnx/forwarder/metis/core/test/test_metis_StreamBuffer.c new file mode 100644 index 00000000..09bd9401 --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/test/test_metis_StreamBuffer.c @@ -0,0 +1,134 @@ +/* + * 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_StreamBuffer.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +LONGBOW_TEST_RUNNER(metis_StreamBuffer) +{ + // 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_StreamBuffer) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_StreamBuffer) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisStreamBuffer_Destroy); + LONGBOW_RUN_TEST_CASE(Global, metisStreamBuffer_DisableCallbacks); + LONGBOW_RUN_TEST_CASE(Global, metisStreamBuffer_EnableCallbacks); + LONGBOW_RUN_TEST_CASE(Global, metisStreamBuffer_Flush); + LONGBOW_RUN_TEST_CASE(Global, metisStreamBuffer_FlushCheckpoint); + LONGBOW_RUN_TEST_CASE(Global, metisStreamBuffer_FlushFinished); + LONGBOW_RUN_TEST_CASE(Global, metisStreamBuffer_SetCallbacks); + LONGBOW_RUN_TEST_CASE(Global, metisStreamBuffer_SetWatermark); +} + +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, metisStreamBuffer_Destroy) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Global, metisStreamBuffer_DisableCallbacks) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Global, metisStreamBuffer_EnableCallbacks) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Global, metisStreamBuffer_Flush) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Global, metisStreamBuffer_FlushCheckpoint) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Global, metisStreamBuffer_FlushFinished) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Global, metisStreamBuffer_SetCallbacks) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Global, metisStreamBuffer_SetWatermark) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_FIXTURE(Local) +{ +} + +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; +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_StreamBuffer); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/core/test/test_metis_ThreadedForwarder.c b/metis/ccnx/forwarder/metis/core/test/test_metis_ThreadedForwarder.c new file mode 100644 index 00000000..75579900 --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/test/test_metis_ThreadedForwarder.c @@ -0,0 +1,145 @@ +/* + * 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_ThreadedForwarder.c" + +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metis_ThreadedForwarder) +{ + // 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_ThreadedForwarder) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_ThreadedForwarder) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisThreadedForwarder_AddCLI); + LONGBOW_RUN_TEST_CASE(Global, metisThreadedForwarder_Create); + LONGBOW_RUN_TEST_CASE(Global, metisThreadedForwarder_Destroy); + LONGBOW_RUN_TEST_CASE(Global, metisThreadedForwarder_SetupAllListeners); + LONGBOW_RUN_TEST_CASE(Global, metisThreadedForwarder_Start); + LONGBOW_RUN_TEST_CASE(Global, metisThreadedForwarder_Stop); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisThreadedForwarder_AddCLI) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Global, metisThreadedForwarder_Create) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Global, metisThreadedForwarder_Destroy) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Global, metisThreadedForwarder_SetupAllListeners) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Global, metisThreadedForwarder_Start) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Global, metisThreadedForwarder_Stop) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisThreadedForwarder_BroadcastStatus); + LONGBOW_RUN_TEST_CASE(Local, metisThreadedForwarder_LockState); + LONGBOW_RUN_TEST_CASE(Local, metisThreadedForwarder_Run); + LONGBOW_RUN_TEST_CASE(Local, metisThreadedForwarder_UnlockState); + LONGBOW_RUN_TEST_CASE(Local, metisThreadedForwarder_WaitStatus); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, metisThreadedForwarder_BroadcastStatus) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, metisThreadedForwarder_LockState) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, metisThreadedForwarder_Run) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, metisThreadedForwarder_UnlockState) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, metisThreadedForwarder_WaitStatus) +{ + testUnimplemented(""); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_ThreadedForwarder); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/core/test/testrig_MetisIoOperations.h b/metis/ccnx/forwarder/metis/core/test/testrig_MetisIoOperations.h new file mode 100644 index 00000000..80735862 --- /dev/null +++ b/metis/ccnx/forwarder/metis/core/test/testrig_MetisIoOperations.h @@ -0,0 +1,224 @@ +/* + * 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_MetisIoOperations_h +#define Metis_testrig_MetisIoOperations_h + +/** + * Setup a test rig around a MetisIoOperation so we have visibility in to + * what the connection table is doing + * + * Usage: Use <code>mockIoOperationsData_Create()</code> or <code>mockIoOperationsData_CreateSimple()</code> + * to create the MetisIoOperations. You can then inspect the TestData inside the context + * by mapping <code>TestData *data = (TestData *) metisIoOperations_GetClosure(ops)</code>. + * + * IMPORTANT: ops->destroy(&ops) will not destroy the test rig. It will increment a counter. + * you must call <code>testdata_Destroy(&ops)</code> yourself. You should call this + * as the very last thing, even after <code>metisForwarder_Destroy()</code>, if you put + * the MetisIoOpereations in the connection table. + */ +static bool mockIoOperations_Send(MetisIoOperations *ops, const CPIAddress *nexthop, MetisMessage *message); +static const CPIAddress *mockIoOperations_GetRemoteAddress(const MetisIoOperations *ops); +static const MetisAddressPair *mockIoOperations_GetAddressPair(const MetisIoOperations *ops); +static bool mockIoOperations_IsUp(const MetisIoOperations *ops); +static bool mockIoOperations_IsLocal(const MetisIoOperations *ops); +static unsigned mockIoOperations_GetConnectionId(const MetisIoOperations *ops); +static void mockIoOperations_Destroy(MetisIoOperations **opsPtr); +static CPIConnectionType mockIoOperations_GetConnectionType(const MetisIoOperations *ops); +static const void *mockIoOperations_Class(const MetisIoOperations *ops); + +static MetisIoOperations mockIoOperationsTemplate = { + .closure = NULL, + .send = &mockIoOperations_Send, + .getRemoteAddress = &mockIoOperations_GetRemoteAddress, + .getAddressPair = &mockIoOperations_GetAddressPair, + .isUp = &mockIoOperations_IsUp, + .isLocal = &mockIoOperations_IsLocal, + .getConnectionId = &mockIoOperations_GetConnectionId, + .destroy = &mockIoOperations_Destroy, + .getConnectionType = &mockIoOperations_GetConnectionType, + .class = &mockIoOperations_Class +}; + +typedef struct mock_io_operations_data { + // counters for each call + unsigned sendCount; + unsigned getRemoteAddressCount; + unsigned getAddressPairCount; + unsigned isUpCount; + unsigned isLocalCount; + unsigned getConnectionIdCount; + unsigned destroyCount; + unsigned getConnectionTypeCount; + unsigned classCount; + + MetisMessage *lastMessage; + MetisAddressPair *addressPair; + unsigned id; + bool isUp; + bool isLocal; + bool sendResult; // what to return when send() called + CPIConnectionType connType; +} MockIoOperationsData; + +/** + * @function testdata_Create + * @abstract Creates a data set for testing MetisIoOperations + * @discussion + * Caller must explicitly use <code>testdata_Destroy()</code> when done. Calling the destroyer through + * the io operations only increments counters, it does not destroy the object. + * + * @param <#param1#> + * @return <#return#> + */ +static MetisIoOperations * +mockIoOperationsData_Create(MetisAddressPair *pair, unsigned id, bool isUp, bool sendResult, bool isLocal, CPIConnectionType connType) +{ + MockIoOperationsData *data = parcMemory_AllocateAndClear(sizeof(MockIoOperationsData)); + data->addressPair = pair; + data->id = id; + data->isUp = isUp; + data->sendResult = sendResult; + data->lastMessage = NULL; + data->isLocal = isLocal; + data->connType = connType; + + MetisIoOperations *ops = parcMemory_AllocateAndClear(sizeof(MetisIoOperations)); + memcpy(ops, &mockIoOperationsTemplate, sizeof(MetisIoOperations)); + ops->closure = data; + + return ops; +} + +/** + * @function testdata_CreateSimple + * @abstract Creates a data set for testing MetisIoOperations + * @discussion + * Caller must explicitly use <code>testdata_Destroy()</code> when done. Calling the destroyer through + * the io operations only increments counters, it does not destroy the object. + * + * @param <#param1#> + * @return <#return#> + */ +static MetisIoOperations * +mockIoOperationsData_CreateSimple(unsigned addressLocal, unsigned addressRemote, unsigned id, bool isUp, bool sendResult, bool isLocal) +{ + CPIAddress *local = cpiAddress_CreateFromInterface(addressLocal); + CPIAddress *remote = cpiAddress_CreateFromInterface(addressRemote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + MetisIoOperations *ops = mockIoOperationsData_Create(pair, id, isUp, sendResult, isLocal, cpiConnection_UDP); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); + return ops; +} + +static void +mockIoOperationsData_Destroy(MetisIoOperations **opsPtr) +{ + MetisIoOperations *ops = *opsPtr; + MockIoOperationsData *data = (MockIoOperationsData *) metisIoOperations_GetClosure(ops); + + metisAddressPair_Release(&data->addressPair); + if (data->lastMessage) { + metisMessage_Release(&data->lastMessage); + } + parcMemory_Deallocate((void **) &data); + ops->closure = NULL; + parcMemory_Deallocate((void **) &ops); + *opsPtr = NULL; +} + +static bool +mockIoOperations_Send(MetisIoOperations *ops, const CPIAddress *nexthop, MetisMessage *message) +{ + MockIoOperationsData *data = (MockIoOperationsData *) metisIoOperations_GetClosure(ops); + data->sendCount++; + + if (message) { + if (data->lastMessage) { + metisMessage_Release(&data->lastMessage); + } + + data->lastMessage = metisMessage_Acquire(message); + } + + return data->sendResult; +} + +static const CPIAddress * +mockIoOperations_GetRemoteAddress(const MetisIoOperations *ops) +{ + MockIoOperationsData *data = (MockIoOperationsData *) metisIoOperations_GetClosure(ops); + data->getRemoteAddressCount++; + return metisAddressPair_GetRemote(data->addressPair); +} + +static const MetisAddressPair * +mockIoOperations_GetAddressPair(const MetisIoOperations *ops) +{ + MockIoOperationsData *data = (MockIoOperationsData *) metisIoOperations_GetClosure(ops); + data->getAddressPairCount++; + return data->addressPair; +} + +static bool +mockIoOperations_IsUp(const MetisIoOperations *ops) +{ + MockIoOperationsData *data = (MockIoOperationsData *) metisIoOperations_GetClosure(ops); + data->isUpCount++; + return data->isUp; +} + +static bool +mockIoOperations_IsLocal(const MetisIoOperations *ops) +{ + MockIoOperationsData *data = (MockIoOperationsData *) metisIoOperations_GetClosure(ops); + data->isLocalCount++; + return data->isLocal; +} + + +static unsigned +mockIoOperations_GetConnectionId(const MetisIoOperations *ops) +{ + MockIoOperationsData *data = (MockIoOperationsData *) metisIoOperations_GetClosure(ops); + data->getConnectionIdCount++; + return data->id; +} + +static void +mockIoOperations_Destroy(MetisIoOperations **opsPtr) +{ + MockIoOperationsData *data = (MockIoOperationsData *) (*opsPtr)->closure; + data->destroyCount++; + *opsPtr = NULL; +} + +static CPIConnectionType +mockIoOperations_GetConnectionType(const MetisIoOperations *ops) +{ + MockIoOperationsData *data = (MockIoOperationsData *) metisIoOperations_GetClosure(ops); + data->getConnectionTypeCount++; + return data->connType; +} + +static const void * +mockIoOperations_Class(const MetisIoOperations *ops) +{ + MockIoOperationsData *data = (MockIoOperationsData *) metisIoOperations_GetClosure(ops); + data->classCount++; + return __FILE__; +} +#endif // Metis_testrig_MetisIoOperations_h diff --git a/metis/ccnx/forwarder/metis/io/metis_AddressPair.c b/metis/ccnx/forwarder/metis/io/metis_AddressPair.c new file mode 100644 index 00000000..7725281f --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_AddressPair.c @@ -0,0 +1,148 @@ +/* + * 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/io/metis_AddressPair.h> +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_Hash.h> +#include <parc/algol/parc_Object.h> + +#include <LongBow/runtime.h> + +struct metis_address_pair { + CPIAddress *local; + CPIAddress *remote; +}; + +static void +_metisAddressPair_Destroy(MetisAddressPair **addressPairPtr) +{ + MetisAddressPair *pair = *addressPairPtr; + + cpiAddress_Destroy(&pair->local); + cpiAddress_Destroy(&pair->remote); +} + +parcObject_ExtendPARCObject(MetisAddressPair, _metisAddressPair_Destroy, + NULL, metisAddressPair_ToString, metisAddressPair_Equals, NULL, metisAddressPair_HashCode, NULL); + +parcObject_ImplementAcquire(metisAddressPair, MetisAddressPair); + +parcObject_ImplementRelease(metisAddressPair, MetisAddressPair); + +MetisAddressPair * +metisAddressPair_Create(const CPIAddress *local, const CPIAddress *remote) +{ + assertNotNull(local, "Parameter local must be non-null"); + assertNotNull(remote, "Parameter remote must be non-null"); + + MetisAddressPair *pair = parcObject_CreateInstance(MetisAddressPair); + assertNotNull(pair, "Got null from parcObject_Create()"); + + pair->local = cpiAddress_Copy(local); + pair->remote = cpiAddress_Copy(remote); + + return pair; +} + +bool +metisAddressPair_Equals(const MetisAddressPair *a, const MetisAddressPair *b) +{ + if (a == b) { + return true; + } + if (a == NULL || b == NULL) { + return false; + } + + if (cpiAddress_Equals(a->local, b->local)) { + if (cpiAddress_Equals(a->remote, b->remote)) { + return true; + } + } + + return false; +} + +bool +metisAddressPair_EqualsAddresses(const MetisAddressPair *a, const CPIAddress *local, const CPIAddress *remote) +{ + if (a == NULL || local == NULL || remote == NULL) { + return false; + } + + if (cpiAddress_Equals(a->local, local)) { + if (cpiAddress_Equals(a->remote, remote)) { + return true; + } + } + + return false; +} + +char * +metisAddressPair_ToString(const MetisAddressPair *pair) +{ + assertNotNull(pair, "Parameter pair must be non-null"); + + char *local = cpiAddress_ToString(pair->local); + char *remote = cpiAddress_ToString(pair->remote); + + char *output; + int failure = asprintf(&output, "{ .local=%s, .remote=%s }", local, remote); + assertTrue(failure > -1, "Error on asprintf"); + + parcMemory_Deallocate((void **) &local); + parcMemory_Deallocate((void **) &remote); + + return output; +} + +const CPIAddress * +metisAddressPair_GetLocal(const MetisAddressPair *pair) +{ + assertNotNull(pair, "Parameter pair must be non-null"); + return pair->local; +} + +const CPIAddress * +metisAddressPair_GetRemote(const MetisAddressPair *pair) +{ + assertNotNull(pair, "Parameter pair must be non-null"); + return pair->remote; +} + +/** + * @function metisAddressPair_HashCode + * @abstract Hash useful for tables. Consistent with Equals. + * @discussion + * Returns a non-cryptographic hash that is consistent with equals. That is, + * if a == b, then hash(a) == hash(b). + * + * @param <#param1#> + * @return <#return#> + */ +PARCHashCode +metisAddressPair_HashCode(const MetisAddressPair *pair) +{ + PARCHashCode hashpair[2]; + hashpair[0] = cpiAddress_HashCode(pair->local); + hashpair[1] = cpiAddress_HashCode(pair->remote); + return parcHashCode_Hash((const uint8_t *) hashpair, sizeof(hashpair)); +// return parcHash32_Data(hashpair, sizeof(hashpair)); +} diff --git a/metis/ccnx/forwarder/metis/io/metis_AddressPair.h b/metis/ccnx/forwarder/metis/io/metis_AddressPair.h new file mode 100644 index 00000000..a6678f23 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_AddressPair.h @@ -0,0 +1,166 @@ +/* + * 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. + */ + + +/** + * Used to identify a connection between a specific local address and + * a specific remote address. + */ + +#ifndef Metis_metis_AddressPair_h +#define Metis_metis_AddressPair_h + +#include <ccnx/api/control/cpi_Address.h> + +struct metis_address_pair; +typedef struct metis_address_pair MetisAddressPair; + +/** + * @function metisAddressPair_Create + * @abstract Creates and address pair. There is no restriction on the address types. + * @discussion + * Creates an ordered pair of addresses, where the first is considered the "local" address + * and the second is the "remote" address. Those designations are purely a convention used + * to name them, and does not imply any specifici types of operations. + * + * The two addresses may be of any address types (e.g. IPv4, IPv6, Local, Ethernet). + * However, some functions that use an AddressPair may require that the local and remote + * addresses be the same type. + * + * @param <#param1#> + * @return <#return#> + */ +MetisAddressPair *metisAddressPair_Create(const CPIAddress *local, const CPIAddress *remote); + +/** + * Returns a reference counted copy of the address pair + * + * Increments the reference count and returns the same address pair + * + * @param [in] addressPair An allocated address pair + * + * @retval non-null A reference counted copy + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisAddressPair *metisAddressPair_Acquire(const MetisAddressPair *addressPair); + + +/** + * Releases a reference count to the object + * + * Decrements the reference count and destroys the object when it reaches 0. + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisAddressPair_Release(MetisAddressPair **pairPtr); + +/** + * Determine if two MetisAddressPair instances are equal. + * + * Two MetisAddressPair instances are equal if, and only if, the local and remote addresses are identical. + * Equality is determined by cpiAddress_Equals(a->local, b->local) and + * cpiAdress_Equals(a->remote, b->remote). + * + * The following equivalence relations on non-null `MetisAddressPair` instances are maintained: + * + * * It is reflexive: for any non-null reference value x, `MetisAddressPair_Equals(x, x)` + * must return true. + * + * * It is symmetric: for any non-null reference values x and y, + * `metisAddressPair_Equals(x, y)` must return true if and only if + * `metisAddressPair_Equals(y, x)` returns true. + * + * * It is transitive: for any non-null reference values x, y, and z, if + * `metisAddressPair_Equals(x, y)` returns true and + * `metisAddressPair_Equals(y, z)` returns true, + * then `metisAddressPair_Equals(x, z)` must return true. + * + * * It is consistent: for any non-null reference values x and y, multiple + * invocations of `metisAddressPair_Equals(x, y)` consistently return true or + * consistently return false. + * + * * For any non-null reference value x, `metisAddressPair_Equals(x, NULL)` must + * return false. + * + * @param a A pointer to a `MetisAddressPair` instance. + * @param b A pointer to a `MetisAddressPair` instance. + * @return true if the two `MetisAddressPair` instances are equal. + * + * Example: + * @code + * { + * MetisAddressPair *a = metisAddressPair_Create(); + * MetisAddressPair *b = metisAddressPair_Create(); + * + * if (metisAddressPair_Equals(a, b)) { + * // true + * } else { + * // false + * } + * } + * @endcode + */ +bool metisAddressPair_Equals(const MetisAddressPair *a, const MetisAddressPair *b); + +/** + * @function metisAddressPair_EqualsAddresses + * @abstract As MetisAddressEquals, but "b" is broken out + * @discussion + * Equality is determined by cpiAddress_Equals(a->local, local) and + * cpiAdress_Equals(a->remote, remote). + * + * @param <#param1#> + * @return <#return#> + */ +bool metisAddressPair_EqualsAddresses(const MetisAddressPair *a, const CPIAddress *local, const CPIAddress *remote); + +const CPIAddress *metisAddressPair_GetLocal(const MetisAddressPair *pair); +const CPIAddress *metisAddressPair_GetRemote(const MetisAddressPair *pair); + +/** + * @function metisAddressPair_HashCode + * @abstract Hash useful for tables. Consistent with Equals. + * @discussion + * Returns a non-cryptographic hash that is consistent with equals. That is, + * if a == b, then hash(a) == hash(b). + * + * @param <#param1#> + * @return <#return#> + */ +PARCHashCode metisAddressPair_HashCode(const MetisAddressPair *pair); + +/** + * @function metisAddressPair_ToString + * @abstract Human readable string representation. Caller must use free(3). + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +char *metisAddressPair_ToString(const MetisAddressPair *pair); +#endif // Metis_metis_AddressPair_h diff --git a/metis/ccnx/forwarder/metis/io/metis_EtherConnection.c b/metis/ccnx/forwarder/metis/io/metis_EtherConnection.c new file mode 100644 index 00000000..63e9ab83 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_EtherConnection.c @@ -0,0 +1,388 @@ +/* + * 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. + */ + +/** + * Embodies the reader/writer for an Ethernet connection + * + * TODO: Put in a maintenance timer to timeout MAC entries if not used. Need to add a function + * the listener can call to bump up activity when a packet is received. + * + */ + +#include <config.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <net/ethernet.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Hash.h> +#include <parc/algol/parc_Memory.h> + + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_Connection.h> +#include <ccnx/forwarder/metis/core/metis_Message.h> +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> +#include <ccnx/forwarder/metis/io/metis_EtherConnection.h> +#include <ccnx/forwarder/metis/messenger/metis_MissiveType.h> +#include <ccnx/forwarder/metis/io/metis_HopByHopFragmenter.h> + +typedef struct metis_ether_state { + MetisForwarder *metis; + MetisLogger *logger; + + // the udp listener socket we receive packets on + MetisGenericEther *ether; + + MetisAddressPair *addressPair; + + MetisHopByHopFragmenter *fragmenter; + + // We need to access this all the time, so grab it out + // of the addressPair; + uint8_t myAddress[ETHER_ADDR_LEN]; + uint8_t peerAddress[ETHER_ADDR_LEN]; + uint16_t networkOrderEtherType; + + bool isUp; + unsigned id; +} _MetisEtherState; + +// Prototypes +static bool _metisEtherConnection_Send(MetisIoOperations *ops, const CPIAddress *nexthop, MetisMessage *message); +static const CPIAddress *_metisEtherConnection_GetRemoteAddress(const MetisIoOperations *ops); +static const MetisAddressPair *_metisEtherConnection_GetAddressPair(const MetisIoOperations *ops); +static unsigned _metisEtherConnection_GetConnectionId(const MetisIoOperations *ops); +static bool _metisEtherConnection_IsUp(const MetisIoOperations *ops); +static bool _metisEtherConnection_IsLocal(const MetisIoOperations *ops); +static void _metisEtherConnection_Release(MetisIoOperations **opsPtr); + +static void _setConnectionState(_MetisEtherState *ether, bool isUp); +static CPIConnectionType _metisEtherConnection_getConnectionType(const MetisIoOperations *ops); +static MetisTicks _sendProbe(MetisIoOperations *ops, unsigned probeType); + +/* + * This assigns a unique pointer to the void * which we use + * as a GUID for this class. + */ +static const void *_metisIoOperationsGuid = __FILE__; + +/* + * Return our GUID + */ +static const void * +_metisEtherConnection_Class(const MetisIoOperations *ops) +{ + return _metisIoOperationsGuid; +} + +static MetisIoOperations _template = { + .closure = NULL, + .send = &_metisEtherConnection_Send, + .getRemoteAddress = &_metisEtherConnection_GetRemoteAddress, + .getAddressPair = &_metisEtherConnection_GetAddressPair, + .getConnectionId = &_metisEtherConnection_GetConnectionId, + .isUp = &_metisEtherConnection_IsUp, + .isLocal = &_metisEtherConnection_IsLocal, + .destroy = &_metisEtherConnection_Release, + .class = &_metisEtherConnection_Class, + .getConnectionType = &_metisEtherConnection_getConnectionType, + .sendProbe = &_sendProbe, +}; + +// ================================================================= + +static bool +_metisEtherConnection_FillInMacAddress(uint8_t *mac, const CPIAddress *source) +{ + PARCBuffer *macAddress = cpiAddress_GetLinkAddress(source); + if (macAddress) { + parcBuffer_GetBytes(macAddress, ETHER_ADDR_LEN, mac); + parcBuffer_Rewind(macAddress); + return true; + } + return false; +} + +MetisIoOperations * +metisEtherConnection_Create(MetisForwarder *metis, MetisGenericEther *ether, MetisAddressPair *pair) +{ + bool success = false; + size_t allocationSize = sizeof(_MetisEtherState) + sizeof(MetisIoOperations); + MetisIoOperations *ops = parcMemory_AllocateAndClear(allocationSize); + if (ops) { + memcpy(ops, &_template, sizeof(MetisIoOperations)); + ops->closure = (uint8_t *) ops + sizeof(MetisIoOperations); + + _MetisEtherState *etherConnState = metisIoOperations_GetClosure(ops); + + if (_metisEtherConnection_FillInMacAddress(etherConnState->myAddress, metisAddressPair_GetLocal(pair))) { + if (_metisEtherConnection_FillInMacAddress(etherConnState->peerAddress, metisAddressPair_GetRemote(pair))) { + etherConnState->metis = metis; + etherConnState->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + etherConnState->ether = metisGenericEther_Acquire(ether); + etherConnState->id = metisForwarder_GetNextConnectionId(metis); + etherConnState->addressPair = metisAddressPair_Acquire(pair); + etherConnState->networkOrderEtherType = htons(metisGenericEther_GetEtherType(ether)); + etherConnState->fragmenter = metisHopByHopFragmenter_Create(etherConnState->logger, metisGenericEther_GetMTU(ether)); + + _setConnectionState(etherConnState, true); + + if (metisLogger_IsLoggable(etherConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + char *str = metisAddressPair_ToString(pair); + metisLogger_Log(etherConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "EtherConnection %p created address pair %s", + (void *) etherConnState, str); + free(str); + } + + metisMessenger_Send(metisForwarder_GetMessenger(metis), metisMissive_Create(MetisMissiveType_ConnectionCreate, etherConnState->id)); + metisMessenger_Send(metisForwarder_GetMessenger(metis), metisMissive_Create(MetisMissiveType_ConnectionUp, etherConnState->id)); + + success = true; + } + } + } + + if (ops && !success) { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error)) { + char *str = metisAddressPair_ToString(pair); + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Error creating EtherConnection address pair %s", + str); + free(str); + } + + parcMemory_Deallocate(&ops); + } + + return ops; +} + +// ================================================================= +// I/O Operations implementation + +/* + * We only do one allocation of the combined MetisIoOperations and _MetisEtherState, so this + * function only needs to destroy the internal state + */ +static void +_metisEtherConnection_InternalRelease(_MetisEtherState *etherConnState) +{ + if (metisLogger_IsLoggable(etherConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(etherConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "EtherConnection %p destroyed", + (void *) etherConnState); + } + + metisMessenger_Send(metisForwarder_GetMessenger(etherConnState->metis), metisMissive_Create(MetisMissiveType_ConnectionDestroyed, etherConnState->id)); + + metisAddressPair_Release(ðerConnState->addressPair); + metisGenericEther_Release(ðerConnState->ether); + metisHopByHopFragmenter_Release(ðerConnState->fragmenter); + metisLogger_Release(ðerConnState->logger); +} + +static void +_metisEtherConnection_Release(MetisIoOperations **opsPtr) +{ + assertNotNull(opsPtr, "Parameter opsPtr must be non-null double pointer"); + assertNotNull(*opsPtr, "Parameter opsPtr must dereference to non-null pointer"); + + MetisIoOperations *ops = *opsPtr; + assertNotNull(metisIoOperations_GetClosure(ops), "ops->context must not be null"); + + _MetisEtherState *etherConnState = (_MetisEtherState *) metisIoOperations_GetClosure(ops); + _metisEtherConnection_InternalRelease(etherConnState); + + // do not close udp->udpListenerSocket, the listener will close + // that when its done + + parcMemory_Deallocate((void **) &ops); +} + +static bool +_metisEtherConnection_IsUp(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisEtherState *etherConnState = (const _MetisEtherState *) metisIoOperations_GetClosure(ops); + return etherConnState->isUp; +} + +static bool +_metisEtherConnection_IsLocal(const MetisIoOperations *ops) +{ + return false; +} + +static const CPIAddress * +_metisEtherConnection_GetRemoteAddress(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisEtherState *etherConnState = (const _MetisEtherState *) metisIoOperations_GetClosure(ops); + return metisAddressPair_GetRemote(etherConnState->addressPair); +} + +static const MetisAddressPair * +_metisEtherConnection_GetAddressPair(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisEtherState *etherConnState = (const _MetisEtherState *) metisIoOperations_GetClosure(ops); + return etherConnState->addressPair; +} + + +static unsigned +_metisEtherConnection_GetConnectionId(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisEtherState *etherConnState = (const _MetisEtherState *) metisIoOperations_GetClosure(ops); + return etherConnState->id; +} + +static bool +_sendFrame(_MetisEtherState *etherConnState, MetisMessage *message) +{ + PARCEventBuffer *writeBuffer = parcEventBuffer_Create(); + + int failure = metisMessage_Append(writeBuffer, message); + if (failure) { + parcEventBuffer_Destroy(&writeBuffer); + return false; + } + + // Add an Ethernet header + struct ether_header header; + + // Fill in the ethernet header + header.ether_type = etherConnState->networkOrderEtherType; + memcpy(header.ether_dhost, etherConnState->peerAddress, ETHER_ADDR_LEN); + memcpy(header.ether_shost, etherConnState->myAddress, ETHER_ADDR_LEN); + + // and put it at the front of the output buffer + parcEventBuffer_Prepend(writeBuffer, &header, sizeof(header)); + + bool success = metisGenericEther_SendFrame(etherConnState->ether, writeBuffer); + + // we're done with the buffer + parcEventBuffer_Destroy(&writeBuffer); + + // BugzID: 3343 - close the connection on certain errors?? + return success; +} + +/** + * @function metisEtherConnection_Send + * @abstract Non-destructive send of the message. + * @discussion + * sends a message to the peer. + * + * @param dummy is ignored. A udp connection has only one peer. + * @return <#return#> + */ +static bool +_metisEtherConnection_Send(MetisIoOperations *ops, const CPIAddress *dummy, MetisMessage *message) +{ + assertNotNull(ops, "Parameter ops must be non-null"); + assertNotNull(message, "Parameter message must be non-null"); + _MetisEtherState *etherConnState = (_MetisEtherState *) metisIoOperations_GetClosure(ops); + + bool success = metisHopByHopFragmenter_Send(etherConnState->fragmenter, message); + + MetisMessage *fragment; + while (success && (fragment = metisHopByHopFragmenter_PopSendQueue(etherConnState->fragmenter)) != NULL) { + success = _sendFrame(etherConnState, fragment); + metisMessage_Release(&fragment); + } + + // if we failed, drain the other fragments + if (!success) { + while ((fragment = metisHopByHopFragmenter_PopSendQueue(etherConnState->fragmenter)) != NULL) { + metisMessage_Release(&fragment); + } + } + + return success; +} + +static void +_setConnectionState(_MetisEtherState *etherConnState, bool isUp) +{ + assertNotNull(etherConnState, "Parameter Udp must be non-null"); + + MetisMessenger *messenger = metisForwarder_GetMessenger(etherConnState->metis); + + bool oldStateIsUp = etherConnState->isUp; + etherConnState->isUp = isUp; + + if (oldStateIsUp && !isUp) { + // bring connection DOWN + MetisMissive *missive = metisMissive_Create(MetisMissiveType_ConnectionDown, etherConnState->id); + metisMessenger_Send(messenger, missive); + return; + } + + + if (!oldStateIsUp && isUp) { + // bring connection UP + MetisMissive *missive = metisMissive_Create(MetisMissiveType_ConnectionUp, etherConnState->id); + metisMessenger_Send(messenger, missive); + return; + } +} + +static CPIConnectionType +_metisEtherConnection_getConnectionType(const MetisIoOperations *ops) +{ + return cpiConnection_L2; +} + + +bool +metisEtherConnection_IsInstanceOf(const MetisConnection *conn) +{ + bool result = false; + if (conn != NULL) { + const void *class = metisConnection_Class(conn); + if (class == _metisIoOperationsGuid) { + result = true; + } + } + return result; +} + + +MetisHopByHopFragmenter * +metisEtherConnection_GetFragmenter(const MetisConnection *conn) +{ + MetisHopByHopFragmenter *fragmenter = NULL; + + if (metisEtherConnection_IsInstanceOf(conn)) { + MetisIoOperations *ops = metisConnection_GetIoOperations(conn); + _MetisEtherState *state = (_MetisEtherState *) metisIoOperations_GetClosure(ops); + fragmenter = state->fragmenter; + } + return fragmenter; +} + +static MetisTicks +_sendProbe(MetisIoOperations *ops, unsigned probeType) +{ + //TODO + return 0; +} + diff --git a/metis/ccnx/forwarder/metis/io/metis_EtherConnection.h b/metis/ccnx/forwarder/metis/io/metis_EtherConnection.h new file mode 100644 index 00000000..c92587f1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_EtherConnection.h @@ -0,0 +1,95 @@ +/* + * 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_EtherConnection.h + * @brief Represents an ethernet pair (source address, destination address) in the connection table + * + * Ethernet connections are never local. + * + */ + +#ifndef Metis_metis_EtherConnection_h +#define Metis_metis_EtherConnection_h + +#include <ccnx/forwarder/metis/io/metis_IoOperations.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/io/metis_AddressPair.h> +#include <ccnx/api/control/cpi_Address.h> +#include <ccnx/forwarder/metis/io/metis_GenericEther.h> +#include <ccnx/forwarder/metis/io/metis_HopByHopFragmenter.h> +#include <ccnx/forwarder/metis/core/metis_Connection.h> + +/** + * @function metisEtherConnection_Create + * @abstract <#OneLineDescription#> + * @discussion + * <#Discussion#> + * + * @param pair the address pair that uniquely identifies the connection. Takes ownership of this memory. + * @return <#return#> + */ +MetisIoOperations *metisEtherConnection_Create(MetisForwarder *metis, MetisGenericEther *ether, MetisAddressPair *pair); + +/** + * If the IO Operats are of type MetisEtherConnection, return its fragmenter + * + * <#Paragraphs Of Explanation#> + * + * @param [in] conn An allocated MetisConnection + * + * @return non-null The fragmenter associated with this conneciton + * @return null There is no such fragmenter or the ops is not a MetisEtherConnection + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +MetisHopByHopFragmenter *metisEtherConnection_GetFragmenter(const MetisConnection *conn); + +/** + * Tests if MetisEtherConnection is the underlying implementation of the connection + * + * <#Paragraphs Of Explanation#> + * + * @param [in] conn An allocated MetisConnection + * + * @return true The connection is of type MetisEtherConnection + * @return false The connection is of other type + * + * Example: + * @code + * { + * MetisHopByHopFragmenter * + * metisEtherConnection_GetFragmenter(const MetisConnection *conn) + * { + * MetisHopByHopFragmenter *fragmenter = NULL; + * + * if (metisEtherConnection_IsInstanceOf(conn)) { + * MetisIoOperations *ops = metisConnection_GetIoOperations(conn); + * _MetisEtherState *state = (_MetisEtherState *) ops->context; + * fragmenter = state->fragmenter; + * } + * return fragmenter; + * } + * } + * @endcode + */ +bool metisEtherConnection_IsInstanceOf(const MetisConnection *conn); + +#endif // Metis_metis_EtherConnection_h diff --git a/metis/ccnx/forwarder/metis/io/metis_EtherListener.c b/metis/ccnx/forwarder/metis/io/metis_EtherListener.c new file mode 100644 index 00000000..58ac07d1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_EtherListener.c @@ -0,0 +1,728 @@ +/* + * 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. + */ + +/** + * Implements the listener over Ethernet. + * + * Right now only supports non-VLAN frames Ethernet (not 802.3/802.2 LLC) frames. + * + */ + +#include <config.h> +#include <stdio.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> +#include <arpa/inet.h> + +#include <net/ethernet.h> + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/ioctl.h> + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_Connection.h> +#include <ccnx/forwarder/metis/core/metis_Message.h> +#include <ccnx/forwarder/metis/messenger/metis_Messenger.h> + +#include <ccnx/forwarder/metis/core/metis_Dispatcher.h> + +#include <ccnx/forwarder/metis/io/metis_GenericEther.h> +#include <ccnx/forwarder/metis/io/metis_EtherConnection.h> + +#ifndef ntohll +#define ntohll(x) (((uint64_t) (ntohl((uint32_t) (x & 0x00000000FFFFFFFF))) << 32) | ntohl(((uint32_t) ((((uint64_t) x) >> 32))))) +#define htonll(x) ntohll(x) +#endif + +#include <LongBow/runtime.h> +#include <parc/algol/parc_Memory.h> + +typedef struct metis_ether_stats { + uint64_t framesIn; + uint64_t framesError; + uint64_t framesReceived; + uint64_t framesReassembled; + uint64_t framesNotForUs; +} _MetisEtherStats; + +typedef struct metis_ether_listener { + MetisForwarder *metis; + MetisLogger *logger; + + MetisGenericEther *genericEther; + + unsigned id; + CPIAddress *localAddress; + + uint16_t ethertype; + + int ether_fd; + PARCEvent *ether_event; + + // what size do the read buffers need to be? + size_t ether_buffer_length; + + // buffer to read next packet in to + PARCEventBuffer *nextReadBuffer; + + // We store MAC addresses in uint64 and mask them down to 6 bytes. + // this means all our address comparisons are simple "==" operations + + uint64_t *destinationAddressList; + size_t destinationAddressSize; + + uint64_t *sourceAddressList; + size_t sourceAddressSize; + + _MetisEtherStats stats; +} _MetisEtherListener; + +// network byte order mask to go from 8-bytes to 6-bytes +#define MAC_MASK (htonll(0xFFFFFFFFFFFF0000ULL)) + +static void _metisEtherListener_OpsDestroy(MetisListenerOps **listenerOpsPtr); +static unsigned _metisEtherListener_OpsGetInterfaceIndex(const MetisListenerOps *ops); +static const CPIAddress *_metisEtherListener_OpsGetListenAddress(const MetisListenerOps *ops); +static MetisEncapType _metisEtherListener_OpsGetEncapType(const MetisListenerOps *ops); +static int _metisEtherListener_OpsGetSocket(const MetisListenerOps *ops); + +static MetisListenerOps _etherTemplate = { + .context = NULL, + .destroy = &_metisEtherListener_OpsDestroy, + .getInterfaceIndex = &_metisEtherListener_OpsGetInterfaceIndex, + .getListenAddress = &_metisEtherListener_OpsGetListenAddress, + .getEncapType = &_metisEtherListener_OpsGetEncapType, + .getSocket = &_metisEtherListener_OpsGetSocket +}; + +// Called by Libevent +static void _metisEtherListener_ReadCallback(int fd, PARCEventType what, void *user_data); + +static void +_logStats(_MetisEtherListener *listener, PARCLogLevel level) +{ + if (metisLogger_IsLoggable(listener->logger, MetisLoggerFacility_IO, level)) { + metisLogger_Log(listener->logger, MetisLoggerFacility_IO, level, __func__, + "EtherListener %p frames in %" PRIu64 ", errors %" PRIu64 " ok %" PRIu64 " reassemble %" PRIu64 " reject %" PRIu64, + (void *) listener, + listener->stats.framesIn, + listener->stats.framesError, + listener->stats.framesReceived, + listener->stats.framesReassembled, + listener->stats.framesNotForUs); + } +} + + +// ============================================= +// Public API + +static void +_metisEtherListener_FillInEthernetAddresses(_MetisEtherListener *etherListener) +{ + // this may be null + PARCBuffer *myAddress = metisGenericEther_GetMacAddress(etherListener->genericEther); + uint64_t macAsUint64 = 0; + + if (myAddress) { + while (parcBuffer_Remaining(myAddress) > 0) { + uint8_t b = parcBuffer_GetUint8(myAddress); + macAsUint64 <<= 8; + macAsUint64 |= b; + } + // the mac address is only 6 bytes, so shift two more + macAsUint64 <<= 16; + parcBuffer_Rewind(myAddress); + + // loopback interface as a 0-length link address + if (parcBuffer_Remaining(myAddress) > 0) { + etherListener->localAddress = cpiAddress_CreateFromLink(parcBuffer_Overlay(myAddress, 0), parcBuffer_Remaining(myAddress)); + } + } + + etherListener->destinationAddressList = parcMemory_AllocateAndClear(sizeof(uint64_t) * 3); + etherListener->destinationAddressSize = 3; + etherListener->destinationAddressList[0] = htonll(macAsUint64); // our address + etherListener->destinationAddressList[1] = htonll(0x01005E0017AA0000); // CCN address + etherListener->destinationAddressList[2] = htonll(0xFFFFFFFFFFFF0000); // broadcast + + etherListener->sourceAddressList = parcMemory_AllocateAndClear(sizeof(uint64_t)); + etherListener->sourceAddressSize = 1; + etherListener->sourceAddressList[0] = htonll(macAsUint64); // our address +} + +static void +_metisEtherListener_ReleaseEthernetAddresses(_MetisEtherListener *etherListener) +{ + parcMemory_Deallocate((void **) ðerListener->destinationAddressList); + parcMemory_Deallocate((void **) ðerListener->sourceAddressList); + + if (etherListener->localAddress) { + cpiAddress_Destroy(ðerListener->localAddress); + } +} + +MetisListenerOps * +metisEtherListener_Create(MetisForwarder *metis, const char *deviceName, uint16_t ethertype) +{ + assertNotNull(metis, "Parameter metis must be non-null"); + assertNotNull(deviceName, "Parameter deviceName must be non-null"); + + _MetisEtherListener *etherListener = parcMemory_AllocateAndClear(sizeof(_MetisEtherListener)); + etherListener->ethertype = ethertype; + etherListener->genericEther = metisGenericEther_Create(metis, deviceName, etherListener->ethertype); + + MetisListenerOps *ops = NULL; + + if (etherListener->genericEther != NULL) { + etherListener->metis = metis; + etherListener->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + etherListener->nextReadBuffer = parcEventBuffer_Create(); + etherListener->id = metisForwarder_GetNextConnectionId(metis); + + int etherSocket = metisGenericEther_GetDescriptor(etherListener->genericEther); + bool persistent = true; + + // now wrap it in an event callback + etherListener->ether_event = metisDispatcher_CreateNetworkEvent( + metisForwarder_GetDispatcher(etherListener->metis), + persistent, + _metisEtherListener_ReadCallback, + etherListener, + etherSocket); + + assertNotNull(etherListener->ether_event, "got null event from metisDispatcher_CreateNetworkEvent: %s", strerror(errno)); + + // Setup the destination and source ethernet addresses we want to use + _metisEtherListener_FillInEthernetAddresses(etherListener); + + // Finished all initialization, so start the network event. + metisDispatcher_StartNetworkEvent(metisForwarder_GetDispatcher(etherListener->metis), etherListener->ether_event); + + // Construct an instance of the MetisListenerOps particular to this context. + ops = parcMemory_Allocate(sizeof(MetisListenerOps)); + assertNotNull(ops, "Got null from parc_memory_new"); + + memcpy(ops, &_etherTemplate, sizeof(MetisListenerOps)); + ops->context = etherListener; + + if (metisLogger_IsLoggable(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + char *str = cpiAddress_ToString(etherListener->localAddress); + metisLogger_Log(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Create Ethernet Listener id %d on %s addr %s ethertype 0x%04x ether socket %d", + etherListener->id, + deviceName, + str, + ethertype, + etherSocket); + parcMemory_Deallocate((void **) &str); + } + } else { + // failed to setup an Ethernet device + parcMemory_Deallocate((void **) ðerListener); + } + + return ops; +} + +MetisGenericEther * +metisEtherListener_GetGenericEtherFromListener(MetisListenerOps *listenerOps) +{ + assertNotNull(listenerOps, "Parameter listenerOps must be non-null"); + assertTrue(listenerOps->getEncapType(listenerOps) == METIS_ENCAP_ETHER, "Can only call on a METIS_ENCAP_ETHER listener"); + + _MetisEtherListener *etherListener = (_MetisEtherListener *) listenerOps->context; + return etherListener->genericEther; +} + +static void +_metisEtherListener_Destroy(_MetisEtherListener **listenerPtr) +{ + assertNotNull(listenerPtr, "Parameter must be non-null double pointer"); + assertNotNull(*listenerPtr, "Parameter must derefernce to non-null pointer"); + + _MetisEtherListener *etherListener = *listenerPtr; + parcEventBuffer_Destroy(&(etherListener->nextReadBuffer)); + metisDispatcher_DestroyNetworkEvent(metisForwarder_GetDispatcher(etherListener->metis), ðerListener->ether_event); + + metisLogger_Release(ðerListener->logger); + + metisGenericEther_Release(ðerListener->genericEther); + _metisEtherListener_ReleaseEthernetAddresses(etherListener); + parcMemory_Deallocate((void **) ðerListener); + *listenerPtr = NULL; +} + +static void +_metisEtherListener_OpsDestroy(MetisListenerOps **listenerOpsPtr) +{ + MetisListenerOps *ops = *listenerOpsPtr; + _MetisEtherListener *etherListener = (_MetisEtherListener *) ops->context; + _metisEtherListener_Destroy(ðerListener); + parcMemory_Deallocate((void **) &ops); + *listenerOpsPtr = NULL; +} + +static unsigned +_metisEtherListener_OpsGetInterfaceIndex(const MetisListenerOps *ops) +{ + _MetisEtherListener *etherListener = (_MetisEtherListener *) ops->context; + return etherListener->id; +} + +static const CPIAddress * +_metisEtherListener_OpsGetListenAddress(const MetisListenerOps *ops) +{ + _MetisEtherListener *etherListener = (_MetisEtherListener *) ops->context; + return etherListener->localAddress; +} + +static MetisEncapType +_metisEtherListener_OpsGetEncapType(const MetisListenerOps *ops) +{ + return METIS_ENCAP_ETHER; +} + +static int +_metisEtherListener_OpsGetSocket(const MetisListenerOps *ops) +{ + _MetisEtherListener *etherListener = (_MetisEtherListener *) ops->context; + return etherListener->ether_fd; +} + + +// ============================================= +// Internal functions + +/** + * Construct an address pair to match the remote + * + * The pair will always be (ourMacAddress, header->sourceAddress), even if the + * packet was received via a group or broadcast dmac. + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +static MetisAddressPair * +_metisEtherListener_ConstructAddressPair(_MetisEtherListener *etherListener, PARCEventBuffer *buffer) +{ + struct ether_header *header = (struct ether_header *) parcEventBuffer_Pullup(buffer, ETHER_HDR_LEN); + + CPIAddress *remoteAddress = cpiAddress_CreateFromLink(header->ether_shost, ETHER_ADDR_LEN); + + MetisAddressPair *pair = metisAddressPair_Create(etherListener->localAddress, remoteAddress); + cpiAddress_Destroy(&remoteAddress); + + return pair; +} + +/** + * Lookup a connection in the connection table based on an address pair + * + * <#Paragraphs Of Explanation#> + * + * @param [in] etherListener An allocated MetisEtherListener + * @param [in] pair The Address pair to lookup + * + * @return null Not found + * @return non-null The connection + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +static const MetisConnection * +_metisEtherListener_LookupConnectionId(_MetisEtherListener *etherListener, MetisAddressPair *pair) +{ + MetisConnectionTable *connTable = metisForwarder_GetConnectionTable(etherListener->metis); + + const MetisConnection *conn = metisConnectionTable_FindByAddressPair(connTable, pair); + return conn; +} + +/** + * @function _metisEtherListener_CreateNewConnection + * @abstract Creates a new Metis connection for the peer + * @discussion + * PRECONDITION: you know there's not an existing connection with the address pair + * + * Creates a new connection and adds it to the connection table. + * + * @param <#param1#> + * @return The connection id for the new connection + */ +static const MetisConnection * +_metisEtherListener_CreateNewConnection(_MetisEtherListener *etherListener, MetisAddressPair *pair) +{ + // metisEtherConnection_Create takes ownership of the pair + MetisIoOperations *ops = metisEtherConnection_Create(etherListener->metis, etherListener->genericEther, pair); + MetisConnection *conn = metisConnection_Create(ops); + + metisConnectionTable_Add(metisForwarder_GetConnectionTable(etherListener->metis), conn); + + return conn; +} + +/** + * Read an ethernet frame and return its buffer. + * + * Will use ether->nextReadBuffer. If we read a frame, will allocate a new nextReadBuffer. + * + * @param [in] fd The ethernet frame socket + * + * @return NULL could not read a frame + * @return non-null Ethernet frame in the buffer + * + * Example: + * @code + * <#example#> + * @endcode + */ +static PARCEventBuffer * +_metisEtherListener_ReadEtherFrame(_MetisEtherListener *etherListener) +{ + PARCEventBuffer *readBuffer = NULL; + + bool success = metisGenericEther_ReadNextFrame(etherListener->genericEther, etherListener->nextReadBuffer); + + if (success) { + readBuffer = etherListener->nextReadBuffer; + etherListener->nextReadBuffer = parcEventBuffer_Create(); + + if (metisLogger_IsLoggable(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "read %zu bytes", + parcEventBuffer_GetLength(readBuffer)); + } + } + + return readBuffer; +} + +/** + * Compares source MAC address to our address + * + * buffer points to the start of the Ethernet frame. Checks if the source address + * is our address. + * + * The check is done against the array of addresses in ether->sourceAddressList + * + * @param [in] header The Ethernet header + * + * @return true The Ethernet source address is our MAC address + * @return false It is not our MAC address + * + * Example: + * @code + * <#example#> + * @endcode + */ +static bool +_metisEtherListener_IsOurSourceAddress(_MetisEtherListener *ether, struct ether_header *header) +{ + // this copies the source address, then masks our copy + uint64_t u64_shost; + memcpy(&u64_shost, header->ether_shost, ETHER_ADDR_LEN); + u64_shost &= MAC_MASK; + + for (int i = 0; i < ether->sourceAddressSize; i++) { + if (u64_shost == ether->sourceAddressList[i]) { + return true; + } + } + return false; +} + +/** + * Compares destination MAC address to our receive addresses + * + * The check is done against the array of addresses in ether->destinationAddressList + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +static bool +_metisEtherListener_IsOurDestinationAddress(_MetisEtherListener *ether, struct ether_header *header) +{ + // this copies the source address, then masks our copy + uint64_t u64_dhost; + memcpy(&u64_dhost, header->ether_dhost, ETHER_ADDR_LEN); + u64_dhost &= MAC_MASK; + + for (int i = 0; i < ether->destinationAddressSize; i++) { + if (u64_dhost == ether->destinationAddressList[i]) { + return true; + } + } + return false; +} + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +static bool +_metisEtherListener_IsOurProtocol(_MetisEtherListener *ether, struct ether_header *header) +{ + // TODO: check the ethertype + return true; +} + +typedef enum { + ParseResult_Accept, + ParseResult_Reject, + ParseResult_Error +} _ParseResult; + +/** + * Processes an ethernet frame to make sure its for us + * + * Ensures that the frame is for us, and not from our source address. + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return ParseResult_Accept We should recieve and process the frame + * @return ParseResult_Reject Do not receive the frame + * @return ParseResult_Error There was an error looking at the Ethernet header + * + * Example: + * @code + * <#example#> + * @endcode + */ +static _ParseResult +_metisEtherListener_ParseEtherFrame(_MetisEtherListener *etherListener, PARCEventBuffer *buffer) +{ + _ParseResult result = ParseResult_Error; + + struct ether_header *header = (struct ether_header *) parcEventBuffer_Pullup(buffer, ETHER_HDR_LEN); + + if (header) { + result = ParseResult_Reject; + if (_metisEtherListener_IsOurProtocol(etherListener, header)) { + if (_metisEtherListener_IsOurDestinationAddress(etherListener, header)) { + if (!_metisEtherListener_IsOurSourceAddress(etherListener, header)) { + // ok, it is the right protocol, a good destination address + // and not our source address. We should ccept this. + + result = ParseResult_Accept; + } + } + } + } + + return result; +} + +static const MetisConnection * +_metisEtherListener_LookupOrCreateConnection(_MetisEtherListener *etherListener, PARCEventBuffer *buffer) +{ + MetisAddressPair *pair = _metisEtherListener_ConstructAddressPair(etherListener, buffer); + + const MetisConnection *conn = _metisEtherListener_LookupConnectionId(etherListener, pair); + + if (!conn) { + conn = _metisEtherListener_CreateNewConnection(etherListener, pair); + + if (metisLogger_IsLoggable(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + char *str = metisAddressPair_ToString(pair); + metisLogger_Log(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Create connid %u address pair %s", metisConnection_GetConnectionId(conn), str); + free(str); + } + } + metisAddressPair_Release(&pair); + + return conn; +} + +/** + * Accept a fragment, put it in rassembler, and pass reassembled frames up stack. + * + * precondition: message is not null + * + * @param [<#in#> | <#out#> | <#in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +static void +_acceptFragment(_MetisEtherListener *etherListener, const MetisConnection *conn, MetisMessage *message) +{ + MetisHopByHopFragmenter *fragmenter = metisEtherConnection_GetFragmenter(conn); + assertNotNull(fragmenter, "Could not get fragmenter from the underlying connection"); + + bool receiveQueueNotEmpty = metisHopByHopFragmenter_Receive(fragmenter, message); + if (receiveQueueNotEmpty) { + MetisMessage *assembled = NULL; + while ((assembled = metisHopByHopFragmenter_PopReceiveQueue(fragmenter)) != NULL) { + etherListener->stats.framesReassembled++; + metisForwarder_Receive(etherListener->metis, assembled); + } + } +} + +static void +_acceptFrame(_MetisEtherListener *etherListener, PARCEventBuffer *buffer, int fd) +{ + const MetisConnection *conn = _metisEtherListener_LookupOrCreateConnection(etherListener, buffer); + + // remove the ethernet header + parcEventBuffer_Read(buffer, NULL, ETHER_HDR_LEN); + + // takes ownership of buffer (will destroy it if there's an error) + MetisMessage *message = metisMessage_CreateFromBuffer(metisConnection_GetConnectionId(conn), metisForwarder_GetTicks(etherListener->metis), buffer, etherListener->logger); + + size_t readLength = parcEventBuffer_GetLength(buffer); + if (message) { + etherListener->stats.framesReceived++; + + if (metisLogger_IsLoggable(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "read %zu bytes from fd %d connid %d", + readLength, + fd, + metisConnection_GetConnectionId(conn)); + _logStats(etherListener, PARCLogLevel_Debug); + } + + _acceptFragment(etherListener, conn, message); + metisMessage_Release(&message); + + } else { + etherListener->stats.framesError++; + + if (metisLogger_IsLoggable(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning)) { + metisLogger_Log(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "read %zu bytes from fd %d connid %d: Error parsing skeleton", + readLength, + fd, + metisConnection_GetConnectionId(conn)); + _logStats(etherListener, PARCLogLevel_Warning); + } + } +} + +static void +_rejectFrame(_MetisEtherListener *etherListener, PARCEventBuffer *buffer, int fd) +{ + etherListener->stats.framesNotForUs++; + + if (metisLogger_IsLoggable(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning)) { + metisLogger_Log(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "read %zu bytes from fd %d: reject frame", + parcEventBuffer_GetLength(buffer), + fd); + _logStats(etherListener, PARCLogLevel_Warning); + } +} + +static void +_errorFrame(_MetisEtherListener *etherListener, PARCEventBuffer *buffer, int fd) +{ + etherListener->stats.framesError++; + + if (metisLogger_IsLoggable(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning)) { + metisLogger_Log(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "read %zu bytes from fd %d: error parsing Ethernet header", + parcEventBuffer_GetLength(buffer), + fd); + _logStats(etherListener, PARCLogLevel_Warning); + } +} + +static void +_metisEtherListener_ReadCallback(int fd, PARCEventType what, void *user_data) +{ + // ether is datagram based, we don't have a connection + _MetisEtherListener *etherListener = (_MetisEtherListener *) user_data; + + if (metisLogger_IsLoggable(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "socket %d what %s%s%s%s data %p", + fd, + (what & PARCEventType_Timeout) ? " timeout" : "", + (what & PARCEventType_Read) ? " read" : "", + (what & PARCEventType_Write) ? " write" : "", + (what & PARCEventType_Signal) ? " signal" : "", + user_data); + } + + if (what & PARCEventType_Read) { + while (true) { + PARCEventBuffer *buffer = _metisEtherListener_ReadEtherFrame(etherListener); + + if (buffer == NULL) { + break; + } + + etherListener->stats.framesIn++; + + _ParseResult result = _metisEtherListener_ParseEtherFrame(etherListener, buffer); + + switch (result) { + case ParseResult_Accept: + _acceptFrame(etherListener, buffer, fd); + break; + + case ParseResult_Reject: + _rejectFrame(etherListener, buffer, fd); + parcEventBuffer_Destroy(&buffer); + break; + + case ParseResult_Error: + _errorFrame(etherListener, buffer, fd); + parcEventBuffer_Destroy(&buffer); + break; + + default: + trapUnexpectedState("Do not understand parse result %d", result); + } + } + } +} diff --git a/metis/ccnx/forwarder/metis/io/metis_EtherListener.h b/metis/ccnx/forwarder/metis/io/metis_EtherListener.h new file mode 100644 index 00000000..12aee973 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_EtherListener.h @@ -0,0 +1,67 @@ +/* + * 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_EtherListener.h + * @brief Listen for raw ethernet frames on an interface + * + * <#Detailed Description#> + * + */ + +#ifndef Metis_metis_EtherListener_h +#define Metis_metis_EtherListener_h + +#include <ccnx/forwarder/metis/io/metis_GenericEther.h> + +struct metis_listener_ether; +typedef struct metis_listener_ether MetisListenerEther; + +/** + * @function metisListenerEther_Create + * @abstract Create a L2 listener on a raw ethertype + * @discussion + * Requires root, will send/receive ethernet frames on the specified device. + * The exact mechanism varies by system. + * + * @param deviceName is the system name of the interface (e.g. "en0") + * @return <#return#> + */ +MetisListenerOps *metisEtherListener_Create(MetisForwarder *metis, const char *deviceName, uint16_t ethertype); + +/** + * Return the underlying GenericEther of the listener + * + * The MetisGenericEther wraps the platform-specific IO operations of the ethernet connection. + * Will assert if the listenerOps is not of type METIS_ENCAP_ETHER. + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval non-null The underlying generic ethernet + * @retval null An error + * + * Example: + * @code + * { + * MetisListenerSet *listenerSet = metisForwarder_GetListenerSet(metis); + * MetisListenerOps *listenerOps = metisListenerSet_Find(listenerSet, METIS_ENCAP_ETHER, linkAddress); + * if (listenerOps) { + * MetisGenericEther *ether = metisEtherListener_GetGenericEtherFromListener(listenerOps); + * } + * } + * @endcode + */ +MetisGenericEther *metisEtherListener_GetGenericEtherFromListener(MetisListenerOps *listenerOps); +#endif // Metis_metis_EtherListener_h diff --git a/metis/ccnx/forwarder/metis/io/metis_Ethernet.h b/metis/ccnx/forwarder/metis/io/metis_Ethernet.h new file mode 100644 index 00000000..9c560e94 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_Ethernet.h @@ -0,0 +1,36 @@ +/* + * 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_Ethernet.h + * @brief Helpers for Ethernet frames + * + */ + +#ifndef Metis_metis_Ethernet_h +#define Metis_metis_Ethernet_h + +/** + * returns true is the ethertype is at least 0x0600 indicating + * a type II frame (IEEE 802.3x-1997) + * + * @param [in] ethertype The ethertype in host byte order + * + * @retval true if the ethertype is at least 0x0600 + * @retval false Otherwise + */ +#define metisEthernet_IsValidEthertype(ethertype) ((ethertype) >= 0x0600) + +#endif diff --git a/metis/ccnx/forwarder/metis/io/metis_GenericEther.h b/metis/ccnx/forwarder/metis/io/metis_GenericEther.h new file mode 100644 index 00000000..248a55ec --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_GenericEther.h @@ -0,0 +1,200 @@ +/* + * 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 generic_ether.h + * @brief Generic interface to working with Ethernet frames. + * + * Wraps platform-specific code. The implementation is found in the metis/platforms directory. + * + */ + +#ifndef Metis_metis_GenericEther_h +#define Metis_metis_GenericEther_h + +#include <stdbool.h> +#include <parc/algol/parc_EventBuffer.h> +#include <parc/algol/parc_Buffer.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +struct metis_generic_ether; +typedef struct metis_generic_ether MetisGenericEther; + +/** + * Create a generic ethernet object + * + * Hides system dependent ethernet. Creates an ethernet object that is ready to send and + * receive ethernet frames on a given device and ether type. There may be system limits + * to the number of these you can open (i.e. 4 BPF devices on Mac OS). + * + * If the device name is NULL, it will not bind to a specific interface. + * + * You generally need elevated permissions to access an Ethernet device. This function + * may return NULL due to a permissions error. + * + * @param [in] deviceName The name of the device, e.g. "eth0" or "en1" + * @param [in] etherType The host-byte-order ethertype (i.e. 0x0801) + * @param [in] logger The MetisLogger to use + * + * @retval non-null An allocated ethernet object + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisGenericEther *metisGenericEther_Create(MetisForwarder *metis, const char *deviceName, uint16_t etherType); + +/** + * Acquire a reference counted copy + * + * Returns a reference counted copy of the generic Ethernet object + * + * @param [in] ether An allocated Ethernet object + * + * @retval non-null A reference counted copy + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisGenericEther *metisGenericEther_Acquire(const MetisGenericEther *ether); + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisGenericEther_Release(MetisGenericEther **etherPtr); + +/** + * Returns the descriptor for i/o + * + * Returns a system descriptor that can be used to select, poll, etc. + * + * Do not close the socket. It will be closed when metisGenericEther_Release() is called. + * + * @param [in] ether An allocated generic ethernet + * + * @retval non-negative The system descriptor + * @retval negative An error (use errno) + * + * Example: + * @code + * <#example#> + * @endcode + */ +int metisGenericEther_GetDescriptor(const MetisGenericEther *ether); + + +/** + * Reads the next ethernet frame in to the provided buffer + * + * The frame will have the Ethernet header + * + * @param [in] ether An allocated generic ethernet + * @param [in] buffer The allocated buffer to put the packet in + * + * @retval true A frame was ready and put in the buffer + * @retval false No frame is ready + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisGenericEther_ReadNextFrame(MetisGenericEther *ether, PARCEventBuffer *buffer); + +/** + * Sends an Ethernet frame out the device + * + * The frame must have an Ethernet header filled in with all values. + * + * @param [in] ether An allocated GenericEther object + * @param [in] buffer The buffer to send, including the Ethernet header + * + * @retval true The frame was sent + * @retval false An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisGenericEther_SendFrame(MetisGenericEther *ether, PARCEventBuffer *buffer); + +/** + * Return the MAC address the object is bound to + * + * Returns a PARCBuffer with the 6-byte mac address of the interface + * + * @param [in] ether An allocated GenericEther object + * + * @retval non-null The MAC address of the interface + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +PARCBuffer *metisGenericEther_GetMacAddress(const MetisGenericEther *ether); + +/** + * Returns the ethertype associated with this object + * + * Returns the ethertype, in host byte order. + * + * @param [in] ether An allocated GenericEther object + * + * @retval number Ethertype (host byte order) + * + * Example: + * @code + * <#example#> + * @endcode + */ +uint16_t metisGenericEther_GetEtherType(const MetisGenericEther *ether); + +/** + * Returns the maximum transmission unit (MTU) + * + * The MTU is the largest user payload allowed in a frame + * + * @param [<#in#> | <#out#> | <#in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +unsigned metisGenericEther_GetMTU(const MetisGenericEther *ether); +#endif // Metis_metis_GenericEther_h diff --git a/metis/ccnx/forwarder/metis/io/metis_HopByHopFragmenter.c b/metis/ccnx/forwarder/metis/io/metis_HopByHopFragmenter.c new file mode 100644 index 00000000..ce8f5956 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_HopByHopFragmenter.c @@ -0,0 +1,660 @@ +/* + * 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. + */ + +/** + * See https://www.ietf.org/proceedings/interim/2015/03/22/icnrg/slides/slides-interim-2015-icnrg-2-3.pptx + * + * B flag - indicates the start of a fragment + * E flag - indicates the end of a fragment (may be in same frame as B frame) + * I flag - an Idle frame (may only occur between E and B frames). + * X flag - extended format (not supported) + * + * in the basic protocol that we implement, there is a 20-bit sequence number + * + */ + +#include <config.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <ccnx/forwarder/metis/io/metis_HopByHopFragmenter.h> + +#include <parc/algol/parc_EventBuffer.h> + +#include <parc/concurrent/parc_RingBuffer_1x1.h> + +/* + * Complete header for the Basic Encoding in a V1 FixedHeader. The blob[3] array + * holds the protocol header fields. See the macros below for accessing specific fields. + * The tlvType and tlvLength begin the container to hold the fragment payload. + */ +typedef struct hopbyhop_header { + uint8_t version; + uint8_t packetType; + uint16_t packetLength; + uint8_t blob[3]; + uint8_t headerLength; + uint16_t tlvType; + uint16_t tlvLength; +} __attribute__((packed)) _HopByHopHeader; + +// These two values are also defined in metis_TlvShcemaV1.c +#define METIS_PACKET_TYPE_HOPFRAG 4 +#define T_HOPFRAG_PAYLOAD 0x0005 + +/* + * Mask a uint32_t down to the 20-bit sequence number + */ +#define SEQNUM_MASK ((uint32_t) (0x000FFFFF)) + +/* + * This will right-pad the seqnum out to 32 bits. By filling up a uint32_t it allows + * us to use 2's compliment math to compare two sequence numbers rather than the cumbersome + * multiple branches required by the method outlined in RFC 1982. + * We use a 20-bit sequence number, so need to shift 12 bits to the left. + */ +#define SEQNUM_SHIFT 12 + +/* + * The X bit value in the top byte of the header + */ +#define XMASK 0x80 + +/* + * The B bit value in the top byte of the header + */ +#define BMASK 0x40 + +/* + * The E bit value in the top byte of the header + */ +#define EMASK 0x20 + +/* + * The I bit value in the top byte of the header + */ +#define IMASK 0x10 + +/* + * Mask out the flags from the top byte of the header + */ +#define _hopByHopHeader_GetFlags(header) ((header)->blob[0] & 0xF0) + +/* + * non-zero if the X flag is set + */ +#define _hopByHopHeader_GetXFlag(header) ((header)->blob[0] & XMASK) + +/* + * non-zero if the B flag is set + */ +#define _hopByHopHeader_GetBFlag(header) ((header)->blob[0] & BMASK) + +/* + * non-zero if the E flag is set + */ +#define _hopByHopHeader_GetEFlag(header) ((header)->blob[0] & EMASK) + +/* + * non-zero if the I flag is set + */ +#define _hopByHopHeader_GetIFlag(header) ((header)->blob[0] & IMASK) + +/* + * Sets the X flag in the header + */ +#define _hopByHopHeader_SetXFlag(header) ((header)->blob[0] |= XMASK) + +/* + * Sets the B flag in the header + */ +#define _hopByHopHeader_SetBFlag(header) ((header)->blob[0] |= BMASK) + +/* + * Sets the E flag in the header + */ +#define _hopByHopHeader_SetEFlag(header) ((header)->blob[0] |= EMASK) + +/* + * Sets the I flag in the header + */ +#define _hopByHopHeader_SetIFlag(header) ((header)->blob[0] |= IMASK) + +typedef enum { + _ParserState_Idle, // not parsing anything + _ParserState_Busy, // we have received a B but not an E +} _ParserState; + +struct metis_hopbyhop_fragment { + MetisLogger *logger; + unsigned mtu; + + // The next expected sequence number (i.e. compare then increment) + uint32_t nextReceiveFragSequenceNumber; + + // The next seqnum to use in out-going message (i.e. use then increment) + uint32_t nextSendFragSequenceNumber; + + unsigned receiveQueueCapacity; + unsigned sendQueueCapacity; + PARCRingBuffer1x1 *receiveQueue; + PARCRingBuffer1x1 *sendQueue; + + // We are only ever reassembling one packet at a time + PARCEventBuffer *currentReceiveBuffer; + + // these two are set from the "B" fragment so a reassembled frame + // will have the time and ingress port of the first fragment. + MetisTicks currentReceiveBufferStartTicks; + unsigned currentReceiveBufferIngressId; + + // Determines if we are currently reassembling a fragment + _ParserState parserState; +}; + +static uint32_t +_hopByHopHeader_GetSeqnum(const _HopByHopHeader *header) +{ + uint32_t seqnum = ((uint32_t) header->blob[0] & 0x0F) << 16 | (uint32_t) header->blob[1] << 8 | header->blob[2]; + return seqnum; +} + +static void __attribute__((unused)) +_hopByHopHeader_SetSeqnum(_HopByHopHeader *header, uint32_t seqnum) +{ + header->blob[2] = seqnum & 0xFF; + header->blob[1] = (seqnum >> 8) & 0xFF; + + header->blob[0] &= 0xF0; + header->blob[0] |= (seqnum >> 16) & 0x0F; +} + +static void +_ringBufferDestroyer(void **ptr) +{ + MetisMessage *message = *ptr; + metisMessage_Release(&message); + *ptr = NULL; +} + +/** + * Compares sequence numbers as per RFC 1982 + * + * Handles wrap-around using the 1/2 buffer rule as per RFC 1982. The indefinate state + * at exactly the middle is handled by having 2^(N-1)-1 greater than and 2^(N-1) less than. + * + * @param [in] a The first sequence number + * @param [in] b The second sequence number + * + * @return negative If a < b + * @return 0 If a == b + * @return positive if a > b + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +static int +_compareSequenceNumbers(uint32_t a, uint32_t b) +{ + // shift the numbers so they take up a full 32-bits and then use 2's compliment + // arithmatic to determine the ordering + + a <<= SEQNUM_SHIFT; + b <<= SEQNUM_SHIFT; + + int32_t c = (int32_t) (a - b); + return c; +} + +static uint32_t +_incrementSequenceNumber(const uint32_t seqnum, const uint32_t mask) +{ + uint32_t result = (seqnum + 1) & mask; + return result; +} + +static uint32_t +_nextSendSequenceNumber(MetisHopByHopFragmenter *fragmenter) +{ + uint32_t seqnum = fragmenter->nextSendFragSequenceNumber; + fragmenter->nextSendFragSequenceNumber = _incrementSequenceNumber(fragmenter->nextSendFragSequenceNumber, SEQNUM_MASK); + return seqnum; +} + +// =================================================== +// RECEIVE PROCESS + +static void +_resetParser(MetisHopByHopFragmenter *fragmenter) +{ + // throw away the re-assembly buffer and reset state to Idle + parcEventBuffer_Read(fragmenter->currentReceiveBuffer, NULL, UINT32_MAX); + fragmenter->parserState = _ParserState_Idle; +} + +/** + * Apply the sequence number rules + * + * a) If the sequence number is in order, no action. + * b) If the sequence number is out of order, reset the parser. + * c) Update the next expected sequence number to be this packet's seqnum + 1. + * + * @param [in] fragmenter An allocated MetisHopByHopFragmenter + * @param [in] fixedHeader The packet's fixed header + * + * Example: + * @code + * <#example#> + * @endcode + */ +static void +_applySequenceNumberRules(MetisHopByHopFragmenter *fragmenter, const _HopByHopHeader *fixedHeader) +{ + uint32_t segnum = _hopByHopHeader_GetSeqnum(fixedHeader); + + int compare = _compareSequenceNumbers(segnum, fragmenter->nextReceiveFragSequenceNumber); + + if (compare == 0) { + // In order + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Fragmenter %p in-order seqnum %u", + (void *) fragmenter, segnum); + } else if (compare < 0) { + // it is an old sequence number + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__, + "Fragmenter %p out-of-order seqnum %u expecting %u", + (void *) fragmenter, segnum, fragmenter->nextReceiveFragSequenceNumber); + _resetParser(fragmenter); + } else if (compare > 0) { + // lost packets + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__, + "Fragmenter %p out-of-order seqnum %u expecting %u", + (void *) fragmenter, segnum, fragmenter->nextReceiveFragSequenceNumber); + _resetParser(fragmenter); + } + + // the next seqnum we expect will be 1 after what we just received. For example, if we lost packets + // this will put us back in line with the new series. + fragmenter->nextReceiveFragSequenceNumber = _incrementSequenceNumber(segnum, SEQNUM_MASK); +} + +/* + * We've reach the END fragment of the reassembly buffer. + * 1) Make a metis message out of the reassembly buffer, + * 2) put the message in the receive queue (discard if queue full) + * 3) allocate a new reassembly buffer + * 4) reset the parser + */ +static void +_finalizeReassemblyBuffer(MetisHopByHopFragmenter *fragmenter) +{ + // This takes ownership of fragmenter->currentReceiveBuffer + MetisMessage *reassembled = metisMessage_CreateFromBuffer(fragmenter->currentReceiveBufferIngressId, + fragmenter->currentReceiveBufferStartTicks, + fragmenter->currentReceiveBuffer, + fragmenter->logger); + + if (reassembled) { + bool success = parcRingBuffer1x1_Put(fragmenter->receiveQueue, reassembled); + if (success) { + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Fragmenter %p putting reassembed message %p in receive queue", + (void *) fragmenter, (void *) reassembled); + } else { + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Fragmenter %p failed to put reassembled message in receive queue, dropping", + (void *) fragmenter); + + metisMessage_Release(&reassembled); + } + + fragmenter->currentReceiveBuffer = parcEventBuffer_Create(); + } else { + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "Fragmenter %p failed to parse reassembled packet to MetisMessage, dropping", + (void *) fragmenter); + } + + _resetParser(fragmenter); +} + +static void +_appendFragmentToReassemblyBuffer(MetisHopByHopFragmenter *fragmenter, const MetisMessage *message) +{ + size_t length = metisMessage_AppendFragmentPayload(message, fragmenter->currentReceiveBuffer); + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Fragmenter %p append %zu bytes to reassembly buffer", + (void *) fragmenter, length); +} + +/* + * Parser is in Idle state. We can only accept a B or BE frame. + * 1) If B frame: + * 1a) append to current receive buffer + * 1b) set parser state to Busy + * 1c) set the currentReceiveBufferStartTicks + * 1d) set the currentReceiveBufferIngressId + * 2) If BE frame, do B actions and finalize it (side effect: will reset state to Idle) + * 3) Otherwise ignore it + * + * Precondition: You know that the parser is in the Idle state + */ +static void +_receiveInIdleState(MetisHopByHopFragmenter *fragmenter, const MetisMessage *message, const _HopByHopHeader *fixedHeader) +{ + trapUnexpectedStateIf(fragmenter->parserState != _ParserState_Idle, + "Parser in wrong state, expected %d got %d", + _ParserState_Idle, fragmenter->parserState); + + if (_hopByHopHeader_GetBFlag(fixedHeader)) { + // start a new packet + fragmenter->currentReceiveBufferStartTicks = metisMessage_GetReceiveTime(message); + fragmenter->currentReceiveBufferIngressId = metisMessage_GetIngressConnectionId(message); + fragmenter->parserState = _ParserState_Busy; + + _appendFragmentToReassemblyBuffer(fragmenter, message); + + if (_hopByHopHeader_GetEFlag(fixedHeader)) { + // it is also the last fragment + _finalizeReassemblyBuffer(fragmenter); + } + } else if (_hopByHopHeader_GetIFlag(fixedHeader)) { + // nothing to do + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Fragmenter %p idle frame, ignorning", + (void *) fragmenter); + } else { + // nothing we can do with this frame + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "Fragmenter %p received bad header flags (0x%02X), ignorning", + (void *) fragmenter, _hopByHopHeader_GetFlags(fixedHeader)); + } +} + +/* + * In the Busy state, we can only accept a packet with no flag (middle) or end flag (end of packet). + * Anything else is an error and will cause the parser to be reset. + * + * 1) If no flags + * 1a) append to reassembly buffer + * 2) If E flag + * 2a) append to reassembly buffer + * 2b) finalize the buffer (side effect: will reset the parser and place in receive queue) + * 3) Otherwise, its an error, reset the parser + * + * PRECONDITION: you know the packet is in-order relative to the assembly buffer. + * This is handled by calling _applySequenceNumberRules() before this function. + * PRECONDITION: you know the parser is in the Busy state. + */ +static void +_receiveInBusyState(MetisHopByHopFragmenter *fragmenter, const MetisMessage *message, const _HopByHopHeader *fixedHeader) +{ + trapUnexpectedStateIf(fragmenter->parserState != _ParserState_Busy, + "Parser in wrong state, expected %d got %d", + _ParserState_Busy, fragmenter->parserState); + + if (_hopByHopHeader_GetFlags(fixedHeader) == 0) { + // It's a middle packet + + _appendFragmentToReassemblyBuffer(fragmenter, message); + } else if (_hopByHopHeader_GetEFlag(fixedHeader)) { + // It is the last fragment + + _appendFragmentToReassemblyBuffer(fragmenter, message); + + _finalizeReassemblyBuffer(fragmenter); + } else { + // nothing we can do with this frame, and it's a state machine error + + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "Fragmenter %p received invalid headers (0x%02X) in Busy state, resetting", + (void *) fragmenter, _hopByHopHeader_GetFlags(fixedHeader)); + _resetParser(fragmenter); + } +} + +/** + * Receives a fragment and applies the protocol algorithm + * + * 1) A receiver maintains one reassembly queue per peer. + * 2) Discard Idle fragments. + * 3) Discard fragments until a 'B' fragment is received. Store the received sequence number for this sender. + * 4) If an out-of-order fragment is received next, discard the reassembly buffer and go to step (2). + * 5) Continue receiving in-order fragments until the first 'E’ fragment. At this time, the fragmented + * packet is fully re-assembled and may be passed on to the next layer. + * 6) The receiver cannot assume it will receive the 'E' fragment or a subsequence 'I' frame, so it should + * use a timeout mechanism appropriate to the link to release allocated memory resources. + * + * @param [in] fragmenter An allocated MetisHopByHopFragmenter + * @param [in] message The fragment packet + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +static void +_receiveFragment(MetisHopByHopFragmenter *fragmenter, const MetisMessage *message) +{ + const _HopByHopHeader *fixedHeader = (const _HopByHopHeader *) metisMessage_FixedHeader(message); + + _applySequenceNumberRules(fragmenter, fixedHeader); + + // ====== + // Now apply the receiver rules + + + switch (fragmenter->parserState) { + case _ParserState_Idle: + _receiveInIdleState(fragmenter, message, fixedHeader); + break; + + case _ParserState_Busy: + _receiveInBusyState(fragmenter, message, fixedHeader); + break; + + default: + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Fragmenter %p Invalid parser state, discarding current buffer and resetting to Idle: %d", + (void *) fragmenter, fragmenter->parserState); + break; + } +} + +// =================================================== +// SEND PROCESS + +/** + * Fragments a message and puts all the fragments in the send queue + * + * Splits up the message in to fragments. The frist fragment will have the B flag and + * the last fragment will have the E flag. If the message fits in one fragment, it will + * have both the BE flags. Middle messages have no flags. + * + * @param [in] fragmenter An allocated MetisHopByHopFragmenter + * @param [in] message The message to fragment down to MTU size + * + * @return true Message was fragmented and all fragments put on send queue + * @return false Error durring fragmentation (likley full send queue) + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +static bool +_sendFragments(MetisHopByHopFragmenter *fragmenter, const MetisMessage *message) +{ + const size_t length = metisMessage_Length(message); + size_t offset = 0; + + const size_t maxPayload = fragmenter->mtu - sizeof(_HopByHopHeader); + + _HopByHopHeader header; + memset(&header, 0, sizeof(header)); + _hopByHopHeader_SetBFlag(&header); + + while (offset < length) { + size_t payloadLength = maxPayload; + const size_t remaining = length - offset; + + if (remaining < maxPayload) { + payloadLength = remaining; + _hopByHopHeader_SetEFlag(&header); + } + + const size_t packetLength = sizeof(_HopByHopHeader) + payloadLength; + + header.version = 1; + header.packetType = METIS_PACKET_TYPE_HOPFRAG; + header.packetLength = htons(packetLength); + header.headerLength = 8; + header.tlvType = htons(T_HOPFRAG_PAYLOAD); + header.tlvLength = htons(payloadLength); + + uint32_t seqnum = _nextSendSequenceNumber(fragmenter); + _hopByHopHeader_SetSeqnum(&header, seqnum); + + MetisMessage *fragment = metisMessage_Slice(message, offset, payloadLength, sizeof(header), (uint8_t *) &header); + bool goodput = parcRingBuffer1x1_Put(fragmenter->sendQueue, fragment); + if (!goodput) { + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "Fragmenter %p message %p send queue full offset %zu length %zu", + (void *) fragmenter, (void *) message, offset, payloadLength); + metisMessage_Release(&fragment); + break; + } + + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Fragmenter %p message %p send queue fragment %p offset %zu length %zu", + (void *) fragmenter, (void *) message, (void *) fragment, offset, payloadLength); + + offset += payloadLength; + + memset(&header, 0, sizeof(header)); + } + + return (offset == length); +} + +// =================================================== + +MetisHopByHopFragmenter * +metisHopByHopFragmenter_Create(MetisLogger *logger, unsigned mtu) +{ + MetisHopByHopFragmenter *fragmenter = parcMemory_Allocate(sizeof(MetisHopByHopFragmenter)); + if (fragmenter) { + fragmenter->nextReceiveFragSequenceNumber = 0; + fragmenter->nextSendFragSequenceNumber = 0; + + fragmenter->logger = metisLogger_Acquire(logger); + fragmenter->mtu = mtu; + fragmenter->receiveQueueCapacity = 128; // this is a many-to-one queue, so not too big + fragmenter->sendQueueCapacity = 2048; // this is a one-to-many queue, so bigger (e.g. 64 KB in to 1KB payloads) + fragmenter->receiveQueue = parcRingBuffer1x1_Create(fragmenter->receiveQueueCapacity, _ringBufferDestroyer); + fragmenter->sendQueue = parcRingBuffer1x1_Create(fragmenter->sendQueueCapacity, _ringBufferDestroyer); + fragmenter->currentReceiveBuffer = parcEventBuffer_Create(); + fragmenter->parserState = _ParserState_Idle; + } + return fragmenter; +} + +void +metisHopByHopFragmenter_Release(MetisHopByHopFragmenter **fragmenterPtr) +{ + MetisHopByHopFragmenter *fragmenter = *fragmenterPtr; + parcEventBuffer_Destroy(&fragmenter->currentReceiveBuffer); + parcRingBuffer1x1_Release(&fragmenter->sendQueue); + parcRingBuffer1x1_Release(&fragmenter->receiveQueue); + metisLogger_Release(&fragmenter->logger); + parcMemory_Deallocate((void **) fragmenterPtr); +} + +bool +metisHopByHopFragmenter_Receive(MetisHopByHopFragmenter *fragmenter, const MetisMessage *message) +{ + if (metisMessage_GetType(message) == MetisMessagePacketType_HopByHopFrag) { + _receiveFragment(fragmenter, message); + } else { + // put the whole thing on the output queue + MetisMessage *copy = metisMessage_Acquire(message); + bool putSuccess = parcRingBuffer1x1_Put(fragmenter->receiveQueue, copy); + if (!putSuccess) { + metisMessage_Release(©); + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "Failed to add message %p to receive queue", (void *) message); + } else { + if (metisLogger_IsLoggable(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Add message %p to receive queue", (void *) message); + } + } + } + + // the maximum remaining is its capacity - 1 + return (parcRingBuffer1x1_Remaining(fragmenter->receiveQueue) < fragmenter->receiveQueueCapacity - 1); +} + +MetisMessage * +metisHopByHopFragmenter_PopReceiveQueue(MetisHopByHopFragmenter *fragmenter) +{ + MetisMessage *message = NULL; + parcRingBuffer1x1_Get(fragmenter->receiveQueue, (void **) &message); + return message; +} + +bool +metisHopByHopFragmenter_Send(MetisHopByHopFragmenter *fragmenter, MetisMessage *message) +{ + bool success = false; + + // If the packet will fit in the MTU without fragmentation, do not use fragmentation + if (metisMessage_Length(message) > fragmenter->mtu) { + success = _sendFragments(fragmenter, message); + } else { + MetisMessage *copy = metisMessage_Acquire(message); + success = parcRingBuffer1x1_Put(fragmenter->sendQueue, copy); + if (!success) { + metisMessage_Release(©); + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "Failed to add message %p to send queue", (void *) message); + } else { + if (metisLogger_IsLoggable(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Add message %p to send queue", (void *) message); + } + } + } + + return success; +} + +MetisMessage * +metisHopByHopFragmenter_PopSendQueue(MetisHopByHopFragmenter *fragmenter) +{ + MetisMessage *message = NULL; + parcRingBuffer1x1_Get(fragmenter->sendQueue, (void **) &message); + return message; +} + diff --git a/metis/ccnx/forwarder/metis/io/metis_HopByHopFragmenter.h b/metis/ccnx/forwarder/metis/io/metis_HopByHopFragmenter.h new file mode 100644 index 00000000..6fd83f19 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_HopByHopFragmenter.h @@ -0,0 +1,216 @@ +/* + * 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_HopByHopFragmenterer.h + * @brief Implements a fragmenter for a hop-by-hop protocol + * + * The hop-by-hop fragmenter can be though of as a bi-directional queue. + * + * The receive process takes fragmented packets from the wire and reassembles them. Once a packet is + * reassembed, it is put on end of the receive queue. The user can then pop a message of the top of + * the receive queue and process it normally. + * + * The send process takes a potentially large input packet and fragments it in to pieces that are places + * in order on the send queue. The caller can then pop messages of the head of the send queue and + * put them on the wire. + * + * In the next update, we probalby want to make the send queue and receive queue external so I/O and + * message processing threads can access them directly. + * + */ + +#ifndef __Metis__metis_HopByHopFragmenter__ +#define __Metis__metis_HopByHopFragmenter__ + +#include <stdbool.h> +#include <ccnx/forwarder/metis/core/metis_Logger.h> +#include <ccnx/forwarder/metis/core/metis_Message.h> + +struct metis_hopbyhop_fragment; +typedef struct metis_hopbyhop_fragment MetisHopByHopFragmenter; + +/** + * Creates a fragmentation class for a specific session + * + * The fragmenter is specific to a given flow (i.e. source-destination-ethertype tuple). + * It is the responsibility of the caller to create the appropriate number of fragmenters + * and classify packets in to the right fragmenter. + * + * @param [in] logger The logger to use for output + * @param [in] mtu The MTU to use for send operations + * + * @return non-null An allocted MetisHopByHopFragmenter + * @return null An error + * + * Example: + * @code + * { + * MetisHopByHopFragmenter *fragmenter = metisHopByHopFragmenter_Create(logger, mtu); + * metisHopByHopFragmenter_Release(&fragmenter); + * } + * @endcode + */ +MetisHopByHopFragmenter *metisHopByHopFragmenter_Create(MetisLogger *logger, unsigned mtu); + +/** + * Release a reference to the fragmenter + * + * Will destroy any packets in the receive and send queues. + * + * @param [in,out] fragmenterPtr A pointer to an allocated MetisHopByHopFragmenter + * + * Example: + * @code + * { + * MetisHopByHopFragmenter *fragmenter = metisHopByHopFragmenter_Create(logger, mtu); + * metisHopByHopFragmenter_Release(&fragmenter); + * } + * @endcode + */ +void metisHopByHopFragmenter_Release(MetisHopByHopFragmenter **fragmenterPtr); + +/** + * Receives a message as part of the fragmentation session + * + * Receives a fragment. If this causes a reassembly to complete, the completed packet + * will be placed in the receive queue and may be accessed by metisHopByHopFragmenter_PopReceiveQueue(). + * The caller is reponsible for releasing message. + * + * If a non-fragment packet is received, it is placed directly on the receive queue. + * + * The caller is responsible for releasing the message. + * + * @param [in] fragmenter An allocated MetisHopByHopFragmenter + * @param [in] message An allocated MetisMessage + * + * @return true The receive buffer has an assembled packet ready for read + * @return false the receive buffer does not have a complete packet ready. + * + * Example: + * @code + * { + * void + * acceptFragment(MetisHopByHopFragmenter *fragmenter, MetisMessage *message) + * { + * bool receiveQueueNotEmpty = metisHopByHopFragmenter_Receive(fragmenter, message); + * if (receiveQueueNotEmpty) { + * MetisMessage *assembled = NULL; + * while ((assembled = metisHopByHopFragmenter_PopReceiveQueue(fragmenter)) != NULL) { + * etherListener->stats.framesReassembled++; + * metisForwarder_Receive(etherListener->metis, assembled); + * } + * } + * } + * } + * @endcode + */ +bool metisHopByHopFragmenter_Receive(MetisHopByHopFragmenter *fragmenter, const MetisMessage *message); + +/** + * Pops the top assembed message from the receive queue + * + * Reads the top reassembled packet from the receive queue. The caller must + * release the returned message. + * + * @param [in] fragmenter An allocated MetisHopByHopFragmenter + * + * @return NULL The receive queue is empty (i.e. the current reassembly is not complete) + * @return non-null A re-assembed message + * + * Example: + * @code + * { + * void + * acceptFragment(MetisHopByHopFragmenter *fragmenter, MetisMessage *message) + * { + * bool receiveQueueNotEmpty = metisHopByHopFragmenter_Receive(fragmenter, message); + * if (receiveQueueNotEmpty) { + * MetisMessage *assembled = NULL; + * while ((assembled = metisHopByHopFragmenter_PopReceiveQueue(fragmenter)) != NULL) { + * etherListener->stats.framesReassembled++; + * metisForwarder_Receive(etherListener->metis, assembled); + * } + * } + * } + * } + * @endcode + */ +MetisMessage *metisHopByHopFragmenter_PopReceiveQueue(MetisHopByHopFragmenter *fragmenter); + +/** + * Adds a message to the send buffer + * + * This may make multiple references to the original message where each fragment is + * pointing as an extent in to the original message. + * + * @param [in] fragmenter An allocated MetisHopByHopFragmenter + * @param [in] message An allocated MetisMessage + * + * @return true The message was fragmented and put on the send queue + * @return false An error + * + * Example: + * @code + * { + * void sendFragments(MetisHopByHopFragmenter *fragmenter, const MetisMessage *message) { + * bool success = metisHopByHopFragmenter_Send(fragmenter, message); + * + * MetisMessage *fragment; + * while (success && (fragment = metisHopByHopFragmenter_PopSendQueue(fragmenter)) != NULL) { + * success = _sendFrame(fragment); + * metisMessage_Release(&fragment); + * } + * + * // if we failed, drain the other fragments + * if (!success) { + * while ((fragment = metisHopByHopFragmenter_PopSendQueue(fragmenter)) != NULL) { + * metisMessage_Release(&fragment); + * } + * } + * // caller must release message + * } + * @endcode + */ +bool metisHopByHopFragmenter_Send(MetisHopByHopFragmenter *fragmenter, MetisMessage *message); + +/** + * Pops the next message to send to the wire from the send queue + * + * Returns the front of the Send FIFO queue of fragments that should be + * sent on the wire. + * + * @param [in] fragmenter An allocated MetisHopByHopFragmenter + * + * @return null there is no message awaiting transmit + * @return non-null A message to send + * + * Example: + * @code + * { + * void sendIdleFragment(MetisHopByHopFragmenter *fragmenter) { + * bool success = metisHopByHopFragmenter_SendIdle(fragmenter); + * + * MetisMessage *fragment; + * while (success && (fragment = metisHopByHopFragmenter_PopSendQueue(fragmenter)) != NULL) { + * success = _sendFrame(fragment); + * metisMessage_Release(&fragment); + * } + * } + * } + * @endcode + */ +MetisMessage *metisHopByHopFragmenter_PopSendQueue(MetisHopByHopFragmenter *fragmenter); +#endif /* defined(__Metis__metis_HopByHopFragmenter__) */ diff --git a/metis/ccnx/forwarder/metis/io/metis_IPMulticastListener.c b/metis/ccnx/forwarder/metis/io/metis_IPMulticastListener.c new file mode 100644 index 00000000..0dc5606d --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_IPMulticastListener.c @@ -0,0 +1,35 @@ +/* + * 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 <stdbool.h> +#include <string.h> +#include <errno.h> +#include <arpa/inet.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/io/metis_IPMulticastListener.h> + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_Connection.h> +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> +#include <ccnx/forwarder/metis/core/metis_Message.h> + +MetisListenerOps *metisIPMulticastListener_CreateInet6(MetisForwarder *metis, struct sockaddr_in6 sin6); +MetisListenerOps *metisIPMulticastListener_CreateInet(MetisForwarder *metis, struct sockaddr_in sin); diff --git a/metis/ccnx/forwarder/metis/io/metis_IPMulticastListener.h b/metis/ccnx/forwarder/metis/io/metis_IPMulticastListener.h new file mode 100644 index 00000000..ca1134e2 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_IPMulticastListener.h @@ -0,0 +1,27 @@ +/* + * 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_IPMulticastListener_h +#define Metis_metis_IPMulticastListener_h + +#include <netinet/in.h> +#include <stdlib.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/io/metis_Listener.h> + +MetisListenerOps *metisIPMulticastListener_CreateInet6(MetisForwarder *metis, struct sockaddr_in6 sin6); +MetisListenerOps *metisIPMulticastListener_CreateInet(MetisForwarder *metis, struct sockaddr_in sin); +#endif // Metis_metis_IPMulticastListener_h diff --git a/metis/ccnx/forwarder/metis/io/metis_IoOperations.c b/metis/ccnx/forwarder/metis/io/metis_IoOperations.c new file mode 100644 index 00000000..03feb737 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_IoOperations.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 <ccnx/forwarder/metis/io/metis_IoOperations.h> + +#include <LongBow/runtime.h> + +void * +metisIoOperations_GetClosure(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter ops must be non-null"); + return ops->closure; +} + +bool +metisIoOperations_Send(MetisIoOperations *ops, const CPIAddress *nexthop, MetisMessage *message) +{ + return ops->send(ops, nexthop, message); +} + +const CPIAddress * +metisIoOperations_GetRemoteAddress(const MetisIoOperations *ops) +{ + return ops->getRemoteAddress(ops); +} + +const MetisAddressPair * +metisIoOperations_GetAddressPair(const MetisIoOperations *ops) +{ + return ops->getAddressPair(ops); +} + +bool +metisIoOperations_IsUp(const MetisIoOperations *ops) +{ + return ops->isUp(ops); +} + +bool +metisIoOperations_IsLocal(const MetisIoOperations *ops) +{ + return ops->isLocal(ops); +} + +unsigned +metisIoOperations_GetConnectionId(const MetisIoOperations *ops) +{ + return ops->getConnectionId(ops); +} + +void +metisIoOperations_Release(MetisIoOperations **opsPtr) +{ + MetisIoOperations *ops = *opsPtr; + ops->destroy(opsPtr); +} + +const void * +metisIoOperations_Class(const MetisIoOperations *ops) +{ + return ops->class(ops); +} + +CPIConnectionType +metisIoOperations_GetConnectionType(const MetisIoOperations *ops) +{ + return ops->getConnectionType(ops); +} + +MetisTicks +metisIoOperations_SendProbe(MetisIoOperations *ops, unsigned probeType) +{ + return ops->sendProbe(ops, probeType); +} + diff --git a/metis/ccnx/forwarder/metis/io/metis_IoOperations.h b/metis/ccnx/forwarder/metis/io/metis_IoOperations.h new file mode 100644 index 00000000..86129865 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_IoOperations.h @@ -0,0 +1,369 @@ +/* + * 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. + */ + +/** + * Defines the interface all connections use to communicate with the forwarder. + * + * @code + * + * static MetisIoOperations _template = { + * .closure = NULL, + * .send = &_metisEtherConnection_Send, + * .getRemoteAddress = &_metisEtherConnection_GetRemoteAddress, + * .getAddressPair = &_metisEtherConnection_GetAddressPair, + * .getConnectionId = &_metisEtherConnection_GetConnectionId, + * .isUp = &_metisEtherConnection_IsUp, + * .isLocal = &_metisEtherConnection_IsLocal, + * .destroy = &_metisEtherConnection_DestroyOperations, + * .class = &_metisEtherConnection_Class, + * .getConnectionType = &_metisEtherConnection_getConnectionType + * }; + * + * MetisIoOperations * + * metisEtherConnection_Create(MetisForwarder *metis, MetisGenericEther *ether, MetisAddressPair *pair) + * { + * _MetisEtherState *etherConnState = parcMemory_Allocate(sizeof(_MetisEtherState)); + * // Fill in etherConnState with instance variables + * + * MetisIoOperations *io_ops = parcMemory_Allocate(sizeof(MetisIoOperations)); + * memcpy(io_ops, &_template, sizeof(MetisIoOperations)); + * io_ops->closure = etherConnState; + * // Add to connection table, send missives about connection state + * + * return op_ops; + * } + * @endcode + * + */ + +/** + * I/O is built around a callback structure. The connection table contains an operations + * structure built around function pointers. These allow the connection table to be + * agnostic about underlying connections. + */ + +#ifndef Metis_metis_io_h +#define Metis_metis_io_h + +#include <ccnx/api/control/cpi_Connection.h> +#include <ccnx/api/control/cpi_Address.h> +#include <ccnx/forwarder/metis/io/metis_AddressPair.h> +#include <ccnx/forwarder/metis/core/metis_Message.h> +#include <ccnx/forwarder/metis/core/metis_Ticks.h> + +//packet types for probing +#define METIS_PACKET_TYPE_PROBE_REQUEST 5 +#define METIS_PACKET_TYPE_PROBE_REPLY 6 + +struct metis_io_ops; +typedef struct metis_io_ops MetisIoOperations; + +/** + * @typedef MetisIoOperations + * @abstract The IO Operations structure abstracts an connection's properties and send() method + * @constant context Implementation specific opaque data, passed back on each call + * @constant send function pointer to send a message, does not destroy the message + * @constant getRemoteAddress function pointer to return the "to" address associated with the connection. + * Some connections might not have a specific peer, such as multicast, where its the group address. + * @constant isUp test if the connection is up, ready to send a message. + * @constant isLocal test if the connection is local to the host. + * @constant getConnectionId returns the Metis id for the connection. + * @constant destroy releases a refernce count on the connection and possibly destroys the connection. + * @constant class A unique identifier for each class that instantiates MetisIoOperations. + * @constant getConnectionType Returns the type of connection (TCP, UDP, L2, etc.) of the underlying connection. + * @discussion <#Discussion#> + */ +struct metis_io_ops { + void *closure; + bool (*send)(MetisIoOperations *ops, const CPIAddress *nexthop, MetisMessage *message); + const CPIAddress * (*getRemoteAddress)(const MetisIoOperations *ops); + const MetisAddressPair * (*getAddressPair)(const MetisIoOperations *ops); + bool (*isUp)(const MetisIoOperations *ops); + bool (*isLocal)(const MetisIoOperations *ops); + unsigned (*getConnectionId)(const MetisIoOperations *ops); + void (*destroy)(MetisIoOperations **opsPtr); + const void * (*class)(const MetisIoOperations *ops); + CPIConnectionType (*getConnectionType)(const MetisIoOperations *ops); + MetisTicks (*sendProbe)(MetisIoOperations *ops, unsigned probeType); +}; + +/** + * Returns the closure of the interface + * + * The creator of the closure sets this parameter to store its state. + * + * @param [in] ops A concrete instance of the interface + * + * @return The value set by the concrete instance of the interface. + * + * Example: + * @clode + * { + + * } + * @endcode + */ +void *metisIoOperations_GetClosure(const MetisIoOperations *ops); + +/** + * Release all memory related to the interface and implementation + * + * This function must release all referenced memory in the concrete implementation and + * memory related to the MetisIoOperations. It should NULL the input parameter. + * + * @param [in,out] opsPtr Pointer to interface. Will be NULLed. + * + * Example: + * @code + * + * static void + * _metisEtherConnection_InternalRelease(_MetisEtherState *etherConnState) + * { + * // release internal state of _MetisEtherState + * } + * + * static void + * _metisEtherConnection_Release(MetisIoOperations **opsPtr) + * { + * MetisIoOperations *ops = *opsPtr; + * + * _MetisEtherState *etherConnState = (_MetisEtherState *) metisIoOperations_GetClosure(ops); + * _metisEtherConnection_InternalRelease(etherConnState); + * + * parcMemory_Deallocate((void **) &ops); + * } + * + * MetisIoOperations * + * metisEtherConnection_Create(MetisForwarder *metis, MetisGenericEther *ether, MetisAddressPair *pair) + * { + * size_t allocationSize = sizeof(_MetisEtherState) + sizeof(MetisIoOperations); + * MetisIoOperations *ops = parcMemory_AllocateAndClear(allocationSize); + * if (ops) { + * // fill in other interface functions + * ops->destroy = &_metisEtherConnection_Release; + * ops->closure = (uint8_t *) ops + sizeof(MetisIoOperations); + * + * _MetisEtherState *etherConnState = metisIoOperations_GetClosure(ops); + * // fill in Ethernet state + * } + * return ops; + * } + * @endcode + */ +void metisIoOperations_Release(MetisIoOperations **opsPtr); + +/** + * Sends the specified MetisMessage out this connection + * + * The the implementation of send may queue the message, it must acquire a reference to it. + * + * @param [in] ops The connection implementation. + * @param [in] nexthop On multiple access networks, this parameter might be used, usually NULL. + * @param [in] message The message to send. If the message will be queued, it will be acquired. + * + * @return true The message was sent or queued + * @retrun false An error occured and the message will not be sent or queued + * + * Example: + * @code + * { + * if (metisIoOperations_IsUp(conn->ops)) { + * return metisIoOperations_Send(conn->ops, NULL, message); + * } + * } + * @endcode + */ +bool metisIoOperations_Send(MetisIoOperations *ops, const CPIAddress *nexthop, MetisMessage *message); + +/** + * A connection is made up of a local and a remote address. This function returns the remote address. + * + * Represents the destination endpoint of the communication. + * + * @param [in] ops The connection implementation. + * + * @return non-null The remote address + * @return null The connection does not have a remote address + * + * Example: + * @code + * { + * CPIAddress *local = cpiAddress_CreateFromLink((uint8_t []) { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }, 6); + * CPIAddress *remote = cpiAddress_CreateFromLink((uint8_t []) { 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }, 6); + * MetisAddressPair *pair = metisAddressPair_Create(local, remote); + * MetisIoOperations *ops = metisEtherConnection_Create(metis, ether, pair); + * + * const CPIAddress *test = metisIoOperations_GetRemoteAddress(ops); + * assertTrue(cpiAddress_Equals(test, remote), "Wrong remote address"); + * metisIoOperations_Release(&ops); + * metisAddressPair_Release(&pair); + * cpiAddress_Destroy(&local); + * cpiAddress_Destroy(&remote); + * } + * @endcode + */ +const CPIAddress *metisIoOperations_GetRemoteAddress(const MetisIoOperations *ops); + +/** + * A connection is made up of a local and a remote address. This function returns the address pair. + * + * Represents the destination endpoint of the communication. + * + * @param [in] ops The connection implementation. + * + * @return non-null The address pair + * @return null An error. + * + * Example: + * @code + * { + * CPIAddress *local = cpiAddress_CreateFromLink((uint8_t []) { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }, 6); + * CPIAddress *remote = cpiAddress_CreateFromLink((uint8_t []) { 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }, 6); + * MetisAddressPair *pair = metisAddressPair_Create(local, remote); + * MetisIoOperations *ops = metisEtherConnection_Create(metis, ether, pair); + * + * const MetisAddressPair *test = metisIoOperations_GetAddressPair(ops); + * assertTrue(metisAddressPair(test, pair), "Wrong address pair"); + * metisIoOperations_Release(&ops); + * metisAddressPair_Release(&pair); + * cpiAddress_Destroy(&local); + * cpiAddress_Destroy(&remote); + * } + * @endcode + */ +const MetisAddressPair *metisIoOperations_GetAddressPair(const MetisIoOperations *ops); + +/** + * Returns true if the underlying connection is in operation + * + * An UP connection is able to send and receive packets. If a subsystem needs to take actions + * when a connection goes UP or DOWN, it should subscribe as a MetisMissive listener. + * + * @param [in] ops The connection implementation. + * + * @return true The connection is UP + * @return false The connection is not UP + * + * Example: + * @code + * { + * if (metisIoOperations_IsUp(conn->ops)) { + * return metisIoOperations_Send(conn->ops, NULL, message); + * } + * } + * @endcode + */ +bool metisIoOperations_IsUp(const MetisIoOperations *ops); + +/** + * If the remote address is local to this system, returns true + * + * Will return true if an INET or INET6 connection is on localhost. Will return + * true for AF_UNIX. An Ethernet connection is not local. + * + * @param [in] ops The connection implementation. + * + * @return true The remote address is local to the system + * @return false The remote address is not local + * + * Example: + * @code + * { + * // Is the ingress connection remote? If so check for non-zero and decrement + * if (!metisIoOperations(ingressConnectionOps) { + * uint8_t hoplimit = metisMessage_GetHopLimit(interestMessage); + * if (hoplimit == 0) { + * // error + * } else { + * hoplimit--; + * } + * // take actions on hoplimit + * } + * } + * @endcode + */ +bool metisIoOperations_IsLocal(const MetisIoOperations *ops); + +/** + * Returns the connection ID represented by this MetisIoOperations in the ConnectionTable. + * + * <#Paragraphs Of Explanation#> + * + * @param [in] ops The connection implementation. + * + * @return number The connection ID in the connection table. + * + * Example: + * @code + * { + * unsigned id = metisIoOperations_GetConnectionId(ingressIoOps); + * const MetisConnection *conn = metisConnectionTable_FindById(metis->connectionTable, id); + * } + * @endcode + */ +unsigned metisIoOperations_GetConnectionId(const MetisIoOperations *ops); + +/** + * A pointer that represents the class of the connection + * + * Each concrete implementation has a class pointer that is unique to the implementation (not instance). + * Each implementation is free to choose how to determine the value, so long as it is unique on the system. + * This is a system-local value. + * + * @param [in] ops The connection implementation. + * + * @return non-null A pointer value unique to the implementation (not instance). + * + * Example: + * @code + * bool + * metisEtherConnection_IsInstanceOf(const MetisConnection *conn) + * { + * bool result = false; + * if (conn != NULL) { + * MetisIoOperations *ops = metisConnection_GetIoOperations(conn); + * const void *class = metisIoOperations_Class(ops); + * result = (class == _metisEtherConnection_Class(ops)); + * } + * return result; + * } + * @endcode + */ +const void *metisIoOperations_Class(const MetisIoOperations *ops); + +/** + * Returns the transport type of the connection (TCP, UDP, L2, etc.). + * + * TCP and AF_UNIX are both stream connections and will both return "cpiConnection_TCP". + * Ethernet will return "cpiConnection_L2". + * + * @param [in] ops The connection implementation. + * + * @return cpiConnection_TCP A TCP4, TCP6, or AF_UNIX connection + * @return cpiConnection_UDP A UDP4 or UDP6 connection + * @return cpiConnection_L2 An Ethernet connection + * + * Example: + * @code + * { + * CPIConnectionType type = metisIoOperations_GetConnectionType(metisConnection_GetIoOperations(connection)); + * CPIConnection *cpiConn = cpiConnection_Create(metisConnection_GetConnectionId(connection), localAddress, remoteAddress, type); + * } + * @endcode + */ +CPIConnectionType metisIoOperations_GetConnectionType(const MetisIoOperations *ops); + +MetisTicks metisIoOperations_SendProbe(MetisIoOperations *ops, unsigned probeType); +#endif // Metis_metis_io_h diff --git a/metis/ccnx/forwarder/metis/io/metis_Listener.h b/metis/ccnx/forwarder/metis/io/metis_Listener.h new file mode 100644 index 00000000..056f76e6 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_Listener.h @@ -0,0 +1,103 @@ +/* + * 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_Listener.h + * @brief Provides the function abstraction of all Listeners. + * + * A listener accepts in coming packets. A Stream listener will accept the connection + * then pass it off to the {@link MetisStreamConnection} class. A datagram listener + * will have to have its own way to multiplex packets. + * + */ + +#ifndef Metis_metis_Listener_h +#define Metis_metis_Listener_h + +#include <ccnx/api/control/cpi_Address.h> + +struct metis_listener_ops; +typedef struct metis_listener_ops MetisListenerOps; + +typedef enum { + METIS_ENCAP_TCP, /**< TCP encapsulation type */ + METIS_ENCAP_UDP, /**< UDP encapsulation type */ + METIS_ENCAP_ETHER, /**< Ethernet encapsulation type */ + METIS_ENCAP_LOCAL /**< A connection to a local protocol stack */ +} MetisEncapType; + +struct metis_listener_ops { + /** + * A user-defined parameter + */ + void *context; + + /** + * Called to destroy the Listener. + * + * @param [in] listenerOpsPtr Double pointer to this structure + */ + void (*destroy)(MetisListenerOps **listenerOpsPtr); + + /** + * Returns the interface index of the listener. + * + * @param [in] ops Pointer to this structure + * + * @return the interface index of the listener + */ + unsigned (*getInterfaceIndex)(const MetisListenerOps *ops); + + /** + * Returns the address pair that defines the listener (local, remote) + * + * @param [in] ops Pointer to this structure + * + * @return the (local, remote) pair of addresses + */ + const CPIAddress * (*getListenAddress)(const MetisListenerOps *ops); + + /** + * Returns the encapsulation type of the listener (e.g. TCP, UDP, Ethernet) + * + * @param [in] ops Pointer to this structure + * + * @return the listener encapsulation type + */ + MetisEncapType (*getEncapType)(const MetisListenerOps *ops); + + /** + * Returns the underlying socket associated with the listener + * + * Not all listeners are capable of returning a useful socket. In those + * cases, this function pointer is NULL. + * + * TCP does not support this operation (function is NULL). UDP returns its local socket. + * + * The caller should never close this socket, the listener will do that when its + * destroy method is called. + * + * @param [in] ops Pointer to this structure + * + * @retval integer The socket descriptor + * + * Example: + * @code + * <#example#> + * @endcode + */ + int (*getSocket)(const MetisListenerOps *ops); +}; +#endif // Metis_metis_Listener_h diff --git a/metis/ccnx/forwarder/metis/io/metis_ListenerSet.c b/metis/ccnx/forwarder/metis/io/metis_ListenerSet.c new file mode 100644 index 00000000..0a0d32fd --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_ListenerSet.c @@ -0,0 +1,146 @@ +/* + * 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_ArrayList.h> + +#include <ccnx/forwarder/metis/io/metis_ListenerSet.h> + +#include <LongBow/runtime.h> + +struct metis_listener_set { + PARCArrayList *listOfListeners; +}; + +static void +metisListenerSet_DestroyListenerOps(void **opsPtr) +{ + MetisListenerOps *ops = *((MetisListenerOps **) opsPtr); + ops->destroy(&ops); +} + +MetisListenerSet * +metisListenerSet_Create() +{ + MetisListenerSet *set = parcMemory_AllocateAndClear(sizeof(MetisListenerSet)); + assertNotNull(set, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisListenerSet)); + set->listOfListeners = parcArrayList_Create(metisListenerSet_DestroyListenerOps); + + return set; +} + +void +metisListenerSet_Destroy(MetisListenerSet **setPtr) +{ + assertNotNull(setPtr, "Parameter must be non-null double pointer"); + assertNotNull(*setPtr, "Parameter must dereference to non-null pointer"); + + MetisListenerSet *set = *setPtr; + parcArrayList_Destroy(&set->listOfListeners); + parcMemory_Deallocate((void **) &set); + *setPtr = NULL; +} + +/** + * @function metisListenerSet_Add + * @abstract Adds the listener to the set + * @discussion + * Unique set based on pair (MetisEncapType, localAddress) + * + * @param <#param1#> + * @return <#return#> + */ +bool +metisListenerSet_Add(MetisListenerSet *set, MetisListenerOps *ops) +{ + assertNotNull(set, "Parameter set must be non-null"); + assertNotNull(ops, "Parameter ops must be non-null"); + + int opsEncap = ops->getEncapType(ops); + const CPIAddress *opsAddress = ops->getListenAddress(ops); + + // make sure its not in the set + size_t length = parcArrayList_Size(set->listOfListeners); + for (size_t i = 0; i < length; i++) { + MetisListenerOps *entry = parcArrayList_Get(set->listOfListeners, i); + + int entryEncap = entry->getEncapType(entry); + const CPIAddress *entryAddress = entry->getListenAddress(entry); + + if (opsEncap == entryEncap && cpiAddress_Equals(opsAddress, entryAddress)) { + // duplicate + return false; + } + } + + parcArrayList_Add(set->listOfListeners, ops); + return true; +} + +size_t +metisListenerSet_Length(const MetisListenerSet *set) +{ + assertNotNull(set, "Parameter set must be non-null"); + return parcArrayList_Size(set->listOfListeners); +} + +/** + * Returns the listener at the given index + * + * <#Paragraphs Of Explanation#> + * + * @param [in] set An allocated listener set + * @param [in] index The index position (0 <= index < metisListenerSet_Count) + * + * @retval non-null The listener at index + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisListenerOps * +metisListenerSet_Get(const MetisListenerSet *set, size_t index) +{ + assertNotNull(set, "Parameter set must be non-null"); + return parcArrayList_Get(set->listOfListeners, index); +} + +MetisListenerOps * +metisListenerSet_Find(const MetisListenerSet *set, MetisEncapType encapType, const CPIAddress *localAddress) +{ + assertNotNull(set, "Parameter set must be non-null"); + assertNotNull(localAddress, "Parameter localAddress must be non-null"); + + MetisListenerOps *match = NULL; + + for (size_t i = 0; i < parcArrayList_Size(set->listOfListeners) && !match; i++) { + MetisListenerOps *ops = parcArrayList_Get(set->listOfListeners, i); + assertNotNull(ops, "Got null listener ops at index %zu", i); + + if (ops->getEncapType(ops) == encapType) { + if (cpiAddress_Equals(localAddress, ops->getListenAddress(ops))) { + match = ops; + } + } + } + + return match; +} diff --git a/metis/ccnx/forwarder/metis/io/metis_ListenerSet.h b/metis/ccnx/forwarder/metis/io/metis_ListenerSet.h new file mode 100644 index 00000000..dad876dd --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_ListenerSet.h @@ -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. + */ + +/** + * @file metis_ListenerSet.h + * @brief A listener set is unique on (MetisEncapType, localAddress) + * + * Keeps track of all the running listeners. The set is unique on the + * encapsulation type and the local address. For example, with TCP encapsulation and + * local address 127.0.0.1 or Ethernet encapsulation and MAC address 00:11:22:33:44:55. + * + * NOTE: This does not allow multiple EtherType on the same interface because the CPIAddress for + * a LINK address does not include an EtherType. + * + */ + +#ifndef Metis_metis_ListenerSet_h +#define Metis_metis_ListenerSet_h + +#include <ccnx/forwarder/metis/io/metis_Listener.h> + +struct metis_listener_set; +typedef struct metis_listener_set MetisListenerSet; + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisListenerSet *metisListenerSet_Create(void); + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisListenerSet_Destroy(MetisListenerSet **setPtr); + +/** + * @function metisListenerSet_Add + * @abstract Adds the listener to the set + * @discussion + * Unique set based on pair (MetisEncapType, localAddress). + * Takes ownership of the ops memory if added. + * + * @param <#param1#> + * @return true if added, false if not + */ +bool metisListenerSet_Add(MetisListenerSet *set, MetisListenerOps *ops); + +/** + * The number of listeners in the set + * + * <#Paragraphs Of Explanation#> + * + * @param [in] set An allocated listener set + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +size_t metisListenerSet_Length(const MetisListenerSet *set); + +/** + * Returns the listener at the given index + * + * <#Paragraphs Of Explanation#> + * + * @param [in] set An allocated listener set + * @param [in] index The index position (0 <= index < metisListenerSet_Lenght) + * + * @retval non-null The listener at index + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisListenerOps *metisListenerSet_Get(const MetisListenerSet *set, size_t index); + +/** + * Looks up a listener by its key (EncapType, LocalAddress) + * + * <#Paragraphs Of Explanation#> + * + * @param [in] set An allocated listener set + * @param [in] encapType the listener type + * @param [in] localAddress The local bind address (e.g. MAC address or TCP socket) + * + * @retval non-null The listener matching the query + * @retval null Does not exist + * + * Example: + * @code + * + * @endcode + */ +MetisListenerOps *metisListenerSet_Find(const MetisListenerSet *set, MetisEncapType encapType, const CPIAddress *localAddress); +#endif diff --git a/metis/ccnx/forwarder/metis/io/metis_LocalListener.c b/metis/ccnx/forwarder/metis/io/metis_LocalListener.c new file mode 100644 index 00000000..6e188966 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_LocalListener.c @@ -0,0 +1,189 @@ +/* + * 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. + */ + +/** + * Implements a listener that works with stream connections over a named pipe. + * + */ + +#include <config.h> +#include <stdio.h> +#include <stdbool.h> +#include <string.h> +#include <sys/un.h> +#include <errno.h> +#include <unistd.h> + +#include <ccnx/forwarder/metis/core/metis_ConnectionTable.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/io/metis_Listener.h> +#include <ccnx/forwarder/metis/io/metis_LocalListener.h> +#include <ccnx/forwarder/metis/io/metis_StreamConnection.h> + +#include <LongBow/runtime.h> +#include <parc/algol/parc_Memory.h> + +struct metis_local_listener { + MetisForwarder *metis; + MetisLogger *logger; + PARCEventSocket *listener; + CPIAddress *localAddress; + unsigned id; +}; + +static void _metisLocalListener_OpsDestroy(MetisListenerOps **listenerOpsPtr); +static unsigned _metisLocalListener_OpsGetInterfaceIndex(const MetisListenerOps *ops); +static const CPIAddress *_metisLocalListener_OpsGetListenAddress(const MetisListenerOps *ops); +static MetisEncapType _metisLocalListener_OpsGetEncapType(const MetisListenerOps *ops); + +static MetisListenerOps localTemplate = { + .context = NULL, + .destroy = &_metisLocalListener_OpsDestroy, + .getInterfaceIndex = &_metisLocalListener_OpsGetInterfaceIndex, + .getListenAddress = &_metisLocalListener_OpsGetListenAddress, + .getEncapType = &_metisLocalListener_OpsGetEncapType, +}; + +// STREAM daemon listener callback +static void metisListenerLocal_Listen(int, struct sockaddr *, int socklen, void *localVoid); + +MetisListenerOps * +metisLocalListener_Create(MetisForwarder *metis, const char *path) +{ + MetisLocalListener *local = parcMemory_AllocateAndClear(sizeof(MetisLocalListener)); + assertNotNull(local, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisLocalListener)); + local->metis = metis; + local->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + + struct sockaddr_un addr_unix; + memset(&addr_unix, 0, sizeof(addr_unix)); + + addr_unix.sun_family = PF_UNIX; + strcpy(addr_unix.sun_path, path); + + unlink(path); + + local->listener = metisDispatcher_CreateListener(metisForwarder_GetDispatcher(metis), metisListenerLocal_Listen, + (void *) local, -1, (struct sockaddr*) &addr_unix, sizeof(addr_unix)); + + assertNotNull(local->listener, "Got null listener from metisDispatcher_CreateListener: (%d) %s", errno, strerror(errno)); + + struct sockaddr_un addr_un; + memset(&addr_un, 0, sizeof(addr_un)); + addr_un.sun_family = AF_UNIX; + strcpy(addr_un.sun_path, path); + + local->localAddress = cpiAddress_CreateFromUnix(&addr_un); + local->id = metisForwarder_GetNextConnectionId(metis); + + MetisListenerOps *ops = parcMemory_AllocateAndClear(sizeof(MetisListenerOps)); + assertNotNull(ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisListenerOps)); + memcpy(ops, &localTemplate, sizeof(MetisListenerOps)); + ops->context = local; + + return ops; +} + +void +metisLocalListener_Destroy(MetisLocalListener **listenerPtr) +{ + assertNotNull(listenerPtr, "Parameter must be non-null double pointer"); + assertNotNull(*listenerPtr, "Parameter must dereference to non-null pointer"); + + MetisLocalListener *local = *listenerPtr; + + metisLogger_Release(&local->logger); + + cpiAddress_Destroy(&local->localAddress); + metisDispatcher_DestroyListener(metisForwarder_GetDispatcher(local->metis), &local->listener); + + parcMemory_Deallocate((void **) &local); + *listenerPtr = NULL; +} + +// ================================================== + +/** + * @function metisListenerLocal_Listen + * @abstract Called when a client connects to the server socket + * @discussion + * Accepts a client connection. Creates a new Stream connection and adds it + * to the connection table. + * + * @param fd the remote client socket (it will be AF_UNIX type) + * @param sa the remote client address + * @param socklen the bytes of sa + * @param localVoid a void point to the MetisLocalListener that owns the server socket + */ +static void +metisListenerLocal_Listen(int fd, + struct sockaddr *sa, int socklen, void *localVoid) +{ + MetisLocalListener *local = (MetisLocalListener *) localVoid; + assertTrue(sa->sa_family == AF_UNIX, "Got wrong address family, expected %d got %d", AF_UNIX, sa->sa_family); + + CPIAddress *remote = cpiAddress_CreateFromUnix((struct sockaddr_un *) sa); + MetisAddressPair *pair = metisAddressPair_Create(local->localAddress, remote); + + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(local->metis, fd, pair, true); + MetisConnection *conn = metisConnection_Create(ops); + + metisConnectionTable_Add(metisForwarder_GetConnectionTable(local->metis), conn); + + if (metisLogger_IsLoggable(local->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + char *str = metisAddressPair_ToString(pair); + metisLogger_Log(local->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Listener %p started on address pair %s", (void *) local, str); + free(str); + } + + cpiAddress_Destroy(&remote); +} + +static void +_metisLocalListener_OpsDestroy(MetisListenerOps **listenerOpsPtr) +{ + MetisListenerOps *ops = *listenerOpsPtr; + MetisLocalListener *local = (MetisLocalListener *) ops->context; + + if (metisLogger_IsLoggable(local->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(local->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Listener %p destroyed", (void *) local); + } + + metisLocalListener_Destroy(&local); + parcMemory_Deallocate((void **) &ops); + *listenerOpsPtr = NULL; +} + +static unsigned +_metisLocalListener_OpsGetInterfaceIndex(const MetisListenerOps *ops) +{ + MetisLocalListener *local = (MetisLocalListener *) ops->context; + return local->id; +} + +static const CPIAddress * +_metisLocalListener_OpsGetListenAddress(const MetisListenerOps *ops) +{ + MetisLocalListener *local = (MetisLocalListener *) ops->context; + return local->localAddress; +} + +static MetisEncapType +_metisLocalListener_OpsGetEncapType(const MetisListenerOps *ops) +{ + return METIS_ENCAP_LOCAL; +} diff --git a/metis/ccnx/forwarder/metis/io/metis_LocalListener.h b/metis/ccnx/forwarder/metis/io/metis_LocalListener.h new file mode 100644 index 00000000..1e497a18 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_LocalListener.h @@ -0,0 +1,29 @@ +/* + * 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_ListenerLocal_h +#define Metis_metis_ListenerLocal_h + +#include <stdlib.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +struct metis_local_listener; +typedef struct metis_local_listener MetisLocalListener; + +MetisListenerOps *metisLocalListener_Create(MetisForwarder *metis, const char *path); +void metisLocalListener_Destroy(MetisLocalListener **listenerPtr); +#endif // Metis_metis_ListenerLocal_h diff --git a/metis/ccnx/forwarder/metis/io/metis_StreamConnection.c b/metis/ccnx/forwarder/metis/io/metis_StreamConnection.c new file mode 100644 index 00000000..225e6d0d --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_StreamConnection.c @@ -0,0 +1,570 @@ +/* + * 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. + */ + +/** + * Common activity for STREAM based listeners. + */ + +#include <config.h> +#include <stdio.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> + +#include <ccnx/forwarder/metis/io/metis_StreamConnection.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_Connection.h> +#include <ccnx/forwarder/metis/core/metis_Message.h> +#include <parc/algol/parc_Hash.h> + +#include <LongBow/runtime.h> +#include <parc/algol/parc_Memory.h> +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> + +// 128 KB output queue +#define OUTPUT_QUEUE_BYTES (128 * 1024) + +static void +_conn_readcb(PARCEventQueue *bufferEventVector, PARCEventType type, void *ioOpsVoid); + +static void +_conn_eventcb(PARCEventQueue *bufferEventVector, PARCEventQueueEventType events, void *ioOpsVoid); + +typedef struct metis_stream_state { + MetisForwarder *metis; + MetisLogger *logger; + + int fd; + + MetisAddressPair *addressPair; + PARCEventQueue *bufferEventVector; + + bool isLocal; + bool isUp; + bool isClosed; + unsigned id; + + size_t nextMessageLength; +} _MetisStreamState; + +// Prototypes +static bool _metisStreamConnection_Send(MetisIoOperations *ops, const CPIAddress *nexthop, MetisMessage *message); +static const CPIAddress *_metisStreamConnection_GetRemoteAddress(const MetisIoOperations *ops); +static const MetisAddressPair *_metisStreamConnection_GetAddressPair(const MetisIoOperations *ops); +static unsigned _metisStreamConnection_GetConnectionId(const MetisIoOperations *ops); +static bool _metisStreamConnection_IsUp(const MetisIoOperations *ops); +static bool _metisStreamConnection_IsLocal(const MetisIoOperations *ops); +static void _metisStreamConnection_DestroyOperations(MetisIoOperations **opsPtr); + +static void _setConnectionState(_MetisStreamState *stream, bool isUp); +static CPIConnectionType _metisStreamConnection_GetConnectionType(const MetisIoOperations *ops); +static MetisTicks _sendProbe(MetisIoOperations *ops, unsigned probeType); + +/* + * This assigns a unique pointer to the void * which we use + * as a GUID for this class. + */ +static const void *_metisIoOperationsGuid = __FILE__; + +/* + * Return our GUID + */ +static const void * +_metisStreamConnection_Class(const MetisIoOperations *ops) +{ + return _metisIoOperationsGuid; +} + +static MetisIoOperations _template = { + .closure = NULL, + .send = &_metisStreamConnection_Send, + .getRemoteAddress = &_metisStreamConnection_GetRemoteAddress, + .getAddressPair = &_metisStreamConnection_GetAddressPair, + .getConnectionId = &_metisStreamConnection_GetConnectionId, + .isUp = &_metisStreamConnection_IsUp, + .isLocal = &_metisStreamConnection_IsLocal, + .destroy = &_metisStreamConnection_DestroyOperations, + .class = &_metisStreamConnection_Class, + .getConnectionType = &_metisStreamConnection_GetConnectionType, + .sendProbe = &_sendProbe, +}; + +MetisIoOperations * +metisStreamConnection_AcceptConnection(MetisForwarder *metis, int fd, MetisAddressPair *pair, bool isLocal) +{ + _MetisStreamState *stream = parcMemory_AllocateAndClear(sizeof(_MetisStreamState)); + assertNotNull(stream, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisStreamState)); + + MetisDispatcher *dispatcher = metisForwarder_GetDispatcher(metis); + PARCEventScheduler *eventBase = metisDispatcher_GetEventScheduler(dispatcher); + stream->bufferEventVector = parcEventQueue_Create(eventBase, fd, PARCEventQueueOption_CloseOnFree | PARCEventQueueOption_DeferCallbacks); + + stream->metis = metis; + stream->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + stream->fd = fd; + stream->id = metisForwarder_GetNextConnectionId(metis); + stream->addressPair = pair; + stream->isClosed = false; + + // allocate a connection + MetisIoOperations *io_ops = parcMemory_AllocateAndClear(sizeof(MetisIoOperations)); + assertNotNull(io_ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisIoOperations)); + memcpy(io_ops, &_template, sizeof(MetisIoOperations)); + io_ops->closure = stream; + stream->isLocal = isLocal; + + parcEventQueue_SetCallbacks(stream->bufferEventVector, _conn_readcb, NULL, _conn_eventcb, (void *) io_ops); + parcEventQueue_Enable(stream->bufferEventVector, PARCEventType_Read); + + metisMessenger_Send(metisForwarder_GetMessenger(stream->metis), metisMissive_Create(MetisMissiveType_ConnectionCreate, stream->id)); + + // As we are acceting a connection, we begin in the UP state + _setConnectionState(stream, true); + + if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + char *pair_str = metisAddressPair_ToString(pair); + metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "StreamConnection %p accept for address pair %s", (void *) stream, pair_str); + free(pair_str); + } + + return io_ops; +} + +MetisIoOperations * +metisStreamConnection_OpenConnection(MetisForwarder *metis, MetisAddressPair *pair, bool isLocal) +{ + assertNotNull(metis, "Parameter metis must be non-null"); + assertNotNull(pair, "Parameter pair must be non-null"); + + // if there's an error on the bind or connect, will return NULL + PARCEventQueue *bufferEventVector = metisDispatcher_StreamBufferConnect(metisForwarder_GetDispatcher(metis), pair); + if (bufferEventVector == NULL) { + // error opening connection + return NULL; + } + + _MetisStreamState *stream = parcMemory_AllocateAndClear(sizeof(_MetisStreamState)); + assertNotNull(stream, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisStreamState)); + + stream->metis = metis; + stream->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + stream->fd = parcEventQueue_GetFileDescriptor(bufferEventVector); + stream->bufferEventVector = bufferEventVector; + stream->id = metisForwarder_GetNextConnectionId(metis); + stream->addressPair = pair; + stream->isClosed = false; + + // allocate a connection + MetisIoOperations *io_ops = parcMemory_AllocateAndClear(sizeof(MetisIoOperations)); + assertNotNull(io_ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisIoOperations)); + memcpy(io_ops, &_template, sizeof(MetisIoOperations)); + io_ops->closure = stream; + stream->isLocal = isLocal; + + parcEventQueue_SetCallbacks(stream->bufferEventVector, _conn_readcb, NULL, _conn_eventcb, (void *) io_ops); + parcEventQueue_Enable(stream->bufferEventVector, PARCEventType_Read); + + // we start in DOWN state, until remote side answers + metisMessenger_Send(metisForwarder_GetMessenger(stream->metis), metisMissive_Create(MetisMissiveType_ConnectionCreate, stream->id)); + _setConnectionState(stream, false); + + if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Info)) { + char *pair_str = metisAddressPair_ToString(pair); + metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__, + "StreamConnection %p connect for address pair %s", (void *) stream, pair_str); + free(pair_str); + } + + return io_ops; +} + +static void +_metisStreamConnection_DestroyOperations(MetisIoOperations **opsPtr) +{ + assertNotNull(opsPtr, "Parameter opsPtr must be non-null double pointer"); + assertNotNull(*opsPtr, "Parameter opsPtr must dereference to non-null pointer"); + + MetisIoOperations *ops = *opsPtr; + assertNotNull(metisIoOperations_GetClosure(ops), "ops->context must not be null"); + + _MetisStreamState *stream = (_MetisStreamState *) metisIoOperations_GetClosure(ops); + + parcEventQueue_Destroy(&stream->bufferEventVector); + + metisAddressPair_Release(&stream->addressPair); + + if (!stream->isClosed) { + stream->isClosed = true; + metisMessenger_Send(metisForwarder_GetMessenger(stream->metis), metisMissive_Create(MetisMissiveType_ConnectionClosed, stream->id)); + } + + metisMessenger_Send(metisForwarder_GetMessenger(stream->metis), metisMissive_Create(MetisMissiveType_ConnectionDestroyed, stream->id)); + + if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Info)) { + metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__, + "StreamConnection %p destroyed", (void *) stream); + } + + metisLogger_Release(&stream->logger); + parcMemory_Deallocate((void **) &stream); + parcMemory_Deallocate((void **) &ops); + + *opsPtr = NULL; +} + +static bool +_metisStreamConnection_IsUp(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisStreamState *stream = (const _MetisStreamState *) metisIoOperations_GetClosure(ops); + return stream->isUp; +} + +static bool +_metisStreamConnection_IsLocal(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisStreamState *stream = (const _MetisStreamState *) metisIoOperations_GetClosure(ops); + return stream->isLocal; +} + +static const CPIAddress * +_metisStreamConnection_GetRemoteAddress(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisStreamState *stream = (const _MetisStreamState *) metisIoOperations_GetClosure(ops); + return metisAddressPair_GetRemote(stream->addressPair); +} + +static const MetisAddressPair * +_metisStreamConnection_GetAddressPair(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisStreamState *stream = (const _MetisStreamState *) metisIoOperations_GetClosure(ops); + return stream->addressPair; +} + +static unsigned +_metisStreamConnection_GetConnectionId(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisStreamState *stream = (const _MetisStreamState *) metisIoOperations_GetClosure(ops); + return stream->id; +} + +/** + * @function metisStreamConnection_Send + * @abstract Non-destructive send of the message. + * @discussion + * Send uses metisMessage_CopyToStreamBuffer, which is a non-destructive write. + * The send may fail if there's no buffer space in the output queue. + * + * @param dummy is ignored. A stream has only one peer. + * @return <#return#> + */ +static bool +_metisStreamConnection_Send(MetisIoOperations *ops, const CPIAddress *dummy, MetisMessage *message) +{ + assertNotNull(ops, "Parameter ops must be non-null"); + assertNotNull(message, "Parameter message must be non-null"); + _MetisStreamState *stream = (_MetisStreamState *) metisIoOperations_GetClosure(ops); + + bool success = false; + if (stream->isUp) { + PARCEventBuffer *buffer = parcEventBuffer_GetQueueBufferOutput(stream->bufferEventVector); + size_t buffer_backlog = parcEventBuffer_GetLength(buffer); + parcEventBuffer_Destroy(&buffer); + + if (buffer_backlog < OUTPUT_QUEUE_BYTES) { + if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "connid %u Writing %zu bytes to buffer with backlog %zu bytes", + stream->id, + metisMessage_Length(message), + buffer_backlog); + } + + int failure = metisMessage_Write(stream->bufferEventVector, message); + if (failure == 0) { + success = true; + } + } else { + if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning)) { + metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "connid %u Writing to buffer backlog %zu bytes DROP MESSAGE", + stream->id, + buffer_backlog); + } + } + } else { + if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "connid %u tried to send to down connection (isUp %d isClosed %d)", + stream->id, + stream->isUp, + stream->isClosed); + } + } + + return success; +} + +static CPIConnectionType +_metisStreamConnection_GetConnectionType(const MetisIoOperations *ops) +{ + return cpiConnection_TCP; +} + +static MetisTicks +_sendProbe(MetisIoOperations *ops, unsigned probeType) +{ + //TODO + return 0; +} + +// ================================================================= +// the actual I/O functions + +/** + * @function startNewMessage + * @abstract Peek at the fixed header and set the stream->nextMessageLength + * @discussion + * This function manipulates the stream->nextMessageLength. After reading a FixedHeader, set nextMessageLength + * to the total length of the message. + * + * PRECONDITION: stream->nextMessageLength == 0 + * PRECONDITION: inputBytesAvailable >= FIXED_HEADER_LEN + * + * @param stream is the stream begin parsed. + * @param input is the input PARCEventBuffer (corresponds to the buffer event's input) + * @param inputBytesAvailable is the number of bytes available in the input PARCEventBuffer. + * @return <#return#> + */ +static void +_startNewMessage(_MetisStreamState *stream, PARCEventBuffer *input, size_t inputBytesAvailable) +{ + assertTrue(stream->nextMessageLength == 0, "Invalid state, nextMessageLength not zero: %zu", stream->nextMessageLength); + assertTrue(inputBytesAvailable >= metisTlv_FixedHeaderLength(), "read_length not a whole fixed header!: %zd", inputBytesAvailable); + + // this linearizes the first FIXED_HEADER_LEN bytes of the input buffer's iovecs and + // returns a pointer to it. + uint8_t *fh = parcEventBuffer_Pullup(input, metisTlv_FixedHeaderLength()); + + // Calculate the total message size based on the fixed header + stream->nextMessageLength = metisTlv_TotalPacketLength(fh); +} + +/** + * @function readMessage + * @abstract Read the complete message from the input + * @discussion + * Called to read a complete message from the input and return a MetisMessage + * + * PRECONDITION: There are at least <code>stream->nextMessageLength</code> + * bytes available on the input PARCEventBuffer. + * + * @param stream is the stream being parsed + * @param time is the current forwarder time (metisForwarder_GetTicks(stream->metis)) + * @param input is the input PARCEventBuffer to readessage bytes. + * @return <#return#> + */ +static MetisMessage * +_readMessage(_MetisStreamState *stream, MetisTicks time, PARCEventBuffer *input) +{ + MetisMessage *message = metisMessage_ReadFromBuffer(stream->id, time, input, stream->nextMessageLength, stream->logger); + + return message; +} + +/** + * @function single_read + * @abstract Read at most 1 message from the network + * @discussion + * If a complete message is ready on the input buffer, will allocate and return it. + * + * This function manipulates the stream->nextMessageLength. (1) Initializes with nextMessageLength = 0, + * which means we have not started parsing a packet. (2) After reading a FixedHeader, set nextMessageLength + * to the total length of the message. (3) After reading nextMessageLength bytes, return the outputBuffer + * and reset nextMessageLength to 0. + * + * @param input is the PARCEventBuffer to read + * @param stream is the related stream state for the input + * @return true if there's more to read after this message. + */ +static MetisMessage * +_single_read(PARCEventBuffer *input, _MetisStreamState *stream) +{ + size_t bytesAvailable = parcEventBuffer_GetLength(input); + + assertTrue(bytesAvailable >= metisTlv_FixedHeaderLength(), "Called with too short an input: %zu", bytesAvailable); + + if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "connid %u read %zu bytes", + stream->id, bytesAvailable); + } + + if (stream->nextMessageLength == 0) { + _startNewMessage(stream, input, bytesAvailable); + } + + // This is not an ELSE statement. We can both start a new message then + // check if there's enough bytes to read the whole thing. + + if (bytesAvailable >= stream->nextMessageLength) { + MetisMessage *message = _readMessage(stream, metisForwarder_GetTicks(stream->metis), input); + + if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "connid %u msg_length %zu read_length %zu, resetting parser", + stream->id, + stream->nextMessageLength, + bytesAvailable); + } + + // now reset message length for next packet + stream->nextMessageLength = 0; + + return message; + } + + return NULL; +} + +/** + * @function conn_readcb + * @abstract Event callback for reads + * @discussion + * Will read messages off the input. Continues reading as long as we + * can get a header to determine the next message length or as long as we + * can read a complete message. + * + * This function manipulates the read low water mark. (1) read a fixed header plus complete message, + * then set the low water mark to FIXED_HEADER_LEN. (2) read a fixed header, but not a complete + * message, then set low water mark to the total mesage length. Using the low water mark like this + * means the buffer event will only trigger on meaningful byte boundaries when we can get actual + * work done. + * + * @param <#param1#> + * @return <#return#> + */ +static void +_conn_readcb(PARCEventQueue *event, PARCEventType type, void *ioOpsVoid) +{ + MetisIoOperations *ops = (MetisIoOperations *) ioOpsVoid; + _MetisStreamState *stream = (_MetisStreamState *) metisIoOperations_GetClosure(ops); + + PARCEventBuffer *input = parcEventBuffer_GetQueueBufferInput(event); + + // drain the input buffer + while (parcEventBuffer_GetLength(input) >= metisTlv_FixedHeaderLength() && parcEventBuffer_GetLength(input) >= stream->nextMessageLength) { + // this may set the stream->nextMessageLength + MetisMessage *message = _single_read(input, stream); + + if (message) { + metisForwarder_Receive(stream->metis, message); + } + } + + if (stream->nextMessageLength == 0) { + // we don't have the next header, so set it to the header length + metisStreamBuffer_SetWatermark(event, true, false, metisTlv_FixedHeaderLength(), 0); + } else { + // set it to the packet length + metisStreamBuffer_SetWatermark(event, true, false, stream->nextMessageLength, 0); + } + parcEventBuffer_Destroy(&input); +} + +static void +_setConnectionState(_MetisStreamState *stream, bool isUp) +{ + assertNotNull(stream, "Parameter stream must be non-null"); + + MetisMessenger *messenger = metisForwarder_GetMessenger(stream->metis); + + bool oldStateIsUp = stream->isUp; + stream->isUp = isUp; + + if (oldStateIsUp && !isUp) { + // bring connection DOWN + MetisMissive *missive = metisMissive_Create(MetisMissiveType_ConnectionDown, stream->id); + metisMessenger_Send(messenger, missive); + return; + } + + + if (!oldStateIsUp && isUp) { + // bring connection UP + MetisMissive *missive = metisMissive_Create(MetisMissiveType_ConnectionUp, stream->id); + metisMessenger_Send(messenger, missive); + return; + } +} + +static void +_conn_eventcb(PARCEventQueue *event, PARCEventQueueEventType events, void *ioOpsVoid) +{ + MetisIoOperations *ops = (MetisIoOperations *) ioOpsVoid; + _MetisStreamState *stream = (_MetisStreamState *) metisIoOperations_GetClosure(ops); + + if (events & PARCEventQueueEventType_Connected) { + if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Info)) { + metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__, + "Connection %u is connected", stream->id); + } + + // if the stream was closed, do not transition to an UP state + if (!stream->isClosed) { + _setConnectionState(stream, true); + } + } else + if (events & PARCEventQueueEventType_EOF) { + if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Info)) { + metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__, + "connid %u closed.", + stream->id); + } + + parcEventQueue_Disable(stream->bufferEventVector, PARCEventType_Read); + + _setConnectionState(stream, false); + + if (!stream->isClosed) { + stream->isClosed = true; + // this will cause the connection manager to destroy the connection later + metisMessenger_Send(metisForwarder_GetMessenger(stream->metis), metisMissive_Create(MetisMissiveType_ConnectionClosed, stream->id)); + } + } else + if (events & PARCEventQueueEventType_Error) { + if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Got an error on the connection %u: %s", stream->id, strerror(errno)); + } + + parcEventQueue_Disable(stream->bufferEventVector, PARCEventType_Read | PARCEventType_Write); + + _setConnectionState(stream, false); + + if (!stream->isClosed) { + stream->isClosed = true; + // this will cause the connection manager to destroy the connection later + metisMessenger_Send(metisForwarder_GetMessenger(stream->metis), metisMissive_Create(MetisMissiveType_ConnectionClosed, stream->id)); + } + } + /* None of the other events can happen here, since we haven't enabled + * timeouts */ +} diff --git a/metis/ccnx/forwarder/metis/io/metis_StreamConnection.h b/metis/ccnx/forwarder/metis/io/metis_StreamConnection.h new file mode 100644 index 00000000..ee614862 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_StreamConnection.h @@ -0,0 +1,65 @@ +/* + * 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. + */ + + +/** + * Methods common to TCP and PF_LOCAL stream-based listeners + */ + +#ifndef Metis_metis_StreamConnection_h +#define Metis_metis_StreamConnection_h + +#include <ccnx/forwarder/metis/io/metis_IoOperations.h> +#include <ccnx/forwarder/metis/io/metis_AddressPair.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/api/control/cpi_Address.h> + +/** + * @function metisStreamConnection_AcceptConnection + * @abstract Receive a connection from a remote peer + * @discussion + * We are the "server side" of the stream connection, so we need to accept the client connection + * and setup state for her. + * + * @param <#param1#> + * @return <#return#> + */ +MetisIoOperations *metisStreamConnection_AcceptConnection(MetisForwarder *metis, int fd, MetisAddressPair *pair, bool isLocal); + +/** + * @function metisStreamConnection_OpenConnection + * @abstract Initiate a connection to a remote peer + * @discussion + * We are the "client side" of the stream connection. We'll create state for the peer, but it will + * be in the "down" state until the connection establishes. + * + * For TCP, both address pairs need to be the same address family: both INET or both INET6. The remote + * address must have the complete socket information (address, port). The local socket could be wildcarded or + * may specify down to the (address, port) pair. + * + * If the local address is IPADDR_ANY and the port is 0, then it is a normal call to "connect" that will use + * whatever local IP address and whatever local port for the connection. If either the address or port is + * set, the local socket will first be bound (via bind(2)), and then call connect(). + * + * AF_UNIX is not yet supported + * + * If there's an error binding to the specified address or connecting to the remote address, + * will return NULL. + * + * @param pair (takes ownership of this) is the complete socket pair of (address, port) for each end, if INET or INET6. + * @return NULL on error, otherwise the connections IO operations. + */ +MetisIoOperations *metisStreamConnection_OpenConnection(MetisForwarder *metis, MetisAddressPair *pair, bool isLocal); +#endif // Metis_metis_StreamConnection_h diff --git a/metis/ccnx/forwarder/metis/io/metis_TcpListener.c b/metis/ccnx/forwarder/metis/io/metis_TcpListener.c new file mode 100644 index 00000000..e9d232ce --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_TcpListener.c @@ -0,0 +1,237 @@ +/* + * 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 <stdbool.h> +#include <string.h> +#include <errno.h> + +#include <ccnx/forwarder/metis/io/metis_TcpListener.h> +#include <ccnx/forwarder/metis/io/metis_StreamConnection.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_ConnectionTable.h> +#include <ccnx/forwarder/metis/io/metis_Listener.h> + +#include <LongBow/runtime.h> +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_Network.h> + +typedef struct metis_tcp_listener { + MetisForwarder *metis; + MetisLogger *logger; + + PARCEventSocket *listener; + + CPIAddress *localAddress; + + unsigned id; + + // is the localAddress as 127.0.0.0 address? + bool isLocalAddressLocal; +} _MetisTcpListener; + +static void _metisTcpListener_Destroy(_MetisTcpListener **listenerPtr); + +static void _metisTcpListener_OpsDestroy(MetisListenerOps **listenerOpsPtr); +static unsigned _metisTcpListener_OpsGetInterfaceIndex(const MetisListenerOps *ops); +static const CPIAddress *_metisTcpListener_OpsGetListenAddress(const MetisListenerOps *ops); +static MetisEncapType _metisTcpListener_OpsGetEncapType(const MetisListenerOps *ops); + +static MetisListenerOps _tcpTemplate = { + .context = NULL, + .destroy = &_metisTcpListener_OpsDestroy, + .getInterfaceIndex = &_metisTcpListener_OpsGetInterfaceIndex, + .getListenAddress = &_metisTcpListener_OpsGetListenAddress, + .getEncapType = &_metisTcpListener_OpsGetEncapType, + .getSocket = NULL +}; + +// STREAM daemon listener callback +static void _metisTcpListener_Listen(int, struct sockaddr *, int socklen, void *tcpVoid); + + +MetisListenerOps * +metisTcpListener_CreateInet6(MetisForwarder *metis, struct sockaddr_in6 sin6) +{ + _MetisTcpListener *tcp = parcMemory_AllocateAndClear(sizeof(_MetisTcpListener)); + assertNotNull(tcp, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisTcpListener)); + + tcp->metis = metis; + tcp->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + + tcp->listener = metisDispatcher_CreateListener(metisForwarder_GetDispatcher(metis), _metisTcpListener_Listen, + (void *) tcp, -1, (struct sockaddr*) &sin6, sizeof(sin6)); + + if (tcp->listener == NULL) { + metisLogger_Log(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "metisDispatcher_CreateListener failed to create listener (%d) %s", + errno, strerror(errno)); + metisLogger_Release(&tcp->logger); + parcMemory_Deallocate((void **) &tcp); + return NULL; + } + + tcp->localAddress = cpiAddress_CreateFromInet6(&sin6); + tcp->id = metisForwarder_GetNextConnectionId(metis); + tcp->isLocalAddressLocal = parcNetwork_IsSocketLocal((struct sockaddr *) &sin6); + + MetisListenerOps *ops = parcMemory_AllocateAndClear(sizeof(MetisListenerOps)); + assertNotNull(ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisListenerOps)); + + memcpy(ops, &_tcpTemplate, sizeof(MetisListenerOps)); + ops->context = tcp; + + if (metisLogger_IsLoggable(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + char *str = cpiAddress_ToString(tcp->localAddress); + metisLogger_Log(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "TcpListener %p created for address %s (isLocal %d)", + (void *) tcp, str, tcp->isLocalAddressLocal); + parcMemory_Deallocate((void **) &str); + } + + return ops; +} + +MetisListenerOps * +metisTcpListener_CreateInet(MetisForwarder *metis, struct sockaddr_in sin) +{ + _MetisTcpListener *tcp = parcMemory_AllocateAndClear(sizeof(_MetisTcpListener)); + assertNotNull(tcp, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisTcpListener)); + + tcp->metis = metis; + tcp->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + tcp->listener = metisDispatcher_CreateListener(metisForwarder_GetDispatcher(metis), _metisTcpListener_Listen, + (void *) tcp, -1, (struct sockaddr*) &sin, sizeof(sin)); + + if (tcp->listener == NULL) { + metisLogger_Log(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "metisDispatcher_CreateListener failed to create listener (%d) %s", + errno, strerror(errno)); + + metisLogger_Release(&tcp->logger); + parcMemory_Deallocate((void **) &tcp); + return NULL; + } + + tcp->localAddress = cpiAddress_CreateFromInet(&sin); + tcp->id = metisForwarder_GetNextConnectionId(metis); + tcp->isLocalAddressLocal = parcNetwork_IsSocketLocal((struct sockaddr *) &sin); + + MetisListenerOps *ops = parcMemory_AllocateAndClear(sizeof(MetisListenerOps)); + assertNotNull(ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisListenerOps)); + + memcpy(ops, &_tcpTemplate, sizeof(MetisListenerOps)); + ops->context = tcp; + + if (metisLogger_IsLoggable(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + char *str = cpiAddress_ToString(tcp->localAddress); + metisLogger_Log(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "TcpListener %p created for address %s (isLocal %d)", + (void *) tcp, str, tcp->isLocalAddressLocal); + parcMemory_Deallocate((void **) &str); + } + + return ops; +} + +static void +_metisTcpListener_Destroy(_MetisTcpListener **listenerPtr) +{ + assertNotNull(listenerPtr, "Parameter must be non-null double pointer"); + assertNotNull(*listenerPtr, "Parameter must derefernce to non-null pointer"); + _MetisTcpListener *tcp = *listenerPtr; + + if (metisLogger_IsLoggable(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + char *str = cpiAddress_ToString(tcp->localAddress); + metisLogger_Log(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "TcpListener %p destroyed", (void *) tcp); + parcMemory_Deallocate((void **) &str); + } + + metisLogger_Release(&tcp->logger); + metisDispatcher_DestroyListener(metisForwarder_GetDispatcher(tcp->metis), &tcp->listener); + cpiAddress_Destroy(&tcp->localAddress); + parcMemory_Deallocate((void **) &tcp); + *listenerPtr = NULL; +} + +// ================================================== + +static void +_metisTcpListener_Listen(int fd, struct sockaddr *sa, int socklen, void *tcpVoid) +{ + _MetisTcpListener *tcp = (_MetisTcpListener *) tcpVoid; + + CPIAddress *remote; + + switch (sa->sa_family) { + case AF_INET: + remote = cpiAddress_CreateFromInet((struct sockaddr_in *) sa); + break; + + case AF_INET6: + remote = cpiAddress_CreateFromInet6((struct sockaddr_in6 *) sa); + break; + + default: + trapIllegalValue(sa, "Expected INET or INET6, got %d", sa->sa_family); + abort(); + } + + MetisAddressPair *pair = metisAddressPair_Create(tcp->localAddress, remote); + + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(tcp->metis, fd, pair, tcp->isLocalAddressLocal); + MetisConnection *conn = metisConnection_Create(ops); + + metisConnectionTable_Add(metisForwarder_GetConnectionTable(tcp->metis), conn); + + if (metisLogger_IsLoggable(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "TcpListener %p listen started", (void *) tcp); + } + + cpiAddress_Destroy(&remote); +} + +static void +_metisTcpListener_OpsDestroy(MetisListenerOps **listenerOpsPtr) +{ + MetisListenerOps *ops = *listenerOpsPtr; + _MetisTcpListener *tcp = (_MetisTcpListener *) ops->context; + _metisTcpListener_Destroy(&tcp); + parcMemory_Deallocate((void **) &ops); + *listenerOpsPtr = NULL; +} + +static unsigned +_metisTcpListener_OpsGetInterfaceIndex(const MetisListenerOps *ops) +{ + _MetisTcpListener *tcp = (_MetisTcpListener *) ops->context; + return tcp->id; +} + +static const CPIAddress * +_metisTcpListener_OpsGetListenAddress(const MetisListenerOps *ops) +{ + _MetisTcpListener *tcp = (_MetisTcpListener *) ops->context; + return tcp->localAddress; +} + +static MetisEncapType +_metisTcpListener_OpsGetEncapType(const MetisListenerOps *ops) +{ + return METIS_ENCAP_TCP; +} diff --git a/metis/ccnx/forwarder/metis/io/metis_TcpListener.h b/metis/ccnx/forwarder/metis/io/metis_TcpListener.h new file mode 100644 index 00000000..e62edba9 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_TcpListener.h @@ -0,0 +1,35 @@ +/* + * 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_TcpListener.h + * @brief Listens for in coming TCP connections + * + * This is the "server socket" of metis for TCP connections. The actual I/O is + * handled by {@link MetisStreamConnection}. + * + */ + +#ifndef Metis_metis_TcpListener_h +#define Metis_metis_TcpListener_h + +#include <netinet/in.h> +#include <stdlib.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/io/metis_Listener.h> + +MetisListenerOps *metisTcpListener_CreateInet6(MetisForwarder *metis, struct sockaddr_in6 sin6); +MetisListenerOps *metisTcpListener_CreateInet(MetisForwarder *metis, struct sockaddr_in sin); +#endif // Metis_metis_TcpListener_h diff --git a/metis/ccnx/forwarder/metis/io/metis_TcpTunnel.c b/metis/ccnx/forwarder/metis/io/metis_TcpTunnel.c new file mode 100644 index 00000000..b0182240 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_TcpTunnel.c @@ -0,0 +1,44 @@ +/* + * 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 TCP out-bound tunnel is almost identical to the in-bound tunnel. + * We use MetisStreamConneciton for the out-bound tunnels too. We call a different + * constructor than the in-bound so the MetisStreamConneciton knows that it is starting + * unconnected and needs to wait for the Connected event before putting it in the UP state. + * + */ + +#include <config.h> +#include <stdio.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> + +#include <ccnx/forwarder/metis/io/metis_TcpTunnel.h> +#include <ccnx/forwarder/metis/io/metis_StreamConnection.h> + +#include <LongBow/runtime.h> + +MetisIoOperations * +metisTcpTunnel_Create(MetisForwarder *metis, const CPIAddress *localAddress, const CPIAddress *remoteAddress) +{ + MetisAddressPair *pair = metisAddressPair_Create(localAddress, remoteAddress); + + bool isLocal = false; + + // this takes ownership of the address pair + return metisStreamConnection_OpenConnection(metis, pair, isLocal); +} diff --git a/metis/ccnx/forwarder/metis/io/metis_TcpTunnel.h b/metis/ccnx/forwarder/metis/io/metis_TcpTunnel.h new file mode 100644 index 00000000..ec53c7cd --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_TcpTunnel.h @@ -0,0 +1,49 @@ +/* + * 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_TcpTunnel.h + * @brief Creates a TCP Tunnel to a remote address + * + * The connection will be established in "connecting" state and once the remote accepts, it will + * be promoted to "up" state. + * + */ + +#ifndef Metis_metis_TcpTunnel_h +#define Metis_metis_TcpTunnel_h + +#include <ccnx/api/control/cpi_Address.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/io/metis_IoOperations.h> + +/** + * @function metisTcpTunnel_Create + * @abstract Creates a TCP tunnel to a remote system. + * @discussion + * The two addresses must be the same type (i.e. both INET or INET6) and cannot point to the same system. + * + * The tunnel will look just like an in-bound connection after its built. It exposes the standard + * MetisIoOperations so it can be put in the MetisConnectionTable. + * + * The connection will go in the table immediately, but will be in the "down" state until the + * connection is established. + * + * + * @param <#param1#> + * @return The I/O ops for the tunnel. + */ +MetisIoOperations *metisTcpTunnel_Create(MetisForwarder *metis, const CPIAddress *localAddress, const CPIAddress *remoteAddress); +#endif // Metis_metis_TcpTunnel_h diff --git a/metis/ccnx/forwarder/metis/io/metis_UdpConnection.c b/metis/ccnx/forwarder/metis/io/metis_UdpConnection.c new file mode 100644 index 00000000..aecd68fa --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_UdpConnection.c @@ -0,0 +1,396 @@ +/* + * 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. + */ + +/** + * Embodies the reader/writer for a UDP connection + * + * NB The Send() function may overflow the output buffer + * + */ + +#include <config.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include <ccnx/forwarder/metis/io/metis_UdpConnection.h> + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_Connection.h> +#include <ccnx/forwarder/metis/core/metis_Message.h> +#include <parc/algol/parc_Hash.h> + +#include <LongBow/runtime.h> +#include <parc/algol/parc_Memory.h> +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> + +typedef struct metis_udp_state { + MetisForwarder *metis; + MetisLogger *logger; + + // the udp listener socket we receive packets on + int udpListenerSocket; + + MetisAddressPair *addressPair; + + // We need to access this all the time, so grab it out + // of the addressPair; + struct sockaddr *peerAddress; + socklen_t peerAddressLength; + + bool isLocal; + bool isUp; + unsigned id; + + unsigned delay; +} _MetisUdpState; + +// Prototypes +static bool _send(MetisIoOperations *ops, const CPIAddress *nexthop, MetisMessage *message); +static const CPIAddress *_getRemoteAddress(const MetisIoOperations *ops); +static const MetisAddressPair *_getAddressPair(const MetisIoOperations *ops); +static unsigned _getConnectionId(const MetisIoOperations *ops); +static bool _isUp(const MetisIoOperations *ops); +static bool _isLocal(const MetisIoOperations *ops); +static void _destroy(MetisIoOperations **opsPtr); +static CPIConnectionType _getConnectionType(const MetisIoOperations *ops); +static MetisTicks _sendProbe(MetisIoOperations *ops, unsigned probeType); +/* + * This assigns a unique pointer to the void * which we use + * as a GUID for this class. + */ +static const void *_metisIoOperationsGuid = __FILE__; + +/* + * Return our GUID + */ +static const void * +_metisStreamConnection_Class(const MetisIoOperations *ops) +{ + return _metisIoOperationsGuid; +} + +static MetisIoOperations _template = { + .closure = NULL, + .send = &_send, + .getRemoteAddress = &_getRemoteAddress, + .getAddressPair = &_getAddressPair, + .getConnectionId = &_getConnectionId, + .isUp = &_isUp, + .isLocal = &_isLocal, + .destroy = &_destroy, + .class = &_metisStreamConnection_Class, + .getConnectionType = &_getConnectionType, + .sendProbe = &_sendProbe +}; + +// ================================================================= + +static void _setConnectionState(_MetisUdpState *Udp, bool isUp); +static bool _saveSockaddr(_MetisUdpState *udpConnState, const MetisAddressPair *pair); + +MetisIoOperations * +metisUdpConnection_Create(MetisForwarder *metis, int fd, const MetisAddressPair *pair, bool isLocal) +{ + MetisIoOperations *io_ops = NULL; + + _MetisUdpState *udpConnState = parcMemory_AllocateAndClear(sizeof(_MetisUdpState)); + assertNotNull(udpConnState, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisUdpState)); + + udpConnState->metis = metis; + udpConnState->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + + bool saved = _saveSockaddr(udpConnState, pair); + if (saved) { + udpConnState->udpListenerSocket = fd; + udpConnState->id = metisForwarder_GetNextConnectionId(metis); + udpConnState->addressPair = metisAddressPair_Acquire(pair); + udpConnState->isLocal = isLocal; + + // allocate a connection + io_ops = parcMemory_AllocateAndClear(sizeof(MetisIoOperations)); + assertNotNull(io_ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisIoOperations)); + memcpy(io_ops, &_template, sizeof(MetisIoOperations)); + io_ops->closure = udpConnState; + + _setConnectionState(udpConnState, true); + + if (metisLogger_IsLoggable(udpConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Info)) { + char *str = metisAddressPair_ToString(udpConnState->addressPair); + metisLogger_Log(udpConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__, + "UdpConnection %p created for address %s (isLocal %d)", + (void *) udpConnState, str, udpConnState->isLocal); + free(str); + } + + metisMessenger_Send(metisForwarder_GetMessenger(metis), metisMissive_Create(MetisMissiveType_ConnectionCreate, udpConnState->id)); + metisMessenger_Send(metisForwarder_GetMessenger(metis), metisMissive_Create(MetisMissiveType_ConnectionUp, udpConnState->id)); + } else { + // _saveSockaddr will already log an error, no need for extra log message here + metisLogger_Release(&udpConnState->logger); + parcMemory_Deallocate((void **) &udpConnState); + } + + return io_ops; +} + +// ================================================================= +// I/O Operations implementation + +static void +_destroy(MetisIoOperations **opsPtr) +{ + assertNotNull(opsPtr, "Parameter opsPtr must be non-null double pointer"); + assertNotNull(*opsPtr, "Parameter opsPtr must dereference to non-null pointer"); + + MetisIoOperations *ops = *opsPtr; + assertNotNull(metisIoOperations_GetClosure(ops), "ops->context must not be null"); + + _MetisUdpState *udpConnState = (_MetisUdpState *) metisIoOperations_GetClosure(ops); + metisAddressPair_Release(&udpConnState->addressPair); + parcMemory_Deallocate((void **) &(udpConnState->peerAddress)); + + metisMessenger_Send(metisForwarder_GetMessenger(udpConnState->metis), metisMissive_Create(MetisMissiveType_ConnectionDestroyed, udpConnState->id)); + + if (metisLogger_IsLoggable(udpConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Info)) { + metisLogger_Log(udpConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__, + "UdpConnection %p destroyed", + (void *) udpConnState); + } + + // do not close udp->udpListenerSocket, the listener will close + // that when its done + + metisLogger_Release(&udpConnState->logger); + parcMemory_Deallocate((void **) &udpConnState); + parcMemory_Deallocate((void **) &ops); + + *opsPtr = NULL; +} + +static bool +_isUp(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisUdpState *udpConnState = (const _MetisUdpState *) metisIoOperations_GetClosure(ops); + return udpConnState->isUp; +} + +static bool +_isLocal(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisUdpState *udpConnState = (const _MetisUdpState *) metisIoOperations_GetClosure(ops); + return udpConnState->isLocal; +} + +static const CPIAddress * +_getRemoteAddress(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisUdpState *udpConnState = (const _MetisUdpState *) metisIoOperations_GetClosure(ops); + return metisAddressPair_GetRemote(udpConnState->addressPair); +} + +static const MetisAddressPair * +_getAddressPair(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisUdpState *udpConnState = (const _MetisUdpState *) metisIoOperations_GetClosure(ops); + return udpConnState->addressPair; +} + +static unsigned +_getConnectionId(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisUdpState *udpConnState = (const _MetisUdpState *) metisIoOperations_GetClosure(ops); + return udpConnState->id; +} + +/** + * @function metisUdpConnection_Send + * @abstract Non-destructive send of the message. + * @discussion + * sends a message to the peer. + * + * @param dummy is ignored. A udp connection has only one peer. + * @return <#return#> + */ +static bool +_send(MetisIoOperations *ops, const CPIAddress *dummy, MetisMessage *message) +{ + assertNotNull(ops, "Parameter ops must be non-null"); + assertNotNull(message, "Parameter message must be non-null"); + _MetisUdpState *udpConnState = (_MetisUdpState *) metisIoOperations_GetClosure(ops); + + PARCEventBuffer *writeBuffer = parcEventBuffer_Create(); + metisMessage_Append(writeBuffer, message); + + const uint8_t *buffer = parcEventBuffer_Pullup(writeBuffer, -1); + size_t bufferLength = parcEventBuffer_GetLength(writeBuffer); + + ssize_t writeLength = sendto(udpConnState->udpListenerSocket, buffer, bufferLength, 0, udpConnState->peerAddress, udpConnState->peerAddressLength); + + if (writeLength < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + parcEventBuffer_Destroy(&writeBuffer); + return false; + } else { + //this print is for debugging + printf("Incorrect write length %zd, expected %zd: (%d) %s\n", writeLength, bufferLength, errno, strerror(errno)); + parcEventBuffer_Destroy(&writeBuffer); + return false; + } + } + + parcEventBuffer_Destroy(&writeBuffer); + return true; +} + +static CPIConnectionType +_getConnectionType(const MetisIoOperations *ops) +{ + return cpiConnection_UDP; +} + +static MetisTicks +_sendProbe(MetisIoOperations *ops, unsigned probeType) +{ + assertNotNull(ops, "Parameter ops must be non-null"); + _MetisUdpState *udpConnState = (_MetisUdpState *) metisIoOperations_GetClosure(ops); + + + uint8_t *pkt; + size_t pkt_size = 8; + pkt = (uint8_t *) malloc(sizeof(uint8_t) * pkt_size); + for (unsigned i = 0; i < pkt_size; i++) { + pkt[i] = 0; + } + pkt[0] = 1; //tlv type + pkt[1] = probeType; //packet type + pkt[6] = 8; //header len (16bit, network order) + + ssize_t writeLen = sendto(udpConnState->udpListenerSocket, pkt, pkt_size, 0, udpConnState->peerAddress, udpConnState->peerAddressLength); + + if (writeLen < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + free(pkt); + return 0; + } else { + //this print is for debugging + printf("Incorrect write length %zd, expected %zd: (%d) %s\n", writeLen, pkt_size, errno, strerror(errno)); + free(pkt); + return 0; + } + } + + free(pkt); + return metisForwarder_GetTicks(udpConnState->metis); +} + +/*static MetisTicks + * _handleProbe(MetisIoOperations *ops, MetisTicks last_sent, uint8_t *pkt) + * { + * assertNotNull(ops, "Parameter ops must be non-null"); + * assertNotNull(pkt, "Parameter pkt must be non-null"); + * + * _MetisUdpState *udpConnState = (_MetisUdpState *) metisIoOperations_GetClosure(ops); + * + * MetisTicks delay = 0; + * + * if(pkt[1] == METIS_PACKET_TYPE_PROBE_REQUEST){ + * _sendProbeType(ops, METIS_PACKET_TYPE_PROBE_REPLY); + * } else if (pkt[1] == METIS_PACKET_TYPE_PROBE_REPLY) { + * MetisTicks now = metisForwarder_GetTicks(udpConnState->metis); + * delay = now - last_sent; + * } else { + * printf("receivde unkwon probe type\n"); + * } + * + * return delay; + * }*/ + +// ================================================================= +// Internal API + +static bool +_saveSockaddr(_MetisUdpState *udpConnState, const MetisAddressPair *pair) +{ + bool success = false; + const CPIAddress *remoteAddress = metisAddressPair_GetRemote(pair); + + switch (cpiAddress_GetType(remoteAddress)) { + case cpiAddressType_INET: { + size_t bytes = sizeof(struct sockaddr_in); + udpConnState->peerAddress = parcMemory_Allocate(bytes); + assertNotNull(udpConnState->peerAddress, "parcMemory_Allocate(%zu) returned NULL", bytes); + + cpiAddress_GetInet(remoteAddress, (struct sockaddr_in *) udpConnState->peerAddress); + udpConnState->peerAddressLength = (socklen_t) bytes; + + success = true; + break; + } + + case cpiAddressType_INET6: { + size_t bytes = sizeof(struct sockaddr_in6); + udpConnState->peerAddress = parcMemory_Allocate(bytes); + assertNotNull(udpConnState->peerAddress, "parcMemory_Allocate(%zu) returned NULL", bytes); + + cpiAddress_GetInet6(remoteAddress, (struct sockaddr_in6 *) udpConnState->peerAddress); + udpConnState->peerAddressLength = (socklen_t) bytes; + + success = true; + break; + } + + default: + if (metisLogger_IsLoggable(udpConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + char *str = cpiAddress_ToString(remoteAddress); + metisLogger_Log(udpConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Remote address is not INET or INET6: %s", str); + parcMemory_Deallocate((void **) &str); + } + break; + } + return success; +} + +static void +_setConnectionState(_MetisUdpState *udpConnState, bool isUp) +{ + assertNotNull(udpConnState, "Parameter Udp must be non-null"); + + MetisMessenger *messenger = metisForwarder_GetMessenger(udpConnState->metis); + + bool oldStateIsUp = udpConnState->isUp; + udpConnState->isUp = isUp; + + if (oldStateIsUp && !isUp) { + // bring connection DOWN + MetisMissive *missive = metisMissive_Create(MetisMissiveType_ConnectionDown, udpConnState->id); + metisMessenger_Send(messenger, missive); + return; + } + + + if (!oldStateIsUp && isUp) { + // bring connection UP + MetisMissive *missive = metisMissive_Create(MetisMissiveType_ConnectionUp, udpConnState->id); + metisMessenger_Send(messenger, missive); + return; + } +} diff --git a/metis/ccnx/forwarder/metis/io/metis_UdpConnection.h b/metis/ccnx/forwarder/metis/io/metis_UdpConnection.h new file mode 100644 index 00000000..86af9296 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_UdpConnection.h @@ -0,0 +1,51 @@ +/* + * 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_UdpConnection.h + * @brief Represents a UDP connection (socket) for the connection table + * + * <#Detailed Description#> + * + */ + +#ifndef Metis_metis_UdpConnection_h +#define Metis_metis_UdpConnection_h + +#include <ccnx/forwarder/metis/io/metis_IoOperations.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/io/metis_AddressPair.h> +#include <ccnx/api/control/cpi_Address.h> + +/** + * Creates a UDP connection that can send to the remote address + * + * The address pair must both be same type (i.e. INET or INET6). + * + * @param [in] metis An allocated MetisForwarder (saves reference) + * @param [in] fd The socket to use + * @param [in] pair An allocated address pair for the connection (saves reference) + * @param [in] isLocal determines if the remote address is on the current system + * + * @retval non-null An allocated Io operations + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisIoOperations *metisUdpConnection_Create(MetisForwarder *metis, int fd, const MetisAddressPair *pair, bool isLocal); +#endif // Metis_metis_UdpConnection_h diff --git a/metis/ccnx/forwarder/metis/io/metis_UdpController.h b/metis/ccnx/forwarder/metis/io/metis_UdpController.h new file mode 100644 index 00000000..db27a0e1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_UdpController.h @@ -0,0 +1,20 @@ +/* + * 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_UdpController_h +#define Metis_metis_UdpController_h +#endif // Metis_metis_UdpController_h diff --git a/metis/ccnx/forwarder/metis/io/metis_UdpListener.c b/metis/ccnx/forwarder/metis/io/metis_UdpListener.c new file mode 100644 index 00000000..1bd54187 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_UdpListener.c @@ -0,0 +1,599 @@ +/* + * 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 <fcntl.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> +#include <arpa/inet.h> +#include <unistd.h> + +#include <ccnx/forwarder/metis/io/metis_UdpListener.h> +#include <ccnx/forwarder/metis/io/metis_UdpConnection.h> + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_Connection.h> +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> +#include <ccnx/forwarder/metis/core/metis_Message.h> +#include <ccnx/forwarder/metis/messenger/metis_Messenger.h> + +#include <ccnx/forwarder/metis/core/metis_Wldr.h> + +#include <LongBow/runtime.h> +#include <parc/algol/parc_Memory.h> + +typedef struct metis_udp_stats { + uint64_t framesIn; + uint64_t framesError; + uint64_t framesReceived; +} _MetisUdpStats; + +struct metis_udp_listener { + MetisForwarder *metis; + MetisLogger *logger; + + //MetisNetworkEvent *udp_event; + PARCEvent *udp_event; + MetisSocketType udp_socket; + uint16_t port; + + unsigned id; + CPIAddress *localAddress; + + _MetisUdpStats stats; +}; + +static void _destroy(MetisListenerOps **listenerOpsPtr); +static unsigned _getInterfaceIndex(const MetisListenerOps *ops); +static const CPIAddress *_getListenAddress(const MetisListenerOps *ops); +static MetisEncapType _getEncapType(const MetisListenerOps *ops); +static int _getSocket(const MetisListenerOps *ops); + +static MetisListenerOps udpTemplate = { + .context = NULL, + .destroy = &_destroy, + .getInterfaceIndex = &_getInterfaceIndex, + .getListenAddress = &_getListenAddress, + .getEncapType = &_getEncapType, + .getSocket = &_getSocket +}; + +static void _readcb(int fd, PARCEventType what, void *udpVoid); + +MetisListenerOps * +metisUdpListener_CreateInet6(MetisForwarder *metis, struct sockaddr_in6 sin6) +{ + MetisListenerOps *ops = NULL; + + MetisUdpListener *udp = parcMemory_AllocateAndClear(sizeof(MetisUdpListener)); + assertNotNull(udp, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisUdpListener)); + udp->metis = metis; + udp->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + udp->localAddress = cpiAddress_CreateFromInet6(&sin6); + udp->id = metisForwarder_GetNextConnectionId(metis); + + udp->udp_socket = socket(AF_INET6, SOCK_DGRAM, 0); + assertFalse(udp->udp_socket < 0, "Error opening UDP socket: (%d) %s", errno, strerror(errno)); + + // Set non-blocking flag + int flags = fcntl(udp->udp_socket, F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)", errno); + int failure = fcntl(udp->udp_socket, F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)", errno); + + int one = 1; + // don't hang onto address after listener has closed + failure = setsockopt(udp->udp_socket, SOL_SOCKET, SO_REUSEADDR, (void *) &one, (socklen_t) sizeof(one)); + assertFalse(failure, "failed to set REUSEADDR on socket(%d)", errno); + + failure = bind(udp->udp_socket, (struct sockaddr *) &sin6, sizeof(sin6)); + if (failure == 0) { + udp->udp_event = metisDispatcher_CreateNetworkEvent(metisForwarder_GetDispatcher(metis), true, _readcb, (void *) udp, udp->udp_socket); + metisDispatcher_StartNetworkEvent(metisForwarder_GetDispatcher(metis), udp->udp_event); + + ops = parcMemory_AllocateAndClear(sizeof(MetisListenerOps)); + assertNotNull(ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisListenerOps)); + memcpy(ops, &udpTemplate, sizeof(MetisListenerOps)); + ops->context = udp; + + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + char *str = cpiAddress_ToString(udp->localAddress); + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "UdpListener %p created for address %s", + (void *) udp, str); + parcMemory_Deallocate((void **) &str); + } + } else { + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + int myerrno = errno; + char *str = cpiAddress_ToString(udp->localAddress); + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Error binding UDP socket to address %s: (%d) %s", str, myerrno, strerror(myerrno)); + parcMemory_Deallocate((void **) &str); + } + + close(udp->udp_socket); + cpiAddress_Destroy(&udp->localAddress); + metisLogger_Release(&udp->logger); + parcMemory_Deallocate((void **) &udp); + } + + return ops; +} + +MetisListenerOps * +metisUdpListener_CreateInet(MetisForwarder *metis, struct sockaddr_in sin) +{ + MetisListenerOps *ops = NULL; + + MetisUdpListener *udp = parcMemory_AllocateAndClear(sizeof(MetisUdpListener)); + assertNotNull(udp, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisUdpListener)); + udp->metis = metis; + udp->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + udp->localAddress = cpiAddress_CreateFromInet(&sin); + udp->id = metisForwarder_GetNextConnectionId(metis); + + udp->udp_socket = socket(AF_INET, SOCK_DGRAM, 0); + assertFalse(udp->udp_socket < 0, "Error opening UDP socket: (%d) %s", errno, strerror(errno)); + + // Set non-blocking flag + int flags = fcntl(udp->udp_socket, F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)", errno); + int failure = fcntl(udp->udp_socket, F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)", errno); + + int one = 1; + // don't hang onto address after listener has closed + failure = setsockopt(udp->udp_socket, SOL_SOCKET, SO_REUSEADDR, (void *) &one, (socklen_t) sizeof(one)); + assertFalse(failure, "failed to set REUSEADDR on socket(%d)", errno); + + failure = bind(udp->udp_socket, (struct sockaddr *) &sin, sizeof(sin)); + if (failure == 0) { + udp->udp_event = metisDispatcher_CreateNetworkEvent(metisForwarder_GetDispatcher(metis), true, _readcb, (void *) udp, udp->udp_socket); + metisDispatcher_StartNetworkEvent(metisForwarder_GetDispatcher(metis), udp->udp_event); + + ops = parcMemory_AllocateAndClear(sizeof(MetisListenerOps)); + assertNotNull(ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisListenerOps)); + memcpy(ops, &udpTemplate, sizeof(MetisListenerOps)); + ops->context = udp; + + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + char *str = cpiAddress_ToString(udp->localAddress); + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "UdpListener %p created for address %s", + (void *) udp, str); + parcMemory_Deallocate((void **) &str); + } + } else { + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + int myerrno = errno; + char *str = cpiAddress_ToString(udp->localAddress); + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Error binding UDP socket to address %s: (%d) %s", str, myerrno, strerror(myerrno)); + parcMemory_Deallocate((void **) &str); + } + + close(udp->udp_socket); + cpiAddress_Destroy(&udp->localAddress); + metisLogger_Release(&udp->logger); + parcMemory_Deallocate((void **) &udp); + } + + return ops; +} + +static void +metisUdpListener_Destroy(MetisUdpListener **listenerPtr) +{ + assertNotNull(listenerPtr, "Parameter must be non-null double pointer"); + assertNotNull(*listenerPtr, "Parameter must derefernce to non-null pointer"); + + MetisUdpListener *udp = *listenerPtr; + + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "UdpListener %p destroyed", + (void *) udp); + } + + close(udp->udp_socket); + cpiAddress_Destroy(&udp->localAddress); + metisDispatcher_DestroyNetworkEvent(metisForwarder_GetDispatcher(udp->metis), &udp->udp_event); + metisLogger_Release(&udp->logger); + parcMemory_Deallocate((void **) &udp); + *listenerPtr = NULL; +} + +static void +_destroy(MetisListenerOps **listenerOpsPtr) +{ + MetisListenerOps *ops = *listenerOpsPtr; + MetisUdpListener *udp = (MetisUdpListener *) ops->context; + metisUdpListener_Destroy(&udp); + parcMemory_Deallocate((void **) &ops); + *listenerOpsPtr = NULL; +} + +static unsigned +_getInterfaceIndex(const MetisListenerOps *ops) +{ + MetisUdpListener *udp = (MetisUdpListener *) ops->context; + return udp->id; +} + +static const CPIAddress * +_getListenAddress(const MetisListenerOps *ops) +{ + MetisUdpListener *udp = (MetisUdpListener *) ops->context; + return udp->localAddress; +} + +static MetisEncapType +_getEncapType(const MetisListenerOps *ops) +{ + return METIS_ENCAP_UDP; +} + +static int +_getSocket(const MetisListenerOps *ops) +{ + MetisUdpListener *udp = (MetisUdpListener *) ops->context; + return (int) udp->udp_socket; +} + +static void +_logStats(MetisUdpListener *udp, PARCLogLevel level) +{ + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, level)) { + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, level, __func__, + "UdpListener %p frames in %" PRIu64 ", errors %" PRIu64 " ok %" PRIu64, + (void *) udp, + udp->stats.framesIn, + udp->stats.framesError, + udp->stats.framesReceived); + } +} + + +// ===================================================================== + + +static void _receiveProbeMessage(MetisUdpListener *udp, int fd, uint8_t *pkt, struct sockaddr *peerIpAddress, socklen_t peerIpAddressLength); + + +/** + * @function peekMesageLength + * @abstract Peek at the next packet to learn its length by reading the fixed header + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +static size_t +_peekMessageLength(MetisUdpListener *udp, int fd, struct sockaddr *peerIpAddress, socklen_t *peerIpAddressLengthPtr) +{ + size_t packetLength = 0; + + uint8_t fixedHeader[metisTlv_FixedHeaderLength()]; + + // peek at the UDP packet and read in the fixed header. + // Also returns the socket information for the remote peer + + uint8_t wldr_flag[1]; + ssize_t checkWldrHeader = recvfrom(fd, wldr_flag, 1, MSG_PEEK, (struct sockaddr *) peerIpAddress, peerIpAddressLengthPtr); + if (checkWldrHeader == -1) { + return -1; + } + ssize_t readLength; + if (wldr_flag[0] == WLDR_HEADER) { + //the message contains wldr header + uint8_t tmp[metisTlv_FixedHeaderLength() + WLDR_HEADER_SIZE]; + readLength = recvfrom(fd, tmp, metisTlv_FixedHeaderLength() + WLDR_HEADER_SIZE, MSG_PEEK, (struct sockaddr *) peerIpAddress, peerIpAddressLengthPtr); + if (readLength == (metisTlv_FixedHeaderLength() + WLDR_HEADER_SIZE)) { + for (int i = 6; i < metisTlv_FixedHeaderLength() + WLDR_HEADER_SIZE; ++i) { + fixedHeader[i - WLDR_HEADER_SIZE] = tmp[i]; + } + readLength = metisTlv_FixedHeaderLength(); + } else { + readLength = 0; + } + } else { + readLength = recvfrom(fd, fixedHeader, metisTlv_FixedHeaderLength(), MSG_PEEK, (struct sockaddr *) peerIpAddress, peerIpAddressLengthPtr); + } + + if (readLength == -1) { + return -1; + } + + if (readLength == metisTlv_FixedHeaderLength()) { + packetLength = metisTlv_TotalPacketLength(fixedHeader); + if (packetLength == 0) { + uint8_t *pkt; + pkt = (uint8_t *) malloc(sizeof(uint8_t) * metisTlv_FixedHeaderLength()); + for (unsigned i = 0; i < metisTlv_FixedHeaderLength(); i++) { + pkt[i] = fixedHeader[i]; + } + + _receiveProbeMessage(udp, fd, pkt, peerIpAddress, *peerIpAddressLengthPtr); + + free(pkt); + } + } else { + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning)) { + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "read %zu bytes from fd %d, wrong size for a FixedHeader", + readLength, + fd); + } + + if (readLength < 0) { + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Error reading fd %d: (%d) %s", fd, errno, strerror(errno)); + } + } + } + + if (wldr_flag[0] == WLDR_HEADER) { + return packetLength + WLDR_HEADER_SIZE; + } else { + return packetLength; + } +} + +static MetisMessage * +_readMessage(MetisForwarder *metis, unsigned connid, int fd, size_t packetLength) +{ + PARCEventBuffer *readbuffer = parcEventBuffer_Create(); + int readLength = parcEventBuffer_ReadFromFileDescriptor(readbuffer, fd, packetLength); + + MetisMessage *message = NULL; + if (readLength == packetLength) { + message = metisMessage_CreateFromBuffer(connid, metisForwarder_GetTicks(metis), readbuffer, metisForwarder_GetLogger(metis)); + + // because metisMessage_CreateFromBuffer takes ownership of readbuffer, if there is + // an error and it returns null, metisMessage_CreateFromBuffer will destroy the readbuffer. + } else { + parcEventBuffer_Destroy(&readbuffer); + + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Warning)) { + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "read %d bytes from fd %d, expected %zu", + readLength, + fd, + packetLength); + } + + if (readLength < 0) { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Error reading fd %d: (%d) %s", fd, errno, strerror(errno)); + } + } + } + + return message; +} + +/** + * @function _constructAddressPair + * @abstract Creates the address pair that uniquely identifies the connection + * @discussion + * The peerIpAddress must be of AF_INET or AF_INET6 family. + * + * @param <#param1#> + * @return Allocated MetisAddressPair, must be destroyed + */ +static MetisAddressPair * +_constructAddressPair(MetisUdpListener *udp, struct sockaddr *peerIpAddress, socklen_t peerIpAddressLength) +{ + CPIAddress *remoteAddress; + + switch (peerIpAddress->sa_family) { + case AF_INET: + remoteAddress = cpiAddress_CreateFromInet((struct sockaddr_in *) peerIpAddress); + break; + + case AF_INET6: + remoteAddress = cpiAddress_CreateFromInet6((struct sockaddr_in6 *) peerIpAddress); + break; + + default: + trapIllegalValue(peerIpAddress, "Peer address unrecognized family for IP: %d", peerIpAddress->sa_family); + } + + MetisAddressPair *pair = metisAddressPair_Create(udp->localAddress, remoteAddress); + cpiAddress_Destroy(&remoteAddress); + + return pair; +} + +/** + * @function _lookupConnectionId + * @abstract Lookup a connection in the connection table + * @discussion + * Looks up the connection in the connection table and returns the connection id if it exists. + * + * @param outputConnectionIdPtr is the output parameter + * @return true if connection found and outputConnectionIdPtr set + */ +static bool +_lookupConnectionId(MetisUdpListener *udp, MetisAddressPair *pair, unsigned *outputConnectionIdPtr) +{ + MetisConnectionTable *connTable = metisForwarder_GetConnectionTable(udp->metis); + + const MetisConnection *conn = metisConnectionTable_FindByAddressPair(connTable, pair); + if (conn) { + *outputConnectionIdPtr = metisConnection_GetConnectionId(conn); + return true; + } + + return false; +} + +/** + * @function _createNewConnection + * @abstract Creates a new Metis connection for the peer + * @discussion + * PRECONDITION: you know there's not an existing connection with the address pair + * + * Creates a new connection and adds it to the connection table. + * + * @param <#param1#> + * @return The connection id for the new connection + */ + +//for the moment this is not used anymore +//we need to handle the connection tables in a better way in order to avoid multiple connections +//with the same address pair. +/*static unsigned + * _createNewConnection(MetisUdpListener *udp, int fd, const MetisAddressPair *pair) + * { + * bool isLocal = false; + * + * // metisUdpConnection_Create takes ownership of the pair + * MetisIoOperations *ops = metisUdpConnection_Create(udp->metis, fd, pair, isLocal); + * MetisConnection *conn = metisConnection_Create(ops); + * + * metisConnectionTable_Add(metisForwarder_GetConnectionTable(udp->metis), conn); + * unsigned connid = metisIoOperations_GetConnectionId(ops); + * + * return connid; + * }*/ + +static void +_receivePacket(MetisUdpListener *udp, int fd, size_t packetLength, struct sockaddr_storage *peerIpAddress, socklen_t peerIpAddressLength) +{ + unsigned connid = 0; + MetisAddressPair *pair = _constructAddressPair(udp, (struct sockaddr *) peerIpAddress, peerIpAddressLength); + bool foundConnection = _lookupConnectionId(udp, pair, &connid); + + if (!foundConnection) { + PARCEventBuffer *readbuffer = parcEventBuffer_Create(); + parcEventBuffer_ReadFromFileDescriptor(readbuffer, fd, packetLength); + parcEventBuffer_Destroy(&readbuffer); + metisAddressPair_Release(&pair); + return; + //connid = _createNewConnection(udp, fd, pair); + } + + metisAddressPair_Release(&pair); + + MetisMessage *message = _readMessage(udp->metis, connid, fd, packetLength); + + if (message) { + udp->stats.framesReceived++; + + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "read %zu bytes from fd %d sa %s:%d connid %d", + metisMessage_Length(message), + fd, + inet_ntoa(((struct sockaddr_in *) peerIpAddress)->sin_addr), + ntohs(((struct sockaddr_in *) peerIpAddress)->sin_port), + connid); + } + + _logStats(udp, PARCLogLevel_Debug); + + metisForwarder_Receive(udp->metis, message); + } else { + udp->stats.framesError++; + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning)) { + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "Could not parse frame from fd %d, discarding", fd); + } + _logStats(udp, PARCLogLevel_Warning); + } +} + + +static void +_readFrameToDiscard(MetisUdpListener *udp, int fd) +{ + // we need to discard the frame. Read 1 byte. This will clear it off the stack. + uint8_t buffer; + ssize_t nread = read(fd, &buffer, 1); + + udp->stats.framesError++; + + if (nread == 1) { + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Discarded frame from fd %d", fd); + } + _logStats(udp, PARCLogLevel_Debug); + } else if (nread < 0) { + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Error trying to discard frame from fd %d: (%d) %s", fd, errno, strerror(errno)); + } + _logStats(udp, PARCLogLevel_Error); + } +} + +static void +_receiveProbeMessage(MetisUdpListener *udp, int fd, uint8_t *pkt, struct sockaddr *peerIpAddress, socklen_t peerIpAddressLength) +{ + MetisAddressPair *pair = _constructAddressPair(udp, peerIpAddress, peerIpAddressLength); + MetisConnectionTable *connTable = metisForwarder_GetConnectionTable(udp->metis); + const MetisConnection *conn = metisConnectionTable_FindByAddressPair(connTable, pair); + + if (conn == NULL) { + metisAddressPair_Release(&pair); + return; //we discard probes coming from connections that we don't know. this should never happen. + } + + metisAddressPair_Release(&pair); + + //handle the probe. + metisConnection_HandleProbe((MetisConnection *) conn, pkt, metisForwarder_GetTicks(udp->metis)); +} + + +static void +_readcb(int fd, PARCEventType what, void *udpVoid) +{ + MetisUdpListener *udp = (MetisUdpListener *) udpVoid; + + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "%s socket %d what %s%s%s%s data %p", + __func__, fd, + (what & PARCEventType_Timeout) ? " timeout" : "", + (what & PARCEventType_Read) ? " read" : "", + (what & PARCEventType_Write) ? " write" : "", + (what & PARCEventType_Signal) ? " signal" : "", + udpVoid); + } + + if (what & PARCEventType_Read) { + udp->stats.framesIn++; + struct sockaddr_storage peerIpAddress; + socklen_t peerIpAddressLength = sizeof(peerIpAddress); + + size_t packetLength = _peekMessageLength(udp, fd, (struct sockaddr *) &peerIpAddress, &peerIpAddressLength); + + if (packetLength > 0) { + _receivePacket(udp, fd, packetLength, &peerIpAddress, peerIpAddressLength); + } else { + _readFrameToDiscard(udp, fd); + } + } +} diff --git a/metis/ccnx/forwarder/metis/io/metis_UdpListener.h b/metis/ccnx/forwarder/metis/io/metis_UdpListener.h new file mode 100644 index 00000000..d4dc129d --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_UdpListener.h @@ -0,0 +1,31 @@ +/* + * 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_UdpListener_h +#define Metis_metis_UdpListener_h + +#include <netinet/in.h> +#include <stdlib.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/io/metis_Listener.h> + +struct metis_udp_listener; +typedef struct metis_udp_listener MetisUdpListener; + +MetisListenerOps *metisUdpListener_CreateInet6(MetisForwarder *metis, struct sockaddr_in6 sin6); +MetisListenerOps *metisUdpListener_CreateInet(MetisForwarder *metis, struct sockaddr_in sin); +#endif // Metis_metis_UdpListener_h diff --git a/metis/ccnx/forwarder/metis/io/metis_UdpTunnel.c b/metis/ccnx/forwarder/metis/io/metis_UdpTunnel.c new file mode 100644 index 00000000..77424dce --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_UdpTunnel.c @@ -0,0 +1,90 @@ +/* + * 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 <stdbool.h> +#include <string.h> +#include <errno.h> + +#include <ccnx/forwarder/metis/io/metis_UdpTunnel.h> +#include <ccnx/forwarder/metis/io/metis_UdpConnection.h> + +#include <LongBow/runtime.h> + +MetisIoOperations * +metisUdpTunnel_CreateOnListener(MetisForwarder *metis, MetisListenerOps *localListener, const CPIAddress *remoteAddress) +{ + assertNotNull(metis, "Parameter metis must be non-null"); + assertNotNull(localListener, "Parameter localListener must be non-null"); + assertNotNull(remoteAddress, "Parameter remoteAddress must be non-null"); + + MetisLogger *logger = metisForwarder_GetLogger(metis); + + MetisIoOperations *ops = NULL; + if (localListener->getEncapType(localListener) == METIS_ENCAP_UDP) { + const CPIAddress *localAddress = localListener->getListenAddress(localListener); + CPIAddressType localType = cpiAddress_GetType(localAddress); + CPIAddressType remoteType = cpiAddress_GetType(remoteAddress); + + if (localType == remoteType) { + MetisAddressPair *pair = metisAddressPair_Create(localAddress, remoteAddress); + bool isLocal = false; + int fd = localListener->getSocket(localListener); + ops = metisUdpConnection_Create(metis, fd, pair, isLocal); + + metisAddressPair_Release(&pair); + } else { + if (metisLogger_IsLoggable(logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Local listener of type %s and remote type %s, cannot establish tunnel", + cpiAddress_TypeToString(localType), + cpiAddress_TypeToString(remoteType)); + } + } + } else { + if (metisLogger_IsLoggable(logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Local listener %p is not type UDP, cannot establish tunnel", (void *) localListener); + } + } + + return ops; +} + +/* + * wrapper for metisUdpTunnel_CreateOnListener. Lookup to see if we have a listener on the local address. + * If so, call metisUdpTunnel_CreateOnListener, otherwise return NULL + */ +MetisIoOperations * +metisUdpTunnel_Create(MetisForwarder *metis, const CPIAddress *localAddress, const CPIAddress *remoteAddress) +{ + MetisListenerSet *set = metisForwarder_GetListenerSet(metis); + MetisListenerOps *listener = metisListenerSet_Find(set, METIS_ENCAP_UDP, localAddress); + MetisIoOperations *ops = NULL; + if (listener) { + ops = metisUdpTunnel_CreateOnListener(metis, listener, remoteAddress); + } else { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error)) { + char *str = cpiAddress_ToString(localAddress); + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Could not find listener to match address %s", str); + parcMemory_Deallocate((void **) &str); + } + } + return ops; +} + diff --git a/metis/ccnx/forwarder/metis/io/metis_UdpTunnel.h b/metis/ccnx/forwarder/metis/io/metis_UdpTunnel.h new file mode 100644 index 00000000..8043da7c --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_UdpTunnel.h @@ -0,0 +1,80 @@ +/* + * 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_UdpTunnel.h + * @brief Establish a tunnel to a remote system + * + * Creates a "udp tunnel" to a remote system. There must already be a local UDP listener for the + * local side of the connection. Because UDP is connectionless and we do not have a link protocol, + * the udp tunnel will go in the connection table immediately in the "up" state. + * + */ + +#ifndef Metis_metis_UdpTunnel_h +#define Metis_metis_UdpTunnel_h + +#include <ccnx/api/control/cpi_Address.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/io/metis_Listener.h> +#include <ccnx/forwarder/metis/io/metis_IoOperations.h> + +/** + * Establishes a connection to a remote system over UDP + * + * The remoteAddress must be of the same type (i.e. v4 or v6) as the localListener. + * + * The connection will go in the table immediately, and will be in the "up" state. + * + * + * @param [in] metis An allocated MetisForwarder + * @param [in] localListener The local receiver for UDP messages + * @param [in] remote Address the remote IP address for the connection, must include a destination port. + * + * @retval non-null An allocated Io Operations structure for the connection + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisIoOperations *metisUdpTunnel_CreateOnListener(MetisForwarder *metis, MetisListenerOps *localListener, const CPIAddress *remoteAddress); + +/** + * Establishes a connection to a remote system over UDP + * + * The remoteAddress must be of the same type (i.e. v4 or v6) as the localAddress. There must be an existing UDP listener + * on the local address. If either of these are not true, will return NULL. + * + * The connection will go in the table immediately, and will be in the "up" state. + * + * This function will lookup the appropraite listener, then use metisUdpTunnel_CreateOnListener(). + * + * @param [in] metis An allocated MetisForwarder + * @param [in] localAddress The local IP address and port to use for the connection + * @param [in] remote Address the remote IP address for the connection, must include a destination port. + * + * @retval non-null An allocated Io Operations structure for the connection + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisIoOperations *metisUdpTunnel_Create(MetisForwarder *metis, const CPIAddress *localAddress, const CPIAddress *remoteAddress); + +#endif // Metis_metis_UdpTunnel_h diff --git a/metis/ccnx/forwarder/metis/io/test/CMakeLists.txt b/metis/ccnx/forwarder/metis/io/test/CMakeLists.txt new file mode 100644 index 00000000..1e22e32e --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/CMakeLists.txt @@ -0,0 +1,25 @@ +# Enable gcov output for the tests +add_definitions(--coverage) +set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} " --coverage") + +set(TestsExpectedToPass + test_metis_AddressPair + test_metis_EtherConnection + test_metis_EtherListener + test_metis_HopByHopFragmenter + test_metis_IPMulticastListener + test_metis_ListenerSet + test_metis_LocalListener + test_metis_StreamConnection + test_metis_TcpListener + test_metis_TcpTunnel + test_metis_UdpConnection + test_metis_UdpListener + test_metis_UdpTunnel +) + + +foreach(test ${TestsExpectedToPass}) + AddTest(${test}) +endforeach() + diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_AddressPair.c b/metis/ccnx/forwarder/metis/io/test/test_metis_AddressPair.c new file mode 100644 index 00000000..34ff9d9d --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_AddressPair.c @@ -0,0 +1,153 @@ +/* + * 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_AddressPair.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +LONGBOW_TEST_RUNNER(metis_AddressPair) +{ + // 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_AddressPair) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_AddressPair) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisAddressPair_Create_Destroy); + LONGBOW_RUN_TEST_CASE(Global, metisAddressPair_Equals); + LONGBOW_RUN_TEST_CASE(Global, metisAddressPair_Equals_NotEqual); + LONGBOW_RUN_TEST_CASE(Global, metisAddressPair_EqualsAddresses); +} + +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, metisAddressPair_Create_Destroy) +{ + CPIAddress *a = cpiAddress_CreateFromInterface(1); + CPIAddress *b = cpiAddress_CreateFromInterface(2); + + size_t allocbase = parcSafeMemory_Outstanding(); + MetisAddressPair *pair = metisAddressPair_Create(a, b); + metisAddressPair_Release(&pair); + + assertTrue(parcSafeMemory_Outstanding() == allocbase, + "Memory out of balance, expected %zu got %u", + allocbase, + parcSafeMemory_Outstanding()); + + cpiAddress_Destroy(&a); + cpiAddress_Destroy(&b); +} + +LONGBOW_TEST_CASE(Global, metisAddressPair_Equals) +{ + CPIAddress *a = cpiAddress_CreateFromInterface(1); + CPIAddress *b = cpiAddress_CreateFromInterface(2); + + MetisAddressPair *pair_a = metisAddressPair_Create(a, b); + MetisAddressPair *pair_b = metisAddressPair_Create(a, b); + + assertTrue(metisAddressPair_Equals(pair_a, pair_b), "Two equal address pairs did not compare equal"); + + metisAddressPair_Release(&pair_a); + metisAddressPair_Release(&pair_b); + + cpiAddress_Destroy(&a); + cpiAddress_Destroy(&b); +} + +LONGBOW_TEST_CASE(Global, metisAddressPair_Equals_NotEqual) +{ + CPIAddress *a = cpiAddress_CreateFromInterface(1); + CPIAddress *b = cpiAddress_CreateFromInterface(2); + + MetisAddressPair *pair_a = metisAddressPair_Create(a, b); + MetisAddressPair *pair_b = metisAddressPair_Create(b, a); + + assertFalse(metisAddressPair_Equals(pair_a, pair_b), "Two unequal address pairs compared equal"); + + metisAddressPair_Release(&pair_a); + metisAddressPair_Release(&pair_b); + cpiAddress_Destroy(&a); + cpiAddress_Destroy(&b); +} + +LONGBOW_TEST_CASE(Global, metisAddressPair_EqualsAddresses) +{ + CPIAddress *a = cpiAddress_CreateFromInterface(1); + CPIAddress *b = cpiAddress_CreateFromInterface(2); + + MetisAddressPair *pair_a = metisAddressPair_Create(a, b); + + assertTrue(metisAddressPair_EqualsAddresses(pair_a, a, b), "Two equal address pairs did not compare equal"); + + metisAddressPair_Release(&pair_a); + cpiAddress_Destroy(&a); + cpiAddress_Destroy(&b); +} + +LONGBOW_TEST_FIXTURE(Local) +{ +} + +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; +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_AddressPair); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_EtherConnection.c b/metis/ccnx/forwarder/metis/io/test/test_metis_EtherConnection.c new file mode 100644 index 00000000..081903b1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_EtherConnection.c @@ -0,0 +1,271 @@ +/* + * 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 "testrig_GenericEther.c" +#include "../metis_EtherConnection.c" + +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h> + +typedef struct test_data { + MetisForwarder *metis; + MetisGenericEther *ether; + MetisAddressPair *pair; + MetisIoOperations *io_ops; +} TestData; + +static void +commonSetup(const LongBowTestCase *testCase, uint16_t ethertype) +{ + TestData *data = parcMemory_AllocateAndClear(sizeof(TestData)); + + data->metis = metisForwarder_Create(NULL); + data->ether = metisGenericEther_Create(data->metis, "foo", ethertype); + data->io_ops = NULL; + + // crank the libevent handle + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis), &((struct timeval) {0, 10000})); + + PARCBuffer *localMacBuffer = metisGenericEther_GetMacAddress(data->ether); + CPIAddress *local = cpiAddress_CreateFromLink(parcBuffer_Overlay(localMacBuffer, 0), parcBuffer_Remaining(localMacBuffer)); + CPIAddress *remote = cpiAddress_CreateFromLink((uint8_t []) { 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }, 6); + + data->pair = metisAddressPair_Create(local, remote); + + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); + + longBowTestCase_SetClipBoardData(testCase, data); +} + +static void +commonTeardown(const LongBowTestCase *testCase) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis), &((struct timeval) {0, 10000})); + metisGenericEther_Release(&data->ether); + metisAddressPair_Release(&data->pair); + + if (data->io_ops) { + data->io_ops->destroy(&data->io_ops); + } + + // destroy metis last + metisForwarder_Destroy(&data->metis); + parcMemory_Deallocate((void **) &data); +} + + +LONGBOW_TEST_RUNNER(metis_EtherConnection) +{ + // 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_EtherConnection) +{ + 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_EtherConnection) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// =========================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisEtherConnection_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + commonSetup(testCase, 0x0801); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + commonTeardown(testCase); + 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, metisEtherConnection_Create) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + data->io_ops = metisEtherConnection_Create(data->metis, data->ether, data->pair); + assertNotNull(data->io_ops, "Got null MetisIoOperations from metisEtherConnection_Create"); +} + +// =========================================== + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_DestroyOperations); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_FillInMacAddress); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_GetAddressPair); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_GetConnectionId); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_GetRemoteAddress); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_IsLocal); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_IsUp); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_Send); + LONGBOW_RUN_TEST_CASE(Local, _setConnectionState); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_getConnectionType); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + commonSetup(testCase, 0x0801); + + // for the local tests we also pre-create the EtherConnection + TestData *data = longBowTestCase_GetClipBoardData(testCase); + data->io_ops = metisEtherConnection_Create(data->metis, data->ether, data->pair); + + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + commonTeardown(testCase); + 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, _metisEtherConnection_DestroyOperations) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_FillInMacAddress) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_GetAddressPair) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherState *etherConn = (_MetisEtherState *) metisIoOperations_GetClosure(data->io_ops); + + const MetisAddressPair *test = _metisEtherConnection_GetAddressPair(data->io_ops); + + assertTrue(metisAddressPair_Equals(test, etherConn->addressPair), "Address pair did not compare"); +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_GetConnectionId) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherState *etherConn = (_MetisEtherState *) metisIoOperations_GetClosure(data->io_ops); + unsigned connid = _metisEtherConnection_GetConnectionId(data->io_ops); + + assertTrue(connid == etherConn->id, "Wrong connection id, got %u exepected %u", connid, etherConn->id); +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_GetRemoteAddress) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + const CPIAddress *test = _metisEtherConnection_GetRemoteAddress(data->io_ops); + + _MetisEtherState *etherConn = (_MetisEtherState *) metisIoOperations_GetClosure(data->io_ops); + + assertTrue(cpiAddress_Equals(test, metisAddressPair_GetRemote(etherConn->addressPair)), "Remote addresses did not compare"); +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_IsLocal) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + bool isLocal = _metisEtherConnection_IsLocal(data->io_ops); + assertFalse(isLocal, "Ethernet should always be remote"); +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_IsUp) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + bool isUp = _metisEtherConnection_IsUp(data->io_ops); + assertTrue(isUp, "Ethernet should be up"); +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_Send) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest), 1, 2, metisForwarder_GetLogger(data->metis)); + + bool success = _metisEtherConnection_Send(data->io_ops, NULL, message); + assertTrue(success, "Failed to write message to ethernet"); + + // we should now be able to read the ethernet frame from the test socket + int testSocket = mockGenericEther_GetTestDescriptor(data->ether); + assertTrue(testSocket > 0, "Error getting test socket from mock ethernet"); + + uint32_t testBufferSize = 2048; + uint8_t testBuffer[testBufferSize]; + ssize_t bytesRead = read(testSocket, testBuffer, testBufferSize); + + size_t expectedRead = sizeof(struct ether_header) + sizeof(metisTestDataV0_EncodedInterest); + + assertTrue(bytesRead == expectedRead, "Wrong read size, got %zd expected %zu", bytesRead, expectedRead); + + uint8_t *frame = testBuffer + sizeof(struct ether_header); + assertTrue(memcmp(frame, metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest)) == 0, "Buffers do not match"); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Local, _setConnectionState) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherState *etherConn = (_MetisEtherState *) metisIoOperations_GetClosure(data->io_ops); + + _setConnectionState(etherConn, false); +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_getConnectionType) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + CPIConnectionType connType = _metisEtherConnection_getConnectionType(data->io_ops); + + assertTrue(connType == cpiConnection_L2, "Wrong connection type expected %d got %d", cpiConnection_L2, connType); +} + +// =========================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_EtherConnection); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_EtherListener.c b/metis/ccnx/forwarder/metis/io/test/test_metis_EtherListener.c new file mode 100644 index 00000000..2ae7596c --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_EtherListener.c @@ -0,0 +1,387 @@ +/* + * 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. + */ + + +// force the use of the generic ethernet mockup +#include "testrig_GenericEther.c" +#include "../metis_EtherListener.c" +#include "testrig_GenericEther.h" + +#include <parc/algol/parc_SafeMemory.h> + +#include <LongBow/unit-test.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h> + + +typedef struct test_data { + MetisForwarder *metis; + MetisListenerOps *ops; +} TestData; + +static void +commonSetup(const LongBowTestCase *testCase, uint16_t ethertype) +{ + TestData *data = parcMemory_AllocateAndClear(sizeof(TestData)); + + data->metis = metisForwarder_Create(NULL); + data->ops = metisEtherListener_Create(data->metis, "test0", ethertype); + + // crank the libevent handle + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis), &((struct timeval) {0, 10000})); + + longBowTestCase_SetClipBoardData(testCase, data); +} + +static void +commonTeardown(const LongBowTestCase *testCase) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis), &((struct timeval) {0, 10000})); + data->ops->destroy(&data->ops); + metisForwarder_Destroy(&data->metis); + + parcMemory_Deallocate((void **) &data); +} + +// ============================================================ + +LONGBOW_TEST_RUNNER(metis_EtherListener) +{ + // 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_EtherListener) +{ + 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_EtherListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ============================================================ + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisEtherListener_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + commonSetup(testCase, 0x0801); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + commonTeardown(testCase); + 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, metisEtherListener_Create) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + assertNotNull(data->ops, "Null return from metisEtherListener_Create"); +} + +// ============================================================ + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisEtherListener_Destroy); + LONGBOW_RUN_TEST_CASE(Local, metisEtherListener_OpsDestroy); + LONGBOW_RUN_TEST_CASE(Local, metisEtherListener_OpsGetInterfaceIndex); + LONGBOW_RUN_TEST_CASE(Local, metisEtherListener_OpsGetListenAddress); + LONGBOW_RUN_TEST_CASE(Local, metisEtherListener_OpsGetEncapType); + + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_ReadCallback); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_ReadCallback_FragmentBegin); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_ReadCallback_FragmentEnd); + + + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_ReadEtherFrame_EmptyQueue); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_ReadEtherFrame_PacketWaiting); + + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_IsOurSourceAddress_True); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_IsOurSourceAddress_False); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_IsOurDestinationAddress_Unicast); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_IsOurDestinationAddress_Group); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_IsOurDestinationAddress_Broadcast); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_IsOurDestinationAddress_False); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_IsOurProtocol); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_ParseEtherFrame); + + + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_FillInEthernetAddresses); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_ReleaseEthernetAddresses); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + commonSetup(testCase, 0x0801); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + commonTeardown(testCase); + 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, metisEtherListener_Destroy) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisListenerOps *ops = metisEtherListener_Create(data->metis, "fake0", 0x0801); + + _metisEtherListener_Destroy((_MetisEtherListener **) &ops->context); + assertNull(ops->context, "Destory did not null context"); + parcMemory_Deallocate((void **) &ops); +} + +LONGBOW_TEST_CASE(Local, metisEtherListener_OpsDestroy) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisListenerOps *ops = metisEtherListener_Create(data->metis, "fake1", 0x0801); + _metisEtherListener_OpsDestroy(&ops); + assertNull(ops, "OpsDestroy did not null ops"); +} + +LONGBOW_TEST_CASE(Local, metisEtherListener_OpsGetInterfaceIndex) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, metisEtherListener_OpsGetListenAddress) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, metisEtherListener_OpsGetEncapType) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_ReadCallback) +{ + testUnimplemented(""); +} + +/* + * Read only a B frame, so its not a complete reassembly + */ +LONGBOW_TEST_CASE(Local, _metisEtherListener_ReadCallback_FragmentBegin) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + uint8_t headerArray[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x08, 0x01 }; + + PARCBuffer *frameBuffer = parcBuffer_Allocate(sizeof(struct ether_header) + sizeof(metisTestDataV1_HopByHopFrag_Begin)); + parcBuffer_PutArray(frameBuffer, sizeof(struct ether_header), headerArray); + parcBuffer_PutArray(frameBuffer, sizeof(metisTestDataV1_HopByHopFrag_Begin), metisTestDataV1_HopByHopFrag_Begin); + + parcBuffer_Flip(frameBuffer); + + mockGenericEther_QueueFrame(etherListener->genericEther, frameBuffer); + + _metisEtherListener_ReadCallback(0, PARCEventType_Read, data->ops->context); + + assertTrue(etherListener->stats.framesIn == 1, "Wrong framesIn count, expected 1 got %" PRIu64, etherListener->stats.framesIn); + assertTrue(etherListener->stats.framesReceived == 1, "Wrong framesReceived count, expected 1 got %" PRIu64, etherListener->stats.framesReceived); + assertTrue(etherListener->stats.framesReassembled == 0, "Wrong framesReassembled count, expected 0 got %" PRIu64, etherListener->stats.framesReassembled); + + parcBuffer_Release(&frameBuffer); +} + +/* + * Read a B and middle and E frame, so it is complete reassembly + */ +LONGBOW_TEST_CASE(Local, _metisEtherListener_ReadCallback_FragmentEnd) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + uint8_t headerArray[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x08, 0x01 }; + + PARCBuffer *frameBuffer = parcBuffer_Allocate(sizeof(struct ether_header) + sizeof(metisTestDataV1_HopByHopFrag_BeginEnd)); + parcBuffer_PutArray(frameBuffer, sizeof(struct ether_header), headerArray); + parcBuffer_PutArray(frameBuffer, sizeof(metisTestDataV1_HopByHopFrag_BeginEnd), metisTestDataV1_HopByHopFrag_BeginEnd); + + parcBuffer_Flip(frameBuffer); + + mockGenericEther_QueueFrame(etherListener->genericEther, frameBuffer); + + _metisEtherListener_ReadCallback(0, PARCEventType_Read, data->ops->context); + + assertTrue(etherListener->stats.framesIn == 1, "Wrong framesIn count, expected 1 got %" PRIu64, etherListener->stats.framesIn); + assertTrue(etherListener->stats.framesReceived == 1, "Wrong framesReceived count, expected 1 got %" PRIu64, etherListener->stats.framesReceived); + assertTrue(etherListener->stats.framesReassembled == 1, "Wrong framesReassembled count, expected 1 got %" PRIu64, etherListener->stats.framesReassembled); + + parcBuffer_Release(&frameBuffer); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_ReadEtherFrame_PacketWaiting) +{ + // create a frame and queue it + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + // Ethernet frame addressed to us with a 0-length CCNx TLV packet (i.e. just the fixed header) + uint8_t frame[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 't', 'e', 's', 't', '0', 0xA0, 0x08, 0x01, + 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 }; + + PARCBuffer *frameBuffer = parcBuffer_Wrap(frame, sizeof(frame), 0, sizeof(frame)); + + mockGenericEther_QueueFrame(etherListener->genericEther, frameBuffer); + + PARCEventBuffer *buffer = _metisEtherListener_ReadEtherFrame(etherListener); + assertNotNull(buffer, "Got null buffer from ReadEtherFrame with a frame queued"); + + assertTrue(parcEventBuffer_GetLength(buffer) == sizeof(frame), "Wrong size, got %zu expected %zu", parcEventBuffer_GetLength(buffer), sizeof(frame)); + + parcEventBuffer_Destroy(&buffer); + parcBuffer_Release(&frameBuffer); +} + + +LONGBOW_TEST_CASE(Local, _metisEtherListener_ReadEtherFrame_EmptyQueue) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + PARCEventBuffer *buffer = _metisEtherListener_ReadEtherFrame(etherListener); + assertNull(buffer, "Should get null buffer from ReadEtherFrame without a frame queued"); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_IsOurSourceAddress_True) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + uint8_t frame[] = { 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 't', 'e', 's', 't', '0', 0x06, 0x08, 0x01 }; + struct ether_header *header = (struct ether_header *) frame; + + bool success = _metisEtherListener_IsOurSourceAddress(etherListener, header); + assertTrue(success, "Did not match our source address."); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_IsOurSourceAddress_False) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + uint8_t frame[] = { 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x11, 0x22, 0x33, 0x44, 0x05, 0x06, 0x08, 0x01 }; + struct ether_header *header = (struct ether_header *) frame; + + bool success = _metisEtherListener_IsOurSourceAddress(etherListener, header); + assertFalse(success, "Should not match our address."); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_IsOurDestinationAddress_Unicast) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + uint8_t frame[] = { 't', 'e', 's', 't', '0', 0x06, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x08, 0x01 }; + struct ether_header *header = (struct ether_header *) frame; + + bool success = _metisEtherListener_IsOurDestinationAddress(etherListener, header); + assertTrue(success, "Did not match our destination address."); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_IsOurDestinationAddress_Group) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + uint8_t frame[] = { 0x01, 0x00, 0x5e, 0x00, 0x17, 0xaa, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x08, 0x01 }; + struct ether_header *header = (struct ether_header *) frame; + + bool success = _metisEtherListener_IsOurDestinationAddress(etherListener, header); + assertTrue(success, "Did not match group address."); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_IsOurDestinationAddress_Broadcast) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + uint8_t frame[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x08, 0x01 }; + struct ether_header *header = (struct ether_header *) frame; + + bool success = _metisEtherListener_IsOurDestinationAddress(etherListener, header); + assertTrue(success, "Did not match broadcast address."); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_IsOurDestinationAddress_False) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + uint8_t frame[] = { 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x08, 0x01 }; + struct ether_header *header = (struct ether_header *) frame; + + bool success = _metisEtherListener_IsOurDestinationAddress(etherListener, header); + assertFalse(success, "Should not match one of our addresses."); +} + + +LONGBOW_TEST_CASE(Local, _metisEtherListener_IsOurProtocol) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_ParseEtherFrame) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_FillInEthernetAddresses) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_ReleaseEthernetAddresses) +{ + testUnimplemented(""); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_EtherListener); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_HopByHopFragmenter.c b/metis/ccnx/forwarder/metis/io/test/test_metis_HopByHopFragmenter.c new file mode 100644 index 00000000..5be165c5 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_HopByHopFragmenter.c @@ -0,0 +1,1087 @@ +/* + * 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 "../metis_HopByHopFragmenter.c" + +#include <parc/algol/parc_SafeMemory.h> +#include <parc/logging/parc_LogReporterTextStdout.h> + +#include <LongBow/unit-test.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h> + +typedef struct test_data { + unsigned mtu; + MetisLogger *logger; + MetisHopByHopFragmenter *fragmenter; +} TestData; + +static TestData * +_createTestData(void) +{ + TestData *data = parcMemory_Allocate(sizeof(TestData)); + + data->mtu = 2000; + + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + data->logger = metisLogger_Create(reporter, parcClock_Wallclock()); + metisLogger_SetLogLevel(data->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug); + parcLogReporter_Release(&reporter); + data->fragmenter = metisHopByHopFragmenter_Create(data->logger, data->mtu); + return data; +} + +static void +_destroyTestData(TestData **dataPtr) +{ + TestData *data = *dataPtr; + metisHopByHopFragmenter_Release(&data->fragmenter); + metisLogger_Release(&data->logger); + parcMemory_Deallocate((void **) dataPtr); +} + +/* + * Create a well-formed packet with the given length. + * length is the total packet length, including fixed header. + */ +static uint8_t * +_conjurePacket(size_t length) +{ + _HopByHopHeader header; + memset(&header, 0, sizeof(header)); + + size_t payloadLength = length - sizeof(header); + + header.version = 1; + header.packetType = 2; // interest return -- does not require a name. + header.packetLength = htons(length); + header.headerLength = 8; + header.tlvType = 0; + header.tlvLength = htons(payloadLength); + + uint8_t *packet = parcMemory_Allocate(length); + memcpy(packet, &header, sizeof(header)); + return packet; +} + +// ============================================================ + +LONGBOW_TEST_RUNNER(metis_HopByHopFragmenter) +{ + 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_HopByHopFragmenter) +{ + 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_HopByHopFragmenter) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ============================================================ + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisHopByHopFragmenter_Create); + LONGBOW_RUN_TEST_CASE(Global, metisHopByHopFragmenter_Receive_NotHopByHop); + LONGBOW_RUN_TEST_CASE(Global, metisHopByHopFragmenter_Receive_ReceiveQueueFull); + LONGBOW_RUN_TEST_CASE(Global, metisHopByHopFragmenter_Receive_Ok); + + LONGBOW_RUN_TEST_CASE(Global, metisHopByHopFragmenter_Send_OneMtu); + LONGBOW_RUN_TEST_CASE(Global, metisHopByHopFragmenter_Send_ReceiveQueueFull); + LONGBOW_RUN_TEST_CASE(Global, metisHopByHopFragmenter_Send_Ok); +} + + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + TestData *data = _createTestData(); + longBowTestCase_SetClipBoardData(testCase, data); + + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _destroyTestData(&data); + + 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, metisHopByHopFragmenter_Create) +{ + // nothing really to do here, just need to make sure there's no memory leak in teardown + TestData *data = longBowTestCase_GetClipBoardData(testCase); + assertNotNull(data->fragmenter, "Got null fragmenter"); +} + +/* + * Receive a non-hop-by-hop packet. Should go straight in to the receive queue + */ +LONGBOW_TEST_CASE(Global, metisHopByHopFragmenter_Receive_NotHopByHop) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + MetisTicks startTicks = 1111111; + unsigned ingressId = 77; + + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV1_Interest_AllFields, sizeof(metisTestDataV1_Interest_AllFields), ingressId, startTicks, data->logger); + metisHopByHopFragmenter_Receive(data->fragmenter, message); + + /* + * 1) Make a metis message out of the reassembly buffer, + * 2) put the message in the receive queue (discard if queue full) + * 3) allocate a new reassembly buffer + * 4) reset the parser + */ + + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Got null reassembled message"); + + assertTrue(test == message, "Message not in receive queue, expected %p got %p", (void *) message, (void *) test); + + metisMessage_Release(&message); + metisMessage_Release(&test); +} + +LONGBOW_TEST_CASE(Global, metisHopByHopFragmenter_Receive_ReceiveQueueFull) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // create a full recieve queue + parcRingBuffer1x1_Release(&data->fragmenter->receiveQueue); + data->fragmenter->receiveQueue = parcRingBuffer1x1_Create(2, _ringBufferDestroyer); + + void *fakeData = (void *) 1; + parcRingBuffer1x1_Put(data->fragmenter->receiveQueue, fakeData); + + assertTrue(parcRingBuffer1x1_Remaining(data->fragmenter->receiveQueue) == 0, "expected queue to be full"); + + // === run test + MetisTicks startTicks = 1111111; + unsigned ingressId = 77; + + data->fragmenter->nextReceiveFragSequenceNumber = 1; + + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV1_Interest_AllFields, sizeof(metisTestDataV1_Interest_AllFields), ingressId, startTicks, data->logger); + metisHopByHopFragmenter_Receive(data->fragmenter, message); + + // should still only be the fake data in the queue + void *test = NULL; + parcRingBuffer1x1_Get(data->fragmenter->receiveQueue, &test); + assertTrue(test == fakeData, "Wrong pointer, expected %p got %p", fakeData, test); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisHopByHopFragmenter_Receive_Ok) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + MetisTicks startTicks = 1111111; + unsigned ingressId = 77; + + data->fragmenter->nextReceiveFragSequenceNumber = 1; + + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Begin, sizeof(metisTestDataV1_HopByHopFrag_Begin), ingressId, startTicks, data->logger); + metisHopByHopFragmenter_Receive(data->fragmenter, message); + + /* + * We should now be in the Busy state + */ + assertTrue(data->fragmenter->parserState == _ParserState_Busy, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisHopByHopFragmenter_Send_OneMtu) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // make a packet shorter than one MTU (so it will fit with the fragment overhead) + size_t length = data->fragmenter->mtu - 100; + uint8_t *packet = _conjurePacket(length); + MetisMessage *message = metisMessage_CreateFromArray(packet, length, 1, 2, data->logger); + assertNotNull(message, "Could not conjure packet"); + + bool success = metisHopByHopFragmenter_Send(data->fragmenter, message); + + assertTrue(success, "Failed to send fragments"); + MetisMessage *fragment = metisHopByHopFragmenter_PopSendQueue(data->fragmenter); + assertNotNull(fragment, "Did not find a fragment in the send queue"); + + // === + // defragment it + + metisHopByHopFragmenter_Receive(data->fragmenter, fragment); + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Should have gotten the original message back"); + assertTrue(metisMessage_Length(test) == metisMessage_Length(message), + "Reconstructed message length wrong expected %zu got %zu", + metisMessage_Length(message), + metisMessage_Length(test)); + + // === + // cleanup + + metisMessage_Release(&fragment); + metisMessage_Release(&message); + metisMessage_Release(&test); + parcMemory_Deallocate((void **) &packet); +} + +LONGBOW_TEST_CASE(Global, metisHopByHopFragmenter_Send_ReceiveQueueFull) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // create a full send queue + parcRingBuffer1x1_Release(&data->fragmenter->sendQueue); + data->fragmenter->sendQueue = parcRingBuffer1x1_Create(2, _ringBufferDestroyer); + + void *fakeData = (void *) 1; + parcRingBuffer1x1_Put(data->fragmenter->sendQueue, fakeData); + + + // less than 1 MTU + size_t length = data->fragmenter->mtu - 100; + uint8_t *packet = _conjurePacket(length); + MetisMessage *message = metisMessage_CreateFromArray(packet, length, 1, 2, data->logger); + assertNotNull(message, "Could not conjure packet"); + + + bool success = metisHopByHopFragmenter_Send(data->fragmenter, message); + assertFalse(success, "Should have failed to send fragments"); + + // === + // cleanup + + // manually pop this off as it is not a proper MetisMessage + parcRingBuffer1x1_Get(data->fragmenter->sendQueue, &fakeData); + metisMessage_Release(&message); + parcMemory_Deallocate((void **) &packet); +} + +LONGBOW_TEST_CASE(Global, metisHopByHopFragmenter_Send_Ok) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // Take up 2 MTUs (minus a little for fragmentation overhead) + size_t length = 2 * data->fragmenter->mtu - 100; + uint8_t *packet = _conjurePacket(length); + MetisMessage *message = metisMessage_CreateFromArray(packet, length, 1, 2, data->logger); + assertNotNull(message, "Could not conjure packet"); + + + bool success = metisHopByHopFragmenter_Send(data->fragmenter, message); + assertTrue(success, "Failed to send fragments"); + + // === + // defragment it + + MetisMessage *fragment; + while ((fragment = metisHopByHopFragmenter_PopSendQueue(data->fragmenter)) != NULL) { + metisHopByHopFragmenter_Receive(data->fragmenter, fragment); + metisMessage_Release(&fragment); + } + ; + + + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Should have gotten the original message back"); + assertTrue(metisMessage_Length(test) == metisMessage_Length(message), + "Reconstructed message length wrong expected %zu got %zu", + metisMessage_Length(message), + metisMessage_Length(test)); + + // === + // cleanup + + metisMessage_Release(&message); + metisMessage_Release(&test); + parcMemory_Deallocate((void **) &packet); +} + +// ============================================================ + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _compareSequenceNumbers); + LONGBOW_RUN_TEST_CASE(Local, _incrementSequenceNumber); + LONGBOW_RUN_TEST_CASE(Local, _resetParser); + LONGBOW_RUN_TEST_CASE(Local, _applySequenceNumberRules_InOrder); + LONGBOW_RUN_TEST_CASE(Local, _applySequenceNumberRules_Early); + LONGBOW_RUN_TEST_CASE(Local, _applySequenceNumberRules_Late); + LONGBOW_RUN_TEST_CASE(Local, _finalizeReassemblyBuffer_NotFull); + LONGBOW_RUN_TEST_CASE(Local, _finalizeReassemblyBuffer_Full); + LONGBOW_RUN_TEST_CASE(Local, _appendFragmentToReassemblyBuffer_Once); + LONGBOW_RUN_TEST_CASE(Local, _appendFragmentToReassemblyBuffer_Multiple); + + LONGBOW_RUN_TEST_CASE(Local, _receiveInIdleState_BFrame); + LONGBOW_RUN_TEST_CASE(Local, _receiveInIdleState_BEFrame); + LONGBOW_RUN_TEST_CASE(Local, _receiveInIdleState_OtherFrame); + + LONGBOW_RUN_TEST_CASE(Local, _receiveInBusyState_EFrame); + LONGBOW_RUN_TEST_CASE(Local, _receiveInBusyState_NoFlagFrame); + LONGBOW_RUN_TEST_CASE(Local, _receiveInBusyState_OtherFrame); + + LONGBOW_RUN_TEST_CASE(Local, _receiveFragment_IdleState); + LONGBOW_RUN_TEST_CASE(Local, _receiveFragment_BusyState); + + LONGBOW_RUN_TEST_CASE(Local, _sendFragments_OneFragment); + LONGBOW_RUN_TEST_CASE(Local, _sendFragments_TwoFragments); + LONGBOW_RUN_TEST_CASE(Local, _sendFragments_ThreeFragments); + LONGBOW_RUN_TEST_CASE(Local, _sendFragments_SendQueueFull); + + LONGBOW_RUN_TEST_CASE(Local, _ringBufferDestroyer); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + TestData *data = _createTestData(); + longBowTestCase_SetClipBoardData(testCase, data); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _destroyTestData(&data); + + 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, _compareSequenceNumbers) +{ + struct test_vector { + uint32_t a; + uint32_t b; + int signum; + bool sentinel; + } testVectors[] = { + // compared to b = 0, then a = {1 ... 0x07FFFF} is greater than b + // compared to b = 0, then a = {0x080000 ... 0x0FFFFF} is less than b + + { .a = 0x00000000, .b = 0x00000000, .signum = 0, .sentinel = false }, + { .a = 0x00000001, .b = 0x00000000, .signum = +1, .sentinel = false }, + { .a = 0x0007FFFF, .b = 0x00000000, .signum = +1, .sentinel = false }, + { .a = 0x00080000, .b = 0x00000000, .signum = -1, .sentinel = false }, + { .a = 0x000FFFFF, .b = 0x00000000, .signum = -1, .sentinel = false }, + + // now do the same thing but use b = 0x00040000 + { .a = 0x00040000, .b = 0x00040000, .signum = 0, .sentinel = false }, + { .a = 0x00040001, .b = 0x00040000, .signum = +1, .sentinel = false }, + { .a = 0x000BFFFF, .b = 0x00040000, .signum = +1, .sentinel = false }, + { .a = 0x000C0000, .b = 0x00040000, .signum = -1, .sentinel = false }, + { .a = 0x0003FFFF, .b = 0x00040000, .signum = -1, .sentinel = false }, + + // end test set + { .a = 0x00000000, .b = 0x00000000, .signum = 0, .sentinel = true }, + }; + + for (int i = 0; !testVectors[i].sentinel; i++) { + int result = _compareSequenceNumbers(testVectors[i].a, testVectors[i].b); + + if (testVectors[i].signum == 0) { + assertTrue(result == 0, "Wrong result, expected 0 got %d index %d a 0x%08x b 0x%08x", + result, i, testVectors[i].a, testVectors[i].b); + } + + if (testVectors[i].signum < 0) { + assertTrue(result < 0, "Wrong result, expected negative got %d index %d a 0x%08x b 0x%08x", + result, i, testVectors[i].a, testVectors[i].b); + } + + if (testVectors[i].signum > 0) { + assertTrue(result > 0, "Wrong result, expected positive got %d index %d a 0x%08x b 0x%08x", + result, i, testVectors[i].a, testVectors[i].b); + } + } +} + +LONGBOW_TEST_CASE(Local, _incrementSequenceNumber) +{ + struct test_vector { + uint32_t a; + uint32_t b; + bool sentinel; + } testVectors[] = { + { .a = 0x00000000, .b = 0x00000001, .sentinel = false }, + { .a = 0x00000001, .b = 0x00000002, .sentinel = false }, + { .a = 0x0007FFFF, .b = 0x00080000, .sentinel = false }, + { .a = 0x000FFFFF, .b = 0x00000000, .sentinel = false }, + // end test set + { .a = 0x00000000, .b = 0x00000000, .sentinel = true }, + }; + + for (int i = 0; !testVectors[i].sentinel; i++) { + uint32_t result = _incrementSequenceNumber(testVectors[i].a, 0x000FFFFF); + + assertTrue(result == testVectors[i].b, "Wrong result 0x%08X index %d, got for a 0x%08x b 0x%08x", + result, i, testVectors[i].a, testVectors[i].b); + } +} + +LONGBOW_TEST_CASE(Local, _resetParser) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // put something in the buffer and set the parser state to Busy + data->fragmenter->parserState = _ParserState_Busy; + parcEventBuffer_Append(data->fragmenter->currentReceiveBuffer, data, sizeof(data)); + + _resetParser(data->fragmenter); + assertTrue(data->fragmenter->parserState == _ParserState_Idle, "Wrong parser state, exepcted %d got %d", _ParserState_Idle, data->fragmenter->parserState); + + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == 0, "Wrong length, expected 0 bytes, got %zu", length); +} + +LONGBOW_TEST_CASE(Local, _applySequenceNumberRules_InOrder) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + data->fragmenter->parserState = _ParserState_Busy; + data->fragmenter->nextReceiveFragSequenceNumber = 1000; + + _HopByHopHeader header; + memset(&header, 0, sizeof(header)); + + _hopByHopHeader_SetSeqnum(&header, 1000); + + _applySequenceNumberRules(data->fragmenter, &header); + + // should still be in Busy mode and expecting 1001 + assertTrue(data->fragmenter->parserState == _ParserState_Busy, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + assertTrue(data->fragmenter->nextReceiveFragSequenceNumber == 1001, "Wrong next seqnum, expected 1001 got %u", data->fragmenter->nextReceiveFragSequenceNumber); +} + +LONGBOW_TEST_CASE(Local, _applySequenceNumberRules_Early) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + data->fragmenter->parserState = _ParserState_Busy; + data->fragmenter->nextReceiveFragSequenceNumber = 1000; + + _HopByHopHeader header; + memset(&header, 0, sizeof(header)); + + _hopByHopHeader_SetSeqnum(&header, 998); + + _applySequenceNumberRules(data->fragmenter, &header); + + // should reset state and set next to 999 + assertTrue(data->fragmenter->parserState == _ParserState_Idle, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + assertTrue(data->fragmenter->nextReceiveFragSequenceNumber == 999, "Wrong next seqnum, expected 999 got %u", data->fragmenter->nextReceiveFragSequenceNumber); +} + +LONGBOW_TEST_CASE(Local, _applySequenceNumberRules_Late) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + data->fragmenter->parserState = _ParserState_Busy; + data->fragmenter->nextReceiveFragSequenceNumber = 1000; + + _HopByHopHeader header; + memset(&header, 0, sizeof(header)); + + _hopByHopHeader_SetSeqnum(&header, 1001); + + _applySequenceNumberRules(data->fragmenter, &header); + + // should reset state and set next to 1002 + assertTrue(data->fragmenter->parserState == _ParserState_Idle, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + assertTrue(data->fragmenter->nextReceiveFragSequenceNumber == 1002, "Wrong next seqnum, expected 1002 got %u", data->fragmenter->nextReceiveFragSequenceNumber); +} + +LONGBOW_TEST_CASE(Local, _finalizeReassemblyBuffer_NotFull) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + MetisTicks startTicks = 1111111; + unsigned ingressId = 77; + + // set up as just finished with a message, so the currentReceiveBuffer has + // a complete CCNx message array in it + data->fragmenter->parserState = _ParserState_Busy; + data->fragmenter->currentReceiveBufferIngressId = ingressId; + data->fragmenter->currentReceiveBufferStartTicks = startTicks; + parcEventBuffer_Append(data->fragmenter->currentReceiveBuffer, metisTestDataV1_Interest_AllFields, sizeof(metisTestDataV1_Interest_AllFields)); + + _finalizeReassemblyBuffer(data->fragmenter); + + /* + * 1) Make a metis message out of the reassembly buffer, + * 2) put the message in the receive queue (discard if queue full) + * 3) allocate a new reassembly buffer + * 4) reset the parser + */ + + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Got null reassembled message"); + assertTrue(data->fragmenter->parserState == _ParserState_Idle, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + assertNotNull(data->fragmenter->currentReceiveBuffer, "Current receive buffer should not be null"); + assertTrue(parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer) == 0, "Current receive buffer should be empty, got %zu bytes", parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer)); + + assertTrue(metisMessage_GetIngressConnectionId(test) == ingressId, "Wrong ingress id expected %u got %u", ingressId, metisMessage_GetIngressConnectionId(test)); + assertTrue(metisMessage_GetReceiveTime(test) == startTicks, "Wrong receive time expected %" PRIu64 " got %" PRIu64, + startTicks, metisMessage_GetReceiveTime(test)); + + metisMessage_Release(&test); +} + +LONGBOW_TEST_CASE(Local, _finalizeReassemblyBuffer_Full) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + MetisTicks startTicks = 1111111; + unsigned ingressId = 77; + + // set up as just finished with a message, so the currentReceiveBuffer has + // a complete CCNx message array in it + data->fragmenter->parserState = _ParserState_Busy; + data->fragmenter->currentReceiveBufferIngressId = ingressId; + data->fragmenter->currentReceiveBufferStartTicks = startTicks; + parcEventBuffer_Append(data->fragmenter->currentReceiveBuffer, metisTestDataV1_Interest_AllFields, sizeof(metisTestDataV1_Interest_AllFields)); + + // create a full recieve queue + parcRingBuffer1x1_Release(&data->fragmenter->receiveQueue); + data->fragmenter->receiveQueue = parcRingBuffer1x1_Create(2, _ringBufferDestroyer); + + void *fakeData = (void *) 1; + parcRingBuffer1x1_Put(data->fragmenter->receiveQueue, fakeData); + + assertTrue(parcRingBuffer1x1_Remaining(data->fragmenter->receiveQueue) == 0, "expected queue to be full"); + + /* + * Call with a full receive queue + */ + _finalizeReassemblyBuffer(data->fragmenter); + + void *test = NULL; + parcRingBuffer1x1_Get(data->fragmenter->receiveQueue, &test); + assertTrue(test == fakeData, "Wrong pointer, expected %p got %p", fakeData, test); + + // teardown should show no memory leak +} + +LONGBOW_TEST_CASE(Local, _appendFragmentToReassemblyBuffer_Once) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + unsigned connid = 7; + MetisTicks receiveTime = 9999; + + MetisMessage *fragment = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Begin, sizeof(metisTestDataV1_HopByHopFrag_Begin), connid, receiveTime, data->logger); + _appendFragmentToReassemblyBuffer(data->fragmenter, fragment); + + int fragmentLength = sizeof(metisTestDataV1_HopByHopFrag_Begin_Fragment); + + assertTrue(parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer) == fragmentLength, + "currentReceiveBuffer wrong lenth, expected %d got %zu", + fragmentLength, + parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer)); + + uint8_t *test = parcEventBuffer_Pullup(data->fragmenter->currentReceiveBuffer, -1); + assertTrue(memcmp(test, metisTestDataV1_HopByHopFrag_Begin_Fragment, fragmentLength) == 0, "Fragment payload did not match"); + + metisMessage_Release(&fragment); +} + +LONGBOW_TEST_CASE(Local, _appendFragmentToReassemblyBuffer_Multiple) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + unsigned connid = 7; + MetisTicks receiveTime = 9999; + + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Begin, sizeof(metisTestDataV1_HopByHopFrag_Begin), connid, receiveTime, data->logger); + _appendFragmentToReassemblyBuffer(data->fragmenter, fragment1); + + MetisMessage *fragment2 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Middle, sizeof(metisTestDataV1_HopByHopFrag_Middle), connid, receiveTime, data->logger); + _appendFragmentToReassemblyBuffer(data->fragmenter, fragment2); + + MetisMessage *fragment3 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_End, sizeof(metisTestDataV1_HopByHopFrag_End), connid, receiveTime, data->logger); + _appendFragmentToReassemblyBuffer(data->fragmenter, fragment3); + + int fragmentLength = sizeof(metisTestDataV1_HopByHopFrag_BeginEnd_Fragment); + + assertTrue(parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer) == fragmentLength, + "currentReceiveBuffer wrong lenth, expected %d got %zu", + fragmentLength, + parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer)); + + uint8_t *test = parcEventBuffer_Pullup(data->fragmenter->currentReceiveBuffer, -1); + + // compares against the fragment metisTestDataV1_HopByHopFrag_BeginEnd which has the whole payload + assertTrue(memcmp(test, metisTestDataV1_HopByHopFrag_BeginEnd_Fragment, fragmentLength) == 0, "Fragment payload did not match"); + + metisMessage_Release(&fragment1); + metisMessage_Release(&fragment2); + metisMessage_Release(&fragment3); +} + + +/* + * B frame should be added to currentReceiveBuffer and state should become Busy. + * Also, the currentReceiveBufferIngressId and currentReceiveBufferReceiveTime should be set. + */ +LONGBOW_TEST_CASE(Local, _receiveInIdleState_BFrame) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // ensure we're in Idle state + _resetParser(data->fragmenter); + + unsigned connid = 7; + MetisTicks receiveTime = 9999; + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Begin, sizeof(metisTestDataV1_HopByHopFrag_Begin), connid, receiveTime, data->logger); + + const _HopByHopHeader *header = (const _HopByHopHeader *) metisTestDataV1_HopByHopFrag_Begin; + _receiveInIdleState(data->fragmenter, fragment1, header); + + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == sizeof(metisTestDataV1_HopByHopFrag_Begin_Fragment), "Wrong reassembly buffer length expected %zu got %zu", sizeof(metisTestDataV1_HopByHopFrag_Begin_Fragment), length); + assertTrue(data->fragmenter->parserState == _ParserState_Busy, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + assertTrue(data->fragmenter->currentReceiveBufferIngressId == connid, "Wrong ingress id expected %u got %u", connid, data->fragmenter->currentReceiveBufferIngressId); + assertTrue(data->fragmenter->currentReceiveBufferStartTicks == receiveTime, "Wrong receive time expected %" PRIu64 " got %" PRIu64, + receiveTime, data->fragmenter->currentReceiveBufferStartTicks); + metisMessage_Release(&fragment1); +} + +/* + * BE frame should be added to currentReceiveBuffer and finalized. + * State should stay in Idle but the receiveQueue should have the frame in it. + */ +LONGBOW_TEST_CASE(Local, _receiveInIdleState_BEFrame) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // ensure we're in Idle state + _resetParser(data->fragmenter); + + unsigned connid = 7; + MetisTicks receiveTime = 9999; + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_BeginEnd, sizeof(metisTestDataV1_HopByHopFrag_BeginEnd), connid, receiveTime, data->logger); + + const _HopByHopHeader *header = (const _HopByHopHeader *) metisTestDataV1_HopByHopFrag_BeginEnd; + _receiveInIdleState(data->fragmenter, fragment1, header); + + // should not be in the reassembly buffer + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == 0, "Wrong reassembly buffer length expected 0 got %zu", length); + + // it should be in the receive queue + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Message was not in receive queue"); + metisMessage_Release(&test); + + assertTrue(data->fragmenter->parserState == _ParserState_Idle, "Wrong parser state, exepcted %d got %d", _ParserState_Idle, data->fragmenter->parserState); + + + assertTrue(data->fragmenter->currentReceiveBufferIngressId == connid, "Wrong ingress id expected %u got %u", connid, data->fragmenter->currentReceiveBufferIngressId); + assertTrue(data->fragmenter->currentReceiveBufferStartTicks == receiveTime, "Wrong receive time expected %" PRIu64 " got %" PRIu64, + receiveTime, data->fragmenter->currentReceiveBufferStartTicks); + metisMessage_Release(&fragment1); +} + +/* + * Not B and Not BE frames should be ignored + */ +LONGBOW_TEST_CASE(Local, _receiveInIdleState_OtherFrame) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + struct test_vector { + uint8_t flags; + bool sentinel; + } testVectors[] = { + // All combinations except 0x40 and 0x60 + { .flags = 0x00, .sentinel = false }, + { .flags = 0x10, .sentinel = false }, + { .flags = 0x20, .sentinel = false }, + { .flags = 0x30, .sentinel = false }, + { .flags = 0x80, .sentinel = false }, + { .flags = 0x90, .sentinel = false }, + { .flags = 0xA0, .sentinel = false }, + { .flags = 0xB0, .sentinel = false }, + { .flags = 0x00, .sentinel = true }, + }; + + for (int i = 0; !testVectors[i].sentinel; i++) { + _HopByHopHeader header; + memset(&header, 0, sizeof(header)); + + header.blob[0] |= testVectors[i].flags; + + unsigned connid = 7; + MetisTicks receiveTime = 9999; + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_BeginEnd, sizeof(metisTestDataV1_HopByHopFrag_BeginEnd), connid, receiveTime, data->logger); + + _receiveInIdleState(data->fragmenter, fragment1, &header); + + metisMessage_Release(&fragment1); + + // should not be in the reassembly buffer + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == 0, "Wrong reassembly buffer length expected 0 got %zu", length); + + assertTrue(data->fragmenter->parserState == _ParserState_Idle, "Wrong parser state, exepcted %d got %d", _ParserState_Idle, data->fragmenter->parserState); + } +} + +/* + * 2) If E flag + * 2a) append to reassembly buffer + * 2b) finalize the buffer (side effect: will reset the parser and place in receive queue) + */ +LONGBOW_TEST_CASE(Local, _receiveInBusyState_EFrame) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + unsigned connid = 7; + MetisTicks receiveTime = 9999; + + // ensure we're in Busy state (the precondition of this test) + _resetParser(data->fragmenter); + data->fragmenter->parserState = _ParserState_Busy; + + // and put the Begin and Middle fragments in the reassembly buffer so the packet will decode properly + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Begin, sizeof(metisTestDataV1_HopByHopFrag_Begin), connid, receiveTime, data->logger); + _appendFragmentToReassemblyBuffer(data->fragmenter, fragment1); + + MetisMessage *fragment2 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Middle, sizeof(metisTestDataV1_HopByHopFrag_Middle), connid, receiveTime, data->logger); + _appendFragmentToReassemblyBuffer(data->fragmenter, fragment2); + + // ==== + // Now do the test + + MetisMessage *fragment3 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_End, sizeof(metisTestDataV1_HopByHopFrag_End), connid, receiveTime, data->logger); + + const _HopByHopHeader *header = (const _HopByHopHeader *) metisTestDataV1_HopByHopFrag_End; + _receiveInBusyState(data->fragmenter, fragment3, header); + + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == 0, "Wrong reassembly buffer length expected 0 got %zu", length); + + assertTrue(data->fragmenter->parserState == _ParserState_Idle, "Wrong parser state, exepcted %d got %d", _ParserState_Idle, data->fragmenter->parserState); + + // it should be in the receive queue + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Message was not in receive queue"); + metisMessage_Release(&test); + + metisMessage_Release(&fragment1); + metisMessage_Release(&fragment2); + metisMessage_Release(&fragment3); +} + +/* + * 1) If no flags + * 1a) append to reassembly buffer + */ +LONGBOW_TEST_CASE(Local, _receiveInBusyState_NoFlagFrame) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // ensure we're in Busy state (the precondition of this test) + _resetParser(data->fragmenter); + data->fragmenter->parserState = _ParserState_Busy; + + unsigned connid = 7; + MetisTicks receiveTime = 9999; + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Middle, sizeof(metisTestDataV1_HopByHopFrag_Middle), connid, receiveTime, data->logger); + + const _HopByHopHeader *header = (const _HopByHopHeader *) metisTestDataV1_HopByHopFrag_Middle; + _receiveInBusyState(data->fragmenter, fragment1, header); + + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == sizeof(metisTestDataV1_HopByHopFrag_Middle_Fragment), "Wrong reassembly buffer length expected %zu got %zu", sizeof(metisTestDataV1_HopByHopFrag_Middle_Fragment), length); + + assertTrue(data->fragmenter->parserState == _ParserState_Busy, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + metisMessage_Release(&fragment1); +} + +LONGBOW_TEST_CASE(Local, _receiveInBusyState_OtherFrame) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + struct test_vector { + uint8_t flags; + bool sentinel; + } testVectors[] = { + // All combinations except 0x00 and 0x20 + { .flags = 0x10, .sentinel = false }, + { .flags = 0x40, .sentinel = false }, + { .flags = 0x80, .sentinel = false }, + { .flags = 0x50, .sentinel = false }, + { .flags = 0x90, .sentinel = false }, + { .flags = 0xC0, .sentinel = false }, + { .flags = 0x00, .sentinel = true }, + }; + + for (int i = 0; !testVectors[i].sentinel; i++) { + _HopByHopHeader header; + memset(&header, 0, sizeof(header)); + + header.blob[0] |= testVectors[i].flags; + + unsigned connid = 7; + MetisTicks receiveTime = 9999; + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_BeginEnd, sizeof(metisTestDataV1_HopByHopFrag_BeginEnd), connid, receiveTime, data->logger); + + // ensure we're in Busy state (the precondition of this test) + _resetParser(data->fragmenter); + data->fragmenter->parserState = _ParserState_Busy; + + _receiveInBusyState(data->fragmenter, fragment1, &header); + + metisMessage_Release(&fragment1); + + // should not be in the reassembly buffer + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == 0, "Wrong reassembly buffer length expected 0 got %zu", length); + + assertTrue(data->fragmenter->parserState == _ParserState_Idle, "Wrong parser state, exepcted %d got %d", _ParserState_Idle, data->fragmenter->parserState); + } +} + +/* + * Receive a B frame in Idle state + */ +LONGBOW_TEST_CASE(Local, _receiveFragment_IdleState) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + unsigned connid = 7; + MetisTicks receiveTime = 9999; + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Begin, sizeof(metisTestDataV1_HopByHopFrag_Begin), connid, receiveTime, data->logger); + + _receiveFragment(data->fragmenter, fragment1); + + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == sizeof(metisTestDataV1_HopByHopFrag_Begin_Fragment), "Wrong reassembly buffer length expected %zu got %zu", sizeof(metisTestDataV1_HopByHopFrag_Begin_Fragment), length); + assertTrue(data->fragmenter->parserState == _ParserState_Busy, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + assertTrue(data->fragmenter->currentReceiveBufferIngressId == connid, "Wrong ingress id expected %u got %u", connid, data->fragmenter->currentReceiveBufferIngressId); + assertTrue(data->fragmenter->currentReceiveBufferStartTicks == receiveTime, "Wrong receive time expected %" PRIu64 " got %" PRIu64, + receiveTime, data->fragmenter->currentReceiveBufferStartTicks); + metisMessage_Release(&fragment1); +} + +LONGBOW_TEST_CASE(Local, _receiveFragment_BusyState) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // ensure we're in Busy state (the precondition of this test) + // Make sure the packet will be in-order by setting the next expected seqnum. + _resetParser(data->fragmenter); + data->fragmenter->parserState = _ParserState_Busy; + data->fragmenter->nextReceiveFragSequenceNumber = 2; + + unsigned connid = 7; + MetisTicks receiveTime = 9999; + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Middle, sizeof(metisTestDataV1_HopByHopFrag_Middle), connid, receiveTime, data->logger); + + _receiveFragment(data->fragmenter, fragment1); + + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == sizeof(metisTestDataV1_HopByHopFrag_Middle_Fragment), "Wrong reassembly buffer length expected %zu got %zu", sizeof(metisTestDataV1_HopByHopFrag_Middle_Fragment), length); + + assertTrue(data->fragmenter->parserState == _ParserState_Busy, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + metisMessage_Release(&fragment1); +} + +LONGBOW_TEST_CASE(Local, _sendFragments_OneFragment) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // make a packet shorter than one MTU (so it will fit with the fragment overhead) + size_t length = data->fragmenter->mtu - 100; + uint8_t *packet = _conjurePacket(length); + MetisMessage *message = metisMessage_CreateFromArray(packet, length, 1, 2, data->logger); + assertNotNull(message, "Could not conjure packet"); + + + bool success = _sendFragments(data->fragmenter, message); + assertTrue(success, "Failed to send fragments"); + MetisMessage *fragment = metisHopByHopFragmenter_PopSendQueue(data->fragmenter); + assertNotNull(fragment, "Did not find a fragment in the send queue"); + + // === + // defragment it + + _receiveFragment(data->fragmenter, fragment); + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Should have gotten the original message back"); + assertTrue(metisMessage_Length(test) == metisMessage_Length(message), + "Reconstructed message length wrong expected %zu got %zu", + metisMessage_Length(message), + metisMessage_Length(test)); + + // === + // cleanup + + metisMessage_Release(&message); + metisMessage_Release(&fragment); + metisMessage_Release(&test); + parcMemory_Deallocate((void **) &packet); +} + +LONGBOW_TEST_CASE(Local, _sendFragments_TwoFragments) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // Take up 2 MTUs (minus a little for fragmentation overhead) + size_t length = 2 * data->fragmenter->mtu - 100; + uint8_t *packet = _conjurePacket(length); + MetisMessage *message = metisMessage_CreateFromArray(packet, length, 1, 2, data->logger); + assertNotNull(message, "Could not conjure packet"); + + + bool success = _sendFragments(data->fragmenter, message); + assertTrue(success, "Failed to send fragments"); + + // === + // defragment it + + MetisMessage *fragment; + while ((fragment = metisHopByHopFragmenter_PopSendQueue(data->fragmenter)) != NULL) { + _receiveFragment(data->fragmenter, fragment); + metisMessage_Release(&fragment); + } + ; + + + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Should have gotten the original message back"); + assertTrue(metisMessage_Length(test) == metisMessage_Length(message), + "Reconstructed message length wrong expected %zu got %zu", + metisMessage_Length(message), + metisMessage_Length(test)); + + // === + // cleanup + + metisMessage_Release(&message); + metisMessage_Release(&test); + parcMemory_Deallocate((void **) &packet); +} + +LONGBOW_TEST_CASE(Local, _sendFragments_ThreeFragments) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // Take up 2 MTUs (minus a little for fragmentation overhead) + size_t length = 3 * data->fragmenter->mtu - 100; + uint8_t *packet = _conjurePacket(length); + MetisMessage *message = metisMessage_CreateFromArray(packet, length, 1, 2, data->logger); + assertNotNull(message, "Could not conjure packet"); + + + bool success = _sendFragments(data->fragmenter, message); + assertTrue(success, "Failed to send fragments"); + + // === + // defragment it + + MetisMessage *fragment; + while ((fragment = metisHopByHopFragmenter_PopSendQueue(data->fragmenter)) != NULL) { + _receiveFragment(data->fragmenter, fragment); + metisMessage_Release(&fragment); + } + ; + + + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Should have gotten the original message back"); + assertTrue(metisMessage_Length(test) == metisMessage_Length(message), + "Reconstructed message length wrong expected %zu got %zu", + metisMessage_Length(message), + metisMessage_Length(test)); + + // === + // cleanup + + metisMessage_Release(&message); + metisMessage_Release(&test); + parcMemory_Deallocate((void **) &packet); +} + +LONGBOW_TEST_CASE(Local, _sendFragments_SendQueueFull) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // create a full send queue + parcRingBuffer1x1_Release(&data->fragmenter->sendQueue); + data->fragmenter->sendQueue = parcRingBuffer1x1_Create(2, _ringBufferDestroyer); + + void *fakeData = (void *) 1; + parcRingBuffer1x1_Put(data->fragmenter->sendQueue, fakeData); + + + // Take up 2 MTUs (minus a little for fragmentation overhead) + size_t length = 3 * data->fragmenter->mtu - 100; + uint8_t *packet = _conjurePacket(length); + MetisMessage *message = metisMessage_CreateFromArray(packet, length, 1, 2, data->logger); + assertNotNull(message, "Could not conjure packet"); + + + bool success = _sendFragments(data->fragmenter, message); + assertFalse(success, "Should have failed to send fragments"); + // === + // cleanup + + // manually pop this off as it is not a proper MetisMessage + parcRingBuffer1x1_Get(data->fragmenter->sendQueue, &fakeData); + + metisMessage_Release(&message); + parcMemory_Deallocate((void **) &packet); +} + +LONGBOW_TEST_CASE(Local, _ringBufferDestroyer) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + unsigned connid = 7; + MetisTicks receiveTime = 9999; + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Middle, sizeof(metisTestDataV1_HopByHopFrag_Middle), connid, receiveTime, data->logger); + + bool success = parcRingBuffer1x1_Put(data->fragmenter->receiveQueue, fragment1); + assertTrue(success, "Failed to put test message in queue"); + + // nothing to do here. When the fragmenter is destroyed it should destroy the message + // and we will not trip a memory imbalance +} + +// ============================================================ +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_HopByHopFragmenter); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_IPMulticastListener.c b/metis/ccnx/forwarder/metis/io/test/test_metis_IPMulticastListener.c new file mode 100644 index 00000000..12dc17dc --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_IPMulticastListener.c @@ -0,0 +1,80 @@ +/* + * 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_IPMulticastListener.c" +#include <parc/algol/parc_SafeMemory.h> + +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metis_IPMulticastListener) +{ + // 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_IPMulticastListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_IPMulticastListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +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_IPMulticastListener); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_IoOperations.c b/metis/ccnx/forwarder/metis/io/test/test_metis_IoOperations.c new file mode 100644 index 00000000..a78715c1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_IoOperations.c @@ -0,0 +1,185 @@ +/* + * 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_IoOperations.c" +#include <parc/algol/parc_SafeMemory.h> + +#include <LongBow/unit-test.h> + +#include <ccnx/forwarder/metis/core/test/testrig_MetisIoOperations.h> + +// =========================================== + +LONGBOW_TEST_RUNNER(metis_IoOperations) +{ + LONGBOW_RUN_TEST_FIXTURE(Global); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_IoOperations) +{ + 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_IoOperations) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_GetClosure); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_Send); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_GetRemoteAddress); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_GetAddressPair); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_IsUp); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_IsLocal); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_GetConnectionId); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_Release); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_Class); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_GetConnectionType); +} + +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, metisIoOperations_GetClosure) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + void *closure = metisIoOperations_GetClosure(ops); + assertTrue(closure == ops->closure, "Wrong closure, expected %p got %p", ops->closure, closure); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_Send) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_Send(ops, NULL, NULL); + assertTrue(data->sendCount == 1, "Wrong metisIoOperations_Send count expected 1 got %u", data->sendCount); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_GetRemoteAddress) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_GetRemoteAddress(ops); + assertTrue(data->getRemoteAddressCount == 1, "Wrong metisIoOperations_GetRemoteAddress count expected 1 got %u", data->getRemoteAddressCount); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_GetAddressPair) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_GetAddressPair(ops); + assertTrue(data->getAddressPairCount == 1, "Wrong metisIoOperations_GetAddressPairexpected count 1 got %u", data->getAddressPairCount); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_IsUp) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_IsUp(ops); + assertTrue(data->isUpCount == 1, "Wrong metisIoOperations_IsUp count expected 1 got %u", data->isUpCount); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_IsLocal) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_IsLocal(ops); + assertTrue(data->isLocalCount == 1, "Wrong metisIoOperations_IsLocal count expected 1 got %u", data->isLocalCount); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_GetConnectionId) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_GetConnectionId(ops); + assertTrue(data->getConnectionIdCount == 1, "Wrong metisIoOperations_GetConnectionId count expected 1 got %u", data->getConnectionIdCount); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_Release) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MetisIoOperations *copy = ops; + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_Release(&ops); + assertTrue(data->destroyCount == 1, "Wrong metisIoOperations_Release count expected 1 got %u", data->destroyCount); + mockIoOperationsData_Destroy(©); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_Class) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_Class(ops); + assertTrue(data->classCount == 1, "Wrong metisIoOperations_Class count expected 1 got %u", data->classCount); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_GetConnectionType) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_GetConnectionType(ops); + assertTrue(data->getConnectionTypeCount == 1, "Wrong getConnectionTypeCount count expected 1 got %u", data->getConnectionTypeCount); + mockIoOperationsData_Destroy(&ops); +} + + + +// =========================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_IoOperations); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_ListenerSet.c b/metis/ccnx/forwarder/metis/io/test/test_metis_ListenerSet.c new file mode 100644 index 00000000..b786d9b3 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_ListenerSet.c @@ -0,0 +1,259 @@ +/* + * 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_ListenerSet.c" +#include <parc/algol/parc_SafeMemory.h> + +#include <LongBow/unit-test.h> + +#include "testrig_MetisListenerOps.c" + + +LONGBOW_TEST_RUNNER(metis_ListenerSet) +{ + // 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_ListenerSet) +{ + 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_ListenerSet) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisListenerSet_Add_Single); + LONGBOW_RUN_TEST_CASE(Global, metisListenerSet_Add_Unique); + LONGBOW_RUN_TEST_CASE(Global, metisListenerSet_Add_Duplicate); + LONGBOW_RUN_TEST_CASE(Global, metisListenerSet_Create_Destroy); + + + LONGBOW_RUN_TEST_CASE(Global, metisListenerSet_Length); + LONGBOW_RUN_TEST_CASE(Global, metisListenerSet_Get); + LONGBOW_RUN_TEST_CASE(Global, metisListenerSet_Find_InSet); + LONGBOW_RUN_TEST_CASE(Global, metisListenerSet_Find_NotInSet); +} + +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; +} + +// Adds a single MockListenerData to the listener set. +static MockListenerData * +addSingle(MetisListenerSet *set) +{ + CPIAddress *listenAddress = cpiAddress_CreateFromInterface(44); + MockListenerData *data = mockListenData_Create(1, listenAddress, METIS_ENCAP_ETHER); + MetisListenerOps *listenerOps = mockListener_Create(data); + + bool success = metisListenerSet_Add(set, listenerOps); + assertTrue(success, "Got failure adding one listener to the set"); + assertTrue(parcArrayList_Size(set->listOfListeners) == 1, + "Got wrong list length, got %zu expected %u", + parcArrayList_Size(set->listOfListeners), 1); + + cpiAddress_Destroy(&listenAddress); + return data; +} + +LONGBOW_TEST_CASE(Global, metisListenerSet_Add_Single) +{ + MetisListenerSet *set = metisListenerSet_Create(); + + MockListenerData *data = addSingle(set); + + metisListenerSet_Destroy(&set); + assertTrue(data->destroyCount == 1, + "Wrong destroy count, got %u expected %u", + data->destroyCount, 1); + + mockListenerData_Destroy(&data); +} + +LONGBOW_TEST_CASE(Global, metisListenerSet_Add_Unique) +{ + CPIAddress *listenAddress_A = cpiAddress_CreateFromInterface(44); + MockListenerData *data_A = mockListenData_Create(1, listenAddress_A, METIS_ENCAP_ETHER); + MetisListenerOps *listenerOps_A = mockListener_Create(data_A); + + CPIAddress *listenAddress_B = cpiAddress_CreateFromInterface(55); + MockListenerData *data_B = mockListenData_Create(1, listenAddress_B, METIS_ENCAP_ETHER); + MetisListenerOps *listenerOps_B = mockListener_Create(data_B); + + + MetisListenerSet *set = metisListenerSet_Create(); + bool success_A = metisListenerSet_Add(set, listenerOps_A); + assertTrue(success_A, "Got failure adding listener A to the set"); + + bool success_B = metisListenerSet_Add(set, listenerOps_B); + assertTrue(success_B, "Got failure adding listener B to the set"); + + cpiAddress_Destroy(&listenAddress_A); + cpiAddress_Destroy(&listenAddress_B); + metisListenerSet_Destroy(&set); + + mockListenerData_Destroy(&data_A); + mockListenerData_Destroy(&data_B); +} + +LONGBOW_TEST_CASE(Global, metisListenerSet_Add_Duplicate) +{ + CPIAddress *listenAddress_A = cpiAddress_CreateFromInterface(44); + MockListenerData *data_A = mockListenData_Create(1, listenAddress_A, METIS_ENCAP_ETHER); + MetisListenerOps *listenerOps_A = mockListener_Create(data_A); + + CPIAddress *listenAddress_B = cpiAddress_CreateFromInterface(44); + MockListenerData *data_B = mockListenData_Create(1, listenAddress_B, METIS_ENCAP_ETHER); + MetisListenerOps *listenerOps_B = mockListener_Create(data_B); + + + MetisListenerSet *set = metisListenerSet_Create(); + bool success_A = metisListenerSet_Add(set, listenerOps_A); + assertTrue(success_A, "Got failure adding listener A to the set"); + + bool success_B = metisListenerSet_Add(set, listenerOps_B); + assertFalse(success_B, "Got success adding listener B to the set, duplicate should have failed"); + + cpiAddress_Destroy(&listenAddress_A); + cpiAddress_Destroy(&listenAddress_B); + metisListenerSet_Destroy(&set); + + mockListener_Destroy(&listenerOps_B); + mockListenerData_Destroy(&data_A); + mockListenerData_Destroy(&data_B); +} + + +LONGBOW_TEST_CASE(Global, metisListenerSet_Create_Destroy) +{ + MetisListenerSet *set = metisListenerSet_Create(); + assertNotNull(set, "Got null from Create"); + + metisListenerSet_Destroy(&set); + assertNull(set, "Destroy did not null parameter"); +} + +LONGBOW_TEST_CASE(Global, metisListenerSet_Length) +{ + MetisListenerSet *set = metisListenerSet_Create(); + MockListenerData *data = addSingle(set); + + size_t length = metisListenerSet_Length(set); + + metisListenerSet_Destroy(&set); + mockListenerData_Destroy(&data); + + assertTrue(length == 1, + "Wrong length, got %zu expected %u", + length, 1); +} + +LONGBOW_TEST_CASE(Global, metisListenerSet_Get) +{ + MetisListenerSet *set = metisListenerSet_Create(); + MockListenerData *data = addSingle(set); + + MetisListenerOps *ops = metisListenerSet_Get(set, 0); + + assertNotNull(ops, "Did not fetch the listener ops"); + + metisListenerSet_Destroy(&set); + mockListenerData_Destroy(&data); +} + +LONGBOW_TEST_CASE(Global, metisListenerSet_Find_InSet) +{ + MetisListenerSet *set = metisListenerSet_Create(); + MockListenerData *data = addSingle(set); + + MetisListenerOps *ops = metisListenerSet_Find(set, data->encapType, data->listenAddress); + assertNotNull(ops, "Did not retrieve the listener that is in the set"); + + metisListenerSet_Destroy(&set); + mockListenerData_Destroy(&data); +} + +LONGBOW_TEST_CASE(Global, metisListenerSet_Find_NotInSet) +{ + MetisListenerSet *set = metisListenerSet_Create(); + MockListenerData *data = addSingle(set); + + // use wrong encap type + MetisListenerOps *ops = metisListenerSet_Find(set, data->encapType + 1, data->listenAddress); + assertNull(ops, "Should not have found anything with wrong encap type"); + + metisListenerSet_Destroy(&set); + mockListenerData_Destroy(&data); +} + + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisListenerSet_DestroyListenerOps); +} + +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, metisListenerSet_DestroyListenerOps) +{ + testUnimplemented(""); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_ListenerSet); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_LocalListener.c b/metis/ccnx/forwarder/metis/io/test/test_metis_LocalListener.c new file mode 100644 index 00000000..2852d253 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_LocalListener.c @@ -0,0 +1,97 @@ +/* + * 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_LocalListener.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +LONGBOW_TEST_RUNNER(metis_LocalListener) +{ + // 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_LocalListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_LocalListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisListenerLocal_Create_Destroy); +} + +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, metisListenerLocal_Create_Destroy) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisListenerLocal_Listen); +} + +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; +} + +LONGBOW_TEST_CASE(Local, metisListenerLocal_Listen) +{ + testUnimplemented("This test is unimplemented"); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_LocalListener); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_StreamConnection.c b/metis/ccnx/forwarder/metis/io/test/test_metis_StreamConnection.c new file mode 100644 index 00000000..5ae50bcd --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_StreamConnection.c @@ -0,0 +1,767 @@ +/* + * 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_StreamConnection.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <parc/logging/parc_LogReporterTextStdout.h> + +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> +#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h> + +// inet_pton +#include <arpa/inet.h> + +#include <fcntl.h> + +#include <stdio.h> + +#ifndef INPORT_ANY +#define INPORT_ANY 0 +#endif + +// we hand-code some packets in the unit tests +typedef struct __attribute__ ((__packed__)) metis_tlv_fixed_header { + uint8_t version; + uint8_t packetType; + uint16_t payloadLength; + uint16_t reserved; + uint16_t headerLength; +} _MetisTlvFixedHeaderV0; + + +LONGBOW_TEST_RUNNER(metis_StreamConnection) +{ + // 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_StreamConnection) +{ + 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_StreamConnection) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ================================================================================ +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisStreamConnection_Create); + LONGBOW_RUN_TEST_CASE(Global, metisStreamConnection_OpenConnection); +} + +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, metisStreamConnection_Create) +{ + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fd, pair, false); + + ops->destroy(&ops); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + + metisForwarder_Destroy(&metis); + close(fd); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); + assertTrue(parcSafeMemory_Outstanding() == 0, "Got memory imbalance: %u", parcSafeMemory_Outstanding()); +} + +static int +listenToInet(struct sockaddr_in *server) +{ + int fd = socket(PF_INET, SOCK_STREAM, 0); + assertFalse(fd < 0, "error on bind: (%d) %s", errno, strerror(errno)); + + // Set non-blocking flag + int flags = fcntl(fd, F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)\n", errno); + int failure = fcntl(fd, F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)\n", errno); + + failure = bind(fd, (struct sockaddr *) server, sizeof(struct sockaddr_in)); + assertFalse(failure, "error on bind: (%d) %s", errno, strerror(errno)); + + failure = listen(fd, 16); + assertFalse(failure, "error on listen: (%d) %s", errno, strerror(errno)); + + return fd; +} + +LONGBOW_TEST_CASE(Global, metisStreamConnection_OpenConnection) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + + struct sockaddr_in serverAddr; + memset(&serverAddr, 0, sizeof(serverAddr)); + serverAddr.sin_family = PF_INET; + serverAddr.sin_port = INPORT_ANY; + inet_pton(AF_INET, "127.0.0.1", &(serverAddr.sin_addr)); + + int serverSocket = listenToInet(&serverAddr); + socklen_t x = sizeof(serverAddr); + int failure = getsockname(serverSocket, (struct sockaddr *) &serverAddr, &x); + assertFalse(failure, "error on getsockname: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in localAddr; + memset(&localAddr, 0, sizeof(localAddr)); + localAddr.sin_family = PF_INET; + localAddr.sin_addr.s_addr = INADDR_ANY; + localAddr.sin_port = INPORT_ANY; + + CPIAddress *local = cpiAddress_CreateFromInet(&localAddr); + + // change from 0.0.0.0 to 127.0.0.1 + inet_pton(AF_INET, "127.0.0.1", &(serverAddr.sin_addr)); + + CPIAddress *remote = cpiAddress_CreateFromInet(&serverAddr); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); + + MetisIoOperations *ops = metisStreamConnection_OpenConnection(metis, pair, false); + assertNotNull(ops, "Got null ops from metisStreamConnection_OpenConnection"); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); +} + +// ======================================================================= + +// ================================================================================== + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, conn_eventcb_Connected); + LONGBOW_RUN_TEST_CASE(Local, conn_eventcb_EOF); + LONGBOW_RUN_TEST_CASE(Local, conn_eventcb_ERROR); + + LONGBOW_RUN_TEST_CASE(Local, conn_readcb); + LONGBOW_RUN_TEST_CASE(Local, metisStreamConnection_Equals); + LONGBOW_RUN_TEST_CASE(Local, metisStreamConnection_GetAddress); + LONGBOW_RUN_TEST_CASE(Local, metisStreamConnection_GetAddressPair); + LONGBOW_RUN_TEST_CASE(Local, metisStreamConnection_GetConnectionId); + LONGBOW_RUN_TEST_CASE(Local, metisStreamConnection_HashCode); + LONGBOW_RUN_TEST_CASE(Local, metisStreamConnection_IsUp); + LONGBOW_RUN_TEST_CASE(Local, metisStreamConnection_Send); + LONGBOW_RUN_TEST_CASE(Local, metisStreamConnection_GetConnectionType); + LONGBOW_RUN_TEST_CASE(Local, printConnection); + LONGBOW_RUN_TEST_CASE(Local, readMessage); + LONGBOW_RUN_TEST_CASE(Local, setConnectionState); + LONGBOW_RUN_TEST_CASE(Local, single_read_ZeroNextMessageLength); + LONGBOW_RUN_TEST_CASE(Local, single_read_PartialRead); + LONGBOW_RUN_TEST_CASE(Local, single_read_FullRead); + LONGBOW_RUN_TEST_CASE(Local, startNewMessage); +} + +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, conn_eventcb_Connected) +{ + int fds[2]; + int failure = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds); + assertFalse(failure, "Error socketpair: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fds[0], pair, false); + _MetisStreamState *stream = (_MetisStreamState *) metisIoOperations_GetClosure(ops); + + stream->isUp = false; + + // ---- the actual test + _conn_eventcb(stream->bufferEventVector, PARCEventQueueEventType_Connected, ops); + assertTrue(stream->isUp, "PARCEventQueueEventType_Connected did not trigger stream to up state"); + // ---- + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + close(fds[0]); + close(fds[1]); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); +} + +LONGBOW_TEST_CASE(Local, conn_eventcb_EOF) +{ + int fds[2]; + int failure = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds); + assertFalse(failure, "Error socketpair: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fds[0], pair, false); + _MetisStreamState *stream = (_MetisStreamState *) metisIoOperations_GetClosure(ops); + + stream->isUp = true; + + // ---- the actual test + _conn_eventcb(stream->bufferEventVector, PARCEventQueueEventType_EOF, ops); + assertFalse(stream->isUp, "PARCEventQueueEventType_EOF did not trigger stream to down state"); + // ---- + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + close(fds[0]); + close(fds[1]); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); +} + +LONGBOW_TEST_CASE(Local, conn_eventcb_ERROR) +{ + int fds[2]; + int failure = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds); + assertFalse(failure, "Error socketpair: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fds[0], pair, false); + _MetisStreamState *stream = (_MetisStreamState *) metisIoOperations_GetClosure(ops); + + stream->isUp = true; + + // ---- the actual test + _conn_eventcb(stream->bufferEventVector, PARCEventQueueEventType_Error, ops); + assertFalse(stream->isUp, "PARCEventQueueEventType_Error did not trigger stream to down state"); + // ---- + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + close(fds[0]); + close(fds[1]); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); +} + +LONGBOW_TEST_CASE(Local, conn_readcb) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, metisStreamConnection_Equals) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, metisStreamConnection_GetAddress) +{ + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fd, pair, false); + + const CPIAddress *test_addr = ops->getRemoteAddress(ops); + + assertTrue(cpiAddress_Equals(remote, test_addr), "ops->getAddress incorrect"); + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); + + close(fd); +} + +LONGBOW_TEST_CASE(Local, metisStreamConnection_GetAddressPair) +{ + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fd, pair, false); + + const MetisAddressPair *test_pair = ops->getAddressPair(ops); + + assertTrue(metisAddressPair_Equals(pair, test_pair), "ops->getRemoteAddress incorrect"); + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); + + close(fd); +} + +LONGBOW_TEST_CASE(Local, metisStreamConnection_GetConnectionId) +{ + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + unsigned truth_connid = metisForwarder_GetNextConnectionId(metis) + 1; + + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fd, pair, false); + + assertTrue(ops->getConnectionId(ops) == truth_connid, "Got wrong connection id, expected %u got %u", truth_connid, ops->getConnectionId(ops)); + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); + + close(fd); +} + +LONGBOW_TEST_CASE(Local, metisStreamConnection_HashCode) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, metisStreamConnection_IsUp) +{ + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fd, pair, false); + + assertTrue(ops->isUp(ops), "isUp incorrect, expected true, got false"); + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); + close(fd); +} + +LONGBOW_TEST_CASE(Local, metisStreamConnection_Send) +{ + // StreamConnection_Create needs a socket and address to represent the peer + // we use a socket pair so we can actaully read from it and verify what is sent. + + int fds[2]; + int failure = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds); + assertFalse(failure, "Error socketpair: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fds[0], pair, false); + + // ---------- + // Create a fake message. Send does not care what the message is, it just writes it out. + // We include a real header, but it is not needed. + + char message_str[] = "\x00Once upon a jiffie, in a stack far away, a dangling pointer found its way to the top of the heap."; + _MetisTlvFixedHeaderV0 *hdr = (_MetisTlvFixedHeaderV0 *) message_str; + hdr->payloadLength = htons(92); + hdr->headerLength = htons(0); + + MetisMessage *sendmessage = metisMessage_CreateFromArray((uint8_t *) message_str, sizeof(message_str), 1, 2, metisForwarder_GetLogger(metis)); + + // ---------- + // actually send it + ops->send(ops, NULL, sendmessage); + metisMessage_Release(&sendmessage); + + // ---------- + // turn the handleto crank + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + + // ---------- + // Now read the result from our end of the socket pair. + + uint8_t read_buffer[1024]; + + // read it and verify + ssize_t read_length = read(fds[1], read_buffer, 1024); + assertTrue(read_length == sizeof(message_str), + "Incorrect read length, expected %zu got %zd: (%d) %s", + sizeof(message_str), read_length, errno, strerror(errno)); + + assertTrue(memcmp(read_buffer, message_str, sizeof(message_str)) == 0, "read_buffer does not match message_str"); + + // ---------- + // hurray, no messages where harmed in this experiment + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + close(fds[0]); + close(fds[1]); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); +} + +LONGBOW_TEST_CASE(Local, metisStreamConnection_GetConnectionType) +{ + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fd, pair, false); + + CPIConnectionType connType = _metisStreamConnection_GetConnectionType(ops); + assertTrue(connType == cpiConnection_TCP, "Wrong connection type expected %d got %d", cpiConnection_TCP, connType); + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); + close(fd); +} + +LONGBOW_TEST_CASE(Local, printConnection) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, readMessage) +{ + char message_str[] = "\x00Once upon a jiffie, in a stack far away, a dangling pointer found its way to the top of the heap."; + _MetisTlvFixedHeaderV0 *hdr = (_MetisTlvFixedHeaderV0 *) message_str; + hdr->payloadLength = htons(92); + hdr->headerLength = htons(0); + + PARCEventBuffer *buff = parcEventBuffer_Create(); + parcEventBuffer_Append(buff, message_str, sizeof(message_str)); + + _MetisStreamState *stream = parcMemory_AllocateAndClear(sizeof(_MetisStreamState)); + assertNotNull(stream, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisStreamState)); + stream->nextMessageLength = parcEventBuffer_GetLength(buff); + stream->id = 77; + + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + stream->logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + MetisMessage *message = _readMessage(stream, 444, buff); + + assertNotNull(message, "Got null message from readMessage"); + assertTrue(parcEventBuffer_GetLength(buff) == 0, "Did not drain input buffer, expected 0 got %zu", parcEventBuffer_GetLength(buff)); + //assertTrue(metisMessage_Length(message) == sizeof(message_str), + //"Message length wrong, expected %zu got %zu", + //sizeof(message_str), + //metisMessage_Length(message)); + + metisMessage_Release(&message); + parcEventBuffer_Destroy(&buff); + metisLogger_Release(&stream->logger); + parcMemory_Deallocate((void **) &stream); +} + +LONGBOW_TEST_CASE(Local, setConnectionState) +{ + testUnimplemented("This test is unimplemented"); +} + +/** + * Call like the beignning of a new packet, with stream->nextMessageLength set to 0 + */ +LONGBOW_TEST_CASE(Local, single_read_ZeroNextMessageLength) +{ + PARCEventBuffer *buff = parcEventBuffer_Create(); + + // do it like a short read, only 12 bytes + parcEventBuffer_Append(buff, metisTestDataV0_EncodedInterest, 12); + + MetisForwarder *metis = metisForwarder_Create(NULL); + _MetisStreamState *stream = parcMemory_AllocateAndClear(sizeof(_MetisStreamState)); + assertNotNull(stream, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisStreamState)); + stream->metis = metis; + stream->nextMessageLength = 0; + stream->id = 77; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + stream->logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = _single_read(buff, stream); + + assertNull(message, "message should be null, its a short read"); + assertTrue(parcEventBuffer_GetLength(buff) == 12, "Should not have drained buffer, expected %d got %zu", 12, parcEventBuffer_GetLength(buff)); + assertTrue(stream->nextMessageLength == sizeof(metisTestDataV0_EncodedInterest), + "NextMessageLength not set correctly, expected %zu got %zu", + sizeof(metisTestDataV0_EncodedInterest), + stream->nextMessageLength); + + parcEventBuffer_Destroy(&buff); + metisLogger_Release(&stream->logger); + parcMemory_Deallocate((void **) &stream); + metisForwarder_Destroy(&metis); +} + +/** + * Call with stream->nextMessageLength set correctly, but not enough bytes in the buffer + */ +LONGBOW_TEST_CASE(Local, single_read_PartialRead) +{ + PARCEventBuffer *buff = parcEventBuffer_Create(); + + // do it like a short read, only 12 bytes + parcEventBuffer_Append(buff, metisTestDataV0_EncodedInterest, 12); + + MetisForwarder *metis = metisForwarder_Create(NULL); + _MetisStreamState *stream = parcMemory_AllocateAndClear(sizeof(_MetisStreamState)); + assertNotNull(stream, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisStreamState)); + stream->metis = metis; + stream->nextMessageLength = sizeof(metisTestDataV0_EncodedInterest); + stream->id = 77; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + stream->logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = _single_read(buff, stream); + + assertNull(message, "message should be null, its a short read"); + assertTrue(parcEventBuffer_GetLength(buff) == 12, "Should not have drained buffer, expected %d got %zu", 12, parcEventBuffer_GetLength(buff)); + assertTrue(stream->nextMessageLength == sizeof(metisTestDataV0_EncodedInterest), + "NextMessageLength not set correctly, expected %zu got %zu", + sizeof(metisTestDataV0_EncodedInterest), + stream->nextMessageLength); + + parcEventBuffer_Destroy(&buff); + metisLogger_Release(&stream->logger); + parcMemory_Deallocate((void **) &stream); + metisForwarder_Destroy(&metis); +} + +/** + * Call with enough bytes in the buffer to read the whole message + */ +LONGBOW_TEST_CASE(Local, single_read_FullRead) +{ + PARCEventBuffer *buff = parcEventBuffer_Create(); + + // do it like a full read + parcEventBuffer_Append(buff, metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest)); + + MetisForwarder *metis = metisForwarder_Create(NULL); + _MetisStreamState *stream = parcMemory_AllocateAndClear(sizeof(_MetisStreamState)); + assertNotNull(stream, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisStreamState)); + stream->metis = metis; + stream->nextMessageLength = sizeof(metisTestDataV0_EncodedInterest); + stream->id = 77; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + stream->logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = _single_read(buff, stream); + + assertNotNull(message, "message should not be null, its a short read"); + assertTrue(parcEventBuffer_GetLength(buff) == 0, "Should have drained buffer, expected %d got %zu", 0, parcEventBuffer_GetLength(buff)); + + // should reset the next message length after reading a whole packet + assertTrue(stream->nextMessageLength == 0, + "NextMessageLength not set correctly, expected %u got %zu", + 0, + stream->nextMessageLength); + + metisMessage_Release(&message); + parcEventBuffer_Destroy(&buff); + metisLogger_Release(&stream->logger); + parcMemory_Deallocate((void **) &stream); + metisForwarder_Destroy(&metis); +} + +LONGBOW_TEST_CASE(Local, startNewMessage) +{ + _MetisStreamState *stream = parcMemory_AllocateAndClear(sizeof(_MetisStreamState)); + assertNotNull(stream, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisStreamState)); + + // add data to the buffer to fake out having read from the network + PARCEventBuffer *buff = parcEventBuffer_Create(); + uint8_t *truth_message = parcMemory_Allocate(100); + assertNotNull(truth_message, "parcMemory_Allocate(%u) returned NULL", 100); + + _MetisTlvFixedHeaderV0 *hdr = (_MetisTlvFixedHeaderV0 *) truth_message; + hdr->version = 0; + hdr->payloadLength = htons(92); + hdr->headerLength = htons(0); + + parcEventBuffer_Append(buff, truth_message, 100); + + stream->nextMessageLength = 0; + + _startNewMessage(stream, buff, 100); + + assertTrue(stream->nextMessageLength == 100, "nextMessageLength wrong, expected %d got %zu", 100, stream->nextMessageLength); + + parcEventBuffer_Destroy(&buff); + parcMemory_Deallocate((void **) &stream); + parcMemory_Deallocate((void **) &truth_message); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_StreamConnection); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_TcpListener.c b/metis/ccnx/forwarder/metis/io/test/test_metis_TcpListener.c new file mode 100644 index 00000000..87e51ae2 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_TcpListener.c @@ -0,0 +1,364 @@ +/* + * 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. + */ + +/* + * hard-coded in port 49009 on localhost + */ + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_TcpListener.c" +#include <LongBow/unit-test.h> +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> +#include <parc/algol/parc_SafeMemory.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h> + +#include <parc/algol/parc_Network.h> + +// for inet_pton +#include <arpa/inet.h> + +#include <signal.h> + +struct test_set { + CPIAddress *listenAddress; + MetisForwarder *metis; + MetisListenerOps *ops; +} TestSet; + +static void +setupInetListener() +{ + struct sockaddr_in addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(49009); + inet_pton(AF_INET, "127.0.0.1", &(addr.sin_addr)); + + TestSet.metis = metisForwarder_Create(NULL); + TestSet.ops = metisTcpListener_CreateInet(TestSet.metis, addr); + TestSet.listenAddress = cpiAddress_CreateFromInet(&addr); + + // crank the event scheduler once + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) {0, 10000})); +} + +static void +setupInet6Listener() +{ + struct sockaddr_in6 addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(49009); + + // "::1" is the ipv6 loopback address + inet_pton(AF_INET6, "::1", &(addr.sin6_addr)); + + TestSet.metis = metisForwarder_Create(NULL); + TestSet.ops = metisTcpListener_CreateInet6(TestSet.metis, addr); + TestSet.listenAddress = cpiAddress_CreateFromInet6(&addr); + + // crank the event scheduler once + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) {0, 10000})); +} + +static void +teardownListener() +{ + cpiAddress_Destroy(&TestSet.listenAddress); + TestSet.ops->destroy(&TestSet.ops); + metisForwarder_Destroy(&TestSet.metis); +} + +struct sigaction save_sigchld; +struct sigaction save_sigpipe; + +static void +blockSigChild() +{ + struct sigaction ignore_action; + ignore_action.sa_handler = SIG_IGN; + sigemptyset(&ignore_action.sa_mask); + ignore_action.sa_flags = 0; + + sigaction(SIGCHLD, NULL, &save_sigchld); + sigaction(SIGPIPE, NULL, &save_sigpipe); + + sigaction(SIGCHLD, &ignore_action, NULL); + sigaction(SIGPIPE, &ignore_action, NULL); +} + +static void +unblockSigChild() +{ + sigaction(SIGCHLD, &save_sigchld, NULL); + sigaction(SIGPIPE, &save_sigpipe, NULL); +} + +LONGBOW_TEST_RUNNER(metis_TcpListener) +{ + // 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_Inet); + LONGBOW_RUN_TEST_FIXTURE(Global_Inet6); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_TcpListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_TcpListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// =========================================================================== + +LONGBOW_TEST_FIXTURE(Global_Inet) +{ + LONGBOW_RUN_TEST_CASE(Global_Inet, metisListenerTcp_CreateInet); + LONGBOW_RUN_TEST_CASE(Global_Inet, metisListenerTcp_Connect); + LONGBOW_RUN_TEST_CASE(Global_Inet, metisListenerTcp_SendPacket); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global_Inet) +{ + setupInetListener(); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global_Inet) +{ + teardownListener(); + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global_Inet, metisListenerTcp_CreateInet) +{ + // now verify that we are listening + // tcp4 0 0 127.0.0.1.49009 *.* LISTEN + + blockSigChild(); + FILE *fp = popen("netstat -an -p tcp", "r"); + assertNotNull(fp, "Got null opening netstat for reading"); + + char str[1035]; + bool found = false; + while (fgets(str, sizeof(str) - 1, fp) != NULL) { + if (strstr(str, "127.0.0.1.49009") != NULL) { + found = true; + break; + } + if (strstr(str, "127.0.0.1:49009") != NULL) { + found = true; + break; + } + } + + pclose(fp); + unblockSigChild(); + + if (!found) { + int ret = system("netstat -an -p tcp"); + assertTrue(ret > -1, "Error on system call"); + } + + assertTrue(found, "Did not find 127.0.0.1.49009 in netstat output"); +} + +LONGBOW_TEST_CASE(Global_Inet, metisListenerTcp_Connect) +{ + int fd = socket(PF_INET, SOCK_STREAM, 0); + assertFalse(fd < 0, "Error on socket: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in serverAddress; + cpiAddress_GetInet(TestSet.listenAddress, &serverAddress); + + int failure = connect(fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)); + assertFalse(failure, "Error on connect: (%d) %s", errno, strerror(errno)); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) {0, 10000})); + + struct sockaddr_in connectAddress; + socklen_t connectAddressLength = sizeof(connectAddress); + failure = getsockname(fd, (struct sockaddr *) &connectAddress, &connectAddressLength); + assertFalse(failure, "Error on getsockname: (%d) %s", errno, strerror(errno)); + assertTrue(connectAddressLength == sizeof(struct sockaddr_in), + "connectAddressLength wrong size, expected %zu got %u", + sizeof(struct sockaddr_in), connectAddressLength); + + // make sure its in the connection table + MetisConnectionTable *table = metisForwarder_GetConnectionTable(TestSet.metis); + CPIAddress *remote = cpiAddress_CreateFromInet(&connectAddress); + MetisAddressPair *pair = metisAddressPair_Create(TestSet.listenAddress, remote); + const MetisConnection *conn = metisConnectionTable_FindByAddressPair(table, pair); + assertNotNull(conn, "Did not find connection in connection table"); + + cpiAddress_Destroy(&remote); + metisAddressPair_Release(&pair); + + close(fd); +} + +LONGBOW_TEST_CASE(Global_Inet, metisListenerTcp_SendPacket) +{ + int fd = socket(PF_INET, SOCK_STREAM, 0); + assertFalse(fd < 0, "Error on socket: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in serverAddress; + cpiAddress_GetInet(TestSet.listenAddress, &serverAddress); + + int failure = connect(fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)); + assertFalse(failure, "Error on connect: (%d) %s", errno, strerror(errno)); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) {0, 10000})); + + ssize_t write_length = write(fd, metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName)); + assertFalse(write_length < 0, "Error on write: (%d) %s", errno, strerror(errno)); + assertTrue(write_length == sizeof(metisTestDataV0_InterestWithName), "Got partial write, expected %zu got %zd", sizeof(metisTestDataV0_InterestWithName), write_length); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) {0, 10000})); + + close(fd); +} + +// =========================================================================== + +LONGBOW_TEST_FIXTURE(Global_Inet6) +{ + LONGBOW_RUN_TEST_CASE(Global_Inet6, metisListenerTcp_CreateInet6); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global_Inet6) +{ + setupInet6Listener(); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global_Inet6) +{ + teardownListener(); + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global_Inet6, metisListenerTcp_CreateInet6) +{ + // now verify that we are listening + // tcp6 0 0 ::1.49009 *.* LISTEN + + blockSigChild(); + FILE *fp = popen("netstat -an -p tcp", "r"); + assertNotNull(fp, "Got null opening netstat for reading"); + + char str[1035]; + bool found = false; + while (fgets(str, sizeof(str) - 1, fp) != NULL) { + if (strstr(str, "::1.49009") != NULL) { + found = true; + break; + } + if (strstr(str, "::1:49009") != NULL) { + found = true; + break; + } + } + + pclose(fp); + unblockSigChild(); + + if (!found) { + int ret = system("netstat -an -p tcp"); + assertTrue(ret > -1, "Error on system call"); + } + + assertTrue(found, "Did not find ::1.49009 in netstat output"); +} + +// =========================================================================== + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisListenerTcp_Listen); +} + +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; +} + +/** + * Create a TCP INET listener then connect to it. + */ +LONGBOW_TEST_CASE(Local, metisListenerTcp_Listen) +{ + setupInetListener(); + + struct sockaddr_in addr_remote; + + memset(&addr_remote, 0, sizeof(addr_remote)); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(49010); + inet_pton(AF_INET, "127.0.0.1", &(addr_remote.sin_addr)); + + _MetisTcpListener *tcp = (_MetisTcpListener *) TestSet.ops->context; + + int fds[2]; + int failure = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds); + assertFalse(failure, "Failed with socketpair: (%d) %s", errno, strerror(errno)); + + _metisTcpListener_Listen(fds[0], (struct sockaddr *) &addr_remote, sizeof(addr_remote), tcp); + + // now verify the connection is in the connection table + MetisConnectionTable *table = metisForwarder_GetConnectionTable(TestSet.metis); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(tcp->localAddress, remote); + const MetisConnection *conn = metisConnectionTable_FindByAddressPair(table, pair); + assertNotNull(conn, "Did not find connection in connection table"); + + cpiAddress_Destroy(&remote); + metisAddressPair_Release(&pair); + teardownListener(); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_TcpListener); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_TcpTunnel.c b/metis/ccnx/forwarder/metis/io/test/test_metis_TcpTunnel.c new file mode 100644 index 00000000..2d86fb17 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_TcpTunnel.c @@ -0,0 +1,216 @@ +/* + * 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_TcpTunnel.c" +#include <LongBow/unit-test.h> + +#include <parc/algol/parc_SafeMemory.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h> + +// so we can see packet events +#include "../../processor/test/testrig_MockTap.h" + +// inet_pton +#include <arpa/inet.h> + +#include <fcntl.h> + +#ifndef INPORT_ANY +#define INPORT_ANY 0 +#endif + +LONGBOW_TEST_RUNNER(metis_TcpTunnel) +{ + // 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_TcpTunnel) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_TcpTunnel) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +typedef struct test_data { + MetisForwarder *metis; + MetisDispatcher *dispatcher; + + int serverSocket; + struct sockaddr_in serverAddr; + struct sockaddr_in localAddr; + + CPIAddress *localCpiAddress; + CPIAddress *remoteCpiAddress; + + MetisIoOperations *tunnelOps; +} TestData; + +static void +listenToInet(TestData *data) +{ + int fd = socket(PF_INET, SOCK_STREAM, 0); + assertFalse(fd < 0, "error on bind: (%d) %s", errno, strerror(errno)); + + // Set non-blocking flag + int flags = fcntl(fd, F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)\n", errno); + int failure = fcntl(fd, F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)\n", errno); + + failure = bind(fd, (struct sockaddr *) &data->serverAddr, sizeof(struct sockaddr_in)); + assertFalse(failure, "error on bind: (%d) %s", errno, strerror(errno)); + + failure = listen(fd, 16); + assertFalse(failure, "error on listen: (%d) %s", errno, strerror(errno)); + + data->serverSocket = fd; + socklen_t x = sizeof(data->serverAddr); + getsockname(fd, (struct sockaddr *) &data->serverAddr, &x); +} + + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisTcpTunnel_Create); + LONGBOW_RUN_TEST_CASE(Global, metisTcpTunnel_Create_ConnectionStartsDown); + LONGBOW_RUN_TEST_CASE(Global, metisTcpTunnel_Create_UpStateAfterAccept); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + memset(&testTap, 0, sizeof(testTap)); + + TestData *data = malloc(sizeof(TestData)); + memset(data, 0, sizeof(TestData)); + + data->metis = metisForwarder_Create(NULL); + data->serverAddr.sin_family = PF_INET; + data->serverAddr.sin_port = INPORT_ANY; + inet_pton(AF_INET, "127.0.0.1", &(data->serverAddr.sin_addr)); + + data->localAddr.sin_family = PF_INET; + data->localAddr.sin_addr.s_addr = INADDR_ANY; + data->localAddr.sin_port = INPORT_ANY; + + listenToInet(data); + + data->localCpiAddress = cpiAddress_CreateFromInet(&data->localAddr); + data->remoteCpiAddress = cpiAddress_CreateFromInet(&data->serverAddr); + + data->dispatcher = metisForwarder_GetDispatcher(data->metis); + + longBowTestCase_SetClipBoardData(testCase, data); + + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + cpiAddress_Destroy(&data->localCpiAddress); + cpiAddress_Destroy(&data->remoteCpiAddress); + + close(data->serverSocket); + metisForwarder_Destroy(&data->metis); + free(data); + + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisTcpTunnel_Create) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + data->tunnelOps = metisTcpTunnel_Create(data->metis, data->localCpiAddress, data->remoteCpiAddress); + assertNotNull(data->tunnelOps, "Got null IO operations for the tunnel"); + data->tunnelOps->destroy(&data->tunnelOps); +} + + +LONGBOW_TEST_CASE(Global, metisTcpTunnel_Create_ConnectionStartsDown) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + data->tunnelOps = metisTcpTunnel_Create(data->metis, data->localCpiAddress, data->remoteCpiAddress); + assertFalse(data->tunnelOps->isUp(data->tunnelOps), "Connection is not down on start"); + data->tunnelOps->destroy(&data->tunnelOps); +} + +LONGBOW_TEST_CASE(Global, metisTcpTunnel_Create_UpStateAfterAccept) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + data->tunnelOps = metisTcpTunnel_Create(data->metis, data->localCpiAddress, data->remoteCpiAddress); + + // run for a milli second + metisDispatcher_RunDuration(data->dispatcher, &((struct timeval) { 0, 1000 })); + + // we should be able to accept + struct sockaddr_in clientAddr; + socklen_t clientAddrLength = sizeof(clientAddr); + + int clientSocket = accept(data->serverSocket, (struct sockaddr *) &clientAddr, &clientAddrLength); + assertFalse(clientSocket < 0, "error on accept: (%d) %s", errno, strerror(errno)); + + // run for a milli second + metisDispatcher_RunDuration(data->dispatcher, &((struct timeval) { 0, 1000 })); + + assertTrue(data->tunnelOps->isUp(data->tunnelOps), "Connection is not up after accept"); + data->tunnelOps->destroy(&data->tunnelOps); +} + + +LONGBOW_TEST_FIXTURE(Local) +{ +} + +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; +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_TcpTunnel); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_UdpConnection.c b/metis/ccnx/forwarder/metis/io/test/test_metis_UdpConnection.c new file mode 100644 index 00000000..db1f9c97 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_UdpConnection.c @@ -0,0 +1,535 @@ +/* + * 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 "../metis_UdpConnection.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +#include <ccnx/forwarder/metis/io/metis_UdpListener.h> +#include <ccnx/forwarder/metis/config/metis_Configuration.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h> + +// for inet_pton +#include <arpa/inet.h> + +#define ALICE_PORT 49018 +#define BOB_PORT 49019 + +// ---- Used to monitor Missive messages so we know when a connection is up +typedef struct test_notifier_data { + MetisMissiveType type; + unsigned connectionid; +} TestNotifierData; + +static void +testNotifier(MetisMessengerRecipient *recipient, MetisMissive *missive) +{ + struct test_notifier_data *data = metisMessengerRecipient_GetRecipientContext(recipient); + data->type = metisMissive_GetType(missive); + data->connectionid = metisMissive_GetConnectionId(missive); + metisMissive_Release(&missive); +} + +// ---- + +typedef struct test_tap_data { + unsigned onReceiveCount; + MetisMessage *message; +} TestTapData; + +static bool +testTap_IsTapOnReceive(const MetisTap *tap) +{ + return true; +} + +static void +testTap_TapOnReceive(MetisTap *tap, const MetisMessage *message) +{ + TestTapData *mytap = (TestTapData *) tap->context; + mytap->onReceiveCount++; + if (mytap->message) { + metisMessage_Release(&mytap->message); + } + + mytap->message = metisMessage_Acquire(message); +} + +static MetisTap testTapTemplate = { + .context = NULL, + .isTapOnReceive = &testTap_IsTapOnReceive, + .isTapOnSend = NULL, + .isTapOnDrop = NULL, + .tapOnReceive = &testTap_TapOnReceive, + .tapOnSend = NULL, + .tapOnDrop = NULL +}; + +// --- Used to inspect packets received + + +typedef struct test_data { + int remoteSocket; + +#define ALICE 0 +#define BOB 1 + + MetisForwarder *metis[2]; + MetisListenerOps *listener[2]; + MetisMessengerRecipient *recipient[2]; + TestNotifierData notifierData[2]; + MetisTap taps[2]; + TestTapData tapData[2]; +} TestData; + + + +static void +_crankHandle(TestData *data) +{ + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis[ALICE]), &((struct timeval) {0, 10000})); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis[BOB]), &((struct timeval) {0, 10000})); +} + +static void +_setup(TestData *data, int side, uint16_t port) +{ + data->metis[side] = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(data->metis[side]), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + struct sockaddr_in addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + inet_pton(AF_INET, "127.0.0.1", &(addr.sin_addr)); + + data->listener[side] = metisUdpListener_CreateInet(data->metis[side], addr); + + // snoop events + data->recipient[side] = metisMessengerRecipient_Create(&data->notifierData[side], testNotifier); + MetisMessenger *messenger = metisForwarder_GetMessenger(data->metis[side]); + metisMessenger_Register(messenger, data->recipient[side]); + + // snoop packets + memcpy(&data->taps[side], &testTapTemplate, sizeof(testTapTemplate)); + data->taps[side].context = &data->tapData[side]; + metisForwarder_AddTap(data->metis[side], &data->taps[side]); + + // save in Metis + metisListenerSet_Add(metisForwarder_GetListenerSet(data->metis[side]), data->listener[side]); +} + +/* + * Create a UDP socket pair + */ +static void +_commonSetup(const LongBowTestCase *testCase) +{ + TestData *data = parcMemory_AllocateAndClear(sizeof(TestData)); + + _setup(data, ALICE, ALICE_PORT); + _setup(data, BOB, BOB_PORT); + + _crankHandle(data); + + longBowTestCase_SetClipBoardData(testCase, data); +} + +static void +_commonTeardown(const LongBowTestCase *testCase) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // the listeners are stored in the respectie Metis, so we don't need to + // destroy those separately. + + metisMessengerRecipient_Destroy(&data->recipient[ALICE]); + metisMessengerRecipient_Destroy(&data->recipient[BOB]); + + if (data->tapData[ALICE].message) { + metisMessage_Release(&data->tapData[ALICE].message); + } + + if (data->tapData[BOB].message) { + metisMessage_Release(&data->tapData[BOB].message); + } + + metisForwarder_Destroy(&data->metis[ALICE]); + metisForwarder_Destroy(&data->metis[BOB]); + + parcMemory_Deallocate((void **) &data); +} + +LONGBOW_TEST_RUNNER(metis_UdpConnection) +{ + 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_UdpConnection) +{ + 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_UdpConnection) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + //XXX: this test does not work anymore because we do not create the connection in metis + //LONGBOW_RUN_TEST_CASE(Global, metisUdpConnection_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + _commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + _commonTeardown(testCase); + 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; +} + +/* + * Create connection from ALICE to BOB + */ +LONGBOW_TEST_CASE(Global, metisUdpConnection_Create) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + const CPIAddress *bobAddress = data->listener[BOB]->getListenAddress(data->listener[BOB]); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + int fd = data->listener[ALICE]->getSocket(data->listener[ALICE]); + MetisIoOperations *ops = metisUdpConnection_Create(data->metis[ALICE], fd, pair, false); + metisAddressPair_Release(&pair); + + _crankHandle(data); + + // send a data packet to bring it up on BOB + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV1_Interest_NameA_Crc32c, sizeof(metisTestDataV1_Interest_NameA_Crc32c), 2, 3, metisForwarder_GetLogger(data->metis[ALICE])); + + ops->send(ops, NULL, message); + + metisMessage_Release(&message); + + // wait until we indicate that the connection is up on Bob's side + while (data->notifierData[BOB].type == MetisMissiveType_ConnectionDestroyed) { + _crankHandle(data); + } + + // and verify that the message was sent in to the message processor on Bob's side + assertTrue(data->tapData[BOB].onReceiveCount == 1, "Wrong receive count, expected 1 got %u", data->tapData[BOB].onReceiveCount); + + ops->destroy(&ops); +} + +// =========================================================== + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _saveSockaddr_INET); + LONGBOW_RUN_TEST_CASE(Local, _saveSockaddr_INET6); + LONGBOW_RUN_TEST_CASE(Local, _send); + LONGBOW_RUN_TEST_CASE(Local, _getRemoteAddress); + LONGBOW_RUN_TEST_CASE(Local, _getAddressPair); + LONGBOW_RUN_TEST_CASE(Local, _getConnectionId); + LONGBOW_RUN_TEST_CASE(Local, _isUp); + LONGBOW_RUN_TEST_CASE(Local, _isLocal_True); + LONGBOW_RUN_TEST_CASE(Local, _setConnectionState); + LONGBOW_RUN_TEST_CASE(Local, _getConnectionType); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + _commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + _commonTeardown(testCase); + 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, _saveSockaddr_INET) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + _MetisUdpState *udpConnState = parcMemory_AllocateAndClear(sizeof(_MetisUdpState)); + assertNotNull(udpConnState, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisUdpState)); + + udpConnState->metis = data->metis[ALICE]; + udpConnState->logger = metisLogger_Acquire(metisForwarder_GetLogger(udpConnState->metis)); + + struct sockaddr_in sin1, sin2; + + memset(&sin1, 0, sizeof(sin1)); + sin1.sin_family = AF_INET; + sin1.sin_port = htons(ALICE_PORT); + inet_pton(AF_INET, "127.0.0.1", &(sin1.sin_addr)); + + memset(&sin2, 0, sizeof(sin2)); + sin2.sin_family = AF_INET; + sin2.sin_port = htons(BOB_PORT); + inet_pton(AF_INET, "127.0.0.1", &(sin2.sin_addr)); + + CPIAddress *aliceAddress = cpiAddress_CreateFromInet(&sin1); + CPIAddress *bobAddress = cpiAddress_CreateFromInet(&sin2); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + + bool saved = _saveSockaddr(udpConnState, pair); + assertTrue(saved, "Failed to save address"); + assertTrue(udpConnState->peerAddressLength == sizeof(sin1), "Wrong length, expected %zu got %u", sizeof(sin1), udpConnState->peerAddressLength); + + cpiAddress_Destroy(&aliceAddress); + cpiAddress_Destroy(&bobAddress); + + metisAddressPair_Release(&pair); + metisLogger_Release(&udpConnState->logger); + parcMemory_Deallocate((void **) &udpConnState->peerAddress); + parcMemory_Deallocate((void **) &udpConnState); +} + +LONGBOW_TEST_CASE(Local, _saveSockaddr_INET6) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + _MetisUdpState *udpConnState = parcMemory_AllocateAndClear(sizeof(_MetisUdpState)); + assertNotNull(udpConnState, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisUdpState)); + + udpConnState->metis = data->metis[ALICE]; + udpConnState->logger = metisLogger_Acquire(metisForwarder_GetLogger(udpConnState->metis)); + + struct sockaddr_in6 sin1, sin2; + + memset(&sin1, 0, sizeof(sin1)); + sin1.sin6_family = AF_INET6; + sin1.sin6_port = htons(ALICE_PORT); + int ok = inet_pton(AF_INET6, "::1", &(sin1.sin6_addr)); + + if (ok) { + memset(&sin2, 0, sizeof(sin2)); + sin2.sin6_family = AF_INET6; + sin2.sin6_port = htons(BOB_PORT); + inet_pton(AF_INET6, "::1", &(sin2.sin6_addr)); + + CPIAddress *aliceAddress = cpiAddress_CreateFromInet6(&sin1); + CPIAddress *bobAddress = cpiAddress_CreateFromInet6(&sin2); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + + bool saved = _saveSockaddr(udpConnState, pair); + assertTrue(saved, "Failed to save address"); + assertTrue(udpConnState->peerAddressLength == sizeof(sin1), "Wrong length, expected %zu got %u", sizeof(sin1), udpConnState->peerAddressLength); + + cpiAddress_Destroy(&aliceAddress); + cpiAddress_Destroy(&bobAddress); + + metisAddressPair_Release(&pair); + } else { + testSkip("Skipping inet6 test"); + } + + metisLogger_Release(&udpConnState->logger); + parcMemory_Deallocate((void **) &udpConnState->peerAddress); + parcMemory_Deallocate((void **) &udpConnState); +} + +LONGBOW_TEST_CASE(Local, _send) +{ +} + +LONGBOW_TEST_CASE(Local, _getRemoteAddress) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + const CPIAddress *bobAddress = data->listener[BOB]->getListenAddress(data->listener[BOB]); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + int fd = data->listener[ALICE]->getSocket(data->listener[ALICE]); + MetisIoOperations *ops = metisUdpConnection_Create(data->metis[ALICE], fd, pair, false); + metisAddressPair_Release(&pair); + + // now run test + const CPIAddress *test = _getRemoteAddress(ops); + assertNotNull(test, "Got null remote address"); + assertTrue(cpiAddress_Equals(test, bobAddress), "Addresses do not match"); + ops->destroy(&ops); +} + +LONGBOW_TEST_CASE(Local, _getAddressPair) +{ +} + +LONGBOW_TEST_CASE(Local, _getConnectionId) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + const CPIAddress *bobAddress = data->listener[BOB]->getListenAddress(data->listener[BOB]); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + int fd = data->listener[ALICE]->getSocket(data->listener[ALICE]); + MetisIoOperations *ops = metisUdpConnection_Create(data->metis[ALICE], fd, pair, false); + metisAddressPair_Release(&pair); + + // now run test + unsigned connid = _getConnectionId(ops); + + assertTrue(connid > 0, "Expected positive connid, got 0"); + ops->destroy(&ops); +} + +LONGBOW_TEST_CASE(Local, _isUp) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + const CPIAddress *bobAddress = data->listener[BOB]->getListenAddress(data->listener[BOB]); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + int fd = data->listener[ALICE]->getSocket(data->listener[ALICE]); + MetisIoOperations *ops = metisUdpConnection_Create(data->metis[ALICE], fd, pair, false); + metisAddressPair_Release(&pair); + + // now run test + bool isup = _isUp(ops); + + assertTrue(isup, "Expected connection to be up"); + ops->destroy(&ops); +} + +LONGBOW_TEST_CASE(Local, _isLocal_True) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + const CPIAddress *bobAddress = data->listener[BOB]->getListenAddress(data->listener[BOB]); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + int fd = data->listener[ALICE]->getSocket(data->listener[ALICE]); + MetisIoOperations *ops = metisUdpConnection_Create(data->metis[ALICE], fd, pair, true); + metisAddressPair_Release(&pair); + + // now run test + bool islocal = _isLocal(ops); + + assertTrue(islocal, "Expected connection to be local"); + ops->destroy(&ops); +} + +LONGBOW_TEST_CASE(Local, _setConnectionState) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + const CPIAddress *bobAddress = data->listener[BOB]->getListenAddress(data->listener[BOB]); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + int fd = data->listener[ALICE]->getSocket(data->listener[ALICE]); + MetisIoOperations *ops = metisUdpConnection_Create(data->metis[ALICE], fd, pair, true); + metisAddressPair_Release(&pair); + + // now run test + _MetisUdpState *udpConnState = (_MetisUdpState *) metisIoOperations_GetClosure(ops); + + _setConnectionState(udpConnState, false); + bool isup = _isUp(ops); + + assertFalse(isup, "Expected connection to be down"); + ops->destroy(&ops); +} + +LONGBOW_TEST_CASE(Local, _getConnectionType) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + const CPIAddress *bobAddress = data->listener[BOB]->getListenAddress(data->listener[BOB]); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + int fd = data->listener[ALICE]->getSocket(data->listener[ALICE]); + MetisIoOperations *ops = metisUdpConnection_Create(data->metis[ALICE], fd, pair, false); + metisAddressPair_Release(&pair); + + // now run test + CPIConnectionType connType = _getConnectionType(ops); + + assertTrue(connType == cpiConnection_UDP, "Expected connection to be %d got %d", cpiConnection_UDP, connType); + ops->destroy(&ops); +} + + +// =========================================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_UdpConnection); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_UdpListener.c b/metis/ccnx/forwarder/metis/io/test/test_metis_UdpListener.c new file mode 100644 index 00000000..96dfc4fd --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_UdpListener.c @@ -0,0 +1,458 @@ +/* + * 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. + */ + +/** + */ + +/* + * hard-coded in port 49009 on localhost + */ + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_UdpListener.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> +#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h> +#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h> + +// for inet_pton +#include <arpa/inet.h> + +#include <signal.h> + +// ======================================================== + +struct test_set { + CPIAddress *listenAddress; + MetisForwarder *metis; + MetisListenerOps *ops; +} TestSet; + +static void +setupInetListener() +{ + struct sockaddr_in addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(49009); + inet_pton(AF_INET, "127.0.0.1", &(addr.sin_addr)); + + TestSet.metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(TestSet.metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + TestSet.ops = metisUdpListener_CreateInet(TestSet.metis, addr); + TestSet.listenAddress = cpiAddress_CreateFromInet(&addr); + + // crank the handle + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); +} + +static void +teardownListener() +{ + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + cpiAddress_Destroy(&TestSet.listenAddress); + TestSet.ops->destroy(&TestSet.ops); + metisForwarder_Destroy(&TestSet.metis); +} + +static bool +setupInet6Listener() +{ + struct sockaddr_in6 addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(49009); + + // "::1" is the ipv6 loopback address + int ok = inet_pton(AF_INET6, "::1", &(addr.sin6_addr)); + if (ok > 0) { + TestSet.metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(TestSet.metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + TestSet.ops = metisUdpListener_CreateInet6(TestSet.metis, addr); + TestSet.listenAddress = cpiAddress_CreateFromInet6(&addr); + + // crank the handle + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + return true; + } + return false; +} + +struct sigaction save_sigchld; +struct sigaction save_sigpipe; + +static void +blockSigChild() +{ + struct sigaction ignore_action; + ignore_action.sa_handler = SIG_IGN; + sigemptyset(&ignore_action.sa_mask); + ignore_action.sa_flags = 0; + + sigaction(SIGCHLD, NULL, &save_sigchld); + sigaction(SIGPIPE, NULL, &save_sigpipe); + + sigaction(SIGCHLD, &ignore_action, NULL); + sigaction(SIGPIPE, &ignore_action, NULL); +} + +static void +unblockSigChild() +{ + sigaction(SIGCHLD, &save_sigchld, NULL); + sigaction(SIGPIPE, &save_sigpipe, NULL); +} + +// ======================================================== + +LONGBOW_TEST_RUNNER(metis_UdpListener) +{ + LONGBOW_RUN_TEST_FIXTURE(Global_Inet); + // XXX: Udp code has issues. It should check return values from calls. + // There are bugs in the UDP code that need to be fixed. These are shown in + // this test. The code needs to be fixed first. + //LONGBOW_RUN_TEST_FIXTURE(Global_Inet6); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_UdpListener) +{ + 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_UdpListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ================================================================================ + +LONGBOW_TEST_FIXTURE(Global_Inet) +{ + LONGBOW_RUN_TEST_CASE(Global_Inet, metisListenerUdp_CreateInet); + //XXX: this does not work anymore because we do not create the udp connection + //LONGBOW_RUN_TEST_CASE(Global_Inet, metisListenerUdp_Connect); + LONGBOW_RUN_TEST_CASE(Global_Inet, metisListenerUdp_SendPacket); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global_Inet) +{ + setupInetListener(); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global_Inet) +{ + teardownListener(); + 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_Inet, metisListenerUdp_CreateInet) +{ + // now verify that we are listening + // udp4 0 0 127.0.0.1.49009 *.* LISTEN + + blockSigChild(); + FILE *fp = popen("netstat -an -p udp", "r"); + assertNotNull(fp, "Got null opening netstat for reading"); + + char str[1035]; + bool found = false; + while (fgets(str, sizeof(str) - 1, fp) != NULL) { + if (strstr(str, "127.0.0.1.49009") != NULL) { + found = true; + break; + } + if (strstr(str, "127.0.0.1:49009") != NULL) { + found = true; + break; + } + } + + pclose(fp); + unblockSigChild(); + + if (!found) { + int ret = system("netstat -an -p udp"); + assertTrue(ret > -1, "Error on system call"); + } + + assertTrue(found, "Did not find 127.0.0.1.49009 in netstat output"); +} + +LONGBOW_TEST_CASE(Global_Inet, metisListenerUdp_Connect) +{ + int fd = socket(PF_INET, SOCK_DGRAM, 0); + assertFalse(fd < 0, "Error on socket: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in serverAddress; + cpiAddress_GetInet(TestSet.listenAddress, &serverAddress); + + int failure = connect(fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)); + assertFalse(failure, "Error on connect: (%d) %s", errno, strerror(errno)); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + + struct sockaddr_in connectAddress; + socklen_t connectAddressLength = sizeof(connectAddress); + failure = getsockname(fd, (struct sockaddr *) &connectAddress, &connectAddressLength); + assertFalse(failure, "Error on getsockname: (%d) %s", errno, strerror(errno)); + assertTrue(connectAddressLength == sizeof(struct sockaddr_in), + "connectAddressLength wrong size, expected %zu got %u", + sizeof(struct sockaddr_in), connectAddressLength); + + // Unlike TCP, we need to actually send something + ssize_t nwritten = write(fd, metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest)); + assertTrue(nwritten == sizeof(metisTestDataV0_EncodedInterest), "Error on write expected %zu got %zd", sizeof(metisTestDataV0_EncodedInterest), nwritten); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + + // make sure its in the connection table + MetisConnectionTable *table = metisForwarder_GetConnectionTable(TestSet.metis); + CPIAddress *remote = cpiAddress_CreateFromInet(&connectAddress); + MetisAddressPair *pair = metisAddressPair_Create(TestSet.listenAddress, remote); + const MetisConnection *conn = metisConnectionTable_FindByAddressPair(table, pair); + assertNotNull(conn, "Did not find connection in connection table"); + + cpiAddress_Destroy(&remote); + metisAddressPair_Release(&pair); + + close(fd); +} + +LONGBOW_TEST_CASE(Global_Inet, metisListenerUdp_SendPacket) +{ + int fd = socket(PF_INET, SOCK_DGRAM, 0); + assertFalse(fd < 0, "Error on socket: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in serverAddress; + cpiAddress_GetInet(TestSet.listenAddress, &serverAddress); + + int failure = connect(fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)); + assertFalse(failure, "Error on connect socket %d: (%d) %s\n", fd, errno, strerror(errno)); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + + ssize_t write_length = write(fd, metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName)); + assertFalse(write_length < 0, "Error on write: (%d) %s", errno, strerror(errno)); + assertTrue(write_length == sizeof(metisTestDataV0_InterestWithName), "Got partial write, expected %zu got %zd", sizeof(metisTestDataV0_InterestWithName), write_length); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + + close(fd); +} + +// ================================================================================ + +LONGBOW_TEST_FIXTURE(Global_Inet6) +{ + LONGBOW_RUN_TEST_CASE(Global_Inet6, metisListenerUdp_CreateInet6); + LONGBOW_RUN_TEST_CASE(Global_Inet6, metisListenerUdp_Connect); + LONGBOW_RUN_TEST_CASE(Global_Inet6, metisListenerUdp_SendPacket); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global_Inet6) +{ + bool success = setupInet6Listener(); + if (!success) { + return LONGBOW_STATUS_SKIPPED; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global_Inet6) +{ + teardownListener(); + 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_Inet6, metisListenerUdp_CreateInet6) +{ + // now verify that we are listening + // udp6 0 0 ::1.49009 *.* + + blockSigChild(); + FILE *fp = popen("netstat -an -p udp", "r"); + assertNotNull(fp, "Got null opening netstat for reading"); + + char str[1035]; + bool found = false; + while (fgets(str, sizeof(str) - 1, fp) != NULL) { + //printf("%s\n", str); + if (strstr(str, "::1.49009") != NULL) { + found = true; + break; + } + if (strstr(str, "::1:49009") != NULL) { + found = true; + break; + } + } + + pclose(fp); + unblockSigChild(); + + if (!found) { + int ret = system("netstat -an -p udp"); + assertTrue(ret != -1, "Error on system() call"); + } + + assertTrue(found, "Did not find 127.0.0.1.49009 in netstat output"); +} + +LONGBOW_TEST_CASE(Global_Inet6, metisListenerUdp_Connect) +{ + int fd = socket(PF_INET6, SOCK_DGRAM, 0); + assertFalse(fd < 0, "Error on socket: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in6 serverAddress; + cpiAddress_GetInet6(TestSet.listenAddress, &serverAddress); + + int failure = connect(fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)); + assertFalse(failure, "Error on connect: (%d) %s", errno, strerror(errno)); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + + struct sockaddr_in6 connectAddress; + socklen_t connectAddressLength = sizeof(connectAddress); + failure = getsockname(fd, (struct sockaddr *) &connectAddress, &connectAddressLength); + assertFalse(failure, "Error on getsockname: (%d) %s", errno, strerror(errno)); + assertTrue(connectAddressLength == sizeof(struct sockaddr_in6), + "connectAddressLength wrong size, expected %zu got %u", + sizeof(struct sockaddr_in6), connectAddressLength); + + // Unlike TCP, we need to actually send something + ssize_t nwritten = write(fd, metisTestDataV1_Interest_NameA_Crc32c, sizeof(metisTestDataV1_Interest_NameA_Crc32c)); + assertTrue(nwritten == sizeof(metisTestDataV1_Interest_NameA_Crc32c), "Write failed, expected %zu got %zd", sizeof(metisTestDataV1_Interest_NameA_Crc32c), nwritten); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + + // make sure its in the connection table + MetisConnectionTable *table = metisForwarder_GetConnectionTable(TestSet.metis); + CPIAddress *remote = cpiAddress_CreateFromInet6(&connectAddress); + MetisAddressPair *pair = metisAddressPair_Create(TestSet.listenAddress, remote); + const MetisConnection *conn = metisConnectionTable_FindByAddressPair(table, pair); + assertNotNull(conn, "Did not find connection in connection table"); + + cpiAddress_Destroy(&remote); + metisAddressPair_Release(&pair); + + close(fd); +} + +LONGBOW_TEST_CASE(Global_Inet6, metisListenerUdp_SendPacket) +{ + int fd = socket(PF_INET6, SOCK_DGRAM, 0); + assertFalse(fd < 0, "Error on socket: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in6 serverAddress; + cpiAddress_GetInet6(TestSet.listenAddress, &serverAddress); + + int failure = connect(fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)); + assertFalse(failure, "Error on connect socket %d: (%d) %s\n", fd, errno, strerror(errno)); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + + ssize_t write_length = write(fd, metisTestDataV1_Interest_NameA_Crc32c, sizeof(metisTestDataV1_Interest_NameA_Crc32c)); + assertFalse(write_length < 0, "Error on write: (%d) %s", errno, strerror(errno)); + assertTrue(write_length == sizeof(metisTestDataV1_Interest_NameA_Crc32c), + "Got partial write, expected %zu got %zd", + sizeof(metisTestDataV1_Interest_NameA_Crc32c), write_length); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + + close(fd); +} + +// ================================================================================ + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _getInterfaceIndex); + LONGBOW_RUN_TEST_CASE(Local, _getListenAddress); + LONGBOW_RUN_TEST_CASE(Local, _getEncapType); + LONGBOW_RUN_TEST_CASE(Local, _getSocket); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + setupInetListener(); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + teardownListener(); + 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, _getInterfaceIndex) +{ + unsigned test = _getInterfaceIndex(TestSet.ops); + assertTrue(test > 0, "Unexpected interface index: %u", test); +} + +LONGBOW_TEST_CASE(Local, _getListenAddress) +{ + const CPIAddress *listenAddr = _getListenAddress(TestSet.ops); + assertNotNull(listenAddr, "Got null listen address"); +} + +LONGBOW_TEST_CASE(Local, _getEncapType) +{ + MetisEncapType type = _getEncapType(TestSet.ops); + assertTrue(type == METIS_ENCAP_UDP, "Unexpected address type, got %d expected %d", type, METIS_ENCAP_UDP); +} + +LONGBOW_TEST_CASE(Local, _getSocket) +{ + int fd = _getSocket(TestSet.ops); + assertTrue(fd > 0, "Unexpected socket, got %d, expected positive", fd); +} + + +// ================================================================================ + + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_UdpListener); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_UdpTunnel.c b/metis/ccnx/forwarder/metis/io/test/test_metis_UdpTunnel.c new file mode 100644 index 00000000..cba18ed3 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_UdpTunnel.c @@ -0,0 +1,327 @@ +/* + * 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. + */ + +/** + * Need a solution to avoid hard-coding port numbers + * + */ + +#include "../metis_UdpTunnel.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +#include <ccnx/forwarder/metis/io/metis_UdpListener.h> +#include <ccnx/forwarder/metis/config/metis_Configuration.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h> + +// for inet_pton +#include <arpa/inet.h> + +#define ALICE_PORT 49028 +#define BOB_PORT 49029 + +// ---- Used to monitor Missive messages so we know when a connection is up +typedef struct test_notifier_data { + MetisMissiveType type; + unsigned connectionid; +} TestNotifierData; + +static void +testNotifier(MetisMessengerRecipient *recipient, MetisMissive *missive) +{ + struct test_notifier_data *data = metisMessengerRecipient_GetRecipientContext(recipient); + data->type = metisMissive_GetType(missive); + data->connectionid = metisMissive_GetConnectionId(missive); + metisMissive_Release(&missive); +} + +// ---- + +typedef struct test_tap_data { + unsigned onReceiveCount; + MetisMessage *message; +} TestTapData; + +static bool +testTap_IsTapOnReceive(const MetisTap *tap) +{ + return true; +} + +static void +testTap_TapOnReceive(MetisTap *tap, const MetisMessage *message) +{ + TestTapData *mytap = (TestTapData *) tap->context; + mytap->onReceiveCount++; + if (mytap->message) { + metisMessage_Release(&mytap->message); + } + + mytap->message = metisMessage_Acquire(message); +} + +static MetisTap testTapTemplate = { + .context = NULL, + .isTapOnReceive = &testTap_IsTapOnReceive, + .isTapOnSend = NULL, + .isTapOnDrop = NULL, + .tapOnReceive = &testTap_TapOnReceive, + .tapOnSend = NULL, + .tapOnDrop = NULL +}; + +// --- Used to inspect packets received + + +typedef struct test_data { + int remoteSocket; + +#define ALICE 0 +#define BOB 1 + + MetisForwarder *metis[2]; + MetisListenerOps *listener[2]; + MetisMessengerRecipient *recipient[2]; + TestNotifierData notifierData[2]; + MetisTap taps[2]; + TestTapData tapData[2]; +} TestData; + + + +static void +_crankHandle(TestData *data) +{ + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis[ALICE]), &((struct timeval) {0, 10000})); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis[BOB]), &((struct timeval) {0, 10000})); +} + +static void +_setup(TestData *data, int side, uint16_t port) +{ + data->metis[side] = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(data->metis[side]), MetisLoggerFacility_IO, PARCLogLevel_Error); + + struct sockaddr_in addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + inet_pton(AF_INET, "127.0.0.1", &(addr.sin_addr)); + + data->listener[side] = metisUdpListener_CreateInet(data->metis[side], addr); + + // snoop events + data->recipient[side] = metisMessengerRecipient_Create(&data->notifierData[side], testNotifier); + MetisMessenger *messenger = metisForwarder_GetMessenger(data->metis[side]); + metisMessenger_Register(messenger, data->recipient[side]); + + // snoop packets + memcpy(&data->taps[side], &testTapTemplate, sizeof(testTapTemplate)); + data->taps[side].context = &data->tapData[side]; + metisForwarder_AddTap(data->metis[side], &data->taps[side]); + + // save in Metis + metisListenerSet_Add(metisForwarder_GetListenerSet(data->metis[side]), data->listener[side]); +} + +/* + * Create a UDP socket pair + */ +static void +_commonSetup(const LongBowTestCase *testCase) +{ + TestData *data = parcMemory_AllocateAndClear(sizeof(TestData)); + + _setup(data, ALICE, ALICE_PORT); + _setup(data, BOB, BOB_PORT); + + _crankHandle(data); + + longBowTestCase_SetClipBoardData(testCase, data); +} + +static void +_commonTeardown(const LongBowTestCase *testCase) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // the listeners are stored in the respectie Metis, so we don't need to + // destroy those separately. + + metisMessengerRecipient_Destroy(&data->recipient[ALICE]); + metisMessengerRecipient_Destroy(&data->recipient[BOB]); + + if (data->tapData[ALICE].message) { + metisMessage_Release(&data->tapData[ALICE].message); + } + + if (data->tapData[BOB].message) { + metisMessage_Release(&data->tapData[BOB].message); + } + + metisForwarder_Destroy(&data->metis[ALICE]); + metisForwarder_Destroy(&data->metis[BOB]); + + parcMemory_Deallocate((void **) &data); +} + +// ================================== + +LONGBOW_TEST_RUNNER(metis_UdpTunnel) +{ + LONGBOW_RUN_TEST_FIXTURE(Global); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_UdpTunnel) +{ + 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_UdpTunnel) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisUdpTunnel_CreateOnListener); + //XXX: this test does not work anymore beacuase we don't create the connection in metis + //LONGBOW_RUN_TEST_CASE(Global, metisUdpTunnel_Create); + LONGBOW_RUN_TEST_CASE(Global, metisUdpTunnel_Create_MismatchedTypes); + LONGBOW_RUN_TEST_CASE(Global, metisUdpTunnel_Create_NotFound); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + _commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + _commonTeardown(testCase); + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + + +LONGBOW_TEST_CASE(Global, metisUdpTunnel_CreateOnListener) +{ +} + +/* + * Create from Alice to Bob + */ +LONGBOW_TEST_CASE(Global, metisUdpTunnel_Create) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + const CPIAddress *bobAddress = data->listener[BOB]->getListenAddress(data->listener[BOB]); + + MetisIoOperations *ops = metisUdpTunnel_Create(data->metis[ALICE], aliceAddress, bobAddress); + + _crankHandle(data); + + // send a data packet to bring it up on BOB + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV1_Interest_NameA_Crc32c, sizeof(metisTestDataV1_Interest_NameA_Crc32c), 2, 3, metisForwarder_GetLogger(data->metis[ALICE])); + + ops->send(ops, NULL, message); + + metisMessage_Release(&message); + + // wait until we indicate that the connection is up on Bob's side + while (data->notifierData[BOB].type == MetisMissiveType_ConnectionDestroyed) { + _crankHandle(data); + } + + // and verify that the message was sent in to the message processor on Bob's side + assertTrue(data->tapData[BOB].onReceiveCount == 1, "Wrong receive count, expected 1 got %u", data->tapData[BOB].onReceiveCount); + + ops->destroy(&ops); +} + +/* + * sockets not same address family + */ +LONGBOW_TEST_CASE(Global, metisUdpTunnel_Create_MismatchedTypes) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + + struct sockaddr_in6 sin6; + memset(&sin6, 0, sizeof(sin6)); + CPIAddress *bobAddress = cpiAddress_CreateFromInet6(&sin6); + + MetisIoOperations *ops = metisUdpTunnel_Create(data->metis[ALICE], aliceAddress, bobAddress); + assertNull(ops, "Should have gotten null return for mismatched address types"); + + cpiAddress_Destroy(&bobAddress); +} + +/* + * Listener not found + */ +LONGBOW_TEST_CASE(Global, metisUdpTunnel_Create_NotFound) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + CPIAddress *aliceAddress = cpiAddress_CreateFromInet(&sin); + CPIAddress *bobAddress = cpiAddress_CreateFromInet(&sin); + + MetisIoOperations *ops = metisUdpTunnel_Create(data->metis[ALICE], aliceAddress, bobAddress); + assertNull(ops, "Should have gotten null return for mismatched address types"); + + cpiAddress_Destroy(&aliceAddress); + cpiAddress_Destroy(&bobAddress); +} + + + +// ================================== + + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_UdpTunnel); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/testrig_GenericEther.c b/metis/ccnx/forwarder/metis/io/test/testrig_GenericEther.c new file mode 100644 index 00000000..d5c641d2 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/testrig_GenericEther.c @@ -0,0 +1,236 @@ +/* + * 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. + */ + +/** + * GenericEther mockup for testing metis_EtherListener. + * + */ + +#include <config.h> +#include <unistd.h> +#include <errno.h> +#include <stdio.h> +#include <fcntl.h> +#include <net/ethernet.h> +#include <arpa/inet.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_Deque.h> +#include <parc/algol/parc_Buffer.h> +#include <parc/algol/parc_EventBuffer.h> + +#include <ccnx/forwarder/metis/core/metis_System.h> +#include <ccnx/forwarder/metis/io/metis_GenericEther.h> + +#include <LongBow/runtime.h> + +struct metis_generic_ether { + PARCDeque *inputQueue; + int testSocket; + int etherSocket; + PARCBuffer *macAddress; + uint16_t ethertype; + unsigned refcount; + MetisLogger *logger; + unsigned mtu; +}; + +void +_metisGenericEther_Destroy(MetisGenericEther **etherPtr) +{ + MetisGenericEther *ether = *etherPtr; + PARCBuffer *buffer; + + // the input queue is simple byte arrays of ethernet frames + while ((buffer = parcDeque_RemoveFirst(ether->inputQueue))) { + parcBuffer_Release(&buffer); + } + + metisLogger_Release(ðer->logger); + parcDeque_Release(ðer->inputQueue); + parcBuffer_Release(ðer->macAddress); + + close(ether->testSocket); + close(ether->etherSocket); +} + +parcObject_ExtendPARCObject(MetisGenericEther, _metisGenericEther_Destroy, NULL, NULL, NULL, NULL, NULL, NULL); + +parcObject_ImplementAcquire(metisGenericEther, MetisGenericEther); + +parcObject_ImplementRelease(metisGenericEther, MetisGenericEther); + +MetisGenericEther * +metisGenericEther_Create(MetisForwarder *metis, const char *deviceName, uint16_t etherType) +{ + assertNotNull(deviceName, "Parameter deviceName must be non-null"); + assertTrue(etherType >= 0x0600, "EtherType must be greater than or equal to 0x0600"); + + MetisGenericEther *ether = parcObject_CreateInstance(MetisGenericEther); + + int fd[2]; + int failure = socketpair(PF_LOCAL, SOCK_DGRAM, 0, fd); + assertFalse(failure, "Error on socketpair"); + + ether->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + ether->testSocket = fd[0]; + ether->etherSocket = fd[1]; + ether->ethertype = etherType; + ether->inputQueue = parcDeque_Create(); + ether->mtu = 4000; + + // Set non-blocking flag + int flags = fcntl(ether->testSocket, F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)\n", errno); + failure = fcntl(ether->testSocket, F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)\n", errno); + flags = fcntl(ether->etherSocket, F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)\n", errno); + failure = fcntl(ether->etherSocket, F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)\n", errno); + + // If we are passed a real interface name, use its MAC address otherwise make up something + CPIAddress *realMacAddress = metisSystem_GetMacAddressByName(metis, deviceName); + + if (realMacAddress) { + PARCBuffer *realMac = cpiAddress_GetLinkAddress(realMacAddress); + ether->macAddress = parcBuffer_Copy(realMac); + cpiAddress_Destroy(&realMacAddress); + } else { + uint8_t macAddress[] = { 1, 2, 3, 4, 5, 6 }; + + // copy up to 6 bytes of the deviceName in to the mac address, for debugging + size_t maxbytes = (strlen(deviceName) > 6) ? 6 : strlen(deviceName); + memcpy(macAddress, deviceName, maxbytes); + ether->macAddress = parcBuffer_Allocate(sizeof(macAddress)); + parcBuffer_PutArray(ether->macAddress, sizeof(macAddress), macAddress); + parcBuffer_Flip(ether->macAddress); + } + + return ether; +} + +int +metisGenericEther_GetDescriptor(const MetisGenericEther *ether) +{ + return ether->etherSocket; +} + +bool +metisGenericEther_ReadNextFrame(MetisGenericEther *ether, PARCEventBuffer *buffer) +{ + // read a byte off the etherSocket if available to clear the notification. + // its non-blocking so no harm if nothing there. + uint8_t one; + ssize_t nread = read(ether->etherSocket, &one, 1); + assertTrue(errno == EWOULDBLOCK || nread > -1, "Error on read"); + + if (!parcDeque_IsEmpty(ether->inputQueue)) { + PARCBuffer *frame = parcDeque_RemoveFirst(ether->inputQueue); + uint8_t *bytes = parcBuffer_Overlay(frame, 0); + size_t length = parcBuffer_Remaining(frame); + + parcEventBuffer_Append(buffer, bytes, length); + parcBuffer_Release(&frame); + return true; + } + return false; +} + +bool +metisGenericEther_SendFrame(MetisGenericEther *ether, PARCEventBuffer *buffer) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + + // cannot use parcEventBuffer_WriteToFileDescriptor because we need to write the length in one go, not use the + // iovec approach in parcEventBuffer_WriteToFileDescriptor. It can cause problems on some platforms. + + uint8_t *linear = parcEventBuffer_Pullup(buffer, -1); + size_t length = parcEventBuffer_GetLength(buffer); + + ssize_t written = write(ether->etherSocket, linear, length); + if (written == length) { + return true; + } + + return false; +} + +PARCBuffer * +metisGenericEther_GetMacAddress(const MetisGenericEther *ether) +{ + return ether->macAddress; +} + +uint16_t +metisGenericEther_GetEtherType(const MetisGenericEther *ether) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + return ether->ethertype; +} + +unsigned +metisGenericEther_GetMTU(const MetisGenericEther *ether) +{ + return ether->mtu; +} + +// ========= +// Extra functions for testing + +int +mockGenericEther_GetTestDescriptor(MetisGenericEther *ether) +{ + return ether->testSocket; +} + +void +mockGenericEther_QueueFrame(MetisGenericEther *ether, PARCBuffer *ethernetFrame) +{ + parcDeque_Append(ether->inputQueue, parcBuffer_Acquire(ethernetFrame)); +} + +void +mockGenericEther_Notify(MetisGenericEther *ether) +{ + // notify the etherSocket by writing to the testSocket + uint8_t one = 1; + ssize_t nwritten = write(ether->testSocket, &one, 1); + assertTrue(nwritten == 1, "Error on write, expected 1, got %zd", nwritten); +} + +/* + * Create an Ethernet frame enclosing the ccnxPacket. does not include the FCS. + */ +PARCBuffer * +mockGenericEther_createFrame(size_t length, const uint8_t *ccnxPacket, const uint8_t dmac[ETHER_ADDR_LEN], const uint8_t smac[ETHER_ADDR_LEN], uint16_t ethertype) +{ + size_t totalLength = sizeof(struct ether_header) + length; + PARCBuffer *buffer = parcBuffer_Allocate(totalLength); + + struct ether_header hdr; + + memcpy(hdr.ether_dhost, dmac, ETHER_ADDR_LEN); + memcpy(hdr.ether_shost, smac, ETHER_ADDR_LEN); + hdr.ether_type = htons(ethertype); + + parcBuffer_PutArray(buffer, sizeof(hdr), (uint8_t *) &hdr); + + parcBuffer_PutArray(buffer, length, ccnxPacket); + + parcBuffer_Flip(buffer); + return buffer; +} + diff --git a/metis/ccnx/forwarder/metis/io/test/testrig_GenericEther.h b/metis/ccnx/forwarder/metis/io/test/testrig_GenericEther.h new file mode 100644 index 00000000..dc89841e --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/testrig_GenericEther.h @@ -0,0 +1,109 @@ +/* + * 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 testrig_GenericEther.h + * @brief A mockup of a platform Ethernet + * + * The mockup is connected to a socketpair, so you can read frames that the metis_EtherListener sends. + * It also has an input queue so you can queue frames to be read by metis_EtherListener. + * + * This mockup implements the metis_GenericEther.h API plus two additional functions for the mockup. + * + */ + +#ifndef Metis_testrig_GenericEther_h +#define Metis_testrig_GenericEther_h + +#include <ccnx/forwarder/metis/io/metis_GenericEther.h> +#include <parc/algol/parc_Buffer.h> + +/** + * Returns the other end of a socketpair that mocks up the ethernet wire + * + * The mockup does not connect to a RAW or BPF socket, it connects to a socketpair. + * This function gets the remote end of the socket pair, which is where you can read + * frames that you send. + * + * DO NOT WRITE PACKETS HERE. To queue packets for input, use mockGenericEther_QueueFrame(). + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval number System socketpair + * + * Example: + * @code + * <#example#> + * @endcode + */ +int mockGenericEther_GetTestDescriptor(MetisGenericEther *ether); + +/** + * Queue an Ethernet frame to be read + * + * The mockup maintains an input queue (deque) for input frames. These should be full + * Ethernet frames (not including the frame check sequence). + * + * This stores a reference, so caller must also release the PARCBuffer. + * + * This function will not notify the etherSocket being watched by Libevent in Metis. + * To notify Libevent, use mockGenericEther_Notify() after queuing packets. + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void mockGenericEther_QueueFrame(MetisGenericEther *ether, PARCBuffer *ethernetFrame); + +/** + * Writes a byte to the etherSocket + * + * Tickles Libevent by writing a byte to the etherSocket. + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void mockGenericEther_Notify(MetisGenericEther *ether); + +/** + * Convenience function to encapsulate a packet in an ethernet frame + * + * Creates a PARCBuffer that has an Ethernet header followed by a user-provided byte array. + * Does not include the frame check sequence. + * + * @param [in] length The length of the ccnxPacket to be encapsulated + * @param [in] ccnxPacket the byte array to put after the ethernet header + * @param [in] dmac[6] The destination mac + * @param [in] smac[6] The source mac + * @param [in] ethertype The ethertype in host byte order + * + * @retval non-null An allocated PARCBuffer wrapping an ethernet frame, ready to read + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +PARCBuffer *mockGenericEther_createFrame(size_t length, const uint8_t ccnxPacket[length], const uint8_t dmac[ETHER_ADDR_LEN], const uint8_t smac[ETHER_ADDR_LEN], uint16_t ethertype); + +#endif // Metis_testrig_GenericEther_h diff --git a/metis/ccnx/forwarder/metis/io/test/testrig_MetisListenerOps.c b/metis/ccnx/forwarder/metis/io/test/testrig_MetisListenerOps.c new file mode 100644 index 00000000..d0a1f4c0 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/testrig_MetisListenerOps.c @@ -0,0 +1,113 @@ +/* + * 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. + */ + +/** + * This is a mock for MetisListenerOps + * + */ + +/** + * You should #include this C file in your unit test code + */ + +// =============================== +// Setup a mock for the MetisListenerOps + +typedef struct mock_listener_data { + unsigned destroyCount; + unsigned getInterfaceIndexCount; + unsigned getListenAddressCount; + unsigned getEncapTypeCount; + + // These values will be returned by the appropriate getter + unsigned interfaceIndex; + CPIAddress *listenAddress; + MetisEncapType encapType; +} MockListenerData; + +static void +mockListener_Destroy(MetisListenerOps **opsPtr) +{ + // Don't actually destroy the data, we want to keep the counts + MetisListenerOps *ops = *opsPtr; + MockListenerData *data = ops->context; + data->destroyCount++; + parcMemory_Deallocate((void **) &ops); + *opsPtr = NULL; +} + +static unsigned +mockListener_GetInterfaceIndex(const MetisListenerOps *ops) +{ + MockListenerData *data = ops->context; + data->getInterfaceIndexCount++; + return data->interfaceIndex; +} + +static const CPIAddress * +mockListener_GetListenAddress(const MetisListenerOps *ops) +{ + MockListenerData *data = ops->context; + data->getListenAddressCount++; + return data->listenAddress; +} + +static MetisEncapType +mockListener_GetEncapType(const MetisListenerOps *ops) +{ + MockListenerData *data = ops->context; + data->getEncapTypeCount++; + return data->encapType; +} + +static MetisListenerOps + mockListenerTemplate = { + .context = NULL, + .destroy = &mockListener_Destroy, + .getInterfaceIndex = &mockListener_GetInterfaceIndex, + .getListenAddress = &mockListener_GetListenAddress, + .getEncapType = &mockListener_GetEncapType +}; + +MockListenerData * +mockListenData_Create(unsigned interfaceIndex, CPIAddress *listenAddress, MetisEncapType encapType) +{ + MockListenerData *data = parcMemory_AllocateAndClear(sizeof(MockListenerData)); + assertNotNull(data, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MockListenerData)); + memset(data, 0, sizeof(MockListenerData)); + data->encapType = encapType; + data->interfaceIndex = interfaceIndex; + data->listenAddress = cpiAddress_Copy(listenAddress); + return data; +} + +MetisListenerOps * +mockListener_Create(MockListenerData *data) +{ + MetisListenerOps *ops = parcMemory_AllocateAndClear(sizeof(MetisListenerOps)); + assertNotNull(ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisListenerOps)); + memcpy(ops, &mockListenerTemplate, sizeof(MetisListenerOps)); + ops->context = data; + return ops; +} + +void +mockListenerData_Destroy(MockListenerData **dataPtr) +{ + MockListenerData *data = *dataPtr; + cpiAddress_Destroy(&data->listenAddress); + parcMemory_Deallocate((void **) &data); + *dataPtr = NULL; +} diff --git a/metis/ccnx/forwarder/metis/messenger/metis_Messenger.c b/metis/ccnx/forwarder/metis/messenger/metis_Messenger.c new file mode 100644 index 00000000..6313a3c1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/messenger/metis_Messenger.c @@ -0,0 +1,189 @@ +/* + * 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 messenger is contructued with a reference to the forwarder's dispatcher so it can + * schedule future events. When someone calls metisMessenger_Send(...), it will put the + * message on a queue. If the queue was empty, it will scheudle itself to be run. + * By running the queue in a future dispatcher slice, it guarantees that there will be + * no re-entrant behavior between callers and message listeners. + * + * A recipient will receive a reference counted copy of the missive, so it must call + * {@link metisMissive_Release} on it. + * + */ + +#include <config.h> +#include <stdio.h> +#include <LongBow/runtime.h> +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_ArrayList.h> +#include <parc/algol/parc_EventScheduler.h> +#include <parc/algol/parc_Event.h> + +#include <ccnx/forwarder/metis/messenger/metis_Messenger.h> +#include <ccnx/forwarder/metis/messenger/metis_MissiveDeque.h> + +struct metis_messenger { + PARCArrayList *callbacklist; + MetisDispatcher *dispatcher; + MetisMissiveDeque *eventQueue; + + PARCEventTimer *timerEvent; +}; + +static void metisMessenger_Dequeue(int fd, PARCEventType which_event, void *messengerVoidPtr); + +// ========================================= +// Public API + +MetisMessenger * +metisMessenger_Create(MetisDispatcher *dispatcher) +{ + MetisMessenger *messenger = parcMemory_AllocateAndClear(sizeof(MetisMessenger)); + assertNotNull(messenger, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisMessenger)); + + // NULL destroyer because we're storing structures owned by the caller + messenger->dispatcher = dispatcher; + messenger->callbacklist = parcArrayList_Create(NULL); + messenger->eventQueue = metisMissiveDeque_Create(); + + // creates the timer, but does not start it + messenger->timerEvent = metisDispatcher_CreateTimer(dispatcher, false, metisMessenger_Dequeue, messenger); + + return messenger; +} + +void +metisMessenger_Destroy(MetisMessenger **messengerPtr) +{ + assertNotNull(messengerPtr, "Parameter must be non-null double pointer"); + assertNotNull(*messengerPtr, "Parameter must dereference to non-null pointer"); + + MetisMessenger *messenger = *messengerPtr; + parcArrayList_Destroy(&messenger->callbacklist); + metisMissiveDeque_Release(&messenger->eventQueue); + metisDispatcher_DestroyTimerEvent(messenger->dispatcher, &messenger->timerEvent); + parcMemory_Deallocate((void **) &messenger); + *messengerPtr = NULL; +} + +void +metisMessenger_Send(MetisMessenger *messenger, MetisMissive *missive) +{ + assertNotNull(messenger, "Parameter messenger must be non-null"); + assertNotNull(missive, "Parameter event must be non-null"); + + metisMissiveDeque_Append(messenger->eventQueue, missive); + if (metisMissiveDeque_Size(messenger->eventQueue) == 1) { + // We need to scheudle ourself when an event is added to an empty queue + + // precondition: timer should not be running. + struct timeval immediateTimeout = { 0, 0 }; + metisDispatcher_StartTimer(messenger->dispatcher, messenger->timerEvent, &immediateTimeout); + } +} + +static void +removeRecipient(MetisMessenger *messenger, const MetisMessengerRecipient *recipient) +{ + // don't increment i in the loop + for (size_t i = 0; i < parcArrayList_Size(messenger->callbacklist); ) { + const void *p = parcArrayList_Get(messenger->callbacklist, i); + if (p == recipient) { + // removing will compact the list, so next element will also be at i. + parcArrayList_RemoveAndDestroyAtIndex(messenger->callbacklist, i); + } else { + i++; + } + } +} + +/** + * @function metisEventMessenger_Register + * @abstract Receive all event messages + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +void +metisMessenger_Register(MetisMessenger *messenger, const MetisMessengerRecipient *recipient) +{ + assertNotNull(messenger, "Parameter messenger must be non-null"); + assertNotNull(recipient, "Parameter recipient must be non-null"); + + // do not allow duplicates + removeRecipient(messenger, recipient); + + parcArrayList_Add(messenger->callbacklist, recipient); +} + +/** + * @function metisEventMessenger_Unregister + * @abstract Stop receiving event messages + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +void +metisMessenger_Unregister(MetisMessenger *messenger, const MetisMessengerRecipient *recipient) +{ + assertNotNull(messenger, "Parameter messenger must be non-null"); + assertNotNull(recipient, "Parameter recipient must be non-null"); + + removeRecipient(messenger, recipient); +} + +/** + * Called by event scheduler to give us a slice in which to dequeue events + * + * Called inside an event callback, so we now have exclusive access to the system. + * Dequeues all pending events and calls all the listeners for each one. + * + * @param [in] fd unused, required for compliance with function prototype + * @param [in] which_event unused, required for compliance with function prototype + * @param [in] messengerVoidPtr A void* to MetisMessenger + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +static void +metisMessenger_Dequeue(int fd, PARCEventType which_event, void *messengerVoidPtr) +{ + MetisMessenger *messenger = (MetisMessenger *) messengerVoidPtr; + assertNotNull(messenger, "Called with null messenger pointer"); + + MetisMissive *missive; + while ((missive = metisMissiveDeque_RemoveFirst(messenger->eventQueue)) != NULL) { + for (size_t i = 0; i < parcArrayList_Size(messenger->callbacklist); i++) { + MetisMessengerRecipient *recipient = parcArrayList_Get(messenger->callbacklist, i); + assertTrue(recipient, "Recipient is null at index %zu", i); + + metisMessengerRecipient_Deliver(recipient, metisMissive_Acquire(missive)); + } + + // now let go of our reference to the missive + metisMissive_Release(&missive); + } +} diff --git a/metis/ccnx/forwarder/metis/messenger/metis_Messenger.h b/metis/ccnx/forwarder/metis/messenger/metis_Messenger.h new file mode 100644 index 00000000..627cd8bc --- /dev/null +++ b/metis/ccnx/forwarder/metis/messenger/metis_Messenger.h @@ -0,0 +1,88 @@ +/* + * 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 EventMessenger is the system that messages events between + * producers and consumers. + * + * Events are delivered in a deferred event cycle to avoid event callbacks + * firing when the event generator is still running. + */ + +#ifndef Metis_metis_Messenger_h +#define Metis_metis_Messenger_h + +#include <ccnx/forwarder/metis/core/metis_Dispatcher.h> +#include <ccnx/forwarder/metis/messenger/metis_Missive.h> +#include <ccnx/forwarder/metis/messenger/metis_MessengerRecipient.h> + +struct metis_messenger; +typedef struct metis_messenger MetisMessenger; + +/** + * @function metisEventmessenger_Create + * @abstract Creates an event notification system + * @discussion + * Typically there's only one of these managed by metisForwarder. + * + * @param dispatcher is the event dispatcher to use to schedule events. + * @return <#return#> + */ +MetisMessenger *metisMessenger_Create(MetisDispatcher *dispatcher); + +/** + * @function metisEventMessenger_Destroy + * @abstract Destroys the messenger system, no notification is sent + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +void metisMessenger_Destroy(MetisMessenger **messengerPtr); + +/** + * @function metisEventMessenger_Send + * @abstract Send an event message, takes ownership of the event memory + * @discussion + * <#Discussion#> + * + * @param <#param1#> + */ +void metisMessenger_Send(MetisMessenger *messenger, MetisMissive *missive); + +/** + * @function metisEventMessenger_Register + * @abstract Receive all event messages + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +void metisMessenger_Register(MetisMessenger *messenger, const MetisMessengerRecipient *recipient); + +/** + * @function metisEventMessenger_Unregister + * @abstract Stop receiving event messages + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +void metisMessenger_Unregister(MetisMessenger *messenger, const MetisMessengerRecipient *recipient); +#endif // Metis_metis_Messenger_h diff --git a/metis/ccnx/forwarder/metis/messenger/metis_MessengerRecipient.c b/metis/ccnx/forwarder/metis/messenger/metis_MessengerRecipient.c new file mode 100644 index 00000000..77967c9c --- /dev/null +++ b/metis/ccnx/forwarder/metis/messenger/metis_MessengerRecipient.c @@ -0,0 +1,65 @@ +/* + * 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 <LongBow/runtime.h> +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/messenger/metis_Messenger.h> +#include <ccnx/forwarder/metis/messenger/metis_MessengerRecipient.h> + +struct metis_messenger_recipient { + void *context; + MetisMessengerRecipientCallback *notify; +}; + +MetisMessengerRecipient * +metisMessengerRecipient_Create(void *recipientContext, MetisMessengerRecipientCallback *recipientCallback) +{ + assertNotNull(recipientCallback, "Parameter recipientCallback must be non-null"); + + MetisMessengerRecipient *recipient = parcMemory_AllocateAndClear(sizeof(MetisMessengerRecipient)); + assertNotNull(recipient, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisMessengerRecipient)); + recipient->context = recipientContext; + recipient->notify = recipientCallback; + return recipient; +} + +void +metisMessengerRecipient_Destroy(MetisMessengerRecipient **recipientPtr) +{ + assertNotNull(recipientPtr, "Parameter must be non-null double pointer"); + assertNotNull(*recipientPtr, "Parameter must dereference to non-null pointer"); + + parcMemory_Deallocate((void **) recipientPtr); + *recipientPtr = NULL; +} + +void * +metisMessengerRecipient_GetRecipientContext(MetisMessengerRecipient *recipient) +{ + assertNotNull(recipient, "Parameter must be non-null"); + + return recipient->context; +} + +void +metisMessengerRecipient_Deliver(MetisMessengerRecipient *recipient, MetisMissive *missive) +{ + assertNotNull(recipient, "Parameter must be non-null"); + recipient->notify(recipient, missive); +} diff --git a/metis/ccnx/forwarder/metis/messenger/metis_MessengerRecipient.h b/metis/ccnx/forwarder/metis/messenger/metis_MessengerRecipient.h new file mode 100644 index 00000000..6e92051a --- /dev/null +++ b/metis/ccnx/forwarder/metis/messenger/metis_MessengerRecipient.h @@ -0,0 +1,114 @@ +/* + * 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_MessengerRecipient.h + * @brief A recipient represents the entity that will recieve a Missive from the Messenger. + * + * A recipient is identified by the pair (contenxt, callback). The context is the recipients + * context, such as it's object pointer. The callback is the function the recipient uses + * to receive a Missive. + * + * If the receiver is going to do a lot of work or potentially send other missives, the receiver + * should queue the received notifications and process them in its own slice. + * + * A recipient will receive a reference counted copy of the missive, so it must call + * {@link metisMissive_Release} on it. + * + * + */ + +#ifndef Metis_metis_MessengerRecipient_h +#define Metis_metis_MessengerRecipient_h + +struct metis_messenger_recipient; +typedef struct metis_messenger_recipient MetisMessengerRecipient; + +/** + * @typedef MetisMessengerRecipientCallback + * @abstract A recipient implements a callback to receive Missives. + * @constant recipient The recipient to recieve the missive + * @constant missive The missive, recipient must call {@link metisMissive_Release} on it + * @discussion <#Discussion#> + */ +typedef void (MetisMessengerRecipientCallback)(MetisMessengerRecipient *recipient, MetisMissive *missive); + +/** + * Creates a Recipient, which represents a reciever of missives. + * + * Creates a Recipient that can be registerd with the Messenger using {@link metisMessenger_Register}. + * + * @param [in] recipientContext This pointer will be passed back to the recipient with each missive, may be NULL + * @param [in] recipientCallback The function that receives the missive, must be non-NULL. + * + * @return non-null A recipient object + * + * Example: + * @code + * @endcode + */ +MetisMessengerRecipient *metisMessengerRecipient_Create(void *recipientContext, MetisMessengerRecipientCallback *recipientCallback); + +/** + * Destroys a recipient. You should unregister it first. + * + * Destroying a recipient does not unregister it, so be sure to call + * {@link metisMessenger_Unregister} first. + * + * @param [in,out] recipientPtr Double pointer to the recipient to destroy, will be NULL'd. + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisMessengerRecipient_Destroy(MetisMessengerRecipient **recipientPtr); + +/** + * Returns the recipient context passed on Create + * + * <#Paragraphs Of Explanation#> + * + * @param [in] recipient The recipient object + * + * @return pointer The context pointer used to create the object, maybe NULL + * + * Example: + * @code + * <#example#> + * @endcode + */ +void *metisMessengerRecipient_GetRecipientContext(MetisMessengerRecipient *recipient); + +/** + * Delivers a Missive to the recipient + * + * Passes the missive to the recipients callback. + * + * A recipient will receive a reference counted copy of the missive, so it must call + * {@link metisMissive_Release} on it. + * + * @param [in] recipient The receiver + * @param [in] missive The message to send + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisMessengerRecipient_Deliver(MetisMessengerRecipient *recipient, MetisMissive *missive); +#endif // Metis_metis_MessengerRecipient_h diff --git a/metis/ccnx/forwarder/metis/messenger/metis_Missive.c b/metis/ccnx/forwarder/metis/messenger/metis_Missive.c new file mode 100644 index 00000000..9eace760 --- /dev/null +++ b/metis/ccnx/forwarder/metis/messenger/metis_Missive.c @@ -0,0 +1,66 @@ +/* + * 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 <LongBow/runtime.h> +#include <parc/algol/parc_Object.h> +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/messenger/metis_Missive.h> + +struct metis_missive { + MetisMissiveType missiveType; + unsigned connectionid; +}; + +parcObject_Override(MetisMissive, PARCObject, + .isLockable = false); + +MetisMissive * +metisMissive_Create(MetisMissiveType missiveType, unsigned connectionid) +{ + MetisMissive *missive = parcObject_CreateInstance(MetisMissive); + missive->missiveType = missiveType; + missive->connectionid = connectionid; + return missive; +} + +MetisMissive * +metisMissive_Acquire(const MetisMissive *missive) +{ + return parcObject_Acquire(missive); +} + +void +metisMissive_Release(MetisMissive **missivePtr) +{ + parcObject_Release((void **) missivePtr); +} + +MetisMissiveType +metisMissive_GetType(const MetisMissive *missive) +{ + assertNotNull(missive, "Parameter missive must be non-null"); + return missive->missiveType; +} + +unsigned +metisMissive_GetConnectionId(const MetisMissive *missive) +{ + assertNotNull(missive, "Parameter missive must be non-null"); + return missive->connectionid; +} diff --git a/metis/ccnx/forwarder/metis/messenger/metis_Missive.h b/metis/ccnx/forwarder/metis/messenger/metis_Missive.h new file mode 100644 index 00000000..8f3b4906 --- /dev/null +++ b/metis/ccnx/forwarder/metis/messenger/metis_Missive.h @@ -0,0 +1,112 @@ +/* + * 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_Missive.h + * @brief A Missive is a status message sent over a broadcast channel inside Metis + * + * Recipients use {@link metisMessenger_Register} to receive missives. They are + * broadcast to all recipients. + * + */ +#ifndef Metis_metis_missive_h +#define Metis_metis_missive_h + +#include <ccnx/forwarder/metis/messenger/metis_MissiveType.h> + +struct metis_missive; +typedef struct metis_missive MetisMissive; + +/** + * Creates a Missive and sets the reference count to 1 + * + * A Missive may be sent to listeners of the MetisMessenger to inform them of events on a connection id. + * + * @param [in] MetisMissiveType The event type + * @param [in] connectionid The relevant conneciton id + * + * @return non-null A message + * @retrun null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisMissive *metisMissive_Create(MetisMissiveType missiveType, unsigned connectionid); + +/** + * Acquire a reference counted copy + * + * Increases the reference count by 1 and returns the original object. + * + * @param [in] missive An allocated missive + * + * @return non-null The original missive with increased reference count + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisMissive *metisMissive_Acquire(const MetisMissive *missive); + +/** + * Releases a reference counted copy. + * + * If it is the last reference, the missive is freed. + * + * @param [in,out] missivePtr Double pointer to a missive, will be nulled. + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisMissive_Release(MetisMissive **missivePtr); + +/** + * Returns the type of the missive + * + * Returns the type of event the missive represents + * + * @param [in] missive An allocated missive + * + * @return MetisMissiveType The event type + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisMissiveType metisMissive_GetType(const MetisMissive *missive); + +/** + * Returns the connection ID of the missive + * + * An event is usually associated with a connection id (i.e. the I/O channel + * that originaged the event). + * + * @param [in] missive An allocated missive + * + * @return number The relevant connection id. + * + * Example: + * @code + * <#example#> + * @endcode + */ +unsigned metisMissive_GetConnectionId(const MetisMissive *missive); +#endif // Metis_metis_missive_h diff --git a/metis/ccnx/forwarder/metis/messenger/metis_MissiveDeque.c b/metis/ccnx/forwarder/metis/messenger/metis_MissiveDeque.c new file mode 100644 index 00000000..a9501022 --- /dev/null +++ b/metis/ccnx/forwarder/metis/messenger/metis_MissiveDeque.c @@ -0,0 +1,167 @@ +/* + * 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. + */ + +/** + * A type-safe wrapper for Missives around a {@link PARCDeque}. We only implement + * the subset of functions used. + * + */ + +#include <config.h> +#include <stdio.h> +#include <LongBow/runtime.h> +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_Deque.h> + +#include <ccnx/forwarder/metis/messenger/metis_Missive.h> +#include <ccnx/forwarder/metis/messenger/metis_MissiveDeque.h> + +struct metis_missive_deque { + PARCDeque *queue; +}; + +/** + * Create a `PARCDeque` instance with the default element equals function. + * + * The queue is created with no elements. + * + * The default element equals function is used by the `parcDeque_Equals` function and + * simply compares the values using the `==` operator. + * Users that need more sophisticated comparisons of the elements need to supply their own + * function via the `parcDeque_CreateCustom` function. + * + * @return non-NULL A pointer to a PARCDeque instance. + * + * Example: + * @code + * <#example#> + * @endcode + * + * @see <#references#> + */ +MetisMissiveDeque * +metisMissiveDeque_Create(void) +{ + MetisMissiveDeque *missiveDeque = parcMemory_AllocateAndClear(sizeof(MetisMissiveDeque)); + assertNotNull(missiveDeque, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisMissiveDeque)); + missiveDeque->queue = parcDeque_Create(); + return missiveDeque; +} + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + * + * @see <#references#> + */ +void +metisMissiveDeque_Release(MetisMissiveDeque **dequePtr) +{ + assertNotNull(dequePtr, "Double pointer must be non-null"); + assertNotNull(*dequePtr, "Double pointer must dereference to non-null"); + MetisMissiveDeque *missiveDeque = *dequePtr; + + // flush the queue + while (!parcDeque_IsEmpty(missiveDeque->queue)) { + MetisMissive *missive = metisMissiveDeque_RemoveFirst(missiveDeque); + metisMissive_Release(&missive); + } + + parcDeque_Release(&missiveDeque->queue); + parcMemory_Deallocate((void **) &missiveDeque); + *dequePtr = NULL; +} + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + * + * @see <#references#> + */ +MetisMissiveDeque * +metisMissiveDeque_Append(MetisMissiveDeque *deque, MetisMissive *missive) +{ + assertNotNull(deque, "Parameter deque must be non-null"); + assertNotNull(missive, "Parameter missive must be non-null"); + + parcDeque_Append(deque->queue, missive); + return deque; +} + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + * + * @see <#references#> + */ +MetisMissive * +metisMissiveDeque_RemoveFirst(MetisMissiveDeque *deque) +{ + assertNotNull(deque, "Parameter deque must be non-null"); + return (MetisMissive *) parcDeque_RemoveFirst(deque->queue); +} + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + * + * @see <#references#> + */ +size_t +metisMissiveDeque_Size(const MetisMissiveDeque *deque) +{ + assertNotNull(deque, "Parameter deque must be non-null"); + return parcDeque_Size(deque->queue); +} diff --git a/metis/ccnx/forwarder/metis/messenger/metis_MissiveDeque.h b/metis/ccnx/forwarder/metis/messenger/metis_MissiveDeque.h new file mode 100644 index 00000000..b616a75b --- /dev/null +++ b/metis/ccnx/forwarder/metis/messenger/metis_MissiveDeque.h @@ -0,0 +1,123 @@ +/* + * 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_MissiveDeque + * @brief Double ended queue of Missives + * + * Used to queue Missives. This is a type-safe wrapper around {@link PARCDeque} + * + */ + +#ifndef Metis_metis_MissiveDeque_h +#define Metis_metis_MissiveDeque_h + +struct metis_missive_deque; + +typedef struct metis_missive_deque MetisMissiveDeque; + +/** + * Create a `PARCDeque` instance with the default element equals function. + * + * The queue is created with no elements. + * + * The default element equals function is used by the `parcDeque_Equals` function and + * simply compares the values using the `==` operator. + * Users that need more sophisticated comparisons of the elements need to supply their own + * function via the `parcDeque_CreateCustom` function. + * + * @return non-NULL A pointer to a PARCDeque instance. + * + * Example: + * @code + * <#example#> + * @endcode + * + * @see <#references#> + */ +MetisMissiveDeque *metisMissiveDeque_Create(void); + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + * + * @see <#references#> + */ +void metisMissiveDeque_Release(MetisMissiveDeque **dequePtr); + +/** + * Appends the missive to the queue, taking ownership of the memory + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + * + * @see <#references#> + */ +MetisMissiveDeque *metisMissiveDeque_Append(MetisMissiveDeque *deque, MetisMissive *missive); + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + * + * @see <#references#> + */ +MetisMissive *metisMissiveDeque_RemoveFirst(MetisMissiveDeque *deque); + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + * + * @see <#references#> + */ +size_t metisMissiveDeque_Size(const MetisMissiveDeque *deque); +#endif // Metis_metis_MissiveDeque_h diff --git a/metis/ccnx/forwarder/metis/messenger/metis_MissiveType.h b/metis/ccnx/forwarder/metis/messenger/metis_MissiveType.h new file mode 100644 index 00000000..7b39acd3 --- /dev/null +++ b/metis/ccnx/forwarder/metis/messenger/metis_MissiveType.h @@ -0,0 +1,51 @@ +/* + * 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_MissiveType + * @brief Defines what a Missive represents + * + * Currently, missives only carry information about the state of a connection + * (created, up, down, closed, destroyed). + * + */ + +#ifndef Metis_metis_MissiveType_h +#define Metis_metis_MissiveType_h + +/** + * @typedef Represents the state of a connection + * @abstract CREATE is the initial state. UP & DOWN are recurrent states. CLOSED is transient. DESTROYED is the terminal state. + * @constant MetisMissiveType_ConnectionCreate Connection created (new) + * @constant MetisMissiveType_ConnectionUp Connection is active and passing data + * @constant MetisMissiveType_ConnectionDown Connection is inactive and cannot pass data + * @constant MetisMissiveType_ConnectionClosed Connection closed and will be destroyed + * @constant MetisMissiveType_ConnectionDestroyed Connection destroyed + * @discussion State transitions: + * initial -> CREATE + * CREATE -> (UP | DOWN) + * UP -> (DOWN | DESTROYED) + * DOWN -> (UP | CLOSED | DESTROYED) + * CLOSED -> DESTROYED + * DESTROYED -> terminal + */ +typedef enum { + MetisMissiveType_ConnectionCreate, + MetisMissiveType_ConnectionUp, + MetisMissiveType_ConnectionDown, + MetisMissiveType_ConnectionClosed, + MetisMissiveType_ConnectionDestroyed +} MetisMissiveType; +#endif // Metis_metis_MissiveType_h diff --git a/metis/ccnx/forwarder/metis/messenger/test/CMakeLists.txt b/metis/ccnx/forwarder/metis/messenger/test/CMakeLists.txt new file mode 100644 index 00000000..22a8c41d --- /dev/null +++ b/metis/ccnx/forwarder/metis/messenger/test/CMakeLists.txt @@ -0,0 +1,16 @@ +# Enable gcov output for the tests +add_definitions(--coverage) +set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} " --coverage") + +set(TestsExpectedToPass + test_metis_Missive + test_metis_MissiveDeque + test_metis_Messenger + test_metis_MessengerRecipient +) + + +foreach(test ${TestsExpectedToPass}) + AddTest(${test}) +endforeach() + diff --git a/metis/ccnx/forwarder/metis/messenger/test/test_metis_Messenger.c b/metis/ccnx/forwarder/metis/messenger/test/test_metis_Messenger.c new file mode 100644 index 00000000..7b85391f --- /dev/null +++ b/metis/ccnx/forwarder/metis/messenger/test/test_metis_Messenger.c @@ -0,0 +1,272 @@ +/* + * 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_Messenger.c" +#include <inttypes.h> +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <parc/logging/parc_LogReporterTextStdout.h> + +LONGBOW_TEST_RUNNER(metis_Messenger) +{ + // 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_Messenger) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_Messenger) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisMessenger_Create_Destroy); + LONGBOW_RUN_TEST_CASE(Global, metisMessenger_Register); + LONGBOW_RUN_TEST_CASE(Global, metisMessenger_Register_Twice); + LONGBOW_RUN_TEST_CASE(Global, metisMessenger_Unregister); + LONGBOW_RUN_TEST_CASE(Global, metisMessenger_Send); +} + +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, metisMessenger_Create_Destroy) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisDispatcher *dispatcher = metisDispatcher_Create(logger); + metisLogger_Release(&logger); + + MetisMessenger *messenger = metisMessenger_Create(dispatcher); + metisMessenger_Destroy(&messenger); + metisDispatcher_Destroy(&dispatcher); + + assertTrue(parcSafeMemory_ReportAllocation(STDOUT_FILENO) == 0, "Create/Destroy has memory leak"); +} + +// The callback will compare what its called back with these "truth" values +static const MetisMissive *truth_missive; +static const MetisMessengerRecipient *truth_recipient; + +static void +test_notify(MetisMessengerRecipient *recipient, MetisMissive *missive) +{ + assertTrue(recipient == truth_recipient, "Got wrong recipient in callback expected %" PRIXPTR " got %" PRIXPTR, (uintptr_t) truth_recipient, (uintptr_t) recipient); + assertTrue(missive == truth_missive, "Got wrong event in callback expected %p got %p", (void *) truth_missive, (void *) missive); + metisMissive_Release(&missive); +} + +LONGBOW_TEST_CASE(Global, metisMessenger_Register) +{ + int a = 1; + MetisMessengerRecipient *recipient = metisMessengerRecipient_Create(&a, &test_notify); + + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisDispatcher *dispatcher = metisDispatcher_Create(logger); + metisLogger_Release(&logger); + MetisMessenger *messenger = metisMessenger_Create(dispatcher); + + metisMessenger_Register(messenger, recipient); + assertTrue(parcArrayList_Size(messenger->callbacklist) == 1, + "messenger array list wrong size, expected %u got %zu", + 1, + parcArrayList_Size(messenger->callbacklist)); + + const void *p = parcArrayList_Get(messenger->callbacklist, 0); + assertTrue(p == recipient, + "Messenger callbacklist contained wrong pointer, expected %p got %p", + (void *) recipient, + p); + + metisMessenger_Destroy(&messenger); + metisDispatcher_Destroy(&dispatcher); + metisMessengerRecipient_Destroy(&recipient); +} + +/** + * Register same callback twice, should only appear once in list + */ +LONGBOW_TEST_CASE(Global, metisMessenger_Register_Twice) +{ + int a = 1; + MetisMessengerRecipient *recipient = metisMessengerRecipient_Create(&a, &test_notify); + + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisDispatcher *dispatcher = metisDispatcher_Create(logger); + metisLogger_Release(&logger); + MetisMessenger *messenger = metisMessenger_Create(dispatcher); + + metisMessenger_Register(messenger, recipient); + metisMessenger_Register(messenger, recipient); + assertTrue(parcArrayList_Size(messenger->callbacklist) == 1, + "messenger array list wrong size, expected %u got %zu", + 1, + parcArrayList_Size(messenger->callbacklist)); + + const void *p = parcArrayList_Get(messenger->callbacklist, 0); + assertTrue(p == recipient, + "Messenger callbacklist contained wrong pointer, expected %p got %p", + (void *) recipient, + p); + + metisMessenger_Destroy(&messenger); + metisDispatcher_Destroy(&dispatcher); + metisMessengerRecipient_Destroy(&recipient); +} + +LONGBOW_TEST_CASE(Global, metisMessenger_Unregister) +{ + int a = 1; + MetisMessengerRecipient *recipient = metisMessengerRecipient_Create(&a, &test_notify); + + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisDispatcher *dispatcher = metisDispatcher_Create(logger); + metisLogger_Release(&logger); + MetisMessenger *messenger = metisMessenger_Create(dispatcher); + + metisMessenger_Register(messenger, recipient); + metisMessenger_Unregister(messenger, recipient); + + assertTrue(parcArrayList_Size(messenger->callbacklist) == 0, + "messenger array list wrong size, expected %u got %zu", + 0, + parcArrayList_Size(messenger->callbacklist)); + + metisMessenger_Destroy(&messenger); + metisDispatcher_Destroy(&dispatcher); + metisMessengerRecipient_Destroy(&recipient); +} + +LONGBOW_TEST_CASE(Global, metisMessenger_Send) +{ + int a = 1; + MetisMessengerRecipient *recipient = metisMessengerRecipient_Create(&a, &test_notify); + + truth_recipient = recipient; + + MetisMissive *missive = metisMissive_Create(MetisMissiveType_ConnectionUp, 12); + truth_missive = missive; + + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisDispatcher *dispatcher = metisDispatcher_Create(logger); + metisLogger_Release(&logger); + MetisMessenger *messenger = metisMessenger_Create(dispatcher); + + metisMessenger_Send(messenger, missive); + + metisDispatcher_RunDuration(dispatcher, &((struct timeval) {0, 10000})); + + // if we didn't assert, it is correct. + + metisMessenger_Destroy(&messenger); + metisDispatcher_Destroy(&dispatcher); + metisMessengerRecipient_Destroy(&recipient); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, removeCallback); +} + +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; +} + +LONGBOW_TEST_CASE(Local, removeCallback) +{ + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisDispatcher *dispatcher = metisDispatcher_Create(logger); + metisLogger_Release(&logger); + MetisMessenger *messenger = metisMessenger_Create(dispatcher); + + parcArrayList_Add(messenger->callbacklist, (void *) 1); + parcArrayList_Add(messenger->callbacklist, (void *) 2); + parcArrayList_Add(messenger->callbacklist, (void *) 3); + + removeRecipient(messenger, (void *) 2); + + assertTrue(parcArrayList_Size(messenger->callbacklist) == 2, + "messenger array list wrong size, expected %u got %zu", + 2, + parcArrayList_Size(messenger->callbacklist)); + + const void *p = parcArrayList_Get(messenger->callbacklist, 0); + assertTrue(p == (void *) 1, + "Messenger callbacklist contained wrong pointer at 0, expected %p got %p", + (void *) 1, + p); + + p = parcArrayList_Get(messenger->callbacklist, 1); + assertTrue(p == (void *) 3, + "Messenger callbacklist contained wrong pointer at 1, expected %p got %p", + (void *) 3, + p); + + + metisMessenger_Destroy(&messenger); + metisDispatcher_Destroy(&dispatcher); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_Messenger); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/messenger/test/test_metis_MessengerRecipient.c b/metis/ccnx/forwarder/metis/messenger/test/test_metis_MessengerRecipient.c new file mode 100644 index 00000000..fea68fe4 --- /dev/null +++ b/metis/ccnx/forwarder/metis/messenger/test/test_metis_MessengerRecipient.c @@ -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. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_MessengerRecipient.c" + +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +LONGBOW_TEST_RUNNER(metis_MessengerRecipient) +{ + // 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_MessengerRecipient) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_MessengerRecipient) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisMessengerRecipient_Create); + LONGBOW_RUN_TEST_CASE(Global, metisMessengerRecipient_Deliver); + LONGBOW_RUN_TEST_CASE(Global, metisMessengerRecipient_GetRecipientContext); +} + +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; +} + +typedef struct my_context { + MetisMissive *lastMessage; +} MyContext; + +static void +testRecipientCallback(MetisMessengerRecipient *recipient, MetisMissive *missive) +{ + MyContext *mycontext = metisMessengerRecipient_GetRecipientContext(recipient); + mycontext->lastMessage = missive; +} + +LONGBOW_TEST_CASE(Global, metisMessengerRecipient_Create) +{ + MyContext mycontext; + + // create and destroy and make sure no memory leaks + MetisMessengerRecipient *recipient = metisMessengerRecipient_Create(&mycontext, testRecipientCallback); + metisMessengerRecipient_Destroy(&recipient); + size_t balance = parcMemory_Outstanding(); + assertTrue(balance == 0, "Memory imbalance, expected 0, got %zu", balance); +} + +LONGBOW_TEST_CASE(Global, metisMessengerRecipient_Deliver) +{ + MyContext mycontext; + MetisMissive *truthMissive = metisMissive_Create(MetisMissiveType_ConnectionUp, 33); + + // create and destroy and make sure no memory leaks + MetisMessengerRecipient *recipient = metisMessengerRecipient_Create(&mycontext, testRecipientCallback); + + metisMessengerRecipient_Deliver(recipient, truthMissive); + + assertTrue(mycontext.lastMessage == truthMissive, "Recipient callback not right missve, expected %p got %p", (void *) truthMissive, (void *) mycontext.lastMessage); + + metisMessengerRecipient_Destroy(&recipient); + + metisMissive_Release(&truthMissive); +} + +LONGBOW_TEST_CASE(Global, metisMessengerRecipient_GetRecipientContext) +{ + MyContext mycontext; + + // create and destroy and make sure no memory leaks + MetisMessengerRecipient *recipient = metisMessengerRecipient_Create(&mycontext, testRecipientCallback); + + void *testcontext = metisMessengerRecipient_GetRecipientContext(recipient); + assertTrue(testcontext == &mycontext, "Got wrong context back, expected %p got %p", (void *) &mycontext, (void *) testcontext); + metisMessengerRecipient_Destroy(&recipient); +} + +LONGBOW_TEST_FIXTURE(Local) +{ +} + +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; +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_MessengerRecipient); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/messenger/test/test_metis_Missive.c b/metis/ccnx/forwarder/metis/messenger/test/test_metis_Missive.c new file mode 100644 index 00000000..04403fbe --- /dev/null +++ b/metis/ccnx/forwarder/metis/messenger/test/test_metis_Missive.c @@ -0,0 +1,109 @@ +/* + * 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_Missive.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +LONGBOW_TEST_RUNNER(metis_Event) +{ + // 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_Event) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_Event) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisMissive_Create_Destroy); + LONGBOW_RUN_TEST_CASE(Global, metisMissive_GetType); + LONGBOW_RUN_TEST_CASE(Global, metisMissive_GetConnectionId); +} + +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, metisMissive_Create_Destroy) +{ + MetisMissive *missive = metisMissive_Create(MetisMissiveType_ConnectionUp, 5); + metisMissive_Release(&missive); +} + +LONGBOW_TEST_CASE(Global, metisMissive_GetType) +{ + MetisMissive *missive = metisMissive_Create(MetisMissiveType_ConnectionUp, 5); + MetisMissiveType type = metisMissive_GetType(missive); + metisMissive_Release(&missive); + + assertTrue(type == MetisMissiveType_ConnectionUp, "Got wrong type, expected %d got %d\n", MetisMissiveType_ConnectionUp, type); +} + +LONGBOW_TEST_CASE(Global, metisMissive_GetConnectionId) +{ + MetisMissive *missive = metisMissive_Create(MetisMissiveType_ConnectionUp, 5); + unsigned connid = metisMissive_GetConnectionId(missive); + metisMissive_Release(&missive); + + assertTrue(connid == 5, "Got wrong connection id, expected %u got %u\n", 5, connid); +} + +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_Event); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/messenger/test/test_metis_MissiveDeque.c b/metis/ccnx/forwarder/metis/messenger/test/test_metis_MissiveDeque.c new file mode 100644 index 00000000..e0a170e3 --- /dev/null +++ b/metis/ccnx/forwarder/metis/messenger/test/test_metis_MissiveDeque.c @@ -0,0 +1,109 @@ +/* + * 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_MissiveDeque.c" + +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metis_MissiveDeque) +{ + // 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_MissiveDeque) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_MissiveDeque) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisMissiveDeque_Append); + LONGBOW_RUN_TEST_CASE(Global, metisMissiveDeque_Create); + LONGBOW_RUN_TEST_CASE(Global, metisMissiveDeque_Release); + LONGBOW_RUN_TEST_CASE(Global, metisMissiveDeque_RemoveFirst); + LONGBOW_RUN_TEST_CASE(Global, metisMissiveDeque_Size); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisMissiveDeque_Append) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Global, metisMissiveDeque_Create) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Global, metisMissiveDeque_Release) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Global, metisMissiveDeque_RemoveFirst) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Global, metisMissiveDeque_Size) +{ + 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_MissiveDeque); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/metis_About.c b/metis/ccnx/forwarder/metis/metis_About.c new file mode 100644 index 00000000..783865c2 --- /dev/null +++ b/metis/ccnx/forwarder/metis/metis_About.c @@ -0,0 +1,44 @@ +// DO NOT EDIT THIS FILE. IT IS AUTOMATICALLY GENERATED. +// longbow-generate-about 1.0.20170215.54ef86fe 2017-02-15T09:29:05Z + +#include "metis_About.h" + +const char *metis_What = "@(#)" "metis " RELEASE_VERSION " 2017-02-17T15:34:37.319950" + "@(#)" "\tCopyright (c) 2017 Cisco and/or its affiliates."; + +const char * +metisAbout_Name(void) +{ + return "metis"; +} + +const char * +metisAbout_Version(void) +{ + return RELEASE_VERSION; +} + +const char * +metisAbout_About(void) +{ + return "metis "RELEASE_VERSION " 2017-02-17T15:34:37.319950" "\nCopyright (c) 2017 Cisco and/or its affiliates.\n"; +} + +const char * +metisAbout_MiniNotice(void) +{ + return "Copyright (c) 2017 Cisco and/or its affiliates.\n"; +} + +const char * +metisAbout_ShortNotice(void) +{ + return "Copyright (c) 2017 Cisco and/or its affiliates.\n"; +} + +const char * +metisAbout_LongNotice(void) +{ + return "Copyright (c) 2017 Cisco and/or its affiliates.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at:\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"; +} + diff --git a/metis/ccnx/forwarder/metis/metis_About.h b/metis/ccnx/forwarder/metis/metis_About.h new file mode 100644 index 00000000..f740c961 --- /dev/null +++ b/metis/ccnx/forwarder/metis/metis_About.h @@ -0,0 +1,54 @@ +// DO NOT EDIT THIS FILE. IT IS AUTOMATICALLY GENERATED. +// longbow-generate-about 1.0.20170215.54ef86fe 2017-02-15T09:29:05Z + +#ifndef metis_About_h +#define metis_About_h +/** + * Embedded string containing information for the what(1) command. + * + */ +extern const char *metis_What; + +/** + * Return the name as a C string. + * + * @return The name as a C string. + */ +const char *metisAbout_Name(void); + +/** + * Return the version as a C string. + * + * @return The version as a C string. + */ +const char *metisAbout_Version(void); + +/** + * Return the About text as a C string. + * + * @return The About text as a C string. + */ +const char *metisAbout_About(void); + +/** + * Return the minimum copyright notice as a C string. + * + * @return The minimum copyright notice as a C string. + */ +const char *metisAbout_MiniNotice(void); + +/** + * Return the short copyright notice as a C string. + * + * @return The short copyright notice as a C string. + */ +const char *metisAbout_ShortNotice(void); + +/** + * Return the long copyright notice as a C string. + * + * @return The long copyright notice as a C string. + */ +const char *metisAbout_LongNotice(void); + +#endif // metis_About_h diff --git a/metis/ccnx/forwarder/metis/platforms/README.txt b/metis/ccnx/forwarder/metis/platforms/README.txt new file mode 100644 index 00000000..4670bd02 --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/README.txt @@ -0,0 +1,20 @@ +/* + * 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. + */ + +Operating system dependent modules. + +We currently do not support BSD, so its module is not part of the build. +It's also not finished. + diff --git a/metis/ccnx/forwarder/metis/platforms/android/ifaddrs.c b/metis/ccnx/forwarder/metis/platforms/android/ifaddrs.c new file mode 100644 index 00000000..338fff88 --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/android/ifaddrs.c @@ -0,0 +1,600 @@ +/* +Copyright (c) 2013, Kenneth MacKay +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "ifaddrs.h" + +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <sys/socket.h> +#include <net/if_arp.h> +#include <netinet/in.h> +#include <linux/netlink.h> +#include <linux/rtnetlink.h> + +typedef struct NetlinkList +{ + struct NetlinkList *m_next; + struct nlmsghdr *m_data; + unsigned int m_size; +} NetlinkList; + +static int netlink_socket(void) +{ + int l_socket = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if(l_socket < 0) + { + return -1; + } + + struct sockaddr_nl l_addr; + memset(&l_addr, 0, sizeof(l_addr)); + l_addr.nl_family = AF_NETLINK; + if(bind(l_socket, (struct sockaddr *)&l_addr, sizeof(l_addr)) < 0) + { + close(l_socket); + return -1; + } + + return l_socket; +} + +static int netlink_send(int p_socket, int p_request) +{ + char l_buffer[NLMSG_ALIGN(sizeof(struct nlmsghdr)) + NLMSG_ALIGN(sizeof(struct rtgenmsg))]; + memset(l_buffer, 0, sizeof(l_buffer)); + struct nlmsghdr *l_hdr = (struct nlmsghdr *)l_buffer; + struct rtgenmsg *l_msg = (struct rtgenmsg *)NLMSG_DATA(l_hdr); + + l_hdr->nlmsg_len = NLMSG_LENGTH(sizeof(*l_msg)); + l_hdr->nlmsg_type = p_request; + l_hdr->nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; + l_hdr->nlmsg_pid = 0; + l_hdr->nlmsg_seq = p_socket; + l_msg->rtgen_family = AF_UNSPEC; + + struct sockaddr_nl l_addr; + memset(&l_addr, 0, sizeof(l_addr)); + l_addr.nl_family = AF_NETLINK; + return (sendto(p_socket, l_hdr, l_hdr->nlmsg_len, 0, (struct sockaddr *)&l_addr, sizeof(l_addr))); +} + +static int netlink_recv(int p_socket, void *p_buffer, size_t p_len) +{ + struct msghdr l_msg; + struct iovec l_iov = { p_buffer, p_len }; + struct sockaddr_nl l_addr; + int l_result; + + for(;;) + { + l_msg.msg_name = (void *)&l_addr; + l_msg.msg_namelen = sizeof(l_addr); + l_msg.msg_iov = &l_iov; + l_msg.msg_iovlen = 1; + l_msg.msg_control = NULL; + l_msg.msg_controllen = 0; + l_msg.msg_flags = 0; + int l_result = recvmsg(p_socket, &l_msg, 0); + + if(l_result < 0) + { + if(errno == EINTR) + { + continue; + } + return -2; + } + + if(l_msg.msg_flags & MSG_TRUNC) + { // buffer was too small + return -1; + } + return l_result; + } +} + +static struct nlmsghdr *getNetlinkResponse(int p_socket, int *p_size, int *p_done) +{ + size_t l_size = 4096; + void *l_buffer = NULL; + + for(;;) + { + free(l_buffer); + l_buffer = malloc(l_size); + + int l_read = netlink_recv(p_socket, l_buffer, l_size); + *p_size = l_read; + if(l_read == -2) + { + free(l_buffer); + return NULL; + } + if(l_read >= 0) + { + pid_t l_pid = getpid(); + struct nlmsghdr *l_hdr; + for(l_hdr = (struct nlmsghdr *)l_buffer; NLMSG_OK(l_hdr, (unsigned int)l_read); l_hdr = (struct nlmsghdr *)NLMSG_NEXT(l_hdr, l_read)) + { + if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket) + { + continue; + } + + if(l_hdr->nlmsg_type == NLMSG_DONE) + { + *p_done = 1; + break; + } + + if(l_hdr->nlmsg_type == NLMSG_ERROR) + { + free(l_buffer); + return NULL; + } + } + return l_buffer; + } + + l_size *= 2; + } +} + +static NetlinkList *newListItem(struct nlmsghdr *p_data, unsigned int p_size) +{ + NetlinkList *l_item = malloc(sizeof(NetlinkList)); + l_item->m_next = NULL; + l_item->m_data = p_data; + l_item->m_size = p_size; + return l_item; +} + +static void freeResultList(NetlinkList *p_list) +{ + NetlinkList *l_cur; + while(p_list) + { + l_cur = p_list; + p_list = p_list->m_next; + free(l_cur->m_data); + free(l_cur); + } +} + +static NetlinkList *getResultList(int p_socket, int p_request) +{ + if(netlink_send(p_socket, p_request) < 0) + { + return NULL; + } + + NetlinkList *l_list = NULL; + NetlinkList *l_end = NULL; + int l_size; + int l_done = 0; + while(!l_done) + { + struct nlmsghdr *l_hdr = getNetlinkResponse(p_socket, &l_size, &l_done); + if(!l_hdr) + { // error + freeResultList(l_list); + return NULL; + } + + NetlinkList *l_item = newListItem(l_hdr, l_size); + if(!l_list) + { + l_list = l_item; + } + else + { + l_end->m_next = l_item; + } + l_end = l_item; + } + return l_list; +} + +static size_t maxSize(size_t a, size_t b) +{ + return (a > b ? a : b); +} + +static size_t calcAddrLen(sa_family_t p_family, int p_dataSize) +{ + switch(p_family) + { + case AF_INET: + return sizeof(struct sockaddr_in); + case AF_INET6: + return sizeof(struct sockaddr_in6); + case AF_PACKET: + return maxSize(sizeof(struct sockaddr_ll), offsetof(struct sockaddr_ll, sll_addr) + p_dataSize); + default: + return maxSize(sizeof(struct sockaddr), offsetof(struct sockaddr, sa_data) + p_dataSize); + } +} + +static void makeSockaddr(sa_family_t p_family, struct sockaddr *p_dest, void *p_data, size_t p_size) +{ + switch(p_family) + { + case AF_INET: + memcpy(&((struct sockaddr_in*)p_dest)->sin_addr, p_data, p_size); + break; + case AF_INET6: + memcpy(&((struct sockaddr_in6*)p_dest)->sin6_addr, p_data, p_size); + break; + case AF_PACKET: + memcpy(((struct sockaddr_ll*)p_dest)->sll_addr, p_data, p_size); + ((struct sockaddr_ll*)p_dest)->sll_halen = p_size; + break; + default: + memcpy(p_dest->sa_data, p_data, p_size); + break; + } + p_dest->sa_family = p_family; +} + +static void addToEnd(struct ifaddrs **p_resultList, struct ifaddrs *p_entry) +{ + if(!*p_resultList) + { + *p_resultList = p_entry; + } + else + { + struct ifaddrs *l_cur = *p_resultList; + while(l_cur->ifa_next) + { + l_cur = l_cur->ifa_next; + } + l_cur->ifa_next = p_entry; + } +} + +static void interpretLink(struct nlmsghdr *p_hdr, struct ifaddrs **p_links, struct ifaddrs **p_resultList) +{ + struct ifinfomsg *l_info = (struct ifinfomsg *)NLMSG_DATA(p_hdr); + + size_t l_nameSize = 0; + size_t l_addrSize = 0; + size_t l_dataSize = 0; + + size_t l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg)); + struct rtattr *l_rta; + for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifinfomsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) + { + void *l_rtaData = RTA_DATA(l_rta); + size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); + switch(l_rta->rta_type) + { + case IFLA_ADDRESS: + case IFLA_BROADCAST: + l_addrSize += NLMSG_ALIGN(calcAddrLen(AF_PACKET, l_rtaDataSize)); + break; + case IFLA_IFNAME: + l_nameSize += NLMSG_ALIGN(l_rtaSize + 1); + break; + case IFLA_STATS: + l_dataSize += NLMSG_ALIGN(l_rtaSize); + break; + default: + break; + } + } + + struct ifaddrs *l_entry = malloc(sizeof(struct ifaddrs) + l_nameSize + l_addrSize + l_dataSize); + memset(l_entry, 0, sizeof(struct ifaddrs)); + l_entry->ifa_name = ""; + + char *l_name = ((char *)l_entry) + sizeof(struct ifaddrs); + char *l_addr = l_name + l_nameSize; + char *l_data = l_addr + l_addrSize; + + l_entry->ifa_flags = l_info->ifi_flags; + + l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg)); + for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifinfomsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) + { + void *l_rtaData = RTA_DATA(l_rta); + size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); + switch(l_rta->rta_type) + { + case IFLA_ADDRESS: + case IFLA_BROADCAST: + { + size_t l_addrLen = calcAddrLen(AF_PACKET, l_rtaDataSize); + makeSockaddr(AF_PACKET, (struct sockaddr *)l_addr, l_rtaData, l_rtaDataSize); + ((struct sockaddr_ll *)l_addr)->sll_ifindex = l_info->ifi_index; + ((struct sockaddr_ll *)l_addr)->sll_hatype = l_info->ifi_type; + if(l_rta->rta_type == IFLA_ADDRESS) + { + l_entry->ifa_addr = (struct sockaddr *)l_addr; + } + else + { + l_entry->ifa_broadaddr = (struct sockaddr *)l_addr; + } + l_addr += NLMSG_ALIGN(l_addrLen); + break; + } + case IFLA_IFNAME: + strncpy(l_name, l_rtaData, l_rtaDataSize); + l_name[l_rtaDataSize] = '\0'; + l_entry->ifa_name = l_name; + break; + case IFLA_STATS: + memcpy(l_data, l_rtaData, l_rtaDataSize); + l_entry->ifa_data = l_data; + break; + default: + break; + } + } + + addToEnd(p_resultList, l_entry); + p_links[l_info->ifi_index - 1] = l_entry; +} + +static void interpretAddr(struct nlmsghdr *p_hdr, struct ifaddrs **p_links, struct ifaddrs **p_resultList) +{ + struct ifaddrmsg *l_info = (struct ifaddrmsg *)NLMSG_DATA(p_hdr); + + size_t l_nameSize = 0; + size_t l_addrSize = 0; + + int l_addedNetmask = 0; + + size_t l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg)); + struct rtattr *l_rta; + for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifaddrmsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) + { + void *l_rtaData = RTA_DATA(l_rta); + size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); + if(l_info->ifa_family == AF_PACKET) + { + continue; + } + + switch(l_rta->rta_type) + { + case IFA_ADDRESS: + case IFA_LOCAL: + if((l_info->ifa_family == AF_INET || l_info->ifa_family == AF_INET6) && !l_addedNetmask) + { // make room for netmask + l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize)); + l_addedNetmask = 1; + } + case IFA_BROADCAST: + l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize)); + break; + case IFA_LABEL: + l_nameSize += NLMSG_ALIGN(l_rtaSize + 1); + break; + default: + break; + } + } + + struct ifaddrs *l_entry = malloc(sizeof(struct ifaddrs) + l_nameSize + l_addrSize); + memset(l_entry, 0, sizeof(struct ifaddrs)); + l_entry->ifa_name = p_links[l_info->ifa_index - 1]->ifa_name; + + char *l_name = ((char *)l_entry) + sizeof(struct ifaddrs); + char *l_addr = l_name + l_nameSize; + + l_entry->ifa_flags = l_info->ifa_flags | p_links[l_info->ifa_index - 1]->ifa_flags; + + l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg)); + for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifaddrmsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) + { + void *l_rtaData = RTA_DATA(l_rta); + size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); + switch(l_rta->rta_type) + { + case IFA_ADDRESS: + case IFA_BROADCAST: + case IFA_LOCAL: + { + size_t l_addrLen = calcAddrLen(l_info->ifa_family, l_rtaDataSize); + makeSockaddr(l_info->ifa_family, (struct sockaddr *)l_addr, l_rtaData, l_rtaDataSize); + if(l_info->ifa_family == AF_INET6) + { + if(IN6_IS_ADDR_LINKLOCAL((struct in6_addr *)l_rtaData) || IN6_IS_ADDR_MC_LINKLOCAL((struct in6_addr *)l_rtaData)) + { + ((struct sockaddr_in6 *)l_addr)->sin6_scope_id = l_info->ifa_index; + } + } + + if(l_rta->rta_type == IFA_ADDRESS) + { // apparently in a point-to-point network IFA_ADDRESS contains the dest address and IFA_LOCAL contains the local address + if(l_entry->ifa_addr) + { + l_entry->ifa_dstaddr = (struct sockaddr *)l_addr; + } + else + { + l_entry->ifa_addr = (struct sockaddr *)l_addr; + } + } + else if(l_rta->rta_type == IFA_LOCAL) + { + if(l_entry->ifa_addr) + { + l_entry->ifa_dstaddr = l_entry->ifa_addr; + } + l_entry->ifa_addr = (struct sockaddr *)l_addr; + } + else + { + l_entry->ifa_broadaddr = (struct sockaddr *)l_addr; + } + l_addr += NLMSG_ALIGN(l_addrLen); + break; + } + case IFA_LABEL: + strncpy(l_name, l_rtaData, l_rtaDataSize); + l_name[l_rtaDataSize] = '\0'; + l_entry->ifa_name = l_name; + break; + default: + break; + } + } + + if(l_entry->ifa_addr && (l_entry->ifa_addr->sa_family == AF_INET || l_entry->ifa_addr->sa_family == AF_INET6)) + { + unsigned l_maxPrefix = (l_entry->ifa_addr->sa_family == AF_INET ? 32 : 128); + unsigned l_prefix = (l_info->ifa_prefixlen > l_maxPrefix ? l_maxPrefix : l_info->ifa_prefixlen); + char l_mask[16] = {0}; + unsigned i; + for(i=0; i<(l_prefix/8); ++i) + { + l_mask[i] = 0xff; + } + l_mask[i] = 0xff << (8 - (l_prefix % 8)); + + makeSockaddr(l_entry->ifa_addr->sa_family, (struct sockaddr *)l_addr, l_mask, l_maxPrefix / 8); + l_entry->ifa_netmask = (struct sockaddr *)l_addr; + } + + addToEnd(p_resultList, l_entry); +} + +static void interpret(int p_socket, NetlinkList *p_netlinkList, struct ifaddrs **p_links, struct ifaddrs **p_resultList) +{ + pid_t l_pid = getpid(); + for(; p_netlinkList; p_netlinkList = p_netlinkList->m_next) + { + unsigned int l_nlsize = p_netlinkList->m_size; + struct nlmsghdr *l_hdr; + for(l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize); l_hdr = NLMSG_NEXT(l_hdr, l_nlsize)) + { + if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket) + { + continue; + } + + if(l_hdr->nlmsg_type == NLMSG_DONE) + { + break; + } + + if(l_hdr->nlmsg_type == RTM_NEWLINK) + { + interpretLink(l_hdr, p_links, p_resultList); + } + else if(l_hdr->nlmsg_type == RTM_NEWADDR) + { + interpretAddr(l_hdr, p_links, p_resultList); + } + } + } +} + +static unsigned countLinks(int p_socket, NetlinkList *p_netlinkList) +{ + unsigned l_links = 0; + pid_t l_pid = getpid(); + for(; p_netlinkList; p_netlinkList = p_netlinkList->m_next) + { + unsigned int l_nlsize = p_netlinkList->m_size; + struct nlmsghdr *l_hdr; + for(l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize); l_hdr = NLMSG_NEXT(l_hdr, l_nlsize)) + { + if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket) + { + continue; + } + + if(l_hdr->nlmsg_type == NLMSG_DONE) + { + break; + } + + if(l_hdr->nlmsg_type == RTM_NEWLINK) + { + ++l_links; + } + } + } + + return l_links; +} + +int getifaddrs(struct ifaddrs **ifap) +{ + if(!ifap) + { + return -1; + } + *ifap = NULL; + + int l_socket = netlink_socket(); + if(l_socket < 0) + { + return -1; + } + + NetlinkList *l_linkResults = getResultList(l_socket, RTM_GETLINK); + if(!l_linkResults) + { + close(l_socket); + return -1; + } + + NetlinkList *l_addrResults = getResultList(l_socket, RTM_GETADDR); + if(!l_addrResults) + { + close(l_socket); + freeResultList(l_linkResults); + return -1; + } + + unsigned l_numLinks = countLinks(l_socket, l_linkResults) + countLinks(l_socket, l_addrResults); + struct ifaddrs *l_links[l_numLinks]; + memset(l_links, 0, l_numLinks * sizeof(struct ifaddrs *)); + + interpret(l_socket, l_linkResults, l_links, ifap); + interpret(l_socket, l_addrResults, l_links, ifap); + + freeResultList(l_linkResults); + freeResultList(l_addrResults); + close(l_socket); + return 0; +} + +void freeifaddrs(struct ifaddrs *ifa) +{ + struct ifaddrs *l_cur; + while(ifa) + { + l_cur = ifa; + ifa = ifa->ifa_next; + free(l_cur); + } +} diff --git a/metis/ccnx/forwarder/metis/platforms/android/ifaddrs.h b/metis/ccnx/forwarder/metis/platforms/android/ifaddrs.h new file mode 100644 index 00000000..9cd19fec --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/android/ifaddrs.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 1995, 1999 + * Berkeley Software Design, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY Berkeley Software Design, Inc. ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Berkeley Software Design, Inc. BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * BSDI ifaddrs.h,v 2.5 2000/02/23 14:51:59 dab Exp + */ + +#ifndef _IFADDRS_H_ +#define _IFADDRS_H_ + +struct ifaddrs { + struct ifaddrs *ifa_next; + char *ifa_name; + unsigned int ifa_flags; + struct sockaddr *ifa_addr; + struct sockaddr *ifa_netmask; + struct sockaddr *ifa_dstaddr; + void *ifa_data; +}; + +/* + * This may have been defined in <net/if.h>. Note that if <net/if.h> is + * to be included it must be included before this header file. + */ +#ifndef ifa_broadaddr +#define ifa_broadaddr ifa_dstaddr /* broadcast address interface */ +#endif + +#include <sys/cdefs.h> + +__BEGIN_DECLS +extern int getifaddrs(struct ifaddrs **ifap); +extern void freeifaddrs(struct ifaddrs *ifa); +__END_DECLS + +#endif diff --git a/metis/ccnx/forwarder/metis/platforms/android/metis_GenericEther.c b/metis/ccnx/forwarder/metis/platforms/android/metis_GenericEther.c new file mode 100644 index 00000000..e2d108e0 --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/android/metis_GenericEther.c @@ -0,0 +1,368 @@ +/* + * 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 <fcntl.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> + +#include <arpa/inet.h> +#include <linux/if_packet.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <net/if.h> +#include <netinet/ether.h> +#include <net/ethernet.h> +#include <linux/if_arp.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> +#include <ccnx/forwarder/metis/io/metis_GenericEther.h> +#include <ccnx/forwarder/metis/core/metis_System.h> +#include <parc/algol/parc_Object.h> +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> +#include <ccnx/forwarder/metis/io/metis_Ethernet.h> + +struct metis_generic_ether { + uint16_t ethertype; + int etherSocket; + + int linuxInterfaceIndex; + + PARCBuffer *macAddress; + MetisLogger *logger; + + // MTU set on interface when we are created + unsigned mtu; +}; + +static bool _linuxEthernet_SetupSocket(MetisGenericEther *ether, const char *devstr); + +static void +_metisGenericEther_Destroy(MetisGenericEther **etherPtr) +{ + MetisGenericEther *ether = *etherPtr; + + if (ether->etherSocket > 0) { + close(ether->etherSocket); + } + + if (ether->macAddress) { + parcBuffer_Release(ðer->macAddress); + } + + metisLogger_Release(ðer->logger); +} + +parcObject_ExtendPARCObject(MetisGenericEther, _metisGenericEther_Destroy, NULL, NULL, NULL, NULL, NULL, NULL); + +parcObject_ImplementAcquire(metisGenericEther, MetisGenericEther); + +parcObject_ImplementRelease(metisGenericEther, MetisGenericEther); + +// ========================= +// PUBLIC API +// ========================= + +MetisGenericEther * +metisGenericEther_Create(MetisForwarder *metis, const char *deviceName, uint16_t etherType) +{ + assertNotNull(metis, "Parameter metis must be non-null"); + assertNotNull(deviceName, "Parameter deviceName must be non-null"); + + MetisGenericEther *ether = NULL; + + if (metisEthernet_IsValidEthertype(etherType)) { + ether = parcObject_CreateInstance(MetisGenericEther); + ether->ethertype = etherType; + ether->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + ether->mtu = metisSystem_InterfaceMtu(metis, deviceName); + + ether->etherSocket = -1; // invalid valid + ether->macAddress = NULL; + + bool success = _linuxEthernet_SetupSocket(ether, deviceName); + + if (success) { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + char *str = parcBuffer_ToHexString(ether->macAddress); + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "GenericEther %p created on device %s (%s) for ethertype 0x%04x fd %d ifindex %u mtu %u", + (void *) ether, deviceName, str, etherType, ether->etherSocket, ether->linuxInterfaceIndex, + ether->mtu); + parcMemory_Deallocate((void **) &str); + } + } else { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "GenericEther failed to created on device %s for ethertype 0x%04x", + deviceName, etherType); + } + + // this will also null ether + metisGenericEther_Release(ðer); + } + } else { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "GenericEther failed to created on device %s for ethertype 0x%04x, invalid ethertype", + deviceName, etherType); + } + } + + return ether; +} + +int +metisGenericEther_GetDescriptor(const MetisGenericEther *ether) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + return ether->etherSocket; +} + +/** + * Based on the fixed header, trim the buffer + * + * Some platforms do not strip the ethernet CRC from the raw packet. Trim the buffer to + * the right sized based on the fixed header. + * + * @param [in] ether An allocated ethernet interface + * @param [in] readBuffer The buffer to trim + * + * Example: + * @code + * <#example#> + * @endcode + */ +static void +_linuxEthernet_TrimBuffer(MetisGenericEther *ether, PARCEventBuffer *readBuffer) +{ + // read the fixed header + uint8_t *etherHeader = parcEventBuffer_Pullup(readBuffer, ETHER_HDR_LEN + metisTlv_FixedHeaderLength()); + + if (etherHeader) { + uint8_t *fixedHeader = etherHeader + ETHER_HDR_LEN; + size_t totalLength = metisTlv_TotalPacketLength(fixedHeader) + ETHER_HDR_LEN; + + if (parcEventBuffer_GetLength(readBuffer) > totalLength) { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "%s buffer length %zu, actual length %zu (ether header + ccnx packet), trimming %zu bytes", + __func__, parcEventBuffer_GetLength(readBuffer), totalLength, + parcEventBuffer_GetLength(readBuffer) - totalLength); + } + + PARCEventBuffer *temp = parcEventBuffer_Create(); + + // there's no way to drain from the end, so we move to a tempororary buffer, + // drain the unwanted part, then move back. + + int movedBytes = parcEventBuffer_ReadIntoBuffer(readBuffer, temp, totalLength); + assertTrue(movedBytes == totalLength, "Failed to move all the bytes, got %d expected %zu", movedBytes, totalLength); + + // flush all the bytes out of the read buffer + parcEventBuffer_Read(readBuffer, NULL, -1); + + // now put back what we want + int failure = parcEventBuffer_AppendBuffer(temp, readBuffer); + assertFalse(failure, "parcEventBuffer_AppendBuffer failed"); + + parcEventBuffer_Destroy(&temp); + } + } +} + +/* + * Reading a raw socket, on some systems, may include the FCS trailer + */ +bool +metisGenericEther_ReadNextFrame(MetisGenericEther *ether, PARCEventBuffer *readBuffer) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + assertNotNull(readBuffer, "Parameter readBuffer must be non-null"); + + bool success = false; + + int evread_length = parcEventBuffer_ReadFromFileDescriptor(readBuffer, ether->etherSocket, (int) -1); + + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "%s read length %d", __func__, evread_length); + } + + if (evread_length > 0) { + _linuxEthernet_TrimBuffer(ether, readBuffer); + success = true; + } + + return success; +} + +bool +metisGenericEther_SendFrame(MetisGenericEther *ether, PARCEventBuffer *buffer) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + + // cannot use parcEventBuffer_WriteToFileDescriptor because we need to write the length in one go, not use the + // iovec approach in parcEventBuffer_WriteToFileDescriptor. It can cause problems on some platforms. + + uint8_t *linear = parcEventBuffer_Pullup(buffer, -1); + size_t length = parcEventBuffer_GetLength(buffer); + + ssize_t written = write(ether->etherSocket, linear, length); + if (written == length) { + return true; + } + return false; +} + +PARCBuffer * +metisGenericEther_GetMacAddress(const MetisGenericEther *ether) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + return ether->macAddress; +} + +uint16_t +metisGenericEther_GetEtherType(const MetisGenericEther *ether) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + return ether->ethertype; +} + +unsigned +metisGenericEther_GetMTU(const MetisGenericEther *ether) +{ + return ether->mtu; +} + +// ================== +// PRIVATE API + +static bool +_linuxEthernet_SetInterfaceIndex(MetisGenericEther *ether, const char *devstr) +{ + // get the interface index of the desired device + bool success = false; + + struct ifreq if_idx; + memset(&if_idx, 0, sizeof(if_idx)); + strncpy(if_idx.ifr_name, devstr, IFNAMSIZ - 1); + if (!ioctl(ether->etherSocket, SIOCGIFINDEX, &if_idx)) { + ether->linuxInterfaceIndex = if_idx.ifr_ifindex; + success = true; + } else { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "ioctl SIOCGIFINDEX error: (%d) %s", errno, strerror(errno)); + } + } + + return success; +} + +static bool +_linuxEthernet_SetInterfaceAddress(MetisGenericEther *ether, const char *devstr) +{ + bool success = false; + + assertNull(ether->macAddress, "Should only be called once with null macAddress"); + + // get the MAC address of the device + struct ifreq if_mac; + memset(&if_mac, 0, sizeof(if_mac)); + strncpy(if_mac.ifr_name, devstr, IFNAMSIZ - 1); + if (!ioctl(ether->etherSocket, SIOCGIFHWADDR, &if_mac)) { + if (if_mac.ifr_hwaddr.sa_family == ARPHRD_ETHER) { + ether->macAddress = parcBuffer_Allocate(ETHER_ADDR_LEN); + parcBuffer_PutArray(ether->macAddress, ETHER_ADDR_LEN, (uint8_t *) if_mac.ifr_hwaddr.sa_data); + parcBuffer_Flip(ether->macAddress); + success = true; + } else { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Device %s does not have an Ethernet hardware address", devstr); + } + } + } + return success; +} + +static bool +_linuxEthernet_Bind(MetisGenericEther *ether) +{ + bool success = false; + + // we need to bind to the ethertype to receive packets + struct sockaddr_ll my_addr; + my_addr.sll_family = PF_PACKET; + my_addr.sll_protocol = htons(ether->ethertype); + my_addr.sll_ifindex = ether->linuxInterfaceIndex; + + if (!bind(ether->etherSocket, (struct sockaddr *) &my_addr, sizeof(struct sockaddr_ll))) { + success = true; + } else { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "bind error: (%d) %s", errno, strerror(errno)); + } + } + return success; +} + +static bool +_linuxEthernet_SetNonBlocking(MetisGenericEther *ether) +{ + bool success = false; + uint32_t on = 1; + if (!ioctl(ether->etherSocket, FIONBIO, &on)) { + success = true; + } + return success; +} + +static bool +_linuxEthernet_SetupSocket(MetisGenericEther *ether, const char *devstr) +{ + bool success = false; + ether->etherSocket = socket(AF_PACKET, SOCK_RAW, htons(ether->ethertype)); + if (ether->etherSocket > 0) { + if (_linuxEthernet_SetInterfaceIndex(ether, devstr)) { + if (_linuxEthernet_SetInterfaceAddress(ether, devstr)) { + if (_linuxEthernet_Bind(ether)) { + // set non-blocking + if (_linuxEthernet_SetNonBlocking(ether)) { + success = true; + } + } + } + } + } + + if (!success) { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "setup socket error: (%d) %s", errno, strerror(errno)); + } + } + + return success; +} + + + diff --git a/metis/ccnx/forwarder/metis/platforms/android/metis_System.c b/metis/ccnx/forwarder/metis/platforms/android/metis_System.c new file mode 100644 index 00000000..a8f42854 --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/android/metis_System.c @@ -0,0 +1,193 @@ +/* + * 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 <unistd.h> +#include <sys/types.h> +#include <sys/ioctl.h> + + +#include <errno.h> +#include <string.h> + +//#define __USE_MISC +#include <net/if.h> + +// to get the list of arp types +#include <net/if_arp.h> + +// for the mac address +#include <netpacket/packet.h> + +#include <ccnx/api/control/cpi_InterfaceSet.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +#include <LongBow/runtime.h> + +#include "ifaddrs.h" + +/** + * Returns the MTU for a named interface + * + * On linux, we get the MTU by opening a socket and reading SIOCGIFMTU + * + * @param [in] ifname Interface name (e.g. "eth0") + * + * @retval number The MTU in bytes + * + * Example: + * @code + * <#example#> + * @endcode + */ +static int +getMtu(const char *ifname) +{ + struct ifreq ifr; + int fd; + + fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); + + strcpy(ifr.ifr_name, ifname); + ioctl(fd, SIOCGIFMTU, &ifr); + + close(fd); + return ifr.ifr_mtu; +} + +CPIInterfaceSet * +metisSystem_Interfaces(MetisForwarder *metis) +{ + CPIInterfaceSet *set = cpiInterfaceSet_Create(); + + MetisLogger *logger = metisForwarder_GetLogger(metis); + + // this is the dynamically allocated head of the list + struct ifaddrs *ifaddr; + int failure = getifaddrs(&ifaddr); + assertFalse(failure, "Error getifaddrs: (%d) %s", errno, strerror(errno)); + + struct ifaddrs *next; + for (next = ifaddr; next != NULL; next = next->ifa_next) { + if ((next->ifa_addr == NULL) || ((next->ifa_flags & IFF_UP) == 0)) { + continue; + } + + CPIInterface *iface = cpiInterfaceSet_GetByName(set, next->ifa_name); + if (iface == NULL) { + unsigned mtu = (unsigned) getMtu(next->ifa_name); + + iface = cpiInterface_Create(next->ifa_name, + metisForwarder_GetNextConnectionId(metis), + next->ifa_flags & IFF_LOOPBACK, + next->ifa_flags & IFF_MULTICAST, + mtu); + + cpiInterfaceSet_Add(set, iface); + } + + int family = next->ifa_addr->sa_family; + switch (family) { + case AF_INET: { + CPIAddress *address = cpiAddress_CreateFromInet((struct sockaddr_in *) next->ifa_addr); + cpiInterface_AddAddress(iface, address); + break; + } + + case AF_INET6: { + CPIAddress *address = cpiAddress_CreateFromInet6((struct sockaddr_in6 *) next->ifa_addr); + cpiInterface_AddAddress(iface, address); + break; + } + + case AF_PACKET: { + struct sockaddr_ll *addr_ll = (struct sockaddr_ll *) next->ifa_addr; + + if (metisLogger_IsLoggable(logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "sockaddr_ll family %d proto %d ifindex %d hatype %d pkttype %d halen %d", + addr_ll->sll_family, + addr_ll->sll_protocol, + addr_ll->sll_ifindex, + addr_ll->sll_hatype, + addr_ll->sll_pkttype, + addr_ll->sll_halen); + } + + switch (addr_ll->sll_hatype) { + // list of the ARP hatypes we can extract a MAC address from + case ARPHRD_ETHER: + // fallthrough + case ARPHRD_IEEE802: { + CPIAddress *address = cpiAddress_CreateFromLink((uint8_t *) addr_ll->sll_addr, addr_ll->sll_halen); + cpiInterface_AddAddress(iface, address); + break; + } + default: + break; + } + + break; + } + } + } + + freeifaddrs(ifaddr); + return set; +} + +CPIAddress * +metisSystem_GetMacAddressByName(MetisForwarder *metis, const char *interfaceName) +{ + CPIAddress *linkAddress = NULL; + + CPIInterfaceSet *interfaceSet = metisSystem_Interfaces(metis); + CPIInterface *interface = cpiInterfaceSet_GetByName(interfaceSet, interfaceName); + + if (interface) { + const CPIAddressList *addressList = cpiInterface_GetAddresses(interface); + + size_t length = cpiAddressList_Length(addressList); + for (size_t i = 0; i < length && !linkAddress; i++) { + const CPIAddress *a = cpiAddressList_GetItem(addressList, i); + if (cpiAddress_GetType(a) == cpiAddressType_LINK) { + linkAddress = cpiAddress_Copy(a); + } + } + } + + cpiInterfaceSet_Destroy(&interfaceSet); + + return linkAddress; +} + +unsigned +metisSystem_InterfaceMtu(MetisForwarder *metis, const char *interfaceName) +{ + unsigned mtu = 0; + + CPIInterfaceSet *interfaceSet = metisSystem_Interfaces(metis); + CPIInterface *interface = cpiInterfaceSet_GetByName(interfaceSet, interfaceName); + + if (interface) { + mtu = cpiInterface_GetMTU(interface); + } + + cpiInterfaceSet_Destroy(&interfaceSet); + + return mtu; +} diff --git a/metis/ccnx/forwarder/metis/platforms/darwin/metis_GenericEther.c b/metis/ccnx/forwarder/metis/platforms/darwin/metis_GenericEther.c new file mode 100644 index 00000000..b49b6579 --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/darwin/metis_GenericEther.c @@ -0,0 +1,618 @@ +/* + * 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. + */ + +/** + * Implements the platform-specific code for working with an Ethernet interface. + * + * Uses the Berkeley Packet Filter (BPF) approach to reading the Ethernet device. + */ + +#include <config.h> +#include <stdio.h> +#include <net/bpf.h> +#include <sys/ioctl.h> +#include <net/if.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <net/if_arp.h> +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_types.h> +#include <ifaddrs.h> + +#include <net/ethernet.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> +#include <ccnx/forwarder/metis/io/metis_GenericEther.h> +#include <ccnx/forwarder/metis/core/metis_System.h> +#include <parc/algol/parc_Object.h> +#include <ccnx/forwarder/metis/io/metis_Ethernet.h> + +struct metis_generic_ether { + uint16_t ethertype; + int etherSocket; + + // what size do the read buffers need to be? ioctl BIOCGBLEN will tell us. + unsigned etherBufferLength; + + // MTU set on interface when we are created + unsigned mtu; + + PARCEventBuffer *workBuffer; + + PARCBuffer *macAddress; + MetisLogger *logger; +}; + +static void +_metisGenericEther_Destroy(MetisGenericEther **etherPtr) +{ + MetisGenericEther *ether = *etherPtr; + + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "GenericEther %p destroyed", (void *) ether); + } + + if (ether->etherSocket > 0) { + close(ether->etherSocket); + } + + if (ether->macAddress) { + parcBuffer_Release(ðer->macAddress); + } + + metisLogger_Release(ðer->logger); + parcEventBuffer_Destroy(ðer->workBuffer); +} + + +// ========================= +// PRIVATE API +// ========================= + +/* + * Returns the total legth of the good data: the ethernet header plus ccnx packet. + * If there is a FCS, it will be excluded. + * + * PRECONDITION: You have drained off any BPF headers and the first byte + * of the work buffer points to the first byte of the Ethernet header + */ +static uint16_t +_getFrameLengthFromWorkBuffer(MetisGenericEther *ether) +{ + uint16_t frameLength = 0; + + // read the fixed header + uint8_t *etherHeader = parcEventBuffer_Pullup(ether->workBuffer, ETHER_HDR_LEN + metisTlv_FixedHeaderLength()); + + if (etherHeader) { + uint8_t *fixedHeader = etherHeader + ETHER_HDR_LEN; + frameLength = metisTlv_TotalPacketLength(fixedHeader) + ETHER_HDR_LEN; + } + + return frameLength; +} + +/* + * An attempt to read from the workbuffer to the readbuffer can succeed (ok), fail because + * the work buffer does not have enough bytes (empty), or an error causes a frame to be + * discarded (tryagain). + */ +typedef enum { + ReadWorkBufferResult_Ok, + ReadWorkBufferResult_Empty, + ReadWorkBufferResult_TryAgain +} _ReadWorkBufferResult; + +/** + * Parses the work buffer to extract packets + * + * The work buffer should be filled in with a set of tuples (bh_hdrlen, frame, pad). + * The pad extends each packet out to BPF_WORDALIGN. + * + * If the CCNxMessage PacketLength says it is larger than the read capture length (caplen), + * then this is an invalid packet and it will be discarded. This error will result in a + * ReadWorkBufferResult_TryAgain condition. + * + * struct bpf_hdr { + * struct BPF_TIMEVAL bh_tstamp; // time stamp + * bpf_u_int32 bh_caplen; // length of captured portion + * bpf_u_int32 bh_datalen; // original length of packet + * u_short bh_hdrlen; // length of bpf header (this struct plus alignment padding) + * } + * + * @param [in] ether An allocated Darwin ethernet. + * @param [in] readbuffer A user-provided read buffer. + * + * @retval ReadWorkBufferResult_Ok A frame was moved from workbuffer to readbuffer + * @retval ReadWorkBufferResult_Empty There's not enough bytes in the workbuffer + * @retval ReadWorkBufferResult_TryAgain (likely discard) caused this call to fail, but you should try again + * + * Example: + * @code + * <#example#> + * @endcode + */ +static _ReadWorkBufferResult +_darwinEthernet_ReadWorkBuffer(MetisGenericEther *ether, PARCEventBuffer *readbuffer) +{ + _ReadWorkBufferResult result = ReadWorkBufferResult_Empty; + + // Make sure we have linear memory for the BPF header + struct bpf_hdr *bpfHeader = (struct bpf_hdr *) parcEventBuffer_Pullup(ether->workBuffer, sizeof(struct bpf_hdr)); + + // make sure we have enough bytes to process the frame + // bpfHeader may be NULL if there are not sizeof(struct bpf_hdr) bytes available. + if (bpfHeader && parcEventBuffer_GetLength(ether->workBuffer) >= bpfHeader->bh_hdrlen + bpfHeader->bh_caplen) { + // (0) Save the needed fields from the bpf header + // (1) pop off the bpf header + // (2) move the iovec from work buffer to readBuffer. + // (3) remove any BPF_WORDALIGN padding to the start of the next packet + + // (0) Save the needed fields from the bpf header + uint16_t hdrlen = bpfHeader->bh_hdrlen; + uint32_t caplen = bpfHeader->bh_caplen; + + // (1) pop off the bpf header + parcEventBuffer_Read(ether->workBuffer, NULL, hdrlen); + + // (1a) Determine the packet length from the fixed header and only transfer that many bytes + uint16_t packetlen = _getFrameLengthFromWorkBuffer(ether); + + if (packetlen <= caplen) { + // (2) move the iovec from work buffer to readBuffer. + parcEventBuffer_ReadIntoBuffer(ether->workBuffer, readbuffer, packetlen); + + // (2a) drain off any trailer (i.e. FCS) + parcEventBuffer_Read(ether->workBuffer, NULL, caplen - packetlen); + + result = ReadWorkBufferResult_Ok; + } else { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "%s reading fd %d discard packetlen %u greater than caplen %u", + __func__, ether->etherSocket, packetlen, caplen); + } + + // discard all of caplen + parcEventBuffer_Read(ether->workBuffer, NULL, caplen); + + // tell the caller that this read failed, but they could try again. + result = ReadWorkBufferResult_TryAgain; + } + + // (3) remove any BPF_WORDALIGN padding to the start of the next packet + size_t alignedLength = BPF_WORDALIGN(hdrlen + caplen); + size_t pad = alignedLength - hdrlen - caplen; + parcEventBuffer_Read(ether->workBuffer, NULL, pad); + } + return result; +} + +/** + * Reads at most a single frame from the BPF queue in the workbuffer + * + * Reads at most a single frame from the BPF queue in the workbuffer to the user-provided + * readbuffer. Returns true if one frame moved to readbuffer or false otherwise. + * + * The frame will be appended to the readbuffer. If return failure, the readbuffer will + * be unmodified. + * + * The returned frame will not contain a FCS, even if one was read. + * + * @param [in] ether An allocated Darwin ethernet. + * @param [in] readbuffer A user-provided read buffer. + * + * @retval true A frame was added to the readbuffer + * @retval false No frame is available at this time + * + * Example: + * @code + * <#example#> + * @endcode + */ +static bool +_darwinEthernet_WorkBufferToReadBuffer(MetisGenericEther *ether, PARCEventBuffer *readbuffer) +{ + _ReadWorkBufferResult result = ReadWorkBufferResult_Empty; + do { + result = _darwinEthernet_ReadWorkBuffer(ether, readbuffer); + } while (result == ReadWorkBufferResult_TryAgain); + + return (result == ReadWorkBufferResult_Ok); +} + +/** + * Reads from the socket to fill in the work buffer + * + * Reads one or more packets from the socket to the work buffer It will append to the work buffer. + * The BFP socket is non-blocking. The BPF interface may return multiple packets in one read + * that need to be parsed as in _darwinEthernet_ReadWorkBuffer(). + * + * @param [in] ether The Darwin ethernet interface + * + * @retval true We added something to the work buffer + * @retval false Nothing was read + * + * Example: + * @code + * <#example#> + * @endcode + */ +static bool +_darwinEthernet_ReadSocket(MetisGenericEther *ether) +{ + bool success = false; + + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "%s reading fd %d bufferLength %u", __func__, ether->etherSocket, ether->etherBufferLength); + } + + // The buffer we're reading must be exactly ether->etherBufferLength + // TODO: Fix the parcEventBuffer_ReadFromFileDescriptor call to reserve that space so it's all there. + + uint8_t tempBuffer[ether->etherBufferLength]; + ssize_t read_length = read(ether->etherSocket, tempBuffer, ether->etherBufferLength); + if (read_length > 0) { + parcEventBuffer_Append(ether->workBuffer, tempBuffer, read_length); + if (read_length > 0) { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "%s read %zd bytes from fd %d", + __func__, + read_length, + ether->etherSocket); + } + success = true; + } + } + + return success; +} + + +// open the first available /dev/bpf. Returns +// the fd or -1 on none found +static int +_darwinEthernet_OpenBfpDevice(void) +{ + for (int i = 0; i < 255; i++) { + char bpfstr[255]; + snprintf(bpfstr, sizeof(bpfstr), "/dev/bpf%d", i); + + int fd = open(bpfstr, O_RDWR); + if (fd > -1) { + return fd; + } + + if (errno == EBUSY) { + continue; + } + + if (errno == EBADF) { + continue; + } + + // its an error + return -1; + } + + errno = ENOENT; + return -1; +} + +/** + * Set the ioctrl for the BPF socket + * + * (a) bind to a specific name + * (b) The kernel will fill in the source MAC address + * (c) We will see out-going packets [turn off?] + * (d) immediate read() [don't wait to queue multiple packets] + * (e) Include the buffer length + * + * Settings BIOCIMMEDIATE means that read() calls will not wait for several packets + * to accumulate. It does not, however, guarantee only one packet per read(). + * + * @param [in] fd BPF socket + * @param [in] devstr The device name + * + * @retval true Set all needed ioctrl + * @retval false An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +static bool +_darwinEthernet_SetDeviceOptions(int fd, const char *devstr) +{ + struct ifreq ifr; + uint32_t on = 1; + + bool success = false; + + // Always null terminate. If devstr did not fit in ifr_name, then + // the ioctl call will fail anyway. + if (devstr != NULL) { + (void) strncpy(ifr.ifr_name, devstr, IF_NAMESIZE - 1); + ifr.ifr_name[IF_NAMESIZE - 1] = 0; + + if (ioctl(fd, BIOCSETIF, &ifr)) { + return false; + } + } + + // on = immediate read on packet reception + if (!ioctl(fd, BIOCIMMEDIATE, &on)) { + // on = request the buffer length prepended to each packet + if (!ioctl(fd, BIOCGBLEN, &on)) { + // set non-blocking + if (!ioctl(fd, FIONBIO, &on)) { + success = true; + } + } + } + + return success; +} + +/** + * Create the berkeley packet filter for our ethertype + * + * Creates a BPF for our ether type. + * + * @param [in,out] ether The MetisGenericEther to modify + * + * @retval true success + * @retval false failure + * + * Example: + * @code + * <#example#> + * @endcode + */ +static bool +_darwinEthernet_SetFilter(MetisGenericEther *ether) +{ + struct bpf_program filterCode = { 0 }; + + // BPF instructions: + // Load 12 to accumulator (offset of ethertype) + // Jump Equal to netbyteorder_ethertype 0 instructions, otherwise 1 instruction + // 0: return a length of -1 (meaning whole packet) + // 1: return a length of 0 (meaning skip packet) + struct bpf_insn instructions[] = { + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0801, 0, 1), + BPF_STMT(BPF_RET + BPF_K, (u_int) - 1), + BPF_STMT(BPF_RET + BPF_K, 0), + }; + // BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ether->ethertype, 0, 1), + + /* Set the filter */ + filterCode.bf_len = sizeof(instructions) / sizeof(struct bpf_insn); + filterCode.bf_insns = &instructions[0]; + + if (ioctl(ether->etherSocket, BIOCSETF, &filterCode) < 0) { + return false; + } + + return true; +} + +static bool +_darwinEthernet_SetupReceive(MetisGenericEther *ether, const char *devstr) +{ + // If we cannot open the Ethernet BPF (likely due to permissions), return + // a soft error so we can move past this failure. + if ((ether->etherSocket = _darwinEthernet_OpenBfpDevice()) < 0) { + return false; + } + + if (!_darwinEthernet_SetDeviceOptions(ether->etherSocket, devstr)) { + trapUnrecoverableState("error setting options: %s", strerror(errno)); + } + + + if (!_darwinEthernet_SetFilter(ether)) { + trapUnrecoverableState("error setting filter: %s", strerror(errno)); + } + + if (ioctl(ether->etherSocket, BIOCGBLEN, ðer->etherBufferLength)) { + trapUnrecoverableState("error getting buffer length: %s", strerror(errno)); + } + + return true; +} + +/** + * If the user specified a device name, set the MAC address in ether->macAddress + * + * <#Paragraphs Of Explanation#> + * + * @param [in] ether An allocated MetisGenericEther + * @param [in] devstr A C-String of the device name + * + * Example: + * @code + * <#example#> + * @endcode + */ +static void +_darwinEthernet_SetInterfaceAddress(MetisGenericEther *ether, const char *devstr) +{ + if (devstr) { + struct ifaddrs *ifaddr; + int failure = getifaddrs(&ifaddr); + assertFalse(failure, "Error getifaddrs: (%d) %s", errno, strerror(errno)); + + struct ifaddrs *next; + for (next = ifaddr; next != NULL; next = next->ifa_next) { + if (strcmp(next->ifa_name, devstr) == 0) { + if (next->ifa_addr->sa_family == AF_LINK) { + struct sockaddr_dl *addr_dl = (struct sockaddr_dl *) next->ifa_addr; + + // addr_dl->sdl_data[12] contains the interface name followed by the MAC address, so + // need to offset in to the array past the interface name. + PARCBuffer *addr = parcBuffer_Allocate(addr_dl->sdl_alen); + parcBuffer_PutArray(addr, addr_dl->sdl_alen, (uint8_t *) &addr_dl->sdl_data[ addr_dl->sdl_nlen]); + parcBuffer_Flip(addr); + ether->macAddress = addr; + + // break out of loop and freeifaddrs + break; + } + } + } + freeifaddrs(ifaddr); + } +} + +parcObject_ExtendPARCObject(MetisGenericEther, _metisGenericEther_Destroy, NULL, NULL, NULL, NULL, NULL, NULL); + +parcObject_ImplementAcquire(metisGenericEther, MetisGenericEther); + +parcObject_ImplementRelease(metisGenericEther, MetisGenericEther); + +// ========================= +// PUBLIC API +// ========================= + +MetisGenericEther * +metisGenericEther_Create(MetisForwarder *metis, const char *deviceName, uint16_t etherType) +{ + assertNotNull(metis, "Parameter metis must be non-null"); + + // The Darwin generic ether allows a NULL device name, it is used in the unit tests. + + MetisGenericEther *ether = NULL; + + if (metisEthernet_IsValidEthertype(etherType)) { + ether = parcObject_CreateInstance(MetisGenericEther); + ether->ethertype = etherType; + ether->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + ether->etherSocket = -1; // invalid valid + ether->workBuffer = parcEventBuffer_Create(); + ether->macAddress = NULL; + ether->mtu = metisSystem_InterfaceMtu(metis, deviceName); + + _darwinEthernet_SetInterfaceAddress(ether, deviceName); + + bool success = _darwinEthernet_SetupReceive(ether, deviceName); + + if (success) { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Info)) { + char *str = parcBuffer_ToHexString(ether->macAddress); + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__, + "GenericEther %p created on device %s (%s) for ethertype 0x%04x fd %d bufferLength %u mtu %u", + (void *) ether, deviceName, str, etherType, ether->etherSocket, ether->etherBufferLength, ether->mtu); + parcMemory_Deallocate((void **) &str); + } + } else { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "GenericEther failed to created on device %s for ethertype 0x%04x", + deviceName, etherType); + } + + // this will also null ether + metisGenericEther_Release(ðer); + } + + assertTrue(ether->etherBufferLength < 65536, "Buffer length way too big, expected less than 65536 got %u", ether->etherBufferLength); + } else { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "GenericEther failed to created on device %s for ethertype 0x%04x, invalid ethertype", + deviceName, etherType); + } + } + + return ether; +} + +int +metisGenericEther_GetDescriptor(const MetisGenericEther *ether) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + return ether->etherSocket; +} + + +/* + * Reading a BPF packet will include the BPF header. Frame may include the FCS. + */ +bool +metisGenericEther_ReadNextFrame(MetisGenericEther *ether, PARCEventBuffer *readbuffer) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + assertNotNull(readbuffer, "Parameter readbuffer must be non-null"); + + bool success = false; + + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Workbuffer length %zu", __func__, parcEventBuffer_GetLength(ether->workBuffer)); + } + + // Do we already have something in our work buffer? If not, try to read something. + if (parcEventBuffer_GetLength(ether->workBuffer) == 0) { + _darwinEthernet_ReadSocket(ether); + } + + success = _darwinEthernet_WorkBufferToReadBuffer(ether, readbuffer); + return success; +} + +bool +metisGenericEther_SendFrame(MetisGenericEther *ether, PARCEventBuffer *buffer) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + + size_t length = parcEventBuffer_GetLength(buffer); + int written = parcEventBuffer_WriteToFileDescriptor(buffer, ether->etherSocket, -1); + if (written == length) { + return true; + } + return false; +} + +PARCBuffer * +metisGenericEther_GetMacAddress(const MetisGenericEther *ether) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + return ether->macAddress; +} + +uint16_t +metisGenericEther_GetEtherType(const MetisGenericEther *ether) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + return ether->ethertype; +} + +unsigned +metisGenericEther_GetMTU(const MetisGenericEther *ether) +{ + return ether->mtu; +} + diff --git a/metis/ccnx/forwarder/metis/platforms/darwin/metis_System.c b/metis/ccnx/forwarder/metis/platforms/darwin/metis_System.c new file mode 100644 index 00000000..f4261e8a --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/darwin/metis_System.c @@ -0,0 +1,149 @@ +/* + * 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 <sys/types.h> +#include <ifaddrs.h> +#include <errno.h> +#include <string.h> + +#include <sys/socket.h> +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_types.h> + +#include <LongBow/runtime.h> + +#include <ccnx/api/control/cpi_InterfaceSet.h> + +#include <ccnx/forwarder/metis/core/metis_System.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +CPIInterfaceSet * +metisSystem_Interfaces(MetisForwarder *metis) +{ + CPIInterfaceSet *set = cpiInterfaceSet_Create(); + + // this is the dynamically allocated head of the list + struct ifaddrs *ifaddr; + int failure = getifaddrs(&ifaddr); + assertFalse(failure, "Error getifaddrs: (%d) %s", errno, strerror(errno)); + + struct ifaddrs *next; + for (next = ifaddr; next != NULL; next = next->ifa_next) { + if ((next->ifa_addr == NULL) || ((next->ifa_flags & IFF_UP) == 0)) { + continue; + } + + // This assumes the LINK address comes first so we can get the MTU + // when the interface is created. + + CPIInterface *iface = cpiInterfaceSet_GetByName(set, next->ifa_name); + if (iface == NULL) { + unsigned mtu = 0; + + if (next->ifa_data != NULL) { + struct if_data *ifdata = (struct if_data *) next->ifa_data; + mtu = ifdata->ifi_mtu; + } + + iface = cpiInterface_Create(next->ifa_name, + metisForwarder_GetNextConnectionId(metis), + next->ifa_flags & IFF_LOOPBACK, + next->ifa_flags & IFF_MULTICAST, + mtu); + + cpiInterfaceSet_Add(set, iface); + } + + int family = next->ifa_addr->sa_family; + switch (family) { + case AF_INET: { + CPIAddress *address = cpiAddress_CreateFromInet((struct sockaddr_in *) next->ifa_addr); + cpiInterface_AddAddress(iface, address); + break; + } + + case AF_INET6: { + CPIAddress *address = cpiAddress_CreateFromInet6((struct sockaddr_in6 *) next->ifa_addr); + cpiInterface_AddAddress(iface, address); + break; + } + + case AF_LINK: { + struct sockaddr_dl *addr_dl = (struct sockaddr_dl *) next->ifa_addr; + + // skip links with 0-length address + if (addr_dl->sdl_alen > 0) { + // addr_dl->sdl_data[12] contains the interface name followed by the MAC address, so + // need to offset in to the array past the interface name. + CPIAddress *address = cpiAddress_CreateFromLink((uint8_t *) &addr_dl->sdl_data[ addr_dl->sdl_nlen], addr_dl->sdl_alen); + cpiInterface_AddAddress(iface, address); + } + break; + } + } + } + + freeifaddrs(ifaddr); + + return set; +} + +CPIAddress * +metisSystem_GetMacAddressByName(MetisForwarder *metis, const char *interfaceName) +{ + CPIAddress *linkAddress = NULL; + + CPIInterfaceSet *interfaceSet = metisSystem_Interfaces(metis); + CPIInterface *interface = cpiInterfaceSet_GetByName(interfaceSet, interfaceName); + + if (interface) { + const CPIAddressList *addressList = cpiInterface_GetAddresses(interface); + + size_t length = cpiAddressList_Length(addressList); + for (size_t i = 0; i < length && !linkAddress; i++) { + const CPIAddress *a = cpiAddressList_GetItem(addressList, i); + if (cpiAddress_GetType(a) == cpiAddressType_LINK) { + linkAddress = cpiAddress_Copy(a); + } + } + } + + cpiInterfaceSet_Destroy(&interfaceSet); + + return linkAddress; +} + +unsigned +metisSystem_InterfaceMtu(MetisForwarder *metis, const char *interfaceName) +{ + unsigned mtu = 0; + + if (interfaceName) { + CPIInterfaceSet *interfaceSet = metisSystem_Interfaces(metis); + CPIInterface *interface = cpiInterfaceSet_GetByName(interfaceSet, interfaceName); + + if (interface) { + mtu = cpiInterface_GetMTU(interface); + } + + cpiInterfaceSet_Destroy(&interfaceSet); + } + return mtu; +} + diff --git a/metis/ccnx/forwarder/metis/platforms/darwin/test/.gitignore b/metis/ccnx/forwarder/metis/platforms/darwin/test/.gitignore new file mode 100644 index 00000000..44d3cf6d --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/darwin/test/.gitignore @@ -0,0 +1,2 @@ +test_metis_GenericEther +test_metis_System diff --git a/metis/ccnx/forwarder/metis/platforms/darwin/test/test_metis_GenericEther.c b/metis/ccnx/forwarder/metis/platforms/darwin/test/test_metis_GenericEther.c new file mode 100644 index 00000000..fd6bdaa4 --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/darwin/test/test_metis_GenericEther.c @@ -0,0 +1,606 @@ +/* + * 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_GenericEther.c" + +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <parc/algol/parc_Memory.h> +#include <parc/logging/parc_LogReporterTextStdout.h> +#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h> + +typedef struct __attribute__ ((__packed__)) metis_tlv_fixed_header { + uint8_t version; + uint8_t packetType; + uint16_t packetLength; + uint8_t interestHopLimit; + uint8_t returnCode; + uint8_t flags; + uint8_t headerLength; +} _MetisTlvFixedHeaderV1; + +static char * +getInterfaceName(void) +{ + // Lookup the MAC address of an interface that is up, then ask for it. Don't use loopback. + struct ifaddrs *ifaddr; + int failure = getifaddrs(&ifaddr); + assertFalse(failure, "Error getifaddrs: (%d) %s", errno, strerror(errno)); + + char *ifname = NULL; + + struct ifaddrs *next; + for (next = ifaddr; next != NULL && ifname == NULL; next = next->ifa_next) { + if ((next->ifa_addr == NULL) || ((next->ifa_flags & IFF_UP) == 0)) { + continue; + } + + if (next->ifa_flags & IFF_LOOPBACK) { + continue; + } + + if (next->ifa_addr->sa_family == AF_INET) { + ifname = strdup(next->ifa_name); + } + } + freeifaddrs(ifaddr); + return ifname; +} + +LONGBOW_TEST_RUNNER(darwin_Ethernet) +{ + // 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); +} + +static bool +_testPermissions(void) +{ + int fd = _darwinEthernet_OpenBfpDevice(); + if (fd > 0) { + close(fd); + return true; + } + return false; +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(darwin_Ethernet) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + + if (!_testPermissions()) { + fprintf(stderr, "Could not open a /dev/bpf device. Check permissions.\n"); + exit(77); + return LONGBOW_STATUS_SETUP_SKIPTESTS; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(darwin_Ethernet) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +/* + * Creates a BPF-encapsulated ethernet frame. + * + * @param [in] frameLength The capture length (ether header + ccnx packet) + * @param [out] totalLengthPtr The total read length, including BPF header and padding + * + * @return the parcMemory allocted byte array + */ +static uint8_t * +createBpfFrame(uint32_t frameLength, size_t *totalLengthPtr) +{ + uint16_t hdrlen = BPF_WORDALIGN(sizeof(struct bpf_hdr)); + uint32_t actualLength = BPF_WORDALIGN(frameLength + hdrlen); + uint32_t caplen = frameLength; + + uint8_t *buffer = parcMemory_AllocateAndClear(actualLength); + + struct bpf_hdr *hdr = (struct bpf_hdr *) buffer; + uint8_t *frame = buffer + hdrlen; + + memset(hdr, 0, hdrlen); + hdr->bh_hdrlen = hdrlen; + hdr->bh_caplen = caplen; + + for (int i = 0; i < caplen; i++) { + frame[i] = i * frameLength; + } + + // we need a valid FixedHeader length + struct ether_header *etherHeader = (struct ether_header *) frame; + _MetisTlvFixedHeaderV1 *fixedHeader = (_MetisTlvFixedHeaderV1 *)(frame + sizeof(struct ether_header)); + + etherHeader->ether_type = htons(0x0801); + fixedHeader->version = 1; + fixedHeader->packetLength = htons(caplen - sizeof(struct ether_header)); + + *totalLengthPtr = actualLength; + + return buffer; +} + +/* + * Create a BPF frame from a given Ethernet frame + * + * @param [out] totalLengthPtr The total frame length including the BPF header + */ +static uint8_t * +createBpfFrameFromEthernet(uint32_t length, const uint8_t etherframe[length], size_t *totalLengthPtr) +{ + uint16_t hdrlen = BPF_WORDALIGN(sizeof(struct bpf_hdr)); + uint32_t actualLength = BPF_WORDALIGN(length + hdrlen); + uint32_t caplen = length; + + uint8_t *buffer = parcMemory_AllocateAndClear(actualLength); + + struct bpf_hdr *hdr = (struct bpf_hdr *) buffer; + uint8_t *frame = buffer + hdrlen; + + memset(hdr, 0, hdrlen); + hdr->bh_hdrlen = hdrlen; + hdr->bh_caplen = caplen; + + memcpy(frame, etherframe, length); + + *totalLengthPtr = actualLength; + + return buffer; +} + +// ================================================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_Create); + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_Create_BadEtherType); + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_Release); + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_GetDescriptor); + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_ReadNextFrame); + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_ReadNextFrame_WithPadding); + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_SendFrame); +} + +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, metisGenericEther_Create) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + uint16_t ethertype = 0x0801; + MetisGenericEther *ether = metisGenericEther_Create(metis, NULL, ethertype); + metisForwarder_Destroy(&metis); + + assertNotNull(ether, "Got null ether"); + assertTrue(ether->ethertype == ethertype, "Wrong ethertype, got %x expected %x", ether->ethertype, ethertype); + assertNotNull(ether->workBuffer, "Work buffer is null"); + assertTrue(ether->etherSocket > 0, "Invalid etherSocket, got %d", ether->etherSocket); + + metisGenericEther_Release(ðer); +} + +LONGBOW_TEST_CASE(Global, metisGenericEther_Create_BadEtherType) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + uint16_t ethertype = 0; + MetisGenericEther *ether = metisGenericEther_Create(metis, NULL, ethertype); + metisForwarder_Destroy(&metis); + + assertNull(ether, "Should have gotten NULL for bad ethertype"); +} + +LONGBOW_TEST_CASE(Global, metisGenericEther_Release) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + uint16_t ethertype = 0x0801; + MetisGenericEther *ether = metisGenericEther_Create(metis, NULL, ethertype); + metisForwarder_Destroy(&metis); + + metisGenericEther_Release(ðer); + assertTrue(parcMemory_Outstanding() == 0, "Memory imbalance after release"); + assertNull(ether, "release did not null the pointer"); +} + +LONGBOW_TEST_CASE(Global, metisGenericEther_GetDescriptor) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + uint16_t ethertype = 0x0801; + MetisGenericEther *ether = metisGenericEther_Create(metis, NULL, ethertype); + metisForwarder_Destroy(&metis); + + int fd = metisGenericEther_GetDescriptor(ether); + assertTrue(fd == ether->etherSocket, "Returned wrong descriptor"); + metisGenericEther_Release(ðer); +} + +static void +assertFrameEquals(uint8_t *frame, PARCEventBuffer *test, size_t caplen) +{ + assertTrue(parcEventBuffer_GetLength(test) == caplen, "Wrong length, got %zu expected %zu", parcEventBuffer_GetLength(test), caplen); + + uint8_t *linear = parcEventBuffer_Pullup(test, -1); + assertTrue(memcmp(linear, frame, caplen) == 0, "Buffers do not compare"); +} + +LONGBOW_TEST_CASE(Global, metisGenericEther_ReadNextFrame) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + uint16_t ethertype = 0x0801; + MetisGenericEther *ether = metisGenericEther_Create(metis, NULL, ethertype); + metisForwarder_Destroy(&metis); + + size_t length_a; + uint8_t *buffer_a = createBpfFrame(129, &length_a); + struct bpf_hdr *hdr_a = (struct bpf_hdr *) buffer_a; + uint8_t *frame_a = buffer_a + hdr_a->bh_hdrlen; + + parcEventBuffer_Append(ether->workBuffer, buffer_a, length_a); + + size_t length_b; + uint8_t *buffer_b = createBpfFrame(777, &length_b); + struct bpf_hdr *hdr_b = (struct bpf_hdr *) buffer_b; + uint8_t *frame_b = buffer_b + hdr_b->bh_hdrlen; + + parcEventBuffer_Append(ether->workBuffer, buffer_b, length_b); + + bool success; + + PARCEventBuffer *output = parcEventBuffer_Create(); + + success = metisGenericEther_ReadNextFrame(ether, output); + assertTrue(success, "Failed to read frame A"); + assertFrameEquals(frame_a, output, hdr_a->bh_caplen); + + // clear the buffer before next packet + parcEventBuffer_Read(output, NULL, -1); + + success = metisGenericEther_ReadNextFrame(ether, output); + assertTrue(success, "Failed to read frame B"); + assertFrameEquals(frame_b, output, hdr_b->bh_caplen); + + + parcMemory_Deallocate((void **) &buffer_a); + parcMemory_Deallocate((void **) &buffer_b); + parcEventBuffer_Destroy(&output); + metisGenericEther_Release(ðer); +} + +LONGBOW_TEST_CASE(Global, metisGenericEther_ReadNextFrame_WithPadding) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + uint16_t ethertype = 0x0801; + MetisGenericEther *ether = metisGenericEther_Create(metis, NULL, ethertype); + metisForwarder_Destroy(&metis); + + size_t totalLength = 0; + uint8_t *bpf = createBpfFrameFromEthernet(sizeof(metisTestDataV1_InterestWithEthernetPadding), metisTestDataV1_InterestWithEthernetPadding, &totalLength); + parcEventBuffer_Append(ether->workBuffer, bpf, totalLength); + + PARCEventBuffer *output = parcEventBuffer_Create(); + + bool success; + + success = metisGenericEther_ReadNextFrame(ether, output); + assertTrue(success, "Failed to read frame A"); + assertFrameEquals(metisTestDataV1_InterestWithEthernetPaddingStripped, output, sizeof(metisTestDataV1_InterestWithEthernetPaddingStripped)); + + parcMemory_Deallocate((void **) &bpf); + parcEventBuffer_Destroy(&output); + metisGenericEther_Release(ðer); +} + + +LONGBOW_TEST_CASE(Global, metisGenericEther_SendFrame) +{ + char *interfaceName = getInterfaceName(); + uint16_t etherType = 0x0801; + + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + MetisGenericEther *ether = metisGenericEther_Create(metis, interfaceName, etherType); + metisForwarder_Destroy(&metis); + + PARCEventBuffer *parcEventBuffer = parcEventBuffer_Create(); + char dataBuffer[1024 * 1024]; + parcEventBuffer_Append(parcEventBuffer, dataBuffer, 16); + + bool result = metisGenericEther_SendFrame(ether, parcEventBuffer); + assertTrue(result, "metisGenericEther_Sendframe failed to send smallest packet"); + + parcEventBuffer_Append(parcEventBuffer, dataBuffer, 1024 * 1024); + + result = metisGenericEther_SendFrame(ether, parcEventBuffer); + assertFalse(result, "metisGenericEther_Sendframe should have failed to send packet larger than our MTU"); + + parcEventBuffer_Destroy(&parcEventBuffer); + + metisGenericEther_Release(ðer); + free(interfaceName); +} + +// ================================================================== + +typedef struct test_data { + MetisGenericEther *ether; +} TestData; + +static void +commonSetup(const LongBowTestCase *testCase, const char *device, uint16_t ethertype) +{ + TestData *data = parcMemory_AllocateAndClear(sizeof(TestData)); + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + data->ether = metisGenericEther_Create(metis, device, ethertype); + metisForwarder_Destroy(&metis); + longBowTestCase_SetClipBoardData(testCase, data); +} + +static void +commonTeardown(const LongBowTestCase *testCase) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + metisGenericEther_Release(&data->ether); + parcMemory_Deallocate((void **) &data); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _darwinEthernet_SetupReceive); + LONGBOW_RUN_TEST_CASE(Local, _darwinEthernet_SetFilter); + LONGBOW_RUN_TEST_CASE(Local, _darwinEthernet_SetDeviceOptions); + LONGBOW_RUN_TEST_CASE(Local, _darwinEthernet_OpenBfpDevice); + LONGBOW_RUN_TEST_CASE(Local, _darwinEthernet_ReadSocket_True); + LONGBOW_RUN_TEST_CASE(Local, _darwinEthernet_ReadSocket_False); + LONGBOW_RUN_TEST_CASE(Local, _darwinEthernet_ReadWorkBuffer); + LONGBOW_RUN_TEST_CASE(Local, _darwinEthernet_ReadWorkBuffer_Short); + + LONGBOW_RUN_TEST_CASE(Local, _darwinEthernet_SetInterfaceAddress); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + commonSetup(testCase, NULL, 0x0801); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + commonTeardown(testCase); + 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, _darwinEthernet_SetupReceive) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _darwinEthernet_SetFilter) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _darwinEthernet_SetDeviceOptions) +{ + int fd = _darwinEthernet_OpenBfpDevice(); + bool success = _darwinEthernet_SetDeviceOptions(fd, NULL); + assertTrue(success, "Error setting device options"); + close(fd); +} + +LONGBOW_TEST_CASE(Local, _darwinEthernet_OpenBfpDevice) +{ + int fd = _darwinEthernet_OpenBfpDevice(); + assertTrue(fd > -1, "Error opening device"); + close(fd); +} + +LONGBOW_TEST_CASE(Local, _darwinEthernet_ReadSocket_True) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // Create a socket pair and pump some test data in + int fd[2]; + int failure = socketpair(PF_LOCAL, SOCK_DGRAM, 0, fd); + assertFalse(failure, "Error on socketpair"); + + // Set non-blocking flag + int flags = fcntl(fd[1], F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)\n", errno); + failure = fcntl(fd[1], F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)\n", errno); + + // swap out the file descriptor from the BPF to our sockpair + close(data->ether->etherSocket); + data->ether->etherSocket = fd[1]; + + size_t totalLength; + uint8_t *buffer = createBpfFrame(129, &totalLength); + + ssize_t bytesWritten = write(fd[0], buffer, totalLength); + assertTrue(bytesWritten == totalLength, "Error on write, got %zd bytes expected %zu bytes", bytesWritten, totalLength); + + bool success = _darwinEthernet_ReadSocket(data->ether); + assertTrue(success, "Did not read buffer even though we put data in socket"); + + // The buffer should contain the whole frame with the BPF header + assertTrue(parcEventBuffer_GetLength(data->ether->workBuffer) == totalLength, "Wrong lenght, got %zu expected %zu", parcEventBuffer_GetLength(data->ether->workBuffer), totalLength); + + uint8_t *test = parcEventBuffer_Pullup(data->ether->workBuffer, -1); + assertTrue(memcmp(test, buffer, totalLength) == 0, "Buffers do not match"); + + parcMemory_Deallocate((void **) &buffer); + close(fd[0]); +} + +LONGBOW_TEST_CASE(Local, _darwinEthernet_ReadSocket_False) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // Create a socket pair and pump some test data in + int fd[2]; + int failure = socketpair(PF_LOCAL, SOCK_DGRAM, 0, fd); + assertFalse(failure, "Error on socketpair"); + + // Set non-blocking flag + int flags = fcntl(fd[1], F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)\n", errno); + failure = fcntl(fd[1], F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)\n", errno); + + // swap out the file descriptor from the BPF to our sockpair + close(data->ether->etherSocket); + data->ether->etherSocket = fd[1]; + + bool success = _darwinEthernet_ReadSocket(data->ether); + assertFalse(success, "Should have failed to read when no data present"); + + close(fd[0]); +} + +LONGBOW_TEST_CASE(Local, _darwinEthernet_ReadWorkBuffer) +{ + // Put a BFP packet in to the work buffer and make sure it is read correctly + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + size_t totalLength; + uint8_t *buffer = createBpfFrame(129, &totalLength); + struct bpf_hdr *hdr = (struct bpf_hdr *) buffer; + uint8_t *frame = buffer + hdr->bh_hdrlen; + + parcEventBuffer_Append(data->ether->workBuffer, buffer, totalLength); + + // now read the work buffer and make sure we get the right frame + PARCEventBuffer *output = parcEventBuffer_Create(); + _ReadWorkBufferResult result = _darwinEthernet_ReadWorkBuffer(data->ether, output); + assertTrue(result == ReadWorkBufferResult_Ok, "Failed on ReadWorkBuffer"); + + uint8_t *test = parcEventBuffer_Pullup(output, -1); + assertTrue(memcmp(test, frame, hdr->bh_caplen) == 0, "Frames do not match"); + + parcMemory_Deallocate((void **) &buffer); + parcEventBuffer_Destroy(&output); +} + +LONGBOW_TEST_CASE(Local, _darwinEthernet_ReadWorkBuffer_Short) +{ + // put an incomplete frame in to the work buffer + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + size_t totalLength; + uint8_t *buffer = createBpfFrame(129, &totalLength); + + parcEventBuffer_Append(data->ether->workBuffer, buffer, 5); + + // now read the work buffer and make sure we get the right frame + PARCEventBuffer *output = parcEventBuffer_Create(); + _ReadWorkBufferResult result = _darwinEthernet_ReadWorkBuffer(data->ether, output); + assertTrue(result == ReadWorkBufferResult_Empty, "Failed on ReadWorkBuffer"); + + parcMemory_Deallocate((void **) &buffer); + parcEventBuffer_Destroy(&output); +} + +LONGBOW_TEST_CASE(Local, _darwinEthernet_SetInterfaceAddress) +{ + PARCBuffer *addr = NULL; + char ifname[1024]; + + // Lookup the MAC address of an interface that is up, then ask for it. Don't use loopback. + struct ifaddrs *ifaddr; + int failure = getifaddrs(&ifaddr); + assertFalse(failure, "Error getifaddrs: (%d) %s", errno, strerror(errno)); + + struct ifaddrs *next; + for (next = ifaddr; next != NULL; next = next->ifa_next) { + if ((next->ifa_addr == NULL) || ((next->ifa_flags & IFF_UP) == 0)) { + continue; + } + + if (next->ifa_flags & IFF_LOOPBACK) { + continue; + } + + if (next->ifa_addr->sa_family == AF_LINK) { + strcpy(ifname, next->ifa_name); + + struct sockaddr_dl *addr_dl = (struct sockaddr_dl *) next->ifa_addr; + + // addr_dl->sdl_data[12] contains the interface name followed by the MAC address, so + // need to offset in to the array past the interface name. + addr = parcBuffer_Allocate(addr_dl->sdl_alen); + parcBuffer_PutArray(addr, addr_dl->sdl_alen, (uint8_t *) &addr_dl->sdl_data[ addr_dl->sdl_nlen]); + parcBuffer_Flip(addr); + break; + } + } + freeifaddrs(ifaddr); + + + // If we could find an address, try to get it + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _darwinEthernet_SetInterfaceAddress(data->ether, ifname); + + assertTrue(parcBuffer_Equals(addr, data->ether->macAddress), "Addresses do not match") + { + parcBuffer_Display(addr, 0); + parcBuffer_Display(data->ether->macAddress, 0); + } + + parcBuffer_Release(&addr); +} + + +// ================================================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(darwin_Ethernet); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/platforms/darwin/test/test_metis_System.c b/metis/ccnx/forwarder/metis/platforms/darwin/test/test_metis_System.c new file mode 100644 index 00000000..126a5bf7 --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/darwin/test/test_metis_System.c @@ -0,0 +1,149 @@ +/* + * 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. + +#pragma weak metisSystem_Interfaces + +#include "../metis_System.c" + +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <parc/algol/parc_Memory.h> + + +// Include the generic tests of MetisSystem +#include "../../test/testrig_metis_System.c" + +LONGBOW_TEST_RUNNER(darwin_Interface) +{ + LONGBOW_RUN_TEST_FIXTURE(Global); + + // these are defined in testrig_metis_System.c + LONGBOW_RUN_TEST_FIXTURE(PublicApi); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(darwin_Interface) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(darwin_Interface) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ================================================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisSystem_Interfaces); + LONGBOW_RUN_TEST_CASE(Global, metisSystem_InterfaceMTU); +} + +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, metisSystem_Interfaces) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + assertNotNull(set, "metisSystem_Interfaces return null set"); + + // XXX we need some sort of validation test. e.g. open a socket, then ioctl to + // XXX get the interface name, then verify its in the list. + + size_t length = cpiInterfaceSet_Length(set); + assertTrue(length > 0, "metisSystem_Interfaces returned no interfaces"); + + for (size_t i = 0; i < length; i++) { + CPIInterface *iface = cpiInterfaceSet_GetByOrdinalIndex(set, i); + printf("Interface Index %u\n", cpiInterface_GetInterfaceIndex(iface)); + const CPIAddressList *list = cpiInterface_GetAddresses(iface); + PARCJSONArray *jsonArray = cpiAddressList_ToJson(list); + char *str = parcJSONArray_ToString(jsonArray); + printf("%s\n", str); + parcMemory_Deallocate((void **) &str); + parcJSONArray_Release(&jsonArray); + } + + cpiInterfaceSet_Destroy(&set); + metisForwarder_Destroy(&metis); +} + +// returns a strdup() of the interface name, use free(3) +static char * +_pickInterfaceName(MetisForwarder *metis) +{ + char *ifname = NULL; + + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + size_t length = cpiInterfaceSet_Length(set); + assertTrue(length > 0, "metisSystem_Interfaces returned no interfaces"); + + for (size_t i = 0; i < length; i++) { + CPIInterface *iface = cpiInterfaceSet_GetByOrdinalIndex(set, i); + const CPIAddressList *addressList = cpiInterface_GetAddresses(iface); + + size_t length = cpiAddressList_Length(addressList); + for (size_t i = 0; i < length && !ifname; i++) { + const CPIAddress *a = cpiAddressList_GetItem(addressList, i); + if (cpiAddress_GetType(a) == cpiAddressType_LINK) { + ifname = strdup(cpiInterface_GetName(iface)); + } + } + } + + cpiInterfaceSet_Destroy(&set); + return ifname; +} + +LONGBOW_TEST_CASE(Global, metisSystem_InterfaceMTU) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + + char *deviceName = _pickInterfaceName(metis); + unsigned mtu = metisSystem_InterfaceMtu(metis, deviceName); + + assertTrue(mtu > 0, "Did not get mtu for interface %s", deviceName); + free(deviceName); + metisForwarder_Destroy(&metis); +} + +// ================================================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(darwin_Interface); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/platforms/linux/metis_GenericEther.c b/metis/ccnx/forwarder/metis/platforms/linux/metis_GenericEther.c new file mode 100644 index 00000000..82542983 --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/linux/metis_GenericEther.c @@ -0,0 +1,374 @@ +/* + * 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. + */ + +/** + * Implements the platform-specific code for working with an Ethernet interface. + * + * The Linux Ethernet device uses an AF_PACKET socket SOCK_RAW. + * + */ + +#include <config.h> +#include <stdio.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> + +#include <arpa/inet.h> +#include <linux/if_packet.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <net/if.h> +#include <netinet/ether.h> +#include <net/ethernet.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> +#include <ccnx/forwarder/metis/io/metis_GenericEther.h> +#include <ccnx/forwarder/metis/core/metis_System.h> +#include <parc/algol/parc_Object.h> +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> +#include <ccnx/forwarder/metis/io/metis_Ethernet.h> + +struct metis_generic_ether { + uint16_t ethertype; + int etherSocket; + + int linuxInterfaceIndex; + + PARCBuffer *macAddress; + MetisLogger *logger; + + // MTU set on interface when we are created + unsigned mtu; +}; + +static bool _linuxEthernet_SetupSocket(MetisGenericEther *ether, const char *devstr); + +static void +_metisGenericEther_Destroy(MetisGenericEther **etherPtr) +{ + MetisGenericEther *ether = *etherPtr; + + if (ether->etherSocket > 0) { + close(ether->etherSocket); + } + + if (ether->macAddress) { + parcBuffer_Release(ðer->macAddress); + } + + metisLogger_Release(ðer->logger); +} + +parcObject_ExtendPARCObject(MetisGenericEther, _metisGenericEther_Destroy, NULL, NULL, NULL, NULL, NULL, NULL); + +parcObject_ImplementAcquire(metisGenericEther, MetisGenericEther); + +parcObject_ImplementRelease(metisGenericEther, MetisGenericEther); + +// ========================= +// PUBLIC API +// ========================= + +MetisGenericEther * +metisGenericEther_Create(MetisForwarder *metis, const char *deviceName, uint16_t etherType) +{ + assertNotNull(metis, "Parameter metis must be non-null"); + assertNotNull(deviceName, "Parameter deviceName must be non-null"); + + MetisGenericEther *ether = NULL; + + if (metisEthernet_IsValidEthertype(etherType)) { + ether = parcObject_CreateInstance(MetisGenericEther); + ether->ethertype = etherType; + ether->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + ether->mtu = metisSystem_InterfaceMtu(metis, deviceName); + + ether->etherSocket = -1; // invalid valid + ether->macAddress = NULL; + + bool success = _linuxEthernet_SetupSocket(ether, deviceName); + + if (success) { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + char *str = parcBuffer_ToHexString(ether->macAddress); + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "GenericEther %p created on device %s (%s) for ethertype 0x%04x fd %d ifindex %u mtu %u", + (void *) ether, deviceName, str, etherType, ether->etherSocket, ether->linuxInterfaceIndex, + ether->mtu); + parcMemory_Deallocate((void **) &str); + } + } else { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "GenericEther failed to created on device %s for ethertype 0x%04x", + deviceName, etherType); + } + + // this will also null ether + metisGenericEther_Release(ðer); + } + } else { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "GenericEther failed to created on device %s for ethertype 0x%04x, invalid ethertype", + deviceName, etherType); + } + } + + return ether; +} + +int +metisGenericEther_GetDescriptor(const MetisGenericEther *ether) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + return ether->etherSocket; +} + +/** + * Based on the fixed header, trim the buffer + * + * Some platforms do not strip the ethernet CRC from the raw packet. Trim the buffer to + * the right sized based on the fixed header. + * + * @param [in] ether An allocated ethernet interface + * @param [in] readBuffer The buffer to trim + * + * Example: + * @code + * <#example#> + * @endcode + */ +static void +_linuxEthernet_TrimBuffer(MetisGenericEther *ether, PARCEventBuffer *readBuffer) +{ + // read the fixed header + uint8_t *etherHeader = parcEventBuffer_Pullup(readBuffer, ETHER_HDR_LEN + metisTlv_FixedHeaderLength()); + + if (etherHeader) { + uint8_t *fixedHeader = etherHeader + ETHER_HDR_LEN; + size_t totalLength = metisTlv_TotalPacketLength(fixedHeader) + ETHER_HDR_LEN; + + if (parcEventBuffer_GetLength(readBuffer) > totalLength) { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "%s buffer length %zu, actual length %zu (ether header + ccnx packet), trimming %zu bytes", + __func__, parcEventBuffer_GetLength(readBuffer), totalLength, + parcEventBuffer_GetLength(readBuffer) - totalLength); + } + + PARCEventBuffer *temp = parcEventBuffer_Create(); + + // there's no way to drain from the end, so we move to a tempororary buffer, + // drain the unwanted part, then move back. + + int movedBytes = parcEventBuffer_ReadIntoBuffer(readBuffer, temp, totalLength); + assertTrue(movedBytes == totalLength, "Failed to move all the bytes, got %d expected %zu", movedBytes, totalLength); + + // flush all the bytes out of the read buffer + parcEventBuffer_Read(readBuffer, NULL, -1); + + // now put back what we want + int failure = parcEventBuffer_AppendBuffer(temp, readBuffer); + assertFalse(failure, "parcEventBuffer_AppendBuffer failed"); + + parcEventBuffer_Destroy(&temp); + } + } +} + +/* + * Reading a raw socket, on some systems, may include the FCS trailer + */ +bool +metisGenericEther_ReadNextFrame(MetisGenericEther *ether, PARCEventBuffer *readBuffer) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + assertNotNull(readBuffer, "Parameter readBuffer must be non-null"); + + bool success = false; + + int evread_length = parcEventBuffer_ReadFromFileDescriptor(readBuffer, ether->etherSocket, (int) -1); + + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "%s read length %d", __func__, evread_length); + } + + if (evread_length > 0) { + _linuxEthernet_TrimBuffer(ether, readBuffer); + success = true; + } + + return success; +} + +bool +metisGenericEther_SendFrame(MetisGenericEther *ether, PARCEventBuffer *buffer) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + + // cannot use parcEventBuffer_WriteToFileDescriptor because we need to write the length in one go, not use the + // iovec approach in parcEventBuffer_WriteToFileDescriptor. It can cause problems on some platforms. + + uint8_t *linear = parcEventBuffer_Pullup(buffer, -1); + size_t length = parcEventBuffer_GetLength(buffer); + + ssize_t written = write(ether->etherSocket, linear, length); + if (written == length) { + return true; + } + return false; +} + +PARCBuffer * +metisGenericEther_GetMacAddress(const MetisGenericEther *ether) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + return ether->macAddress; +} + +uint16_t +metisGenericEther_GetEtherType(const MetisGenericEther *ether) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + return ether->ethertype; +} + +unsigned +metisGenericEther_GetMTU(const MetisGenericEther *ether) +{ + return ether->mtu; +} + +// ================== +// PRIVATE API + +static bool +_linuxEthernet_SetInterfaceIndex(MetisGenericEther *ether, const char *devstr) +{ + // get the interface index of the desired device + bool success = false; + + struct ifreq if_idx; + memset(&if_idx, 0, sizeof(if_idx)); + strncpy(if_idx.ifr_name, devstr, IFNAMSIZ - 1); + if (!ioctl(ether->etherSocket, SIOCGIFINDEX, &if_idx)) { + ether->linuxInterfaceIndex = if_idx.ifr_ifindex; + success = true; + } else { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "ioctl SIOCGIFINDEX error: (%d) %s", errno, strerror(errno)); + } + } + + return success; +} + +static bool +_linuxEthernet_SetInterfaceAddress(MetisGenericEther *ether, const char *devstr) +{ + bool success = false; + + assertNull(ether->macAddress, "Should only be called once with null macAddress"); + + // get the MAC address of the device + struct ifreq if_mac; + memset(&if_mac, 0, sizeof(if_mac)); + strncpy(if_mac.ifr_name, devstr, IFNAMSIZ - 1); + if (!ioctl(ether->etherSocket, SIOCGIFHWADDR, &if_mac)) { + if (if_mac.ifr_hwaddr.sa_family == ARPHRD_ETHER) { + ether->macAddress = parcBuffer_Allocate(ETHER_ADDR_LEN); + parcBuffer_PutArray(ether->macAddress, ETHER_ADDR_LEN, (uint8_t *) if_mac.ifr_hwaddr.sa_data); + parcBuffer_Flip(ether->macAddress); + success = true; + } else { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Device %s does not have an Ethernet hardware address", devstr); + } + } + } + return success; +} + +static bool +_linuxEthernet_Bind(MetisGenericEther *ether) +{ + bool success = false; + + // we need to bind to the ethertype to receive packets + struct sockaddr_ll my_addr; + my_addr.sll_family = PF_PACKET; + my_addr.sll_protocol = htons(ether->ethertype); + my_addr.sll_ifindex = ether->linuxInterfaceIndex; + + if (!bind(ether->etherSocket, (struct sockaddr *) &my_addr, sizeof(struct sockaddr_ll))) { + success = true; + } else { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "bind error: (%d) %s", errno, strerror(errno)); + } + } + return success; +} + +static bool +_linuxEthernet_SetNonBlocking(MetisGenericEther *ether) +{ + bool success = false; + uint32_t on = 1; + if (!ioctl(ether->etherSocket, FIONBIO, &on)) { + success = true; + } + return success; +} + +static bool +_linuxEthernet_SetupSocket(MetisGenericEther *ether, const char *devstr) +{ + bool success = false; + ether->etherSocket = socket(AF_PACKET, SOCK_RAW, htons(ether->ethertype)); + if (ether->etherSocket > 0) { + if (_linuxEthernet_SetInterfaceIndex(ether, devstr)) { + if (_linuxEthernet_SetInterfaceAddress(ether, devstr)) { + if (_linuxEthernet_Bind(ether)) { + // set non-blocking + if (_linuxEthernet_SetNonBlocking(ether)) { + success = true; + } + } + } + } + } + + if (!success) { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "setup socket error: (%d) %s", errno, strerror(errno)); + } + } + + return success; +} + + + diff --git a/metis/ccnx/forwarder/metis/platforms/linux/metis_System.c b/metis/ccnx/forwarder/metis/platforms/linux/metis_System.c new file mode 100644 index 00000000..828566d8 --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/linux/metis_System.c @@ -0,0 +1,190 @@ +/* + * 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 <unistd.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <ifaddrs.h> +#include <errno.h> +#include <string.h> + +//#define __USE_MISC +#include <net/if.h> + +// to get the list of arp types +#include <net/if_arp.h> + +// for the mac address +#include <netpacket/packet.h> + +#include <ccnx/api/control/cpi_InterfaceSet.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +#include <LongBow/runtime.h> + +/** + * Returns the MTU for a named interface + * + * On linux, we get the MTU by opening a socket and reading SIOCGIFMTU + * + * @param [in] ifname Interface name (e.g. "eth0") + * + * @retval number The MTU in bytes + * + * Example: + * @code + * <#example#> + * @endcode + */ +static int +getMtu(const char *ifname) +{ + struct ifreq ifr; + int fd; + + fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); + + strcpy(ifr.ifr_name, ifname); + ioctl(fd, SIOCGIFMTU, &ifr); + + close(fd); + return ifr.ifr_mtu; +} + +CPIInterfaceSet * +metisSystem_Interfaces(MetisForwarder *metis) +{ + CPIInterfaceSet *set = cpiInterfaceSet_Create(); + + MetisLogger *logger = metisForwarder_GetLogger(metis); + + // this is the dynamically allocated head of the list + struct ifaddrs *ifaddr; + int failure = getifaddrs(&ifaddr); + assertFalse(failure, "Error getifaddrs: (%d) %s", errno, strerror(errno)); + + struct ifaddrs *next; + for (next = ifaddr; next != NULL; next = next->ifa_next) { + if ((next->ifa_addr == NULL) || ((next->ifa_flags & IFF_UP) == 0)) { + continue; + } + + CPIInterface *iface = cpiInterfaceSet_GetByName(set, next->ifa_name); + if (iface == NULL) { + unsigned mtu = (unsigned) getMtu(next->ifa_name); + + iface = cpiInterface_Create(next->ifa_name, + metisForwarder_GetNextConnectionId(metis), + next->ifa_flags & IFF_LOOPBACK, + next->ifa_flags & IFF_MULTICAST, + mtu); + + cpiInterfaceSet_Add(set, iface); + } + + int family = next->ifa_addr->sa_family; + switch (family) { + case AF_INET: { + CPIAddress *address = cpiAddress_CreateFromInet((struct sockaddr_in *) next->ifa_addr); + cpiInterface_AddAddress(iface, address); + break; + } + + case AF_INET6: { + CPIAddress *address = cpiAddress_CreateFromInet6((struct sockaddr_in6 *) next->ifa_addr); + cpiInterface_AddAddress(iface, address); + break; + } + + case AF_PACKET: { + struct sockaddr_ll *addr_ll = (struct sockaddr_ll *) next->ifa_addr; + + if (metisLogger_IsLoggable(logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "sockaddr_ll family %d proto %d ifindex %d hatype %d pkttype %d halen %d", + addr_ll->sll_family, + addr_ll->sll_protocol, + addr_ll->sll_ifindex, + addr_ll->sll_hatype, + addr_ll->sll_pkttype, + addr_ll->sll_halen); + } + + switch (addr_ll->sll_hatype) { + // list of the ARP hatypes we can extract a MAC address from + case ARPHRD_ETHER: + // fallthrough + case ARPHRD_IEEE802: { + CPIAddress *address = cpiAddress_CreateFromLink((uint8_t *) addr_ll->sll_addr, addr_ll->sll_halen); + cpiInterface_AddAddress(iface, address); + break; + } + default: + break; + } + + break; + } + } + } + + freeifaddrs(ifaddr); + return set; +} + +CPIAddress * +metisSystem_GetMacAddressByName(MetisForwarder *metis, const char *interfaceName) +{ + CPIAddress *linkAddress = NULL; + + CPIInterfaceSet *interfaceSet = metisSystem_Interfaces(metis); + CPIInterface *interface = cpiInterfaceSet_GetByName(interfaceSet, interfaceName); + + if (interface) { + const CPIAddressList *addressList = cpiInterface_GetAddresses(interface); + + size_t length = cpiAddressList_Length(addressList); + for (size_t i = 0; i < length && !linkAddress; i++) { + const CPIAddress *a = cpiAddressList_GetItem(addressList, i); + if (cpiAddress_GetType(a) == cpiAddressType_LINK) { + linkAddress = cpiAddress_Copy(a); + } + } + } + + cpiInterfaceSet_Destroy(&interfaceSet); + + return linkAddress; +} + +unsigned +metisSystem_InterfaceMtu(MetisForwarder *metis, const char *interfaceName) +{ + unsigned mtu = 0; + + CPIInterfaceSet *interfaceSet = metisSystem_Interfaces(metis); + CPIInterface *interface = cpiInterfaceSet_GetByName(interfaceSet, interfaceName); + + if (interface) { + mtu = cpiInterface_GetMTU(interface); + } + + cpiInterfaceSet_Destroy(&interfaceSet); + + return mtu; +} diff --git a/metis/ccnx/forwarder/metis/platforms/linux/test/.gitignore b/metis/ccnx/forwarder/metis/platforms/linux/test/.gitignore new file mode 100644 index 00000000..44d3cf6d --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/linux/test/.gitignore @@ -0,0 +1,2 @@ +test_metis_GenericEther +test_metis_System diff --git a/metis/ccnx/forwarder/metis/platforms/linux/test/test_metis_GenericEther.c b/metis/ccnx/forwarder/metis/platforms/linux/test/test_metis_GenericEther.c new file mode 100644 index 00000000..aedc5363 --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/linux/test/test_metis_GenericEther.c @@ -0,0 +1,566 @@ +/* + * 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_GenericEther.c" + +#include <ifaddrs.h> +#include <poll.h> + +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <parc/algol/parc_Memory.h> +#include <parc/logging/parc_LogReporterTextStdout.h> + +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h> +#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h> + +/* + * This is no longer gobally exported, so reproduce here for tests + */ +typedef struct __attribute__ ((__packed__)) metis_tlv_fixed_header { + uint8_t version; + uint8_t packetType; + uint16_t payloadLength; + uint16_t reserved; + uint16_t headerLength; +} _MetisTlvFixedHeaderV0; + +static char * +getInterfaceName(void) +{ + // Lookup the MAC address of an interface that is up, then ask for it. Don't use loopback. + struct ifaddrs *ifaddr; + int failure = getifaddrs(&ifaddr); + assertFalse(failure, "Error getifaddrs: (%d) %s", errno, strerror(errno)); + + char *ifname = NULL; + + struct ifaddrs *next; + for (next = ifaddr; next != NULL && ifname == NULL; next = next->ifa_next) { + if ((next->ifa_addr == NULL) || ((next->ifa_flags & IFF_UP) == 0)) { + continue; + } + + if (next->ifa_flags & IFF_LOOPBACK) { + continue; + } + + if (next->ifa_addr->sa_family == AF_PACKET) { + ifname = strdup(next->ifa_name); + } + } + freeifaddrs(ifaddr); + return ifname; +} + +static char *interfaceName = NULL; + +// ===================================== + + +LONGBOW_TEST_RUNNER(linux_Ethernet) +{ + // 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); +} + +/* + * If we cannot open a raw socket, we cannot run any of these tests. + */ +static bool +_checkForRawAbility(void) +{ + bool success = false; + uint16_t ethertype = 0x0801; + int fd = socket(AF_PACKET, SOCK_RAW, htons(ethertype)); + if (fd > 0) { + success = true; + close(fd); + } + + return success; +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(linux_Ethernet) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + if (_checkForRawAbility()) { + interfaceName = getInterfaceName(); + return LONGBOW_STATUS_SUCCEEDED; + } + + fprintf(stderr, "%s failed to open an AF_PACKET SOCK_RAW socket, cannot execute tests\n", __func__); + // exit here with autoconf SKIP code until LongBow exits that way for skipped tests + exit(77); + return LONGBOW_STATUS_SETUP_SKIPTESTS; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(linux_Ethernet) +{ + free(interfaceName); + + return LONGBOW_STATUS_SUCCEEDED; +} + +static uint8_t * +createEtherFrame(uint32_t frameLength) +{ + uint8_t *frame = parcMemory_AllocateAndClear(frameLength); + + for (int i = 0; i < frameLength; i++) { + frame[i] = i * frameLength; + } + + // Create a proper header + size_t messageLength = frameLength - ETHER_HDR_LEN; + size_t payloadLength = messageLength - metisTlv_FixedHeaderLength(); + + _MetisTlvFixedHeaderV0 *fixedHeader = (_MetisTlvFixedHeaderV0 *) (frame + ETHER_HDR_LEN); + fixedHeader->version = 0; + fixedHeader->packetType = 1; + fixedHeader->payloadLength = payloadLength; + fixedHeader->headerLength = 0; + + return frame; +} + +static PARCBuffer * +createInterestFrame(size_t extrabytes) +{ + size_t totalLength = sizeof(metisTestDataV0_EncodedInterest) + extrabytes + ETHER_HDR_LEN; + uint8_t *frame = createEtherFrame(totalLength); + + memcpy(frame + ETHER_HDR_LEN, metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest)); + + PARCBuffer *buffer = parcBuffer_Allocate(totalLength); + parcBuffer_PutArray(buffer, totalLength, frame); + parcBuffer_Flip(buffer); + parcMemory_Deallocate((void **) &frame); + + return buffer; +} + +// ================================================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_Create); + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_Create_BadEtherType); + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_Release); + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_GetDescriptor); + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_ReadNextFrame); + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_ReadNextFrame_WithPadding); + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_SendFrame); +} + +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, metisGenericEther_Create) +{ + uint16_t ethertype = 0x0801; + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + MetisGenericEther *ether = metisGenericEther_Create(metis, interfaceName, ethertype); + metisForwarder_Destroy(&metis); + + assertNotNull(ether, "Got null ether"); + assertTrue(ether->ethertype == ethertype, "Wrong ethertype, got %x expected %x", ether->ethertype, ethertype); + assertTrue(ether->etherSocket > 0, "Invalid etherSocket, got %d", ether->etherSocket); + + metisGenericEther_Release(ðer); +} + +LONGBOW_TEST_CASE(Global, metisGenericEther_Create_BadEtherType) +{ + uint16_t ethertype = 0x0000; + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + MetisGenericEther *ether = metisGenericEther_Create(metis, interfaceName, ethertype); + metisForwarder_Destroy(&metis); + + assertNull(ether, "Should have gotten NULL for bad ethertype"); +} + + +LONGBOW_TEST_CASE(Global, metisGenericEther_Release) +{ + uint16_t ethertype = 0x0801; + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + MetisGenericEther *ether = metisGenericEther_Create(metis, interfaceName, ethertype); + metisForwarder_Destroy(&metis); + metisGenericEther_Release(ðer); + assertTrue(parcMemory_Outstanding() == 0, "Memory imbalance after release"); + assertNull(ether, "release did not null the pointer"); +} + +LONGBOW_TEST_CASE(Global, metisGenericEther_GetDescriptor) +{ + uint16_t ethertype = 0x0801; + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + MetisGenericEther *ether = metisGenericEther_Create(metis, interfaceName, ethertype); + metisForwarder_Destroy(&metis); + int fd = metisGenericEther_GetDescriptor(ether); + assertTrue(fd == ether->etherSocket, "Returned wrong descriptor"); + metisGenericEther_Release(ðer); +} + +static void +assertFrameEquals(uint8_t *frame, PARCEventBuffer *test, size_t caplen) +{ + assertTrue(parcEventBuffer_GetLength(test) == caplen, "Wrong length, got %zu expected %zu", parcEventBuffer_GetLength(test), caplen); + + uint8_t *linear = parcEventBuffer_Pullup(test, -1); + assertTrue(memcmp(linear, frame, caplen) == 0, "Buffers do not compare"); +} + +LONGBOW_TEST_CASE(Global, metisGenericEther_ReadNextFrame) +{ + uint16_t ethertype = 0x0801; + + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + MetisGenericEther *ether = metisGenericEther_Create(metis, interfaceName, ethertype); + metisForwarder_Destroy(&metis); + + // swap out the PF_PACKET socket for a socket pair + close(ether->etherSocket); + + int fd[2]; + socketpair(PF_LOCAL, SOCK_DGRAM, 0, fd); + + int localSocket = fd[0]; + ether->etherSocket = fd[1]; + _linuxEthernet_SetNonBlocking(ether); + + size_t length_a = 129; + uint8_t *frame_a = createEtherFrame(length_a); + + size_t length_b = 777; + uint8_t *frame_b = createEtherFrame(length_b); + + ssize_t nwritten = write(localSocket, frame_a, length_a); + assertTrue(nwritten == length_a, "Error on write, expected %zu got %zd", length_a, nwritten); + + nwritten = write(localSocket, frame_b, length_b); + assertTrue(nwritten == length_b, "Error on write, expected %zu got %zd", length_b, nwritten); + + + // wait for it to become available + struct pollfd pfd; + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = ether->etherSocket; + pfd.events = POLLIN | POLLERR; + + poll(&pfd, 1, 10); + + // Something is ready to read + + bool success; + + PARCEventBuffer *output = parcEventBuffer_Create(); + + success = metisGenericEther_ReadNextFrame(ether, output); + assertTrue(success, "Failed to read frame A"); + assertFrameEquals(frame_a, output, length_a); + + // clear the buffer before next packet + parcEventBuffer_Read(output, NULL, -1); + + success = metisGenericEther_ReadNextFrame(ether, output); + assertTrue(success, "Failed to read frame B"); + assertFrameEquals(frame_b, output, length_b); + + close(localSocket); + + parcMemory_Deallocate((void **) &frame_a); + parcMemory_Deallocate((void **) &frame_b); + parcEventBuffer_Destroy(&output); + metisGenericEther_Release(ðer); +} + +LONGBOW_TEST_CASE(Global, metisGenericEther_ReadNextFrame_WithPadding) +{ + uint16_t ethertype = 0x0801; + + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + MetisGenericEther *ether = metisGenericEther_Create(metis, interfaceName, ethertype); + metisForwarder_Destroy(&metis); + + // swap out the PF_PACKET socket for a socket pair + close(ether->etherSocket); + + int fd[2]; + socketpair(PF_LOCAL, SOCK_DGRAM, 0, fd); + + int localSocket = fd[0]; + ether->etherSocket = fd[1]; + _linuxEthernet_SetNonBlocking(ether); + + ssize_t nwritten = write(localSocket, metisTestDataV1_InterestWithEthernetPadding, sizeof(metisTestDataV1_InterestWithEthernetPadding)); + assertTrue(nwritten == sizeof(metisTestDataV1_InterestWithEthernetPadding), + "Error on write, expected %zu got %zd", + sizeof(metisTestDataV1_InterestWithEthernetPadding), nwritten); + + // wait for it to become available + struct pollfd pfd; + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = ether->etherSocket; + pfd.events = POLLIN | POLLERR; + + poll(&pfd, 1, 10); + + // Something is ready to read + + bool success; + + PARCEventBuffer *output = parcEventBuffer_Create(); + + success = metisGenericEther_ReadNextFrame(ether, output); + assertTrue(success, "Failed to read frame A"); + assertFrameEquals(metisTestDataV1_InterestWithEthernetPaddingStripped, output, sizeof(metisTestDataV1_InterestWithEthernetPaddingStripped)); + + close(localSocket); + + parcEventBuffer_Destroy(&output); + metisGenericEther_Release(ðer); +} + +LONGBOW_TEST_CASE(Global, metisGenericEther_SendFrame) +{ + char *interfaceName = getInterfaceName(); + uint16_t ethertype = 0x0801; + + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + MetisGenericEther *ether = metisGenericEther_Create(metis, interfaceName, ethertype); + metisForwarder_Destroy(&metis); + + PARCEventBuffer *parcEventBuffer = parcEventBuffer_Create(); + char dataBuffer[1024 * 1024]; + parcEventBuffer_Append(parcEventBuffer, dataBuffer, 16); + + bool result = metisGenericEther_SendFrame(ether, parcEventBuffer); + assertTrue(result, "metisGenericEther_Sendframe failed to send smallest packet"); + + parcEventBuffer_Append(parcEventBuffer, dataBuffer, 1024 * 1024); + + result = metisGenericEther_SendFrame(ether, parcEventBuffer); + assertFalse(result, "metisGenericEther_Sendframe should have failed to send packet larger than our MTU"); + + parcEventBuffer_Destroy(&parcEventBuffer); + + metisGenericEther_Release(ðer); + + free(interfaceName); +} + +// ================================================================== + +typedef struct test_data { + MetisGenericEther *ether; +} TestData; + +static void +commonSetup(const LongBowTestCase *testCase, const char *device, uint16_t ethertype) +{ + TestData *data = parcMemory_AllocateAndClear(sizeof(TestData)); + + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + data->ether = metisGenericEther_Create(metis, interfaceName, ethertype); + metisForwarder_Destroy(&metis); + + longBowTestCase_SetClipBoardData(testCase, data); +} + +static void +commonTeardown(const LongBowTestCase *testCase) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + metisGenericEther_Release(&data->ether); + parcMemory_Deallocate((void **) &data); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _linuxEthernet_SetInterfaceIndex); + LONGBOW_RUN_TEST_CASE(Local, _linuxEthernet_SetInterfaceAddress); + LONGBOW_RUN_TEST_CASE(Local, _linuxEthernet_Bind); + LONGBOW_RUN_TEST_CASE(Local, _linuxEthernet_SetNonBlocking); + LONGBOW_RUN_TEST_CASE(Local, _linuxEthernet_SetupSocket); + LONGBOW_RUN_TEST_CASE(Local, _linuxEthernet_TrimBuffer_Length_OK); + LONGBOW_RUN_TEST_CASE(Local, _linuxEthernet_TrimBuffer_Length_Trim); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + commonSetup(testCase, interfaceName, 0x0801); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + commonTeardown(testCase); + 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, _linuxEthernet_SetInterfaceIndex) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _linuxEthernet_Bind) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _linuxEthernet_SetNonBlocking) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _linuxEthernet_SetupSocket) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _linuxEthernet_SetInterfaceAddress) +{ + PARCBuffer *addr = NULL; + char ifname[1024]; + + // Lookup the MAC address of the interface we chose back in the fixture setup + struct ifaddrs *ifaddr; + int failure = getifaddrs(&ifaddr); + assertFalse(failure, "Error getifaddrs: (%d) %s", errno, strerror(errno)); + + struct ifaddrs *next; + for (next = ifaddr; next != NULL && addr == NULL; next = next->ifa_next) { + if (strcmp(interfaceName, next->ifa_name) == 0) { + if (next->ifa_addr->sa_family == AF_PACKET) { + strcpy(ifname, next->ifa_name); + + struct sockaddr_ll *addr_ll = (struct sockaddr_ll *) next->ifa_addr; + + switch (addr_ll->sll_hatype) { + // list of the ARP hatypes we can extract a MAC address from + case ARPHRD_ETHER: + // fallthrough + case ARPHRD_IEEE802: { + addr = parcBuffer_Allocate(addr_ll->sll_halen); + parcBuffer_PutArray(addr, addr_ll->sll_halen, (uint8_t *) addr_ll->sll_addr); + parcBuffer_Flip(addr); + break; + } + default: + break; + } + } + } + } + freeifaddrs(ifaddr); + + if (addr) { + uint16_t ethertype = 0x0801; + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + MetisGenericEther *ether = metisGenericEther_Create(metis, interfaceName, ethertype); + metisForwarder_Destroy(&metis); + + assertTrue(parcBuffer_Equals(addr, ether->macAddress), "Addresses do not match") + { + parcBuffer_Display(addr, 0); + parcBuffer_Display(ether->macAddress, 0); + } + + parcBuffer_Release(&addr); + metisGenericEther_Release(ðer); + } +} + +static void +trimBufferTest(const LongBowTestCase *testCase, size_t extraBytes) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + PARCEventBuffer *buffer = parcEventBuffer_Create(); + + PARCBuffer *frameBuffer = createInterestFrame(extraBytes); + size_t expectedSize = parcBuffer_Remaining(frameBuffer) - extraBytes; + + parcEventBuffer_Append(buffer, parcBuffer_Overlay(frameBuffer, 0), parcBuffer_Remaining(frameBuffer)); + + _linuxEthernet_TrimBuffer(data->ether, buffer); + + assertTrue(parcEventBuffer_GetLength(buffer) == expectedSize, + "Buffer incorrect size got %zu expected %zu", + parcEventBuffer_GetLength(buffer), expectedSize); + + parcBuffer_Release(&frameBuffer); + parcEventBuffer_Destroy(&buffer); +} + +LONGBOW_TEST_CASE(Local, _linuxEthernet_TrimBuffer_Length_OK) +{ + trimBufferTest(testCase, 0); +} + +LONGBOW_TEST_CASE(Local, _linuxEthernet_TrimBuffer_Length_Trim) +{ + trimBufferTest(testCase, 4); +} + + +// ================================================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(linux_Ethernet); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/platforms/linux/test/test_metis_System.c b/metis/ccnx/forwarder/metis/platforms/linux/test/test_metis_System.c new file mode 100644 index 00000000..d65d2e2f --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/linux/test/test_metis_System.c @@ -0,0 +1,148 @@ +/* + * 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_System.c" + +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <parc/algol/parc_Memory.h> + +// Include the generic tests of MetisSystem +#include "../../test/testrig_metis_System.c" + +LONGBOW_TEST_RUNNER(linux_Interface) +{ + // 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); + + // these are defined in testrig_metis_System.c + LONGBOW_RUN_TEST_FIXTURE(PublicApi); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(linux_Interface) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(linux_Interface) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ================================================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisSystem_Interfaces); + LONGBOW_RUN_TEST_CASE(Global, metisSystem_InterfaceMTU); +} + +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, metisSystem_Interfaces) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + assertNotNull(set, "metisSystem_Interfaces return null set"); + + // XXX we need some sort of validation test. e.g. open a socket, then ioctl to + // XXX get the interface name, then verify its in the list. + + size_t length = cpiInterfaceSet_Length(set); + assertTrue(length > 0, "metisSystem_Interfaces returned no interfaces"); + + for (size_t i = 0; i < length; i++) { + CPIInterface *iface = cpiInterfaceSet_GetByOrdinalIndex(set, i); + printf("Interface Index %u\n", cpiInterface_GetInterfaceIndex(iface)); + const CPIAddressList *list = cpiInterface_GetAddresses(iface); + PARCJSONArray *json = cpiAddressList_ToJson(list); + char *str = parcJSONArray_ToString(json); + printf("%s\n", str); + parcMemory_Deallocate((void **) &str); + parcJSONArray_Release(&json); + } + + cpiInterfaceSet_Destroy(&set); + metisForwarder_Destroy(&metis); +} + +// returns a strdup() of the interface name, use free(3) +static char * +_pickInterfaceName(MetisForwarder *metis) +{ + char *ifname = NULL; + + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + size_t length = cpiInterfaceSet_Length(set); + assertTrue(length > 0, "metisSystem_Interfaces returned no interfaces"); + + for (size_t i = 0; i < length; i++) { + CPIInterface *iface = cpiInterfaceSet_GetByOrdinalIndex(set, i); + const CPIAddressList *addressList = cpiInterface_GetAddresses(iface); + + size_t length = cpiAddressList_Length(addressList); + for (size_t i = 0; i < length && !ifname; i++) { + const CPIAddress *a = cpiAddressList_GetItem(addressList, i); + if (cpiAddress_GetType(a) == cpiAddressType_LINK) { + ifname = strdup(cpiInterface_GetName(iface)); + } + } + } + + cpiInterfaceSet_Destroy(&set); + return ifname; +} + +LONGBOW_TEST_CASE(Global, metisSystem_InterfaceMTU) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + + char *deviceName = _pickInterfaceName(metis); + unsigned mtu = metisSystem_InterfaceMtu(metis, deviceName); + + assertTrue(mtu > 0, "Did not get mtu for interface %s", deviceName); + free(deviceName); + metisForwarder_Destroy(&metis); +} + +// ================================================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(linux_Interface); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/platforms/test/testrig_metis_System.c b/metis/ccnx/forwarder/metis/platforms/test/testrig_metis_System.c new file mode 100644 index 00000000..6302303c --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/test/testrig_metis_System.c @@ -0,0 +1,106 @@ +/* + * 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. + */ + +/** + * Tests the public API for the SystemInterfaces calls. + * + */ + +// ================================================================== + +LONGBOW_TEST_FIXTURE(PublicApi) +{ + LONGBOW_RUN_TEST_CASE(PublicApi, metisSystem_Interfaces); + LONGBOW_RUN_TEST_CASE(PublicApi, metisSystem_GetMacAddressByName); +} + +LONGBOW_TEST_FIXTURE_SETUP(PublicApi) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(PublicApi) +{ + 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(PublicApi, metisSystem_Interfaces) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + assertNotNull(set, "metisSystem_Interfaces return null set"); + + size_t length = cpiInterfaceSet_Length(set); + assertTrue(length > 0, "metisSystem_Interfaces returned no interfaces"); + + for (size_t i = 0; i < length; i++) { + CPIInterface *iface = cpiInterfaceSet_GetByOrdinalIndex(set, i); + printf("Interface Index %u\n", cpiInterface_GetInterfaceIndex(iface)); + const CPIAddressList *list = cpiInterface_GetAddresses(iface); + PARCJSONArray *jsonArray = cpiAddressList_ToJson(list); + char *str = parcJSONArray_ToString(jsonArray); + printf("%s\n", str); + parcMemory_Deallocate((void **) &str); + parcJSONArray_Release(&jsonArray); + } + + cpiInterfaceSet_Destroy(&set); + metisForwarder_Destroy(&metis); +} + +// returns a strdup() of the interface name, use free(3) +char * +pickInterfaceName(MetisForwarder *metis) +{ + char *ifname = NULL; + + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + size_t length = cpiInterfaceSet_Length(set); + assertTrue(length > 0, "metisSystem_Interfaces returned no interfaces"); + + for (size_t i = 0; i < length; i++) { + CPIInterface *iface = cpiInterfaceSet_GetByOrdinalIndex(set, i); + const CPIAddressList *addressList = cpiInterface_GetAddresses(iface); + + size_t length = cpiAddressList_Length(addressList); + for (size_t i = 0; i < length && !ifname; i++) { + const CPIAddress *a = cpiAddressList_GetItem(addressList, i); + if (cpiAddress_GetType(a) == cpiAddressType_LINK) { + ifname = strdup(cpiInterface_GetName(iface)); + } + } + } + + cpiInterfaceSet_Destroy(&set); + return ifname; +} + +LONGBOW_TEST_CASE(PublicApi, metisSystem_GetMacAddressByName) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + char *ifname = pickInterfaceName(metis); + + CPIAddress *a = metisSystem_GetMacAddressByName(metis, ifname); + assertNotNull(a, "Got null mac address for interface %s", ifname); + cpiAddress_Destroy(&a); + metisForwarder_Destroy(&metis); + free(ifname); +} + 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 diff --git a/metis/ccnx/forwarder/metis/strategies/metis_Strategy.h b/metis/ccnx/forwarder/metis/strategies/metis_Strategy.h new file mode 100644 index 00000000..251d4284 --- /dev/null +++ b/metis/ccnx/forwarder/metis/strategies/metis_Strategy.h @@ -0,0 +1,33 @@ +/* + * 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. + */ + + + +/** + * A forwarding strategy for a namespace + */ + +#ifndef Metis_metis_Strategy_h +#define Metis_metis_Strategy_h + +#include <ccnx/forwarder/metis/strategies/metis_StrategyImpl.h> + +struct metis_strategy; +typedef struct metis_strategy MetisStrategy; + +MetisStrategy *metisStrategy_Create(MetisStrategyImpl *impl); + +MetisNumberSet *metisStrategy_LookupNexthops(const CCNxName *name, uint32_t ingressInterface); +#endif // Metis_metis_Strategy_h diff --git a/metis/ccnx/forwarder/metis/strategies/metis_StrategyImpl.c b/metis/ccnx/forwarder/metis/strategies/metis_StrategyImpl.c new file mode 100644 index 00000000..f383ecd7 --- /dev/null +++ b/metis/ccnx/forwarder/metis/strategies/metis_StrategyImpl.c @@ -0,0 +1,33 @@ +/* + * 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_StrategyImpl.h + * @brief Defines the function structure for a Strategy implementation + * + * <#Detailed Description#> + * + */ + +/** + * A dispatch structure for a concrete implementation of a forwarding strategy. + */ + + +const char *FWD_STRATEGY_LOADBALANCER = "loadbalancer"; +const char *FWD_STRATEGY_RANDOM = "random"; +const char *FWD_STRATEGY_RANDOM_PER_DASH_SEGMENT = "random_per_dash_segment"; +const char *FWD_STRATEGY_LOADBALANCER_WITH_DELAY = "loadbalancer_with_delay"; + diff --git a/metis/ccnx/forwarder/metis/strategies/metis_StrategyImpl.h b/metis/ccnx/forwarder/metis/strategies/metis_StrategyImpl.h new file mode 100644 index 00000000..cd93da66 --- /dev/null +++ b/metis/ccnx/forwarder/metis/strategies/metis_StrategyImpl.h @@ -0,0 +1,67 @@ +/* + * 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_StrategyImpl.h + * @brief Defines the function structure for a Strategy implementation + * + * <#Detailed Description#> + * + */ + +/** + * A dispatch structure for a concrete implementation of a forwarding strategy. + */ + +#ifndef Metis_metis_StrategyImpl_h +#define Metis_metis_StrategyImpl_h + +#include <ccnx/forwarder/metis/core/metis_NumberSet.h> +#include <ccnx/forwarder/metis/core/metis_Message.h> +#include <ccnx/api/control/cpi_RouteEntry.h> + +struct metis_strategy_impl; +typedef struct metis_strategy_impl MetisStrategyImpl; + +extern const char *FWD_STRATEGY_LOADBALANCER; +extern const char *FWD_STRATEGY_RANDOM; +extern const char *FWD_STRATEGY_RANDOM_PER_DASH_SEGMENT; +extern const char *FWD_STRATEGY_LOADBALANCER_WITH_DELAY; + +/** + * @typedef MetisStrategyImpl + * @abstract Forwarding strategy implementation + * @constant receiveObject is called when we receive an object and have a measured round trip time. This + * allows a strategy to update its performance data. + * @constant lookupNexthop Find the set of nexthops to use for the Interest. + * May be empty, should not be NULL. Must be destroyed. + * @constant addNexthop Add a nexthop to the list of available nexthops with a routing protocol-specific cost. + * @constant destroy cleans up the strategy, freeing all memory and state. A strategy is reference counted, + * so the final destruction only happens after the last reference is released. + * @discussion <#Discussion#> + */ +struct metis_strategy_impl { + void *context; + void (*receiveObject)(MetisStrategyImpl *strategy, const MetisNumberSet *egressId, const MetisMessage *objectMessage, MetisTicks rtt); + void (*onTimeout)(MetisStrategyImpl *strategy, const MetisNumberSet *egressId); + MetisNumberSet * (*lookupNexthop)(MetisStrategyImpl * strategy, const MetisMessage * interestMessage); + MetisNumberSet * (*returnNexthops)(MetisStrategyImpl * strategy); + unsigned (*countNexthops)(MetisStrategyImpl *strategy); + void (*addNexthop)(MetisStrategyImpl *strategy, CPIRouteEntry *route); + void (*removeNexthop)(MetisStrategyImpl *strategy, CPIRouteEntry *route); + void (*destroy)(MetisStrategyImpl **strategyPtr); + const char * (*getStrategy)(MetisStrategyImpl * strategy); +}; +#endif // Metis_metis_StrategyImpl_h diff --git a/metis/ccnx/forwarder/metis/strategies/strategy_All.c b/metis/ccnx/forwarder/metis/strategies/strategy_All.c new file mode 100644 index 00000000..5882b38c --- /dev/null +++ b/metis/ccnx/forwarder/metis/strategies/strategy_All.c @@ -0,0 +1,102 @@ +/* + * 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. + */ + +/** + * THIS STRATEGY IS DEPRECATED + */ + +#include <config.h> +#include <stdio.h> +#include <string.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/strategies/strategy_All.h> + +static void _strategyAll_ReceiveObject(MetisStrategyImpl *strategy, const MetisNumberSet * egressId, const MetisMessage *objectMessage, MetisTicks rtt); +static MetisNumberSet *_strategyAll_LookupNexthop(MetisStrategyImpl *strategy, const MetisMessage *interestMessage); +static void _strategyAll_AddNexthop(MetisStrategyImpl *strategy, CPIRouteEntry *route); +static void _strategyAll_RemoveNexthop(MetisStrategyImpl *strategy, CPIRouteEntry *route); +static void _strategyAll_ImplDestroy(MetisStrategyImpl **strategyPtr); + +static MetisStrategyImpl _template = { + .context = NULL, + .receiveObject = &_strategyAll_ReceiveObject, + .lookupNexthop = &_strategyAll_LookupNexthop, + .addNexthop = &_strategyAll_AddNexthop, + .removeNexthop = &_strategyAll_RemoveNexthop, + .destroy = &_strategyAll_ImplDestroy, +}; + +struct strategy_all; +typedef struct strategy_all StrategyAll; + +struct strategy_all { + int x; +}; + +MetisStrategyImpl * +strategyAll_Create() +{ + StrategyAll *strategy = parcMemory_AllocateAndClear(sizeof(StrategyAll)); + assertNotNull(strategy, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(StrategyAll)); + + MetisStrategyImpl *impl = parcMemory_AllocateAndClear(sizeof(MetisStrategyImpl)); + assertNotNull(impl, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisStrategyImpl)); + memcpy(impl, &_template, sizeof(MetisStrategyImpl)); + impl->context = strategy; + + return impl; +} + +// ======================================================= +// Dispatch API + +static void +_strategyAll_ReceiveObject(MetisStrategyImpl *strategy, const MetisNumberSet * egressId, const MetisMessage *objectMessage, MetisTicks rtt) +{ +} + +static MetisNumberSet * +_strategyAll_LookupNexthop(MetisStrategyImpl *strategy, const MetisMessage *interestMessage) +{ + return NULL; +} + +static void +_strategyAll_AddNexthop(MetisStrategyImpl *strategy, CPIRouteEntry *route) +{ +} + +static void +_strategyAll_RemoveNexthop(MetisStrategyImpl *strategy, CPIRouteEntry *route) +{ +} + +static void +_strategyAll_ImplDestroy(MetisStrategyImpl **strategyPtr) +{ + assertNotNull(strategyPtr, "Parameter must be non-null double pointer"); + assertNotNull(*strategyPtr, "Parameter must dereference to non-null pointer"); + + MetisStrategyImpl *impl = *strategyPtr; + StrategyAll *strategy = (StrategyAll *) impl->context; + + parcMemory_Deallocate((void **) &strategy); + parcMemory_Deallocate((void **) &impl); + *strategyPtr = NULL; +} diff --git a/metis/ccnx/forwarder/metis/strategies/strategy_All.h b/metis/ccnx/forwarder/metis/strategies/strategy_All.h new file mode 100644 index 00000000..521e4903 --- /dev/null +++ b/metis/ccnx/forwarder/metis/strategies/strategy_All.h @@ -0,0 +1,29 @@ +/* + * 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. + */ + + + +/** + * Forward to all nexthops + * THIS STRATEGY IS DEPRECATED + */ + +#ifndef Metis_strategy_All_h +#define Metis_strategy_All_h + +#include <ccnx/forwarder/metis/strategies/metis_Strategy.h> + +MetisStrategyImpl *metisStrategyAll_Create(); +#endif // Metis_strategy_All_h diff --git a/metis/ccnx/forwarder/metis/strategies/strategy_BestUnipath.h b/metis/ccnx/forwarder/metis/strategies/strategy_BestUnipath.h new file mode 100644 index 00000000..ee36d07d --- /dev/null +++ b/metis/ccnx/forwarder/metis/strategies/strategy_BestUnipath.h @@ -0,0 +1,26 @@ +/* + * 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. + */ + + + +/** + * THIS STRATEGY IS NOT IMPLEMENTED + */ + +#ifndef Metis_strategy_BestUnipath_h +#define Metis_strategy_BestUnipath_h + +#include <ccnx/forwarder/metis/strategies/metis_Strategy.h> +#endif // Metis_strategy_BestUnipath_h diff --git a/metis/ccnx/forwarder/metis/strategies/strategy_CNF.h b/metis/ccnx/forwarder/metis/strategies/strategy_CNF.h new file mode 100644 index 00000000..2fdda7a3 --- /dev/null +++ b/metis/ccnx/forwarder/metis/strategies/strategy_CNF.h @@ -0,0 +1,41 @@ +/* + * 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. + */ + + + +/** + * Conjunctive Normal Form forwarding. Nexthops are a conjunctive set of + * disjunctive sets of interface IDs. That is, at the first conjunctive + * level, we have: + * A_1 and A_2 and A_3 and ... and A_m + * + * Each set A_i is made up of a disjunctive set: + * B_(i,1) or B_(i,2) or ... or B_(i,n) + * + * An interest is forwarded to every conjunctive set A_i. If a set A_i has + * more than one B_(i,j), we pick a B_(i,j) using a weighted round robin, but only + * one B_(i,j) is used. + * + * The final set of egress interfaces is the union of B_(i,j) over all i. + * + * + * THIS STRATEGY IS NOT IMPLEMENTED + */ + +#ifndef Metis_strategy_CNF_h +#define Metis_strategy_CNF_h + +#include <ccnx/forwarder/metis/strategy/metis_Strategy.h> +#endif // Metis_strategy_CNF_h diff --git a/metis/ccnx/forwarder/metis/strategies/strategy_LoadBalancer.c b/metis/ccnx/forwarder/metis/strategies/strategy_LoadBalancer.c new file mode 100644 index 00000000..145c9b2a --- /dev/null +++ b/metis/ccnx/forwarder/metis/strategies/strategy_LoadBalancer.c @@ -0,0 +1,291 @@ +/* + * 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 <time.h> +#include <stdlib.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_HashMap.h> +#include <parc/algol/parc_Object.h> +#include <parc/algol/parc_Unsigned.h> + +#include <ccnx/forwarder/metis/strategies/strategy_NexthopState.h> +#include <ccnx/forwarder/metis/strategies/strategy_LoadBalancer.h> + + + +static void _strategyLoadBalancer_ReceiveObject(MetisStrategyImpl *strategy, const MetisNumberSet *egressId, const MetisMessage *objectMessage, MetisTicks rtt); +static void _strategyLoadBalancer_OnTimeout(MetisStrategyImpl *strategy, const MetisNumberSet *egressId); +static MetisNumberSet *_strategyLoadBalancer_LookupNexthop(MetisStrategyImpl *strategy, const MetisMessage *interestMessage); +static MetisNumberSet *_strategyLoadBalancer_ReturnNexthops(MetisStrategyImpl *strategy); +static unsigned _strategyLoadBalancer_CountNexthops(MetisStrategyImpl *strategy); +static void _strategyLoadBalancer_AddNexthop(MetisStrategyImpl *strategy, CPIRouteEntry *route); +static void _strategyLoadBalancer_RemoveNexthop(MetisStrategyImpl *strategy, CPIRouteEntry *route); +static void _strategyLoadBalancer_ImplDestroy(MetisStrategyImpl **strategyPtr); +static const char *_strategyLoadBalancer_GetStrategy(MetisStrategyImpl *strategy); + +static MetisStrategyImpl _template = { + .context = NULL, + .receiveObject = &_strategyLoadBalancer_ReceiveObject, + .onTimeout = &_strategyLoadBalancer_OnTimeout, + .lookupNexthop = &_strategyLoadBalancer_LookupNexthop, + .returnNexthops = &_strategyLoadBalancer_ReturnNexthops, + .countNexthops = &_strategyLoadBalancer_CountNexthops, + .addNexthop = &_strategyLoadBalancer_AddNexthop, + .removeNexthop = &_strategyLoadBalancer_RemoveNexthop, + .destroy = &_strategyLoadBalancer_ImplDestroy, + .getStrategy = &_strategyLoadBalancer_GetStrategy, +}; + +struct strategy_load_balancer; +typedef struct strategy_load_balancer StrategyLoadBalancer; + + +struct strategy_load_balancer { + double weights_sum; + //hash map from connectionId to StrategyNexthopState + PARCHashMap *strategy_state; + MetisNumberSet *nexthops; +}; + +MetisStrategyImpl * +strategyLoadBalancer_Create() +{ + StrategyLoadBalancer *strategy = parcMemory_AllocateAndClear(sizeof(StrategyLoadBalancer)); + assertNotNull(strategy, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(StrategyLoadBalancer)); + + strategy->weights_sum = 0.0; + strategy->strategy_state = parcHashMap_Create(); + strategy->nexthops = metisNumberSet_Create(); + srand(time(NULL)); + + MetisStrategyImpl *impl = parcMemory_AllocateAndClear(sizeof(MetisStrategyImpl)); + assertNotNull(impl, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisStrategyImpl)); + memcpy(impl, &_template, sizeof(MetisStrategyImpl)); + impl->context = strategy; + + return impl; +} + +// ======================================================= +// Dispatch API + + +static const char* +_strategyLoadBalancer_GetStrategy(MetisStrategyImpl *strategy) +{ + return FWD_STRATEGY_LOADBALANCER; +} + +static void +_update_Stats(StrategyLoadBalancer *strategy, StrategyNexthopState *state, bool inc) +{ + const double ALPHA = 0.9; + double w = strategyNexthopState_GetWeight(state); + strategy->weights_sum -= w; + w = strategyNexthopState_UpdateState(state, inc, ALPHA); + strategy->weights_sum += w; +} + +static unsigned +_select_Nexthop(StrategyLoadBalancer *strategy) +{ + double rnd = (double) rand() / (double) RAND_MAX; + double start_range = 0.0; + + PARCIterator *it = parcHashMap_CreateKeyIterator(strategy->strategy_state); + + unsigned nexthop = 100000; + while (parcIterator_HasNext(it)) { + PARCUnsigned *cid = parcIterator_Next(it); + const StrategyNexthopState *elem = parcHashMap_Get(strategy->strategy_state, cid); + + double w = strategyNexthopState_GetWeight(elem); + + double prob = w / strategy->weights_sum; + if ((rnd >= start_range) && (rnd < (start_range + prob))) { + nexthop = parcUnsigned_GetUnsigned(cid); + break; + } else { + start_range += prob; + } + } + + parcIterator_Release(&it); + + //if no face is selected by the algorithm (for example because of a wrong round in the weights) + //we may always select the last face here. Double check this! + return nexthop; +} + + +static void +_strategyLoadBalancer_ReceiveObject(MetisStrategyImpl *strategy, const MetisNumberSet *egressId, const MetisMessage *objectMessage, MetisTicks rtt) +{ + _strategyLoadBalancer_OnTimeout(strategy, egressId); +} + +static void +_strategyLoadBalancer_OnTimeout(MetisStrategyImpl *strategy, const MetisNumberSet *egressId) +{ + StrategyLoadBalancer *lb = (StrategyLoadBalancer *) strategy->context; + + for (unsigned i = 0; i < metisNumberSet_Length(egressId); i++) { + unsigned outId = metisNumberSet_GetItem(egressId, i); + PARCUnsigned *cid = parcUnsigned_Create(outId); + + const StrategyNexthopState *state = parcHashMap_Get(lb->strategy_state, cid); + if (state != NULL) { + _update_Stats(lb, (StrategyNexthopState *) state, false); + } else { + //this may happen if we remove a face/route while downloading a file + //we should ignore this timeout + } + parcUnsigned_Release(&cid); + } +} + + +//ATTENTION!! This interface force us to create a MetisNumberSet which need to be delited somewhere +//The specification in the interface requires that this function never returns NULL. in case we have no output face we need to return an empty MetisNumberSet +static MetisNumberSet * +_strategyLoadBalancer_LookupNexthop(MetisStrategyImpl *strategy, const MetisMessage *interestMessage) +{ + StrategyLoadBalancer *lb = (StrategyLoadBalancer *) strategy->context; + + unsigned in_connection = metisMessage_GetIngressConnectionId(interestMessage); + PARCUnsigned *in = parcUnsigned_Create(in_connection); + + unsigned mapSize = parcHashMap_Size(lb->strategy_state); + MetisNumberSet *outList = metisNumberSet_Create(); + + if ((mapSize == 0) || ((mapSize == 1) && parcHashMap_Contains(lb->strategy_state, in))) { + //there are no output faces or the input face is also the only output face. return null to avoid loops + parcUnsigned_Release(&in); + return outList; + } + + unsigned out_connection; + do { + out_connection = _select_Nexthop(lb); + } while (out_connection == in_connection); + + PARCUnsigned *out = parcUnsigned_Create(out_connection); + + const StrategyNexthopState *state = parcHashMap_Get(lb->strategy_state, out); + if (state == NULL) { + //this is an error and should not happen! + trapNotImplemented("Try to send an interest on a face that does not exists"); + } + + _update_Stats(lb, (StrategyNexthopState *) state, true); + + parcUnsigned_Release(&in); + parcUnsigned_Release(&out); + + metisNumberSet_Add(outList, out_connection); + return outList; +} + +static MetisNumberSet * +_strategyLoadBalancer_ReturnNexthops(MetisStrategyImpl *strategy) +{ + StrategyLoadBalancer *lb = (StrategyLoadBalancer *) strategy->context; + return lb->nexthops; +} + +unsigned +_strategyLoadBalancer_CountNexthops(MetisStrategyImpl *strategy) +{ + StrategyLoadBalancer *lb = (StrategyLoadBalancer *) strategy->context; + return metisNumberSet_Length(lb->nexthops); +} + +static void +_strategyLoadBalancer_resetState(MetisStrategyImpl *strategy) +{ + StrategyLoadBalancer *lb = (StrategyLoadBalancer *) strategy->context; + lb->weights_sum = 0.0; + PARCIterator *it = parcHashMap_CreateKeyIterator(lb->strategy_state); + + while (parcIterator_HasNext(it)) { + PARCUnsigned *cid = parcIterator_Next(it); + StrategyNexthopState *elem = (StrategyNexthopState *) parcHashMap_Get(lb->strategy_state, cid); + + strategyNexthopState_Reset(elem); + lb->weights_sum += strategyNexthopState_GetWeight(elem); + } + + parcIterator_Release(&it); +} + +static void +_strategyLoadBalancer_AddNexthop(MetisStrategyImpl *strategy, CPIRouteEntry *route) +{ + unsigned connectionId = cpiRouteEntry_GetInterfaceIndex(route); //this returns what in all the rest of the ccnx code is called connection id! + + + StrategyNexthopState *state = strategyNexthopState_Create(); + + PARCUnsigned *cid = parcUnsigned_Create(connectionId); + + StrategyLoadBalancer *lb = (StrategyLoadBalancer *) strategy->context; + + if (!parcHashMap_Contains(lb->strategy_state, cid)) { + parcHashMap_Put(lb->strategy_state, cid, state); + metisNumberSet_Add(lb->nexthops, connectionId); + _strategyLoadBalancer_resetState(strategy); + } +} + +static void +_strategyLoadBalancer_RemoveNexthop(MetisStrategyImpl *strategy, CPIRouteEntry *route) +{ + unsigned connectionId = cpiRouteEntry_GetInterfaceIndex(route); + StrategyLoadBalancer *lb = (StrategyLoadBalancer *) strategy->context; + + PARCUnsigned *cid = parcUnsigned_Create(connectionId); + + if (parcHashMap_Contains(lb->strategy_state, cid)) { + parcHashMap_Remove(lb->strategy_state, cid); + metisNumberSet_Remove(lb->nexthops, connectionId); + _strategyLoadBalancer_resetState(strategy); + } + + parcUnsigned_Release(&cid); +} + +static void +_strategyLoadBalancer_ImplDestroy(MetisStrategyImpl **strategyPtr) +{ + assertNotNull(strategyPtr, "Parameter must be non-null double pointer"); + assertNotNull(*strategyPtr, "Parameter must dereference to non-null pointer"); + + MetisStrategyImpl *impl = *strategyPtr; + StrategyLoadBalancer *strategy = (StrategyLoadBalancer *) impl->context; + + parcHashMap_Release(&(strategy->strategy_state)); + metisNumberSet_Release(&(strategy->nexthops)); + + parcMemory_Deallocate((void **) &strategy); + parcMemory_Deallocate((void **) &impl); + *strategyPtr = NULL; +} diff --git a/metis/ccnx/forwarder/metis/strategies/strategy_LoadBalancer.h b/metis/ccnx/forwarder/metis/strategies/strategy_LoadBalancer.h new file mode 100644 index 00000000..cb73d4a9 --- /dev/null +++ b/metis/ccnx/forwarder/metis/strategies/strategy_LoadBalancer.h @@ -0,0 +1,27 @@ +/* + * 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. + */ + + +/** + * Forward on the less loaded path + */ + +#ifndef Metis_strategy_LoadBalancer_h +#define Metis_strategy_LoadBalancer_h + +#include <ccnx/forwarder/metis/strategies/metis_Strategy.h> + +MetisStrategyImpl *strategyLoadBalancer_Create(); +#endif // Metis_strategy_LoadBalancer_h diff --git a/metis/ccnx/forwarder/metis/strategies/strategy_LoadBalancerWithPD.c b/metis/ccnx/forwarder/metis/strategies/strategy_LoadBalancerWithPD.c new file mode 100644 index 00000000..7aee000a --- /dev/null +++ b/metis/ccnx/forwarder/metis/strategies/strategy_LoadBalancerWithPD.c @@ -0,0 +1,356 @@ +/* + * 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 <time.h> +#include <stdlib.h> +#include <limits.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_HashMap.h> +#include <parc/algol/parc_Object.h> +#include <parc/algol/parc_Unsigned.h> + +#include <ccnx/forwarder/metis/strategies/strategy_NexthopStateWithPD.h> +#include <ccnx/forwarder/metis/strategies/strategy_LoadBalancerWithPD.h> + +const unsigned PROBE_FREQUENCY = 1024; + +static void _strategyLoadBalancerWithPD_ReceiveObject(MetisStrategyImpl *strategy, const MetisNumberSet *egressId, const MetisMessage *objectMessage, MetisTicks rtt); +static void _strategyLoadBalancerWithPD_OnTimeout(MetisStrategyImpl *strategy, const MetisNumberSet *egressId); +static MetisNumberSet *_strategyLoadBalancerWithPD_LookupNexthop(MetisStrategyImpl *strategy, const MetisMessage *interestMessage); +static MetisNumberSet *_strategyLoadBalancerWithPD_ReturnNexthops(MetisStrategyImpl *strategy); +static unsigned _strategyLoadBalancerWithPD_CountNexthops(MetisStrategyImpl *strategy); +static void _strategyLoadBalancerWithPD_AddNexthop(MetisStrategyImpl *strategy, CPIRouteEntry *route); +static void _strategyLoadBalancerWithPD_RemoveNexthop(MetisStrategyImpl *strategy, CPIRouteEntry *route); +static void _strategyLoadBalancerWithPD_ImplDestroy(MetisStrategyImpl **strategyPtr); +static const char *_strategyLoadBalancerWithPD_GetStrategy(MetisStrategyImpl *strategy); + + +static MetisStrategyImpl _template = { + .context = NULL, + .receiveObject = &_strategyLoadBalancerWithPD_ReceiveObject, + .onTimeout = &_strategyLoadBalancerWithPD_OnTimeout, + .lookupNexthop = &_strategyLoadBalancerWithPD_LookupNexthop, + .returnNexthops = &_strategyLoadBalancerWithPD_ReturnNexthops, + .countNexthops = &_strategyLoadBalancerWithPD_CountNexthops, + .addNexthop = &_strategyLoadBalancerWithPD_AddNexthop, + .removeNexthop = &_strategyLoadBalancerWithPD_RemoveNexthop, + .destroy = &_strategyLoadBalancerWithPD_ImplDestroy, + .getStrategy = &_strategyLoadBalancerWithPD_GetStrategy, +}; + +struct strategy_load_balancer_with_pd; +typedef struct strategy_load_balancer_with_pd StrategyLoadBalancerWithPD; + +struct strategy_load_balancer_with_pd { + double weights_sum; + unsigned min_delay; + //hash map from connectionId to StrategyNexthopState + PARCHashMap *strategy_state; + MetisNumberSet *nexthops; + MetisConnectionTable *connTable; + bool toInit; + unsigned int fwdPackets; +}; + +MetisStrategyImpl * +strategyLoadBalancerWithPD_Create() +{ + StrategyLoadBalancerWithPD *strategy = parcMemory_AllocateAndClear(sizeof(StrategyLoadBalancerWithPD)); + assertNotNull(strategy, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(StrategyLoadBalancerWithPD)); + + strategy->weights_sum = 0.0; + strategy->min_delay = INT_MAX; + strategy->strategy_state = parcHashMap_Create(); + strategy->nexthops = metisNumberSet_Create(); + srand(time(NULL)); + + MetisStrategyImpl *impl = parcMemory_AllocateAndClear(sizeof(MetisStrategyImpl)); + assertNotNull(impl, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisStrategyImpl)); + memcpy(impl, &_template, sizeof(MetisStrategyImpl)); + impl->context = strategy; + strategy->connTable = NULL; + strategy->fwdPackets = 0; + strategy->toInit = true; + + return impl; +} + +void +strategyLoadBalancerWithPD_SetConnectionTable(MetisStrategyImpl *strategy, MetisConnectionTable *connTable) +{ + StrategyLoadBalancerWithPD *lb = (StrategyLoadBalancerWithPD *) strategy->context; + lb->connTable = connTable; +} + +// ======================================================= +// Dispatch API + + +static const char* +_strategyLoadBalancerWithPD_GetStrategy(MetisStrategyImpl *strategy) +{ + return FWD_STRATEGY_LOADBALANCER_WITH_DELAY; +} + +static void +_update_Stats(StrategyLoadBalancerWithPD *strategy, StrategyNexthopStateWithPD *state, bool inc, MetisTicks rtt) +{ + const double ALPHA = 0.9; + double w = strategyNexthopStateWithPD_GetWeight(state); + strategy->weights_sum -= w; + w = strategyNexthopStateWithPD_UpdateState(state, inc, strategy->min_delay, ALPHA); + strategy->weights_sum += w; +} + +static void +_sendProbes(StrategyLoadBalancerWithPD *strategy) +{ + unsigned size = metisNumberSet_Length(strategy->nexthops); + for (unsigned i = 0; i < size; i++) { + unsigned nhop = metisNumberSet_GetItem(strategy->nexthops, i); + MetisConnection *conn = (MetisConnection *) metisConnectionTable_FindById(strategy->connTable, nhop); + if (conn != NULL) { + metisConnection_Probe(conn); + unsigned delay = metisConnection_GetDelay(conn); + PARCUnsigned *cid = parcUnsigned_Create(nhop); + StrategyNexthopStateWithPD *elem = (StrategyNexthopStateWithPD *) parcHashMap_Get(strategy->strategy_state, cid); + strategyNexthopStateWithPD_SetDelay(elem, delay); + if (delay < strategy->min_delay && delay != 0) { + strategy->min_delay = delay; + } + + parcUnsigned_Release(&cid); + } + } +} + +static unsigned +_select_Nexthop(StrategyLoadBalancerWithPD *strategy) +{ + strategy->fwdPackets++; + if (strategy->toInit || strategy->fwdPackets == PROBE_FREQUENCY) { + strategy->toInit = false; + strategy->fwdPackets = 0; + _sendProbes(strategy); + } + double rnd = (double) rand() / (double) RAND_MAX; + double start_range = 0.0; + + PARCIterator *it = parcHashMap_CreateKeyIterator(strategy->strategy_state); + + unsigned nexthop = 100000; + while (parcIterator_HasNext(it)) { + PARCUnsigned *cid = parcIterator_Next(it); + const StrategyNexthopStateWithPD *elem = parcHashMap_Get(strategy->strategy_state, cid); + + double w = strategyNexthopStateWithPD_GetWeight(elem); + + //printf("next = %u .. pi %u avgpi %f w %f avgrtt %f\n",parcUnsigned_GetUnsigned(cid), strategyNexthopStateWithPD_GetPI(elem), + // strategyNexthopStateWithPD_GetWeight(elem), strategyNexthopStateWithPD_GetWeight(elem), strategyNexthopStateWithPD_GetAvgRTT(elem)); + + double prob = w / strategy->weights_sum; + if ((rnd >= start_range) && (rnd < (start_range + prob))) { + nexthop = parcUnsigned_GetUnsigned(cid); + break; + } else { + start_range += prob; + } + } + + parcIterator_Release(&it); + + //if no face is selected by the algorithm (for example because of a wrong round in the weights) + //we may always select the last face here. Double check this! + return nexthop; +} + + +static void +_strategyLoadBalancerWithPD_ReceiveObject(MetisStrategyImpl *strategy, const MetisNumberSet *egressId, const MetisMessage *objectMessage, MetisTicks rtt) +{ + StrategyLoadBalancerWithPD *lb = (StrategyLoadBalancerWithPD *) strategy->context; + + for (unsigned i = 0; i < metisNumberSet_Length(egressId); i++) { + unsigned outId = metisNumberSet_GetItem(egressId, i); + PARCUnsigned *cid = parcUnsigned_Create(outId); + + const StrategyNexthopStateWithPD *state = parcHashMap_Get(lb->strategy_state, cid); + if (state != NULL) { + _update_Stats(lb, (StrategyNexthopStateWithPD *) state, false, 0); + } else { + //this may happen if we remove a face/route while downloading a file + //we should ignore this timeout + } + parcUnsigned_Release(&cid); + } +} + +static void +_strategyLoadBalancerWithPD_OnTimeout(MetisStrategyImpl *strategy, const MetisNumberSet *egressId) +{ + StrategyLoadBalancerWithPD *lb = (StrategyLoadBalancerWithPD *) strategy->context; + + for (unsigned i = 0; i < metisNumberSet_Length(egressId); i++) { + unsigned outId = metisNumberSet_GetItem(egressId, i); + PARCUnsigned *cid = parcUnsigned_Create(outId); + + const StrategyNexthopStateWithPD *state = parcHashMap_Get(lb->strategy_state, cid); + if (state != NULL) { + _update_Stats(lb, (StrategyNexthopStateWithPD *) state, false, 0); + } else { + //this may happen if we remove a face/route while downloading a file + //we should ignore this timeout + } + parcUnsigned_Release(&cid); + } +} + + +//ATTENTION!! This interface force us to create a MetisNumberSet which need to be delited somewhere +//The specification in the interface requires that this function never returns NULL. in case we have no output face we need to return an empty MetisNumberSet +static MetisNumberSet * +_strategyLoadBalancerWithPD_LookupNexthop(MetisStrategyImpl *strategy, const MetisMessage *interestMessage) +{ + StrategyLoadBalancerWithPD *lb = (StrategyLoadBalancerWithPD *) strategy->context; + + unsigned in_connection = metisMessage_GetIngressConnectionId(interestMessage); + PARCUnsigned *in = parcUnsigned_Create(in_connection); + + unsigned mapSize = parcHashMap_Size(lb->strategy_state); + MetisNumberSet *outList = metisNumberSet_Create(); + + if ((mapSize == 0) || ((mapSize == 1) && parcHashMap_Contains(lb->strategy_state, in))) { + //there are no output faces or the input face is also the only output face. return null to avoid loops + parcUnsigned_Release(&in); + return outList; + } + + unsigned out_connection; + do { + out_connection = _select_Nexthop(lb); + } while (out_connection == in_connection); + + PARCUnsigned *out = parcUnsigned_Create(out_connection); + + const StrategyNexthopStateWithPD *state = parcHashMap_Get(lb->strategy_state, out); + if (state == NULL) { + //this is an error and should not happen! + trapNotImplemented("Try to send an interest on a face that does not exists"); + } + + _update_Stats(lb, (StrategyNexthopStateWithPD *) state, true, 0); + + parcUnsigned_Release(&in); + parcUnsigned_Release(&out); + + metisNumberSet_Add(outList, out_connection); + return outList; +} + +static MetisNumberSet * +_strategyLoadBalancerWithPD_ReturnNexthops(MetisStrategyImpl *strategy) +{ + StrategyLoadBalancerWithPD *lb = (StrategyLoadBalancerWithPD *) strategy->context; + return lb->nexthops; +} + +unsigned +_strategyLoadBalancerWithPD_CountNexthops(MetisStrategyImpl *strategy) +{ + StrategyLoadBalancerWithPD *lb = (StrategyLoadBalancerWithPD *) strategy->context; + return metisNumberSet_Length(lb->nexthops); +} + +static void +_strategyLoadBalancerWithPD_resetState(MetisStrategyImpl *strategy) +{ + StrategyLoadBalancerWithPD *lb = (StrategyLoadBalancerWithPD *) strategy->context; + lb->weights_sum = 0.0; + lb->min_delay = INT_MAX; + lb->toInit = true; + PARCIterator *it = parcHashMap_CreateKeyIterator(lb->strategy_state); + + while (parcIterator_HasNext(it)) { + PARCUnsigned *cid = parcIterator_Next(it); + StrategyNexthopStateWithPD *elem = (StrategyNexthopStateWithPD *) parcHashMap_Get(lb->strategy_state, cid); + + strategyNexthopStateWithPD_Reset(elem); + lb->weights_sum += strategyNexthopStateWithPD_GetWeight(elem); + } + + parcIterator_Release(&it); +} + + +static void +_strategyLoadBalancerWithPD_AddNexthop(MetisStrategyImpl *strategy, CPIRouteEntry *route) +{ + unsigned connectionId = cpiRouteEntry_GetInterfaceIndex(route); //this returns what in all the rest of the ccnx code is called connection id! + + + StrategyNexthopStateWithPD *state = strategyNexthopStateWithPD_Create(); + + PARCUnsigned *cid = parcUnsigned_Create(connectionId); + + StrategyLoadBalancerWithPD *lb = (StrategyLoadBalancerWithPD *) strategy->context; + + if (!parcHashMap_Contains(lb->strategy_state, cid)) { + parcHashMap_Put(lb->strategy_state, cid, state); + metisNumberSet_Add(lb->nexthops, connectionId); + _strategyLoadBalancerWithPD_resetState(strategy); + } +} + +static void +_strategyLoadBalancerWithPD_RemoveNexthop(MetisStrategyImpl *strategy, CPIRouteEntry *route) +{ + unsigned connectionId = cpiRouteEntry_GetInterfaceIndex(route); + StrategyLoadBalancerWithPD *lb = (StrategyLoadBalancerWithPD *) strategy->context; + + PARCUnsigned *cid = parcUnsigned_Create(connectionId); + + if (parcHashMap_Contains(lb->strategy_state, cid)) { + parcHashMap_Remove(lb->strategy_state, cid); + metisNumberSet_Remove(lb->nexthops, connectionId); + _strategyLoadBalancerWithPD_resetState(strategy); + } + + parcUnsigned_Release(&cid); +} + +static void +_strategyLoadBalancerWithPD_ImplDestroy(MetisStrategyImpl **strategyPtr) +{ + assertNotNull(strategyPtr, "Parameter must be non-null double pointer"); + assertNotNull(*strategyPtr, "Parameter must dereference to non-null pointer"); + + MetisStrategyImpl *impl = *strategyPtr; + StrategyLoadBalancerWithPD *strategy = (StrategyLoadBalancerWithPD *) impl->context; + + parcHashMap_Release(&(strategy->strategy_state)); + metisNumberSet_Release(&(strategy->nexthops)); + + parcMemory_Deallocate((void **) &strategy); + parcMemory_Deallocate((void **) &impl); + *strategyPtr = NULL; +} diff --git a/metis/ccnx/forwarder/metis/strategies/strategy_LoadBalancerWithPD.h b/metis/ccnx/forwarder/metis/strategies/strategy_LoadBalancerWithPD.h new file mode 100644 index 00000000..434855d7 --- /dev/null +++ b/metis/ccnx/forwarder/metis/strategies/strategy_LoadBalancerWithPD.h @@ -0,0 +1,30 @@ +/* + * 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. + */ + + + +/** + * Forward on the less loaded path taking into account the propagation delay of the first hop + */ + +#ifndef Metis_strategy_LoadBalancerWithPD_h +#define Metis_strategy_LoadBalancerWithPD_h + +#include <ccnx/forwarder/metis/strategies/metis_Strategy.h> +#include <ccnx/forwarder/metis/core/metis_ConnectionTable.h> + +MetisStrategyImpl *strategyLoadBalancerWithPD_Create(); +void strategyLoadBalancerWithPD_SetConnectionTable(MetisStrategyImpl *strategy, MetisConnectionTable *connTable); +#endif // Metis_strategy_LoadBalancerWithPD_h diff --git a/metis/ccnx/forwarder/metis/strategies/strategy_NexthopState.c b/metis/ccnx/forwarder/metis/strategies/strategy_NexthopState.c new file mode 100644 index 00000000..839ece06 --- /dev/null +++ b/metis/ccnx/forwarder/metis/strategies/strategy_NexthopState.c @@ -0,0 +1,237 @@ +/* + * 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 <stdio.h> +#include <config.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_Object.h> +#include <parc/algol/parc_DisplayIndented.h> + +#include <ccnx/forwarder/metis/strategies/strategy_NexthopState.h> + +struct strategy_nexthop_state { + unsigned int pi; + double avg_pi; + double weight; +}; + +static bool +_strategyNexthopState_Destructor(StrategyNexthopState **instancePtr) +{ + return true; +} + +parcObject_ImplementAcquire(strategyNexthopState, StrategyNexthopState); + +parcObject_ImplementRelease(strategyNexthopState, StrategyNexthopState); + +parcObject_Override(StrategyNexthopState, PARCObject, + .destructor = (PARCObjectDestructor *) _strategyNexthopState_Destructor, + .copy = (PARCObjectCopy *) strategyNexthopState_Copy, + .display = (PARCObjectDisplay *) strategyNexthopState_Display, + .toString = (PARCObjectToString *) strategyNexthopState_ToString, + .equals = (PARCObjectEquals *) strategyNexthopState_Equals, + .compare = (PARCObjectCompare *) strategyNexthopState_Compare, + .hashCode = (PARCObjectHashCode *) strategyNexthopState_HashCode, + .toJSON = (PARCObjectToJSON *) strategyNexthopState_ToJSON, + .display = (PARCObjectDisplay *) strategyNexthopState_Display); + +void +strategyNexthopState_AssertValid(const StrategyNexthopState *instance) +{ + assertTrue(strategyNexthopState_IsValid(instance), + "StrategyNexthopState is not valid."); +} + +StrategyNexthopState * +strategyNexthopState_Create() +{ + StrategyNexthopState *result = parcObject_CreateInstance(StrategyNexthopState); + if (result != NULL) { + result->pi = 0; + result->avg_pi = 0.0; + result->weight = 1; + } + return result; +} + +void +strategyNexthopState_Reset(StrategyNexthopState *x) +{ + x->pi = 0; + x->avg_pi = 0.0; + x->weight = 1; +} + + +int +strategyNexthopState_Compare(const StrategyNexthopState *val, const StrategyNexthopState *other) +{ + if (val == NULL) { + if (other != NULL) { + return -1; + } + } else if (other == NULL) { + return 1; + } else { + strategyNexthopState_OptionalAssertValid(val); + strategyNexthopState_OptionalAssertValid(other); + + if (val->pi < other->pi) { + return -1; + } else if (val->pi > other->pi) { + return 1; + } + + if (val->avg_pi < other->avg_pi) { + return -1; + } else if (val->avg_pi > other->avg_pi) { + return 1; + } + + if (val->weight < other->weight) { + return -1; + } else if (val->weight > other->weight) { + return 1; + } + } + + return 0; +} + +StrategyNexthopState * +strategyNexthopState_Copy(const StrategyNexthopState *original) +{ + StrategyNexthopState *result = strategyNexthopState_Create(); + result->pi = original->pi; + result->avg_pi = original->avg_pi; + result->weight = original->weight; + + return result; +} + +void +strategyNexthopState_Display(const StrategyNexthopState *instance, int indentation) +{ + parcDisplayIndented_PrintLine(indentation, "StrategyNexthopState@%p {", instance); + parcDisplayIndented_PrintLine(indentation + 1, "%d", instance->pi); + parcDisplayIndented_PrintLine(indentation + 1, "%f", instance->avg_pi); + parcDisplayIndented_PrintLine(indentation + 1, "%f", instance->weight); + parcDisplayIndented_PrintLine(indentation, "}"); +} + +bool +strategyNexthopState_Equals(const StrategyNexthopState *x, const StrategyNexthopState *y) +{ + bool result = false; + + if (x == y) { + result = true; + } else if (x == NULL || y == NULL) { + result = false; + } else { + strategyNexthopState_OptionalAssertValid(x); + strategyNexthopState_OptionalAssertValid(y); + + if (strategyNexthopState_Compare(x, y) == 0) { + result = true; + } + } + + return result; +} + +PARCHashCode +strategyNexthopState_HashCode(const StrategyNexthopState *x) +{ + PARCHashCode result = 0; + char str[128]; + sprintf(str, "PI:%d: AVG_PI:%f: W:%f", x->pi, x->avg_pi, x->weight); + result = parcHashCode_Hash((uint8_t *) &str, strlen(str)); + return result; +} + +bool +strategyNexthopState_IsValid(const StrategyNexthopState *x) +{ + bool result = false; + + if (x != NULL) { + result = true; + } + + return result; +} + +PARCJSON * +strategyNexthopState_ToJSON(const StrategyNexthopState *x) +{ + PARCJSON *result = parcJSON_Create(); + + return result; +} + +char * +strategyNexthopState_ToString(const StrategyNexthopState *x) +{ + //this is not implemented + trapNotImplemented("strategyNexthopState_ToString is not implemented"); + return NULL; +} + +unsigned +strategyNexthopState_GetPI(const StrategyNexthopState *x) +{ + strategyNexthopState_OptionalAssertValid(x); + + return x->pi; +} + +double +strategyNexthopState_GetAvgPI(const StrategyNexthopState *x) +{ + strategyNexthopState_OptionalAssertValid(x); + + return x->avg_pi; +} + +double +strategyNexthopState_GetWeight(const StrategyNexthopState *x) +{ + strategyNexthopState_OptionalAssertValid(x); + + return x->weight; +} + +double +strategyNexthopState_UpdateState(StrategyNexthopState *x, bool inc, double alpha) +{ + if (inc) { + x->pi++; + } else { + if (x->pi > 0) { + x->pi--; + } + } + x->avg_pi = (x->avg_pi * alpha) + (x->pi * (1 - alpha)); + if (x->avg_pi == 0.0) { + x->avg_pi = 0.1; + } + x->weight = 1 / x->avg_pi; + + return x->weight; +} + diff --git a/metis/ccnx/forwarder/metis/strategies/strategy_NexthopState.h b/metis/ccnx/forwarder/metis/strategies/strategy_NexthopState.h new file mode 100644 index 00000000..4a3c5de5 --- /dev/null +++ b/metis/ccnx/forwarder/metis/strategies/strategy_NexthopState.h @@ -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. + */ + +#ifndef Metis_strategy_nexthopstate_h +#define Metis_strategy_nexthopstate_h + +#include <parc/algol/parc_JSON.h> +#include <parc/algol/parc_HashCode.h> +#include <parc/algol/parc_Object.h> + +struct strategy_nexthop_state; +typedef struct strategy_nexthop_state StrategyNexthopState; +extern parcObjectDescriptor_Declaration(StrategyNexthopState); + +/** + */ +StrategyNexthopState *strategyNexthopState_Acquire(const StrategyNexthopState *instance); + +#ifdef PARCLibrary_DISABLE_VALIDATION +# define strategyNexthopState_OptionalAssertValid(_instance_) +#else +# define strategyNexthopState_OptionalAssertValid(_instance_) strategyNexthopState_AssertValid(_instance_) +#endif + +/** + */ +void strategyNexthopState_AssertValid(const StrategyNexthopState *instance); + +/** + */ +StrategyNexthopState *strategyNexthopState_Create(); + +void strategyNexthopState_Reset(StrategyNexthopState *x); +/** + */ +int strategyNexthopState_Compare(const StrategyNexthopState *instance, const StrategyNexthopState *other); + +/** + */ +StrategyNexthopState *strategyNexthopState_Copy(const StrategyNexthopState *original); + +/** + */ +void strategyNexthopState_Display(const StrategyNexthopState *instance, int indentation); + +/** + */ +bool strategyNexthopState_Equals(const StrategyNexthopState *x, const StrategyNexthopState *y); + +/** + */ +PARCHashCode strategyNexthopState_HashCode(const StrategyNexthopState *instance); + +/** + */ +bool strategyNexthopState_IsValid(const StrategyNexthopState *instance); + +/** + */ +void strategyNexthopState_Release(StrategyNexthopState **instancePtr); + +/** + */ +PARCJSON *strategyNexthopState_ToJSON(const StrategyNexthopState *instance); + +/** + */ +char *strategyNexthopState_ToString(const StrategyNexthopState *instance); + +/** + */ +unsigned strategyNexthopState_GetPI(const StrategyNexthopState *x); + +double strategyNexthopState_GetAvgPI(const StrategyNexthopState *x); + +double strategyNexthopState_GetWeight(const StrategyNexthopState *x); + +double strategyNexthopState_UpdateState(StrategyNexthopState *x, bool inc, double alpha); +#endif diff --git a/metis/ccnx/forwarder/metis/strategies/strategy_NexthopStateWithPD.c b/metis/ccnx/forwarder/metis/strategies/strategy_NexthopStateWithPD.c new file mode 100644 index 00000000..e93c5b6b --- /dev/null +++ b/metis/ccnx/forwarder/metis/strategies/strategy_NexthopStateWithPD.c @@ -0,0 +1,276 @@ +/* + * 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 <stdio.h> +#include <config.h> +#include <float.h> +#include <limits.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_Object.h> +#include <parc/algol/parc_DisplayIndented.h> + +#include <ccnx/forwarder/metis/strategies/strategy_NexthopStateWithPD.h> + +struct strategy_nexthop_state_with_pd { + unsigned int pi; + unsigned delay; + double weight; + double avg_pi; +}; + +static bool +_strategyNexthopStateWithPD_Destructor(StrategyNexthopStateWithPD **instancePtr) +{ + return true; +} + +parcObject_ImplementAcquire(strategyNexthopStateWithPD, StrategyNexthopStateWithPD); + +parcObject_ImplementRelease(strategyNexthopStateWithPD, StrategyNexthopStateWithPD); + +parcObject_Override(StrategyNexthopStateWithPD, PARCObject, + .destructor = (PARCObjectDestructor *) _strategyNexthopStateWithPD_Destructor, + .copy = (PARCObjectCopy *) strategyNexthopStateWithPD_Copy, + .display = (PARCObjectDisplay *) strategyNexthopStateWithPD_Display, + .toString = (PARCObjectToString *) strategyNexthopStateWithPD_ToString, + .equals = (PARCObjectEquals *) strategyNexthopStateWithPD_Equals, + .compare = (PARCObjectCompare *) strategyNexthopStateWithPD_Compare, + .hashCode = (PARCObjectHashCode *) strategyNexthopStateWithPD_HashCode, + .toJSON = (PARCObjectToJSON *) strategyNexthopStateWithPD_ToJSON, + .display = (PARCObjectDisplay *) strategyNexthopStateWithPD_Display); + +void +strategyNexthopStateWithPD_AssertValid(const StrategyNexthopStateWithPD *instance) +{ + assertTrue(strategyNexthopStateWithPD_IsValid(instance), + "StrategyNexthopStateWithPD is not valid."); +} + +StrategyNexthopStateWithPD * +strategyNexthopStateWithPD_Create() +{ + StrategyNexthopStateWithPD *result = parcObject_CreateInstance(StrategyNexthopStateWithPD); + if (result != NULL) { + result->pi = 0; + result->avg_pi = 1.0; + result->weight = 1; + result->delay = 0; + } + return result; +} + +void +strategyNexthopStateWithPD_Reset(StrategyNexthopStateWithPD *x) +{ + x->pi = 0; + x->avg_pi = 1.0; + x->weight = 1; + x->delay = 0; +} + +int +strategyNexthopStateWithPD_Compare(const StrategyNexthopStateWithPD *val, const StrategyNexthopStateWithPD *other) +{ + if (val == NULL) { + if (other != NULL) { + return -1; + } + } else if (other == NULL) { + return 1; + } else { + strategyNexthopStateWithPD_OptionalAssertValid(val); + strategyNexthopStateWithPD_OptionalAssertValid(other); + + if (val->pi < other->pi) { + return -1; + } else if (val->pi > other->pi) { + return 1; + } + + if (val->avg_pi < other->avg_pi) { + return -1; + } else if (val->avg_pi > other->avg_pi) { + return 1; + } + + if (val->weight < other->weight) { + return -1; + } else if (val->weight > other->weight) { + return 1; + } + + if (val->delay < other->delay) { + return -1; + } else if (val->delay > other->delay) { + return 1; + } + } + + return 0; +} + +StrategyNexthopStateWithPD * +strategyNexthopStateWithPD_Copy(const StrategyNexthopStateWithPD *original) +{ + StrategyNexthopStateWithPD *result = strategyNexthopStateWithPD_Create(); + result->pi = original->pi; + result->avg_pi = original->avg_pi; + result->weight = original->weight; + result->delay = original->delay; + + return result; +} + +void +strategyNexthopStateWithPD_Display(const StrategyNexthopStateWithPD *instance, int indentation) +{ + parcDisplayIndented_PrintLine(indentation, "StrategyNexthopStateWithPD@%p {", instance); + parcDisplayIndented_PrintLine(indentation + 1, "%d", instance->pi); + parcDisplayIndented_PrintLine(indentation + 1, "%f", instance->avg_pi); + parcDisplayIndented_PrintLine(indentation + 1, "%f", instance->weight); + parcDisplayIndented_PrintLine(indentation + 1, "%f", instance->delay); + parcDisplayIndented_PrintLine(indentation, "}"); +} + +bool +strategyNexthopStateWithPD_Equals(const StrategyNexthopStateWithPD *x, const StrategyNexthopStateWithPD *y) +{ + bool result = false; + + if (x == y) { + result = true; + } else if (x == NULL || y == NULL) { + result = false; + } else { + strategyNexthopStateWithPD_OptionalAssertValid(x); + strategyNexthopStateWithPD_OptionalAssertValid(y); + + if (strategyNexthopStateWithPD_Compare(x, y) == 0) { + result = true; + } + } + + return result; +} + +PARCHashCode +strategyNexthopStateWithPD_HashCode(const StrategyNexthopStateWithPD *x) +{ + PARCHashCode result = 0; + char str[128]; + sprintf(str, "PI:%d: AVG_PI:%f: W:%f D:%d", x->pi, x->avg_pi, x->weight, x->delay); + result = parcHashCode_Hash((uint8_t *) &str, strlen(str)); + return result; +} + +bool +strategyNexthopStateWithPD_IsValid(const StrategyNexthopStateWithPD *x) +{ + bool result = false; + + if (x != NULL) { + result = true; + } + + return result; +} + +PARCJSON * +strategyNexthopStateWithPD_ToJSON(const StrategyNexthopStateWithPD *x) +{ + PARCJSON *result = parcJSON_Create(); + //TODO! + + return result; +} + +char * +strategyNexthopStateWithPD_ToString(const StrategyNexthopStateWithPD *x) +{ + //this is not implemented + trapNotImplemented("strategyNexthopStateWithPD_ToString is not implemented"); + return NULL; +} + +unsigned +strategyNexthopStateWithPD_GetPI(const StrategyNexthopStateWithPD *x) +{ + strategyNexthopStateWithPD_OptionalAssertValid(x); + + return x->pi; +} + +double +strategyNexthopStateWithPD_GetAvgPI(const StrategyNexthopStateWithPD *x) +{ + strategyNexthopStateWithPD_OptionalAssertValid(x); + + return x->avg_pi; +} + +double +strategyNexthopStateWithPD_GetWeight(const StrategyNexthopStateWithPD *x) +{ + strategyNexthopStateWithPD_OptionalAssertValid(x); + + return x->weight; +} + +unsigned +strategyNexthopStateWithPD_GetDelay(const StrategyNexthopStateWithPD *x) +{ + strategyNexthopStateWithPD_OptionalAssertValid(x); + + return x->delay; +} + +void +strategyNexthopStateWithPD_SetDelay(StrategyNexthopStateWithPD *x, unsigned delay) +{ + strategyNexthopStateWithPD_OptionalAssertValid(x); + if (delay != 0) { + x->delay = delay; + } +} + +double +strategyNexthopStateWithPD_UpdateState(StrategyNexthopStateWithPD *x, bool inc, unsigned min_delay, double alpha) +{ + strategyNexthopStateWithPD_OptionalAssertValid(x); + + if (inc) { + x->pi++; + } else { + if (x->pi > 0) { + x->pi--; + } + } + + x->avg_pi = (x->avg_pi * alpha) + (x->pi * (1 - alpha)); + if (x->avg_pi == 0.0) { + x->avg_pi = 0.1; + } + + double factor = 1.0; + if (min_delay != INT_MAX && x->delay != 0) { + factor = ((double) min_delay / (double) x->delay); + } + + x->weight = 1 / (x->avg_pi * factor); + + return x->weight; +} + diff --git a/metis/ccnx/forwarder/metis/strategies/strategy_NexthopStateWithPD.h b/metis/ccnx/forwarder/metis/strategies/strategy_NexthopStateWithPD.h new file mode 100644 index 00000000..ef248f67 --- /dev/null +++ b/metis/ccnx/forwarder/metis/strategies/strategy_NexthopStateWithPD.h @@ -0,0 +1,94 @@ +/* + * 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_strategy_nexthopstatewithpd_h +#define Metis_strategy_nexthopstatewithpd_h + +#include <parc/algol/parc_JSON.h> +#include <parc/algol/parc_HashCode.h> +#include <parc/algol/parc_Object.h> + +struct strategy_nexthop_state_with_pd; +typedef struct strategy_nexthop_state_with_pd StrategyNexthopStateWithPD; +extern parcObjectDescriptor_Declaration(StrategyNexthopStateWithPD); + +/** + */ +StrategyNexthopStateWithPD *strategyNexthopStateWithPD_Acquire(const StrategyNexthopStateWithPD *instance); + +#ifdef PARCLibrary_DISABLE_VALIDATION +# define strategyNexthopStateWithPD_OptionalAssertValid(_instance_) +#else +# define strategyNexthopStateWithPD_OptionalAssertValid(_instance_) strategyNexthopStateWithPD_AssertValid(_instance_) +#endif + +/** + */ +void strategyNexthopStateWithPD_AssertValid(const StrategyNexthopStateWithPD *instance); + +/** + */ +StrategyNexthopStateWithPD *strategyNexthopStateWithPD_Create(); + +void strategyNexthopStateWithPD_Reset(StrategyNexthopStateWithPD *x); +/** + */ +int strategyNexthopStateWithPD_Compare(const StrategyNexthopStateWithPD *instance, const StrategyNexthopStateWithPD *other); + +/** + */ +StrategyNexthopStateWithPD *strategyNexthopStateWithPD_Copy(const StrategyNexthopStateWithPD *original); + +/** + */ +void strategyNexthopStateWithPD_Display(const StrategyNexthopStateWithPD *instance, int indentation); + +/** + */ +bool strategyNexthopStateWithPD_Equals(const StrategyNexthopStateWithPD *x, const StrategyNexthopStateWithPD *y); + +/** + */ +PARCHashCode strategyNexthopStateWithPD_HashCode(const StrategyNexthopStateWithPD *instance); + +/** + */ +bool strategyNexthopStateWithPD_IsValid(const StrategyNexthopStateWithPD *instance); + +/** + */ +void strategyNexthopStateWithPD_Release(StrategyNexthopStateWithPD **instancePtr); + +/** + */ +PARCJSON *strategyNexthopStateWithPD_ToJSON(const StrategyNexthopStateWithPD *instance); + +/** + */ +char *strategyNexthopStateWithPD_ToString(const StrategyNexthopStateWithPD *instance); + +/** + */ +unsigned strategyNexthopStateWithPD_GetPI(const StrategyNexthopStateWithPD *x); + +double strategyNexthopStateWithPD_GetAvgPI(const StrategyNexthopStateWithPD *x); + +double strategyNexthopStateWithPD_GetWeight(const StrategyNexthopStateWithPD *x); + +unsigned strategyNexthopStateWithPD_GetDelay(const StrategyNexthopStateWithPD *x); +void strategyNexthopStateWithPD_SetDelay(StrategyNexthopStateWithPD *x, unsigned delay); + +double strategyNexthopStateWithPD_UpdateState(StrategyNexthopStateWithPD *x, bool inc, unsigned min_delay, double alpha); +#endif diff --git a/metis/ccnx/forwarder/metis/strategies/strategy_Rnd.c b/metis/ccnx/forwarder/metis/strategies/strategy_Rnd.c new file mode 100644 index 00000000..a170b9b7 --- /dev/null +++ b/metis/ccnx/forwarder/metis/strategies/strategy_Rnd.c @@ -0,0 +1,191 @@ +/* + * 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 <time.h> +#include <stdlib.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_HashMap.h> + +#include <ccnx/forwarder/metis/strategies/strategy_Rnd.h> + + +static void _strategyRnd_ReceiveObject(MetisStrategyImpl *strategy, const MetisNumberSet *egressId, const MetisMessage *objectMessage, MetisTicks rtt); +static void _strategyRnd_OnTimeout(MetisStrategyImpl *strategy, const MetisNumberSet *egressId); +static MetisNumberSet *_strategyRnd_LookupNexthop(MetisStrategyImpl *strategy, const MetisMessage *interestMessage); +static MetisNumberSet *_strategyRnd_ReturnNexthops(MetisStrategyImpl *strategy); +static unsigned _strategyRnd_CountNexthops(MetisStrategyImpl *strategy); +static void _strategyRnd_AddNexthop(MetisStrategyImpl *strategy, CPIRouteEntry *route); +static void _strategyRnd_RemoveNexthop(MetisStrategyImpl *strategy, CPIRouteEntry *route); +static void _strategyRnd_ImplDestroy(MetisStrategyImpl **strategyPtr); +static const char *_strategyRnd_GetStrategy(MetisStrategyImpl *strategy); + +static MetisStrategyImpl _template = { + .context = NULL, + .receiveObject = &_strategyRnd_ReceiveObject, + .onTimeout = &_strategyRnd_OnTimeout, + .lookupNexthop = &_strategyRnd_LookupNexthop, + .returnNexthops = &_strategyRnd_ReturnNexthops, + .countNexthops = &_strategyRnd_CountNexthops, + .addNexthop = &_strategyRnd_AddNexthop, + .removeNexthop = &_strategyRnd_RemoveNexthop, + .destroy = &_strategyRnd_ImplDestroy, + .getStrategy = &_strategyRnd_GetStrategy, +}; + +struct strategy_rnd; +typedef struct strategy_rnd StrategyRnd; + + +struct strategy_rnd { + MetisNumberSet *nexthops; +}; + +MetisStrategyImpl * +strategyRnd_Create() +{ + StrategyRnd *strategy = parcMemory_AllocateAndClear(sizeof(StrategyRnd)); + assertNotNull(strategy, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(StrategyRnd)); + + strategy->nexthops = metisNumberSet_Create(); + srand(time(NULL)); + + MetisStrategyImpl *impl = parcMemory_AllocateAndClear(sizeof(MetisStrategyImpl)); + assertNotNull(impl, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisStrategyImpl)); + memcpy(impl, &_template, sizeof(MetisStrategyImpl)); + impl->context = strategy; + + return impl; +} + +// ======================================================= +// Dispatch API + +static const char* +_strategyRnd_GetStrategy(MetisStrategyImpl *strategy) +{ + return FWD_STRATEGY_RANDOM; +} + + +static int +_select_Nexthop(StrategyRnd *strategy) +{ + unsigned len = metisNumberSet_Length(strategy->nexthops); + if (len == 0) { + return -1; + } + + int rnd = (rand() % len); + return metisNumberSet_GetItem(strategy->nexthops, rnd); +} + + +static void +_strategyRnd_ReceiveObject(MetisStrategyImpl *strategy, const MetisNumberSet *egressId, const MetisMessage *objectMessage, MetisTicks rtt) +{ +} + +static void +_strategyRnd_OnTimeout(MetisStrategyImpl *strategy, const MetisNumberSet *egressId) +{ +} + +//ATTENTION!! This interface force us to create a MetisNumberSet which need to be delited somewhere (maybe in the FIB where we call this function) +//The specification in the interface requires that this function never returns NULL. in case we have no output face we need to return an empty MetisNumberSet +static MetisNumberSet * +_strategyRnd_LookupNexthop(MetisStrategyImpl *strategy, const MetisMessage *interestMessage) +{ + StrategyRnd *srnd = (StrategyRnd *) strategy->context; + + unsigned in_connection = metisMessage_GetIngressConnectionId(interestMessage); + unsigned nexthopSize = metisNumberSet_Length(srnd->nexthops); + + MetisNumberSet *out = metisNumberSet_Create(); + if ((nexthopSize == 0) || ((nexthopSize == 1) && metisNumberSet_Contains(srnd->nexthops, in_connection))) { + //there are no output faces or the input face is also the only output face. return null to avoid loops + return out; + } + + unsigned out_connection; + do { + out_connection = _select_Nexthop(srnd); + } while (out_connection == in_connection); + + if (out_connection == -1) { + return out; + } + + metisNumberSet_Add(out, out_connection); + return out; +} + +static MetisNumberSet * +_strategyRnd_ReturnNexthops(MetisStrategyImpl *strategy) +{ + StrategyRnd *srnd = (StrategyRnd *) strategy->context; + return srnd->nexthops; +} + +unsigned +_strategyRnd_CountNexthops(MetisStrategyImpl *strategy) +{ + StrategyRnd *srnd = (StrategyRnd *) strategy->context; + return metisNumberSet_Length(srnd->nexthops); +} + +static void +_strategyRnd_AddNexthop(MetisStrategyImpl *strategy, CPIRouteEntry *route) +{ + unsigned connectionId = cpiRouteEntry_GetInterfaceIndex(route); //this returns what in all the rest of the ccnx code is called connection id! + + StrategyRnd *srnd = (StrategyRnd *) strategy->context; + if (!metisNumberSet_Contains(srnd->nexthops, connectionId)) { + metisNumberSet_Add(srnd->nexthops, connectionId); + } +} + +static void +_strategyRnd_RemoveNexthop(MetisStrategyImpl *strategy, CPIRouteEntry *route) +{ + unsigned connectionId = cpiRouteEntry_GetInterfaceIndex(route); + StrategyRnd *srnd = (StrategyRnd *) strategy->context; + + if (metisNumberSet_Contains(srnd->nexthops, connectionId)) { + metisNumberSet_Remove(srnd->nexthops, connectionId); + } +} + +static void +_strategyRnd_ImplDestroy(MetisStrategyImpl **strategyPtr) +{ + assertNotNull(strategyPtr, "Parameter must be non-null double pointer"); + assertNotNull(*strategyPtr, "Parameter must dereference to non-null pointer"); + + MetisStrategyImpl *impl = *strategyPtr; + StrategyRnd *strategy = (StrategyRnd *) impl->context; + + metisNumberSet_Release(&(strategy->nexthops)); + + parcMemory_Deallocate((void **) &strategy); + parcMemory_Deallocate((void **) &impl); + *strategyPtr = NULL; +} diff --git a/metis/ccnx/forwarder/metis/strategies/strategy_Rnd.h b/metis/ccnx/forwarder/metis/strategies/strategy_Rnd.h new file mode 100644 index 00000000..b3d60da5 --- /dev/null +++ b/metis/ccnx/forwarder/metis/strategies/strategy_Rnd.h @@ -0,0 +1,28 @@ +/* + * 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. + */ + + + +/** + * Forward randomly + */ + +#ifndef Metis_strategy_Rnd_h +#define Metis_strategy_Rnd_h + +#include <ccnx/forwarder/metis/strategies/metis_Strategy.h> + +MetisStrategyImpl * strategyRnd_Create(); +#endif // Metis_strategy_Rnd_h diff --git a/metis/ccnx/forwarder/metis/strategies/strategy_RndSegment.c b/metis/ccnx/forwarder/metis/strategies/strategy_RndSegment.c new file mode 100644 index 00000000..d3fd5dab --- /dev/null +++ b/metis/ccnx/forwarder/metis/strategies/strategy_RndSegment.c @@ -0,0 +1,226 @@ +/* + * 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 <time.h> +#include <stdlib.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_HashMap.h> +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> +#include <ccnx/forwarder/metis/strategies/strategy_RndSegment.h> + + +static void _strategyRndSegment_ReceiveObject(MetisStrategyImpl *strategy, const MetisNumberSet *egressId, const MetisMessage *objectMessage, MetisTicks rtt); +static void _strategyRndSegment_OnTimeout(MetisStrategyImpl *strategy, const MetisNumberSet *egressId); +static MetisNumberSet *_strategyRndSegment_LookupNexthop(MetisStrategyImpl *strategy, const MetisMessage *interestMessage); +static MetisNumberSet *_strategyRndSegment_ReturnNexthops(MetisStrategyImpl *strategy); +static unsigned _strategyRndSegment_CountNexthops(MetisStrategyImpl *strategy); +static void _strategyRndSegment_AddNexthop(MetisStrategyImpl *strategy, CPIRouteEntry *route); +static void _strategyRndSegment_RemoveNexthop(MetisStrategyImpl *strategy, CPIRouteEntry *route); +static void _strategyRndSegment_ImplDestroy(MetisStrategyImpl **strategyPtr); +static const char *_strategyRndSegment_GetStrategy(MetisStrategyImpl *strategy); + +static MetisStrategyImpl _template = { + .context = NULL, + .receiveObject = &_strategyRndSegment_ReceiveObject, + .onTimeout = &_strategyRndSegment_OnTimeout, + .lookupNexthop = &_strategyRndSegment_LookupNexthop, + .returnNexthops = &_strategyRndSegment_ReturnNexthops, + .countNexthops = &_strategyRndSegment_CountNexthops, + .addNexthop = &_strategyRndSegment_AddNexthop, + .removeNexthop = &_strategyRndSegment_RemoveNexthop, + .destroy = &_strategyRndSegment_ImplDestroy, + .getStrategy = &_strategyRndSegment_GetStrategy, +}; + +struct strategy_rnd_segment; +typedef struct strategy_rnd_segment StrategyRndSegment; + + +struct strategy_rnd_segment { + MetisNumberSet *nexthops; + MetisTlvName *segmentName; + int last_used_face; +}; + +MetisStrategyImpl * +strategyRndSegment_Create() +{ + StrategyRndSegment *strategy = parcMemory_AllocateAndClear(sizeof(StrategyRndSegment)); + assertNotNull(strategy, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(StrategyRndSegment)); + + strategy->nexthops = metisNumberSet_Create(); + strategy->segmentName = NULL; + strategy->last_used_face = 0; + srand(time(NULL)); + + MetisStrategyImpl *impl = parcMemory_AllocateAndClear(sizeof(MetisStrategyImpl)); + assertNotNull(impl, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisStrategyImpl)); + memcpy(impl, &_template, sizeof(MetisStrategyImpl)); + impl->context = strategy; + + return impl; +} + +// ======================================================= +// Dispatch API + + +static const char* +_strategyRndSegment_GetStrategy(MetisStrategyImpl *strategy) +{ + return FWD_STRATEGY_RANDOM_PER_DASH_SEGMENT; +} + + +static int +_select_Nexthop(StrategyRndSegment *strategy) +{ + unsigned len = metisNumberSet_Length(strategy->nexthops); + if (len == 0) { + return -1; + } + + int rnd = (rand() % len); + return metisNumberSet_GetItem(strategy->nexthops, rnd); +} + + +static void +_strategyRndSegment_ReceiveObject(MetisStrategyImpl *strategy, const MetisNumberSet *egressId, const MetisMessage *objectMessage, MetisTicks rtt) +{ +} + +static void +_strategyRndSegment_OnTimeout(MetisStrategyImpl *strategy, const MetisNumberSet *egressId) +{ +} + +//ATTENTION!! This interface force us to create a MetisNumberSet which need to be delited somewhere (maybe in the FIB where we call this function) +//The specification in the interface requires that this function never returns NULL. in case we have no output face we need to return an empty MetisNumberSet +static MetisNumberSet * +_strategyRndSegment_LookupNexthop(MetisStrategyImpl *strategy, const MetisMessage *interestMessage) +{ + StrategyRndSegment *srnd = (StrategyRndSegment *) strategy->context; + + + unsigned in_connection = metisMessage_GetIngressConnectionId(interestMessage); + unsigned nexthopSize = metisNumberSet_Length(srnd->nexthops); + + MetisNumberSet *out = metisNumberSet_Create(); + if ((nexthopSize == 0) || ((nexthopSize == 1) && metisNumberSet_Contains(srnd->nexthops, in_connection))) { + //there are no output faces or the input face is also the only output face. return null to avoid loops + return out; + } + + if (metisMessage_HasName(interestMessage)) { + MetisTlvName *interestName = metisMessage_GetName(interestMessage); + size_t sc = metisTlvName_SegmentCount(interestName); + interestName = metisTlvName_Slice(interestName, (sc - 1)); + + if (srnd->segmentName == NULL) { + srnd->segmentName = interestName; + } else if (!metisTlvName_Equals(srnd->segmentName, interestName)) { + metisTlvName_Release(&srnd->segmentName); + srnd->segmentName = interestName; + } else { + //here we need to check if the output face still exists or if someone erase it + if (metisNumberSet_Contains(srnd->nexthops, srnd->last_used_face)) { + // face exists, so keep using it! + metisTlvName_Release(&interestName); + metisNumberSet_Add(out, srnd->last_used_face); + return out; + } else { + //the face does not exists anymore, try to find a new face but keep the name + //of the dash segment + metisTlvName_Release(&interestName); + } + } + } + + int out_connection; + do { + out_connection = _select_Nexthop(srnd); + } while (out_connection == in_connection); + + if (out_connection == -1) { + return out; + } + + srnd->last_used_face = out_connection; + metisNumberSet_Add(out, out_connection); + return out; +} + +static MetisNumberSet * +_strategyRndSegment_ReturnNexthops(MetisStrategyImpl *strategy) +{ + StrategyRndSegment *srnd = (StrategyRndSegment *) strategy->context; + return srnd->nexthops; +} + +unsigned +_strategyRndSegment_CountNexthops(MetisStrategyImpl *strategy) +{ + StrategyRndSegment *srnd = (StrategyRndSegment *) strategy->context; + return metisNumberSet_Length(srnd->nexthops); +} + +static void +_strategyRndSegment_AddNexthop(MetisStrategyImpl *strategy, CPIRouteEntry *route) +{ + unsigned connectionId = cpiRouteEntry_GetInterfaceIndex(route); //this returns what in all the rest of the ccnx code is called connection id! + + StrategyRndSegment *srnd = (StrategyRndSegment *) strategy->context; + if (!metisNumberSet_Contains(srnd->nexthops, connectionId)) { + metisNumberSet_Add(srnd->nexthops, connectionId); + } +} + +static void +_strategyRndSegment_RemoveNexthop(MetisStrategyImpl *strategy, CPIRouteEntry *route) +{ + unsigned connectionId = cpiRouteEntry_GetInterfaceIndex(route); + StrategyRndSegment *srnd = (StrategyRndSegment *) strategy->context; + + if (metisNumberSet_Contains(srnd->nexthops, connectionId)) { + metisNumberSet_Remove(srnd->nexthops, connectionId); + } +} + +static void +_strategyRndSegment_ImplDestroy(MetisStrategyImpl **strategyPtr) +{ + assertNotNull(strategyPtr, "Parameter must be non-null double pointer"); + assertNotNull(*strategyPtr, "Parameter must dereference to non-null pointer"); + + MetisStrategyImpl *impl = *strategyPtr; + StrategyRndSegment *strategy = (StrategyRndSegment *) impl->context; + + metisNumberSet_Release(&(strategy->nexthops)); + if (strategy->segmentName != NULL) { + metisTlvName_Release(&strategy->segmentName); + } + + parcMemory_Deallocate((void **) &strategy); + parcMemory_Deallocate((void **) &impl); + *strategyPtr = NULL; +} diff --git a/metis/ccnx/forwarder/metis/strategies/strategy_RndSegment.h b/metis/ccnx/forwarder/metis/strategies/strategy_RndSegment.h new file mode 100644 index 00000000..8feed482 --- /dev/null +++ b/metis/ccnx/forwarder/metis/strategies/strategy_RndSegment.h @@ -0,0 +1,26 @@ +/* + * 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. + */ + +/** + * Forward randomly, selects a path every time the client ask for a new dash segment + */ + +#ifndef Metis_strategy_Rnd_Segment_h +#define Metis_strategy_Rnd_Segment_h + +#include <ccnx/forwarder/metis/strategies/metis_Strategy.h> + +MetisStrategyImpl * strategyRndSegment_Create(); +#endif // Metis_strategy_Rnd_Segment_h diff --git a/metis/ccnx/forwarder/metis/strategies/strategy_WeightedRoundRobin.h b/metis/ccnx/forwarder/metis/strategies/strategy_WeightedRoundRobin.h new file mode 100644 index 00000000..ae44768e --- /dev/null +++ b/metis/ccnx/forwarder/metis/strategies/strategy_WeightedRoundRobin.h @@ -0,0 +1,28 @@ +/* + * 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. + */ + + +/** + * Weighted round robin on multiple interfaces based on their performance + * THIS STRATEGY IS NOT IMPLEMENTED + */ + +#ifndef Metis_strategy_WeightedRoundRobin_h +#define Metis_strategy_WeightedRoundRobin_h + +#include <ccnx/forwarder/metis/strategy/metis_Strategy.h> + +MetisStrategyImpl *metisStrategyWrr_Create(); +#endif // Metis_strategy_WeightedRoundRobin_h diff --git a/metis/ccnx/forwarder/metis/strategies/test/CMakeLists.txt b/metis/ccnx/forwarder/metis/strategies/test/CMakeLists.txt new file mode 100644 index 00000000..5e4f6928 --- /dev/null +++ b/metis/ccnx/forwarder/metis/strategies/test/CMakeLists.txt @@ -0,0 +1,13 @@ +# Enable gcov output for the tests +add_definitions(--coverage) +set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} " --coverage") + +set(TestsExpectedToPass + test_strategy_All +) + + +foreach(test ${TestsExpectedToPass}) + AddTest(${test}) +endforeach() + diff --git a/metis/ccnx/forwarder/metis/strategies/test/test_strategy_All.c b/metis/ccnx/forwarder/metis/strategies/test/test_strategy_All.c new file mode 100644 index 00000000..ef3118a0 --- /dev/null +++ b/metis/ccnx/forwarder/metis/strategies/test/test_strategy_All.c @@ -0,0 +1,132 @@ +/* + * 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 "../strategy_All.c" +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(strategy_All) +{ + // 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(strategy_All) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(strategy_All) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, strategyAll_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, strategyAll_Create) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, __llvm_gcov_flush); + LONGBOW_RUN_TEST_CASE(Local, __llvm_gcov_init); + LONGBOW_RUN_TEST_CASE(Local, __llvm_gcov_writeout); + LONGBOW_RUN_TEST_CASE(Local, strategyAll_AddNexthop); + LONGBOW_RUN_TEST_CASE(Local, strategyAll_ImplDestroy); + LONGBOW_RUN_TEST_CASE(Local, strategyAll_LookupNexthop); + LONGBOW_RUN_TEST_CASE(Local, strategyAll_ReceiveObject); + LONGBOW_RUN_TEST_CASE(Local, strategyAll_RemoveNexthop); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, __llvm_gcov_flush) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, __llvm_gcov_init) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, __llvm_gcov_writeout) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, strategyAll_AddNexthop) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, strategyAll_ImplDestroy) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, strategyAll_LookupNexthop) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, strategyAll_ReceiveObject) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, strategyAll_RemoveNexthop) +{ + testUnimplemented("This test is unimplemented"); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(strategy_All); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/test/CMakeLists.txt b/metis/ccnx/forwarder/metis/test/CMakeLists.txt new file mode 100644 index 00000000..9cdcac48 --- /dev/null +++ b/metis/ccnx/forwarder/metis/test/CMakeLists.txt @@ -0,0 +1,16 @@ +# Enable gcov output for the tests +add_definitions(--coverage) +set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} " --coverage") + +set(TestsExpectedToPass + test_sys_Errors + test_sys_EtherEndToEnd + test_sys_TcpEndToEnd + test_sys_UdpEndToEnd + test_sys_TcpTunnel +) + +foreach(test ${TestsExpectedToPass}) + AddTest(${test}) +endforeach() + diff --git a/metis/ccnx/forwarder/metis/test/test_sys_Errors.c b/metis/ccnx/forwarder/metis/test/test_sys_Errors.c new file mode 100644 index 00000000..956348b1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/test/test_sys_Errors.c @@ -0,0 +1,293 @@ +/* + * 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 are tests for error conditions, mostly in packet formats. + */ + +#include <config.h> +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <netinet/in.h> +#include <stdlib.h> +#include <arpa/inet.h> +#include <errno.h> +#include <net/ethernet.h> + +#include <ccnx/api/control/cpi_RouteEntry.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/io/metis_UdpListener.h> +#include <ccnx/forwarder/metis/io/metis_TcpListener.h> +#include <ccnx/forwarder/metis/io/metis_EtherListener.h> +#include <ccnx/forwarder/metis/io/metis_EtherConnection.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h> +#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h> + +#include <ccnx/forwarder/metis/io/test/testrig_GenericEther.c> + +LONGBOW_TEST_RUNNER(sys_Errors) +{ + LONGBOW_RUN_TEST_FIXTURE(Global); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(sys_Errors) +{ + 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(sys_Errors) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ---- Used to monitor Missive messages so we know when +// a connection is setup +static struct test_notifier_data { + MetisMissiveType type; + unsigned connectionid; +} testNotifierData; + +static void +_testNotifier(MetisMessengerRecipient *recipient, MetisMissive *missive) +{ + struct test_notifier_data *data = metisMessengerRecipient_GetRecipientContext(recipient); + data->type = metisMissive_GetType(missive); + data->connectionid = metisMissive_GetConnectionId(missive); + metisMissive_Release(&missive); +} + +// ---- Utility functions to setup endpoints + +static void +_setupListener(MetisForwarder *metis, uint16_t port, MetisEncapType type) +{ + struct sockaddr_in addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + inet_pton(AF_INET, "127.0.0.1", &(addr.sin_addr)); + + MetisListenerSet *listeners = metisForwarder_GetListenerSet(metis); + MetisListenerOps *ops = NULL; + + switch (type) { + case METIS_ENCAP_UDP: + ops = metisUdpListener_CreateInet(metis, addr); + break; + + case METIS_ENCAP_TCP: + ops = metisTcpListener_CreateInet(metis, addr); + break; + + case METIS_ENCAP_ETHER: + ops = metisEtherListener_Create(metis, "fake0", port); + break; + + default: + trapUnexpectedState("Unsupported "); + } + + assertNotNull(ops, "Got null io operations"); + + metisListenerSet_Add(listeners, ops); + + // crank the handle once + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); +} + +static int +_setupInetClient(MetisForwarder *metis, uint16_t port, int type) +{ + struct sockaddr_in addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + inet_pton(AF_INET, "127.0.0.1", &(addr.sin_addr)); + + int fd = socket(PF_INET, type, 0); + assertFalse(fd < 0, "Error on socket: (%d) %s", errno, strerror(errno)); + + int failure = connect(fd, (struct sockaddr *) &addr, sizeof(addr)); + assertFalse(failure, "Error on connect: (%d) %s", errno, strerror(errno)); + + // crank the handle once + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + return fd; +} + +// ---- Global state used by tests + +typedef struct test_data { + MetisForwarder *metis; + MetisMessengerRecipient *recipient; + + int fd_sender; +} TestData; + +static void +_commonSetup(const LongBowTestCase *testCase) +{ + TestData *data = parcMemory_Allocate(sizeof(TestData)); + + data->metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(data->metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + metisLogger_SetLogLevel(metisForwarder_GetLogger(data->metis), MetisLoggerFacility_Message, PARCLogLevel_Debug); + metisLogger_SetLogLevel(metisForwarder_GetLogger(data->metis), MetisLoggerFacility_Core, PARCLogLevel_Debug); + metisLogger_SetLogLevel(metisForwarder_GetLogger(data->metis), MetisLoggerFacility_Processor, PARCLogLevel_Debug); + + data->recipient = metisMessengerRecipient_Create(&testNotifierData, _testNotifier); + + // register a messenger callback so we know when the connections get setup + MetisMessenger *messenger = metisForwarder_GetMessenger(data->metis); + metisMessenger_Register(messenger, data->recipient); + + longBowTestCase_SetClipBoardData(testCase, data); +} + +static void +_setupInetEncap(const LongBowTestCase *testCase, uint16_t port, MetisEncapType encap) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + _setupListener(data->metis, port, encap); + + // create a client + switch (encap) { + case METIS_ENCAP_UDP: + data->fd_sender = _setupInetClient(data->metis, port, SOCK_DGRAM); + break; + + case METIS_ENCAP_TCP: + data->fd_sender = _setupInetClient(data->metis, port, SOCK_STREAM); + break; + + default: + trapUnexpectedState("Unsupported encap type: %d", encap); + } + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis), &((struct timeval) { 0, 1000 })); + + + // send something to actually connect it, this is a good packet + ssize_t nwritten = write(data->fd_sender, metisTestDataV1_Interest_NameA_Crc32c, sizeof(metisTestDataV1_Interest_NameA_Crc32c)); + assertTrue(nwritten == sizeof(metisTestDataV1_Interest_NameA_Crc32c), "Short write, expected %zu got %zd", sizeof(metisTestDataV1_Interest_NameA_Crc32c), nwritten); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis), &((struct timeval) { 0, 1000 })); + + printf("sender port %u connection id = %u\n", port, testNotifierData.connectionid); +} + +static void +_commonTeardown(const LongBowTestCase *testCase) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + metisForwarder_Destroy(&data->metis); + metisMessengerRecipient_Destroy(&data->recipient); + + close(data->fd_sender); + parcMemory_Deallocate((void **) &data); +} + +// ========================================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, udp); + LONGBOW_RUN_TEST_CASE(Global, ether); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + _commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + _commonTeardown(testCase); + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, udp) +{ + _setupInetEncap(testCase, 44999, METIS_ENCAP_UDP); + + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + for (int i = 0; metisTestDataV1_ErrorFrames[i].frame != NULL; i++) { + const uint8_t *frame = metisTestDataV1_ErrorFrames[i].frame; + const size_t length = metisTestDataV1_ErrorFrames[i].length; + + printf("Writing frame %d length %zu\n", i, length); + + const ssize_t nwritten = write(data->fd_sender, frame, length); + assertTrue(nwritten == length, "Short write, expected %zu got %zd", length, nwritten); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis), &((struct timeval) { 0, 1000 })); + } +} + +LONGBOW_TEST_CASE(Global, ether) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + _setupListener(data->metis, 0x0801, METIS_ENCAP_ETHER); + + // there's only 1 listener, so it's at index 0 + MetisListenerOps *listener = metisListenerSet_Get(metisForwarder_GetListenerSet(data->metis), 0); + MetisGenericEther *ether = metisEtherListener_GetGenericEtherFromListener(listener); + PARCBuffer *localAddressBuffer = metisGenericEther_GetMacAddress(ether); + uint8_t *dmac = parcBuffer_Overlay(localAddressBuffer, 0); + uint8_t smac[ETHER_ADDR_LEN] = { 1, 2, 3, 4, 5, 6 }; + + // we're now ready to start receiving data. We "send" data to the mock ethernet + // by using the API to the mock GenericEther. + + for (int i = 0; metisTestDataV1_ErrorFrames[i].frame != NULL; i++) { + const uint8_t *frame = metisTestDataV1_ErrorFrames[i].frame; + const size_t length = metisTestDataV1_ErrorFrames[i].length; + + printf("Writing frame %d length %zu\n", i, length); + + PARCBuffer *buffer = mockGenericEther_createFrame(length, frame, dmac, smac, 0x0801); + mockGenericEther_QueueFrame(ether, buffer); + mockGenericEther_Notify(ether); + parcBuffer_Release(&buffer); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis), &((struct timeval) { 0, 1000 })); + } +} + + +// ================================================= + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(sys_Errors); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/test/test_sys_EtherEndToEnd.c b/metis/ccnx/forwarder/metis/test/test_sys_EtherEndToEnd.c new file mode 100644 index 00000000..7061f74c --- /dev/null +++ b/metis/ccnx/forwarder/metis/test/test_sys_EtherEndToEnd.c @@ -0,0 +1,347 @@ +/* + * 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 are end-to-end system tests. They nail up two Ethernet connections, setup a FIB entry, and send + * and interest then a content object back. + * + * To Metis, it looks like it has 2 ethernet interfaces, "fake0" and "fake1". We send an Interest + * in "fake0" and it should come out "fake1", then send a content object back the other way. + * + * Uses the mock GenericEthernet object so there's no actual network required. + * + */ + +#include <config.h> +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <netinet/in.h> +#include <stdlib.h> +#include <arpa/inet.h> +#include <errno.h> + +// include the mock Ethernet implementation +#include "../io/test/testrig_GenericEther.c" +#include <ccnx/forwarder/metis/io/metis_EtherListener.c> + +#include <ccnx/api/control/cpi_RouteEntry.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h> + +#define ETHERTYPE 0x0801 + +LONGBOW_TEST_RUNNER(sys_EtherEndToEnd) +{ + LONGBOW_RUN_TEST_FIXTURE(Global); +} + +LONGBOW_TEST_RUNNER_SETUP(sys_EtherEndToEnd) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_RUNNER_TEARDOWN(sys_EtherEndToEnd) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +/* + * Used to monitor Missive messages so we know when + * a connection is setup + */ +static struct test_notifier_data { + MetisMissiveType type; + unsigned connectionid; +} testNotifierData; + +static void +_testNotifier(MetisMessengerRecipient *recipient, MetisMissive *missive) +{ + struct test_notifier_data *data = metisMessengerRecipient_GetRecipientContext(recipient); + data->type = metisMissive_GetType(missive); + data->connectionid = metisMissive_GetConnectionId(missive); + metisMissive_Release(&missive); +} + +// ---- Utility functions to setup Ethernet endpoints + +static MetisListenerOps * +_setupEtherListener(MetisForwarder *metis, const char *devName, uint16_t ethertype) +{ + MetisListenerSet *listeners = metisForwarder_GetListenerSet(metis); + MetisListenerOps *ops = metisEtherListener_Create(metis, devName, ethertype); + metisListenerSet_Add(listeners, ops); + + // crank the libevent handle + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + return ops; +} + +// ---- Clipboard state used by tests + +typedef struct test_data { + MetisForwarder *metis; + MetisMessengerRecipient *recipient; + +#define CLIENTSIDE 0 +#define SERVERSIDE 1 + + MetisListenerOps *etherListener[2]; + MetisGenericEther *ether[2]; + + // These are the peer addresses off ether0 and ether1 (i.e. the "remote" systems + // that are sending the frames to metis). + uint8_t ether_peerAddress[ETHER_ADDR_LEN][2]; +} TestData; + +static void +_sendPing(TestData *data, int side) +{ + PARCBuffer *ether0_mac = metisGenericEther_GetMacAddress(data->ether[side]); + PARCBuffer *frame = mockGenericEther_createFrame(sizeof(metisTestDataV0_EncodedInterest), metisTestDataV0_EncodedInterest, parcBuffer_Overlay(ether0_mac, 0), data->ether_peerAddress[side], ETHERTYPE); + + mockGenericEther_QueueFrame(data->ether[side], frame); + mockGenericEther_Notify(data->ether[side]); + + parcBuffer_Release(&frame); +} + +static void +_bringUpListener(TestData *data, int side, const char *devName) +{ + data->etherListener[side] = _setupEtherListener(data->metis, devName, ETHERTYPE); + data->ether[side] = ((_MetisEtherListener *) data->etherListener[side]->context)->genericEther; + + // send a ping to Metis to bring up a connection + _sendPing(data, side); + + // crank the dispatcher handle + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis), &((struct timeval) {0, 10000})); + printf("side %d connection id = %u\n", side, testNotifierData.connectionid); +} + +static void +_commonSetup(const LongBowTestCase *testCase) +{ + TestData *data = parcMemory_Allocate(sizeof(TestData)); + + data->metis = metisForwarder_Create(NULL); + + // setup a messenger recpient so we get a notification when the connection is up + data->recipient = metisMessengerRecipient_Create(&testNotifierData, _testNotifier); + + // setup the peer addresses (need to do this before bringing up the listeners) + memset(data->ether_peerAddress[CLIENTSIDE], 0xAA, ETHER_ADDR_LEN); + memset(data->ether_peerAddress[SERVERSIDE], 0xBB, ETHER_ADDR_LEN); + + // register a messenger callback so we know when the connections get setup + MetisMessenger *messenger = metisForwarder_GetMessenger(data->metis); + metisMessenger_Register(messenger, data->recipient); + + _bringUpListener(data, CLIENTSIDE, "fake0"); + + _bringUpListener(data, SERVERSIDE, "fake1"); + + // Add a FIB entry out the receiver connection (testNotifierData.connectionid will be set to the "fake1" connection id + // because it was the last thing to get a missive sent) + CCNxName *ccnxNameToAdd = ccnxName_CreateFromCString("lci:/2=hello/0xF000=ouch"); + CPIRouteEntry *routeAdd = cpiRouteEntry_Create(ccnxNameToAdd, testNotifierData.connectionid, NULL, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, NULL, 1); + metisForwarder_AddOrUpdateRoute(data->metis, routeAdd); + cpiRouteEntry_Destroy(&routeAdd); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis), &((struct timeval) { 0, 10000 })); + + longBowTestCase_SetClipBoardData(testCase, data); +} + +static void +_commonTeardown(const LongBowTestCase *testCase) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + metisForwarder_Destroy(&data->metis); + metisMessengerRecipient_Destroy(&data->recipient); + parcMemory_Deallocate((void **) &data); +} + +// ========================================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, passInterest_Unicast); + LONGBOW_RUN_TEST_CASE(Global, returnContentObject); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + _commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + _commonTeardown(testCase); + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +/* + * Send the Interest to the client socket (ether0). + * Returns the frame sent (with Ethernet header) + */ +static PARCBuffer * +_sendInterest(TestData *data) +{ + PARCBuffer *ether0_mac = metisGenericEther_GetMacAddress(data->ether[CLIENTSIDE]); + PARCBuffer *frame = mockGenericEther_createFrame(sizeof(metisTestDataV0_InterestWithName_keyid), metisTestDataV0_InterestWithName_keyid, parcBuffer_Overlay(ether0_mac, 0), data->ether_peerAddress[CLIENTSIDE], ETHERTYPE); + + mockGenericEther_QueueFrame(data->ether[CLIENTSIDE], frame); + + // wake up the client's side of metis to receive a frame + mockGenericEther_Notify(data->ether[CLIENTSIDE]); + + return frame; +} + +/* + * Receives an Interest Ethernet frame on the server's side of Metis and verifies it matches what was sent + * Has to recompute the hop-limit in the truthFrame, uses magic knowledge of V0 packets. + */ +static void +_receiveInterest(TestData *data, PARCBuffer *truthFrame) +{ + uint8_t receiveBuffer[1024]; + + int serverSocket = mockGenericEther_GetTestDescriptor(data->ether[SERVERSIDE]); + + ssize_t read_length = read(serverSocket, receiveBuffer, 1024); + assertTrue(read_length == parcBuffer_Remaining(truthFrame), + "Incorrect read, expected %zd got %zd: (%d) %s", + parcBuffer_Remaining(truthFrame), read_length, errno, strerror(errno)); + + // skip over the ethernet header + parcBuffer_SetPosition(truthFrame, sizeof(struct ether_header)); + + // We decrement the hoplimit, so need a new truth value + // TODO: There's a lot of magic knowledge that the hoplimit is at byte offset 12 and + // originally had the value 32. + parcBuffer_PutAtIndex(truthFrame, sizeof(struct ether_header) + 12, 31); + + PARCBuffer *testFrame = parcBuffer_Wrap(receiveBuffer, parcBuffer_Limit(truthFrame), sizeof(struct ether_header), parcBuffer_Limit(truthFrame)); + assertTrue(parcBuffer_Equals(truthFrame, testFrame), "Messages do not match") + { + parcBuffer_Display(truthFrame, 3); + parcBuffer_Display(testFrame, 3); + } + + parcBuffer_Release(&testFrame); +} + +/* + * Send the Interest to the client socket (ether0). + * Returns the frame sent (with Ethernet header) + */ +static PARCBuffer * +_sendContentObject(TestData *data) +{ + // metisTestDataV0_EncodedObject has the same name and keyid as in the Interest + + PARCBuffer *ether0_mac = metisGenericEther_GetMacAddress(data->ether[SERVERSIDE]); + PARCBuffer *frame = mockGenericEther_createFrame(sizeof(metisTestDataV0_EncodedObject), metisTestDataV0_EncodedObject, parcBuffer_Overlay(ether0_mac, 0), data->ether_peerAddress[SERVERSIDE], ETHERTYPE); + + mockGenericEther_QueueFrame(data->ether[SERVERSIDE], frame); + + // wake up the server's side of metis to receive a frame + mockGenericEther_Notify(data->ether[SERVERSIDE]); + + return frame; +} + +/* + * Receives a ContentObject Ethernet frame on the client's side of Metis and verifies it matches what was sent + */ +static void +_receiveContentObject(TestData *data, PARCBuffer *truthFrame) +{ + uint8_t receiveBuffer[1024]; + + int serverSocket = mockGenericEther_GetTestDescriptor(data->ether[CLIENTSIDE]); + + ssize_t read_length = read(serverSocket, receiveBuffer, 1024); + assertTrue(read_length == parcBuffer_Remaining(truthFrame), + "Incorrect read, expected %zd got %zd: (%d) %s", + parcBuffer_Remaining(truthFrame), read_length, errno, strerror(errno)); + + // skip over the ethernet header + parcBuffer_SetPosition(truthFrame, sizeof(struct ether_header)); + + PARCBuffer *testFrame = parcBuffer_Wrap(receiveBuffer, parcBuffer_Limit(truthFrame), sizeof(struct ether_header), parcBuffer_Limit(truthFrame)); + assertTrue(parcBuffer_Equals(truthFrame, testFrame), "Messages do not match") + { + parcBuffer_Display(truthFrame, 3); + parcBuffer_Display(testFrame, 3); + } + + parcBuffer_Release(&testFrame); +} + +LONGBOW_TEST_CASE(Global, passInterest_Unicast) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + + PARCBuffer *truthFrame = _sendInterest(data); + + // run for a duration so libevent has time to read the message, pass it off to the handler, then send the message out + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis), &((struct timeval) { 0, 10000 })); + + _receiveInterest(data, truthFrame); + + parcBuffer_Release(&truthFrame); +} + +LONGBOW_TEST_CASE(Global, returnContentObject) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // send the Interest so we have a PIT entry + + PARCBuffer *truthInterest = _sendInterest(data); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis), &((struct timeval) { 0, 10000 })); + _receiveInterest(data, truthInterest); + parcBuffer_Release(&truthInterest); + + // send the content object back + + PARCBuffer *truthContentObject = _sendContentObject(data); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis), &((struct timeval) { 0, 10000 })); + _receiveContentObject(data, truthContentObject); + parcBuffer_Release(&truthContentObject); +} + + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(sys_EtherEndToEnd); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} + diff --git a/metis/ccnx/forwarder/metis/test/test_sys_TcpEndToEnd.c b/metis/ccnx/forwarder/metis/test/test_sys_TcpEndToEnd.c new file mode 100644 index 00000000..2d045110 --- /dev/null +++ b/metis/ccnx/forwarder/metis/test/test_sys_TcpEndToEnd.c @@ -0,0 +1,249 @@ +/* + * 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 are end-to-end system tests. They nail up two TCP connections, setup a FIB entry, and send + * and interest then a content object back. + */ + +#include <config.h> +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <netinet/in.h> +#include <stdlib.h> +#include <arpa/inet.h> +#include <errno.h> +#include <stdio.h> + +#include <ccnx/api/control/cpi_RouteEntry.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/io/metis_TcpListener.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h> + +LONGBOW_TEST_RUNNER(sys_TcpEndToEnd) +{ + // 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); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(sys_TcpEndToEnd) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(sys_TcpEndToEnd) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ---- Used to monitor Missive messages so we know when +// a connection is setup +static struct test_notifier_data { + MetisMissiveType type; + unsigned connectionid; +} testNotifierData; + +static void +testNotifier(MetisMessengerRecipient *recipient, MetisMissive *missive) +{ + struct test_notifier_data *data = metisMessengerRecipient_GetRecipientContext(recipient); + data->type = metisMissive_GetType(missive); + data->connectionid = metisMissive_GetConnectionId(missive); + metisMissive_Release(&missive); +} + +// ---- Utility functions to setup TCP endpoints + +static void +setupInetListener(MetisForwarder *metis, uint16_t port) +{ + struct sockaddr_in addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + inet_pton(AF_INET, "127.0.0.1", &(addr.sin_addr)); + + MetisListenerSet *listeners = metisForwarder_GetListenerSet(metis); + MetisListenerOps *ops = metisTcpListener_CreateInet(metis, addr); + metisListenerSet_Add(listeners, ops); + + // crank the handle + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); +} + +static int +setupInetClient(MetisForwarder *metis, uint16_t port) +{ + struct sockaddr_in addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + inet_pton(AF_INET, "127.0.0.1", &(addr.sin_addr)); + + int fd = socket(PF_INET, SOCK_STREAM, 0); + assertFalse(fd < 0, "Error on socket: (%d) %s", errno, strerror(errno)); + + int failure = connect(fd, (struct sockaddr *) &addr, sizeof(addr)); + assertFalse(failure, "Error on connect: (%d) %s", errno, strerror(errno)); + + // crank the handle + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + return fd; +} + +// ---- Global state used by tests + +static struct global_state { + MetisForwarder *metis; + MetisMessengerRecipient *recipient; + int fd_sender; + int fd_receiver; +} globalState; + +static void +setupTcp(void) +{ + globalState.metis = metisForwarder_Create(NULL); + globalState.recipient = metisMessengerRecipient_Create(&testNotifierData, testNotifier); + + // register a messenger callback so we know when the connections get setup + MetisMessenger *messenger = metisForwarder_GetMessenger(globalState.metis); + metisMessenger_Register(messenger, globalState.recipient); + + setupInetListener(globalState.metis, 49996); + setupInetListener(globalState.metis, 49997); + + // create two test connections and learn their connection IDs via + // the messenger callback + + globalState.fd_sender = setupInetClient(globalState.metis, 49996); + metisDispatcher_RunCount(metisForwarder_GetDispatcher(globalState.metis), 1); + printf("sender connection id = %u\n", testNotifierData.connectionid); + + globalState.fd_receiver = setupInetClient(globalState.metis, 49997); + metisDispatcher_RunCount(metisForwarder_GetDispatcher(globalState.metis), 1); + unsigned receiverConnectionId = testNotifierData.connectionid; + printf("receiver connection id = %u\n", testNotifierData.connectionid); + + // Add a FIB entry out the receiver connection + CCNxName *ccnxNameToAdd = + ccnxName_CreateFromCString("lci:/2=hello/0xF000=ouch"); + + CPIRouteEntry *routeAdd = cpiRouteEntry_Create(ccnxNameToAdd, receiverConnectionId, NULL, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, NULL, 1); + metisForwarder_AddOrUpdateRoute(globalState.metis, routeAdd); + cpiRouteEntry_Destroy(&routeAdd); + + metisDispatcher_RunCount(metisForwarder_GetDispatcher(globalState.metis), 1); +} + +static void +teardownTcp(void) +{ + metisForwarder_Destroy(&globalState.metis); + metisMessengerRecipient_Destroy(&globalState.recipient); + + close(globalState.fd_sender); + close(globalState.fd_receiver); +} + +// ========================================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, passInterest); + //LONGBOW_RUN_TEST_CASE(Global, returnContentObject); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + setupTcp(); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + teardownTcp(); + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, passInterest) +{ + uint8_t receiveBuffer[1024]; + + // now send the interest on the sender and see if we get it on the receiver + ssize_t write_length = write(globalState.fd_sender, metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName)); + assertTrue(write_length == sizeof(metisTestDataV0_InterestWithName), + "Partial write, expected %zd got %zd: (%d) %s", + sizeof(metisTestDataV0_InterestWithName), write_length, errno, strerror(errno)); + // run for a duration so there is time to read the message, pass it off to the handler, then send the message out + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(globalState.metis), &((struct timeval) { 0, 10000 })); + + ssize_t read_length = read(globalState.fd_receiver, receiveBuffer, 1024); + assertTrue(read_length == sizeof(metisTestDataV0_InterestWithName), "Incorrect read, expected %zd got %zd: (%d) %s", sizeof(metisTestDataV0_InterestWithName), read_length, errno, strerror(errno)); + + // We decrement the hoplimit, so need a new truth value + uint8_t truth[1024]; + memcpy(truth, metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName)); + //truth[12] = 31; + + assertTrue(memcmp(receiveBuffer, truth, read_length) == 0, "Messages do not match"); +} + +LONGBOW_TEST_CASE(Global, returnContentObject) +{ + uint8_t receiveBuffer[1024]; + + // send the interest on the sender and see if we get it on the receiver + ssize_t write_length = write(globalState.fd_sender, metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName)); + assertTrue(write_length == sizeof(metisTestDataV0_InterestWithName), "Partial write of interest, expected %zd got %zd: (%d) %s", sizeof(metisTestDataV0_InterestWithName), write_length, errno, strerror(errno)); + + // run for a duration so there is time to read the message, pass it off to the handler, then send the message out + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(globalState.metis), &((struct timeval) { 0, 1000 })); + + ssize_t read_length = read(globalState.fd_receiver, receiveBuffer, 1024); + assertTrue(read_length == sizeof(metisTestDataV0_InterestWithName), "Incorrect read of interest, expected %zd got %zd: (%d) %s", sizeof(metisTestDataV0_InterestWithName), read_length, errno, strerror(errno)); + + // send content object back + write_length = write(globalState.fd_receiver, metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject)); + assertTrue(write_length == sizeof(metisTestDataV0_EncodedObject), "Partial write of object, expected %zd got %zd: (%d) %s", sizeof(metisTestDataV0_EncodedObject), write_length, errno, strerror(errno)); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(globalState.metis), &((struct timeval) { 0, 1000 })); + + // now check that we got the object + read_length = read(globalState.fd_sender, receiveBuffer, 1024); + assertTrue(read_length == sizeof(metisTestDataV0_EncodedObject), "Incorrect read of object, expected %zd got %zd: (%d) %s", sizeof(metisTestDataV0_EncodedObject), read_length, errno, strerror(errno)); + + assertTrue(memcmp(receiveBuffer, metisTestDataV0_EncodedObject, read_length) == 0, "Objects do not match"); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(sys_TcpEndToEnd); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/test/test_sys_TcpTunnel.c b/metis/ccnx/forwarder/metis/test/test_sys_TcpTunnel.c new file mode 100644 index 00000000..0ce2525d --- /dev/null +++ b/metis/ccnx/forwarder/metis/test/test_sys_TcpTunnel.c @@ -0,0 +1,298 @@ +/* + * 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. + */ + +/** + * Run two instances of metis. + * Client_1 - Metis_A - Metis_B - Client_2 + * + * Steps + * 1) run two instances of Metis + * 2) Create TCP listeners on 127.0.0.1:10001 and 127.0.0.1:10002 + * 3) create a tunnel from A->B. + * 4) setup route to /foo from a to b + * 5) Connect client 1 to A + * 6) Connect client 2 to B + * 7) Setup route to /foo from metis B to client 2. + * 8) Sent interest from #1 to #2 + * 9) Send object back from #2 to #1 + * + */ + +#include <config.h> +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <netinet/in.h> +#include <stdlib.h> +#include <arpa/inet.h> +#include <errno.h> +#include <stdio.h> + +#include <ccnx/api/control/cpi_RouteEntry.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/io/metis_TcpTunnel.h> +#include <ccnx/forwarder/metis/config/metis_Configuration.h> +#include <ccnx/forwarder/metis/config/metis_ConfigurationListeners.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h> + +LONGBOW_TEST_RUNNER(test_sys_TcpTunnel) +{ + // 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); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(test_sys_TcpTunnel) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(test_sys_TcpTunnel) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, tcpTunnel); +} + +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; +} + +typedef struct notify_receiver { + MetisMissive *lastMessage; +} NotifyReceiver; + +static void +missiveNotify(MetisMessengerRecipient *recipient, MetisMissive *missive) +{ + NotifyReceiver *receiver = (NotifyReceiver *) metisMessengerRecipient_GetRecipientContext(recipient); + if (receiver->lastMessage != NULL) { + metisMissive_Release(&receiver->lastMessage); + } + receiver->lastMessage = missive; +} + +LONGBOW_TEST_CASE(Global, tcpTunnel) +{ + uint16_t metisA_port = 10001; + uint16_t metisB_port = 10002; + + // these will get filled in with the most recent message + NotifyReceiver receiver_a = { NULL }; + NotifyReceiver receiver_b = { NULL }; + + MetisMessengerRecipient *recipient_a = metisMessengerRecipient_Create(&receiver_a, missiveNotify); + MetisMessengerRecipient *recipient_b = metisMessengerRecipient_Create(&receiver_b, missiveNotify); + + // in between each step, run the dispatchers for 1 msec to let things settle. + + // =============================================== + /* 1) run two instances of Metis */ + MetisForwarder *metis_a = metisForwarder_Create(NULL); + MetisForwarder *metis_b = metisForwarder_Create(NULL); + + MetisDispatcher *dispatcher_a = metisForwarder_GetDispatcher(metis_a); + MetisDispatcher *dispatcher_b = metisForwarder_GetDispatcher(metis_b); + + // register to receive notifications + metisMessenger_Register(metisForwarder_GetMessenger(metis_a), recipient_a); + metisMessenger_Register(metisForwarder_GetMessenger(metis_b), recipient_b); + + // =============================================== + /* 2) Create TCP listeners on 127.0.0.1:10001 and 10002 */ + + metisConfigurationListeners_SetupAll(metisForwarder_GetConfiguration(metis_a), metisA_port, NULL); + metisConfigurationListeners_SetupAll(metisForwarder_GetConfiguration(metis_b), metisB_port, NULL); + + // ---- run + metisDispatcher_RunDuration(dispatcher_a, &((struct timeval) { 0, 1000 })); + metisDispatcher_RunDuration(dispatcher_b, &((struct timeval) { 0, 1000 })); + // ---- + + // =============================================== + /* 3) create a tunnel from A->B. */ + + // connect from any address + struct sockaddr_in metisA_AnyIpAddress; + memset(&metisA_AnyIpAddress, 0, sizeof(metisA_AnyIpAddress)); + metisA_AnyIpAddress.sin_family = PF_INET; + metisA_AnyIpAddress.sin_addr.s_addr = INADDR_ANY; + + // connect to 127.0.0.1:10002 + struct sockaddr_in metisB_LoopbackAddress; + memset(&metisB_LoopbackAddress, 0, sizeof(metisB_LoopbackAddress)); + metisB_LoopbackAddress.sin_family = PF_INET; + metisB_LoopbackAddress.sin_port = htons(metisB_port); + inet_pton(AF_INET, "127.0.0.1", &(metisB_LoopbackAddress.sin_addr)); + + CPIAddress *metisA_localCpiAddress = cpiAddress_CreateFromInet(&metisA_AnyIpAddress); + CPIAddress *metisA_remoteCpiAddress = cpiAddress_CreateFromInet(&metisB_LoopbackAddress); + + MetisIoOperations *ops = metisTcpTunnel_Create(metis_a, metisA_localCpiAddress, metisA_remoteCpiAddress); + MetisConnection *conn = metisConnection_Create(ops); + metisConnectionTable_Add(metisForwarder_GetConnectionTable(metis_a), conn); + + cpiAddress_Destroy(&metisA_localCpiAddress); + cpiAddress_Destroy(&metisA_remoteCpiAddress); + + // ---- run + metisDispatcher_RunDuration(dispatcher_a, &((struct timeval) { 0, 1000 })); + metisDispatcher_RunDuration(dispatcher_b, &((struct timeval) { 0, 1000 })); + // ---- + + // =============================================== + /* 4) setup route to /foo from a to b */ + + CCNxName *ccnxName = ccnxName_CreateFromCString("lci:/2=hello"); + CPIRouteEntry *route = cpiRouteEntry_Create(ccnxName, ops->getConnectionId(ops), NULL, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, NULL, 1); + bool success = metisForwarder_AddOrUpdateRoute(metis_a, route); + cpiRouteEntry_Destroy(&route); + assertTrue(success, "error adding route from A to B"); + + // ---- run + metisDispatcher_RunDuration(dispatcher_a, &((struct timeval) { 0, 1000 })); + metisDispatcher_RunDuration(dispatcher_b, &((struct timeval) { 0, 1000 })); + // ---- + + // =============================================== + /* 5) Connect client 1 to A */ + + struct sockaddr_in metisA_LoopbackAddress; + memset(&metisA_LoopbackAddress, 0, sizeof(metisA_LoopbackAddress)); + metisA_LoopbackAddress.sin_family = PF_INET; + metisA_LoopbackAddress.sin_port = htons(metisA_port); + inet_pton(AF_INET, "127.0.0.1", &(metisA_LoopbackAddress.sin_addr)); + + int client1_Socket = socket(PF_INET, SOCK_STREAM, 0); + assertFalse(client1_Socket < 0, "Error creating socket: (%d) %s", errno, strerror(errno)); + + int failure = connect(client1_Socket, (struct sockaddr *) &metisA_LoopbackAddress, sizeof(metisA_LoopbackAddress)); + assertFalse(failure, "Error connect: (%d) %s", errno, strerror(errno)); + + // ---- run + metisDispatcher_RunDuration(dispatcher_a, &((struct timeval) { 0, 1000 })); + metisDispatcher_RunDuration(dispatcher_b, &((struct timeval) { 0, 1000 })); + // ---- + + // =============================================== + /* 6) Connect client 2 to B */ + + // We need to sniff connections on metis b to learn the connection ID of the client + + int client2_Socket = socket(PF_INET, SOCK_STREAM, 0); + assertFalse(client2_Socket < 0, "Error creating socket: (%d) %s", errno, strerror(errno)); + + failure = connect(client2_Socket, (struct sockaddr *) &metisB_LoopbackAddress, sizeof(metisB_LoopbackAddress)); + assertFalse(failure, "Error connect: (%d) %s", errno, strerror(errno)); + + // ---- run + metisDispatcher_RunDuration(dispatcher_a, &((struct timeval) { 0, 1000 })); + metisDispatcher_RunDuration(dispatcher_b, &((struct timeval) { 0, 1000 })); + // ---- + + unsigned client2_ConnectionId = metisMissive_GetConnectionId(receiver_b.lastMessage); + printf("client 2 connection id is %u\n", client2_ConnectionId); + + // =============================================== + /* 7) Setup route to /foo from metis B to client 2. */ + + ccnxName = ccnxName_CreateFromCString("lci:/2=hello"); + route = cpiRouteEntry_Create(ccnxName, client2_ConnectionId, NULL, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, NULL, 1); + success = metisForwarder_AddOrUpdateRoute(metis_b, route); + cpiRouteEntry_Destroy(&route); + assertTrue(success, "error adding route from B to #2"); + + // ---- run + metisDispatcher_RunDuration(dispatcher_a, &((struct timeval) { 0, 1000 })); + metisDispatcher_RunDuration(dispatcher_b, &((struct timeval) { 0, 1000 })); + // ---- + + // =============================================== + /* 8) Sent interest from #1 to #2 */ + + ssize_t interest_write_length = write(client1_Socket, metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName)); + assertTrue(interest_write_length == sizeof(metisTestDataV0_InterestWithName), + "Wrong write length, expected %zu got %zu", + sizeof(metisTestDataV0_EncodedInterest), + interest_write_length); + + metisDispatcher_RunDuration(dispatcher_a, &((struct timeval) { 0, 1000 })); + metisDispatcher_RunDuration(dispatcher_b, &((struct timeval) { 0, 1000 })); + + // wait to receive it + uint8_t readBuffer[1024]; + ssize_t interest_read_length = read(client2_Socket, readBuffer, 1024); + assertTrue(interest_read_length == sizeof(metisTestDataV0_InterestWithName), + "Wrong write length, expected %zu got %zu", + sizeof(metisTestDataV0_InterestWithName), + interest_read_length); + + metisDispatcher_RunDuration(dispatcher_a, &((struct timeval) { 0, 1000 })); + metisDispatcher_RunDuration(dispatcher_b, &((struct timeval) { 0, 1000 })); + + // =============================================== + /* 9) Send object back from #2 to #1 */ + + ssize_t object_write_length = write(client2_Socket, metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject)); + assertTrue(object_write_length == sizeof(metisTestDataV0_EncodedObject), + "Wrong write length, expected %zu got %zu", + sizeof(metisTestDataV0_EncodedInterest), + object_write_length); + + // very important: run b first, then a + metisDispatcher_RunDuration(dispatcher_b, &((struct timeval) { 0, 1000 })); + metisDispatcher_RunDuration(dispatcher_a, &((struct timeval) { 0, 1000 })); + + // wait to receive it + ssize_t object_read_length = read(client1_Socket, readBuffer, 1024); + assertTrue(object_read_length == sizeof(metisTestDataV0_EncodedObject), + "Wrong write length, expected %zu got %zu", + sizeof(metisTestDataV0_EncodedObject), + object_read_length); + + + // =============================================== + // cleanup + metisMissive_Release(&receiver_a.lastMessage); + metisMissive_Release(&receiver_b.lastMessage); + metisMessengerRecipient_Destroy(&recipient_a); + metisMessengerRecipient_Destroy(&recipient_b); + metisForwarder_Destroy(&metis_b); + metisForwarder_Destroy(&metis_a); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(test_sys_TcpTunnel); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/test/test_sys_UdpEndToEnd.c b/metis/ccnx/forwarder/metis/test/test_sys_UdpEndToEnd.c new file mode 100644 index 00000000..2ce7fdca --- /dev/null +++ b/metis/ccnx/forwarder/metis/test/test_sys_UdpEndToEnd.c @@ -0,0 +1,255 @@ +/* + * 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 are end-to-end system tests. They nail up two UDP connections, setup a FIB entry, and send + * and interest then a content object back. + */ + +#include <config.h> +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <netinet/in.h> +#include <stdlib.h> +#include <arpa/inet.h> +#include <errno.h> +#include <stdio.h> + +#include <ccnx/api/control/cpi_RouteEntry.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/io/metis_UdpListener.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h> + +LONGBOW_TEST_RUNNER(sys_UdpEndToEnd) +{ + // 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); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(sys_UdpEndToEnd) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(sys_UdpEndToEnd) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ---- Used to monitor Missive messages so we know when +// a connection is setup +static struct test_notifier_data { + MetisMissiveType type; + unsigned connectionid; +} testNotifierData; + +static void +testNotifier(MetisMessengerRecipient *recipient, MetisMissive *missive) +{ + struct test_notifier_data *data = metisMessengerRecipient_GetRecipientContext(recipient); + data->type = metisMissive_GetType(missive); + data->connectionid = metisMissive_GetConnectionId(missive); + metisMissive_Release(&missive); +} + +// ---- Utility functions to setup TCP endpoints + +static void +setupInetListener(MetisForwarder *metis, uint16_t port) +{ + struct sockaddr_in addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + inet_pton(AF_INET, "127.0.0.1", &(addr.sin_addr)); + + MetisListenerSet *listeners = metisForwarder_GetListenerSet(metis); + MetisListenerOps *ops = metisUdpListener_CreateInet(metis, addr); + metisListenerSet_Add(listeners, ops); + + // crank the handle + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); +} + +static int +setupInetClient(MetisForwarder *metis, uint16_t port) +{ + struct sockaddr_in addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + inet_pton(AF_INET, "127.0.0.1", &(addr.sin_addr)); + + int fd = socket(PF_INET, SOCK_DGRAM, 0); + assertFalse(fd < 0, "Error on socket: (%d) %s", errno, strerror(errno)); + + int failure = connect(fd, (struct sockaddr *) &addr, sizeof(addr)); + assertFalse(failure, "Error on connect: (%d) %s", errno, strerror(errno)); + + // crank the handle + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + return fd; +} + +// ---- Global state used by tests + +static struct global_state { + MetisForwarder *metis; + MetisMessengerRecipient *recipient; + + int fd_sender; + int fd_receiver; +} globalState; + +static void +setupUdp(void) +{ + globalState.metis = metisForwarder_Create(NULL); + globalState.recipient = metisMessengerRecipient_Create(&testNotifierData, testNotifier); + + // register a messenger callback so we know when the connections get setup + MetisMessenger *messenger = metisForwarder_GetMessenger(globalState.metis); + metisMessenger_Register(messenger, globalState.recipient); + + setupInetListener(globalState.metis, 49996); + setupInetListener(globalState.metis, 49997); + + // create two test connections and learn their connection IDs via + // the messenger callback + + globalState.fd_sender = setupInetClient(globalState.metis, 49996); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(globalState.metis), &((struct timeval) { 0, 10000 })); + + printf("sender connection id = %u\n", testNotifierData.connectionid); + + globalState.fd_receiver = setupInetClient(globalState.metis, 49997); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(globalState.metis), &((struct timeval) { 0, 10000 })); + + // send something to actually connect it + ssize_t nwritten = write(globalState.fd_receiver, metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject)); + assertTrue(nwritten == sizeof(metisTestDataV0_EncodedObject), "Error on write, expected %zu got %zd", sizeof(metisTestDataV0_EncodedObject), nwritten); + + // crank the handle + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(globalState.metis), &((struct timeval) { 0, 10000 })); + + unsigned receiverConnectionId = testNotifierData.connectionid; + printf("receiver connection id = %u\n", testNotifierData.connectionid); + + // Add a FIB entry out the receiver connection + CCNxName *ccnxNameToAdd = ccnxName_CreateFromCString("lci:/2=hello/0xF000=ouch"); + CPIRouteEntry *routeAdd = cpiRouteEntry_Create(ccnxNameToAdd, receiverConnectionId, NULL, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, NULL, 1); + metisForwarder_AddOrUpdateRoute(globalState.metis, routeAdd); + cpiRouteEntry_Destroy(&routeAdd); +} + +static void +teardownUdp(void) +{ + metisForwarder_Destroy(&globalState.metis); + metisMessengerRecipient_Destroy(&globalState.recipient); + + close(globalState.fd_sender); + close(globalState.fd_receiver); +} + +// ========================================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + //XXX: these tests do not work anymore because we do not create the UDP connection from the listener + //LONGBOW_RUN_TEST_CASE(Global, passInterest); + //LONGBOW_RUN_TEST_CASE(Global, returnContentObject); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + setupUdp(); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + teardownUdp(); + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, passInterest) +{ + uint8_t receiveBuffer[1024]; + + // now send the interest on the sender and see if we get it on the receiver + ssize_t write_length = write(globalState.fd_sender, metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName)); + assertTrue(write_length == sizeof(metisTestDataV0_InterestWithName), "Partial write, expected %zd got %zd: (%d) %s", sizeof(metisTestDataV0_InterestWithName), write_length, errno, strerror(errno)); + + // run for a duration so there is time to read the message, pass it off to the handler, then send the message out + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(globalState.metis), &((struct timeval) { 0, 10000 })); + + ssize_t read_length = read(globalState.fd_receiver, receiveBuffer, 1024); + assertTrue(read_length == sizeof(metisTestDataV0_InterestWithName), "Incorrect read, expected %zd got %zd: (%d) %s", sizeof(metisTestDataV0_InterestWithName), read_length, errno, strerror(errno)); + + // We decrement the hoplimit, so need a new truth value + uint8_t truth[1024]; + memcpy(truth, metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName)); + truth[12] = 31; + + assertTrue(memcmp(receiveBuffer, truth, read_length) == 0, "Messages do not match"); +} + +LONGBOW_TEST_CASE(Global, returnContentObject) +{ + uint8_t receiveBuffer[1024]; + + // send the interest on the sender and see if we get it on the receiver + ssize_t write_length = write(globalState.fd_sender, metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName)); + assertTrue(write_length == sizeof(metisTestDataV0_InterestWithName), "Partial write of interest, expected %zd got %zd: (%d) %s", sizeof(metisTestDataV0_InterestWithName), write_length, errno, strerror(errno)); + + // run for a duration so there is time to read the message, pass it off to the handler, then send the message out + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(globalState.metis), &((struct timeval) { 0, 1000 })); + + ssize_t read_length = read(globalState.fd_receiver, receiveBuffer, 1024); + assertTrue(read_length == sizeof(metisTestDataV0_InterestWithName), "Incorrect read of interest, expected %zd got %zd: (%d) %s", sizeof(metisTestDataV0_InterestWithName), read_length, errno, strerror(errno)); + + // send content object back + write_length = write(globalState.fd_receiver, metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject)); + assertTrue(write_length == sizeof(metisTestDataV0_EncodedObject), "Partial write of object, expected %zd got %zd: (%d) %s", sizeof(metisTestDataV0_EncodedObject), write_length, errno, strerror(errno)); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(globalState.metis), &((struct timeval) { 0, 1000 })); + + // now check that we got the object + read_length = read(globalState.fd_sender, receiveBuffer, 1024); + assertTrue(read_length == sizeof(metisTestDataV0_EncodedObject), "Incorrect read of object, expected %zd got %zd: (%d) %s", sizeof(metisTestDataV0_EncodedObject), read_length, errno, strerror(errno)); + + assertTrue(memcmp(receiveBuffer, metisTestDataV0_EncodedObject, read_length) == 0, "Objects do not match"); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(sys_UdpEndToEnd); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/testdata/metis_TestDataV0.h b/metis/ccnx/forwarder/metis/testdata/metis_TestDataV0.h new file mode 100644 index 00000000..700952ea --- /dev/null +++ b/metis/ccnx/forwarder/metis/testdata/metis_TestDataV0.h @@ -0,0 +1,500 @@ +/* + * 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. + */ + +/** + * This test rig provides some hand-made interests and objects for testing. + * We use __attribute__((unused)) on each of them, because a test might not use all off + * the messages and it would otherwise generate a warning mesage at compile time. + */ + +#ifndef Metis_metis_TestDataV0_h +#define Metis_metis_TestDataV0_h + +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> + +uint8_t metisTestDataV0_EncodedInterest[] = { + 0x00, 0x01, 0x00, 0x36, // ver = 0, type = interest, length = 54 + 0x00, 0x00, 0x00, 0x15, // reserved = 0, header length = 21 + // ------------------------ + 0x00, 0x02, 0x00, 0x01, // HOPLIMIT + 0x20, // 32 + // ------------------------ + 0x00, 0x03, 0x00, 0x0C, // Interest Fragment + 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, // fragment 0x0102030405060708 + 0x05, 0xDC, 0x04, 0x00, // MTU 1500, fragcnt 4, fragnum 0 + // ------------------------ + 0x00, 0x01, 0x00, 0x32, // type = interest, length = 50 + // ------------------------ + 0x00, 0x00, 0x00, 0x11, // type = name, length = 17 + 0x00, 0x02, 0x00, 0x05, // type = binary, length = 5 + 'h', 'e', 'l', 'l', + 'o', // "hello" + 0xF0, 0x00, 0x00, 0x04, // type = app, length = 4 + 'o', 'u', 'c', 'h', // "ouch" + // ------------------------ + 0x00, 0x01, 0x00, 0x04, // type = keyid, length = 4 + 0xA0, 0xB0, 0xC0, 0xD0, // 0xA0B0C0D0 + // ------------------------ + 0x00, 0x02, 0x00, 0x06, // type = objhash, length = 6 + 0x11, 0x22, 0x33, 0x44, + 0x55, 0x66, // 0x112233445566 + // ------------------------ + 0x00, 0x03, 0x00, 0x01, // scope, length = 1 + 0x02, // value = 2 + // ------------------------ + 0x00, 0x05, 0x00, 0x02, // interest lifetime, length = 2 + 0x7D, 0x00 // 32000 +}; + +uint8_t metisTestDataV0_EncodedInterest_no_hoplimit[] = { + 0x00, 0x01, 0x00, 0x36, // ver = 0, type = interest, length = 54 + 0x00, 0x00, 0x00, 0x15, // reserved = 0, header length = 21 + // ------------------------ + 0x00, 0xFF, 0x00, 0x01, // Unknown TLV value + 0x20, // 32 + // ------------------------ + 0x00, 0x03, 0x00, 0x0C, // Interest Fragment + 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, // fragment 0x0102030405060708 + 0x05, 0xDC, 0x04, 0x00, // MTU 1500, fragcnt 4, fragnum 0 + // ------------------------ + 0x00, 0x01, 0x00, 0x32, // type = interest, length = 50 + // ------------------------ + 0x00, 0x00, 0x00, 0x11, // type = name, length = 17 + 0x00, 0x02, 0x00, 0x05, // type = binary, length = 5 + 'h', 'e', 'l', 'l', + 'o', // "hello" + 0xF0, 0x00, 0x00, 0x04, // type = app, length = 4 + 'o', 'u', 'c', 'h', // "ouch" + // ------------------------ + 0x00, 0x01, 0x00, 0x04, // type = keyid, length = 4 + 0xA0, 0xB0, 0xC0, 0xD0, // 0xA0B0C0D0 + // ------------------------ + 0x00, 0x02, 0x00, 0x06, // type = objhash, length = 6 + 0x11, 0x22, 0x33, 0x44, + 0x55, 0x66, // 0x112233445566 + // ------------------------ + 0x00, 0x03, 0x00, 0x01, // scope, length = 1 + 0x02, // value = 2 + // ------------------------ + 0x00, 0x05, 0x00, 0x02, // interest lifetime, length = 2 + 0x7D, 0x00 // 32000 +}; + +uint8_t metisTestDataV0_EncodedInterest_zero_hoplimit[] = { + 0x00, 0x01, 0x00, 0x36, // ver = 0, type = interest, length = 54 + 0x00, 0x00, 0x00, 0x15, // reserved = 0, header length = 21 + // ------------------------ + 0x00, 0x02, 0x00, 0x01, // HOPLIMIT + 0x00, // 0 + // ------------------------ + 0x00, 0x03, 0x00, 0x0C, // Interest Fragment + 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, // fragment 0x0102030405060708 + 0x05, 0xDC, 0x04, 0x00, // MTU 1500, fragcnt 4, fragnum 0 + // ------------------------ + 0x00, 0x01, 0x00, 0x32, // type = interest, length = 50 + // ------------------------ + 0x00, 0x00, 0x00, 0x11, // type = name, length = 17 + 0x00, 0x02, 0x00, 0x05, // type = binary, length = 5 + 'h', 'e', 'l', 'l', + 'o', // "hello" + 0xF0, 0x00, 0x00, 0x04, // type = app, length = 4 + 'o', 'u', 'c', 'h', // "ouch" + // ------------------------ + 0x00, 0x01, 0x00, 0x04, // type = keyid, length = 4 + 0xA0, 0xB0, 0xC0, 0xD0, // 0xA0B0C0D0 + // ------------------------ + 0x00, 0x02, 0x00, 0x06, // type = objhash, length = 6 + 0x11, 0x22, 0x33, 0x44, + 0x55, 0x66, // 0x112233445566 + // ------------------------ + 0x00, 0x03, 0x00, 0x01, // scope, length = 1 + 0x02, // value = 2 + // ------------------------ + 0x00, 0x05, 0x00, 0x02, // interest lifetime, length = 2 + 0x7D, 0x00 // 32000 +}; + +uint8_t metisTestDataV0_InterestAllFields[] = { + 0x00, 0x01, 0x00, 0x36, // ver = 0, type = interest, length = 54 + 0x00, 0x00, 0x00, 0x15, // reserved = 0, header length = 21 + // ------------------------ + 0x00, 0x02, 0x00, 0x01, // HOPLIMIT + 0x20, // 32 + // ------------------------ + 0x00, 0x03, 0x00, 0x0C, // Interest Fragment + 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, // fragment 0x0102030405060708 + 0x05, 0xDC, 0x04, 0x00, // MTU 1500, fragcnt 4, fragnum 0 + // ------------------------ + 0x00, 0x01, 0x00, 0x32, // type = interest, length = 50 + // ------------------------ + 0x00, 0x00, 0x00, 0x11, // type = name, length = 17 + 0x00, 0x02, 0x00, 0x05, // type = binary, length = 5 + 'h', 'e', 'l', 'l', + 'o', // "hello" + 0xF0, 0x00, 0x00, 0x04, // type = app, length = 4 + 'o', 'u', 'c', 'h', // "ouch" + // ------------------------ + 0x00, 0x01, 0x00, 0x04, // type = keyid, length = 4 + 0xA0, 0xB0, 0xC0, 0xD0, // 0xA0B0C0D0 + // ------------------------ + 0x00, 0x02, 0x00, 0x06, // type = objhash, length = 6 + 0x11, 0x22, 0x33, 0x44, + 0x55, 0x66, // 0x112233445566 + // ------------------------ + 0x00, 0x03, 0x00, 0x01, // scope, length = 1 + 0x02, // value = 2 + // ------------------------ + 0x00, 0x05, 0x00, 0x02, // interest lifetime, length = 2 + 0x7D, 0x00 // 32000 +}; + +uint8_t metisTestDataV0_InterestWithName[] = { + 0x00, 0x01, 0x00, 0x19, // ver = 0, type = interest, length = 25 + 0x00, 0x00, 0x00, 0x15, // reserved = 0, header length = 21 + // ------------------------ + 0x00, 0x02, 0x00, 0x01, // HOPLIMIT + 0x20, // 32 + // ------------------------ + 0x00, 0x03, 0x00, 0x0C, // Interest Fragment + 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, // fragment 0x0102030405060708 + 0x05, 0xDC, 0x04, 0x00, // MTU 1500, fragcnt 4, fragnum 0 + // ------------------------ + 0x00, 0x01, 0x00, 0x15, // type = interest, length = 21 + // ------------------------ + 0x00, 0x00, 0x00, 0x11, // type = name, length = 17 + 0x00, 0x02, 0x00, 0x05, // type = binary, length = 5 + 'h', 'e', 'l', 'l', // "hello" + 'o', + 0xF0, 0x00, 0x00, 0x04, // type = app, length = 4 + 'o', 'u', 'c', 'h', // "ouch" +}; + +uint8_t metisTestDataV0_InterestWithOtherName[] = { + 0x00, 0x01, 0x00, 0x19, // ver = 0, type = interest, length = 25 + 0x00, 0x00, 0x00, 0x15, // reserved = 0, header length = 21 + // ------------------------ + 0x00, 0x02, 0x00, 0x01, // HOPLIMIT + 0x20, // 32 + // ------------------------ + 0x00, 0x03, 0x00, 0x0C, // Interest Fragment + 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, // fragment 0x0102030405060708 + 0x05, 0xDC, 0x04, 0x00, // MTU 1500, fragcnt 4, fragnum 0 + // ------------------------ + 0x00, 0x01, 0x00, 0x15, // type = interest, length = 21 + // ------------------------ + 0x00, 0x00, 0x00, 0x11, // type = name, length = 17 + 0x00, 0x02, 0x00, 0x05, // type = binary, length = 5 + 'p', 'a', 'r', 't', // "party" + 'y', + 0xF0, 0x00, 0x00, 0x04, // type = app, length = 4 + 'o', 'u', 'c', 'h', // "ouch" +}; + +uint8_t metisTestDataV0_InterestWithName_keyid[] = { + 0x00, 0x01, 0x00, 0x21, // ver = 0, type = interest, length = 33 + 0x00, 0x00, 0x00, 0x15, // reserved = 0, header length = 21 + // ------------------------ + 0x00, 0x02, 0x00, 0x01, // HOPLIMIT + 0x20, // 32 + // ------------------------ + 0x00, 0x03, 0x00, 0x0C, // Interest Fragment + 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, // fragment 0x0102030405060708 + 0x05, 0xDC, 0x04, 0x00, // MTU 1500, fragcnt 4, fragnum 0 + // ------------------------ + 0x00, 0x01, 0x00, 0x1D, // type = interest, length = 29 + // ------------------------ + 0x00, 0x00, 0x00, 0x11, // type = name, length = 17 + 0x00, 0x02, 0x00, 0x05, // type = binary, length = 5 + 'h', 'e', 'l', 'l', // "hello" + 'o', + 0xF0, 0x00, 0x00, 0x04, // type = app, length = 4 + 'o', 'u', 'c', 'h', // "ouch" + // ------------------------ + 0x00, 0x01, 0x00, 0x04, // type = keyid, length = 4 + 0xA0, 0xB0, 0xC0, 0xD0, // 0xA0B0C0D0 +}; + +uint8_t metisTestDataV0_InterestWithName_keyid2[] = { + 0x00, 0x01, 0x00, 0x21, // ver = 0, type = interest, length = 33 + 0x00, 0x00, 0x00, 0x15, // reserved = 0, header length = 21 + // ------------------------ + 0x00, 0x02, 0x00, 0x01, // HOPLIMIT + 0x20, // 32 + // ------------------------ + 0x00, 0x03, 0x00, 0x0C, // Interest Fragment + 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, // fragment 0x0102030405060708 + 0x05, 0xDC, 0x04, 0x00, // MTU 1500, fragcnt 4, fragnum 0 + // ------------------------ + 0x00, 0x01, 0x00, 0x1D, // type = interest, length = 29 + // ------------------------ + 0x00, 0x00, 0x00, 0x11, // type = name, length = 17 + 0x00, 0x02, 0x00, 0x05, // type = binary, length = 5 + 'h', 'e', 'l', 'l', // "hello" + 'o', + 0xF0, 0x00, 0x00, 0x04, // type = app, length = 4 + 'o', 'u', 'c', 'h', // "ouch" + // ------------------------ + 0x00, 0x01, 0x00, 0x04, // type = keyid, length = 4 + 0x22, 0x22, 0x22, 0x22, // 0x22222222 +}; + +/** + * This interest will match "metisTestDataV0_EncodedObject" by name and content object hash. + * The content object hash was computed out-of-band and manually placed here. + */ +uint8_t metisTestDataV0_InterestWithName_objecthash[] = { + 0x00, 0x01, 0x00, 0x21, // ver = 0, type = interest, length = 33 + 0x00, 0x00, 0x00, 0x15, // reserved = 0, header length = 21 + // ------------------------ + 0x00, 0x02, 0x00, 0x01, // HOPLIMIT + 0x20, // 32 + // ------------------------ + 0x00, 0x03, 0x00, 0x0C, // Interest Fragment + 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, // fragment 0x0102030405060708 + 0x05, 0xDC, 0x04, 0x00, // MTU 1500, fragcnt 4, fragnum 0 + // ------------------------ + 0x00, 0x01, 0x00, 0x1D, // type = interest, length = 29 + // ------------------------ + 0x00, 0x00, 0x00, 0x11, // type = name, length = 17 + 0x00, 0x02, 0x00, 0x05, // type = binary, length = 5 + 'h', 'e', 'l', 'l', // "hello" + 'o', + 0xF0, 0x00, 0x00, 0x04, // type = app, length = 4 + 'o', 'u', 'c', 'h', // "ouch" + // ------------------------ + 0x00, 0x02, 0x00, 0x20, // type = objhash, length = 32 + 0x9B, 0x86, 0x46, 0x3F, // matches "metisTestDataV0_EncodedObject" + 0x8F, 0xFC, 0x3C, 0x26, + 0x9B, 0xE0, 0x91, 0xDE, + 0xA7, 0xC4, 0x33, 0xDF, + 0xF0, 0xBA, 0xBD, 0x4E, + 0xA7, 0x7E, 0x86, 0xA6, + 0x76, 0x16, 0x5E, 0x40, + 0xF2, 0x73, 0x60, 0xCE +}; + +uint8_t metisTestDataV0_SecondInterest[] = { + 0x00, 0x01, 0x00, 0x36, // ver = 0, type = interest, length = 54 + 0x00, 0x00, 0x00, 0x15, // reserved = 0, header length = 21 + // ------------------------ + 0x00, 0x02, 0x00, 0x01, // HOPLIMIT + 0x20, // 32 + // ------------------------ + 0x00, 0x03, 0x00, 0x0C, // Interest Fragment + 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, // fragment 0x0102030405060708 + 0x05, 0xDC, 0x04, 0x00, // MTU 1500, fragcnt 4, fragnum 0 + // ------------------------ + 0x00, 0x01, 0x00, 0x32, // type = interest, length = 50 + // ------------------------ + 0x00, 0x00, 0x00, 0x11, // type = name, length = 17 + 0x00, 0x02, 0x00, 0x05, // type = binary, length = 5 + 'h', 'e', 'l', 'l', + 'o', // "hello" + 0xF0, 0x00, 0x00, 0x04, // type = app, length = 4 + 'o', 'u', 'c', 'h', // "ouch" + // ------------------------ + 0x00, 0x01, 0x00, 0x04, // type = keyid, length = 4 + 0x11, 0x11, 0x11, 0x11, // 0x11111111 + // ------------------------ + 0x00, 0x02, 0x00, 0x06, // type = objhash, length = 6 + 0x11, 0x22, 0x33, 0x44, + 0x55, 0x66, // 0x112233445566 + // ------------------------ + 0x00, 0x03, 0x00, 0x01, // scope, length = 1 + 0x02, // value = 2 + // ------------------------ + 0x00, 0x05, 0x00, 0x02, // interest lifetime, length = 2 + 0x7D, 0x00 // 32000 +}; + +uint8_t metisTestDataV0_EncodedObject[] = { + 0x00, 0x02, 0x00, 0x3A, // ver = 0, type = object, length = 58 + 0x00, 0x00, 0x00, 0x1D, // reserved = 0, header length = 29 + // ------------------------ + 0x00, 0x02, 0x00, 0x01, // HOPLIMIT + 0x20, // 32 + // ------------------------ + 0x00, 0x04, 0x00, 0x14, // object Fragment + 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, // fragment 0x0102030405060708 + 0x05, 0xDC, 0x04, 0x00, // MTU 1500, fragcnt 4, fragnum 0 + 0x12, 0x23, 0x34, 0x45, + 0x56, 0x67, 0x78, 0x89, // interest fragid 0x1223344556677889 + // ------------------------ + 0x00, 0x02, 0x00, 0x36, // type = object, length = 54 + // ------------------------ + 0x00, 0x00, 0x00, 0x11, // type = name, length = 17 + 0x00, 0x02, 0x00, 0x05, // type = binary length = 5 (OFFSET is 45 here) + 'h', 'e', 'l', 'l', + 'o', // "hello" + 0xF0, 0x00, 0x00, 0x04, // type = app, length = 4 + 'o', 'u', 'c', 'h', + // ------------------------ + 0x00, 0x02, 0x00, 0x08, // nameauth, length = 8 + 0x00, 0x01, 0x00, 0x04, // type = keyid, length = 4 + 0xA0, 0xB0, 0xC0, 0xD0, // value = 0xA0B0C0D0 + // ------------------------ + 0x00, 0x04, 0x00, 0x07, // contents, length = 7 + 0xAA, 0xBB, 0xCC, 0xDD, + 0xEE, 0xFF, 0xFF, // value = 0xAABBCCDDEEFFFF + // ------------------------ + 0x00, 0x05, 0x00, 0x06, // signature block, length = 6 + 0x00, 0x0E, 0x00, 0x02, // signature bits, length = 2 + 0x00, 0x00 // value = 0x0000 +}; + +const MetisTlvExtent metisTestDataV0_EncodedObject_name = { .offset = 45, .length = 17 }; +const MetisTlvExtent metisTestDataV0_EncodedObject_keyid = { .offset = 70, .length = 4 }; + +uint8_t metisTestDataV0_EncodedObject_no_name[] = { + 0x00, 0x02, 0x00, 0x3A, // ver = 0, type = object, length = 58 + 0x00, 0x00, 0x00, 0x1D, // reserved = 0, header length = 29 + // ------------------------ + 0x00, 0x02, 0x00, 0x01, // HOPLIMIT + 0x20, // 32 + // ------------------------ + 0x00, 0x04, 0x00, 0x14, // object Fragment + 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, // fragment 0x0102030405060708 + 0x05, 0xDC, 0x04, 0x00, // MTU 1500, fragcnt 4, fragnum 0 + 0x12, 0x23, 0x34, 0x45, + 0x56, 0x67, 0x78, 0x89, // interest fragid 0x1223344556677889 + // ------------------------ + 0x00, 0x02, 0x00, 0x36, // type = object, length = 54 + // ------------------------ + 0xFF, 0xFF, 0x00, 0x11, // type = Unknown, length = 17 + 0x00, 0x02, 0x00, 0x05, // type = binary, length = 5 OFFSET = 45 + 'h', 'e', 'l', 'l', + 'o', // "hello" + 0xF0, 0x00, 0x00, 0x04, // type = app, length = 4 + 'o', 'u', 'c', 'h', + // ------------------------ + 0x00, 0x02, 0x00, 0x08, // nameauth, length = 8 + 0x00, 0x01, 0x00, 0x04, // type = keyid, length = 4 + 0xA0, 0xB0, 0xC0, 0xD0, // value = 0xA0B0C0D0 + // ------------------------ + 0x00, 0x04, 0x00, 0x07, // contents, length = 7 + 0xAA, 0xBB, 0xCC, 0xDD, + 0xEE, 0xFF, 0xFF, // value = 0xAABBCCDDEEFFFF + // ------------------------ + 0x00, 0x05, 0x00, 0x06, // signature block, length = 6 + 0x00, 0x0E, 0x00, 0x02, // signature bits, length = 2 + 0x00, 0x00 // value = 0x0000 +}; + +/** + * Same name as metisTestDataV0_EncodedObject, but different keyid and thus different object hash + */ +uint8_t metisTestDataV0_SecondObject[] = { + 0x00, 0x02, 0x00, 0x3E, // ver = 0, type = object, length = 62 + 0x00, 0x00, 0x00, 0x1D, // reserved = 0, header length = 29 + // ------------------------ + 0x00, 0x02, 0x00, 0x01, // HOPLIMIT + 0x20, // 32 + // ------------------------ + 0x00, 0x04, 0x00, 0x14, // object Fragment + 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, // fragment 0x0102030405060708 + 0x05, 0xDC, 0x04, 0x00, // MTU 1500, fragcnt 4, fragnum 0 + 0x12, 0x23, 0x34, 0x45, + 0x56, 0x67, 0x78, 0x89, // interest fragid 0x1223344556677889 + // ------------------------ + 0x00, 0x02, 0x00, 0x36, // type = object, length = 54 + // ------------------------ + 0x00, 0x00, 0x00, 0x11, // type = name, length = 17 + 0x00, 0x02, 0x00, 0x05, // type = binary, length = 5 OFFSET = 45 + 'h', 'e', 'l', 'l', + 'o', // "hello" + 0xF0, 0x00, 0x00, 0x04, // type = app, length = 4 + 'o', 'u', 'c', 'h', + // ------------------------ + 0x00, 0x02, 0x00, 0x08, // nameauth, length = 8 + 0x00, 0x01, 0x00, 0x08, // type = keyid, length = 8 + 0xA0, 0xB0, 0xC0, 0xD0, // value = 0xA0B0C0D011223344 + 0x11, 0x22, 0x33, 0x44, + // ------------------------ + 0x00, 0x04, 0x00, 0x07, // contents, length = 7 + 0xAA, 0xBB, 0xCC, 0xDD, + 0xEE, 0xFF, 0xFF, // value = 0xAABBCCDDEEFFFF + // ------------------------ + 0x00, 0x05, 0x00, 0x06, // signature block, length = 6 + 0x00, 0x0E, 0x00, 0x02, // signature bits, length = 2 + 0x00, 0x00 // value = 0x0000 +}; + +uint8_t metisTestDataV0_object_with_othername[] = { + 0x00, 0x02, 0x00, 0x3E, // ver = 0, type = object, length = 62 + 0x00, 0x00, 0x00, 0x1D, // reserved = 0, header length = 29 + // ------------------------ + 0x00, 0x02, 0x00, 0x01, // HOPLIMIT + 0x20, // 32 + // ------------------------ + 0x00, 0x04, 0x00, 0x14, // object Fragment + 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, // fragment 0x0102030405060708 + 0x05, 0xDC, 0x04, 0x00, // MTU 1500, fragcnt 4, fragnum 0 + 0x12, 0x23, 0x34, 0x45, + 0x56, 0x67, 0x78, 0x89, // interest fragid 0x1223344556677889 + // ------------------------ + 0x00, 0x02, 0x00, 0x36, // type = object, length = 54 + // ------------------------ + 0x00, 0x00, 0x00, 0x11, // type = name, length = 17 + 0x00, 0x02, 0x00, 0x05, // type = binary, length = 5 OFFSET = 45 + 'p', 'a', 'r', 't', // "party" + 'y', + 0xF0, 0x00, 0x00, 0x04, // type = app, length = 4 + 'o', 'u', 'c', 'h', + // ------------------------ + 0x00, 0x02, 0x00, 0x08, // nameauth, length = 8 + 0x00, 0x01, 0x00, 0x08, // type = keyid, length = 8 + 0xA0, 0xB0, 0xC0, 0xD0, // value = 0xA0B0C0D011223344 + 0x11, 0x22, 0x33, 0x44, + // ------------------------ + 0x00, 0x04, 0x00, 0x07, // contents, length = 7 + 0xAA, 0xBB, 0xCC, 0xDD, + 0xEE, 0xFF, 0xFF, // value = 0xAABBCCDDEEFFFF + // ------------------------ + 0x00, 0x05, 0x00, 0x06, // signature block, length = 6 + 0x00, 0x0E, 0x00, 0x02, // signature bits, length = 2 + 0x00, 0x00 // value = 0x0000 +}; + + +/** + * An example control message + */ +uint8_t metisTestDataV0_CPIMessage[] = { + 0x00, 0xa4, 0x00, 0x33, // ver = 0, type = control, length = 51 + 0x00, 0x00, 0x00, 0x00, + // ------------------------ + 0xbe, 0xef, 0x00, 0x2f, + 0x7b, 0x22, 0x43, 0x50,0x49, 0x5f, 0x52, 0x45, // {"CPI_RE + 0x51, 0x55, 0x45, 0x53,0x54, 0x22, 0x3a, 0x7b, // QUEST":{ + 0x22, 0x53, 0x45, 0x51,0x55, 0x45, 0x4e, 0x43, // "SEQUENC + 0x45, 0x22, 0x3a, 0x31,0x2c, 0x22, 0x52, 0x4f, // E":1,"RO + 0x55, 0x54, 0x45, 0x5f,0x4c, 0x49, 0x53, 0x54, // UTE_LIST + 0x22, 0x3a, 0x7b, 0x7d,0x7d, 0x7d, 0x00, // ":{}}} +}; +#endif // Metis_metis_TestDataV0_h diff --git a/metis/ccnx/forwarder/metis/testdata/metis_TestDataV1.h b/metis/ccnx/forwarder/metis/testdata/metis_TestDataV1.h new file mode 100644 index 00000000..2bcf4fe4 --- /dev/null +++ b/metis/ccnx/forwarder/metis/testdata/metis_TestDataV1.h @@ -0,0 +1,600 @@ +/* + * 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_TestDataV1 + * @brief v1 schema test packets + * + * Test vectors of V1 schema packets. + * + */ + +#ifndef Metis_metis_TestDataV1_h +#define Metis_metis_TestDataV1_h + +// ======================= +// Interests + +__attribute__((unused)) +static uint8_t metisTestDataV1_Interest_AllFields[] = { + 0x01, 0x00, 0x00, 100, // ver = 1, type = interest, length = 100 + 0x20, 0x00, 0x11, 14, // HopLimit = 32, reserved = 0, flags = 0x11, header length = 14 + // ------------------------ + 0x00, 0x01, 0x00, 2, // Interest Lifetime (2 bytes) + 0xEA, 0xEB, + // ------------------------ + 0x00, 0x01, 0x00, 82, // type = interest, length = 82 + // ------------------------ + 0x00, 0x00, 0x00, 8, // type = name, length = 8 + 0x00, 0x02, 0x00, 4, // type = binary, length = 4 + 'c', 'o', 'o', 'l', // "cool" + // ------------------------ + 0x00, 0x02, 0x00, 16, // type = keyid restriction, length = 16 + 0xa0, 0xa1, 0xa2, 0xa3, + 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, + 0xac, 0xad, 0xae, 0xaf, + // ------------------------ + 0x00, 0x03, 0x00, 32, // type = hash restriction, length = 32 + 0xb0, 0xb1, 0xb2, 0xb3, + 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, + 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, + 0xcc, 0xcd, 0xce, 0xcf, + // ------------------------ + 0x00, 0x04, 0x00, 1, // Interest payload method (1 byte) + 0x00, + // ------------------------ + 0x00, 0x01, 0x00, 5, // type = payload, length = 5 + 0xD0, 0xD1, 0xD2, 0xD3, + 0xD4, +}; + +__attribute__((unused)) +static uint8_t metisTestDataV1_Interest_NameA_Crc32c[] = { + 0x01, 0x00, 0x00, 65, // ver = 1, type = interest, length = 65 + 0x20, 0x00, 0x00, 24, // HopLimit = 32, reserved = 0, header length = 24 + // ------------------------ + 0x00, 0x03, 0x00, 12, // Interest Fragment + 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, // fragment 0x0102030405060708 + 0x05, 0xDC, 0x00, 0x00, // MTU 1500, fragcnt 0, fragnum 0 + // ------------------------ + 0x00, 0x01, 0x00, 0x15, // type = interest, length = 21 + // ------------------------ + 0x00, 0x00, 0x00, 0x11, // type = name, length = 17 + 0x00, 0x02, 0x00, 0x05, // type = binary, length = 5 + 'h', 'e', 'l', 'l', // "hello" + 'o', + 0xF0, 0x00, 0x00, 0x04, // type = app, length = 4 + 'o', 'u', 'c', 'h', // "ouch" + // ------------------------ + 0x00, 0x03, 0x00, 4, // validation alg, length = 4 + 0x00, 0x02, 0x00, 0x00, // CRC32C + // ------------------------ + 0x00, 0x04, 0x00, 4, // validation payload + 0x6A, 0xD7, 0xB1, 0xF2 // 6AD7B1F2 +}; + +__attribute__((unused)) +static uint8_t metisTestDataV1_Interest_NameAAndKeyId[] = { + 0x01, 0x00, 0x00, 89, // ver = 1, type = interest, length = 73 + 0x20, 0x00, 0x11, 14, // HopLimit = 32, reserved = 0, flags = 0x11, header length = 14 + // ------------------------ + 0x00, 0x01, 0x00, 2, // Interest Lifetime (2 bytes) + 0xEA, 0xEB, + // ------------------------ + 0x00, 0x01, 0x00, 71, // type = interest, length = 55 + // ------------------------ + 0x00, 0x00, 0x00, 0x11, // type = name, length = 17 + 0x00, 0x02, 0x00, 0x05, // type = binary, length = 5 + 'h', 'e', 'l', 'l', // "hello" + 'o', + 0xF0, 0x00, 0x00, 0x04, // type = app, length = 4 + 'o', 'u', 'c', 'h', // "ouch" + // ------------------------ + 0x00, 0x02, 0x00, 32, // type = keyid restriction, length = 32 + 0x5c, 0x23, 0x4c, 0x28, + 0x50, 0xda, 0x20, 0x7b, + 0x88, 0x25, 0x8b, 0xf3, + 0x62, 0x61, 0x96, 0xd8, + 0xf0, 0x60, 0x76, 0x38, + 0xa2, 0xd4, 0xe0, 0xe2, + 0x49, 0xb2, 0xa9, 0xaf, + 0xce, 0xb8, 0x85, 0x59, + // ------------------------ + 0x00, 0x04, 0x00, 1, // Interest payload method (1 byte) + 0x00, + // ------------------------ + 0x00, 0x01, 0x00, 5, // type = payload, length = 5 + 0xD0, 0xD1, 0xD2, 0xD3, + 0xD4, +}; + + +// =========================== +// Content Objects + +__attribute__((unused)) +static uint8_t metisTestDataV1_ContentObject_NameA_Crc32c[] = { + 0x01, 0x01, 0x00, 85, // ver = 1, type = content object, length = 85 + 0x00, 0x00, 0x00, 44, // HopLimit = 31, reserved = 0, header length = 44 + // ------------------------ + 0x00, 0x04, 0x00, 20, // ContentObject Fragment, length = 20 + 0x12, 0x23, 0x34, 0x45, + 0x56, 0x67, 0x78, 0x89, // fragid 0x1223344556677889 + 0x05, 0xDC, 0x01, 0x00, // MTU 1500, fragcnt 1, fragnum 0 + 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, // interest fragment 0x0102030405060708 + // ------------------------ + 0x00, 0x02, 0x00, 8, // Recommended Cache Time + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x6D, 0xDD, 0x00, // 2 hours (0x6DDD00 milli seconds) + // ------------------------ + 0x00, 0x02, 0x00, 21, // type = content object, length = 21 + // ------------------------ + 0x00, 0x00, 0x00, 0x11, // type = name, length = 17 + 0x00, 0x02, 0x00, 0x05, // type = binary, length = 5 + 'h', 'e', 'l', 'l', // "hello" + 'o', + 0xF0, 0x00, 0x00, 0x04, // type = app, length = 4 + 'o', 'u', 'c', 'h', // "ouch" + // ------------------------ + 0x00, 0x03, 0x00, 4, // validation alg, length = 4 + 0x00, 0x02, 0x00, 0x00, // CRC32C + // ------------------------ + 0x00, 0x04, 0x00, 4, // validation payload + 0x2C, 0x3C, 0xC0, 0xAF // 2C3CC0AF +}; + +__attribute__((unused)) +static uint8_t metisTestDataV1_ContentObject_NameA_KeyId1_RsaSha256[] = { + 0x01, 0x01, 0x01, 0xB4, // ver = 1, type = content object, length = 436 + 0x00, 0x00, 0x00, 32, // HopLimit = 0, reserved = 0, header length = 32 + // ------------------------ + 0x00, 0x04, 0x00, 20, // ContentObject Fragment, length = 20 + 0x12, 0x23, 0x34, 0x45, + 0x56, 0x67, 0x78, 0x89, // fragid 0x1223344556677889 + 0x05, 0xDC, 0x01, 0x00, // MTU 1500, fragcnt 1, fragnum 0 + 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, // interest fragment 0x0102030405060708 + // ------------------------ + 0x00, 0x02, 0x00, 58, // type = content object, length = 58 + // ------------------------ + 0x00, 0x00, 0x00, 17, // type = name, length = 17 + 0x00, 0x02, 0x00, 0x05, // type = binary, length = 5 + 'h', 'e', 'l', 'l', // "hello" + 'o', + 0xF0, 0x00, 0x00, 0x04, // type = app, length = 4 + 'o', 'u', 'c', 'h', // "ouch" + // ------------------------ + 0x00, 0x05, 0x00, 1, // PayloadType + 1, // type 1 = key + 0x00, 0x06, 0x00, 0x08, // expiry time in msec + 0x00, 0x00, 0x01, 0x43, // 1,388,534,400,000 msec + 0x4B, 0x19, 0x84, 0x00, + 0x00, 0x19, 0x00, 4, // end chunk number + 0x06, 0x05, 0x04, 0x03, + // ------------------------ + 0x00, 0x01, 0x00, 8, // paylaod, length = 8 + 0x73, 0x75, 0x72, 0x70, + 0x72, 0x69, 0x73, 0x65, + // ------------------------ + 0x00, 0x03, 0x00, 206, // validation alg, length = 206 + 0x00, 0x06, 0x00, 202, // RSA-SHA256, length = 162 + 4 + 32 + 4 = 202 + 0x00, 0x09, 0x00, 32, // type = keyid, length = 32 + 0x5c, 0x23, 0x4c, 0x28, + 0x50, 0xda, 0x20, 0x7b, + 0x88, 0x25, 0x8b, 0xf3, + 0x62, 0x61, 0x96, 0xd8, + 0xf0, 0x60, 0x76, 0x38, + 0xa2, 0xd4, 0xe0, 0xe2, + 0x49, 0xb2, 0xa9, 0xaf, + 0xce, 0xb8, 0x85, 0x59, + 0x00, 0x0B, 0x00, 162, // public key, length = 162 + 0x30, 0x81, 0x9f, 0x30,0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x01,0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30, 0x81, + 0x89, 0x02, 0x81, 0x81,0x00, 0xa7, 0xd6, 0x93, 0xc5, 0xf1, 0x73, 0xc6, + 0x12, 0xb5, 0xfa, 0x79,0xc5, 0xef, 0x18, 0x8b, 0xef, 0xaa, 0xcd, 0x04, + 0x01, 0x8f, 0x83, 0x77,0x3f, 0xb1, 0x66, 0xa1, 0x5a, 0xb9, 0x39, 0x2a, + 0xb2, 0x3e, 0x30, 0xfb,0x11, 0xfc, 0x57, 0xfc, 0x9d, 0xd1, 0x2f, 0x77, + 0xf0, 0xab, 0x77, 0xad,0x66, 0x2c, 0x26, 0xc8, 0x9b, 0x51, 0x6f, 0x69, + 0xbf, 0x26, 0x10, 0x06,0x29, 0xee, 0xcb, 0xb0, 0x2c, 0x5e, 0x91, 0x0e, + 0x24, 0x42, 0x8d, 0xe6,0x75, 0xa0, 0x4e, 0x04, 0x43, 0x0c, 0x3e, 0x4c, + 0x06, 0x74, 0x67, 0xad,0x84, 0xc3, 0xe8, 0xf4, 0xc5, 0x94, 0x73, 0xc4, + 0x9f, 0x25, 0xa8, 0x42,0x06, 0xbf, 0x45, 0x19, 0xe2, 0x98, 0x1c, 0x36, + 0xa1, 0x43, 0x4d, 0x9b,0x71, 0x08, 0xe1, 0x82, 0xdf, 0xe2, 0x4f, 0x2a, + 0x3c, 0x3d, 0x0f, 0x35,0x92, 0xae, 0x70, 0x27, 0xfb, 0xd2, 0xe4, 0x1e, + 0x27, 0x02, 0x03, 0x01,0x00, 0x01, + // ------------------------ + 0x00, 0x04, 0x00, 128, // validation payload, length = 128 + 0x03, 0x46, 0xee, 0xb7,0x30, 0x1c, 0xea, 0x13, 0x0c, 0xce, 0x83, 0x5b, + 0x7b, 0x4f, 0xf5, 0x83,0x37, 0x08, 0x7f, 0xe0, 0xe1, 0xc9, 0x70, 0x09, + 0x5e, 0xc2, 0x1c, 0xd3,0x74, 0xbb, 0xbd, 0x72, 0x35, 0xa4, 0x1b, 0x0f, + 0x3d, 0x04, 0x5e, 0xf7,0xc1, 0xdf, 0xea, 0xc3, 0x50, 0x47, 0x14, 0xf9, + 0xb7, 0xbb, 0x42, 0xf9,0x3e, 0xaa, 0x49, 0xd2, 0x9f, 0xd1, 0xab, 0xf6, + 0xda, 0x32, 0x4a, 0xb1,0xb9, 0x69, 0x91, 0x57, 0x43, 0x5d, 0x06, 0xcf, + 0x1d, 0x9f, 0x7c, 0x28,0xee, 0x35, 0xaa, 0xd0, 0xb2, 0x8d, 0x34, 0x09, + 0xcd, 0xdb, 0x01, 0xf7,0xda, 0xe8, 0x59, 0x98, 0x4e, 0x59, 0xfa, 0x13, + 0xd0, 0xd1, 0x54, 0x8e,0x64, 0x8c, 0xc6, 0xd7, 0x6b, 0xc5, 0x89, 0xeb, + 0x37, 0x8f, 0x53, 0x04,0xba, 0x03, 0x05, 0xb4, 0x67, 0x73, 0xe1, 0x51, + 0x59, 0x12, 0xbc, 0x25,0xaa, 0xa2, 0xc1, 0x18 +}; + +__attribute__((unused)) +static uint8_t metisTestDataV1_CPI_AddRoute_Crc32c[] = "\x01\xA4\x00\xB7" + "\x00\x00\x00\x08" + "\xBE\xEF\x00\x9A" + "{\"CPI_REQUEST\":{\"SEQUENCE\":22,\"REGISTER\":{\"PREFIX\":\"lci:/howdie/stranger\",\"INTERFACE\":55,\"FLAGS\":0,\"PROTOCOL\":\"STATIC\",\"ROUTETYPE\":\"LONGEST\",\"COST\":200}}}" + "\x00\x03\x00\x04" + "\x00\x02\x00\x00" + "\x00\x04\x00\x04" + "\x78\xfd\x92\x6a"; + +// =============================================== +// HopByHop Fragments +// These fragments together make a small Interest packet. + +__attribute__((unused)) +static uint8_t metisTestDataV1_HopByHopFrag_Begin[] = { + 0x01, 4, 0x00, 20, // ver = 1, type = hop-by-hop frag, length = 20 + 0x40, 0x00, 0x01, 8, // B, seqnum = 1, header length = 8 + // ------------------------ + 0x00, 0x05, 0x00, 8, // Interest Fragment + // ------------------------ + 0x01, 0x00, 0x00, 24, // ver = 1, type = interest, length = 24 + 0x20, 0x00, 0x11, 8, // HopLimit = 32, reserved = 0, flags = 0x11, header length = 8 +}; + +__attribute__((unused)) +static uint8_t metisTestDataV1_HopByHopFrag_Begin_Fragment[] = { + 0x01, 0x00, 0x00, 24, // ver = 1, type = interest, length = 24 + 0x20, 0x00, 0x11, 8, // HopLimit = 32, reserved = 0, flags = 0x11, header length = 8 +}; + +__attribute__((unused)) +static uint8_t metisTestDataV1_HopByHopFrag_Middle[] = { + 0x01, 4, 0x00, 16, // ver = 1, type = hop-by-hop frag, length = 16 + 0x00, 0x00, 0x02, 8, // no flag, seqnum = 2, header length = 8 + // ------------------------ + 0x00, 0x05, 0x00, 4, // Interest Fragment + // ------------------------ + 0x00, 0x01, 0x00, 12, // type = interest, length = 12 +}; + +__attribute__((unused)) +static uint8_t metisTestDataV1_HopByHopFrag_Middle_Fragment[] = { + 0x00, 0x01, 0x00, 12, // type = interest, length = 12 +}; + +__attribute__((unused)) +static uint8_t metisTestDataV1_HopByHopFrag_End[] = { + 0x01, 4, 0x00, 24, // ver = 1, type = hop-by-hop frag, length = 24 + 0x20, 0x00, 0x03, 8, // E, seqnum = 3, header length = 8 + // ------------------------ + 0x00, 0x05, 0x00, 12, // Interest Fragment + // ------------------------ + 0x00, 0x00, 0x00, 8, // type = name, length = 8 + 0x00, 0x02, 0x00, 4, // type = binary, length = 4 + 'c', 'o', 'o', 'l', // "cool" +}; + +__attribute__((unused)) +static uint8_t metisTestDataV1_HopByHopFrag_End_Fragment[] = { + 0x00, 0x00, 0x00, 8, // type = name, length = 8 + 0x00, 0x02, 0x00, 4, // type = binary, length = 4 + 'c', 'o', 'o', 'l', // "cool" +}; + +__attribute__((unused)) +static uint8_t metisTestDataV1_HopByHopFrag_BeginEnd[] = { + 0x01, 4, 0x00, 36, // ver = 1, type = hop-by-hop frag, length = 36 + 0x60, 0x00, 0x04, 8, // B, seqnum = 4, header length = 8 + // ------------------------ + 0x00, 0x05, 0x00, 24, // Interest Fragment + // ------------------------ + 0x01, 0x00, 0x00, 24, // ver = 1, type = interest, length = 24 + 0x20, 0x00, 0x11, 8, // HopLimit = 32, reserved = 0, flags = 0x11, header length = 8 + 0x00, 0x01, 0x00, 12, // type = interest, length = 12 + 0x00, 0x00, 0x00, 8, // type = name, length = 8 + 0x00, 0x02, 0x00, 4, // type = binary, length = 4 + 'c', 'o', 'o', 'l', // "cool" +}; + +__attribute__((unused)) +static uint8_t metisTestDataV1_HopByHopFrag_BeginEnd_Fragment[] = { + 0x01, 0x00, 0x00, 24, // ver = 1, type = interest, length = 24 + 0x20, 0x00, 0x11, 8, // HopLimit = 32, reserved = 0, flags = 0x11, header length = 8 + 0x00, 0x01, 0x00, 12, // type = interest, length = 12 + 0x00, 0x00, 0x00, 8, // type = name, length = 8 + 0x00, 0x02, 0x00, 4, // type = binary, length = 4 + 'c', 'o', 'o', 'l', // "cool" +}; + +__attribute__((unused)) +static uint8_t metisTestDataV1_HopByHopFrag_Idle[] = { + 0x01, 4, 0x00, 8, // ver = 1, type = hop-by-hop frag, length = 16 + 0x10, 0x00, 0x05, 8, // B, seqnum = 1, header length = 8 +}; + +// =============================================== +// Ethernet padded interest + +__attribute__((unused)) +static uint8_t metisTestDataV1_InterestWithEthernetPadding[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // dmac + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, // smac + 0x08, 0x01, // ethertype + 0x01, 0x00, 0x00, 0x18, // ver = 1, len = 24 + 0xfe, 0x00, 0x00, 0x08, // hoplimit = 254, header length = 8 + 0x00, 0x01, 0x00, 0x0c, // interest + 0x00, 0x00, 0x00, 0x08, // name + 0x00, 0x01, 0x00, 0x04, // name component + 0x64, 0x61, 0x74, 0x65, // "date" + 0x00, 0x00, 0x00, 0x00, // ethernet padding + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00 +}; + +__attribute__((unused)) +static uint8_t metisTestDataV1_InterestWithEthernetPaddingStripped[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // dmac + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, // smac + 0x08, 0x01, // ethertype + 0x01, 0x00, 0x00, 0x18, // ver = 1, len = 24 + 0xfe, 0x00, 0x00, 0x08, // hoplimit = 254, header length = 8 + 0x00, 0x01, 0x00, 0x0c, // interest + 0x00, 0x00, 0x00, 0x08, // name + 0x00, 0x01, 0x00, 0x04, // name component + 0x64, 0x61, 0x74, 0x65, // "date" +}; + +// =============================================== +// Error frames for coding violation testing + +// Less than fixed header size +__attribute__((unused)) +static uint8_t metisTestDataV1_Runt[] = { + 0x01, 0x00, 0x00, 30, // ver = 1, type = interest, length = 30 + 0x20, 0x00, 0x11 +}; + +// Version Unknown +__attribute__((unused)) +static uint8_t metisTestDataV1_BadVersion[] = { + 0xFF, 0x00, 0x00, 30, // ver = 255 (bad), type = interest, length = 30 + 0x20, 0x00, 0x11, 14, // HopLimit = 32, reserved = 0, flags = 0x11, header length = 14 + // ------------------------ + 0x00, 0x01, 0x00, 2, // Interest Lifetime (2 bytes) + 0xEA, 0xEB, + // ------------------------ + 0x00, 0x01, 0x00, 12, // type = interest, length = 12 + // ------------------------ + 0x00, 0x00, 0x00, 8, // type = name, length = 8 + 0x00, 0x02, 0x00, 4, // type = binary, length = 4 + 'c', 'o', 'o', 'l', // "cool" +}; + +// PacketType Unknown +__attribute__((unused)) +static uint8_t metisTestDataV1_BadPacketType[] = { + 0x01, 0x77, 0x00, 30, // ver = 1, type = 77 (bad), length = 30 + 0x20, 0x00, 0x11, 14, // HopLimit = 32, reserved = 0, flags = 0x11, header length = 14 + // ------------------------ + 0x00, 0x01, 0x00, 2, // Interest Lifetime (2 bytes) + 0xEA, 0xEB, + // ------------------------ + 0x00, 0x01, 0x00, 12, // type = interest, length = 12 + // ------------------------ + 0x00, 0x00, 0x00, 8, // type = name, length = 8 + 0x00, 0x02, 0x00, 4, // type = binary, length = 4 + 'c', 'o', 'o', 'l', // "cool" +}; + +// packet length less than fixed header length +__attribute__((unused)) +static uint8_t metisTestDataV1_PacketLengthLessFixedHeaderLength[] = { + 0x01, 0x00, 0x00, 6, // ver = 1, type = interest, length = 6 (too short) + 0x20, 0x00, 0x11, 14, // HopLimit = 32, reserved = 0, flags = 0x11, header length = 14 + // ------------------------ + 0x00, 0x01, 0x00, 2, // Interest Lifetime (2 bytes) + 0xEA, 0xEB, + // ------------------------ + 0x00, 0x01, 0x00, 12, // type = interest, length = 12 + // ------------------------ + 0x00, 0x00, 0x00, 8, // type = name, length = 8 + 0x00, 0x02, 0x00, 4, // type = binary, length = 4 + 'c', 'o', 'o', 'l', // "cool" +}; + +// packet length less than fixed header length +__attribute__((unused)) +static uint8_t metisTestDataV1_PacketLengthLessBeyondEnd[] = { + 0x01, 0x00, 0x00, 40, // ver = 1, type = interest, length = 40 (beyond end of frame) + 0x20, 0x00, 0x11, 14, // HopLimit = 32, reserved = 0, flags = 0x11, header length = 14 + // ------------------------ + 0x00, 0x01, 0x00, 2, // Interest Lifetime (2 bytes) + 0xEA, 0xEB, + // ------------------------ + 0x00, 0x01, 0x00, 12, // type = interest, length = 12 + // ------------------------ + 0x00, 0x00, 0x00, 8, // type = name, length = 8 + 0x00, 0x02, 0x00, 4, // type = binary, length = 4 + 'c', 'o', 'o', 'l', // "cool" +}; + + +// header length less than fixed header length +__attribute__((unused)) +static uint8_t metisTestDataV1_HeaderLengthLessFixedHeaderLength[] = { + 0x01, 0x00, 0x00, 30, // ver = 1, type = interest, length = 30 + 0x20, 0x00, 0x11, 7, // HopLimit = 32, reserved = 0, flags = 0x11, header length = 7 + // ------------------------ + 0x00, 0x01, 0x00, 2, // Interest Lifetime (2 bytes) + 0xEA, 0xEB, + // ------------------------ + 0x00, 0x01, 0x00, 12, // type = interest, length = 12 + // ------------------------ + 0x00, 0x00, 0x00, 8, // type = name, length = 8 + 0x00, 0x02, 0x00, 4, // type = binary, length = 4 + 'c', 'o', 'o', 'l', // "cool" +}; + +// header length less than actual header length +__attribute__((unused)) +static uint8_t metisTestDataV1_HeaderLengthTooShort[] = { + 0x01, 0x00, 0x00, 30, // ver = 1, type = interest, length = 30 + 0x20, 0x00, 0x11, 13, // HopLimit = 32, reserved = 0, flags = 0x11, header length = 13 (1 byte too short) + // ------------------------ + 0x00, 0x01, 0x00, 2, // Interest Lifetime (2 bytes) + 0xEA, 0xEB, + // ------------------------ + 0x00, 0x01, 0x00, 12, // type = interest, length = 12 + // ------------------------ + 0x00, 0x00, 0x00, 8, // type = name, length = 8 + 0x00, 0x02, 0x00, 4, // type = binary, length = 4 + 'c', 'o', 'o', 'l', // "cool" +}; + +// header length less than actual header length +__attribute__((unused)) +static uint8_t metisTestDataV1_HeaderLengthTooLong[] = { + 0x01, 0x00, 0x00, 30, // ver = 1, type = interest, length = 30 + 0x20, 0x00, 0x11, 16, // HopLimit = 32, reserved = 0, flags = 0x11, header length = 16 (2 bytes too long) + // ------------------------ + 0x00, 0x01, 0x00, 2, // Interest Lifetime (2 bytes) + 0xEA, 0xEB, + // ------------------------ + 0x00, 0x01, 0x00, 12, // type = interest, length = 12 + // ------------------------ + 0x00, 0x00, 0x00, 8, // type = name, length = 8 + 0x00, 0x02, 0x00, 4, // type = binary, length = 4 + 'c', 'o', 'o', 'l', // "cool" +}; + +// header length swallows whole message +__attribute__((unused)) +static uint8_t metisTestDataV1_HeaderLengthWholePacket[] = { + 0x01, 0x00, 0x00, 30, // ver = 1, type = interest, length = 30 + 0x20, 0x00, 0x11, 30, // HopLimit = 32, reserved = 0, flags = 0x11, header length = 30 + // ------------------------ + 0x00, 0x01, 0x00, 2, // Interest Lifetime (2 bytes) + 0xEA, 0xEB, + // ------------------------ + 0x00, 0x01, 0x00, 12, // type = interest, length = 12 + // ------------------------ + 0x00, 0x00, 0x00, 8, // type = name, length = 8 + 0x00, 0x02, 0x00, 4, // type = binary, length = 4 + 'c', 'o', 'o', 'l', // "cool" +}; + +// header length beyond end of frame +__attribute__((unused)) +static uint8_t metisTestDataV1_HeaderLengthBeyondEnd[] = { + 0x01, 0x00, 0x00, 30, // ver = 1, type = interest, length = 30 + 0x20, 0x00, 0x11, 40, // HopLimit = 32, reserved = 0, flags = 0x11, header length = 40 + // ------------------------ + 0x00, 0x01, 0x00, 2, // Interest Lifetime (2 bytes) + 0xEA, 0xEB, + // ------------------------ + 0x00, 0x01, 0x00, 12, // type = interest, length = 12 + // ------------------------ + 0x00, 0x00, 0x00, 8, // type = name, length = 8 + 0x00, 0x02, 0x00, 4, // type = binary, length = 4 + 'c', 'o', 'o', 'l', // "cool" +}; + + +// packet length less than optional header length +__attribute__((unused)) +static uint8_t metisTestDataV1_PacketLengthLessHeaderLength[] = { + 0x01, 0x00, 0x00, 12, // ver = 1, type = interest, length = 12 (past fixed header, less than optional header) + 0x20, 0x00, 0x11, 14, // HopLimit = 32, reserved = 0, flags = 0x11, header length = 14 + // ------------------------ + 0x00, 0x01, 0x00, 2, // Interest Lifetime (2 bytes) + 0xEA, 0xEB, + // ------------------------ + 0x00, 0x01, 0x00, 12, // type = interest, length = 12 + // ------------------------ + 0x00, 0x00, 0x00, 8, // type = name, length = 8 + 0x00, 0x02, 0x00, 4, // type = binary, length = 4 + 'c', 'o', 'o', 'l', // "cool" +}; + + +// T_INTEREST length too long +__attribute__((unused)) +static uint8_t metisTestDataV1_Interest_MessageLengthTooLong[] = { + 0x01, 0x00, 0x00, 30, // ver = 1, type = interest, length = 30 + 0x20, 0x00, 0x11, 14, // HopLimit = 31, reserved = 0, flags = 0x11, header length = 14 + // ------------------------ + 0x00, 0x01, 0x00, 2, // Interest Lifetime (2 bytes) + 0xEA, 0xEB, + // ------------------------ + 0x00, 0x01, 0x00, 13, // type = interest, length = 13 (1 byte too far) + // ------------------------ + 0x00, 0x00, 0x00, 8, // type = name, length = 8 + 0x00, 0x02, 0x00, 4, // type = binary, length = 4 (this goes beyond the T_INTEREST length) + 'c', 'o', 'o', 'l', // "cool" +}; + + +// T_INTEREST length too short +__attribute__((unused)) +static uint8_t metisTestDataV1_Interest_MessageLengthTooShort[] = { + 0x01, 0x00, 0x00, 30, // ver = 1, type = interest, length = 30 + 0x20, 0x00, 0x11, 14, // HopLimit = 31, reserved = 0, flags = 0x11, header length = 14 + // ------------------------ + 0x00, 0x01, 0x00, 2, // Interest Lifetime (2 bytes) + 0xEA, 0xEB, + // ------------------------ + 0x00, 0x01, 0x00, 11, // type = interest, length = 11 (1 byte too short) + // ------------------------ + 0x00, 0x00, 0x00, 8, // type = name, length = 8 + 0x00, 0x02, 0x00, 4, // type = binary, length = 4 (this goes beyond the T_INTEREST length) + 'c', 'o', 'o', 'l', // "cool" +}; + +#define FRAMEPAIR(array) { .frame = array, .length = sizeof(array) } + +__attribute__((unused)) +struct metisTestDataV1_frame_array { + const uint8_t *frame; + size_t length; +} metisTestDataV1_ErrorFrames[] = { + FRAMEPAIR(metisTestDataV1_Runt), // 0 + FRAMEPAIR(metisTestDataV1_BadVersion), + FRAMEPAIR(metisTestDataV1_BadPacketType), + FRAMEPAIR(metisTestDataV1_PacketLengthLessFixedHeaderLength), + FRAMEPAIR(metisTestDataV1_PacketLengthLessBeyondEnd), + FRAMEPAIR(metisTestDataV1_HeaderLengthLessFixedHeaderLength), // 5 + FRAMEPAIR(metisTestDataV1_HeaderLengthTooShort), + FRAMEPAIR(metisTestDataV1_HeaderLengthTooLong), + FRAMEPAIR(metisTestDataV1_HeaderLengthWholePacket), + FRAMEPAIR(metisTestDataV1_HeaderLengthBeyondEnd), + FRAMEPAIR(metisTestDataV1_PacketLengthLessHeaderLength), + FRAMEPAIR(metisTestDataV1_Interest_MessageLengthTooLong), + FRAMEPAIR(metisTestDataV1_Interest_MessageLengthTooShort), + { .frame = NULL, .length= 0 } +}; + + +#endif // Metis_metis_TestDataV1_h diff --git a/metis/ccnx/forwarder/metis/testdata/metis_TestDataV1_generated.h b/metis/ccnx/forwarder/metis/testdata/metis_TestDataV1_generated.h new file mode 100644 index 00000000..39368d03 --- /dev/null +++ b/metis/ccnx/forwarder/metis/testdata/metis_TestDataV1_generated.h @@ -0,0 +1,1151 @@ +/* + * 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. + */ + + +/** + * metisTestDataV1_ContentObject_01_Name1_Payload1_SHA256_KeyId1 + * ContentObject + * Name: [lci:/apple/banana/cherry] + * Payload: [The dog barks at midnight.] + * KeyId: [232346697273744B65794964] ('##FirstKeyId') + * ValPayload: [585858205369676E617475726542697473576F756C6442654865726520585858] + * ContentObjectHash: [926E8780E73B1B4F4BDB8B765559856DACF57AA62F2C4FD26338FA1540B3FE6B] + */ +static uint8_t metisTestDataV1_ContentObject_01_Name1_Payload1_SHA256_KeyId1[] = { + 0x01, 0x01, 0x00, 0x87, 0x00, 0x00, 0x00, 0x08, + + 0x00, 0x02, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x1d, + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x65, 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, + 0x61, 0x6e, 0x61, 0x00, 0x01, 0x00, 0x06, 0x63, + 0x68, 0x65, 0x72, 0x72, 0x79, 0x00, 0x01, 0x00, + 0x1a, 0x54, 0x68, 0x65, 0x20, 0x64, 0x6f, 0x67, + 0x20, 0x62, 0x61, 0x72, 0x6b, 0x73, 0x20, 0x61, + 0x74, 0x20, 0x6d, 0x69, 0x64, 0x6e, 0x69, 0x67, + 0x68, 0x74, 0x2e, 0x00, 0x03, 0x00, 0x14, 0x00, + 0x06, 0x00, 0x10, 0x00, 0x09, 0x00, 0x0c, 0x23, + 0x23, 0x46, 0x69, 0x72, 0x73, 0x74, 0x4b, 0x65, + 0x79, 0x49, 0x64, 0x00, 0x04, 0x00, 0x20, 0x58, + 0x58, 0x58, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x42, 0x69, 0x74, 0x73, + 0x57, 0x6f, 0x75, 0x6c, 0x64, 0x42, 0x65, 0x48, + 0x65, 0x72, 0x65, 0x20, 0x58, 0x58, 0x58, +}; + +/** + * metisTestDataV1_ContentObject_02_Name1_Payload2_SHA256_KeyId1 + * ContentObject + * Name: [lci:/apple/banana/cherry] + * Payload: [Cats are not dogs. Nor are hamburgers.] + * KeyId: [232346697273744B65794964] ('##FirstKeyId') + * ValPayload: [585858205369676E617475726542697473576F756C6442654865726520585858] + * ContentObjectHash: [B55ABFDEA353A39A08587C5D42EBC382B1322E2BCD2EAAE27AAFFB29EDA2725F] + */ +static uint8_t metisTestDataV1_ContentObject_02_Name1_Payload2_SHA256_KeyId1[] = { + 0x01, 0x01, 0x00, 0x93, 0x00, 0x00, 0x00, 0x08, + + 0x00, 0x02, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x1d, + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x65, 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, + 0x61, 0x6e, 0x61, 0x00, 0x01, 0x00, 0x06, 0x63, + 0x68, 0x65, 0x72, 0x72, 0x79, 0x00, 0x01, 0x00, + 0x26, 0x43, 0x61, 0x74, 0x73, 0x20, 0x61, 0x72, + 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x64, 0x6f, + 0x67, 0x73, 0x2e, 0x20, 0x4e, 0x6f, 0x72, 0x20, + 0x61, 0x72, 0x65, 0x20, 0x68, 0x61, 0x6d, 0x62, + 0x75, 0x72, 0x67, 0x65, 0x72, 0x73, 0x2e, 0x00, + 0x03, 0x00, 0x14, 0x00, 0x06, 0x00, 0x10, 0x00, + 0x09, 0x00, 0x0c, 0x23, 0x23, 0x46, 0x69, 0x72, + 0x73, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x00, + 0x04, 0x00, 0x20, 0x58, 0x58, 0x58, 0x20, 0x53, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x42, 0x69, 0x74, 0x73, 0x57, 0x6f, 0x75, 0x6c, + 0x64, 0x42, 0x65, 0x48, 0x65, 0x72, 0x65, 0x20, + 0x58, 0x58, 0x58, +}; + +/** + * metisTestDataV1_ContentObject_03_Name1_Payload1_SHA256_KeyId2 + * ContentObject + * Name: [lci:/apple/banana/cherry] + * Payload: [Cats are not dogs. Nor are hamburgers.] + * KeyId: [23235365636F6E644B65794964] ('##SecondKeyId') + * ValPayload: [585858205369676E617475726542697473576F756C6442654865726520585858] + * ContentObjectHash: [602EF3993828EFC666FE075CED9A2F1307ECC14895D4168D2BB839AC50F033EB] + */ +static uint8_t metisTestDataV1_ContentObject_03_Name1_Payload1_SHA256_KeyId2[] = { + 0x01, 0x01, 0x00, 0x94, 0x00, 0x00, 0x00, 0x08, + + 0x00, 0x02, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x1d, + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x65, 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, + 0x61, 0x6e, 0x61, 0x00, 0x01, 0x00, 0x06, 0x63, + 0x68, 0x65, 0x72, 0x72, 0x79, 0x00, 0x01, 0x00, + 0x26, 0x43, 0x61, 0x74, 0x73, 0x20, 0x61, 0x72, + 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x64, 0x6f, + 0x67, 0x73, 0x2e, 0x20, 0x4e, 0x6f, 0x72, 0x20, + 0x61, 0x72, 0x65, 0x20, 0x68, 0x61, 0x6d, 0x62, + 0x75, 0x72, 0x67, 0x65, 0x72, 0x73, 0x2e, 0x00, + 0x03, 0x00, 0x15, 0x00, 0x06, 0x00, 0x11, 0x00, + 0x09, 0x00, 0x0d, 0x23, 0x23, 0x53, 0x65, 0x63, + 0x6f, 0x6e, 0x64, 0x4b, 0x65, 0x79, 0x49, 0x64, + 0x00, 0x04, 0x00, 0x20, 0x58, 0x58, 0x58, 0x20, + 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x42, 0x69, 0x74, 0x73, 0x57, 0x6f, 0x75, + 0x6c, 0x64, 0x42, 0x65, 0x48, 0x65, 0x72, 0x65, + 0x20, 0x58, 0x58, 0x58, +}; + +/** + * metisTestDataV1_ContentObject_04_Name1_Payload2_SHA256_KeyId2 + * ContentObject + * Name: [lci:/apple/banana/cherry] + * Payload: [Cats are not dogs. Nor are hamburgers.] + * KeyId: [23235365636F6E644B65794964] ('##SecondKeyId') + * ValPayload: [585858205369676E617475726542697473576F756C6442654865726520585858] + * ContentObjectHash: [602EF3993828EFC666FE075CED9A2F1307ECC14895D4168D2BB839AC50F033EB] + */ +static uint8_t metisTestDataV1_ContentObject_04_Name1_Payload2_SHA256_KeyId2[] = { + 0x01, 0x01, 0x00, 0x94, 0x00, 0x00, 0x00, 0x08, + + 0x00, 0x02, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x1d, + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x65, 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, + 0x61, 0x6e, 0x61, 0x00, 0x01, 0x00, 0x06, 0x63, + 0x68, 0x65, 0x72, 0x72, 0x79, 0x00, 0x01, 0x00, + 0x26, 0x43, 0x61, 0x74, 0x73, 0x20, 0x61, 0x72, + 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x64, 0x6f, + 0x67, 0x73, 0x2e, 0x20, 0x4e, 0x6f, 0x72, 0x20, + 0x61, 0x72, 0x65, 0x20, 0x68, 0x61, 0x6d, 0x62, + 0x75, 0x72, 0x67, 0x65, 0x72, 0x73, 0x2e, 0x00, + 0x03, 0x00, 0x15, 0x00, 0x06, 0x00, 0x11, 0x00, + 0x09, 0x00, 0x0d, 0x23, 0x23, 0x53, 0x65, 0x63, + 0x6f, 0x6e, 0x64, 0x4b, 0x65, 0x79, 0x49, 0x64, + 0x00, 0x04, 0x00, 0x20, 0x58, 0x58, 0x58, 0x20, + 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x42, 0x69, 0x74, 0x73, 0x57, 0x6f, 0x75, + 0x6c, 0x64, 0x42, 0x65, 0x48, 0x65, 0x72, 0x65, + 0x20, 0x58, 0x58, 0x58, +}; + +/** + * metisTestDataV1_ContentObject_05_Name2_Payload2_SHA256_KeyId1 + * ContentObject + * Name: [lci:/lions/tigers/bears] + * Payload: [That's no moon.] + * KeyId: [232346697273744B65794964] ('##FirstKeyId') + * ValPayload: [585858205369676E617475726542697473576F756C6442654865726520585858] + * ContentObjectHash: [B9477A91F702C8BE59EF560082148D9AA0FF03180003956841C67BD5516406FA] + */ +static uint8_t metisTestDataV1_ContentObject_05_Name2_Payload2_SHA256_KeyId1[] = { + 0x01, 0x01, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x08, + + 0x00, 0x02, 0x00, 0x33, 0x00, 0x00, 0x00, 0x1c, + 0x00, 0x01, 0x00, 0x05, 0x6c, 0x69, 0x6f, 0x6e, + 0x73, 0x00, 0x01, 0x00, 0x06, 0x74, 0x69, 0x67, + 0x65, 0x72, 0x73, 0x00, 0x01, 0x00, 0x05, 0x62, + 0x65, 0x61, 0x72, 0x73, 0x00, 0x01, 0x00, 0x0f, + 0x54, 0x68, 0x61, 0x74, 0x27, 0x73, 0x20, 0x6e, + 0x6f, 0x20, 0x6d, 0x6f, 0x6f, 0x6e, 0x2e, 0x00, + 0x03, 0x00, 0x14, 0x00, 0x06, 0x00, 0x10, 0x00, + 0x09, 0x00, 0x0c, 0x23, 0x23, 0x46, 0x69, 0x72, + 0x73, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x00, + 0x04, 0x00, 0x20, 0x58, 0x58, 0x58, 0x20, 0x53, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x42, 0x69, 0x74, 0x73, 0x57, 0x6f, 0x75, 0x6c, + 0x64, 0x42, 0x65, 0x48, 0x65, 0x72, 0x65, 0x20, + 0x58, 0x58, 0x58, +}; + +/** + * metisTestDataV1_ContentObject_05_Name2_Payload2_SHA256_KeyId2 + * ContentObject + * Name: [lci:/lions/tigers/bears] + * Payload: [That's no moon.] + * KeyId: [23235365636F6E644B65794964] ('##SecondKeyId') + * ValPayload: [585858205369676E617475726542697473576F756C6442654865726520585858] + * ContentObjectHash: [015AF98C9FA7CBD13FE7F5BE3C0E7F009F18F92F2024FE06C1E8A6654B7EA636] + */ +static uint8_t metisTestDataV1_ContentObject_05_Name2_Payload2_SHA256_KeyId2[] = { + 0x01, 0x01, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x08, + + 0x00, 0x02, 0x00, 0x33, 0x00, 0x00, 0x00, 0x1c, + 0x00, 0x01, 0x00, 0x05, 0x6c, 0x69, 0x6f, 0x6e, + 0x73, 0x00, 0x01, 0x00, 0x06, 0x74, 0x69, 0x67, + 0x65, 0x72, 0x73, 0x00, 0x01, 0x00, 0x05, 0x62, + 0x65, 0x61, 0x72, 0x73, 0x00, 0x01, 0x00, 0x0f, + 0x54, 0x68, 0x61, 0x74, 0x27, 0x73, 0x20, 0x6e, + 0x6f, 0x20, 0x6d, 0x6f, 0x6f, 0x6e, 0x2e, 0x00, + 0x03, 0x00, 0x15, 0x00, 0x06, 0x00, 0x11, 0x00, + 0x09, 0x00, 0x0d, 0x23, 0x23, 0x53, 0x65, 0x63, + 0x6f, 0x6e, 0x64, 0x4b, 0x65, 0x79, 0x49, 0x64, + 0x00, 0x04, 0x00, 0x20, 0x58, 0x58, 0x58, 0x20, + 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x42, 0x69, 0x74, 0x73, 0x57, 0x6f, 0x75, + 0x6c, 0x64, 0x42, 0x65, 0x48, 0x65, 0x72, 0x65, + 0x20, 0x58, 0x58, 0x58, +}; + +/** + * metisTestDataV1_ContentObject_05_Name3_Payload3_SHA256_KeyId1 + * ContentObject + * Name: [lci:/boose/roo/pie] + * Payload: [Do, or do not. There is no 'try'.] + * KeyId: [232346697273744B65794964] ('##FirstKeyId') + * ValPayload: [585858205369676E617475726542697473576F756C6442654865726520585858] + * ContentObjectHash: [AEE8C5584DFC905EF0B58123E6A2A2A1B5DB9C3CF6D8B882120238E6DA522039] + */ +static uint8_t metisTestDataV1_ContentObject_05_Name3_Payload3_SHA256_KeyId1[] = { + 0x01, 0x01, 0x00, 0x88, 0x00, 0x00, 0x00, 0x08, + + 0x00, 0x02, 0x00, 0x40, 0x00, 0x00, 0x00, 0x17, + 0x00, 0x01, 0x00, 0x05, 0x62, 0x6f, 0x6f, 0x73, + 0x65, 0x00, 0x01, 0x00, 0x03, 0x72, 0x6f, 0x6f, + 0x00, 0x01, 0x00, 0x03, 0x70, 0x69, 0x65, 0x00, + 0x01, 0x00, 0x21, 0x44, 0x6f, 0x2c, 0x20, 0x6f, + 0x72, 0x20, 0x64, 0x6f, 0x20, 0x6e, 0x6f, 0x74, + 0x2e, 0x20, 0x54, 0x68, 0x65, 0x72, 0x65, 0x20, + 0x69, 0x73, 0x20, 0x6e, 0x6f, 0x20, 0x27, 0x74, + 0x72, 0x79, 0x27, 0x2e, 0x00, 0x03, 0x00, 0x14, + 0x00, 0x06, 0x00, 0x10, 0x00, 0x09, 0x00, 0x0c, + 0x23, 0x23, 0x46, 0x69, 0x72, 0x73, 0x74, 0x4b, + 0x65, 0x79, 0x49, 0x64, 0x00, 0x04, 0x00, 0x20, + 0x58, 0x58, 0x58, 0x20, 0x53, 0x69, 0x67, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x69, 0x74, + 0x73, 0x57, 0x6f, 0x75, 0x6c, 0x64, 0x42, 0x65, + 0x48, 0x65, 0x72, 0x65, 0x20, 0x58, 0x58, 0x58, + +}; + +/** + * metisTestDataV1_Interest_01_Name1_Payload1_COHashRestriction_WithPayload_CRC32 + * Interest + * Name: [lci:/apple/banana/cherry] + * ContentObject Hash Restriction: [926E8780E73B1B4F4BDB8B765559856DACF57AA62F2C4FD26338FA1540B3FE6B] + * KeyIdRestriction: NONE + * PayloadString: [The dog barks at midnight.] + * PayloadBytes: [54686520646F67206261726B73206174206D69646E696768742E] + */ +static uint8_t metisTestDataV1_Interest_01_Name1_Payload1_COHashRestriction_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x7f, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x63, 0x00, 0x00, 0x00, 0x1d, + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x65, 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, + 0x61, 0x6e, 0x61, 0x00, 0x01, 0x00, 0x06, 0x63, + 0x68, 0x65, 0x72, 0x72, 0x79, 0x00, 0x03, 0x00, + 0x20, 0x92, 0x6e, 0x87, 0x80, 0xe7, 0x3b, 0x1b, + 0x4f, 0x4b, 0xdb, 0x8b, 0x76, 0x55, 0x59, 0x85, + 0x6d, 0xac, 0xf5, 0x7a, 0xa6, 0x2f, 0x2c, 0x4f, + 0xd2, 0x63, 0x38, 0xfa, 0x15, 0x40, 0xb3, 0xfe, + 0x6b, 0x00, 0x01, 0x00, 0x1a, 0x54, 0x68, 0x65, + 0x20, 0x64, 0x6f, 0x67, 0x20, 0x62, 0x61, 0x72, + 0x6b, 0x73, 0x20, 0x61, 0x74, 0x20, 0x6d, 0x69, + 0x64, 0x6e, 0x69, 0x67, 0x68, 0x74, 0x2e, 0x00, + 0x03, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x04, 0xb8, 0x5e, 0xe0, 0x0c, +}; + +/** + * metisTestDataV1_Interest_01_Name1_Payload1_KeyIdRestriction_KeyId1_COHashRestriction_WithPayload_CRC32 + * Interest + * Name: [lci:/apple/banana/cherry] + * ContentObject Hash Restriction: [926E8780E73B1B4F4BDB8B765559856DACF57AA62F2C4FD26338FA1540B3FE6B] + * KeyIdRestriction: [232346697273744B65794964] + * PayloadString: [The dog barks at midnight.] + * PayloadBytes: [54686520646F67206261726B73206174206D69646E696768742E] + */ +static uint8_t metisTestDataV1_Interest_01_Name1_Payload1_KeyIdRestriction_KeyId1_COHashRestriction_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x8f, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x73, 0x00, 0x00, 0x00, 0x1d, + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x65, 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, + 0x61, 0x6e, 0x61, 0x00, 0x01, 0x00, 0x06, 0x63, + 0x68, 0x65, 0x72, 0x72, 0x79, 0x00, 0x02, 0x00, + 0x0c, 0x23, 0x23, 0x46, 0x69, 0x72, 0x73, 0x74, + 0x4b, 0x65, 0x79, 0x49, 0x64, 0x00, 0x03, 0x00, + 0x20, 0x92, 0x6e, 0x87, 0x80, 0xe7, 0x3b, 0x1b, + 0x4f, 0x4b, 0xdb, 0x8b, 0x76, 0x55, 0x59, 0x85, + 0x6d, 0xac, 0xf5, 0x7a, 0xa6, 0x2f, 0x2c, 0x4f, + 0xd2, 0x63, 0x38, 0xfa, 0x15, 0x40, 0xb3, 0xfe, + 0x6b, 0x00, 0x01, 0x00, 0x1a, 0x54, 0x68, 0x65, + 0x20, 0x64, 0x6f, 0x67, 0x20, 0x62, 0x61, 0x72, + 0x6b, 0x73, 0x20, 0x61, 0x74, 0x20, 0x6d, 0x69, + 0x64, 0x6e, 0x69, 0x67, 0x68, 0x74, 0x2e, 0x00, + 0x03, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x04, 0x0d, 0x8c, 0x5d, 0x11, +}; + +/** + * metisTestDataV1_Interest_01_Name1_Payload1_KeyIdRestriction_KeyId1_WithPayload_CRC32 + * Interest + * Name: [lci:/apple/banana/cherry] + * KeyIdRestriction: [232346697273744B65794964] + * ContentObject Hash Restriction: NONE + * PayloadString: [The dog barks at midnight.] + * PayloadBytes: [54686520646F67206261726B73206174206D69646E696768742E] + */ +static uint8_t metisTestDataV1_Interest_01_Name1_Payload1_KeyIdRestriction_KeyId1_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x6b, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x1d, + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x65, 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, + 0x61, 0x6e, 0x61, 0x00, 0x01, 0x00, 0x06, 0x63, + 0x68, 0x65, 0x72, 0x72, 0x79, 0x00, 0x02, 0x00, + 0x0c, 0x23, 0x23, 0x46, 0x69, 0x72, 0x73, 0x74, + 0x4b, 0x65, 0x79, 0x49, 0x64, 0x00, 0x01, 0x00, + 0x1a, 0x54, 0x68, 0x65, 0x20, 0x64, 0x6f, 0x67, + 0x20, 0x62, 0x61, 0x72, 0x6b, 0x73, 0x20, 0x61, + 0x74, 0x20, 0x6d, 0x69, 0x64, 0x6e, 0x69, 0x67, + 0x68, 0x74, 0x2e, 0x00, 0x03, 0x00, 0x04, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x02, + 0x85, 0x88, 0xb1, +}; + +/** + * metisTestDataV1_Interest_01_Name1_Payload1_NameOnly_CRC32 + * Interest + * Name: [lci:/apple/banana/cherry] + * ContentObject Hash Restriction: NONE + * KeyIdRestriction: NONE + */ +static uint8_t metisTestDataV1_Interest_01_Name1_Payload1_NameOnly_CRC32[] = { + 0x01, 0x00, 0x00, 0x3d, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x21, 0x00, 0x00, 0x00, 0x1d, + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x65, 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, + 0x61, 0x6e, 0x61, 0x00, 0x01, 0x00, 0x06, 0x63, + 0x68, 0x65, 0x72, 0x72, 0x79, 0x00, 0x03, 0x00, + 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x04, 0x37, 0x31, 0x3c, 0x2c, +}; + +/** + * metisTestDataV1_Interest_01_Name1_Payload1_NameOnly_CRC32_Bad_CRC32 + * Interest (with bad CRC32) + * Name: [lci:/apple/banana/cherry] + * ContentObject Hash Restriction: NONE + * KeyIdRestriction: NONE + */ +static uint8_t metisTestDataV1_Interest_01_Name1_Payload1_NameOnly_CRC32_Bad_CRC32[] = { + 0x01, 0x00, 0x00, 0x3d, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x21, 0x00, 0x00, 0x00, 0x1d, + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x65, 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, + 0x61, 0x6e, 0x61, 0x00, 0x01, 0x00, 0x06, 0x63, + 0x68, 0x65, 0x72, 0x72, 0x79, 0x00, 0x03, 0x00, + 0x04, 0x00, 0x02, 0x00, 0x00, + + 0x00, 0x04, 0x00, 0x04, 0x37, 0x31, 0xff, 0xff, // BAD CRC32 +}; + + + +/** + * metisTestDataV1_Interest_01_Name1_Payload1_NameOnly_CRC32_0_HopLimit + * Interest (with 0 hop limit) + * Name: [lci:/apple/banana/cherry] + * ContentObject Hash Restriction: NONE + * KeyIdRestriction: NONE + */ +static uint8_t metisTestDataV1_Interest_01_Name1_Payload1_NameOnly_CRC32_0_HopLimit[] = { + 0x01, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x21, 0x00, 0x00, 0x00, 0x1d, + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x65, 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, + 0x61, 0x6e, 0x61, 0x00, 0x01, 0x00, 0x06, 0x63, + 0x68, 0x65, 0x72, 0x72, 0x79, 0x00, 0x03, 0x00, + 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x04, 0x37, 0x31, 0x3c, 0x2c, +}; + +/** + * metisTestDataV1_Interest_01_Name1_Payload1_NameOnly_WithPayload_CRC32 + * Interest + * Name: [lci:/apple/banana/cherry] + * ContentObject Hash Restriction: NONE + * KeyIdRestriction: NONE + * PayloadString: [The dog barks at midnight.] + * PayloadBytes: [54686520646F67206261726B73206174206D69646E696768742E] + */ +static uint8_t metisTestDataV1_Interest_01_Name1_Payload1_NameOnly_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x5b, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x3f, + 0x00, 0x00, 0x00, 0x1d, + + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, // apple + 0x65, + + 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, // banana + 0x61, 0x6e, 0x61, + + 0x00, 0x01, 0x00, 0x06, 0x63, // cherry + 0x68, 0x65, 0x72, 0x72, 0x79, + + 0x00, 0x01, 0x00, 0x1a, // payload + 0x54, 0x68, 0x65, 0x20, 0x64, 0x6f, 0x67, 0x20, + 0x62, 0x61, 0x72, 0x6b, 0x73, 0x20, 0x61, 0x74, + 0x20, 0x6d, 0x69, 0x64, 0x6e, 0x69, 0x67, 0x68, + 0x74, 0x2e, + + 0x00, 0x03, 0x00, 0x04, // Validation Alg CRC32 + 0x00, 0x02, 0x00, 0x00, + + 0x00, 0x04, 0x00, 0x04, + 0xdb, 0x40, 0xde, 0xe2, // Validation payload: 0xDB40DEE2 (CRC32) +}; + +/** + * metisTestDataV1_Interest_02_Name1_Payload2_COHashRestriction_WithPayload_CRC32 + * Interest + * Name: [lci:/apple/banana/cherry] + * ContentObject Hash Restriction: [B55ABFDEA353A39A08587C5D42EBC382B1322E2BCD2EAAE27AAFFB29EDA2725F] + * KeyIdRestriction: NONE + * PayloadString: [Cats are not dogs. Nor are hamburgers.] + * PayloadBytes: [4361747320617265206E6F7420646F67732E204E6F72206172652068616D627572676572732E] + */ +static uint8_t metisTestDataV1_Interest_02_Name1_Payload2_COHashRestriction_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x8b, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x1d, + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x65, 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, + 0x61, 0x6e, 0x61, 0x00, 0x01, 0x00, 0x06, 0x63, + 0x68, 0x65, 0x72, 0x72, 0x79, 0x00, 0x03, 0x00, + 0x20, 0xb5, 0x5a, 0xbf, 0xde, 0xa3, 0x53, 0xa3, + 0x9a, 0x08, 0x58, 0x7c, 0x5d, 0x42, 0xeb, 0xc3, + 0x82, 0xb1, 0x32, 0x2e, 0x2b, 0xcd, 0x2e, 0xaa, + 0xe2, 0x7a, 0xaf, 0xfb, 0x29, 0xed, 0xa2, 0x72, + 0x5f, 0x00, 0x01, 0x00, 0x26, 0x43, 0x61, 0x74, + 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, + 0x74, 0x20, 0x64, 0x6f, 0x67, 0x73, 0x2e, 0x20, + 0x4e, 0x6f, 0x72, 0x20, 0x61, 0x72, 0x65, 0x20, + 0x68, 0x61, 0x6d, 0x62, 0x75, 0x72, 0x67, 0x65, + 0x72, 0x73, 0x2e, 0x00, 0x03, 0x00, 0x04, 0x00, + 0x02, 0x00, 0x00, + + 0x00, 0x04, 0x00, 0x04, 0xbd, 0x3a, 0x95, 0x60, // Validation payload: 0xBD3A9560 (CRC32) +}; + +/** + * metisTestDataV1_Interest_02_Name1_Payload2_KeyIdRestriction_KeyId1_COHashRestriction_WithPayload_CRC32 + * Interest + * Name: [lci:/apple/banana/cherry] + * ContentObject Hash Restriction: [B55ABFDEA353A39A08587C5D42EBC382B1322E2BCD2EAAE27AAFFB29EDA2725F] + * KeyIdRestriction: [232346697273744B65794964] + * PayloadString: [Cats are not dogs. Nor are hamburgers.] + * PayloadBytes: [4361747320617265206E6F7420646F67732E204E6F72206172652068616D627572676572732E] + */ +static uint8_t metisTestDataV1_Interest_02_Name1_Payload2_KeyIdRestriction_KeyId1_COHashRestriction_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x9b, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x1d, + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x65, 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, + 0x61, 0x6e, 0x61, 0x00, 0x01, 0x00, 0x06, 0x63, + 0x68, 0x65, 0x72, 0x72, 0x79, 0x00, 0x02, 0x00, + 0x0c, 0x23, 0x23, 0x46, 0x69, 0x72, 0x73, 0x74, + 0x4b, 0x65, 0x79, 0x49, 0x64, 0x00, 0x03, 0x00, + 0x20, 0xb5, 0x5a, 0xbf, 0xde, 0xa3, 0x53, 0xa3, + 0x9a, 0x08, 0x58, 0x7c, 0x5d, 0x42, 0xeb, 0xc3, + 0x82, 0xb1, 0x32, 0x2e, 0x2b, 0xcd, 0x2e, 0xaa, + 0xe2, 0x7a, 0xaf, 0xfb, 0x29, 0xed, 0xa2, 0x72, + 0x5f, 0x00, 0x01, 0x00, 0x26, 0x43, 0x61, 0x74, + 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, + 0x74, 0x20, 0x64, 0x6f, 0x67, 0x73, 0x2e, 0x20, + 0x4e, 0x6f, 0x72, 0x20, 0x61, 0x72, 0x65, 0x20, + 0x68, 0x61, 0x6d, 0x62, 0x75, 0x72, 0x67, 0x65, + 0x72, 0x73, 0x2e, 0x00, 0x03, 0x00, 0x04, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0xd9, + 0xa4, 0x61, 0x05, +}; + +/** + * metisTestDataV1_Interest_02_Name1_Payload2_KeyIdRestriction_KeyId1_WithPayload_CRC32 + * Interest + * Name: [lci:/apple/banana/cherry] + * KeyIdRestriction: [232346697273744B65794964] + * ContentObject Hash Restriction: NONE + * PayloadString: [Cats are not dogs. Nor are hamburgers.] + * PayloadBytes: [4361747320617265206E6F7420646F67732E204E6F72206172652068616D627572676572732E] + */ +static uint8_t metisTestDataV1_Interest_02_Name1_Payload2_KeyIdRestriction_KeyId1_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x77, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x5b, 0x00, 0x00, 0x00, 0x1d, + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x65, 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, + 0x61, 0x6e, 0x61, 0x00, 0x01, 0x00, 0x06, 0x63, + 0x68, 0x65, 0x72, 0x72, 0x79, 0x00, 0x02, 0x00, + 0x0c, 0x23, 0x23, 0x46, 0x69, 0x72, 0x73, 0x74, + 0x4b, 0x65, 0x79, 0x49, 0x64, 0x00, 0x01, 0x00, + 0x26, 0x43, 0x61, 0x74, 0x73, 0x20, 0x61, 0x72, + 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x64, 0x6f, + 0x67, 0x73, 0x2e, 0x20, 0x4e, 0x6f, 0x72, 0x20, + 0x61, 0x72, 0x65, 0x20, 0x68, 0x61, 0x6d, 0x62, + 0x75, 0x72, 0x67, 0x65, 0x72, 0x73, 0x2e, 0x00, + 0x03, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x04, 0x64, 0xcc, 0x37, 0x5c, +}; + +/** + * metisTestDataV1_Interest_02_Name1_Payload2_NameOnly_CRC32 + * Interest + * Name: [lci:/apple/banana/cherry] + * ContentObject Hash Restriction: NONE + * KeyIdRestriction: NONE + */ +static uint8_t metisTestDataV1_Interest_02_Name1_Payload2_NameOnly_CRC32[] = { + 0x01, 0x00, 0x00, 0x3d, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x21, 0x00, 0x00, 0x00, 0x1d, + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x65, 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, + 0x61, 0x6e, 0x61, 0x00, 0x01, 0x00, 0x06, 0x63, + 0x68, 0x65, 0x72, 0x72, 0x79, 0x00, 0x03, 0x00, + 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x04, 0x37, 0x31, 0x3c, 0x2c, +}; + +/** + * metisTestDataV1_Interest_02_Name1_Payload2_NameOnly_WithPayload_CRC32 + * Interest + * Name: [lci:/apple/banana/cherry] + * ContentObject Hash Restriction: NONE + * KeyIdRestriction: NONE + * PayloadString: [Cats are not dogs. Nor are hamburgers.] + * PayloadBytes: [4361747320617265206E6F7420646F67732E204E6F72206172652068616D627572676572732E] + */ +static uint8_t metisTestDataV1_Interest_02_Name1_Payload2_NameOnly_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x67, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x1d, + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x65, 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, + 0x61, 0x6e, 0x61, 0x00, 0x01, 0x00, 0x06, 0x63, + 0x68, 0x65, 0x72, 0x72, 0x79, 0x00, 0x01, 0x00, + 0x26, 0x43, 0x61, 0x74, 0x73, 0x20, 0x61, 0x72, + 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x64, 0x6f, + 0x67, 0x73, 0x2e, 0x20, 0x4e, 0x6f, 0x72, 0x20, + 0x61, 0x72, 0x65, 0x20, 0x68, 0x61, 0x6d, 0x62, + 0x75, 0x72, 0x67, 0x65, 0x72, 0x73, 0x2e, 0x00, + 0x03, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, + + 0x00, 0x04, 0x00, 0x04, 0x47, 0x0e, 0x8c, 0x1a, // Validation Payload (CRC32): 0x470E8C1A +}; + +/** + * metisTestDataV1_Interest_03_Name1_Payload1_COHashRestriction_WithPayload_CRC32 + * Interest + * Name: [lci:/apple/banana/cherry] + * ContentObject Hash Restriction: [602EF3993828EFC666FE075CED9A2F1307ECC14895D4168D2BB839AC50F033EB] + * KeyIdRestriction: NONE + * PayloadString: [Cats are not dogs. Nor are hamburgers.] + * PayloadBytes: [4361747320617265206E6F7420646F67732E204E6F72206172652068616D627572676572732E] + */ +static uint8_t metisTestDataV1_Interest_03_Name1_Payload1_COHashRestriction_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x8b, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x1d, + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x65, 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, + 0x61, 0x6e, 0x61, 0x00, 0x01, 0x00, 0x06, 0x63, + 0x68, 0x65, 0x72, 0x72, 0x79, 0x00, 0x03, 0x00, + 0x20, 0x60, 0x2e, 0xf3, 0x99, 0x38, 0x28, 0xef, + 0xc6, 0x66, 0xfe, 0x07, 0x5c, 0xed, 0x9a, 0x2f, + 0x13, 0x07, 0xec, 0xc1, 0x48, 0x95, 0xd4, 0x16, + 0x8d, 0x2b, 0xb8, 0x39, 0xac, 0x50, 0xf0, 0x33, + 0xeb, 0x00, 0x01, 0x00, 0x26, 0x43, 0x61, 0x74, + 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, + 0x74, 0x20, 0x64, 0x6f, 0x67, 0x73, 0x2e, 0x20, + 0x4e, 0x6f, 0x72, 0x20, 0x61, 0x72, 0x65, 0x20, + 0x68, 0x61, 0x6d, 0x62, 0x75, 0x72, 0x67, 0x65, + 0x72, 0x73, 0x2e, 0x00, 0x03, 0x00, 0x04, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x85, + 0xbb, 0x6a, 0x86, +}; + +/** + * metisTestDataV1_Interest_03_Name1_Payload1_KeyIdRestriction_KeyId2_COHashRestriction_WithPayload_CRC32 + * Interest + * Name: [lci:/apple/banana/cherry] + * ContentObject Hash Restriction: [602EF3993828EFC666FE075CED9A2F1307ECC14895D4168D2BB839AC50F033EB] + * KeyIdRestriction: [23235365636F6E644B65794964] + * PayloadString: [Cats are not dogs. Nor are hamburgers.] + * PayloadBytes: [4361747320617265206E6F7420646F67732E204E6F72206172652068616D627572676572732E] + */ +static uint8_t metisTestDataV1_Interest_03_Name1_Payload1_KeyIdRestriction_KeyId2_COHashRestriction_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x9c, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x1d, + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x65, 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, + 0x61, 0x6e, 0x61, 0x00, 0x01, 0x00, 0x06, 0x63, + 0x68, 0x65, 0x72, 0x72, 0x79, 0x00, 0x02, 0x00, + 0x0d, 0x23, 0x23, 0x53, 0x65, 0x63, 0x6f, 0x6e, + 0x64, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x00, 0x03, + 0x00, 0x20, 0x60, 0x2e, 0xf3, 0x99, 0x38, 0x28, + 0xef, 0xc6, 0x66, 0xfe, 0x07, 0x5c, 0xed, 0x9a, + 0x2f, 0x13, 0x07, 0xec, 0xc1, 0x48, 0x95, 0xd4, + 0x16, 0x8d, 0x2b, 0xb8, 0x39, 0xac, 0x50, 0xf0, + 0x33, 0xeb, 0x00, 0x01, 0x00, 0x26, 0x43, 0x61, + 0x74, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, + 0x6f, 0x74, 0x20, 0x64, 0x6f, 0x67, 0x73, 0x2e, + 0x20, 0x4e, 0x6f, 0x72, 0x20, 0x61, 0x72, 0x65, + 0x20, 0x68, 0x61, 0x6d, 0x62, 0x75, 0x72, 0x67, + 0x65, 0x72, 0x73, 0x2e, 0x00, 0x03, 0x00, 0x04, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, + 0x65, 0x3b, 0xdc, 0x1e, +}; + +/** + * metisTestDataV1_Interest_03_Name1_Payload1_KeyIdRestriction_KeyId2_WithPayload_CRC32 + * Interest + * Name: [lci:/apple/banana/cherry] + * KeyIdRestriction: [23235365636F6E644B65794964] + * ContentObject Hash Restriction: NONE + * PayloadString: [Cats are not dogs. Nor are hamburgers.] + * PayloadBytes: [4361747320617265206E6F7420646F67732E204E6F72206172652068616D627572676572732E] + */ +static uint8_t metisTestDataV1_Interest_03_Name1_Payload1_KeyIdRestriction_KeyId2_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x78, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x1d, + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x65, 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, + 0x61, 0x6e, 0x61, 0x00, 0x01, 0x00, 0x06, 0x63, + 0x68, 0x65, 0x72, 0x72, 0x79, 0x00, 0x02, 0x00, + 0x0d, 0x23, 0x23, 0x53, 0x65, 0x63, 0x6f, 0x6e, + 0x64, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x00, 0x01, + 0x00, 0x26, 0x43, 0x61, 0x74, 0x73, 0x20, 0x61, + 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x64, + 0x6f, 0x67, 0x73, 0x2e, 0x20, 0x4e, 0x6f, 0x72, + 0x20, 0x61, 0x72, 0x65, 0x20, 0x68, 0x61, 0x6d, + 0x62, 0x75, 0x72, 0x67, 0x65, 0x72, 0x73, 0x2e, + 0x00, 0x03, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x04, 0xf8, 0x87, 0xe2, 0x49, + +}; + +/** + * metisTestDataV1_Interest_03_Name1_Payload1_NameOnly_CRC32 + * Interest + * Name: [lci:/apple/banana/cherry] + * ContentObject Hash Restriction: NONE + * KeyIdRestriction: NONE + */ +static uint8_t metisTestDataV1_Interest_03_Name1_Payload1_NameOnly_CRC32[] = { + 0x01, 0x00, 0x00, 0x3d, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x21, 0x00, 0x00, 0x00, 0x1d, + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x65, 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, + 0x61, 0x6e, 0x61, 0x00, 0x01, 0x00, 0x06, 0x63, + 0x68, 0x65, 0x72, 0x72, 0x79, 0x00, 0x03, 0x00, + 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x04, 0x37, 0x31, 0x3c, 0x2c, +}; + +/** + * metisTestDataV1_Interest_03_Name1_Payload1_NameOnly_WithPayload_CRC32 + * Interest + * Name: [lci:/apple/banana/cherry] + * ContentObject Hash Restriction: NONE + * KeyIdRestriction: NONE + * PayloadString: [Cats are not dogs. Nor are hamburgers.] + * PayloadBytes: [4361747320617265206E6F7420646F67732E204E6F72206172652068616D627572676572732E] + */ +static uint8_t metisTestDataV1_Interest_03_Name1_Payload1_NameOnly_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x67, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x1d, + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x65, 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, + 0x61, 0x6e, 0x61, 0x00, 0x01, 0x00, 0x06, 0x63, + 0x68, 0x65, 0x72, 0x72, 0x79, 0x00, 0x01, 0x00, + 0x26, 0x43, 0x61, 0x74, 0x73, 0x20, 0x61, 0x72, + 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x64, 0x6f, + 0x67, 0x73, 0x2e, 0x20, 0x4e, 0x6f, 0x72, 0x20, + 0x61, 0x72, 0x65, 0x20, 0x68, 0x61, 0x6d, 0x62, + 0x75, 0x72, 0x67, 0x65, 0x72, 0x73, 0x2e, 0x00, + 0x03, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x04, 0x47, 0x0e, 0x8c, 0x1a, +}; + +/** + * metisTestDataV1_Interest_04_Name1_Payload2_COHashRestriction_WithPayload_CRC32 + * Interest + * Name: [lci:/apple/banana/cherry] + * ContentObject Hash Restriction: [602EF3993828EFC666FE075CED9A2F1307ECC14895D4168D2BB839AC50F033EB] + * KeyIdRestriction: NONE + * PayloadString: [Cats are not dogs. Nor are hamburgers.] + * PayloadBytes: [4361747320617265206E6F7420646F67732E204E6F72206172652068616D627572676572732E] + */ +static uint8_t metisTestDataV1_Interest_04_Name1_Payload2_COHashRestriction_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x8b, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x1d, + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x65, 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, + 0x61, 0x6e, 0x61, 0x00, 0x01, 0x00, 0x06, 0x63, + 0x68, 0x65, 0x72, 0x72, 0x79, 0x00, 0x03, 0x00, + 0x20, 0x60, 0x2e, 0xf3, 0x99, 0x38, 0x28, 0xef, + 0xc6, 0x66, 0xfe, 0x07, 0x5c, 0xed, 0x9a, 0x2f, + 0x13, 0x07, 0xec, 0xc1, 0x48, 0x95, 0xd4, 0x16, + 0x8d, 0x2b, 0xb8, 0x39, 0xac, 0x50, 0xf0, 0x33, + 0xeb, 0x00, 0x01, 0x00, 0x26, 0x43, 0x61, 0x74, + 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, + 0x74, 0x20, 0x64, 0x6f, 0x67, 0x73, 0x2e, 0x20, + 0x4e, 0x6f, 0x72, 0x20, 0x61, 0x72, 0x65, 0x20, + 0x68, 0x61, 0x6d, 0x62, 0x75, 0x72, 0x67, 0x65, + 0x72, 0x73, 0x2e, 0x00, 0x03, 0x00, 0x04, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x85, + 0xbb, 0x6a, 0x86, +}; + +/** + * metisTestDataV1_Interest_04_Name1_Payload2_KeyIdRestriction_KeyId2_COHashRestriction_WithPayload_CRC32 + * Interest + * Name: [lci:/apple/banana/cherry] + * ContentObject Hash Restriction: [602EF3993828EFC666FE075CED9A2F1307ECC14895D4168D2BB839AC50F033EB] + * KeyIdRestriction: [23235365636F6E644B65794964] + * PayloadString: [Cats are not dogs. Nor are hamburgers.] + * PayloadBytes: [4361747320617265206E6F7420646F67732E204E6F72206172652068616D627572676572732E] + */ +static uint8_t metisTestDataV1_Interest_04_Name1_Payload2_KeyIdRestriction_KeyId2_COHashRestriction_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x9c, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x1d, + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x65, 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, + 0x61, 0x6e, 0x61, 0x00, 0x01, 0x00, 0x06, 0x63, + 0x68, 0x65, 0x72, 0x72, 0x79, 0x00, 0x02, 0x00, + 0x0d, 0x23, 0x23, 0x53, 0x65, 0x63, 0x6f, 0x6e, + 0x64, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x00, 0x03, + 0x00, 0x20, 0x60, 0x2e, 0xf3, 0x99, 0x38, 0x28, + 0xef, 0xc6, 0x66, 0xfe, 0x07, 0x5c, 0xed, 0x9a, + 0x2f, 0x13, 0x07, 0xec, 0xc1, 0x48, 0x95, 0xd4, + 0x16, 0x8d, 0x2b, 0xb8, 0x39, 0xac, 0x50, 0xf0, + 0x33, 0xeb, 0x00, 0x01, 0x00, 0x26, 0x43, 0x61, + 0x74, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, + 0x6f, 0x74, 0x20, 0x64, 0x6f, 0x67, 0x73, 0x2e, + 0x20, 0x4e, 0x6f, 0x72, 0x20, 0x61, 0x72, 0x65, + 0x20, 0x68, 0x61, 0x6d, 0x62, 0x75, 0x72, 0x67, + 0x65, 0x72, 0x73, 0x2e, 0x00, 0x03, 0x00, 0x04, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, + 0x65, 0x3b, 0xdc, 0x1e, +}; + +/** + * metisTestDataV1_Interest_04_Name1_Payload2_KeyIdRestriction_KeyId2_WithPayload_CRC32 + * Interest + * Name: [lci:/apple/banana/cherry] + * KeyIdRestriction: [23235365636F6E644B65794964] + * ContentObject Hash Restriction: NONE + * PayloadString: [Cats are not dogs. Nor are hamburgers.] + * PayloadBytes: [4361747320617265206E6F7420646F67732E204E6F72206172652068616D627572676572732E] + */ +static uint8_t metisTestDataV1_Interest_04_Name1_Payload2_KeyIdRestriction_KeyId2_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x78, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x1d, + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x65, 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, + 0x61, 0x6e, 0x61, 0x00, 0x01, 0x00, 0x06, 0x63, + 0x68, 0x65, 0x72, 0x72, 0x79, 0x00, 0x02, 0x00, + 0x0d, 0x23, 0x23, 0x53, 0x65, 0x63, 0x6f, 0x6e, + 0x64, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x00, 0x01, + 0x00, 0x26, 0x43, 0x61, 0x74, 0x73, 0x20, 0x61, + 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x64, + 0x6f, 0x67, 0x73, 0x2e, 0x20, 0x4e, 0x6f, 0x72, + 0x20, 0x61, 0x72, 0x65, 0x20, 0x68, 0x61, 0x6d, + 0x62, 0x75, 0x72, 0x67, 0x65, 0x72, 0x73, 0x2e, + 0x00, 0x03, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x04, 0xf8, 0x87, 0xe2, 0x49, + +}; + +/** + * metisTestDataV1_Interest_04_Name1_Payload2_NameOnly_CRC32 + * Interest + * Name: [lci:/apple/banana/cherry] + * ContentObject Hash Restriction: NONE + * KeyIdRestriction: NONE + */ +static uint8_t metisTestDataV1_Interest_04_Name1_Payload2_NameOnly_CRC32[] = { + 0x01, 0x00, 0x00, 0x3d, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x21, 0x00, 0x00, 0x00, 0x1d, + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x65, 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, + 0x61, 0x6e, 0x61, 0x00, 0x01, 0x00, 0x06, 0x63, + 0x68, 0x65, 0x72, 0x72, 0x79, 0x00, 0x03, 0x00, + 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x04, 0x37, 0x31, 0x3c, 0x2c, +}; + +/** + * metisTestDataV1_Interest_04_Name1_Payload2_NameOnly_WithPayload_CRC32 + * Interest + * Name: [lci:/apple/banana/cherry] + * ContentObject Hash Restriction: NONE + * KeyIdRestriction: NONE + * PayloadString: [Cats are not dogs. Nor are hamburgers.] + * PayloadBytes: [4361747320617265206E6F7420646F67732E204E6F72206172652068616D627572676572732E] + */ +static uint8_t metisTestDataV1_Interest_04_Name1_Payload2_NameOnly_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x67, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x1d, + 0x00, 0x01, 0x00, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x65, 0x00, 0x01, 0x00, 0x06, 0x62, 0x61, 0x6e, + 0x61, 0x6e, 0x61, 0x00, 0x01, 0x00, 0x06, 0x63, + 0x68, 0x65, 0x72, 0x72, 0x79, 0x00, 0x01, 0x00, + 0x26, 0x43, 0x61, 0x74, 0x73, 0x20, 0x61, 0x72, + 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x64, 0x6f, + 0x67, 0x73, 0x2e, 0x20, 0x4e, 0x6f, 0x72, 0x20, + 0x61, 0x72, 0x65, 0x20, 0x68, 0x61, 0x6d, 0x62, + 0x75, 0x72, 0x67, 0x65, 0x72, 0x73, 0x2e, 0x00, + 0x03, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x04, 0x47, 0x0e, 0x8c, 0x1a, +}; + +/** + * metisTestDataV1_Interest_05_Name2_Payload2_COHashRestriction_WithPayload_CRC32 + * Interest + * Name: [lci:/lions/tigers/bears] + * ContentObject Hash Restriction: [015AF98C9FA7CBD13FE7F5BE3C0E7F009F18F92F2024FE06C1E8A6654B7EA636] + * KeyIdRestriction: NONE + * PayloadString: [That's no moon.] + * PayloadBytes: [546861742773206E6F206D6F6F6E2E] + */ +static uint8_t metisTestDataV1_Interest_05_Name2_Payload2_COHashRestriction_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x73, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x57, 0x00, 0x00, 0x00, 0x1c, + 0x00, 0x01, 0x00, 0x05, 0x6c, 0x69, 0x6f, 0x6e, + 0x73, 0x00, 0x01, 0x00, 0x06, 0x74, 0x69, 0x67, + 0x65, 0x72, 0x73, 0x00, 0x01, 0x00, 0x05, 0x62, + 0x65, 0x61, 0x72, 0x73, 0x00, 0x03, 0x00, 0x20, + 0x01, 0x5a, 0xf9, 0x8c, 0x9f, 0xa7, 0xcb, 0xd1, + 0x3f, 0xe7, 0xf5, 0xbe, 0x3c, 0x0e, 0x7f, 0x00, + 0x9f, 0x18, 0xf9, 0x2f, 0x20, 0x24, 0xfe, 0x06, + 0xc1, 0xe8, 0xa6, 0x65, 0x4b, 0x7e, 0xa6, 0x36, + 0x00, 0x01, 0x00, 0x0f, 0x54, 0x68, 0x61, 0x74, + 0x27, 0x73, 0x20, 0x6e, 0x6f, 0x20, 0x6d, 0x6f, + 0x6f, 0x6e, 0x2e, 0x00, 0x03, 0x00, 0x04, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x68, + 0x37, 0xf3, 0x6f, +}; + +/** + * metisTestDataV1_Interest_05_Name2_Payload2_KeyIdRestriction_KeyId1_COHashRestriction_WithPayload_CRC32 + * Interest + * Name: [lci:/lions/tigers/bears] + * ContentObject Hash Restriction: [B9477A91F702C8BE59EF560082148D9AA0FF03180003956841C67BD5516406FA] + * KeyIdRestriction: [232346697273744B65794964] + * PayloadString: [That's no moon.] + * PayloadBytes: [546861742773206E6F206D6F6F6E2E] + */ +static uint8_t metisTestDataV1_Interest_05_Name2_Payload2_KeyIdRestriction_KeyId1_COHashRestriction_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x83, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x67, 0x00, 0x00, 0x00, 0x1c, + 0x00, 0x01, 0x00, 0x05, 0x6c, 0x69, 0x6f, 0x6e, + 0x73, 0x00, 0x01, 0x00, 0x06, 0x74, 0x69, 0x67, + 0x65, 0x72, 0x73, 0x00, 0x01, 0x00, 0x05, 0x62, + 0x65, 0x61, 0x72, 0x73, 0x00, 0x02, 0x00, 0x0c, + 0x23, 0x23, 0x46, 0x69, 0x72, 0x73, 0x74, 0x4b, + 0x65, 0x79, 0x49, 0x64, 0x00, 0x03, 0x00, 0x20, + 0xb9, 0x47, 0x7a, 0x91, 0xf7, 0x02, 0xc8, 0xbe, + 0x59, 0xef, 0x56, 0x00, 0x82, 0x14, 0x8d, 0x9a, + 0xa0, 0xff, 0x03, 0x18, 0x00, 0x03, 0x95, 0x68, + 0x41, 0xc6, 0x7b, 0xd5, 0x51, 0x64, 0x06, 0xfa, + 0x00, 0x01, 0x00, 0x0f, 0x54, 0x68, 0x61, 0x74, + 0x27, 0x73, 0x20, 0x6e, 0x6f, 0x20, 0x6d, 0x6f, + 0x6f, 0x6e, 0x2e, 0x00, 0x03, 0x00, 0x04, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x9a, + 0x5c, 0x93, 0x79, +}; + +/** + * metisTestDataV1_Interest_05_Name2_Payload2_KeyIdRestriction_KeyId1_WithPayload_CRC32 + * Interest + * Name: [lci:/lions/tigers/bears] + * KeyIdRestriction: [232346697273744B65794964] + * ContentObject Hash Restriction: NONE + * PayloadString: [That's no moon.] + * PayloadBytes: [546861742773206E6F206D6F6F6E2E] + */ +static uint8_t metisTestDataV1_Interest_05_Name2_Payload2_KeyIdRestriction_KeyId1_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x5f, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x43, 0x00, 0x00, 0x00, 0x1c, + 0x00, 0x01, 0x00, 0x05, 0x6c, 0x69, 0x6f, 0x6e, + 0x73, 0x00, 0x01, 0x00, 0x06, 0x74, 0x69, 0x67, + 0x65, 0x72, 0x73, 0x00, 0x01, 0x00, 0x05, 0x62, + 0x65, 0x61, 0x72, 0x73, 0x00, 0x02, 0x00, 0x0c, + 0x23, 0x23, 0x46, 0x69, 0x72, 0x73, 0x74, 0x4b, + 0x65, 0x79, 0x49, 0x64, 0x00, 0x01, 0x00, 0x0f, + 0x54, 0x68, 0x61, 0x74, 0x27, 0x73, 0x20, 0x6e, + 0x6f, 0x20, 0x6d, 0x6f, 0x6f, 0x6e, 0x2e, 0x00, + 0x03, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x04, 0x77, 0xb6, 0xe1, 0x8d, +}; + +/** + * metisTestDataV1_Interest_05_Name2_Payload2_KeyIdRestriction_KeyId2_COHashRestriction_WithPayload_CRC32 + * Interest + * Name: [lci:/lions/tigers/bears] + * ContentObject Hash Restriction: [015AF98C9FA7CBD13FE7F5BE3C0E7F009F18F92F2024FE06C1E8A6654B7EA636] + * KeyIdRestriction: [23235365636F6E644B65794964] + * PayloadString: [That's no moon.] + * PayloadBytes: [546861742773206E6F206D6F6F6E2E] + */ +static uint8_t metisTestDataV1_Interest_05_Name2_Payload2_KeyIdRestriction_KeyId2_COHashRestriction_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x84, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x68, 0x00, 0x00, 0x00, 0x1c, + 0x00, 0x01, 0x00, 0x05, 0x6c, 0x69, 0x6f, 0x6e, + 0x73, 0x00, 0x01, 0x00, 0x06, 0x74, 0x69, 0x67, + 0x65, 0x72, 0x73, 0x00, 0x01, 0x00, 0x05, 0x62, + 0x65, 0x61, 0x72, 0x73, 0x00, 0x02, 0x00, 0x0d, + 0x23, 0x23, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, + 0x4b, 0x65, 0x79, 0x49, 0x64, 0x00, 0x03, 0x00, + 0x20, 0x01, 0x5a, 0xf9, 0x8c, 0x9f, 0xa7, 0xcb, + 0xd1, 0x3f, 0xe7, 0xf5, 0xbe, 0x3c, 0x0e, 0x7f, + 0x00, 0x9f, 0x18, 0xf9, 0x2f, 0x20, 0x24, 0xfe, + 0x06, 0xc1, 0xe8, 0xa6, 0x65, 0x4b, 0x7e, 0xa6, + 0x36, 0x00, 0x01, 0x00, 0x0f, 0x54, 0x68, 0x61, + 0x74, 0x27, 0x73, 0x20, 0x6e, 0x6f, 0x20, 0x6d, + 0x6f, 0x6f, 0x6e, 0x2e, // 112 + + 0x00, 0x03, 0x00, 0x04, // 116 + 0x00, 0x02, 0x00, 0x00, // Validation alg type 2 (CRC32) + + 0x00, 0x04, 0x00, 0x04, 0xeb, 0x9e, 0x34, 0xb9, // Validation payload TLV +}; +static uint32_t metisTestDataV1_Interest_05_Name2_Payload2_KeyIdRestriction_KeyId2_COHashRestriction_WithPayload_CRC32_algOffset = 120; + + +/** + * metisTestDataV1_Interest_05_Name2_Payload2_KeyIdRestriction_KeyId2_WithPayload_CRC32 + * Interest + * Name: [lci:/lions/tigers/bears] + * KeyIdRestriction: [23235365636F6E644B65794964] + * ContentObject Hash Restriction: NONE + * PayloadString: [That's no moon.] + * PayloadBytes: [546861742773206E6F206D6F6F6E2E] + */ +static uint8_t metisTestDataV1_Interest_05_Name2_Payload2_KeyIdRestriction_KeyId2_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x60, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x44, 0x00, 0x00, 0x00, 0x1c, + 0x00, 0x01, 0x00, 0x05, 0x6c, 0x69, 0x6f, 0x6e, + 0x73, 0x00, 0x01, 0x00, 0x06, 0x74, 0x69, 0x67, + 0x65, 0x72, 0x73, 0x00, 0x01, 0x00, 0x05, 0x62, + 0x65, 0x61, 0x72, 0x73, 0x00, 0x02, 0x00, 0x0d, + 0x23, 0x23, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, + 0x4b, 0x65, 0x79, 0x49, 0x64, 0x00, 0x01, 0x00, + 0x0f, 0x54, 0x68, 0x61, 0x74, 0x27, 0x73, 0x20, + 0x6e, 0x6f, 0x20, 0x6d, 0x6f, 0x6f, 0x6e, 0x2e, + 0x00, 0x03, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x04, 0x7b, 0x18, 0x84, 0x23, + +}; + +/** + * metisTestDataV1_Interest_05_Name2_Payload2_NameOnly_CRC32 + * Interest + * Name: [lci:/lions/tigers/bears] + * ContentObject Hash Restriction: NONE + * KeyIdRestriction: NONE + */ +static uint8_t metisTestDataV1_Interest_05_Name2_Payload2_NameOnly_CRC32[] = { + 0x01, 0x00, 0x00, 0x3c, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, 0x1c, + 0x00, 0x01, 0x00, 0x05, 0x6c, 0x69, 0x6f, 0x6e, + 0x73, 0x00, 0x01, 0x00, 0x06, 0x74, 0x69, 0x67, + 0x65, 0x72, 0x73, 0x00, 0x01, 0x00, 0x05, 0x62, + 0x65, 0x61, 0x72, 0x73, // 40 + + 0x00, 0x03, 0x00, 0x04, // Validation TLV + 0x00, 0x02, 0x00, 0x00, // Validation Alg type(CRC32) + 0x00, 0x04, 0x00, 0x04, 0xdd, 0x09, 0x69, 0x5e, // Validation payload (0xDD09695E) +}; +static int metisTestDataV1_Interest_05_Name2_Payload2_NameOnly_CRC32_ValidationAlgOffset = 48; + +/** + * metisTestDataV1_Interest_05_Name2_Payload2_NameOnly_WithPayload_CRC32 + * Interest + * Name: [lci:/lions/tigers/bears] + * ContentObject Hash Restriction: NONE + * KeyIdRestriction: NONE + * PayloadString: [That's no moon.] + * PayloadBytes: [546861742773206E6F206D6F6F6E2E] + */ +static uint8_t metisTestDataV1_Interest_05_Name2_Payload2_NameOnly_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x4f, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x33, 0x00, 0x00, 0x00, 0x1c, + 0x00, 0x01, 0x00, 0x05, 0x6c, 0x69, 0x6f, 0x6e, + 0x73, 0x00, 0x01, 0x00, 0x06, 0x74, 0x69, 0x67, + 0x65, 0x72, 0x73, 0x00, 0x01, 0x00, 0x05, 0x62, + 0x65, 0x61, 0x72, 0x73, 0x00, 0x01, 0x00, 0x0f, + 0x54, 0x68, 0x61, 0x74, 0x27, 0x73, 0x20, 0x6e, + 0x6f, 0x20, 0x6d, 0x6f, 0x6f, 0x6e, 0x2e, 0x00, + 0x03, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x04, 0xbe, 0x7f, 0x8d, 0xb0, +}; + +/** + * metisTestDataV1_Interest_05_Name3_Payload3_COHashRestriction_WithPayload_CRC32 + * Interest + * Name: [lci:/boose/roo/pie] + * ContentObject Hash Restriction: [AEE8C5584DFC905EF0B58123E6A2A2A1B5DB9C3CF6D8B882120238E6DA522039] + * KeyIdRestriction: NONE + * PayloadString: [Do, or do not. There is no 'try'.] + * PayloadBytes: [446F2C206F7220646F206E6F742E205468657265206973206E6F2027747279272E] + */ +static uint8_t metisTestDataV1_Interest_05_Name3_Payload3_COHashRestriction_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x80, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x64, 0x00, 0x00, 0x00, 0x17, + 0x00, 0x01, 0x00, 0x05, 0x62, 0x6f, 0x6f, 0x73, + 0x65, 0x00, 0x01, 0x00, 0x03, 0x72, 0x6f, 0x6f, + 0x00, 0x01, 0x00, 0x03, 0x70, 0x69, 0x65, 0x00, + 0x03, 0x00, 0x20, 0xae, 0xe8, 0xc5, 0x58, 0x4d, + 0xfc, 0x90, 0x5e, 0xf0, 0xb5, 0x81, 0x23, 0xe6, + 0xa2, 0xa2, 0xa1, 0xb5, 0xdb, 0x9c, 0x3c, 0xf6, + 0xd8, 0xb8, 0x82, 0x12, 0x02, 0x38, 0xe6, 0xda, + 0x52, 0x20, 0x39, 0x00, 0x01, 0x00, 0x21, 0x44, + 0x6f, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x64, 0x6f, + 0x20, 0x6e, 0x6f, 0x74, 0x2e, 0x20, 0x54, 0x68, + 0x65, 0x72, 0x65, 0x20, 0x69, 0x73, 0x20, 0x6e, + 0x6f, 0x20, 0x27, 0x74, 0x72, 0x79, 0x27, 0x2e, + 0x00, 0x03, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x04, 0x2e, 0xa7, 0x62, 0xf7, + +}; + +/** + * metisTestDataV1_Interest_05_Name3_Payload3_KeyIdRestriction_KeyId1_COHashRestriction_WithPayload_CRC32 + * Interest + * Name: [lci:/boose/roo/pie] + * ContentObject Hash Restriction: [AEE8C5584DFC905EF0B58123E6A2A2A1B5DB9C3CF6D8B882120238E6DA522039] + * KeyIdRestriction: [232346697273744B65794964] + * PayloadString: [Do, or do not. There is no 'try'.] + * PayloadBytes: [446F2C206F7220646F206E6F742E205468657265206973206E6F2027747279272E] + */ +static uint8_t metisTestDataV1_Interest_05_Name3_Payload3_KeyIdRestriction_KeyId1_COHashRestriction_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x90, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x74, 0x00, 0x00, 0x00, 0x17, + 0x00, 0x01, 0x00, 0x05, 0x62, 0x6f, 0x6f, 0x73, + 0x65, 0x00, 0x01, 0x00, 0x03, 0x72, 0x6f, 0x6f, + 0x00, 0x01, 0x00, 0x03, 0x70, 0x69, 0x65, 0x00, + 0x02, 0x00, 0x0c, 0x23, 0x23, 0x46, 0x69, 0x72, + 0x73, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x00, + 0x03, 0x00, 0x20, 0xae, 0xe8, 0xc5, 0x58, 0x4d, + 0xfc, 0x90, 0x5e, 0xf0, 0xb5, 0x81, 0x23, 0xe6, + 0xa2, 0xa2, 0xa1, 0xb5, 0xdb, 0x9c, 0x3c, 0xf6, + 0xd8, 0xb8, 0x82, 0x12, 0x02, 0x38, 0xe6, 0xda, + 0x52, 0x20, 0x39, 0x00, 0x01, 0x00, 0x21, 0x44, + 0x6f, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x64, 0x6f, + 0x20, 0x6e, 0x6f, 0x74, 0x2e, 0x20, 0x54, 0x68, + 0x65, 0x72, 0x65, 0x20, 0x69, 0x73, 0x20, 0x6e, + 0x6f, 0x20, 0x27, 0x74, 0x72, 0x79, 0x27, 0x2e, + 0x00, 0x03, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x04, 0x6e, 0xb4, 0x92, 0xfc, + +}; + +/** + * metisTestDataV1_Interest_05_Name3_Payload3_KeyIdRestriction_KeyId1_WithPayload_CRC32 + * Interest + * Name: [lci:/boose/roo/pie] + * KeyIdRestriction: [232346697273744B65794964] + * ContentObject Hash Restriction: NONE + * PayloadString: [Do, or do not. There is no 'try'.] + * PayloadBytes: [446F2C206F7220646F206E6F742E205468657265206973206E6F2027747279272E] + */ +static uint8_t metisTestDataV1_Interest_05_Name3_Payload3_KeyIdRestriction_KeyId1_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x6c, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x50, 0x00, 0x00, 0x00, 0x17, + 0x00, 0x01, 0x00, 0x05, 0x62, 0x6f, 0x6f, 0x73, + 0x65, 0x00, 0x01, 0x00, 0x03, 0x72, 0x6f, 0x6f, + 0x00, 0x01, 0x00, 0x03, 0x70, 0x69, 0x65, 0x00, + 0x02, 0x00, 0x0c, 0x23, 0x23, 0x46, 0x69, 0x72, + 0x73, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x00, + 0x01, 0x00, 0x21, 0x44, 0x6f, 0x2c, 0x20, 0x6f, + 0x72, 0x20, 0x64, 0x6f, 0x20, 0x6e, 0x6f, 0x74, + 0x2e, 0x20, 0x54, 0x68, 0x65, 0x72, 0x65, 0x20, + 0x69, 0x73, 0x20, 0x6e, 0x6f, 0x20, 0x27, 0x74, + 0x72, 0x79, 0x27, 0x2e, 0x00, 0x03, 0x00, 0x04, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, + 0x5d, 0x6e, 0x09, 0x8f, +}; + +/** + * metisTestDataV1_Interest_05_Name3_Payload3_NameOnly_CRC32 + * Interest + * Name: [lci:/boose/roo/pie] + * ContentObject Hash Restriction: NONE + * KeyIdRestriction: NONE + */ +static uint8_t metisTestDataV1_Interest_05_Name3_Payload3_NameOnly_CRC32[] = { + 0x01, 0x00, 0x00, 0x37, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x17, + 0x00, 0x01, 0x00, 0x05, 0x62, 0x6f, 0x6f, 0x73, + 0x65, 0x00, 0x01, 0x00, 0x03, 0x72, 0x6f, 0x6f, + 0x00, 0x01, 0x00, 0x03, 0x70, 0x69, 0x65, 0x00, + 0x03, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x04, 0x90, 0x93, 0x83, 0x6a, +}; + +/** + * metisTestDataV1_Interest_05_Name3_Payload3_NameOnly_WithPayload_CRC32 + * Interest + * Name: [lci:/boose/roo/pie] + * ContentObject Hash Restriction: NONE + * KeyIdRestriction: NONE + * PayloadString: [Do, or do not. There is no 'try'.] + * PayloadBytes: [446F2C206F7220646F206E6F742E205468657265206973206E6F2027747279272E] + */ +static uint8_t metisTestDataV1_Interest_05_Name3_Payload3_NameOnly_WithPayload_CRC32[] = { + 0x01, 0x00, 0x00, 0x5c, 0xff, 0x00, 0x00, 0x08, + + 0x00, 0x01, 0x00, 0x40, 0x00, 0x00, 0x00, 0x17, + 0x00, 0x01, 0x00, 0x05, 0x62, 0x6f, 0x6f, 0x73, + 0x65, 0x00, 0x01, 0x00, 0x03, 0x72, 0x6f, 0x6f, + 0x00, 0x01, 0x00, 0x03, 0x70, 0x69, 0x65, 0x00, + 0x01, 0x00, 0x21, 0x44, 0x6f, 0x2c, 0x20, 0x6f, + 0x72, 0x20, 0x64, 0x6f, 0x20, 0x6e, 0x6f, 0x74, + 0x2e, 0x20, 0x54, 0x68, 0x65, 0x72, 0x65, 0x20, + 0x69, 0x73, 0x20, 0x6e, 0x6f, 0x20, 0x27, 0x74, + 0x72, 0x79, 0x27, 0x2e, 0x00, 0x03, 0x00, 0x04, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, + 0x06, 0x72, 0x87, 0xe1, +}; + diff --git a/metis/ccnx/forwarder/metis/tlv/metis_Tlv.c b/metis/ccnx/forwarder/metis/tlv/metis_Tlv.c new file mode 100644 index 00000000..f1afe34e --- /dev/null +++ b/metis/ccnx/forwarder/metis/tlv/metis_Tlv.c @@ -0,0 +1,176 @@ +/* + * 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 <LongBow/runtime.h> + +#include <stdio.h> + + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> +#include <ccnx/forwarder/metis/tlv/metis_TlvSchemaV0.h> +#include <ccnx/forwarder/metis/tlv/metis_TlvSchemaV1.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +// a reasonably large enough number that we capture name parsing without +// needing to re-alloc. Not a big deal if this is wrong, it just means +// we have to do one pass to count and another pass to fillin, and waste +// one malloc and free. +static const size_t _initialLengthForNameExtents = 24; + + +// ----------------------------- + +size_t +metisTlv_FixedHeaderLength(void) +{ + // at some point this will no longer be true and we will have to refactor + return 8; +} + + +size_t +metisTlv_TotalHeaderLength(const uint8_t *packet) +{ + size_t length = 0; + uint8_t version = packet[0]; + switch (version) { + case 0: + length = MetisTlvSchemaV0_Ops.totalHeaderLength(packet); // Deprecated + break; + + case 1: + length = MetisTlvSchemaV1_Ops.totalHeaderLength(packet); + break; + + default: + break; + } + return length; +} + +size_t +metisTlv_TotalPacketLength(const uint8_t *packet) +{ + size_t length = 0; + uint8_t version = packet[0]; + switch (version) { + case 0: + length = MetisTlvSchemaV0_Ops.totalPacketLength(packet); // Deprecated + break; + + case 1: + length = MetisTlvSchemaV1_Ops.totalPacketLength(packet); + break; + + default: + break; + } + return length; +} + +PARCBuffer * +metisTlv_EncodeControlPlaneInformation(const CCNxControl *cpiControlMessage) +{ + PARCBuffer *encoded = NULL; + CCNxTlvDictionary_SchemaVersion version = ccnxTlvDictionary_GetSchemaVersion(cpiControlMessage); + switch (version) { + case CCNxTlvDictionary_SchemaVersion_V0: + encoded = MetisTlvSchemaV0_Ops.encodeControlPlaneInformation(cpiControlMessage); + break; + + case CCNxTlvDictionary_SchemaVersion_V1: + encoded = MetisTlvSchemaV1_Ops.encodeControlPlaneInformation(cpiControlMessage); + break; + + default: + break; + } + return encoded; +} + +/** + * @function metisTlv_ParseName + * @abstract Parse a name into the provided output array, ensuring it does not exceed outputLength + * @discussion + * <#Discussion#> + * + * @param outputArray may be NULL to count the number of name elements. + * @para outputLength is the maximum number of name segments to parse in to outputArray + * @return The number of name elements parsed + */ +static size_t +_metisTlv_ParseName(uint8_t *name, size_t nameLength, MetisTlvExtent *outputArray, size_t outputLength) +{ + size_t offset = 0; + size_t count = 0; + const size_t tl_length = 4; + while (offset < nameLength) { + MetisTlvType *tlv = (MetisTlvType *) (name + offset); + uint16_t v_length = htons(tlv->length); + + if (count < outputLength) { + outputArray[count].offset = offset; + outputArray[count].length = tl_length + v_length; + } + + // skip past the TL and V + offset += tl_length + v_length; + count++; + } + return count; +} + +void +metisTlv_NameSegments(uint8_t *name, size_t nameLength, MetisTlvExtent **outputArrayPtr, size_t *outputLengthPtr) +{ + // allocate an array that's kind of big. if name does not fit, we'll need to re-alloc. + MetisTlvExtent *output = parcMemory_Allocate(_initialLengthForNameExtents * sizeof(MetisTlvExtent)); + assertNotNull(output, "parcMemory_Allocate(%zu) returned NULL", _initialLengthForNameExtents * sizeof(MetisTlvExtent)); + + size_t actualLength = _metisTlv_ParseName(name, nameLength, output, _initialLengthForNameExtents); + if (actualLength > _initialLengthForNameExtents) { + // Oops, do over + parcMemory_Deallocate((void **) &output); + output = parcMemory_Allocate(actualLength * sizeof(MetisTlvExtent)); + assertNotNull(output, "parcMemory_Allocate(%zu) returned NULL", actualLength * sizeof(MetisTlvExtent)); + _metisTlv_ParseName(name, nameLength, output, actualLength); + } + + *outputArrayPtr = output; + *outputLengthPtr = actualLength; +} + +bool +metisTlv_ExtentToVarInt(const uint8_t *packet, const MetisTlvExtent *extent, uint64_t *output) +{ + assertNotNull(packet, "Parameter buffer must be non-null"); + assertNotNull(extent, "Parameter output must be non-null"); + + bool success = false; + if (extent->length >= 1 && extent->length <= 8) { + uint64_t value = 0; + for (int i = 0; i < extent->length; i++) { + value = value << 8 | packet[extent->offset + i]; + } + *output = value; + success = true; + } + return success; +} + diff --git a/metis/ccnx/forwarder/metis/tlv/metis_Tlv.h b/metis/ccnx/forwarder/metis/tlv/metis_Tlv.h new file mode 100644 index 00000000..1cd04631 --- /dev/null +++ b/metis/ccnx/forwarder/metis/tlv/metis_Tlv.h @@ -0,0 +1,215 @@ +/* + * 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_Tlv.h + * @brief The generic Tlv utilities. + * + * Provides generaic Tlv utilities, particularly for packets that have not been + * decoded in to their skeleton. Once packets are in the skeleton format, one should + * use functions in metis_TlvSkeleton. + * + */ + +#ifndef Metis_metis_Tlv_h +#define Metis_metis_Tlv_h + +#include <stdlib.h> +#include <parc/security/parc_CryptoHash.h> +#include <ccnx/api/control/cpi_ControlMessage.h> +#include <parc/algol/parc_ByteArray.h> + +#include <ccnx/forwarder/metis/tlv/metis_TlvExtent.h> +#include <ccnx/forwarder/metis/tlv/metis_TlvSkeleton.h> + +/** + * The TLV format + * + * Mapping of the TLV format to a structure. Remember to use + * <code>htons()</code> or <code>ntohs()</code> on the values + * if working in host byte order. + * + * The 'length' is the length of the 'value', it does not include + * the T and L of the TLV. A length of "0" is acceptable. + * + * @param type is in network byte order + * @param length is in network byte order + * + * Example: + * @code + * { + * uint8_t *packet = // packet received from network + * size_t offset = // where you are in parsing the packet + * + * MetisTlvType *tlv = (MetisTlvType *) (packet + offset); + * uint16_t type = htons(tlv->type); + * uint16_t v_length = htons(tlv->length); + * } + * @endcode + */ +typedef struct __attribute__ ((packed)) metis_tlv_type { + uint16_t type; + uint16_t length; +} MetisTlvType; + + +/** + * Returns the length of the fixed header + * + * This is assumed to be the same for all versions. At some point this will no longer be true + * and metis will need to be re-factored. This function works for V0 and V1 packets. + * + * @return positive The bytes of the fixed header. + * + * Example: + * @code + * { + * if (parcEventBuffer_GetLength(input) >= metisTlv_FixedHeaderLength()) { + * uint8_t fixedheader[metisTlv_FixedHeaderLength()]; + * read(fd, &fixedheader, metisTlv_FixedHeaderLength()); + * // process fixed header + * } + * } + * @endcode + */ +size_t metisTlv_FixedHeaderLength(void); + +/** + * Returns the length of all headers, which is the offset where the CCNx message starts + * + * Includes both the fixed header and the per hop headers. + * Will return "0" for unknown packet version + * + * @param [in] packet Pointer to bytes 0 of the fixed header + * + * @retval positive The total header length (minimum 8) + * @retval 0 Unsupported packet version or other error + * + * Example: + * @code + * <#example#> + * @endcode + */ +size_t metisTlv_TotalHeaderLength(const uint8_t *packet); + +/** + * The total packet length based on the fixed header + * + * Parses the fixed header and returns the total packet length + * Will return "0" for unknown packet version + * + * @param [in] packet Packet memory pointer + * + * @retval number Total packet length + * + * Example: + * @code + * { + * // in an example packet parser (does not handle any error conditions or partial reads) + * if (parcEventBuffer_GetLength(input) >= metisTlv_FixedHeaderLength()) { + * uint8_t fixedheader[metisTlv_FixedHeaderLength()]; + * read(fd, &fixedheader, metisTlv_FixedHeaderLength()); + * + * size_t remainingBytes = metisTlv_TotalPacketLength(&fixedheader) - metisTlv_FixedHeaderLength(); + * if (parcEventBuffer_GetLength(input) >= remainingBytes) { + * uint8_t *packet = parcMemory_Allocate(metisTlv_TotalPacketLength(&fixedheader)); + * read(fd, packet + metisTlv_FixedHeaderLength(), remainingBytes); + * } + * } + * } + * @endcode + */ +size_t metisTlv_TotalPacketLength(const uint8_t *packet); + +/** + * @function metisTlv_NameSegments + * @abstract Treats the input as a TLV-encoded name, generating an output array of name segment extents + * @discussion + * The outputArray is an ordered list of extents, giving the offset and length of each path segment. + * The lengths include the path segment type, length, and value. + * + * Example: Lets represent the name as a set of of tuples of T, L, and V: + * (t=1, len=4, value="help"), (t=1, len=2, value="me"), (t=7, len=10, value="understand") + * This name as 3 path segments The first segment is of type 1, length 4, and value "help". + * The total length of that segment is 4 + 4 = 8, including the T and L. + * The outputArray would be { {.offset=0, .length=8}, {.offset=8, .length=6}, {.offset=14, .length=14} }. + * The outputLenght would be 3, because there are 3 elements in the array. + * + * @param name is a TLV-encoded name, not including the container name TLV + * @param nameLength is the length of the name + * @param outputArrayPtr is an allocated array of ordered extents, must be freed with <code>parcMemory_Deallocate()</code> + * @param outputLengthPtr is the number of elements allocated in the array. + * + * Example: + * @code + * { + * MetisTlvExtent *extentArray; + * size_t arrayLength; + * uint8_t encodedName[] = "\x00\x01\x00\x05" "apple" "\x00\x01\x00\x03" "pie"; + * metisTlv_NameSegments(encodedName, sizeof(encodedName), &extentArray, &arrayLength); + * // arrrayLength = 2 + * // extentArray[1].offset = 4 + * // extentArray[1].length = 5 + * // etc. + * parcMemory_Deallocate(&extentArray); + * } + * @endcode + */ +void metisTlv_NameSegments(uint8_t *name, size_t nameLength, MetisTlvExtent **outputArrayPtr, size_t *outputLengthPtr); + +/** + * Given a CCNxControl packet, encode it in the proper schema + * + * Based on the dictionary schema version, will encode the control packet with the + * correct encoder. + * + * @param [in] cpiControlMessage A an allocated control message + * + * @retval non-null An allocation wire format buffer + * @retval null An error (likely unsupported schema version) + * + * Example: + * @code + * <#example#> + * @endcode + */ +PARCBuffer *metisTlv_EncodeControlPlaneInformation(const CCNxControl *cpiControlMessage); + +/** + * Parse an extent as a VarInt + * + * The extent must be 1 to 8 bytes. + * + * @param [in] packet The packet memory pointer + * @param [in] extent The byte extent of the varint buffer + * @param [out] output The VarInt value + * + * @retval true The buffer was correctly parsed + * @retval false The buffer was not parsed (likely extent length is 0 or greater than 8) + * + * Example: + * @code + * { + * uint8_t packet[] = { 0x00, 0x03, 0x00, 0x03, 0xa0, 0xa1, 0xa3 }; + * MetisTlvExtent extent = { .offset = 4, .length = 3 }; + * uint64_t output; + * metisTlv_ExtentToVarInt(packet, &extent, &output); + * // output = 0xa0a1a3 + * } + * @endcode + */ +bool metisTlv_ExtentToVarInt(const uint8_t *packet, const MetisTlvExtent *extent, uint64_t *output); + +#endif // Metis_metis_Tlv_h diff --git a/metis/ccnx/forwarder/metis/tlv/metis_TlvExtent.c b/metis/ccnx/forwarder/metis/tlv/metis_TlvExtent.c new file mode 100644 index 00000000..6529b710 --- /dev/null +++ b/metis/ccnx/forwarder/metis/tlv/metis_TlvExtent.c @@ -0,0 +1,30 @@ +/* + * 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 "metis_TlvExtent.h" + +const MetisTlvExtent metisTlvExtent_NotFound = { 0x0, 0x0 }; + +bool +metisTlvExtent_Equals(const MetisTlvExtent *a, const MetisTlvExtent *b) +{ + if (a->offset == b->offset && a->length == b->length) { + return true; + } + return false; +} diff --git a/metis/ccnx/forwarder/metis/tlv/metis_TlvExtent.h b/metis/ccnx/forwarder/metis/tlv/metis_TlvExtent.h new file mode 100644 index 00000000..056ad34d --- /dev/null +++ b/metis/ccnx/forwarder/metis/tlv/metis_TlvExtent.h @@ -0,0 +1,89 @@ +/* + * 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_TlvExtent.h + * @brief Defines the extent structure used to map a TLV packet + * + * In MetisTlvSkeleton, all the pertinent fields used by Metis are stored by their extent range in the + * received packet buffer. An extent is (offset, length). + * + */ +#ifndef Metis_metis_tlv_Extent_h +#define Metis_metis_tlv_Extent_h + +#include <stdint.h> +#include <stdbool.h> + +/** + * Stores the location of a field within a buffer. + * The values are usually in host byte order. + */ +typedef struct metis_tlv_extent { + uint16_t offset; + uint16_t length; +} MetisTlvExtent; + +/** + * Used to detect a "not found" or "not present" condition. + * Equal to { 0x0, 0x0 }, which is an invalid extent value. + */ +extern const MetisTlvExtent metisTlvExtent_NotFound; + + +/** + * Determine if two MetisTlvExtent instances are equal. + * + * The following equivalence relations on non-null `MetisTlvExtent` instances are maintained: + * + * * It is reflexive: for any non-null reference value x, `MetisTlvExtent_Equals(x, x)` + * must return true. + * + * * It is symmetric: for any non-null reference values x and y, + * `metisTlvExtent_Equals(x, y)` must return true if and only if + * `metisTlvExtent_Equals(y, x)` returns true. + * + * * It is transitive: for any non-null reference values x, y, and z, if + * `metisTlvExtent_Equals(x, y)` returns true and + * `metisTlvExtent_Equals(y, z)` returns true, + * then `metisTlvExtent_Equals(x, z)` must return true. + * + * * It is consistent: for any non-null reference values x and y, multiple + * invocations of `metisTlvExtent_Equals(x, y)` consistently return true or + * consistently return false. + * + * * For any non-null reference value x, `metisTlvExtent_Equals(x, NULL)` must + * return false. + * + * @param a A pointer to a `MetisTlvExtent` instance. + * @param b A pointer to a `MetisTlvExtent` instance. + * @return true if the two `MetisTlvExtent` instances are equal. + * + * Example: + * @code + * { + * MetisTlvExtent *a = { .offset = 5, .length = 7 }; + * MetisTlvExtent *b = { .offset = 5, .length = 8 }; + * + * if (metisTlvExtent_Equals(a, b)) { + * // true + * } else { + * // false (this is the block executed) + * } + * } + * @endcode + */ +bool metisTlvExtent_Equals(const MetisTlvExtent *a, const MetisTlvExtent *b); +#endif // Metis_metis_tlv_Extent_h diff --git a/metis/ccnx/forwarder/metis/tlv/metis_TlvName.c b/metis/ccnx/forwarder/metis/tlv/metis_TlvName.c new file mode 100644 index 00000000..852978c5 --- /dev/null +++ b/metis/ccnx/forwarder/metis/tlv/metis_TlvName.c @@ -0,0 +1,319 @@ +/* + * 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. + */ + +/** + * A name built around the TLV representation. + * + * A common operation is to get a sub-string of the name, specifically prefixes. Use + * metisTlvName_Slice() for that. + * + * To be efficient about Slice(), the reference counters are pointers, so every allocated + * copy shares the reference counter and all the allocated memory. Each Slice() will do + * one malloc for the new shell and do a shallow memcpy of the struct. Destroy will always + * free the shell, but will only free the guts when the shared reference count goes to zero. + * + */ + +#include <config.h> +#include <stdio.h> +#include <string.h> +#include <limits.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_Hash.h> +#include <parc/algol/parc_BufferComposer.h> + +#include <ccnx/forwarder/metis/tlv/metis_TlvName.h> +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> +#include <ccnx/forwarder/metis/tlv/metis_TlvNameCodec.h> + +#include <LongBow/runtime.h> + +struct metis_tlv_name { + uint8_t *memory; + size_t memoryLength; + + // the refcount is shared between all copies + unsigned *refCountPtr; + + // the memory extents of each path segment + MetisTlvExtent *segmentArray; + size_t segmentArrayLength; + + // hashes of the name through different prefix lengths + // It is allocated out to the limit (same assegmentArrayLength), + // but only computed so far through segmentCumulativeHashArrayLength + // This is to avoid computing the hash over unnecessary suffix segments. + size_t segmentCumulativeHashArrayLimit; + + // the cumulative hash array length is shared between all copies, so if + // one copy extends the array, all copies see it + size_t *segmentCumulativeHashArrayLengthPtr; + uint32_t *segmentCumulativeHashArray; +}; + +// ===================================================== + +static unsigned +_getRefCount(const MetisTlvName *name) +{ + return *name->refCountPtr; +} + +static void +_incrementRefCount(MetisTlvName *name) +{ + assertTrue(*name->refCountPtr > 0, "Illegal State: Trying to increment a 0 refcount!"); + (*name->refCountPtr)++; +} + +static void +_decrementRefCount(MetisTlvName *name) +{ + assertTrue(*name->refCountPtr > 0, "Illegal State: Trying to decrement a 0 refcount!"); + (*name->refCountPtr)--; +} + +// ============================================================================ + +/** + * Common parts of setting up a MetisTlvName after the backing memory has been allocated and copied in to. + * + * PRECONDITIONS: name->memory and name->memoryLength set + */ +static void +_setup(MetisTlvName *name) +{ + name->refCountPtr = parcMemory_Allocate(sizeof(unsigned)); + assertNotNull(name->refCountPtr, "parcMemory_Allocate(%zu) returned NULL", sizeof(unsigned)); + *name->refCountPtr = 1; + + metisTlv_NameSegments(name->memory, name->memoryLength, &name->segmentArray, &name->segmentArrayLength); + + name->segmentCumulativeHashArray = parcMemory_Allocate(name->segmentArrayLength * sizeof(uint32_t)); + assertNotNull(name->segmentCumulativeHashArray, "parcMemory_Allocate(%zu) returned NULL", name->segmentArrayLength * sizeof(uint32_t)); + + name->segmentCumulativeHashArrayLengthPtr = parcMemory_Allocate(sizeof(size_t)); + assertNotNull(name->segmentCumulativeHashArrayLengthPtr, "parcMemory_Allocate(%zu) returned NULL", sizeof(size_t)); + + *name->segmentCumulativeHashArrayLengthPtr = 1; + name->segmentCumulativeHashArrayLimit = name->segmentArrayLength; + + + if (name->segmentArrayLength > 0) { + // always hash the 1st name component. This is needed as the initial case + // to do the cumulative hashes in metisTlvName_HashCode + name->segmentCumulativeHashArray[0] = parcHash32_Data(&name->memory[name->segmentArray[0].offset], name->segmentArray[0].length); + } +} + +MetisTlvName * +metisTlvName_Create(const uint8_t *memory, size_t memoryLength) +{ + MetisTlvName *name = parcMemory_AllocateAndClear(sizeof(MetisTlvName)); + assertNotNull(name, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisTlvName)); + + name->memory = parcMemory_Allocate(memoryLength); + assertNotNull(name->memory, "parcMemory_Allocate(%zu) returned NULL", memoryLength); + + memcpy(name->memory, memory, memoryLength); + name->memoryLength = memoryLength; + + _setup(name); + + return name; +} + +MetisTlvName * +metisTlvName_CreateFromCCNxName(const CCNxName *ccnxName) +{ + // to avoid reallocs, calculate the exact size we need + size_t memoryLength = 0; + for (size_t i = 0; i < ccnxName_GetSegmentCount(ccnxName); i++) { + CCNxNameSegment *segment = ccnxName_GetSegment(ccnxName, i); + memoryLength += 4 + ccnxNameSegment_Length(segment); + } + + MetisTlvName *name = parcMemory_AllocateAndClear(sizeof(MetisTlvName)); + assertNotNull(name, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisTlvName)); + + name->memoryLength = memoryLength; + name->memory = parcMemory_Allocate(memoryLength); + assertNotNull(name->memory, "parcMemory_Allocate(%zu) returned NULL", memoryLength); + + uint8_t *p = name->memory; + uint8_t *end = p + memoryLength; + for (size_t i = 0; i < ccnxName_GetSegmentCount(ccnxName); i++) { + CCNxNameSegment *segment = ccnxName_GetSegment(ccnxName, i); + uint16_t type = ccnxNameSegment_GetType(segment); + uint16_t length = ccnxNameSegment_Length(segment); + + *(uint16_t *) p = htons(type); + p += 2; + + *(uint16_t *) p = htons(length); + p += 2; + + if (length >0) { + PARCBuffer *buffer = ccnxNameSegment_GetValue(segment); + uint8_t *overlay = parcBuffer_Overlay(buffer, 0); + memcpy(p, overlay, length); + + p += length; + } + + // sanity check + assertTrue(p <= end, "Wrote past the end of buffer, now at %p end at %p", p, end); + } + + _setup(name); + return name; +} + +void +metisTlvName_Release(MetisTlvName **namePtr) +{ + assertNotNull(namePtr, "Parameter must be non-null double pointer"); + assertNotNull(*namePtr, "Parameter must dereference to non-null pointer"); + + MetisTlvName *name = *namePtr; + _decrementRefCount(name); + if (_getRefCount(name) == 0) { + parcMemory_Deallocate((void **) &(name->refCountPtr)); + parcMemory_Deallocate((void **) &(name->segmentArray)); + parcMemory_Deallocate((void **) &(name->segmentCumulativeHashArray)); + parcMemory_Deallocate((void **) &(name->segmentCumulativeHashArrayLengthPtr)); + parcMemory_Deallocate((void **) &(name->memory)); + } + parcMemory_Deallocate((void **) &name); + *namePtr = NULL; +} + +MetisTlvName * +metisTlvName_Acquire(const MetisTlvName *original) +{ + return metisTlvName_Slice(original, UINT_MAX); +} + +MetisTlvName * +metisTlvName_Slice(const MetisTlvName *original, size_t segmentCount) +{ + assertNotNull(original, "Parameter must be non-null"); + MetisTlvName *copy = parcMemory_AllocateAndClear(sizeof(MetisTlvName)); + assertNotNull(copy, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisTlvName)); + + memcpy(copy, original, sizeof(MetisTlvName)); + _incrementRefCount(copy); + + copy->segmentArrayLength = (copy->segmentArrayLength < segmentCount) ? copy->segmentArrayLength : segmentCount; + + // for equality to work, we need to shorten the MemoryLength to the amount + // actually used by the number of segments. + size_t startOfLastSegment = copy->segmentArray[ copy->segmentArrayLength - 1 ].offset; + size_t lengthOfLastSegment = copy->segmentArray[ copy->segmentArrayLength - 1 ].length; + + copy->memoryLength = startOfLastSegment + lengthOfLastSegment; + + return copy; +} + +uint32_t +metisTlvName_HashCode(const MetisTlvName *name) +{ + if ((name == NULL) || (name->segmentArrayLength == 0)) { + return 0; + } + + size_t lastSegment = name->segmentArrayLength - 1; + + if (lastSegment >= *name->segmentCumulativeHashArrayLengthPtr) { + // we have not yet computed this, so lets do it now! + // Note that we go up to and including lastSegment in the for loop. lastSegment is not a "length", it is + // the actual array index. + for (size_t i = (*name->segmentCumulativeHashArrayLengthPtr); i <= lastSegment; i++) { + // the -1 is ok in the 3rd argument, because segmentCumulativeHashArrayLength always has length at least 1 + // if there are any name components to hash. + name->segmentCumulativeHashArray[i] = parcHash32_Data_Cumulative(&name->memory[ name->segmentArray[i].offset ], + name->segmentArray[i].length, + name->segmentCumulativeHashArray[i - 1]); + } + *name->segmentCumulativeHashArrayLengthPtr = lastSegment + 1; + } + + return name->segmentCumulativeHashArray[lastSegment]; +} + +bool +metisTlvName_Equals(const MetisTlvName *a, const MetisTlvName *b) +{ + assertNotNull(a, "Parameter a must be non-null"); + assertNotNull(b, "Parameter b must be non-null"); + + if (a->memoryLength == b->memoryLength) { + return (memcmp(a->memory, b->memory, a->memoryLength) == 0); + } + return false; +} + +int +metisTlvName_Compare(const MetisTlvName *a, const MetisTlvName *b) +{ + if (a == NULL && b == NULL) { + return 0; + } + if (a == NULL) { + return -1; + } + if (b == NULL) { + return +1; + } + + if (a->memoryLength < b->memoryLength) { + return -1; + } + + if (a->memoryLength > b->memoryLength) { + return +1; + } + + return memcmp(a->memory, b->memory, a->memoryLength); +} + +bool +metisTlvName_StartsWith(const MetisTlvName *name, const MetisTlvName *prefix) +{ + assertNotNull(name, "Parameter name must be non-null"); + assertNotNull(prefix, "Parameter prefix must be non-null"); + + if (prefix->memoryLength <= name->memoryLength) { + return (memcmp(prefix->memory, name->memory, prefix->memoryLength) == 0); + } + + return false; +} + +size_t +metisTlvName_SegmentCount(const MetisTlvName *name) +{ + assertNotNull(name, "Parameter name must be non-null"); + return name->segmentArrayLength; +} + +CCNxName * +metisTlvName_ToCCNxName(const MetisTlvName *name) +{ + return metisTlvNameCodec_Decode(name->memory, 0, name->memoryLength); +} + diff --git a/metis/ccnx/forwarder/metis/tlv/metis_TlvName.h b/metis/ccnx/forwarder/metis/tlv/metis_TlvName.h new file mode 100644 index 00000000..b63f4273 --- /dev/null +++ b/metis/ccnx/forwarder/metis/tlv/metis_TlvName.h @@ -0,0 +1,300 @@ +/* + * 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_TlvName.h + * @brief Representation of the name + * + * It is stored as an array of TLV extents, one for each name segment. This allows longest matching + * prefix comparisons as well as equality comparisons. + * + */ + +#ifndef Metis_metis_tlv_Name_h +#define Metis_metis_tlv_Name_h + +#include <stdlib.h> +#include <stdbool.h> +#include <ccnx/common/ccnx_Name.h> + +struct metis_tlv_name; +typedef struct metis_tlv_name MetisTlvName; + +/** + * Creates a name from packet memory + * + * <#Paragraphs Of Explanation#> + * + * @param [in] memory A pointer to the beginning of the Name TLV "value". + * @param [in] The length of the "value" + * + * @retval non-null An allocated MetisTlvName + * @retval null An error + * + * Example: + * @code + * { + * uint8_t encodedName[] = {0x00, 0x01, 0x00, 0x05, 'a', 'p', 'p', 'l', 'e', 0x00, 0x01, 0x00, 0x03, 'p', 'i', 'e'}; + * MetisTlvName *name = metisTlvName_Create(encodedName, sizeof(encodedName)); + * metisTlvName_Release(&name); + * } + * @endcode + */ +MetisTlvName *metisTlvName_Create(const uint8_t *memory, size_t length); + +/** + * Creates a Metis-sytle name from a CCNxName + * + * Converts a CCNxName to a Metis Name. The Metis name has its own backing memory, + * so it is independent of the CCNxName. + * + * @param [in] ccnxName An allocated CCNxName + * + * @retval non-null An allocated MetisTlvName + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisTlvName *metisTlvName_CreateFromCCNxName(const CCNxName *ccnxName); + +/** + * Releases one reference count, and frees memory after last reference + * + * <#Paragraphs Of Explanation#> + * + * @param [in,out] namePtr pointer to the name to free, *namePtr will be NULL'd + * + * Example: + * @code + * { + * uint8_t encodedName[] = {0x00, 0x01, 0x00, 0x05, 'a', 'p', 'p', 'l', 'e', 0x00, 0x01, 0x00, 0x03, 'p', 'i', 'e'}; + * MetisTlvName *name = metisTlvName_Create(encodedName, sizeof(encodedName)); + * metisTlvName_Release(&name); + * } + * @endcode + */ +void metisTlvName_Release(MetisTlvName **namePtr); + +/** + * Acquires a reference to the name + * + * The MetisTlvName wrapper is allocated but the underlying name memory is shared. + * The returned pointer will not be the same as the original. + * + * @param [in] original The name to acquire a reference to + * + * @retval non-null A reference counted copy + * @retval null An error + * + * Example: + * @code + * { + * uint8_t encodedName[] = {0x00, 0x01, 0x00, 0x05, 'a', 'p', 'p', 'l', 'e', 0x00, 0x01, 0x00, 0x03, 'p', 'i', 'e'}; + * MetisTlvName *name = metisTlvName_Create(encodedName, sizeof(encodedName)); + * MetisTlvName *copy = metisTlvName_Acquire(name); + * metisTlvName_Release(&name); + * metisTlvName_Release(©); + * } + * @endcode + */ +MetisTlvName *metisTlvName_Acquire(const MetisTlvName *original); + +/** + * Acquire a reference to the first name, but only use first 'segmentCount' name segments + * + * A reference to the underlying name segments is increased but a new MetisTlvName wrapper is + * put around them. This wrapper will only have 'segmentCount' name segments -- any name segments + * after that are ignored. + * + * If segmentCount is longer than the name (e.g. UINT_MAX), it will be the same as the orignal + * name, but have a different wrapper. You sould probalby use metisTlvName_Acquire() in this case, + * as it will avoid re-calculating the name's hash. + * + * This is a reference counted way to shorten a name, such as to store it as a shorter FIB entry. + * + * @param [in] original The name to acquire and slice + * @param [in] segmentCount The number of segments, may be longer than the name + * + * @retval non-null A new slice + * @retval null An error + * + * Example: + * @code + * { + * uint8_t encodedName[] = {0x00, 0x01, 0x00, 0x05, 'a', 'p', 'p', 'l', 'e', 0x00, 0x01, 0x00, 0x03, 'p', 'i', 'e'}; + * MetisTlvName *name = metisTlvName_Create(encodedName, sizeof(encodedName)); + * MetisTlvName *slice = metisTlvName_Slice(name, 1); + * // slice is only lci:/apple + * metisTlvName_Release(&name); + * metisTlvName_Release(&slice); + * } + * @endcode + */ +MetisTlvName *metisTlvName_Slice(const MetisTlvName *original, size_t segmentCount); + +/** + * A hash value for use in hash tables + * + * Will only be calculated once, then cached inside the MetisTlvName. + * + * @param [in] name The name to hash + * + * @retval number A hash value for use in hash tables + * + * Example: + * @code + * <#example#> + * @endcode + */ +uint32_t metisTlvName_HashCode(const MetisTlvName *name); + +/** + * Determine if two MetisTlvName instances are equal. + * + * Two MetisTlvName instances are equal if, and only if, + * both objects have a name and they are equal. + * + * The following equivalence relations on non-null `MetisTlvName` instances are maintained: + * + * * It is reflexive: for any non-null reference value x, `MetisTlvName_Equals(x, x)` + * must return true. + * + * * It is symmetric: for any non-null reference values x and y, + * `metisTlvName_Equals(x, y)` must return true if and only if + * `metisTlvName_Equals(y, x)` returns true. + * + * * It is transitive: for any non-null reference values x, y, and z, if + * `metisTlvName_Equals(x, y)` returns true and + * `metisTlvName_Equals(y, z)` returns true, + * then `metisTlvName_Equals(x, z)` must return true. + * + * * It is consistent: for any non-null reference values x and y, multiple + * invocations of `metisTlvName_Equals(x, y)` consistently return true or + * consistently return false. + * + * * For any non-null reference value x, `metisTlvName_Equals(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 (metisTlvName_Equals(a, b)) { + * // true + * } else { + * // false + * } + * } + * @endcode + */ +bool metisTlvName_Equals(const MetisTlvName *a, const MetisTlvName *b); + +/** + * Compares two names and returns their ordering + * + * If |A| < |B| or |A|=|B| & A < B, return -1 + * If A = B, return 0 + * If |A| > |B| or |A|=|B| & A > B, return +1 + * + * @param [in] a The first name + * @param [in] b The second name + * + * @retval negative If |A| < |B| or |A|=|B| & A < B + * @retval zero If A = B, return 0 + * @retval positive If |A| > |B| or |A|=|B| & A > B + * + * Example: + * @code + * <#example#> + * @endcode + */ +int metisTlvName_Compare(const MetisTlvName *a, const MetisTlvName *b); + +/** + * @function metsName_StartsWith + * @abstract Tests if name starts with prefix + * @discussion + * Byte-by-byte prefix comparison + * + * @return True if the name is equal to or begins with prefix + */ + +/** + * Determines if name begins with prefix + * + * Returns true if the given name begins or equals the given prefix. + * + * @param [in] name The name to test (must be non-null) + * @param [in] prefix The prefix to check the name against (must be non-null) + * + * @retval true name is equal to or prefixed by prefix + * @retval false prefix is unreleated to name + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisTlvName_StartsWith(const MetisTlvName *name, const MetisTlvName *prefix); + +/** + * The number of name segments in the name + * + * <#Paragraphs Of Explanation#> + * + * @param [in] name An allocated MetisTlvName + * + * @retval number The number of name segments + * + * Example: + * @code + * { + * uint8_t encodedName[] = {0x00, 0x01, 0x00, 0x05, 'a', 'p', 'p', 'l', 'e', 0x00, 0x01, 0x00, 0x03, 'p', 'i', 'e'}; + * MetisTlvName *name = metisTlvName_Create(encodedName, sizeof(encodedName)); + * size_t count = metisTlvName_SegmentCount(name); + * // count = 2 + * metisTlvName_Release(&name); + * } + * @endcode + */ +size_t metisTlvName_SegmentCount(const MetisTlvName *name); + +/** + * Converts a MetisTlvName to a CCNxName + * + * The new name will use its own memory unrelated to the MetisTlvName. + * You must release the reference to the CCNxName when done with it. + * + * @param [in] name An allocated MetisTlvName + * + * @retval non-null An allocated CCNxName + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +CCNxName *metisTlvName_ToCCNxName(const MetisTlvName *name); +#endif // Metis_metis_tlv_Name_h diff --git a/metis/ccnx/forwarder/metis/tlv/metis_TlvNameCodec.c b/metis/ccnx/forwarder/metis/tlv/metis_TlvNameCodec.c new file mode 100644 index 00000000..f3847e6a --- /dev/null +++ b/metis/ccnx/forwarder/metis/tlv/metis_TlvNameCodec.c @@ -0,0 +1,64 @@ +/* + * 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 <arpa/inet.h> + +#include <parc/algol/parc_Memory.h> +#include <LongBow/runtime.h> + +#include "metis_TlvNameCodec.h" +#include "metis_Tlv.h" + +CCNxName * +metisTlvNameCodec_Decode(uint8_t *buffer, size_t offset, size_t end) +{ + assertNotNull(buffer, "Parameter buffer must be non-null"); + assertTrue(end >= offset, "Buffer must be at least 4 bytes"); + + CCNxName *ccnxName = ccnxName_Create(); + + while (offset < end) { + trapIllegalValueIf(end < offset + 4, "Buffer must be at least 4 bytes") + { + ccnxName_Release(&ccnxName); + } + + MetisTlvType *tlv = (MetisTlvType *) (buffer + offset); + uint16_t type = htons(tlv->type); + uint16_t length = htons(tlv->length); + + offset += sizeof(MetisTlvType); + + trapIllegalValueIf(offset + length > end, "name component extends beyond end of name") + { + ccnxName_Release(&ccnxName); + } + + PARCBuffer *nameValue = parcBuffer_Wrap(&buffer[offset], length, 0, length); + CCNxNameSegment *segment = ccnxNameSegment_CreateTypeValue(type, nameValue); + parcBuffer_Release(&nameValue); + + ccnxName_Append(ccnxName, segment); + ccnxNameSegment_Release(&segment); + + offset += length; + } + + return ccnxName; +} + diff --git a/metis/ccnx/forwarder/metis/tlv/metis_TlvNameCodec.h b/metis/ccnx/forwarder/metis/tlv/metis_TlvNameCodec.h new file mode 100644 index 00000000..c047f50d --- /dev/null +++ b/metis/ccnx/forwarder/metis/tlv/metis_TlvNameCodec.h @@ -0,0 +1,65 @@ +/* + * 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 tlv_NameCodec.h + * @brief Encode/Decode a Name tlv + * + * Encodes a CCNxName to a Name TLV container plus one NameComponent TLV container + * per name segment. + * + * Decodes a buffer as a Name TLV that contains one NameComponent TLV per name segment. + * + * + * Example: + * @code + * <#example#> + * @endcode + */ +#ifndef Metis_metis_tlv_NameCodec_h +#define Metis_metis_tlv_NameCodec_h + +#include <ccnx/common/ccnx_Name.h> + +/** + * Decodes a byte array as the segments of a Name. + * + * The (buffer + offset) should point to the beginning of the first NameSegment. + * + * The length (end - offset) may be 0 length, in which case an empty name is returned. + * Otherwise, it must be at least 4 bytes long. + * + * @param [in] buffer The byte array + * @param [in] offset The starting location of the Name + * @param [in] end The location just past the end of the name + * + * @return non-null the Name + * + * Example: + * @code + * { + * // offset 0 1 2 3 4 5 6 7 8 9 10 11 12 13 + * |-- type --|-- length --|-- type --|-- length --| ----- value -----| + * uint8_t buffer[] = { 0xFF, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 0x04, 'a', 'b', 'c', 'd', 0xFF }; + * + * // skip the two 0xFF bytes + * // name = "lci:/%02=abcd" + * CCNxName * name = tlvName_Decode(buffer, 5, 13); + * } + * @endcode + * + */ +CCNxName *metisTlvNameCodec_Decode(uint8_t *buffer, size_t offset, size_t end); +#endif // Metis_metis_tlv_NameCodec_h diff --git a/metis/ccnx/forwarder/metis/tlv/metis_TlvOps.h b/metis/ccnx/forwarder/metis/tlv/metis_TlvOps.h new file mode 100644 index 00000000..35dae490 --- /dev/null +++ b/metis/ccnx/forwarder/metis/tlv/metis_TlvOps.h @@ -0,0 +1,216 @@ +/* + * 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_TlvOps.h + * @brief The API for TLV schemas + * + * Each TLV schema must implement this API + * + */ + +#ifndef Metis_metis_TlvOps_h +#define Metis_metis_TlvOps_h + +#include <stdbool.h> +#include <parc/algol/parc_Buffer.h> +#include <parc/security/parc_CryptoHash.h> +#include <ccnx/forwarder/metis/tlv/metis_TlvSkeleton.h> +#include <ccnx/api/control/cpi_ControlMessage.h> + +typedef struct metis_tlv_ops { + /** + * Fills in the packet TLV skeleton + * + * The skeleton must have been initialized with the correct parser and packet buffer. + * + * @param [in] skeleton An allocated MetisTlvSkeleton to fill in + * + * @retval true Good parse + * @retval false Error + * + * Example: + * @code + * <#example#> + * @endcode + */ + bool (*parse)(MetisTlvSkeleton *skeleton); + + /** + * Computes the ContentObjectHash over a packet + * + * <#Paragraphs Of Explanation#> + * + * @param [in] packet Packet memory, pointing to byte 0 of the fixed header + * + * @return non-null The sha256 hash + * @return null An error (or not a content object) + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ + PARCCryptoHash * (*computeContentObjectHash)(const uint8_t *packet); + + /** + * @function metisTlv_EncodeCPI + * @abstract Encodes a CPI control message in TLV format + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return An allocated message, must call <code>metisMessage_Destroy()</code> on it. + */ + PARCBuffer *(*encodeControlPlaneInformation)(const CCNxControl *cpiControlMessage); + + /** + * Returns the total header length based on the Fixed Header + * + * The length may be 0 for an unsupported FixedHeader version or other error. + * + * @param [in] packet Packet memory pointing to byte 0 of the Fixed Header + * + * @retval number Total header length + * + * Example: + * @code + * <#example#> + * @endcode + */ + size_t (*totalHeaderLength)(const uint8_t *packet); + + /** + * Returns the total packet length based on the Fixed Header + * + * The length may be 0 for an unsupported FixedHeader version or other error. + * + * @param [in] packet Packet memory pointing to byte 0 of the Fixed Header + * + * @retval number Total packet length + * + * Example: + * @code + * <#example#> + * @endcode + */ + size_t (*totalPacketLength)(const uint8_t *packet); + + /** + * Returns the length of the fixed header + * + * The length may be 0 for an unsupported FixedHeader version or other error. + * + * @param [in] packet Packet memory pointing to byte 0 of the Fixed Header + * + * @retval number Total packet length + * + * Example: + * @code + * <#example#> + * @endcode + */ + size_t (*fixedHeaderLength)(const uint8_t *packet); + + /** + * Determines if the FixedHeader PacketType is Intereest + * + * <#Paragraphs Of Explanation#> + * + * @param [in] packet Packet memory, pointing to byte 0 of fixed header + * + * @retval true PacketType is Interest + * @retval false PacketType is not Interest + * + * Example: + * @code + * <#example#> + * @endcode + */ + bool (*isPacketTypeInterest)(const uint8_t *packet); + + /** + * Determines if the FixedHeader PacketType is ContentObject + * + * <#Paragraphs Of Explanation#> + * + * @param [in] packet Packet memory, pointing to byte 0 of fixed header + * + * @retval true PacketType is ContentObject + * @retval false PacketType is not ContentObject + * + * Example: + * @code + * <#example#> + * @endcode + */ + bool (*isPacketTypeContentObject)(const uint8_t *packet); + + /** + * Determines if the FixedHeader PacketType is InterestReturn + * + * <#Paragraphs Of Explanation#> + * + * @param [in] packet Packet memory, pointing to byte 0 of fixed header + * + * @retval true PacketType is InterestReturn + * @retval false PacketType is not InterestReturn + * + * Example: + * @code + * <#example#> + * @endcode + */ + bool (*isPacketTypeInterestReturn)(const uint8_t *packet); + + /** + * Determines if the FixedHeader PacketType is Control + * + * <#Paragraphs Of Explanation#> + * + * @param [in] packet Packet memory, pointing to byte 0 of fixed header + * + * @retval true PacketType is Control + * @retval false PacketType is not Control + * + * Example: + * @code + * <#example#> + * @endcode + */ + bool (*isPacketTypeControl)(const uint8_t *packet); + + /** + * Determines if the FixedHeader PacketType is Hop By Hop Fragment + * + * <#Paragraphs Of Explanation#> + * + * @param [in] packet Packet memory, pointing to byte 0 of fixed header + * + * @retval true PacketType is Hop By Hop Fragment + * @retval false PacketType is not Hop By Hop Fragment + * + * Example: + * @code + * <#example#> + * @endcode + */ + bool (*isPacketTypeHopByHopFragment)(const uint8_t *packet); +} MetisTlvOps; + + +#endif // Metis_metis_TlvOps_h diff --git a/metis/ccnx/forwarder/metis/tlv/metis_TlvSchemaV0.c b/metis/ccnx/forwarder/metis/tlv/metis_TlvSchemaV0.c new file mode 100644 index 00000000..e4bba4c8 --- /dev/null +++ b/metis/ccnx/forwarder/metis/tlv/metis_TlvSchemaV0.c @@ -0,0 +1,419 @@ +/* + * 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. + */ + +/** + */ + +/** + * THIS IS A DEPRECATED CLASS. V0 IS NO LONGER IN USE. + */ + +#include <config.h> + +#include <LongBow/runtime.h> + +#include <stdio.h> +#include <string.h> +#include <arpa/inet.h> + +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> +#include <ccnx/forwarder/metis/tlv/metis_TlvExtent.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/security/parc_CryptoHasher.h> + +#include <ccnx/forwarder/metis/tlv/metis_TlvSchemaV0.h> + +typedef struct __attribute__ ((__packed__)) metis_tlv_fixed_header { + uint8_t version; + uint8_t packetType; + uint16_t payloadLength; + uint16_t reserved; + uint16_t headerLength; +} _MetisTlvFixedHeaderV0; + +#define FIXED_HEADER_LEN 8 + + +#define TotalPacketLength(fhPtr) (htons((fhPtr)->payloadLength) + htons((fhPtr)->headerLength) + FIXED_HEADER_LEN) + + +#define METIS_PACKET_TYPE_INTEREST 0x01 +#define METIS_PACKET_TYPE_CONTENT 0x02 + +// The message type for a Metis control packet +#define METIS_PACKET_TYPE_CONTROL 0xA4 + +// ----------------------------- +// in host byte order + +#define T_NAME 0x0000 + +#define T_HOPLIMIT 0x0002 +#define T_INTFRAG 0x0003 +#define T_OBJFRAG 0x0004 + +// inside interest +#define T_KEYID 0x0001 +#define T_OBJHASH 0x0002 +#define T_SCOPE 0x0003 +#define T_INTLIFE 0x0005 + +// inside an object +#define T_NAMEAUTH 0x0002 +#define T_CONTENTS 0x0004 +#define T_SIGBLOCK 0x0005 +#define T_SIGBITS 0x000E + +// inside a CPI +#define T_CPI 0xBEEF + +// ----------------------------- +// Internal API + +static void +_parsePerHopV0(const uint8_t *packet, size_t offset, size_t endHeaders, MetisTlvSkeleton *skeleton) +{ + int foundCount = 0; + const size_t tl_length = 4; + + // we only parse to the end of the per-hop headers or until we've found + // the 1 header we want is hoplimit. Others ignored. + while (offset < endHeaders && foundCount < 1) { + MetisTlvType *tlv = (MetisTlvType *) (packet + offset); + uint16_t type = htons(tlv->type); + uint16_t v_length = htons(tlv->length); + + // move past the TL header + offset += tl_length; + + switch (type) { + case T_HOPLIMIT: + metisTlvSkeleton_SetHopLimit(skeleton, offset, v_length); + foundCount++; + break; + + default: + break; + } + + offset += v_length; + } +} + +static void +_parseNameAuth(const uint8_t *packet, size_t offset, size_t endSection, MetisTlvSkeleton *skeleton) +{ + const size_t tl_length = 4; + + while (offset < endSection) { + MetisTlvType *tlv = (MetisTlvType *) (packet + offset); + uint16_t type = htons(tlv->type); + uint16_t v_length = htons(tlv->length); + + // move past the TL header + offset += tl_length; + + switch (type) { + case T_KEYID: + metisTlvSkeleton_SetKeyId(skeleton, offset, v_length); + return; + + default: + break; + } + + offset += v_length; + } +} + +static void +_parseObjectV0(const uint8_t *packet, size_t offset, size_t endMessage, MetisTlvSkeleton *skeleton) +{ + int foundCount = 0; + const size_t tl_length = 4; + + // skip the opending content object TLV + offset += 4; + + // parse to the end or until we find the two things we need (name, keyid) + while (offset < endMessage && foundCount < 2) { + MetisTlvType *tlv = (MetisTlvType *) (packet + offset); + uint16_t type = htons(tlv->type); + uint16_t v_length = htons(tlv->length); + + // move past the TL header + offset += tl_length; + + switch (type) { + case T_NAME: + metisTlvSkeleton_SetName(skeleton, offset, v_length); + foundCount++; + break; + + case T_NAMEAUTH: + _parseNameAuth(packet, offset, offset + v_length, skeleton); + foundCount++; + break; + + default: + break; + } + + offset += v_length; + } +} + +static void +_parseInterestV0(const uint8_t *packet, size_t offset, size_t endMessage, MetisTlvSkeleton *skeleton) +{ + int foundCount = 0; + const size_t tl_length = sizeof(MetisTlvType); + + // skip the Interest wrapper + offset += 4; + + // parse to the end or until we find all 5 things (name, keyid, objecthash, scope, interest lifetime) + while (offset < endMessage && foundCount < 5) { + MetisTlvType *tlv = (MetisTlvType *) (packet + offset); + uint16_t type = htons(tlv->type); + uint16_t v_length = htons(tlv->length); + + // skip past the TLV header + offset += tl_length; + + switch (type) { + case T_NAME: + metisTlvSkeleton_SetName(skeleton, offset, v_length); + foundCount++; + break; + + case T_KEYID: + metisTlvSkeleton_SetKeyId(skeleton, offset, v_length); + foundCount++; + break; + + case T_OBJHASH: + metisTlvSkeleton_SetObjectHash(skeleton, offset, v_length); + foundCount++; + break; + + case T_INTLIFE: + metisTlvSkeleton_SetInterestLifetime(skeleton, offset, v_length); + foundCount++; + break; + + default: + break; + } + + offset += v_length; + } +} + + +static void +_parseControlPlaneInterface(const uint8_t *packet, size_t offset, size_t endMessage, MetisTlvSkeleton *skeleton) +{ + int foundCount = 0; + const size_t tl_length = 4; + + // parse to the end or until we find all 5 things (name, keyid, objecthash, scope, interest lifetime) + while (offset < endMessage && foundCount < 1) { + MetisTlvType *tlv = (MetisTlvType *) (packet + offset); + uint16_t type = htons(tlv->type); + uint16_t v_length = htons(tlv->length); + + // skip past the TLV header + offset += tl_length; + + switch (type) { + case T_CPI: + metisTlvSkeleton_SetCPI(skeleton, offset, v_length); + foundCount++; + break; + + default: + break; + } + + offset += v_length; + } +} + +static PARCCryptoHash * +_computeHash(const uint8_t *packet, size_t offset, size_t endMessage) +{ + PARCCryptoHasher *hasher = parcCryptoHasher_Create(PARCCryptoHashType_SHA256); + parcCryptoHasher_Init(hasher); + parcCryptoHasher_UpdateBytes(hasher, packet + offset, endMessage - offset); + PARCCryptoHash *hash = parcCryptoHasher_Finalize(hasher); + parcCryptoHasher_Release(&hasher); + return hash; +} + +// ================== +// TlvOps functions + +static PARCBuffer * +_encodeControlPlaneInformation(const CCNxControl *cpiControlMessage) +{ + PARCJSON *json = ccnxControl_GetJson(cpiControlMessage); + char *str = parcJSON_ToCompactString(json); + + // include +1 because we need the NULL byte + size_t len = strlen(str) + 1; + + size_t packetLength = sizeof(_MetisTlvFixedHeaderV0) + sizeof(MetisTlvType) + len; + PARCBuffer *packet = parcBuffer_Allocate(packetLength); + + _MetisTlvFixedHeaderV0 hdr; + memset(&hdr, 0, sizeof(hdr)); + hdr.version = 0; + hdr.packetType = METIS_PACKET_TYPE_CONTROL; + hdr.payloadLength = htons(len + sizeof(MetisTlvType)); + + parcBuffer_PutArray(packet, sizeof(hdr), (uint8_t *) &hdr); + + MetisTlvType tlv = { .type = htons(T_CPI), .length = htons(len) }; + parcBuffer_PutArray(packet, sizeof(tlv), (uint8_t *) &tlv); + + parcBuffer_PutArray(packet, len, (uint8_t *) str); + + parcMemory_Deallocate((void **) &str); + return parcBuffer_Flip(packet); +} + + +static PARCCryptoHash * +_computeContentObjectHash(const uint8_t *packet) +{ + assertNotNull(packet, "Parameter packet must be non-null"); + + _MetisTlvFixedHeaderV0 *hdr = (_MetisTlvFixedHeaderV0 *) packet; + if (hdr->packetType == METIS_PACKET_TYPE_CONTENT) { + size_t headerLength = htons(hdr->headerLength); + size_t endHeaders = FIXED_HEADER_LEN + headerLength; + size_t endPacket = TotalPacketLength(hdr); + + return _computeHash(packet, endHeaders, endPacket); + } + + return NULL; +} + +static bool +_isPacketTypeInterest(const uint8_t *packet) +{ + _MetisTlvFixedHeaderV0 *hdr = (_MetisTlvFixedHeaderV0 *) packet; + return (hdr->packetType == METIS_PACKET_TYPE_INTEREST); +} + +static bool +_isPacketTypeInterestReturn(const uint8_t *packet) +{ + return false; +} + +static bool +_isPacketTypeContentObject(const uint8_t *packet) +{ + _MetisTlvFixedHeaderV0 *hdr = (_MetisTlvFixedHeaderV0 *) packet; + return (hdr->packetType == METIS_PACKET_TYPE_CONTENT); +} + +static bool +_isPacketTypeControl(const uint8_t *packet) +{ + _MetisTlvFixedHeaderV0 *hdr = (_MetisTlvFixedHeaderV0 *) packet; + return (hdr->packetType == METIS_PACKET_TYPE_CONTROL); +} + +static bool +_isPacketTypeHopByHopFragment(const uint8_t *packet) +{ + // does not exist for version 0 packets + return false; +} + +static size_t +_fixedHeaderLength(const uint8_t *packet) +{ + return sizeof(_MetisTlvFixedHeaderV0); +} + +static size_t +_totalHeaderLength(const uint8_t *packet) +{ + _MetisTlvFixedHeaderV0 *hdr = (_MetisTlvFixedHeaderV0 *) packet; + + return htons(hdr->headerLength) + sizeof(_MetisTlvFixedHeaderV0); +} + +static size_t +_totalPacketLength(const uint8_t *packet) +{ + _MetisTlvFixedHeaderV0 *hdr = (_MetisTlvFixedHeaderV0 *) packet; + + return TotalPacketLength(hdr); +} + +static bool +_parse(MetisTlvSkeleton *skeleton) +{ + _MetisTlvFixedHeaderV0 *hdr = (_MetisTlvFixedHeaderV0 *) metisTlvSkeleton_GetPacket(skeleton); + size_t headerLength = htons(hdr->headerLength); + + size_t endHeaders = FIXED_HEADER_LEN + headerLength; + size_t endPacket = TotalPacketLength(hdr); + + trapUnexpectedStateIf(hdr->version != 0, "Version not 0"); + + switch (hdr->packetType) { + case METIS_PACKET_TYPE_INTEREST: + _parsePerHopV0(metisTlvSkeleton_GetPacket(skeleton), FIXED_HEADER_LEN, endHeaders, skeleton); + _parseInterestV0(metisTlvSkeleton_GetPacket(skeleton), endHeaders, endPacket, skeleton); + break; + + case METIS_PACKET_TYPE_CONTENT: + _parsePerHopV0(metisTlvSkeleton_GetPacket(skeleton), FIXED_HEADER_LEN, endHeaders, skeleton); + _parseObjectV0(metisTlvSkeleton_GetPacket(skeleton), endHeaders, endPacket, skeleton); + break; + + case METIS_PACKET_TYPE_CONTROL: + _parseControlPlaneInterface(metisTlvSkeleton_GetPacket(skeleton), endHeaders, endPacket, skeleton); + break; + + default: + break; + } + + return true; +} + +const MetisTlvOps MetisTlvSchemaV0_Ops = { + .parse = _parse, + .computeContentObjectHash = _computeContentObjectHash, + .encodeControlPlaneInformation = _encodeControlPlaneInformation, + .fixedHeaderLength = _fixedHeaderLength, + .totalHeaderLength = _totalHeaderLength, + .totalPacketLength = _totalPacketLength, + .isPacketTypeInterest = _isPacketTypeInterest, + .isPacketTypeContentObject = _isPacketTypeContentObject, + .isPacketTypeInterestReturn = _isPacketTypeInterestReturn, + .isPacketTypeControl = _isPacketTypeControl, + .isPacketTypeHopByHopFragment = _isPacketTypeHopByHopFragment, +}; + diff --git a/metis/ccnx/forwarder/metis/tlv/metis_TlvSchemaV0.h b/metis/ccnx/forwarder/metis/tlv/metis_TlvSchemaV0.h new file mode 100644 index 00000000..cbbbbe83 --- /dev/null +++ b/metis/ccnx/forwarder/metis/tlv/metis_TlvSchemaV0.h @@ -0,0 +1,51 @@ +/* + * 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_TlvSchemaV0.h + * @brief Define the TLV Operations for the V0 schema + * + * Defines the operations for parsing a V0 schema name + * + * THIS IS A DEPRECATED CLASS. V0 IS NO LONGER IN USE. + * + * + */ + +#ifndef Metis_metis_TlvSchemaV0 +#define Metis_metis_TlvSchemaV0 + +#include <ccnx/forwarder/metis/tlv/metis_TlvOps.h> + +/** + * Defines the TLV Operations for the V0 Schema + * + * Example: + * @code + * { + * uint8_t *packet = // read a packet from the network + * MetisTlvSkeleton skeleton; + * bool success = MetisTlvSchemaV0_Ops.parseSkeleton(packet, &skeleton); + * if (success) { + * if (MetisTlvSchemaV0_Ops.isPacketTypeInterest(packet)) { + * // parse interest + * } + * } + * @endcode + */ +extern const MetisTlvOps MetisTlvSchemaV0_Ops; + +#endif // Metis_metis_TlvSchemaV0 + diff --git a/metis/ccnx/forwarder/metis/tlv/metis_TlvSchemaV1.c b/metis/ccnx/forwarder/metis/tlv/metis_TlvSchemaV1.c new file mode 100644 index 00000000..bf3695f2 --- /dev/null +++ b/metis/ccnx/forwarder/metis/tlv/metis_TlvSchemaV1.c @@ -0,0 +1,567 @@ +/* + * 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 <LongBow/runtime.h> + +#include <stdio.h> +#include <string.h> +#include <arpa/inet.h> + +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> +#include <ccnx/forwarder/metis/tlv/metis_TlvExtent.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/security/parc_CryptoHasher.h> + +#include <ccnx/forwarder/metis/tlv/metis_TlvSchemaV1.h> + +typedef struct __attribute__ ((__packed__)) metis_tlv_fixed_header { + uint8_t version; + uint8_t packetType; + uint16_t packetLength; + uint8_t interestHopLimit; + uint8_t returnCode; + uint8_t flags; + uint8_t headerLength; +} _MetisTlvFixedHeaderV1; + +#define METIS_PACKET_TYPE_INTEREST 0 +#define METIS_PACKET_TYPE_CONTENT 1 +#define METIS_PACKET_TYPE_INTERESTRETURN 2 +#define METIS_PACKET_TYPE_HOPFRAG 4 +#define METIS_PACKET_TYPE_CONTROL 0xA4 + +// ----------------------------- +// in host byte order + +#define T_NAME 0x0000 + +// perhop headers +#define T_INTLIFE 0x0001 +#define T_CACHETIME 0x0002 +#define T_PATHLABEL 0x0003 +#define T_FLOW 0x0005 + +// Top-level TLVs +#define T_INTEREST 0x0001 +#define T_OBJECT 0x0002 +#define T_VALALG 0x0003 +#define T_VALPAYLOAD 0x0004 +#define T_HOPFRAG_PAYLOAD 0x0005 +#define T_MANIFEST 0x0006 + +// inside interest +#define T_KEYIDRES 0x0002 +#define T_OBJHASHRES 0x0003 + +// inside a content object +#define T_EXPIRYTIME 0x0006 + +// ValidationAlg + +// these are the algorithms we need a KEYID for +#define T_RSA_SHA256 0x0006 +#define T_EC_SECP_256K1 0x0007 +#define T_EC_SECP_384R1 0x0008 + +#define T_KEYID 0x0009 +#define T_PUBLICKEY 0x000B +#define T_CERT 0x000C + +// inside a CPI +#define T_CPI 0xBEEF + + +// ----------------------------- +// Internal API + +/** + * Parse the per-hop headers. + * + * Will return the absolute offset of the next byte to parse (i.e. 'endHeaders') + * + * @param [in] packet The packet buffer + * @param [in] offset The first byte to begin parsing at + * @param [in] endMessage The ceiling of bytes to parse + * @param [in] skeleton The structure to fill in + */ +static void +_parsePerHopV1(const uint8_t *packet, size_t offset, size_t endHeaders, MetisTlvSkeleton *skeleton) +{ + const size_t tl_length = sizeof(MetisTlvType); + + // we only parse to the end of the per-hop headers or until we've found + // the 2 headers we want (hoplimit, fragmentation header) + while (offset + sizeof(MetisTlvType) < endHeaders) { + const MetisTlvType *tlv = (MetisTlvType *) (packet + offset); + const uint16_t type = htons(tlv->type); + const uint16_t v_length = htons(tlv->length); + + // move past the TL header + offset += tl_length; + + size_t endSubSection = offset + v_length; + if (endSubSection <= endHeaders) { + switch (type) { + case T_INTLIFE: + metisTlvSkeleton_SetInterestLifetime(skeleton, offset, v_length); + break; + + // should verify that we dont have both INTFRAG and OBJFRAG + case T_CACHETIME: + metisTlvSkeleton_SetCacheTimeHeader(skeleton, offset, v_length); + break; + + case T_PATHLABEL: + metisTlvSkeleton_SetPathLabel(skeleton, offset, v_length); + break; + + default: + break; + } + } + + offset = endSubSection; + } +} + +static void +_parseSignatureParameters(const uint8_t *packet, size_t offset, size_t endSection, struct tlv_skeleton *skeleton) +{ + // Scan the section for KeyId, and optional Certificate or PublicKey. + while (offset + sizeof(MetisTlvType) < endSection) { + const MetisTlvType *tlv = (MetisTlvType *) (packet + offset); + const uint16_t type = htons(tlv->type); + const uint16_t v_length = htons(tlv->length); + + // move past the TL header + offset += sizeof(MetisTlvType); + + size_t endSubSection = offset + v_length; + if (endSubSection <= endSection) { + switch (type) { + case T_KEYID: + metisTlvSkeleton_SetKeyId(skeleton, offset, v_length); + break; + + case T_CERT: + metisTlvSkeleton_SetCertificate(skeleton, offset, v_length); + break; + + case T_PUBLICKEY: + metisTlvSkeleton_SetPublicKey(skeleton, offset, v_length); + + default: + break; + } + } + offset += v_length; + } +} + +static void +_parseValidationType(const uint8_t *packet, size_t offset, size_t endSection, struct tlv_skeleton *skeleton) +{ + if (offset + sizeof(MetisTlvType) < endSection) { + const MetisTlvType *tlv = (MetisTlvType *) (packet + offset); + const uint16_t type = htons(tlv->type); + const uint16_t v_length = htons(tlv->length); + + // move past the TL header + offset += sizeof(MetisTlvType); + + size_t endSubSection = offset + v_length; + if (endSubSection <= endSection) { + switch (type) { + // These are the Validation Algorithms that have a usable KeyId + case T_EC_SECP_256K1: // fallthrough + case T_EC_SECP_384R1: // fallthrough + case T_RSA_SHA256: + _parseSignatureParameters(packet, offset, endSubSection, skeleton); + return; + + default: + break; + } + } + } +} + +static size_t +_parseValidationAlg(const uint8_t *packet, size_t offset, size_t endMessage, struct tlv_skeleton *skeleton) +{ + size_t endSection = endMessage; + + if (offset + sizeof(MetisTlvType) < endMessage) { + const MetisTlvType *tlv = (MetisTlvType *) (packet + offset); + const uint16_t type = htons(tlv->type); + const uint16_t v_length = htons(tlv->length); + + offset += sizeof(MetisTlvType); + endSection = offset + v_length; + + // make sure we don't have container overrun + if (endSection <= endMessage && type == T_VALALG) { + _parseValidationType(packet, offset, endSection, skeleton); + } + } + + return endSection; +} + +/** + * Parse the "value" of a T_OBJECT + * + * 'offset' should point to the first byte of the "value" of the T_OBJECT container + * + * @param [in] packet The packet buffer + * @param [in] offset The first byte to begin parsing at + * @param [in] endSection The ceiling of bytes to parse + * @param [in] skeleton The structure to fill in + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +static void +_parseObjectV1(const uint8_t *packet, size_t offset, size_t endSection, MetisTlvSkeleton *skeleton) +{ + int foundCount = 0; + + // parse to the end or until we find the two things we need (name, keyid) + while (offset < endSection && foundCount < 2) { + const MetisTlvType *tlv = (MetisTlvType *) (packet + offset); + const uint16_t type = htons(tlv->type); + const uint16_t v_length = htons(tlv->length); + + // move past the TL header + offset += sizeof(MetisTlvType); + size_t endSubSection = offset + v_length; + if (endSubSection <= endSection) { + switch (type) { + case T_NAME: + metisTlvSkeleton_SetName(skeleton, offset, v_length); + foundCount++; + break; + + case T_EXPIRYTIME: + metisTlvSkeleton_SetExpiryTime(skeleton, offset, v_length); + foundCount++; + break; + + default: + break; + } + } + + offset = endSubSection; + } +} + +/** + * Parse the "value" of a T_INTEREST + * + * 'offset' should point to the first byte of the "value" of the T_INTEREST container + * + * @param [in] packet The packet buffer + * @param [in] offset The first byte to begin parsing at + * @param [in] endSection The ceiling of bytes to parse + * @param [in] skeleton The structure to fill in + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +static void +_parseInterestV1(const uint8_t *packet, size_t offset, size_t endSection, struct tlv_skeleton *skeleton) +{ + int foundCount = 0; + + // parse to the end or until we find all 3 things (name, keyid, objecthash) + while (offset < endSection && foundCount < 3) { + const MetisTlvType *tlv = (MetisTlvType *) (packet + offset); + const uint16_t type = htons(tlv->type); + const uint16_t v_length = htons(tlv->length); + + // skip past the TLV header + offset += sizeof(MetisTlvType); + size_t endSubSection = offset + v_length; + if (endSubSection <= endSection) { + switch (type) { + case T_NAME: + metisTlvSkeleton_SetName(skeleton, offset, v_length); + foundCount++; + break; + + case T_KEYIDRES: + metisTlvSkeleton_SetKeyId(skeleton, offset, v_length); + foundCount++; + break; + + case T_OBJHASHRES: + metisTlvSkeleton_SetObjectHash(skeleton, offset, v_length); + foundCount++; + break; + + default: + break; + } + } + + offset = endSubSection; + } +} + +/** + * Parses the message body + * + * 'offset' should point to the first byte of the T_INTEREST, T_CONTENTOBJECT, etc. + * + * @param [<#in#> | <#out#> | <#in,out#>] <#name#> <#description#> + * + * @return number The absolute byte offset of the next location to parse + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +static size_t +_parseMessage(const uint8_t *packet, size_t offset, size_t endMessage, struct tlv_skeleton *skeleton) +{ + size_t endSection = endMessage; + + if (offset + sizeof(MetisTlvType) < endMessage) { + const MetisTlvType *tlv = (MetisTlvType *) (packet + offset); + const uint16_t type = htons(tlv->type); + const uint16_t v_length = htons(tlv->length); + + offset += sizeof(MetisTlvType); + size_t endSubSection = offset + v_length; + + // make sure we don't have container overrun + if (endSubSection <= endMessage) { + switch (type) { + case T_INTEREST: + _parseInterestV1(packet, offset, endSubSection, skeleton); + break; + + case T_MANIFEST: + case T_OBJECT: + _parseObjectV1(packet, offset, endSubSection, skeleton); + break; + + case T_CPI: + // There is nothing nested here, its just the value + metisTlvSkeleton_SetCPI(skeleton, offset, v_length); + break; + + case T_HOPFRAG_PAYLOAD: + // There is nothing nested here, its just the value + metisTlvSkeleton_SetFragmentPayload(skeleton, offset, v_length); + break; + + default: + break; + } + + endSection = endSubSection; + } + } + return endSection; +} + +static PARCCryptoHash * +_computeHash(const uint8_t *packet, size_t offset, size_t endMessage) +{ + PARCCryptoHasher *hasher = parcCryptoHasher_Create(PARCCryptoHashType_SHA256); + parcCryptoHasher_Init(hasher); + parcCryptoHasher_UpdateBytes(hasher, packet + offset, endMessage - offset); + PARCCryptoHash *hash = parcCryptoHasher_Finalize(hasher); + parcCryptoHasher_Release(&hasher); + return hash; +} + +// ================== +// TlvOps functions + +static PARCBuffer * +_encodeControlPlaneInformation(const CCNxControl *cpiControlMessage) +{ + PARCJSON *json = ccnxControl_GetJson(cpiControlMessage); + char *str = parcJSON_ToCompactString(json); + + // include +1 because we need the NULL byte + size_t len = strlen(str) + 1; + + size_t packetLength = sizeof(_MetisTlvFixedHeaderV1) + sizeof(MetisTlvType) + len; + PARCBuffer *packet = parcBuffer_Allocate(packetLength); + + _MetisTlvFixedHeaderV1 hdr; + memset(&hdr, 0, sizeof(hdr)); + hdr.version = 1; + hdr.packetType = METIS_PACKET_TYPE_CONTROL; + hdr.packetLength = htons(packetLength); + hdr.headerLength = 8; + + parcBuffer_PutArray(packet, sizeof(hdr), (uint8_t *) &hdr); + + MetisTlvType tlv = { .type = htons(T_CPI), .length = htons(len) }; + parcBuffer_PutArray(packet, sizeof(tlv), (uint8_t *) &tlv); + + parcBuffer_PutArray(packet, len, (uint8_t *) str); + + parcMemory_Deallocate((void **) &str); + return parcBuffer_Flip(packet); +} + + +static bool +_isPacketTypeInterest(const uint8_t *packet) +{ + _MetisTlvFixedHeaderV1 *hdr = (_MetisTlvFixedHeaderV1 *) packet; + return (hdr->packetType == METIS_PACKET_TYPE_INTEREST); +} + +static bool +_isPacketTypeInterestReturn(const uint8_t *packet) +{ + _MetisTlvFixedHeaderV1 *hdr = (_MetisTlvFixedHeaderV1 *) packet; + return (hdr->packetType == METIS_PACKET_TYPE_INTERESTRETURN); +} + +static bool +_isPacketTypeContentObject(const uint8_t *packet) +{ + _MetisTlvFixedHeaderV1 *hdr = (_MetisTlvFixedHeaderV1 *) packet; + return (hdr->packetType == METIS_PACKET_TYPE_CONTENT); +} + +static bool +_isPacketTypeControl(const uint8_t *packet) +{ + _MetisTlvFixedHeaderV1 *hdr = (_MetisTlvFixedHeaderV1 *) packet; + return (hdr->packetType == METIS_PACKET_TYPE_CONTROL); +} + +static bool +_isPacketTypeHopByHopFragment(const uint8_t *packet) +{ + _MetisTlvFixedHeaderV1 *hdr = (_MetisTlvFixedHeaderV1 *) packet; + return (hdr->packetType == METIS_PACKET_TYPE_HOPFRAG); +} + +static size_t +_fixedHeaderLength(const uint8_t *packet) +{ + return sizeof(_MetisTlvFixedHeaderV1); +} + +static size_t +_totalHeaderLength(const uint8_t *packet) +{ + _MetisTlvFixedHeaderV1 *hdr = (_MetisTlvFixedHeaderV1 *) packet; + return hdr->headerLength; +} + +static size_t +_totalPacketLength(const uint8_t *packet) +{ + _MetisTlvFixedHeaderV1 *hdr = (_MetisTlvFixedHeaderV1 *) packet; + return htons(hdr->packetLength); +} + +static PARCCryptoHash * +_computeContentObjectHash(const uint8_t *packet) +{ + assertNotNull(packet, "Parameter packet must be non-null"); + _MetisTlvFixedHeaderV1 *hdr = (_MetisTlvFixedHeaderV1 *) packet; + if (hdr->packetType == METIS_PACKET_TYPE_CONTENT) { + const size_t endHeaders = _totalHeaderLength(packet); + const size_t endPacket = _totalPacketLength(packet); + return _computeHash(packet, endHeaders, endPacket); + } + + return NULL; +} + + +static bool +_goodPacketType(uint8_t packetType) +{ + bool goodType = false; + if (packetType == METIS_PACKET_TYPE_INTEREST || packetType == METIS_PACKET_TYPE_CONTENT || + packetType == METIS_PACKET_TYPE_CONTROL || packetType == METIS_PACKET_TYPE_INTERESTRETURN || + packetType == METIS_PACKET_TYPE_HOPFRAG) { + goodType = true; + } + return goodType; +} + +static bool +_parse(MetisTlvSkeleton *skeleton) +{ + bool success = false; + + _MetisTlvFixedHeaderV1 *hdr = (_MetisTlvFixedHeaderV1 *) metisTlvSkeleton_GetPacket(skeleton); + + // this function should only be called for version 1 packets + trapUnexpectedStateIf(hdr->version != 1, "Version not 1"); + + if (_goodPacketType(hdr->packetType) && hdr->headerLength >= sizeof(_MetisTlvFixedHeaderV1)) { + size_t endHeaders = hdr->headerLength; + size_t endPacket = htons(hdr->packetLength); + + if (endPacket >= endHeaders) { + if (_isPacketTypeInterest((uint8_t *) hdr)) { + metisTlvSkeleton_SetHopLimit(skeleton, 4, 1); + } + + _parsePerHopV1(metisTlvSkeleton_GetPacket(skeleton), sizeof(_MetisTlvFixedHeaderV1), endHeaders, skeleton); + size_t offset = _parseMessage(metisTlvSkeleton_GetPacket(skeleton), endHeaders, endPacket, skeleton); + _parseValidationAlg(metisTlvSkeleton_GetPacket(skeleton), offset, endPacket, skeleton); + success = true; + } + } + + return success; +} + +const MetisTlvOps MetisTlvSchemaV1_Ops = { + .parse = _parse, + .computeContentObjectHash = _computeContentObjectHash, + .encodeControlPlaneInformation = _encodeControlPlaneInformation, + .fixedHeaderLength = _fixedHeaderLength, + .totalHeaderLength = _totalHeaderLength, + .totalPacketLength = _totalPacketLength, + .isPacketTypeInterest = _isPacketTypeInterest, + .isPacketTypeContentObject = _isPacketTypeContentObject, + .isPacketTypeInterestReturn = _isPacketTypeInterestReturn, + .isPacketTypeHopByHopFragment = _isPacketTypeHopByHopFragment, + .isPacketTypeControl = _isPacketTypeControl, +}; diff --git a/metis/ccnx/forwarder/metis/tlv/metis_TlvSchemaV1.h b/metis/ccnx/forwarder/metis/tlv/metis_TlvSchemaV1.h new file mode 100644 index 00000000..628bc556 --- /dev/null +++ b/metis/ccnx/forwarder/metis/tlv/metis_TlvSchemaV1.h @@ -0,0 +1,49 @@ +/* + * 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_TlvSchemaV1.h + * @brief API to handle the v1 packet format + * + * Defines the operations for parsing a V1 schema name + * + */ + +#ifndef Metis_metis_TlvSchemaV1 +#define Metis_metis_TlvSchemaV1 + +#include <ccnx/forwarder/metis/tlv/metis_TlvOps.h> + +/** + * Defines the TLV Operations for the V1 Schema + * + * Example: + * @code + * { + * uint8_t *packet = // read a packet from the network + * MetisTlvSkeleton skeleton; + * bool success = MetisTlvSchemaV1_Ops.parseSkeleton(packet, &skeleton); + * if (success) { + * if (MetisTlvSchemaV1_Ops.isPacketTypeInterest(packet)) { + * // parse interest + * } + * } + * @endcode + */ +extern const MetisTlvOps MetisTlvSchemaV1_Ops; + + +#endif // Metis_metis_TlvSchemaV1 + diff --git a/metis/ccnx/forwarder/metis/tlv/metis_TlvSkeleton.c b/metis/ccnx/forwarder/metis/tlv/metis_TlvSkeleton.c new file mode 100644 index 00000000..ce479ce5 --- /dev/null +++ b/metis/ccnx/forwarder/metis/tlv/metis_TlvSkeleton.c @@ -0,0 +1,484 @@ +/* + * 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 <stdio.h> +#include <config.h> +#include <string.h> +#include <LongBow/runtime.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +#include <ccnx/forwarder/metis/tlv/metis_TlvSkeleton.h> + +#include <ccnx/forwarder/metis/tlv/metis_TlvSchemaV0.h> +#include <ccnx/forwarder/metis/tlv/metis_TlvSchemaV1.h> + +#define INDEX_NAME 0 +#define INDEX_KEYID 1 +#define INDEX_OBJHASH 2 +#define INDEX_HOPLIMIT 3 +#define INDEX_INTLIFETIME 4 +#define INDEX_CACHETIME 5 +#define INDEX_EXPIRYTIME 6 +#define INDEX_CPI 7 +#define INDEX_FRAGMENTPAYLOAD 8 +#define INDEX_CERTIFICATE 9 +#define INDEX_PUBKEY 10 +#define INDEX_PATHLABEL 11 + +/** + * The non-opaque representation of the MetisTlvSkeleton. + * + * IMPORTANT: if you change this structure, you must make sure the corresponding + * opaque structure in metis_TlvSkeleton.h has at least that much memory in it. + */ +typedef struct internal_skeleton { + const struct metis_tlv_ops *tlvOps; + uint8_t *packet; + MetisLogger *logger; + + MetisTlvExtent array[MetisTlvSkeleton_ArrayLength]; +} _InternalSkeleton; + +static void +_assertInvariants(const _InternalSkeleton *skeleton) +{ + assertNotNull(skeleton->tlvOps, "Invalid skeleton, does not have a schema ops"); + assertNotNull(skeleton->packet, "Invalid skeleton, does not have a packet buffer"); +} + +/** + * Initialize the skeleton memory + * + * Clears all the extents to {0, 0} and sets the tlvOps and packet members for further parsing. + * + * @param [in] skeleton The skeleton to initialize + * @param [in] tlvOps The parser operations to use + * @param [in] packet the packet buffer (points to byte "0" of the fixed header) + * + * Example: + * @code + * { + * MetisTlvSkeleton skeleton; + * _initialize(&skeleton, &MetisTlvSchemaV0_Ops, packet); + * } + * @endcode + */ +static void +_initialize(_InternalSkeleton *skeleton, const struct metis_tlv_ops *tlvOps, uint8_t *packet, MetisLogger *logger) +{ + memset(skeleton, 0, sizeof(MetisTlvSkeleton)); + skeleton->packet = packet; + skeleton->tlvOps = tlvOps; + skeleton->logger = logger; + _assertInvariants(skeleton); +} + + +bool +metisTlvSkeleton_Parse(MetisTlvSkeleton *opaque, uint8_t *packet, MetisLogger *logger) +{ + // do not assert invariants here. Parse will setup the invariants. + _InternalSkeleton *skeleton = (_InternalSkeleton *) opaque; + uint8_t version = packet[0]; + + switch (version) { + case 0: + _initialize(skeleton, &MetisTlvSchemaV0_Ops, packet, logger); + return MetisTlvSchemaV0_Ops.parse(opaque); + + case 1: + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + return MetisTlvSchemaV1_Ops.parse(opaque); + + default: + if (metisLogger_IsLoggable(logger, MetisLoggerFacility_Message, PARCLogLevel_Warning)) { + metisLogger_Log(logger, MetisLoggerFacility_Message, PARCLogLevel_Warning, __func__, + "Parsing unknown packet version %u", version); + } + break; + } + return false; +} + +// ========================================================== +// Setters + +void +metisTlvSkeleton_SetName(MetisTlvSkeleton *opaque, size_t offset, size_t length) +{ + _InternalSkeleton *skeleton = (_InternalSkeleton *) opaque; + skeleton->array[INDEX_NAME].offset = offset; + skeleton->array[INDEX_NAME].length = length; + + if (metisLogger_IsLoggable(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) { + metisLogger_Log(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__, + "Set name extent {%u, %u}", offset, length); + } +} + +void +metisTlvSkeleton_SetKeyId(MetisTlvSkeleton *opaque, size_t offset, size_t length) +{ + _InternalSkeleton *skeleton = (_InternalSkeleton *) opaque; + skeleton->array[INDEX_KEYID].offset = offset; + skeleton->array[INDEX_KEYID].length = length; + + if (metisLogger_IsLoggable(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) { + metisLogger_Log(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__, + "Set keyid extent {%u, %u}", offset, length); + } +} + +void +metisTlvSkeleton_SetCertificate(MetisTlvSkeleton *opaque, size_t offset, size_t length) +{ + _InternalSkeleton *skeleton = (_InternalSkeleton *) opaque; + skeleton->array[INDEX_CERTIFICATE].offset = offset; + skeleton->array[INDEX_CERTIFICATE].length = length; + + if (metisLogger_IsLoggable(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) { + metisLogger_Log(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__, + "Set certificate extent {%u, %u}", offset, length); + } +} + +void +metisTlvSkeleton_SetPublicKey(MetisTlvSkeleton *opaque, size_t offset, size_t length) +{ + _InternalSkeleton *skeleton = (_InternalSkeleton *) opaque; + skeleton->array[INDEX_PUBKEY].offset = offset; + skeleton->array[INDEX_PUBKEY].length = length; + + if (metisLogger_IsLoggable(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) { + metisLogger_Log(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__, + "Set public key extent {%u, %u}", offset, length); + } +} + +void +metisTlvSkeleton_SetObjectHash(MetisTlvSkeleton *opaque, size_t offset, size_t length) +{ + _InternalSkeleton *skeleton = (_InternalSkeleton *) opaque; + skeleton->array[INDEX_OBJHASH].offset = offset; + skeleton->array[INDEX_OBJHASH].length = length; + + if (metisLogger_IsLoggable(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) { + metisLogger_Log(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__, + "Set objhash extent {%u, %u}", offset, length); + } +} + +void +metisTlvSkeleton_SetHopLimit(MetisTlvSkeleton *opaque, size_t offset, size_t length) +{ + _InternalSkeleton *skeleton = (_InternalSkeleton *) opaque; + skeleton->array[INDEX_HOPLIMIT].offset = offset; + skeleton->array[INDEX_HOPLIMIT].length = length; + + if (metisLogger_IsLoggable(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) { + metisLogger_Log(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__, + "Set hoplimit extent {%u, %u}", offset, length); + } +} + +void +metisTlvSkeleton_SetInterestLifetime(MetisTlvSkeleton *opaque, size_t offset, size_t length) +{ + _InternalSkeleton *skeleton = (_InternalSkeleton *) opaque; + skeleton->array[INDEX_INTLIFETIME].offset = offset; + skeleton->array[INDEX_INTLIFETIME].length = length; + + if (metisLogger_IsLoggable(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) { + metisLogger_Log(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__, + "Set int lifetime extent {%u, %u}", offset, length); + } +} + +void +metisTlvSkeleton_SetPathLabel(MetisTlvSkeleton *opaque, size_t offset, size_t length) +{ + _InternalSkeleton *skeleton = (_InternalSkeleton *) opaque; + skeleton->array[INDEX_PATHLABEL].offset = offset; + skeleton->array[INDEX_PATHLABEL].length = length; + + if (metisLogger_IsLoggable(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) { + metisLogger_Log(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__, + "Set path label extent {%u, %u}", offset, length); + } +} + + +void +metisTlvSkeleton_SetCacheTimeHeader(MetisTlvSkeleton *opaque, size_t offset, size_t length) +{ + _InternalSkeleton *skeleton = (_InternalSkeleton *) opaque; + skeleton->array[INDEX_CACHETIME].offset = offset; + skeleton->array[INDEX_CACHETIME].length = length; + + if (metisLogger_IsLoggable(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) { + metisLogger_Log(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__, + "Set cachetime extent {%u, %u}", offset, length); + } +} + +void +metisTlvSkeleton_SetExpiryTime(MetisTlvSkeleton *opaque, size_t offset, size_t length) +{ + _InternalSkeleton *skeleton = (_InternalSkeleton *) opaque; + skeleton->array[INDEX_EXPIRYTIME].offset = offset; + skeleton->array[INDEX_EXPIRYTIME].length = length; + + if (metisLogger_IsLoggable(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) { + metisLogger_Log(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__, + "Set expirytime extent {%u, %u}", offset, length); + } +} + +void +metisTlvSkeleton_SetCPI(MetisTlvSkeleton *opaque, size_t offset, size_t length) +{ + _InternalSkeleton *skeleton = (_InternalSkeleton *) opaque; + skeleton->array[INDEX_CPI].offset = offset; + skeleton->array[INDEX_CPI].length = length; + + if (metisLogger_IsLoggable(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) { + metisLogger_Log(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__, + "Set cpi extent {%u, %u}", offset, length); + } +} + +bool +metisTlvSkeleton_UpdateHopLimit(MetisTlvSkeleton *opaque, uint8_t hoplimit) +{ + _InternalSkeleton *skeleton = (_InternalSkeleton *) opaque; + bool updated = false; + if (!metisTlvExtent_Equals(&skeleton->array[INDEX_HOPLIMIT], &metisTlvExtent_NotFound)) { + if (skeleton->array[INDEX_HOPLIMIT].length == 1) { + updated = true; + uint8_t *value = skeleton->packet + skeleton->array[INDEX_HOPLIMIT].offset; + *value = hoplimit; + + if (metisLogger_IsLoggable(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) { + metisLogger_Log(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__, + "set hoplimit %u", hoplimit); + } + } + } + return updated; +} + +bool +metisTlvSkeleton_UpdatePathLabel(MetisTlvSkeleton *opaque, uint8_t outFace) +{ + _InternalSkeleton *skeleton = (_InternalSkeleton *) opaque; + bool updated = false; + if (!metisTlvExtent_Equals(&skeleton->array[INDEX_PATHLABEL], &metisTlvExtent_NotFound)) { + if (skeleton->array[INDEX_PATHLABEL].length == 1) { + updated = true; + uint8_t *value = skeleton->packet + skeleton->array[INDEX_PATHLABEL].offset; + uint8_t oldPathLabel = (uint8_t) (*value); + uint8_t tmp = (oldPathLabel << 1) | (oldPathLabel >> 7); + uint8_t newPathLabel = (tmp ^ outFace); + *value = newPathLabel; + if (metisLogger_IsLoggable(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) { + metisLogger_Log(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__, + "set pathlabel %u", newPathLabel); + } + } + } + return updated; +} + +bool +metisTlvSkeleton_ResetPathLabel(MetisTlvSkeleton *opaque) +{ + _InternalSkeleton *skeleton = (_InternalSkeleton *) opaque; + bool updated = false; + if (!metisTlvExtent_Equals(&skeleton->array[INDEX_PATHLABEL], &metisTlvExtent_NotFound)) { + if (skeleton->array[INDEX_PATHLABEL].length == 1) { + updated = true; + uint8_t *value = skeleton->packet + skeleton->array[INDEX_PATHLABEL].offset; + *value = 0; + if (metisLogger_IsLoggable(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) { + metisLogger_Log(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__, + "reset pathlabel 0"); + } + } + } + return updated; +} + +void +metisTlvSkeleton_SetFragmentPayload(MetisTlvSkeleton *opaque, size_t offset, size_t length) +{ + _InternalSkeleton *skeleton = (_InternalSkeleton *) opaque; + skeleton->array[INDEX_FRAGMENTPAYLOAD].offset = offset; + skeleton->array[INDEX_FRAGMENTPAYLOAD].length = length; + + if (metisLogger_IsLoggable(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) { + metisLogger_Log(skeleton->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__, + "Set fragment payload extent {%u, %u}", offset, length); + } +} + + + +// ========================================================== + +MetisTlvExtent +metisTlvSkeleton_GetName(const MetisTlvSkeleton *skeleton) +{ + return skeleton->array[INDEX_NAME]; +} + +MetisTlvExtent +metisTlvSkeleton_GetKeyId(const MetisTlvSkeleton *skeleton) +{ + return skeleton->array[INDEX_KEYID]; +} + +MetisTlvExtent +metisTlvSkeleton_GetCertificate(const MetisTlvSkeleton *skeleton) +{ + return skeleton->array[INDEX_CERTIFICATE]; +} + +MetisTlvExtent +metisTlvSkeleton_GetPublicKey(const MetisTlvSkeleton *skeleton) +{ + return skeleton->array[INDEX_PUBKEY]; +} + +MetisTlvExtent +metisTlvSkeleton_GetObjectHash(const MetisTlvSkeleton *skeleton) +{ + return skeleton->array[INDEX_OBJHASH]; +} + +MetisTlvExtent +metisTlvSkeleton_GetHopLimit(const MetisTlvSkeleton *skeleton) +{ + return skeleton->array[INDEX_HOPLIMIT]; +} + +MetisTlvExtent +metisTlvSkeleton_GetInterestLifetime(const MetisTlvSkeleton *skeleton) +{ + return skeleton->array[INDEX_INTLIFETIME]; +} + +MetisTlvExtent +metisTlvSkeleton_GetPathLabel(const MetisTlvSkeleton *skeleton) +{ + return skeleton->array[INDEX_PATHLABEL]; +} + +MetisTlvExtent +metisTlvSkeleton_GetCacheTimeHeader(const MetisTlvSkeleton *skeleton) +{ + return skeleton->array[INDEX_CACHETIME]; +} + +MetisTlvExtent +metisTlvSkeleton_GetExpiryTime(const MetisTlvSkeleton *skeleton) +{ + return skeleton->array[INDEX_EXPIRYTIME]; +} + +MetisTlvExtent +metisTlvSkeleton_GetCPI(const MetisTlvSkeleton *skeleton) +{ + return skeleton->array[INDEX_CPI]; +} + +MetisTlvExtent +metisTlvSkeleton_GetFragmentPayload(const MetisTlvSkeleton *skeleton) +{ + return skeleton->array[INDEX_FRAGMENTPAYLOAD]; +} + + +const uint8_t * +metisTlvSkeleton_GetPacket(const MetisTlvSkeleton *opaque) +{ + const _InternalSkeleton *skeleton = (const _InternalSkeleton *) opaque; + _assertInvariants(skeleton); + return skeleton->packet; +} + +PARCCryptoHash * +metisTlvSkeleton_ComputeContentObjectHash(const MetisTlvSkeleton *opaque) +{ + const _InternalSkeleton *skeleton = (const _InternalSkeleton *) opaque; + _assertInvariants(skeleton); + return skeleton->tlvOps->computeContentObjectHash(skeleton->packet); +} + +size_t +metisTlvSkeleton_TotalPacketLength(const MetisTlvSkeleton *opaque) +{ + const _InternalSkeleton *skeleton = (const _InternalSkeleton *) opaque; + _assertInvariants(skeleton); + return skeleton->tlvOps->totalPacketLength(skeleton->packet); +} + +bool +metisTlvSkeleton_IsPacketTypeInterest(const MetisTlvSkeleton *opaque) +{ + const _InternalSkeleton *skeleton = (const _InternalSkeleton *) opaque; + _assertInvariants(skeleton); + return skeleton->tlvOps->isPacketTypeInterest(skeleton->packet); +} + +bool +metisTlvSkeleton_IsPacketTypeContentObject(const MetisTlvSkeleton *opaque) +{ + const _InternalSkeleton *skeleton = (const _InternalSkeleton *) opaque; + _assertInvariants(skeleton); + return skeleton->tlvOps->isPacketTypeContentObject(skeleton->packet); +} + +bool +metisTlvSkeleton_IsPacketTypeControl(const MetisTlvSkeleton *opaque) +{ + const _InternalSkeleton *skeleton = (const _InternalSkeleton *) opaque; + _assertInvariants(skeleton); + return skeleton->tlvOps->isPacketTypeControl(skeleton->packet); +} + +bool +metisTlvSkeleton_IsPacketTypeInterestReturn(const MetisTlvSkeleton *opaque) +{ + const _InternalSkeleton *skeleton = (const _InternalSkeleton *) opaque; + _assertInvariants(skeleton); + return skeleton->tlvOps->isPacketTypeInterestReturn(skeleton->packet); +} + +bool +metisTlvSkeleton_IsPacketTypeHopByHopFragment(const MetisTlvSkeleton *opaque) +{ + const _InternalSkeleton *skeleton = (const _InternalSkeleton *) opaque; + _assertInvariants(skeleton); + return skeleton->tlvOps->isPacketTypeHopByHopFragment(skeleton->packet); +} + +MetisLogger * +metisTlvSkeleton_GetLogger(const MetisTlvSkeleton *opaque) +{ + const _InternalSkeleton *skeleton = (const _InternalSkeleton *) opaque; + _assertInvariants(skeleton); + return skeleton->logger; +} diff --git a/metis/ccnx/forwarder/metis/tlv/metis_TlvSkeleton.h b/metis/ccnx/forwarder/metis/tlv/metis_TlvSkeleton.h new file mode 100644 index 00000000..7e4eb603 --- /dev/null +++ b/metis/ccnx/forwarder/metis/tlv/metis_TlvSkeleton.h @@ -0,0 +1,770 @@ +/* + * 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_TlvSkeleton.h + * @brief The structure used to store the set of fields used by the forwarder + * + * The TLV skeleton is the parsed form of the packet. It contains the TLV extents of each + * field relevant to the forwarder. + * + * To use MetisTlvSkeleton, you first parse a packet in to the skeleton and can then access the various + * TlvExtents from the getters. The Tlv parsers use the Setters. + * + * The TlvSkeleton is not allocated memory (in general). It is defined as a sized opaque struct so one + * can use it as a member of another struct without deep allocations. The general use is as shown in + * the example below. + * + * @code + * typedef struct my_data_s { + * unsigned id; + * MetisTlvSkeleton skeleton; + * } MyData; + * + * void foo(uint8_t *packetBuffer) + * { + * MyData *mydata = parcMemory_Allocate(sizeof(MyData)); + * mydata->id = _global_id++; + * metisTlvSkeleton_Parse(&mydata->skeleton, packetBuffer); + * // now forward the packet using the data in the skeleton + * parcMemory_Deallocate(&mydata); + * } + * + */ + +#ifndef Metis_metis_TlvSkeleton_h +#define Metis_metis_TlvSkeleton_h + +#include <ccnx/forwarder/metis/tlv/metis_TlvExtent.h> +#include <ccnx/forwarder/metis/core/metis_Logger.h> + +#define MetisTlvSkeleton_ArrayLength 12 + +/** + * The MetisTlvSkeleton is an opaque object defined in the header so it + * can be pre-allocated as part of another data structure. The user should have + * no direct access to any of the fields. + */ +typedef struct tlv_skeleton { + void *field1; + void *field2; + void *field3; + + MetisTlvExtent array[MetisTlvSkeleton_ArrayLength]; +} MetisTlvSkeleton; + +// =================================== +// Setters + +/** + * Fills in the packet TLV skeleton + * + * Sets the skeleton's tlv operations to the correct value and sets the packet buffer. + * Will call metisTlvSkeleton_Initialize(). + * + * Because the MetisTlvSkeleton is meant to be allocated as part of a larger object, it does + * not store its own reference to the logger, as there is no _Destroy or _Release method. Therefore, + * the caller must ensure the logger stays allocated for the lifetime of the skeleton. + * + * @param [in] packet Packet memory, pointing to byte 0 of the fixed header + * @param [in] skeleton An allocated MetisTlvSkeleton to fill in + * @param [in] logger The logger to use + * + * @retval true Good parse + * @retval false Error + * + * Example: + * @code + * { + * MetisTlvSkeleton skeleton; + * bool success = metisTlvSkeleton_Parse(&skeleton, packet); + * } + * @endcode + */ +bool metisTlvSkeleton_Parse(MetisTlvSkeleton *skeleton, uint8_t *packet, MetisLogger *logger); + +/** + * Sets the Name extent + * + * Sets the name extent to the specified offset and length. + * + * @param [in] skeleton A MetisTlvSkeleton structure + * @param [in] offset The byte offset of the beginning of the 'value' + * @param [in] length The byte length of the 'value' + * + * Example: + * @code + * { + * MetisTlvType *tlv = (MetisTlvType *) (packet + offset); + * uint16_t type = htons(tlv->type); + * uint16_t v_length = htons(tlv->length); + * offset += sizeof(MetisTlvType); + * + * if (type == _ParserNameType) { + * metisTlvSkeleton_SetName(skeleton, offset, v_length); + * } + * } + * @endcode + */ +void metisTlvSkeleton_SetName(MetisTlvSkeleton *skeleton, size_t offset, size_t length); + +/** + * Sets the KeyId extent + * + * For an Interest, the KeyId extent is the KeyIdRestriction. For a ContentObject, it is + * the KeyId in the Validation Algorithm. + * + * @param [in] skeleton A MetisTlvSkeleton structure + * @param [in] offset The byte offset of the beginning of the 'value' + * @param [in] length The byte length of the 'value' + * + * Example: + * @code + * { + * MetisTlvType *tlv = (MetisTlvType *) (packet + offset); + * uint16_t type = htons(tlv->type); + * uint16_t v_length = htons(tlv->length); + * offset += sizeof(MetisTlvType); + * + * if (type == _ParserKeyIdType) { + * metisTlvSkeleton_SetKeyId(skeleton, offset, v_length); + * } + * } + * @endcode + */ +void metisTlvSkeleton_SetKeyId(MetisTlvSkeleton *skeleton, size_t offset, size_t length); + +/* + * Sets the Certificate extent. + * + * In a ContentObject, in the Validation dependent data may contain a certificate. + * Use this to set the extents of the certificate. + * + * @param [in] skeleton A MetisTlvSkeleton structure + * @param [in] offset The byte offset of the beginning of the certificate. + * @param [in] length The byte length of the certificate. + */ +void metisTlvSkeleton_SetCertificate(MetisTlvSkeleton *skeleton, size_t offset, size_t length); + +/* + * Sets the Public Key extent. + * + * In a ContentObject, in the Validation dependent data may contain a public key. + * Use this to set the extents of the certificate. + * + * @param [in] skeleton A MetisTlvSkeleton structure + * @param [in] offset The byte offset of the beginning of the public key. + * @param [in] length The byte length of the public key. + */ +void metisTlvSkeleton_SetPublicKey(MetisTlvSkeleton *skeleton, size_t offset, size_t length); + +/** + * Sets the Content Object Hash extent + * + * For an Interest, this is the Content Object hash restriction + * + * @param [in] skeleton A MetisTlvSkeleton structure + * @param [in] offset The byte offset of the beginning of the 'value' + * @param [in] length The byte length of the 'value' + * + * Example: + * @code + * { + * MetisTlvType *tlv = (MetisTlvType *) (packet + offset); + * uint16_t type = htons(tlv->type); + * uint16_t v_length = htons(tlv->length); + * offset += sizeof(MetisTlvType); + * + * if (type == _ParserKeyIdType) { + * metisTlvSkeleton_SetObjectHash(skeleton, offset, v_length); + * } + * } + * @endcode + */ +void metisTlvSkeleton_SetObjectHash(MetisTlvSkeleton *skeleton, size_t offset, size_t length); + +/** + * Sets the Hop Limit extent + * + * For an Interest, the hoplimit is found in the fixed header (v1) or in a perhop header (v0) + * + * @param [in] skeleton A MetisTlvSkeleton structure + * @param [in] offset The byte offset of the beginning of the 'value' + * @param [in] length The byte length of the 'value' + * + * Example: + * @code + * { + * // For a version 1 packet, point it to the field in the fixed header + * metisTlvSkeleton_SetHopLimit(skeleton, 4, 1); + * } + * @endcode + */ +void metisTlvSkeleton_SetHopLimit(MetisTlvSkeleton *skeleton, size_t offset, size_t length); + +/** + * Sets the Interest Lifetime extent + * + * Sets the extent for the Interest lifetime + * + * @param [in] skeleton A MetisTlvSkeleton structure + * @param [in] offset The byte offset of the beginning of the 'value' + * @param [in] length The byte length of the 'value' + * + * Example: + * @code + * { + * MetisTlvType *tlv = (MetisTlvType *) (packet + offset); + * uint16_t type = htons(tlv->type); + * uint16_t v_length = htons(tlv->length); + * offset += sizeof(MetisTlvType); + * + * if (type == _ParserInterestLifetimeType) { + * metisTlvSkeleton_SetInterestLifetime(skeleton, offset, v_length); + * } + * } + * @endcode + */ +void metisTlvSkeleton_SetInterestLifetime(MetisTlvSkeleton *skeleton, size_t offset, size_t length); + +/** + * Sets the Recommended Cache Time extent + * + * Sets the extent for the Recommended Cache Time for a Content Object + * + * @param [in] skeleton A MetisTlvSkeleton structure + * @param [in] offset The byte offset of the beginning of the 'value' + * @param [in] length The byte length of the 'value' + * + * Example: + * @code + * { + * MetisTlvType *tlv = (MetisTlvType *) (packet + offset); + * uint16_t type = htons(tlv->type); + * uint16_t v_length = htons(tlv->length); + * offset += sizeof(MetisTlvType); + * + * if (type == _ParserRecommendedCacheTimeType) { + * metisTlvSkeleton_SetCacheTimeHeader(skeleton, offset, v_length); + * } + * } + * @endcode + */ +void metisTlvSkeleton_SetCacheTimeHeader(MetisTlvSkeleton *skeleton, size_t offset, size_t length); + +/** + * Sets the ExpiryTime extent + * + * Sets the extent for the Expiry Time for a Content Object + * + * @param [in] skeleton A MetisTlvSkeleton structure + * @param [in] offset The byte offset of the beginning of the 'value' + * @param [in] length The byte length of the 'value' + * + * Example: + * @code + * { + * MetisTlvType *tlv = (MetisTlvType *) (packet + offset); + * uint16_t type = htons(tlv->type); + * uint16_t v_length = htons(tlv->length); + * offset += sizeof(MetisTlvType); + * + * if (type == _ParserExpiryTimeType) { + * metisTlvSkeleton_SetExpiryTime(skeleton, offset, v_length); + * } + * } + * @endcode + */ +void metisTlvSkeleton_SetExpiryTime(MetisTlvSkeleton *skeleton, size_t offset, size_t length); + +/** + * Sets the Control Plane Interface (CPI) extent + * + * Sets the extent for the CPI value. + * + * @param [in] skeleton A MetisTlvSkeleton structure + * @param [in] offset The byte offset of the beginning of the 'value' + * @param [in] length The byte length of the 'value' + * + * Example: + * @code + * { + * MetisTlvType *tlv = (MetisTlvType *) (packet + offset); + * uint16_t type = htons(tlv->type); + * uint16_t v_length = htons(tlv->length); + * offset += sizeof(MetisTlvType); + * + * if (type == _ParserControlType) { + * metisTlvSkeleton_SetCPI(skeleton, offset, v_length); + * } + * } + * @endcode + */ +void metisTlvSkeleton_SetCPI(MetisTlvSkeleton *skeleton, size_t offset, size_t length); + +/** + * Sets the Fragment Payload extent + * + * This is the payload of the fragment, i.e. part of the original packet. + * + * @param [in] skeleton A MetisTlvSkeleton structure + * @param [in] offset The byte offset of the beginning of the 'value' + * @param [in] length The byte length of the 'value' + * + * Example: + * @code + * { + * MetisTlvType *tlv = (MetisTlvType *) (packet + offset); + * uint16_t type = htons(tlv->type); + * uint16_t v_length = htons(tlv->length); + * offset += sizeof(MetisTlvType); + * + * if (type == _ParserFragmentPayloadType) { + * metisTlvSkeleton_SetFragmentPayload(skeleton, offset, v_length); + * } + * } + * @endcode + */ +void metisTlvSkeleton_SetFragmentPayload(MetisTlvSkeleton *skeleton, size_t offset, size_t length); + +/** + * Updates the HopLimit inside the packet buffer + * + * If the HopLimit extent is not metisTlvExtent_NotFound, it will update the specified location + * with the new hoplimit. The call will fail if the HopLimit extent is not exactly 1 byte. + * + * @param [in] skeleton A parsed MetisTlvSkeleton + * @param [in] hoplimit The new value + * + * @retval true Updated + * @retval false Not updated (likely was metisTlvExtent_NotFound) + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisTlvSkeleton_UpdateHopLimit(MetisTlvSkeleton *skeleton, uint8_t hoplimit); + +void metisTlvSkeleton_SetPathLabel(MetisTlvSkeleton *skeleton, size_t offset, size_t length); +bool metisTlvSkeleton_UpdatePathLabel(MetisTlvSkeleton *opaque, uint8_t outFace); +bool metisTlvSkeleton_ResetPathLabel(MetisTlvSkeleton *opaque); + +// ==================================== +// Getters + + +MetisTlvExtent metisTlvSkeleton_GetPathLabel(const MetisTlvSkeleton *skeleton); + +/** + * Returns the extent of the Name + * + * Returns the previously set Name extent or metisTlvExtent_NotFound if not set. + * + * @param [in] skeleton An initialized and parsed skeleton + * + * @retval {0,0} Not Present + * @retval other The set value of the extent + * + * Example: + * @code + * { + * uint8_t *packet = // read from network + * MetisTlvSkeleton skeleton; + * if (metisTlvSkeleton_Parse(&skeleton, packet)) { + * MetisTlvExtent extent = metisTlvSkeleton_GetName(&skeleton); + * } + * } + * @endcode + */ +MetisTlvExtent metisTlvSkeleton_GetName(const MetisTlvSkeleton *skeleton); + +/** + * Returns the extent of the KeyId + * + * Returns the previously set KeyId extent or metisTlvExtent_NotFound if not set. + * + * @param [in] skeleton An initialized and parsed skeleton + * + * @retval {0,0} Not Present + * @retval other The set value of the extent + * + * Example: + * @code + * { + * uint8_t *packet = // read from network + * MetisTlvSkeleton skeleton; + * if (metisTlvSkeleton_Parse(&skeleton, packet)) { + * MetisTlvExtent extent = metisTlvSkeleton_GetKeyId(&skeleton); + * } + * } + * @endcode + */ +MetisTlvExtent metisTlvSkeleton_GetKeyId(const MetisTlvSkeleton *skeleton); + +/** + * Returns the extent of the Public Key. + * + * Returns the previously set Public Key extent or metisTlvExtent_NotFound if not set. + * + * @param [in] skeleton An initialized and parsed skeleton + * + * @retval {0,0} Not Present + * @retval other The set value of the extent + * + * Example: + * @code + * { + * uint8_t *packet = // read from network + * MetisTlvSkeleton skeleton; + * if (metisTlvSkeleton_Parse(&skeleton, packet)) { + * MetisTlvExtent extent = metisTlvSkeleton_GetPublicKey(&skeleton); + * } + * } + * @endcode + */ +MetisTlvExtent metisTlvSkeleton_GetPublicKey(const MetisTlvSkeleton *skeleton); + +/** + * Returns the extent of the Certificate. + * + * Returns the previously set Certificate extent or metisTlvExtent_NotFound if not set. + * + * @param [in] skeleton An initialized and parsed skeleton + * + * @retval {0,0} Not Present + * @retval other The set value of the extent + * + * Example: + * @code + * { + * uint8_t *packet = // read from network + * MetisTlvSkeleton skeleton; + * if (metisTlvSkeleton_Parse(&skeleton, packet)) { + * MetisTlvExtent extent = metisTlvSkeleton_GetCertificate(&skeleton); + * } + * } + * @endcode + */ +MetisTlvExtent metisTlvSkeleton_GetCertificate(const MetisTlvSkeleton *skeleton); + +/** + * Returns the extent of the Object Hash + * + * Returns the previously set Object hash extent or metisTlvExtent_NotFound if not set. + * + * @param [in] skeleton An initialized and parsed skeleton + * + * @retval {0,0} Not Present + * @retval other The set value of the extent + * + * Example: + * @code + * { + * uint8_t *packet = // read from network + * MetisTlvSkeleton skeleton; + * if (metisTlvSkeleton_Parse(&skeleton, packet)) { + * MetisTlvExtent extent = metisTlvSkeleton_GetObjectHash(&skeleton); + * } + * } + * @endcode + */ +MetisTlvExtent metisTlvSkeleton_GetObjectHash(const MetisTlvSkeleton *skeleton); + +/** + * Returns the extent of the Hop Limit + * + * Returns the previously set Hop Limit extent or metisTlvExtent_NotFound if not set. + * + * @param [in] skeleton An initialized and parsed skeleton + * + * @retval {0,0} Not Present + * @retval other The set value of the extent + * + * Example: + * @code + * { + * uint8_t *packet = // read from network + * MetisTlvSkeleton skeleton; + * if (metisTlvSkeleton_Parse(&skeleton, packet)) { + * MetisTlvExtent extent = metisTlvSkeleton_GetObjectHash(&skeleton); + * } + * } + * @endcode + */ +MetisTlvExtent metisTlvSkeleton_GetHopLimit(const MetisTlvSkeleton *skeleton); + +/** + * Returns the extent of the Interest Lifetime + * + * Returns the previously set Interest Lifetime extent or metisTlvExtent_NotFound if not set. + * + * @param [in] skeleton An initialized and parsed skeleton + * + * @retval {0,0} Not Present + * @retval other The set value of the extent + * + * Example: + * @code + * { + * uint8_t *packet = // read from network + * MetisTlvSkeleton skeleton; + * if (metisTlvSkeleton_Parse(&skeleton, packet)) { + * MetisTlvExtent extent = metisTlvSkeleton_GetInterestLifetime(&skeleton); + * } + * } + * @endcode + */ +MetisTlvExtent metisTlvSkeleton_GetInterestLifetime(const MetisTlvSkeleton *skeleton); + +/** + * Returns the extent of the Recommended Cache Time + * + * Returns the previously set Recommended Cache Time extent or metisTlvExtent_NotFound if not set. + * + * @param [in] skeleton An initialized and parsed skeleton + * + * @retval {0,0} Not Present + * @retval other The set value of the extent + * + * Example: + * @code + * { + * uint8_t *packet = // read from network + * MetisTlvSkeleton skeleton; + * if (metisTlvSkeleton_Parse(&skeleton, packet)) { + * MetisTlvExtent extent = metisTlvSkeleton_GetCacheTimeHeader(&skeleton); + * } + * } + * @endcode + */ +MetisTlvExtent metisTlvSkeleton_GetCacheTimeHeader(const MetisTlvSkeleton *skeleton); + +/** + * Returns the extent of the Expiry Time + * + * Returns the previously set Expiry Time extent or metisTlvExtent_NotFound if not set. + * + * @param [in] skeleton An initialized and parsed skeleton + * + * @retval {0,0} Not Present + * @retval other The set value of the extent + * + * Example: + * @code + * { + * uint8_t *packet = // read from network + * MetisTlvSkeleton skeleton; + * if (metisTlvSkeleton_Parse(&skeleton, packet)) { + * MetisTlvExtent extent = metisTlvSkeleton_GetExpiryTime(&skeleton); + * } + * } + * @endcode + */ +MetisTlvExtent metisTlvSkeleton_GetExpiryTime(const MetisTlvSkeleton *skeleton); + +/** + * Returns the extent of the CPI payload + * + * Returns the previously set CPI payload extent or metisTlvExtent_NotFound if not set. + * + * @param [in] skeleton An initialized and parsed skeleton + * + * @retval {0,0} Not Present + * @retval other The set value of the extent + * + * Example: + * @code + * { + * uint8_t *packet = // read from network + * MetisTlvSkeleton skeleton; + * if (metisTlvSkeleton_Parse(&skeleton, packet)) { + * MetisTlvExtent extent = metisTlvSkeleton_GetCPI(&skeleton); + * } + * } + * @endcode + */ +MetisTlvExtent metisTlvSkeleton_GetCPI(const MetisTlvSkeleton *skeleton); + +/** + * Returns the extent of the fragment payload + * + * Returns the previously set fragment payload extent or metisTlvExtent_NotFound if not set. + * + * @param [in] skeleton An initialized and parsed skeleton + * + * @retval {0,0} Not Present + * @retval other The set value of the extent + * + * Example: + * @code + * { + * uint8_t *packet = // read from network + * MetisTlvSkeleton skeleton; + * if (metisTlvSkeleton_Parse(&skeleton, packet)) { + * MetisTlvExtent extent = metisTlvSkeleton_GetFragmentPayload(&skeleton); + * } + * } + * @endcode + */ +MetisTlvExtent metisTlvSkeleton_GetFragmentPayload(const MetisTlvSkeleton *skeleton); + +/** + * Returns the pointer to the packet buffer + * + * Returns pointer to byte 0 of the fixed header + * + * @param [in] skeleton A parsed MetisTlvSkeleton + * + * @retval non-null The packet buffer + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +const uint8_t *metisTlvSkeleton_GetPacket(const MetisTlvSkeleton *skeleton); + +/** + * The total packet length based on the fixed header + * + * Parses the fixed header and returns the total packet length + * Will return "0" for unknown packet version + * + * @param [in] packet Packet memory pointer + * + * @retval number Total packet length + * + * Example: + * @code + * <#example#> + * @endcode + */ +size_t metisTlvSkeleton_TotalPacket(const MetisTlvSkeleton *skeleton); + +/** + * @function metisTlv_ComputeContentObjectHash + * @abstract Computes the ContentObjectHash over a packet + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return NULL if not a content object, otherwise the SHA256 hash + */ +PARCCryptoHash *metisTlvSkeleton_ComputeContentObjectHash(const MetisTlvSkeleton *skeleton); + +/** + * Determines if the packet type is Interest + * + * <#Paragraphs Of Explanation#> + * + * @param [in] packet Packet memory pointer + * + * @retval true It is an Interest + * @retval false Not an Interest + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisTlvSkeleton_IsPacketTypeInterest(const MetisTlvSkeleton *skeleton); + +/** + * Determines if the packet type is Content Object + * + * <#Paragraphs Of Explanation#> + * + * @param [in] packet Packet memory pointer + * + * @retval true It is a ContentObject + * @retval false Not a ContentObject + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisTlvSkeleton_IsPacketTypeContentObject(const MetisTlvSkeleton *skeleton); + +/** + * Determines if the packet type is Control + * + * <#Paragraphs Of Explanation#> + * + * @param [in] packet Packet memory pointer + * + * @retval true It is a Control + * @retval false Not a Control + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisTlvSkeleton_IsPacketTypeControl(const MetisTlvSkeleton *skeleton); + +/** + * Determines if the packet type is InterestReturn + * + * <#Paragraphs Of Explanation#> + * + * @param [in] packet Packet memory pointer + * + * @retval true It is a InterestReturn + * @retval false Not a InterestReturn + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisTlvSkeleton_IsPacketTypeInterestReturn(const MetisTlvSkeleton *skeleton); + +/** + * Determines if the packet type is HopByHop Fragment + * + * <#Paragraphs Of Explanation#> + * + * @param [in] packet Packet memory pointer + * + * @retval true It is a InterestReturn + * @retval false Not a InterestReturn + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisTlvSkeleton_IsPacketTypeHopByHopFragment(const MetisTlvSkeleton *skeleton); + +/** + * Returns the logger associated with the skeleton + * + * Returns the logger the user passed to the skeleton, which may be NULL + * + * @param [in] skeleton An initialized skeleton + * + * @retval non-null An allocated logger + * @retval null No logger associated with skeleton + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisLogger *metisTlvSkeleton_GetLogger(const MetisTlvSkeleton *skeleton); +#endif // Metis_metis_TlvSkeleton_h diff --git a/metis/ccnx/forwarder/metis/tlv/test/.gitignore b/metis/ccnx/forwarder/metis/tlv/test/.gitignore new file mode 100644 index 00000000..5cba902c --- /dev/null +++ b/metis/ccnx/forwarder/metis/tlv/test/.gitignore @@ -0,0 +1,8 @@ +test_metis_Tlv +test_metis_TlvExtent +test_metis_TlvName +test_metis_TlvNameCodec +test_metis_TlvSchemaV0 +test_metis_TlvSchemaV1 +test_metis_TlvSkeleton + diff --git a/metis/ccnx/forwarder/metis/tlv/test/CMakeLists.txt b/metis/ccnx/forwarder/metis/tlv/test/CMakeLists.txt new file mode 100644 index 00000000..73f1d34d --- /dev/null +++ b/metis/ccnx/forwarder/metis/tlv/test/CMakeLists.txt @@ -0,0 +1,19 @@ +# Enable gcov output for the tests +add_definitions(--coverage) +set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} " --coverage") + +set(TestsExpectedToPass + test_metis_Tlv + test_metis_TlvExtent + test_metis_TlvName + test_metis_TlvNameCodec + test_metis_TlvSchemaV0 + test_metis_TlvSchemaV1 + test_metis_TlvSkeleton +) + + +foreach(test ${TestsExpectedToPass}) + AddTest(${test}) +endforeach() + diff --git a/metis/ccnx/forwarder/metis/tlv/test/test_metis_Tlv.c b/metis/ccnx/forwarder/metis/tlv/test/test_metis_Tlv.c new file mode 100644 index 00000000..79a28ed3 --- /dev/null +++ b/metis/ccnx/forwarder/metis/tlv/test/test_metis_Tlv.c @@ -0,0 +1,249 @@ +/* + * 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_Tlv.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> + +LONGBOW_TEST_RUNNER(metis_Tlv) +{ + // 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_Tlv) +{ + 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_Tlv) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisTlv_NameSegments); + LONGBOW_RUN_TEST_CASE(Global, metisTlv_NameSegments_Realloc); + LONGBOW_RUN_TEST_CASE(Global, metisTlv_ExtentToVarInt); + + LONGBOW_RUN_TEST_CASE(Global, metisTlv_FixedHeaderLength); + LONGBOW_RUN_TEST_CASE(Global, metisTlv_TotalHeaderLength_V0); + LONGBOW_RUN_TEST_CASE(Global, metisTlv_TotalHeaderLength_V1); + LONGBOW_RUN_TEST_CASE(Global, metisTlv_TotalPacketLength_V0); + LONGBOW_RUN_TEST_CASE(Global, metisTlv_TotalPacketLength_V1); + + LONGBOW_RUN_TEST_CASE(TlvOpsFunctions, metisTlv_EncodeControlPlaneInformation_V0); + LONGBOW_RUN_TEST_CASE(TlvOpsFunctions, metisTlv_EncodeControlPlaneInformation_V1); +} + +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, metisTlv_NameSegments) +{ + uint8_t name[] = { + 0x00, 0x02, 0x00, 0x05, // type = binary, length = 5 + 'h', 'e', 'l', 'l', + 'o', // "hello" + 0xF0, 0x00, 0x00, 0x04, // type = app, length = 4 + 'o', 'u', 'c', 'h' + }; + + MetisTlvExtent *nameExtents; + size_t nameExtentsLength; + + MetisTlvExtent truthExtents[] = { { .offset = 0, .length = 9 }, { .offset = 9, .length = 8 } }; + size_t truthExtentsLength = 2; + + metisTlv_NameSegments(name, sizeof(name), &nameExtents, &nameExtentsLength); + + assertTrue(nameExtentsLength == truthExtentsLength, "nameExtentsLength wrong, expected %zu got %zu", truthExtentsLength, nameExtentsLength); + for (int i = 0; i < nameExtentsLength; i++) { + assertTrue(truthExtents[i].offset == nameExtents[i].offset, + "nameExtents[%d].offset wrong, expected %u got %u", + i, + truthExtents[i].offset, + nameExtents[i].offset); + + assertTrue(truthExtents[i].length == nameExtents[i].length, + "nameExtents[%d].offset wrong, expected %u got %u", + i, + truthExtents[i].length, + nameExtents[i].length); + } + + parcMemory_Deallocate((void **) &nameExtents); +} + +/** + * Create a name with enough name components to cause a re-alloc in the parser + */ +LONGBOW_TEST_CASE(Global, metisTlv_NameSegments_Realloc) +{ + uint8_t oneSegment[] = { + 0x00, 0x02, 0x00, 0x04, // type = binary, length = 4 + 'h', 'e', 'l', 'l' + }; + + // build a name with neededComponents copies of oneSegment such that it will + // exceed the initialLengthForNameExtents allocation in the parser + + size_t neededComponents = _initialLengthForNameExtents + 2; + size_t nameBufferLength = neededComponents * sizeof(oneSegment); + + uint8_t *nameBuffer = parcMemory_Allocate(nameBufferLength); + assertNotNull(nameBuffer, "parcMemory_Allocate(%zu) returned NULL", nameBufferLength); + for (int i = 0; i < neededComponents; i++) { + memcpy(nameBuffer + i * sizeof(oneSegment), oneSegment, sizeof(oneSegment)); + } + + MetisTlvExtent *nameExtents; + size_t nameExtentsLength; + + metisTlv_NameSegments(nameBuffer, nameBufferLength, &nameExtents, &nameExtentsLength); + + assertTrue(nameExtentsLength == neededComponents, + "metisTlv_NameSegments returned wrong number of segments, expected %zu got %zu", + neededComponents, + nameExtentsLength); + + + parcMemory_Deallocate((void **) &nameExtents); + parcMemory_Deallocate((void **) &nameBuffer); +} + +LONGBOW_TEST_CASE(Global, metisTlv_ExtentToVarInt) +{ + uint8_t packet[] = { 0xff, 0xff, 0x00, 0x01, 0x02, 0xff, 0xff }; + MetisTlvExtent extent = { 2, 3 }; + uint64_t truth = 0x0102; + uint64_t test = 0; + + bool success = metisTlv_ExtentToVarInt(packet, &extent, &test); + assertTrue(success, "Failed to parse a good extent"); + assertTrue(truth == test, "Wrong value, expected %#" PRIx64 "got %#" PRIx64, truth, test); +} + +LONGBOW_TEST_CASE(Global, metisTlv_FixedHeaderLength) +{ + size_t test = metisTlv_FixedHeaderLength(); + assertTrue(test == 8, "Wrong fixed header length, got %zu", test); +} + +LONGBOW_TEST_CASE(Global, metisTlv_TotalHeaderLength_V0) +{ + size_t test = metisTlv_TotalHeaderLength(metisTestDataV0_EncodedInterest); + assertTrue(test == 29, "Wrong total header length, expected 29 got %zu", test); +} + +LONGBOW_TEST_CASE(Global, metisTlv_TotalHeaderLength_V1) +{ + size_t test = metisTlv_TotalHeaderLength(metisTestDataV1_Interest_AllFields); + assertTrue(test == 14, "Wrong total header length, expected 14 got %zu", test); +} + +LONGBOW_TEST_CASE(Global, metisTlv_TotalPacketLength_V0) +{ + size_t test = metisTlv_TotalPacketLength(metisTestDataV0_EncodedInterest); + assertTrue(test == sizeof(metisTestDataV0_EncodedInterest), "Wrong total header length, expected %zu got %zu", sizeof(metisTestDataV0_EncodedInterest), test); +} + +LONGBOW_TEST_CASE(Global, metisTlv_TotalPacketLength_V1) +{ + size_t test = metisTlv_TotalPacketLength(metisTestDataV1_Interest_AllFields); + assertTrue(test == sizeof(metisTestDataV1_Interest_AllFields), "Wrong total header length, expected %zu got %zu", sizeof(metisTestDataV1_Interest_AllFields), test); +} + +LONGBOW_TEST_CASE(TlvOpsFunctions, metisTlv_EncodeControlPlaneInformation_V0) +{ + CCNxControl *control = ccnxControl_CreateRouteListRequest(); + + PARCBuffer *buffer = metisTlv_EncodeControlPlaneInformation(control); + ccnxControl_Release(&control); + + assertNotNull(buffer, "Got null encoding buffer"); + uint8_t *overlay = parcBuffer_Overlay(buffer, 0); + + assertTrue(overlay[1] == 0xA4, "PacketType is not Control"); + parcBuffer_Release(&buffer); +} + +LONGBOW_TEST_CASE(TlvOpsFunctions, metisTlv_EncodeControlPlaneInformation_V1) +{ + // there's no easy way to test this right now, cannot contruct a v1 CCNxControl +} + + +// =================================================== + +LONGBOW_TEST_FIXTURE(Local) +{ + // computeHash and parseName Auth are tested called through by other tests + + LONGBOW_RUN_TEST_CASE(Local, _metisTlv_ParseName); +} + +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, _metisTlv_ParseName) +{ +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_Tlv); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/tlv/test/test_metis_TlvExtent.c b/metis/ccnx/forwarder/metis/tlv/test/test_metis_TlvExtent.c new file mode 100644 index 00000000..f61904f9 --- /dev/null +++ b/metis/ccnx/forwarder/metis/tlv/test/test_metis_TlvExtent.c @@ -0,0 +1,90 @@ +/* + * 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_TlvExtent.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +LONGBOW_TEST_RUNNER(tlv_Extent) +{ + // 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); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(tlv_Extent) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(tlv_Extent) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, tlvExtent_Equals_IsEqual); + LONGBOW_RUN_TEST_CASE(Global, tlvExtent_Equals_IsNotEqual); +} + +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, tlvExtent_Equals_IsEqual) +{ + MetisTlvExtent a = { .offset = 5, .length = 7 }; + MetisTlvExtent b = { .offset = 5, .length = 7 }; + MetisTlvExtent c = { .offset = 5, .length = 7 }; + + // transitivity testing too + assertTrue(metisTlvExtent_Equals(&a, &b), "Two equal extents did not compare equal"); + assertTrue(metisTlvExtent_Equals(&b, &c), "Two equal extents did not compare equal"); + assertTrue(metisTlvExtent_Equals(&c, &a), "Two equal extents did not compare equal"); +} + +LONGBOW_TEST_CASE(Global, tlvExtent_Equals_IsNotEqual) +{ + MetisTlvExtent a = { .offset = 5, .length = 7 }; + MetisTlvExtent b = { .offset = 3, .length = 7 }; + + assertFalse(metisTlvExtent_Equals(&a, &b), "Two unequal extents compare equal"); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(tlv_Extent); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/tlv/test/test_metis_TlvName.c b/metis/ccnx/forwarder/metis/tlv/test/test_metis_TlvName.c new file mode 100644 index 00000000..5fba7669 --- /dev/null +++ b/metis/ccnx/forwarder/metis/tlv/test/test_metis_TlvName.c @@ -0,0 +1,470 @@ +/* + * 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_TlvName.c" +#include <LongBow/unit-test.h> + +#include <stdint.h> +#include <limits.h> +#include <parc/algol/parc_SafeMemory.h> + +uint8_t encoded_name[] = { + 0x00, 0x02, 0x00, 0x05, // type = binary, length = 5 + 'h', 'e', 'l', 'l', + 'o', // "hello" + 0xF0, 0x00, 0x00, 0x04, // type = app, length = 4 + 'o', 'u', 'c', 'h', // value = "ouch" + 0xF0, 0x01, 0x00, 0x02, // type = app, length = 2 + 0x01, 0xFF // value = 0x01FF +}; + +uint8_t second_name[] = { + 0x00, 0x02, 0x00, 0x05, // type = binary, length = 5 + 'h', 'e', 'l', 'l', + 'o', // "hello" + 0xF0, 0x00, 0x00, 0x04, // type = app, length = 4 + 'o', 'u', 'c', 'h', // value = "ouch" + 0xF0, 0x01, 0x00, 0x02, // type = app, length = 2 + 0xFF, 0xFF // value = 0xFFFF +}; + +uint8_t prefixOf_name[] = { + 0x00, 0x02, 0x00, 0x05, // type = binary, length = 5 + 'h', 'e', 'l', 'l', + 'o', // "hello" + 0xF0, 0x00, 0x00, 0x04, // type = app, length = 4 + 'o', 'u', 'c', 'h', // value = "ouch" +}; + +uint8_t default_route_name[] = { + 0x00, 0x01, 0x00, 0x00, // type = name, length = 0 +}; + +LONGBOW_TEST_RUNNER(metis_TlvName) +{ + LONGBOW_RUN_TEST_FIXTURE(Global); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_TlvName) +{ + 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_TlvName) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisTlvName_Acquire); + LONGBOW_RUN_TEST_CASE(Global, metisTlvName_Acquire_CopyAtMost0); + LONGBOW_RUN_TEST_CASE(Global, metisTlvName_Acquire_CopyAtMost1); + LONGBOW_RUN_TEST_CASE(Global, metisTlvName_Acquire_CopyAtMost2); + LONGBOW_RUN_TEST_CASE(Global, metisTlvName_Acquire_CopyAtMostAll); + + LONGBOW_RUN_TEST_CASE(Global, metisTlvName_Create_Destroy); + LONGBOW_RUN_TEST_CASE(Global, metisTlvName_CreateFromCCNxName); + LONGBOW_RUN_TEST_CASE(Global, metisTlvName_CreateFromCCNxName_DefaultRoute); + + LONGBOW_RUN_TEST_CASE(Global, metisTlvName_Equals_IsEqual); + LONGBOW_RUN_TEST_CASE(Global, metisTlvName_Equals_SameCountDifferentBytes); + LONGBOW_RUN_TEST_CASE(Global, metisTlvName_Equals_DifferentCount); + + LONGBOW_RUN_TEST_CASE(Global, metisTlvName_Compare); + LONGBOW_RUN_TEST_CASE(Global, metisTlvName_Compare_DefaultRoute); + LONGBOW_RUN_TEST_CASE(Global, metisTlvName_Compare_DefaultRoute_Binary); + + LONGBOW_RUN_TEST_CASE(Global, metisTlvName_HashCode); + + LONGBOW_RUN_TEST_CASE(Global, metisTlvName_SegmentCount); + LONGBOW_RUN_TEST_CASE(Global, metisTlvName_StartsWith_SelfPrefix); + LONGBOW_RUN_TEST_CASE(Global, metisTlvName_StartsWith_IsPrefix); + LONGBOW_RUN_TEST_CASE(Global, metisTlvName_StartsWith_PrefixTooLong); + LONGBOW_RUN_TEST_CASE(Global, metisTlvName_StartsWith_IsNotPrefix); +} + +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, metisTlvName_Acquire) +{ + MetisTlvName *name = metisTlvName_Create(encoded_name, sizeof(encoded_name)); + + MetisTlvName *copy = metisTlvName_Acquire(name); + assertTrue(_getRefCount(name) == 2, "Name created with wrong refcount, expected %u got %u", 2, _getRefCount(name)); + + metisTlvName_Release(©); + assertTrue(_getRefCount(name) == 1, "Name created with wrong refcount, expected %u got %u", 1, _getRefCount(name)); + + metisTlvName_Release(&name); +} + +LONGBOW_TEST_CASE(Global, metisTlvName_Create_Destroy) +{ + MetisTlvName *name = metisTlvName_Create(encoded_name, sizeof(encoded_name)); + assertTrue(_getRefCount(name) == 1, "Name created with wrong refcount, expected %u got %u", 1, _getRefCount(name)); + metisTlvName_Release(&name); + + assertTrue(parcSafeMemory_ReportAllocation(STDOUT_FILENO) == 0, "Memory imbalance after create/destroy: %u", parcMemory_Outstanding()); +} + +LONGBOW_TEST_CASE(Global, metisTlvName_CreateFromCCNxName) +{ + char uri[] = "lci:/2=hello/0xF000=ouch/0xF001=%01%FF"; + CCNxName *ccnxName = ccnxName_CreateFromCString(uri); + + MetisTlvName *truth = metisTlvName_Create(encoded_name, sizeof(encoded_name)); + MetisTlvName *name = metisTlvName_CreateFromCCNxName(ccnxName); + + assertTrue(metisTlvName_Equals(truth, name), "MetisTlvName from ccnxName did not equal expected"); + metisTlvName_Release(&name); + metisTlvName_Release(&truth); + ccnxName_Release(&ccnxName); + + assertTrue(parcSafeMemory_ReportAllocation(STDOUT_FILENO) == 0, "Memory imbalance after create/destroy: %u", parcMemory_Outstanding()); +} + +LONGBOW_TEST_CASE(Global, metisTlvName_CreateFromCCNxName_DefaultRoute) +{ + char uri[] = "lci:/"; + CCNxName *ccnxName = ccnxName_CreateFromCString(uri); + + MetisTlvName *truth = metisTlvName_Create(default_route_name, sizeof(default_route_name)); + MetisTlvName *name = metisTlvName_CreateFromCCNxName(ccnxName); + + assertTrue(metisTlvName_Equals(truth, name), "MetisTlvName from ccnxName did not equal expected"); + metisTlvName_Release(&name); + metisTlvName_Release(&truth); + ccnxName_Release(&ccnxName); + + assertTrue(parcSafeMemory_ReportAllocation(STDOUT_FILENO) == 0, "Memory imbalance after create/destroy: %u", parcMemory_Outstanding()); +} + + +LONGBOW_TEST_CASE(Global, metisTlvName_Equals_IsEqual) +{ + MetisTlvName *a = metisTlvName_Create(encoded_name, sizeof(encoded_name)); + MetisTlvName *b = metisTlvName_Create(encoded_name, sizeof(encoded_name)); + + assertTrue(metisTlvName_Equals(a, b), "Two equal names did not compare"); + metisTlvName_Release(&a); + metisTlvName_Release(&b); +} + +LONGBOW_TEST_CASE(Global, metisTlvName_Equals_SameCountDifferentBytes) +{ + MetisTlvName *a = metisTlvName_Create(encoded_name, sizeof(encoded_name)); + MetisTlvName *b = metisTlvName_Create(second_name, sizeof(second_name)); + + assertFalse(metisTlvName_Equals(a, b), "Two names with same # component but differnet bytes compared the same."); + metisTlvName_Release(&a); + metisTlvName_Release(&b); +} + +LONGBOW_TEST_CASE(Global, metisTlvName_Equals_DifferentCount) +{ + MetisTlvName *a = metisTlvName_Create(encoded_name, sizeof(encoded_name)); + MetisTlvName *b = metisTlvName_Create(prefixOf_name, sizeof(prefixOf_name)); + + assertFalse(metisTlvName_Equals(a, b), "Two names with different # component compared the same."); + metisTlvName_Release(&a); + metisTlvName_Release(&b); +} + +int +compareWrapper(void *a, void *b) +{ + return metisTlvName_Compare((MetisTlvName *) a, (MetisTlvName *) b); +} + +LONGBOW_TEST_CASE(Global, metisTlvName_Compare) +{ + CCNxName *basename = ccnxName_CreateFromCString("lci:/middle/of/6=the"); + CCNxName *equal_1 = ccnxName_CreateFromCString("lci:/middle/of/6=the"); + CCNxName *defaultRoute = ccnxName_CreateFromCString("lci:/"); + CCNxName *lesser_by_count = ccnxName_CreateFromCString("lci:/middle/of"); + CCNxName *lesser_by_value = ccnxName_CreateFromCString("lci:/middle/of/6=th"); + CCNxName *lesser_by_type_2 = ccnxName_CreateFromCString("lci:/middle/of/2=the"); + CCNxName *greater_by_count = ccnxName_CreateFromCString("lci:/middle/of/the/road"); + CCNxName *greater_by_type = ccnxName_CreateFromCString("lci:/middle/of/7=the"); + CCNxName *greater_by_value = ccnxName_CreateFromCString("lci:/middle/of/the/town"); + CCNxName *greater_2 = ccnxName_CreateFromCString("lci:/nox/arcana/occulta"); + + void *equivalent[] = { equal_1, NULL }; + void *lesser[] = { defaultRoute, lesser_by_count, lesser_by_type_2, lesser_by_value, NULL }; + void *greater[] = { greater_by_count, greater_by_type, greater_by_value, greater_2, NULL }; + + MetisTlvName *tlv_basename = metisTlvName_CreateFromCCNxName(basename); + void **tlv_equivalent = parcMemory_AllocateAndClear(sizeof(equivalent) * sizeof(void *)); + assertNotNull(tlv_equivalent, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(equivalent) * sizeof(void *)); + void **tlv_lesser = parcMemory_AllocateAndClear(sizeof(lesser) * sizeof(void *)); + assertNotNull(tlv_lesser, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(lesser) * sizeof(void *)); + void **tlv_greater = parcMemory_AllocateAndClear(sizeof(greater) * sizeof(void *)); + assertNotNull(tlv_greater, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(greater) * sizeof(void *)); + + for (int i = 0; equivalent[i] != NULL; i++) { + tlv_equivalent[i] = metisTlvName_CreateFromCCNxName(equivalent[i]); + } + + for (int i = 0; lesser[i] != NULL; i++) { + tlv_lesser[i] = metisTlvName_CreateFromCCNxName(lesser[i]); + } + + for (int i = 0; greater[i] != NULL; i++) { + tlv_greater[i] = metisTlvName_CreateFromCCNxName(greater[i]); + } + + // Use this: + assertCompareToContract(compareWrapper, tlv_basename, tlv_equivalent, tlv_lesser, tlv_greater); + // Not this, unless you provide the necessary casts to avoid warnings. + // longbow_AssertCompareToContract(compareWrapper, tlv_basename, tlv_equivalent, tlv_lesser, tlv_greater); + + for (int i = 0; equivalent[i] != NULL; i++) { + ccnxName_Release((CCNxName **) &equivalent[i]); + metisTlvName_Release((MetisTlvName **) &tlv_equivalent[i]); + } + + for (int i = 0; lesser[i] != NULL; i++) { + ccnxName_Release((CCNxName **) &lesser[i]); + metisTlvName_Release((MetisTlvName **) &tlv_lesser[i]); + } + + for (int i = 0; greater[i] != NULL; i++) { + ccnxName_Release((CCNxName **) &greater[i]); + metisTlvName_Release((MetisTlvName **) &tlv_greater[i]); + } + + ccnxName_Release(&basename); + metisTlvName_Release(&tlv_basename); + parcMemory_Deallocate((void **) &tlv_equivalent); + parcMemory_Deallocate((void **) &tlv_greater); + parcMemory_Deallocate((void **) &tlv_lesser); +} + +LONGBOW_TEST_CASE(Global, metisTlvName_Compare_DefaultRoute) +{ + CCNxName *defaultRoute = ccnxName_CreateFromCString("lci:/"); + MetisTlvName *metisDefaultRoute = metisTlvName_CreateFromCCNxName(defaultRoute); + + // THis name cannot be constructed via CCNxName, so do it as a byte array + // Empty name with "0" type + uint8_t shortest[] = { 0x00, 0x00, 0x00, 4, + 0x00, 0x00, 0x00, 0 }; + + MetisTlvName *metisShortest = metisTlvName_Create(shortest, sizeof(shortest)); + + int compare = metisTlvName_Compare(metisDefaultRoute, metisShortest); + assertTrue(compare < 0, "Default route should have compared less than shortest name, compared = %d", compare); + + metisTlvName_Release(&metisShortest); + metisTlvName_Release(&metisDefaultRoute); + ccnxName_Release(&defaultRoute); +} + +LONGBOW_TEST_CASE(Global, metisTlvName_Compare_DefaultRoute_Binary) +{ + // The empty name (default route) + uint8_t defaultRoute[] = { 0x00, 0x00, 0x00, 0}; + MetisTlvName *metisDefaultRoute = metisTlvName_Create(defaultRoute, sizeof(defaultRoute)); + + // THis name cannot be constructed via CCNxName, so do it as a byte array + // Empty name with "0" type + uint8_t shortest[] = { 0x00, 0x00, 0x00, 4, + 0x00, 0x00, 0x00, 0 }; + + MetisTlvName *metisShortest = metisTlvName_Create(shortest, sizeof(shortest)); + + int compare = metisTlvName_Compare(metisDefaultRoute, metisShortest); + assertTrue(compare < 0, "Default route should have compared less than shortest name, compared = %d", compare); + + metisTlvName_Release(&metisShortest); + metisTlvName_Release(&metisDefaultRoute); +} + +LONGBOW_TEST_CASE(Global, metisTlvName_HashCode) +{ + // first, compute the hashes of the name + uint32_t hash_0 = parcHash32_Data(&encoded_name[0], 9); + uint32_t hash_1 = parcHash32_Data_Cumulative(&encoded_name[ 9], 8, hash_0); + uint32_t hash_2 = parcHash32_Data_Cumulative(&encoded_name[17], 6, hash_1); + + MetisTlvName *name = metisTlvName_Create(encoded_name, sizeof(encoded_name)); + + uint32_t test_hash; + test_hash = metisTlvName_HashCode(name); + assertTrue(test_hash == hash_2, "Incorrect hash for segment %d, expected %08X got %08X", 2, hash_2, test_hash); + + metisTlvName_Release(&name); +} + +LONGBOW_TEST_CASE(Global, metisTlvName_Acquire_CopyAtMost0) +{ + unsigned copyLength = 0; + MetisTlvName *name = metisTlvName_Create(encoded_name, sizeof(encoded_name)); + MetisTlvName *copy = metisTlvName_Slice(name, copyLength); + + // hash of a 0-length name is 0 + uint32_t hash_0 = 0; + + assertTrue(_getRefCount(name) == 2, "Wrong refcount in name, expected %u got %u", 2, _getRefCount(name)); + assertTrue(_getRefCount(copy) == 2, "Wrong refcount in copy, expected %u got %u", 2, _getRefCount(copy)); + assertTrue(copy->segmentArrayLength == copyLength, "Wrong array length, expected %u got %zu", copyLength, copy->segmentArrayLength); + uint32_t test_hash = metisTlvName_HashCode(copy); + assertTrue(test_hash == hash_0, "Incorrect hash for segment %d, expected %08X got %08X", 0, hash_0, test_hash); + + metisTlvName_Release(©); + assertTrue(_getRefCount(name) == 1, "Wrong refcount in name, expected %u got %u", 1, _getRefCount(name)); + metisTlvName_Release(&name); +} + +LONGBOW_TEST_CASE(Global, metisTlvName_Acquire_CopyAtMost1) +{ + unsigned copyLength = 1; + MetisTlvName *name = metisTlvName_Create(encoded_name, sizeof(encoded_name)); + MetisTlvName *copy = metisTlvName_Slice(name, copyLength); + uint32_t hash_0 = parcHash32_Data(&encoded_name[0], 9); + + assertTrue(_getRefCount(name) == 2, "Wrong refcount in name, expected %u got %u", 2, _getRefCount(name)); + assertTrue(_getRefCount(copy) == 2, "Wrong refcount in copy, expected %u got %u", 2, _getRefCount(copy)); + assertTrue(copy->segmentArrayLength == copyLength, "Wrong array length, expected %u got %zu", copyLength, copy->segmentArrayLength); + uint32_t test_hash = metisTlvName_HashCode(copy); + assertTrue(test_hash == hash_0, "Incorrect hash for segment %d, expected %08X got %08X", 0, hash_0, test_hash); + + metisTlvName_Release(©); + assertTrue(_getRefCount(name) == 1, "Wrong refcount in name, expected %u got %u", 1, _getRefCount(name)); + metisTlvName_Release(&name); +} + +LONGBOW_TEST_CASE(Global, metisTlvName_Acquire_CopyAtMost2) +{ + unsigned copyLength = 2; + MetisTlvName *name = metisTlvName_Create(encoded_name, sizeof(encoded_name)); + MetisTlvName *copy = metisTlvName_Slice(name, copyLength); + uint32_t hash_0 = parcHash32_Data(&encoded_name[0], 9); + uint32_t hash_1 = parcHash32_Data_Cumulative(&encoded_name[ 9], 8, hash_0); + + assertTrue(_getRefCount(name) == 2, "Wrong refcount in name, expected %u got %u", 2, _getRefCount(name)); + assertTrue(_getRefCount(copy) == 2, "Wrong refcount in copy, expected %u got %u", 2, _getRefCount(copy)); + assertTrue(copy->segmentArrayLength == copyLength, "Wrong array length, expected %u got %zu", copyLength, copy->segmentArrayLength); + uint32_t test_hash = metisTlvName_HashCode(copy); + assertTrue(test_hash == hash_1, "Incorrect hash for segment %d, expected %08X got %08X", 1, hash_1, test_hash); + + metisTlvName_Release(©); + assertTrue(_getRefCount(name) == 1, "Wrong refcount in name, expected %u got %u", 1, _getRefCount(name)); + metisTlvName_Release(&name); +} + +LONGBOW_TEST_CASE(Global, metisTlvName_Acquire_CopyAtMostAll) +{ + unsigned copyLength = 3; + MetisTlvName *name = metisTlvName_Create(encoded_name, sizeof(encoded_name)); + MetisTlvName *copy = metisTlvName_Slice(name, UINT_MAX); + uint32_t hash_0 = parcHash32_Data(&encoded_name[0], 9); + uint32_t hash_1 = parcHash32_Data_Cumulative(&encoded_name[ 9], 8, hash_0); + uint32_t hash_2 = parcHash32_Data_Cumulative(&encoded_name[17], 6, hash_1); + + assertTrue(_getRefCount(name) == 2, "Wrong refcount in name, expected %u got %u", 2, _getRefCount(name)); + assertTrue(_getRefCount(copy) == 2, "Wrong refcount in copy, expected %u got %u", 2, _getRefCount(copy)); + assertTrue(copy->segmentArrayLength == copyLength, "Wrong array length, expected %u got %zu", copyLength, copy->segmentArrayLength); + uint32_t test_hash = metisTlvName_HashCode(copy); + assertTrue(test_hash == hash_2, "Incorrect hash for segment %d, expected %08X got %08X", 2, hash_2, test_hash); + + metisTlvName_Release(©); + assertTrue(_getRefCount(name) == 1, "Wrong refcount in name, expected %u got %u", 1, _getRefCount(name)); + metisTlvName_Release(&name); +} + +LONGBOW_TEST_CASE(Global, metisTlvName_SegmentCount) +{ + MetisTlvName *a = metisTlvName_Create(encoded_name, sizeof(encoded_name)); + + size_t count = metisTlvName_SegmentCount(a); + assertTrue(count == 3, "Incorrect segment count, expected %u got %zu", 3, count); + + metisTlvName_Release(&a); +} + +LONGBOW_TEST_CASE(Global, metisTlvName_StartsWith_SelfPrefix) +{ + MetisTlvName *name = metisTlvName_Create(encoded_name, sizeof(encoded_name)); + + // a name is always a prefix of itself + bool success = metisTlvName_StartsWith(name, name); + assertTrue(success, "Name is not prefix of self in metisTlvName_StartsWith"); + metisTlvName_Release(&name); +} + +LONGBOW_TEST_CASE(Global, metisTlvName_StartsWith_IsPrefix) +{ + MetisTlvName *name = metisTlvName_Create(encoded_name, sizeof(encoded_name)); + MetisTlvName *prefix = metisTlvName_Create(prefixOf_name, sizeof(prefixOf_name)); + + bool success = metisTlvName_StartsWith(name, prefix); + assertTrue(success, "Valid prefix did not test true in metisTlvName_StartsWith"); + metisTlvName_Release(&name); + metisTlvName_Release(&prefix); +} + +LONGBOW_TEST_CASE(Global, metisTlvName_StartsWith_PrefixTooLong) +{ + MetisTlvName *name = metisTlvName_Create(encoded_name, sizeof(encoded_name)); + MetisTlvName *prefix = metisTlvName_Create(prefixOf_name, sizeof(prefixOf_name)); + + // we just reversed the prefix and name from the test metisTlvName_StartsWith_IsPrefix, + // so the prefix is longer than the name + bool success = metisTlvName_StartsWith(prefix, name); + assertFalse(success, "Invalid prefix tested true in metisTlvName_StartsWith"); + metisTlvName_Release(&name); + metisTlvName_Release(&prefix); +} + +LONGBOW_TEST_CASE(Global, metisTlvName_StartsWith_IsNotPrefix) +{ + MetisTlvName *name = metisTlvName_Create(encoded_name, sizeof(encoded_name)); + MetisTlvName *other = metisTlvName_Create(second_name, sizeof(second_name)); + + // we just reversed the prefix and name from the test metisTlvName_StartsWith_IsPrefix + bool success = metisTlvName_StartsWith(other, name); + assertFalse(success, "Invalid prefix tested true in metisTlvName_StartsWith"); + metisTlvName_Release(&name); + metisTlvName_Release(&other); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_TlvName); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/tlv/test/test_metis_TlvNameCodec.c b/metis/ccnx/forwarder/metis/tlv/test/test_metis_TlvNameCodec.c new file mode 100644 index 00000000..121adf29 --- /dev/null +++ b/metis/ccnx/forwarder/metis/tlv/test/test_metis_TlvNameCodec.c @@ -0,0 +1,172 @@ +/* + * 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_TlvNameCodec.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(tlv_NameCodec) +{ + // 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); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(tlv_NameCodec) +{ + 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(tlv_NameCodec) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, tlvName_Decode_0_Length_Name); + LONGBOW_RUN_TEST_CASE(Global, tlvName_Decode_0_Length_Segment); + LONGBOW_RUN_TEST_CASE(Global, tlvName_Decode_Good); + LONGBOW_RUN_TEST_CASE(Global, tlvName_Decode_Overflow); + LONGBOW_RUN_TEST_CASE(Global, tlvName_Decode_UnderRun); +} + +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; +} + +/** + * Buffer is 1 .. 3 bytes + */ +LONGBOW_TEST_CASE_EXPECTS(Global, tlvName_Decode_UnderRun, .event = &LongBowTrapIllegalValue) +{ + // offset 0 1 2 3 4 5 6 7 8 9 10 11 12 13 + // |-- type --|-- length --|| + uint8_t buffer[] = { 0xFF, 0x00, 0x00, 0x00, 0x04, 0xFF }; + + // This will assert + // CCNxName *name = + metisTlvNameCodec_Decode(buffer, 5, 6); +} + +/** + * Buffer exactly 0 bytes + */ +LONGBOW_TEST_CASE(Global, tlvName_Decode_0_Length_Name) +{ + // offset 0 1 2 3 4 5 6 7 8 9 10 11 12 13 + // |-- type --|-- length --|| + uint8_t buffer[] = { 0xFF, 0x00, 0x00, 0x00, 0x04, 0xFF }; + + // skip the two 0xFF bytes + // name = "lci:/%02=abcd" + CCNxName *test = metisTlvNameCodec_Decode(buffer, 5, 5); + CCNxName *truth = ccnxName_Create(); + char *nameString = ccnxName_ToString(test); + + assertTrue(ccnxName_Equals(truth, test), "Names not equal, got %s", nameString); + + parcMemory_Deallocate((void **) &nameString); + ccnxName_Release(&truth); + ccnxName_Release(&test); +} + +/** + * Buffer exactly 4 bytes + */ +LONGBOW_TEST_CASE(Global, tlvName_Decode_0_Length_Segment) +{ + // offset 0 1 2 3 4 5 6 7 8 9 10 11 12 13 + // |-- type --|-- length --|-- type --|-- length --|| + uint8_t buffer[] = { 0xFF, 0x00, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0xFF }; + + // skip the two 0xFF bytes + // name = "lci:/%02=abcd" + CCNxName *test = metisTlvNameCodec_Decode(buffer, 5, 9); +// CCNxName *truth = ccnxName_CreateFromCString("lci:/%02="); + CCNxName *truth = ccnxName_CreateFromCString("lci:/2="); + char *nameString = ccnxName_ToString(test); + + assertTrue(ccnxName_Equals(truth, test), "Names not equal, got %s", nameString); + + parcMemory_Deallocate((void **) &nameString); + ccnxName_Release(&truth); + ccnxName_Release(&test); +} + +/** + * A good, normal name + */ +LONGBOW_TEST_CASE(Global, tlvName_Decode_Good) +{ + // offset 0 1 2 3 4 5 6 7 8 9 10 11 12 13 + // |-- type --|-- length --|-- type --|-- length --| ----- value -----| + uint8_t buffer[] = { 0xFF, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 0x04, 'a', 'b', 'c', 'd', 0xFF }; + + // skip the two 0xFF bytes + // name = "lci:/%02=abcd" + CCNxName *test = metisTlvNameCodec_Decode(buffer, 5, 13); + +// CCNxName *truth = ccnxName_CreateFromCString("lci:/%02=abcd"); + CCNxName *truth = ccnxName_CreateFromCString("lci:/2=abcd"); + char *nameString = ccnxName_ToString(test); + + assertTrue(ccnxName_Equals(truth, test), "Names not equal, got %s", nameString); + + parcMemory_Deallocate((void **) &nameString); + ccnxName_Release(&truth); + ccnxName_Release(&test); +} + +/** + * The name component length shoots beyond the end of the buffer. Byte 8 is "5" instead of "4". + */ +LONGBOW_TEST_CASE_EXPECTS(Global, tlvName_Decode_Overflow, .event = &LongBowTrapIllegalValue) +{ + // offset 0 1 2 3 4 5 6 7 8 9 10 11 12 13 + // |-- type --|-- length --|-- type --|-- length --| ----- value -----| + uint8_t buffer[] = { 0xFF, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 0x05, 'a', 'b', 'c', 'd', 0xFF }; + + // This will trap because the length 5 will go beyond 12 + metisTlvNameCodec_Decode(buffer, 5, 13); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(tlv_NameCodec); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/tlv/test/test_metis_TlvSchemaV0.c b/metis/ccnx/forwarder/metis/tlv/test/test_metis_TlvSchemaV0.c new file mode 100644 index 00000000..bfb5a47c --- /dev/null +++ b/metis/ccnx/forwarder/metis/tlv/test/test_metis_TlvSchemaV0.c @@ -0,0 +1,342 @@ +/* + * 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_TlvSchemaV0.c" +#include "../metis_TlvSkeleton.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> + +static void +verifyInterestPerHop(MetisTlvSkeleton *skeleton) +{ + MetisTlvExtent extent = metisTlvSkeleton_GetHopLimit(skeleton); + assertTrue(extent.offset == 12, "Incorrect hopLimit offset, expected %u got %u", 12, extent.offset); + assertTrue(extent.length == 1, "Incorrect hopLimit length, expected %u got %u", 1, extent.length); +} + +static void +verifyInterestSkeleton(MetisTlvSkeleton *skeleton) +{ + MetisTlvExtent nameExtent = metisTlvSkeleton_GetName(skeleton); + assertTrue(nameExtent.offset == 37, "Incorrect name offset, expected %u got %u", 37, nameExtent.offset); + assertTrue(nameExtent.length == 17, "Incorrect name length, expected %u got %u", 17, nameExtent.length); + + MetisTlvExtent keyidExtent = metisTlvSkeleton_GetKeyId(skeleton); + assertTrue(keyidExtent.offset == 58, "Incorrect keyId offset, expected %u got %u", 58, keyidExtent.offset); + assertTrue(keyidExtent.length == 4, "Incorrect keyId length, expected %u got %u", 4, keyidExtent.length); + + MetisTlvExtent objHashExtent = metisTlvSkeleton_GetObjectHash(skeleton); + assertTrue(objHashExtent.offset == 66, "Incorrect objectHash offset, expected %u got %u", 66, objHashExtent.offset); + assertTrue(objHashExtent.length == 6, "Incorrect objectHash length, expected %u got %u", 6, objHashExtent.length); + + MetisTlvExtent lifetimeExtent = metisTlvSkeleton_GetInterestLifetime(skeleton); + assertTrue(lifetimeExtent.offset == 81, "Incorrect interestLifetime offset, expected %u got %u", 81, lifetimeExtent.offset); + assertTrue(lifetimeExtent.length == 2, "Incorrect interestLifetime length, expected %u got %u", 2, lifetimeExtent.length); +} + +static void +verifyObjectPerHop(MetisTlvSkeleton *skeleton) +{ + MetisTlvExtent hoplimitExtent = metisTlvSkeleton_GetHopLimit(skeleton); + assertTrue(hoplimitExtent.offset == 12, "Incorrect hopLimit offset, expected %u got %u", 12, hoplimitExtent.offset); + assertTrue(hoplimitExtent.length == 1, "Incorrect hopLimit length, expected %u got %u", 1, hoplimitExtent.length); +} + +static void +verifyObjectSkeleton(MetisTlvSkeleton *skeleton) +{ + MetisTlvExtent nameExtent = metisTlvSkeleton_GetName(skeleton); + assertTrue(nameExtent.offset == metisTestDataV0_EncodedObject_name.offset, "Incorrect name offset, expected %u got %u", metisTestDataV0_EncodedObject_name.offset, nameExtent.offset); + assertTrue(nameExtent.length == metisTestDataV0_EncodedObject_name.length, "Incorrect name length, expected %u got %u", metisTestDataV0_EncodedObject_name.length, nameExtent.length); + + MetisTlvExtent keyidExtent = metisTlvSkeleton_GetKeyId(skeleton); + assertTrue(keyidExtent.offset == metisTestDataV0_EncodedObject_keyid.offset, "Incorrect keyId offset, expected %u got %u", metisTestDataV0_EncodedObject_keyid.offset, keyidExtent.offset); + assertTrue(keyidExtent.length == metisTestDataV0_EncodedObject_keyid.length, "Incorrect keyId length, expected %u got %u", metisTestDataV0_EncodedObject_keyid.length, keyidExtent.length); +} + +LONGBOW_TEST_RUNNER(metis_TlvSchemaV0) +{ + // 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_TlvSchemaV0) +{ + 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_TlvSchemaV0) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisTlvSchemaV0_ComputeContentObjectHash); + LONGBOW_RUN_TEST_CASE(Global, metisTlvSchemaV0_Skeleton_Interest); + LONGBOW_RUN_TEST_CASE(Global, metisTlvSchemaV0_Skeleton_Object); + LONGBOW_RUN_TEST_CASE(Global, metisTlvSchemaV0_Skeleton_Control); + + LONGBOW_RUN_TEST_CASE(Global, metisTlvSchemaV0_IsPacketTypeInterest_True); + LONGBOW_RUN_TEST_CASE(Global, metisTlvSchemaV0_IsPacketTypeContentObject_True); + LONGBOW_RUN_TEST_CASE(Global, metisTlvSchemaV0_IsPacketTypeInterest_False); + LONGBOW_RUN_TEST_CASE(Global, metisTlvSchemaV0_IsPacketTypeContentObject_False); + + LONGBOW_RUN_TEST_CASE(Global, metisTlvSchemaV0_EncodeControlPlaneInformation); +} + +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, metisTlvSchemaV0_ComputeContentObjectHash) +{ + _MetisTlvFixedHeaderV0 *hdr = (_MetisTlvFixedHeaderV0 *) metisTestDataV0_EncodedObject; + size_t headerLength = htons(hdr->headerLength); + size_t endHeaders = FIXED_HEADER_LEN + headerLength; + size_t endPacket = metisTlv_TotalPacketLength((uint8_t *) hdr); + + uint8_t *start = &metisTestDataV0_EncodedObject[endHeaders]; + size_t length = endPacket - endHeaders; + + PARCCryptoHasher *hasher = parcCryptoHasher_Create(PARCCryptoHashType_SHA256); + parcCryptoHasher_Init(hasher); + parcCryptoHasher_UpdateBytes(hasher, start, length); + + PARCCryptoHash *hash_truth = parcCryptoHasher_Finalize(hasher); + + PARCCryptoHash *hash_test = _computeContentObjectHash(metisTestDataV0_EncodedObject); + + assertTrue(parcCryptoHash_Equals(hash_truth, hash_test), + "Content object digests did not match: truth %s test %s", + parcBuffer_ToString(parcCryptoHash_GetDigest(hash_truth)), + parcBuffer_ToString(parcCryptoHash_GetDigest(hash_test))); + + parcCryptoHash_Release(&hash_truth); + parcCryptoHash_Release(&hash_test); + parcCryptoHasher_Release(&hasher); +} + +LONGBOW_TEST_CASE(Global, metisTlvSchemaV0_Skeleton_Interest) +{ + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize(skeleton, &MetisTlvSchemaV0_Ops, metisTestDataV0_EncodedInterest, logger); + _parse(&opaque); + verifyInterestPerHop(&opaque); + verifyInterestSkeleton(&opaque); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Global, metisTlvSchemaV0_Skeleton_Object) +{ + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize(skeleton, &MetisTlvSchemaV0_Ops, metisTestDataV0_EncodedObject, logger); + _parse(&opaque); + verifyObjectPerHop(&opaque); + verifyObjectSkeleton(&opaque); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Global, metisTlvSchemaV0_Skeleton_Control) +{ + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize(skeleton, &MetisTlvSchemaV0_Ops, metisTestDataV0_CPIMessage, logger); + _parse(&opaque); + + MetisTlvExtent cpiExtent = metisTlvSkeleton_GetCPI(&opaque); + assertTrue(cpiExtent.offset == 12, "cpi offset wrong, got %u expected %u", cpiExtent.offset, 12); + assertTrue(cpiExtent.length == 47, "cpi length wrong, got %u expected %u", cpiExtent.length, 47); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Global, metisTlvSchemaV0_IsPacketTypeInterest_True) +{ + bool result = _isPacketTypeInterest(metisTestDataV0_EncodedInterest); + assertTrue(result, "Interest packet type did not return true for IsInterest test"); +} + +LONGBOW_TEST_CASE(Global, metisTlvSchemaV0_IsPacketTypeContentObject_True) +{ + bool result = _isPacketTypeContentObject(metisTestDataV0_EncodedObject); + assertTrue(result, "ContentObject packet type did not return true for IsContentObject test"); +} + +LONGBOW_TEST_CASE(Global, metisTlvSchemaV0_IsPacketTypeInterest_False) +{ + bool result = _isPacketTypeInterest(metisTestDataV0_EncodedObject); + assertFalse(result, "ContentObject packet type did not return false for IsInterest test"); +} + +LONGBOW_TEST_CASE(Global, metisTlvSchemaV0_IsPacketTypeContentObject_False) +{ + bool result = _isPacketTypeContentObject(metisTestDataV0_EncodedInterest); + assertFalse(result, "Interest packet type did not return false for IsContentObject test"); +} + +LONGBOW_TEST_CASE(Global, metisTlvSchemaV0_EncodeControlPlaneInformation) +{ + CCNxControl *control = ccnxControl_CreateRouteListRequest(); + PARCBuffer *buffer = _encodeControlPlaneInformation(control); + PARCBuffer *truth = parcBuffer_Flip(parcBuffer_PutArray(parcBuffer_Allocate(sizeof(metisTestDataV0_CPIMessage)), sizeof(metisTestDataV0_CPIMessage), metisTestDataV0_CPIMessage)); + + assertTrue(parcBuffer_Equals(truth, buffer), "Buffers not equal") + { + printf("expected:\n"); + parcBuffer_Display(truth, 3); + printf("got:\n"); + parcBuffer_Display(buffer, 3); + } + ccnxControl_Release(&control); + parcBuffer_Release(&truth); + parcBuffer_Release(&buffer); +} + + +// ====================================================== + +LONGBOW_TEST_FIXTURE(Local) +{ + // computeHash and parseName Auth are tested called through by other tests + + LONGBOW_RUN_TEST_CASE(Local, _parseInterestV0); + LONGBOW_RUN_TEST_CASE(Local, _parseObjectV0); + LONGBOW_RUN_TEST_CASE(Local, _parsePerHopV0_Interest); + LONGBOW_RUN_TEST_CASE(Local, _parsePerHopV0_Object); +} + +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, _parseInterestV0) +{ + MetisTlvSkeleton skeleton; + memset(&skeleton, 0, sizeof(skeleton)); + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize((_InternalSkeleton *) &skeleton, &MetisTlvSchemaV0_Ops, metisTestDataV0_EncodedInterest, logger); + + _MetisTlvFixedHeaderV0 *hdr = (_MetisTlvFixedHeaderV0 *) metisTestDataV0_EncodedInterest; + size_t headerLength = htons(hdr->headerLength); + size_t endHeaders = FIXED_HEADER_LEN + headerLength; + size_t endPacket = metisTlv_TotalPacketLength((uint8_t *) hdr); + + _parseInterestV0(metisTestDataV0_EncodedInterest, endHeaders, endPacket, &skeleton); + verifyInterestSkeleton(&skeleton); + + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Local, _parseObjectV0) +{ + MetisTlvSkeleton skeleton; + memset(&skeleton, 0, sizeof(skeleton)); + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize((_InternalSkeleton *) &skeleton, &MetisTlvSchemaV0_Ops, metisTestDataV0_EncodedInterest, logger); + + _MetisTlvFixedHeaderV0 *hdr = (_MetisTlvFixedHeaderV0 *) metisTestDataV0_EncodedObject; + size_t headerLength = htons(hdr->headerLength); + size_t endHeaders = FIXED_HEADER_LEN + headerLength; + size_t endPacket = metisTlv_TotalPacketLength((uint8_t *) hdr); + + _parseObjectV0(metisTestDataV0_EncodedObject, endHeaders, endPacket, &skeleton); + verifyObjectSkeleton(&skeleton); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Local, _parsePerHopV0_Interest) +{ + MetisTlvSkeleton skeleton; + memset(&skeleton, 0, sizeof(skeleton)); + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize((_InternalSkeleton *) &skeleton, &MetisTlvSchemaV0_Ops, metisTestDataV0_EncodedInterest, logger); + + _parsePerHopV0(metisTestDataV0_EncodedInterest, 8, 29, &skeleton); + verifyInterestPerHop(&skeleton); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Local, _parsePerHopV0_Object) +{ + MetisTlvSkeleton skeleton; + memset(&skeleton, 0, sizeof(skeleton)); + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize((_InternalSkeleton *) &skeleton, &MetisTlvSchemaV0_Ops, metisTestDataV0_EncodedInterest, logger); + + _parsePerHopV0(metisTestDataV0_EncodedObject, 8, 29, &skeleton); + verifyObjectPerHop(&skeleton); + metisLogger_Release(&logger); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_TlvSchemaV0); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/tlv/test/test_metis_TlvSchemaV1.c b/metis/ccnx/forwarder/metis/tlv/test/test_metis_TlvSchemaV1.c new file mode 100644 index 00000000..75dcd68d --- /dev/null +++ b/metis/ccnx/forwarder/metis/tlv/test/test_metis_TlvSchemaV1.c @@ -0,0 +1,518 @@ +/* + * 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_TlvSchemaV1.c" +#include "../metis_TlvSkeleton.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <parc/logging/parc_LogReporterTextStdout.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h> + + +LONGBOW_TEST_RUNNER(metis_TlvSchemaV1) +{ + LONGBOW_RUN_TEST_FIXTURE(TlvOpsFunctions); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_TlvSchemaV1) +{ + 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_TlvSchemaV1) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ====================================================== + +LONGBOW_TEST_FIXTURE(TlvOpsFunctions) +{ + LONGBOW_RUN_TEST_CASE(TlvOpsFunctions, _parse_Interest); + LONGBOW_RUN_TEST_CASE(TlvOpsFunctions, _parse_ContentObject); + LONGBOW_RUN_TEST_CASE(TlvOpsFunctions, _parse_Control); + LONGBOW_RUN_TEST_CASE(TlvOpsFunctions, _parse_InterestReturn); + LONGBOW_RUN_TEST_CASE(TlvOpsFunctions, _parse_Unknown); + LONGBOW_RUN_TEST_CASE(TlvOpsFunctions, _parse_HopByHopFragment); + + LONGBOW_RUN_TEST_CASE(TlvOpsFunctions, _computeContentObjectHash); + LONGBOW_RUN_TEST_CASE(TlvOpsFunctions, _encodeControlPlaneInformation); + LONGBOW_RUN_TEST_CASE(TlvOpsFunctions, _fixedHeaderLength); + LONGBOW_RUN_TEST_CASE(TlvOpsFunctions, _totalHeaderLength); + LONGBOW_RUN_TEST_CASE(TlvOpsFunctions, _totalPacketLength); + LONGBOW_RUN_TEST_CASE(TlvOpsFunctions, _isPacketTypeInterest); + LONGBOW_RUN_TEST_CASE(TlvOpsFunctions, _isPacketTypeContentObject); + LONGBOW_RUN_TEST_CASE(TlvOpsFunctions, _isPacketTypeInterestReturn); + LONGBOW_RUN_TEST_CASE(TlvOpsFunctions, _isPacketTypeControl); + LONGBOW_RUN_TEST_CASE(TlvOpsFunctions, _isPacketTypeHopByHopFragment); +} + +LONGBOW_TEST_FIXTURE_SETUP(TlvOpsFunctions) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(TlvOpsFunctions) +{ + 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(TlvOpsFunctions, _parse_Interest) +{ + MetisTlvSkeleton skeleton; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize((_InternalSkeleton *) &skeleton, &MetisTlvSchemaV1_Ops, metisTestDataV1_Interest_AllFields, logger); + bool success = _parse(&skeleton); + assertTrue(success, "_parse(Interest) did not succeed"); + + // spot check + { + MetisTlvExtent trueExtent = { .offset = 4, .length = 1 }; + MetisTlvExtent testExtent = metisTlvSkeleton_GetHopLimit(&skeleton); + assertTrue(metisTlvExtent_Equals(&trueExtent, &testExtent), "Wrong hoplimit extent, expected {%u, %u} got {%u, %u}", + trueExtent.offset, trueExtent.length, testExtent.offset, testExtent.length); + } + + { + MetisTlvExtent trueExtent = { .offset = 12, .length = 2 }; + MetisTlvExtent testExtent = metisTlvSkeleton_GetInterestLifetime(&skeleton); + assertTrue(metisTlvExtent_Equals(&trueExtent, &testExtent), "Wrong interest lifetime extent, expected {%u, %u} got {%u, %u}", + trueExtent.offset, trueExtent.length, testExtent.offset, testExtent.length); + } + + { + MetisTlvExtent trueExtent = { .offset = 22, .length = 8 }; + MetisTlvExtent testExtent = metisTlvSkeleton_GetName(&skeleton); + assertTrue(metisTlvExtent_Equals(&trueExtent, &testExtent), "Wrong name extent, expected {%u, %u} got {%u, %u}", + trueExtent.offset, trueExtent.length, testExtent.offset, testExtent.length); + } + + { + MetisTlvExtent trueExtent = { .offset = 34, .length = 16 }; + MetisTlvExtent testExtent = metisTlvSkeleton_GetKeyId(&skeleton); + assertTrue(metisTlvExtent_Equals(&trueExtent, &testExtent), "Wrong keyid extent, expected {%u, %u} got {%u, %u}", + trueExtent.offset, trueExtent.length, testExtent.offset, testExtent.length); + } + + { + MetisTlvExtent trueExtent = { .offset = 54, .length = 32 }; + MetisTlvExtent testExtent = metisTlvSkeleton_GetObjectHash(&skeleton); + assertTrue(metisTlvExtent_Equals(&trueExtent, &testExtent), "Wrong objhash extent, expected {%u, %u} got {%u, %u}", + trueExtent.offset, trueExtent.length, testExtent.offset, testExtent.length); + } + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(TlvOpsFunctions, _parse_ContentObject) +{ + MetisTlvSkeleton skeleton; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize((_InternalSkeleton *) &skeleton, &MetisTlvSchemaV1_Ops, metisTestDataV1_ContentObject_NameA_Crc32c, logger); + bool success = _parse(&skeleton); + assertTrue(success, "_parse(ContentObject) did not succeed"); + + // spot check + { + MetisTlvExtent trueExtent = { .offset = 36, .length = 8 }; + MetisTlvExtent testExtent = metisTlvSkeleton_GetCacheTimeHeader(&skeleton); + assertTrue(metisTlvExtent_Equals(&trueExtent, &testExtent), "Wrong cache time extent, expected {%u, %u} got {%u, %u}", + trueExtent.offset, trueExtent.length, testExtent.offset, testExtent.length); + } + + { + MetisTlvExtent trueExtent = { .offset = 52, .length = 17 }; + MetisTlvExtent testExtent = metisTlvSkeleton_GetName(&skeleton); + assertTrue(metisTlvExtent_Equals(&trueExtent, &testExtent), "Wrong name extent, expected {%u, %u} got {%u, %u}", + trueExtent.offset, trueExtent.length, testExtent.offset, testExtent.length); + } + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(TlvOpsFunctions, _parse_Control) +{ + MetisTlvSkeleton skeleton; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize((_InternalSkeleton *) &skeleton, &MetisTlvSchemaV1_Ops, metisTestDataV1_CPI_AddRoute_Crc32c, logger); + bool success = _parse(&skeleton); + assertTrue(success, "_parse(Control) did not succeed"); + + // spot check + { + MetisTlvExtent trueExtent = { .offset = 12, .length = 154 }; + MetisTlvExtent testExtent = metisTlvSkeleton_GetCPI(&skeleton); + assertTrue(metisTlvExtent_Equals(&trueExtent, &testExtent), "Wrong CPI extent, expected {%u, %u} got {%u, %u}", + trueExtent.offset, trueExtent.length, testExtent.offset, testExtent.length); + } + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(TlvOpsFunctions, _parse_HopByHopFragment) +{ + MetisTlvSkeleton skeleton; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize((_InternalSkeleton *) &skeleton, &MetisTlvSchemaV1_Ops, metisTestDataV1_HopByHopFrag_BeginEnd, logger); + bool success = _parse(&skeleton); + assertTrue(success, "_parse(Control) did not succeed"); + + // spot check + { + MetisTlvExtent trueExtent = { .offset = 12, .length = sizeof(metisTestDataV1_HopByHopFrag_BeginEnd_Fragment) }; + MetisTlvExtent testExtent = metisTlvSkeleton_GetFragmentPayload(&skeleton); + assertTrue(metisTlvExtent_Equals(&trueExtent, &testExtent), "Wrong fragment payload extent, expected {%u, %u} got {%u, %u}", + trueExtent.offset, trueExtent.length, testExtent.offset, testExtent.length); + } + metisLogger_Release(&logger); +} + + +LONGBOW_TEST_CASE(TlvOpsFunctions, _parse_InterestReturn) +{ + // not implemented yet +} + +LONGBOW_TEST_CASE(TlvOpsFunctions, _parse_Unknown) +{ + uint8_t unknown[] = { 0x01, 0x77, 0x00, 8, 0x00, 0x00, 0x00, 8 }; + + MetisTlvSkeleton skeleton; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize((_InternalSkeleton *) &skeleton, &MetisTlvSchemaV1_Ops, unknown, logger); + bool success = _parse(&skeleton); + assertFalse(success, "_parse(Unknown) should have failed"); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(TlvOpsFunctions, _computeContentObjectHash) +{ + _MetisTlvFixedHeaderV1 *hdr = (_MetisTlvFixedHeaderV1 *) metisTestDataV1_ContentObject_NameA_Crc32c; + size_t endHeaders = _totalHeaderLength(metisTestDataV1_ContentObject_NameA_Crc32c); + size_t endPacket = _totalPacketLength((uint8_t *) hdr); + + uint8_t *start = &metisTestDataV1_ContentObject_NameA_Crc32c[endHeaders]; + size_t length = endPacket - endHeaders; + + PARCCryptoHasher *hasher = parcCryptoHasher_Create(PARCCryptoHashType_SHA256); + parcCryptoHasher_Init(hasher); + parcCryptoHasher_UpdateBytes(hasher, start, length); + + PARCCryptoHash *hash_truth = parcCryptoHasher_Finalize(hasher); + + PARCCryptoHash *hash_test = _computeContentObjectHash(metisTestDataV1_ContentObject_NameA_Crc32c); + + assertTrue(parcCryptoHash_Equals(hash_truth, hash_test), + "Content object digests did not match") + { + parcBuffer_Display(parcCryptoHash_GetDigest(hash_truth), 3), + parcBuffer_Display(parcCryptoHash_GetDigest(hash_test), 3); + } + + parcCryptoHash_Release(&hash_truth); + parcCryptoHash_Release(&hash_test); + parcCryptoHasher_Release(&hasher); +} + +LONGBOW_TEST_CASE(TlvOpsFunctions, _encodeControlPlaneInformation) +{ + CCNxControl *control = ccnxControl_CreateRouteListRequest(); + + PARCBuffer *buffer = _encodeControlPlaneInformation(control); + ccnxControl_Release(&control); + + assertNotNull(buffer, "Got null encoding buffer"); + uint8_t *overlay = parcBuffer_Overlay(buffer, 0); + + assertTrue(_isPacketTypeControl(overlay), "PacketType is not Control"); + parcBuffer_Release(&buffer); +} + +LONGBOW_TEST_CASE(TlvOpsFunctions, _fixedHeaderLength) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5 }; + size_t test = _fixedHeaderLength(packet); + assertTrue(test == sizeof(_MetisTlvFixedHeaderV1), "wrong fixed header lenght, expected %zu got %zu", sizeof(_MetisTlvFixedHeaderV1), test); +} + +LONGBOW_TEST_CASE(TlvOpsFunctions, _totalHeaderLength) +{ + size_t test = _totalHeaderLength(metisTestDataV1_ContentObject_NameA_Crc32c); + assertTrue(test == metisTestDataV1_ContentObject_NameA_Crc32c[7], "Wrong header length, expected %u got %zu", metisTestDataV1_ContentObject_NameA_Crc32c[7], test); +} + +LONGBOW_TEST_CASE(TlvOpsFunctions, _totalPacketLength) +{ + size_t test = _totalPacketLength(metisTestDataV1_ContentObject_NameA_Crc32c); + assertTrue(test == sizeof(metisTestDataV1_ContentObject_NameA_Crc32c), "Wrong packet length, expected %zu got %zu", sizeof(metisTestDataV1_ContentObject_NameA_Crc32c), test); +} + +LONGBOW_TEST_CASE(TlvOpsFunctions, _isPacketTypeInterest) +{ + bool match = _isPacketTypeInterest(metisTestDataV1_Interest_AllFields); + assertTrue(match, "Interest did not match"); +} + +LONGBOW_TEST_CASE(TlvOpsFunctions, _isPacketTypeContentObject) +{ + bool match = _isPacketTypeContentObject(metisTestDataV1_ContentObject_NameA_Crc32c); + assertTrue(match, "Content object did not match"); +} + +LONGBOW_TEST_CASE(TlvOpsFunctions, _isPacketTypeInterestReturn) +{ +} + +LONGBOW_TEST_CASE(TlvOpsFunctions, _isPacketTypeControl) +{ + bool match = _isPacketTypeControl(metisTestDataV1_CPI_AddRoute_Crc32c); + assertTrue(match, "Control did not match"); +} + +LONGBOW_TEST_CASE(TlvOpsFunctions, _isPacketTypeHopByHopFragment) +{ + bool match = _isPacketTypeHopByHopFragment(metisTestDataV1_HopByHopFrag_Begin); + assertTrue(match, "HopByHop Fragment did not match"); +} + + +// ====================================================== + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _parsePerHopV1); + LONGBOW_RUN_TEST_CASE(Local, _parseSignatureParameters); + LONGBOW_RUN_TEST_CASE(Local, _parseSignatureParameters_NoKeyid); + LONGBOW_RUN_TEST_CASE(Local, _parseValidationType); + LONGBOW_RUN_TEST_CASE(Local, _parseValidationType_NotSignature); + LONGBOW_RUN_TEST_CASE(Local, _parseValidationAlg); + LONGBOW_RUN_TEST_CASE(Local, _parseObjectV1); + LONGBOW_RUN_TEST_CASE(Local, _parseInterestV1); + LONGBOW_RUN_TEST_CASE(Local, _parseMessage); + LONGBOW_RUN_TEST_CASE(Local, _computeHash); +} + +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, _parsePerHopV1) +{ +} + +LONGBOW_TEST_CASE(Local, _parseSignatureParameters) +{ + uint8_t encoded[] = { + 0x00, T_KEYID, 0x00, 6, + 0xa0, 0xa1, 0xa2, 0xa3,0xa4,0xa5, + + 0x00, T_PUBLICKEY, 0x00, 8, + 0xb1, 0xb2, 0xb3, 0xb4, + 0xb5, 0xb6, 0xb7, 0xb8, + + 0x00, T_CERT, 0x00, 8, + 0xc1, 0xc2, 0xc3, 0xc4, + 0xc5, 0xc6, 0xc7, 0xc8, + + 0x00, 0xFF, 0x00, 2, + 0xb0, 0xb1 + }; + + MetisTlvSkeleton skeleton; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize((_InternalSkeleton *) &skeleton, &MetisTlvSchemaV1_Ops, encoded, logger); + _parseSignatureParameters(encoded, 0, sizeof(encoded), &skeleton); + + MetisTlvExtent truth = { .offset = 4, .length = 6 }; + MetisTlvExtent keyid = metisTlvSkeleton_GetKeyId(&skeleton); + assertTrue(metisTlvExtent_Equals(&truth, &keyid), "Wrong extent, expected {%u, %u} got {%u, %u}", + truth.offset, truth.length, keyid.offset, keyid.length); + + // Check the public key was found. + MetisTlvExtent pubKeyTruth = { .offset = 14, .length = 8 }; + MetisTlvExtent pubKey = metisTlvSkeleton_GetPublicKey(&skeleton); + assertTrue(metisTlvExtent_Equals(&pubKeyTruth, &pubKey), "Wrong extent, expected {%u, %u} got {%u, %u}", + pubKeyTruth.offset, pubKeyTruth.length, pubKey.offset, pubKey.length); + + // Check that the cert was found. + MetisTlvExtent certTruth = { .offset = 26, .length = 8 }; + MetisTlvExtent cert = metisTlvSkeleton_GetCertificate(&skeleton); + assertTrue(metisTlvExtent_Equals(&certTruth, &cert), "Wrong extent, expected {%u, %u} got {%u, %u}", + certTruth.offset, certTruth.length, cert.offset, cert.length); + + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Local, _parseSignatureParameters_NoKeyid) +{ + uint8_t encoded[] = { + 0x00, 0xFF, 0x00, 2, + 0xb0, 0xb1 + }; + + MetisTlvSkeleton skeleton; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize((_InternalSkeleton *) &skeleton, &MetisTlvSchemaV1_Ops, encoded, logger); + _parseSignatureParameters(encoded, 0, sizeof(encoded), &skeleton); + + MetisTlvExtent keyid = metisTlvSkeleton_GetKeyId(&skeleton); + assertTrue(metisTlvExtent_Equals(&metisTlvExtent_NotFound, &keyid), "Wrong extent, expected {%u, %u} got {%u, %u}", + metisTlvExtent_NotFound.offset, metisTlvExtent_NotFound.length, keyid.offset, keyid.length); + + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Local, _parseValidationType) +{ + uint8_t encoded[] = { + 0x00, T_RSA_SHA256, 0x00, 10, + 0x00, T_KEYID, 0x00, 6, + 0xa0, 0xa1, 0xa2, 0xa3,0xa4,0xa5, + 0x00, 0xFF, 0x00, 2, + 0xb0, 0xb1 + }; + + MetisTlvSkeleton skeleton; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize((_InternalSkeleton *) &skeleton, &MetisTlvSchemaV1_Ops, encoded, logger); + _parseValidationType(encoded, 0, sizeof(encoded), &skeleton); + + MetisTlvExtent truth = { .offset = 8, .length = 6 }; + MetisTlvExtent keyid = metisTlvSkeleton_GetKeyId(&skeleton); + assertTrue(metisTlvExtent_Equals(&truth, &keyid), "Wrong extent, expected {%u, %u} got {%u, %u}", + truth.offset, truth.length, keyid.offset, keyid.length); + + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Local, _parseValidationType_NotSignature) +{ + uint8_t encoded[] = { + 0x00, 0xFF, 0x00, 10, + 0x00, T_KEYID, 0x00, 6, + 0xa0, 0xa1, 0xa2, 0xa3,0xa4,0xa5, + 0x00, 0xFF, 0x00, 2, + 0xb0, 0xb1 + }; + + MetisTlvSkeleton skeleton; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize((_InternalSkeleton *) &skeleton, &MetisTlvSchemaV1_Ops, encoded, logger); + _parseValidationType(encoded, 0, sizeof(encoded), &skeleton); + + MetisTlvExtent keyid = metisTlvSkeleton_GetKeyId(&skeleton); + assertTrue(metisTlvExtent_Equals(&metisTlvExtent_NotFound, &keyid), "Wrong extent, expected {%u, %u} got {%u, %u}", + metisTlvExtent_NotFound.offset, metisTlvExtent_NotFound.length, keyid.offset, keyid.length); + + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Local, _parseValidationAlg) +{ +} + +LONGBOW_TEST_CASE(Local, _parseObjectV1) +{ + uint8_t encoded[] = { + 0x00, 0x00, 0x00, 8, // type = name, length = 8 + 0x00, 0x02, 0x00, 4, // type = binary, length = 4 + 'c', 'o', 'o', 'l', // "cool" + + 0x00, T_EXPIRYTIME, 0x00, 2, // type = name, length = 2 + 0xa0, 0xa1 + }; + + MetisTlvSkeleton skeleton; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize((_InternalSkeleton *) &skeleton, &MetisTlvSchemaV1_Ops, encoded, logger); + _parseObjectV1(encoded, 0, sizeof(encoded), &skeleton); + + MetisTlvExtent truth = { .offset = 16, .length = 2 }; + MetisTlvExtent expiryTime = metisTlvSkeleton_GetExpiryTime(&skeleton); + assertTrue(metisTlvExtent_Equals(&truth, &expiryTime), "Wrong extent, expected {%u, %u} got {%u, %u}", + truth.offset, truth.length, expiryTime.offset, expiryTime.length); + + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Local, _parseInterestV1) +{ +} + +LONGBOW_TEST_CASE(Local, _parseMessage) +{ +} + +LONGBOW_TEST_CASE(Local, _computeHash) +{ +} + +// ====================================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_TlvSchemaV1); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} + + + +// ==================================== + + + diff --git a/metis/ccnx/forwarder/metis/tlv/test/test_metis_TlvSkeleton.c b/metis/ccnx/forwarder/metis/tlv/test/test_metis_TlvSkeleton.c new file mode 100644 index 00000000..243b28f3 --- /dev/null +++ b/metis/ccnx/forwarder/metis/tlv/test/test_metis_TlvSkeleton.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 the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_TlvSkeleton.c" +#include <stdio.h> + +#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_TlvSkeleton) +{ + // 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(SchemaV1); + LONGBOW_RUN_TEST_FIXTURE(Setters); + LONGBOW_RUN_TEST_FIXTURE(Getters); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_TlvSkeleton) +{ + 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_TlvSkeleton) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// =================================================== + +LONGBOW_TEST_FIXTURE(SchemaV1) +{ + LONGBOW_RUN_TEST_CASE(SchemaV1, metisTlvSkeleton_ComputeContentObjectHash); + LONGBOW_RUN_TEST_CASE(SchemaV1, metisTlvSkeleton_Skeleton_Interest); + LONGBOW_RUN_TEST_CASE(SchemaV1, metisTlvSkeleton_Skeleton_Object); + LONGBOW_RUN_TEST_CASE(SchemaV1, metisTlvSkeleton_IsPacketTypeInterest); + LONGBOW_RUN_TEST_CASE(SchemaV1, metisTlvSkeleton_IsPacketTypeContentObject); + LONGBOW_RUN_TEST_CASE(SchemaV1, metisTlvSkeleton_IsPacketTypeControl); + LONGBOW_RUN_TEST_CASE(SchemaV1, metisTlvSkeleton_IsPacketTypeInterestReturn); + LONGBOW_RUN_TEST_CASE(SchemaV1, metisTlvSkeleton_TotalPacketLength); +} + +LONGBOW_TEST_FIXTURE_SETUP(SchemaV1) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(SchemaV1) +{ + 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; +} + +static void +_schemaV1_verifyInterestPerHop(MetisTlvSkeleton *opaque) +{ + _InternalSkeleton *skeleton = (_InternalSkeleton *) opaque; + assertTrue(skeleton->array[INDEX_HOPLIMIT].offset == 4, "Incorrect hopLimit offset, expected %u got %u", 4, skeleton->array[INDEX_HOPLIMIT].offset); + assertTrue(skeleton->array[INDEX_HOPLIMIT].length == 1, "Incorrect hopLimit length, expected %u got %u", 1, skeleton->array[INDEX_HOPLIMIT].length); +} + +static void +_schemaV1_verifyInterestSkeleton(MetisTlvSkeleton *opaque) +{ + _InternalSkeleton *skeleton = (_InternalSkeleton *) opaque; + assertTrue(skeleton->array[INDEX_NAME].offset == 22, "Incorrect name offset, expected %u got %u", 2, skeleton->array[INDEX_NAME].offset); + assertTrue(skeleton->array[INDEX_NAME].length == 8, "Incorrect name length, expected %u got %u", 8, skeleton->array[INDEX_NAME].length); + + assertTrue(skeleton->array[INDEX_KEYID].offset == 34, "Incorrect keyId offset, expected %u got %u", 34, skeleton->array[INDEX_KEYID].offset); + assertTrue(skeleton->array[INDEX_KEYID].length == 16, "Incorrect keyId length, expected %u got %u", 16, skeleton->array[INDEX_KEYID].length); + + assertTrue(skeleton->array[INDEX_OBJHASH].offset == 54, "Incorrect objectHash offset, expected %u got %u", 54, skeleton->array[INDEX_OBJHASH].offset); + assertTrue(skeleton->array[INDEX_OBJHASH].length == 32, "Incorrect objectHash length, expected %u got %u", 32, skeleton->array[INDEX_OBJHASH].length); + + assertTrue(skeleton->array[INDEX_INTLIFETIME].offset == 12, "Incorrect interestLifetime offset, expected %u got %u", 12, skeleton->array[INDEX_INTLIFETIME].offset); + assertTrue(skeleton->array[INDEX_INTLIFETIME].length == 2, "Incorrect interestLifetime length, expected %u got %u", 2, skeleton->array[INDEX_INTLIFETIME].length); +} + +static void +_schemaV1_verifyObjectSkeleton(MetisTlvSkeleton *opaque) +{ + _InternalSkeleton *skeleton = (_InternalSkeleton *) opaque; + assertTrue(skeleton->array[INDEX_NAME].offset == 40, "Incorrect name offset, expected %u got %u", 40, skeleton->array[INDEX_NAME].offset); + assertTrue(skeleton->array[INDEX_NAME].length == 17, "Incorrect name length, expected %u got %u", 17, skeleton->array[INDEX_NAME].length); + + assertTrue(skeleton->array[INDEX_KEYID].offset == 106, "Incorrect keyId offset, expected %u got %u", 106, skeleton->array[INDEX_KEYID].offset); + assertTrue(skeleton->array[INDEX_KEYID].length == 32, "Incorrect keyId length, expected %u got %u", 32, skeleton->array[INDEX_KEYID].length); +} + + +LONGBOW_TEST_CASE(SchemaV1, metisTlvSkeleton_ComputeContentObjectHash) +{ + size_t endHeaders = metisTlv_TotalHeaderLength(metisTestDataV1_ContentObject_NameA_KeyId1_RsaSha256); + size_t endPacket = metisTlv_TotalPacketLength(metisTestDataV1_ContentObject_NameA_KeyId1_RsaSha256); + uint8_t *start = &metisTestDataV1_ContentObject_NameA_KeyId1_RsaSha256[endHeaders]; + + + size_t length = endPacket - endHeaders; + + PARCCryptoHasher *hasher = parcCryptoHasher_Create(PARCCryptoHashType_SHA256); + parcCryptoHasher_Init(hasher); + parcCryptoHasher_UpdateBytes(hasher, start, length); + + PARCCryptoHash *hash_truth = parcCryptoHasher_Finalize(hasher); + + MetisTlvSkeleton skeleton; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + metisTlvSkeleton_Parse(&skeleton, metisTestDataV1_ContentObject_NameA_KeyId1_RsaSha256, logger); + metisLogger_Release(&logger); + + PARCCryptoHash *hash_test = metisTlvSkeleton_ComputeContentObjectHash(&skeleton); + + assertTrue(parcCryptoHash_Equals(hash_truth, hash_test), + "Content object digests did not match") + { + printf("Expected:\n"); + parcBuffer_Display(parcCryptoHash_GetDigest(hash_truth), 3); + printf("Got:\n"); + parcBuffer_Display(parcCryptoHash_GetDigest(hash_test), 3); + } + + parcCryptoHash_Release(&hash_truth); + parcCryptoHash_Release(&hash_test); + parcCryptoHasher_Release(&hasher); +} + +LONGBOW_TEST_CASE(SchemaV1, metisTlvSkeleton_Skeleton_Interest) +{ + MetisTlvSkeleton skeleton; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + metisLogger_SetLogLevel(logger, MetisLoggerFacility_Message, PARCLogLevel_Debug); + + metisTlvSkeleton_Parse(&skeleton, metisTestDataV1_Interest_AllFields, logger); + metisLogger_Release(&logger); + _schemaV1_verifyInterestPerHop(&skeleton); + _schemaV1_verifyInterestSkeleton(&skeleton); +} + +LONGBOW_TEST_CASE(SchemaV1, metisTlvSkeleton_Skeleton_Object) +{ + MetisTlvSkeleton skeleton; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + metisLogger_SetLogLevel(logger, MetisLoggerFacility_Message, PARCLogLevel_Debug); + + metisTlvSkeleton_Parse(&skeleton, metisTestDataV1_ContentObject_NameA_KeyId1_RsaSha256, logger); + metisLogger_Release(&logger); + _schemaV1_verifyObjectSkeleton(&skeleton); +} + +LONGBOW_TEST_CASE(SchemaV1, metisTlvSkeleton_IsPacketTypeInterest) +{ + MetisTlvSkeleton skeleton; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + metisTlvSkeleton_Parse(&skeleton, metisTestDataV1_Interest_AllFields, logger); + metisLogger_Release(&logger); + bool match = metisTlvSkeleton_IsPacketTypeInterest(&skeleton); + assertTrue(match, "Packet should have tested true as Interest"); +} + +LONGBOW_TEST_CASE(SchemaV1, metisTlvSkeleton_IsPacketTypeContentObject) +{ + MetisTlvSkeleton skeleton; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + metisTlvSkeleton_Parse(&skeleton, metisTestDataV1_ContentObject_NameA_KeyId1_RsaSha256, logger); + metisLogger_Release(&logger); + bool match = metisTlvSkeleton_IsPacketTypeContentObject(&skeleton); + assertTrue(match, "Packet should have tested true as Content Object"); +} + +LONGBOW_TEST_CASE(SchemaV1, metisTlvSkeleton_IsPacketTypeControl) +{ + MetisTlvSkeleton skeleton; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + metisTlvSkeleton_Parse(&skeleton, metisTestDataV1_CPI_AddRoute_Crc32c, logger); + metisLogger_Release(&logger); + bool match = metisTlvSkeleton_IsPacketTypeControl(&skeleton); + assertTrue(match, "Packet should have tested true as Control"); +} + +LONGBOW_TEST_CASE(SchemaV1, metisTlvSkeleton_IsPacketTypeInterestReturn) +{ + MetisTlvSkeleton skeleton; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + metisTlvSkeleton_Parse(&skeleton, metisTestDataV1_CPI_AddRoute_Crc32c, logger); + metisLogger_Release(&logger); + bool match = metisTlvSkeleton_IsPacketTypeInterestReturn(&skeleton); + assertFalse(match, "Packet should have tested false as Interest Return"); +} + +LONGBOW_TEST_CASE(SchemaV1, metisTlvSkeleton_TotalPacketLength) +{ + MetisTlvSkeleton skeleton; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + metisLogger_SetLogLevel(logger, MetisLoggerFacility_Message, PARCLogLevel_Debug); + + metisTlvSkeleton_Parse(&skeleton, metisTestDataV1_Interest_AllFields, logger); + metisLogger_Release(&logger); + size_t truth = sizeof(metisTestDataV1_Interest_AllFields); + size_t test = metisTlvSkeleton_TotalPacketLength(&skeleton); + + assertTrue(truth == test, "Wrong value, expected %zu got %zu", truth, test); +} + +// ====================================================== + +LONGBOW_TEST_FIXTURE(Setters) +{ + LONGBOW_RUN_TEST_CASE(Setters, metisTlvSkeleton_SetName); + LONGBOW_RUN_TEST_CASE(Setters, metisTlvSkeleton_SetKeyId); + LONGBOW_RUN_TEST_CASE(Setters, metisTlvSkeleton_SetObjectHash); + LONGBOW_RUN_TEST_CASE(Setters, metisTlvSkeleton_SetHopLimit); + LONGBOW_RUN_TEST_CASE(Setters, metisTlvSkeleton_SetInterestLifetime); + LONGBOW_RUN_TEST_CASE(Setters, metisTlvSkeleton_SetCacheTimeHeader); + LONGBOW_RUN_TEST_CASE(Setters, metisTlvSkeleton_SetExpiryTime); + LONGBOW_RUN_TEST_CASE(Setters, metisTlvSkeleton_SetCPI); + LONGBOW_RUN_TEST_CASE(Setters, metisTlvSkeleton_SetFragmentPayload); + LONGBOW_RUN_TEST_CASE(Setters, metisTlvSkeleton_UpdateHopLimit); + + LONGBOW_RUN_TEST_CASE(Setters, metisTlvSkeleton_SetKeyId); + LONGBOW_RUN_TEST_CASE(Setters, metisTlvSkeleton_SetCertificate); + LONGBOW_RUN_TEST_CASE(Setters, metisTlvSkeleton_SetPublicKey); +} + +LONGBOW_TEST_FIXTURE_SETUP(Setters) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Setters) +{ + 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(Setters, metisTlvSkeleton_SetName) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + size_t offset = 2; + size_t length = 4; + int element = INDEX_NAME; + + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + metisLogger_SetLogLevel(logger, MetisLoggerFacility_Message, PARCLogLevel_Debug); + + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + metisTlvSkeleton_SetName(&opaque, offset, length); + + assertTrue(skeleton->array[element].offset == offset, "Wrong offset for index %d, expected %zu got %u", element, offset, skeleton->array[element].offset); + assertTrue(skeleton->array[element].length == length, "Wrong length for index %d, expected %zu got %u", element, length, skeleton->array[element].length); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Setters, metisTlvSkeleton_SetKeyId) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + size_t offset = 2; + size_t length = 4; + int element = INDEX_KEYID; + + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + metisLogger_SetLogLevel(logger, MetisLoggerFacility_Message, PARCLogLevel_Debug); + + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + metisTlvSkeleton_SetKeyId(&opaque, offset, length); + + assertTrue(skeleton->array[element].offset == offset, "Wrong offset for index %d, expected %zu got %u", element, offset, skeleton->array[element].offset); + assertTrue(skeleton->array[element].length == length, "Wrong length for index %d, expected %zu got %u", element, length, skeleton->array[element].length); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Setters, metisTlvSkeleton_SetObjectHash) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + size_t offset = 2; + size_t length = 4; + int element = INDEX_OBJHASH; + + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + metisLogger_SetLogLevel(logger, MetisLoggerFacility_Message, PARCLogLevel_Debug); + + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + metisTlvSkeleton_SetObjectHash(&opaque, offset, length); + + assertTrue(skeleton->array[element].offset == offset, "Wrong offset for index %d, expected %zu got %u", element, offset, skeleton->array[element].offset); + assertTrue(skeleton->array[element].length == length, "Wrong length for index %d, expected %zu got %u", element, length, skeleton->array[element].length); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Setters, metisTlvSkeleton_SetHopLimit) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + size_t offset = 2; + size_t length = 1; + int element = INDEX_HOPLIMIT; + + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + metisLogger_SetLogLevel(logger, MetisLoggerFacility_Message, PARCLogLevel_Debug); + + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + metisTlvSkeleton_SetHopLimit(&opaque, offset, length); + + assertTrue(skeleton->array[element].offset == offset, "Wrong offset for index %d, expected %zu got %u", element, offset, skeleton->array[element].offset); + assertTrue(skeleton->array[element].length == length, "Wrong length for index %d, expected %zu got %u", element, length, skeleton->array[element].length); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Setters, metisTlvSkeleton_SetInterestLifetime) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + size_t offset = 2; + size_t length = 4; + int element = INDEX_INTLIFETIME; + + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + metisLogger_SetLogLevel(logger, MetisLoggerFacility_Message, PARCLogLevel_Debug); + + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + metisTlvSkeleton_SetInterestLifetime(&opaque, offset, length); + + assertTrue(skeleton->array[element].offset == offset, "Wrong offset for index %d, expected %zu got %u", element, offset, skeleton->array[element].offset); + assertTrue(skeleton->array[element].length == length, "Wrong length for index %d, expected %zu got %u", element, length, skeleton->array[element].length); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Setters, metisTlvSkeleton_SetCacheTimeHeader) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + size_t offset = 2; + size_t length = 4; + int element = INDEX_CACHETIME; + + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + metisLogger_SetLogLevel(logger, MetisLoggerFacility_Message, PARCLogLevel_Debug); + + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + metisTlvSkeleton_SetCacheTimeHeader(&opaque, offset, length); + + assertTrue(skeleton->array[element].offset == offset, "Wrong offset for index %d, expected %zu got %u", element, offset, skeleton->array[element].offset); + assertTrue(skeleton->array[element].length == length, "Wrong length for index %d, expected %zu got %u", element, length, skeleton->array[element].length); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Setters, metisTlvSkeleton_SetExpiryTime) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + size_t offset = 2; + size_t length = 4; + int element = INDEX_EXPIRYTIME; + + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + metisLogger_SetLogLevel(logger, MetisLoggerFacility_Message, PARCLogLevel_Debug); + + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + metisTlvSkeleton_SetExpiryTime(&opaque, offset, length); + + assertTrue(skeleton->array[element].offset == offset, "Wrong offset for index %d, expected %zu got %u", element, offset, skeleton->array[element].offset); + assertTrue(skeleton->array[element].length == length, "Wrong length for index %d, expected %zu got %u", element, length, skeleton->array[element].length); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Setters, metisTlvSkeleton_SetCPI) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + size_t offset = 2; + size_t length = 4; + int element = INDEX_CPI; + + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + metisLogger_SetLogLevel(logger, MetisLoggerFacility_Message, PARCLogLevel_Debug); + + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + metisTlvSkeleton_SetCPI(&opaque, offset, length); + + assertTrue(skeleton->array[element].offset == offset, "Wrong offset for index %d, expected %zu got %u", element, offset, skeleton->array[element].offset); + assertTrue(skeleton->array[element].length == length, "Wrong length for index %d, expected %zu got %u", element, length, skeleton->array[element].length); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Setters, metisTlvSkeleton_SetFragmentPayload) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + size_t offset = 2; + size_t length = 4; + int element = INDEX_FRAGMENTPAYLOAD; + + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + metisLogger_SetLogLevel(logger, MetisLoggerFacility_Message, PARCLogLevel_Debug); + + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + metisTlvSkeleton_SetFragmentPayload(&opaque, offset, length); + + assertTrue(skeleton->array[element].offset == offset, "Wrong offset for index %d, expected %zu got %u", element, offset, skeleton->array[element].offset); + assertTrue(skeleton->array[element].length == length, "Wrong length for index %d, expected %zu got %u", element, length, skeleton->array[element].length); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Setters, metisTlvSkeleton_UpdateHopLimit) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + size_t offset = 2; + size_t length = 1; + + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + metisLogger_SetLogLevel(logger, MetisLoggerFacility_Message, PARCLogLevel_Debug); + + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + metisTlvSkeleton_SetHopLimit(&opaque, offset, length); + + metisTlvSkeleton_UpdateHopLimit(&opaque, 77); + + assertTrue(packet[offset] == 77, "Wrong hop limit, expected 77 got %u", packet[offset]); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Setters, metisTlvSkeleton_SetCertificate) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + size_t offset = 6; + size_t length = 2; + int element = INDEX_CERTIFICATE; + + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + metisLogger_SetLogLevel(logger, MetisLoggerFacility_Message, PARCLogLevel_Debug); + + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + metisTlvSkeleton_SetCertificate(&opaque, offset, length); + + assertTrue(skeleton->array[element].offset == offset, "Wrong offset for index %d, expected %zu got %u", + element, offset, skeleton->array[element].offset); + assertTrue(skeleton->array[element].length == length, "Wrong length for index %d, expected %zu got %u", + element, length, skeleton->array[element].length); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Setters, metisTlvSkeleton_SetPublicKey) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + size_t offset = 5; + size_t length = 3; + int element = INDEX_PUBKEY; + + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + metisLogger_SetLogLevel(logger, MetisLoggerFacility_Message, PARCLogLevel_Debug); + + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + metisTlvSkeleton_SetPublicKey(&opaque, offset, length); + + assertTrue(skeleton->array[element].offset == offset, "Wrong offset for index %d, expected %zu got %u", element, offset, skeleton->array[element].offset); + assertTrue(skeleton->array[element].length == length, "Wrong length for index %d, expected %zu got %u", element, length, skeleton->array[element].length); + metisLogger_Release(&logger); +} + +// ====================================================== + +LONGBOW_TEST_FIXTURE(Getters) +{ + LONGBOW_RUN_TEST_CASE(Getters, metisTlvSkeleton_GetName); + LONGBOW_RUN_TEST_CASE(Getters, metisTlvSkeleton_GetKeyId); + LONGBOW_RUN_TEST_CASE(Getters, metisTlvSkeleton_GetObjectHash); + LONGBOW_RUN_TEST_CASE(Getters, metisTlvSkeleton_GetHopLimit); + LONGBOW_RUN_TEST_CASE(Getters, metisTlvSkeleton_GetInterestLifetime); + LONGBOW_RUN_TEST_CASE(Getters, metisTlvSkeleton_GetCacheTimeHeader); + LONGBOW_RUN_TEST_CASE(Getters, metisTlvSkeleton_GetExpiryTime); + LONGBOW_RUN_TEST_CASE(Getters, metisTlvSkeleton_GetCPI); + LONGBOW_RUN_TEST_CASE(Getters, metisTlvSkeleton_GetFragmentPayload); + LONGBOW_RUN_TEST_CASE(Getters, metisTlvSkeleton_GetPacket); + LONGBOW_RUN_TEST_CASE(Getters, metisTlvSkeleton_GetLogger); + + LONGBOW_RUN_TEST_CASE(Getters, metisTlvSkeleton_GetPublicKey); + LONGBOW_RUN_TEST_CASE(Getters, metisTlvSkeleton_GetCertificate); +} + +LONGBOW_TEST_FIXTURE_SETUP(Getters) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Getters) +{ + 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(Getters, metisTlvSkeleton_GetName) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + MetisTlvExtent extent = { .offset = 3, .length = 2 }; + + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + metisTlvSkeleton_SetName(&opaque, extent.offset, extent.length); + + MetisTlvExtent test = metisTlvSkeleton_GetName(&opaque); + + assertTrue(metisTlvExtent_Equals(&extent, &test), "Wrong extent, expected {%u, %u}, got {%u, %u}", + extent.offset, extent.length, test.offset, test.length); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Getters, metisTlvSkeleton_GetKeyId) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + MetisTlvExtent extent = { .offset = 3, .length = 2 }; + + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + metisTlvSkeleton_SetKeyId(&opaque, extent.offset, extent.length); + + MetisTlvExtent test = metisTlvSkeleton_GetKeyId(&opaque); + + assertTrue(metisTlvExtent_Equals(&extent, &test), "Wrong extent, expected {%u, %u}, got {%u, %u}", + extent.offset, extent.length, test.offset, test.length); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Getters, metisTlvSkeleton_GetObjectHash) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + MetisTlvExtent extent = { .offset = 3, .length = 2 }; + + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + metisTlvSkeleton_SetObjectHash(&opaque, extent.offset, extent.length); + + MetisTlvExtent test = metisTlvSkeleton_GetObjectHash(&opaque); + + assertTrue(metisTlvExtent_Equals(&extent, &test), "Wrong extent, expected {%u, %u}, got {%u, %u}", + extent.offset, extent.length, test.offset, test.length); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Getters, metisTlvSkeleton_GetHopLimit) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + MetisTlvExtent extent = { .offset = 3, .length = 1 }; + + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + metisTlvSkeleton_SetHopLimit(&opaque, extent.offset, extent.length); + + MetisTlvExtent test = metisTlvSkeleton_GetHopLimit(&opaque); + + assertTrue(metisTlvExtent_Equals(&extent, &test), "Wrong extent, expected {%u, %u}, got {%u, %u}", + extent.offset, extent.length, test.offset, test.length); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Getters, metisTlvSkeleton_GetInterestLifetime) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + MetisTlvExtent extent = { .offset = 3, .length = 2 }; + + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + metisTlvSkeleton_SetInterestLifetime(&opaque, extent.offset, extent.length); + + MetisTlvExtent test = metisTlvSkeleton_GetInterestLifetime(&opaque); + + assertTrue(metisTlvExtent_Equals(&extent, &test), "Wrong extent, expected {%u, %u}, got {%u, %u}", + extent.offset, extent.length, test.offset, test.length); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Getters, metisTlvSkeleton_GetCacheTimeHeader) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + MetisTlvExtent extent = { .offset = 3, .length = 2 }; + + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + metisTlvSkeleton_SetCacheTimeHeader(&opaque, extent.offset, extent.length); + + MetisTlvExtent test = metisTlvSkeleton_GetCacheTimeHeader(&opaque); + + assertTrue(metisTlvExtent_Equals(&extent, &test), "Wrong extent, expected {%u, %u}, got {%u, %u}", + extent.offset, extent.length, test.offset, test.length); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Getters, metisTlvSkeleton_GetExpiryTime) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + MetisTlvExtent extent = { .offset = 3, .length = 2 }; + + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + metisTlvSkeleton_SetExpiryTime(&opaque, extent.offset, extent.length); + + MetisTlvExtent test = metisTlvSkeleton_GetExpiryTime(&opaque); + + assertTrue(metisTlvExtent_Equals(&extent, &test), "Wrong extent, expected {%u, %u}, got {%u, %u}", + extent.offset, extent.length, test.offset, test.length); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Getters, metisTlvSkeleton_GetCPI) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + MetisTlvExtent extent = { .offset = 3, .length = 2 }; + + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + metisTlvSkeleton_SetCPI(&opaque, extent.offset, extent.length); + + MetisTlvExtent test = metisTlvSkeleton_GetCPI(&opaque); + + assertTrue(metisTlvExtent_Equals(&extent, &test), "Wrong extent, expected {%u, %u}, got {%u, %u}", + extent.offset, extent.length, test.offset, test.length); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Getters, metisTlvSkeleton_GetFragmentPayload) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + MetisTlvExtent extent = { .offset = 3, .length = 2 }; + + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + metisTlvSkeleton_SetFragmentPayload(&opaque, extent.offset, extent.length); + + MetisTlvExtent test = metisTlvSkeleton_GetFragmentPayload(&opaque); + + assertTrue(metisTlvExtent_Equals(&extent, &test), "Wrong extent, expected {%u, %u}, got {%u, %u}", + extent.offset, extent.length, test.offset, test.length); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Getters, metisTlvSkeleton_GetPacket) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + + const uint8_t *test = metisTlvSkeleton_GetPacket(&opaque); + + assertTrue(packet == test, "Wrong packet pointer, expected %p, got %p", + (void *) packet, (void *) test); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Getters, metisTlvSkeleton_GetLogger) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + + MetisLogger *test = metisTlvSkeleton_GetLogger(&opaque); + assertNotNull(test, "Got null logger from skeleton"); + + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Getters, metisTlvSkeleton_GetPublicKey) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + MetisTlvExtent extent = { .offset = 5, .length = 2 }; + + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + + metisTlvSkeleton_SetPublicKey(&opaque, extent.offset, extent.length); + + MetisTlvExtent test = metisTlvSkeleton_GetPublicKey(&opaque); + + assertTrue(metisTlvExtent_Equals(&extent, &test), "Wrong extent, expected {%u, %u}, got {%u, %u}", + extent.offset, extent.length, test.offset, test.length); + metisLogger_Release(&logger); +} + +LONGBOW_TEST_CASE(Getters, metisTlvSkeleton_GetCertificate) +{ + uint8_t packet[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + MetisTlvExtent extent = { .offset = 5, .length = 2 }; + + MetisTlvSkeleton opaque; + _InternalSkeleton *skeleton = (_InternalSkeleton *) &opaque; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + _initialize(skeleton, &MetisTlvSchemaV1_Ops, packet, logger); + + metisTlvSkeleton_SetCertificate(&opaque, extent.offset, extent.length); + + MetisTlvExtent test = metisTlvSkeleton_GetCertificate(&opaque); + + assertTrue(metisTlvExtent_Equals(&extent, &test), "Wrong extent, expected {%u, %u}, got {%u, %u}", + extent.offset, extent.length, test.offset, test.length); + metisLogger_Release(&logger); +} + + + +// ====================================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_TlvSkeleton); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} + + + +// ==================================== + + |