diff options
Diffstat (limited to 'metis/ccnx/forwarder/metis/config')
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, ðer_EncapType, &symbolic) == MetisCommandReturn_Success) { + CPIConnectionEthernet *ether_Conn = cpiConnectionEthernet_Create(local_addr, remote_addr, ether_EncapType, symbolic); + CCNxControl *control_Message = cpiConnectionEthernet_CreateAddMessage(ether_Conn); + + CCNxMetaMessage *msg = ccnxMetaMessage_CreateFromControl(control_Message); + CCNxMetaMessage *control_Response = metisControlState_WriteRead(metis_State, msg); + ccnxMetaMessage_Release(&msg); + + if (metisControlState_GetDebug(metis_State)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(control_Message)); + printf("reponse:\n%s\n", str); + parcMemory_Deallocate((void **) &str); + } + + ccnxMetaMessage_Release(&control_Response); + ccnxControl_Release(&control_Message); + cpiConnectionEthernet_Release(ðer_Conn); + cpiAddress_Destroy(&remote_addr); + return MetisCommandReturn_Success; + } + + return MetisCommandReturn_Failure; +} + +// =================================================== diff --git a/metis/ccnx/forwarder/metis/config/metisControl_AddConnection.h b/metis/ccnx/forwarder/metis/config/metisControl_AddConnection.h new file mode 100644 index 00000000..dee6e461 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_AddConnection.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metisControl_AddConnection.h + * @brief Command-line "add connection" node + * + * Implements the "add connection" node of the CLI tree + * + * + */ + +#ifndef Metis_metisControl_AddConnection_h +#define Metis_metisControl_AddConnection_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlAddConnection_Create(MetisControlState *state); +MetisCommandOps *metisControlAddConnection_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_AddConnection_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_AddListener.c b/metis/ccnx/forwarder/metis/config/metisControl_AddListener.c new file mode 100644 index 00000000..9268c76f --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_AddListener.c @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> +#include <inttypes.h> +#include <ctype.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Network.h> +#include <parc/algol/parc_Memory.h> +#include <ccnx/api/control/cpi_Listener.h> + +#include <ccnx/forwarder/metis/config/metisControl_AddListener.h> +#include <ccnx/api/control/controlPlaneInterface.h> +#include <ccnx/api/control/cpi_Acks.h> + +static MetisCommandReturn _metisControlAddListener_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlAddListener_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *command_add_listener = "add listener"; +static const char *command_help_add_listener = "help add listener"; + +MetisCommandOps * +metisControlAddListener_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, command_add_listener, NULL, _metisControlAddListener_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlAddListener_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, command_help_add_listener, NULL, _metisControlAddListener_HelpExecute, metisCommandOps_Destroy); +} + +/** + * A symbolic name must be at least 1 character and must begin with an alpha. + * The remainder must be an alphanum. + */ +static bool +_validateSymbolicName(const char *symbolic) +{ + bool success = false; + size_t len = strlen(symbolic); + if (len > 0) { + if (isalpha(symbolic[0])) { + success = true; + for (size_t i = 1; i < len; i++) { + if (!isalnum(symbolic[i])) { + success = false; + break; + } + } + } + } + return success; +} + +// ==================================================== + +static const int _indexProtocol = 2; +static const int _indexSymbolic = 3; +static const int _indexAddress = 4; +static const int _indexPort = 5; + +static MetisCommandReturn +_metisControlAddListener_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("commands:\n"); + printf(" add listener <protocol> <symbolic> <localAddress> <PortOrEtherType>\n"); + printf("\n"); + printf(" symbolic: User defined name for listener, must start with alpha and be alphanum\n"); + printf(" protocol: tcp | udp | ether\n"); + printf(" localAddress: IPv4 or IPv6 or hostname or interface name (see examples)\n"); + printf(" PortOrEtherType: TCP/UDP port or EtherType (base 10 or use 0x for base 16)\n"); + printf("\n"); + printf("Notes:\n"); + printf(" The local address must be on the system (see 'help list interfaces' command).\n"); + printf(" For Ethernet, the broadcast and CCNx group address will also be added.\n"); + printf(" The symblic name must be unique or the forwarder will reject it.\n"); + printf("\n"); + printf("Examples:\n"); + printf(" Listens to 192.168.1.7 on tcp port 9695\n"); + printf(" add listener tcp homenet 192.168.1.7 9695\n"); + printf("\n"); + printf(" Listens to IPv6 localhost on udp port 9695\n"); + printf(" add listener udp localhost6 ::1 9695\n"); + printf("\n"); + printf(" Listens to interface 'en0' on ethertype 0x0801\n"); + printf(" add listener ether nic0 en0 0x0801\n"); + printf("\n"); + return MetisCommandReturn_Success; +} + +static CPIAddress * +_convertStringsToCpiAddress(const char *ip_string, const char *port_string) +{ + int port = atoi(port_string); + struct sockaddr *addr = parcNetwork_SockAddress(ip_string, port); + + if (addr == NULL) { + printf("Error converting address '%s' port '%s' to socket address\n", ip_string, port_string); + return NULL; + } + + CPIAddress *remote_cpi_address = NULL; + switch (addr->sa_family) { + case PF_INET: { + remote_cpi_address = cpiAddress_CreateFromInet((struct sockaddr_in *) addr); + break; + } + + case PF_INET6: { + remote_cpi_address = cpiAddress_CreateFromInet6((struct sockaddr_in6 *) addr); + break; + } + + default: { + printf("Error converting address '%s' port '%s' to socket address, unsupported address family %d\n", + ip_string, port_string, addr->sa_family); + break; + } + } + parcMemory_Deallocate((void **) &addr); + return remote_cpi_address; +} + +static MetisCommandReturn +_sendAndVerify(MetisControlState *metis_State, CCNxControl *control) +{ + MetisCommandReturn result = MetisCommandReturn_Failure; + uint64_t seqnum = cpi_GetSequenceNumber(control); + + CCNxMetaMessage *requestMessage = ccnxMetaMessage_CreateFromControl(control); + CCNxMetaMessage *responseMessage = metisControlState_WriteRead(metis_State, requestMessage); + ccnxMetaMessage_Release(&requestMessage); + + if (metisControlState_GetDebug(metis_State)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(responseMessage)); + printf("reponse:\n%s\n", str); + parcMemory_Deallocate((void **) &str); + } + + if (ccnxMetaMessage_IsControl(responseMessage)) { + CCNxControl *responseControl = ccnxMetaMessage_GetControl(responseMessage); + if (ccnxControl_IsACK(responseControl)) { + uint64_t ackedSeqnum = cpiAcks_GetAckOriginalSequenceNumber(ccnxControl_GetJson(responseControl)); + if (ackedSeqnum == seqnum) { + result = MetisCommandReturn_Success; + } else { + printf("Error: received wrong seqnum expected %" PRIu64 " got %" PRIu64 "\n", seqnum, ackedSeqnum); + } + } + } + + ccnxMetaMessage_Release(&responseMessage); + return result; +} + +static MetisCommandReturn +_createTcpListener(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + MetisCommandReturn result = MetisCommandReturn_Failure; + + const char *symbolic = parcList_GetAtIndex(args, _indexSymbolic); + const char *host = parcList_GetAtIndex(args, _indexAddress); + const char *port = parcList_GetAtIndex(args, _indexPort); + + CPIAddress *socket = _convertStringsToCpiAddress(host, port); + if (socket) { + CPIListener *listener = cpiListener_CreateIP(IPTUN_TCP, socket, symbolic); + CCNxControl *control = cpiListener_CreateAddMessage(listener); + + MetisControlState *metis_State = ops->closure; + result = _sendAndVerify(metis_State, control); + ccnxControl_Release(&control); + cpiListener_Release(&listener); + cpiAddress_Destroy(&socket); + } + + return result; +} + +static MetisCommandReturn +_createUdpListener(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + MetisCommandReturn result = MetisCommandReturn_Failure; + + const char *symbolic = parcList_GetAtIndex(args, _indexSymbolic); + const char *host = parcList_GetAtIndex(args, _indexAddress); + const char *port = parcList_GetAtIndex(args, _indexPort); + + CPIAddress *socket = _convertStringsToCpiAddress(host, port); + if (socket) { + CPIListener *listener = cpiListener_CreateIP(IPTUN_UDP, socket, symbolic); + CCNxControl *control = cpiListener_CreateAddMessage(listener); + + MetisControlState *metis_State = ops->closure; + result = _sendAndVerify(metis_State, control); + ccnxControl_Release(&control); + cpiListener_Release(&listener); + cpiAddress_Destroy(&socket); + } + + return result; +} + +static MetisCommandReturn +_createEtherListener(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + MetisCommandReturn result = MetisCommandReturn_Failure; + + const char *symbolic = parcList_GetAtIndex(args, _indexSymbolic); + const char *ifname = parcList_GetAtIndex(args, _indexAddress); + uint16_t ethertype = (uint16_t) strtoul(parcList_GetAtIndex(args, _indexPort), NULL, 0); + + { + CPIListener *listener = cpiListener_CreateEther(ifname, (uint16_t) ethertype, symbolic); + CCNxControl *control = cpiListener_CreateAddMessage(listener); + + MetisControlState *metis_State = ops->closure; + result = _sendAndVerify(metis_State, control); + ccnxControl_Release(&control); + cpiListener_Release(&listener); + } + + return result; +} + + +static MetisCommandReturn +_metisControlAddListener_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + if (parcList_Size(args) != 6) { + _metisControlAddListener_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + MetisCommandReturn result = MetisCommandReturn_Failure; + + const char *symbolic = parcList_GetAtIndex(args, _indexSymbolic); + if (_validateSymbolicName(symbolic)) { + const char *protocol = parcList_GetAtIndex(args, _indexProtocol); + if (strcasecmp("tcp", protocol) == 0) { + result = _createTcpListener(parser, ops, args); + } else if (strcasecmp("udp", protocol) == 0) { + result = _createUdpListener(parser, ops, args); + } else if (strcasecmp("ether", protocol) == 0) { + result = _createEtherListener(parser, ops, args); + } else { + printf("Error: unrecognized protocol '%s'\n", protocol); + } + } else { + printf("Error: symbolic name must begin with an alpha and be alphanum after\n"); + } + + + return result; +} + + + diff --git a/metis/ccnx/forwarder/metis/config/metisControl_AddListener.h b/metis/ccnx/forwarder/metis/config/metisControl_AddListener.h new file mode 100644 index 00000000..847dfe34 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_AddListener.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metisControl_AddListener.h + * @brief Add a listener to an interface + * + * <#Detailed Description#> + * + */ + +#ifndef Metis_metisControl_AddListener_h +#define Metis_metisControl_AddListener_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlAddListener_Create(MetisControlState *state); +MetisCommandOps *metisControlAddListener_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_AddListener_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_AddRoute.c b/metis/ccnx/forwarder/metis/config/metisControl_AddRoute.c new file mode 100644 index 00000000..215bcee1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_AddRoute.c @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> +#include <ccnx/api/control/cpi_NameRouteProtocolType.h> +#include <ccnx/api/control/cpi_RouteEntry.h> +#include <ccnx/api/control/cpi_Forwarding.h> + +#include <ccnx/forwarder/metis/config/metisControl_AddRoute.h> + +static MetisCommandReturn _metisControlAddRoute_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlAddRoute_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandAddRoute = "add route"; +static const char *_commandAddRouteHelp = "help add route"; + +MetisCommandOps * +metisControlAddRoute_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandAddRoute, NULL, _metisControlAddRoute_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlAddRoute_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandAddRouteHelp, NULL, _metisControlAddRoute_HelpExecute, metisCommandOps_Destroy); +} + +/** + * Return true if string is purely an integer + */ +static bool +_isNumber(const char *string) +{ + size_t len = strlen(string); + for (size_t i = 0; i < len; i++) { + if (!isdigit(string[i])) { + return false; + } + } + return true; +} + +/** + * A symbolic name must be at least 1 character and must begin with an alpha. + * The remainder must be an alphanum. + */ +static bool +_validateSymbolicName(const char *symbolic) +{ + bool success = false; + size_t len = strlen(symbolic); + if (len > 0) { + if (isalpha(symbolic[0])) { + success = true; + for (size_t i = 1; i < len; i++) { + if (!isalnum(symbolic[i])) { + success = false; + break; + } + } + } + } + return success; +} + +// ==================================================== + +static MetisCommandReturn +_metisControlAddRoute_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("commands:\n"); + printf(" add route <symbolic | connid> <prefix> <cost>\n"); + printf("\n"); + printf(" symbolic: The symbolic name for an exgress\n"); + printf(" connid: The egress connection id (see 'help list connections')\n"); + printf(" prefix: The CCNx name as a URI (e.g. lci:/foo/bar)\n"); + printf(" cost: positive integer representing cost\n"); + printf(" nexthop: Optional network endpoint on the connection\n"); + printf(" seconds: Create a route that will expire if not refresed within the lifetime\n"); + printf("\n"); + printf("Examples:\n"); + printf(" add route 7 lci:/foo/bar 1\n"); + printf(" adds route to prefix '/foo/bar' on egress connection 7 with cost 1\n"); + printf(" add route tun3 lci:/foo/bar 1\n"); + printf(" adds route to prefix '/foo/bar' on egress connection 'tun3' with cost 1\n"); + printf("\n"); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlAddRoute_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + MetisControlState *state = ops->closure; + + if (parcList_Size(args) != 5) { + _metisControlAddRoute_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + const char *symbolicOrConnid = parcList_GetAtIndex(args, 2); + + if (_validateSymbolicName(symbolicOrConnid) || _isNumber(symbolicOrConnid)) { + const char *prefixString = parcList_GetAtIndex(args, 3); + unsigned cost = atoi(parcList_GetAtIndex(args, 4)); + + if (cost == 0) { + printf("ERROR: cost must be positive integer, got %u from '%s'\n", cost, (char *) parcList_GetAtIndex(args, 4)); + return MetisCommandReturn_Failure; + } + + CCNxName *prefix = ccnxName_CreateFromCString(prefixString); + if (prefix == NULL) { + printf("ERROR: could not parse prefix '%s'\n", prefixString); + return MetisCommandReturn_Failure; + } + + char *protocolTypeAsString = "static"; + + CPINameRouteProtocolType protocolType = cpiNameRouteProtocolType_FromString(protocolTypeAsString); + CPINameRouteType routeType = cpiNameRouteType_LONGEST_MATCH; + CPIAddress *nexthop = NULL; + + struct timeval *lifetime = NULL; + + CPIRouteEntry *route = NULL; + + if (_isNumber(symbolicOrConnid)) { + unsigned connid = (unsigned) strtold(symbolicOrConnid, NULL); + route = cpiRouteEntry_Create(prefix, connid, nexthop, protocolType, routeType, lifetime, cost); + } else { + route = cpiRouteEntry_CreateSymbolic(prefix, symbolicOrConnid, protocolType, routeType, lifetime, cost); + } + + CCNxControl *addRouteRequest = ccnxControl_CreateAddRouteRequest(route); + + cpiRouteEntry_Destroy(&route); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(addRouteRequest)); + printf("request: %s\n", str); + parcMemory_Deallocate((void **) &str); + } + + CCNxMetaMessage *message = ccnxMetaMessage_CreateFromControl(addRouteRequest); + CCNxMetaMessage *rawResponse = metisControlState_WriteRead(state, message); + ccnxMetaMessage_Release(&message); + + ccnxControl_Release(&addRouteRequest); + + CCNxControl *response = ccnxMetaMessage_GetControl(rawResponse); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(response)); + printf("response: %s\n", str); + parcMemory_Deallocate((void **) &str); + } + + ccnxMetaMessage_Release(&rawResponse); + + return MetisCommandReturn_Success; + } else { + printf("ERROR: Invalid symbolic or connid. Symbolic name must begin with an alpha followed by alphanum. connid must be an integer\n"); + return MetisCommandReturn_Failure; + } +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_AddRoute.h b/metis/ccnx/forwarder/metis/config/metisControl_AddRoute.h new file mode 100644 index 00000000..7ffda24c --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_AddRoute.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metisControl_AddRoute.h + * @brief Add a static route + * + * Implements the "add route" node of the CLI tree + * + */ + +#ifndef Metis_metisControl_AddRoute_h +#define Metis_metisControl_AddRoute_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlAddRoute_Create(MetisControlState *state); +MetisCommandOps *metisControlAddRoute_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_AddRoute_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Cache.c b/metis/ccnx/forwarder/metis/config/metisControl_Cache.c new file mode 100644 index 00000000..c2fde132 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Cache.c @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/security/parc_Security.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/config/metisControl_Cache.h> +#include <ccnx/forwarder/metis/config/metisControl_CacheServe.h> +#include <ccnx/forwarder/metis/config/metisControl_CacheStore.h> +#include <ccnx/forwarder/metis/config/metisControl_CacheClear.h> + +static void _metisControlCache_Init(MetisCommandParser *parser, MetisCommandOps *ops); +static MetisCommandReturn _metisControlCache_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlCache_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandCache = "cache"; +static const char *_commandCacheHelp = "help cache"; + +MetisCommandOps * +metisControlCache_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandCache, _metisControlCache_Init, _metisControlCache_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlCache_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandCacheHelp, NULL, _metisControlCache_HelpExecute, metisCommandOps_Destroy); +} + +// ===================================================== + +static MetisCommandReturn +_metisControlCache_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + MetisCommandOps *ops_cache_serve = metisControlCacheServe_HelpCreate(NULL); + MetisCommandOps *ops_cache_store = metisControlCacheStore_HelpCreate(NULL); + MetisCommandOps *ops_cache_clear = metisControlCacheClear_HelpCreate(NULL); + + printf("Available commands:\n"); + printf(" %s\n", ops_cache_serve->command); + printf(" %s\n", ops_cache_store->command); + printf(" %s\n", ops_cache_clear->command); + printf("\n"); + + metisCommandOps_Destroy(&ops_cache_serve); + metisCommandOps_Destroy(&ops_cache_store); + metisCommandOps_Destroy(&ops_cache_clear); + + return MetisCommandReturn_Success; +} + +static void +_metisControlCache_Init(MetisCommandParser *parser, MetisCommandOps *ops) +{ + MetisControlState *state = ops->closure; + metisControlState_RegisterCommand(state, metisControlCacheServe_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlCacheStore_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlCacheClear_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlCacheServe_Create(state)); + metisControlState_RegisterCommand(state, metisControlCacheStore_Create(state)); + metisControlState_RegisterCommand(state, metisControlCacheClear_Create(state)); +} + +static MetisCommandReturn +_metisControlCache_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + return _metisControlCache_HelpExecute(parser, ops, args); +} + +// ====================================================================== diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Cache.h b/metis/ccnx/forwarder/metis/config/metisControl_Cache.h new file mode 100644 index 00000000..94f614f2 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Cache.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef Metis_metis_ControlCache_h +#define Metis_metis_ControlCache_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlCache_Create(MetisControlState *state); +MetisCommandOps *metisControlCache_HelpCreate(MetisControlState *state); +#endif // Metis_metis_ControlCache_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_CacheClear.c b/metis/ccnx/forwarder/metis/config/metisControl_CacheClear.c new file mode 100644 index 00000000..1b2c2e8f --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_CacheClear.c @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/config/metisControl_CacheClear.h> + +#include <ccnx/api/control/cpi_ManageLinks.h> +#include <ccnx/api/control/cpi_Acks.h> +#include <ccnx/api/control/cpi_Forwarding.h> + +static MetisCommandReturn _metisControlCacheClear_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlCacheClear_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandCacheClear = "cache clear"; +static const char *_commandCacheClearHelp = "help cache clear"; + +// ==================================================== + +MetisCommandOps * +metisControlCacheClear_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandCacheClear, NULL, _metisControlCacheClear_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlCacheClear_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandCacheClearHelp, NULL, _metisControlCacheClear_HelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + +static MetisCommandReturn +_metisControlCacheClear_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("cache clear\n"); + printf("\n"); + + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlCacheClear_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + if (parcList_Size(args) != 2) { + _metisControlCacheClear_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + CCNxControl * cacheRequest = ccnxControl_CreateCacheClearRequest(); + + MetisControlState *state = ops->closure; + CCNxMetaMessage *message = ccnxMetaMessage_CreateFromControl(cacheRequest); + CCNxMetaMessage *rawResponse = metisControlState_WriteRead(state, message); + ccnxMetaMessage_Release(&message); + + CCNxControl *response = ccnxMetaMessage_GetControl(rawResponse); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(response)); + printf("reponse:\n%s\n", str); + parcMemory_Deallocate((void **) &str); + } + + if(!cpiAcks_IsAck(ccnxControl_GetJson(response))){ + printf("command failed\n"); + } + + ccnxMetaMessage_Release(&rawResponse); + + return MetisCommandReturn_Success; + +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_CacheClear.h b/metis/ccnx/forwarder/metis/config/metisControl_CacheClear.h new file mode 100644 index 00000000..126345be --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_CacheClear.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metisControl_ListInterfaces.h + * @brief List the metis interfaces + * + * Implements the "list interfaces" and "help list interfaces" nodes of the command tree + * + */ + +#ifndef Metis_metisControl_CacheClear_h +#define Metis_metisControl_CacheClear_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlCacheClear_Create(MetisControlState *state); +MetisCommandOps *metisControlCacheClear_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_CacheClear_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_CacheServe.c b/metis/ccnx/forwarder/metis/config/metisControl_CacheServe.c new file mode 100644 index 00000000..6fb7bf4d --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_CacheServe.c @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/config/metisControl_CacheServe.h> + +#include <ccnx/api/control/cpi_ManageLinks.h> +#include <ccnx/api/control/cpi_Acks.h> +#include <ccnx/api/control/cpi_Forwarding.h> + +static MetisCommandReturn _metisControlCacheServe_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlCacheServe_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandCacheServe = "cache serve"; +static const char *_commandCacheServeHelp = "help cache serve"; + +// ==================================================== + +MetisCommandOps * +metisControlCacheServe_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandCacheServe, NULL, _metisControlCacheServe_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlCacheServe_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandCacheServeHelp, NULL, _metisControlCacheServe_HelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + +static MetisCommandReturn +_metisControlCacheServe_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("cache serve [on|off]\n"); + printf("\n"); + + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlCacheServe_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + if (parcList_Size(args) != 3) { + _metisControlCacheServe_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + CCNxControl *cacheRequest; + if (strcmp(parcList_GetAtIndex(args, 2), "on") == 0) { + cacheRequest = ccnxControl_CreateCacheServeRequest(true); + } else if (strcmp(parcList_GetAtIndex(args, 2), "off") == 0) { + cacheRequest = ccnxControl_CreateCacheServeRequest(false); + } else { + _metisControlCacheServe_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + MetisControlState *state = ops->closure; + CCNxMetaMessage *message = ccnxMetaMessage_CreateFromControl(cacheRequest); + CCNxMetaMessage *rawResponse = metisControlState_WriteRead(state, message); + ccnxMetaMessage_Release(&message); + + CCNxControl *response = ccnxMetaMessage_GetControl(rawResponse); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(response)); + printf("reponse:\n%s\n", str); + parcMemory_Deallocate((void **) &str); + } + + if (!cpiAcks_IsAck(ccnxControl_GetJson(response))) { + printf("command failed\n"); + } + + ccnxMetaMessage_Release(&rawResponse); + + return MetisCommandReturn_Success; +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_CacheServe.h b/metis/ccnx/forwarder/metis/config/metisControl_CacheServe.h new file mode 100644 index 00000000..a0e45a1c --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_CacheServe.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef Metis_metisControl_CacheServe_h +#define Metis_metisControl_CacheServe_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlCacheServe_Create(MetisControlState *state); +MetisCommandOps *metisControlCacheServe_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_CacheServe_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_CacheStore.c b/metis/ccnx/forwarder/metis/config/metisControl_CacheStore.c new file mode 100644 index 00000000..1a620b06 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_CacheStore.c @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/config/metisControl_CacheStore.h> + +#include <ccnx/api/control/cpi_ManageLinks.h> +#include <ccnx/api/control/cpi_Acks.h> +#include <ccnx/api/control/cpi_Forwarding.h> + +static MetisCommandReturn _metisControlCacheStore_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlCacheStore_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandCacheStore = "cache store"; +static const char *_commandCacheStoreHelp = "help cache store"; + +// ==================================================== + +MetisCommandOps * +metisControlCacheStore_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandCacheStore, NULL, _metisControlCacheStore_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlCacheStore_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandCacheStoreHelp, NULL, _metisControlCacheStore_HelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + +static MetisCommandReturn +_metisControlCacheStore_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("cache store [on|off]\n"); + printf("\n"); + + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlCacheStore_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + if (parcList_Size(args) != 3) { + _metisControlCacheStore_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + CCNxControl *cacheRequest; + if (strcmp(parcList_GetAtIndex(args, 2), "on") == 0) { + cacheRequest = ccnxControl_CreateCacheStoreRequest(true); + } else if (strcmp(parcList_GetAtIndex(args, 2), "off") == 0) { + cacheRequest = ccnxControl_CreateCacheStoreRequest(false); + } else { + _metisControlCacheStore_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + MetisControlState *state = ops->closure; + CCNxMetaMessage *message = ccnxMetaMessage_CreateFromControl(cacheRequest); + CCNxMetaMessage *rawResponse = metisControlState_WriteRead(state, message); + ccnxMetaMessage_Release(&message); + + CCNxControl *response = ccnxMetaMessage_GetControl(rawResponse); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(response)); + printf("reponse:\n%s\n", str); + parcMemory_Deallocate((void **) &str); + } + + if (!cpiAcks_IsAck(ccnxControl_GetJson(response))) { + printf("command failed:\n"); + } + + ccnxMetaMessage_Release(&rawResponse); + + return MetisCommandReturn_Success; +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_CacheStore.h b/metis/ccnx/forwarder/metis/config/metisControl_CacheStore.h new file mode 100644 index 00000000..02511ea3 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_CacheStore.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef Metis_metisControl_CacheStore_h +#define Metis_metisControl_CacheStore_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlCacheStore_Create(MetisControlState *state); +MetisCommandOps *metisControlCacheStore_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_CacheStore_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_List.c b/metis/ccnx/forwarder/metis/config/metisControl_List.c new file mode 100644 index 00000000..76995a31 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_List.c @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/security/parc_Security.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/config/metisControl_List.h> +#include <ccnx/forwarder/metis/config/metisControl_ListConnections.h> +#include <ccnx/forwarder/metis/config/metisControl_ListInterfaces.h> +#include <ccnx/forwarder/metis/config/metisControl_ListRoutes.h> + +static void _metisControlList_Init(MetisCommandParser *parser, MetisCommandOps *ops); +static MetisCommandReturn _metisControlList_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlList_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandList = "list"; +static const char *_commandListHelp = "help list"; + +MetisCommandOps * +metisControlList_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandList, _metisControlList_Init, _metisControlList_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlList_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandListHelp, NULL, _metisControlList_HelpExecute, metisCommandOps_Destroy); +} + +// ===================================================== + +static MetisCommandReturn +_metisControlList_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + MetisCommandOps *ops_list_connections = metisControlListConnections_HelpCreate(NULL); + MetisCommandOps *ops_list_interfaces = metisControlListInterfaces_HelpCreate(NULL); + MetisCommandOps *ops_list_routes = metisControlListRoutes_HelpCreate(NULL); + + printf("Available commands:\n"); + printf(" %s\n", ops_list_connections->command); + printf(" %s\n", ops_list_interfaces->command); + printf(" %s\n", ops_list_routes->command); + printf("\n"); + + metisCommandOps_Destroy(&ops_list_connections); + metisCommandOps_Destroy(&ops_list_interfaces); + metisCommandOps_Destroy(&ops_list_routes); + + return MetisCommandReturn_Success; +} + +static void +_metisControlList_Init(MetisCommandParser *parser, MetisCommandOps *ops) +{ + MetisControlState *state = ops->closure; + metisControlState_RegisterCommand(state, metisControlListConnections_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlListInterfaces_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlListRoutes_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlListConnections_Create(state)); + metisControlState_RegisterCommand(state, metisControlListInterfaces_Create(state)); + metisControlState_RegisterCommand(state, metisControlListRoutes_Create(state)); +} + +static MetisCommandReturn +_metisControlList_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + return _metisControlList_HelpExecute(parser, ops, args); +} + +// ====================================================================== diff --git a/metis/ccnx/forwarder/metis/config/metisControl_List.h b/metis/ccnx/forwarder/metis/config/metisControl_List.h new file mode 100644 index 00000000..95ed7f82 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_List.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metisControl_List.h + * @brief Root node for the "list" commands + * + * Implements the "list" node of the CLI tree. + * + */ + +#ifndef Metis_metis_ControlList_h +#define Metis_metis_ControlList_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlList_Create(MetisControlState *state); +MetisCommandOps *metisControlList_HelpCreate(MetisControlState *state); +#endif // Metis_metis_ControlList_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_ListConnections.c b/metis/ccnx/forwarder/metis/config/metisControl_ListConnections.c new file mode 100644 index 00000000..a6a645dd --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_ListConnections.c @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/config/metisControl_ListConnections.h> + +#include <ccnx/api/control/cpi_ManageLinks.h> +#include <ccnx/api/control/cpi_Forwarding.h> + +static MetisCommandReturn _metisControlListConnections_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlListConnections_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandListConnections = "list connections"; +static const char *_commandListConnectionsHelp = "help list connections"; + +MetisCommandOps * +metisControlListConnections_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandListConnections, NULL, _metisControlListConnections_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlListConnections_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandListConnectionsHelp, NULL, _metisControlListConnections_HelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + +static MetisCommandReturn +_metisControlListConnections_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("list connections: displays a 1-line summary of each connection\n"); + printf("\n"); + printf("The columns are:\n"); + printf(" connection id : an integer index for the connection\n"); + printf(" state : UP or DOWN\n"); + printf(" local address : the local network address associated with the connection\n"); + printf(" remote address: the remote network address associated with the connection\n"); + printf(" protocol : the network protocol (tcp, udp, gre, mcast, ether)\n"); + printf("\n"); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlListConnections_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + if (parcList_Size(args) != 2) { + _metisControlListConnections_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + MetisControlState *state = ops->closure; + + CCNxControl *connectionListRequest = ccnxControl_CreateConnectionListRequest(); + + CCNxMetaMessage *message = ccnxMetaMessage_CreateFromControl(connectionListRequest); + CCNxMetaMessage *rawResponse = metisControlState_WriteRead(state, message); + ccnxMetaMessage_Release(&message); + + CCNxControl *response = ccnxMetaMessage_GetControl(rawResponse); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(response)); + printf("reponse:\n%s\n", str); + parcMemory_Deallocate((void **) &str); + } + + CPIConnectionList *list = cpiLinks_ConnectionListFromControlMessage(response); + // + // //"%3u %10s %1s%1s %8u " + // // printf("%3.3s %10.10s %1.1s%1.1s %8.8s \n", "interface", "name", "loopback", "multicast", "MTU"); + for (size_t i = 0; i < cpiConnectionList_Length(list); i++) { + CPIConnection *connection = cpiConnectionList_Get(list, i); + char *string = cpiConnection_ToString(connection); + puts(string); + parcMemory_Deallocate((void **) &string); + cpiConnection_Release(&connection); + } + cpiConnectionList_Destroy(&list); + ccnxMetaMessage_Release(&rawResponse); + + return MetisCommandReturn_Success; +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_ListConnections.h b/metis/ccnx/forwarder/metis/config/metisControl_ListConnections.h new file mode 100644 index 00000000..4c9d5146 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_ListConnections.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metisControl_ListConnections.h + * @brief List the current connections of metis + * + * Implements the "list connections" node of the CLI tree + * + */ + +#ifndef Metis_metisControl_ListConnections_h +#define Metis_metisControl_ListConnections_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlListConnections_Create(MetisControlState *state); +MetisCommandOps *metisControlListConnections_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_ListConnections_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_ListInterfaces.c b/metis/ccnx/forwarder/metis/config/metisControl_ListInterfaces.c new file mode 100644 index 00000000..d08db325 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_ListInterfaces.c @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/config/metisControl_ListInterfaces.h> + +#include <ccnx/api/control/cpi_ManageLinks.h> +#include <ccnx/api/control/cpi_Forwarding.h> + +static MetisCommandReturn _metisControlListInterfaces_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlListInterfaces_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandListInterfaces = "list interfaces"; +static const char *_commandListInterfacesHelp = "help list interfaces"; + +// ==================================================== + +MetisCommandOps * +metisControlListInterfaces_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandListInterfaces, NULL, _metisControlListInterfaces_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlListInterfaces_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandListInterfacesHelp, NULL, _metisControlListInterfaces_HelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + +static MetisCommandReturn +_metisControlListInterfaces_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("list interfaces\n"); + printf("\n"); + + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlListInterfaces_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + if (parcList_Size(args) != 2) { + _metisControlListInterfaces_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + MetisControlState *state = ops->closure; + CCNxControl *listRequest = ccnxControl_CreateInterfaceListRequest(); + + CCNxMetaMessage *message = ccnxMetaMessage_CreateFromControl(listRequest); + CCNxMetaMessage *rawResponse = metisControlState_WriteRead(state, message); + ccnxMetaMessage_Release(&message); + + CCNxControl *response = ccnxMetaMessage_GetControl(rawResponse); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(response)); + printf("reponse:\n%s\n", str); + parcMemory_Deallocate((void **) &str); + } + + CPIInterfaceSet *set = cpiLinks_InterfacesFromControlMessage(response); + + //"%3u %10s %1s%1s %8u " + printf("%3.3s %10.10s %1.1s%1.1s %8.8s \n", "interface", "name", "loopback", "multicast", "MTU"); + for (size_t i = 0; i < cpiInterfaceSet_Length(set); i++) { + CPIInterface *interface = cpiInterfaceSet_GetByOrdinalIndex(set, i); + char *string = cpiInterface_ToString(interface); + puts(string); + parcMemory_Deallocate((void **) &string); + } + + cpiInterfaceSet_Destroy(&set); + + ccnxMetaMessage_Release(&rawResponse); + + return MetisCommandReturn_Success; +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_ListInterfaces.h b/metis/ccnx/forwarder/metis/config/metisControl_ListInterfaces.h new file mode 100644 index 00000000..9d874ab1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_ListInterfaces.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metisControl_ListInterfaces.h + * @brief List the metis interfaces + * + * Implements the "list interfaces" and "help list interfaces" nodes of the command tree + * + */ + +#ifndef Metis_metisControl_ListInterfaces_h +#define Metis_metisControl_ListInterfaces_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlListInterfaces_Create(MetisControlState *state); +MetisCommandOps *metisControlListInterfaces_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_ListInterfaces_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_ListRoutes.c b/metis/ccnx/forwarder/metis/config/metisControl_ListRoutes.c new file mode 100644 index 00000000..0f84fa3a --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_ListRoutes.c @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_Time.h> + +#include <ccnx/forwarder/metis/config/metisControl_ListRoutes.h> + +#include <ccnx/api/control/cpi_ManageLinks.h> +#include <ccnx/api/control/cpi_Forwarding.h> + +static MetisCommandReturn _metisControlListRoutes_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlListRoutes_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandListRoutes = "list routes"; +static const char *_commandListRoutesHelp = "help list routes"; + +// ==================================================== + +MetisCommandOps * +metisControlListRoutes_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandListRoutes, NULL, _metisControlListRoutes_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlListRoutes_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandListRoutesHelp, NULL, _metisControlListRoutes_HelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + +static MetisCommandReturn +_metisControlListRoutes_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("command: list routes\n"); + printf("\n"); + printf("This command will fetch the prefix routing table. For each route, it will list:\n"); + printf(" iface: interface\n"); + printf(" protocol: the routing protocol, such as STATIC, CONNECTED, etc.\n"); + printf(" type: LMP or EXACT (longest matching prefix or exact match)\n"); + printf(" cost: The route cost, lower being preferred\n"); + printf(" next: List of next hops by interface id\n"); + printf(" prefix: The CCNx name prefix\n"); + printf("\n"); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlListRoutes_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + if (parcList_Size(args) != 2) { + _metisControlListRoutes_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + MetisControlState *state = ops->closure; + + CCNxControl *routeListRequest = ccnxControl_CreateRouteListRequest(); + + CCNxMetaMessage *message = ccnxMetaMessage_CreateFromControl(routeListRequest); + + CCNxMetaMessage *rawResponse = metisControlState_WriteRead(state, message); + + CCNxControl *response = ccnxMetaMessage_GetControl(rawResponse); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(response)); + printf("reponse:\n%s\n", str); + parcMemory_Deallocate((void **) &str); + } + + CPIRouteEntryList *list = cpiForwarding_RouteListFromControlMessage(response); + + printf("%6.6s %9.9s %7.7s %8.8s %20.20s %s\n", "iface", "protocol", "route", "cost", "next", "prefix"); + + for (size_t i = 0; i < cpiRouteEntryList_Length(list); i++) { + CPIRouteEntry *route = cpiRouteEntryList_Get(list, i); + + PARCBufferComposer *composer = parcBufferComposer_Create(); + + parcBufferComposer_Format(composer, "%6d %9.9s %7.7s %8u ", + cpiRouteEntry_GetInterfaceIndex(route), + cpiNameRouteProtocolType_ToString(cpiRouteEntry_GetRouteProtocolType(route)), + cpiNameRouteType_ToString(cpiRouteEntry_GetRouteType(route)), + cpiRouteEntry_GetCost(route)); + + if (cpiRouteEntry_GetNexthop(route) != NULL) { + cpiAddress_BuildString(cpiRouteEntry_GetNexthop(route), composer); + } else { + parcBufferComposer_PutString(composer, "---.---.---.---/...."); + } + + if (cpiRouteEntry_HasLifetime(route)) { + char *timeString = parcTime_TimevalAsString(cpiRouteEntry_GetLifetime(route)); + parcBufferComposer_PutString(composer, timeString); + parcMemory_Deallocate((void **) &timeString); + } else { + parcBufferComposer_PutString(composer, " "); + } + + char *ccnxName = ccnxName_ToString(cpiRouteEntry_GetPrefix(route)); + parcBufferComposer_PutString(composer, ccnxName); + parcMemory_Deallocate((void **) &ccnxName); + + PARCBuffer *tempBuffer = parcBufferComposer_ProduceBuffer(composer); + char *result = parcBuffer_ToString(tempBuffer); + parcBuffer_Release(&tempBuffer); + + puts(result); + parcMemory_Deallocate((void **) &result); + parcBufferComposer_Release(&composer); + cpiRouteEntry_Destroy(&route); + } + + cpiRouteEntryList_Destroy(&list); + ccnxMetaMessage_Release(&rawResponse); + ccnxControl_Release(&routeListRequest); + + printf("Done\n\n"); + + return MetisCommandReturn_Success; +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_ListRoutes.h b/metis/ccnx/forwarder/metis/config/metisControl_ListRoutes.h new file mode 100644 index 00000000..f7611259 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_ListRoutes.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metisControl_ListRoutes.h + * @brief List the metis routes + * + * Implements the "list routes" and "help list routes" nodes of the command tree + * + */ +#ifndef Metis_metisControl_ListRoutes_h +#define Metis_metisControl_ListRoutes_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlListRoutes_Create(MetisControlState *state); +MetisCommandOps *metisControlListRoutes_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_ListRoutes_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Quit.c b/metis/ccnx/forwarder/metis/config/metisControl_Quit.c new file mode 100644 index 00000000..c8b0e783 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Quit.c @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/security/parc_Security.h> +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/config/metisControl_Quit.h> + +static MetisCommandReturn _metisControlQuit_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlQuit_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandQuit = "quit"; +static const char *_commandQuitHelp = "help quit"; + +// ==================================================== + +MetisCommandOps * +metisControlQuit_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandQuit, NULL, _metisControlQuit_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlQuit_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandQuitHelp, NULL, _metisControlQuit_HelpExecute, metisCommandOps_Destroy); +} + +// ============================================== + +static MetisCommandReturn +_metisControlQuit_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("Exits the interactive control program\n\n"); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlQuit_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("exiting interactive shell\n"); + return MetisCommandReturn_Exit; +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Quit.h b/metis/ccnx/forwarder/metis/config/metisControl_Quit.h new file mode 100644 index 00000000..a3c278f8 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Quit.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metisControl_Quit.h + * @brief The quit command + * + * Implements the "quit" and "help quit" nodes of the command tree + * + */ +#ifndef Metis_metisControl_Quit_h +#define Metis_metisControl_Quit_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlQuit_Create(MetisControlState *state); +MetisCommandOps *metisControlQuit_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_Quit_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Remove.c b/metis/ccnx/forwarder/metis/config/metisControl_Remove.c new file mode 100644 index 00000000..2b494bf3 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Remove.c @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/security/parc_Security.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/config/metisControl_Remove.h> +#include <ccnx/forwarder/metis/config/metisControl_RemoveConnection.h> +#include <ccnx/forwarder/metis/config/metisControl_RemoveRoute.h> + +static void _metisControlRemove_Init(MetisCommandParser *parser, MetisCommandOps *ops); +static MetisCommandReturn _metisControlRemove_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlRemove_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandRemove = "remove"; +static const char *_commandRemoveHelp = "help remove"; + +// ==================================================== + +MetisCommandOps * +metisControlRemove_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandRemove, _metisControlRemove_Init, _metisControlRemove_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlRemove_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandRemoveHelp, NULL, _metisControlRemove_HelpExecute, metisCommandOps_Destroy); +} + +// ============================================== + +static MetisCommandReturn +_metisControlRemove_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + MetisCommandOps *ops_remove_connection = metisControlRemoveConnection_Create(NULL); + MetisCommandOps *ops_remove_route = metisControlRemoveRoute_Create(NULL); + + printf("Available commands:\n"); + printf(" %s\n", ops_remove_connection->command); + printf(" %s\n", ops_remove_route->command); + printf("\n"); + + metisCommandOps_Destroy(&ops_remove_connection); + metisCommandOps_Destroy(&ops_remove_route); + return MetisCommandReturn_Success; +} + +static void +_metisControlRemove_Init(MetisCommandParser *parser, MetisCommandOps *ops) +{ + MetisControlState *state = ops->closure; + metisControlState_RegisterCommand(state, metisControlRemoveConnection_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlRemoveRoute_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlRemoveConnection_Create(state)); + metisControlState_RegisterCommand(state, metisControlRemoveRoute_Create(state)); +} + +static MetisCommandReturn +_metisControlRemove_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + return _metisControlRemove_HelpExecute(parser, ops, args); +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Remove.h b/metis/ccnx/forwarder/metis/config/metisControl_Remove.h new file mode 100644 index 00000000..b6ddc229 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Remove.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metisControl_Remove.h + * @brief Implements the remove node of the CLI tree + * + * Implements the "remove" and "help remove" nodes of the command tree + * + */ +#ifndef Metis_metis_ControlRemove_h +#define Metis_metis_ControlRemove_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlRemove_Create(MetisControlState *state); +MetisCommandOps *metisControlRemove_HelpCreate(MetisControlState *state); +#endif // Metis_metis_ControlRemove_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_RemoveConnection.c b/metis/ccnx/forwarder/metis/config/metisControl_RemoveConnection.c new file mode 100644 index 00000000..12c796c1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_RemoveConnection.c @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_Network.h> +#include <ccnx/api/control/cpi_Address.h> +#include <ccnx/api/control/cpi_InterfaceIPTunnel.h> +#include <ccnx/api/control/cpi_ManageLinks.h> +#include <ccnx/api/control/cpi_ConnectionEthernet.h> + +#include <ccnx/forwarder/metis/config/metisControl_RemoveConnection.h> + +static void _metisControlRemoveConnection_Init(MetisCommandParser *parser, MetisCommandOps *ops); +static MetisCommandReturn _metisControlRemoveConnection_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlRemoveConnection_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +// =================================================== + +//TODO: implement this also for TCP and ethernet + +static MetisCommandReturn _metisControlRemoveConnection_UdpHelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlRemoveConnection_UdpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +// =================================================== + + +static const char *_commandRemoveConnection = "remove connection"; +static const char *_commandRemoveConnectionUdp = "remove connection udp"; +static const char *_commandRemoveConnectionHelp = "help remove connection"; +static const char *_commandRemoveConnectionUdpHelp = "help remove connection udp"; + +// ==================================================== + +MetisCommandOps * +metisControlRemoveConnection_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandRemoveConnection, _metisControlRemoveConnection_Init, _metisControlRemoveConnection_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlRemoveConnection_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandRemoveConnectionHelp, NULL, _metisControlRemoveConnection_HelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + +static MetisCommandOps * +_metisControlRemoveConnection_UdpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandRemoveConnectionUdp, NULL, + _metisControlRemoveConnection_UdpExecute, metisCommandOps_Destroy); +} + +static MetisCommandOps * +_metisControlRemoveConnection_UdpHelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandRemoveConnectionUdpHelp, NULL, + _metisControlRemoveConnection_UdpHelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + +/** + * A symbolic name must be at least 1 character and must begin with an alpha. + * The remainder must be an alphanum. + */ +static bool +_validateSymbolicName(const char *symbolic) +{ + bool success = false; + size_t len = strlen(symbolic); + if (len > 0) { + if (isalpha(symbolic[0])) { + success = true; + for (size_t i = 1; i < len; i++) { + if (!isalnum(symbolic[i])) { + success = false; + break; + } + } + } + } + return success; +} + +// ==================================================== + +static MetisCommandReturn +_metisControlRemoveConnection_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("Available commands:\n"); + printf(" %s\n", _commandRemoveConnectionUdp); + return MetisCommandReturn_Success; +} + +static void +_metisControlRemoveConnection_Init(MetisCommandParser *parser, MetisCommandOps *ops) +{ + MetisControlState *state = ops->closure; + metisControlState_RegisterCommand(state, _metisControlRemoveConnection_UdpHelpCreate(state)); + + metisControlState_RegisterCommand(state, _metisControlRemoveConnection_UdpCreate(state)); +} + + +static MetisCommandReturn +_metisControlRemoveConnection_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + return _metisControlRemoveConnection_HelpExecute(parser, ops, args); +} + +// ================================================== + +static bool +_parseMessage(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args, char **symbolicPtr) +{ + if (parcList_Size(args) != 4) { + _metisControlRemoveConnection_UdpHelpExecute(parser, ops, args); + return false; + } + + if ((strcmp(parcList_GetAtIndex(args, 0), "remove") != 0) || + (strcmp(parcList_GetAtIndex(args, 1), "connection") != 0) || + (strcmp(parcList_GetAtIndex(args, 2), "udp") != 0)) { + _metisControlRemoveConnection_UdpHelpExecute(parser, ops, args); + return false; + } + + char *symbolic = parcList_GetAtIndex(args, 3); + if (_validateSymbolicName(symbolic)) { + *symbolicPtr = symbolic; + return true; + } + return false; +} + +static void +_removeUdpConnection(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args, char *symbolic) +{ + MetisControlState *state = ops->closure; + struct sockaddr_in *local = parcNetwork_SockInet4AddressAny(); //parcNetwork_SockInet4Address("192.168.56.27", 12346); + struct sockaddr_in *remote = parcNetwork_SockInet4AddressAny(); //parcNetwork_SockInet4Address("192.168.62.10", 19695); + CPIAddress *localAddress = cpiAddress_CreateFromInet(local); + CPIAddress *remoteAddress = cpiAddress_CreateFromInet(remote); + + CPIInterfaceIPTunnel *ipTunnel = cpiInterfaceIPTunnel_Create(0, localAddress, remoteAddress, IPTUN_UDP, symbolic); + + PARCJSON *cpiMessage = cpiLinks_RemoveIPTunnel(ipTunnel); + CCNxControl *controlMessage = ccnxControl_CreateCPIRequest(cpiMessage); + parcJSON_Release(&cpiMessage); + + CCNxMetaMessage *message = ccnxMetaMessage_CreateFromControl(controlMessage); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(message)); + printf("request: %s\n", str); + parcMemory_Deallocate((void **) &str); + } + + + CCNxMetaMessage *rawResponse = metisControlState_WriteRead(state, message); + ccnxMetaMessage_Release(&message); + + CCNxControl *response = ccnxMetaMessage_GetControl(rawResponse); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(response)); + printf("reponse:\n%s\n", str); + parcMemory_Deallocate((void **) &str); + } + + ccnxControl_Release(&controlMessage); + ccnxMetaMessage_Release(&rawResponse); + cpiInterfaceIPTunnel_Release(&ipTunnel); + parcMemory_Deallocate((void **) &remote); + parcMemory_Deallocate((void **) &local); +} + +static MetisCommandReturn +_metisControlRemoveConnection_UdpHelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("command:\n"); + printf(" remove connection upd <symbolic>\n"); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlRemoveConnection_UdpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + char *symbolic = NULL; + if (!_parseMessage(parser, ops, args, &symbolic)) { + return MetisCommandReturn_Success; + } + + _removeUdpConnection(parser, ops, args, symbolic); + + return MetisCommandReturn_Success; +} + + diff --git a/metis/ccnx/forwarder/metis/config/metisControl_RemoveConnection.h b/metis/ccnx/forwarder/metis/config/metisControl_RemoveConnection.h new file mode 100644 index 00000000..ffaa98f0 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_RemoveConnection.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metisControl_RemoveConnection.h + * @brief Remove a connection from the connection table + * + * Implements the "remove connection" and "help remove connection" nodes of the CLI tree + * + */ + +#ifndef Metis_metisControl_RemoveConnection_h +#define Metis_metisControl_RemoveConnection_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlRemoveConnection_Create(MetisControlState *state); +MetisCommandOps *metisControlRemoveConnection_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_RemoveConnection_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_RemoveRoute.c b/metis/ccnx/forwarder/metis/config/metisControl_RemoveRoute.c new file mode 100644 index 00000000..fa51a268 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_RemoveRoute.c @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_List.h> + +#include <ccnx/api/control/cpi_NameRouteProtocolType.h> +#include <ccnx/api/control/cpi_RouteEntry.h> +#include <ccnx/api/control/cpi_Forwarding.h> + +#include <ccnx/forwarder/metis/config/metisControl_RemoveRoute.h> + +static MetisCommandReturn _metisControlRemoveRoute_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlRemoveRoute_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandRemoveRoute = "remove route"; +static const char *_commandRemoveRouteHelp = "help remove route"; + +// ==================================================== + +MetisCommandOps * +metisControlRemoveRoute_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandRemoveRoute, NULL, _metisControlRemoveRoute_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlRemoveRoute_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandRemoveRouteHelp, NULL, _metisControlRemoveRoute_HelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + + +/** + * Return true if string is purely an integer + */ +static bool +_isNumber(const char *string) +{ + size_t len = strlen(string); + for (size_t i = 0; i < len; i++) { + if (!isdigit(string[i])) { + return false; + } + } + return true; +} + +/** + * A symbolic name must be at least 1 character and must begin with an alpha. + * The remainder must be an alphanum. + */ +static bool +_validateSymbolicName(const char *symbolic) +{ + bool success = false; + size_t len = strlen(symbolic); + if (len > 0) { + if (isalpha(symbolic[0])) { + success = true; + for (size_t i = 1; i < len; i++) { + if (!isalnum(symbolic[i])) { + success = false; + break; + } + } + } + } + return success; +} + +static MetisCommandReturn +_metisControlRemoveRoute_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("commands:\n"); + printf(" remove route <symbolic | connid> <prefix>\n"); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlRemoveRoute_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + + MetisControlState *state = ops->closure; + + if (parcList_Size(args) != 4) { + _metisControlRemoveRoute_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + const char *symbolicOrConnid = parcList_GetAtIndex(args, 2); + if (_validateSymbolicName(symbolicOrConnid) || _isNumber(symbolicOrConnid)) { + const char *prefixString = parcList_GetAtIndex(args, 3); + + CCNxName *prefix = ccnxName_CreateFromCString(prefixString); + if (prefix == NULL) { + printf("ERROR: could not parse prefix '%s'\n", prefixString); + return MetisCommandReturn_Failure; + } + + char *protocolTypeAsString = "static"; + + CPINameRouteProtocolType protocolType = cpiNameRouteProtocolType_FromString(protocolTypeAsString); + CPINameRouteType routeType = cpiNameRouteType_LONGEST_MATCH; + CPIAddress *nexthop = NULL; + + struct timeval *lifetime = NULL; + + CPIRouteEntry *route = NULL; + + unsigned cost = 1; + + if (_isNumber(symbolicOrConnid)) { + unsigned connid = (unsigned) strtold(symbolicOrConnid, NULL); + route = cpiRouteEntry_Create(prefix, connid, nexthop, protocolType, routeType, lifetime, cost); + } else { + route = cpiRouteEntry_CreateSymbolic(prefix, symbolicOrConnid, protocolType, routeType, lifetime, cost); + } + + CCNxControl *removeRouteRequest = ccnxControl_CreateRemoveRouteRequest(route); + + cpiRouteEntry_Destroy(&route); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(removeRouteRequest)); + printf("request: %s\n", str); + parcMemory_Deallocate((void **) &str); + } + + CCNxMetaMessage *message = ccnxMetaMessage_CreateFromControl(removeRouteRequest); + CCNxMetaMessage *rawResponse = metisControlState_WriteRead(state, message); + ccnxMetaMessage_Release(&message); + + ccnxControl_Release(&removeRouteRequest); + + CCNxControl *response = ccnxMetaMessage_GetControl(rawResponse); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(response)); + printf("response: %s\n", str); + parcMemory_Deallocate((void **) &str); + } + + ccnxMetaMessage_Release(&rawResponse); + + return MetisCommandReturn_Success; + + }else{ + printf("ERROR: Invalid symbolic or connid. Symbolic name must begin with an alpha followed by alphanum. connid must be an integer\n"); + return MetisCommandReturn_Failure; + } + +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_RemoveRoute.h b/metis/ccnx/forwarder/metis/config/metisControl_RemoveRoute.h new file mode 100644 index 00000000..ef438680 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_RemoveRoute.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metisControl_RemoveRoute.h + * @brief Remove a route from the FIB + * + * Implements the "remove route" and "help remove route" nodes of the command tree + * + */ + +#ifndef Metis_metisControl_RemoveRoute_h +#define Metis_metisControl_RemoveRoute_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlRemoveRoute_Create(MetisControlState *state); +MetisCommandOps *metisControlRemoveRoute_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_RemoveRoute_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Root.c b/metis/ccnx/forwarder/metis/config/metisControl_Root.c new file mode 100644 index 00000000..61e9ba50 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Root.c @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <ccnx/forwarder/metis/config/metisControl_Root.h> +#include <ccnx/forwarder/metis/config/metisControl_Add.h> +#include <ccnx/forwarder/metis/config/metisControl_List.h> +#include <ccnx/forwarder/metis/config/metisControl_Quit.h> +#include <ccnx/forwarder/metis/config/metisControl_Remove.h> +#include <ccnx/forwarder/metis/config/metisControl_Set.h> +#include <ccnx/forwarder/metis/config/metisControl_Unset.h> +#include <ccnx/forwarder/metis/config/metisControl_Cache.h> + +static void _metisControlRoot_Init(MetisCommandParser *parser, MetisCommandOps *ops); +static MetisCommandReturn _metisControlRoot_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlRoot_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandRoot = ""; +static const char *_commandRootHelp = "help"; + +// ==================================================== + +MetisCommandOps * +metisControlRoot_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandRoot, _metisControlRoot_Init, _metisControlRoot_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlRoot_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandRootHelp, NULL, _metisControlRoot_HelpExecute, metisCommandOps_Destroy); +} + +// =================================================== + +static MetisCommandReturn +_metisControlRoot_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("Command-line execution:\n"); + printf(" metis_control [--keystore <keystorepath>] [--password <password>] command\n"); + printf("\n"); + printf("Interactive execution:\n"); + printf(" metis_control [--keystore <keystorepath>] [--password <password>]\n"); + printf("\n"); + printf("If the keystore is not specified, the default path is used. Keystore must exist prior to running program.\n"); + printf("If the password is not specified, the user will be prompted.\n"); + printf("\n"); + + MetisCommandOps *ops_help_add = metisControlAdd_CreateHelp(NULL); + MetisCommandOps *ops_help_list = metisControlList_HelpCreate(NULL); + MetisCommandOps *ops_help_quit = metisControlQuit_HelpCreate(NULL); + MetisCommandOps *ops_help_remove = metisControlRemove_HelpCreate(NULL); + MetisCommandOps *ops_help_set = metisControlSet_HelpCreate(NULL); + MetisCommandOps *ops_help_unset = metisControlUnset_HelpCreate(NULL); + MetisCommandOps *ops_help_cache = metisControlCache_HelpCreate(NULL); + + printf("Available commands:\n"); + printf(" %s\n", ops_help_add->command); + printf(" %s\n", ops_help_list->command); + printf(" %s\n", ops_help_quit->command); + printf(" %s\n", ops_help_remove->command); + printf(" %s\n", ops_help_set->command); + printf(" %s\n", ops_help_unset->command); + printf(" %s\n", ops_help_cache->command); + printf("\n"); + + metisCommandOps_Destroy(&ops_help_add); + metisCommandOps_Destroy(&ops_help_list); + metisCommandOps_Destroy(&ops_help_quit); + metisCommandOps_Destroy(&ops_help_remove); + metisCommandOps_Destroy(&ops_help_set); + metisCommandOps_Destroy(&ops_help_unset); + metisCommandOps_Destroy(&ops_help_cache); + + return MetisCommandReturn_Success; +} + +static void +_metisControlRoot_Init(MetisCommandParser *parser, MetisCommandOps *ops) +{ + MetisControlState *state = ops->closure; + + metisControlState_RegisterCommand(state, metisControlAdd_CreateHelp(state)); + metisControlState_RegisterCommand(state, metisControlList_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlQuit_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlRemove_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlSet_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlUnset_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlCache_HelpCreate(state)); + + metisControlState_RegisterCommand(state, metisControlAdd_Create(state)); + metisControlState_RegisterCommand(state, metisControlList_Create(state)); + metisControlState_RegisterCommand(state, metisControlQuit_Create(state)); + metisControlState_RegisterCommand(state, metisControlRemove_Create(state)); + metisControlState_RegisterCommand(state, metisControlSet_Create(state)); + metisControlState_RegisterCommand(state, metisControlUnset_Create(state)); + metisControlState_RegisterCommand(state, metisControlCache_Create(state)); +} + +static MetisCommandReturn +_metisControlRoot_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + return MetisCommandReturn_Success; +} + +// ====================================================================== diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Root.h b/metis/ccnx/forwarder/metis/config/metisControl_Root.h new file mode 100644 index 00000000..8ab37359 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Root.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metisControl_Root.h + * @brief Root of the command tree + * + * Implements the root of the command tree. This is the one module that + * needs to be seeded to the control state to build the whole tree. + * + */ + +#ifndef Metis_metisControl_Root_h +#define Metis_metisControl_Root_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlRoot_Create(MetisControlState *state); +MetisCommandOps *metisControlRoot_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_Root_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Set.c b/metis/ccnx/forwarder/metis/config/metisControl_Set.c new file mode 100644 index 00000000..c18fc297 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Set.c @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/security/parc_Security.h> +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/config/metisControl_Set.h> +#include <ccnx/forwarder/metis/config/metisControl_SetDebug.h> +#include <ccnx/forwarder/metis/config/metisControl_SetStrategy.h> +#include <ccnx/forwarder/metis/config/metisControl_SetWldr.h> + +static void _metisControlSet_Init(MetisCommandParser *parser, MetisCommandOps *ops); +static MetisCommandReturn _metisControlSet_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlSet_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandSet = "set"; +static const char *_commandSetHelp = "help set"; + +// =========================================================== + +MetisCommandOps * +metisControlSet_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandSet, _metisControlSet_Init, _metisControlSet_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlSet_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandSetHelp, NULL, _metisControlSet_HelpExecute, metisCommandOps_Destroy); +} + +// =========================================================== + +static void +_metisControlSet_Init(MetisCommandParser *parser, MetisCommandOps *ops) +{ + MetisControlState *state = ops->closure; + metisControlState_RegisterCommand(state, metisControlSetDebug_Create(state)); + metisControlState_RegisterCommand(state, metisControlSetDebug_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlSetStrategy_Create(state)); + metisControlState_RegisterCommand(state, metisControlSetStrategy_HelpCreate(state)); + metisControlState_RegisterCommand(state, metisControlSetWldr_Create(state)); + metisControlState_RegisterCommand(state, metisControlSetWldr_HelpCreate(state)); +} + +static MetisCommandReturn +_metisControlSet_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + MetisCommandOps *ops_help_set_debug = metisControlSetDebug_HelpCreate(NULL); + MetisCommandOps *ops_help_set_strategy = metisControlSetStrategy_HelpCreate(NULL); + MetisCommandOps *ops_help_set_wldr = metisControlSetWldr_HelpCreate(NULL); + + printf("Available commands:\n"); + printf(" %s\n", ops_help_set_debug->command); + printf(" %s\n", ops_help_set_strategy->command); + printf(" %s\n", ops_help_set_wldr->command); + printf("\n"); + + metisCommandOps_Destroy(&ops_help_set_debug); + metisCommandOps_Destroy(&ops_help_set_strategy); + metisCommandOps_Destroy(&ops_help_set_wldr); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlSet_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + return _metisControlSet_HelpExecute(parser, ops, args); +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Set.h b/metis/ccnx/forwarder/metis/config/metisControl_Set.h new file mode 100644 index 00000000..a7d10549 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Set.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metisControl_Set.h + * @brief Implements the set node of the CLI tree + * + * Implements the "set" and "help set" nodes of the command tree + * + */ +#ifndef Metis_metisControl_Set_h +#define Metis_metisControl_Set_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlSet_Create(MetisControlState *state); +MetisCommandOps *metisControlSet_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_Set_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_SetDebug.c b/metis/ccnx/forwarder/metis/config/metisControl_SetDebug.c new file mode 100644 index 00000000..30510230 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_SetDebug.c @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/api/control/cpi_ManageLinks.h> +#include <ccnx/api/control/cpi_Forwarding.h> + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_Dispatcher.h> +#include <ccnx/forwarder/metis/config/metisControl_SetDebug.h> + + +static MetisCommandReturn _metisControlSetDebug_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlSetDebug_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandSetDebug = "set debug"; +static const char *_commandSetDebugHelp = "help set debug"; + +// ==================================================== + +MetisCommandOps * +metisControlSetDebug_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandSetDebug, NULL, _metisControlSetDebug_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlSetDebug_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandSetDebugHelp, NULL, _metisControlSetDebug_HelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + +static MetisCommandReturn +_metisControlSetDebug_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("set debug: will enable the debug flag for more verbose output\n"); + printf("\n"); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlSetDebug_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + if (parcList_Size(args) != 2) { + _metisControlSetDebug_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + MetisControlState *state = ops->closure; + metisControlState_SetDebug(state, true); + printf("Debug flag set\n\n"); + return MetisCommandReturn_Success; +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_SetDebug.h b/metis/ccnx/forwarder/metis/config/metisControl_SetDebug.h new file mode 100644 index 00000000..56b085e3 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_SetDebug.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metisControl_SetDebug.h + * @brief Sets the debug flag for more verbose output + * + * Implements the "set debug" and "help set debug" nodes of the command tree + * + */ + +#ifndef Metis_metisControl_SetDebug_h +#define Metis_metisControl_SetDebug_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlSetDebug_Create(MetisControlState *state); +MetisCommandOps *metisControlSetDebug_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_SetDebug_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_SetStrategy.c b/metis/ccnx/forwarder/metis/config/metisControl_SetStrategy.c new file mode 100644 index 00000000..f7b805ca --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_SetStrategy.c @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/api/control/cpi_ManageLinks.h> +#include <ccnx/api/control/cpi_Forwarding.h> + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_Dispatcher.h> +#include <ccnx/forwarder/metis/config/metisControl_SetDebug.h> + +static MetisCommandReturn _metisControlSetStrategy_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlSetStrategy_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandSetStrategy = "set strategy"; +static const char *_commandSetStrategyHelp = "help set strategy"; + +// ==================================================== + +MetisCommandOps * +metisControlSetStrategy_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandSetStrategy, NULL, _metisControlSetStrategy_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlSetStrategy_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandSetStrategyHelp, NULL, _metisControlSetStrategy_HelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + +static MetisCommandReturn +_metisControlSetStrategy_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("set strategy <prefix> <strategy>\n"); + printf("available strateies:\n"); + printf(" random\n"); + printf(" loadbalancer\n"); + printf(" random_per_dash_segment\n"); + printf(" loadbalancer_with_delay\n"); + printf("\n"); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlSetStrategy_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + MetisControlState *state = ops->closure; + + if (parcList_Size(args) != 4) { + _metisControlSetStrategy_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + if (((strcmp(parcList_GetAtIndex(args, 0), "set") != 0) || (strcmp(parcList_GetAtIndex(args, 1), "strategy") != 0))) { + _metisControlSetStrategy_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + const char *prefixString = parcList_GetAtIndex(args, 2); + const char *strategy = parcList_GetAtIndex(args, 3); + CCNxName *prefix = ccnxName_CreateFromCString(prefixString); + if (prefix == NULL) { + printf("ERROR: could not parse prefix '%s'\n", prefixString); + return MetisCommandReturn_Failure; + } + + CPIForwardingStrategy *fwdStrategy = cpiForwardingStrategy_Create(prefix, (char *) strategy); + + CCNxControl *setFwdStrategyRequest = ccnxControl_CreateSetStrategyRequest(fwdStrategy); + + cpiForwardingStrategy_Destroy(&fwdStrategy); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(setFwdStrategyRequest)); + printf("request: %s\n", str); + parcMemory_Deallocate((void **) &str); + } + + CCNxMetaMessage *message = ccnxMetaMessage_CreateFromControl(setFwdStrategyRequest); + CCNxMetaMessage *rawResponse = metisControlState_WriteRead(state, message); + ccnxMetaMessage_Release(&message); + + ccnxControl_Release(&setFwdStrategyRequest); + + CCNxControl *response = ccnxMetaMessage_GetControl(rawResponse); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(response)); + printf("response: %s\n", str); + parcMemory_Deallocate((void **) &str); + } + + ccnxMetaMessage_Release(&rawResponse); + + return MetisCommandReturn_Success; +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_SetStrategy.h b/metis/ccnx/forwarder/metis/config/metisControl_SetStrategy.h new file mode 100644 index 00000000..e25b2c29 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_SetStrategy.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef Metis_metisControl_SetStrategy_h +#define Metis_metisControl_SetStrategy_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlSetStrategy_Create(MetisControlState *state); +MetisCommandOps *metisControlSetStrategy_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_SetStrategy_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_SetWldr.c b/metis/ccnx/forwarder/metis/config/metisControl_SetWldr.c new file mode 100644 index 00000000..6cc9c951 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_SetWldr.c @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/api/control/cpi_ManageWldr.h> +#include <ccnx/api/control/cpi_Forwarding.h> + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_Dispatcher.h> +#include <ccnx/forwarder/metis/config/metisControl_SetDebug.h> + + +static MetisCommandReturn _metisControlSetWldr_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlSetWldr_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandSetWldr = "set wldr"; +static const char *_commandSetWldrHelp = "help set wldr"; + +// ==================================================== + +MetisCommandOps * +metisControlSetWldr_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandSetWldr, NULL, _metisControlSetWldr_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlSetWldr_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandSetWldrHelp, NULL, _metisControlSetWldr_HelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + +static MetisCommandReturn +_metisControlSetWldr_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("set wldr <on|off> <connection_id>\n"); + printf("\n"); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlSetWldr_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + MetisControlState *state = ops->closure; + + if (parcList_Size(args) != 4) { + _metisControlSetWldr_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + if (((strcmp(parcList_GetAtIndex(args, 0), "set") != 0) || (strcmp(parcList_GetAtIndex(args, 1), "wldr") != 0))) { + _metisControlSetWldr_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + const char *activeStr = parcList_GetAtIndex(args, 2); + bool active; + if(strcmp(activeStr, "on") == 0){ + active = true; + }else if(strcmp(activeStr, "off") == 0){ + active = false; + }else{ + _metisControlSetWldr_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + const char *connId = parcList_GetAtIndex(args, 3); + + CPIManageWldr *cpiWldr = cpiManageWldr_Create(active, (char *) connId); + + CCNxControl *setWldrRequest = ccnxControl_CreateSetWldrRequest(cpiWldr); + + cpiManageWldr_Destroy(&cpiWldr); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(setWldrRequest)); + printf("request: %s\n", str); + parcMemory_Deallocate((void **) &str); + } + + CCNxMetaMessage *message = ccnxMetaMessage_CreateFromControl(setWldrRequest); + CCNxMetaMessage *rawResponse = metisControlState_WriteRead(state, message); + ccnxMetaMessage_Release(&message); + + ccnxControl_Release(&setWldrRequest); + + CCNxControl *response = ccnxMetaMessage_GetControl(rawResponse); + + if (metisControlState_GetDebug(state)) { + char *str = parcJSON_ToString(ccnxControl_GetJson(response)); + printf("response: %s\n", str); + parcMemory_Deallocate((void **) &str); + } + + if(ccnxControl_IsNACK(response)){ + printf("command set wldr failed"); + ccnxMetaMessage_Release(&rawResponse); + return MetisCommandReturn_Failure; + } + + ccnxMetaMessage_Release(&rawResponse); + + return MetisCommandReturn_Success; +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_SetWldr.h b/metis/ccnx/forwarder/metis/config/metisControl_SetWldr.h new file mode 100644 index 00000000..492d0865 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_SetWldr.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef Metis_metisControl_SetWldr_h +#define Metis_metisControl_SetWldr_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlSetWldr_Create(MetisControlState *state); +MetisCommandOps *metisControlSetWldr_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_SetWldr_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Unset.c b/metis/ccnx/forwarder/metis/config/metisControl_Unset.c new file mode 100644 index 00000000..852b8a9a --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Unset.c @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/security/parc_Security.h> +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/config/metisControl_Unset.h> +#include <ccnx/forwarder/metis/config/metisControl_UnsetDebug.h> + +static void _metisControlUnset_Init(MetisCommandParser *parser, MetisCommandOps *ops); + +static MetisCommandReturn _metisControlUnset_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlUnset_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandUnset = "unset"; +static const char *_commandUnsetHelp = "help unset"; + +// =========================================================== + +MetisCommandOps * +metisControlUnset_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandUnset, _metisControlUnset_Init, _metisControlUnset_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlUnset_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandUnsetHelp, NULL, _metisControlUnset_HelpExecute, metisCommandOps_Destroy); +} + +// =========================================================== + +static void +_metisControlUnset_Init(MetisCommandParser *parser, MetisCommandOps *ops) +{ + MetisControlState *state = ops->closure; + metisControlState_RegisterCommand(state, metisControlUnsetDebug_Create(state)); + metisControlState_RegisterCommand(state, metisControlUnsetDebug_HelpCreate(state)); +} + +static MetisCommandReturn +_metisControlUnset_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + MetisCommandOps *ops_help_unset_debug = metisControlUnsetDebug_HelpCreate(NULL); + + printf("Available commands:\n"); + printf(" %s\n", ops_help_unset_debug->command); + printf("\n"); + + metisCommandOps_Destroy(&ops_help_unset_debug); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlUnset_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + return _metisControlUnset_HelpExecute(parser, ops, args); +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_Unset.h b/metis/ccnx/forwarder/metis/config/metisControl_Unset.h new file mode 100644 index 00000000..d8ca6c16 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_Unset.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metisControl_Unset.h + * @brief Implements the unset node of the CLI tree + * + * Implements the "unset" and "help unset" nodes of the command tree + * + */ +#ifndef Metis_metisControl_Unset_h +#define Metis_metisControl_Unset_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlUnset_Create(MetisControlState *state); +MetisCommandOps *metisControlUnset_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_Unset_h diff --git a/metis/ccnx/forwarder/metis/config/metisControl_UnsetDebug.c b/metis/ccnx/forwarder/metis/config/metisControl_UnsetDebug.c new file mode 100644 index 00000000..acc877d6 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_UnsetDebug.c @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/api/control/cpi_ManageLinks.h> +#include <ccnx/api/control/cpi_Forwarding.h> + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_Dispatcher.h> +#include <ccnx/forwarder/metis/config/metisControl_UnsetDebug.h> + +static MetisCommandReturn _metisControlUnsetDebug_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); +static MetisCommandReturn _metisControlUnsetDebug_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args); + +static const char *_commandUnsetDebug = "unset debug"; +static const char *_commandUnsetDebugHelp = "help unset debug"; + +// ==================================================== + +MetisCommandOps * +metisControlUnsetDebug_Create(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandUnsetDebug, NULL, _metisControlUnsetDebug_Execute, metisCommandOps_Destroy); +} + +MetisCommandOps * +metisControlUnsetDebug_HelpCreate(MetisControlState *state) +{ + return metisCommandOps_Create(state, _commandUnsetDebugHelp, NULL, _metisControlUnsetDebug_HelpExecute, metisCommandOps_Destroy); +} + +// ==================================================== + +static MetisCommandReturn +_metisControlUnsetDebug_HelpExecute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + printf("unset debug: will disable the debug flag\n"); + printf("\n"); + return MetisCommandReturn_Success; +} + +static MetisCommandReturn +_metisControlUnsetDebug_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + if (parcList_Size(args) != 2) { + _metisControlUnsetDebug_HelpExecute(parser, ops, args); + return MetisCommandReturn_Failure; + } + + MetisControlState *state = ops->closure; + metisControlState_SetDebug(state, false); + printf("Debug flag cleared\n\n"); + + return MetisCommandReturn_Success; +} diff --git a/metis/ccnx/forwarder/metis/config/metisControl_UnsetDebug.h b/metis/ccnx/forwarder/metis/config/metisControl_UnsetDebug.h new file mode 100644 index 00000000..c3a02b05 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metisControl_UnsetDebug.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metisControl_UnsetDebug.h + * @brief Unsets the debug flag for more verbose output + * + * Implements the "unset debug" and "help unset debug" nodes of the CLI tree + * + */ + +#ifndef Metis_metisControl_UnsetDebug_h +#define Metis_metisControl_UnsetDebug_h + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +MetisCommandOps *metisControlUnsetDebug_Create(MetisControlState *state); +MetisCommandOps *metisControlUnsetDebug_HelpCreate(MetisControlState *state); +#endif // Metis_metisControl_UnsetDebug_h diff --git a/metis/ccnx/forwarder/metis/config/metis_CommandLineInterface.c b/metis/ccnx/forwarder/metis/config/metis_CommandLineInterface.c new file mode 100644 index 00000000..10857a29 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_CommandLineInterface.c @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NB: binds to all interfaces on the listen port, which might be a security issue. + * + * The CLI runs as an event managed listener. The Api here creates, starts, stops, and destroys it. + * + * The CLI is a user interface to the programmatic interface in <code>metis_Configuration.h</code> + * + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <arpa/inet.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_ArrayList.h> + +#include "metis_CommandLineInterface.h" + +struct metis_command_line_interface { + MetisForwarder *metis; + PARCEventSocket *listener; + PARCArrayList *openSessions; + + uint16_t port; +}; + +typedef struct metis_cli_session { + MetisCommandLineInterface *parentCli; + MetisSocketType clientSocket; + struct sockaddr *clientAddress; + int clientAddressLength; + PARCEventQueue *streamBuffer; + bool doingTheRightThing; +} _MetisCommandLineInterface_Session; + +struct metis_cli_command; +typedef struct metis_cli_command _MetisCommandLineInterface_Command; + +struct metis_cli_command { + char *text; + char *helpDescription; + void (*func)(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params); +}; + +static void _metisCommandLineInterface_ListenerCallback(MetisSocketType client_socket, + struct sockaddr *client_addr, int socklen, void *user_data); + +static _MetisCommandLineInterface_Session *metisCliSession_Create(MetisCommandLineInterface *cli, MetisSocketType client_socket, struct sockaddr *client_addr, int socklen); +static void _metisCliSession_Destory(_MetisCommandLineInterface_Session **cliSessionPtr); +static void _metisCliSession_ReadCallback(PARCEventQueue *event, PARCEventType type, void *cliSessionVoid); +static void _metisCliSession_EventCallback(PARCEventQueue *event, PARCEventQueueEventType what, void *cliSessionVoid); +static bool _metisCliSession_ProcessCommand(_MetisCommandLineInterface_Session *session, char *cmdline); +static void _metisCliSession_DisplayMotd(_MetisCommandLineInterface_Session *session); +static void _metisCliSession_DisplayPrompt(_MetisCommandLineInterface_Session *session); + +// used by PARCArrayList +static void +_session_VoidDestroyer(void **cliSessionVoidPtr) +{ + _MetisCommandLineInterface_Session **cliSessionPtr = (_MetisCommandLineInterface_Session **) cliSessionVoidPtr; + (*cliSessionPtr)->doingTheRightThing = true; + _metisCliSession_Destory(cliSessionPtr); +} + +// ==================================================================================== + +static void _cmd_Help(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params); +static void _cmd_Show(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params); +static void _cmd_Exit(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params); +static void _cmd_Tunnel(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params); +static void _cmd_Version(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params); + +/** + * @typedef _cliCommands + * The commands, their short help, and their function pointer + * @constant <#name#> <#description#> + * List must be terminated with a NULL entry + * + * Example: + * @code + * <#example#> + * @endcode + */ +static _MetisCommandLineInterface_Command _cliCommands[] = { + { "exit", "Ends the session", _cmd_Exit }, + { "help", "Displays the help menu", _cmd_Help }, + { "show", "Displays state", _cmd_Show }, + { "tunnel", "manage tunnels", _cmd_Tunnel }, + { "ver", "Forwarder version", _cmd_Version }, + { NULL, NULL, NULL } +}; + +// ==================================================================================== + +MetisCommandLineInterface * +metisCommandLineInterface_Create(MetisForwarder *metis, uint16_t port) +{ + MetisCommandLineInterface *cli = parcMemory_AllocateAndClear(sizeof(MetisCommandLineInterface)); + assertNotNull(cli, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisCommandLineInterface)); + cli->port = port; + cli->listener = NULL; + cli->metis = metis; + cli->openSessions = parcArrayList_Create(_session_VoidDestroyer); + + return cli; +} + +void +metisCommandLineInterface_Start(MetisCommandLineInterface *cli) +{ + // listen address + struct sockaddr_in6 addr6; + memset(&addr6, 0, sizeof(addr6)); + addr6.sin6_family = PF_INET6; + addr6.sin6_port = htons(cli->port); + + MetisDispatcher *dispatcher = metisForwarder_GetDispatcher(cli->metis); + PARCEventSocket *listener = metisDispatcher_CreateListener(dispatcher, _metisCommandLineInterface_ListenerCallback, cli, -1, (struct sockaddr *) &addr6, sizeof(addr6)); + assertNotNull(listener, "Got null listener"); + + cli->listener = listener; +} + +void +metisCommandLineInterface_Destroy(MetisCommandLineInterface **cliPtr) +{ + assertNotNull(cliPtr, "Parameter must be non-null double pointer"); + assertNotNull(*cliPtr, "Parameter must dereference to non-null pointer"); + + MetisCommandLineInterface *cli = *cliPtr; + + parcArrayList_Destroy(&cli->openSessions); + + if (cli->listener) { + MetisDispatcher *dispatcher = metisForwarder_GetDispatcher(cli->metis); + metisDispatcher_DestroyListener(dispatcher, &(cli->listener)); + } + + parcMemory_Deallocate((void **) &cli); + *cliPtr = NULL; +} + +/** + * Creates a client-specific session + * + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +static _MetisCommandLineInterface_Session * +metisCliSession_Create(MetisCommandLineInterface *cli, MetisSocketType clientSocket, struct sockaddr *clientAddress, int clientAddressLength) +{ + _MetisCommandLineInterface_Session *session = parcMemory_AllocateAndClear(sizeof(_MetisCommandLineInterface_Session)); + assertNotNull(session, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisCommandLineInterface_Session)); + session->parentCli = cli; + session->clientAddress = parcMemory_Allocate(clientAddressLength); + assertNotNull(session->clientAddress, "parcMemory_Allocate(%d) returned NULL", clientAddressLength); + session->clientAddressLength = clientAddressLength; + session->clientSocket = clientSocket; + + memcpy(session->clientAddress, clientAddress, clientAddressLength); + + MetisDispatcher *dispatcher = metisForwarder_GetDispatcher(cli->metis); + PARCEventScheduler *eventBase = metisDispatcher_GetEventScheduler(dispatcher); + session->streamBuffer = parcEventQueue_Create(eventBase, clientSocket, PARCEventQueueOption_CloseOnFree | PARCEventQueueOption_DeferCallbacks); + + parcEventQueue_SetCallbacks(session->streamBuffer, _metisCliSession_ReadCallback, NULL, _metisCliSession_EventCallback, session); + parcEventQueue_Enable(session->streamBuffer, PARCEventType_Read); + + return session; +} + +/** + * SHOULD ONLY BE CALLED FROM ARRAYLIST + * + * Do not call this on your own!! It should only be called when an + * item is removed from the cli->openSessions array list. + * + * Will close the tcp session and free memory. + * + * @param <#param1#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +static void +_metisCliSession_Destory(_MetisCommandLineInterface_Session **cliSessionPtr) +{ + assertNotNull(cliSessionPtr, "Parameter must be non-null double pointer"); + assertNotNull(*cliSessionPtr, "Parameter must dereference to non-null pointer"); + _MetisCommandLineInterface_Session *session = *cliSessionPtr; + + assertTrue(session->doingTheRightThing, "Ha! caught you! You called Destroy outside the PARCArrayList"); + + parcEventQueue_Destroy(&(session->streamBuffer)); + parcMemory_Deallocate((void **) &(session->clientAddress)); + parcMemory_Deallocate((void **) &session); + *cliSessionPtr = NULL; +} + +/** + * Called on a new connection to the server socket + * + * Will allocate a new _MetisCommandLineInterface_Session and put it in the + * server's PARCArrayList + * + * @param <#param1#> + * @return <#return#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +static void +_metisCommandLineInterface_ListenerCallback(MetisSocketType client_socket, + struct sockaddr *client_addr, int socklen, void *user_data) +{ + MetisCommandLineInterface *cli = (MetisCommandLineInterface *) user_data; + _MetisCommandLineInterface_Session *session = metisCliSession_Create(cli, client_socket, client_addr, socklen); + parcArrayList_Add(cli->openSessions, session); + + _metisCliSession_DisplayMotd(session); + _metisCliSession_DisplayPrompt(session); +} + +static void +_metisCliSession_ReadCallback(PARCEventQueue *event, PARCEventType type, void *cliSessionVoid) +{ + assertTrue(type == PARCEventType_Read, "Illegal type: expected read event, got %d\n", type); + _MetisCommandLineInterface_Session *session = (_MetisCommandLineInterface_Session *) cliSessionVoid; + PARCEventBuffer *input = parcEventBuffer_GetQueueBufferInput(event); + + while (parcEventBuffer_GetLength(input) > 0) { + size_t readLength = 0; + char *cmdline = parcEventBuffer_ReadLine(input, &readLength); + if (cmdline == NULL) { + // we have run out of input, we're done + parcEventBuffer_Destroy(&input); + return; + } + + // we have a whole command line + bool success = _metisCliSession_ProcessCommand(session, cmdline); + parcEventBuffer_FreeLine(input, &cmdline); + + if (!success) { + // the session is dead + parcEventBuffer_Destroy(&input); + return; + } + + _metisCliSession_DisplayPrompt(session); + } + parcEventBuffer_Destroy(&input); +} + +static void +_metisCommandLineInterface_RemoveSession(MetisCommandLineInterface *cli, _MetisCommandLineInterface_Session *session) +{ + size_t length = parcArrayList_Size(cli->openSessions); + for (size_t i = 0; i < length; i++) { + _MetisCommandLineInterface_Session *x = parcArrayList_Get(cli->openSessions, i); + if (x == session) { + // removing from list will call the session destroyer + parcArrayList_RemoveAndDestroyAtIndex(cli->openSessions, i); + return; + } + } + assertTrue(0, "Illegal state: did not find a session in openSessions %p", (void *) session); +} + +static void +_metisCliSession_EventCallback(PARCEventQueue *event, PARCEventQueueEventType what, void *cliSessionVoid) +{ + _MetisCommandLineInterface_Session *session = (_MetisCommandLineInterface_Session *) cliSessionVoid; + if (what & PARCEventQueueEventType_Error) { + MetisCommandLineInterface *cli = session->parentCli; + _metisCommandLineInterface_RemoveSession(cli, session); + } +} + +static void +_metisCliSession_DisplayMotd(_MetisCommandLineInterface_Session *session) +{ + parcEventQueue_Printf(session->streamBuffer, "Metis Forwarder CLI\n"); + parcEventQueue_Printf(session->streamBuffer, "Copyright (c) 2017 Cisco and/or its affiliates.\n\n"); + + parcEventQueue_Flush(session->streamBuffer, PARCEventType_Write); +} + +static void +_metisCliSession_DisplayPrompt(_MetisCommandLineInterface_Session *session) +{ + parcEventQueue_Printf(session->streamBuffer, "metis> "); + parcEventQueue_Flush(session->streamBuffer, PARCEventType_Write); +} + +/** + * Process commands until there's not a whole line (upto CRLF) + * + * <#Discussion#> + * + * @param <#param1#> + * @return false if the session died, true if its still going + * + * Example: + * @code + * <#example#> + * @endcode + */ +static bool +_metisCliSession_ProcessCommand(_MetisCommandLineInterface_Session *session, char *cmdline) +{ + // parse out the first word. The NULL goes in after a space or tab. + // "cmd" will be the string prior to the NULL, and "cmdline" will be what's after the NULL. + char *cmd = strsep(&cmdline, " \t"); + + // there's a secret command for unit testing + if (strcasecmp(cmd, "~~") == 0) { + parcEventQueue_Printf(session->streamBuffer, "success: %s\n", cmdline); + return true; + } + + for (int i = 0; _cliCommands[i].text != NULL; i++) { + if (strcasecmp(_cliCommands[i].text, cmd) == 0) { + _cliCommands[i].func(session, &_cliCommands[i], cmdline); + if (_cliCommands[i].func == _cmd_Exit) { + return false; + } + return true; + } + } + + // could not find command, print the help menu + parcEventQueue_Printf(session->streamBuffer, "Unrecognized command: %s, displaying help menu\n", cmdline); + _cmd_Help(session, NULL, NULL); + return true; +} + +static void +_cmd_Help(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params) +{ + for (int i = 0; _cliCommands[i].text != NULL; i++) { + parcEventQueue_Printf(session->streamBuffer, "%-8s %s\n", _cliCommands[i].text, _cliCommands[i].helpDescription); + } +} + +static void +_cmd_Show(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params) +{ + parcEventQueue_Printf(session->streamBuffer, "not implemented\n"); +} + +static void +_cmd_Tunnel(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params) +{ + parcEventQueue_Printf(session->streamBuffer, "not implemented\n"); +} + +static void +_cmd_Exit(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params) +{ + parcEventQueue_Printf(session->streamBuffer, "Exiting session, goodby\n\n"); + parcEventQueue_Flush(session->streamBuffer, PARCEventType_Write); + _metisCommandLineInterface_RemoveSession(session->parentCli, session); +} + +static void +_cmd_Version(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params) +{ + PARCJSON *versionJson = metisConfiguration_GetVersion(metisForwarder_GetConfiguration(session->parentCli->metis)); + char *str = parcJSON_ToString(versionJson); + parcEventQueue_Printf(session->streamBuffer, "%s", str); + parcMemory_Deallocate((void **) &str); + parcJSON_Release(&versionJson); +} diff --git a/metis/ccnx/forwarder/metis/config/metis_CommandLineInterface.h b/metis/ccnx/forwarder/metis/config/metis_CommandLineInterface.h new file mode 100644 index 00000000..f4ab85dc --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_CommandLineInterface.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metis_CommandLineInterface.h + * @brief A telnet-like server for management interface + * + * + * We do not start the CLI until metisCommandLineInterface_Start() is called. This allows us to always create it in + * metisForwarder_Create(), but not bind the port until needed. Binding the port in metisForwarder_Create() + * causes severe issues in rapid execution of unit tests. + * + * + */ + +#ifndef Metis_metis_CommandLineInterface_h +#define Metis_metis_CommandLineInterface_h + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +struct metis_command_line_interface; +typedef struct metis_command_line_interface MetisCommandLineInterface; + +/** + * Creates a CLI on the given port. + * + * A telnet-style interface. Creating it will not bind the port or start + * the service. You need to call <code>metisCli_Start()</code> + * + * @param port the command port, in host byte order + * + * @return NULL if cannot be created on the port + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisCommandLineInterface *metisCommandLineInterface_Create(MetisForwarder *metis, uint16_t port); + +/** + * Stops and destroys the CLI. Existing sessions are destroyed. + * + * <#Discussion#> + * + * @param cliPtr + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisCommandLineInterface_Destroy(MetisCommandLineInterface **cliPtr); + +/* + * Binds the port and starts the CLI service + * + * <#Discussion#> + * + * @param cli + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisCommandLineInterface_Start(MetisCommandLineInterface *cli); +#endif // Metis_metis_CommandLineInterface_h diff --git a/metis/ccnx/forwarder/metis/config/metis_CommandOps.c b/metis/ccnx/forwarder/metis/config/metis_CommandOps.c new file mode 100644 index 00000000..3b2f49ca --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_CommandOps.c @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> +#include <string.h> + +#ifndef _ANDROID_ +# ifdef HAVE_ERRNO_H +# include <errno.h> +# else +extern int errno; +# endif +#endif + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/config/metis_CommandOps.h> +#include <ccnx/forwarder/metis/config/metis_CommandParser.h> + +MetisCommandOps * +metisCommandOps_Create(void *closure, const char *command, void (*init)(MetisCommandParser *parser, MetisCommandOps *ops), + MetisCommandReturn (*execute)(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args), + void (*destroyer)(MetisCommandOps **opsPtr)) +{ + assertNotNull(command, "Parameter command must be non-null"); + assertNotNull(execute, "Parameter execute must be non-null"); + MetisCommandOps *ops = parcMemory_AllocateAndClear(sizeof(MetisCommandOps)); + assertNotNull(ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisCommandOps)); + ops->closure = closure; + ops->command = parcMemory_StringDuplicate(command, strlen(command) + 1); + ops->init = init; + ops->execute = execute; + ops->destroyer = destroyer; + return ops; +} + +void +metisCommandOps_Destroy(MetisCommandOps **opsPtr) +{ + assertNotNull(opsPtr, "Parameter opsPtr must be non-null"); + assertNotNull(*opsPtr, "Parameter opsPtr must dereference to non-null pointer"); + + MetisCommandOps *ops = *opsPtr; + parcMemory_Deallocate((void **) &(ops->command)); + // DO NOT call ops->destroyer, we are one! + parcMemory_Deallocate((void **) &ops); + + *opsPtr = NULL; +} diff --git a/metis/ccnx/forwarder/metis/config/metis_CommandOps.h b/metis/ccnx/forwarder/metis/config/metis_CommandOps.h new file mode 100644 index 00000000..e37024aa --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_CommandOps.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metis_CommandOps.h + * @brief The function structure defining a CLI command + * + * The function structure that defines a CLI command. Each command will return one + * of these which defines how to run the command. + * + */ + +#ifndef Metis_metis_CommandOps_h +#define Metis_metis_CommandOps_h + +#include <parc/algol/parc_List.h> + +#include <ccnx/forwarder/metis/config/metis_CommandReturn.h> + +// forward reference +struct metis_command_parser; + +struct metis_command_ops; +typedef struct metis_command_ops MetisCommandOps; + +/** + * @typedef MetisCommandOps + * @abstract Each command implements a MetisCommandOps + * @constant closure is a user-specified pointer for any state the user needs + * @constant command The text string of the command, must be the spelled out string, e.g. "help list routes" + * @constant init A function to call to initialize the command at program startup + * @constant execute A function to call to execute the command + * @constant destroyer A function to call to release the command + * @discussion + * Typically, the root of the thee has an Init function that then initilizes the + * rest of the tree. For example: + * + * @code + * const MetisCommandOps metisControl_Root = { + * .closure = NULL, + * .command = "", // empty string for root + * .init = metisControl_Root_Init, + * .execute = metisControl_Root_Execute + * .destroyer = NULL + * }; + * @endcode + * + * The metisControl_Root_Init function will then begin adding the subtree under root. For example: + * + * @code + * const MetisCommandOps metisControl_Add = { + * .closure = NULL, + * .command = "add", + * .init = metisControl_Add_Init, + * .execute = metisControl_Add_Execute, + * .destroyer = NULL + * }; + * + * static void + * metisControl_Root_Init(MetisControlState *state, MetisCommandOps *ops) + * { + * metisControlState_RegisterCommand(state, &metisControl_Add); + * } + * @endcode + */ +struct metis_command_ops { + void *closure; + char *command; + void (*init)(struct metis_command_parser *parser, MetisCommandOps *ops); + MetisCommandReturn (*execute)(struct metis_command_parser *parser, MetisCommandOps *ops, PARCList *args); + void (*destroyer)(MetisCommandOps **opsPtr); +}; + +/** + * A helper function to create the pubically defined MetisCommandOps. + * + * Retruns allocated memory of the command + * + * @param [in] command The string is copied + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisCommandOps *metisCommandOps_Create(void *closure, + const char *command, + void (*init)(struct metis_command_parser *parser, MetisCommandOps *ops), + MetisCommandReturn (*execute)(struct metis_command_parser *parser, MetisCommandOps *ops, PARCList *args), + void (*destroyer)(MetisCommandOps **opsPtr)); + +/** + * De-allocates the memory of the MetisCommandOps and the copied command string + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisCommandOps_Destroy(MetisCommandOps **opsPtr); +#endif // Metis_metis_CommandOps_h diff --git a/metis/ccnx/forwarder/metis/config/metis_CommandParser.c b/metis/ccnx/forwarder/metis/config/metis_CommandParser.c new file mode 100644 index 00000000..6cc19311 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_CommandParser.c @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> +#include <string.h> + +#include <parc/security/parc_Security.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_List.h> +#include <parc/algol/parc_TreeRedBlack.h> +#include <parc/algol/parc_Time.h> + +#include <ccnx/common/ccnx_KeystoreUtilities.h> + +#include <ccnx/forwarder/metis/config/metis_CommandParser.h> + +#ifndef _ANDROID_ +# ifdef HAVE_ERRNO_H +# include <errno.h> +# else +extern int errno; +# endif +#endif + +struct metis_command_parser { + // key = command, value = MetisCommandOps + PARCTreeRedBlack *commandTree; + bool debugFlag; +}; + +static int +_stringCompare(const void *key1, const void *key2) +{ + return strcasecmp((const char *) key1, (const char *) key2); +} + +MetisCommandParser * +metisCommandParser_Create(void) +{ + MetisCommandParser *state = parcMemory_AllocateAndClear(sizeof(MetisCommandParser)); + assertNotNull(state, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisCommandParser)); + state->commandTree = parcTreeRedBlack_Create( + _stringCompare, // key compare + NULL, // key free + NULL, // key copy + NULL, // value equals + NULL, // value free + NULL // value copy + ); + state->debugFlag = false; + return state; +} + +void +metisCommandParser_Destroy(MetisCommandParser **parserPtr) +{ + MetisCommandParser *parser = *parserPtr; + + // destroy every element if it has a destroyer + PARCArrayList *values = parcTreeRedBlack_Values(parser->commandTree); + if (values) { + for (int i = 0; i < parcArrayList_Size(values); i++) { + MetisCommandOps *ops = parcArrayList_Get(values, i); + parcTreeRedBlack_Remove(parser->commandTree, ops->command); + if (ops->destroyer) { + ops->destroyer(&ops); + } + } + parcArrayList_Destroy(&values); + } + + parcTreeRedBlack_Destroy(&parser->commandTree); + + parcMemory_Deallocate((void **) &parser); + *parserPtr = NULL; +} + +void +metisCommandParser_SetDebug(MetisCommandParser *state, bool debugFlag) +{ + state->debugFlag = debugFlag; +} + +bool +metisCommandParser_GetDebug(MetisCommandParser *state) +{ + return state->debugFlag; +} + +void +metisCommandParser_RegisterCommand(MetisCommandParser *state, MetisCommandOps *ops) +{ + assertNotNull(state, "Parameter state must be non-null"); + assertNotNull(ops, "Parameter ops must be non-null"); + assertNotNull(ops->command, "Operation command string must be non-null"); + + void *exists = parcTreeRedBlack_Get(state->commandTree, ops->command); + assertNull(exists, "Command '%s' already exists in the tree %p\n", ops->command, (void *) exists); + + parcTreeRedBlack_Insert(state->commandTree, (void *) ops->command, (void *) ops); + + // if the command being registered asked for an init function to be called, call it + if (ops->init != NULL) { + ops->init(state, ops); + } +} + +static PARCList * +parseStringIntoTokens(const char *originalString) +{ + PARCList *list = parcList(parcArrayList_Create(parcArrayList_StdlibFreeFunction), PARCArrayListAsPARCList); + + char *token; + + char *tofree = parcMemory_StringDuplicate(originalString, strlen(originalString) + 1); + char *string = tofree; + + while ((token = strsep(&string, " \t\n")) != NULL) { + if (strlen(token) > 0) { + parcList_Add(list, strdup(token)); + } + } + + parcMemory_Deallocate((void **) &tofree); + + return list; +} + +/** + * Matches the user arguments to available commands, returning the command or NULL if not found + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +static MetisCommandOps * +metisCommandParser_MatchCommand(MetisCommandParser *state, PARCList *args) +{ + // Find the longest matching prefix command. + // Pretty wildly inefficient + + size_t longest_token_count = 0; + char *longest_command = NULL; + + PARCArrayList *commands = parcTreeRedBlack_Keys(state->commandTree); + for (int i = 0; i < parcArrayList_Size(commands); i++) { + char *command = parcArrayList_Get(commands, i); + PARCList *command_tokens = parseStringIntoTokens(command); + + // is it a prefix match? + if (parcList_Size(args) >= parcList_Size(command_tokens)) { + bool possible_match = true; + for (int i = 0; i < parcList_Size(command_tokens) && possible_match; i++) { + const char *a = parcList_GetAtIndex(command_tokens, i); + const char *b = parcList_GetAtIndex(args, i); + if (strncasecmp(a, b, strlen(a) + 1) != 0) { + possible_match = false; + } + } + + if (possible_match && parcList_Size(command_tokens) > longest_token_count) { + longest_token_count = parcList_Size(command_tokens); + longest_command = command; + } + } + + parcList_Release(&command_tokens); + } + + parcArrayList_Destroy(&commands); + + if (longest_token_count == 0) { + return NULL; + } else { + MetisCommandOps *ops = parcTreeRedBlack_Get(state->commandTree, longest_command); + assertNotNull(ops, "Got null operations for command '%s'\n", longest_command); + return ops; + } +} + +MetisCommandReturn +metisCommandParser_DispatchCommand(MetisCommandParser *state, PARCList *args) +{ + MetisCommandOps *ops = metisCommandParser_MatchCommand(state, args); + + if (ops == NULL) { + printf("Command not found.\n"); + return MetisCommandReturn_Failure; + } else { + return ops->execute(state, ops, args); + } +} + +bool +metisCommandParser_ContainsCommand(MetisCommandParser *parser, const char *command) +{ + MetisCommandOps *ops = parcTreeRedBlack_Get(parser->commandTree, command); + return (ops != NULL); +} diff --git a/metis/ccnx/forwarder/metis/config/metis_CommandParser.h b/metis/ccnx/forwarder/metis/config/metis_CommandParser.h new file mode 100644 index 00000000..dc274291 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_CommandParser.h @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metis_CommandParser.h + * @brief Creates a dictionary of commands and parses a command-line to match against them + * + * A user creates individual CommandParserEntry that map a command-line to a function + * to execute. The CommandParser then does a longest-matching prefix match of a command-line + * to the dictionary of commands and executes the appropriate command. + * + */ + +#ifndef Metis_metis_command_parser_h +#define Metis_metis_command_parser_h + +#include <ccnx/transport/common/transport_MetaMessage.h> + +#include <ccnx/forwarder/metis/config/metis_CommandReturn.h> +#include <ccnx/forwarder/metis/config/metis_CommandOps.h> + +struct metis_command_parser; +typedef struct metis_command_parser MetisCommandParser; + +/** + * metisControlState_Create + * + * Creates the global state for the MetisControl program + * + * @return non-null A command parser + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisCommandParser *metisCommandParser_Create(void); + +/** + * Destroys the control state, closing all network connections + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisCommandParser_Destroy(MetisCommandParser **statePtr); + +/** + * Registers a MetisCommandOps with the system. + * + * Each command has its complete command prefix in the "command" field. RegisterCommand + * will put these command prefixes in to a tree and then match what a user types against + * the longest-matching prefix in the tree. If there's a match, it will call the "execute" + * function. + * + * When the parser is destroyed, each command's destroyer function will be called. + * + * @param [in] state An allocated MetisControlState + * @param [in] command The command to register with the system + * + * Example: + * @code + * static MetisControlReturn + * metisControl_Root_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) + * { + * printf("Root Command\n"); + * return MetisCommandReturn_Success; + * } + * + * static MetisControlReturn + * metisControl_FooBar_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) + * { + * printf("Foo Bar Command\n"); + * return MetisCommandReturn_Success; + * } + * + * const MetisCommandOps metisControl_Root = { + * .closure = NULL, + * .command = "", // empty string for root + * .init = NULL, + * .execute = metisControl_Root_Execute + * }; + * + * const MetisCommandOps metisControl_FooBar = { + * .closure = NULL, + * .command = "foo bar", // empty string for root + * .init = NULL, + * .execute = metisControl_FooBar_Execute + * }; + * + * void startup(void) + * { + * MetisControlState *state = metisControlState_Create("happy", "day"); + * metisControlState_RegisterCommand(state, metisControl_FooBar); + * metisControlState_RegisterCommand(state, metisControl_Root); + * + * // this executes "root" + * metisControlState_DispatchCommand(state, "foo"); + * metisControlState_Destroy(&state); + * } + * @endcode + */ +void metisCommandParser_RegisterCommand(MetisCommandParser *state, MetisCommandOps *command); + +/** + * Performs a longest-matching prefix of the args to the command tree + * + * The command tree is created with metisControlState_RegisterCommand. + * + * @param [in] state The allocated MetisControlState + * @param [in] args Each command-line word parsed to the ordered list + * + * @return MetisCommandReturn_Success the command was successful + * @return MetisCommandReturn_Failure the command failed or was not found + * @return MetisCommandReturn_Exit the command indicates that the interactive mode should exit + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisCommandReturn metisCommandParser_DispatchCommand(MetisCommandParser *state, PARCList *args); + +/** + * Sets the Debug mode, which will print out much more information. + * + * Prints out much more diagnostic information about what metis-control is doing. + * yes, you would make a MetisCommandOps to set and unset this :) + * + * @param [in] debugFlag true means to print debug info, false means to turn it off + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisCommandParser_SetDebug(MetisCommandParser *state, bool debugFlag); + +/** + * Returns the debug state of MetisControlState + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisCommandParser_GetDebug(MetisCommandParser *state); + +/** + * Checks if the command is registered + * + * Checks if the exact command given is registered. This is not a prefix match. + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return true The command is registered + * @return false The command is not registered + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisCommandParser_ContainsCommand(MetisCommandParser *parser, const char *command); +#endif // Metis_metis_command_parser_h diff --git a/metis/ccnx/forwarder/metis/config/metis_CommandReturn.h b/metis/ccnx/forwarder/metis/config/metis_CommandReturn.h new file mode 100644 index 00000000..f12215d3 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_CommandReturn.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metis_CommandReturn.h + * @brief The return code used by CLI commands + * + * This return code is used throughout the command parser and command implementations + * to indicate success, failure, or if the program should exit. + * + */ + +#ifndef Metis_metis_command_return_h +#define Metis_metis_command_return_h + +/** + * @typedef MetisControlReturn + * @abstract A command returns one of (SUCCESS, FAILURE, EXIT) + * @constant SUCCESS means the command succeeded + * @constant FAILURE indicates failure + * @constant EXIT means the command indicated that metis-control should exit. + * @discussion <#Discussion#> + */ +typedef enum metis_command_return { + MetisCommandReturn_Success, // command returned success + MetisCommandReturn_Failure, // command failure + MetisCommandReturn_Exit // command indicates program should exit +} MetisCommandReturn; + +#endif // Metis_metis_command_return_h diff --git a/metis/ccnx/forwarder/metis/config/metis_Configuration.c b/metis/ccnx/forwarder/metis/config/metis_Configuration.c new file mode 100644 index 00000000..3703c6ef --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_Configuration.c @@ -0,0 +1,936 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * + * Example: + * @code + * <#example#> + * @endcode + */ +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <arpa/inet.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_HashMap.h> +#include <parc/algol/parc_String.h> + +#include <ccnx/api/control/cpi_InterfaceSet.h> +#include <ccnx/api/control/cpi_ConnectionEthernet.h> +#include <ccnx/api/control/cpi_Listener.h> +#include <ccnx/api/control/controlPlaneInterface.h> +#include <ccnx/api/control/cpi_InterfaceIPTunnel.h> +#include <ccnx/api/control/cpi_InterfaceIPTunnelList.h> +#include <ccnx/api/control/cpi_ForwardingStrategy.h> + +#include <ccnx/forwarder/metis/config/metis_CommandLineInterface.h> +#include <ccnx/forwarder/metis/config/metis_SymbolicNameTable.h> +#include <ccnx/forwarder/metis/config/metis_ConfigurationListeners.h> + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_System.h> +#include <ccnx/forwarder/metis/core/metis_ConnectionTable.h> +#include <ccnx/forwarder/metis/core/metis_Connection.h> + +#include <ccnx/forwarder/metis/io/metis_TcpTunnel.h> +#include <ccnx/forwarder/metis/io/metis_UdpTunnel.h> +#include <ccnx/forwarder/metis/io/metis_UdpConnection.h> +#include <ccnx/forwarder/metis/io/metis_EtherListener.h> +#include <ccnx/forwarder/metis/io/metis_EtherConnection.h> + +#include <ccnx/forwarder/metis/metis_About.h> + +#define ETHERTYPE 0x0801 + +struct metis_configuration { + MetisForwarder *metis; + MetisLogger *logger; + MetisCommandLineInterface *cli; + + size_t maximumContentObjectStoreSize; + + // map from prefix to strategy + PARCHashMap *strategy_map; + + // translates between a symblic name and a connection id + MetisSymbolicNameTable *symbolicNameTable; +}; + + +// ======================================================================================== + +MetisConfiguration * +metisConfiguration_Create(MetisForwarder *metis) +{ + assertNotNull(metis, "Parameter metis must be non-null"); + MetisConfiguration *config = parcMemory_AllocateAndClear(sizeof(MetisConfiguration)); + assertNotNull(config, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisConfiguration)); + config->metis = metis; + config->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + config->cli = NULL; + config->maximumContentObjectStoreSize = 100000; + config->strategy_map = parcHashMap_Create(); + config->symbolicNameTable = metisSymbolicNameTable_Create(); + + return config; +} + +void +metisConfiguration_Destroy(MetisConfiguration **configPtr) +{ + assertNotNull(configPtr, "Parameter must be non-null double poitner"); + assertNotNull(*configPtr, "Parameter must dereference to non-null pointer"); + + MetisConfiguration *config = *configPtr; + metisLogger_Release(&config->logger); + if (config->cli != NULL) { + metisCommandLineInterface_Destroy(&config->cli); + } + parcHashMap_Release(&(config->strategy_map)); + metisSymbolicNameTable_Destroy(&config->symbolicNameTable); + parcMemory_Deallocate((void **) &config); + *configPtr = NULL; +} + +void +metisConfiguration_StartCLI(MetisConfiguration *config, uint16_t port) +{ + assertNull(config->cli, "You cannot start more than one CLI"); + config->cli = metisCommandLineInterface_Create(config->metis, port); + metisCommandLineInterface_Start(config->cli); +} + +PARCJSON * +metisConfiguration_GetVersion(MetisConfiguration *config) +{ + PARCJSON *fwd = parcJSON_Create(); + parcJSON_AddString(fwd, "NAME", metisAbout_Name()); + parcJSON_AddString(fwd, "COPYRIGHT", metisAbout_MiniNotice()); + parcJSON_AddString(fwd, "VERSION", metisAbout_Version()); + return fwd; +} + +static void +metisConfiguration_SendResponse(MetisConfiguration *config, CCNxControl *response, unsigned egressId) +{ + PARCBuffer *responseBuffer = metisTlv_EncodeControlPlaneInformation(response); + MetisMessage *tlvEncodedResponse = metisMessage_CreateFromParcBuffer(responseBuffer, 0, metisForwarder_GetTicks(config->metis), metisForwarder_GetLogger(config->metis)); + + parcBuffer_Release(&responseBuffer); + + MetisConnectionTable *connectionTable = metisForwarder_GetConnectionTable(config->metis); + const MetisConnection *conn = metisConnectionTable_FindById(connectionTable, egressId); + assertNotNull(conn, "Got null connection for control message we just received"); + + metisConnection_Send(conn, tlvEncodedResponse); + metisMessage_Release(&tlvEncodedResponse); +} + +static CCNxControl * +_createNack(MetisConfiguration *config, CCNxControl *control, unsigned ingressId) +{ + PARCJSON *json = ccnxControl_GetJson(control); + + PARCJSON *jsonNack = cpiAcks_CreateNack(json); + + CCNxControl *response = ccnxControl_CreateCPIRequest(jsonNack); + parcJSON_Release(&jsonNack); + return response; +} + +static CCNxControl * +_createAck(MetisConfiguration *config, CCNxControl *control, unsigned ingressId) +{ + PARCJSON *json = ccnxControl_GetJson(control); + PARCJSON *jsonAck = cpiAcks_CreateAck(json); + + CCNxControl *response = ccnxControl_CreateCPIRequest(jsonAck); + parcJSON_Release(&jsonAck); + return response; +} + + +static CCNxControl * +metisConfiguration_ProcessForwarderVersion(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + PARCJSON *fwd = metisConfiguration_GetVersion(config); + + // this process of getting to a MetisMesage needs streamlining + + CCNxControl *response = cpi_CreateResponse(request, fwd); + return response; +} + +static CCNxControl * +metisConfiguration_ProcessInterfaceList(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + CPIInterfaceSet *set = metisSystem_Interfaces(config->metis); + PARCJSON *setJson = cpiInterfaceSet_ToJson(set); + + CCNxControl *response = cpi_CreateResponse(request, setJson); + parcJSON_Release(&setJson); + cpiInterfaceSet_Destroy(&set); + return response; +} + +static bool +_symbolicRegisterPrefix(MetisConfiguration *config, CPIRouteEntry *route) +{ + bool success = false; + + const char *symbolic = cpiRouteEntry_GetSymbolicName(route); + unsigned ifidx = metisSymbolicNameTable_Get(config->symbolicNameTable, symbolic); + if (ifidx != UINT32_MAX) { + cpiRouteEntry_SetInterfaceIndex(route, ifidx); + if (metisLogger_IsLoggable(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Debug)) { + metisLogger_Log(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Debug, __func__, + "Add route resolve name '%s' to connid %u", + symbolic, ifidx); + } + + success = metisForwarder_AddOrUpdateRoute(config->metis, route); + } else { + if (metisLogger_IsLoggable(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Warning)) { + metisLogger_Log(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Warning, __func__, + "Add route symbolic name '%s' could not be resolved", symbolic); + } + // this is a failure + } + return success; +} + +static CCNxControl * +metisConfiguration_ProcessRegisterPrefix(MetisConfiguration *config, CCNxControl *control, unsigned ingressId) +{ + CPIRouteEntry *route = cpiForwarding_RouteFromControlMessage(control); + + bool success = false; + + // if it has a symbolic name set the interface index + if (cpiRouteEntry_GetSymbolicName(route) != NULL) { + success = _symbolicRegisterPrefix(config, route); + } else { + if (cpiRouteEntry_GetInterfaceIndex(route) == CPI_CURRENT_INTERFACE) { + // We want to use the ingress interface + cpiRouteEntry_SetInterfaceIndex(route, ingressId); + } + success = metisForwarder_AddOrUpdateRoute(config->metis, route); + } + + CCNxControl *response; + if (success) { + response = _createAck(config, control, ingressId); + } else { + response = _createNack(config, control, ingressId); + } + + cpiRouteEntry_Destroy(&route); + return response; +} + +static CCNxControl * +metisConfiguration_ProcessUnregisterPrefix(MetisConfiguration *config, CCNxControl *control, unsigned ingressId) +{ + CPIRouteEntry *route = cpiForwarding_RouteFromControlMessage(control); + + bool success = false; + + if (cpiRouteEntry_GetSymbolicName(route) != NULL) { + const char *symbolic = cpiRouteEntry_GetSymbolicName(route); + unsigned ifidx = metisSymbolicNameTable_Get(config->symbolicNameTable, symbolic); + if (ifidx != UINT32_MAX) { + cpiRouteEntry_SetInterfaceIndex(route, ifidx); + success = metisForwarder_RemoveRoute(config->metis, route); + } else { + // this is a failure + success = false; + } + } else { + if (cpiRouteEntry_GetInterfaceIndex(route) == CPI_CURRENT_INTERFACE) { + // We want to use the ingress interface + cpiRouteEntry_SetInterfaceIndex(route, ingressId); + } + success = metisForwarder_RemoveRoute(config->metis, route); + } + + CCNxControl *response; + if (success) { + response = _createAck(config, control, ingressId); + } else { + response = _createNack(config, control, ingressId); + } + + cpiRouteEntry_Destroy(&route); + return response; +} + +static CCNxControl * +metisConfiguration_ProcessRegistrationList(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + MetisFibEntryList *fibList = metisForwarder_GetFibEntries(config->metis); + + CPIRouteEntryList *routeEntryList = cpiRouteEntryList_Create(); + for (size_t i = 0; i < metisFibEntryList_Length(fibList); i++) { + const MetisFibEntry *fibEntry = metisFibEntryList_Get(fibList, i); + MetisTlvName *prefix = metisFibEntry_GetPrefix(fibEntry); + const MetisNumberSet *nexthops = metisFibEntry_GetNexthops(fibEntry); + + for (int j = 0; j < metisNumberSet_Length(nexthops); j++) { + CPIRouteEntry *route = cpiRouteEntry_Create(metisTlvName_ToCCNxName(prefix), + metisNumberSet_GetItem(nexthops, j), + NULL, + cpiNameRouteProtocolType_STATIC, + cpiNameRouteType_LONGEST_MATCH, + NULL, // lifetime + 1); // cost + cpiRouteEntryList_Append(routeEntryList, route); + } + + metisTlvName_Release(&prefix); + } + PARCJSON *entryListJson = cpiRouteEntryList_ToJson(routeEntryList); + CCNxControl *response = cpi_CreateResponse(request, entryListJson); + parcJSON_Release(&entryListJson); + cpiRouteEntryList_Destroy(&routeEntryList); + metisFibEntryList_Destroy(&fibList); + return response; +} + +static void +_logProcessCreateTunnelMessage(MetisConfiguration *config, CPIInterfaceIPTunnel *iptun, PARCLogLevel logLevel, const char *message) +{ + if (metisLogger_IsLoggable(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Info)) { + const CPIAddress *source = cpiInterfaceIPTunnel_GetSourceAddress(iptun); + const CPIAddress *destination = cpiInterfaceIPTunnel_GetDestinationAddress(iptun); + + const char *symbolicName = cpiInterfaceIPTunnel_GetSymbolicName(iptun); + + char *sourceString = cpiAddress_ToString(source); + char *remoteString = cpiAddress_ToString(destination); + + metisLogger_Log(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Info, __func__, + "Add connection %s on %s to %s: %s", + symbolicName, + sourceString, + remoteString, + message); + + parcMemory_Deallocate((void **) &sourceString); + parcMemory_Deallocate((void **) &remoteString); + } +} + +/** + * Add an IP-based tunnel. + * + * The call cal fail if the symbolic name is a duplicate. It could also fail if there's an problem creating + * the local side of the tunnel (i.e. the local socket address is not usable). + * + * @return true Tunnel added + * @return false Tunnel not added (an error) + */ +static CCNxControl * +metisConfiguration_ProcessCreateTunnel(MetisConfiguration *config, CCNxControl *control, unsigned ingressId) +{ + bool success = false; + + CPIInterfaceIPTunnel *iptun = cpiLinks_CreateIPTunnelFromControlMessage(control); + + const char *symbolicName = cpiInterfaceIPTunnel_GetSymbolicName(iptun); + + if (!metisSymbolicNameTable_Exists(config->symbolicNameTable, symbolicName)) { + const CPIAddress *source = cpiInterfaceIPTunnel_GetSourceAddress(iptun); + const CPIAddress *destination = cpiInterfaceIPTunnel_GetDestinationAddress(iptun); + + MetisIoOperations *ops = NULL; + switch (cpiInterfaceIPTunnel_GetTunnelType(iptun)) { + case IPTUN_TCP: + ops = metisTcpTunnel_Create(config->metis, source, destination); + break; + case IPTUN_UDP: + ops = metisUdpTunnel_Create(config->metis, source, destination); + break; + case IPTUN_GRE: + metisLogger_Log(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Error, __func__, + "Unsupported tunnel protocol: GRE"); + break; + default: + metisLogger_Log(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Error, __func__, + "Unsupported tunnel protocol: %d", + cpiInterfaceIPTunnel_GetTunnelType(iptun)); + break; + } + + + if (ops != NULL) { + MetisConnection *conn = metisConnection_Create(ops); + + const char *suffix = "wldr"; + size_t suffix_len = 4; + size_t name_len = strlen(symbolicName); + if (suffix_len < name_len && (strncmp(symbolicName + name_len - suffix_len, suffix, suffix_len) == 0)) { + printf("WARNING: WLDR initialized with legacy code. please use the command set wldr <on|off> <connId>\n"); + printf("see metis_control help for more information\n"); + metisConnection_EnableWldr(conn); + } + + metisConnectionTable_Add(metisForwarder_GetConnectionTable(config->metis), conn); + metisSymbolicNameTable_Add(config->symbolicNameTable, symbolicName, metisConnection_GetConnectionId(conn)); + + success = true; + + _logProcessCreateTunnelMessage(config, iptun, PARCLogLevel_Info, "success"); + } else { + _logProcessCreateTunnelMessage(config, iptun, PARCLogLevel_Warning, "failed, could not create IoOperations"); + } + } else { + _logProcessCreateTunnelMessage(config, iptun, PARCLogLevel_Warning, "failed, symbolic name exists"); + } + + // send the ACK or NACK + CCNxControl *response; + if (success) { + response = _createAck(config, control, ingressId); + } else { + response = _createNack(config, control, ingressId); + } + + cpiInterfaceIPTunnel_Release(&iptun); + + return response; +} + +static bool +_metisConfiguration_AddConnectionEthernet(MetisConfiguration *config, CPIConnectionEthernet *etherConn, CPIAddress *linkAddress, MetisListenerOps *listenerOps) +{ + bool success = false; + + const char *symbolic = cpiConnectionEthernet_GetSymbolicName(etherConn); + if (!metisSymbolicNameTable_Exists(config->symbolicNameTable, symbolic)) { + const CPIAddress *remote = cpiConnectionEthernet_GetPeerLinkAddress(etherConn); + MetisAddressPair *pair = metisAddressPair_Create(linkAddress, remote); + + MetisGenericEther *ether = metisEtherListener_GetGenericEtherFromListener(listenerOps); + + if (ether) { + MetisIoOperations *ops = metisEtherConnection_Create(config->metis, ether, pair); + + if (ops) { + MetisConnection *conn = metisConnection_Create(ops); + assertNotNull(conn, "Failed to create connection"); + + metisConnectionTable_Add(metisForwarder_GetConnectionTable(config->metis), conn); + metisSymbolicNameTable_Add(config->symbolicNameTable, symbolic, metisConnection_GetConnectionId(conn)); + + success = true; + + if (metisLogger_IsLoggable(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Debug)) { + char *peerAddressString = cpiAddress_ToString(remote); + metisLogger_Log(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Debug, __func__, + "Add connection %s on %s to %s, connid %u", + symbolic, + cpiConnectionEthernet_GetInterfaceName(etherConn), + peerAddressString, + metisConnection_GetConnectionId(conn)); + parcMemory_Deallocate((void **) &peerAddressString); + } + } + } else { + metisLogger_Log(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Error, __func__, + "Could not get MetisGenericEther for listener %p", + listenerOps); + } + + metisAddressPair_Release(&pair); + } else { + if (metisLogger_IsLoggable(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Warning)) { + const CPIAddress *remote = cpiConnectionEthernet_GetPeerLinkAddress(etherConn); + char *peerAddressString = cpiAddress_ToString(remote); + metisLogger_Log(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Warning, __func__, + "Add connection %s on %s to %s failed, symbolic name exists", + symbolic, + cpiConnectionEthernet_GetInterfaceName(etherConn), + peerAddressString); + parcMemory_Deallocate((void **) &peerAddressString); + } + } + + return success; +} + + +static CCNxControl * +metisConfiguration_ProcessAddConnectionEthernet(MetisConfiguration *config, CCNxControl *control, unsigned ingressId) +{ + bool success = false; + + CPIConnectionEthernet *etherConn = cpiConnectionEthernet_FromControl(control); + assertNotNull(etherConn, "Control message did not parse to CPIConnectionEthernet"); + + if (cpiConnectionEthernet_GetEthertype(etherConn) == ETHERTYPE) { + CPIAddress *linkAddress = metisSystem_GetMacAddressByName(config->metis, cpiConnectionEthernet_GetInterfaceName(etherConn)); + if (linkAddress != NULL) { + MetisListenerSet *listenerSet = metisForwarder_GetListenerSet(config->metis); + MetisListenerOps *listenerOps = metisListenerSet_Find(listenerSet, METIS_ENCAP_ETHER, linkAddress); + + if (listenerOps) { + // Now add the connection + success = _metisConfiguration_AddConnectionEthernet(config, etherConn, linkAddress, listenerOps); + } else { + char *str = cpiAddress_ToString(linkAddress); + metisLogger_Log(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Error, __func__, + "Could not find listener for interface '%s' addr %s ethertype 0x%04x", + cpiConnectionEthernet_GetInterfaceName(etherConn), + str, + cpiConnectionEthernet_GetEthertype(etherConn)); + parcMemory_Deallocate((void **) &str); + } + + + cpiAddress_Destroy(&linkAddress); + } else { + metisLogger_Log(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Error, __func__, + "Could not resolve interface '%s' to a MAC address", + cpiConnectionEthernet_GetInterfaceName(etherConn)); + } + } else { + metisLogger_Log(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Error, __func__, + "Control message asked for ethertype %04x, only %04x supported", + cpiConnectionEthernet_GetEthertype(etherConn), ETHERTYPE); + } + + CCNxControl *response; + if (success) { + response = _createAck(config, control, ingressId); + } else { + response = _createNack(config, control, ingressId); + } + + cpiConnectionEthernet_Release(ðerConn); + return response; +} + +static CCNxControl * +metisConfiguration_ProcessRemoveConnectionEthernet(MetisConfiguration *config, CCNxControl *control, unsigned ingressId) +{ + printf("%s not implemented\n", __func__); + return _createNack(config, control, ingressId); +} + + +static CCNxControl * +metisConfiguration_ProcessRemoveTunnel(MetisConfiguration *config, CCNxControl *control, unsigned ingressId) +{ + CPIInterfaceIPTunnel *iptun = cpiLinks_CreateIPTunnelFromControlMessage(control); + const char *symbolic = cpiInterfaceIPTunnel_GetSymbolicName(iptun); + unsigned ifidx = metisSymbolicNameTable_Get(config->symbolicNameTable, symbolic); + if (ifidx != UINT32_MAX) { + //remove connection from the FIB + metisForwarder_RemoveConnectionIdFromRoutes(config->metis, ifidx); + MetisConnectionTable *table = metisForwarder_GetConnectionTable(config->metis); + //remove connection from connection table + metisConnectionTable_RemoveById(table, ifidx); + //remove connection from symbolicNameTable + metisSymbolicNameTable_Remove(config->symbolicNameTable, symbolic); + //and ... this is it! + CCNxControl *response = _createAck(config, control, ingressId); + cpiInterfaceIPTunnel_Release(&iptun); + return response; + } else { + CCNxControl *response = _createNack(config, control, ingressId); + cpiInterfaceIPTunnel_Release(&iptun); + return response; + } +} + +static CCNxControl * +metisConfiguration_ProcessConnectionList(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + CPIConnectionList *tunnelList = cpiConnectionList_Create(); + + MetisConnectionTable *table = metisForwarder_GetConnectionTable(config->metis); + MetisConnectionList *connList = metisConnectionTable_GetEntries(table); + + for (size_t i = 0; i < metisConnectionList_Length(connList); i++) { + // Don't release original, we're not storing it + MetisConnection *original = metisConnectionList_Get(connList, i); + const MetisAddressPair *addressPair = metisConnection_GetAddressPair(original); + CPIAddress *localAddress = cpiAddress_Copy(metisAddressPair_GetLocal(addressPair)); + CPIAddress *remoteAddress = cpiAddress_Copy(metisAddressPair_GetRemote(addressPair)); + + CPIConnectionType type = metisIoOperations_GetConnectionType(metisConnection_GetIoOperations(original)); + + CPIConnection *cpiConn = cpiConnection_Create(metisConnection_GetConnectionId(original), + localAddress, + remoteAddress, + type); + + cpiConnection_SetState(cpiConn, metisConnection_IsUp(original) ? CPI_IFACE_UP : CPI_IFACE_DOWN); + cpiConnectionList_Append(tunnelList, cpiConn); + } + + PARCJSON *connectListJson = cpiConnectionList_ToJson(tunnelList); + CCNxControl *response = cpi_CreateResponse(request, connectListJson); + parcJSON_Release(&connectListJson); + cpiConnectionList_Destroy(&tunnelList); + metisConnectionList_Destroy(&connList); + + return response; +} + +static CCNxControl * +metisConfiguration_ProcessCacheStoreOn(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + bool success = false; + + metisForwarder_SetChacheStoreFlag(config->metis, true); + if (metisForwarder_GetChacheStoreFlag(config->metis)) { + success = true; + } + + CCNxControl *response; + if (success) { + response = _createAck(config, request, ingressId); + } else { + response = _createNack(config, request, ingressId); + } + return response; +} + +static CCNxControl * +metisConfiguration_ProcessCacheStoreOff(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + bool success = false; + + metisForwarder_SetChacheStoreFlag(config->metis, false); + if (!metisForwarder_GetChacheStoreFlag(config->metis)) { + success = true; + } + + CCNxControl *response; + if (success) { + response = _createAck(config, request, ingressId); + } else { + response = _createNack(config, request, ingressId); + } + return response; +} + +static CCNxControl * +metisConfiguration_ProcessCacheServeOn(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + bool success = true; + + metisForwarder_SetChacheServeFlag(config->metis, true); + if (metisForwarder_GetChacheServeFlag(config->metis)) { + success = true; + } + + CCNxControl *response; + if (success) { + response = _createAck(config, request, ingressId); + } else { + response = _createNack(config, request, ingressId); + } + return response; +} + +static CCNxControl * +metisConfiguration_ProcessCacheServeOff(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + bool success = true; + + metisForwarder_SetChacheServeFlag(config->metis, false); + if (!metisForwarder_GetChacheServeFlag(config->metis)) { + success = true; + } + + + CCNxControl *response; + if (success) { + response = _createAck(config, request, ingressId); + } else { + response = _createNack(config, request, ingressId); + } + return response; +} + +static CCNxControl * +metisConfiguration_ProcessCacheClear(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + metisForwarder_ClearCache(config->metis); + + CCNxControl *response = _createAck(config, request, ingressId); + + return response; +} + + +size_t +metisConfiguration_GetObjectStoreSize(MetisConfiguration *config) +{ + return config->maximumContentObjectStoreSize; +} + +static CCNxControl * +metisConfiguration_SetForwarginStrategy(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + CPIForwardingStrategy *fwdStrategy = cpiForwarding_ForwardingStrategyFromControlMessage(request); + + const CCNxName *prefix = cpiForwardingStrategy_GetPrefix(fwdStrategy); + const char *strategy = cpiForwardingStrategy_GetStrategy(fwdStrategy); + const char *existingFwdStrategy = metisConfiguration_GetForwarginStrategy(config, prefix); + + if ((existingFwdStrategy == NULL) || (strcmp(strategy, existingFwdStrategy) != 0)) { + PARCString *fwdStrategy = parcString_Create(strategy); + parcHashMap_Put(config->strategy_map, prefix, fwdStrategy); + metisForwarder_SetStrategy(config->metis, (CCNxName *) prefix, (char *) strategy); + } + + cpiForwardingStrategy_Destroy(&fwdStrategy); + CCNxControl *response = _createAck(config, request, ingressId); + return response; +} + +static CCNxControl * +metisConfiguration_SetWldr(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + CPIManageWldr *cpiWldr = cpiLinks_ManageWldrFromControlMessage(request); + + const char * connId = cpiManageWldr_GetConnection(cpiWldr); + bool active = cpiManageWldr_IsActive(cpiWldr); + + bool success = false; + unsigned ifidx = metisSymbolicNameTable_Get(config->symbolicNameTable, connId); + if (ifidx != UINT32_MAX) { + MetisConnectionTable *table = metisForwarder_GetConnectionTable(config->metis); + MetisConnection *conn = (MetisConnection *) metisConnectionTable_FindById(table, ifidx); + if(conn == NULL){ + success = false; + } else { + if(active){ + metisConnection_EnableWldr(conn); + } else { + metisConnection_DisableWldr(conn); + } + success = true; + } + } else { + success = false; + } + + CCNxControl *response; + if(success){ + response = _createAck(config, request, ingressId); + } else { + response = _createNack(config, request, ingressId); + } + + cpiManageWldr_Destroy(&cpiWldr); + return response; +} + +const char * +metisConfiguration_GetForwarginStrategy(MetisConfiguration *config, const CCNxName *prefix) +{ + const PARCString *val = parcHashMap_Get(config->strategy_map, prefix); + if (val == NULL) { + return NULL; + } else { + return (const char *) parcString_ToString(val); + } +} + +void +metisConfiguration_SetObjectStoreSize(MetisConfiguration *config, size_t maximumObjectCount) +{ + config->maximumContentObjectStoreSize = maximumObjectCount; + + metisForwarder_SetContentObjectStoreSize(config->metis, config->maximumContentObjectStoreSize); +} + +MetisForwarder * +metisConfiguration_GetForwarder(const MetisConfiguration *config) +{ + return config->metis; +} + +MetisLogger * +metisConfiguration_GetLogger(const MetisConfiguration *config) +{ + return config->logger; +} + +// =========================== +// Main functions that deal with receiving commands, executing them, and sending ACK/NACK + +static CCNxControl * +metisConfiguration_DispatchCommandOldStyle(MetisConfiguration *config, CCNxControl *control, unsigned ingressId) +{ + CCNxControl *response = NULL; + switch (cpi_GetMessageOperation(control)) { + case CPI_FORWARDER_VERSION: + response = metisConfiguration_ProcessForwarderVersion(config, control, ingressId); + break; + + case CPI_INTERFACE_LIST: + response = metisConfiguration_ProcessInterfaceList(config, control, ingressId); + break; + + case CPI_PREFIX_REGISTRATION_LIST: + response = metisConfiguration_ProcessRegistrationList(config, control, ingressId); + break; + + case CPI_REGISTER_PREFIX: + response = metisConfiguration_ProcessRegisterPrefix(config, control, ingressId); + break; + + case CPI_UNREGISTER_PREFIX: + response = metisConfiguration_ProcessUnregisterPrefix(config, control, ingressId); + break; + + case CPI_CREATE_TUNNEL: + response = metisConfiguration_ProcessCreateTunnel(config, control, ingressId); + break; + + case CPI_REMOVE_TUNNEL: + response = metisConfiguration_ProcessRemoveTunnel(config, control, ingressId); + break; + + case CPI_CONNECTION_LIST: + response = metisConfiguration_ProcessConnectionList(config, control, ingressId); + break; + + case CPI_PAUSE: + break; + + case CPI_CACHE_STORE_ON: + response = metisConfiguration_ProcessCacheStoreOn(config, control, ingressId); + break; + + case CPI_CACHE_STORE_OFF: + response = metisConfiguration_ProcessCacheStoreOff(config, control, ingressId); + break; + + case CPI_CACHE_SERVE_ON: + response = metisConfiguration_ProcessCacheServeOn(config, control, ingressId); + break; + + case CPI_CACHE_SERVE_OFF: + response = metisConfiguration_ProcessCacheServeOff(config, control, ingressId); + break; + + case CPI_CACHE_CLEAR: + response = metisConfiguration_ProcessCacheClear(config, control, ingressId); + break; + + case CPI_SET_FORWARDING_STRATEGY: + response = metisConfiguration_SetForwarginStrategy(config, control, ingressId); + break; + + case CPI_SET_WLDR: + response = metisConfiguration_SetWldr(config, control, ingressId); + break; + + default: + break; + } + + return response; +} + +static CCNxControl * +_processControl(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + CCNxControl *response = NULL; + + switch (cpi_GetMessageType(request)) { + case CPI_REQUEST: { + if (cpiConnectionEthernet_IsAddMessage(request)) { + response = metisConfiguration_ProcessAddConnectionEthernet(config, request, ingressId); + } else if (cpiConnectionEthernet_IsRemoveMessage(request)) { + response = metisConfiguration_ProcessRemoveConnectionEthernet(config, request, ingressId); + } else if (cpiListener_IsAddMessage(request)) { + bool success = metisConfigurationListeners_Add(config, request, ingressId); + if (success) { + response = _createAck(config, request, ingressId); + } else { + response = _createNack(config, request, ingressId); + } + } else if (cpiListener_IsRemoveMessage(request)) { + bool success = metisConfigurationListeners_Remove(config, request, ingressId); + if (success) { + response = _createAck(config, request, ingressId); + } else { + response = _createNack(config, request, ingressId); + } + } else { + response = metisConfiguration_DispatchCommandOldStyle(config, request, ingressId); + } + break; + } + + default: + break; + } + + assertNotNull(response, "Got null CCNxControl response"); + return response; +} + +CCNxControl * +metisConfiguration_ReceiveControl(MetisConfiguration *config, CCNxControl *request, unsigned ingressId) +{ + assertNotNull(config, "Parameter config must be non-null"); + assertNotNull(request, "Parameter request must be non-null"); + + CCNxControl *response = _processControl(config, request, ingressId); + return response; +} + +void +metisConfiguration_Receive(MetisConfiguration *config, MetisMessage *message) +{ + assertNotNull(config, "Parameter config must be non-null"); + assertNotNull(message, "Parameter message must be non-null"); + assertTrue(metisMessage_GetType(message) == MetisMessagePacketType_Control, + "Message must be type CPI, expected %02x got %02x", + MetisMessagePacketType_Control, metisMessage_GetType(message)); + + CCNxControl *control = metisMessage_CreateControlMessage(message); + unsigned ingressId = metisMessage_GetIngressConnectionId(message); + + if (metisLogger_IsLoggable(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Debug)) { + char *str = parcJSON_ToCompactString(ccnxControl_GetJson(control)); + metisLogger_Log(config->logger, MetisLoggerFacility_Config, PARCLogLevel_Debug, __func__, + "%s received %s\n", __func__, str); + parcMemory_Deallocate((void **) &str); + } + + CCNxControl *response = _processControl(config, control, ingressId); + metisConfiguration_SendResponse(config, response, ingressId); + ccnxControl_Release(&response); + + ccnxControl_Release(&control); + metisMessage_Release(&message); +} + + diff --git a/metis/ccnx/forwarder/metis/config/metis_Configuration.h b/metis/ccnx/forwarder/metis/config/metis_Configuration.h new file mode 100644 index 00000000..59b18821 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_Configuration.h @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metis_Configuration.h + * @brief Metis configuration, such as in-band commands or CLI + * + * Manages all user configuration of the system, such as from the CLI or web interface + * It remembers the user commands and will be able to write out a config file. + * + */ + +#ifndef Metis_metis_Configuration_h +#define Metis_metis_Configuration_h + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +struct metis_configuration; +typedef struct metis_configuration MetisConfiguration; + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisConfiguration *metisConfiguration_Create(MetisForwarder *metis); + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisConfiguration_Destroy(MetisConfiguration **configPtr); + +void metisConfiguration_SetupAllListeners(MetisConfiguration *config, uint16_t port, const char *localPath); + +/** + * Receive a CPI control message from the user encapsulated in a MetisMessage + * + * Takes ownership of the message, and will destroy it as needed. + * + * @param message is of type CCNX_MSG_CPI. + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisConfiguration_Receive(MetisConfiguration *config, MetisMessage *message); + +/** + * Receives a CPI control message from the user + * + * Processes the message and generates the CPI control response. The response should always + * be non-null and must be released by the caller. + * + * @param [in] config Allocated MetisConfiguration + * @param [in] request The CPI Request to process + * @param [in] ingressId The ingress connection ID, used to track in logging messages + * + * @retval CCNxControl The response control message (an ACK, NACK, or Response). + * + * Example: + * @code + * <#example#> + * @endcode + */ +CCNxControl *metisConfiguration_ReceiveControl(MetisConfiguration *config, CCNxControl *request, unsigned ingressId); + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +PARCJSON *metisConfiguration_GetVersion(MetisConfiguration *config); + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisConfiguration_StartCLI(MetisConfiguration *config, uint16_t port); + +/** + * Returns the configured size of the content store + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +size_t metisConfiguration_GetObjectStoreSize(MetisConfiguration *config); + +/** + * Sets the size of the content store (in objects, not bytes) + * + * Must be set before starting the forwarder + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisConfiguration_SetObjectStoreSize(MetisConfiguration *config, size_t maximumContentObjectCount); + +const char * metisConfiguration_GetForwarginStrategy(MetisConfiguration *config, const CCNxName * prefix); + +/** + * Returns the MetisForwarder that owns the MetisConfiguration + * + * Returns the Metis Forwarder. Used primarily by associated classes in the + * configuration group. + * + * @param [in] config An allocated MetisConfiguration + * + * @return non-null The owning MetisForwarder + * @return null An error + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +MetisForwarder *metisConfiguration_GetForwarder(const MetisConfiguration *config); + +/** + * Returns the logger used by the Configuration subsystem + * + * Returns the logger specified when the MetisConfiguration was created. + * + * @param [in] config An allocated MetisConfiguration + * + * @retval non-null The logger + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisLogger *metisConfiguration_GetLogger(const MetisConfiguration *config); +#endif // Metis_metis_Configuration_h diff --git a/metis/ccnx/forwarder/metis/config/metis_ConfigurationFile.c b/metis/ccnx/forwarder/metis/config/metis_ConfigurationFile.c new file mode 100644 index 00000000..59e4c5a8 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_ConfigurationFile.c @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <config.h> +#include <errno.h> +#include <string.h> +#include <ctype.h> +#include <stdio.h> + +#include <LongBow/longBow_Runtime.h> +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_Object.h> +#include <parc/algol/parc_ArrayList.h> +#include <parc/algol/parc_List.h> +#include <ccnx/forwarder/metis/config/metis_ConfigurationFile.h> +#include <ccnx/forwarder/metis/config/metis_Configuration.h> +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +#include <ccnx/forwarder/metis/config/metisControl_Root.h> + +struct metis_configuration_file { + MetisForwarder *metis; + const char *filename; + FILE *fh; + + size_t linesRead; + + // our custom state machine. + MetisControlState *controlState; +}; + + +/** + * Called by the command parser for each command. + * + * The command parser will make a CCNxControl message inside the CCNxMetaMessage and send it here. + * This function must return a ACK or NACK in a CCNxControl in a CCNxMetaMessage. + * + * @param [in] userdata A void * to MetisConfigurationFile + * @param [in] msg The CCNxControl message to process + * + * @retval CCNxMetaMessage A CPI ACK or NACK in a CCNxControl in a CCNxMetaMessage + * + * Example: + * @code + * <#example#> + * @endcode + */ +static CCNxMetaMessage * +_writeRead(void *userdata, CCNxMetaMessage *msgin) +{ + MetisConfigurationFile *configFile = (MetisConfigurationFile *) userdata; + + CCNxControl *request = ccnxMetaMessage_GetControl(msgin); + CCNxControl *response = metisConfiguration_ReceiveControl(metisForwarder_GetConfiguration(configFile->metis), request, 0); + + CCNxMetaMessage *msgout = ccnxMetaMessage_CreateFromControl(response); + ccnxControl_Release(&response); + + return msgout; +} + +/** + * Removes leading whitespace (space + tab). + * + * If the string is all whitespace, the return value will point to the terminating '\0'. + * + * @param [in] str A null-terminated c-string + * + * @retval non-null A pointer in to string of the first non-whitespace + * + * + * Example: + * @code + * <#example#> + * @endcode + */ +static char * +_stripLeadingWhitespace(char *str) +{ + while (isspace(*str)) { + str++; + } + return str; +} + +/** + * Removes trailing whitespace + * + * Inserts a NULL after the last non-whitespace character, modiyfing the input string. + * + * @param [in] str A null-terminated c-string + * + * @return non-null A pointer to the input string + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +static char * +_stripTrailingWhitespace(char *str) +{ + char *p = str + strlen(str) - 1; + while (p > str && isspace(*p)) { + p--; + } + + // cap it. If no whitespace, p+1 == str + strlen(str), so will overwrite the + // current null. If all whitespace p+1 == str+1. For an empty string, p+1 = str. + *(p + 1) = 0; + + // this does not catch the case where the entire string is whitespace + if (p == str && isspace(*p)) { + *p = 0; + } + + return str; +} + +/** + * Removed leading and trailing whitespace + * + * Modifies the input string (may add a NULL at the end). Will return + * a pointer to the first non-whitespace character or the terminating NULL. + * + * @param [in] str A null-terminated c-string + * + * @return non-null A pointer in to the input string + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +static char * +_trim(char *str) +{ + return _stripTrailingWhitespace(_stripLeadingWhitespace(str)); +} + +/** + * Parse a string in to a PARCList with one word per element + * + * The string passed will be modified by inserting NULLs after each token. + * + * @param [in] str A c-string (will be modified) + * + * @retval non-null A PARCList where each item is a single word + * + * Example: + * @code + * <#example#> + * @endcode + */ +static PARCList * +_parseArgs(char *str) +{ + PARCList *list = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + + const char delimiters[] = " \t"; + + char *token; + while ((token = strsep(&str, delimiters)) != NULL) { + parcList_Add(list, token); + } + + return list; +} + +// ============================================================= + +static void +_destroy(MetisConfigurationFile **configFilePtr) +{ + MetisConfigurationFile *configFile = *configFilePtr; + parcMemory_Deallocate((void **) &configFile->filename); + + if (configFile->fh != NULL) { + fclose(configFile->fh); + } + + metisControlState_Destroy(&configFile->controlState); +} + +parcObject_ExtendPARCObject(MetisConfigurationFile, _destroy, NULL, NULL, NULL, NULL, NULL, NULL); + +parcObject_ImplementRelease(metisConfigurationFile, MetisConfigurationFile); + +MetisConfigurationFile * +metisConfigurationFile_Create(MetisForwarder *metis, const char *filename) +{ + assertNotNull(metis, "Parameter metis must be non-null"); + assertNotNull(filename, "Parameter filename must be non-null"); + + MetisConfigurationFile *configFile = parcObject_CreateInstance(MetisConfigurationFile); + + if (configFile) { + configFile->linesRead = 0; + configFile->metis = metis; + configFile->filename = parcMemory_StringDuplicate(filename, strlen(filename)); + assertNotNull(configFile->filename, "Could not copy string '%s'", filename); + + // setup the control state for the command parser + configFile->controlState = metisControlState_Create(configFile, _writeRead); + + // we do not register Help commands + metisControlState_RegisterCommand(configFile->controlState, metisControlRoot_Create(configFile->controlState)); + + // open the file and make sure we can read it + configFile->fh = fopen(configFile->filename, "r"); + + if (configFile->fh) { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug)) { + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug, __func__, + "Open config file %s", + configFile->filename); + } + } else { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Error)) { + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Error, __func__, + "Could not open config file %s: (%d) %s", + configFile->filename, + errno, + strerror(errno)); + } + + // failure cleanup the object -- this nulls it so final return null be NULL + metisConfigurationFile_Release(&configFile); + } + } + return configFile; +} + + +bool +metisConfigurationFile_Process(MetisConfigurationFile *configFile) +{ + assertNotNull(configFile, "Parameter configFile must be non-null"); + + // default to a "true" return value and only set to false if we encounter an error. + bool success = true; + + #define BUFFERLEN 2048 + char buffer[BUFFERLEN]; + + configFile->linesRead = 0; + + // always clear errors and fseek to start of file in case we get called multiple times. + clearerr(configFile->fh); + rewind(configFile->fh); + + while (success && fgets(buffer, BUFFERLEN, configFile->fh) != NULL) { + configFile->linesRead++; + + char *stripedBuffer = _trim(buffer); + if (strlen(stripedBuffer) > 0) { + if (stripedBuffer[0] != '#') { + // not empty and not a comment + + // _parseArgs will modify the string + char *copy = parcMemory_StringDuplicate(stripedBuffer, strlen(stripedBuffer)); + PARCList *args = _parseArgs(copy); + MetisCommandReturn result = metisControlState_DispatchCommand(configFile->controlState, args); + + // we ignore EXIT from the configuration file + if (result == MetisCommandReturn_Failure) { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(configFile->metis), MetisLoggerFacility_Config, PARCLogLevel_Error)) { + metisLogger_Log(metisForwarder_GetLogger(configFile->metis), MetisLoggerFacility_Config, PARCLogLevel_Error, __func__, + "Error on input file %s line %d: %s", + configFile->filename, + configFile->linesRead, + stripedBuffer); + } + success = false; + } + parcList_Release(&args); + parcMemory_Deallocate((void **) ©); + } + } + } + + if (ferror(configFile->fh)) { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(configFile->metis), MetisLoggerFacility_Config, PARCLogLevel_Error)) { + metisLogger_Log(metisForwarder_GetLogger(configFile->metis), MetisLoggerFacility_Config, PARCLogLevel_Error, __func__, + "Error on input file %s line %d: (%d) %s", + configFile->filename, + configFile->linesRead, + errno, + strerror(errno)); + } + success = false; + } + + return success; +} + diff --git a/metis/ccnx/forwarder/metis/config/metis_ConfigurationFile.h b/metis/ccnx/forwarder/metis/config/metis_ConfigurationFile.h new file mode 100644 index 00000000..e2f07085 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_ConfigurationFile.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metis_ConfigurationFile.h + * @brief Accepts a filename and provides a means to read it into MetisConfiguration + * + * Reads a configuration file and converts the lines in to configuration commands for use + * in MetisConfiguration. + * + * Accepts '#' lines as comments. Skips blank and whitespace-only lines. + * + */ + +#ifndef Metis__metis_ConfigurationFile_h +#define Metis__metis_ConfigurationFile_h + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +struct metis_configuration_file; +typedef struct metis_configuration_file MetisConfigurationFile; + +/** + * Creates a MetisConfigurationFile to prepare to process the file + * + * Prepares the object and opens the file. Makes sure we can read the file. + * Does not read the file or process any commands from the file. + * + * @param [in] metis An allocated MetisForwarder to configure with the file + * @param [in] filename The file to use + * + * @retval non-null An allocated MetisConfigurationFile that is readable + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisConfigurationFile *metisConfigurationFile_Create(MetisForwarder *metis, const char *filename); + +/** + * Reads the configuration file line-by-line and issues commands to MetisConfiguration + * + * Reads the file line by line. Skips '#' and blank lines. Creates CPI objects from the + * lines and feeds them to MetisConfiguration. + * + * Will stop on the first error. Lines already processed will not be un-done. + * + * @param [in] configFile An allocated MetisConfigurationFile + * + * @retval true The entire files was processed without error. + * @retval false There was an error in the file. + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisConfigurationFile_Process(MetisConfigurationFile *configFile); + +//void metisConfigurationFile_ProcessForwardingStrategies(MetisConfiguration * config, MetisConfigurationFile * configFile); + +/** + * Closes the underlying file and releases memory + * + * <#Paragraphs Of Explanation#> + * + * @param [in,out] configFilePtr An allocated MetisConfigurationFile that will be NULL'd as output + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisConfigurationFile_Release(MetisConfigurationFile **configFilePtr); + +#endif /* defined(Metis__metis_ConfigurationFile_h) */ diff --git a/metis/ccnx/forwarder/metis/config/metis_ConfigurationListeners.c b/metis/ccnx/forwarder/metis/config/metis_ConfigurationListeners.c new file mode 100644 index 00000000..5d744ac1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_ConfigurationListeners.c @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <arpa/inet.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/api/control/cpi_InterfaceSet.h> +#include <ccnx/api/control/cpi_Listener.h> +#include <ccnx/forwarder/metis/core/metis_System.h> + +#include <ccnx/forwarder/metis/config/metis_ConfigurationListeners.h> +#include <ccnx/forwarder/metis/io/metis_TcpListener.h> +#include <ccnx/forwarder/metis/io/metis_UdpListener.h> +#include <ccnx/forwarder/metis/io/metis_LocalListener.h> +#include <ccnx/forwarder/metis/io/metis_EtherListener.h> + +static bool +_setupTcpListenerOnInet(MetisForwarder *metis, const CPIAddress *address, uint16_t port) +{ + bool success = false; + struct sockaddr_in addr_sin; + cpiAddress_GetInet(address, &addr_sin); + addr_sin.sin_port = htons(port); + + MetisListenerOps *ops = metisTcpListener_CreateInet(metis, addr_sin); + if (ops) { + success = metisListenerSet_Add(metisForwarder_GetListenerSet(metis), ops); + assertTrue(success, "Failed to add TCP listener on %s to ListenerSet", cpiAddress_ToString(ops->getListenAddress(ops))); + } + return success; +} + +static bool +_setupUdpListenerOnInet(MetisForwarder *metis, const CPIAddress *address, uint16_t port) +{ + bool success = false; + struct sockaddr_in addr_sin; + cpiAddress_GetInet(address, &addr_sin); + addr_sin.sin_port = htons(port); + + MetisListenerOps *ops = metisUdpListener_CreateInet(metis, addr_sin); + if (ops) { + success = metisListenerSet_Add(metisForwarder_GetListenerSet(metis), ops); + assertTrue(success, "Failed to add UDP listener on %s to ListenerSet", cpiAddress_ToString(ops->getListenAddress(ops))); + } + return success; +} + +static bool +_setupTcpListenerOnInet6(MetisForwarder *metis, const CPIAddress *address, uint16_t port) +{ + bool success = false; + struct sockaddr_in6 addr_sin6; + cpiAddress_GetInet6(address, &addr_sin6); + addr_sin6.sin6_port = htons(port); + + MetisListenerOps *ops = metisTcpListener_CreateInet6(metis, addr_sin6); + if (ops) { + success = metisListenerSet_Add(metisForwarder_GetListenerSet(metis), ops); + assertTrue(success, "Failed to add TCP6 listener on %s to ListenerSet", cpiAddress_ToString(ops->getListenAddress(ops))); + } + return success; +} + +static bool +_setupUdpListenerOnInet6(MetisForwarder *metis, const CPIAddress *address, uint16_t port) +{ + bool success = false; + struct sockaddr_in6 addr_sin6; + cpiAddress_GetInet6(address, &addr_sin6); + addr_sin6.sin6_port = htons(port); + + MetisListenerOps *ops = metisUdpListener_CreateInet6(metis, addr_sin6); + if (ops) { + success = metisListenerSet_Add(metisForwarder_GetListenerSet(metis), ops); + assertTrue(success, "Failed to add UDP6 listener on %s to ListenerSet", cpiAddress_ToString(ops->getListenAddress(ops))); + } + return success; +} + +static bool +_setupLocalListener(MetisForwarder *metis, const char *path) +{ + bool success = false; + MetisListenerOps *ops = metisLocalListener_Create(metis, path); + if (ops) { + success = metisListenerSet_Add(metisForwarder_GetListenerSet(metis), ops); + assertTrue(success, "Failed to add Local listener on %s to ListenerSet", path); + } + return success; +} + +static MetisListenerOps * +_setupEthernetListenerOnLink(MetisForwarder *metis, const CPIAddress *address, const char *interfaceName, uint16_t ethertype) +{ + MetisListenerOps *ops = metisEtherListener_Create(metis, interfaceName, ethertype); + if (ops) { + bool success = metisListenerSet_Add(metisForwarder_GetListenerSet(metis), ops); + if (!success) { + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Error, __func__, + "Failed to add Ethernet listener on %s ethertype 0x%04x to ListenerSet (likely already one on interface)", interfaceName, ethertype); + + // this will null ops for the return value + ops->destroy(&ops); + } + } else { + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Error, __func__, + "Could not start Ethernet listener on interface %s\n", interfaceName); + } + return ops; +} + +static void +_setupListenersOnInet(MetisForwarder *metis, const CPIAddress *address, uint16_t port) +{ + _setupTcpListenerOnInet(metis, address, port); + _setupUdpListenerOnInet(metis, address, port); +} + +static void +_setupListenersOnInet6(MetisForwarder *metis, const CPIAddress *address, uint16_t port) +{ + _setupTcpListenerOnInet6(metis, address, port); + _setupUdpListenerOnInet6(metis, address, port); +} + +static void +_setupListenersOnAddress(MetisForwarder *metis, const CPIAddress *address, uint16_t port, const char *interfaceName) +{ + CPIAddressType type = cpiAddress_GetType(address); + switch (type) { + case cpiAddressType_INET: + _setupListenersOnInet(metis, address, port); + break; + + case cpiAddressType_INET6: + _setupListenersOnInet6(metis, address, port); + break; + + case cpiAddressType_LINK: + // not used + break; + + default: + // dont' know how to handle this, so no listeners + break; + } +} + +void +metisConfigurationListeners_SetupAll(const MetisConfiguration *config, uint16_t port, const char *localPath) +{ + MetisForwarder *metis = metisConfiguration_GetForwarder(config); + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + + size_t interfaceSetLength = cpiInterfaceSet_Length(set); + for (size_t i = 0; i < interfaceSetLength; i++) { + CPIInterface *iface = cpiInterfaceSet_GetByOrdinalIndex(set, i); + + const CPIAddressList *addresses = cpiInterface_GetAddresses(iface); + size_t addressListLength = cpiAddressList_Length(addresses); + + for (size_t j = 0; j < addressListLength; j++) { + const CPIAddress *address = cpiAddressList_GetItem(addresses, j); + + // Do not start on link address + if (cpiAddress_GetType(address) != cpiAddressType_LINK) { + _setupListenersOnAddress(metis, address, port, cpiInterface_GetName(iface)); + } + } + } + + if (localPath != NULL) { + _setupLocalListener(metis, localPath); + } + + cpiInterfaceSet_Destroy(&set); +} + + +static bool +_addEther(const MetisConfiguration *config, const CPIListener *cpiListener, unsigned ingressId) +{ + bool success = false; + CPIAddress *mac = metisSystem_GetMacAddressByName(metisConfiguration_GetForwarder(config), cpiListener_GetInterfaceName(cpiListener)); + if (mac) { + MetisListenerOps *listener = _setupEthernetListenerOnLink(metisConfiguration_GetForwarder(config), mac, cpiListener_GetInterfaceName(cpiListener), cpiListener_GetEtherType(cpiListener)); + success = (listener != NULL); + cpiAddress_Destroy(&mac); + } + return success; +} + +static bool +_addIP(const MetisConfiguration *config, const CPIListener *cpiListener, unsigned ingressId) +{ + bool success = false; + CPIAddress *localAddress = cpiListener_GetAddress(cpiListener); + + switch (cpiAddress_GetType(localAddress)) { + case cpiAddressType_INET: { + // The CPI address, in this case, has the port inside it, so use that + struct sockaddr_in sin; + cpiAddress_GetInet(localAddress, &sin); + if (cpiListener_IsProtocolUdp(cpiListener)) { + success = _setupUdpListenerOnInet(metisConfiguration_GetForwarder(config), localAddress, htons(sin.sin_port)); + } else if (cpiListener_IsProtocolTcp(cpiListener)) { + success = _setupTcpListenerOnInet(metisConfiguration_GetForwarder(config), localAddress, htons(sin.sin_port)); + } + break; + } + + case cpiAddressType_INET6: { + // The CPI address, in this case, has the port inside it, so use that + struct sockaddr_in6 sin6; + cpiAddress_GetInet6(localAddress, &sin6); + if (cpiListener_IsProtocolUdp(cpiListener)) { + success = _setupUdpListenerOnInet6(metisConfiguration_GetForwarder(config), localAddress, htons(sin6.sin6_port)); + } else if (cpiListener_IsProtocolTcp(cpiListener)) { + success = _setupTcpListenerOnInet6(metisConfiguration_GetForwarder(config), localAddress, htons(sin6.sin6_port)); + } + break; + } + + default: + if (metisLogger_IsLoggable(metisConfiguration_GetLogger(config), MetisLoggerFacility_Config, PARCLogLevel_Warning)) { + char *str = cpiAddress_ToString(localAddress); + metisLogger_Log(metisConfiguration_GetLogger(config), MetisLoggerFacility_Config, PARCLogLevel_Warning, __func__, + "Unsupported address type for IP encapsulation ingress id %u: %s", + ingressId, + str); + parcMemory_Deallocate((void **) &str); + } + break; + } + + if (success) { + if (metisLogger_IsLoggable(metisConfiguration_GetLogger(config), MetisLoggerFacility_Config, PARCLogLevel_Info)) { + char *str = cpiAddress_ToString(localAddress); + metisLogger_Log(metisConfiguration_GetLogger(config), MetisLoggerFacility_Config, PARCLogLevel_Info, __func__, + "Setup listener on address %s", + str); + parcMemory_Deallocate((void **) &str); + } + } + + return success; +} + +bool +metisConfigurationListeners_Add(const MetisConfiguration *config, const CCNxControl *control, unsigned ingressId) +{ + bool success = false; + CPIListener *cpiListener = cpiListener_FromControl(control); + if (cpiListener) { + if (cpiListener_IsEtherEncap(cpiListener)) { + success = _addEther(config, cpiListener, ingressId); + } else if (cpiListener_IsIPEncap(cpiListener)) { + success = _addIP(config, cpiListener, ingressId); + } else { + MetisLogger *logger = metisConfiguration_GetLogger(config); + if (metisLogger_IsLoggable(logger, MetisLoggerFacility_Config, PARCLogLevel_Warning)) { + PARCJSON *json = ccnxControl_GetJson(control); + char *str = parcJSON_ToCompactString(json); + metisLogger_Log(logger, MetisLoggerFacility_Config, PARCLogLevel_Warning, __func__, + "Unsupported encapsulation ingress %u control %s", + ingressId, + str); + parcMemory_Deallocate((void **) &str); + } + } + + cpiListener_Release(&cpiListener); + } else { + MetisLogger *logger = metisConfiguration_GetLogger(config); + if (metisLogger_IsLoggable(logger, MetisLoggerFacility_Config, PARCLogLevel_Warning)) { + PARCJSON *json = ccnxControl_GetJson(control); + char *str = parcJSON_ToCompactString(json); + metisLogger_Log(logger, MetisLoggerFacility_Config, PARCLogLevel_Warning, __func__, + "Could not parse control message ingress %u control %s", + ingressId, + str); + parcMemory_Deallocate((void **) &str); + } + } + return success; +} + +bool +metisConfigurationListeners_Remove(const MetisConfiguration *config, const CCNxControl *control, unsigned ingressId) +{ + MetisLogger *logger = metisConfiguration_GetLogger(config); + if (metisLogger_IsLoggable(logger, MetisLoggerFacility_Config, PARCLogLevel_Warning)) { + PARCJSON *json = ccnxControl_GetJson(control); + char *str = parcJSON_ToCompactString(json); + metisLogger_Log(logger, MetisLoggerFacility_Config, PARCLogLevel_Warning, __func__, + "Removing a listener not supported: ingress %u control %s", + ingressId, + str); + parcMemory_Deallocate((void **) &str); + } + + return false; +} + diff --git a/metis/ccnx/forwarder/metis/config/metis_ConfigurationListeners.h b/metis/ccnx/forwarder/metis/config/metis_ConfigurationListeners.h new file mode 100644 index 00000000..c39865b0 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_ConfigurationListeners.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metis_ConfigurationListeners.h + * @brief Configuration routines related to Listeners + * + * Adding and removing listeners. + * + */ + +#ifndef Metis__metis_ConfigurationListeners_h +#define Metis__metis_ConfigurationListeners_h + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/config/metis_Configuration.h> + +#include <ccnx/api/control/cpi_Address.h> +#include <ccnx/api/control/cpi_ControlMessage.h> + +/** + * Setup udp, tcp, and local listeners + * + * Will bind to all available IP protocols on the given port. + * Does not add Ethernet listeners. + * + * @param port is the UPD and TCP port to use + * @param localPath is the AF_UNIX path to use, if NULL no AF_UNIX listener is setup + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisConfigurationListeners_SetupAll(const MetisConfiguration *config, uint16_t port, const char *localPath); + +bool metisConfigurationListeners_Add(const MetisConfiguration *config, const CCNxControl *control, unsigned ingressId); +bool metisConfigurationListeners_Remove(const MetisConfiguration *config, const CCNxControl *control, unsigned ingressId); + + +#endif /* defined(Metis__metis_ConfigurationListeners_h) */ diff --git a/metis/ccnx/forwarder/metis/config/metis_ControlState.c b/metis/ccnx/forwarder/metis/config/metis_ControlState.c new file mode 100644 index 00000000..bf95afce --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_ControlState.c @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include <LongBow/runtime.h> +#include <string.h> + +#include <parc/security/parc_Security.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_Network.h> +#include <parc/algol/parc_List.h> +#include <parc/algol/parc_TreeRedBlack.h> +#include <parc/algol/parc_Time.h> + +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +#include <ccnx/forwarder/metis/config/metisControl_Root.h> +#include <ccnx/forwarder/metis/config/metis_CommandParser.h> + +struct metis_control_state { + MetisCommandParser *parser; + bool debugFlag; + + void *userdata; + CCNxMetaMessage * (*writeRead)(void *userdata, CCNxMetaMessage *msg); +}; + +MetisControlState * +metisControlState_Create(void *userdata, CCNxMetaMessage * (*writeRead)(void *userdata, CCNxMetaMessage * msg)) +{ + MetisControlState *state = parcMemory_AllocateAndClear(sizeof(MetisControlState)); + assertNotNull(state, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisControlState)); + state->parser = metisCommandParser_Create(); + + state->userdata = userdata; + state->writeRead = writeRead; + state->debugFlag = false; + + return state; +} + +void +metisControlState_Destroy(MetisControlState **statePtr) +{ + assertNotNull(statePtr, "Parameter statePtr must be non-null"); + assertNotNull(*statePtr, "Parameter statePtr must dereference t non-null"); + MetisControlState *state = *statePtr; + metisCommandParser_Destroy(&state->parser); + + parcMemory_Deallocate((void **) &state); + *statePtr = NULL; +} + +void +metisControlState_SetDebug(MetisControlState *state, bool debugFlag) +{ + assertNotNull(state, "Parameter state must be non-null"); + state->debugFlag = debugFlag; + metisCommandParser_SetDebug(state->parser, debugFlag); +} + +bool +metisControlState_GetDebug(MetisControlState *state) +{ + assertNotNull(state, "Parameter state must be non-null"); + return state->debugFlag; +} + +void +metisControlState_RegisterCommand(MetisControlState *state, MetisCommandOps *ops) +{ + assertNotNull(state, "Parameter state must be non-null"); + metisCommandParser_RegisterCommand(state->parser, ops); +} + +CCNxMetaMessage * +metisControlState_WriteRead(MetisControlState *state, CCNxMetaMessage *msg) +{ + assertNotNull(state, "Parameter state must be non-null"); + assertNotNull(msg, "Parameter msg must be non-null"); + + return state->writeRead(state->userdata, msg); +} + +static PARCList * +_metisControlState_ParseStringIntoTokens(const char *originalString) +{ + PARCList *list = parcList(parcArrayList_Create(parcArrayList_StdlibFreeFunction), PARCArrayListAsPARCList); + + char *token; + + char *tofree = parcMemory_StringDuplicate(originalString, strlen(originalString) + 1); + char *string = tofree; + + while ((token = strsep(&string, " \t\n")) != NULL) { + if (strlen(token) > 0) { + parcList_Add(list, strdup(token)); + } + } + + parcMemory_Deallocate((void **) &tofree); + + return list; +} + +MetisCommandReturn +metisControlState_DispatchCommand(MetisControlState *state, PARCList *args) +{ + assertNotNull(state, "Parameter state must be non-null"); + return metisCommandParser_DispatchCommand(state->parser, args); +} + +int +metisControlState_Interactive(MetisControlState *state) +{ + assertNotNull(state, "Parameter state must be non-null"); + char *line = NULL; + size_t linecap = 0; + MetisCommandReturn controlReturn = MetisCommandReturn_Success; + + while (controlReturn != MetisCommandReturn_Exit && !feof(stdin)) { + fputs("> ", stdout); fflush(stdout); + ssize_t failure = getline(&line, &linecap, stdin); + assertTrue(failure > -1, "Error getline"); + + PARCList *args = _metisControlState_ParseStringIntoTokens(line); + controlReturn = metisControlState_DispatchCommand(state, args); + parcList_Release(&args); + } + return 0; +} diff --git a/metis/ccnx/forwarder/metis/config/metis_ControlState.h b/metis/ccnx/forwarder/metis/config/metis_ControlState.h new file mode 100644 index 00000000..ed8fc52e --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_ControlState.h @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metis_ControlState.h + * @brief A control program for Metis using CLI commands + * + * Implements the state machine for the control program. It takes a "writeRead" function + * as part of the constructor. This abstracts out the backend. It could be a Portal from + * metis_control program down to the forwarder or it could be an internal function within + * metis. + * + */ + +#ifndef Metis_metis_control_h +#define Metis_metis_control_h + +#include <parc/algol/parc_List.h> +#include <ccnx/transport/common/transport_MetaMessage.h> +#include <ccnx/forwarder/metis/config/metis_CommandParser.h> + +struct metis_control_state; +typedef struct metis_control_state MetisControlState; + +/** + * metisControlState_Create + * + * Creates the global state for the MetisControl program. The user provides the writeRead function + * for sending and receiving the CCNxMetaMessage wrapping a CPIControlMessage. For a CLI program, this + * function would work over a CCNxSocket or CCNxComms. For the baked-in CLI or configuration file + * inside metis, it would make direct calls to MetisConfiguration. + * + * @param [in] userdata A closure passed back to the user when calling writeRead. + * @param [in] writeRead The function to write then read configuration messages to Metis + * + * @return non-null The control state + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisControlState *metisControlState_Create(void *userdata, CCNxMetaMessage * (*writeRead)(void *userdata, CCNxMetaMessage * msg)); + +/** + * Destroys the control state, closing all network connections + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisControlState_Destroy(MetisControlState **statePtr); + +/** + * Registers a MetisCommandOps with the system. + * + * Each command has its complete command prefix in the "command" field. RegisterCommand + * will put these command prefixes in to a tree and then match what a user types against + * the longest-matching prefix in the tree. If there's a match, it will call the "execute" + * function. + * + * @param [in] state An allocated MetisControlState + * @param [in] command The command to register with the system + * + * Example: + * @code + * static MetisCommandReturn + * metisControl_Root_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) + * { + * printf("Root Command\n"); + * return MetisCommandReturn_Success; + * } + * + * static MetisCommandReturn + * metisControl_FooBar_Execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) + * { + * printf("Foo Bar Command\n"); + * return MetisCommandReturn_Success; + * } + * + * const MetisCommandOps metisControl_Root = { + * .command = "", // empty string for root + * .init = NULL, + * .execute = metisControl_Root_Execute + * }; + * + * const MetisCommandOps metisControl_FooBar = { + * .command = "foo bar", // empty string for root + * .init = NULL, + * .execute = metisControl_FooBar_Execute + * }; + * + * void startup(void) + * { + * MetisControlState *state = metisControlState_Create("happy", "day"); + * metisControlState_RegisterCommand(state, metisControl_FooBar); + * metisControlState_RegisterCommand(state, metisControl_Root); + * + * // this executes "root" + * metisControlState_DispatchCommand(state, "foo"); + * metisControlState_Destroy(&state); + * } + * @endcode + */ +void metisControlState_RegisterCommand(MetisControlState *state, MetisCommandOps *command); + +/** + * Performs a longest-matching prefix of the args to the command tree + * + * The command tree is created with metisControlState_RegisterCommand. + * + * @param [in] state The allocated MetisControlState + * @param [in] args Each command-line word parsed to the ordered list + * + * @return MetisCommandReturn_Success the command was successful + * @return MetisCommandReturn_Failure the command failed or was not found + * @return MetisCommandReturn_Exit the command indicates that the interactive mode should exit + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisCommandReturn metisControlState_DispatchCommand(MetisControlState *state, PARCList *args); + +/** + * Begin an interactive shell + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +int metisControlState_Interactive(MetisControlState *state); + +/** + * Write then Read a CPI command + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +CCNxMetaMessage *metisControlState_WriteRead(MetisControlState *state, CCNxMetaMessage *msg); + +/** + * Sets the Debug mode, which will print out much more information. + * + * Prints out much more diagnostic information about what metis-control is doing. + * yes, you would make a MetisCommandOps to set and unset this :) + * + * @param [in] debugFlag true means to print debug info, false means to turn it off + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisControlState_SetDebug(MetisControlState *state, bool debugFlag); + +/** + * Returns the debug state of MetisControlState + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisControlState_GetDebug(MetisControlState *state); +#endif // Metis_metis_control_h diff --git a/metis/ccnx/forwarder/metis/config/metis_SymbolicNameTable.c b/metis/ccnx/forwarder/metis/config/metis_SymbolicNameTable.c new file mode 100644 index 00000000..f6c50688 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_SymbolicNameTable.c @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <config.h> +#include <ctype.h> + +#include <LongBow/runtime.h> +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_HashCodeTable.h> +#include <parc/algol/parc_Hash.h> + +#include <ccnx/forwarder/metis/config/metis_SymbolicNameTable.h> + +struct metis_symblic_name_table { + PARCHashCodeTable *symbolicNameTable; +}; + +// ======================================================================================== +// symbolic name table functions + +static bool +_symbolicNameEquals(const void *keyA, const void *keyB) +{ + return (strcasecmp((const char *) keyA, (const char *) keyB) == 0); +} + +static HashCodeType +_symbolicNameHash(const void *keyA) +{ + const char *str = (const char *) keyA; + size_t length = strlen(str); + return parcHash32_Data(str, length); +} + +// ======================================================================================== + +MetisSymbolicNameTable * +metisSymbolicNameTable_Create(void) +{ + MetisSymbolicNameTable *table = parcMemory_Allocate(sizeof(MetisSymbolicNameTable)); + + if (table) { + // key = char * + // value = uint32_t * + table->symbolicNameTable = parcHashCodeTable_Create(_symbolicNameEquals, _symbolicNameHash, parcMemory_DeallocateImpl, parcMemory_DeallocateImpl); + } + + return table; +} + +void +metisSymbolicNameTable_Destroy(MetisSymbolicNameTable **tablePtr) +{ + MetisSymbolicNameTable *table = *tablePtr; + parcHashCodeTable_Destroy(&table->symbolicNameTable); + parcMemory_Deallocate((void **) &table); + *tablePtr = NULL; +} + +static char * +_createKey(const char *symbolicName) +{ + char *key = parcMemory_StringDuplicate(symbolicName, strlen(symbolicName)); + + // convert key to upper case + char *p = key; + + // keeps looping until the first null + while ((*p = toupper(*p))) { + p++; + } + return key; +} + +bool +metisSymbolicNameTable_Exists(MetisSymbolicNameTable *table, const char *symbolicName) +{ + assertNotNull(table, "Parameter table must be non-null"); + assertNotNull(symbolicName, "Parameter symbolicName must be non-null"); + + char *key = _createKey(symbolicName); + bool found = (parcHashCodeTable_Get(table->symbolicNameTable, key) != NULL); + parcMemory_Deallocate((void **) &key); + return found; +} + +void +metisSymbolicNameTable_Remove(MetisSymbolicNameTable *table, const char *symbolicName) +{ + assertNotNull(table, "Parameter table must be non-null"); + assertNotNull(symbolicName, "Parameter symbolicName must be non-null"); + + char *key = _createKey(symbolicName); + parcHashCodeTable_Del(table->symbolicNameTable, key); + parcMemory_Deallocate((void **) &key); + +} + +bool +metisSymbolicNameTable_Add(MetisSymbolicNameTable *table, const char *symbolicName, unsigned connid) +{ + assertNotNull(table, "Parameter table must be non-null"); + assertNotNull(symbolicName, "Parameter symbolicName must be non-null"); + assertTrue(connid < UINT32_MAX, "Parameter connid must be less than %u", UINT32_MAX); + + char *key = _createKey(symbolicName); + + uint32_t *value = parcMemory_Allocate(sizeof(uint32_t)); + *value = connid; + + bool success = parcHashCodeTable_Add(table->symbolicNameTable, key, value); + if (!success) { + parcMemory_Deallocate((void **) &key); + parcMemory_Deallocate((void **) &value); + } + + return success; +} + +unsigned +metisSymbolicNameTable_Get(MetisSymbolicNameTable *table, const char *symbolicName) +{ + assertNotNull(table, "Parameter table must be non-null"); + assertNotNull(symbolicName, "Parameter symbolicName must be non-null"); + + unsigned connid = UINT32_MAX; + + char *key = _createKey(symbolicName); + + uint32_t *value = parcHashCodeTable_Get(table->symbolicNameTable, key); + if (value) { + connid = *value; + } + + parcMemory_Deallocate((void **) &key); + return connid; +} + diff --git a/metis/ccnx/forwarder/metis/config/metis_SymbolicNameTable.h b/metis/ccnx/forwarder/metis/config/metis_SymbolicNameTable.h new file mode 100644 index 00000000..cff341c8 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_SymbolicNameTable.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metis_SymbolicNameTable.h + * @brief The symbolic name table maps a string name to a connection id + * + * When configuring tunnels/connections, the user provides a string name (symbolic name) that they + * will use to refer to that connection. The symblic name table translates that symbolic name + * to a connection id. + * + */ + +#ifndef __Metis__metis_SymbolicNameTable__ +#define __Metis__metis_SymbolicNameTable__ + +struct metis_symblic_name_table; +typedef struct metis_symblic_name_table MetisSymbolicNameTable; + +#include <stdbool.h> + +/** + * Creates a symbolic name table + * + * Allocates a MetisSymbolicNameTable, which will store the symbolic names + * in a hash table. + * + * @retval non-null An allocated MetisSymbolicNameTable + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisSymbolicNameTable *metisSymbolicNameTable_Create(void); + +/** + * Destroys a name table + * + * All keys and data are released. + * + * @param [in,out] tablePtr A pointer to a MetisSymbolicNameTable, which will be NULL'd + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisSymbolicNameTable_Destroy(MetisSymbolicNameTable **tablePtr); + +/** + * Tests if the name (case insensitive) is in the table + * + * Does a case-insensitive match to see if the name is in the table + * + * @param [in] table An allocated MetisSymbolicNameTable + * @param [in] symbolicName The name to check for + * + * @retval true The name is in the table + * @retval false The name is not in the talbe + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisSymbolicNameTable_Exists(MetisSymbolicNameTable *table, const char *symbolicName); + +/** + * Adds a (name, connid) pair to the table. + * + * The name is stored case insensitive. The value UINT_MAX is used to indicate a + * non-existent key, so it should not be stored as a value in the table. + * + * @param [in] table An allocated MetisSymbolicNameTable + * @param [in] symbolicName The name to save (will make a copy) + * @param [in] connid The connection id to associate with the name + * + * @retval true The pair was added + * @retval false The pair was not added (likely duplicate key) + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisSymbolicNameTable_Add(MetisSymbolicNameTable *table, const char *symbolicName, unsigned connid); + +/** + * Returns the connection id associated with the symbolic name + * + * This function will look for the given name (case insensitive) and return the + * corresponding connid. If the name is not in the table, the function will return UINT_MAX. + * + * @param [in] table An allocated MetisSymbolicNameTable + * @param [in] symbolicName The name to retrieve + * + * @retval UINT_MAX symbolicName not found + * @retval number the corresponding connid. + * + * Example: + * @code + * <#example#> + * @endcode + */ +unsigned metisSymbolicNameTable_Get(MetisSymbolicNameTable *table, const char *symbolicName); + +void metisSymbolicNameTable_Remove(MetisSymbolicNameTable *table, const char *symbolicName); + +#endif /* defined(__Metis__metis_SymbolicNameTable__) */ diff --git a/metis/ccnx/forwarder/metis/config/metis_WebInterface.h b/metis/ccnx/forwarder/metis/config/metis_WebInterface.h new file mode 100644 index 00000000..4813c98e --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_WebInterface.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#ifndef Metis_metis_WebInterface_h +#define Metis_metis_WebInterface_h + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +struct metis_web; +typedef struct metis_web MetisWeb; + +/** + * Creates a Web on the given port. + * + * A http interface. The Web interface is created in the STOPPED mode, so + * you need to start it for it to be usable. + * + * Create will bind the port, but callbacks in the dispatcher will not be + * enabled until it is started. + * + * @param port the command port, in host byte order + * @return NULL if cannot be created on the port + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisWeb *metisWeb_Create(MetisForwarder *metis, uint16_t port); + +/** + * Stops and destroys the web interface. Existing sessions are destroyed. + * + * <#Discussion#> + * + * @param <#param1#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisWeb_Destroy(MetisWeb **WebPtr); + +/** + * Enables the web interface in the event dispatcher. + * + * <#Discussion#> + * + * @param <#param1#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisWeb_Start(MetisWeb *web); + +/** + * Disables callback in the event dispatcher. Existing connections unaffected. + * + * Stopping it only disable accepting new connections. + * + * @param <#param1#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisWeb_Stop(MetisWeb *web); +#endif // Metis_metis_WebInterface_h diff --git a/metis/ccnx/forwarder/metis/config/test/CMakeLists.txt b/metis/ccnx/forwarder/metis/config/test/CMakeLists.txt new file mode 100644 index 00000000..7e14e1d5 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/CMakeLists.txt @@ -0,0 +1,37 @@ +# Enable gcov output for the tests +add_definitions(--coverage) +set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} " --coverage") + +set(TestsExpectedToPass + test_metis_ControlState + #test_metis_CommandLineInterface + test_metis_CommandOps + test_metis_CommandParser + test_metis_Configuration + test_metis_ConfigurationFile + test_metis_ConfigurationListeners + test_metis_SymbolicNameTable + test_metisControl_Add + test_metisControl_AddConnection + test_metisControl_AddListener + test_metisControl_AddRoute + test_metisControl_List + test_metisControl_ListConnections + test_metisControl_ListInterfaces + test_metisControl_ListRoutes + test_metisControl_Quit + test_metisControl_Remove + test_metisControl_RemoveConnection + test_metisControl_RemoveRoute + test_metisControl_Root + test_metisControl_Set + test_metisControl_SetDebug + test_metisControl_Unset + test_metisControl_UnsetDebug +) + + +foreach(test ${TestsExpectedToPass}) + AddTest(${test}) +endforeach() + diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_Add.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_Add.c new file mode 100644 index 00000000..c4001621 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_Add.c @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metisControl_Add.c" + +#include <LongBow/unit-test.h> + +#include "testrig_MetisControl.c" + +LONGBOW_TEST_RUNNER(metisControl_Add) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metisControl_Add) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_Add) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlAdd_Create); + LONGBOW_RUN_TEST_CASE(Global, metisControlAdd_CreateHelp); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisControlAdd_Create) +{ + testCommandCreate(testCase, metisControlAdd_Create, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlAdd_CreateHelp) +{ + testCommandCreate(testCase, metisControlAdd_CreateHelp, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _metisControlAdd_Execute); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAdd_Init); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAdd_HelpExecute); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, _metisControlAdd_Execute) +{ + testHelpExecute(testCase, metisControlAdd_Create, __func__, MetisCommandReturn_Success); +} + +/** + * Init should add 4 commands to the command parser + */ +LONGBOW_TEST_CASE(Local, _metisControlAdd_Init) +{ + testInit(testCase, metisControlAdd_Create, __func__, + (const char *[]) { + "add connection", "add route", + "help add connection", "help add route", + NULL + }); +} + +LONGBOW_TEST_CASE(Local, _metisControlAdd_HelpExecute) +{ + testHelpExecute(testCase, metisControlAdd_CreateHelp, __func__, MetisCommandReturn_Success); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_Add); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_AddConnection.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_AddConnection.c new file mode 100644 index 00000000..c98ab268 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_AddConnection.c @@ -0,0 +1,473 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metisControl_AddConnection.c" +#include "testrig_MetisControl.c" + +LONGBOW_TEST_RUNNER(metisControl_AddConnection) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metisControl_AddConnection) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_AddConnection) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlAddConnection_Create); + LONGBOW_RUN_TEST_CASE(Global, metisControlAddConnection_HelpCreate); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisControlAddConnection_Create) +{ + testCommandCreate(testCase, &metisControlAddConnection_Create, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlAddConnection_HelpCreate) +{ + testCommandCreate(testCase, &metisControlAddConnection_HelpCreate, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_EtherCreate); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_EtherExecute); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_McastCreate); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_McastExecute); + + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_TcpCreate); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_TcpExecute); + + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_UdpCreate); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_UdpExecute); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_Execute); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_Init); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_ConvertStringsToCpiAddress); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_CreateTunnel); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_EtherHelpCreate); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_EtherHelpExecute); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_McastHelpCreate); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_McastHelpExecute); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_TcpHelpCreate); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_TcpHelpExecute); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_UdpHelpCreate); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_UdpHelpExecute); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_HelpExecute); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddConnection_IpHelp); + + LONGBOW_RUN_TEST_CASE(Local, metisControl_ParseIPCommandLine_TooFewArgs); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ParseIPCommandLine_TooManyArgs); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ParseIPCommandLine_BadRemoteIp); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ParseIPCommandLine_GoodRemoteIp); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ParseIPCommandLine_WithLocalIp); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ParseIPCommandLine_WithLocalIpAndPort); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ParseIPCommandLine_BadLocalIp); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ParseIPCommandLine_MismatchLocalAndRemote); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_EtherCreate) +{ + testCommandCreate(testCase, &_metisControlAddConnection_EtherCreate, __func__); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_EtherExecute) +{ + const char *argv[] = { "add", "connection", "ether", "conn3", "e8-06-88-cd-28-de", "em3" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = _metisControlAddConnection_EtherCreate(data->state); + MetisCommandReturn result = ops->execute(data->state->parser, ops, args); + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + assertTrue(result == MetisCommandReturn_Success, "Valid command line should succeed"); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_McastCreate) +{ + testCommandCreate(testCase, &_metisControlAddConnection_McastCreate, __func__); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_McastExecute) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = _metisControlAddConnection_McastCreate(data->state); + MetisCommandReturn result = ops->execute(data->state->parser, ops, NULL); + metisCommandOps_Destroy(&ops); + assertTrue(result == MetisCommandReturn_Failure, "Unimplemented execute should have failed"); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_TcpCreate) +{ + testCommandCreate(testCase, &_metisControlAddConnection_TcpCreate, __func__); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_TcpExecute) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + const char *argv[] = { "add", "connection", "tcp", "conn3", "1.2.3.4", "123" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandOps *ops = _metisControlAddConnection_TcpCreate(data->state); + MetisCommandReturn result = ops->execute(data->state->parser, ops, args); + + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + + assertTrue(result == MetisCommandReturn_Success, "Unimplemented execute should have failed"); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_UdpCreate) +{ + testCommandCreate(testCase, &_metisControlAddConnection_UdpCreate, __func__); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_UdpExecute) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + const char *argv[] = { "add", "connection", "tcp", "conn3", "1.2.3.4", "123" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandOps *ops = _metisControlAddConnection_UdpCreate(data->state); + MetisCommandReturn result = ops->execute(data->state->parser, ops, args); + + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + + assertTrue(result == MetisCommandReturn_Success, "Unimplemented execute should have failed"); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_Execute) +{ + // this just prints a Help message + testHelpExecute(testCase, metisControlAddConnection_Create, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_Init) +{ + testInit(testCase, metisControlAddConnection_Create, __func__, + (const char *[]) { + _commandAddConnectionTcp, _commandAddConnectionUdp, _commandAddConnectionEther, _commandAddConnectionMcast, + _commandAddConnectionTcpHelp, _commandAddConnectionUdpHelp, _commandAddConnectionEtherHelp, _commandAddConnectionMcastHelp, + NULL + }); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_ConvertStringsToCpiAddress) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_CreateTunnel) +{ + // this is actully testred in the Tcp_Execute and Udp_Execute + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_EtherHelpCreate) +{ + testCommandCreate(testCase, &_metisControlAddConnection_EtherHelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_EtherHelpExecute) +{ + testHelpExecute(testCase, _metisControlAddConnection_EtherHelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_McastHelpCreate) +{ + testCommandCreate(testCase, &_metisControlAddConnection_McastHelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_McastHelpExecute) +{ + testHelpExecute(testCase, _metisControlAddConnection_McastHelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_TcpHelpCreate) +{ + testCommandCreate(testCase, &_metisControlAddConnection_TcpHelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_TcpHelpExecute) +{ + testHelpExecute(testCase, _metisControlAddConnection_TcpHelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_UdpHelpCreate) +{ + testCommandCreate(testCase, &_metisControlAddConnection_UdpHelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_UdpHelpExecute) +{ + testHelpExecute(testCase, _metisControlAddConnection_UdpHelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_HelpExecute) +{ + testHelpExecute(testCase, metisControlAddConnection_HelpCreate, __func__, MetisCommandReturn_Success); +} + +/** + * Expectes 5 to 7 options + */ +LONGBOW_TEST_CASE(Local, metisControl_ParseIPCommandLine_TooFewArgs) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + const char *argv[] = { "a", "b", "c" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 3, (void **) &argv[0]); + + MetisCommandOps *ops = _metisControlAddConnection_TcpCreate(data->state); + + CPIAddress *remote; + CPIAddress *local; + char *symbolic = NULL; + + MetisCommandReturn result = _metisControlAddConnection_ParseIPCommandLine(data->state->parser, ops, args, &remote, &local, &symbolic); + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + + assertTrue(result == MetisCommandReturn_Failure, "ParseIPCommandLine with 3 args should have returned %d, got %d", MetisCommandReturn_Failure, result); +} + +/** + * Expects 5 to 7 options + */ +LONGBOW_TEST_CASE(Local, metisControl_ParseIPCommandLine_TooManyArgs) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + const char *argv[] = { "a", "b", "c", "d", "e", "f", "g", "h", "i" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 9, (void **) &argv[0]); + + MetisCommandOps *ops = _metisControlAddConnection_TcpCreate(data->state); + + CPIAddress *remote; + CPIAddress *local; + char *symbolic = NULL; + + MetisCommandReturn result = _metisControlAddConnection_ParseIPCommandLine(data->state->parser, ops, args, &remote, &local, &symbolic); + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + + assertTrue(result == MetisCommandReturn_Failure, "ParseIPCommandLine with 3 args should have returned %d, got %d", MetisCommandReturn_Failure, result); +} + +LONGBOW_TEST_CASE(Local, metisControl_ParseIPCommandLine_BadRemoteIp) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + const char *argv[] = { "a", "b", "c", "tun0", "555.555.555.555", "123", }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandOps *ops = _metisControlAddConnection_TcpCreate(data->state); + + CPIAddress *remote; + CPIAddress *local; + char *symbolic = NULL; + + MetisCommandReturn result = _metisControlAddConnection_ParseIPCommandLine(data->state->parser, ops, args, &remote, &local, &symbolic); + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + + assertTrue(result == MetisCommandReturn_Failure, "ParseIPCommandLine with invalid IP address should have returned %d, got %d", MetisCommandReturn_Failure, result); +} + +/** + * Pass a set of args to metisControl_ParseIPCommandLine, then verify: + * Successful + * remote_ip is what we gave it + * remote_port is what we gave it + * local_ip is 0.0.0.0 or what we gave it + * local_pot is 0 or what we gave it. + */ +static void +verifyParseIpWithGoodAddress(TestData *data, int argc, const char *remote_ip, const char *remote_port, const char *local_ip, const char *local_port) +{ + const char *argv[] = { "a", "b", "c", "tun0", remote_ip, remote_port, local_ip, local_port }; + + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, argc, (void **) &argv[0]); + + MetisCommandOps *ops = _metisControlAddConnection_TcpCreate(data->state); + + CPIAddress *remote; + CPIAddress *local; + char *symbolic = NULL; + + MetisCommandReturn result = _metisControlAddConnection_ParseIPCommandLine(data->state->parser, ops, args, &remote, &local, &symbolic); + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + + assertTrue(result == MetisCommandReturn_Success, "ParseIPCommandLine with invalid IP address should have returned %d, got %d", MetisCommandReturn_Failure, result); + + struct sockaddr *sockaddr_remote = parcNetwork_SockAddress(remote_ip, atoi(remote_port)); + struct sockaddr *sockaddr_local = parcNetwork_SockAddress(local_ip, atoi(local_port)); + CPIAddress *truth_remote = cpiAddress_CreateFromInet((struct sockaddr_in *) sockaddr_remote); + CPIAddress *truth_local = cpiAddress_CreateFromInet((struct sockaddr_in *) sockaddr_local); + parcMemory_Deallocate((void **) &sockaddr_local); + parcMemory_Deallocate((void **) &sockaddr_remote); + + assertTrue(cpiAddress_Equals(truth_remote, remote), "Got wrong remote address"); + assertTrue(cpiAddress_Equals(truth_local, local), "Got wrong local address"); + cpiAddress_Destroy(&truth_remote); + cpiAddress_Destroy(&truth_local); + cpiAddress_Destroy(&remote); + cpiAddress_Destroy(&local); +} + +LONGBOW_TEST_CASE(Local, metisControl_ParseIPCommandLine_GoodRemoteIp) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + verifyParseIpWithGoodAddress(data, 6, "1.2.3.4", "123", "0.0.0.0", "0"); +} + +LONGBOW_TEST_CASE(Local, metisControl_ParseIPCommandLine_WithLocalIp) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + verifyParseIpWithGoodAddress(data, 7, "1.2.3.4", "123", "10.11.12.13", "0"); +} + +LONGBOW_TEST_CASE(Local, metisControl_ParseIPCommandLine_WithLocalIpAndPort) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + verifyParseIpWithGoodAddress(data, 8, "1.2.3.4", "123", "10.11.12.13", "456"); +} + +LONGBOW_TEST_CASE(Local, metisControl_ParseIPCommandLine_BadLocalIp) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + const char *argv[] = { "a", "b", "c", "tun0", "1.2.3.4", "123", "666.666.666.666", "123", }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 8, (void **) &argv[0]); + + MetisCommandOps *ops = _metisControlAddConnection_TcpCreate(data->state); + + CPIAddress *remote; + CPIAddress *local; + char *symbolic = NULL; + + MetisCommandReturn result = _metisControlAddConnection_ParseIPCommandLine(data->state->parser, ops, args, &remote, &local, &symbolic); + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + + assertTrue(result == MetisCommandReturn_Failure, "ParseIPCommandLine with invalid local IP address should have returned %d, got %d", MetisCommandReturn_Failure, result); +} + +/** + * One's an IPv4 and one's an IPv6. + */ +LONGBOW_TEST_CASE(Local, metisControl_ParseIPCommandLine_MismatchLocalAndRemote) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + const char *argv[] = { "a", "b", "c", "tun0", "1.2.3.4", "123", "2001:720:1500:1::a100", "123", }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 8, (void **) &argv[0]); + + MetisCommandOps *ops = _metisControlAddConnection_TcpCreate(data->state); + + CPIAddress *remote; + CPIAddress *local; + char *symbolic = NULL; + + MetisCommandReturn result = _metisControlAddConnection_ParseIPCommandLine(data->state->parser, ops, args, &remote, &local, &symbolic); + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + + assertTrue(result == MetisCommandReturn_Failure, "ParseIPCommandLine with invalid local IP address should have returned %d, got %d", MetisCommandReturn_Failure, result); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddConnection_IpHelp) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = _metisControlAddConnection_McastHelpCreate(data->state); + MetisCommandReturn result = _metisControlAddConnection_IpHelp(NULL, ops, NULL, "WIZARD"); + assertTrue(result == MetisCommandReturn_Success, "Wrong return, got %d expected %d", result, MetisCommandReturn_Success); + metisCommandOps_Destroy(&ops); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_AddConnection); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_AddListener.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_AddListener.c new file mode 100644 index 00000000..b5319aa4 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_AddListener.c @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metisControl_AddListener.c" +#include "testrig_MetisControl.c" + +LONGBOW_TEST_RUNNER(metisControl_AddListener) +{ + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metisControl_AddListener) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_AddListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlAddListener_Create); + LONGBOW_RUN_TEST_CASE(Global, metisControlAddListener_HelpCreate); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisControlAddListener_Create) +{ + testCommandCreate(testCase, &metisControlAddListener_Create, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlAddListener_HelpCreate) +{ + testCommandCreate(testCase, &metisControlAddListener_HelpCreate, __func__); +} + +// =========================================================== + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddListener_Execute_WrongArgCount); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddListener_Execute_Tcp); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddListener_Execute_Udp); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddListener_Execute_Udp6); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddListener_Execute_Ether); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddListener_Execute_UnknownProtocol); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddListener_Execute_BadSymbolic); + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddListener_Execute_BadSymbolic_NotAlphaNum); + + LONGBOW_RUN_TEST_CASE(Local, _metisControlAddListener_HelpExecute); + LONGBOW_RUN_TEST_CASE(Local, _createTcpListener); + LONGBOW_RUN_TEST_CASE(Local, _createUdpListener); + LONGBOW_RUN_TEST_CASE(Local, _createEtherListener); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, _createTcpListener) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = metisControlAddListener_Create(data->state); + + const char *argv[] = { "add", "listener", "tcp", "public0", "13.14.15.16", "9596", }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandReturn test = _createTcpListener(data->state->parser, ops, args); + + assertTrue(test == MetisCommandReturn_Success, "Command did not return success: %d", test); + assertTrue(data->writeread_count == 1, "Wrong write/read count, expected %d got %u", 1, data->writeread_count); + + parcList_Release(&args); + ops->destroyer(&ops); +} + +LONGBOW_TEST_CASE(Local, _createUdpListener) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = metisControlAddListener_Create(data->state); + + const char *argv[] = { "add", "listener", "udp", "public0", "13.14.15.16", "9596", }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandReturn test = _createUdpListener(data->state->parser, ops, args); + + assertTrue(test == MetisCommandReturn_Success, "Command did not return success: %d", test); + assertTrue(data->writeread_count == 1, "Wrong write/read count, expected %d got %u", 1, data->writeread_count); + + parcList_Release(&args); + ops->destroyer(&ops); +} + +LONGBOW_TEST_CASE(Local, _createEtherListener) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = metisControlAddListener_Create(data->state); + + const char *argv[] = { "add", "listener", "ether", "nic3", "eth3", "0x0801", }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandReturn test = _createEtherListener(data->state->parser, ops, args); + + assertTrue(test == MetisCommandReturn_Success, "Command did not return success: %d", test); + assertTrue(data->writeread_count == 1, "Wrong write/read count, expected %d got %u", 1, data->writeread_count); + + parcList_Release(&args); + ops->destroyer(&ops); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddListener_Execute_WrongArgCount) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = metisControlAddListener_Create(data->state); + + const char *argv[] = { "add", "listener", "ether" "nic3", "eth3", "0x0801", "foobar" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 7, (void **) &argv[0]); + + MetisCommandReturn test = _metisControlAddListener_Execute(data->state->parser, ops, args); + + assertTrue(test == MetisCommandReturn_Failure, "Command did not return failure: %d", test); + assertTrue(data->writeread_count == 0, "Wrong write/read count, expected %d got %u", 0, data->writeread_count); + + parcList_Release(&args); + ops->destroyer(&ops); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddListener_Execute_Tcp) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = metisControlAddListener_Create(data->state); + metisControlState_SetDebug(data->state, true); + + const char *argv[] = { "add", "listener", "tcp", "public0", "13.14.15.16", "9596", }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandReturn test = _metisControlAddListener_Execute(data->state->parser, ops, args); + + assertTrue(test == MetisCommandReturn_Success, "Command did not return success: %d", test); + assertTrue(data->writeread_count == 1, "Wrong write/read count, expected %d got %u", 1, data->writeread_count); + + parcList_Release(&args); + ops->destroyer(&ops); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddListener_Execute_Udp) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = metisControlAddListener_Create(data->state); + metisControlState_SetDebug(data->state, true); + + const char *argv[] = { "add", "listener", "udp", "public0", "13.14.15.16", "9596", }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandReturn test = _metisControlAddListener_Execute(data->state->parser, ops, args); + + assertTrue(test == MetisCommandReturn_Success, "Command did not return success: %d", test); + assertTrue(data->writeread_count == 1, "Wrong write/read count, expected %d got %u", 1, data->writeread_count); + + parcList_Release(&args); + ops->destroyer(&ops); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddListener_Execute_Udp6) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = metisControlAddListener_Create(data->state); + metisControlState_SetDebug(data->state, true); + + // INET6 address + const char *argv[] = { "add", "listener", "udp", "public0", "::1", "9596", }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandReturn test = _metisControlAddListener_Execute(data->state->parser, ops, args); + + assertTrue(test == MetisCommandReturn_Success, "Command did not return success: %d", test); + assertTrue(data->writeread_count == 1, "Wrong write/read count, expected %d got %u", 1, data->writeread_count); + + parcList_Release(&args); + ops->destroyer(&ops); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddListener_Execute_Ether) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = metisControlAddListener_Create(data->state); + + const char *argv[] = { "add", "listener", "ether", "nic3", "eth3", "0x0801", }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandReturn test = _metisControlAddListener_Execute(data->state->parser, ops, args); + + assertTrue(test == MetisCommandReturn_Success, "Command did not return success: %d", test); + assertTrue(data->writeread_count == 1, "Wrong write/read count, expected %d got %u", 1, data->writeread_count); + + parcList_Release(&args); + ops->destroyer(&ops); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddListener_Execute_UnknownProtocol) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = metisControlAddListener_Create(data->state); + + const char *argv[] = { "add", "listener", "pup", "nic3", "eth3", "0x0801" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandReturn test = _metisControlAddListener_Execute(data->state->parser, ops, args); + + assertTrue(test == MetisCommandReturn_Failure, "Command did not return failure: %d", test); + assertTrue(data->writeread_count == 0, "Wrong write/read count, expected %d got %u", 0, data->writeread_count); + + parcList_Release(&args); + ops->destroyer(&ops); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddListener_Execute_BadSymbolic) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = metisControlAddListener_Create(data->state); + + const char *argv[] = { "add", "listener", "ether" "111", "eth3", "0x0801" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandReturn test = _metisControlAddListener_Execute(data->state->parser, ops, args); + + assertTrue(test == MetisCommandReturn_Failure, "Command did not return failure: %d", test); + assertTrue(data->writeread_count == 0, "Wrong write/read count, expected %d got %u", 0, data->writeread_count); + + parcList_Release(&args); + ops->destroyer(&ops); +} + + +LONGBOW_TEST_CASE(Local, _metisControlAddListener_Execute_BadSymbolic_NotAlphaNum) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = metisControlAddListener_Create(data->state); + + const char *argv[] = { "add", "listener", "ether", "n()t", "eth3", "0x0801" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 6, (void **) &argv[0]); + + MetisCommandReturn test = _metisControlAddListener_Execute(data->state->parser, ops, args); + + assertTrue(test == MetisCommandReturn_Failure, "Command did not return failure: %d", test); + assertTrue(data->writeread_count == 0, "Wrong write/read count, expected %d got %u", 0, data->writeread_count); + + parcList_Release(&args); + ops->destroyer(&ops); +} + +LONGBOW_TEST_CASE(Local, _metisControlAddListener_HelpExecute) +{ + _metisControlAddListener_HelpExecute(NULL, NULL, NULL); +} + +// =========================================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_AddListener); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_AddRoute.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_AddRoute.c new file mode 100644 index 00000000..4a57dd64 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_AddRoute.c @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metisControl_AddRoute.c" +#include "testrig_MetisControl.c" + +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_AddRoute) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metisControl_AddRoute) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_AddRoute) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlAddRoute_Create); + LONGBOW_RUN_TEST_CASE(Global, metisControlAddRoute_HelpCreate); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisControlAddRoute_Create) +{ + testCommandCreate(testCase, &metisControlAddRoute_Create, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlAddRoute_HelpCreate) +{ + testCommandCreate(testCase, &metisControlAddRoute_HelpCreate, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_AddRoute_Execute_WrongArgCount); + LONGBOW_RUN_TEST_CASE(Local, metisControl_AddRoute_Execute_ZeroCost); + LONGBOW_RUN_TEST_CASE(Local, metisControl_AddRoute_Execute_BadPrefix); + LONGBOW_RUN_TEST_CASE(Local, metisControl_AddRoute_Execute_Good); + + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_AddRoute_Execute); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +static MetisCommandReturn +testAddRoute(const LongBowTestCase *testCase, int argc, const char *prefix, const char *nexthop, const char *cost) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + metisControlState_SetDebug(data->state, true); + + const char *argv[] = { "add", "route", nexthop, prefix, cost }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, argc, (void **) &argv[0]); + + MetisCommandOps *ops = metisControlAddRoute_Create(data->state); + + MetisCommandReturn result = ops->execute(data->state->parser, ops, args); + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + return result; +} + +LONGBOW_TEST_CASE(Local, metisControl_AddRoute_Execute_WrongArgCount) +{ + // argc is wrong, needs to be 5. + MetisCommandReturn result = testAddRoute(testCase, 2, "lci:/foo", "703", "1"); + + assertTrue(result == MetisCommandReturn_Failure, + "metisControl_AddRoute with wrong argc should return %d, got %d", MetisCommandReturn_Failure, result); +} + +LONGBOW_TEST_CASE(Local, metisControl_AddRoute_Execute_ZeroCost) +{ + MetisCommandReturn result = testAddRoute(testCase, 5, "lci:/foo", "702", "0"); + + assertTrue(result == MetisCommandReturn_Failure, + "metisControl_AddRoute with zero cost should return %d, got %d", MetisCommandReturn_Failure, result); +} + +LONGBOW_TEST_CASE(Local, metisControl_AddRoute_Execute_BadPrefix) +{ + MetisCommandReturn result = testAddRoute(testCase, 5, "blah", "701", "1"); + + assertTrue(result == MetisCommandReturn_Failure, + "metisControl_AddRoute with zero cost should return %d, got %d", MetisCommandReturn_Failure, result); +} + +LONGBOW_TEST_CASE(Local, metisControl_AddRoute_Execute_Good) +{ + MetisCommandReturn result = testAddRoute(testCase, 5, "lci:/foo", "700", "1"); + + assertTrue(result == MetisCommandReturn_Success, + "metisControl_AddRoute should return %d, got %d", MetisCommandReturn_Success, result); +} + +LONGBOW_TEST_CASE(Local, metisControl_Help_AddRoute_Execute) +{ + testHelpExecute(testCase, metisControlAddRoute_HelpCreate, __func__, MetisCommandReturn_Success); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_AddRoute); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_List.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_List.c new file mode 100644 index 00000000..1d8f347f --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_List.c @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metisControl_List.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_List) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metisControl_List) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_List) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlList_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlList_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisControlList_HelpCreate) +{ + testCommandCreate(testCase, &metisControlList_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlList_Create) +{ + testCommandCreate(testCase, &metisControlList_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_List_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_List_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_List_Init); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, metisControl_Help_List_Execute) +{ + testHelpExecute(testCase, metisControlList_HelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_List_Execute) +{ + // this just prints the Help function + testHelpExecute(testCase, metisControlList_Create, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_List_Init) +{ + testInit(testCase, metisControlList_Create, __func__, + (const char *[]) { + "list connections", "list interfaces", "list routes", + "help list connections", "help list interfaces", "help list routes", + NULL + }); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_List); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_ListConnections.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_ListConnections.c new file mode 100644 index 00000000..56d691e1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_ListConnections.c @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metisControl_ListConnections.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_ListConnections) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metisControl_ListConnections) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_ListConnections) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlListConnections_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlListConnections_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisControlListConnections_HelpCreate) +{ + testCommandCreate(testCase, &metisControlListConnections_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlListConnections_Create) +{ + testCommandCreate(testCase, &metisControlListConnections_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_ListConnections_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ListConnections_Execute_WrongArgCount); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ListConnections_Execute_Good); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, metisControl_Help_ListConnections_Execute) +{ + testHelpExecute(testCase, &metisControlListConnections_HelpCreate, __func__, MetisCommandReturn_Success); +} + +static CCNxControl * +customWriteReadResponse(void *userdata, CCNxMetaMessage *messageToWrite) +{ + CPIConnectionList *connlist = cpiConnectionList_Create(); + CPIConnection *conn = cpiConnection_Create(1, cpiAddress_CreateFromInterface(1), cpiAddress_CreateFromInterface(2), cpiConnection_L2); + cpiConnectionList_Append(connlist, conn); + + PARCJSON *connectionListAsJson = cpiConnectionList_ToJson(connlist); + + CCNxControl *inboundControlMessage = ccnxMetaMessage_GetControl(messageToWrite); + + // Create a response to the inbound Control message. + CCNxControl *outboundControlMessage = cpi_CreateResponse(inboundControlMessage, connectionListAsJson); + parcJSON_Release(&connectionListAsJson); + + ccnxControl_Release(&inboundControlMessage); + + cpiConnectionList_Destroy(&connlist); + + return outboundControlMessage; +} + +static MetisCommandReturn +testListConnections(const LongBowTestCase *testCase, int argc) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + metisControlState_SetDebug(data->state, true); + data->customWriteReadReply = &customWriteReadResponse; + + const char *argv[] = { "list", "interfaces" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, argc, (void **) &argv[0]); + + MetisCommandOps *ops = metisControlListConnections_Create(data->state); + + MetisCommandReturn result = ops->execute(data->state->parser, ops, args); + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + return result; +} + +LONGBOW_TEST_CASE(Local, metisControl_ListConnections_Execute_WrongArgCount) +{ + // argc is wrong, needs to be 2. + MetisCommandReturn result = testListConnections(testCase, 3); + + assertTrue(result == MetisCommandReturn_Failure, + "metisControl_ListConnections with wrong argc should return %d, got %d", MetisCommandReturn_Failure, result); +} + +LONGBOW_TEST_CASE(Local, metisControl_ListConnections_Execute_Good) +{ + MetisCommandReturn result = testListConnections(testCase, 2); + + assertTrue(result == MetisCommandReturn_Success, + "metisControl_ListConnections should return %d, got %d", MetisCommandReturn_Success, result); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_ListConnections); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_ListInterfaces.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_ListInterfaces.c new file mode 100644 index 00000000..0e1e13d2 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_ListInterfaces.c @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metisControl_ListInterfaces.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_ListInterfaces) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metisControl_ListInterfaces) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_ListInterfaces) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlListInterfaces_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlListInterfaces_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisControlListInterfaces_HelpCreate) +{ + testCommandCreate(testCase, &metisControlListInterfaces_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlListInterfaces_Create) +{ + testCommandCreate(testCase, &metisControlListInterfaces_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_ListInterfaces_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ListInterfaces_Execute_WrongArgCount); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ListInterfaces_Execute_Good); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, metisControl_Help_ListInterfaces_Execute) +{ + testHelpExecute(testCase, &metisControlListInterfaces_HelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_ListInterfaces_Execute) +{ + testUnimplemented(""); +} + +static CCNxControl * +customWriteReadResponse(void *userdata, CCNxMetaMessage *messageToWrite) +{ + CPIInterfaceSet *set = cpiInterfaceSet_Create(); + cpiInterfaceSet_Add(set, cpiInterface_Create("abc0", 1, false, true, 1500)); + cpiInterfaceSet_Add(set, cpiInterface_Create("abc1", 2, false, true, 1500)); + PARCJSON *setJson = cpiInterfaceSet_ToJson(set); + + CCNxControl *inboundControlMessage = ccnxMetaMessage_GetControl(messageToWrite); + + // Create a response to the inbound Control message. + CCNxControl *outboundControlMessage = cpi_CreateResponse(inboundControlMessage, setJson); + parcJSON_Release(&setJson); + + ccnxControl_Release(&inboundControlMessage); + cpiInterfaceSet_Destroy(&set); + + return outboundControlMessage; +} + +static MetisCommandReturn +testListInterfaces(const LongBowTestCase *testCase, int argc) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + metisControlState_SetDebug(data->state, true); + data->customWriteReadReply = &customWriteReadResponse; + + const char *argv[] = { "list", "connections" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, argc, (void **) &argv[0]); + + MetisCommandOps *ops = metisControlListInterfaces_Create(data->state); + + MetisCommandReturn result = ops->execute(data->state->parser, ops, args); + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + return result; +} + +LONGBOW_TEST_CASE(Local, metisControl_ListInterfaces_Execute_WrongArgCount) +{ + // argc is wrong, needs to be 2. + MetisCommandReturn result = testListInterfaces(testCase, 3); + + assertTrue(result == MetisCommandReturn_Failure, + "metisControl_ListInterfaces with wrong argc should return %d, got %d", MetisCommandReturn_Failure, result); +} + +LONGBOW_TEST_CASE(Local, metisControl_ListInterfaces_Execute_Good) +{ + MetisCommandReturn result = testListInterfaces(testCase, 2); + + assertTrue(result == MetisCommandReturn_Success, + "metisControl_ListInterfaces should return %d, got %d", MetisCommandReturn_Success, result); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_ListInterfaces); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_ListRoutes.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_ListRoutes.c new file mode 100644 index 00000000..ca71f93f --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_ListRoutes.c @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metisControl_ListRoutes.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_ListRoutes) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metisControl_ListRoutes) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_ListRoutes) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlListRoutes_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlListRoutes_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisControlListRoutes_HelpCreate) +{ + testCommandCreate(testCase, &metisControlListRoutes_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlListRoutes_Create) +{ + testCommandCreate(testCase, &metisControlListRoutes_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_ListRoutes_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ListRoutes_Execute_WrongArgCount); + LONGBOW_RUN_TEST_CASE(Local, metisControl_ListRoutes_Execute_Good); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, metisControl_Help_ListRoutes_Execute) +{ + testHelpExecute(testCase, &metisControlListRoutes_HelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_ListRoutes_Execute) +{ + testUnimplemented(""); +} + +static CCNxControl * +customWriteReadResponse(void *userdata, CCNxMetaMessage *messageToWrite) +{ + CPIRouteEntryList *routeEntryList = cpiRouteEntryList_Create(); + CPIAddress *nexthop = cpiAddress_CreateFromInterface(10); + CPIRouteEntry *route = cpiRouteEntry_Create(ccnxName_CreateFromCString("lci:/foo"), + 1, + nexthop, + cpiNameRouteProtocolType_STATIC, + cpiNameRouteType_LONGEST_MATCH, + &((struct timeval) { 100, 0 }), // lifetime + 1); // cost + + cpiRouteEntryList_Append(routeEntryList, route); + PARCJSON *setJson = cpiRouteEntryList_ToJson(routeEntryList); + + CCNxControl *inboundControlMessage = ccnxMetaMessage_GetControl(messageToWrite); + + // Create a response to the inbound Control message. + CCNxControl *outboundControlMessage = cpi_CreateResponse(inboundControlMessage, setJson); + parcJSON_Release(&setJson); + + ccnxControl_Release(&inboundControlMessage); + + cpiAddress_Destroy(&nexthop); + cpiRouteEntryList_Destroy(&routeEntryList); + + return outboundControlMessage; +} + +static MetisCommandReturn +testListRoutes(const LongBowTestCase *testCase, int argc) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + metisControlState_SetDebug(data->state, true); + data->customWriteReadReply = &customWriteReadResponse; + + const char *argv[] = { "list", "connections" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, argc, (void **) &argv[0]); + + MetisCommandOps *ops = metisControlListRoutes_Create(data->state); + + MetisCommandReturn result = ops->execute(data->state->parser, ops, args); + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + return result; +} + +LONGBOW_TEST_CASE(Local, metisControl_ListRoutes_Execute_WrongArgCount) +{ + // argc is wrong, needs to be 2. + MetisCommandReturn result = testListRoutes(testCase, 3); + + assertTrue(result == MetisCommandReturn_Failure, + "metisControl_ListRoutes with wrong argc should return %d, got %d", MetisCommandReturn_Failure, result); +} + +LONGBOW_TEST_CASE(Local, metisControl_ListRoutes_Execute_Good) +{ + MetisCommandReturn result = testListRoutes(testCase, 2); + + assertTrue(result == MetisCommandReturn_Success, + "metisControl_ListRoutes should return %d, got %d", MetisCommandReturn_Success, result); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_ListRoutes); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_Quit.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_Quit.c new file mode 100644 index 00000000..192bdf86 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_Quit.c @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metisControl_Quit.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_Quit) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metisControl_Quit) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_Quit) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlQuit_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlQuit_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisControlQuit_HelpCreate) +{ + testCommandCreate(testCase, metisControlQuit_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlQuit_Create) +{ + testCommandCreate(testCase, metisControlQuit_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_Quit_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_Quit_Execute); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, metisControl_Help_Quit_Execute) +{ + testHelpExecute(testCase, metisControlQuit_HelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_Quit_Execute) +{ + // This only displays the Help message + testHelpExecute(testCase, metisControlQuit_Create, __func__, MetisCommandReturn_Exit); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_Quit); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_Remove.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_Remove.c new file mode 100644 index 00000000..b5878797 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_Remove.c @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metisControl_Remove.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_Remove) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metisControl_Remove) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_Remove) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlRemove_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlRemove_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisControlRemove_HelpCreate) +{ + testCommandCreate(testCase, metisControlRemove_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlRemove_Create) +{ + testCommandCreate(testCase, metisControlRemove_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_Remove_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_Remove_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_Remove_Init); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, metisControl_Help_Remove_Execute) +{ + testHelpExecute(testCase, metisControlRemove_HelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_Remove_Execute) +{ + // this only displays the help menu + testHelpExecute(testCase, metisControlRemove_Create, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_Remove_Init) +{ + testInit(testCase, metisControlRemove_Create, __func__, + (const char *[]) { + "remove connection", "remove route", + "help remove connection", "help remove route", + NULL + }); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_Remove); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_RemoveConnection.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_RemoveConnection.c new file mode 100644 index 00000000..76340b77 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_RemoveConnection.c @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metisControl_RemoveConnection.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_RemoveConnection) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metisControl_RemoveConnection) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_RemoveConnection) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlRemoveConnection_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlRemoveConnection_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisControlRemoveConnection_HelpCreate) +{ + testCommandCreate(testCase, &metisControlRemoveConnection_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlRemoveConnection_Create) +{ + testCommandCreate(testCase, &metisControlRemoveConnection_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_RemoveConnection_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_RemoveConnection_Execute); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, metisControl_Help_RemoveConnection_Execute) +{ + testHelpExecute(testCase, &metisControlRemoveConnection_HelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_RemoveConnection_Execute) +{ + testHelpExecute(testCase, &metisControlRemoveConnection_Create, __func__, MetisCommandReturn_Success); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_RemoveConnection); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_RemoveRoute.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_RemoveRoute.c new file mode 100644 index 00000000..636d04b5 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_RemoveRoute.c @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metisControl_RemoveRoute.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_RemoveRoute) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metisControl_RemoveRoute) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_RemoveRoute) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlRemoveRoute_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlRemoveRoute_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisControlRemoveRoute_HelpCreate) +{ + testCommandCreate(testCase, &metisControlRemoveRoute_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlRemoveRoute_Create) +{ + testCommandCreate(testCase, &metisControlRemoveRoute_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_RemoveRoute_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_RemoveRoute_Execute); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, metisControl_Help_RemoveRoute_Execute) +{ + testHelpExecute(testCase, &metisControlRemoveRoute_HelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_RemoveRoute_Execute) +{ + testUnimplemented(""); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_RemoveRoute); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_Root.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_Root.c new file mode 100644 index 00000000..3adf736c --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_Root.c @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metisControl_Root.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_Root) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metisControl_Root) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_Root) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlRoot_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlRoot_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisControlRoot_HelpCreate) +{ + testCommandCreate(testCase, &metisControlRoot_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlRoot_Create) +{ + testCommandCreate(testCase, &metisControlRoot_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_Root_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_Root_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_Root_Init); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, metisControl_Help_Root_Execute) +{ + testHelpExecute(testCase, &metisControlRoot_HelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_Root_Execute) +{ + // this command only displays Help + testHelpExecute(testCase, &metisControlRoot_Create, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_Root_Init) +{ + testInit(testCase, &metisControlRoot_Create, __func__, + (const char *[]) { + "add", "list", "quit", "remove", "set", "unset", + "help add", "help list", "help quit", "help remove", "help set", "help unset", + NULL + }); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_Root); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_Set.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_Set.c new file mode 100644 index 00000000..b224e49f --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_Set.c @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metisControl_Set.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_Set) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metisControl_Set) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_Set) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlSet_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlSet_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisControlSet_HelpCreate) +{ + testCommandCreate(testCase, &metisControlSet_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlSet_Create) +{ + testCommandCreate(testCase, &metisControlSet_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_Set_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_Set_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_Set_Init); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, metisControl_Help_Set_Execute) +{ + testHelpExecute(testCase, &metisControlSet_HelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_Set_Execute) +{ + // Only prints a help menu + testHelpExecute(testCase, &metisControlSet_Create, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_Set_Init) +{ + testInit(testCase, &metisControlSet_Create, __func__, + (const char *[]) { + "set debug", + "help set debug", + NULL + }); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_Set); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_SetDebug.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_SetDebug.c new file mode 100644 index 00000000..5f7f4805 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_SetDebug.c @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metisControl_SetDebug.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_SetDebug) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metisControl_SetDebug) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_SetDebug) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlSetDebug_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlSetDebug_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisControlSetDebug_HelpCreate) +{ + testCommandCreate(testCase, &metisControlSetDebug_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlSetDebug_Create) +{ + testCommandCreate(testCase, &metisControlSetDebug_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_SetDebug_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_SetDebug_Execute_WrongArgCount); + LONGBOW_RUN_TEST_CASE(Local, metisControl_SetDebug_Execute_Good); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, metisControl_Help_SetDebug_Execute) +{ + testHelpExecute(testCase, &metisControlSetDebug_HelpCreate, __func__, MetisCommandReturn_Success); +} + +static MetisCommandReturn +testDebug(const LongBowTestCase *testCase, MetisCommandOps * (*create)(MetisControlState * state), int argc, bool initialDebugSetting, bool expectedDebugSetting) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + const char *argv[] = { "blah", "blah" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, argc, (void **) &argv[0]); + + metisControlState_SetDebug(data->state, initialDebugSetting); + MetisCommandOps *ops = create(data->state); + MetisCommandReturn result = ops->execute(data->state->parser, ops, args); + if (result == MetisCommandReturn_Success) { + assertTrue(data->state->debugFlag == expectedDebugSetting, + "Debug flag wrong, expected %d got %d", + expectedDebugSetting, + data->state->debugFlag); + } + + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + return result; +} + +LONGBOW_TEST_CASE(Local, metisControl_SetDebug_Execute_WrongArgCount) +{ + MetisCommandReturn result = testDebug(testCase, metisControlSetDebug_Create, 3, false, true); + assertTrue(result == MetisCommandReturn_Failure, + "metisControl_SetDebug_Execute should return %d, got %d", MetisCommandReturn_Failure, result); +} + + +LONGBOW_TEST_CASE(Local, metisControl_SetDebug_Execute_Good) +{ + MetisCommandReturn result = testDebug(testCase, metisControlSetDebug_Create, 2, false, true); + assertTrue(result == MetisCommandReturn_Success, + "metisControl_SetDebug_Execute should return %d, got %d", MetisCommandReturn_Success, result); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_SetDebug); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_Unset.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_Unset.c new file mode 100644 index 00000000..f7e27daf --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_Unset.c @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metisControl_Unset.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_Unset) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metisControl_Unset) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_Unset) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlUnset_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlUnset_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisControlUnset_HelpCreate) +{ + testCommandCreate(testCase, &metisControlUnset_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlUnset_Create) +{ + testCommandCreate(testCase, &metisControlUnset_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_Unset_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_Unset_Init); + LONGBOW_RUN_TEST_CASE(Local, metisControl_Unset_Execute); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, metisControl_Help_Unset_Execute) +{ + testHelpExecute(testCase, &metisControlUnset_HelpCreate, __func__, MetisCommandReturn_Success); +} + +LONGBOW_TEST_CASE(Local, metisControl_Unset_Init) +{ + testInit(testCase, &metisControlUnset_Create, __func__, + (const char *[]) { + "unset debug", + "help unset debug", + NULL + }); +} + +LONGBOW_TEST_CASE(Local, metisControl_Unset_Execute) +{ + // Only prints a help menu + testHelpExecute(testCase, &metisControlUnset_Create, __func__, MetisCommandReturn_Success); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_Unset); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metisControl_UnsetDebug.c b/metis/ccnx/forwarder/metis/config/test/test_metisControl_UnsetDebug.c new file mode 100644 index 00000000..bb31b917 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metisControl_UnsetDebug.c @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metisControl_UnsetDebug.c" +#include "testrig_MetisControl.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metisControl_UnsetDebug) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metisControl_UnsetDebug) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metisControl_UnsetDebug) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlUnsetDebug_HelpCreate); + LONGBOW_RUN_TEST_CASE(Global, metisControlUnsetDebug_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisControlUnsetDebug_HelpCreate) +{ + testCommandCreate(testCase, &metisControlUnsetDebug_HelpCreate, __func__); +} + +LONGBOW_TEST_CASE(Global, metisControlUnsetDebug_Create) +{ + testCommandCreate(testCase, &metisControlUnsetDebug_Create, __func__); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisControl_Help_UnsetDebug_Execute); + LONGBOW_RUN_TEST_CASE(Local, metisControl_UnsetDebug_Execute_WrongArgCount); + LONGBOW_RUN_TEST_CASE(Local, metisControl_UnsetDebug_Execute_Good); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + testrigMetisControl_commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + testrigMetisControl_CommonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, metisControl_Help_UnsetDebug_Execute) +{ + testHelpExecute(testCase, &metisControlUnsetDebug_HelpCreate, __func__, MetisCommandReturn_Success); +} + +static MetisCommandReturn +testDebug(const LongBowTestCase *testCase, MetisCommandOps * (*create)(MetisControlState * state), int argc, bool initialDebugSetting, bool expectedDebugSetting) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + const char *argv[] = { "blah", "blah" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, argc, (void **) &argv[0]); + + metisControlState_SetDebug(data->state, initialDebugSetting); + MetisCommandOps *ops = create(data->state); + MetisCommandReturn result = ops->execute(data->state->parser, ops, args); + if (result == MetisCommandReturn_Success) { + assertTrue(data->state->debugFlag == expectedDebugSetting, + "Debug flag wrong, expected %d got %d", + expectedDebugSetting, + data->state->debugFlag); + } + + metisCommandOps_Destroy(&ops); + parcList_Release(&args); + return result; +} + +LONGBOW_TEST_CASE(Local, metisControl_UnsetDebug_Execute_WrongArgCount) +{ + MetisCommandReturn result = testDebug(testCase, metisControlUnsetDebug_Create, 3, true, false); + assertTrue(result == MetisCommandReturn_Failure, + "metisControl_UnsetDebug_Execute should return %d, got %d", MetisCommandReturn_Failure, result); +} + + +LONGBOW_TEST_CASE(Local, metisControl_UnsetDebug_Execute_Good) +{ + MetisCommandReturn result = testDebug(testCase, metisControlUnsetDebug_Create, 2, true, false); + assertTrue(result == MetisCommandReturn_Success, + "metisControl_UnsetDebug_Execute should return %d, got %d", MetisCommandReturn_Success, result); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metisControl_UnsetDebug); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metis_CommandLineInterface.c b/metis/ccnx/forwarder/metis/config/test/test_metis_CommandLineInterface.c new file mode 100644 index 00000000..fe8e911c --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metis_CommandLineInterface.c @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../metis_CommandLineInterface.c" + +#include <errno.h> +#include <string.h> + +#include <LongBow/unit-test.h> +#include <LongBow/debugging.h> + +#include <parc/algol/parc_SafeMemory.h> + +LONGBOW_TEST_RUNNER(test_metis_CommandLineInterface) +{ +// The following Test Fixtures will run their corresponding Test Cases. +// Test Fixtures are run in the order specified, but all tests should be idempotent. +// Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(test_metis_CommandLineInterface) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(test_metis_CommandLineInterface) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + //LONGBOW_RUN_TEST_CASE(Global, myTest); + LONGBOW_RUN_TEST_CASE(Global, Version); +} + +typedef struct test_state { + MetisForwarder *metis; + MetisDispatcher *dispatcher; + MetisCommandLineInterface *cli; + + int clientFd; +} TestState; + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + TestState *state = malloc(sizeof(TestState)); + + state->metis = metisForwarder_Create(NULL); + state->dispatcher = metisForwarder_GetDispatcher(state->metis); + +// we create our own CLI, because the one built in to metisForwarder is not started +// until the forwarder is running. + + state->cli = metisCommandLineInterface_Create(state->metis, 2001); + metisCommandLineInterface_Start(state->cli); + + metisDispatcher_RunCount(state->dispatcher, 1); + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = PF_INET; + addr.sin_port = htons(2001); + inet_pton(AF_INET, "127.0.0.1", &(addr.sin_addr)); + + state->clientFd = socket(PF_INET, SOCK_STREAM, 0); + assertFalse(state->clientFd < 0, "Error on socket: (%d) %s", errno, strerror(errno)); + + int failure = connect(state->clientFd, (struct sockaddr *) &addr, sizeof(addr)); + assertFalse(failure, "Error on connect: (%d) %s", errno, strerror(errno)); + +// crank the handle once + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(state->metis), &((struct timeval) { 0, 1000 })); + + longBowTestCase_SetClipBoardData(testCase, state); + + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + TestState *state = longBowTestCase_GetClipBoardData(testCase); + + close(state->clientFd); + metisCommandLineInterface_Destroy(&state->cli); + metisForwarder_Destroy(&state->metis); + free(state); + + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +/** + * The CLI has a secret command "~~" (two of them) that will echo back whatever the next + * words are. The string "~~ hello world" would echo back "success: hello world" followed by + * the next command prompt. This lets us test that the 1st level of parsing is working. It + * differentiates "~~" as the command and the rest of the string as parameters. + */ +LONGBOW_TEST_CASE(Global, myTest) +{ + TestState *state = longBowTestCase_GetClipBoardData(testCase); + + + char readbuffer[1024]; + +// Skipover the MOTD + ssize_t nread = read(state->clientFd, readbuffer, 1024); + assertTrue(nread > -1, "Error read"); + printf("read:\n%s\n", readbuffer); + + // send special command "~~" followed by a string. It should be repeated back + // as "success: see no hands\nmetis> ", where the stuff after the \n is the next command prompt + char magic[] = "~~ see no hands\r\n"; + ssize_t nwritten = write(state->clientFd, magic, sizeof(magic)); + assertTrue(nwritten == sizeof(magic), "Error write, expected %zu got %zd", sizeof(magic), nwritten); + + metisDispatcher_RunDuration(state->dispatcher, &((struct timeval) { 0, 1000 })); + + memset(readbuffer, 0, 1024); + nread = read(state->clientFd, readbuffer, 1024); + assertTrue(nread > -1, "Error read"); + + // we look for the answer without the "\nmetis> " part. + char answer[] = "success: see no hands"; + assertTrue(strncasecmp(readbuffer, answer, sizeof(answer) - 1) == 0, "Got wrong string: %s", readbuffer); +} + +LONGBOW_TEST_CASE(Global, Version) +{ + TestState *state = longBowTestCase_GetClipBoardData(testCase); + + char readbuffer[1024]; + + // Skipover the MOTD + ssize_t nread = read(state->clientFd, readbuffer, 1024); + assertTrue(nread > -1, "Error read"); + + printf("read:\n%s\n", readbuffer); + + // send special command "~~" followed by a string. It should be repeated back + // as "success: see no hands\nmetis> ", where the stuff after the \n is the next command prompt + char magic[] = "ver\r\n"; + ssize_t nwritten = write(state->clientFd, magic, sizeof(magic)); + assertTrue(nwritten == sizeof(magic), "Error write, expected %zu got %zd", sizeof(magic), nwritten); + + metisDispatcher_RunDuration(state->dispatcher, &((struct timeval) { 0, 1000 })); + + memset(readbuffer, 0, 1024); + nread = read(state->clientFd, readbuffer, 1024); + assertTrue(nread > -1, "Error read"); + + printf("%s", readbuffer); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(test_metis_CommandLineInterface); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metis_CommandOps.c b/metis/ccnx/forwarder/metis/config/test/test_metis_CommandOps.c new file mode 100644 index 00000000..4d6a5c13 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metis_CommandOps.c @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_CommandOps.c" + +#include <inttypes.h> +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +LONGBOW_TEST_RUNNER(metis_CommandOps) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_CommandOps) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_CommandOps) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisCommandOps_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +static void +_init(struct metis_command_parser *parser, MetisCommandOps *ops) +{ +} + +static MetisCommandReturn +_execute(struct metis_command_parser *parser, MetisCommandOps *ops, PARCList *args) +{ + return MetisCommandReturn_Success; +} + +static void +_destroyer(MetisCommandOps **opsPtr) +{ +} + +LONGBOW_TEST_CASE(Global, metisCommandOps_Create) +{ + char hello[] = "hello"; + char command[] = "test"; + + MetisCommandOps *ops = metisCommandOps_Create(hello, command, _init, _execute, _destroyer); + + assertTrue(ops->closure == hello, "closure wrong expected %p got %p", (void *) hello, (void *) ops->closure); + assertTrue(strcmp(ops->command, command) == 0, "command wrong expected '%s' got '%s'", command, ops->command); + assertTrue(ops->init == _init, "Wrong init, expected %" PRIXPTR " got %" PRIXPTR, (uintptr_t) _init, (uintptr_t) ops->init); + assertTrue(ops->execute == _execute, "Wrong execute, expected %" PRIXPTR " got %" PRIXPTR, (uintptr_t) _execute, (uintptr_t) ops->execute); + assertTrue(ops->destroyer == _destroyer, "Wrong destroyer, expected %" PRIXPTR " got %" PRIXPTR, (uintptr_t) _destroyer, (uintptr_t) ops->destroyer); + + metisCommandOps_Destroy(&ops); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_CommandOps); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metis_CommandParser.c b/metis/ccnx/forwarder/metis/config/test/test_metis_CommandParser.c new file mode 100644 index 00000000..b7e6edae --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metis_CommandParser.c @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_CommandParser.c" +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metis_CommandParser) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_CommandParser) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_CommandParser) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisCommandParser_Create_Destroy); + + LONGBOW_RUN_TEST_CASE(Global, metisCommandParser_DispatchCommand_Exact); + LONGBOW_RUN_TEST_CASE(Global, metisCommandParser_DispatchCommand_Longer); + LONGBOW_RUN_TEST_CASE(Global, metisCommandParser_DispatchCommand_Shorter); + LONGBOW_RUN_TEST_CASE(Global, metisCommandParser_DispatchCommand_Sibling); + LONGBOW_RUN_TEST_CASE(Global, metisCommandParser_GetDebug); + LONGBOW_RUN_TEST_CASE(Global, metisCommandParser_Interactive); + LONGBOW_RUN_TEST_CASE(Global, metisCommandParser_RegisterCommand_NullInit); + LONGBOW_RUN_TEST_CASE(Global, metisCommandParser_RegisterCommand_WithInit); + LONGBOW_RUN_TEST_CASE(Global, metisCommandParser_SetDebug); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisCommandParser_Create_Destroy) +{ + MetisCommandParser *parser = metisCommandParser_Create(); + assertNotNull(parser, "Got null parser from metisCommandParser_Create"); + metisCommandParser_Destroy(&parser); + assertTrue(parcSafeMemory_ReportAllocation(STDOUT_FILENO) == 0, "Memory imbalance!"); + assertNull(parser, "metisCommandParser_Destroy did not null pointer"); +} + +static MetisCommandReturn +test_execute(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + bool *execute_called_ptr = (bool *) ops->closure; + *execute_called_ptr = true; + return MetisCommandReturn_Success; +} + +/** + * argc = the exact number of args, don't include the command name + * example: argc = 2, argv = {"Hello", "World"} + * + * expectedResult true means the execute function is called + */ +static void +dispatchCommand(const char *command_string, int argc, char **argv, bool expectedResult) +{ + MetisCommandParser *parser = metisCommandParser_Create(); + + bool execute_called = false; + + MetisCommandOps *ops = metisCommandOps_Create(&execute_called, command_string, NULL, test_execute, metisCommandOps_Destroy); + + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, argc, (void **) &argv[0]); + + execute_called = false; + metisCommandParser_RegisterCommand(parser, ops); + metisCommandParser_DispatchCommand(parser, args); + if (expectedResult) { + assertTrue(execute_called, "Did not call the execute function"); + } else { + assertFalse(execute_called, "The execute function should not have been called but was"); + } + + metisCommandParser_Destroy(&parser); + parcList_Release(&args); +} + +LONGBOW_TEST_CASE(Global, metisCommandParser_DispatchCommand_Exact) +{ + // note that it is not case sensitive + dispatchCommand("hello world", 2, (char *[]) { "Hello", "World" }, true); +} + +LONGBOW_TEST_CASE(Global, metisCommandParser_DispatchCommand_Sibling) +{ + // note that it is not case sensitive + dispatchCommand("hello world", 2, (char *[]) { "Hello", "Universe" }, false); +} + + +LONGBOW_TEST_CASE(Global, metisCommandParser_DispatchCommand_Longer) +{ + // note that it is not case sensitive + dispatchCommand("hello world", 3, (char *[]) { "Hello", "World", "Again" }, true); +} + +LONGBOW_TEST_CASE(Global, metisCommandParser_DispatchCommand_Shorter) +{ + // note that it is not case sensitive + dispatchCommand("hello world", 1, (char *[]) { "Hello" }, false); +} + +LONGBOW_TEST_CASE(Global, metisCommandParser_GetDebug) +{ + MetisCommandParser *parser = metisCommandParser_Create(); + bool test = metisCommandParser_GetDebug(parser); + assertTrue(test == parser->debugFlag, "Got %d expected %d", test, parser->debugFlag); + metisCommandParser_Destroy(&parser); +} + +LONGBOW_TEST_CASE(Global, metisCommandParser_Interactive) +{ + testUnimplemented(""); +} + +static bool called_init = false; +static void +test_init_command(MetisCommandParser *parser, MetisCommandOps *ops) +{ + called_init = true; +} + +LONGBOW_TEST_CASE(Global, metisCommandParser_RegisterCommand_WithInit) +{ + MetisCommandParser *parser = metisCommandParser_Create(); + + MetisCommandOps *ops = metisCommandOps_Create(NULL, "hello world", test_init_command, test_execute, metisCommandOps_Destroy); + + called_init = false; + metisCommandParser_RegisterCommand(parser, ops); + + MetisCommandOps *test = parcTreeRedBlack_Get(parser->commandTree, ops->command); + assertNotNull(test, "Got null looking up command in tree"); + assertTrue(test == ops, "Wrong pointer, got %p expected %p", (void *) test, (void *) ops); + assertTrue(called_init, "Did not call the init function"); + + metisCommandParser_Destroy(&parser); +} + +LONGBOW_TEST_CASE(Global, metisCommandParser_RegisterCommand_NullInit) +{ + MetisCommandParser *parser = metisCommandParser_Create(); + + MetisCommandOps command = { + .command = "hello world", + .init = NULL, + .execute = NULL + }; + + called_init = false; + metisCommandParser_RegisterCommand(parser, &command); + + MetisCommandOps *test = parcTreeRedBlack_Get(parser->commandTree, command.command); + assertNotNull(test, "Got null looking up command in tree"); + assertTrue(test == &command, "Wrong pointer, got %p expected %p", (void *) test, (void *) &command); + assertFalse(called_init, "Somehow called the init function"); + + metisCommandParser_Destroy(&parser); +} + +LONGBOW_TEST_CASE(Global, metisCommandParser_SetDebug) +{ + MetisCommandParser *parser = metisCommandParser_Create(); + // flip the setting + bool truth = ~parser->debugFlag; + metisCommandParser_SetDebug(parser, truth); + assertTrue(truth == parser->debugFlag, "Got %d expected %d", parser->debugFlag, truth); + metisCommandParser_Destroy(&parser); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisCommandParser_MatchCommand); + LONGBOW_RUN_TEST_CASE(Local, parseStringIntoTokens); + LONGBOW_RUN_TEST_CASE(Local, stringCompare); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, metisCommandParser_MatchCommand) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, parseStringIntoTokens) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, stringCompare) +{ + testUnimplemented(""); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_CommandParser); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metis_Configuration.c b/metis/ccnx/forwarder/metis/config/test/test_metis_Configuration.c new file mode 100644 index 00000000..09b9a576 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metis_Configuration.c @@ -0,0 +1,779 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Some of these tests might not execute on certain systems, as they + * depend on having INET and INET6 addresses available. If you system + * does not have one or both of those, the corresponding tests will not + * execute. + */ + +// We need to specifically include the Ethernet mockup and set the proper define so +// we do not need an actual Ethernet listener + +#define METIS_MOCK_ETHERNET 1 +#include "../../io/test/testrig_GenericEther.c" + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_Configuration.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +#include <signal.h> + +// so we can mock up an interface +#include "../../core/test/testrig_MetisIoOperations.h" + +// so we can test content store size +#include "../../core/metis_Forwarder.c" +#include "../../processor/metis_MessageProcessor.c" +#include "../../content_store/metis_ContentStoreInterface.h" + +struct sigaction save_sigchld; +struct sigaction save_sigpipe; + +/** + * Add a connection to the connection table to mock the "ingress" port of a control message + * + * You must release the return value + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval non-null A mockup of a connection + * @retval null An error + * + * Example: + * @code + * { + * unsigned mockConnectionId = 7; + * MetisIoOperations *ops = _addIngressMockConnection(metis, mockConnectionId); + * MockIoOperationsData *data = ops->context; + * mockIoOperationsData_Destroy(&ops); + * } + * @endcode + */ +static MetisIoOperations * +_addIngressMockConnection(MetisForwarder *metis, unsigned mockup_id) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, mockup_id, true, true, true); + + MetisConnection *conn = metisConnection_Create(ops); + MetisConnectionTable *connTable = metisForwarder_GetConnectionTable(metis); + metisConnectionTable_Add(connTable, conn); + return ops; +} + +// returns a strdup() of the interface name, use free(3) +static char * +_pickInterfaceName(MetisForwarder *metis) +{ + char *ifname = NULL; + + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + size_t length = cpiInterfaceSet_Length(set); + assertTrue(length > 0, "metisSystem_Interfaces returned no interfaces"); + + for (size_t i = 0; i < length; i++) { + CPIInterface *iface = cpiInterfaceSet_GetByOrdinalIndex(set, i); + const CPIAddressList *addressList = cpiInterface_GetAddresses(iface); + + size_t length = cpiAddressList_Length(addressList); + for (size_t i = 0; i < length && !ifname; i++) { + const CPIAddress *a = cpiAddressList_GetItem(addressList, i); + if (cpiAddress_GetType(a) == cpiAddressType_LINK) { + ifname = strdup(cpiInterface_GetName(iface)); + } + } + } + + cpiInterfaceSet_Destroy(&set); + return ifname; +} + +/** + * Adds a mock ethernet connection to the given peer address with a symbolic name. + * You must have previously added an Ethernet listener. + * + * @return true Added + * @return false An error + */ +static bool +_addEthernetConnection(MetisForwarder *metis, unsigned connid, const char *symbolicName, MetisListenerOps *listener, uint8_t peerEther[6]) +{ + // Create a CPIConnectionEthernet Add control message + char *ifname = _pickInterfaceName(metis); + + uint16_t etherType = 0x0801; + + CPIAddress *peerAddress = cpiAddress_CreateFromLink(peerEther, 6); + CPIConnectionEthernet *etherConn = cpiConnectionEthernet_Create(ifname, peerAddress, etherType, symbolicName); + bool success = _metisConfiguration_AddConnectionEthernet(metisForwarder_GetConfiguration(metis), etherConn, peerAddress, listener); + + + cpiAddress_Destroy(&peerAddress); + free(ifname); + cpiConnectionEthernet_Release(ðerConn); + + return success; +} + +// ========================================================================= + +LONGBOW_TEST_RUNNER(metis_Configuration) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + printf("line 140\n"); + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_Configuration) +{ + printf("line 148\n"); + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_Configuration) +{ + printf("line 156\n"); + return LONGBOW_STATUS_SUCCEEDED; +} + +// ============================================================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisConfiguration_SetupAllListeners); + LONGBOW_RUN_TEST_CASE(Global, metisConfiguration_Receive); + LONGBOW_RUN_TEST_CASE(Global, metisConfiguration_SetObjectStoreSize); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisConfiguration_SetupAllListeners) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Global, metisConfiguration_Receive) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + // Add a connection to apply the route to + unsigned mockConnectionId = 7000; + MetisIoOperations *ops = _addIngressMockConnection(metis, mockConnectionId); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + CCNxName *prefix = ccnxName_CreateFromCString("lci:/foo"); + CPIRouteEntry *routeEntry = cpiRouteEntry_Create(prefix, mockConnectionId, NULL, + cpiNameRouteProtocolType_STATIC, + cpiNameRouteType_LONGEST_MATCH, NULL, 4); + CCNxControl *request = ccnxControl_CreateAddRouteRequest(routeEntry); + cpiRouteEntry_Destroy(&routeEntry); + + PARCBuffer *buffer = metisTlv_EncodeControlPlaneInformation(request); + + MetisMessage *message = metisMessage_CreateFromArray(parcBuffer_Overlay(buffer, 0), parcBuffer_Limit(buffer), mockConnectionId, 2, metisForwarder_GetLogger(metis)); + parcBuffer_Release(&buffer); + + // this takes ownership of message and disposes of it + metisConfiguration_Receive(metisForwarder_GetConfiguration(metis), message); + + // crank the handle to lets the ACKs or NACKs move + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + + assertTrue(data->sendCount == 1, "Did not send a response message, expected 1 got %u", data->sendCount); + CCNxControl *response = metisMessage_CreateControlMessage(data->lastMessage); + + assertTrue(cpi_GetMessageType(response) == CPI_ACK, + "CPI message not a response: %s", + parcJSON_ToString(ccnxControl_GetJson(response))); + + ccnxControl_Release(&response); + ccnxControl_Release(&request); + metisForwarder_Destroy(&metis); + + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisConfiguration_SetObjectStoreSize) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + MetisContentStoreInterface *store = metis->processor->contentStore; + MetisConfiguration *config = metisForwarder_GetConfiguration(metis); + size_t current_capacity = metisContentStoreInterface_GetObjectCapacity(store); + size_t new_capacity = current_capacity + 5; + + metisConfiguration_SetObjectStoreSize(config, new_capacity); + + // Get the store pointer again, as it may have changed. + store = metis->processor->contentStore; + assertTrue(new_capacity == metisContentStoreInterface_GetObjectCapacity(store), + "Object Store is wrong capacity, got %zu expected %zu", + metisContentStoreInterface_GetObjectCapacity(store), new_capacity); + + metisForwarder_Destroy(&metis); +} + +// ============================================================================== + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_Receive_AddConnectionEthernet); + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_Receive_AddConnectionEthernet_Dup); + + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_ProcessUnregisterPrefix); + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_ProcessRegisterPrefix); + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_ProcessRegisterPrefix_Symbolic); + + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_ProcessInterfaceList); + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_ProcessRegistrationList); + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_ProcessCreateTunnel_Dup); + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_ProcessCreateTunnel_TCP); + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_ProcessCreateTunnel_UDP); + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_ProcessConnectionList); + + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_ProcessAddConnectionEthernet); + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_ProcessRemoveConnectionEthernet); + + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_Receive_AddConnectionEthernet); + LONGBOW_RUN_TEST_CASE(Local, metisConfiguration_Receive_RemoveConnectionEthernet); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, metisConfiguration_ProcessInterfaceList) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + CCNxControl *request = ccnxControl_CreateInterfaceListRequest(); + + unsigned mockConnectionId = 7; + + CCNxControl *response = metisConfiguration_ProcessInterfaceList(metisForwarder_GetConfiguration(metis), request, mockConnectionId); + + assertNotNull(response, "Got null response"); + + assertTrue(cpi_GetMessageType(response) == CPI_RESPONSE, + "CPI message not a response: %s", + parcJSON_ToString(ccnxControl_GetJson(response))); + + assertTrue(cpi_GetMessageOperation(response) == CPI_INTERFACE_LIST, + "CPI message not an interface list: %s", + parcJSON_ToString(ccnxControl_GetJson(response))); + + ccnxControl_Release(&response); + ccnxControl_Release(&request); + metisForwarder_Destroy(&metis); +} + +LONGBOW_TEST_CASE(Local, metisConfiguration_ProcessUnregisterPrefix) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, metisConfiguration_ProcessRegisterPrefix) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + // Add a connection to apply the route to + unsigned mockConnectionId = 7000; + + CCNxName *prefix = ccnxName_CreateFromCString("lci:/foo"); + CPIRouteEntry *routeEntry = cpiRouteEntry_Create(prefix, mockConnectionId, NULL, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, NULL, 4); + CCNxControl *request = ccnxControl_CreateAddRouteRequest(routeEntry); + cpiRouteEntry_Destroy(&routeEntry); + + CCNxControl *response = metisConfiguration_ProcessRegisterPrefix(metisForwarder_GetConfiguration(metis), request, mockConnectionId); + + // crank the handle to lets the ACKs or NACKs move + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + + assertNotNull(response, "LastMessage is not set in the test rig"); + + assertTrue(cpi_GetMessageType(response) == CPI_ACK, + "CPI message not a response: %s", + parcJSON_ToString(ccnxControl_GetJson(response))); + + ccnxControl_Release(&response); + ccnxControl_Release(&request); + metisForwarder_Destroy(&metis); +} + +LONGBOW_TEST_CASE(Local, metisConfiguration_ProcessRegisterPrefix_Symbolic) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + // Add a connection to apply the route to + unsigned mockConnectionId = 7000; + + // hack in the symbolic name because _addIngressMockConnection does not do that + metisSymbolicNameTable_Add(metisForwarder_GetConfiguration(metis)->symbolicNameTable, "foo0", mockConnectionId); + + CCNxName *prefix = ccnxName_CreateFromCString("lci:/foo"); + CPIRouteEntry *routeEntry = cpiRouteEntry_CreateSymbolic(prefix, "foo0", cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, NULL, 4); + CCNxControl *request = ccnxControl_CreateAddRouteRequest(routeEntry); + cpiRouteEntry_Destroy(&routeEntry); + + CCNxControl *response = metisConfiguration_ProcessRegisterPrefix(metisForwarder_GetConfiguration(metis), request, mockConnectionId); + + // crank the handle to lets the ACKs or NACKs move + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + + assertNotNull(response, "Response is NULL"); + + assertTrue(cpi_GetMessageType(response) == CPI_ACK, + "CPI message not a response: %s", + parcJSON_ToString(ccnxControl_GetJson(response))); + + ccnxControl_Release(&response); + ccnxControl_Release(&request); + metisForwarder_Destroy(&metis); +} + +/** + * Add a route, then verify the route shows up in a list + */ +LONGBOW_TEST_CASE(Local, metisConfiguration_ProcessRegistrationList) +{ + printf("\n%s starting\n", __func__); + + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 7; + + // Add a route to the forwarding table + CCNxName *prefix = ccnxName_CreateFromCString("lci:/pancakes/for/all"); + CPIRouteEntry *route = cpiRouteEntry_Create(prefix, 3, NULL, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, NULL, 2); + metisForwarder_AddOrUpdateRoute(metis, route); + cpiRouteEntry_Destroy(&route); + + // Create a request and send it in to MetisConfiguration. The response will be + // sent out the "mockup_id" interface + + CCNxControl *request = ccnxControl_CreateRouteListRequest(); + MetisConfiguration *config = metisForwarder_GetConfiguration(metis); + CCNxControl *response = metisConfiguration_ProcessRegistrationList(config, request, mockup_id); + + assertNotNull(response, "Got null response"); + + assertTrue(cpi_GetMessageType(response) == CPI_RESPONSE, + "CPI message not a response: %s", + parcJSON_ToString(ccnxControl_GetJson(response))); + + assertTrue(cpi_GetMessageOperation(response) == CPI_PREFIX_REGISTRATION_LIST, + "CPI message not an interface list: %s", + parcJSON_ToString(ccnxControl_GetJson(response))); + + ccnxControl_Release(&response); + ccnxControl_Release(&request); + metisForwarder_Destroy(&metis); +} + +LONGBOW_TEST_CASE(Local, metisConfiguration_ProcessCreateTunnel_TCP) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 7; + + // ----- + // Issue a command to create a TCP tunnel. We should be able to verify that it's in + // the connection table and we'll see the ACK come back to our mock interface. + + // --------------------------- + // Tunnel addresses + struct sockaddr_in sockaddr_any; + memset(&sockaddr_any, 0, sizeof(sockaddr_any)); + sockaddr_any.sin_family = PF_INET; + sockaddr_any.sin_addr.s_addr = INADDR_ANY; + + CPIAddress *source = cpiAddress_CreateFromInet(&sockaddr_any); + + struct sockaddr_in sockaddr_dst; + memset(&sockaddr_dst, 0, sizeof(sockaddr_dst)); + sockaddr_dst.sin_family = PF_INET; + sockaddr_dst.sin_port = htons(PORT_NUMBER); + inet_pton(AF_INET, "127.0.0.1", &(sockaddr_dst.sin_addr)); + + CPIAddress *destination = cpiAddress_CreateFromInet(&sockaddr_dst); + + // --------------------------- + + CPIInterfaceIPTunnel *iptun = cpiInterfaceIPTunnel_Create(0, source, destination, IPTUN_TCP, "tun0"); + CCNxControl *request = ccnxControl_CreateIPTunnelRequest(iptun); + + MetisConfiguration *config = metisForwarder_GetConfiguration(metis); + + CCNxControl *response = metisConfiguration_ProcessCreateTunnel(config, request, mockup_id); + + // crank the handle to lets the ACKs or NACKs move + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + + // Validate the ACK + assertNotNull(response, "Got null response"); + + assertTrue(cpi_GetMessageType(response) == CPI_ACK, + "CPI message not an ACK: %s", + parcJSON_ToString(ccnxControl_GetJson(response))); + + ccnxControl_Release(&response); + ccnxControl_Release(&request); + cpiInterfaceIPTunnel_Release(&iptun); + metisForwarder_Destroy(&metis); +} + +LONGBOW_TEST_CASE(Local, metisConfiguration_ProcessCreateTunnel_Dup) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 7000; + MetisIoOperations *ops = _addIngressMockConnection(metis, mockup_id); + + // --------------------------- + // Tunnel addresses + struct sockaddr_in sockaddr_any; + memset(&sockaddr_any, 0, sizeof(sockaddr_any)); + sockaddr_any.sin_family = PF_INET; + sockaddr_any.sin_addr.s_addr = INADDR_ANY; + + CPIAddress *source = cpiAddress_CreateFromInet(&sockaddr_any); + + struct sockaddr_in sockaddr_dst; + memset(&sockaddr_dst, 0, sizeof(sockaddr_dst)); + sockaddr_dst.sin_family = PF_INET; + sockaddr_dst.sin_port = htons(PORT_NUMBER); + inet_pton(AF_INET, "127.0.0.1", &(sockaddr_dst.sin_addr)); + + CPIAddress *destination = cpiAddress_CreateFromInet(&sockaddr_dst); + + // --------------------------- + + CPIInterfaceIPTunnel *iptun = cpiInterfaceIPTunnel_Create(0, source, destination, IPTUN_TCP, "tun0"); + CCNxControl *request = ccnxControl_CreateIPTunnelRequest(iptun); + + MetisConfiguration *config = metisForwarder_GetConfiguration(metis); + + CCNxControl *response_1 = metisConfiguration_ProcessCreateTunnel(config, request, mockup_id); + assertNotNull(response_1, "got null response"); + assertTrue(ccnxControl_IsACK(response_1), "Did not get ACK response for first tunnel"); + ccnxControl_Release(&response_1); + + CCNxControl *response_2 = metisConfiguration_ProcessCreateTunnel(config, request, mockup_id); + assertNotNull(response_2, "got null response"); + assertTrue(ccnxControl_IsNACK(response_2), "Did not get NACK response for second tunnel"); + + ccnxControl_Release(&response_2); + + ccnxControl_Release(&request); + cpiInterfaceIPTunnel_Release(&iptun); + metisForwarder_Destroy(&metis); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Local, metisConfiguration_ProcessCreateTunnel_UDP) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 7; + + // ----- + // Issue a command to create a UDP tunnel. We should be able to verify that it's in + // the connection table and we'll see the ACK come back to our mock interface. + + // --------------------------- + // Tunnel addresses + struct sockaddr_in sockaddr_any; + memset(&sockaddr_any, 0, sizeof(sockaddr_any)); + sockaddr_any.sin_family = PF_INET; + sockaddr_any.sin_addr.s_addr = INADDR_ANY; + + CPIAddress *source = cpiAddress_CreateFromInet(&sockaddr_any); + + struct sockaddr_in sockaddr_dst; + memset(&sockaddr_dst, 0, sizeof(sockaddr_dst)); + sockaddr_dst.sin_family = PF_INET; + sockaddr_dst.sin_port = htons(PORT_NUMBER); + inet_pton(AF_INET, "127.0.0.1", &(sockaddr_dst.sin_addr)); + + CPIAddress *destination = cpiAddress_CreateFromInet(&sockaddr_dst); + + // --------------------------- + + CPIInterfaceIPTunnel *iptun = cpiInterfaceIPTunnel_Create(0, source, destination, IPTUN_UDP, "conn0"); + CCNxControl *request = ccnxControl_CreateIPTunnelRequest(iptun); + + MetisConfiguration *config = metisForwarder_GetConfiguration(metis); + + CCNxControl *response = metisConfiguration_ProcessCreateTunnel(config, request, mockup_id); + + // Validate the ACK + assertNotNull(response, "Got null response"); + + assertTrue(cpi_GetMessageType(response) == CPI_ACK, + "CPI message not an ACK: %s", + parcJSON_ToString(ccnxControl_GetJson(response))); + + ccnxControl_Release(&response); + ccnxControl_Release(&request); + cpiInterfaceIPTunnel_Release(&iptun); + metisForwarder_Destroy(&metis); +} + + +LONGBOW_TEST_CASE(Local, metisConfiguration_ProcessConnectionList) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 7; + MetisIoOperations *ops = _addIngressMockConnection(metis, mockup_id); + + CCNxControl *request = ccnxControl_CreateConnectionListRequest(); + + MetisConfiguration *config = metisForwarder_GetConfiguration(metis); + CCNxControl *response = metisConfiguration_ProcessConnectionList(config, request, mockup_id); + + // Validate the response + assertNotNull(response, "Got null response"); + + // Get the CPI response out of the test mock up + CPIConnectionList *list = cpiLinks_ConnectionListFromControlMessage(response); + assertTrue(cpiConnectionList_Length(list) == 1, "Wrong list size, expected %u got %zu", 1, cpiConnectionList_Length(list)); + + ccnxControl_Release(&response); + ccnxControl_Release(&request); + cpiConnectionList_Destroy(&list); + metisForwarder_Destroy(&metis); + mockIoOperationsData_Destroy(&ops); +} + + +LONGBOW_TEST_CASE(Local, metisConfiguration_ProcessAddConnectionEthernet) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 77; + + // create the listener + char *ifname = _pickInterfaceName(metis); + CPIListener *cpiListener = cpiListener_CreateEther(ifname, 0x0801, "fake0"); + CCNxControl *control = cpiListener_CreateAddMessage(cpiListener); + bool listenerOk = metisConfigurationListeners_Add(metisForwarder_GetConfiguration(metis), control, mockup_id); + assertTrue(listenerOk, "Failed to setup ether listener."); + + ccnxControl_Release(&control); + cpiListener_Release(&cpiListener); + + // ======== + uint8_t peerEther[6] = { 0x02, 0x33, 0x44, 0x55, 0x66, 0x77 }; + CPIAddress *peerAddress = cpiAddress_CreateFromLink(peerEther, sizeof(peerEther)); + CPIConnectionEthernet *etherconn = cpiConnectionEthernet_Create(ifname, peerAddress, 0x0801, "conn3"); + CCNxControl *addRequest = cpiConnectionEthernet_CreateAddMessage(etherconn); + + MetisConfiguration *config = metisForwarder_GetConfiguration(metis); + + CCNxControl *response = metisConfiguration_ProcessAddConnectionEthernet(config, addRequest, mockup_id); + + // crank the handle to lets the ACKs or NACKs move + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + + // Get the CPI response out of the test mock up + assertNotNull(response, "Got null response"); + + assertTrue(ccnxControl_IsACK(response), "Response is not an ACK") + { + ccnxControl_Display(response, 3); + } + + // we must manually destroy a Mock connection + ccnxControl_Release(&response); + free(ifname); + cpiConnectionEthernet_Release(ðerconn); + cpiAddress_Destroy(&peerAddress); + ccnxControl_Release(&addRequest); + metisForwarder_Destroy(&metis); +} + +LONGBOW_TEST_CASE(Local, metisConfiguration_ProcessRemoveConnectionEthernet) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, metisConfiguration_Receive_AddConnectionEthernet) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 7; + MetisIoOperations *ops = _addIngressMockConnection(metis, mockup_id); + + // create the listener + char *ifname = _pickInterfaceName(metis); + CPIListener *cpiListener = cpiListener_CreateEther(ifname, 0x0801, "fake0"); + CCNxControl *control = cpiListener_CreateAddMessage(cpiListener); + bool listenerOk = metisConfigurationListeners_Add(metisForwarder_GetConfiguration(metis), control, mockup_id); + assertTrue(listenerOk, "Failed to setup ether listener."); + + ccnxControl_Release(&control); + cpiListener_Release(&cpiListener); + + // create the connection + uint8_t linkAddrArray[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 }; + uint16_t etherType = 0x0801; + + CPIAddress *peerAddress = cpiAddress_CreateFromLink(linkAddrArray, sizeof(linkAddrArray)); + CPIConnectionEthernet *etherconn = cpiConnectionEthernet_Create(ifname, peerAddress, etherType, "conn3"); + CCNxControl *addRequest = cpiConnectionEthernet_CreateAddMessage(etherconn); + + // Translate the control message to a MetisMessage + PARCBuffer *buffer = metisTlv_EncodeControlPlaneInformation(addRequest); + + MetisMessage *message = metisMessage_CreateFromArray(parcBuffer_Overlay(buffer, 0), parcBuffer_Limit(buffer), mockup_id, 2, metisForwarder_GetLogger(metis)); + + MetisConfiguration *config = metisForwarder_GetConfiguration(metis); + + // this will release the message + metisConfiguration_Receive(config, message); + + // ==== Verify it's in the connection table + + MetisConnectionList *connList = metisConnectionTable_GetEntries(metisForwarder_GetConnectionTable(metis)); + assertNotNull(connList, "Got null connection list"); + + bool found = false; + for (size_t i = 0; i < metisConnectionList_Length(connList) && !found; i++) { + MetisConnection *conn = metisConnectionList_Get(connList, i); + const MetisAddressPair *pair = metisConnection_GetAddressPair(conn); + const CPIAddress *remote = metisAddressPair_GetRemote(pair); + if (cpiAddress_Equals(remote, peerAddress)) { + found = true; + } + } + + assertTrue(found, "Could not find peer address in the connection table as a remote"); + + // ==== Cleanup + + parcBuffer_Release(&buffer); + ccnxControl_Release(&addRequest); + cpiConnectionEthernet_Release(ðerconn); + cpiAddress_Destroy(&peerAddress); + free(ifname); + metisConnectionList_Destroy(&connList); + metisForwarder_Destroy(&metis); + mockIoOperationsData_Destroy(&ops); +} + +/* + * Try to add a second connection with same symbolic name + */ +LONGBOW_TEST_CASE(Local, metisConfiguration_Receive_AddConnectionEthernet_Dup) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + uint8_t peerEther[6] = { 7, 8, 9, 10, 11, 12 }; + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 7000; + MetisIoOperations *ops = _addIngressMockConnection(metis, mockup_id); + + char *ifname = _pickInterfaceName(metis); + CPIListener *cpiListener = cpiListener_CreateEther(ifname, 0x0801, "fake0"); + CCNxControl *control = cpiListener_CreateAddMessage(cpiListener); + metisConfigurationListeners_Add(metisForwarder_GetConfiguration(metis), control, mockup_id); + ccnxControl_Release(&control); + cpiListener_Release(&cpiListener); + free(ifname); + + MetisListenerOps *listener = metisListenerSet_Get(metisForwarder_GetListenerSet(metis), 0); + + // Create a mock up of an interface so we can see the response + bool success = _addEthernetConnection(metis, 1000, "conn3", listener, peerEther); + assertTrue(success, "Failed to add first instance of connection"); + + // now add again, should fail + bool failure = _addEthernetConnection(metis, 1001, "conn3", listener, peerEther); + assertFalse(failure, "Should have failed to add it a second time"); + + metisForwarder_Destroy(&metis); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Local, metisConfiguration_Receive_RemoveConnectionEthernet) +{ +} + +// ====================================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_Configuration); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metis_ConfigurationFile.c b/metis/ccnx/forwarder/metis/config/test/test_metis_ConfigurationFile.c new file mode 100644 index 00000000..6c7e19df --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metis_ConfigurationFile.c @@ -0,0 +1,403 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include "../metis_ConfigurationFile.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <errno.h> +#include <string.h> + +#include <sys/types.h> +#include <sys/stat.h> + +// ========================================================================= + +LONGBOW_TEST_RUNNER(metis_ConfigurationFile) +{ + LONGBOW_RUN_TEST_FIXTURE(Create); + LONGBOW_RUN_TEST_FIXTURE(Process); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_ConfigurationFile) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_ConfigurationFile) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ============================================================================== + +LONGBOW_TEST_FIXTURE(Create) +{ + LONGBOW_RUN_TEST_CASE(Create, metisConfigurationFile_Create); + LONGBOW_RUN_TEST_CASE(Create, metisConfigurationFile_Create_CantRead); + LONGBOW_RUN_TEST_CASE(Create, metisConfigurationFile_Create_Missing); +} + +LONGBOW_TEST_FIXTURE_SETUP(Create) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Create) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +static void +_writeConfigFile(FILE *fh) +{ + ssize_t nwritten = fprintf(fh, "add listener udp conn0 127.0.0.1 9696\n"); + assertTrue(nwritten > 0, "Bad fprintf"); + fflush(fh); +} + +LONGBOW_TEST_CASE(Create, metisConfigurationFile_Create) +{ + char template[] = "/tmp/test_metis_ConfigurationFile.XXXXXX"; + int fd = mkstemp(template); + assertTrue(fd > -1, "Error creating temp file: (%d) %s", errno, strerror(errno)); + + FILE *fh = fdopen(fd, "w"); + _writeConfigFile(fh); + + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + MetisConfigurationFile *cf = metisConfigurationFile_Create(metis, template); + + assertNotNull(cf, "Should have returned non-null for good configuration file"); + + metisConfigurationFile_Release(&cf); + metisForwarder_Destroy(&metis); + fclose(fh); + unlink(template); +} + +LONGBOW_TEST_CASE(Create, metisConfigurationFile_Create_CantRead) +{ + char template[] = "/tmp/test_metis_ConfigurationFile.XXXXXX"; + int fd = mkstemp(template); + assertTrue(fd > -1, "Error creating temp file: (%d) %s", errno, strerror(errno)); + + chmod(template, 0); + + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + MetisConfigurationFile *cf = metisConfigurationFile_Create(metis, template); + + chmod(template, 0600); + unlink(template); + + uid_t uid = getuid(), euid = geteuid(); + if (uid <= 0 || uid != euid) { + metisConfigurationFile_Release(&cf); + } else { + assertNull(cf, "Should have returned null configuration file for non-readable file"); + } + + metisForwarder_Destroy(&metis); + close(fd); +} + +LONGBOW_TEST_CASE(Create, metisConfigurationFile_Create_Missing) +{ + char template[] = "/tmp/test_metis_ConfigurationFile.ZZZZZZZZZ"; + + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + MetisConfigurationFile *cf = metisConfigurationFile_Create(metis, template); + + assertNull(cf, "Should have returned null configuration file for missing file"); + + metisForwarder_Destroy(&metis); +} + +// ====================================================== + +typedef struct test_data { + MetisForwarder *metis; + char template[1024]; + int fd; + FILE *fh; +} TestData; + +LONGBOW_TEST_FIXTURE(Process) +{ + LONGBOW_RUN_TEST_CASE(Process, metisConfigurationFile_Process_NoErrors); + LONGBOW_RUN_TEST_CASE(Process, metisConfigurationFile_Process_WithErrors); + LONGBOW_RUN_TEST_CASE(Process, metisConfigurationFile_Process_WithComments); + LONGBOW_RUN_TEST_CASE(Process, metisConfigurationFile_Process_Whitespace); +} + +LONGBOW_TEST_FIXTURE_SETUP(Process) +{ + TestData *data = parcMemory_Allocate(sizeof(TestData)); + data->metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(data->metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + metisLogger_SetLogLevel(metisForwarder_GetLogger(data->metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + sprintf(data->template, "/tmp/test_metis_ConfigurationFile.XXXXXX"); + + data->fd = mkstemp(data->template); + assertTrue(data->fd > -1, "Error creating temp file: (%d) %s", errno, strerror(errno)); + + data->fh = fdopen(data->fd, "w"); + + longBowTestCase_SetClipBoardData(testCase, data); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Process) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + fclose(data->fh); + unlink(data->template); + metisForwarder_Destroy(&data->metis); + parcMemory_Deallocate((void **) &data); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Process, metisConfigurationFile_Process_NoErrors) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _writeConfigFile(data->fh); + + MetisConfigurationFile *cf = metisConfigurationFile_Create(data->metis, data->template); + + bool success = metisConfigurationFile_Process(cf); + assertTrue(success, "Failed to execute configuration file."); + assertTrue(cf->linesRead == 1, "Should have read 1 line, got %zu", cf->linesRead); + + metisConfigurationFile_Release(&cf); +} + +LONGBOW_TEST_CASE(Process, metisConfigurationFile_Process_WithErrors) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _writeConfigFile(data->fh); + + ssize_t nwritten = fprintf(data->fh, "blah blah\n"); + assertTrue(nwritten > 0, "Bad write"); + + // this should not be executed + nwritten = fprintf(data->fh, "add listener conn3 tcp 127.0.0.1 9696\n"); + assertTrue(nwritten > 0, "Bad write"); + + fflush(data->fh); + + MetisConfigurationFile *cf = metisConfigurationFile_Create(data->metis, data->template); + + bool success = metisConfigurationFile_Process(cf); + assertFalse(success, "Should have failed to execute configuration file.") { + int res; + res = system("netstat -an -p tcp"); + assertTrue(res != -1, "Error on system call"); + res = system("ps -el"); + assertTrue(res != -1, "Error on system call"); + } + assertTrue(cf->linesRead == 2, "Should have read 2 lines, got %zu", cf->linesRead); + + metisConfigurationFile_Release(&cf); +} + +LONGBOW_TEST_CASE(Process, metisConfigurationFile_Process_WithComments) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _writeConfigFile(data->fh); + + ssize_t nwritten = fprintf(data->fh, "# ignore this\n"); + assertTrue(nwritten > 0, "Bad write"); + + nwritten = fprintf(data->fh, "add listener tcp conn3 127.0.0.1 9696\n"); + assertTrue(nwritten > 0, "Bad write"); + + fflush(data->fh); + + MetisConfigurationFile *cf = metisConfigurationFile_Create(data->metis, data->template); + + bool success = metisConfigurationFile_Process(cf); + assertTrue(success, "Should have failed to execute configuration file.") { + int res; + res = system("netstat -an -p tcp"); + assertTrue(res != -1, "Error on system call"); + res = system("ps -el"); + assertTrue(res != -1, "Error on system call"); + } + assertTrue(cf->linesRead == 3, "Should have read 3 lines, got %zu", cf->linesRead); + + metisConfigurationFile_Release(&cf); +} + +LONGBOW_TEST_CASE(Process, metisConfigurationFile_Process_Whitespace) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _writeConfigFile(data->fh); + + ssize_t nwritten = fprintf(data->fh, "add listener tcp conn3 127.0.0.1 9696\n"); + assertTrue(nwritten > 0, "Bad write"); + + fflush(data->fh); + + MetisConfigurationFile *cf = metisConfigurationFile_Create(data->metis, data->template); + + bool success = metisConfigurationFile_Process(cf); + assertTrue(success, "Should have failed to execute configuration file.") { + int res; + res = system("netstat -an -p tcp"); + assertTrue(res != -1, "Error on system call"); + res = system("ps -el"); + assertTrue(res != -1, "Error on system call"); + } + assertTrue(cf->linesRead == 2, "Should have read 2 lines, got %zu", cf->linesRead); + + metisConfigurationFile_Release(&cf); +} + + +// ============================================================================== + + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _stripLeadingWhitespace); + LONGBOW_RUN_TEST_CASE(Local, _stripTrailingWhitespace); + LONGBOW_RUN_TEST_CASE(Local, _trim); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +typedef struct test_vector { + const char *input; + const char *output; + bool sentinel; +} TestVector; + +LONGBOW_TEST_CASE(Local, _stripLeadingWhitespace) +{ + TestVector vectors[] = { + { .input = "", .output = "" }, + { .input = " ", .output = "" }, + { .input = "\t", .output = "" }, + { .input = "a", .output = "a" }, + { .input = "abc", .output = "abc" }, + { .input = " a c ", .output = "a c " }, + { .input = " bc", .output = "bc" }, + { .input = "\tbc", .output = "bc" }, + { .input = " \tbc", .output = "bc" }, + { .input = "\t\tbc ", .output = "bc " }, + { .input = NULL, .output = NULL }, + }; + + for (int i = 0; vectors[i].input != NULL; i++) { + char *copy = parcMemory_StringDuplicate(vectors[i].input, strlen(vectors[i].input)); + char *test = _stripLeadingWhitespace(copy); + assertTrue(strcmp(test, vectors[i].output) == 0, "Bad output index %d. input = '%s' expected = '%s' actual = '%s'", i, vectors[i].input, vectors[i].output, test); + parcMemory_Deallocate((void **) ©); + } +} + +LONGBOW_TEST_CASE(Local, _stripTrailingWhitespace) +{ + TestVector vectors[] = { + { .input = "", .output = "" }, + { .input = " ", .output = "" }, + { .input = "\t", .output = "" }, + { .input = "a", .output = "a" }, + { .input = "abc", .output = "abc" }, + { .input = " a c ", .output = " a c" }, + { .input = "bc ", .output = "bc" }, + { .input = "bc\t", .output = "bc" }, + { .input = "bc \t", .output = "bc" }, + { .input = " bc\t\t", .output = " bc" }, + { .input = NULL, .output = NULL }, + }; + + for (int i = 0; vectors[i].input != NULL; i++) { + char *copy = parcMemory_StringDuplicate(vectors[i].input, strlen(vectors[i].input)); + char *test = _stripTrailingWhitespace(copy); + assertTrue(strcmp(test, vectors[i].output) == 0, "Bad output index %d. input = '%s' expected = '%s' actual = '%s'", i, vectors[i].input, vectors[i].output, test); + parcMemory_Deallocate((void **) ©); + } +} + +LONGBOW_TEST_CASE(Local, _trim) +{ + TestVector vectors[] = { + { .input = "", .output = "" }, + { .input = " ", .output = "" }, + { .input = "\t", .output = "" }, + { .input = "a", .output = "a" }, + { .input = "abc", .output = "abc" }, + { .input = " a c ", .output = "a c" }, + { .input = "bc ", .output = "bc" }, + { .input = "bc\t", .output = "bc" }, + { .input = "bc \t", .output = "bc" }, + { .input = " bc\t\t", .output = "bc" }, + { .input = NULL, .output = NULL }, + }; + + for (int i = 0; vectors[i].input != NULL; i++) { + char *copy = parcMemory_StringDuplicate(vectors[i].input, strlen(vectors[i].input)); + char *test = _trim(copy); + assertTrue(strcmp(test, vectors[i].output) == 0, "Bad output index %d. input = '%s' expected = '%s' actual = '%s'", i, vectors[i].input, vectors[i].output, test); + parcMemory_Deallocate((void **) ©); + } +} + +// ====================================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_ConfigurationFile); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metis_ConfigurationListeners.c b/metis/ccnx/forwarder/metis/config/test/test_metis_ConfigurationListeners.c new file mode 100644 index 00000000..8ac2c82d --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metis_ConfigurationListeners.c @@ -0,0 +1,644 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Some of these tests might not execute on certain systems, as they + * depend on having INET and INET6 addresses available. If you system + * does not have one or both of those, the corresponding tests will not + * execute. + */ + +// We need to specifically include the Ethernet mockup and set the proper define so +// we do not need an actual Ethernet listener + +#define METIS_MOCK_ETHERNET 1 +#include "../../io/test/testrig_GenericEther.c" + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_ConfigurationListeners.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +#include <signal.h> +#include <net/ethernet.h> + +#define TEST_PORT 9697 +static const CPIAddress * +getFirstAddressOfType(CPIInterfaceSet *set, CPIAddressType type) +{ + for (int i = 0; i < cpiInterfaceSet_Length(set); i++) { + CPIInterface *iface = cpiInterfaceSet_GetByOrdinalIndex(set, i); + const CPIAddressList *list = cpiInterface_GetAddresses(iface); + for (int j = 0; j < cpiAddressList_Length(list); j++) { + const CPIAddress *address = cpiAddressList_GetItem(list, j); + if (cpiAddress_GetType(address) == type) { + return address; + } + } + } + return NULL; +} + +struct sigaction save_sigchld; +struct sigaction save_sigpipe; + +static void +blockSigChild() +{ + struct sigaction ignore_action; + ignore_action.sa_handler = SIG_IGN; + sigemptyset(&ignore_action.sa_mask); + ignore_action.sa_flags = 0; + + sigaction(SIGCHLD, NULL, &save_sigchld); + sigaction(SIGPIPE, NULL, &save_sigpipe); + + sigaction(SIGCHLD, &ignore_action, NULL); + sigaction(SIGPIPE, &ignore_action, NULL); +} + +static void +unblockSigChild() +{ + sigaction(SIGCHLD, &save_sigchld, NULL); + sigaction(SIGPIPE, &save_sigpipe, NULL); +} + +static bool +verifyInNetstat(const char *addressString, int port) +{ + // now verify that we are listening + // tcp4 0 0 127.0.0.1.49009 *.* LISTEN + + FILE *fp = popen("netstat -an", "r"); + assertNotNull(fp, "Got null opening netstat for reading"); + + char buffer[4][1024]; + + sprintf(buffer[0], "%s.%d", addressString, port); + sprintf(buffer[1], "%s:%d", addressString, port); + sprintf(buffer[2], "%s%%lo0.%d", addressString, port); + sprintf(buffer[3], "%s%%lo0:%d", addressString, port); + + char str[1035]; + bool found = false; + while (!found && (fgets(str, sizeof(str) - 1, fp) != NULL)) { + for (int i = 0; i < 4; i++) { + if (strstr(str, buffer[i]) != NULL) { + found = true; + } + } + } + + blockSigChild(); + pclose(fp); + unblockSigChild(); + + return found; +} + +// returns a strdup() of the interface name, use free(3) +static char * +_pickInterfaceName(MetisForwarder *metis) +{ + char *ifname = NULL; + + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + size_t length = cpiInterfaceSet_Length(set); + assertTrue(length > 0, "metisSystem_Interfaces returned no interfaces"); + + for (size_t i = 0; i < length; i++) { + CPIInterface *iface = cpiInterfaceSet_GetByOrdinalIndex(set, i); + const CPIAddressList *addressList = cpiInterface_GetAddresses(iface); + + size_t length = cpiAddressList_Length(addressList); + for (size_t i = 0; i < length && !ifname; i++) { + const CPIAddress *a = cpiAddressList_GetItem(addressList, i); + if (cpiAddress_GetType(a) == cpiAddressType_LINK) { + ifname = strdup(cpiInterface_GetName(iface)); + } + } + } + + cpiInterfaceSet_Destroy(&set); + return ifname; +} + +// ========================================================================= + +LONGBOW_TEST_RUNNER(metis_Configuration) +{ + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_Configuration) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_Configuration) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ============================================================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisConfigurationListeners_SetupAll); + LONGBOW_RUN_TEST_CASE(Global, metisConfigurationListeners_Add_Ether); + LONGBOW_RUN_TEST_CASE(Global, metisConfigurationListeners_Add_IP_UDP4); + LONGBOW_RUN_TEST_CASE(Global, metisConfigurationListeners_Add_IP_UDP6); + LONGBOW_RUN_TEST_CASE(Global, metisConfigurationListeners_Add_IP_TCP4); + LONGBOW_RUN_TEST_CASE(Global, metisConfigurationListeners_Add_IP_TCP6); + LONGBOW_RUN_TEST_CASE(Global, metisConfigurationListeners_Remove); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisConfigurationListeners_SetupAll) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + metisConfigurationListeners_SetupAll(metisForwarder_GetConfiguration(metis), TEST_PORT, NULL); + + MetisListenerSet *set = metisForwarder_GetListenerSet(metis); + size_t len = metisListenerSet_Length(set); + assertTrue(len > 0, "Bad listener set size, expected positive, got %zu", len); + + metisForwarder_Destroy(&metis); +} + +LONGBOW_TEST_CASE(Global, metisConfigurationListeners_Add_Ether) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 77; + + // create the listener + char *ifname = _pickInterfaceName(metis); + CPIListener *cpiListener = cpiListener_CreateEther(ifname, 0x0801, "fake0"); + CCNxControl *control = cpiListener_CreateAddMessage(cpiListener); + bool listenerOk = metisConfigurationListeners_Add(metisForwarder_GetConfiguration(metis), control, mockup_id); + assertTrue(listenerOk, "Failed to setup ether listener."); + free(ifname); + + ccnxControl_Release(&control); + cpiListener_Release(&cpiListener); + + MetisListenerSet *set = metisForwarder_GetListenerSet(metis); + size_t len = metisListenerSet_Length(set); + assertTrue(len == 1, "Bad listener set size, expected 1, got %zu", len); + + metisForwarder_Destroy(&metis); +} + +LONGBOW_TEST_CASE(Global, metisConfigurationListeners_Add_IP_UDP4) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 77; + + // create the listener + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(TEST_PORT); + int result = inet_aton("127.0.0.1", &sin.sin_addr); + assertTrue(result == 1, "failed inet_aton: (%d) %s", errno, strerror(errno)); + + CPIAddress *address = cpiAddress_CreateFromInet(&sin); + CPIListener *listener = cpiListener_CreateIP(IPTUN_UDP, address, "conn1"); + cpiAddress_Destroy(&address); + + + CCNxControl *control = cpiListener_CreateAddMessage(listener); + bool listenerOk = metisConfigurationListeners_Add(metisForwarder_GetConfiguration(metis), control, mockup_id); + assertTrue(listenerOk, "Failed to setup ether listener.") { + int res; + res = system("netstat -an -p udp"); + assertTrue(res != -1, "Error on system call"); + res = system("ps -el"); + assertTrue(res != -1, "Error on system call"); + } + + ccnxControl_Release(&control); + cpiListener_Release(&listener); + + MetisListenerSet *set = metisForwarder_GetListenerSet(metis); + size_t len = metisListenerSet_Length(set); + assertTrue(len == 1, "Bad listener set size, expected 1, got %zu", len); + + metisForwarder_Destroy(&metis); +} + +LONGBOW_TEST_CASE(Global, metisConfigurationListeners_Add_IP_UDP6) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 77; + + // create the listener + struct sockaddr_in6 sin6; + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(TEST_PORT); + int result = inet_pton(AF_INET6, "::1", &(sin6.sin6_addr)); + if (result == 1) { + CPIAddress *address = cpiAddress_CreateFromInet6(&sin6); + CPIListener *listener = cpiListener_CreateIP(IPTUN_UDP, address, "conn1"); + cpiAddress_Destroy(&address); + + + CCNxControl *control = cpiListener_CreateAddMessage(listener); + bool listenerOk = metisConfigurationListeners_Add(metisForwarder_GetConfiguration(metis), control, mockup_id); + assertTrue(listenerOk, "Failed to setup ether listener.") { + int res; + res = system("netstat -an -p udp"); + assertTrue(res != -1, "Error on system call"); + res = system("ps -el"); + assertTrue(res != -1, "Error on system call"); + } + + ccnxControl_Release(&control); + cpiListener_Release(&listener); + + MetisListenerSet *set = metisForwarder_GetListenerSet(metis); + size_t len = metisListenerSet_Length(set); + assertTrue(len == 1, "Bad listener set size, expected 1, got %zu", len); + metisForwarder_Destroy(&metis); + } else { + metisForwarder_Destroy(&metis); + testSkip("IPv6 not supported"); + } +} + +LONGBOW_TEST_CASE(Global, metisConfigurationListeners_Add_IP_TCP4) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 77; + + // create the listener + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(TEST_PORT); + int result = inet_aton("127.0.0.1", &sin.sin_addr); + assertTrue(result == 1, "failed inet_aton: (%d) %s", errno, strerror(errno)); + + CPIAddress *address = cpiAddress_CreateFromInet(&sin); + CPIListener *listener = cpiListener_CreateIP(IPTUN_TCP, address, "conn1"); + cpiAddress_Destroy(&address); + + + CCNxControl *control = cpiListener_CreateAddMessage(listener); + bool listenerOk = metisConfigurationListeners_Add(metisForwarder_GetConfiguration(metis), control, mockup_id); + assertTrue(listenerOk, "Failed to setup ether listener.") { + int res; + res = system("netstat -an -p tcp"); + assertTrue(res != -1, "Error on system call"); + res = system("ps -el"); + assertTrue(res != -1, "Error on system call"); + } + + ccnxControl_Release(&control); + cpiListener_Release(&listener); + + MetisListenerSet *set = metisForwarder_GetListenerSet(metis); + size_t len = metisListenerSet_Length(set); + assertTrue(len == 1, "Bad listener set size, expected 1, got %zu", len); + + metisForwarder_Destroy(&metis); +} + +LONGBOW_TEST_CASE(Global, metisConfigurationListeners_Add_IP_TCP6) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + // Create a mock up of an interface so we can see the response + unsigned mockup_id = 77; + + // create the listener + struct sockaddr_in6 sin6; + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(TEST_PORT); + int result = inet_pton(AF_INET6, "::1", &(sin6.sin6_addr)); + if (result == 1) { + CPIAddress *address = cpiAddress_CreateFromInet6(&sin6); + CPIListener *listener = cpiListener_CreateIP(IPTUN_TCP, address, "conn1"); + cpiAddress_Destroy(&address); + + + CCNxControl *control = cpiListener_CreateAddMessage(listener); + bool listenerOk = metisConfigurationListeners_Add(metisForwarder_GetConfiguration(metis), control, mockup_id); + assertTrue(listenerOk, "Failed to setup ether listener.") { + int res; + res = system("netstat -an -p tcp"); + assertTrue(res != -1, "Error on system call"); + res = system("ps -el"); + assertTrue(res != -1, "Error on system call"); + } + + ccnxControl_Release(&control); + cpiListener_Release(&listener); + + MetisListenerSet *set = metisForwarder_GetListenerSet(metis); + size_t len = metisListenerSet_Length(set); + assertTrue(len == 1, "Bad listener set size, expected 1, got %zu", len); + metisForwarder_Destroy(&metis); + } else { + metisForwarder_Destroy(&metis); + testSkip("IPv6 not supported"); + } +} + +LONGBOW_TEST_CASE(Global, metisConfigurationListeners_Remove) +{ + testUnimplemented("This test is unimplemented"); +} + +// ============================================================================== + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, setupEthernetListenerOnLink); + LONGBOW_RUN_TEST_CASE(Local, setupEthernetListenerOnLink_SecondEthertype); + LONGBOW_RUN_TEST_CASE(Local, setupIPMulticastListenerOnInet); + LONGBOW_RUN_TEST_CASE(Local, setupListenersOnAddress); + LONGBOW_RUN_TEST_CASE(Local, setupListenersOnInet); + LONGBOW_RUN_TEST_CASE(Local, setupListenersOnInet6); + LONGBOW_RUN_TEST_CASE(Local, setupListenersOnLink); + LONGBOW_RUN_TEST_CASE(Local, setupLocalListener); + LONGBOW_RUN_TEST_CASE(Local, setupTcpListenerOnInet); + LONGBOW_RUN_TEST_CASE(Local, setupTcpListenerOnInet6); + LONGBOW_RUN_TEST_CASE(Local, setupUdpListenerOnInet); + LONGBOW_RUN_TEST_CASE(Local, setupUdpListenerOnInet6); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, setupEthernetListenerOnLink) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + uint8_t addr[ETHER_ADDR_LEN] = { 1, 2, 3, 4, 5, 6 }; + CPIAddress *localAddress = cpiAddress_CreateFromLink(addr, ETHER_ADDR_LEN); + + char *ifname = _pickInterfaceName(metis); + MetisListenerOps *listenerops = _setupEthernetListenerOnLink(metis, localAddress, ifname, 0x0801); + assertNotNull(listenerops, "Got null result from _setupEthernetListenerOnLink"); + + free(ifname); + cpiAddress_Destroy(&localAddress); + metisForwarder_Destroy(&metis); +} + +/* + * The current system does not allow multiple ether listeners on a single interface. + * even if they are different ethertypes + */ +LONGBOW_TEST_CASE(Local, setupEthernetListenerOnLink_SecondEthertype) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + uint8_t addr[ETHER_ADDR_LEN] = { 1, 2, 3, 4, 5, 6 }; + CPIAddress *localAddress = cpiAddress_CreateFromLink(addr, ETHER_ADDR_LEN); + + char *ifname = _pickInterfaceName(metis); + MetisListenerOps *listenerops = _setupEthernetListenerOnLink(metis, localAddress, ifname, 0x0801); + assertNotNull(listenerops, "Got null result from _setupEthernetListenerOnLink"); + + // now try to add again with different ethertype + MetisListenerOps *second = _setupEthernetListenerOnLink(metis, localAddress, ifname, 0x0802); + assertNull(second, "Should have gotten null for second listener"); + + free(ifname); + cpiAddress_Destroy(&localAddress); + metisForwarder_Destroy(&metis); +} + + +LONGBOW_TEST_CASE(Local, setupIPMulticastListenerOnInet) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, setupListenersOnAddress) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, setupListenersOnInet) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, setupListenersOnInet6) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, setupListenersOnLink) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, setupLocalListener) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, setupTcpListenerOnInet) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + const CPIAddress *address = getFirstAddressOfType(set, cpiAddressType_INET); + if (address != NULL) { + char valueToFind[1024]; + struct sockaddr_in sin; + cpiAddress_GetInet(address, &sin); + _setupTcpListenerOnInet(metis, address, PORT_NUMBER); + bool found = verifyInNetstat(inet_ntoa(sin.sin_addr), PORT_NUMBER); + if (!found) { + // extra diagnostics + int ret = system("netstat -an -p tcp"); + assertTrue(ret > -1, "Error on system call"); + } + assertTrue(found, "Did not find value %s in netstat", valueToFind); + } + + cpiInterfaceSet_Destroy(&set); + metisForwarder_Destroy(&metis); + + if (address == NULL) { + testSkip("No network interfaces of type INET found"); + } +} + +LONGBOW_TEST_CASE(Local, setupTcpListenerOnInet6) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + const CPIAddress *address = getFirstAddressOfType(set, cpiAddressType_INET6); + if (address != NULL) { + char valueToFind[1024]; + char inet6str[INET6_ADDRSTRLEN]; + struct sockaddr_in6 sin6; + cpiAddress_GetInet6(address, &sin6); + inet_ntop(AF_INET6, &sin6.sin6_addr, inet6str, INET6_ADDRSTRLEN); + _setupTcpListenerOnInet6(metis, address, PORT_NUMBER); + bool found = verifyInNetstat(inet6str, PORT_NUMBER); + if (!found) { + // extra diagnostics + int ret = system("netstat -an -p tcp"); + assertTrue(ret > -1, "Error on system call"); + } + assertTrue(found, "Did not find value %s in netstat", valueToFind); + } + + cpiInterfaceSet_Destroy(&set); + metisForwarder_Destroy(&metis); + + if (address == NULL) { + testSkip("No network interfaces of type INET found"); + } +} + +LONGBOW_TEST_CASE(Local, setupUdpListenerOnInet) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + const CPIAddress *address = getFirstAddressOfType(set, cpiAddressType_INET); + if (address != NULL) { + char valueToFind[1024]; + struct sockaddr_in sin; + cpiAddress_GetInet(address, &sin); + _setupUdpListenerOnInet(metis, address, PORT_NUMBER); + bool found = verifyInNetstat(inet_ntoa(sin.sin_addr), PORT_NUMBER); + if (!found) { + // extra diagnostics + int ret = system("netstat -an -p udp"); + assertTrue(ret > -1, "Error on system call"); + } + assertTrue(found, "Did not find value %s in netstat", valueToFind); + } + + cpiInterfaceSet_Destroy(&set); + metisForwarder_Destroy(&metis); + + if (address == NULL) { + testSkip("No network interfaces of type INET found"); + } +} + +LONGBOW_TEST_CASE(Local, setupUdpListenerOnInet6) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_Config, PARCLogLevel_Debug); + + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + const CPIAddress *address = getFirstAddressOfType(set, cpiAddressType_INET6); + if (address != NULL) { + char valueToFind[1024]; + char inet6str[INET6_ADDRSTRLEN]; + struct sockaddr_in6 sin6; + cpiAddress_GetInet6(address, &sin6); + inet_ntop(AF_INET6, &sin6.sin6_addr, inet6str, INET6_ADDRSTRLEN); + _setupUdpListenerOnInet6(metis, address, PORT_NUMBER); + bool found = verifyInNetstat(inet6str, PORT_NUMBER); + if (!found) { + // extra diagnostics + int ret = system("netstat -an -p udp"); + assertTrue(ret > -1, "Error on system call"); + } + assertTrue(found, "Did not find value %s in netstat", valueToFind); + } + + cpiInterfaceSet_Destroy(&set); + metisForwarder_Destroy(&metis); + + if (address == NULL) { + testSkip("No network interfaces of type INET found"); + } +} + + +// ====================================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_Configuration); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metis_ControlState.c b/metis/ccnx/forwarder/metis/config/test/test_metis_ControlState.c new file mode 100644 index 00000000..088118d4 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metis_ControlState.c @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_ControlState.c" + +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +LONGBOW_TEST_RUNNER(metis_Control) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_Control) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_Control) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ================================================== + +static CCNxMetaMessage *_testWriteMessage = NULL; +static CCNxMetaMessage *_testReadMessage = NULL; + +/** + * For testing purposes writes a message to a local buffer and reads response from local buffer + * + * _testWriteMessage will be an allocated reference to what is written + * _testReadMessage will be sent back (returend). You must put an allocated message there + * before calling this test function. + */ +static CCNxMetaMessage * +_testWriteRead(void *userdata, CCNxMetaMessage *msg) +{ + _testWriteMessage = ccnxMetaMessage_Acquire(msg); + return ccnxMetaMessage_Acquire(_testReadMessage); +} + +static unsigned _testCommandExecuteCount = 0; + +static MetisCommandReturn +_testCommand(MetisCommandParser *parser, MetisCommandOps *ops, PARCList *args) +{ + _testCommandExecuteCount++; + return MetisCommandReturn_Success; +} + +static MetisCommandOps _testCommandOps = { + .command = "test", // empty string for root + .init = NULL, + .execute = _testCommand +}; + +// ================================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisControlState_Create); + LONGBOW_RUN_TEST_CASE(Global, metisControlState_DispatchCommand); + LONGBOW_RUN_TEST_CASE(Global, metisControlState_GetDebug); + LONGBOW_RUN_TEST_CASE(Global, metisControlState_Interactive); + LONGBOW_RUN_TEST_CASE(Global, metisControlState_RegisterCommand); + LONGBOW_RUN_TEST_CASE(Global, metisControlState_SetDebug); + LONGBOW_RUN_TEST_CASE(Global, metisControlState_WriteRead); + LONGBOW_RUN_TEST_CASE(Global, _metisControlState_ParseStringIntoTokens); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisControlState_Create) +{ + char hello[] = "hello"; + MetisControlState *state = metisControlState_Create(hello, _testWriteRead); + metisControlState_Destroy(&state); +} + +LONGBOW_TEST_CASE(Global, metisControlState_DispatchCommand) +{ + char hello[] = "hello"; + MetisControlState *state = metisControlState_Create(hello, _testWriteRead); + + metisControlState_RegisterCommand(state, &_testCommandOps); + + const char *argv[] = { "test", "foobar" }; + PARCList *args = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(args, 2, (void **) &argv[0]); + + _testCommandExecuteCount = 0; + + metisControlState_DispatchCommand(state, args); + + assertTrue(_testCommandExecuteCount == 1, "Incorrect execution count, expected 1 got %u", _testCommandExecuteCount); + parcList_Release(&args); + metisControlState_Destroy(&state); +} + +LONGBOW_TEST_CASE(Global, metisControlState_GetDebug) +{ + char hello[] = "hello"; + MetisControlState *state = metisControlState_Create(hello, _testWriteRead); + + bool test = metisControlState_GetDebug(state); + assertTrue(test == state->debugFlag, "debug flag in unexpected state"); + + metisControlState_Destroy(&state); +} + +LONGBOW_TEST_CASE(Global, metisControlState_Interactive) +{ + // this reads commands from stdin. not sure how to test this. + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Global, metisControlState_RegisterCommand) +{ + char hello[] = "hello"; + MetisControlState *state = metisControlState_Create(hello, _testWriteRead); + + metisControlState_RegisterCommand(state, &_testCommandOps); + + bool match = metisCommandParser_ContainsCommand(state->parser, "test"); + assertTrue(match, "Command not found in parser"); + + metisControlState_Destroy(&state); +} + +LONGBOW_TEST_CASE(Global, metisControlState_SetDebug) +{ + char hello[] = "hello"; + MetisControlState *state = metisControlState_Create(hello, _testWriteRead); + + assertFalse(state->debugFlag, "debug flag in unexpected true state"); + metisControlState_SetDebug(state, true); + assertTrue(state->debugFlag, "debug flag in unexpected false state"); + + metisControlState_Destroy(&state); +} + +LONGBOW_TEST_CASE(Global, metisControlState_WriteRead) +{ + char hello[] = "hello"; + MetisControlState *state = metisControlState_Create(hello, _testWriteRead); + + CCNxName *appleName = ccnxName_CreateFromCString("lci:/apple"); + CCNxInterest *appleInterest = ccnxInterest_CreateSimple(appleName); + _testReadMessage = ccnxMetaMessage_CreateFromInterest(appleInterest); + ccnxInterest_Release(&appleInterest); + ccnxName_Release(&appleName); + + CCNxName *pieName = ccnxName_CreateFromCString("lci:/pie"); + CCNxInterest *pieInterest = ccnxInterest_CreateSimple(pieName); + CCNxMetaMessage *writeMessage = ccnxMetaMessage_CreateFromInterest(pieInterest);; + ccnxInterest_Release(&pieInterest); + ccnxName_Release(&pieName); + + CCNxMetaMessage *test = metisControlState_WriteRead(state, writeMessage); + + assertTrue(_testWriteMessage == writeMessage, "write message incorrect, expected %p got %p", (void *) writeMessage, (void *) _testWriteMessage); + assertTrue(_testReadMessage == test, "read message incorrect, expected %p got %p", (void *) _testReadMessage, (void *) test); + + ccnxMetaMessage_Release(&test); + ccnxMetaMessage_Release(&writeMessage); + + ccnxMetaMessage_Release(&_testReadMessage); + ccnxMetaMessage_Release(&_testWriteMessage); + + metisControlState_Destroy(&state); +} + +LONGBOW_TEST_CASE(Global, _metisControlState_ParseStringIntoTokens) +{ + const char *string = "the quick brown fox"; + + const char *argv[] = { "the", "quick", "brown", "fox" }; + PARCList *truth = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + parcList_AddAll(truth, 4, (void **) &argv[0]); + + PARCList *test = _metisControlState_ParseStringIntoTokens(string); + + assertTrue(parcList_Size(test) == parcList_Size(truth), "list wrong size, expected %zu got %zu", parcList_Size(truth), parcList_Size(test)); + + for (int i = 0; i < parcList_Size(truth); i++) { + const char *testString = parcList_GetAtIndex(test, i); + const char *truthString = parcList_GetAtIndex(truth, i); + assertTrue(strcmp(testString, truthString) == 0, "index %d not equal, expected '%s' got '%s'", i, truthString, testString); + } + + parcList_Release(&test); + parcList_Release(&truth); +} + +// ======================================================================== + +LONGBOW_TEST_FIXTURE(Local) +{ +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_Control); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/test_metis_SymbolicNameTable.c b/metis/ccnx/forwarder/metis/config/test/test_metis_SymbolicNameTable.c new file mode 100644 index 00000000..811f5b0f --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/test_metis_SymbolicNameTable.c @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_SymbolicNameTable.c" + +#include <stdio.h> +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metis_SymbolicNameTable) +{ + LONGBOW_RUN_TEST_FIXTURE(Global); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_SymbolicNameTable) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_SymbolicNameTable) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ============================================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisSymbolicNameTable_Create); + LONGBOW_RUN_TEST_CASE(Global, metisSymbolicNameTable_Exists_True); + LONGBOW_RUN_TEST_CASE(Global, metisSymbolicNameTable_Exists_False); + LONGBOW_RUN_TEST_CASE(Global, metisSymbolicNameTable_Add_Unique); + LONGBOW_RUN_TEST_CASE(Global, metisSymbolicNameTable_Add_Duplicate); + LONGBOW_RUN_TEST_CASE(Global, metisSymbolicNameTable_Get_Exists); + LONGBOW_RUN_TEST_CASE(Global, metisSymbolicNameTable_Get_Missing); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisSymbolicNameTable_Create) +{ + MetisSymbolicNameTable *table = metisSymbolicNameTable_Create(); + assertNotNull(table, "Got null table"); + assertNotNull(table->symbolicNameTable, "Table did not have an inner hash table allocated"); + metisSymbolicNameTable_Destroy(&table); +} + +LONGBOW_TEST_CASE(Global, metisSymbolicNameTable_Exists_True) +{ + MetisSymbolicNameTable *table = metisSymbolicNameTable_Create(); + metisSymbolicNameTable_Add(table, "foo", 3); + bool exists = metisSymbolicNameTable_Exists(table, "foo"); + assertTrue(exists, "Failed to find existing key"); + metisSymbolicNameTable_Destroy(&table); +} + +LONGBOW_TEST_CASE(Global, metisSymbolicNameTable_Exists_False) +{ + MetisSymbolicNameTable *table = metisSymbolicNameTable_Create(); + bool exists = metisSymbolicNameTable_Exists(table, "foo"); + assertFalse(exists, "Found non-existent key!"); + metisSymbolicNameTable_Destroy(&table); +} + +LONGBOW_TEST_CASE(Global, metisSymbolicNameTable_Add_Unique) +{ + MetisSymbolicNameTable *table = metisSymbolicNameTable_Create(); + bool success = metisSymbolicNameTable_Add(table, "foo", 3); + assertTrue(success, "Failed to add a unique key"); + metisSymbolicNameTable_Destroy(&table); +} + +LONGBOW_TEST_CASE(Global, metisSymbolicNameTable_Add_Duplicate) +{ + MetisSymbolicNameTable *table = metisSymbolicNameTable_Create(); + metisSymbolicNameTable_Add(table, "foo", 3); + bool failure = metisSymbolicNameTable_Add(table, "foo", 4); + assertFalse(failure, "Should have failed to add a duplicate key"); + metisSymbolicNameTable_Destroy(&table); +} + +LONGBOW_TEST_CASE(Global, metisSymbolicNameTable_Get_Exists) +{ + MetisSymbolicNameTable *table = metisSymbolicNameTable_Create(); + metisSymbolicNameTable_Add(table, "foo", 3); + unsigned value = metisSymbolicNameTable_Get(table, "foo"); + assertTrue(value == 3, "Wrong value, expected %u got %u", 3, value); + metisSymbolicNameTable_Destroy(&table); +} + +LONGBOW_TEST_CASE(Global, metisSymbolicNameTable_Get_Missing) +{ + MetisSymbolicNameTable *table = metisSymbolicNameTable_Create(); + unsigned value = metisSymbolicNameTable_Get(table, "foo"); + assertTrue(value == UINT32_MAX, "Wrong value, expected %u got %u", UINT32_MAX, value); + metisSymbolicNameTable_Destroy(&table); +} + + +// ============================================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_SymbolicNameTable); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/config/test/testrig_MetisControl.c b/metis/ccnx/forwarder/metis/config/test/testrig_MetisControl.c new file mode 100644 index 00000000..f3f18000 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/test/testrig_MetisControl.c @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Common operations for the metisControl tests. This C module + * is intended to be #include'd in to each test. + * + */ + +#include <LongBow/unit-test.h> + +#include "../metis_ControlState.c" +#include <parc/algol/parc_SafeMemory.h> +#include <ccnx/forwarder/metis/config/metis_CommandParser.h> +#include <ccnx/api/control/controlPlaneInterface.h> + +typedef struct test_data { + MetisControlState *state; + unsigned writeread_count; + + // If the user specifies this, it will be used as the reply to all test_WriteRead calls + CCNxControl * (*customWriteReadReply)(void *userdata, CCNxMetaMessage * messageToWrite); +} TestData; + +/** + * As part of the testrig, we simply create a CPIAck of the request message. + * We also increment the call count in TestData. + * + * If the user specified a customWriteReadReply function, we will call that to get + * the specific response to send. + */ +static CCNxMetaMessage * +test_WriteRead(void *userdata, CCNxMetaMessage *messageToWrite) +{ + TestData *data = (TestData *) userdata; + data->writeread_count++; + + assertTrue(ccnxMetaMessage_IsControl(messageToWrite), "messageToWrite is not a control message"); + + CCNxControl *response; + CCNxMetaMessage *result; + + if (data->customWriteReadReply == NULL) { + CCNxControl *request = ccnxMetaMessage_GetControl(messageToWrite); + PARCJSON *json = ccnxControl_GetJson(request); + PARCJSON *jsonAck = cpiAcks_CreateAck(json); + + response = ccnxControl_CreateCPIRequest(jsonAck); + result = ccnxMetaMessage_CreateFromControl(response); + + parcJSON_Release(&jsonAck); + ccnxControl_Release(&response); + } else { + response = data->customWriteReadReply(userdata, messageToWrite); + assertTrue(ccnxMetaMessage_IsControl(response), "response is not a control message"); + result = ccnxMetaMessage_CreateFromControl(response); + ccnxControl_Release(&response); + } + + return result; +} + +static void +testrigMetisControl_commonSetup(const LongBowTestCase *testCase) +{ + TestData *data = parcMemory_AllocateAndClear(sizeof(TestData)); + assertNotNull(data, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(TestData)); + memset(data, 0, sizeof(TestData)); + + data->state = metisControlState_Create(data, test_WriteRead); + longBowTestCase_SetClipBoardData(testCase, data); +} + +static void +testrigMetisControl_CommonTeardown(const LongBowTestCase *testCase) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + metisControlState_Destroy(&data->state); + parcMemory_Deallocate((void **) &data); +} + +/** + * Verify that a Command Create operated correctly + * + * We verify the basic properties of what a Create returns. Will assert if a failure. + * + * @param [in] testCase The LongBow test case (used for the clipboard) + * @param [in] create The command create function pointer to test + * @param [in] title The descriptive title to display in case of error + * + * Example: + * @code + * <#example#> + * @endcode + */ +void +testCommandCreate(const LongBowTestCase *testCase, MetisCommandOps * (*create)(MetisControlState * state), const char *title) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = create(data->state); + assertNotNull(ops, "%s: Got null ops", title); + assertNotNull(ops->execute, "%s: Ops execute must not be null", title); + assertNotNull(ops->command, "%s: Ops command must not be null", title); + assertTrue(ops->closure == data->state, "%s: ops closure should be data->state, got wrong pointer", title); + + metisCommandOps_Destroy(&ops); + assertNull(ops, "Ops not nulled by Destroy"); +} + +/** + * Test a Help command's execution. + * + * A Help execution will display text (which we don't test). We make sure there + * is no memory leak and that it returns successfully. We will call the passed create method + * to create the Help command then execute its execute. + * + * @param [in] testCase The LongBow test case (used for the clipboard) + * @param [in] create The command create function pointer to test + * @param [in] title The descriptive title to display in case of error + * @param [in] expected A MetisCommandReturn to use as the expected result + * + * Example: + * @code + * { + * // expectes MetisCommandReturn_Success + * testHelpExecute(testCase, metisControl_Add_Create, __func__, MetisCommandReturn_Success); + * + * // expectes MetisCommandReturn_Exit + * testHelpExecute(testCase, metisControl_Quit_Create, __func__, MetisCommandReturn_Exit); + * } + * @endcode + */ +void +testHelpExecute(const LongBowTestCase *testCase, MetisCommandOps * (*create)(MetisControlState * state), const char *title, MetisCommandReturn expected) +{ + uint32_t beforeMemory = parcMemory_Outstanding(); + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = create(data->state); + MetisCommandReturn result = ops->execute(NULL, ops, NULL); + assertTrue(result == expected, "Wrong return, got %d expected %d", result, expected); + metisCommandOps_Destroy(&ops); + uint32_t afterMemory = parcMemory_Outstanding(); + + assertTrue(beforeMemory == afterMemory, "Memory leak by %d\n", (int) (afterMemory - beforeMemory)); +} + +/** + * Verify that a list of commands is added by the Init function + * + * <#Paragraphs Of Explanation#> + * + * @param [in] testCase The LongBow test case (used for the clipboard) + * @param [in] create We will create one of these and call it's init() function + * @param [in] title The descriptive title to display in case of error + * @param [in] commandList Null terminated list of commands + * + * Example: + * @code + * <#example#> + * @endcode + */ +void +testInit(const LongBowTestCase *testCase, MetisCommandOps * (*create)(MetisControlState * state), const char *title, const char **commandList) +{ + // this will register 8 commands, so check they exist + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisCommandOps *ops = create(data->state); + assertNotNull(ops, "%s got null ops from the create function", title); + assertNotNull(ops->init, "%s got null ops->init from the create function", title); + + ops->init(data->state->parser, ops); + + for (int i = 0; commandList[i] != NULL; i++) { + bool success = metisCommandParser_ContainsCommand(data->state->parser, commandList[i]); + assertTrue(success, "%s: Missing: %s", title, commandList[i]); + } + + metisCommandOps_Destroy(&ops); +} |