diff options
Diffstat (limited to 'libccnx-transport-rta/ccnx/transport/transport_rta/test')
6 files changed, 1555 insertions, 0 deletions
diff --git a/libccnx-transport-rta/ccnx/transport/transport_rta/test/.gitignore b/libccnx-transport-rta/ccnx/transport/transport_rta/test/.gitignore new file mode 100644 index 00000000..90005943 --- /dev/null +++ b/libccnx-transport-rta/ccnx/transport/transport_rta/test/.gitignore @@ -0,0 +1,19 @@ +rtatest +test_bent_pipe +test_component_Ccnd_Registrar +test_component_Codec_Ccnb +test_component_Codec_Tlv_Hmac +test_connector_Api +test_connector_Forwarder_Ccnd +test_connector_Forwarder_Flan +test_connector_Forwarder_Local +test_fc_vegas +test_multi_connections +test_rta_Commands +test_rta_ConnectionTable +test_rta_Framework +test_rta_Framework_Commands +test_rta_WebService +test_system_passthrough +test_connector_Forwarder_Metis + diff --git a/libccnx-transport-rta/ccnx/transport/transport_rta/test/CMakeLists.txt b/libccnx-transport-rta/ccnx/transport/transport_rta/test/CMakeLists.txt new file mode 100644 index 00000000..c52b0166 --- /dev/null +++ b/libccnx-transport-rta/ccnx/transport/transport_rta/test/CMakeLists.txt @@ -0,0 +1,13 @@ +# Enable gcov output for the tests +add_definitions(--coverage) +set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} " --coverage") + +set(TestsExpectedToPass + test_multi_connections + test_rta_Transport +) + + +foreach(test ${TestsExpectedToPass}) + AddTest(${test}) +endforeach() diff --git a/libccnx-transport-rta/ccnx/transport/transport_rta/test/README b/libccnx-transport-rta/ccnx/transport/transport_rta/test/README new file mode 100644 index 00000000..8a325828 --- /dev/null +++ b/libccnx-transport-rta/ccnx/transport/transport_rta/test/README @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +These are system tests, not specific to any one component or connector diff --git a/libccnx-transport-rta/ccnx/transport/transport_rta/test/test_multi_connections.c b/libccnx-transport-rta/ccnx/transport/transport_rta/test/test_multi_connections.c new file mode 100644 index 00000000..8f0051a0 --- /dev/null +++ b/libccnx-transport-rta/ccnx/transport/transport_rta/test/test_multi_connections.c @@ -0,0 +1,424 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +#include <sys/un.h> +#include <strings.h> +#include <fcntl.h> +#include <limits.h> + +#include <LongBow/unit-test.h> + + +#include <ccnx/api/control/cpi_ControlMessage.h> +#include <ccnx/common/ccnx_ContentObject.h> + +#include <parc/security/parc_Pkcs12KeyStore.h> +#include <parc/security/parc_PublicKeySigner.h> +#include <parc/security/parc_Security.h> +#include <parc/algol/parc_SafeMemory.h> + +#include <ccnx/transport/transport_rta/config/config_All.h> +#include <ccnx/transport/transport_rta/core/rta_Framework.h> +#include <ccnx/transport/transport_rta/core/rta_Framework_Commands.c> +#include <ccnx/transport/transport_rta/core/rta_Framework_NonThreaded.h> + +#include "../../test_tools/bent_pipe.h" +#include <ccnx/transport/test_tools/traffic_tools.h> + +static const char local_name[] = "/tmp/beta"; +static const char alice_keystore_name[] = "/tmp/alice_keystore"; +static const char bob_keystore_name[] = "/tmp/bob_keystore"; + +static int alice_fd; +static int bob_fd; +static TransportContext *transport_context; +static CCNxTransportConfig *alice_params; +static CCNxTransportConfig *bob_params; + +// reflector for FWD_LOCAL +static BentPipeState *bentpipe; + +static int rnd_fd; + +// for statistics +static double total_delay; +static double total_bytes_per_sec; +static unsigned item_count; + +// ====================================================== + +static CCNxTransportConfig * +MultipleConnections_createParams(const char *local_name, const char *keystore_name, const char *keystore_passwd, const char *nonce) +{ + assertNotNull(local_name, "Got null keystore name\n"); + assertNotNull(keystore_name, "Got null keystore name\n"); + assertNotNull(keystore_passwd, "Got null keystore passwd\n"); + + CCNxStackConfig *stackConfig = ccnxStackConfig_Create(); + + apiConnector_ProtocolStackConfig( + tlvCodec_ProtocolStackConfig( + localForwarder_ProtocolStackConfig( + protocolStack_ComponentsConfigArgs(stackConfig, + apiConnector_GetName(), + tlvCodec_GetName(), + localForwarder_GetName(), + NULL) + ))); + + CCNxConnectionConfig *connConfig = apiConnector_ConnectionConfig( + tlvCodec_ConnectionConfig( + localForwarder_ConnectionConfig(ccnxConnectionConfig_Create(), local_name))); + + publicKeySigner_ConnectionConfig(connConfig, keystore_name, keystore_passwd); + + + // add the special nonce + PARCJSONValue *value = parcJSONValue_CreateFromCString(nonce); + ccnxStackConfig_Add(stackConfig, "nonce", value); + parcJSONValue_Release(&value); + + CCNxTransportConfig *result = ccnxTransportConfig_Create(stackConfig, connConfig); + ccnxStackConfig_Release(&stackConfig); + + return result; +} + +/** + * @function sendRandomObject + * @abstract Sends a content object over the given socket. + * @discussion + * The payload of the content object is a "struct timeval" for timing purposes. + * + * @param Transport socket to use + * @return A copy of the content object sent + */ +static CCNxContentObject * +sendRandomObject(int output_fd, int fixed_size) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + + if (fixed_size < (int) sizeof(tv)) { + fixed_size = sizeof(tv); + } + + uint8_t *buffer = parcMemory_Allocate(fixed_size); + assertNotNull(buffer, "parcMemory_Allocate(%d) returned NULL", fixed_size); + memcpy(buffer, (uint8_t *) &tv, sizeof(tv)); + + PARCBuffer *contents = parcBuffer_Flip(parcBuffer_CreateFromArray(buffer, fixed_size)); + CCNxContentObject *object = trafficTools_CreateContentObjectWithPayload(contents); + parcBuffer_Release(&contents); + + // Return value not checked. + // This creates a reference to object, so we still hold the memory and can return it + CCNxMetaMessage *meta = ccnxMetaMessage_CreateFromContentObject(object); + Transport_Send(output_fd, meta); + ccnxMetaMessage_Release(&meta); + + parcMemory_Deallocate((void **) &buffer); + + // truth_co and truth_msg will be freed by Transport_Send + return object; +} + +/** + * @function recvAndCompare + * @abstract Block on receiving a message on the input_fd, then assert its the same as the truth_obj. + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +static bool +recvAndCompare(int input_fd, CCNxContentObject *truth_obj) +{ + struct timeval now, *then, delta; + + CCNxMetaMessage *test_msg; + int res = Transport_Recv(input_fd, &test_msg); + + assertTrue(res == 0, "got error from Transport_Recv (%d)", res); + + // We can't directly compare the two dictionaries with CCNxTlvDictionary_Equals(), + // because the test_obj that we read in was signed in the Transport when + // it was sent. So the dictionaries are different. + + // So, instead, compare the payload - which should have the time at which the ContentObject + // was created. + + CCNxContentObject *testObject = ccnxMetaMessage_GetContentObject(test_msg); + PARCBuffer *contentsA = ccnxContentObject_GetPayload(testObject); + PARCBuffer *contentsB = ccnxContentObject_GetPayload(truth_obj); + assertTrue(parcBuffer_Equals(contentsA, contentsB), "Payloads do not compare"); + + then = (struct timeval *) parcBuffer_Overlay(contentsA, 0); + + gettimeofday(&now, NULL); + timersub(&now, then, &delta); + + double delay = delta.tv_sec + 1E-6 * delta.tv_usec; + double bytes_per_sec = parcBuffer_Remaining(parcBuffer_Rewind(contentsA)) / delay; + + total_delay += delay; + total_bytes_per_sec += bytes_per_sec; + item_count++; + + ccnxMetaMessage_Release(&test_msg); + return true; +} + +static void +assertConnectionOpen(int fd) +{ + // wait for the CONNECTION_OPEN messages + CCNxMetaMessage *firstMessage; + Transport_Recv(fd, &firstMessage); + + assertTrue(ccnxMetaMessage_IsControl(firstMessage), "Expected first message to be a control message"); + + CCNxControl *control = ccnxMetaMessage_GetControl(firstMessage); + + if (ccnxControl_IsNotification(control)) { + NotifyStatus *status = ccnxControl_GetNotifyStatus(control); + + assertTrue(notifyStatus_IsConnectionOpen(status), "Expected notifyStatus_IsConnectionOpen to be true"); + + notifyStatus_Release(&status); + } + + ccnxMetaMessage_Release(&firstMessage); +} + +static void +stackSetup(const char *alice_nonce, const char *bob_nonce) +{ + unlink(local_name); + + bentpipe = bentpipe_Create(local_name); + bentpipe_SetChattyOutput(bentpipe, false); + bentpipe_Start(bentpipe); + + transport_context = Transport_Create(TRANSPORT_RTA); + + assertNotNull(transport_context, "transportRta_Create() returned null"); + + unlink(alice_keystore_name); + unlink(bob_keystore_name); + + bool success = parcPkcs12KeyStore_CreateFile(alice_keystore_name, "23456", "alice", 1024, 30); + assertTrue(success, "parcPkcs12Store_CreateFile() failed."); + success = parcPkcs12KeyStore_CreateFile(bob_keystore_name, "34567", "bob", 2048, 15); + assertTrue(success, "parcPkcs12Store_CreateFile() failed."); + + alice_params = MultipleConnections_createParams(local_name, alice_keystore_name, "23456", alice_nonce); + bob_params = MultipleConnections_createParams(local_name, bob_keystore_name, "34567", bob_nonce); + + // open a connection, this will cause accpet() to fire + alice_fd = Transport_Open(alice_params); + bob_fd = Transport_Open(bob_params); + + assertFalse(alice_fd < 0, "Transport_Open returned error"); + assertFalse(bob_fd < 0, "Transport_Open returned error"); + + assertConnectionOpen(alice_fd); + assertConnectionOpen(bob_fd); +} + +static void +stackTearDown(const char *alice_nonce, const char *bob_nonce) +{ + assertTrue(unlink(alice_keystore_name) == 0 || errno == ENOENT, + "Unable to unlink the file %s: %s", alice_keystore_name, strerror(errno)); + + assertTrue(unlink(bob_keystore_name) == 0 || errno == ENOENT, + "Unable to unlink the file %s: %s", bob_keystore_name, strerror(errno)); + + Transport_Destroy(&transport_context); + bentpipe_Stop(bentpipe); + bentpipe_Destroy(&bentpipe); + + ccnxTransportConfig_Destroy(&alice_params); + ccnxTransportConfig_Destroy(&bob_params); +} + +#include <parc/algol/parc_Object.h> + +/** + * @function ping + * @abstract Send a message from one socket to another socket + * @discussion + * Send a content object from one socket to another, then esure the + * unsigned parts of the received message compare to the sent message. + * + * There's a minimum size (sizeof struct timeval). If the fixed_size is + * larger than that minimum, we'll pad out to the fixed size. + * + * @param fixed_size is the payload content size. + * @return <#return#> + */ +static bool +ping(int from_fd, int to_fd, int fixed_size) +{ + CCNxContentObject *object = sendRandomObject(from_fd, fixed_size); + bool success = recvAndCompare(to_fd, object); + assertTrue(success, "sent and received didn't compare!\n"); + ccnxContentObject_Release(&object); + return success; +} +/** + * use -1 for random size, othewise anything larger than 16 works + * for the payload size + */ +static void +playPingPong(int fixed_size) +{ + int loops = 10; + while (loops-- > 0) { + // send down alice and up bob, then bob to alice + ping(alice_fd, bob_fd, fixed_size); + ping(bob_fd, alice_fd, fixed_size); + } +} + +// ====================================================== + + +LONGBOW_TEST_RUNNER(MultipleConnections) +{ + LONGBOW_RUN_TEST_FIXTURE(SameStack); + LONGBOW_RUN_TEST_FIXTURE(DifferentStacks); +} + +LONGBOW_TEST_RUNNER_SETUP(MultipleConnections) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + rnd_fd = open("/dev/urandom", O_RDONLY); + + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_RUNNER_TEARDOWN(MultipleConnections) +{ + close(rnd_fd); + return LONGBOW_STATUS_SUCCEEDED; +} + +// $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ +// $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ + +/* + * Same Stack tests multiple connections within the same + * protocol stack + */ + +LONGBOW_TEST_FIXTURE(SameStack) +{ + LONGBOW_RUN_TEST_CASE(SameStack, alice_bob_pingpong); +} + +LONGBOW_TEST_FIXTURE_SETUP(SameStack) +{ + parcSecurity_Init(); + stackSetup("apple", "apple"); + + total_delay = total_bytes_per_sec = 0.0; + item_count = 0; + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(SameStack) +{ + longBowDebug("average delay %.6f sec, avg bytes/sec %.3f\n", + total_delay / item_count, total_bytes_per_sec / item_count); + + stackTearDown("apple", "apple"); + + parcSecurity_Fini(); + + if (parcMemory_Outstanding() != 0) { + printf("('%s' leaks memory by %d (allocs - frees)) ", longBowTestCase_GetName(testCase), parcMemory_Outstanding()); + parcSafeMemory_ReportAllocation(STDOUT_FILENO); + return LONGBOW_STATUS_MEMORYLEAK; + } + + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(SameStack, alice_bob_pingpong) +{ + playPingPong(8192); +} + +// $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ +// $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ + +/* + * DifferentStacks tests multiple connections through + * different stacks in the same transport + */ + +LONGBOW_TEST_FIXTURE(DifferentStacks) +{ + LONGBOW_RUN_TEST_CASE(DifferentStacks, alice_bob_pingpong); +} + +LONGBOW_TEST_FIXTURE_SETUP(DifferentStacks) +{ + parcSecurity_Init(); + stackSetup("apple", "oranges"); + total_delay = total_bytes_per_sec = 0.0; + item_count = 0; + + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(DifferentStacks) +{ + stackTearDown("apple", "oranges"); + + parcSecurity_Fini(); + + if (parcMemory_Outstanding() != 0) { + printf("('%s' leaks memory by %d (allocs - frees)) ", longBowTestCase_GetName(testCase), parcMemory_Outstanding()); + return LONGBOW_STATUS_MEMORYLEAK; + } + + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(DifferentStacks, alice_bob_pingpong) +{ + playPingPong(-1); +} + +// $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ +// $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(MultipleConnections); + exit(longBowMain(argc, argv, testRunner, NULL)); +} diff --git a/libccnx-transport-rta/ccnx/transport/transport_rta/test/test_rta_Commands.c b/libccnx-transport-rta/ccnx/transport/transport_rta/test/test_rta_Commands.c new file mode 100644 index 00000000..4f4d7cb0 --- /dev/null +++ b/libccnx-transport-rta/ccnx/transport/transport_rta/test/test_rta_Commands.c @@ -0,0 +1,381 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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 "../rta_Commands.c" + +#include "../core/components.h" +#include <ccnx/transport/transport_rta/config/config_All.h> +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> +#include <strings.h> + +static TransportConfig * +Test_createParams(const char *local_name) +{ + assertNotNull(local_name, "%s got null keystore name\n", __func__); + + ConnectionConfig *connConfig = apiConnector_ConnectionConfig(localForwarder_ConnectionConfig(ccnxConnectionConfig_Create(), local_name)); + + CCNxStackConfig *stackConfig = apiConnector_ProtocolStackConfig( + localForwarder_ProtocolStackConfig( + protocolStack_ComponentsConfigArgs( + ccnxStackConfig_Create(), apiConnector_GetName(), localForwarder_GetName(), NULL))); + + return transportConfig_Create(stackConfig, connConfig); +} + +LONGBOW_TEST_RUNNER(rta_Commands) +{ + // 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(rta_Commands) +{ + 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(rta_Commands) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, rtaCommand_Close); + LONGBOW_RUN_TEST_CASE(Global, rtaCommand_CreateStack); + + LONGBOW_RUN_TEST_CASE(Global, rtaCommand_DestroyStack); + LONGBOW_RUN_TEST_CASE(Global, rtaCommand_GetClose); + LONGBOW_RUN_TEST_CASE(Global, rtaCommand_GetCreateStack); + LONGBOW_RUN_TEST_CASE(Global, rtaCommand_GetDestroyStack); + LONGBOW_RUN_TEST_CASE(Global, rtaCommand_GetOpen); + LONGBOW_RUN_TEST_CASE(Global, rtaCommand_GetType); + LONGBOW_RUN_TEST_CASE(Global, rtaCommand_Open); + LONGBOW_RUN_TEST_CASE(Global, rtaCommand_Read_Write); + LONGBOW_RUN_TEST_CASE(Global, rtaCommand_Shutdown); + LONGBOW_RUN_TEST_CASE(Global, rtaCommand_CreateTransmitStatistics); + LONGBOW_RUN_TEST_CASE(Global, CommandTransmitStatistics_FromJSON); +} + +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, rtaCommand_Close) +{ + CommandClose commmandClose = { .api_fd = 7 }; + char *truth = "{ \"RTA\" : { \"CLOSE\" : 7 } }"; + PARCJSON *truth_json = parcJSON_ParseString(truth); + char *truth_formatted = ccnxJson_ToString(truth_json); + char *test; + + RtaCommand *command = rtaCommand_Close(commmandClose); + + assertTrue(command->type == RTA_COMMAND_CLOSE, "Type not RTA_COMMAND_CLOSE"); + + test = ccnxJson_ToString(command->command); + assertTrue(strcasecmp(test, truth_formatted) == 0, + "JSON does not match\nexpected: %s\ngot: %s\n", + truth_formatted, test); + + rtaCommand_Destroy(&command); + parcJSON_Release(&truth_json); + parcMemory_Deallocate((void **) &test); + parcMemory_Deallocate((void **) &truth_formatted); +} + +LONGBOW_TEST_CASE(Global, rtaCommand_CreateStack) +{ + TransportConfig *params = Test_createParams("/tmp/fwd"); + CCNxStackConfig *stackConfig = transportConfig_GetProtocolStackConfig(params); + char *params_str = ccnxJson_ToString(ccnxStackConfig_GetJson(stackConfig)); + + CommandCreateStack commandCreateStack = { .stack_id = 9, .params = ccnxStackConfig_GetJson(stackConfig) }; + char *truth = "{ \"RTA\" : { \"CREATE STACK\" : 9, \"PARAMS\" : %s } }"; + char buffer[1024]; + + sprintf(buffer, truth, params_str); + + PARCJSON *truth_json = parcJSON_ParseString(buffer); + char *truth_formatted = ccnxJson_ToString(truth_json); + char *test; + + RtaCommand *command = rtaCommand_CreateStack(commandCreateStack); + + assertTrue(command->type == RTA_COMMAND_CREATESTACK, "Type not RTA_COMMAND_CREATESTACK"); + + test = ccnxJson_ToString(command->command); + assertTrue(strcasecmp(test, truth_formatted) == 0, + "JSON does not match\nexpected: %s\ngot: %s\n", + truth_formatted, test); + + rtaCommand_Destroy(&command); + parcJSON_Release(&truth_json); + transportConfig_Destroy(¶ms); + parcMemory_Deallocate((void **) &test); + parcMemory_Deallocate((void **) &truth_formatted); + parcMemory_Deallocate((void **) ¶ms_str); +} + +LONGBOW_TEST_CASE(Global, rtaCommand_DestroyStack) +{ + CommandDestroyStack commandDestroyStack = { .stack_id = 2 }; + char *truth = "{ \"RTA\" : { \"DESTROY STACK\" : 2 } }"; + PARCJSON *truth_json = parcJSON_ParseString(truth); + char *truth_formatted = ccnxJson_ToString(truth_json); + char *test; + + RtaCommand *command = rtaCommand_DestroyStack(commandDestroyStack); + + assertTrue(command->type == RTA_COMMAND_DESTROYSTACK, "Type not RTA_COMMAND_DESTROYSTACK"); + + test = ccnxJson_ToString(command->command); + assertTrue(strcasecmp(test, truth_formatted) == 0, + "JSON does not match\nexpected: %s\ngot: %s\n", + truth_formatted, test); + + rtaCommand_Destroy(&command); + parcJSON_Release(&truth_json); + parcMemory_Deallocate((void **) &test); + parcMemory_Deallocate((void **) &truth_formatted); +} + +LONGBOW_TEST_CASE(Global, rtaCommand_GetClose) +{ + CommandClose commmandClose = { .api_fd = 7 }; + CommandClose test; + + RtaCommand *command = rtaCommand_Close(commmandClose); + rtaCommand_GetClose(command, &test); + assertTrue(memcmp(&commmandClose, &test, sizeof(CommandClose)) == 0, "structures do not match"); + rtaCommand_Destroy(&command); +} + +LONGBOW_TEST_CASE(Global, rtaCommand_GetCreateStack) +{ + TransportConfig *params = Test_createParams("/tmp/fwd"); + CCNxStackConfig *stackConfig = transportConfig_GetProtocolStackConfig(params); + + CommandCreateStack commandCreateStack = { .stack_id = 9, .params = ccnxStackConfig_GetJson(stackConfig) }; + CommandCreateStack test; + + RtaCommand *command = rtaCommand_CreateStack(commandCreateStack); + rtaCommand_GetCreateStack(command, &test); + + assertTrue(test.stack_id == 9, "Wrong stack id, expected %d got %d", 9, test.stack_id); + + char *truth_params = ccnxJson_ToString(ccnxStackConfig_GetJson(stackConfig)); + char *test_params = ccnxJson_ToString(test.params); + assertTrue(strcasecmp(truth_params, test_params) == 0, "params strings did not match"); + + parcMemory_Deallocate((void **) &truth_params); + parcMemory_Deallocate((void **) &test_params); + rtaCommand_Destroy(&command); + transportConfig_Destroy(¶ms); +} + +LONGBOW_TEST_CASE(Global, rtaCommand_GetDestroyStack) +{ + CommandDestroyStack commandDestroyStack = { .stack_id = 133434 }; + CommandDestroyStack test; + + RtaCommand *command = rtaCommand_DestroyStack(commandDestroyStack); + rtaCommand_GetDestroyStack(command, &test); + assertTrue(memcmp(&commandDestroyStack, &test, sizeof(CommandDestroyStack)) == 0, "structures do not match"); + rtaCommand_Destroy(&command); +} + +LONGBOW_TEST_CASE(Global, rtaCommand_GetOpen) +{ + TransportConfig *params = Test_createParams("/tmp/fwd"); + CCNxStackConfig *stackConfig = transportConfig_GetProtocolStackConfig(params); + + CommandOpen commandOpen = { .stack_id = 9, .api_fd = 77, .transport_fd = 102, .params = ccnxStackConfig_GetJson(stackConfig) }; + CommandOpen test; + RtaCommand *command = rtaCommand_Open(commandOpen); + rtaCommand_GetOpen(command, &test); + + assertTrue(test.stack_id == 9, "Wrong stack id, expected %d got %d", 9, test.stack_id); + assertTrue(test.api_fd == 77, "Wrong api_fd, expected %d got %d", 77, test.api_fd); + assertTrue(test.transport_fd == 102, "Wrong transport_fd, expected %d got %d", 102, test.transport_fd); + + char *truth_params = ccnxJson_ToString(ccnxStackConfig_GetJson(stackConfig)); + char *test_params = ccnxJson_ToString(test.params); + assertTrue(strcasecmp(truth_params, test_params) == 0, "params strings did not match"); + + parcMemory_Deallocate((void **) &truth_params); + parcMemory_Deallocate((void **) &test_params); + rtaCommand_Destroy(&command); + transportConfig_Destroy(¶ms); +} + +LONGBOW_TEST_CASE(Global, rtaCommand_GetType) +{ + CommandDestroyStack commandDestroyStack = { .stack_id = 2 }; + RtaCommand *command = rtaCommand_DestroyStack(commandDestroyStack); + assertTrue(rtaCommand_GetType(command) == RTA_COMMAND_DESTROYSTACK, "Type not RTA_COMMAND_DESTROYSTACK"); + rtaCommand_Destroy(&command); +} + +LONGBOW_TEST_CASE(Global, rtaCommand_Open) +{ + TransportConfig *params = Test_createParams("/tmp/fwd"); + CCNxStackConfig *stackConfig = transportConfig_GetProtocolStackConfig(params); + + char *params_str = ccnxJson_ToString(ccnxStackConfig_GetJson(stackConfig)); + + CommandOpen commandOpen = { .stack_id = 9, .api_fd = 77, .transport_fd = 102, .params = ccnxStackConfig_GetJson(stackConfig) }; + char *truth = "{ \"RTA\" : { \"OPEN\" : [9, 77, 102], \"PARAMS\" : %s } }"; + char buffer[1024]; + + sprintf(buffer, truth, params_str); + + PARCJSON *truth_json = parcJSON_ParseString(buffer); + char *truth_formatted = ccnxJson_ToString(truth_json); + char *test; + + RtaCommand *command = rtaCommand_Open(commandOpen); + + assertTrue(command->type == RTA_COMMAND_OPEN, "Type not RTA_COMMAND_OPEN"); + + test = ccnxJson_ToString(command->command); + assertTrue(strcasecmp(test, truth_formatted) == 0, + "JSON does not match\nexpected: %s\ngot: %s\n", + truth_formatted, test); + + rtaCommand_Destroy(&command); + parcJSON_Release(&truth_json); + transportConfig_Destroy(¶ms); + parcMemory_Deallocate((void **) &test); + parcMemory_Deallocate((void **) &truth_formatted); + parcMemory_Deallocate((void **) ¶ms_str); +} + +LONGBOW_TEST_CASE(Global, rtaCommand_Read_Write) +{ + int fds[2]; + pipe(fds); + + CommandDestroyStack commandDestroyStack = { .stack_id = 2 }; + RtaCommand *command = rtaCommand_DestroyStack(commandDestroyStack); + rtaCommand_Write(command, fds[1]); + RtaCommand *test_command = rtaCommand_Read(fds[0]); + CommandDestroyStack test; + rtaCommand_GetDestroyStack(test_command, &test); + assertTrue(memcmp(&commandDestroyStack, &test, sizeof(commandDestroyStack)) == 0, "memcmp did not match"); + + rtaCommand_Destroy(&command); + rtaCommand_Destroy(&test_command); + close(fds[1]); + close(fds[0]); +} + +LONGBOW_TEST_CASE(Global, rtaCommand_Shutdown) +{ + char *truth = "{ \"RTA\" : { \"SHUTDOWN\" : 1 } }"; + PARCJSON *truth_json = parcJSON_ParseString(truth); + char *truth_formatted = ccnxJson_ToString(truth_json); + char *test; + + RtaCommand *command = rtaCommand_Shutdown(); + + assertTrue(command->type == RTA_COMMAND_SHUTDOWN, "Type not RTA_COMMAND_SHUTDOWN"); + + test = ccnxJson_ToString(command->command); + assertTrue(strcasecmp(test, truth_formatted) == 0, + "JSON does not match\nexpected: %s\ngot: %s\n", + truth_formatted, test); + + rtaCommand_Destroy(&command); + parcJSON_Release(&truth_json); + parcMemory_Deallocate((void **) &test); + parcMemory_Deallocate((void **) &truth_formatted); +} + +LONGBOW_TEST_CASE(Global, rtaCommand_CreateTransmitStatistics) +{ + char *truth = "{ \"RTA\" : { \"TransmitStatistics\" : { \"fileName\": \"/tmp/foo\", \"timeval\" : { " + "\"seconds\" : 1, \"microseconds\": 2 } } } }\n"; + + PARCJSON *truth_json = parcJSON_ParseString(truth); + char *truth_formatted = ccnxJson_ToString(truth_json); + + CommandTransmitStatistics transmitStatistics = { + .timeval = { .tv_sec = 1, .tv_usec = 2 }, + .fileName = "/tmp/foo" + }; + + RtaCommand *command = CommandTransmitStatistics_ToRtaCommand(transmitStatistics); + + assertTrue(command->type == RTA_COMMAND_TRANSMIT_STATISTICS, + "Expected RTA_COMMAND_TRANSMIT_STATISTICS, actual %d", command->type); + + char *test = ccnxJson_ToString(command->command); + assertTrue(strcasecmp(test, truth_formatted) == 0, + "JSON does not match\nexpected: %s\ngot: %s\n", + truth_formatted, test); + + rtaCommand_Destroy(&command); + parcJSON_Release(&truth_json); + parcMemory_Deallocate((void **) &test); + parcMemory_Deallocate((void **) &truth_formatted); +} + +LONGBOW_TEST_CASE(Global, CommandTransmitStatistics_FromJSON) +{ + CommandTransmitStatistics transmitStatistics = { + .timeval = { .tv_sec = 1, .tv_usec = 2 }, + .fileName = "/tmp/foo" + }; + RtaCommand *command = CommandTransmitStatistics_ToRtaCommand(transmitStatistics); + + CommandTransmitStatistics actual; + CommandTransmitStatistics_FromRtaCommand(command, &actual); + + assertTrue(transmitStatistics.timeval.tv_sec == actual.timeval.tv_sec, "tv_sec failed to be equal"); + assertTrue(transmitStatistics.timeval.tv_usec == actual.timeval.tv_usec, "tv_usec failed to be equal"); + assertTrue(strcmp(transmitStatistics.fileName, actual.fileName) == 0, "fileName failed to be equal"); + + rtaCommand_Destroy(&command); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(rta_Commands); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/libccnx-transport-rta/ccnx/transport/transport_rta/test/test_rta_Transport.c b/libccnx-transport-rta/ccnx/transport/transport_rta/test/test_rta_Transport.c new file mode 100644 index 00000000..9f6f0611 --- /dev/null +++ b/libccnx-transport-rta/ccnx/transport/transport_rta/test/test_rta_Transport.c @@ -0,0 +1,701 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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 "../rta_Transport.c" +#include <ccnx/transport/transport_rta/config/config_All.h> +#include <ccnx/transport/transport_rta/core/rta_Framework_private.h> +#include <ccnx/transport/transport_rta/components/component_Testing.h> + +#include <ccnx/common/ccnx_WireFormatMessage.h> + +#include <parc/algol/parc_SafeMemory.h> +#include <ccnx/transport/test_tools/traffic_tools.h> + +#include <LongBow/unit-test.h> + +typedef struct test_data { + RTATransport *transport; + CCNxMetaMessage *msg; +} TestData; + +static TestData * +_commonSetup(void) +{ + TestData *data = parcMemory_AllocateAndClear(sizeof(TestData)); + assertNotNull(data, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(TestData)); + data->transport = rtaTransport_Create(); + return data; +} + +static void +_commonTeardown(TestData *data) +{ + rtaTransport_Destroy(&data->transport); + if (data->msg) { + ccnxMetaMessage_Release(&data->msg); + } + + parcMemory_Deallocate((void **) &data); +} + +static CCNxTransportConfig * +createSimpleConfig(TestData *data) +{ + // API connector -> Testing Lower component + + CCNxStackConfig *stackConfig = + testingLower_ProtocolStackConfig(apiConnector_ProtocolStackConfig(ccnxStackConfig_Create())); + + CCNxConnectionConfig *connConfig = + testingLower_ConnectionConfig( + tlvCodec_ConnectionConfig( + apiConnector_ConnectionConfig( + ccnxConnectionConfig_Create()))); + + protocolStack_ComponentsConfigArgs(stackConfig, apiConnector_GetName(), testingLower_GetName(), NULL); + + CCNxTransportConfig *result = ccnxTransportConfig_Create(stackConfig, connConfig); + ccnxStackConfig_Release(&stackConfig); + return result; +} + +/** + * Peek inside the RTA framework's connection table + * + * We will look inside the RTA framework's thread to find a connection by the API_FD. + * + * @param [in] data The test data, holding the transport + * @param [in] api_fd The API FD to lookup + * @param [in] usec_timeout How long to busy wait looking in the connection table (micro seconds) + * + * @return NULL It was not in the table after the timeout period + * @return non-null The connection corresponding to api_fd + * + * Example: + * @code + * <#example#> + * @endcode + */ +static RtaConnection * +lookupRtaConnectionInsideFramework(TestData *data, int api_fd, unsigned usec_timeout) +{ + // busy loop looking for connection to give RTA thread time to process it. + // Remember, we're operating in the "API" thread when issuing these commands. + struct timeval t0; + long timer_usec = 0; + gettimeofday(&t0, NULL); + bool timeout = false; + RtaConnection *conn = NULL; + while (conn == NULL && !timeout) { + usleep(500); + conn = rtaConnectionTable_GetByApiFd(data->transport->framework->connectionTable, api_fd); + struct timeval t1; + gettimeofday(&t1, NULL); + timersub(&t1, &t0, &t1); + timer_usec = t1.tv_sec * 1000000 + t1.tv_usec; + timeout = timer_usec > usec_timeout ? true : false; + } + + if (conn) { + printf("Found connection %p after %.6f seconds\n", (void *) conn, timer_usec * 1E-6); + } + + return conn; +} + +/** + * Wait for a connection to go away + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +static bool +lookupNullRtaConnectionInsideFramework(TestData *data, int api_fd, unsigned usec_timeout) +{ + // busy loop looking for connection to give RTA thread time to process it. + // Remember, we're operating in the "API" thread when issuing these commands. + struct timeval t0; + long timer_usec = 0; + gettimeofday(&t0, NULL); + bool timeout = false; + + // initialize to non-null + RtaConnection *conn = (void *) 1; + while (conn != NULL && !timeout) { + usleep(500); + conn = rtaConnectionTable_GetByApiFd(data->transport->framework->connectionTable, api_fd); + struct timeval t1; + gettimeofday(&t1, NULL); + timersub(&t1, &t0, &t1); + timer_usec = t1.tv_sec * 1000000 + t1.tv_usec; + timeout = timer_usec > usec_timeout ? true : false; + } + + if (conn == NULL) { + printf("Found no connection %p after %.6f seconds\n", (void *) conn, timer_usec * 1E-6); + } + + // if its null, return true + return (conn == NULL); +} + + +// ================================================================================== +// Runner + +LONGBOW_TEST_RUNNER(rta_Transport) +{ + // 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(rta_Transport) +{ + 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(rta_Transport) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ================================================================================== +// Global + +LONGBOW_TEST_FIXTURE(Global) +{ + // These are still static functions, but they are the function pointers used + // in the transport function structure. They comprise the public API. + LONGBOW_RUN_TEST_CASE(Global, rtaTransport_Close); + LONGBOW_RUN_TEST_CASE(Global, rtaTransport_Create_Destroy); + LONGBOW_RUN_TEST_CASE(Global, rtaTransport_Open); + LONGBOW_RUN_TEST_CASE(Global, rtaTransport_PassCommand); + + LONGBOW_RUN_TEST_CASE(Global, rtaTransport_Recv_OK); + LONGBOW_RUN_TEST_CASE(Global, rtaTransport_Recv_WouldBlock); + + LONGBOW_RUN_TEST_CASE(Global, rtaTransport_Send_OK); + LONGBOW_RUN_TEST_CASE(Global, rtaTransport_Send_WouldBlock); + +// LONGBOW_RUN_TEST_CASE(Global, unrecoverable); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + longBowTestCase_SetClipBoardData(testCase, _commonSetup()); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + _commonTeardown(longBowTestCase_GetClipBoardData(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, rtaTransport_Close) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + CCNxTransportConfig *config = createSimpleConfig(data); + + int api_fd = rtaTransport_Open(data->transport, config); + + RtaConnection *conn = lookupRtaConnectionInsideFramework(data, api_fd, 1E+6); + assertNotNull(conn, "Could not find connection"); + + rtaTransport_Close(data->transport, api_fd); + + // now wait until it's gone + bool gone = lookupNullRtaConnectionInsideFramework(data, api_fd, 1E+6); + assertTrue(gone, "Did not remove connection after 1 second timeout"); + + ccnxTransportConfig_Destroy(&config); +} + +LONGBOW_TEST_CASE(Global, rtaTransport_Create_Destroy) +{ + RTATransport *transport = rtaTransport_Create(); + assertNotNull(transport, "rtaTransport_Create() returns NULL"); + + rtaTransport_Destroy(&transport); + assertNull(transport, "rtaTransport_Destroy did not null paramter"); +} + +LONGBOW_TEST_CASE(Global, rtaTransport_Open) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + CCNxTransportConfig *config = createSimpleConfig(data); + + int api_fd = rtaTransport_Open(data->transport, config); + + RtaConnection *conn = lookupRtaConnectionInsideFramework(data, api_fd, 1E+6); + assertNotNull(conn, "Could not find connection"); + + ccnxTransportConfig_Destroy(&config); +} + +/** + * PassCommand sends a user RTA Command over the command channel. + * This test will intercept the transport side of the command channel so + * we can easily verify the command went through. + */ +LONGBOW_TEST_CASE(Global, rtaTransport_PassCommand) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + PARCRingBuffer1x1 *previousRingBuffer = data->transport->commandRingBuffer; + PARCNotifier *previousNotifier = data->transport->commandNotifier; + + PARCRingBuffer1x1 *testRingBuffer = parcRingBuffer1x1_Create(32, NULL); + PARCNotifier *testNotifier = parcNotifier_Create(); + + + // Insert our new socket pair so we can intercept the commands + // No acquire here because we will be resetting them and destroying all in this scope + data->transport->commandRingBuffer = testRingBuffer; + data->transport->commandNotifier = testNotifier; + + // Create a simple command to send + RtaCommand *command = rtaCommand_CreateShutdownFramework(); + rtaTransport_PassCommand(data->transport, command); + rtaCommand_Release(&command); + + RtaCommand *testCommand = rtaCommand_Read(testRingBuffer); + assertNotNull(testCommand, "Got null command from the ring buffer."); + assertTrue(rtaCommand_IsShutdownFramework(testCommand), "Command not a shutdown framework"); + + // All's well + + rtaCommand_Release(&testCommand); + + // now restore the sockets so things close up nicely + data->transport->commandRingBuffer = previousRingBuffer; + data->transport->commandNotifier = previousNotifier; + + parcRingBuffer1x1_Release(&testRingBuffer); + parcNotifier_Release(&testNotifier); +} + +LONGBOW_TEST_CASE(Global, rtaTransport_Recv_OK) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + int api_fd, transport_fd; + + _RTASocketPair pair = _rtaTransport_CreateSocketPair(data->transport, 128 * 1024); + api_fd = pair.up; + transport_fd = pair.down; + + // Set non-blocking flag + int flags = fcntl(api_fd, F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)\n", errno); + int failure = fcntl(api_fd, F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)\n", errno); + + char *buffer = "born free, as free as the wind blows"; + ssize_t nwritten = write(transport_fd, &buffer, sizeof(&buffer)); + assertTrue(nwritten == sizeof(&buffer), "Wrong write size, expected %zu got %zd", sizeof(&buffer), nwritten); + + CCNxMetaMessage *msg = NULL; + TransportIOStatus result = rtaTransport_Recv(data->transport, api_fd, &msg, CCNxStackTimeout_Never); + assertTrue(result != TransportIOStatus_Error, "Failed to read a good socket"); + assertTrue((void *) msg == (void *) buffer, "Read wrong pointer, got %p expected %p", (void *) msg, (void *) buffer); + + close(api_fd); + close(transport_fd); +} + +LONGBOW_TEST_CASE(Global, rtaTransport_Recv_WouldBlock) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + int api_fd, transport_fd; + + _RTASocketPair pair = _rtaTransport_CreateSocketPair(data->transport, 128 * 1024); + api_fd = pair.up; + transport_fd = pair.down; + + // Set non-blocking flag + int flags = fcntl(api_fd, F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)\n", errno); + int failure = fcntl(api_fd, F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)\n", errno); + + // Don't write anything + + CCNxMetaMessage *msg = NULL; + TransportIOStatus result = rtaTransport_Recv(data->transport, api_fd, &msg, CCNxStackTimeout_Immediate); + assertTrue(result == TransportIOStatus_Timeout, "Should have returned failure due to blocking"); + + close(api_fd); + close(transport_fd); +} + + +/** + * This function will receive what the API Connector sends down the stack + */ +static void +mockDowncallRead(PARCEventQueue *queue, PARCEventType type, void *stack) +{ + TransportMessage *tm = rtaComponent_GetMessage(queue); + assertNotNull(tm, "got null transport message"); + + CCNxTlvDictionary *dictionary = transportMessage_GetDictionary(tm); + CCNxCodecNetworkBufferIoVec *vec = ccnxWireFormatMessage_GetIoVec(dictionary); + const struct iovec *iov = ccnxCodecNetworkBufferIoVec_GetArray(vec); + + // we encapsualted a pointer to this counter inside the wire format + unsigned *downcallReadCountPtr = iov[0].iov_base; + (*downcallReadCountPtr)++; + + transportMessage_Destroy(&tm); +} + +CCNxCodecNetworkBufferMemoryBlockFunctions memfunc = { + .allocator = NULL, + .deallocator = NULL +}; + +/** + * This test does not actually need to receive the message in TestingLower. It could have passed + * any socket pair to rtaTransport_Send and inspected the result immediately. + */ +LONGBOW_TEST_CASE(Global, rtaTransport_Send_OK) +{ + testing_null_ops.downcallRead = mockDowncallRead; + + TestData *data = longBowTestCase_GetClipBoardData(testCase); + CCNxTransportConfig *config = createSimpleConfig(data); + + unsigned downcallReadCount = 0; + + CCNxCodecNetworkBuffer *netbuff = ccnxCodecNetworkBuffer_CreateFromArray(&memfunc, NULL, sizeof(downcallReadCount), (uint8_t *) &downcallReadCount); + CCNxCodecNetworkBufferIoVec *vec = ccnxCodecNetworkBuffer_CreateIoVec(netbuff); + CCNxTlvDictionary *wire = ccnxWireFormatMessage_FromInterestPacketTypeIoVec(CCNxTlvDictionary_SchemaVersion_V1, vec); + + int api_fd = rtaTransport_Open(data->transport, config); + + CCNxMetaMessage *msg = ccnxMetaMessage_Acquire(wire); + bool success = rtaTransport_Send(data->transport, api_fd, msg, CCNxStackTimeout_Never); + assertTrue(success, "Got error writing to api_fd %d\n", api_fd); + ccnxMetaMessage_Release(&msg); + + // now spin on it + unsigned maxTries = 2000; // about 1 second + while ((downcallReadCount == 0) && (maxTries > 0)) { + maxTries--; + usleep(500); + } + + printf("Read message after %d tries\n", 2000 - maxTries); + + ccnxTlvDictionary_Release(&wire); + ccnxCodecNetworkBufferIoVec_Release(&vec); + ccnxCodecNetworkBuffer_Release(&netbuff); + + ccnxTransportConfig_Destroy(&config); +} + +/** + * Fill up the socket with junk, then make sure it would blocks + */ +LONGBOW_TEST_CASE(Global, rtaTransport_Send_WouldBlock) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + int api_fd, transport_fd; + + _RTASocketPair pair = _rtaTransport_CreateSocketPair(data->transport, 128 * 1024); + api_fd = pair.up; + transport_fd = pair.down; + + // Set non-blocking flag + int flags = fcntl(api_fd, F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)\n", errno); + int failure = fcntl(api_fd, F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)\n", errno); + + // write junk until it would block + char buffer[1024]; + while (write(api_fd, buffer, 1024) > 0) { + ; + } + + assertTrue(errno == EWOULDBLOCK, "wrote until it would block, but got some other error: (%d) %s", errno, strerror(errno)); + + // now call the function to test and make sure it does the right thing + // if it would block + CCNxTlvDictionary *interest = trafficTools_CreateDictionaryInterest(); + CCNxMetaMessage *msg = ccnxMetaMessage_CreateFromInterest(interest); + + bool success = rtaTransport_Send(data->transport, api_fd, msg, CCNxStackTimeout_Immediate); + printf("success %d, errno %d expected %d\n", success, errno, EWOULDBLOCK); + + assertFalse(success, "Send did not return a failure, even though it would have blocked"); + assertTrue(errno == EWOULDBLOCK, "wrote until it would block, but got some other error: (%d) %s", errno, strerror(errno)); + + ccnxMetaMessage_Release(&msg); + ccnxTlvDictionary_Release(&interest); + + close(api_fd); + close(transport_fd); +} + +/** + * Pass it an invalid socket. This will cause a trap in the send code. + */ +LONGBOW_TEST_CASE_EXPECTS(Global, rtaTransport_Send_Error, .event = &LongBowTrapUnrecoverableState) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + CCNxTlvDictionary *interest = trafficTools_CreateDictionaryInterest(); + data->msg = ccnxMetaMessage_Acquire(interest); + ccnxTlvDictionary_Release(&interest); + + rtaTransport_Send(data->transport, 999, data->msg, CCNxStackTimeout_Immediate); +} + +LONGBOW_TEST_CASE_EXPECTS(Global, unrecoverable, .event = &LongBowTrapUnrecoverableState) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + CCNxTlvDictionary *interest = trafficTools_CreateDictionaryInterest(); + data->msg = ccnxMetaMessage_CreateFromInterest(interest); + ccnxTlvDictionary_Release(&interest); + + rtaTransport_Send(NULL, 999, data->msg, CCNxStackTimeout_Immediate); + + ccnxMetaMessage_Release(&(data->msg)); +} + +// ================================================================================== +// Local + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _rtaTransport_AddStack); + LONGBOW_RUN_TEST_CASE(Local, _rtaTransport_GetStack); + LONGBOW_RUN_TEST_CASE(Local, _rtaTransport_GetStack_Missing); + + LONGBOW_RUN_TEST_CASE(Local, _rtaTransport_CreateSocketPair); + LONGBOW_RUN_TEST_CASE(Local, _rtaTransport_GetProtocolStackEntry_Exists); + LONGBOW_RUN_TEST_CASE(Local, _rtaTransport_GetProtocolStackEntry_NotExists); + LONGBOW_RUN_TEST_CASE(Local, _rtaTransport_AddProtocolStackEntry); + LONGBOW_RUN_TEST_CASE(Local, _rtaTransport_CreateConnection); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + longBowTestCase_SetClipBoardData(testCase, _commonSetup()); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + _commonTeardown(longBowTestCase_GetClipBoardData(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, _rtaTransport_CreateSocketPair) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + int a, b; + + _RTASocketPair pair = _rtaTransport_CreateSocketPair(data->transport, 128 * 1024); + a = pair.up; + b = pair.down; + assertFalse(a < 0, "socket a is error: %d", a); + assertFalse(b < 0, "socket b is error: %d", b); + + ssize_t nwritten = write(a, &a, sizeof(a)); + assertTrue(nwritten == sizeof(a), "Wrong write size, expected %zu got %zd", sizeof(a), nwritten); + + int test; + ssize_t nread = read(b, &test, sizeof(test)); + assertTrue(nread == sizeof(test), "Wrong read size, expected %zu got %zd", sizeof(test), nread); + + assertTrue(test == a, "read wrong value, got %d wrote %d", test, a); + + close(a); + close(b); +} + + +LONGBOW_TEST_CASE(Local, _rtaTransport_GetProtocolStackEntry_Exists) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + CCNxTransportConfig *config = createSimpleConfig(data); + +// uint64_t hash = ccnxStackConfig_HashCode(ccnxTransportConfig_GetStackConfig(config)); + + _StackEntry *truth = _rtaTransport_AddStack(data->transport, ccnxTransportConfig_GetStackConfig(config)); + + _StackEntry *test = _rtaTransport_GetProtocolStackEntry(data->transport, config); + + assertTrue(test == truth, "Wrong pointer, got %p expected %p", (void *) test, (void *) truth); + + ccnxTransportConfig_Destroy(&config); +} + +LONGBOW_TEST_CASE(Local, _rtaTransport_GetProtocolStackEntry_NotExists) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + CCNxTransportConfig *config = createSimpleConfig(data); + + _rtaTransport_AddStack(data->transport, ccnxTransportConfig_GetStackConfig(config)); + + // Now create the missing one to lookup + // this one will have 2x api connectors listed + CCNxStackConfig *missingStackConfig = + apiConnector_ProtocolStackConfig(apiConnector_ProtocolStackConfig(ccnxStackConfig_Create())); + CCNxConnectionConfig *missingConnConfig = apiConnector_ConnectionConfig(ccnxConnectionConfig_Create()); + + CCNxTransportConfig *missingConfig = ccnxTransportConfig_Create(missingStackConfig, missingConnConfig); + ccnxStackConfig_Release(&missingStackConfig); + + _StackEntry *test = _rtaTransport_GetProtocolStackEntry(data->transport, missingConfig); + + assertNull(test, "Wrong pointer, got %p expected %p", (void *) test, (void *) NULL); + ccnxTransportConfig_Destroy(&missingConfig); + ccnxTransportConfig_Destroy(&config); +} + +LONGBOW_TEST_CASE(Local, _rtaTransport_AddProtocolStackEntry) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + CCNxTransportConfig *config = createSimpleConfig(data); + + _StackEntry *entry = _rtaTransport_AddProtocolStackEntry(data->transport, config); + assertNotNull(entry, "Got null entry from _rtaTransport_AddProtocolStackEntry"); + + ccnxTransportConfig_Destroy(&config); +} + +LONGBOW_TEST_CASE(Local, _rtaTransport_CreateConnection) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + CCNxTransportConfig *config = createSimpleConfig(data); + + _StackEntry *entry = _rtaTransport_AddProtocolStackEntry(data->transport, config); + + _RTASocketPair pair = _rtaTransport_CreateSocketPair(data->transport, 128 * 1024); + + _rtaTransport_CreateConnection(data->transport, config, entry, pair); + + // wait up to 1 second + RtaConnection *conn = lookupRtaConnectionInsideFramework(data, pair.up, 1E+6); + assertNotNull(conn, "Could not find connection in connection table, timeout at %.6f seconds", 1.0); + + ccnxTransportConfig_Destroy(&config); +} + +LONGBOW_TEST_CASE(Local, _rtaTransport_AddStack) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + CCNxStackConfig *stackConfig = ccnxStackConfig_Create(); + _StackEntry *entry = _rtaTransport_AddStack(data->transport, stackConfig); + + uint64_t hash = ccnxStackConfig_HashCode(stackConfig); + _StackEntry *test = _rtaTransport_GetStack(data->transport, hash); + assertTrue(test == entry, "Wrong pointer, got %p expected %p", (void *) test, (void *) entry); + + ccnxStackConfig_Release(&stackConfig); +} + +LONGBOW_TEST_CASE(Local, _rtaTransport_GetStack) +{ + struct test_vector { + uint64_t hash; + int stackid; + _StackEntry *entry; + } vector[] = { + { .hash = 20, .stackid = 30, .entry = NULL }, + { .hash = 10, .stackid = 77, .entry = NULL }, + { .hash = 990, .stackid = 31, .entry = NULL }, + { .hash = 0, .stackid = 0, .entry = NULL }, + }; + + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + CCNxStackConfig *stackConfig = ccnxStackConfig_Create(); + + char key[10]; + for (int i = 0; vector[i].hash != 0; i++) { + sprintf(key, "key%d", i); + PARCJSONValue *json = parcJSONValue_CreateFromNULL(); + ccnxStackConfig_Add(stackConfig, key, json); + parcJSONValue_Release(&json); + vector[i].hash = ccnxStackConfig_HashCode(stackConfig); + vector[i].entry = _rtaTransport_AddStack(data->transport, stackConfig); + } + ccnxStackConfig_Release(&stackConfig); + + // now look them up + for (int i = 0; vector[i].hash != 0; i++) { + _StackEntry *test = _rtaTransport_GetStack(data->transport, vector[i].hash); + assertTrue(test == vector[i].entry, "Wrong pointer, got %p expected %p", (void *) test, (void *) vector[i].entry); + } +} + +LONGBOW_TEST_CASE(Local, _rtaTransport_GetStack_Missing) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + CCNxStackConfig *stackConfig = ccnxStackConfig_Create(); + _rtaTransport_AddStack(data->transport, stackConfig); + + PARCJSONValue *json = parcJSONValue_CreateFromNULL(); + ccnxStackConfig_Add(stackConfig, "someKey", json); + parcJSONValue_Release(&json); + + _StackEntry *test = _rtaTransport_GetStack(data->transport, ccnxStackConfig_HashCode(stackConfig)); + + ccnxStackConfig_Release(&stackConfig); + assertNull(test, "Wrong pointer, got %p expected %p", (void *) test, NULL); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(rta_Transport); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} |