diff options
Diffstat (limited to 'metis/ccnx/forwarder/metis/io/test')
18 files changed, 5669 insertions, 0 deletions
diff --git a/metis/ccnx/forwarder/metis/io/test/CMakeLists.txt b/metis/ccnx/forwarder/metis/io/test/CMakeLists.txt new file mode 100644 index 00000000..1e22e32e --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/CMakeLists.txt @@ -0,0 +1,25 @@ +# Enable gcov output for the tests +add_definitions(--coverage) +set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} " --coverage") + +set(TestsExpectedToPass + test_metis_AddressPair + test_metis_EtherConnection + test_metis_EtherListener + test_metis_HopByHopFragmenter + test_metis_IPMulticastListener + test_metis_ListenerSet + test_metis_LocalListener + test_metis_StreamConnection + test_metis_TcpListener + test_metis_TcpTunnel + test_metis_UdpConnection + test_metis_UdpListener + test_metis_UdpTunnel +) + + +foreach(test ${TestsExpectedToPass}) + AddTest(${test}) +endforeach() + diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_AddressPair.c b/metis/ccnx/forwarder/metis/io/test/test_metis_AddressPair.c new file mode 100644 index 00000000..34ff9d9d --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_AddressPair.c @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_AddressPair.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +LONGBOW_TEST_RUNNER(metis_AddressPair) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_AddressPair) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_AddressPair) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisAddressPair_Create_Destroy); + LONGBOW_RUN_TEST_CASE(Global, metisAddressPair_Equals); + LONGBOW_RUN_TEST_CASE(Global, metisAddressPair_Equals_NotEqual); + LONGBOW_RUN_TEST_CASE(Global, metisAddressPair_EqualsAddresses); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisAddressPair_Create_Destroy) +{ + CPIAddress *a = cpiAddress_CreateFromInterface(1); + CPIAddress *b = cpiAddress_CreateFromInterface(2); + + size_t allocbase = parcSafeMemory_Outstanding(); + MetisAddressPair *pair = metisAddressPair_Create(a, b); + metisAddressPair_Release(&pair); + + assertTrue(parcSafeMemory_Outstanding() == allocbase, + "Memory out of balance, expected %zu got %u", + allocbase, + parcSafeMemory_Outstanding()); + + cpiAddress_Destroy(&a); + cpiAddress_Destroy(&b); +} + +LONGBOW_TEST_CASE(Global, metisAddressPair_Equals) +{ + CPIAddress *a = cpiAddress_CreateFromInterface(1); + CPIAddress *b = cpiAddress_CreateFromInterface(2); + + MetisAddressPair *pair_a = metisAddressPair_Create(a, b); + MetisAddressPair *pair_b = metisAddressPair_Create(a, b); + + assertTrue(metisAddressPair_Equals(pair_a, pair_b), "Two equal address pairs did not compare equal"); + + metisAddressPair_Release(&pair_a); + metisAddressPair_Release(&pair_b); + + cpiAddress_Destroy(&a); + cpiAddress_Destroy(&b); +} + +LONGBOW_TEST_CASE(Global, metisAddressPair_Equals_NotEqual) +{ + CPIAddress *a = cpiAddress_CreateFromInterface(1); + CPIAddress *b = cpiAddress_CreateFromInterface(2); + + MetisAddressPair *pair_a = metisAddressPair_Create(a, b); + MetisAddressPair *pair_b = metisAddressPair_Create(b, a); + + assertFalse(metisAddressPair_Equals(pair_a, pair_b), "Two unequal address pairs compared equal"); + + metisAddressPair_Release(&pair_a); + metisAddressPair_Release(&pair_b); + cpiAddress_Destroy(&a); + cpiAddress_Destroy(&b); +} + +LONGBOW_TEST_CASE(Global, metisAddressPair_EqualsAddresses) +{ + CPIAddress *a = cpiAddress_CreateFromInterface(1); + CPIAddress *b = cpiAddress_CreateFromInterface(2); + + MetisAddressPair *pair_a = metisAddressPair_Create(a, b); + + assertTrue(metisAddressPair_EqualsAddresses(pair_a, a, b), "Two equal address pairs did not compare equal"); + + metisAddressPair_Release(&pair_a); + cpiAddress_Destroy(&a); + cpiAddress_Destroy(&b); +} + +LONGBOW_TEST_FIXTURE(Local) +{ +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_AddressPair); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_EtherConnection.c b/metis/ccnx/forwarder/metis/io/test/test_metis_EtherConnection.c new file mode 100644 index 00000000..081903b1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_EtherConnection.c @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include "testrig_GenericEther.c" +#include "../metis_EtherConnection.c" + +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h> + +typedef struct test_data { + MetisForwarder *metis; + MetisGenericEther *ether; + MetisAddressPair *pair; + MetisIoOperations *io_ops; +} TestData; + +static void +commonSetup(const LongBowTestCase *testCase, uint16_t ethertype) +{ + TestData *data = parcMemory_AllocateAndClear(sizeof(TestData)); + + data->metis = metisForwarder_Create(NULL); + data->ether = metisGenericEther_Create(data->metis, "foo", ethertype); + data->io_ops = NULL; + + // crank the libevent handle + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis), &((struct timeval) {0, 10000})); + + PARCBuffer *localMacBuffer = metisGenericEther_GetMacAddress(data->ether); + CPIAddress *local = cpiAddress_CreateFromLink(parcBuffer_Overlay(localMacBuffer, 0), parcBuffer_Remaining(localMacBuffer)); + CPIAddress *remote = cpiAddress_CreateFromLink((uint8_t []) { 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }, 6); + + data->pair = metisAddressPair_Create(local, remote); + + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); + + longBowTestCase_SetClipBoardData(testCase, data); +} + +static void +commonTeardown(const LongBowTestCase *testCase) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis), &((struct timeval) {0, 10000})); + metisGenericEther_Release(&data->ether); + metisAddressPair_Release(&data->pair); + + if (data->io_ops) { + data->io_ops->destroy(&data->io_ops); + } + + // destroy metis last + metisForwarder_Destroy(&data->metis); + parcMemory_Deallocate((void **) &data); +} + + +LONGBOW_TEST_RUNNER(metis_EtherConnection) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_EtherConnection) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_EtherConnection) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// =========================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisEtherConnection_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + commonSetup(testCase, 0x0801); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + commonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisEtherConnection_Create) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + data->io_ops = metisEtherConnection_Create(data->metis, data->ether, data->pair); + assertNotNull(data->io_ops, "Got null MetisIoOperations from metisEtherConnection_Create"); +} + +// =========================================== + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_DestroyOperations); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_FillInMacAddress); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_GetAddressPair); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_GetConnectionId); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_GetRemoteAddress); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_IsLocal); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_IsUp); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_Send); + LONGBOW_RUN_TEST_CASE(Local, _setConnectionState); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_getConnectionType); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + commonSetup(testCase, 0x0801); + + // for the local tests we also pre-create the EtherConnection + TestData *data = longBowTestCase_GetClipBoardData(testCase); + data->io_ops = metisEtherConnection_Create(data->metis, data->ether, data->pair); + + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + commonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_DestroyOperations) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_FillInMacAddress) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_GetAddressPair) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherState *etherConn = (_MetisEtherState *) metisIoOperations_GetClosure(data->io_ops); + + const MetisAddressPair *test = _metisEtherConnection_GetAddressPair(data->io_ops); + + assertTrue(metisAddressPair_Equals(test, etherConn->addressPair), "Address pair did not compare"); +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_GetConnectionId) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherState *etherConn = (_MetisEtherState *) metisIoOperations_GetClosure(data->io_ops); + unsigned connid = _metisEtherConnection_GetConnectionId(data->io_ops); + + assertTrue(connid == etherConn->id, "Wrong connection id, got %u exepected %u", connid, etherConn->id); +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_GetRemoteAddress) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + const CPIAddress *test = _metisEtherConnection_GetRemoteAddress(data->io_ops); + + _MetisEtherState *etherConn = (_MetisEtherState *) metisIoOperations_GetClosure(data->io_ops); + + assertTrue(cpiAddress_Equals(test, metisAddressPair_GetRemote(etherConn->addressPair)), "Remote addresses did not compare"); +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_IsLocal) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + bool isLocal = _metisEtherConnection_IsLocal(data->io_ops); + assertFalse(isLocal, "Ethernet should always be remote"); +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_IsUp) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + bool isUp = _metisEtherConnection_IsUp(data->io_ops); + assertTrue(isUp, "Ethernet should be up"); +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_Send) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest), 1, 2, metisForwarder_GetLogger(data->metis)); + + bool success = _metisEtherConnection_Send(data->io_ops, NULL, message); + assertTrue(success, "Failed to write message to ethernet"); + + // we should now be able to read the ethernet frame from the test socket + int testSocket = mockGenericEther_GetTestDescriptor(data->ether); + assertTrue(testSocket > 0, "Error getting test socket from mock ethernet"); + + uint32_t testBufferSize = 2048; + uint8_t testBuffer[testBufferSize]; + ssize_t bytesRead = read(testSocket, testBuffer, testBufferSize); + + size_t expectedRead = sizeof(struct ether_header) + sizeof(metisTestDataV0_EncodedInterest); + + assertTrue(bytesRead == expectedRead, "Wrong read size, got %zd expected %zu", bytesRead, expectedRead); + + uint8_t *frame = testBuffer + sizeof(struct ether_header); + assertTrue(memcmp(frame, metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest)) == 0, "Buffers do not match"); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Local, _setConnectionState) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherState *etherConn = (_MetisEtherState *) metisIoOperations_GetClosure(data->io_ops); + + _setConnectionState(etherConn, false); +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_getConnectionType) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + CPIConnectionType connType = _metisEtherConnection_getConnectionType(data->io_ops); + + assertTrue(connType == cpiConnection_L2, "Wrong connection type expected %d got %d", cpiConnection_L2, connType); +} + +// =========================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_EtherConnection); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_EtherListener.c b/metis/ccnx/forwarder/metis/io/test/test_metis_EtherListener.c new file mode 100644 index 00000000..2ae7596c --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_EtherListener.c @@ -0,0 +1,387 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// force the use of the generic ethernet mockup +#include "testrig_GenericEther.c" +#include "../metis_EtherListener.c" +#include "testrig_GenericEther.h" + +#include <parc/algol/parc_SafeMemory.h> + +#include <LongBow/unit-test.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h> + + +typedef struct test_data { + MetisForwarder *metis; + MetisListenerOps *ops; +} TestData; + +static void +commonSetup(const LongBowTestCase *testCase, uint16_t ethertype) +{ + TestData *data = parcMemory_AllocateAndClear(sizeof(TestData)); + + data->metis = metisForwarder_Create(NULL); + data->ops = metisEtherListener_Create(data->metis, "test0", ethertype); + + // crank the libevent handle + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis), &((struct timeval) {0, 10000})); + + longBowTestCase_SetClipBoardData(testCase, data); +} + +static void +commonTeardown(const LongBowTestCase *testCase) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis), &((struct timeval) {0, 10000})); + data->ops->destroy(&data->ops); + metisForwarder_Destroy(&data->metis); + + parcMemory_Deallocate((void **) &data); +} + +// ============================================================ + +LONGBOW_TEST_RUNNER(metis_EtherListener) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_EtherListener) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_EtherListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ============================================================ + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisEtherListener_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + commonSetup(testCase, 0x0801); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + commonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisEtherListener_Create) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + assertNotNull(data->ops, "Null return from metisEtherListener_Create"); +} + +// ============================================================ + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisEtherListener_Destroy); + LONGBOW_RUN_TEST_CASE(Local, metisEtherListener_OpsDestroy); + LONGBOW_RUN_TEST_CASE(Local, metisEtherListener_OpsGetInterfaceIndex); + LONGBOW_RUN_TEST_CASE(Local, metisEtherListener_OpsGetListenAddress); + LONGBOW_RUN_TEST_CASE(Local, metisEtherListener_OpsGetEncapType); + + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_ReadCallback); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_ReadCallback_FragmentBegin); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_ReadCallback_FragmentEnd); + + + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_ReadEtherFrame_EmptyQueue); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_ReadEtherFrame_PacketWaiting); + + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_IsOurSourceAddress_True); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_IsOurSourceAddress_False); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_IsOurDestinationAddress_Unicast); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_IsOurDestinationAddress_Group); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_IsOurDestinationAddress_Broadcast); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_IsOurDestinationAddress_False); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_IsOurProtocol); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_ParseEtherFrame); + + + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_FillInEthernetAddresses); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_ReleaseEthernetAddresses); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + commonSetup(testCase, 0x0801); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + commonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, metisEtherListener_Destroy) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisListenerOps *ops = metisEtherListener_Create(data->metis, "fake0", 0x0801); + + _metisEtherListener_Destroy((_MetisEtherListener **) &ops->context); + assertNull(ops->context, "Destory did not null context"); + parcMemory_Deallocate((void **) &ops); +} + +LONGBOW_TEST_CASE(Local, metisEtherListener_OpsDestroy) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisListenerOps *ops = metisEtherListener_Create(data->metis, "fake1", 0x0801); + _metisEtherListener_OpsDestroy(&ops); + assertNull(ops, "OpsDestroy did not null ops"); +} + +LONGBOW_TEST_CASE(Local, metisEtherListener_OpsGetInterfaceIndex) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, metisEtherListener_OpsGetListenAddress) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, metisEtherListener_OpsGetEncapType) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_ReadCallback) +{ + testUnimplemented(""); +} + +/* + * Read only a B frame, so its not a complete reassembly + */ +LONGBOW_TEST_CASE(Local, _metisEtherListener_ReadCallback_FragmentBegin) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + uint8_t headerArray[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x08, 0x01 }; + + PARCBuffer *frameBuffer = parcBuffer_Allocate(sizeof(struct ether_header) + sizeof(metisTestDataV1_HopByHopFrag_Begin)); + parcBuffer_PutArray(frameBuffer, sizeof(struct ether_header), headerArray); + parcBuffer_PutArray(frameBuffer, sizeof(metisTestDataV1_HopByHopFrag_Begin), metisTestDataV1_HopByHopFrag_Begin); + + parcBuffer_Flip(frameBuffer); + + mockGenericEther_QueueFrame(etherListener->genericEther, frameBuffer); + + _metisEtherListener_ReadCallback(0, PARCEventType_Read, data->ops->context); + + assertTrue(etherListener->stats.framesIn == 1, "Wrong framesIn count, expected 1 got %" PRIu64, etherListener->stats.framesIn); + assertTrue(etherListener->stats.framesReceived == 1, "Wrong framesReceived count, expected 1 got %" PRIu64, etherListener->stats.framesReceived); + assertTrue(etherListener->stats.framesReassembled == 0, "Wrong framesReassembled count, expected 0 got %" PRIu64, etherListener->stats.framesReassembled); + + parcBuffer_Release(&frameBuffer); +} + +/* + * Read a B and middle and E frame, so it is complete reassembly + */ +LONGBOW_TEST_CASE(Local, _metisEtherListener_ReadCallback_FragmentEnd) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + uint8_t headerArray[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x08, 0x01 }; + + PARCBuffer *frameBuffer = parcBuffer_Allocate(sizeof(struct ether_header) + sizeof(metisTestDataV1_HopByHopFrag_BeginEnd)); + parcBuffer_PutArray(frameBuffer, sizeof(struct ether_header), headerArray); + parcBuffer_PutArray(frameBuffer, sizeof(metisTestDataV1_HopByHopFrag_BeginEnd), metisTestDataV1_HopByHopFrag_BeginEnd); + + parcBuffer_Flip(frameBuffer); + + mockGenericEther_QueueFrame(etherListener->genericEther, frameBuffer); + + _metisEtherListener_ReadCallback(0, PARCEventType_Read, data->ops->context); + + assertTrue(etherListener->stats.framesIn == 1, "Wrong framesIn count, expected 1 got %" PRIu64, etherListener->stats.framesIn); + assertTrue(etherListener->stats.framesReceived == 1, "Wrong framesReceived count, expected 1 got %" PRIu64, etherListener->stats.framesReceived); + assertTrue(etherListener->stats.framesReassembled == 1, "Wrong framesReassembled count, expected 1 got %" PRIu64, etherListener->stats.framesReassembled); + + parcBuffer_Release(&frameBuffer); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_ReadEtherFrame_PacketWaiting) +{ + // create a frame and queue it + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + // Ethernet frame addressed to us with a 0-length CCNx TLV packet (i.e. just the fixed header) + uint8_t frame[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 't', 'e', 's', 't', '0', 0xA0, 0x08, 0x01, + 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 }; + + PARCBuffer *frameBuffer = parcBuffer_Wrap(frame, sizeof(frame), 0, sizeof(frame)); + + mockGenericEther_QueueFrame(etherListener->genericEther, frameBuffer); + + PARCEventBuffer *buffer = _metisEtherListener_ReadEtherFrame(etherListener); + assertNotNull(buffer, "Got null buffer from ReadEtherFrame with a frame queued"); + + assertTrue(parcEventBuffer_GetLength(buffer) == sizeof(frame), "Wrong size, got %zu expected %zu", parcEventBuffer_GetLength(buffer), sizeof(frame)); + + parcEventBuffer_Destroy(&buffer); + parcBuffer_Release(&frameBuffer); +} + + +LONGBOW_TEST_CASE(Local, _metisEtherListener_ReadEtherFrame_EmptyQueue) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + PARCEventBuffer *buffer = _metisEtherListener_ReadEtherFrame(etherListener); + assertNull(buffer, "Should get null buffer from ReadEtherFrame without a frame queued"); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_IsOurSourceAddress_True) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + uint8_t frame[] = { 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 't', 'e', 's', 't', '0', 0x06, 0x08, 0x01 }; + struct ether_header *header = (struct ether_header *) frame; + + bool success = _metisEtherListener_IsOurSourceAddress(etherListener, header); + assertTrue(success, "Did not match our source address."); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_IsOurSourceAddress_False) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + uint8_t frame[] = { 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x11, 0x22, 0x33, 0x44, 0x05, 0x06, 0x08, 0x01 }; + struct ether_header *header = (struct ether_header *) frame; + + bool success = _metisEtherListener_IsOurSourceAddress(etherListener, header); + assertFalse(success, "Should not match our address."); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_IsOurDestinationAddress_Unicast) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + uint8_t frame[] = { 't', 'e', 's', 't', '0', 0x06, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x08, 0x01 }; + struct ether_header *header = (struct ether_header *) frame; + + bool success = _metisEtherListener_IsOurDestinationAddress(etherListener, header); + assertTrue(success, "Did not match our destination address."); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_IsOurDestinationAddress_Group) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + uint8_t frame[] = { 0x01, 0x00, 0x5e, 0x00, 0x17, 0xaa, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x08, 0x01 }; + struct ether_header *header = (struct ether_header *) frame; + + bool success = _metisEtherListener_IsOurDestinationAddress(etherListener, header); + assertTrue(success, "Did not match group address."); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_IsOurDestinationAddress_Broadcast) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + uint8_t frame[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x08, 0x01 }; + struct ether_header *header = (struct ether_header *) frame; + + bool success = _metisEtherListener_IsOurDestinationAddress(etherListener, header); + assertTrue(success, "Did not match broadcast address."); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_IsOurDestinationAddress_False) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + uint8_t frame[] = { 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x08, 0x01 }; + struct ether_header *header = (struct ether_header *) frame; + + bool success = _metisEtherListener_IsOurDestinationAddress(etherListener, header); + assertFalse(success, "Should not match one of our addresses."); +} + + +LONGBOW_TEST_CASE(Local, _metisEtherListener_IsOurProtocol) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_ParseEtherFrame) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_FillInEthernetAddresses) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_ReleaseEthernetAddresses) +{ + testUnimplemented(""); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_EtherListener); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_HopByHopFragmenter.c b/metis/ccnx/forwarder/metis/io/test/test_metis_HopByHopFragmenter.c new file mode 100644 index 00000000..5be165c5 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_HopByHopFragmenter.c @@ -0,0 +1,1087 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../metis_HopByHopFragmenter.c" + +#include <parc/algol/parc_SafeMemory.h> +#include <parc/logging/parc_LogReporterTextStdout.h> + +#include <LongBow/unit-test.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h> + +typedef struct test_data { + unsigned mtu; + MetisLogger *logger; + MetisHopByHopFragmenter *fragmenter; +} TestData; + +static TestData * +_createTestData(void) +{ + TestData *data = parcMemory_Allocate(sizeof(TestData)); + + data->mtu = 2000; + + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + data->logger = metisLogger_Create(reporter, parcClock_Wallclock()); + metisLogger_SetLogLevel(data->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug); + parcLogReporter_Release(&reporter); + data->fragmenter = metisHopByHopFragmenter_Create(data->logger, data->mtu); + return data; +} + +static void +_destroyTestData(TestData **dataPtr) +{ + TestData *data = *dataPtr; + metisHopByHopFragmenter_Release(&data->fragmenter); + metisLogger_Release(&data->logger); + parcMemory_Deallocate((void **) dataPtr); +} + +/* + * Create a well-formed packet with the given length. + * length is the total packet length, including fixed header. + */ +static uint8_t * +_conjurePacket(size_t length) +{ + _HopByHopHeader header; + memset(&header, 0, sizeof(header)); + + size_t payloadLength = length - sizeof(header); + + header.version = 1; + header.packetType = 2; // interest return -- does not require a name. + header.packetLength = htons(length); + header.headerLength = 8; + header.tlvType = 0; + header.tlvLength = htons(payloadLength); + + uint8_t *packet = parcMemory_Allocate(length); + memcpy(packet, &header, sizeof(header)); + return packet; +} + +// ============================================================ + +LONGBOW_TEST_RUNNER(metis_HopByHopFragmenter) +{ + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_HopByHopFragmenter) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_HopByHopFragmenter) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ============================================================ + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisHopByHopFragmenter_Create); + LONGBOW_RUN_TEST_CASE(Global, metisHopByHopFragmenter_Receive_NotHopByHop); + LONGBOW_RUN_TEST_CASE(Global, metisHopByHopFragmenter_Receive_ReceiveQueueFull); + LONGBOW_RUN_TEST_CASE(Global, metisHopByHopFragmenter_Receive_Ok); + + LONGBOW_RUN_TEST_CASE(Global, metisHopByHopFragmenter_Send_OneMtu); + LONGBOW_RUN_TEST_CASE(Global, metisHopByHopFragmenter_Send_ReceiveQueueFull); + LONGBOW_RUN_TEST_CASE(Global, metisHopByHopFragmenter_Send_Ok); +} + + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + TestData *data = _createTestData(); + longBowTestCase_SetClipBoardData(testCase, data); + + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _destroyTestData(&data); + + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisHopByHopFragmenter_Create) +{ + // nothing really to do here, just need to make sure there's no memory leak in teardown + TestData *data = longBowTestCase_GetClipBoardData(testCase); + assertNotNull(data->fragmenter, "Got null fragmenter"); +} + +/* + * Receive a non-hop-by-hop packet. Should go straight in to the receive queue + */ +LONGBOW_TEST_CASE(Global, metisHopByHopFragmenter_Receive_NotHopByHop) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + MetisTicks startTicks = 1111111; + unsigned ingressId = 77; + + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV1_Interest_AllFields, sizeof(metisTestDataV1_Interest_AllFields), ingressId, startTicks, data->logger); + metisHopByHopFragmenter_Receive(data->fragmenter, message); + + /* + * 1) Make a metis message out of the reassembly buffer, + * 2) put the message in the receive queue (discard if queue full) + * 3) allocate a new reassembly buffer + * 4) reset the parser + */ + + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Got null reassembled message"); + + assertTrue(test == message, "Message not in receive queue, expected %p got %p", (void *) message, (void *) test); + + metisMessage_Release(&message); + metisMessage_Release(&test); +} + +LONGBOW_TEST_CASE(Global, metisHopByHopFragmenter_Receive_ReceiveQueueFull) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // create a full recieve queue + parcRingBuffer1x1_Release(&data->fragmenter->receiveQueue); + data->fragmenter->receiveQueue = parcRingBuffer1x1_Create(2, _ringBufferDestroyer); + + void *fakeData = (void *) 1; + parcRingBuffer1x1_Put(data->fragmenter->receiveQueue, fakeData); + + assertTrue(parcRingBuffer1x1_Remaining(data->fragmenter->receiveQueue) == 0, "expected queue to be full"); + + // === run test + MetisTicks startTicks = 1111111; + unsigned ingressId = 77; + + data->fragmenter->nextReceiveFragSequenceNumber = 1; + + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV1_Interest_AllFields, sizeof(metisTestDataV1_Interest_AllFields), ingressId, startTicks, data->logger); + metisHopByHopFragmenter_Receive(data->fragmenter, message); + + // should still only be the fake data in the queue + void *test = NULL; + parcRingBuffer1x1_Get(data->fragmenter->receiveQueue, &test); + assertTrue(test == fakeData, "Wrong pointer, expected %p got %p", fakeData, test); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisHopByHopFragmenter_Receive_Ok) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + MetisTicks startTicks = 1111111; + unsigned ingressId = 77; + + data->fragmenter->nextReceiveFragSequenceNumber = 1; + + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Begin, sizeof(metisTestDataV1_HopByHopFrag_Begin), ingressId, startTicks, data->logger); + metisHopByHopFragmenter_Receive(data->fragmenter, message); + + /* + * We should now be in the Busy state + */ + assertTrue(data->fragmenter->parserState == _ParserState_Busy, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisHopByHopFragmenter_Send_OneMtu) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // make a packet shorter than one MTU (so it will fit with the fragment overhead) + size_t length = data->fragmenter->mtu - 100; + uint8_t *packet = _conjurePacket(length); + MetisMessage *message = metisMessage_CreateFromArray(packet, length, 1, 2, data->logger); + assertNotNull(message, "Could not conjure packet"); + + bool success = metisHopByHopFragmenter_Send(data->fragmenter, message); + + assertTrue(success, "Failed to send fragments"); + MetisMessage *fragment = metisHopByHopFragmenter_PopSendQueue(data->fragmenter); + assertNotNull(fragment, "Did not find a fragment in the send queue"); + + // === + // defragment it + + metisHopByHopFragmenter_Receive(data->fragmenter, fragment); + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Should have gotten the original message back"); + assertTrue(metisMessage_Length(test) == metisMessage_Length(message), + "Reconstructed message length wrong expected %zu got %zu", + metisMessage_Length(message), + metisMessage_Length(test)); + + // === + // cleanup + + metisMessage_Release(&fragment); + metisMessage_Release(&message); + metisMessage_Release(&test); + parcMemory_Deallocate((void **) &packet); +} + +LONGBOW_TEST_CASE(Global, metisHopByHopFragmenter_Send_ReceiveQueueFull) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // create a full send queue + parcRingBuffer1x1_Release(&data->fragmenter->sendQueue); + data->fragmenter->sendQueue = parcRingBuffer1x1_Create(2, _ringBufferDestroyer); + + void *fakeData = (void *) 1; + parcRingBuffer1x1_Put(data->fragmenter->sendQueue, fakeData); + + + // less than 1 MTU + size_t length = data->fragmenter->mtu - 100; + uint8_t *packet = _conjurePacket(length); + MetisMessage *message = metisMessage_CreateFromArray(packet, length, 1, 2, data->logger); + assertNotNull(message, "Could not conjure packet"); + + + bool success = metisHopByHopFragmenter_Send(data->fragmenter, message); + assertFalse(success, "Should have failed to send fragments"); + + // === + // cleanup + + // manually pop this off as it is not a proper MetisMessage + parcRingBuffer1x1_Get(data->fragmenter->sendQueue, &fakeData); + metisMessage_Release(&message); + parcMemory_Deallocate((void **) &packet); +} + +LONGBOW_TEST_CASE(Global, metisHopByHopFragmenter_Send_Ok) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // Take up 2 MTUs (minus a little for fragmentation overhead) + size_t length = 2 * data->fragmenter->mtu - 100; + uint8_t *packet = _conjurePacket(length); + MetisMessage *message = metisMessage_CreateFromArray(packet, length, 1, 2, data->logger); + assertNotNull(message, "Could not conjure packet"); + + + bool success = metisHopByHopFragmenter_Send(data->fragmenter, message); + assertTrue(success, "Failed to send fragments"); + + // === + // defragment it + + MetisMessage *fragment; + while ((fragment = metisHopByHopFragmenter_PopSendQueue(data->fragmenter)) != NULL) { + metisHopByHopFragmenter_Receive(data->fragmenter, fragment); + metisMessage_Release(&fragment); + } + ; + + + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Should have gotten the original message back"); + assertTrue(metisMessage_Length(test) == metisMessage_Length(message), + "Reconstructed message length wrong expected %zu got %zu", + metisMessage_Length(message), + metisMessage_Length(test)); + + // === + // cleanup + + metisMessage_Release(&message); + metisMessage_Release(&test); + parcMemory_Deallocate((void **) &packet); +} + +// ============================================================ + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _compareSequenceNumbers); + LONGBOW_RUN_TEST_CASE(Local, _incrementSequenceNumber); + LONGBOW_RUN_TEST_CASE(Local, _resetParser); + LONGBOW_RUN_TEST_CASE(Local, _applySequenceNumberRules_InOrder); + LONGBOW_RUN_TEST_CASE(Local, _applySequenceNumberRules_Early); + LONGBOW_RUN_TEST_CASE(Local, _applySequenceNumberRules_Late); + LONGBOW_RUN_TEST_CASE(Local, _finalizeReassemblyBuffer_NotFull); + LONGBOW_RUN_TEST_CASE(Local, _finalizeReassemblyBuffer_Full); + LONGBOW_RUN_TEST_CASE(Local, _appendFragmentToReassemblyBuffer_Once); + LONGBOW_RUN_TEST_CASE(Local, _appendFragmentToReassemblyBuffer_Multiple); + + LONGBOW_RUN_TEST_CASE(Local, _receiveInIdleState_BFrame); + LONGBOW_RUN_TEST_CASE(Local, _receiveInIdleState_BEFrame); + LONGBOW_RUN_TEST_CASE(Local, _receiveInIdleState_OtherFrame); + + LONGBOW_RUN_TEST_CASE(Local, _receiveInBusyState_EFrame); + LONGBOW_RUN_TEST_CASE(Local, _receiveInBusyState_NoFlagFrame); + LONGBOW_RUN_TEST_CASE(Local, _receiveInBusyState_OtherFrame); + + LONGBOW_RUN_TEST_CASE(Local, _receiveFragment_IdleState); + LONGBOW_RUN_TEST_CASE(Local, _receiveFragment_BusyState); + + LONGBOW_RUN_TEST_CASE(Local, _sendFragments_OneFragment); + LONGBOW_RUN_TEST_CASE(Local, _sendFragments_TwoFragments); + LONGBOW_RUN_TEST_CASE(Local, _sendFragments_ThreeFragments); + LONGBOW_RUN_TEST_CASE(Local, _sendFragments_SendQueueFull); + + LONGBOW_RUN_TEST_CASE(Local, _ringBufferDestroyer); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + TestData *data = _createTestData(); + longBowTestCase_SetClipBoardData(testCase, data); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _destroyTestData(&data); + + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, _compareSequenceNumbers) +{ + struct test_vector { + uint32_t a; + uint32_t b; + int signum; + bool sentinel; + } testVectors[] = { + // compared to b = 0, then a = {1 ... 0x07FFFF} is greater than b + // compared to b = 0, then a = {0x080000 ... 0x0FFFFF} is less than b + + { .a = 0x00000000, .b = 0x00000000, .signum = 0, .sentinel = false }, + { .a = 0x00000001, .b = 0x00000000, .signum = +1, .sentinel = false }, + { .a = 0x0007FFFF, .b = 0x00000000, .signum = +1, .sentinel = false }, + { .a = 0x00080000, .b = 0x00000000, .signum = -1, .sentinel = false }, + { .a = 0x000FFFFF, .b = 0x00000000, .signum = -1, .sentinel = false }, + + // now do the same thing but use b = 0x00040000 + { .a = 0x00040000, .b = 0x00040000, .signum = 0, .sentinel = false }, + { .a = 0x00040001, .b = 0x00040000, .signum = +1, .sentinel = false }, + { .a = 0x000BFFFF, .b = 0x00040000, .signum = +1, .sentinel = false }, + { .a = 0x000C0000, .b = 0x00040000, .signum = -1, .sentinel = false }, + { .a = 0x0003FFFF, .b = 0x00040000, .signum = -1, .sentinel = false }, + + // end test set + { .a = 0x00000000, .b = 0x00000000, .signum = 0, .sentinel = true }, + }; + + for (int i = 0; !testVectors[i].sentinel; i++) { + int result = _compareSequenceNumbers(testVectors[i].a, testVectors[i].b); + + if (testVectors[i].signum == 0) { + assertTrue(result == 0, "Wrong result, expected 0 got %d index %d a 0x%08x b 0x%08x", + result, i, testVectors[i].a, testVectors[i].b); + } + + if (testVectors[i].signum < 0) { + assertTrue(result < 0, "Wrong result, expected negative got %d index %d a 0x%08x b 0x%08x", + result, i, testVectors[i].a, testVectors[i].b); + } + + if (testVectors[i].signum > 0) { + assertTrue(result > 0, "Wrong result, expected positive got %d index %d a 0x%08x b 0x%08x", + result, i, testVectors[i].a, testVectors[i].b); + } + } +} + +LONGBOW_TEST_CASE(Local, _incrementSequenceNumber) +{ + struct test_vector { + uint32_t a; + uint32_t b; + bool sentinel; + } testVectors[] = { + { .a = 0x00000000, .b = 0x00000001, .sentinel = false }, + { .a = 0x00000001, .b = 0x00000002, .sentinel = false }, + { .a = 0x0007FFFF, .b = 0x00080000, .sentinel = false }, + { .a = 0x000FFFFF, .b = 0x00000000, .sentinel = false }, + // end test set + { .a = 0x00000000, .b = 0x00000000, .sentinel = true }, + }; + + for (int i = 0; !testVectors[i].sentinel; i++) { + uint32_t result = _incrementSequenceNumber(testVectors[i].a, 0x000FFFFF); + + assertTrue(result == testVectors[i].b, "Wrong result 0x%08X index %d, got for a 0x%08x b 0x%08x", + result, i, testVectors[i].a, testVectors[i].b); + } +} + +LONGBOW_TEST_CASE(Local, _resetParser) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // put something in the buffer and set the parser state to Busy + data->fragmenter->parserState = _ParserState_Busy; + parcEventBuffer_Append(data->fragmenter->currentReceiveBuffer, data, sizeof(data)); + + _resetParser(data->fragmenter); + assertTrue(data->fragmenter->parserState == _ParserState_Idle, "Wrong parser state, exepcted %d got %d", _ParserState_Idle, data->fragmenter->parserState); + + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == 0, "Wrong length, expected 0 bytes, got %zu", length); +} + +LONGBOW_TEST_CASE(Local, _applySequenceNumberRules_InOrder) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + data->fragmenter->parserState = _ParserState_Busy; + data->fragmenter->nextReceiveFragSequenceNumber = 1000; + + _HopByHopHeader header; + memset(&header, 0, sizeof(header)); + + _hopByHopHeader_SetSeqnum(&header, 1000); + + _applySequenceNumberRules(data->fragmenter, &header); + + // should still be in Busy mode and expecting 1001 + assertTrue(data->fragmenter->parserState == _ParserState_Busy, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + assertTrue(data->fragmenter->nextReceiveFragSequenceNumber == 1001, "Wrong next seqnum, expected 1001 got %u", data->fragmenter->nextReceiveFragSequenceNumber); +} + +LONGBOW_TEST_CASE(Local, _applySequenceNumberRules_Early) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + data->fragmenter->parserState = _ParserState_Busy; + data->fragmenter->nextReceiveFragSequenceNumber = 1000; + + _HopByHopHeader header; + memset(&header, 0, sizeof(header)); + + _hopByHopHeader_SetSeqnum(&header, 998); + + _applySequenceNumberRules(data->fragmenter, &header); + + // should reset state and set next to 999 + assertTrue(data->fragmenter->parserState == _ParserState_Idle, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + assertTrue(data->fragmenter->nextReceiveFragSequenceNumber == 999, "Wrong next seqnum, expected 999 got %u", data->fragmenter->nextReceiveFragSequenceNumber); +} + +LONGBOW_TEST_CASE(Local, _applySequenceNumberRules_Late) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + data->fragmenter->parserState = _ParserState_Busy; + data->fragmenter->nextReceiveFragSequenceNumber = 1000; + + _HopByHopHeader header; + memset(&header, 0, sizeof(header)); + + _hopByHopHeader_SetSeqnum(&header, 1001); + + _applySequenceNumberRules(data->fragmenter, &header); + + // should reset state and set next to 1002 + assertTrue(data->fragmenter->parserState == _ParserState_Idle, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + assertTrue(data->fragmenter->nextReceiveFragSequenceNumber == 1002, "Wrong next seqnum, expected 1002 got %u", data->fragmenter->nextReceiveFragSequenceNumber); +} + +LONGBOW_TEST_CASE(Local, _finalizeReassemblyBuffer_NotFull) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + MetisTicks startTicks = 1111111; + unsigned ingressId = 77; + + // set up as just finished with a message, so the currentReceiveBuffer has + // a complete CCNx message array in it + data->fragmenter->parserState = _ParserState_Busy; + data->fragmenter->currentReceiveBufferIngressId = ingressId; + data->fragmenter->currentReceiveBufferStartTicks = startTicks; + parcEventBuffer_Append(data->fragmenter->currentReceiveBuffer, metisTestDataV1_Interest_AllFields, sizeof(metisTestDataV1_Interest_AllFields)); + + _finalizeReassemblyBuffer(data->fragmenter); + + /* + * 1) Make a metis message out of the reassembly buffer, + * 2) put the message in the receive queue (discard if queue full) + * 3) allocate a new reassembly buffer + * 4) reset the parser + */ + + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Got null reassembled message"); + assertTrue(data->fragmenter->parserState == _ParserState_Idle, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + assertNotNull(data->fragmenter->currentReceiveBuffer, "Current receive buffer should not be null"); + assertTrue(parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer) == 0, "Current receive buffer should be empty, got %zu bytes", parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer)); + + assertTrue(metisMessage_GetIngressConnectionId(test) == ingressId, "Wrong ingress id expected %u got %u", ingressId, metisMessage_GetIngressConnectionId(test)); + assertTrue(metisMessage_GetReceiveTime(test) == startTicks, "Wrong receive time expected %" PRIu64 " got %" PRIu64, + startTicks, metisMessage_GetReceiveTime(test)); + + metisMessage_Release(&test); +} + +LONGBOW_TEST_CASE(Local, _finalizeReassemblyBuffer_Full) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + MetisTicks startTicks = 1111111; + unsigned ingressId = 77; + + // set up as just finished with a message, so the currentReceiveBuffer has + // a complete CCNx message array in it + data->fragmenter->parserState = _ParserState_Busy; + data->fragmenter->currentReceiveBufferIngressId = ingressId; + data->fragmenter->currentReceiveBufferStartTicks = startTicks; + parcEventBuffer_Append(data->fragmenter->currentReceiveBuffer, metisTestDataV1_Interest_AllFields, sizeof(metisTestDataV1_Interest_AllFields)); + + // create a full recieve queue + parcRingBuffer1x1_Release(&data->fragmenter->receiveQueue); + data->fragmenter->receiveQueue = parcRingBuffer1x1_Create(2, _ringBufferDestroyer); + + void *fakeData = (void *) 1; + parcRingBuffer1x1_Put(data->fragmenter->receiveQueue, fakeData); + + assertTrue(parcRingBuffer1x1_Remaining(data->fragmenter->receiveQueue) == 0, "expected queue to be full"); + + /* + * Call with a full receive queue + */ + _finalizeReassemblyBuffer(data->fragmenter); + + void *test = NULL; + parcRingBuffer1x1_Get(data->fragmenter->receiveQueue, &test); + assertTrue(test == fakeData, "Wrong pointer, expected %p got %p", fakeData, test); + + // teardown should show no memory leak +} + +LONGBOW_TEST_CASE(Local, _appendFragmentToReassemblyBuffer_Once) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + unsigned connid = 7; + MetisTicks receiveTime = 9999; + + MetisMessage *fragment = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Begin, sizeof(metisTestDataV1_HopByHopFrag_Begin), connid, receiveTime, data->logger); + _appendFragmentToReassemblyBuffer(data->fragmenter, fragment); + + int fragmentLength = sizeof(metisTestDataV1_HopByHopFrag_Begin_Fragment); + + assertTrue(parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer) == fragmentLength, + "currentReceiveBuffer wrong lenth, expected %d got %zu", + fragmentLength, + parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer)); + + uint8_t *test = parcEventBuffer_Pullup(data->fragmenter->currentReceiveBuffer, -1); + assertTrue(memcmp(test, metisTestDataV1_HopByHopFrag_Begin_Fragment, fragmentLength) == 0, "Fragment payload did not match"); + + metisMessage_Release(&fragment); +} + +LONGBOW_TEST_CASE(Local, _appendFragmentToReassemblyBuffer_Multiple) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + unsigned connid = 7; + MetisTicks receiveTime = 9999; + + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Begin, sizeof(metisTestDataV1_HopByHopFrag_Begin), connid, receiveTime, data->logger); + _appendFragmentToReassemblyBuffer(data->fragmenter, fragment1); + + MetisMessage *fragment2 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Middle, sizeof(metisTestDataV1_HopByHopFrag_Middle), connid, receiveTime, data->logger); + _appendFragmentToReassemblyBuffer(data->fragmenter, fragment2); + + MetisMessage *fragment3 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_End, sizeof(metisTestDataV1_HopByHopFrag_End), connid, receiveTime, data->logger); + _appendFragmentToReassemblyBuffer(data->fragmenter, fragment3); + + int fragmentLength = sizeof(metisTestDataV1_HopByHopFrag_BeginEnd_Fragment); + + assertTrue(parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer) == fragmentLength, + "currentReceiveBuffer wrong lenth, expected %d got %zu", + fragmentLength, + parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer)); + + uint8_t *test = parcEventBuffer_Pullup(data->fragmenter->currentReceiveBuffer, -1); + + // compares against the fragment metisTestDataV1_HopByHopFrag_BeginEnd which has the whole payload + assertTrue(memcmp(test, metisTestDataV1_HopByHopFrag_BeginEnd_Fragment, fragmentLength) == 0, "Fragment payload did not match"); + + metisMessage_Release(&fragment1); + metisMessage_Release(&fragment2); + metisMessage_Release(&fragment3); +} + + +/* + * B frame should be added to currentReceiveBuffer and state should become Busy. + * Also, the currentReceiveBufferIngressId and currentReceiveBufferReceiveTime should be set. + */ +LONGBOW_TEST_CASE(Local, _receiveInIdleState_BFrame) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // ensure we're in Idle state + _resetParser(data->fragmenter); + + unsigned connid = 7; + MetisTicks receiveTime = 9999; + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Begin, sizeof(metisTestDataV1_HopByHopFrag_Begin), connid, receiveTime, data->logger); + + const _HopByHopHeader *header = (const _HopByHopHeader *) metisTestDataV1_HopByHopFrag_Begin; + _receiveInIdleState(data->fragmenter, fragment1, header); + + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == sizeof(metisTestDataV1_HopByHopFrag_Begin_Fragment), "Wrong reassembly buffer length expected %zu got %zu", sizeof(metisTestDataV1_HopByHopFrag_Begin_Fragment), length); + assertTrue(data->fragmenter->parserState == _ParserState_Busy, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + assertTrue(data->fragmenter->currentReceiveBufferIngressId == connid, "Wrong ingress id expected %u got %u", connid, data->fragmenter->currentReceiveBufferIngressId); + assertTrue(data->fragmenter->currentReceiveBufferStartTicks == receiveTime, "Wrong receive time expected %" PRIu64 " got %" PRIu64, + receiveTime, data->fragmenter->currentReceiveBufferStartTicks); + metisMessage_Release(&fragment1); +} + +/* + * BE frame should be added to currentReceiveBuffer and finalized. + * State should stay in Idle but the receiveQueue should have the frame in it. + */ +LONGBOW_TEST_CASE(Local, _receiveInIdleState_BEFrame) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // ensure we're in Idle state + _resetParser(data->fragmenter); + + unsigned connid = 7; + MetisTicks receiveTime = 9999; + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_BeginEnd, sizeof(metisTestDataV1_HopByHopFrag_BeginEnd), connid, receiveTime, data->logger); + + const _HopByHopHeader *header = (const _HopByHopHeader *) metisTestDataV1_HopByHopFrag_BeginEnd; + _receiveInIdleState(data->fragmenter, fragment1, header); + + // should not be in the reassembly buffer + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == 0, "Wrong reassembly buffer length expected 0 got %zu", length); + + // it should be in the receive queue + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Message was not in receive queue"); + metisMessage_Release(&test); + + assertTrue(data->fragmenter->parserState == _ParserState_Idle, "Wrong parser state, exepcted %d got %d", _ParserState_Idle, data->fragmenter->parserState); + + + assertTrue(data->fragmenter->currentReceiveBufferIngressId == connid, "Wrong ingress id expected %u got %u", connid, data->fragmenter->currentReceiveBufferIngressId); + assertTrue(data->fragmenter->currentReceiveBufferStartTicks == receiveTime, "Wrong receive time expected %" PRIu64 " got %" PRIu64, + receiveTime, data->fragmenter->currentReceiveBufferStartTicks); + metisMessage_Release(&fragment1); +} + +/* + * Not B and Not BE frames should be ignored + */ +LONGBOW_TEST_CASE(Local, _receiveInIdleState_OtherFrame) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + struct test_vector { + uint8_t flags; + bool sentinel; + } testVectors[] = { + // All combinations except 0x40 and 0x60 + { .flags = 0x00, .sentinel = false }, + { .flags = 0x10, .sentinel = false }, + { .flags = 0x20, .sentinel = false }, + { .flags = 0x30, .sentinel = false }, + { .flags = 0x80, .sentinel = false }, + { .flags = 0x90, .sentinel = false }, + { .flags = 0xA0, .sentinel = false }, + { .flags = 0xB0, .sentinel = false }, + { .flags = 0x00, .sentinel = true }, + }; + + for (int i = 0; !testVectors[i].sentinel; i++) { + _HopByHopHeader header; + memset(&header, 0, sizeof(header)); + + header.blob[0] |= testVectors[i].flags; + + unsigned connid = 7; + MetisTicks receiveTime = 9999; + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_BeginEnd, sizeof(metisTestDataV1_HopByHopFrag_BeginEnd), connid, receiveTime, data->logger); + + _receiveInIdleState(data->fragmenter, fragment1, &header); + + metisMessage_Release(&fragment1); + + // should not be in the reassembly buffer + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == 0, "Wrong reassembly buffer length expected 0 got %zu", length); + + assertTrue(data->fragmenter->parserState == _ParserState_Idle, "Wrong parser state, exepcted %d got %d", _ParserState_Idle, data->fragmenter->parserState); + } +} + +/* + * 2) If E flag + * 2a) append to reassembly buffer + * 2b) finalize the buffer (side effect: will reset the parser and place in receive queue) + */ +LONGBOW_TEST_CASE(Local, _receiveInBusyState_EFrame) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + unsigned connid = 7; + MetisTicks receiveTime = 9999; + + // ensure we're in Busy state (the precondition of this test) + _resetParser(data->fragmenter); + data->fragmenter->parserState = _ParserState_Busy; + + // and put the Begin and Middle fragments in the reassembly buffer so the packet will decode properly + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Begin, sizeof(metisTestDataV1_HopByHopFrag_Begin), connid, receiveTime, data->logger); + _appendFragmentToReassemblyBuffer(data->fragmenter, fragment1); + + MetisMessage *fragment2 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Middle, sizeof(metisTestDataV1_HopByHopFrag_Middle), connid, receiveTime, data->logger); + _appendFragmentToReassemblyBuffer(data->fragmenter, fragment2); + + // ==== + // Now do the test + + MetisMessage *fragment3 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_End, sizeof(metisTestDataV1_HopByHopFrag_End), connid, receiveTime, data->logger); + + const _HopByHopHeader *header = (const _HopByHopHeader *) metisTestDataV1_HopByHopFrag_End; + _receiveInBusyState(data->fragmenter, fragment3, header); + + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == 0, "Wrong reassembly buffer length expected 0 got %zu", length); + + assertTrue(data->fragmenter->parserState == _ParserState_Idle, "Wrong parser state, exepcted %d got %d", _ParserState_Idle, data->fragmenter->parserState); + + // it should be in the receive queue + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Message was not in receive queue"); + metisMessage_Release(&test); + + metisMessage_Release(&fragment1); + metisMessage_Release(&fragment2); + metisMessage_Release(&fragment3); +} + +/* + * 1) If no flags + * 1a) append to reassembly buffer + */ +LONGBOW_TEST_CASE(Local, _receiveInBusyState_NoFlagFrame) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // ensure we're in Busy state (the precondition of this test) + _resetParser(data->fragmenter); + data->fragmenter->parserState = _ParserState_Busy; + + unsigned connid = 7; + MetisTicks receiveTime = 9999; + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Middle, sizeof(metisTestDataV1_HopByHopFrag_Middle), connid, receiveTime, data->logger); + + const _HopByHopHeader *header = (const _HopByHopHeader *) metisTestDataV1_HopByHopFrag_Middle; + _receiveInBusyState(data->fragmenter, fragment1, header); + + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == sizeof(metisTestDataV1_HopByHopFrag_Middle_Fragment), "Wrong reassembly buffer length expected %zu got %zu", sizeof(metisTestDataV1_HopByHopFrag_Middle_Fragment), length); + + assertTrue(data->fragmenter->parserState == _ParserState_Busy, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + metisMessage_Release(&fragment1); +} + +LONGBOW_TEST_CASE(Local, _receiveInBusyState_OtherFrame) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + struct test_vector { + uint8_t flags; + bool sentinel; + } testVectors[] = { + // All combinations except 0x00 and 0x20 + { .flags = 0x10, .sentinel = false }, + { .flags = 0x40, .sentinel = false }, + { .flags = 0x80, .sentinel = false }, + { .flags = 0x50, .sentinel = false }, + { .flags = 0x90, .sentinel = false }, + { .flags = 0xC0, .sentinel = false }, + { .flags = 0x00, .sentinel = true }, + }; + + for (int i = 0; !testVectors[i].sentinel; i++) { + _HopByHopHeader header; + memset(&header, 0, sizeof(header)); + + header.blob[0] |= testVectors[i].flags; + + unsigned connid = 7; + MetisTicks receiveTime = 9999; + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_BeginEnd, sizeof(metisTestDataV1_HopByHopFrag_BeginEnd), connid, receiveTime, data->logger); + + // ensure we're in Busy state (the precondition of this test) + _resetParser(data->fragmenter); + data->fragmenter->parserState = _ParserState_Busy; + + _receiveInBusyState(data->fragmenter, fragment1, &header); + + metisMessage_Release(&fragment1); + + // should not be in the reassembly buffer + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == 0, "Wrong reassembly buffer length expected 0 got %zu", length); + + assertTrue(data->fragmenter->parserState == _ParserState_Idle, "Wrong parser state, exepcted %d got %d", _ParserState_Idle, data->fragmenter->parserState); + } +} + +/* + * Receive a B frame in Idle state + */ +LONGBOW_TEST_CASE(Local, _receiveFragment_IdleState) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + unsigned connid = 7; + MetisTicks receiveTime = 9999; + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Begin, sizeof(metisTestDataV1_HopByHopFrag_Begin), connid, receiveTime, data->logger); + + _receiveFragment(data->fragmenter, fragment1); + + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == sizeof(metisTestDataV1_HopByHopFrag_Begin_Fragment), "Wrong reassembly buffer length expected %zu got %zu", sizeof(metisTestDataV1_HopByHopFrag_Begin_Fragment), length); + assertTrue(data->fragmenter->parserState == _ParserState_Busy, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + assertTrue(data->fragmenter->currentReceiveBufferIngressId == connid, "Wrong ingress id expected %u got %u", connid, data->fragmenter->currentReceiveBufferIngressId); + assertTrue(data->fragmenter->currentReceiveBufferStartTicks == receiveTime, "Wrong receive time expected %" PRIu64 " got %" PRIu64, + receiveTime, data->fragmenter->currentReceiveBufferStartTicks); + metisMessage_Release(&fragment1); +} + +LONGBOW_TEST_CASE(Local, _receiveFragment_BusyState) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // ensure we're in Busy state (the precondition of this test) + // Make sure the packet will be in-order by setting the next expected seqnum. + _resetParser(data->fragmenter); + data->fragmenter->parserState = _ParserState_Busy; + data->fragmenter->nextReceiveFragSequenceNumber = 2; + + unsigned connid = 7; + MetisTicks receiveTime = 9999; + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Middle, sizeof(metisTestDataV1_HopByHopFrag_Middle), connid, receiveTime, data->logger); + + _receiveFragment(data->fragmenter, fragment1); + + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == sizeof(metisTestDataV1_HopByHopFrag_Middle_Fragment), "Wrong reassembly buffer length expected %zu got %zu", sizeof(metisTestDataV1_HopByHopFrag_Middle_Fragment), length); + + assertTrue(data->fragmenter->parserState == _ParserState_Busy, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + metisMessage_Release(&fragment1); +} + +LONGBOW_TEST_CASE(Local, _sendFragments_OneFragment) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // make a packet shorter than one MTU (so it will fit with the fragment overhead) + size_t length = data->fragmenter->mtu - 100; + uint8_t *packet = _conjurePacket(length); + MetisMessage *message = metisMessage_CreateFromArray(packet, length, 1, 2, data->logger); + assertNotNull(message, "Could not conjure packet"); + + + bool success = _sendFragments(data->fragmenter, message); + assertTrue(success, "Failed to send fragments"); + MetisMessage *fragment = metisHopByHopFragmenter_PopSendQueue(data->fragmenter); + assertNotNull(fragment, "Did not find a fragment in the send queue"); + + // === + // defragment it + + _receiveFragment(data->fragmenter, fragment); + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Should have gotten the original message back"); + assertTrue(metisMessage_Length(test) == metisMessage_Length(message), + "Reconstructed message length wrong expected %zu got %zu", + metisMessage_Length(message), + metisMessage_Length(test)); + + // === + // cleanup + + metisMessage_Release(&message); + metisMessage_Release(&fragment); + metisMessage_Release(&test); + parcMemory_Deallocate((void **) &packet); +} + +LONGBOW_TEST_CASE(Local, _sendFragments_TwoFragments) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // Take up 2 MTUs (minus a little for fragmentation overhead) + size_t length = 2 * data->fragmenter->mtu - 100; + uint8_t *packet = _conjurePacket(length); + MetisMessage *message = metisMessage_CreateFromArray(packet, length, 1, 2, data->logger); + assertNotNull(message, "Could not conjure packet"); + + + bool success = _sendFragments(data->fragmenter, message); + assertTrue(success, "Failed to send fragments"); + + // === + // defragment it + + MetisMessage *fragment; + while ((fragment = metisHopByHopFragmenter_PopSendQueue(data->fragmenter)) != NULL) { + _receiveFragment(data->fragmenter, fragment); + metisMessage_Release(&fragment); + } + ; + + + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Should have gotten the original message back"); + assertTrue(metisMessage_Length(test) == metisMessage_Length(message), + "Reconstructed message length wrong expected %zu got %zu", + metisMessage_Length(message), + metisMessage_Length(test)); + + // === + // cleanup + + metisMessage_Release(&message); + metisMessage_Release(&test); + parcMemory_Deallocate((void **) &packet); +} + +LONGBOW_TEST_CASE(Local, _sendFragments_ThreeFragments) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // Take up 2 MTUs (minus a little for fragmentation overhead) + size_t length = 3 * data->fragmenter->mtu - 100; + uint8_t *packet = _conjurePacket(length); + MetisMessage *message = metisMessage_CreateFromArray(packet, length, 1, 2, data->logger); + assertNotNull(message, "Could not conjure packet"); + + + bool success = _sendFragments(data->fragmenter, message); + assertTrue(success, "Failed to send fragments"); + + // === + // defragment it + + MetisMessage *fragment; + while ((fragment = metisHopByHopFragmenter_PopSendQueue(data->fragmenter)) != NULL) { + _receiveFragment(data->fragmenter, fragment); + metisMessage_Release(&fragment); + } + ; + + + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Should have gotten the original message back"); + assertTrue(metisMessage_Length(test) == metisMessage_Length(message), + "Reconstructed message length wrong expected %zu got %zu", + metisMessage_Length(message), + metisMessage_Length(test)); + + // === + // cleanup + + metisMessage_Release(&message); + metisMessage_Release(&test); + parcMemory_Deallocate((void **) &packet); +} + +LONGBOW_TEST_CASE(Local, _sendFragments_SendQueueFull) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // create a full send queue + parcRingBuffer1x1_Release(&data->fragmenter->sendQueue); + data->fragmenter->sendQueue = parcRingBuffer1x1_Create(2, _ringBufferDestroyer); + + void *fakeData = (void *) 1; + parcRingBuffer1x1_Put(data->fragmenter->sendQueue, fakeData); + + + // Take up 2 MTUs (minus a little for fragmentation overhead) + size_t length = 3 * data->fragmenter->mtu - 100; + uint8_t *packet = _conjurePacket(length); + MetisMessage *message = metisMessage_CreateFromArray(packet, length, 1, 2, data->logger); + assertNotNull(message, "Could not conjure packet"); + + + bool success = _sendFragments(data->fragmenter, message); + assertFalse(success, "Should have failed to send fragments"); + // === + // cleanup + + // manually pop this off as it is not a proper MetisMessage + parcRingBuffer1x1_Get(data->fragmenter->sendQueue, &fakeData); + + metisMessage_Release(&message); + parcMemory_Deallocate((void **) &packet); +} + +LONGBOW_TEST_CASE(Local, _ringBufferDestroyer) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + unsigned connid = 7; + MetisTicks receiveTime = 9999; + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Middle, sizeof(metisTestDataV1_HopByHopFrag_Middle), connid, receiveTime, data->logger); + + bool success = parcRingBuffer1x1_Put(data->fragmenter->receiveQueue, fragment1); + assertTrue(success, "Failed to put test message in queue"); + + // nothing to do here. When the fragmenter is destroyed it should destroy the message + // and we will not trip a memory imbalance +} + +// ============================================================ +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_HopByHopFragmenter); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_IPMulticastListener.c b/metis/ccnx/forwarder/metis/io/test/test_metis_IPMulticastListener.c new file mode 100644 index 00000000..12dc17dc --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_IPMulticastListener.c @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_IPMulticastListener.c" +#include <parc/algol/parc_SafeMemory.h> + +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metis_IPMulticastListener) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_IPMulticastListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_IPMulticastListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Local) +{ +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_IPMulticastListener); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_IoOperations.c b/metis/ccnx/forwarder/metis/io/test/test_metis_IoOperations.c new file mode 100644 index 00000000..a78715c1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_IoOperations.c @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_IoOperations.c" +#include <parc/algol/parc_SafeMemory.h> + +#include <LongBow/unit-test.h> + +#include <ccnx/forwarder/metis/core/test/testrig_MetisIoOperations.h> + +// =========================================== + +LONGBOW_TEST_RUNNER(metis_IoOperations) +{ + LONGBOW_RUN_TEST_FIXTURE(Global); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_IoOperations) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_IoOperations) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_GetClosure); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_Send); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_GetRemoteAddress); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_GetAddressPair); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_IsUp); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_IsLocal); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_GetConnectionId); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_Release); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_Class); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_GetConnectionType); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_GetClosure) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + void *closure = metisIoOperations_GetClosure(ops); + assertTrue(closure == ops->closure, "Wrong closure, expected %p got %p", ops->closure, closure); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_Send) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_Send(ops, NULL, NULL); + assertTrue(data->sendCount == 1, "Wrong metisIoOperations_Send count expected 1 got %u", data->sendCount); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_GetRemoteAddress) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_GetRemoteAddress(ops); + assertTrue(data->getRemoteAddressCount == 1, "Wrong metisIoOperations_GetRemoteAddress count expected 1 got %u", data->getRemoteAddressCount); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_GetAddressPair) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_GetAddressPair(ops); + assertTrue(data->getAddressPairCount == 1, "Wrong metisIoOperations_GetAddressPairexpected count 1 got %u", data->getAddressPairCount); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_IsUp) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_IsUp(ops); + assertTrue(data->isUpCount == 1, "Wrong metisIoOperations_IsUp count expected 1 got %u", data->isUpCount); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_IsLocal) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_IsLocal(ops); + assertTrue(data->isLocalCount == 1, "Wrong metisIoOperations_IsLocal count expected 1 got %u", data->isLocalCount); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_GetConnectionId) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_GetConnectionId(ops); + assertTrue(data->getConnectionIdCount == 1, "Wrong metisIoOperations_GetConnectionId count expected 1 got %u", data->getConnectionIdCount); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_Release) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MetisIoOperations *copy = ops; + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_Release(&ops); + assertTrue(data->destroyCount == 1, "Wrong metisIoOperations_Release count expected 1 got %u", data->destroyCount); + mockIoOperationsData_Destroy(©); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_Class) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_Class(ops); + assertTrue(data->classCount == 1, "Wrong metisIoOperations_Class count expected 1 got %u", data->classCount); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_GetConnectionType) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_GetConnectionType(ops); + assertTrue(data->getConnectionTypeCount == 1, "Wrong getConnectionTypeCount count expected 1 got %u", data->getConnectionTypeCount); + mockIoOperationsData_Destroy(&ops); +} + + + +// =========================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_IoOperations); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_ListenerSet.c b/metis/ccnx/forwarder/metis/io/test/test_metis_ListenerSet.c new file mode 100644 index 00000000..b786d9b3 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_ListenerSet.c @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_ListenerSet.c" +#include <parc/algol/parc_SafeMemory.h> + +#include <LongBow/unit-test.h> + +#include "testrig_MetisListenerOps.c" + + +LONGBOW_TEST_RUNNER(metis_ListenerSet) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_ListenerSet) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_ListenerSet) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisListenerSet_Add_Single); + LONGBOW_RUN_TEST_CASE(Global, metisListenerSet_Add_Unique); + LONGBOW_RUN_TEST_CASE(Global, metisListenerSet_Add_Duplicate); + LONGBOW_RUN_TEST_CASE(Global, metisListenerSet_Create_Destroy); + + + LONGBOW_RUN_TEST_CASE(Global, metisListenerSet_Length); + LONGBOW_RUN_TEST_CASE(Global, metisListenerSet_Get); + LONGBOW_RUN_TEST_CASE(Global, metisListenerSet_Find_InSet); + LONGBOW_RUN_TEST_CASE(Global, metisListenerSet_Find_NotInSet); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +// Adds a single MockListenerData to the listener set. +static MockListenerData * +addSingle(MetisListenerSet *set) +{ + CPIAddress *listenAddress = cpiAddress_CreateFromInterface(44); + MockListenerData *data = mockListenData_Create(1, listenAddress, METIS_ENCAP_ETHER); + MetisListenerOps *listenerOps = mockListener_Create(data); + + bool success = metisListenerSet_Add(set, listenerOps); + assertTrue(success, "Got failure adding one listener to the set"); + assertTrue(parcArrayList_Size(set->listOfListeners) == 1, + "Got wrong list length, got %zu expected %u", + parcArrayList_Size(set->listOfListeners), 1); + + cpiAddress_Destroy(&listenAddress); + return data; +} + +LONGBOW_TEST_CASE(Global, metisListenerSet_Add_Single) +{ + MetisListenerSet *set = metisListenerSet_Create(); + + MockListenerData *data = addSingle(set); + + metisListenerSet_Destroy(&set); + assertTrue(data->destroyCount == 1, + "Wrong destroy count, got %u expected %u", + data->destroyCount, 1); + + mockListenerData_Destroy(&data); +} + +LONGBOW_TEST_CASE(Global, metisListenerSet_Add_Unique) +{ + CPIAddress *listenAddress_A = cpiAddress_CreateFromInterface(44); + MockListenerData *data_A = mockListenData_Create(1, listenAddress_A, METIS_ENCAP_ETHER); + MetisListenerOps *listenerOps_A = mockListener_Create(data_A); + + CPIAddress *listenAddress_B = cpiAddress_CreateFromInterface(55); + MockListenerData *data_B = mockListenData_Create(1, listenAddress_B, METIS_ENCAP_ETHER); + MetisListenerOps *listenerOps_B = mockListener_Create(data_B); + + + MetisListenerSet *set = metisListenerSet_Create(); + bool success_A = metisListenerSet_Add(set, listenerOps_A); + assertTrue(success_A, "Got failure adding listener A to the set"); + + bool success_B = metisListenerSet_Add(set, listenerOps_B); + assertTrue(success_B, "Got failure adding listener B to the set"); + + cpiAddress_Destroy(&listenAddress_A); + cpiAddress_Destroy(&listenAddress_B); + metisListenerSet_Destroy(&set); + + mockListenerData_Destroy(&data_A); + mockListenerData_Destroy(&data_B); +} + +LONGBOW_TEST_CASE(Global, metisListenerSet_Add_Duplicate) +{ + CPIAddress *listenAddress_A = cpiAddress_CreateFromInterface(44); + MockListenerData *data_A = mockListenData_Create(1, listenAddress_A, METIS_ENCAP_ETHER); + MetisListenerOps *listenerOps_A = mockListener_Create(data_A); + + CPIAddress *listenAddress_B = cpiAddress_CreateFromInterface(44); + MockListenerData *data_B = mockListenData_Create(1, listenAddress_B, METIS_ENCAP_ETHER); + MetisListenerOps *listenerOps_B = mockListener_Create(data_B); + + + MetisListenerSet *set = metisListenerSet_Create(); + bool success_A = metisListenerSet_Add(set, listenerOps_A); + assertTrue(success_A, "Got failure adding listener A to the set"); + + bool success_B = metisListenerSet_Add(set, listenerOps_B); + assertFalse(success_B, "Got success adding listener B to the set, duplicate should have failed"); + + cpiAddress_Destroy(&listenAddress_A); + cpiAddress_Destroy(&listenAddress_B); + metisListenerSet_Destroy(&set); + + mockListener_Destroy(&listenerOps_B); + mockListenerData_Destroy(&data_A); + mockListenerData_Destroy(&data_B); +} + + +LONGBOW_TEST_CASE(Global, metisListenerSet_Create_Destroy) +{ + MetisListenerSet *set = metisListenerSet_Create(); + assertNotNull(set, "Got null from Create"); + + metisListenerSet_Destroy(&set); + assertNull(set, "Destroy did not null parameter"); +} + +LONGBOW_TEST_CASE(Global, metisListenerSet_Length) +{ + MetisListenerSet *set = metisListenerSet_Create(); + MockListenerData *data = addSingle(set); + + size_t length = metisListenerSet_Length(set); + + metisListenerSet_Destroy(&set); + mockListenerData_Destroy(&data); + + assertTrue(length == 1, + "Wrong length, got %zu expected %u", + length, 1); +} + +LONGBOW_TEST_CASE(Global, metisListenerSet_Get) +{ + MetisListenerSet *set = metisListenerSet_Create(); + MockListenerData *data = addSingle(set); + + MetisListenerOps *ops = metisListenerSet_Get(set, 0); + + assertNotNull(ops, "Did not fetch the listener ops"); + + metisListenerSet_Destroy(&set); + mockListenerData_Destroy(&data); +} + +LONGBOW_TEST_CASE(Global, metisListenerSet_Find_InSet) +{ + MetisListenerSet *set = metisListenerSet_Create(); + MockListenerData *data = addSingle(set); + + MetisListenerOps *ops = metisListenerSet_Find(set, data->encapType, data->listenAddress); + assertNotNull(ops, "Did not retrieve the listener that is in the set"); + + metisListenerSet_Destroy(&set); + mockListenerData_Destroy(&data); +} + +LONGBOW_TEST_CASE(Global, metisListenerSet_Find_NotInSet) +{ + MetisListenerSet *set = metisListenerSet_Create(); + MockListenerData *data = addSingle(set); + + // use wrong encap type + MetisListenerOps *ops = metisListenerSet_Find(set, data->encapType + 1, data->listenAddress); + assertNull(ops, "Should not have found anything with wrong encap type"); + + metisListenerSet_Destroy(&set); + mockListenerData_Destroy(&data); +} + + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisListenerSet_DestroyListenerOps); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, metisListenerSet_DestroyListenerOps) +{ + testUnimplemented(""); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_ListenerSet); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_LocalListener.c b/metis/ccnx/forwarder/metis/io/test/test_metis_LocalListener.c new file mode 100644 index 00000000..2852d253 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_LocalListener.c @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_LocalListener.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +LONGBOW_TEST_RUNNER(metis_LocalListener) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_LocalListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_LocalListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisListenerLocal_Create_Destroy); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisListenerLocal_Create_Destroy) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisListenerLocal_Listen); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, metisListenerLocal_Listen) +{ + testUnimplemented("This test is unimplemented"); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_LocalListener); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_StreamConnection.c b/metis/ccnx/forwarder/metis/io/test/test_metis_StreamConnection.c new file mode 100644 index 00000000..5ae50bcd --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_StreamConnection.c @@ -0,0 +1,767 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_StreamConnection.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <parc/logging/parc_LogReporterTextStdout.h> + +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> +#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h> + +// inet_pton +#include <arpa/inet.h> + +#include <fcntl.h> + +#include <stdio.h> + +#ifndef INPORT_ANY +#define INPORT_ANY 0 +#endif + +// we hand-code some packets in the unit tests +typedef struct __attribute__ ((__packed__)) metis_tlv_fixed_header { + uint8_t version; + uint8_t packetType; + uint16_t payloadLength; + uint16_t reserved; + uint16_t headerLength; +} _MetisTlvFixedHeaderV0; + + +LONGBOW_TEST_RUNNER(metis_StreamConnection) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_StreamConnection) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_StreamConnection) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ================================================================================ +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisStreamConnection_Create); + LONGBOW_RUN_TEST_CASE(Global, metisStreamConnection_OpenConnection); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisStreamConnection_Create) +{ + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fd, pair, false); + + ops->destroy(&ops); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + + metisForwarder_Destroy(&metis); + close(fd); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); + assertTrue(parcSafeMemory_Outstanding() == 0, "Got memory imbalance: %u", parcSafeMemory_Outstanding()); +} + +static int +listenToInet(struct sockaddr_in *server) +{ + int fd = socket(PF_INET, SOCK_STREAM, 0); + assertFalse(fd < 0, "error on bind: (%d) %s", errno, strerror(errno)); + + // Set non-blocking flag + int flags = fcntl(fd, F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)\n", errno); + int failure = fcntl(fd, F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)\n", errno); + + failure = bind(fd, (struct sockaddr *) server, sizeof(struct sockaddr_in)); + assertFalse(failure, "error on bind: (%d) %s", errno, strerror(errno)); + + failure = listen(fd, 16); + assertFalse(failure, "error on listen: (%d) %s", errno, strerror(errno)); + + return fd; +} + +LONGBOW_TEST_CASE(Global, metisStreamConnection_OpenConnection) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + + struct sockaddr_in serverAddr; + memset(&serverAddr, 0, sizeof(serverAddr)); + serverAddr.sin_family = PF_INET; + serverAddr.sin_port = INPORT_ANY; + inet_pton(AF_INET, "127.0.0.1", &(serverAddr.sin_addr)); + + int serverSocket = listenToInet(&serverAddr); + socklen_t x = sizeof(serverAddr); + int failure = getsockname(serverSocket, (struct sockaddr *) &serverAddr, &x); + assertFalse(failure, "error on getsockname: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in localAddr; + memset(&localAddr, 0, sizeof(localAddr)); + localAddr.sin_family = PF_INET; + localAddr.sin_addr.s_addr = INADDR_ANY; + localAddr.sin_port = INPORT_ANY; + + CPIAddress *local = cpiAddress_CreateFromInet(&localAddr); + + // change from 0.0.0.0 to 127.0.0.1 + inet_pton(AF_INET, "127.0.0.1", &(serverAddr.sin_addr)); + + CPIAddress *remote = cpiAddress_CreateFromInet(&serverAddr); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); + + MetisIoOperations *ops = metisStreamConnection_OpenConnection(metis, pair, false); + assertNotNull(ops, "Got null ops from metisStreamConnection_OpenConnection"); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); +} + +// ======================================================================= + +// ================================================================================== + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, conn_eventcb_Connected); + LONGBOW_RUN_TEST_CASE(Local, conn_eventcb_EOF); + LONGBOW_RUN_TEST_CASE(Local, conn_eventcb_ERROR); + + LONGBOW_RUN_TEST_CASE(Local, conn_readcb); + LONGBOW_RUN_TEST_CASE(Local, metisStreamConnection_Equals); + LONGBOW_RUN_TEST_CASE(Local, metisStreamConnection_GetAddress); + LONGBOW_RUN_TEST_CASE(Local, metisStreamConnection_GetAddressPair); + LONGBOW_RUN_TEST_CASE(Local, metisStreamConnection_GetConnectionId); + LONGBOW_RUN_TEST_CASE(Local, metisStreamConnection_HashCode); + LONGBOW_RUN_TEST_CASE(Local, metisStreamConnection_IsUp); + LONGBOW_RUN_TEST_CASE(Local, metisStreamConnection_Send); + LONGBOW_RUN_TEST_CASE(Local, metisStreamConnection_GetConnectionType); + LONGBOW_RUN_TEST_CASE(Local, printConnection); + LONGBOW_RUN_TEST_CASE(Local, readMessage); + LONGBOW_RUN_TEST_CASE(Local, setConnectionState); + LONGBOW_RUN_TEST_CASE(Local, single_read_ZeroNextMessageLength); + LONGBOW_RUN_TEST_CASE(Local, single_read_PartialRead); + LONGBOW_RUN_TEST_CASE(Local, single_read_FullRead); + LONGBOW_RUN_TEST_CASE(Local, startNewMessage); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, conn_eventcb_Connected) +{ + int fds[2]; + int failure = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds); + assertFalse(failure, "Error socketpair: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fds[0], pair, false); + _MetisStreamState *stream = (_MetisStreamState *) metisIoOperations_GetClosure(ops); + + stream->isUp = false; + + // ---- the actual test + _conn_eventcb(stream->bufferEventVector, PARCEventQueueEventType_Connected, ops); + assertTrue(stream->isUp, "PARCEventQueueEventType_Connected did not trigger stream to up state"); + // ---- + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + close(fds[0]); + close(fds[1]); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); +} + +LONGBOW_TEST_CASE(Local, conn_eventcb_EOF) +{ + int fds[2]; + int failure = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds); + assertFalse(failure, "Error socketpair: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fds[0], pair, false); + _MetisStreamState *stream = (_MetisStreamState *) metisIoOperations_GetClosure(ops); + + stream->isUp = true; + + // ---- the actual test + _conn_eventcb(stream->bufferEventVector, PARCEventQueueEventType_EOF, ops); + assertFalse(stream->isUp, "PARCEventQueueEventType_EOF did not trigger stream to down state"); + // ---- + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + close(fds[0]); + close(fds[1]); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); +} + +LONGBOW_TEST_CASE(Local, conn_eventcb_ERROR) +{ + int fds[2]; + int failure = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds); + assertFalse(failure, "Error socketpair: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fds[0], pair, false); + _MetisStreamState *stream = (_MetisStreamState *) metisIoOperations_GetClosure(ops); + + stream->isUp = true; + + // ---- the actual test + _conn_eventcb(stream->bufferEventVector, PARCEventQueueEventType_Error, ops); + assertFalse(stream->isUp, "PARCEventQueueEventType_Error did not trigger stream to down state"); + // ---- + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + close(fds[0]); + close(fds[1]); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); +} + +LONGBOW_TEST_CASE(Local, conn_readcb) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, metisStreamConnection_Equals) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, metisStreamConnection_GetAddress) +{ + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fd, pair, false); + + const CPIAddress *test_addr = ops->getRemoteAddress(ops); + + assertTrue(cpiAddress_Equals(remote, test_addr), "ops->getAddress incorrect"); + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); + + close(fd); +} + +LONGBOW_TEST_CASE(Local, metisStreamConnection_GetAddressPair) +{ + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fd, pair, false); + + const MetisAddressPair *test_pair = ops->getAddressPair(ops); + + assertTrue(metisAddressPair_Equals(pair, test_pair), "ops->getRemoteAddress incorrect"); + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); + + close(fd); +} + +LONGBOW_TEST_CASE(Local, metisStreamConnection_GetConnectionId) +{ + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + unsigned truth_connid = metisForwarder_GetNextConnectionId(metis) + 1; + + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fd, pair, false); + + assertTrue(ops->getConnectionId(ops) == truth_connid, "Got wrong connection id, expected %u got %u", truth_connid, ops->getConnectionId(ops)); + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); + + close(fd); +} + +LONGBOW_TEST_CASE(Local, metisStreamConnection_HashCode) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, metisStreamConnection_IsUp) +{ + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fd, pair, false); + + assertTrue(ops->isUp(ops), "isUp incorrect, expected true, got false"); + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); + close(fd); +} + +LONGBOW_TEST_CASE(Local, metisStreamConnection_Send) +{ + // StreamConnection_Create needs a socket and address to represent the peer + // we use a socket pair so we can actaully read from it and verify what is sent. + + int fds[2]; + int failure = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds); + assertFalse(failure, "Error socketpair: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fds[0], pair, false); + + // ---------- + // Create a fake message. Send does not care what the message is, it just writes it out. + // We include a real header, but it is not needed. + + char message_str[] = "\x00Once upon a jiffie, in a stack far away, a dangling pointer found its way to the top of the heap."; + _MetisTlvFixedHeaderV0 *hdr = (_MetisTlvFixedHeaderV0 *) message_str; + hdr->payloadLength = htons(92); + hdr->headerLength = htons(0); + + MetisMessage *sendmessage = metisMessage_CreateFromArray((uint8_t *) message_str, sizeof(message_str), 1, 2, metisForwarder_GetLogger(metis)); + + // ---------- + // actually send it + ops->send(ops, NULL, sendmessage); + metisMessage_Release(&sendmessage); + + // ---------- + // turn the handleto crank + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + + // ---------- + // Now read the result from our end of the socket pair. + + uint8_t read_buffer[1024]; + + // read it and verify + ssize_t read_length = read(fds[1], read_buffer, 1024); + assertTrue(read_length == sizeof(message_str), + "Incorrect read length, expected %zu got %zd: (%d) %s", + sizeof(message_str), read_length, errno, strerror(errno)); + + assertTrue(memcmp(read_buffer, message_str, sizeof(message_str)) == 0, "read_buffer does not match message_str"); + + // ---------- + // hurray, no messages where harmed in this experiment + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + close(fds[0]); + close(fds[1]); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); +} + +LONGBOW_TEST_CASE(Local, metisStreamConnection_GetConnectionType) +{ + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fd, pair, false); + + CPIConnectionType connType = _metisStreamConnection_GetConnectionType(ops); + assertTrue(connType == cpiConnection_TCP, "Wrong connection type expected %d got %d", cpiConnection_TCP, connType); + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); + close(fd); +} + +LONGBOW_TEST_CASE(Local, printConnection) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, readMessage) +{ + char message_str[] = "\x00Once upon a jiffie, in a stack far away, a dangling pointer found its way to the top of the heap."; + _MetisTlvFixedHeaderV0 *hdr = (_MetisTlvFixedHeaderV0 *) message_str; + hdr->payloadLength = htons(92); + hdr->headerLength = htons(0); + + PARCEventBuffer *buff = parcEventBuffer_Create(); + parcEventBuffer_Append(buff, message_str, sizeof(message_str)); + + _MetisStreamState *stream = parcMemory_AllocateAndClear(sizeof(_MetisStreamState)); + assertNotNull(stream, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisStreamState)); + stream->nextMessageLength = parcEventBuffer_GetLength(buff); + stream->id = 77; + + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + stream->logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + MetisMessage *message = _readMessage(stream, 444, buff); + + assertNotNull(message, "Got null message from readMessage"); + assertTrue(parcEventBuffer_GetLength(buff) == 0, "Did not drain input buffer, expected 0 got %zu", parcEventBuffer_GetLength(buff)); + //assertTrue(metisMessage_Length(message) == sizeof(message_str), + //"Message length wrong, expected %zu got %zu", + //sizeof(message_str), + //metisMessage_Length(message)); + + metisMessage_Release(&message); + parcEventBuffer_Destroy(&buff); + metisLogger_Release(&stream->logger); + parcMemory_Deallocate((void **) &stream); +} + +LONGBOW_TEST_CASE(Local, setConnectionState) +{ + testUnimplemented("This test is unimplemented"); +} + +/** + * Call like the beignning of a new packet, with stream->nextMessageLength set to 0 + */ +LONGBOW_TEST_CASE(Local, single_read_ZeroNextMessageLength) +{ + PARCEventBuffer *buff = parcEventBuffer_Create(); + + // do it like a short read, only 12 bytes + parcEventBuffer_Append(buff, metisTestDataV0_EncodedInterest, 12); + + MetisForwarder *metis = metisForwarder_Create(NULL); + _MetisStreamState *stream = parcMemory_AllocateAndClear(sizeof(_MetisStreamState)); + assertNotNull(stream, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisStreamState)); + stream->metis = metis; + stream->nextMessageLength = 0; + stream->id = 77; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + stream->logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = _single_read(buff, stream); + + assertNull(message, "message should be null, its a short read"); + assertTrue(parcEventBuffer_GetLength(buff) == 12, "Should not have drained buffer, expected %d got %zu", 12, parcEventBuffer_GetLength(buff)); + assertTrue(stream->nextMessageLength == sizeof(metisTestDataV0_EncodedInterest), + "NextMessageLength not set correctly, expected %zu got %zu", + sizeof(metisTestDataV0_EncodedInterest), + stream->nextMessageLength); + + parcEventBuffer_Destroy(&buff); + metisLogger_Release(&stream->logger); + parcMemory_Deallocate((void **) &stream); + metisForwarder_Destroy(&metis); +} + +/** + * Call with stream->nextMessageLength set correctly, but not enough bytes in the buffer + */ +LONGBOW_TEST_CASE(Local, single_read_PartialRead) +{ + PARCEventBuffer *buff = parcEventBuffer_Create(); + + // do it like a short read, only 12 bytes + parcEventBuffer_Append(buff, metisTestDataV0_EncodedInterest, 12); + + MetisForwarder *metis = metisForwarder_Create(NULL); + _MetisStreamState *stream = parcMemory_AllocateAndClear(sizeof(_MetisStreamState)); + assertNotNull(stream, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisStreamState)); + stream->metis = metis; + stream->nextMessageLength = sizeof(metisTestDataV0_EncodedInterest); + stream->id = 77; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + stream->logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = _single_read(buff, stream); + + assertNull(message, "message should be null, its a short read"); + assertTrue(parcEventBuffer_GetLength(buff) == 12, "Should not have drained buffer, expected %d got %zu", 12, parcEventBuffer_GetLength(buff)); + assertTrue(stream->nextMessageLength == sizeof(metisTestDataV0_EncodedInterest), + "NextMessageLength not set correctly, expected %zu got %zu", + sizeof(metisTestDataV0_EncodedInterest), + stream->nextMessageLength); + + parcEventBuffer_Destroy(&buff); + metisLogger_Release(&stream->logger); + parcMemory_Deallocate((void **) &stream); + metisForwarder_Destroy(&metis); +} + +/** + * Call with enough bytes in the buffer to read the whole message + */ +LONGBOW_TEST_CASE(Local, single_read_FullRead) +{ + PARCEventBuffer *buff = parcEventBuffer_Create(); + + // do it like a full read + parcEventBuffer_Append(buff, metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest)); + + MetisForwarder *metis = metisForwarder_Create(NULL); + _MetisStreamState *stream = parcMemory_AllocateAndClear(sizeof(_MetisStreamState)); + assertNotNull(stream, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisStreamState)); + stream->metis = metis; + stream->nextMessageLength = sizeof(metisTestDataV0_EncodedInterest); + stream->id = 77; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + stream->logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = _single_read(buff, stream); + + assertNotNull(message, "message should not be null, its a short read"); + assertTrue(parcEventBuffer_GetLength(buff) == 0, "Should have drained buffer, expected %d got %zu", 0, parcEventBuffer_GetLength(buff)); + + // should reset the next message length after reading a whole packet + assertTrue(stream->nextMessageLength == 0, + "NextMessageLength not set correctly, expected %u got %zu", + 0, + stream->nextMessageLength); + + metisMessage_Release(&message); + parcEventBuffer_Destroy(&buff); + metisLogger_Release(&stream->logger); + parcMemory_Deallocate((void **) &stream); + metisForwarder_Destroy(&metis); +} + +LONGBOW_TEST_CASE(Local, startNewMessage) +{ + _MetisStreamState *stream = parcMemory_AllocateAndClear(sizeof(_MetisStreamState)); + assertNotNull(stream, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisStreamState)); + + // add data to the buffer to fake out having read from the network + PARCEventBuffer *buff = parcEventBuffer_Create(); + uint8_t *truth_message = parcMemory_Allocate(100); + assertNotNull(truth_message, "parcMemory_Allocate(%u) returned NULL", 100); + + _MetisTlvFixedHeaderV0 *hdr = (_MetisTlvFixedHeaderV0 *) truth_message; + hdr->version = 0; + hdr->payloadLength = htons(92); + hdr->headerLength = htons(0); + + parcEventBuffer_Append(buff, truth_message, 100); + + stream->nextMessageLength = 0; + + _startNewMessage(stream, buff, 100); + + assertTrue(stream->nextMessageLength == 100, "nextMessageLength wrong, expected %d got %zu", 100, stream->nextMessageLength); + + parcEventBuffer_Destroy(&buff); + parcMemory_Deallocate((void **) &stream); + parcMemory_Deallocate((void **) &truth_message); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_StreamConnection); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_TcpListener.c b/metis/ccnx/forwarder/metis/io/test/test_metis_TcpListener.c new file mode 100644 index 00000000..87e51ae2 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_TcpListener.c @@ -0,0 +1,364 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * hard-coded in port 49009 on localhost + */ + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_TcpListener.c" +#include <LongBow/unit-test.h> +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> +#include <parc/algol/parc_SafeMemory.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h> + +#include <parc/algol/parc_Network.h> + +// for inet_pton +#include <arpa/inet.h> + +#include <signal.h> + +struct test_set { + CPIAddress *listenAddress; + MetisForwarder *metis; + MetisListenerOps *ops; +} TestSet; + +static void +setupInetListener() +{ + struct sockaddr_in addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(49009); + inet_pton(AF_INET, "127.0.0.1", &(addr.sin_addr)); + + TestSet.metis = metisForwarder_Create(NULL); + TestSet.ops = metisTcpListener_CreateInet(TestSet.metis, addr); + TestSet.listenAddress = cpiAddress_CreateFromInet(&addr); + + // crank the event scheduler once + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) {0, 10000})); +} + +static void +setupInet6Listener() +{ + struct sockaddr_in6 addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(49009); + + // "::1" is the ipv6 loopback address + inet_pton(AF_INET6, "::1", &(addr.sin6_addr)); + + TestSet.metis = metisForwarder_Create(NULL); + TestSet.ops = metisTcpListener_CreateInet6(TestSet.metis, addr); + TestSet.listenAddress = cpiAddress_CreateFromInet6(&addr); + + // crank the event scheduler once + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) {0, 10000})); +} + +static void +teardownListener() +{ + cpiAddress_Destroy(&TestSet.listenAddress); + TestSet.ops->destroy(&TestSet.ops); + metisForwarder_Destroy(&TestSet.metis); +} + +struct sigaction save_sigchld; +struct sigaction save_sigpipe; + +static void +blockSigChild() +{ + struct sigaction ignore_action; + ignore_action.sa_handler = SIG_IGN; + sigemptyset(&ignore_action.sa_mask); + ignore_action.sa_flags = 0; + + sigaction(SIGCHLD, NULL, &save_sigchld); + sigaction(SIGPIPE, NULL, &save_sigpipe); + + sigaction(SIGCHLD, &ignore_action, NULL); + sigaction(SIGPIPE, &ignore_action, NULL); +} + +static void +unblockSigChild() +{ + sigaction(SIGCHLD, &save_sigchld, NULL); + sigaction(SIGPIPE, &save_sigpipe, NULL); +} + +LONGBOW_TEST_RUNNER(metis_TcpListener) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global_Inet); + LONGBOW_RUN_TEST_FIXTURE(Global_Inet6); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_TcpListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_TcpListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// =========================================================================== + +LONGBOW_TEST_FIXTURE(Global_Inet) +{ + LONGBOW_RUN_TEST_CASE(Global_Inet, metisListenerTcp_CreateInet); + LONGBOW_RUN_TEST_CASE(Global_Inet, metisListenerTcp_Connect); + LONGBOW_RUN_TEST_CASE(Global_Inet, metisListenerTcp_SendPacket); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global_Inet) +{ + setupInetListener(); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global_Inet) +{ + teardownListener(); + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global_Inet, metisListenerTcp_CreateInet) +{ + // now verify that we are listening + // tcp4 0 0 127.0.0.1.49009 *.* LISTEN + + blockSigChild(); + FILE *fp = popen("netstat -an -p tcp", "r"); + assertNotNull(fp, "Got null opening netstat for reading"); + + char str[1035]; + bool found = false; + while (fgets(str, sizeof(str) - 1, fp) != NULL) { + if (strstr(str, "127.0.0.1.49009") != NULL) { + found = true; + break; + } + if (strstr(str, "127.0.0.1:49009") != NULL) { + found = true; + break; + } + } + + pclose(fp); + unblockSigChild(); + + if (!found) { + int ret = system("netstat -an -p tcp"); + assertTrue(ret > -1, "Error on system call"); + } + + assertTrue(found, "Did not find 127.0.0.1.49009 in netstat output"); +} + +LONGBOW_TEST_CASE(Global_Inet, metisListenerTcp_Connect) +{ + int fd = socket(PF_INET, SOCK_STREAM, 0); + assertFalse(fd < 0, "Error on socket: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in serverAddress; + cpiAddress_GetInet(TestSet.listenAddress, &serverAddress); + + int failure = connect(fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)); + assertFalse(failure, "Error on connect: (%d) %s", errno, strerror(errno)); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) {0, 10000})); + + struct sockaddr_in connectAddress; + socklen_t connectAddressLength = sizeof(connectAddress); + failure = getsockname(fd, (struct sockaddr *) &connectAddress, &connectAddressLength); + assertFalse(failure, "Error on getsockname: (%d) %s", errno, strerror(errno)); + assertTrue(connectAddressLength == sizeof(struct sockaddr_in), + "connectAddressLength wrong size, expected %zu got %u", + sizeof(struct sockaddr_in), connectAddressLength); + + // make sure its in the connection table + MetisConnectionTable *table = metisForwarder_GetConnectionTable(TestSet.metis); + CPIAddress *remote = cpiAddress_CreateFromInet(&connectAddress); + MetisAddressPair *pair = metisAddressPair_Create(TestSet.listenAddress, remote); + const MetisConnection *conn = metisConnectionTable_FindByAddressPair(table, pair); + assertNotNull(conn, "Did not find connection in connection table"); + + cpiAddress_Destroy(&remote); + metisAddressPair_Release(&pair); + + close(fd); +} + +LONGBOW_TEST_CASE(Global_Inet, metisListenerTcp_SendPacket) +{ + int fd = socket(PF_INET, SOCK_STREAM, 0); + assertFalse(fd < 0, "Error on socket: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in serverAddress; + cpiAddress_GetInet(TestSet.listenAddress, &serverAddress); + + int failure = connect(fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)); + assertFalse(failure, "Error on connect: (%d) %s", errno, strerror(errno)); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) {0, 10000})); + + ssize_t write_length = write(fd, metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName)); + assertFalse(write_length < 0, "Error on write: (%d) %s", errno, strerror(errno)); + assertTrue(write_length == sizeof(metisTestDataV0_InterestWithName), "Got partial write, expected %zu got %zd", sizeof(metisTestDataV0_InterestWithName), write_length); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) {0, 10000})); + + close(fd); +} + +// =========================================================================== + +LONGBOW_TEST_FIXTURE(Global_Inet6) +{ + LONGBOW_RUN_TEST_CASE(Global_Inet6, metisListenerTcp_CreateInet6); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global_Inet6) +{ + setupInet6Listener(); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global_Inet6) +{ + teardownListener(); + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global_Inet6, metisListenerTcp_CreateInet6) +{ + // now verify that we are listening + // tcp6 0 0 ::1.49009 *.* LISTEN + + blockSigChild(); + FILE *fp = popen("netstat -an -p tcp", "r"); + assertNotNull(fp, "Got null opening netstat for reading"); + + char str[1035]; + bool found = false; + while (fgets(str, sizeof(str) - 1, fp) != NULL) { + if (strstr(str, "::1.49009") != NULL) { + found = true; + break; + } + if (strstr(str, "::1:49009") != NULL) { + found = true; + break; + } + } + + pclose(fp); + unblockSigChild(); + + if (!found) { + int ret = system("netstat -an -p tcp"); + assertTrue(ret > -1, "Error on system call"); + } + + assertTrue(found, "Did not find ::1.49009 in netstat output"); +} + +// =========================================================================== + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisListenerTcp_Listen); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +/** + * Create a TCP INET listener then connect to it. + */ +LONGBOW_TEST_CASE(Local, metisListenerTcp_Listen) +{ + setupInetListener(); + + struct sockaddr_in addr_remote; + + memset(&addr_remote, 0, sizeof(addr_remote)); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(49010); + inet_pton(AF_INET, "127.0.0.1", &(addr_remote.sin_addr)); + + _MetisTcpListener *tcp = (_MetisTcpListener *) TestSet.ops->context; + + int fds[2]; + int failure = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds); + assertFalse(failure, "Failed with socketpair: (%d) %s", errno, strerror(errno)); + + _metisTcpListener_Listen(fds[0], (struct sockaddr *) &addr_remote, sizeof(addr_remote), tcp); + + // now verify the connection is in the connection table + MetisConnectionTable *table = metisForwarder_GetConnectionTable(TestSet.metis); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(tcp->localAddress, remote); + const MetisConnection *conn = metisConnectionTable_FindByAddressPair(table, pair); + assertNotNull(conn, "Did not find connection in connection table"); + + cpiAddress_Destroy(&remote); + metisAddressPair_Release(&pair); + teardownListener(); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_TcpListener); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_TcpTunnel.c b/metis/ccnx/forwarder/metis/io/test/test_metis_TcpTunnel.c new file mode 100644 index 00000000..2d86fb17 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_TcpTunnel.c @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_TcpTunnel.c" +#include <LongBow/unit-test.h> + +#include <parc/algol/parc_SafeMemory.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h> + +// so we can see packet events +#include "../../processor/test/testrig_MockTap.h" + +// inet_pton +#include <arpa/inet.h> + +#include <fcntl.h> + +#ifndef INPORT_ANY +#define INPORT_ANY 0 +#endif + +LONGBOW_TEST_RUNNER(metis_TcpTunnel) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_TcpTunnel) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_TcpTunnel) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +typedef struct test_data { + MetisForwarder *metis; + MetisDispatcher *dispatcher; + + int serverSocket; + struct sockaddr_in serverAddr; + struct sockaddr_in localAddr; + + CPIAddress *localCpiAddress; + CPIAddress *remoteCpiAddress; + + MetisIoOperations *tunnelOps; +} TestData; + +static void +listenToInet(TestData *data) +{ + int fd = socket(PF_INET, SOCK_STREAM, 0); + assertFalse(fd < 0, "error on bind: (%d) %s", errno, strerror(errno)); + + // Set non-blocking flag + int flags = fcntl(fd, F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)\n", errno); + int failure = fcntl(fd, F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)\n", errno); + + failure = bind(fd, (struct sockaddr *) &data->serverAddr, sizeof(struct sockaddr_in)); + assertFalse(failure, "error on bind: (%d) %s", errno, strerror(errno)); + + failure = listen(fd, 16); + assertFalse(failure, "error on listen: (%d) %s", errno, strerror(errno)); + + data->serverSocket = fd; + socklen_t x = sizeof(data->serverAddr); + getsockname(fd, (struct sockaddr *) &data->serverAddr, &x); +} + + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisTcpTunnel_Create); + LONGBOW_RUN_TEST_CASE(Global, metisTcpTunnel_Create_ConnectionStartsDown); + LONGBOW_RUN_TEST_CASE(Global, metisTcpTunnel_Create_UpStateAfterAccept); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + memset(&testTap, 0, sizeof(testTap)); + + TestData *data = malloc(sizeof(TestData)); + memset(data, 0, sizeof(TestData)); + + data->metis = metisForwarder_Create(NULL); + data->serverAddr.sin_family = PF_INET; + data->serverAddr.sin_port = INPORT_ANY; + inet_pton(AF_INET, "127.0.0.1", &(data->serverAddr.sin_addr)); + + data->localAddr.sin_family = PF_INET; + data->localAddr.sin_addr.s_addr = INADDR_ANY; + data->localAddr.sin_port = INPORT_ANY; + + listenToInet(data); + + data->localCpiAddress = cpiAddress_CreateFromInet(&data->localAddr); + data->remoteCpiAddress = cpiAddress_CreateFromInet(&data->serverAddr); + + data->dispatcher = metisForwarder_GetDispatcher(data->metis); + + longBowTestCase_SetClipBoardData(testCase, data); + + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + cpiAddress_Destroy(&data->localCpiAddress); + cpiAddress_Destroy(&data->remoteCpiAddress); + + close(data->serverSocket); + metisForwarder_Destroy(&data->metis); + free(data); + + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisTcpTunnel_Create) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + data->tunnelOps = metisTcpTunnel_Create(data->metis, data->localCpiAddress, data->remoteCpiAddress); + assertNotNull(data->tunnelOps, "Got null IO operations for the tunnel"); + data->tunnelOps->destroy(&data->tunnelOps); +} + + +LONGBOW_TEST_CASE(Global, metisTcpTunnel_Create_ConnectionStartsDown) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + data->tunnelOps = metisTcpTunnel_Create(data->metis, data->localCpiAddress, data->remoteCpiAddress); + assertFalse(data->tunnelOps->isUp(data->tunnelOps), "Connection is not down on start"); + data->tunnelOps->destroy(&data->tunnelOps); +} + +LONGBOW_TEST_CASE(Global, metisTcpTunnel_Create_UpStateAfterAccept) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + data->tunnelOps = metisTcpTunnel_Create(data->metis, data->localCpiAddress, data->remoteCpiAddress); + + // run for a milli second + metisDispatcher_RunDuration(data->dispatcher, &((struct timeval) { 0, 1000 })); + + // we should be able to accept + struct sockaddr_in clientAddr; + socklen_t clientAddrLength = sizeof(clientAddr); + + int clientSocket = accept(data->serverSocket, (struct sockaddr *) &clientAddr, &clientAddrLength); + assertFalse(clientSocket < 0, "error on accept: (%d) %s", errno, strerror(errno)); + + // run for a milli second + metisDispatcher_RunDuration(data->dispatcher, &((struct timeval) { 0, 1000 })); + + assertTrue(data->tunnelOps->isUp(data->tunnelOps), "Connection is not up after accept"); + data->tunnelOps->destroy(&data->tunnelOps); +} + + +LONGBOW_TEST_FIXTURE(Local) +{ +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_TcpTunnel); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_UdpConnection.c b/metis/ccnx/forwarder/metis/io/test/test_metis_UdpConnection.c new file mode 100644 index 00000000..db1f9c97 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_UdpConnection.c @@ -0,0 +1,535 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../metis_UdpConnection.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +#include <ccnx/forwarder/metis/io/metis_UdpListener.h> +#include <ccnx/forwarder/metis/config/metis_Configuration.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h> + +// for inet_pton +#include <arpa/inet.h> + +#define ALICE_PORT 49018 +#define BOB_PORT 49019 + +// ---- Used to monitor Missive messages so we know when a connection is up +typedef struct test_notifier_data { + MetisMissiveType type; + unsigned connectionid; +} TestNotifierData; + +static void +testNotifier(MetisMessengerRecipient *recipient, MetisMissive *missive) +{ + struct test_notifier_data *data = metisMessengerRecipient_GetRecipientContext(recipient); + data->type = metisMissive_GetType(missive); + data->connectionid = metisMissive_GetConnectionId(missive); + metisMissive_Release(&missive); +} + +// ---- + +typedef struct test_tap_data { + unsigned onReceiveCount; + MetisMessage *message; +} TestTapData; + +static bool +testTap_IsTapOnReceive(const MetisTap *tap) +{ + return true; +} + +static void +testTap_TapOnReceive(MetisTap *tap, const MetisMessage *message) +{ + TestTapData *mytap = (TestTapData *) tap->context; + mytap->onReceiveCount++; + if (mytap->message) { + metisMessage_Release(&mytap->message); + } + + mytap->message = metisMessage_Acquire(message); +} + +static MetisTap testTapTemplate = { + .context = NULL, + .isTapOnReceive = &testTap_IsTapOnReceive, + .isTapOnSend = NULL, + .isTapOnDrop = NULL, + .tapOnReceive = &testTap_TapOnReceive, + .tapOnSend = NULL, + .tapOnDrop = NULL +}; + +// --- Used to inspect packets received + + +typedef struct test_data { + int remoteSocket; + +#define ALICE 0 +#define BOB 1 + + MetisForwarder *metis[2]; + MetisListenerOps *listener[2]; + MetisMessengerRecipient *recipient[2]; + TestNotifierData notifierData[2]; + MetisTap taps[2]; + TestTapData tapData[2]; +} TestData; + + + +static void +_crankHandle(TestData *data) +{ + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis[ALICE]), &((struct timeval) {0, 10000})); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis[BOB]), &((struct timeval) {0, 10000})); +} + +static void +_setup(TestData *data, int side, uint16_t port) +{ + data->metis[side] = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(data->metis[side]), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + struct sockaddr_in addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + inet_pton(AF_INET, "127.0.0.1", &(addr.sin_addr)); + + data->listener[side] = metisUdpListener_CreateInet(data->metis[side], addr); + + // snoop events + data->recipient[side] = metisMessengerRecipient_Create(&data->notifierData[side], testNotifier); + MetisMessenger *messenger = metisForwarder_GetMessenger(data->metis[side]); + metisMessenger_Register(messenger, data->recipient[side]); + + // snoop packets + memcpy(&data->taps[side], &testTapTemplate, sizeof(testTapTemplate)); + data->taps[side].context = &data->tapData[side]; + metisForwarder_AddTap(data->metis[side], &data->taps[side]); + + // save in Metis + metisListenerSet_Add(metisForwarder_GetListenerSet(data->metis[side]), data->listener[side]); +} + +/* + * Create a UDP socket pair + */ +static void +_commonSetup(const LongBowTestCase *testCase) +{ + TestData *data = parcMemory_AllocateAndClear(sizeof(TestData)); + + _setup(data, ALICE, ALICE_PORT); + _setup(data, BOB, BOB_PORT); + + _crankHandle(data); + + longBowTestCase_SetClipBoardData(testCase, data); +} + +static void +_commonTeardown(const LongBowTestCase *testCase) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // the listeners are stored in the respectie Metis, so we don't need to + // destroy those separately. + + metisMessengerRecipient_Destroy(&data->recipient[ALICE]); + metisMessengerRecipient_Destroy(&data->recipient[BOB]); + + if (data->tapData[ALICE].message) { + metisMessage_Release(&data->tapData[ALICE].message); + } + + if (data->tapData[BOB].message) { + metisMessage_Release(&data->tapData[BOB].message); + } + + metisForwarder_Destroy(&data->metis[ALICE]); + metisForwarder_Destroy(&data->metis[BOB]); + + parcMemory_Deallocate((void **) &data); +} + +LONGBOW_TEST_RUNNER(metis_UdpConnection) +{ + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_UdpConnection) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_UdpConnection) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + //XXX: this test does not work anymore because we do not create the connection in metis + //LONGBOW_RUN_TEST_CASE(Global, metisUdpConnection_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + _commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + _commonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +/* + * Create connection from ALICE to BOB + */ +LONGBOW_TEST_CASE(Global, metisUdpConnection_Create) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + const CPIAddress *bobAddress = data->listener[BOB]->getListenAddress(data->listener[BOB]); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + int fd = data->listener[ALICE]->getSocket(data->listener[ALICE]); + MetisIoOperations *ops = metisUdpConnection_Create(data->metis[ALICE], fd, pair, false); + metisAddressPair_Release(&pair); + + _crankHandle(data); + + // send a data packet to bring it up on BOB + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV1_Interest_NameA_Crc32c, sizeof(metisTestDataV1_Interest_NameA_Crc32c), 2, 3, metisForwarder_GetLogger(data->metis[ALICE])); + + ops->send(ops, NULL, message); + + metisMessage_Release(&message); + + // wait until we indicate that the connection is up on Bob's side + while (data->notifierData[BOB].type == MetisMissiveType_ConnectionDestroyed) { + _crankHandle(data); + } + + // and verify that the message was sent in to the message processor on Bob's side + assertTrue(data->tapData[BOB].onReceiveCount == 1, "Wrong receive count, expected 1 got %u", data->tapData[BOB].onReceiveCount); + + ops->destroy(&ops); +} + +// =========================================================== + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _saveSockaddr_INET); + LONGBOW_RUN_TEST_CASE(Local, _saveSockaddr_INET6); + LONGBOW_RUN_TEST_CASE(Local, _send); + LONGBOW_RUN_TEST_CASE(Local, _getRemoteAddress); + LONGBOW_RUN_TEST_CASE(Local, _getAddressPair); + LONGBOW_RUN_TEST_CASE(Local, _getConnectionId); + LONGBOW_RUN_TEST_CASE(Local, _isUp); + LONGBOW_RUN_TEST_CASE(Local, _isLocal_True); + LONGBOW_RUN_TEST_CASE(Local, _setConnectionState); + LONGBOW_RUN_TEST_CASE(Local, _getConnectionType); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + _commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + _commonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, _saveSockaddr_INET) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + _MetisUdpState *udpConnState = parcMemory_AllocateAndClear(sizeof(_MetisUdpState)); + assertNotNull(udpConnState, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisUdpState)); + + udpConnState->metis = data->metis[ALICE]; + udpConnState->logger = metisLogger_Acquire(metisForwarder_GetLogger(udpConnState->metis)); + + struct sockaddr_in sin1, sin2; + + memset(&sin1, 0, sizeof(sin1)); + sin1.sin_family = AF_INET; + sin1.sin_port = htons(ALICE_PORT); + inet_pton(AF_INET, "127.0.0.1", &(sin1.sin_addr)); + + memset(&sin2, 0, sizeof(sin2)); + sin2.sin_family = AF_INET; + sin2.sin_port = htons(BOB_PORT); + inet_pton(AF_INET, "127.0.0.1", &(sin2.sin_addr)); + + CPIAddress *aliceAddress = cpiAddress_CreateFromInet(&sin1); + CPIAddress *bobAddress = cpiAddress_CreateFromInet(&sin2); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + + bool saved = _saveSockaddr(udpConnState, pair); + assertTrue(saved, "Failed to save address"); + assertTrue(udpConnState->peerAddressLength == sizeof(sin1), "Wrong length, expected %zu got %u", sizeof(sin1), udpConnState->peerAddressLength); + + cpiAddress_Destroy(&aliceAddress); + cpiAddress_Destroy(&bobAddress); + + metisAddressPair_Release(&pair); + metisLogger_Release(&udpConnState->logger); + parcMemory_Deallocate((void **) &udpConnState->peerAddress); + parcMemory_Deallocate((void **) &udpConnState); +} + +LONGBOW_TEST_CASE(Local, _saveSockaddr_INET6) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + _MetisUdpState *udpConnState = parcMemory_AllocateAndClear(sizeof(_MetisUdpState)); + assertNotNull(udpConnState, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisUdpState)); + + udpConnState->metis = data->metis[ALICE]; + udpConnState->logger = metisLogger_Acquire(metisForwarder_GetLogger(udpConnState->metis)); + + struct sockaddr_in6 sin1, sin2; + + memset(&sin1, 0, sizeof(sin1)); + sin1.sin6_family = AF_INET6; + sin1.sin6_port = htons(ALICE_PORT); + int ok = inet_pton(AF_INET6, "::1", &(sin1.sin6_addr)); + + if (ok) { + memset(&sin2, 0, sizeof(sin2)); + sin2.sin6_family = AF_INET6; + sin2.sin6_port = htons(BOB_PORT); + inet_pton(AF_INET6, "::1", &(sin2.sin6_addr)); + + CPIAddress *aliceAddress = cpiAddress_CreateFromInet6(&sin1); + CPIAddress *bobAddress = cpiAddress_CreateFromInet6(&sin2); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + + bool saved = _saveSockaddr(udpConnState, pair); + assertTrue(saved, "Failed to save address"); + assertTrue(udpConnState->peerAddressLength == sizeof(sin1), "Wrong length, expected %zu got %u", sizeof(sin1), udpConnState->peerAddressLength); + + cpiAddress_Destroy(&aliceAddress); + cpiAddress_Destroy(&bobAddress); + + metisAddressPair_Release(&pair); + } else { + testSkip("Skipping inet6 test"); + } + + metisLogger_Release(&udpConnState->logger); + parcMemory_Deallocate((void **) &udpConnState->peerAddress); + parcMemory_Deallocate((void **) &udpConnState); +} + +LONGBOW_TEST_CASE(Local, _send) +{ +} + +LONGBOW_TEST_CASE(Local, _getRemoteAddress) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + const CPIAddress *bobAddress = data->listener[BOB]->getListenAddress(data->listener[BOB]); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + int fd = data->listener[ALICE]->getSocket(data->listener[ALICE]); + MetisIoOperations *ops = metisUdpConnection_Create(data->metis[ALICE], fd, pair, false); + metisAddressPair_Release(&pair); + + // now run test + const CPIAddress *test = _getRemoteAddress(ops); + assertNotNull(test, "Got null remote address"); + assertTrue(cpiAddress_Equals(test, bobAddress), "Addresses do not match"); + ops->destroy(&ops); +} + +LONGBOW_TEST_CASE(Local, _getAddressPair) +{ +} + +LONGBOW_TEST_CASE(Local, _getConnectionId) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + const CPIAddress *bobAddress = data->listener[BOB]->getListenAddress(data->listener[BOB]); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + int fd = data->listener[ALICE]->getSocket(data->listener[ALICE]); + MetisIoOperations *ops = metisUdpConnection_Create(data->metis[ALICE], fd, pair, false); + metisAddressPair_Release(&pair); + + // now run test + unsigned connid = _getConnectionId(ops); + + assertTrue(connid > 0, "Expected positive connid, got 0"); + ops->destroy(&ops); +} + +LONGBOW_TEST_CASE(Local, _isUp) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + const CPIAddress *bobAddress = data->listener[BOB]->getListenAddress(data->listener[BOB]); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + int fd = data->listener[ALICE]->getSocket(data->listener[ALICE]); + MetisIoOperations *ops = metisUdpConnection_Create(data->metis[ALICE], fd, pair, false); + metisAddressPair_Release(&pair); + + // now run test + bool isup = _isUp(ops); + + assertTrue(isup, "Expected connection to be up"); + ops->destroy(&ops); +} + +LONGBOW_TEST_CASE(Local, _isLocal_True) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + const CPIAddress *bobAddress = data->listener[BOB]->getListenAddress(data->listener[BOB]); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + int fd = data->listener[ALICE]->getSocket(data->listener[ALICE]); + MetisIoOperations *ops = metisUdpConnection_Create(data->metis[ALICE], fd, pair, true); + metisAddressPair_Release(&pair); + + // now run test + bool islocal = _isLocal(ops); + + assertTrue(islocal, "Expected connection to be local"); + ops->destroy(&ops); +} + +LONGBOW_TEST_CASE(Local, _setConnectionState) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + const CPIAddress *bobAddress = data->listener[BOB]->getListenAddress(data->listener[BOB]); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + int fd = data->listener[ALICE]->getSocket(data->listener[ALICE]); + MetisIoOperations *ops = metisUdpConnection_Create(data->metis[ALICE], fd, pair, true); + metisAddressPair_Release(&pair); + + // now run test + _MetisUdpState *udpConnState = (_MetisUdpState *) metisIoOperations_GetClosure(ops); + + _setConnectionState(udpConnState, false); + bool isup = _isUp(ops); + + assertFalse(isup, "Expected connection to be down"); + ops->destroy(&ops); +} + +LONGBOW_TEST_CASE(Local, _getConnectionType) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + const CPIAddress *bobAddress = data->listener[BOB]->getListenAddress(data->listener[BOB]); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + int fd = data->listener[ALICE]->getSocket(data->listener[ALICE]); + MetisIoOperations *ops = metisUdpConnection_Create(data->metis[ALICE], fd, pair, false); + metisAddressPair_Release(&pair); + + // now run test + CPIConnectionType connType = _getConnectionType(ops); + + assertTrue(connType == cpiConnection_UDP, "Expected connection to be %d got %d", cpiConnection_UDP, connType); + ops->destroy(&ops); +} + + +// =========================================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_UdpConnection); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_UdpListener.c b/metis/ccnx/forwarder/metis/io/test/test_metis_UdpListener.c new file mode 100644 index 00000000..96dfc4fd --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_UdpListener.c @@ -0,0 +1,458 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + */ + +/* + * hard-coded in port 49009 on localhost + */ + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_UdpListener.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> +#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h> +#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h> + +// for inet_pton +#include <arpa/inet.h> + +#include <signal.h> + +// ======================================================== + +struct test_set { + CPIAddress *listenAddress; + MetisForwarder *metis; + MetisListenerOps *ops; +} TestSet; + +static void +setupInetListener() +{ + struct sockaddr_in addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(49009); + inet_pton(AF_INET, "127.0.0.1", &(addr.sin_addr)); + + TestSet.metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(TestSet.metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + TestSet.ops = metisUdpListener_CreateInet(TestSet.metis, addr); + TestSet.listenAddress = cpiAddress_CreateFromInet(&addr); + + // crank the handle + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); +} + +static void +teardownListener() +{ + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + cpiAddress_Destroy(&TestSet.listenAddress); + TestSet.ops->destroy(&TestSet.ops); + metisForwarder_Destroy(&TestSet.metis); +} + +static bool +setupInet6Listener() +{ + struct sockaddr_in6 addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(49009); + + // "::1" is the ipv6 loopback address + int ok = inet_pton(AF_INET6, "::1", &(addr.sin6_addr)); + if (ok > 0) { + TestSet.metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(TestSet.metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + TestSet.ops = metisUdpListener_CreateInet6(TestSet.metis, addr); + TestSet.listenAddress = cpiAddress_CreateFromInet6(&addr); + + // crank the handle + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + return true; + } + return false; +} + +struct sigaction save_sigchld; +struct sigaction save_sigpipe; + +static void +blockSigChild() +{ + struct sigaction ignore_action; + ignore_action.sa_handler = SIG_IGN; + sigemptyset(&ignore_action.sa_mask); + ignore_action.sa_flags = 0; + + sigaction(SIGCHLD, NULL, &save_sigchld); + sigaction(SIGPIPE, NULL, &save_sigpipe); + + sigaction(SIGCHLD, &ignore_action, NULL); + sigaction(SIGPIPE, &ignore_action, NULL); +} + +static void +unblockSigChild() +{ + sigaction(SIGCHLD, &save_sigchld, NULL); + sigaction(SIGPIPE, &save_sigpipe, NULL); +} + +// ======================================================== + +LONGBOW_TEST_RUNNER(metis_UdpListener) +{ + LONGBOW_RUN_TEST_FIXTURE(Global_Inet); + // XXX: Udp code has issues. It should check return values from calls. + // There are bugs in the UDP code that need to be fixed. These are shown in + // this test. The code needs to be fixed first. + //LONGBOW_RUN_TEST_FIXTURE(Global_Inet6); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_UdpListener) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_UdpListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ================================================================================ + +LONGBOW_TEST_FIXTURE(Global_Inet) +{ + LONGBOW_RUN_TEST_CASE(Global_Inet, metisListenerUdp_CreateInet); + //XXX: this does not work anymore because we do not create the udp connection + //LONGBOW_RUN_TEST_CASE(Global_Inet, metisListenerUdp_Connect); + LONGBOW_RUN_TEST_CASE(Global_Inet, metisListenerUdp_SendPacket); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global_Inet) +{ + setupInetListener(); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global_Inet) +{ + teardownListener(); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global_Inet, metisListenerUdp_CreateInet) +{ + // now verify that we are listening + // udp4 0 0 127.0.0.1.49009 *.* LISTEN + + blockSigChild(); + FILE *fp = popen("netstat -an -p udp", "r"); + assertNotNull(fp, "Got null opening netstat for reading"); + + char str[1035]; + bool found = false; + while (fgets(str, sizeof(str) - 1, fp) != NULL) { + if (strstr(str, "127.0.0.1.49009") != NULL) { + found = true; + break; + } + if (strstr(str, "127.0.0.1:49009") != NULL) { + found = true; + break; + } + } + + pclose(fp); + unblockSigChild(); + + if (!found) { + int ret = system("netstat -an -p udp"); + assertTrue(ret > -1, "Error on system call"); + } + + assertTrue(found, "Did not find 127.0.0.1.49009 in netstat output"); +} + +LONGBOW_TEST_CASE(Global_Inet, metisListenerUdp_Connect) +{ + int fd = socket(PF_INET, SOCK_DGRAM, 0); + assertFalse(fd < 0, "Error on socket: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in serverAddress; + cpiAddress_GetInet(TestSet.listenAddress, &serverAddress); + + int failure = connect(fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)); + assertFalse(failure, "Error on connect: (%d) %s", errno, strerror(errno)); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + + struct sockaddr_in connectAddress; + socklen_t connectAddressLength = sizeof(connectAddress); + failure = getsockname(fd, (struct sockaddr *) &connectAddress, &connectAddressLength); + assertFalse(failure, "Error on getsockname: (%d) %s", errno, strerror(errno)); + assertTrue(connectAddressLength == sizeof(struct sockaddr_in), + "connectAddressLength wrong size, expected %zu got %u", + sizeof(struct sockaddr_in), connectAddressLength); + + // Unlike TCP, we need to actually send something + ssize_t nwritten = write(fd, metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest)); + assertTrue(nwritten == sizeof(metisTestDataV0_EncodedInterest), "Error on write expected %zu got %zd", sizeof(metisTestDataV0_EncodedInterest), nwritten); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + + // make sure its in the connection table + MetisConnectionTable *table = metisForwarder_GetConnectionTable(TestSet.metis); + CPIAddress *remote = cpiAddress_CreateFromInet(&connectAddress); + MetisAddressPair *pair = metisAddressPair_Create(TestSet.listenAddress, remote); + const MetisConnection *conn = metisConnectionTable_FindByAddressPair(table, pair); + assertNotNull(conn, "Did not find connection in connection table"); + + cpiAddress_Destroy(&remote); + metisAddressPair_Release(&pair); + + close(fd); +} + +LONGBOW_TEST_CASE(Global_Inet, metisListenerUdp_SendPacket) +{ + int fd = socket(PF_INET, SOCK_DGRAM, 0); + assertFalse(fd < 0, "Error on socket: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in serverAddress; + cpiAddress_GetInet(TestSet.listenAddress, &serverAddress); + + int failure = connect(fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)); + assertFalse(failure, "Error on connect socket %d: (%d) %s\n", fd, errno, strerror(errno)); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + + ssize_t write_length = write(fd, metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName)); + assertFalse(write_length < 0, "Error on write: (%d) %s", errno, strerror(errno)); + assertTrue(write_length == sizeof(metisTestDataV0_InterestWithName), "Got partial write, expected %zu got %zd", sizeof(metisTestDataV0_InterestWithName), write_length); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + + close(fd); +} + +// ================================================================================ + +LONGBOW_TEST_FIXTURE(Global_Inet6) +{ + LONGBOW_RUN_TEST_CASE(Global_Inet6, metisListenerUdp_CreateInet6); + LONGBOW_RUN_TEST_CASE(Global_Inet6, metisListenerUdp_Connect); + LONGBOW_RUN_TEST_CASE(Global_Inet6, metisListenerUdp_SendPacket); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global_Inet6) +{ + bool success = setupInet6Listener(); + if (!success) { + return LONGBOW_STATUS_SKIPPED; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global_Inet6) +{ + teardownListener(); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global_Inet6, metisListenerUdp_CreateInet6) +{ + // now verify that we are listening + // udp6 0 0 ::1.49009 *.* + + blockSigChild(); + FILE *fp = popen("netstat -an -p udp", "r"); + assertNotNull(fp, "Got null opening netstat for reading"); + + char str[1035]; + bool found = false; + while (fgets(str, sizeof(str) - 1, fp) != NULL) { + //printf("%s\n", str); + if (strstr(str, "::1.49009") != NULL) { + found = true; + break; + } + if (strstr(str, "::1:49009") != NULL) { + found = true; + break; + } + } + + pclose(fp); + unblockSigChild(); + + if (!found) { + int ret = system("netstat -an -p udp"); + assertTrue(ret != -1, "Error on system() call"); + } + + assertTrue(found, "Did not find 127.0.0.1.49009 in netstat output"); +} + +LONGBOW_TEST_CASE(Global_Inet6, metisListenerUdp_Connect) +{ + int fd = socket(PF_INET6, SOCK_DGRAM, 0); + assertFalse(fd < 0, "Error on socket: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in6 serverAddress; + cpiAddress_GetInet6(TestSet.listenAddress, &serverAddress); + + int failure = connect(fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)); + assertFalse(failure, "Error on connect: (%d) %s", errno, strerror(errno)); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + + struct sockaddr_in6 connectAddress; + socklen_t connectAddressLength = sizeof(connectAddress); + failure = getsockname(fd, (struct sockaddr *) &connectAddress, &connectAddressLength); + assertFalse(failure, "Error on getsockname: (%d) %s", errno, strerror(errno)); + assertTrue(connectAddressLength == sizeof(struct sockaddr_in6), + "connectAddressLength wrong size, expected %zu got %u", + sizeof(struct sockaddr_in6), connectAddressLength); + + // Unlike TCP, we need to actually send something + ssize_t nwritten = write(fd, metisTestDataV1_Interest_NameA_Crc32c, sizeof(metisTestDataV1_Interest_NameA_Crc32c)); + assertTrue(nwritten == sizeof(metisTestDataV1_Interest_NameA_Crc32c), "Write failed, expected %zu got %zd", sizeof(metisTestDataV1_Interest_NameA_Crc32c), nwritten); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + + // make sure its in the connection table + MetisConnectionTable *table = metisForwarder_GetConnectionTable(TestSet.metis); + CPIAddress *remote = cpiAddress_CreateFromInet6(&connectAddress); + MetisAddressPair *pair = metisAddressPair_Create(TestSet.listenAddress, remote); + const MetisConnection *conn = metisConnectionTable_FindByAddressPair(table, pair); + assertNotNull(conn, "Did not find connection in connection table"); + + cpiAddress_Destroy(&remote); + metisAddressPair_Release(&pair); + + close(fd); +} + +LONGBOW_TEST_CASE(Global_Inet6, metisListenerUdp_SendPacket) +{ + int fd = socket(PF_INET6, SOCK_DGRAM, 0); + assertFalse(fd < 0, "Error on socket: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in6 serverAddress; + cpiAddress_GetInet6(TestSet.listenAddress, &serverAddress); + + int failure = connect(fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)); + assertFalse(failure, "Error on connect socket %d: (%d) %s\n", fd, errno, strerror(errno)); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + + ssize_t write_length = write(fd, metisTestDataV1_Interest_NameA_Crc32c, sizeof(metisTestDataV1_Interest_NameA_Crc32c)); + assertFalse(write_length < 0, "Error on write: (%d) %s", errno, strerror(errno)); + assertTrue(write_length == sizeof(metisTestDataV1_Interest_NameA_Crc32c), + "Got partial write, expected %zu got %zd", + sizeof(metisTestDataV1_Interest_NameA_Crc32c), write_length); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + + close(fd); +} + +// ================================================================================ + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _getInterfaceIndex); + LONGBOW_RUN_TEST_CASE(Local, _getListenAddress); + LONGBOW_RUN_TEST_CASE(Local, _getEncapType); + LONGBOW_RUN_TEST_CASE(Local, _getSocket); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + setupInetListener(); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + teardownListener(); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, _getInterfaceIndex) +{ + unsigned test = _getInterfaceIndex(TestSet.ops); + assertTrue(test > 0, "Unexpected interface index: %u", test); +} + +LONGBOW_TEST_CASE(Local, _getListenAddress) +{ + const CPIAddress *listenAddr = _getListenAddress(TestSet.ops); + assertNotNull(listenAddr, "Got null listen address"); +} + +LONGBOW_TEST_CASE(Local, _getEncapType) +{ + MetisEncapType type = _getEncapType(TestSet.ops); + assertTrue(type == METIS_ENCAP_UDP, "Unexpected address type, got %d expected %d", type, METIS_ENCAP_UDP); +} + +LONGBOW_TEST_CASE(Local, _getSocket) +{ + int fd = _getSocket(TestSet.ops); + assertTrue(fd > 0, "Unexpected socket, got %d, expected positive", fd); +} + + +// ================================================================================ + + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_UdpListener); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_UdpTunnel.c b/metis/ccnx/forwarder/metis/io/test/test_metis_UdpTunnel.c new file mode 100644 index 00000000..cba18ed3 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_UdpTunnel.c @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Need a solution to avoid hard-coding port numbers + * + */ + +#include "../metis_UdpTunnel.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +#include <ccnx/forwarder/metis/io/metis_UdpListener.h> +#include <ccnx/forwarder/metis/config/metis_Configuration.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h> + +// for inet_pton +#include <arpa/inet.h> + +#define ALICE_PORT 49028 +#define BOB_PORT 49029 + +// ---- Used to monitor Missive messages so we know when a connection is up +typedef struct test_notifier_data { + MetisMissiveType type; + unsigned connectionid; +} TestNotifierData; + +static void +testNotifier(MetisMessengerRecipient *recipient, MetisMissive *missive) +{ + struct test_notifier_data *data = metisMessengerRecipient_GetRecipientContext(recipient); + data->type = metisMissive_GetType(missive); + data->connectionid = metisMissive_GetConnectionId(missive); + metisMissive_Release(&missive); +} + +// ---- + +typedef struct test_tap_data { + unsigned onReceiveCount; + MetisMessage *message; +} TestTapData; + +static bool +testTap_IsTapOnReceive(const MetisTap *tap) +{ + return true; +} + +static void +testTap_TapOnReceive(MetisTap *tap, const MetisMessage *message) +{ + TestTapData *mytap = (TestTapData *) tap->context; + mytap->onReceiveCount++; + if (mytap->message) { + metisMessage_Release(&mytap->message); + } + + mytap->message = metisMessage_Acquire(message); +} + +static MetisTap testTapTemplate = { + .context = NULL, + .isTapOnReceive = &testTap_IsTapOnReceive, + .isTapOnSend = NULL, + .isTapOnDrop = NULL, + .tapOnReceive = &testTap_TapOnReceive, + .tapOnSend = NULL, + .tapOnDrop = NULL +}; + +// --- Used to inspect packets received + + +typedef struct test_data { + int remoteSocket; + +#define ALICE 0 +#define BOB 1 + + MetisForwarder *metis[2]; + MetisListenerOps *listener[2]; + MetisMessengerRecipient *recipient[2]; + TestNotifierData notifierData[2]; + MetisTap taps[2]; + TestTapData tapData[2]; +} TestData; + + + +static void +_crankHandle(TestData *data) +{ + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis[ALICE]), &((struct timeval) {0, 10000})); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis[BOB]), &((struct timeval) {0, 10000})); +} + +static void +_setup(TestData *data, int side, uint16_t port) +{ + data->metis[side] = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(data->metis[side]), MetisLoggerFacility_IO, PARCLogLevel_Error); + + struct sockaddr_in addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + inet_pton(AF_INET, "127.0.0.1", &(addr.sin_addr)); + + data->listener[side] = metisUdpListener_CreateInet(data->metis[side], addr); + + // snoop events + data->recipient[side] = metisMessengerRecipient_Create(&data->notifierData[side], testNotifier); + MetisMessenger *messenger = metisForwarder_GetMessenger(data->metis[side]); + metisMessenger_Register(messenger, data->recipient[side]); + + // snoop packets + memcpy(&data->taps[side], &testTapTemplate, sizeof(testTapTemplate)); + data->taps[side].context = &data->tapData[side]; + metisForwarder_AddTap(data->metis[side], &data->taps[side]); + + // save in Metis + metisListenerSet_Add(metisForwarder_GetListenerSet(data->metis[side]), data->listener[side]); +} + +/* + * Create a UDP socket pair + */ +static void +_commonSetup(const LongBowTestCase *testCase) +{ + TestData *data = parcMemory_AllocateAndClear(sizeof(TestData)); + + _setup(data, ALICE, ALICE_PORT); + _setup(data, BOB, BOB_PORT); + + _crankHandle(data); + + longBowTestCase_SetClipBoardData(testCase, data); +} + +static void +_commonTeardown(const LongBowTestCase *testCase) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // the listeners are stored in the respectie Metis, so we don't need to + // destroy those separately. + + metisMessengerRecipient_Destroy(&data->recipient[ALICE]); + metisMessengerRecipient_Destroy(&data->recipient[BOB]); + + if (data->tapData[ALICE].message) { + metisMessage_Release(&data->tapData[ALICE].message); + } + + if (data->tapData[BOB].message) { + metisMessage_Release(&data->tapData[BOB].message); + } + + metisForwarder_Destroy(&data->metis[ALICE]); + metisForwarder_Destroy(&data->metis[BOB]); + + parcMemory_Deallocate((void **) &data); +} + +// ================================== + +LONGBOW_TEST_RUNNER(metis_UdpTunnel) +{ + LONGBOW_RUN_TEST_FIXTURE(Global); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_UdpTunnel) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_UdpTunnel) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisUdpTunnel_CreateOnListener); + //XXX: this test does not work anymore beacuase we don't create the connection in metis + //LONGBOW_RUN_TEST_CASE(Global, metisUdpTunnel_Create); + LONGBOW_RUN_TEST_CASE(Global, metisUdpTunnel_Create_MismatchedTypes); + LONGBOW_RUN_TEST_CASE(Global, metisUdpTunnel_Create_NotFound); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + _commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + _commonTeardown(testCase); + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + + +LONGBOW_TEST_CASE(Global, metisUdpTunnel_CreateOnListener) +{ +} + +/* + * Create from Alice to Bob + */ +LONGBOW_TEST_CASE(Global, metisUdpTunnel_Create) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + const CPIAddress *bobAddress = data->listener[BOB]->getListenAddress(data->listener[BOB]); + + MetisIoOperations *ops = metisUdpTunnel_Create(data->metis[ALICE], aliceAddress, bobAddress); + + _crankHandle(data); + + // send a data packet to bring it up on BOB + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV1_Interest_NameA_Crc32c, sizeof(metisTestDataV1_Interest_NameA_Crc32c), 2, 3, metisForwarder_GetLogger(data->metis[ALICE])); + + ops->send(ops, NULL, message); + + metisMessage_Release(&message); + + // wait until we indicate that the connection is up on Bob's side + while (data->notifierData[BOB].type == MetisMissiveType_ConnectionDestroyed) { + _crankHandle(data); + } + + // and verify that the message was sent in to the message processor on Bob's side + assertTrue(data->tapData[BOB].onReceiveCount == 1, "Wrong receive count, expected 1 got %u", data->tapData[BOB].onReceiveCount); + + ops->destroy(&ops); +} + +/* + * sockets not same address family + */ +LONGBOW_TEST_CASE(Global, metisUdpTunnel_Create_MismatchedTypes) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + + struct sockaddr_in6 sin6; + memset(&sin6, 0, sizeof(sin6)); + CPIAddress *bobAddress = cpiAddress_CreateFromInet6(&sin6); + + MetisIoOperations *ops = metisUdpTunnel_Create(data->metis[ALICE], aliceAddress, bobAddress); + assertNull(ops, "Should have gotten null return for mismatched address types"); + + cpiAddress_Destroy(&bobAddress); +} + +/* + * Listener not found + */ +LONGBOW_TEST_CASE(Global, metisUdpTunnel_Create_NotFound) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + CPIAddress *aliceAddress = cpiAddress_CreateFromInet(&sin); + CPIAddress *bobAddress = cpiAddress_CreateFromInet(&sin); + + MetisIoOperations *ops = metisUdpTunnel_Create(data->metis[ALICE], aliceAddress, bobAddress); + assertNull(ops, "Should have gotten null return for mismatched address types"); + + cpiAddress_Destroy(&aliceAddress); + cpiAddress_Destroy(&bobAddress); +} + + + +// ================================== + + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_UdpTunnel); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/testrig_GenericEther.c b/metis/ccnx/forwarder/metis/io/test/testrig_GenericEther.c new file mode 100644 index 00000000..d5c641d2 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/testrig_GenericEther.c @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * GenericEther mockup for testing metis_EtherListener. + * + */ + +#include <config.h> +#include <unistd.h> +#include <errno.h> +#include <stdio.h> +#include <fcntl.h> +#include <net/ethernet.h> +#include <arpa/inet.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_Deque.h> +#include <parc/algol/parc_Buffer.h> +#include <parc/algol/parc_EventBuffer.h> + +#include <ccnx/forwarder/metis/core/metis_System.h> +#include <ccnx/forwarder/metis/io/metis_GenericEther.h> + +#include <LongBow/runtime.h> + +struct metis_generic_ether { + PARCDeque *inputQueue; + int testSocket; + int etherSocket; + PARCBuffer *macAddress; + uint16_t ethertype; + unsigned refcount; + MetisLogger *logger; + unsigned mtu; +}; + +void +_metisGenericEther_Destroy(MetisGenericEther **etherPtr) +{ + MetisGenericEther *ether = *etherPtr; + PARCBuffer *buffer; + + // the input queue is simple byte arrays of ethernet frames + while ((buffer = parcDeque_RemoveFirst(ether->inputQueue))) { + parcBuffer_Release(&buffer); + } + + metisLogger_Release(ðer->logger); + parcDeque_Release(ðer->inputQueue); + parcBuffer_Release(ðer->macAddress); + + close(ether->testSocket); + close(ether->etherSocket); +} + +parcObject_ExtendPARCObject(MetisGenericEther, _metisGenericEther_Destroy, NULL, NULL, NULL, NULL, NULL, NULL); + +parcObject_ImplementAcquire(metisGenericEther, MetisGenericEther); + +parcObject_ImplementRelease(metisGenericEther, MetisGenericEther); + +MetisGenericEther * +metisGenericEther_Create(MetisForwarder *metis, const char *deviceName, uint16_t etherType) +{ + assertNotNull(deviceName, "Parameter deviceName must be non-null"); + assertTrue(etherType >= 0x0600, "EtherType must be greater than or equal to 0x0600"); + + MetisGenericEther *ether = parcObject_CreateInstance(MetisGenericEther); + + int fd[2]; + int failure = socketpair(PF_LOCAL, SOCK_DGRAM, 0, fd); + assertFalse(failure, "Error on socketpair"); + + ether->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + ether->testSocket = fd[0]; + ether->etherSocket = fd[1]; + ether->ethertype = etherType; + ether->inputQueue = parcDeque_Create(); + ether->mtu = 4000; + + // Set non-blocking flag + int flags = fcntl(ether->testSocket, F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)\n", errno); + failure = fcntl(ether->testSocket, F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)\n", errno); + flags = fcntl(ether->etherSocket, F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)\n", errno); + failure = fcntl(ether->etherSocket, F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)\n", errno); + + // If we are passed a real interface name, use its MAC address otherwise make up something + CPIAddress *realMacAddress = metisSystem_GetMacAddressByName(metis, deviceName); + + if (realMacAddress) { + PARCBuffer *realMac = cpiAddress_GetLinkAddress(realMacAddress); + ether->macAddress = parcBuffer_Copy(realMac); + cpiAddress_Destroy(&realMacAddress); + } else { + uint8_t macAddress[] = { 1, 2, 3, 4, 5, 6 }; + + // copy up to 6 bytes of the deviceName in to the mac address, for debugging + size_t maxbytes = (strlen(deviceName) > 6) ? 6 : strlen(deviceName); + memcpy(macAddress, deviceName, maxbytes); + ether->macAddress = parcBuffer_Allocate(sizeof(macAddress)); + parcBuffer_PutArray(ether->macAddress, sizeof(macAddress), macAddress); + parcBuffer_Flip(ether->macAddress); + } + + return ether; +} + +int +metisGenericEther_GetDescriptor(const MetisGenericEther *ether) +{ + return ether->etherSocket; +} + +bool +metisGenericEther_ReadNextFrame(MetisGenericEther *ether, PARCEventBuffer *buffer) +{ + // read a byte off the etherSocket if available to clear the notification. + // its non-blocking so no harm if nothing there. + uint8_t one; + ssize_t nread = read(ether->etherSocket, &one, 1); + assertTrue(errno == EWOULDBLOCK || nread > -1, "Error on read"); + + if (!parcDeque_IsEmpty(ether->inputQueue)) { + PARCBuffer *frame = parcDeque_RemoveFirst(ether->inputQueue); + uint8_t *bytes = parcBuffer_Overlay(frame, 0); + size_t length = parcBuffer_Remaining(frame); + + parcEventBuffer_Append(buffer, bytes, length); + parcBuffer_Release(&frame); + return true; + } + return false; +} + +bool +metisGenericEther_SendFrame(MetisGenericEther *ether, PARCEventBuffer *buffer) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + + // cannot use parcEventBuffer_WriteToFileDescriptor because we need to write the length in one go, not use the + // iovec approach in parcEventBuffer_WriteToFileDescriptor. It can cause problems on some platforms. + + uint8_t *linear = parcEventBuffer_Pullup(buffer, -1); + size_t length = parcEventBuffer_GetLength(buffer); + + ssize_t written = write(ether->etherSocket, linear, length); + if (written == length) { + return true; + } + + return false; +} + +PARCBuffer * +metisGenericEther_GetMacAddress(const MetisGenericEther *ether) +{ + return ether->macAddress; +} + +uint16_t +metisGenericEther_GetEtherType(const MetisGenericEther *ether) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + return ether->ethertype; +} + +unsigned +metisGenericEther_GetMTU(const MetisGenericEther *ether) +{ + return ether->mtu; +} + +// ========= +// Extra functions for testing + +int +mockGenericEther_GetTestDescriptor(MetisGenericEther *ether) +{ + return ether->testSocket; +} + +void +mockGenericEther_QueueFrame(MetisGenericEther *ether, PARCBuffer *ethernetFrame) +{ + parcDeque_Append(ether->inputQueue, parcBuffer_Acquire(ethernetFrame)); +} + +void +mockGenericEther_Notify(MetisGenericEther *ether) +{ + // notify the etherSocket by writing to the testSocket + uint8_t one = 1; + ssize_t nwritten = write(ether->testSocket, &one, 1); + assertTrue(nwritten == 1, "Error on write, expected 1, got %zd", nwritten); +} + +/* + * Create an Ethernet frame enclosing the ccnxPacket. does not include the FCS. + */ +PARCBuffer * +mockGenericEther_createFrame(size_t length, const uint8_t *ccnxPacket, const uint8_t dmac[ETHER_ADDR_LEN], const uint8_t smac[ETHER_ADDR_LEN], uint16_t ethertype) +{ + size_t totalLength = sizeof(struct ether_header) + length; + PARCBuffer *buffer = parcBuffer_Allocate(totalLength); + + struct ether_header hdr; + + memcpy(hdr.ether_dhost, dmac, ETHER_ADDR_LEN); + memcpy(hdr.ether_shost, smac, ETHER_ADDR_LEN); + hdr.ether_type = htons(ethertype); + + parcBuffer_PutArray(buffer, sizeof(hdr), (uint8_t *) &hdr); + + parcBuffer_PutArray(buffer, length, ccnxPacket); + + parcBuffer_Flip(buffer); + return buffer; +} + diff --git a/metis/ccnx/forwarder/metis/io/test/testrig_GenericEther.h b/metis/ccnx/forwarder/metis/io/test/testrig_GenericEther.h new file mode 100644 index 00000000..dc89841e --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/testrig_GenericEther.h @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file testrig_GenericEther.h + * @brief A mockup of a platform Ethernet + * + * The mockup is connected to a socketpair, so you can read frames that the metis_EtherListener sends. + * It also has an input queue so you can queue frames to be read by metis_EtherListener. + * + * This mockup implements the metis_GenericEther.h API plus two additional functions for the mockup. + * + */ + +#ifndef Metis_testrig_GenericEther_h +#define Metis_testrig_GenericEther_h + +#include <ccnx/forwarder/metis/io/metis_GenericEther.h> +#include <parc/algol/parc_Buffer.h> + +/** + * Returns the other end of a socketpair that mocks up the ethernet wire + * + * The mockup does not connect to a RAW or BPF socket, it connects to a socketpair. + * This function gets the remote end of the socket pair, which is where you can read + * frames that you send. + * + * DO NOT WRITE PACKETS HERE. To queue packets for input, use mockGenericEther_QueueFrame(). + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval number System socketpair + * + * Example: + * @code + * <#example#> + * @endcode + */ +int mockGenericEther_GetTestDescriptor(MetisGenericEther *ether); + +/** + * Queue an Ethernet frame to be read + * + * The mockup maintains an input queue (deque) for input frames. These should be full + * Ethernet frames (not including the frame check sequence). + * + * This stores a reference, so caller must also release the PARCBuffer. + * + * This function will not notify the etherSocket being watched by Libevent in Metis. + * To notify Libevent, use mockGenericEther_Notify() after queuing packets. + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void mockGenericEther_QueueFrame(MetisGenericEther *ether, PARCBuffer *ethernetFrame); + +/** + * Writes a byte to the etherSocket + * + * Tickles Libevent by writing a byte to the etherSocket. + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void mockGenericEther_Notify(MetisGenericEther *ether); + +/** + * Convenience function to encapsulate a packet in an ethernet frame + * + * Creates a PARCBuffer that has an Ethernet header followed by a user-provided byte array. + * Does not include the frame check sequence. + * + * @param [in] length The length of the ccnxPacket to be encapsulated + * @param [in] ccnxPacket the byte array to put after the ethernet header + * @param [in] dmac[6] The destination mac + * @param [in] smac[6] The source mac + * @param [in] ethertype The ethertype in host byte order + * + * @retval non-null An allocated PARCBuffer wrapping an ethernet frame, ready to read + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +PARCBuffer *mockGenericEther_createFrame(size_t length, const uint8_t ccnxPacket[length], const uint8_t dmac[ETHER_ADDR_LEN], const uint8_t smac[ETHER_ADDR_LEN], uint16_t ethertype); + +#endif // Metis_testrig_GenericEther_h diff --git a/metis/ccnx/forwarder/metis/io/test/testrig_MetisListenerOps.c b/metis/ccnx/forwarder/metis/io/test/testrig_MetisListenerOps.c new file mode 100644 index 00000000..d0a1f4c0 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/testrig_MetisListenerOps.c @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This is a mock for MetisListenerOps + * + */ + +/** + * You should #include this C file in your unit test code + */ + +// =============================== +// Setup a mock for the MetisListenerOps + +typedef struct mock_listener_data { + unsigned destroyCount; + unsigned getInterfaceIndexCount; + unsigned getListenAddressCount; + unsigned getEncapTypeCount; + + // These values will be returned by the appropriate getter + unsigned interfaceIndex; + CPIAddress *listenAddress; + MetisEncapType encapType; +} MockListenerData; + +static void +mockListener_Destroy(MetisListenerOps **opsPtr) +{ + // Don't actually destroy the data, we want to keep the counts + MetisListenerOps *ops = *opsPtr; + MockListenerData *data = ops->context; + data->destroyCount++; + parcMemory_Deallocate((void **) &ops); + *opsPtr = NULL; +} + +static unsigned +mockListener_GetInterfaceIndex(const MetisListenerOps *ops) +{ + MockListenerData *data = ops->context; + data->getInterfaceIndexCount++; + return data->interfaceIndex; +} + +static const CPIAddress * +mockListener_GetListenAddress(const MetisListenerOps *ops) +{ + MockListenerData *data = ops->context; + data->getListenAddressCount++; + return data->listenAddress; +} + +static MetisEncapType +mockListener_GetEncapType(const MetisListenerOps *ops) +{ + MockListenerData *data = ops->context; + data->getEncapTypeCount++; + return data->encapType; +} + +static MetisListenerOps + mockListenerTemplate = { + .context = NULL, + .destroy = &mockListener_Destroy, + .getInterfaceIndex = &mockListener_GetInterfaceIndex, + .getListenAddress = &mockListener_GetListenAddress, + .getEncapType = &mockListener_GetEncapType +}; + +MockListenerData * +mockListenData_Create(unsigned interfaceIndex, CPIAddress *listenAddress, MetisEncapType encapType) +{ + MockListenerData *data = parcMemory_AllocateAndClear(sizeof(MockListenerData)); + assertNotNull(data, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MockListenerData)); + memset(data, 0, sizeof(MockListenerData)); + data->encapType = encapType; + data->interfaceIndex = interfaceIndex; + data->listenAddress = cpiAddress_Copy(listenAddress); + return data; +} + +MetisListenerOps * +mockListener_Create(MockListenerData *data) +{ + MetisListenerOps *ops = parcMemory_AllocateAndClear(sizeof(MetisListenerOps)); + assertNotNull(ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisListenerOps)); + memcpy(ops, &mockListenerTemplate, sizeof(MetisListenerOps)); + ops->context = data; + return ops; +} + +void +mockListenerData_Destroy(MockListenerData **dataPtr) +{ + MockListenerData *data = *dataPtr; + cpiAddress_Destroy(&data->listenAddress); + parcMemory_Deallocate((void **) &data); + *dataPtr = NULL; +} |