aboutsummaryrefslogtreecommitdiffstats
path: root/metis/ccnx/forwarder/metis/config
diff options
context:
space:
mode:
Diffstat (limited to 'metis/ccnx/forwarder/metis/config')
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_Add.c86
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_Add.h32
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_AddConnection.c622
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_AddConnection.h31
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_AddListener.c280
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_AddListener.h30
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_AddRoute.c189
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_AddRoute.h30
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_Cache.c95
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_Cache.h22
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_CacheClear.c97
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_CacheClear.h30
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_CacheServe.c104
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_CacheServe.h22
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_CacheStore.c104
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_CacheStore.h22
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_List.c95
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_List.h30
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_ListConnections.c108
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_ListConnections.h30
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_ListInterfaces.c104
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_ListInterfaces.h30
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_ListRoutes.c146
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_ListRoutes.h29
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_Quit.c66
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_Quit.h29
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_Remove.c87
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_Remove.h29
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_RemoveConnection.c224
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_RemoveConnection.h30
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_RemoveRoute.c178
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_RemoveRoute.h30
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_Root.c129
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_Root.h31
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_Set.c92
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_Set.h29
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_SetDebug.c79
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_SetDebug.h30
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_SetStrategy.c123
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_SetStrategy.h22
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_SetWldr.c130
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_SetWldr.h22
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_Unset.c82
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_Unset.h29
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_UnsetDebug.c79
-rw-r--r--metis/ccnx/forwarder/metis/config/metisControl_UnsetDebug.h30
-rw-r--r--metis/ccnx/forwarder/metis/config/metis_CommandLineInterface.c405
-rw-r--r--metis/ccnx/forwarder/metis/config/metis_CommandLineInterface.h80
-rw-r--r--metis/ccnx/forwarder/metis/config/metis_CommandOps.c69
-rw-r--r--metis/ccnx/forwarder/metis/config/metis_CommandOps.h121
-rw-r--r--metis/ccnx/forwarder/metis/config/metis_CommandParser.c227
-rw-r--r--metis/ccnx/forwarder/metis/config/metis_CommandParser.h190
-rw-r--r--metis/ccnx/forwarder/metis/config/metis_CommandReturn.h42
-rw-r--r--metis/ccnx/forwarder/metis/config/metis_Configuration.c936
-rw-r--r--metis/ccnx/forwarder/metis/config/metis_Configuration.h200
-rw-r--r--metis/ccnx/forwarder/metis/config/metis_ConfigurationFile.c313
-rw-r--r--metis/ccnx/forwarder/metis/config/metis_ConfigurationFile.h90
-rw-r--r--metis/ccnx/forwarder/metis/config/metis_ConfigurationListeners.c326
-rw-r--r--metis/ccnx/forwarder/metis/config/metis_ConfigurationListeners.h53
-rw-r--r--metis/ccnx/forwarder/metis/config/metis_ControlState.c150
-rw-r--r--metis/ccnx/forwarder/metis/config/metis_ControlState.h205
-rw-r--r--metis/ccnx/forwarder/metis/config/metis_SymbolicNameTable.c151
-rw-r--r--metis/ccnx/forwarder/metis/config/metis_SymbolicNameTable.h123
-rw-r--r--metis/ccnx/forwarder/metis/config/metis_WebInterface.h85
-rw-r--r--metis/ccnx/forwarder/metis/config/test/CMakeLists.txt37
-rw-r--r--metis/ccnx/forwarder/metis/config/test/test_metisControl_Add.c133
-rw-r--r--metis/ccnx/forwarder/metis/config/test/test_metisControl_AddConnection.c473
-rw-r--r--metis/ccnx/forwarder/metis/config/test/test_metisControl_AddListener.c327
-rw-r--r--metis/ccnx/forwarder/metis/config/test/test_metisControl_AddRoute.c170
-rw-r--r--metis/ccnx/forwarder/metis/config/test/test_metisControl_List.c130
-rw-r--r--metis/ccnx/forwarder/metis/config/test/test_metisControl_ListConnections.c172
-rw-r--r--metis/ccnx/forwarder/metis/config/test/test_metisControl_ListInterfaces.c177
-rw-r--r--metis/ccnx/forwarder/metis/config/test/test_metisControl_ListRoutes.c187
-rw-r--r--metis/ccnx/forwarder/metis/config/test/test_metisControl_Quit.c119
-rw-r--r--metis/ccnx/forwarder/metis/config/test/test_metisControl_Remove.c130
-rw-r--r--metis/ccnx/forwarder/metis/config/test/test_metisControl_RemoveConnection.c118
-rw-r--r--metis/ccnx/forwarder/metis/config/test/test_metisControl_RemoveRoute.c118
-rw-r--r--metis/ccnx/forwarder/metis/config/test/test_metisControl_Root.c130
-rw-r--r--metis/ccnx/forwarder/metis/config/test/test_metisControl_Set.c130
-rw-r--r--metis/ccnx/forwarder/metis/config/test/test_metisControl_SetDebug.c153
-rw-r--r--metis/ccnx/forwarder/metis/config/test/test_metisControl_Unset.c130
-rw-r--r--metis/ccnx/forwarder/metis/config/test/test_metisControl_UnsetDebug.c153
-rw-r--r--metis/ccnx/forwarder/metis/config/test/test_metis_CommandLineInterface.c181
-rw-r--r--metis/ccnx/forwarder/metis/config/test/test_metis_CommandOps.c104
-rw-r--r--metis/ccnx/forwarder/metis/config/test/test_metis_CommandParser.c259
-rw-r--r--metis/ccnx/forwarder/metis/config/test/test_metis_Configuration.c779
-rw-r--r--metis/ccnx/forwarder/metis/config/test/test_metis_ConfigurationFile.c403
-rw-r--r--metis/ccnx/forwarder/metis/config/test/test_metis_ConfigurationListeners.c644
-rw-r--r--metis/ccnx/forwarder/metis/config/test/test_metis_ControlState.c253
-rw-r--r--metis/ccnx/forwarder/metis/config/test/test_metis_SymbolicNameTable.c140
-rw-r--r--metis/ccnx/forwarder/metis/config/test/testrig_MetisControl.c191
91 files changed, 13655 insertions, 0 deletions
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, &ether_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(&ether_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(&etherConn);
+ 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 **) &copy);
+ }
+ }
+ }
+
+ 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(&etherConn);
+
+ 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(&etherconn);
+ 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(&etherconn);
+ 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 **) &copy);
+ }
+}
+
+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 **) &copy);
+ }
+}
+
+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 **) &copy);
+ }
+}
+
+// ======================================================
+
+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);
+}