diff options
Diffstat (limited to 'libccnx-transport-rta/ccnx/transport/transport_rta/connectors/test')
5 files changed, 1954 insertions, 0 deletions
diff --git a/libccnx-transport-rta/ccnx/transport/transport_rta/connectors/test/CMakeLists.txt b/libccnx-transport-rta/ccnx/transport/transport_rta/connectors/test/CMakeLists.txt new file mode 100644 index 00000000..85e4812f --- /dev/null +++ b/libccnx-transport-rta/ccnx/transport/transport_rta/connectors/test/CMakeLists.txt @@ -0,0 +1,16 @@ +# Enable gcov output for the tests +add_definitions(--coverage) +set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} " --coverage") + +set(TestsExpectedToPass + test_connector_Api + test_rta_ApiConnection + test_connector_Forwarder_Local + test_connector_Forwarder_Metis +) + + +foreach(test ${TestsExpectedToPass}) + AddTest(${test}) +endforeach() + diff --git a/libccnx-transport-rta/ccnx/transport/transport_rta/connectors/test/test_connector_Api.c b/libccnx-transport-rta/ccnx/transport/transport_rta/connectors/test/test_connector_Api.c new file mode 100644 index 00000000..409e075d --- /dev/null +++ b/libccnx-transport-rta/ccnx/transport/transport_rta/connectors/test/test_connector_Api.c @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../connector_Api.c" + +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(connector_Api) +{ + LONGBOW_RUN_TEST_FIXTURE(Global); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(connector_Api) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(connector_Api) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(connector_Api); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/libccnx-transport-rta/ccnx/transport/transport_rta/connectors/test/test_connector_Forwarder_Local.c b/libccnx-transport-rta/ccnx/transport/transport_rta/connectors/test/test_connector_Forwarder_Local.c new file mode 100644 index 00000000..014f4bbe --- /dev/null +++ b/libccnx-transport-rta/ccnx/transport/transport_rta/connectors/test/test_connector_Forwarder_Local.c @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#define DEBUG_OUTPUT 1 +#include "../connector_Forwarder_Local.c" +#include <LongBow/unit-test.h> + +#include <parc/algol/parc_SafeMemory.h> +#include <parc/security/parc_Security.h> +#include <parc/security/parc_Pkcs12KeyStore.h> + +#include <ccnx/api/control/cpi_ControlMessage.h> +#include <ccnx/api/control/controlPlaneInterface.h> + +#include <ccnx/transport/transport_rta/core/rta_Framework_Commands.c> +#include <ccnx/transport/transport_rta/core/rta_Framework_private.h> +#include <ccnx/transport/transport_rta/config/config_All.h> +#include <ccnx/transport/test_tools/bent_pipe.h> + +typedef struct test_data { + PARCRingBuffer1x1 *commandRingBuffer; + PARCNotifier *commandNotifier; + RtaFramework *framework; + + int api_fds[2]; + int listen_fd; + int rnd_fd; + + int stackId; + RtaConnection *connectionUnderTest; + + char bentpipe_LocalName[1024]; + BentPipeState *bentpipe; + char keystoreName[1024]; + char keystorePassword[1024]; +} TestData; + +static CCNxTransportConfig * +_createParams(const char *local_name, const char *keystore_name, const char *keystore_passwd) +{ + 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 = apiConnector_ProtocolStackConfig( + testingUpper_ProtocolStackConfig( + localForwarder_ProtocolStackConfig( + protocolStack_ComponentsConfigArgs(ccnxStackConfig_Create(), + apiConnector_GetName(), + testingUpper_GetName(), + localForwarder_GetName(), NULL)))); + + CCNxConnectionConfig *connConfig = apiConnector_ConnectionConfig( + testingUpper_ConnectionConfig( + tlvCodec_ConnectionConfig( + localForwarder_ConnectionConfig( + ccnxConnectionConfig_Create(), local_name)))); + + publicKeySigner_ConnectionConfig(connConfig, keystore_name, keystore_passwd); + + CCNxTransportConfig *result = ccnxTransportConfig_Create(stackConfig, connConfig); + ccnxStackConfig_Release(&stackConfig); + return result; +} + +static TestData * +_commonSetup(void) +{ + parcSecurity_Init(); + TestData *data = parcMemory_AllocateAndClear(sizeof(TestData)); + assertNotNull(data, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(TestData)); + + sprintf(data->bentpipe_LocalName, "/tmp/bentpipe_%d.sock", getpid()); + data->bentpipe = bentpipe_Create(data->bentpipe_LocalName); + bentpipe_Start(data->bentpipe); + + sprintf(data->keystoreName, "/tmp/keystore_%d.p12", getpid()); + sprintf(data->keystorePassword, "23439429"); + + unlink(data->keystoreName); + + bool success = parcPkcs12KeyStore_CreateFile(data->keystoreName, data->keystorePassword, "user", 1024, 30); + assertTrue(success, "parcPkcs12KeyStore_CreateFile() failed."); + + data->commandRingBuffer = parcRingBuffer1x1_Create(128, NULL); + data->commandNotifier = parcNotifier_Create(); + data->framework = rtaFramework_Create(data->commandRingBuffer, data->commandNotifier); + + // Create a protocol stack and a connection to use + CCNxTransportConfig *params = _createParams(data->bentpipe_LocalName, data->keystoreName, data->keystorePassword); + + data->stackId = 1; + + RtaCommandCreateProtocolStack *createStack = + rtaCommandCreateProtocolStack_Create(data->stackId, ccnxTransportConfig_GetStackConfig(params)); + + _rtaFramework_ExecuteCreateStack(data->framework, createStack); + rtaCommandCreateProtocolStack_Release(&createStack); + + socketpair(PF_LOCAL, SOCK_STREAM, 0, data->api_fds); + RtaCommandOpenConnection *openConnection = rtaCommandOpenConnection_Create(data->stackId, data->api_fds[0], data->api_fds[1], + ccnxConnectionConfig_GetJson(ccnxTransportConfig_GetConnectionConfig(params))); + _rtaFramework_ExecuteOpenConnection(data->framework, openConnection); + rtaCommandOpenConnection_Release(&openConnection); + + ccnxTransportConfig_Destroy(¶ms); + + // now poke in to the connection table to get the connection to test + data->connectionUnderTest = rtaConnectionTable_GetByApiFd(data->framework->connectionTable, data->api_fds[0]); + + return data; +} + +static void +_commonTeardown(TestData *data) +{ + rtaFramework_Teardown(data->framework); + + parcRingBuffer1x1_Release(&data->commandRingBuffer); + parcNotifier_Release(&data->commandNotifier); + rtaFramework_Destroy(&data->framework); + + bentpipe_Stop(data->bentpipe); + bentpipe_Destroy(&data->bentpipe); + unlink(data->keystoreName); + parcMemory_Deallocate((void **) &data); + parcSecurity_Fini(); +} + +// ============================================================= + +LONGBOW_TEST_RUNNER(connector_Forwarder_Local) +{ + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(connector_Forwarder_Local) +{ + 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(connector_Forwarder_Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + + +// ====================================================== +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, connector_Fwd_Local_Init_Release); + LONGBOW_RUN_TEST_CASE(Local, connector_Fwd_Local_Cpi_Pause); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + longBowTestCase_SetClipBoardData(testCase, _commonSetup()); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + _commonTeardown(longBowTestCase_GetClipBoardData(testCase)); + + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 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(Local, connector_Fwd_Local_Init_Release) +{ + // nothing to do, just checking that memory is in balance in teardown +} + +/** + * Send a PAUSE CPI message to the forwarder. It should reflect + * back a CPI ACK + */ +LONGBOW_TEST_CASE(Local, connector_Fwd_Local_Cpi_Pause) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + PARCJSON *controlPause = cpi_CreatePauseInputRequest(); + + CCNxTlvDictionary *controlDictionary = ccnxControlFacade_CreateCPI(controlPause); + TransportMessage *tm_in = transportMessage_CreateFromDictionary(controlDictionary); + + uint64_t pause_seqnum = controlPlaneInterface_GetSequenceNumber(controlPause); + parcJSON_Release(&controlPause); + ccnxTlvDictionary_Release(&controlDictionary); + + transportMessage_SetInfo(tm_in, rtaConnection_Copy(data->connectionUnderTest), (void (*)(void **))rtaConnection_Destroy); + + PARCEventQueue *in = rtaProtocolStack_GetPutQueue(rtaConnection_GetStack(data->connectionUnderTest), TESTING_UPPER, RTA_DOWN); + PARCEventQueue *out = rtaProtocolStack_GetPutQueue(rtaConnection_GetStack(data->connectionUnderTest), TESTING_UPPER, RTA_DOWN); + + rtaComponent_PutMessage(in, tm_in); + + // this will crank it though the forwarder and reflect back up to us + rtaFramework_NonThreadedStepCount(data->framework, 4); + + // The first message out should be a CONNECTION_OPEN + TransportMessage *tm_out = rtaComponent_GetMessage(out); + transportMessage_Destroy(&tm_out); + + tm_out = rtaComponent_GetMessage(out); + + assertTrue(transportMessage_IsControl(tm_out), "got wrong type, not a control message"); + + CCNxControl *control = ccnxMetaMessage_GetControl(transportMessage_GetDictionary(tm_out)); + + assertTrue(ccnxControl_IsACK(control), "Expected ccnxControl_IsACK to be true."); + + uint64_t _ack_original_seqnum = ccnxControl_GetAckOriginalSequenceNumber(control); + + assertTrue(_ack_original_seqnum == pause_seqnum, + "Got wrong original message seqnum, expected %" PRIu64 " got %" PRIu64, pause_seqnum, _ack_original_seqnum); + + transportMessage_Destroy(&tm_out); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(connector_Forwarder_Local); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/libccnx-transport-rta/ccnx/transport/transport_rta/connectors/test/test_connector_Forwarder_Metis.c b/libccnx-transport-rta/ccnx/transport/transport_rta/connectors/test/test_connector_Forwarder_Metis.c new file mode 100644 index 00000000..6fe9e3d2 --- /dev/null +++ b/libccnx-transport-rta/ccnx/transport/transport_rta/connectors/test/test_connector_Forwarder_Metis.c @@ -0,0 +1,1350 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * + * This test will setup a server socket so the Metis connector can connect to it. We can + * then see the packets the connector thinks it is sending to Metis. + */ + +#define DEBUG 1 +#include "../connector_Forwarder_Metis.c" + +#include <parc/algol/parc_SafeMemory.h> +#include <parc/security/parc_Pkcs12KeyStore.h> +#include <parc/security/parc_Security.h> + +#include <ccnx/api/control/cpi_ControlFacade.h> +#include <ccnx/api/control/controlPlaneInterface.h> +#include <ccnx/api/control/cpi_Forwarding.h> + +#include <ccnx/transport/transport_rta/core/rta_Framework_Commands.c> +#include <ccnx/transport/transport_rta/core/rta_Framework_private.h> +#include <ccnx/transport/transport_rta/config/config_All.h> + +#include <ccnx/common/codec/ccnxCodec_TlvPacket.h> +#include <ccnx/transport/test_tools/traffic_tools.h> + +#include <ccnx/common/codec/schema_v1/ccnxCodecSchemaV1_FixedHeader.h> +#include <ccnx/common/codec/schema_v1/ccnxCodecSchemaV1_PacketEncoder.h> +#include <ccnx/common/codec/schema_v1/ccnxCodecSchemaV1_Types.h> +#include <ccnx/common/codec/schema_v1/testdata/v1_interest_nameA.h> +#include <ccnx/common/codec/schema_v1/testdata/v1_content_nameA_crc32c.h> +#include <ccnx/common/codec/schema_v1/testdata/v1_cpi_add_route_crc32c.h> + +#include <ccnx/common/ccnx_WireFormatMessage.h> + +// inet_pton +#include <arpa/inet.h> + +#include <LongBow/unit-test.h> + +static char keystorename[1024]; +static const char keystorepass[] = "2398472983479234"; + +#ifndef INPORT_ANY +#define INPORT_ANY 0 +#endif + +typedef struct test_data { + PARCRingBuffer1x1 *commandRingBuffer; + PARCNotifier *commandNotifier; + + // we will bind to a random port, this is what we end up binding to + // Its in host byte order + uint16_t metis_port; + + // server_socket is a socket we listen to like the Metis forwarder, so + // we can see all the traffic that comes out the bottom of the connector. + int server_socket; + + // when we accept a client on the server socket, this is his socket + int client_socket; + + RtaFramework *framework; + CCNxTransportConfig *params; + + char keystoreName[1024]; + char keystorePassword[1024]; +} TestData; + +/* + * @function setup_server + * @abstract Bind to 127.0.0.1 on a random port, returns the socket and port + * @discussion + * <#Discussion#> + * + * @param portOutput is the port bound to in host byte order + * @return <#return#> + */ +static int +_setup_server(uint16_t *portOutput) +{ + struct sockaddr_in address; + + /* listen on 127.0.0.1 random port */ + address.sin_family = PF_INET; + address.sin_port = INPORT_ANY; + inet_pton(AF_INET, "127.0.0.1", &(address.sin_addr)); + + int fd = socket(PF_INET, SOCK_STREAM, 0); + assertFalse(fd < 0, "error on bind: (%d) %s", errno, strerror(errno)); + + // Set non-blocking flag + int flags = fcntl(fd, F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)\n", errno); + int failure = fcntl(fd, F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)\n", errno); + + failure = bind(fd, (struct sockaddr *) &address, sizeof(struct sockaddr_in)); + assertFalse(failure, "error on bind: (%d) %s", errno, strerror(errno)); + + failure = listen(fd, 16); + assertFalse(failure, "error on listen: (%d) %s", errno, strerror(errno)); + + socklen_t x = sizeof(address); + failure = getsockname(fd, (struct sockaddr *) &address, &x); + assertFalse(failure, "error on getsockname: (%d) %s", errno, strerror(errno)); + + *portOutput = htons(address.sin_port); + + printf("test server setup on port %d\n", *portOutput); + return fd; +} + +static int +_accept_client(int server_socket) +{ + socklen_t addrlen; + struct sockaddr_in address; + int client_socket; + + addrlen = sizeof(struct sockaddr_in); + client_socket = accept(server_socket, (struct sockaddr *) &address, &addrlen); + assertFalse(client_socket < 0, "accept error: %s", strerror(errno)); + + printf("%s accepted client on socket %d\n", __func__, client_socket); + return client_socket; +} + +static RtaConnection * +_openConnection(TestData *data, int stack_id, int fds[2]) +{ + RtaCommandOpenConnection *openConnection = rtaCommandOpenConnection_Create(stack_id, fds[0], fds[1], + ccnxConnectionConfig_GetJson(ccnxTransportConfig_GetConnectionConfig(data->params))); + _rtaFramework_ExecuteOpenConnection(data->framework, openConnection); + rtaCommandOpenConnection_Release(&openConnection); + + return rtaConnectionTable_GetByApiFd(data->framework->connectionTable, fds[0]); +} + +static void +_createStack(TestData *data, int stack_id) +{ + RtaCommandCreateProtocolStack *createStack = + rtaCommandCreateProtocolStack_Create(stack_id, ccnxTransportConfig_GetStackConfig(data->params)); + _rtaFramework_ExecuteCreateStack(data->framework, createStack); + rtaCommandCreateProtocolStack_Release(&createStack); +} + +static CCNxTransportConfig * +_createParams(int port, const char *keystore_name, const char *keystore_passwd) +{ + CCNxStackConfig *stackConfig; + CCNxConnectionConfig *connConfig; + + assertNotNull(keystore_name, "Got null keystore name\n"); + assertNotNull(keystore_passwd, "Got null keystore passwd\n"); + + stackConfig = apiConnector_ProtocolStackConfig( + testingUpper_ProtocolStackConfig( + metisForwarder_ProtocolStackConfig( + protocolStack_ComponentsConfigArgs(ccnxStackConfig_Create(), + apiConnector_GetName(), + testingUpper_GetName(), + metisForwarder_GetName(), NULL)))); + + connConfig = apiConnector_ConnectionConfig( + testingUpper_ConnectionConfig( + metisForwarder_ConnectionConfig( + tlvCodec_ConnectionConfig( + ccnxConnectionConfig_Create()), port))); + + publicKeySigner_ConnectionConfig(connConfig, keystore_name, keystore_passwd); + + CCNxTransportConfig *result = ccnxTransportConfig_Create(stackConfig, connConfig); + ccnxStackConfig_Release(&stackConfig); + return result; +} + +static TestData * +_commonSetup(void) +{ + parcSecurity_Init(); + + TestData *data = parcMemory_AllocateAndClear(sizeof(TestData)); + assertNotNull(data, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(TestData)); + memset(data, 0, sizeof(TestData)); + + data->server_socket = _setup_server(&data->metis_port); + + // printf("%s listening on port %u\n", __func__, data->metis_port); + + sprintf(data->keystoreName, "%s", keystorename); + sprintf(data->keystorePassword, keystorepass); + + data->commandRingBuffer = parcRingBuffer1x1_Create(128, NULL); + data->commandNotifier = parcNotifier_Create(); + data->framework = rtaFramework_Create(data->commandRingBuffer, data->commandNotifier); + + data->params = _createParams(data->metis_port, data->keystoreName, keystorepass); + // we will always create stack #1 as the default stack + _createStack(data, 1); + return data; +} + +static void +_commonTeardown(TestData *data) +{ + if (data != NULL) { + if (data->server_socket > 0) { + close(data->server_socket); + } + + if (data->client_socket > 0) { + close(data->client_socket); + } + + ccnxTransportConfig_Destroy(&data->params); + rtaFramework_Teardown(data->framework); + + parcRingBuffer1x1_Release(&data->commandRingBuffer); + parcNotifier_Release(&data->commandNotifier); + rtaFramework_Destroy(&data->framework); + parcMemory_Deallocate((void **) &data); + } + parcSecurity_Fini(); +} + +// ====================================================== +// helper functions + +/** + * Wait for a READ event on the specifid socket. Has a 1 second timeout. + * + * @return true if READ event + * @return false otherwise + */ +static bool +_waitForSelect(int fd) +{ + fd_set readset; + FD_ZERO(&readset); + FD_SET(fd, &readset); + int result = select(fd + 1, &readset, NULL, NULL, &(struct timeval) { 1, 0 }); + assertFalse(result < 0, "Error on select: (%d) %s", errno, strerror(errno)); + assertFalse(result == 0, "Timeout waiting for connection attempt"); + assertTrue(FD_ISSET(fd, &readset), "server_socket was not set by select"); + + return true; +} + + + +static size_t +_sendPacketToConnectorV1(int fd, size_t payloadLength) +{ + // Setup the header + uint8_t headerLength = 13; + uint16_t packetLength = payloadLength + headerLength; + uint8_t packetType = CCNxCodecSchemaV1Types_PacketType_Interest; + + CCNxCodecSchemaV1FixedHeader hdr = { .version = 1, .packetType = packetType, .packetLength = htons(packetLength), .headerLength = headerLength }; + + // put header in packet and write the packet + uint8_t packet[1024]; + memcpy(packet, &hdr, sizeof(hdr)); + + // write out exactly the number of bytes we need + size_t writeSize = packetLength; + + ssize_t nwritten = write(fd, packet, writeSize); + assertTrue(nwritten == writeSize, "Wrong write size, expected %zu got %zd", writeSize, nwritten); + return writeSize; +} + +static RtaConnection * +setupConnectionAndClientSocket(TestData *data, int *apiSocketOuptut, int *clientSocketOutput) +{ + // Open a listener and accept the forwarders connection + int fds[2]; + socketpair(PF_LOCAL, SOCK_STREAM, 0, fds); + RtaConnection *conn = _openConnection(data, 1, fds); + assertNotNull(conn, "Got null connection opening on stack 1"); + + rtaFramework_NonThreadedStepCount(data->framework, 2); + + // we should now see a connection request + _waitForSelect(data->server_socket); + + // accept the client and set a 1 second read timeout on the socket + int client_fd = _accept_client(data->server_socket); + struct timeval readTimeout = { 1, 0 }; + setsockopt(client_fd, SOL_SOCKET, SO_RCVTIMEO, (char *) &readTimeout, sizeof(readTimeout)); + + *apiSocketOuptut = fds[0]; + *clientSocketOutput = client_fd; + return conn; +} + +// throw away the first control message +static void +_throwAwayControlMessage(PARCEventQueue *out) +{ + TransportMessage *control_tm = rtaComponent_GetMessage(out); + assertNotNull(control_tm, "Did not receive a transport message out of the top of the connector"); + assertTrue(transportMessage_IsControl(control_tm), + "transport message is not a control message") + { + ccnxTlvDictionary_Display(transportMessage_GetDictionary(control_tm), 0); + } + transportMessage_Destroy(&control_tm); +} + +static void +_testReadPacketV1(size_t extraBytes) +{ + const int REMOTE = 0; + const int STACK = 1; + int fds[2]; + socketpair(PF_LOCAL, SOCK_STREAM, 0, fds); + + PARCEventScheduler *scheduler = parcEventScheduler_Create(); + FwdMetisState *fwd_state = connector_Fwd_Metis_CreateConnectionState(scheduler); + + // this replaces "_openSocket" + fwd_state->fd = fds[STACK]; + + _setupSocket(fwd_state); + + // Setup the header + uint16_t packetLength = 24; + uint8_t headerLength = 13; + uint8_t packetType = CCNxCodecSchemaV1Types_PacketType_Interest; + + CCNxCodecSchemaV1FixedHeader hdr = { .version = 1, .packetType = packetType, .packetLength = htons(packetLength), .headerLength = headerLength }; + + // put header in packet and write the packet + uint8_t packet[1024]; + memcpy(packet, &hdr, sizeof(hdr)); + + // write out exactly the number of bytes we need + size_t firstWrite = packetLength; + + ssize_t nwritten = write(fds[REMOTE], packet, firstWrite + extraBytes); + assertTrue(nwritten == firstWrite + extraBytes, "Wrong write size, expected %zu got %zd", + firstWrite + extraBytes, nwritten); + + ReadReturnCode readCode = _readPacket(fwd_state); + + assertTrue(readCode == ReadReturnCode_Finished, "readCode should be %d got %d", ReadReturnCode_Finished, readCode); + + // should indicate there's nothing left to read of the header + assertTrue(fwd_state->nextMessage.remainingReadLength == 0, "Remaining length should be 0 got %zu", fwd_state->nextMessage.remainingReadLength); + + // we should be at position "firstWrite" in the packet buffer + assertNotNull(fwd_state->nextMessage.packet, "Packet buffer is null"); + assertTrue(parcBuffer_Position(fwd_state->nextMessage.packet) == firstWrite, + "Wrong position, expected %zu got %zu", firstWrite, parcBuffer_Position(fwd_state->nextMessage.packet)); + + // cleanup + _fwdMetisState_Release(&fwd_state); + parcEventScheduler_Destroy(&scheduler); + close(fds[REMOTE]); +} + + + +static void +_testReadFromMetisFromArray(TestData *data, size_t length, uint8_t buffer[length]) +{ + // create our connection. This will become part of the RTA framework, so will be + // cleaned up in the teardown + int api_fd; + int client_fd; + RtaConnection *conn = setupConnectionAndClientSocket(data, &api_fd, &client_fd); + + ssize_t nwritten = write(client_fd, buffer, length); + assertTrue(nwritten == length, "Wrong write size, expected %zu got %zd", length, nwritten); + + FwdMetisState *fwd_state = (FwdMetisState *) rtaConnection_GetPrivateData(conn, FWD_METIS);; + + _readFromMetis(fwd_state, conn); + + // now crank the handle to pop those messages up the stack + rtaFramework_NonThreadedStepCount(data->framework, 5); + + PARCEventQueue *out = rtaProtocolStack_GetPutQueue(rtaConnection_GetStack(conn), TESTING_UPPER, RTA_DOWN); + _throwAwayControlMessage(out); + + // verify the wire format is what we wrote + TransportMessage *test_tm = rtaComponent_GetMessage(out); + assertNotNull(test_tm, "Did not receive a transport message out of the top of the connector"); + + CCNxTlvDictionary *testDictionary = transportMessage_GetDictionary(test_tm); + PARCBuffer *writeFormat = ccnxWireFormatMessage_GetWireFormatBuffer(testDictionary); + assertNotNull(writeFormat, + "transport message does not have a wire format"); + + PARCBuffer *truth = parcBuffer_Wrap(buffer, length, 0, length); + assertTrue(parcBuffer_Equals(truth, writeFormat), "Wire format does not match expected") + { + printf("Expected:\n"); + parcBuffer_Display(truth, 3); + printf("Received:\n"); + parcBuffer_Display(writeFormat, 3); + } + + parcBuffer_Release(&truth); + transportMessage_Destroy(&test_tm); +} + + +// ====================================================== + +LONGBOW_TEST_RUNNER(connector_Forwarder_Metis) +{ + LONGBOW_RUN_TEST_FIXTURE(Local); + + LONGBOW_RUN_TEST_FIXTURE(UpDirectionV1); + LONGBOW_RUN_TEST_FIXTURE(DownDirectionV1); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(connector_Forwarder_Metis) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + + snprintf(keystorename, 1024, "/tmp/keystore_%d.p12", getpid()); + + // init + fini here so there's no memory imbalance + parcSecurity_Init(); + parcPkcs12KeyStore_CreateFile(keystorename, keystorepass, "ccnxuser", 1024, 365); + parcSecurity_Fini(); + + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(connector_Forwarder_Metis) +{ + unlink(keystorename); + return LONGBOW_STATUS_SUCCEEDED; +} + +// ====================================================== +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, connector_Fwd_Metis_Init_Release); + LONGBOW_RUN_TEST_CASE(Local, connector_Fwd_Metis_Opener_GoodPort); + LONGBOW_RUN_TEST_CASE(Local, _fwdMetisState_Release); + LONGBOW_RUN_TEST_CASE(Local, _readInEnvironmentConnectionSpecification); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + longBowTestCase_SetClipBoardData(testCase, _commonSetup()); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + _commonTeardown(longBowTestCase_GetClipBoardData(testCase)); + + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 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(Local, connector_Fwd_Metis_Init_Release) +{ + // nothing to do, just checking that memory is in balance in teardown +} + +/** + * Call the opener with the right port. We should see a connection attempt on + * the server socket and be able to accept it. + */ +LONGBOW_TEST_CASE(Local, connector_Fwd_Metis_Opener_GoodPort) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + int fds[2]; + socketpair(PF_LOCAL, SOCK_STREAM, 0, fds); + RtaConnection *conn = _openConnection(data, 1, fds); + assertNotNull(conn, "Got null connection opening on stack 1"); + + rtaFramework_NonThreadedStepCount(data->framework, 2); + + // we should now see a connection request + _waitForSelect(data->server_socket); + + close(fds[1]); +} + +/** + * Make sure everything is released and file descriptor is closed + */ +LONGBOW_TEST_CASE(Local, _fwdMetisState_Release) +{ + const int REMOTE = 0; + const int STACK = 1; + int fds[2]; + socketpair(PF_LOCAL, SOCK_STREAM, 0, fds); + + PARCEventScheduler *scheduler = parcEventScheduler_Create(); + FwdMetisState *fwd_state = connector_Fwd_Metis_CreateConnectionState(scheduler); + + // this replaces "_openSocket" + fwd_state->fd = fds[STACK]; + + _setupSocket(fwd_state); + + + _fwdMetisState_Release(&fwd_state); + parcEventScheduler_Destroy(&scheduler); + + // ensure that fds[STACK] is closed by _fwdMetisState_Release + uint8_t buffer[16]; + ssize_t nread = recv(fds[STACK], buffer, 16, 0); + assertTrue(nread == -1 && errno == EBADF, + "read from closed socket %d should be EBADF, got return %zd and errno (%d) %s", + fds[STACK], nread, errno, strerror(errno)); + + close(fds[REMOTE]); +} + +LONGBOW_TEST_CASE(Local, _readInEnvironmentConnectionSpecification) +{ + char *oldEnv = getenv(FORWARDER_CONNECTION_ENV); + setenv(FORWARDER_CONNECTION_ENV, "tcp://127.0.0.1:9999", 1); + struct sockaddr_in addr_in; + _readInEnvironmentConnectionSpecification(&addr_in); + assertTrue(addr_in.sin_port == htons(9999), "Port specification incorrectly parsed"); + assertTrue(addr_in.sin_addr.s_addr == inet_addr("127.0.0.1"), "Address specification incorrectly parsed");; + if (oldEnv) { + setenv(FORWARDER_CONNECTION_ENV, oldEnv, 1); + } else { + unsetenv(FORWARDER_CONNECTION_ENV); + } +} + + + +// ==================================================================== + + +LONGBOW_TEST_FIXTURE(UpDirectionV1) +{ + LONGBOW_RUN_TEST_CASE(UpDirectionV1, _readPacketHeader_ExactFit); + LONGBOW_RUN_TEST_CASE(UpDirectionV1, _readPacketHeader_TwoReads); + LONGBOW_RUN_TEST_CASE(UpDirectionV1, _setupNextPacket); + + LONGBOW_RUN_TEST_CASE(UpDirectionV1, _readPacket_PartialMessage); + LONGBOW_RUN_TEST_CASE(UpDirectionV1, _readPacket_ExactlyOneMessage); + LONGBOW_RUN_TEST_CASE(UpDirectionV1, _readPacket_MoreThanOneMessage); + + LONGBOW_RUN_TEST_CASE(UpDirectionV1, _readFromMetis_ThreeMessages); + + LONGBOW_RUN_TEST_CASE(UpDirectionV1, _readFromMetis_InterestV1); + LONGBOW_RUN_TEST_CASE(UpDirectionV1, _readFromMetis_ContentObjectV1); + LONGBOW_RUN_TEST_CASE(UpDirectionV1, _readFromMetis_ControlV1); + + LONGBOW_RUN_TEST_CASE(UpDirectionV1, _readPacketHeader_Error); + LONGBOW_RUN_TEST_CASE(UpDirectionV1, _readPacketBody_Error); + + LONGBOW_RUN_TEST_CASE(UpDirectionV1, _readPacketHeader_Closed); + LONGBOW_RUN_TEST_CASE(UpDirectionV1, _readPacketBody_Closed); + LONGBOW_RUN_TEST_CASE(UpDirectionV1, _readFromMetis_Closed); +} + +LONGBOW_TEST_FIXTURE_SETUP(UpDirectionV1) +{ + longBowTestCase_SetClipBoardData(testCase, _commonSetup()); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(UpDirectionV1) +{ + _commonTeardown(longBowTestCase_GetClipBoardData(testCase)); + + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + printf("('%s' leaks memory by %d (allocs - frees)) ", longBowTestCase_GetName(testCase), parcMemory_Outstanding()); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +/** + * Put in exactly 8 bytes. + * This should return NULL, but will set nextMessageLength to be the right thing. + * Does not drain the buffer + */ +LONGBOW_TEST_CASE(UpDirectionV1, _readPacketHeader_ExactFit) +{ + const int REMOTE = 0; + const int STACK = 1; + int fds[2]; + socketpair(PF_LOCAL, SOCK_STREAM, 0, fds); + + PARCEventScheduler *scheduler = parcEventScheduler_Create(); + FwdMetisState *fwd_state = connector_Fwd_Metis_CreateConnectionState(scheduler); + + // this replaces "_openSocket" + fwd_state->fd = fds[STACK]; + + _setupSocket(fwd_state); + + // Setup the header + uint16_t packetLength = 24; + uint8_t headerLength = 13; + uint8_t packetType = CCNxCodecSchemaV1Types_PacketType_Interest; + + CCNxCodecSchemaV1FixedHeader hdr = { .version = 1, .packetType = packetType, .packetLength = htons(packetLength), .headerLength = headerLength }; + + // put header in packet and write the packet + uint8_t packet[1024]; + size_t bufferReadLength = sizeof(hdr); + memcpy(packet, &hdr, bufferReadLength); + + // write out exactly the number of bytes we need + ssize_t nwritten = write(fds[REMOTE], packet, sizeof(CCNxCodecSchemaV1FixedHeader)); + assertTrue(nwritten == sizeof(CCNxCodecSchemaV1FixedHeader), "Wrong write size, expected %zu got %zd", + sizeof(CCNxCodecSchemaV1FixedHeader), nwritten); + + // test the function + ReadReturnCode readCode = _readPacketHeader(fwd_state); + assertTrue(readCode == ReadReturnCode_Finished, "readCode should be %d got %d", ReadReturnCode_Finished, readCode); + assertTrue(fwd_state->nextMessage.remainingReadLength == 0, "Remaining length should be 0 got %zu", fwd_state->nextMessage.remainingReadLength); + + // other properties are tested as part of _setupNextPacket + + // cleanup + _fwdMetisState_Release(&fwd_state); + parcEventScheduler_Destroy(&scheduler); + close(fds[REMOTE]); +} + +/* + * Write the fixed header in two 4 byte writes + */ +LONGBOW_TEST_CASE(UpDirectionV1, _readPacketHeader_TwoReads) +{ + const int REMOTE = 0; + const int STACK = 1; + int fds[2]; + socketpair(PF_LOCAL, SOCK_STREAM, 0, fds); + + PARCEventScheduler *scheduler = parcEventScheduler_Create(); + FwdMetisState *fwd_state = connector_Fwd_Metis_CreateConnectionState(scheduler); + + // this replaces "_openSocket" + fwd_state->fd = fds[STACK]; + + _setupSocket(fwd_state); + + // Setup the header + uint16_t packetLength = 24; + uint8_t headerLength = 13; + uint8_t packetType = CCNxCodecSchemaV1Types_PacketType_Interest; + + CCNxCodecSchemaV1FixedHeader hdr = { + .version = 1, + .packetType = packetType, + .packetLength = htons(packetLength), + .headerLength = headerLength + }; + + // put header in packet and write the packet + uint8_t packet[1024]; + size_t bufferReadLength = sizeof(hdr); + memcpy(packet, &hdr, bufferReadLength); + + // write out exactly the number of bytes we need + size_t firstWrite = 4; + size_t secondWrite = sizeof(CCNxCodecSchemaV1FixedHeader) - firstWrite; + + ssize_t nwritten = write(fds[REMOTE], packet, firstWrite); + assertTrue(nwritten == firstWrite, "Wrong write size, expected %zu got %zd", firstWrite, nwritten); + + ReadReturnCode readCode = _readPacketHeader(fwd_state); + assertTrue(readCode == ReadReturnCode_PartialRead, "readCode should be %d got %d", ReadReturnCode_PartialRead, readCode); + + nwritten = write(fds[REMOTE], packet + firstWrite, secondWrite); + assertTrue(nwritten == secondWrite, "Wrong write size, expected %zu got %zd", secondWrite, nwritten); + + readCode = _readPacketHeader(fwd_state); + assertTrue(readCode == ReadReturnCode_Finished, "readCode should be %d got %d", ReadReturnCode_Finished, readCode); + + assertTrue(fwd_state->nextMessage.remainingReadLength == 0, "Remaining length should be 0 got %zu", fwd_state->nextMessage.remainingReadLength); + + // other properties are tested as part of _setupNextPacket + + // cleanup + _fwdMetisState_Release(&fwd_state); + parcEventScheduler_Destroy(&scheduler); + close(fds[REMOTE]); +} + +LONGBOW_TEST_CASE(UpDirectionV1, _setupNextPacket) +{ + uint16_t packetLength = 24; + uint8_t headerLength = 13; + uint8_t packetType = CCNxCodecSchemaV1Types_PacketType_Interest; + uint8_t version = 1; + CCNxCodecSchemaV1FixedHeader hdr = { .version = version, .packetType = packetType, .packetLength = htons(packetLength), .headerLength = headerLength }; + + // setup fwd_state->nextMessage like we just read a header + PARCEventScheduler *scheduler = parcEventScheduler_Create(); + FwdMetisState *fwd_state = connector_Fwd_Metis_CreateConnectionState(scheduler); + fwd_state->nextMessage.remainingReadLength = 0; + memcpy(&fwd_state->nextMessage.fixedHeader, &hdr, sizeof(hdr)); + + // this is the truth we will test against + size_t nextMessageLength = packetLength; + + _setupNextPacket(fwd_state); + + size_t allocatedLength = parcBuffer_Capacity(fwd_state->nextMessage.packet); + size_t position = parcBuffer_Position(fwd_state->nextMessage.packet); + parcBuffer_Flip(fwd_state->nextMessage.packet); + void *buffer = parcBuffer_Overlay(fwd_state->nextMessage.packet, 0); + + assertTrue(fwd_state->nextMessage.length == nextMessageLength, "Wrong packet length, expected %zu got %zu", nextMessageLength, fwd_state->nextMessage.length); + assertTrue(fwd_state->nextMessage.packetType == packetType, "Wrong packetType, expected %u got %u", packetType, fwd_state->nextMessage.packetType); + assertTrue(fwd_state->nextMessage.version == version, "Wrong version, expected %u got %u", version, fwd_state->nextMessage.version); + assertTrue(allocatedLength == nextMessageLength, "Wrong packet buffer length, expected %zu got %zu", nextMessageLength, allocatedLength); + + // and make sure the beginning of the buffer is the fixed header + assertTrue(position == sizeof(hdr), "Wrong write position, expected %zu got %zu", sizeof(hdr), position); + assertTrue(memcmp(buffer, &hdr, sizeof(hdr)) == 0, "Beginning of buffer not the fixed header"); + + // TODO: Finish me + _fwdMetisState_Release(&fwd_state); + parcEventScheduler_Destroy(&scheduler); +} + +/** + * Write the fixed header plus part of the message body. + */ +LONGBOW_TEST_CASE(UpDirectionV1, _readPacket_PartialMessage) +{ + const int REMOTE = 0; + const int STACK = 1; + int fds[2]; + socketpair(PF_LOCAL, SOCK_STREAM, 0, fds); + + PARCEventScheduler *scheduler = parcEventScheduler_Create(); + FwdMetisState *fwd_state = connector_Fwd_Metis_CreateConnectionState(scheduler); + + // this replaces "_openSocket" + fwd_state->fd = fds[STACK]; + + _setupSocket(fwd_state); + + // Setup the header + uint16_t packetLength = 160; + uint8_t headerLength = 13; + uint8_t packetType = CCNxCodecSchemaV1Types_PacketType_Interest; + uint8_t version = 1; + CCNxCodecSchemaV1FixedHeader hdr = { .version = version, .packetType = packetType, .packetLength = htons(packetLength), .headerLength = headerLength }; + + // put header in packet and write the packet + uint8_t packet[1024]; + memcpy(packet, &hdr, sizeof(hdr)); + + // write out exactly the number of bytes we need + size_t firstWrite = 100; + + ssize_t nwritten = write(fds[REMOTE], packet, firstWrite); + assertTrue(nwritten == firstWrite, "Wrong write size, expected %zu got %zd", firstWrite, nwritten); + + ReadReturnCode readCode = _readPacket(fwd_state); + + assertTrue(readCode == ReadReturnCode_PartialRead, "return value should be %d got %d", ReadReturnCode_PartialRead, readCode); + + // should indicate there's nothing left to read of the header + assertTrue(fwd_state->nextMessage.remainingReadLength == 0, "Remaining length should be 0 got %zu", fwd_state->nextMessage.remainingReadLength); + + // we should be at position "firstWrite" in the packet buffer + assertNotNull(fwd_state->nextMessage.packet, "Packet buffer is null"); + assertTrue(parcBuffer_Position(fwd_state->nextMessage.packet) == firstWrite, + "Wrong position, expected %zu got %zu", firstWrite, parcBuffer_Position(fwd_state->nextMessage.packet)); + + // cleanup + _fwdMetisState_Release(&fwd_state); + parcEventScheduler_Destroy(&scheduler); + close(fds[REMOTE]); +} + +/** + * Write exactly one message + */ +LONGBOW_TEST_CASE(UpDirectionV1, _readPacket_ExactlyOneMessage) +{ + _testReadPacketV1(0); +} + +/** + * Write more than one message. + */ +LONGBOW_TEST_CASE(UpDirectionV1, _readPacket_MoreThanOneMessage) +{ + _testReadPacketV1(100); +} + +/** + * Make 3 messages pending on the read socket and make sure _readFromMetis delivers all + * 3 up the stack. _readFromMetis requires an RtaConnection, so we need a mock framework. + */ +LONGBOW_TEST_CASE(UpDirectionV1, _readFromMetis_ThreeMessages) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // create our connection. This will become part of the RTA framework, so will be + // cleaned up in the teardown + int api_fd; + int client_fd; + RtaConnection *conn = setupConnectionAndClientSocket(data, &api_fd, &client_fd); + + // Write three wire format packets up the bottom of the connector + const int loopCount = 3; + size_t writeSizes[loopCount]; + + for (int i = 0; i < loopCount; i++) { + writeSizes[i] = _sendPacketToConnectorV1(client_fd, (i + 1) * 100); + } + + FwdMetisState *fwd_state = (FwdMetisState *) rtaConnection_GetPrivateData(conn, FWD_METIS);; + + _readFromMetis(fwd_state, conn); + + // now crank the handle to pop those messages up the stack + rtaFramework_NonThreadedStepCount(data->framework, 5); + + // now read the message out of the test component + PARCEventQueue *out = rtaProtocolStack_GetPutQueue(rtaConnection_GetStack(conn), TESTING_UPPER, RTA_DOWN); + + // throw away the first control message + _throwAwayControlMessage(out); + + // Now read the actual messages we want to test + for (int i = 0; i < loopCount; i++) { + TransportMessage *test_tm = rtaComponent_GetMessage(out); + assertNotNull(test_tm, "Did not receive a transport message %d out of %d out of the top of the connector", i + 1, loopCount); + + assertTrue(transportMessage_IsInterest(test_tm), + "second transport message is not an interest") + { + ccnxTlvDictionary_Display(transportMessage_GetDictionary(test_tm), 0); + } + + // Make sure the transport message has the right properties + CCNxTlvDictionary *testDictionary = transportMessage_GetDictionary(test_tm); + PARCBuffer *writeFormat = ccnxWireFormatMessage_GetWireFormatBuffer(testDictionary); + assertNotNull(writeFormat, + "transport message does not have a wire format"); + + assertTrue(parcBuffer_Remaining(writeFormat) == writeSizes[i], + "Raw format message wrong length, expected %zu got %zu", + writeSizes[i], + parcBuffer_Remaining(writeFormat)); + + // cleanup + transportMessage_Destroy(&test_tm); + } + + // no extra cleanup, done in teardown +} + +LONGBOW_TEST_CASE(UpDirectionV1, _readFromMetis_InterestV1) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _testReadFromMetisFromArray(data, sizeof(v1_interest_nameA), v1_interest_nameA); +} + +LONGBOW_TEST_CASE(UpDirectionV1, _readFromMetis_ContentObjectV1) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _testReadFromMetisFromArray(data, sizeof(v1_content_nameA_crc32c), v1_content_nameA_crc32c); +} + +LONGBOW_TEST_CASE(UpDirectionV1, _readFromMetis_ControlV1) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _testReadFromMetisFromArray(data, sizeof(v1_cpi_add_route_crc32c), v1_cpi_add_route_crc32c); +} + +/* + * read from a closed socket + */ +LONGBOW_TEST_CASE(UpDirectionV1, _readPacketHeader_Closed) +{ + const int REMOTE = 0; + const int STACK = 1; + int fds[2]; + socketpair(PF_LOCAL, SOCK_STREAM, 0, fds); + + PARCEventScheduler *scheduler = parcEventScheduler_Create(); + FwdMetisState *fwd_state = connector_Fwd_Metis_CreateConnectionState(scheduler); + fwd_state->fd = fds[STACK]; + _setupSocket(fwd_state); + + // close remote side then try to write to it + close(fds[REMOTE]); + + ReadReturnCode readCode = _readPacketHeader(fwd_state); + + _fwdMetisState_Release(&fwd_state); + parcEventScheduler_Destroy(&scheduler); + + assertTrue(readCode == ReadReturnCode_Closed, "Wrong return code, expected %d got %d", ReadReturnCode_Closed, readCode); +} + +LONGBOW_TEST_CASE(UpDirectionV1, _readPacketBody_Closed) +{ + const int REMOTE = 0; + const int STACK = 1; + int fds[2]; + socketpair(PF_LOCAL, SOCK_STREAM, 0, fds); + + PARCEventScheduler *scheduler = parcEventScheduler_Create(); + FwdMetisState *fwd_state = connector_Fwd_Metis_CreateConnectionState(scheduler); + fwd_state->fd = fds[STACK]; + _setupSocket(fwd_state); + + ssize_t nwritten = write(fds[REMOTE], v1_interest_nameA, 8); + assertTrue(nwritten == 8, "Wrong write size, expected 8 got %zd", nwritten); + + // read the header to setup the read of the body + ReadReturnCode readCode; + + readCode = _readPacketHeader(fwd_state); + assertTrue(readCode == ReadReturnCode_Finished, "Did not read entire header"); + + // close remote side then try to write to it + close(fds[REMOTE]); + + // now try 2nd read + readCode = _readPacketBody(fwd_state); + + _fwdMetisState_Release(&fwd_state); + parcEventScheduler_Destroy(&scheduler); + + assertTrue(readCode == ReadReturnCode_Closed, "Wrong return code, expected %d got %d", ReadReturnCode_Closed, readCode); +} + +/* + * Set the socket to -1 to cause and error + */ +LONGBOW_TEST_CASE(UpDirectionV1, _readPacketHeader_Error) +{ + const int REMOTE = 0; + const int STACK = 1; + int fds[2]; + socketpair(PF_LOCAL, SOCK_STREAM, 0, fds); + + PARCEventScheduler *scheduler = parcEventScheduler_Create(); + FwdMetisState *fwd_state = connector_Fwd_Metis_CreateConnectionState(scheduler); + fwd_state->fd = fds[STACK]; + _setupSocket(fwd_state); + + fwd_state->fd = -1; + + // close remote side then try to write to it + + ReadReturnCode readCode = _readPacketHeader(fwd_state); + + _fwdMetisState_Release(&fwd_state); + parcEventScheduler_Destroy(&scheduler); + close(fds[STACK]); + close(fds[REMOTE]); + + assertTrue(readCode == ReadReturnCode_Error, "Wrong return code, expected %d got %d", ReadReturnCode_Error, readCode); +} + +/* + * Set the socket to -1 to cause and error + */ +LONGBOW_TEST_CASE(UpDirectionV1, _readPacketBody_Error) +{ + const int REMOTE = 0; + const int STACK = 1; + int fds[2]; + socketpair(PF_LOCAL, SOCK_STREAM, 0, fds); + + PARCEventScheduler *scheduler = parcEventScheduler_Create(); + FwdMetisState *fwd_state = connector_Fwd_Metis_CreateConnectionState(scheduler); + fwd_state->fd = fds[STACK]; + _setupSocket(fwd_state); + + ssize_t nwritten = write(fds[REMOTE], v1_interest_nameA, 8); + assertTrue(nwritten == 8, "Wrong write size, expected 8 got %zd", nwritten); + + // read the header to setup the read of the body + ReadReturnCode readCode; + + readCode = _readPacketHeader(fwd_state); + assertTrue(readCode == ReadReturnCode_Finished, "Did not read entire header"); + + // invalidate to cause an error + fwd_state->fd = -1; + + // now try 2nd read + readCode = _readPacketBody(fwd_state); + + _fwdMetisState_Release(&fwd_state); + parcEventScheduler_Destroy(&scheduler); + close(fds[STACK]); + close(fds[REMOTE]); + + assertTrue(readCode == ReadReturnCode_Error, "Wrong return code, expected %d got %d", ReadReturnCode_Error, readCode); +} + +/* + * read from a closed socket. + * This should generate a Notify message that the connection is closed + */ +LONGBOW_TEST_CASE(UpDirectionV1, _readFromMetis_Closed) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // create our connection. This will become part of the RTA framework, so will be + // cleaned up in the teardown + int api_fd; + int client_fd; + RtaConnection *conn = setupConnectionAndClientSocket(data, &api_fd, &client_fd); + FwdMetisState *fwd_state = (FwdMetisState *) rtaConnection_GetPrivateData(conn, FWD_METIS);; + + rtaFramework_NonThreadedStepCount(data->framework, 5); + + close(client_fd); + + _readFromMetis(fwd_state, conn); + + // now crank the handle to pop those messages up the stack + rtaFramework_NonThreadedStepCount(data->framework, 5); + + // now read the message out of the test component + PARCEventQueue *out = rtaProtocolStack_GetPutQueue(rtaConnection_GetStack(conn), TESTING_UPPER, RTA_DOWN); + + // throw away the first control message + _throwAwayControlMessage(out); + + TransportMessage *test_tm = rtaComponent_GetMessage(out); + assertNotNull(test_tm, "Did not receive a transport message out of the top of the connector"); + + assertTrue(transportMessage_IsControl(test_tm), + "second transport message is not a control") + { + ccnxTlvDictionary_Display(transportMessage_GetDictionary(test_tm), 0); + } + + // Make sure the transport message has the right properties + CCNxTlvDictionary *testDictionary = transportMessage_GetDictionary(test_tm); + assertTrue(ccnxControlFacade_IsNotification(testDictionary), "Control message is not Notification") + { + ccnxTlvDictionary_Display(testDictionary, 3); + } + + PARCJSON *json = ccnxControlFacade_GetJson(testDictionary); + NotifyStatus *notify = notifyStatus_ParseJSON(json); + assertTrue(notifyStatus_GetStatusCode(notify) == notifyStatusCode_CONNECTION_CLOSED, + "Wrong code, expected %d got %d", + notifyStatusCode_CONNECTION_CLOSED, + notifyStatus_GetStatusCode(notify)); + notifyStatus_Release(¬ify); + + // verify other properties + assertFalse(fwd_state->isConnected, "Forwarder state should show connection closed"); + + // cleanup + transportMessage_Destroy(&test_tm); + + // no extra cleanup, done in teardown +} + +LONGBOW_TEST_FIXTURE(DownDirectionV1) +{ + LONGBOW_RUN_TEST_CASE(DownDirectionV1, _queueMessageToMetis); + LONGBOW_RUN_TEST_CASE(DownDirectionV1, _dequeueMessagesToMetis); + LONGBOW_RUN_TEST_CASE(DownDirectionV1, _dequeueMessagesToMetis_TwoWrites); + LONGBOW_RUN_TEST_CASE(DownDirectionV1, _dequeueMessagesToMetis_Closed); + + LONGBOW_RUN_TEST_CASE(DownDirectionV1, connector_Fwd_Metis_Downcall_Read_Interst); + LONGBOW_RUN_TEST_CASE(DownDirectionV1, connector_Fwd_Metis_Downcall_Read_CPIRequest); +} + +LONGBOW_TEST_FIXTURE_SETUP(DownDirectionV1) +{ + longBowTestCase_SetClipBoardData(testCase, _commonSetup()); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(DownDirectionV1) +{ + _commonTeardown(longBowTestCase_GetClipBoardData(testCase)); + + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + printf("('%s' leaks memory by %d (allocs - frees)) ", longBowTestCase_GetName(testCase), parcMemory_Outstanding()); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +/* + * _queueMessageToMetis postconditions: + * - increases the reference count to the wireFormat + * - adds the reference to fwd_output buffer + * - increments the debugging counter fwd_metis_references_queued + */ +LONGBOW_TEST_CASE(DownDirectionV1, _queueMessageToMetis) +{ + PARCEventScheduler *scheduler = parcEventScheduler_Create(); + FwdMetisState *fwd_state = connector_Fwd_Metis_CreateConnectionState(scheduler); + PARCBuffer *wireFormat = parcBuffer_Wrap(v1_interest_nameA, sizeof(v1_interest_nameA), 0, sizeof(v1_interest_nameA)); + size_t expectedRefCount = parcObject_GetReferenceCount(wireFormat); + + _queueBufferMessageToMetis(wireFormat, fwd_state->metisOutputQueue); + + assertTrue(parcObject_GetReferenceCount(wireFormat) == expectedRefCount, + "Did not get right ref count for wire format, expected %zu got %" PRIu64, expectedRefCount, parcObject_GetReferenceCount(wireFormat)); + assertTrue(parcEventBuffer_GetLength(fwd_state->metisOutputQueue) == parcBuffer_Remaining(wireFormat), + "Wrong output buffer length, expected %zu got %zu", parcBuffer_Remaining(wireFormat), parcEventBuffer_GetLength(fwd_state->metisOutputQueue)); + + parcBuffer_Release(&wireFormat); + parcEventBuffer_Destroy(&fwd_state->metisOutputQueue); + _fwdMetisState_Release(&fwd_state); + parcEventScheduler_Destroy(&scheduler); +} + +/* + * Dequeue a small message to metis, should all be written out. + */ +LONGBOW_TEST_CASE(DownDirectionV1, _dequeueMessagesToMetis) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + int api_fd; + int client_fd; + RtaConnection *conn = setupConnectionAndClientSocket(data, &api_fd, &client_fd); + + FwdMetisState *fwd_state = (FwdMetisState *) rtaConnection_GetPrivateData(conn, FWD_METIS);; + + // Put data in the output queue + PARCBuffer *wireFormat = parcBuffer_Wrap(v1_interest_nameA, sizeof(v1_interest_nameA), 0, sizeof(v1_interest_nameA)); + _queueBufferMessageToMetis(wireFormat, fwd_state->metisOutputQueue); + + // write it out + _dequeueMessagesToMetis(fwd_state); + rtaFramework_NonThreadedStepCount(data->framework, 5); + + // we should now be able to read it + bool readReady = _waitForSelect(client_fd); + assertTrue(readReady, "client socket %d not ready for read", client_fd); + + uint8_t testArray[sizeof(v1_interest_nameA) + 1]; + ssize_t nrecv = recv(client_fd, testArray, sizeof(testArray), 0); + + assertTrue(nrecv == sizeof(v1_interest_nameA), "Wrong read length, expected %zu got %zd", sizeof(v1_interest_nameA), nrecv); + assertTrue(memcmp(testArray, v1_interest_nameA, sizeof(v1_interest_nameA)) == 0, "Read memory does not compare"); + assertTrue(parcEventBuffer_GetLength(fwd_state->metisOutputQueue) == 0, "Metis output buffer not zero length, got %zu", parcEventBuffer_GetLength(fwd_state->metisOutputQueue)); + parcEventBuffer_Destroy(&(fwd_state->metisOutputQueue)); + parcBuffer_Release(&wireFormat); +} + +/* + * Set the forwarder's send buffer small so it will take two writes to send the packet. + * This will test that when _dequeueMessagesToMetis cannot write the whole thing it will enable the + * write event and that metis will then trigger a second write when there's buffer space. + */ +LONGBOW_TEST_CASE(DownDirectionV1, _dequeueMessagesToMetis_TwoWrites) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + int api_fd; + int client_fd; + RtaConnection *conn = setupConnectionAndClientSocket(data, &api_fd, &client_fd); + + FwdMetisState *fwd_state = (FwdMetisState *) rtaConnection_GetPrivateData(conn, FWD_METIS);; + + // set the send buffer + { + // make it slightly bigger than 1/2 + const int sendBufferSize = sizeof(v1_interest_nameA) / 2 + 1; + int res = setsockopt(fwd_state->fd, SOL_SOCKET, SO_SNDBUF, &sendBufferSize, sizeof(int)); + if (res < 0) { + if (DEBUG_OUTPUT) { + printf("%9c %s failed to set SO_SNDBUF to %d: (%d) %s\n", + ' ', __func__, sendBufferSize, errno, strerror(errno)); + } + // This is a non-fatal error + } + } + + // Put data in the output queue + PARCBuffer *wireFormat = parcBuffer_Wrap(v1_interest_nameA, sizeof(v1_interest_nameA), 0, sizeof(v1_interest_nameA)); + _queueBufferMessageToMetis(wireFormat, fwd_state->metisOutputQueue); + + // write it out + _dequeueMessagesToMetis(fwd_state); + rtaFramework_NonThreadedStepCount(data->framework, 5); + + // we should now be able to read it + bool readReady = _waitForSelect(client_fd); + assertTrue(readReady, "client socket %d not ready for read", client_fd); + + uint8_t testArray[sizeof(v1_interest_nameA) + 1]; + ssize_t nrecv = recv(client_fd, testArray, sizeof(testArray), 0); + + assertTrue(nrecv == sizeof(v1_interest_nameA), "Wrong read length, expected %zu got %zd", sizeof(v1_interest_nameA), nrecv); + assertTrue(memcmp(testArray, v1_interest_nameA, sizeof(v1_interest_nameA)) == 0, "Read memory does not compare"); + assertTrue(parcEventBuffer_GetLength(fwd_state->metisOutputQueue) == 0, "Metis output buffer not zero length, got %zu", parcEventBuffer_GetLength(fwd_state->metisOutputQueue)); + parcEventBuffer_Destroy(&(fwd_state->metisOutputQueue)); + parcBuffer_Release(&wireFormat); +} + +/* + * Dequeue a message to a closed socket + */ +LONGBOW_TEST_CASE(DownDirectionV1, _dequeueMessagesToMetis_Closed) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + int api_fd; + int client_fd; + RtaConnection *conn = setupConnectionAndClientSocket(data, &api_fd, &client_fd); + + FwdMetisState *fwd_state = (FwdMetisState *) rtaConnection_GetPrivateData(conn, FWD_METIS);; + PARCBuffer *wireFormat = parcBuffer_Wrap(v1_interest_nameA, sizeof(v1_interest_nameA), 0, sizeof(v1_interest_nameA)); + _queueBufferMessageToMetis(wireFormat, fwd_state->metisOutputQueue); + + // close remote side then try to write to it + close(client_fd); + + _dequeueMessagesToMetis(fwd_state); + rtaFramework_NonThreadedStepCount(data->framework, 5); + + parcEventBuffer_Destroy(&(fwd_state->metisOutputQueue)); + parcBuffer_Release(&wireFormat); +} + +/** + * Sends an Interest down the stack. We need to create an Interest and encode its TLV wire format, + * then send it down the stack and make sure we receive it on a client socket. We don't actually + * run Metis in this test. + */ +LONGBOW_TEST_CASE(DownDirectionV1, connector_Fwd_Metis_Downcall_Read_Interst) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // create our connection. This will become part of the RTA framework, so will be + // cleaned up in the teardown + int api_fd; + int client_fd; + RtaConnection *conn = setupConnectionAndClientSocket(data, &api_fd, &client_fd); + FwdMetisState *fwd_state = (FwdMetisState *) rtaConnection_GetPrivateData(conn, FWD_METIS); + + // Create the interest with wire format and send it down the stack + TransportMessage *tm = trafficTools_CreateTransportMessageWithDictionaryInterest(conn, CCNxTlvDictionary_SchemaVersion_V1); + CCNxCodecNetworkBufferIoVec *vec = ccnxCodecSchemaV1PacketEncoder_DictionaryEncode(transportMessage_GetDictionary(tm), NULL); + + ccnxWireFormatMessage_PutIoVec(transportMessage_GetDictionary(tm), vec); + ccnxCodecNetworkBufferIoVec_Release(&vec); + + // send it down the stack + PARCEventQueue *in = rtaProtocolStack_GetPutQueue(rtaConnection_GetStack(conn), TESTING_UPPER, RTA_DOWN); + rtaComponent_PutMessage(in, tm); + rtaFramework_NonThreadedStepCount(data->framework, 5); + + bool readReady = _waitForSelect(client_fd); + assertTrue(readReady, "select did not indicate read ready"); + + // now read it from out listener. It has a read timeout so if we dont get it in a reasonable amount + // of time, read will return an error about the timeout + + const size_t maxPacketLength = 1024; + uint8_t packet[maxPacketLength]; + + ssize_t readBytes = read(client_fd, packet, maxPacketLength); + assertFalse(readBytes < 0, "Got error on read: (%d) %s", errno, strerror(errno)); + + parcEventBuffer_Destroy(&(fwd_state->metisOutputQueue)); + close(client_fd); +} + +/** + * Send an AddRoute command down the stack. It does not need a wire format in the transport message, its + * the job of the forwarder to create the Metis-specific message. + */ +LONGBOW_TEST_CASE(DownDirectionV1, connector_Fwd_Metis_Downcall_Read_CPIRequest) +{ + testUnimplemented("No way to create a v1 CPI message yet"); + +// TestData *data = longBowTestCase_GetClipBoardData(testCase); +// +// // create our connection. This will become part of the RTA framework, so will be +// // cleaned up in the teardown +// int api_fd; +// int client_fd; +// RtaConnection *conn = setupConnectionAndClientSocket(data, &api_fd, &client_fd); +// FwdMetisState *fwd_state = (FwdMetisState *) rtaConnection_GetPrivateData(conn, FWD_METIS); +// +// // now make the control message +// TransportMessage *tm = trafficTools_CreateTransportMessageWithDictionaryControl(conn, CCNxTlvDictionary_SchemaVersion_V1); +// CCNxCodecNetworkBufferIoVec *vec = ccnxCodecSchemaV1PacketEncoder_DictionaryEncode(transportMessage_GetDictionary(tm), NULL); +// ccnxWireFormatFacade_PutIoVec(transportMessage_GetDictionary(tm), vec); +// ccnxCodecNetworkBufferIoVec_Release(&vec); +// +// // send it down the stack +// PARCEventQueue *in = rtaProtocolStack_GetPutQueue(rtaConnection_GetStack(conn), TESTING_UPPER, RTA_DOWN); +// rtaComponent_PutMessage(in, tm); +// rtaFramework_NonThreadedStepCount(data->framework, 5); +// +// // now read it from out listener. It has a read timeout so if we dont get it in a reasonable amount +// // of time, read will return an error about the timeout +// +// const size_t maxPacketLength = 1024; +// uint8_t packet[maxPacketLength]; +// +// ssize_t readBytes = read(client_fd, packet, maxPacketLength); +// assertFalse(readBytes < 0, "Got error on read: (%d) %s", errno, strerror(errno)); +// parcEventBuffer_Destroy(&(fwd_state->metisOutputQueue)); +} + +// ==================================================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(connector_Forwarder_Metis); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/libccnx-transport-rta/ccnx/transport/transport_rta/connectors/test/test_rta_ApiConnection.c b/libccnx-transport-rta/ccnx/transport/transport_rta/connectors/test/test_rta_ApiConnection.c new file mode 100644 index 00000000..b0e937cd --- /dev/null +++ b/libccnx-transport-rta/ccnx/transport/transport_rta/connectors/test/test_rta_ApiConnection.c @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Create a non-threaded framework to test internal RTA functions + * + */ +#include "../rta_ApiConnection.c" + +#include <poll.h> + +#include <parc/algol/parc_SafeMemory.h> +#include <LongBow/unit-test.h> +#include <ccnx/transport/transport_rta/config/config_All.h> +#include <ccnx/transport/transport_rta/core/rta_Framework_Commands.c> + +#include <ccnx/transport/test_tools/traffic_tools.h> + +typedef struct test_data { + PARCRingBuffer1x1 *commandRingBuffer; + PARCNotifier *commandNotifier; + RtaFramework *framework; + + int api_fds[2]; + int stackId; + + RtaProtocolStack *stack; + RtaConnection *connection; +} TestData; + +static TestData * +_commonSetup(void) +{ + TestData *data = parcMemory_AllocateAndClear(sizeof(TestData)); + assertNotNull(data, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(TestData)); + + data->commandRingBuffer = parcRingBuffer1x1_Create(128, NULL); + data->commandNotifier = parcNotifier_Create(); + data->framework = rtaFramework_Create(data->commandRingBuffer, data->commandNotifier); + assertNotNull(data->framework, "rtaFramework_Create returned null"); + + CCNxStackConfig *stackConfig = ccnxStackConfig_Create(); + + apiConnector_ProtocolStackConfig(stackConfig); + testingLower_ProtocolStackConfig(stackConfig); + protocolStack_ComponentsConfigArgs(stackConfig, apiConnector_GetName(), testingLower_GetName(), NULL); + + rtaFramework_NonThreadedStepCount(data->framework, 10); + + // Create the protocol stack + + data->stackId = 1; + RtaCommandCreateProtocolStack *createStack = rtaCommandCreateProtocolStack_Create(data->stackId, stackConfig); + _rtaFramework_ExecuteCreateStack(data->framework, createStack); + rtaCommandCreateProtocolStack_Release(&createStack); + + rtaFramework_NonThreadedStepCount(data->framework, 10); + data->stack = (rtaFramework_GetProtocolStackByStackId(data->framework, data->stackId))->stack; + + // Create a connection in the stack + + int error = socketpair(AF_UNIX, SOCK_STREAM, 0, data->api_fds); + assertFalse(error, "Error creating socket pair: (%d) %s", errno, strerror(errno)); + + CCNxConnectionConfig *connConfig = ccnxConnectionConfig_Create(); + apiConnector_ConnectionConfig(connConfig); + + tlvCodec_ConnectionConfig(connConfig); + + testingLower_ConnectionConfig(connConfig); + + RtaCommandOpenConnection *openConnection = rtaCommandOpenConnection_Create(data->stackId, data->api_fds[PAIR_OTHER], data->api_fds[PAIR_TRANSPORT], ccnxConnectionConfig_GetJson(connConfig)); + _rtaFramework_ExecuteOpenConnection(data->framework, openConnection); + rtaCommandOpenConnection_Release(&openConnection); + + rtaFramework_NonThreadedStepCount(data->framework, 10); + + data->connection = rtaConnectionTable_GetByApiFd(data->framework->connectionTable, data->api_fds[PAIR_OTHER]); + + // cleanup + + ccnxConnectionConfig_Destroy(&connConfig); + ccnxStackConfig_Release(&stackConfig); + + return data; +} + +static void +_commonTeardown(TestData *data) +{ + rtaFramework_Teardown(data->framework); + + parcRingBuffer1x1_Release(&data->commandRingBuffer); + parcNotifier_Release(&data->commandNotifier); + rtaFramework_Destroy(&data->framework); + + close(data->api_fds[0]); + close(data->api_fds[1]); + parcMemory_Deallocate((void **) &data); +} + +LONGBOW_TEST_RUNNER(rta_ApiConnection) +{ + LONGBOW_RUN_TEST_FIXTURE(Global); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(rta_ApiConnection) +{ + 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_ApiConnection) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, rtaApiConnection_BlockDown); + LONGBOW_RUN_TEST_CASE(Global, rtaApiConnection_Create_Destroy); + LONGBOW_RUN_TEST_CASE(Global, rtaApiConnection_Create_Checks); + LONGBOW_RUN_TEST_CASE(Global, rtaApiConnection_Create_Check_ApiSocket); + + LONGBOW_RUN_TEST_CASE(Global, rtaApiConnection_UnblockDown); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + longBowTestCase_SetClipBoardData(testCase, _commonSetup()); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + printf("Finishing testcase %s\n", longBowTestCase_GetName(testCase)); + _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, rtaApiConnection_SendToApi) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + RtaApiConnection *apiConnection = rtaConnection_GetPrivateData(data->connection, API_CONNECTOR); + + TransportMessage *tm = trafficTools_CreateTransportMessageWithDictionaryInterest(data->connection, CCNxTlvDictionary_SchemaVersion_V1); + + RtaComponentStats *stats = rtaConnection_GetStats(data->connection, API_CONNECTOR); + rtaApiConnection_SendToApi(apiConnection, tm, stats); + rtaFramework_NonThreadedStepCount(data->framework, 10); + + // Let the dispatcher run + struct pollfd pfd = { .fd = data->api_fds[PAIR_OTHER], .events = POLLIN, .revents = 0 }; + int millisecondTimeout = 1000; + + int pollvalue = poll(&pfd, 1, millisecondTimeout); + assertTrue(pollvalue == 1, "Did not get an event from the API's side of the socket"); + + CCNxMetaMessage *testMessage; + ssize_t bytesRead = read(data->api_fds[PAIR_OTHER], &testMessage, sizeof(testMessage)); + assertTrue(bytesRead == sizeof(testMessage), "Wrong read size, got %zd expected %zd: (%d) %s", + bytesRead, sizeof(testMessage), + errno, strerror(errno)); + assertNotNull(testMessage, "Message read is NULL"); + + + assertTrue(testMessage == transportMessage_GetDictionary(tm), + "Got wrong raw message, got %p expected %p", + (void *) testMessage, (void *) transportMessage_GetDictionary(tm)); + + ccnxMetaMessage_Release(&testMessage); + transportMessage_Destroy(&tm); +} + +LONGBOW_TEST_CASE(Global, rtaApiConnection_BlockDown) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + RtaApiConnection *apiConnection = rtaApiConnection_Create(data->connection); + + // make sure we're startined unblocked + short enabled = parcEventQueue_GetEnabled(apiConnection->bev_api); + assertTrue(enabled & PARCEventType_Read, "PARCEventType_Read is not enabled on a new Api Connector: enabled = %04X", enabled); + + rtaApiConnection_BlockDown(apiConnection); + enabled = parcEventQueue_GetEnabled(apiConnection->bev_api); + assertFalse(enabled & PARCEventType_Read, "PARCEventType_Read is still enabled after caling BlockDown: enabled = %04X", enabled); + + rtaApiConnection_Destroy(&apiConnection); +} + +LONGBOW_TEST_CASE(Global, rtaApiConnection_Create_Destroy) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + uint32_t beforeBalance = parcMemory_Outstanding(); + RtaApiConnection *apiConnection = rtaApiConnection_Create(data->connection); + assertNotNull(apiConnection, "Got null API connection"); + + rtaApiConnection_Destroy(&apiConnection); + assertNull(apiConnection, "Destroy did not null apiConnection"); + uint32_t afterBalance = parcMemory_Outstanding(); + assertTrue(beforeBalance == afterBalance, "Memory imbalance: %d", (int) (afterBalance - beforeBalance)); +} + +LONGBOW_TEST_CASE(Global, rtaApiConnection_Create_Checks) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + RtaApiConnection *apiConnection = rtaApiConnection_Create(data->connection); + assertTrue(apiConnection->api_fd == rtaConnection_GetApiFd(data->connection), + "Wrong api fd, got %d expected %d", + apiConnection->api_fd, rtaConnection_GetApiFd(data->connection)); + + assertTrue(apiConnection->transport_fd == rtaConnection_GetTransportFd(data->connection), + "Wrong api fd, got %d expected %d", + apiConnection->transport_fd, rtaConnection_GetTransportFd(data->connection)); + + assertTrue(apiConnection->connection == data->connection, + "Wrong connection, got %p expected %p", + (void *) apiConnection->connection, + (void *) data->connection); + + rtaApiConnection_Destroy(&apiConnection); +} + +LONGBOW_TEST_CASE(Global, rtaApiConnection_Create_Check_ApiSocket) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + RtaApiConnection *apiConnection = rtaApiConnection_Create(data->connection); + + assertNotNull(apiConnection->bev_api, "API socket event null"); + + rtaApiConnection_Destroy(&apiConnection); +} + +LONGBOW_TEST_CASE(Global, rtaApiConnection_UnblockDown) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + RtaApiConnection *apiConnection = rtaApiConnection_Create(data->connection); + + rtaApiConnection_BlockDown(apiConnection); + // we know from previous test that this puts the apiConnector in blocked state + + rtaApiConnection_UnblockDown(apiConnection); + short enabled = parcEventQueue_GetEnabled(apiConnection->bev_api); + assertTrue(enabled & PARCEventType_Read, "PARCEventType_Read is not enabled after caling UnlockDown: enabled = %04X", enabled); + + rtaApiConnection_Destroy(&apiConnection); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(rta_ApiConnection); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} |