aboutsummaryrefslogtreecommitdiffstats
path: root/metis/ccnx/forwarder/metis/io
diff options
context:
space:
mode:
Diffstat (limited to 'metis/ccnx/forwarder/metis/io')
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_AddressPair.c148
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_AddressPair.h166
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_EtherConnection.c388
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_EtherConnection.h95
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_EtherListener.c728
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_EtherListener.h67
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_Ethernet.h36
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_GenericEther.h200
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_HopByHopFragmenter.c660
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_HopByHopFragmenter.h216
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_IPMulticastListener.c35
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_IPMulticastListener.h27
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_IoOperations.c91
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_IoOperations.h369
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_Listener.h103
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_ListenerSet.c146
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_ListenerSet.h133
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_LocalListener.c189
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_LocalListener.h29
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_StreamConnection.c570
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_StreamConnection.h65
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_TcpListener.c237
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_TcpListener.h35
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_TcpTunnel.c44
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_TcpTunnel.h49
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_UdpConnection.c396
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_UdpConnection.h51
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_UdpController.h20
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_UdpListener.c599
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_UdpListener.h31
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_UdpTunnel.c90
-rw-r--r--metis/ccnx/forwarder/metis/io/metis_UdpTunnel.h80
-rw-r--r--metis/ccnx/forwarder/metis/io/test/CMakeLists.txt25
-rw-r--r--metis/ccnx/forwarder/metis/io/test/test_metis_AddressPair.c153
-rw-r--r--metis/ccnx/forwarder/metis/io/test/test_metis_EtherConnection.c271
-rw-r--r--metis/ccnx/forwarder/metis/io/test/test_metis_EtherListener.c387
-rw-r--r--metis/ccnx/forwarder/metis/io/test/test_metis_HopByHopFragmenter.c1087
-rw-r--r--metis/ccnx/forwarder/metis/io/test/test_metis_IPMulticastListener.c80
-rw-r--r--metis/ccnx/forwarder/metis/io/test/test_metis_IoOperations.c185
-rw-r--r--metis/ccnx/forwarder/metis/io/test/test_metis_ListenerSet.c259
-rw-r--r--metis/ccnx/forwarder/metis/io/test/test_metis_LocalListener.c97
-rw-r--r--metis/ccnx/forwarder/metis/io/test/test_metis_StreamConnection.c767
-rw-r--r--metis/ccnx/forwarder/metis/io/test/test_metis_TcpListener.c364
-rw-r--r--metis/ccnx/forwarder/metis/io/test/test_metis_TcpTunnel.c216
-rw-r--r--metis/ccnx/forwarder/metis/io/test/test_metis_UdpConnection.c535
-rw-r--r--metis/ccnx/forwarder/metis/io/test/test_metis_UdpListener.c458
-rw-r--r--metis/ccnx/forwarder/metis/io/test/test_metis_UdpTunnel.c327
-rw-r--r--metis/ccnx/forwarder/metis/io/test/testrig_GenericEther.c236
-rw-r--r--metis/ccnx/forwarder/metis/io/test/testrig_GenericEther.h109
-rw-r--r--metis/ccnx/forwarder/metis/io/test/testrig_MetisListenerOps.c113
50 files changed, 11762 insertions, 0 deletions
diff --git a/metis/ccnx/forwarder/metis/io/metis_AddressPair.c b/metis/ccnx/forwarder/metis/io/metis_AddressPair.c
new file mode 100644
index 00000000..7725281f
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_AddressPair.c
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include <config.h>
+#include <stdio.h>
+
+#include <ccnx/forwarder/metis/io/metis_AddressPair.h>
+#include <parc/algol/parc_Memory.h>
+#include <parc/algol/parc_Hash.h>
+#include <parc/algol/parc_Object.h>
+
+#include <LongBow/runtime.h>
+
+struct metis_address_pair {
+ CPIAddress *local;
+ CPIAddress *remote;
+};
+
+static void
+_metisAddressPair_Destroy(MetisAddressPair **addressPairPtr)
+{
+ MetisAddressPair *pair = *addressPairPtr;
+
+ cpiAddress_Destroy(&pair->local);
+ cpiAddress_Destroy(&pair->remote);
+}
+
+parcObject_ExtendPARCObject(MetisAddressPair, _metisAddressPair_Destroy,
+ NULL, metisAddressPair_ToString, metisAddressPair_Equals, NULL, metisAddressPair_HashCode, NULL);
+
+parcObject_ImplementAcquire(metisAddressPair, MetisAddressPair);
+
+parcObject_ImplementRelease(metisAddressPair, MetisAddressPair);
+
+MetisAddressPair *
+metisAddressPair_Create(const CPIAddress *local, const CPIAddress *remote)
+{
+ assertNotNull(local, "Parameter local must be non-null");
+ assertNotNull(remote, "Parameter remote must be non-null");
+
+ MetisAddressPair *pair = parcObject_CreateInstance(MetisAddressPair);
+ assertNotNull(pair, "Got null from parcObject_Create()");
+
+ pair->local = cpiAddress_Copy(local);
+ pair->remote = cpiAddress_Copy(remote);
+
+ return pair;
+}
+
+bool
+metisAddressPair_Equals(const MetisAddressPair *a, const MetisAddressPair *b)
+{
+ if (a == b) {
+ return true;
+ }
+ if (a == NULL || b == NULL) {
+ return false;
+ }
+
+ if (cpiAddress_Equals(a->local, b->local)) {
+ if (cpiAddress_Equals(a->remote, b->remote)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+metisAddressPair_EqualsAddresses(const MetisAddressPair *a, const CPIAddress *local, const CPIAddress *remote)
+{
+ if (a == NULL || local == NULL || remote == NULL) {
+ return false;
+ }
+
+ if (cpiAddress_Equals(a->local, local)) {
+ if (cpiAddress_Equals(a->remote, remote)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+char *
+metisAddressPair_ToString(const MetisAddressPair *pair)
+{
+ assertNotNull(pair, "Parameter pair must be non-null");
+
+ char *local = cpiAddress_ToString(pair->local);
+ char *remote = cpiAddress_ToString(pair->remote);
+
+ char *output;
+ int failure = asprintf(&output, "{ .local=%s, .remote=%s }", local, remote);
+ assertTrue(failure > -1, "Error on asprintf");
+
+ parcMemory_Deallocate((void **) &local);
+ parcMemory_Deallocate((void **) &remote);
+
+ return output;
+}
+
+const CPIAddress *
+metisAddressPair_GetLocal(const MetisAddressPair *pair)
+{
+ assertNotNull(pair, "Parameter pair must be non-null");
+ return pair->local;
+}
+
+const CPIAddress *
+metisAddressPair_GetRemote(const MetisAddressPair *pair)
+{
+ assertNotNull(pair, "Parameter pair must be non-null");
+ return pair->remote;
+}
+
+/**
+ * @function metisAddressPair_HashCode
+ * @abstract Hash useful for tables. Consistent with Equals.
+ * @discussion
+ * Returns a non-cryptographic hash that is consistent with equals. That is,
+ * if a == b, then hash(a) == hash(b).
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+PARCHashCode
+metisAddressPair_HashCode(const MetisAddressPair *pair)
+{
+ PARCHashCode hashpair[2];
+ hashpair[0] = cpiAddress_HashCode(pair->local);
+ hashpair[1] = cpiAddress_HashCode(pair->remote);
+ return parcHashCode_Hash((const uint8_t *) hashpair, sizeof(hashpair));
+// return parcHash32_Data(hashpair, sizeof(hashpair));
+}
diff --git a/metis/ccnx/forwarder/metis/io/metis_AddressPair.h b/metis/ccnx/forwarder/metis/io/metis_AddressPair.h
new file mode 100644
index 00000000..a6678f23
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_AddressPair.h
@@ -0,0 +1,166 @@
+/*
+ * 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.
+ */
+
+
+/**
+ * Used to identify a connection between a specific local address and
+ * a specific remote address.
+ */
+
+#ifndef Metis_metis_AddressPair_h
+#define Metis_metis_AddressPair_h
+
+#include <ccnx/api/control/cpi_Address.h>
+
+struct metis_address_pair;
+typedef struct metis_address_pair MetisAddressPair;
+
+/**
+ * @function metisAddressPair_Create
+ * @abstract Creates and address pair. There is no restriction on the address types.
+ * @discussion
+ * Creates an ordered pair of addresses, where the first is considered the "local" address
+ * and the second is the "remote" address. Those designations are purely a convention used
+ * to name them, and does not imply any specifici types of operations.
+ *
+ * The two addresses may be of any address types (e.g. IPv4, IPv6, Local, Ethernet).
+ * However, some functions that use an AddressPair may require that the local and remote
+ * addresses be the same type.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+MetisAddressPair *metisAddressPair_Create(const CPIAddress *local, const CPIAddress *remote);
+
+/**
+ * Returns a reference counted copy of the address pair
+ *
+ * Increments the reference count and returns the same address pair
+ *
+ * @param [in] addressPair An allocated address pair
+ *
+ * @retval non-null A reference counted copy
+ * @retval null An error
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisAddressPair *metisAddressPair_Acquire(const MetisAddressPair *addressPair);
+
+
+/**
+ * Releases a reference count to the object
+ *
+ * Decrements the reference count and destroys the object when it reaches 0.
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @retval <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+void metisAddressPair_Release(MetisAddressPair **pairPtr);
+
+/**
+ * Determine if two MetisAddressPair instances are equal.
+ *
+ * Two MetisAddressPair instances are equal if, and only if, the local and remote addresses are identical.
+ * Equality is determined by cpiAddress_Equals(a->local, b->local) and
+ * cpiAdress_Equals(a->remote, b->remote).
+ *
+ * The following equivalence relations on non-null `MetisAddressPair` instances are maintained:
+ *
+ * * It is reflexive: for any non-null reference value x, `MetisAddressPair_Equals(x, x)`
+ * must return true.
+ *
+ * * It is symmetric: for any non-null reference values x and y,
+ * `metisAddressPair_Equals(x, y)` must return true if and only if
+ * `metisAddressPair_Equals(y, x)` returns true.
+ *
+ * * It is transitive: for any non-null reference values x, y, and z, if
+ * `metisAddressPair_Equals(x, y)` returns true and
+ * `metisAddressPair_Equals(y, z)` returns true,
+ * then `metisAddressPair_Equals(x, z)` must return true.
+ *
+ * * It is consistent: for any non-null reference values x and y, multiple
+ * invocations of `metisAddressPair_Equals(x, y)` consistently return true or
+ * consistently return false.
+ *
+ * * For any non-null reference value x, `metisAddressPair_Equals(x, NULL)` must
+ * return false.
+ *
+ * @param a A pointer to a `MetisAddressPair` instance.
+ * @param b A pointer to a `MetisAddressPair` instance.
+ * @return true if the two `MetisAddressPair` instances are equal.
+ *
+ * Example:
+ * @code
+ * {
+ * MetisAddressPair *a = metisAddressPair_Create();
+ * MetisAddressPair *b = metisAddressPair_Create();
+ *
+ * if (metisAddressPair_Equals(a, b)) {
+ * // true
+ * } else {
+ * // false
+ * }
+ * }
+ * @endcode
+ */
+bool metisAddressPair_Equals(const MetisAddressPair *a, const MetisAddressPair *b);
+
+/**
+ * @function metisAddressPair_EqualsAddresses
+ * @abstract As MetisAddressEquals, but "b" is broken out
+ * @discussion
+ * Equality is determined by cpiAddress_Equals(a->local, local) and
+ * cpiAdress_Equals(a->remote, remote).
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+bool metisAddressPair_EqualsAddresses(const MetisAddressPair *a, const CPIAddress *local, const CPIAddress *remote);
+
+const CPIAddress *metisAddressPair_GetLocal(const MetisAddressPair *pair);
+const CPIAddress *metisAddressPair_GetRemote(const MetisAddressPair *pair);
+
+/**
+ * @function metisAddressPair_HashCode
+ * @abstract Hash useful for tables. Consistent with Equals.
+ * @discussion
+ * Returns a non-cryptographic hash that is consistent with equals. That is,
+ * if a == b, then hash(a) == hash(b).
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+PARCHashCode metisAddressPair_HashCode(const MetisAddressPair *pair);
+
+/**
+ * @function metisAddressPair_ToString
+ * @abstract Human readable string representation. Caller must use free(3).
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+char *metisAddressPair_ToString(const MetisAddressPair *pair);
+#endif // Metis_metis_AddressPair_h
diff --git a/metis/ccnx/forwarder/metis/io/metis_EtherConnection.c b/metis/ccnx/forwarder/metis/io/metis_EtherConnection.c
new file mode 100644
index 00000000..63e9ab83
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_EtherConnection.c
@@ -0,0 +1,388 @@
+/*
+ * 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.
+ */
+
+/**
+ * Embodies the reader/writer for an Ethernet connection
+ *
+ * TODO: Put in a maintenance timer to timeout MAC entries if not used. Need to add a function
+ * the listener can call to bump up activity when a packet is received.
+ *
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <net/ethernet.h>
+
+#include <LongBow/runtime.h>
+
+#include <parc/algol/parc_Hash.h>
+#include <parc/algol/parc_Memory.h>
+
+
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+#include <ccnx/forwarder/metis/core/metis_Connection.h>
+#include <ccnx/forwarder/metis/core/metis_Message.h>
+#include <ccnx/forwarder/metis/tlv/metis_Tlv.h>
+#include <ccnx/forwarder/metis/io/metis_EtherConnection.h>
+#include <ccnx/forwarder/metis/messenger/metis_MissiveType.h>
+#include <ccnx/forwarder/metis/io/metis_HopByHopFragmenter.h>
+
+typedef struct metis_ether_state {
+ MetisForwarder *metis;
+ MetisLogger *logger;
+
+ // the udp listener socket we receive packets on
+ MetisGenericEther *ether;
+
+ MetisAddressPair *addressPair;
+
+ MetisHopByHopFragmenter *fragmenter;
+
+ // We need to access this all the time, so grab it out
+ // of the addressPair;
+ uint8_t myAddress[ETHER_ADDR_LEN];
+ uint8_t peerAddress[ETHER_ADDR_LEN];
+ uint16_t networkOrderEtherType;
+
+ bool isUp;
+ unsigned id;
+} _MetisEtherState;
+
+// Prototypes
+static bool _metisEtherConnection_Send(MetisIoOperations *ops, const CPIAddress *nexthop, MetisMessage *message);
+static const CPIAddress *_metisEtherConnection_GetRemoteAddress(const MetisIoOperations *ops);
+static const MetisAddressPair *_metisEtherConnection_GetAddressPair(const MetisIoOperations *ops);
+static unsigned _metisEtherConnection_GetConnectionId(const MetisIoOperations *ops);
+static bool _metisEtherConnection_IsUp(const MetisIoOperations *ops);
+static bool _metisEtherConnection_IsLocal(const MetisIoOperations *ops);
+static void _metisEtherConnection_Release(MetisIoOperations **opsPtr);
+
+static void _setConnectionState(_MetisEtherState *ether, bool isUp);
+static CPIConnectionType _metisEtherConnection_getConnectionType(const MetisIoOperations *ops);
+static MetisTicks _sendProbe(MetisIoOperations *ops, unsigned probeType);
+
+/*
+ * This assigns a unique pointer to the void * which we use
+ * as a GUID for this class.
+ */
+static const void *_metisIoOperationsGuid = __FILE__;
+
+/*
+ * Return our GUID
+ */
+static const void *
+_metisEtherConnection_Class(const MetisIoOperations *ops)
+{
+ return _metisIoOperationsGuid;
+}
+
+static MetisIoOperations _template = {
+ .closure = NULL,
+ .send = &_metisEtherConnection_Send,
+ .getRemoteAddress = &_metisEtherConnection_GetRemoteAddress,
+ .getAddressPair = &_metisEtherConnection_GetAddressPair,
+ .getConnectionId = &_metisEtherConnection_GetConnectionId,
+ .isUp = &_metisEtherConnection_IsUp,
+ .isLocal = &_metisEtherConnection_IsLocal,
+ .destroy = &_metisEtherConnection_Release,
+ .class = &_metisEtherConnection_Class,
+ .getConnectionType = &_metisEtherConnection_getConnectionType,
+ .sendProbe = &_sendProbe,
+};
+
+// =================================================================
+
+static bool
+_metisEtherConnection_FillInMacAddress(uint8_t *mac, const CPIAddress *source)
+{
+ PARCBuffer *macAddress = cpiAddress_GetLinkAddress(source);
+ if (macAddress) {
+ parcBuffer_GetBytes(macAddress, ETHER_ADDR_LEN, mac);
+ parcBuffer_Rewind(macAddress);
+ return true;
+ }
+ return false;
+}
+
+MetisIoOperations *
+metisEtherConnection_Create(MetisForwarder *metis, MetisGenericEther *ether, MetisAddressPair *pair)
+{
+ bool success = false;
+ size_t allocationSize = sizeof(_MetisEtherState) + sizeof(MetisIoOperations);
+ MetisIoOperations *ops = parcMemory_AllocateAndClear(allocationSize);
+ if (ops) {
+ memcpy(ops, &_template, sizeof(MetisIoOperations));
+ ops->closure = (uint8_t *) ops + sizeof(MetisIoOperations);
+
+ _MetisEtherState *etherConnState = metisIoOperations_GetClosure(ops);
+
+ if (_metisEtherConnection_FillInMacAddress(etherConnState->myAddress, metisAddressPair_GetLocal(pair))) {
+ if (_metisEtherConnection_FillInMacAddress(etherConnState->peerAddress, metisAddressPair_GetRemote(pair))) {
+ etherConnState->metis = metis;
+ etherConnState->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis));
+ etherConnState->ether = metisGenericEther_Acquire(ether);
+ etherConnState->id = metisForwarder_GetNextConnectionId(metis);
+ etherConnState->addressPair = metisAddressPair_Acquire(pair);
+ etherConnState->networkOrderEtherType = htons(metisGenericEther_GetEtherType(ether));
+ etherConnState->fragmenter = metisHopByHopFragmenter_Create(etherConnState->logger, metisGenericEther_GetMTU(ether));
+
+ _setConnectionState(etherConnState, true);
+
+ if (metisLogger_IsLoggable(etherConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) {
+ char *str = metisAddressPair_ToString(pair);
+ metisLogger_Log(etherConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "EtherConnection %p created address pair %s",
+ (void *) etherConnState, str);
+ free(str);
+ }
+
+ metisMessenger_Send(metisForwarder_GetMessenger(metis), metisMissive_Create(MetisMissiveType_ConnectionCreate, etherConnState->id));
+ metisMessenger_Send(metisForwarder_GetMessenger(metis), metisMissive_Create(MetisMissiveType_ConnectionUp, etherConnState->id));
+
+ success = true;
+ }
+ }
+ }
+
+ if (ops && !success) {
+ if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error)) {
+ char *str = metisAddressPair_ToString(pair);
+ metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error, __func__,
+ "Error creating EtherConnection address pair %s",
+ str);
+ free(str);
+ }
+
+ parcMemory_Deallocate(&ops);
+ }
+
+ return ops;
+}
+
+// =================================================================
+// I/O Operations implementation
+
+/*
+ * We only do one allocation of the combined MetisIoOperations and _MetisEtherState, so this
+ * function only needs to destroy the internal state
+ */
+static void
+_metisEtherConnection_InternalRelease(_MetisEtherState *etherConnState)
+{
+ if (metisLogger_IsLoggable(etherConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) {
+ metisLogger_Log(etherConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "EtherConnection %p destroyed",
+ (void *) etherConnState);
+ }
+
+ metisMessenger_Send(metisForwarder_GetMessenger(etherConnState->metis), metisMissive_Create(MetisMissiveType_ConnectionDestroyed, etherConnState->id));
+
+ metisAddressPair_Release(&etherConnState->addressPair);
+ metisGenericEther_Release(&etherConnState->ether);
+ metisHopByHopFragmenter_Release(&etherConnState->fragmenter);
+ metisLogger_Release(&etherConnState->logger);
+}
+
+static void
+_metisEtherConnection_Release(MetisIoOperations **opsPtr)
+{
+ assertNotNull(opsPtr, "Parameter opsPtr must be non-null double pointer");
+ assertNotNull(*opsPtr, "Parameter opsPtr must dereference to non-null pointer");
+
+ MetisIoOperations *ops = *opsPtr;
+ assertNotNull(metisIoOperations_GetClosure(ops), "ops->context must not be null");
+
+ _MetisEtherState *etherConnState = (_MetisEtherState *) metisIoOperations_GetClosure(ops);
+ _metisEtherConnection_InternalRelease(etherConnState);
+
+ // do not close udp->udpListenerSocket, the listener will close
+ // that when its done
+
+ parcMemory_Deallocate((void **) &ops);
+}
+
+static bool
+_metisEtherConnection_IsUp(const MetisIoOperations *ops)
+{
+ assertNotNull(ops, "Parameter must be non-null");
+ const _MetisEtherState *etherConnState = (const _MetisEtherState *) metisIoOperations_GetClosure(ops);
+ return etherConnState->isUp;
+}
+
+static bool
+_metisEtherConnection_IsLocal(const MetisIoOperations *ops)
+{
+ return false;
+}
+
+static const CPIAddress *
+_metisEtherConnection_GetRemoteAddress(const MetisIoOperations *ops)
+{
+ assertNotNull(ops, "Parameter must be non-null");
+ const _MetisEtherState *etherConnState = (const _MetisEtherState *) metisIoOperations_GetClosure(ops);
+ return metisAddressPair_GetRemote(etherConnState->addressPair);
+}
+
+static const MetisAddressPair *
+_metisEtherConnection_GetAddressPair(const MetisIoOperations *ops)
+{
+ assertNotNull(ops, "Parameter must be non-null");
+ const _MetisEtherState *etherConnState = (const _MetisEtherState *) metisIoOperations_GetClosure(ops);
+ return etherConnState->addressPair;
+}
+
+
+static unsigned
+_metisEtherConnection_GetConnectionId(const MetisIoOperations *ops)
+{
+ assertNotNull(ops, "Parameter must be non-null");
+ const _MetisEtherState *etherConnState = (const _MetisEtherState *) metisIoOperations_GetClosure(ops);
+ return etherConnState->id;
+}
+
+static bool
+_sendFrame(_MetisEtherState *etherConnState, MetisMessage *message)
+{
+ PARCEventBuffer *writeBuffer = parcEventBuffer_Create();
+
+ int failure = metisMessage_Append(writeBuffer, message);
+ if (failure) {
+ parcEventBuffer_Destroy(&writeBuffer);
+ return false;
+ }
+
+ // Add an Ethernet header
+ struct ether_header header;
+
+ // Fill in the ethernet header
+ header.ether_type = etherConnState->networkOrderEtherType;
+ memcpy(header.ether_dhost, etherConnState->peerAddress, ETHER_ADDR_LEN);
+ memcpy(header.ether_shost, etherConnState->myAddress, ETHER_ADDR_LEN);
+
+ // and put it at the front of the output buffer
+ parcEventBuffer_Prepend(writeBuffer, &header, sizeof(header));
+
+ bool success = metisGenericEther_SendFrame(etherConnState->ether, writeBuffer);
+
+ // we're done with the buffer
+ parcEventBuffer_Destroy(&writeBuffer);
+
+ // BugzID: 3343 - close the connection on certain errors??
+ return success;
+}
+
+/**
+ * @function metisEtherConnection_Send
+ * @abstract Non-destructive send of the message.
+ * @discussion
+ * sends a message to the peer.
+ *
+ * @param dummy is ignored. A udp connection has only one peer.
+ * @return <#return#>
+ */
+static bool
+_metisEtherConnection_Send(MetisIoOperations *ops, const CPIAddress *dummy, MetisMessage *message)
+{
+ assertNotNull(ops, "Parameter ops must be non-null");
+ assertNotNull(message, "Parameter message must be non-null");
+ _MetisEtherState *etherConnState = (_MetisEtherState *) metisIoOperations_GetClosure(ops);
+
+ bool success = metisHopByHopFragmenter_Send(etherConnState->fragmenter, message);
+
+ MetisMessage *fragment;
+ while (success && (fragment = metisHopByHopFragmenter_PopSendQueue(etherConnState->fragmenter)) != NULL) {
+ success = _sendFrame(etherConnState, fragment);
+ metisMessage_Release(&fragment);
+ }
+
+ // if we failed, drain the other fragments
+ if (!success) {
+ while ((fragment = metisHopByHopFragmenter_PopSendQueue(etherConnState->fragmenter)) != NULL) {
+ metisMessage_Release(&fragment);
+ }
+ }
+
+ return success;
+}
+
+static void
+_setConnectionState(_MetisEtherState *etherConnState, bool isUp)
+{
+ assertNotNull(etherConnState, "Parameter Udp must be non-null");
+
+ MetisMessenger *messenger = metisForwarder_GetMessenger(etherConnState->metis);
+
+ bool oldStateIsUp = etherConnState->isUp;
+ etherConnState->isUp = isUp;
+
+ if (oldStateIsUp && !isUp) {
+ // bring connection DOWN
+ MetisMissive *missive = metisMissive_Create(MetisMissiveType_ConnectionDown, etherConnState->id);
+ metisMessenger_Send(messenger, missive);
+ return;
+ }
+
+
+ if (!oldStateIsUp && isUp) {
+ // bring connection UP
+ MetisMissive *missive = metisMissive_Create(MetisMissiveType_ConnectionUp, etherConnState->id);
+ metisMessenger_Send(messenger, missive);
+ return;
+ }
+}
+
+static CPIConnectionType
+_metisEtherConnection_getConnectionType(const MetisIoOperations *ops)
+{
+ return cpiConnection_L2;
+}
+
+
+bool
+metisEtherConnection_IsInstanceOf(const MetisConnection *conn)
+{
+ bool result = false;
+ if (conn != NULL) {
+ const void *class = metisConnection_Class(conn);
+ if (class == _metisIoOperationsGuid) {
+ result = true;
+ }
+ }
+ return result;
+}
+
+
+MetisHopByHopFragmenter *
+metisEtherConnection_GetFragmenter(const MetisConnection *conn)
+{
+ MetisHopByHopFragmenter *fragmenter = NULL;
+
+ if (metisEtherConnection_IsInstanceOf(conn)) {
+ MetisIoOperations *ops = metisConnection_GetIoOperations(conn);
+ _MetisEtherState *state = (_MetisEtherState *) metisIoOperations_GetClosure(ops);
+ fragmenter = state->fragmenter;
+ }
+ return fragmenter;
+}
+
+static MetisTicks
+_sendProbe(MetisIoOperations *ops, unsigned probeType)
+{
+ //TODO
+ return 0;
+}
+
diff --git a/metis/ccnx/forwarder/metis/io/metis_EtherConnection.h b/metis/ccnx/forwarder/metis/io/metis_EtherConnection.h
new file mode 100644
index 00000000..c92587f1
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_EtherConnection.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file metis_EtherConnection.h
+ * @brief Represents an ethernet pair (source address, destination address) in the connection table
+ *
+ * Ethernet connections are never local.
+ *
+ */
+
+#ifndef Metis_metis_EtherConnection_h
+#define Metis_metis_EtherConnection_h
+
+#include <ccnx/forwarder/metis/io/metis_IoOperations.h>
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+#include <ccnx/forwarder/metis/io/metis_AddressPair.h>
+#include <ccnx/api/control/cpi_Address.h>
+#include <ccnx/forwarder/metis/io/metis_GenericEther.h>
+#include <ccnx/forwarder/metis/io/metis_HopByHopFragmenter.h>
+#include <ccnx/forwarder/metis/core/metis_Connection.h>
+
+/**
+ * @function metisEtherConnection_Create
+ * @abstract <#OneLineDescription#>
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param pair the address pair that uniquely identifies the connection. Takes ownership of this memory.
+ * @return <#return#>
+ */
+MetisIoOperations *metisEtherConnection_Create(MetisForwarder *metis, MetisGenericEther *ether, MetisAddressPair *pair);
+
+/**
+ * If the IO Operats are of type MetisEtherConnection, return its fragmenter
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [in] conn An allocated MetisConnection
+ *
+ * @return non-null The fragmenter associated with this conneciton
+ * @return null There is no such fragmenter or the ops is not a MetisEtherConnection
+ *
+ * Example:
+ * @code
+ * {
+ * <#example#>
+ * }
+ * @endcode
+ */
+MetisHopByHopFragmenter *metisEtherConnection_GetFragmenter(const MetisConnection *conn);
+
+/**
+ * Tests if MetisEtherConnection is the underlying implementation of the connection
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [in] conn An allocated MetisConnection
+ *
+ * @return true The connection is of type MetisEtherConnection
+ * @return false The connection is of other type
+ *
+ * Example:
+ * @code
+ * {
+ * MetisHopByHopFragmenter *
+ * metisEtherConnection_GetFragmenter(const MetisConnection *conn)
+ * {
+ * MetisHopByHopFragmenter *fragmenter = NULL;
+ *
+ * if (metisEtherConnection_IsInstanceOf(conn)) {
+ * MetisIoOperations *ops = metisConnection_GetIoOperations(conn);
+ * _MetisEtherState *state = (_MetisEtherState *) ops->context;
+ * fragmenter = state->fragmenter;
+ * }
+ * return fragmenter;
+ * }
+ * }
+ * @endcode
+ */
+bool metisEtherConnection_IsInstanceOf(const MetisConnection *conn);
+
+#endif // Metis_metis_EtherConnection_h
diff --git a/metis/ccnx/forwarder/metis/io/metis_EtherListener.c b/metis/ccnx/forwarder/metis/io/metis_EtherListener.c
new file mode 100644
index 00000000..58ac07d1
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_EtherListener.c
@@ -0,0 +1,728 @@
+/*
+ * 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.
+ */
+
+/**
+ * Implements the listener over Ethernet.
+ *
+ * Right now only supports non-VLAN frames Ethernet (not 802.3/802.2 LLC) frames.
+ *
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <arpa/inet.h>
+
+#include <net/ethernet.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+#include <ccnx/forwarder/metis/core/metis_Connection.h>
+#include <ccnx/forwarder/metis/core/metis_Message.h>
+#include <ccnx/forwarder/metis/messenger/metis_Messenger.h>
+
+#include <ccnx/forwarder/metis/core/metis_Dispatcher.h>
+
+#include <ccnx/forwarder/metis/io/metis_GenericEther.h>
+#include <ccnx/forwarder/metis/io/metis_EtherConnection.h>
+
+#ifndef ntohll
+#define ntohll(x) (((uint64_t) (ntohl((uint32_t) (x & 0x00000000FFFFFFFF))) << 32) | ntohl(((uint32_t) ((((uint64_t) x) >> 32)))))
+#define htonll(x) ntohll(x)
+#endif
+
+#include <LongBow/runtime.h>
+#include <parc/algol/parc_Memory.h>
+
+typedef struct metis_ether_stats {
+ uint64_t framesIn;
+ uint64_t framesError;
+ uint64_t framesReceived;
+ uint64_t framesReassembled;
+ uint64_t framesNotForUs;
+} _MetisEtherStats;
+
+typedef struct metis_ether_listener {
+ MetisForwarder *metis;
+ MetisLogger *logger;
+
+ MetisGenericEther *genericEther;
+
+ unsigned id;
+ CPIAddress *localAddress;
+
+ uint16_t ethertype;
+
+ int ether_fd;
+ PARCEvent *ether_event;
+
+ // what size do the read buffers need to be?
+ size_t ether_buffer_length;
+
+ // buffer to read next packet in to
+ PARCEventBuffer *nextReadBuffer;
+
+ // We store MAC addresses in uint64 and mask them down to 6 bytes.
+ // this means all our address comparisons are simple "==" operations
+
+ uint64_t *destinationAddressList;
+ size_t destinationAddressSize;
+
+ uint64_t *sourceAddressList;
+ size_t sourceAddressSize;
+
+ _MetisEtherStats stats;
+} _MetisEtherListener;
+
+// network byte order mask to go from 8-bytes to 6-bytes
+#define MAC_MASK (htonll(0xFFFFFFFFFFFF0000ULL))
+
+static void _metisEtherListener_OpsDestroy(MetisListenerOps **listenerOpsPtr);
+static unsigned _metisEtherListener_OpsGetInterfaceIndex(const MetisListenerOps *ops);
+static const CPIAddress *_metisEtherListener_OpsGetListenAddress(const MetisListenerOps *ops);
+static MetisEncapType _metisEtherListener_OpsGetEncapType(const MetisListenerOps *ops);
+static int _metisEtherListener_OpsGetSocket(const MetisListenerOps *ops);
+
+static MetisListenerOps _etherTemplate = {
+ .context = NULL,
+ .destroy = &_metisEtherListener_OpsDestroy,
+ .getInterfaceIndex = &_metisEtherListener_OpsGetInterfaceIndex,
+ .getListenAddress = &_metisEtherListener_OpsGetListenAddress,
+ .getEncapType = &_metisEtherListener_OpsGetEncapType,
+ .getSocket = &_metisEtherListener_OpsGetSocket
+};
+
+// Called by Libevent
+static void _metisEtherListener_ReadCallback(int fd, PARCEventType what, void *user_data);
+
+static void
+_logStats(_MetisEtherListener *listener, PARCLogLevel level)
+{
+ if (metisLogger_IsLoggable(listener->logger, MetisLoggerFacility_IO, level)) {
+ metisLogger_Log(listener->logger, MetisLoggerFacility_IO, level, __func__,
+ "EtherListener %p frames in %" PRIu64 ", errors %" PRIu64 " ok %" PRIu64 " reassemble %" PRIu64 " reject %" PRIu64,
+ (void *) listener,
+ listener->stats.framesIn,
+ listener->stats.framesError,
+ listener->stats.framesReceived,
+ listener->stats.framesReassembled,
+ listener->stats.framesNotForUs);
+ }
+}
+
+
+// =============================================
+// Public API
+
+static void
+_metisEtherListener_FillInEthernetAddresses(_MetisEtherListener *etherListener)
+{
+ // this may be null
+ PARCBuffer *myAddress = metisGenericEther_GetMacAddress(etherListener->genericEther);
+ uint64_t macAsUint64 = 0;
+
+ if (myAddress) {
+ while (parcBuffer_Remaining(myAddress) > 0) {
+ uint8_t b = parcBuffer_GetUint8(myAddress);
+ macAsUint64 <<= 8;
+ macAsUint64 |= b;
+ }
+ // the mac address is only 6 bytes, so shift two more
+ macAsUint64 <<= 16;
+ parcBuffer_Rewind(myAddress);
+
+ // loopback interface as a 0-length link address
+ if (parcBuffer_Remaining(myAddress) > 0) {
+ etherListener->localAddress = cpiAddress_CreateFromLink(parcBuffer_Overlay(myAddress, 0), parcBuffer_Remaining(myAddress));
+ }
+ }
+
+ etherListener->destinationAddressList = parcMemory_AllocateAndClear(sizeof(uint64_t) * 3);
+ etherListener->destinationAddressSize = 3;
+ etherListener->destinationAddressList[0] = htonll(macAsUint64); // our address
+ etherListener->destinationAddressList[1] = htonll(0x01005E0017AA0000); // CCN address
+ etherListener->destinationAddressList[2] = htonll(0xFFFFFFFFFFFF0000); // broadcast
+
+ etherListener->sourceAddressList = parcMemory_AllocateAndClear(sizeof(uint64_t));
+ etherListener->sourceAddressSize = 1;
+ etherListener->sourceAddressList[0] = htonll(macAsUint64); // our address
+}
+
+static void
+_metisEtherListener_ReleaseEthernetAddresses(_MetisEtherListener *etherListener)
+{
+ parcMemory_Deallocate((void **) &etherListener->destinationAddressList);
+ parcMemory_Deallocate((void **) &etherListener->sourceAddressList);
+
+ if (etherListener->localAddress) {
+ cpiAddress_Destroy(&etherListener->localAddress);
+ }
+}
+
+MetisListenerOps *
+metisEtherListener_Create(MetisForwarder *metis, const char *deviceName, uint16_t ethertype)
+{
+ assertNotNull(metis, "Parameter metis must be non-null");
+ assertNotNull(deviceName, "Parameter deviceName must be non-null");
+
+ _MetisEtherListener *etherListener = parcMemory_AllocateAndClear(sizeof(_MetisEtherListener));
+ etherListener->ethertype = ethertype;
+ etherListener->genericEther = metisGenericEther_Create(metis, deviceName, etherListener->ethertype);
+
+ MetisListenerOps *ops = NULL;
+
+ if (etherListener->genericEther != NULL) {
+ etherListener->metis = metis;
+ etherListener->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis));
+ etherListener->nextReadBuffer = parcEventBuffer_Create();
+ etherListener->id = metisForwarder_GetNextConnectionId(metis);
+
+ int etherSocket = metisGenericEther_GetDescriptor(etherListener->genericEther);
+ bool persistent = true;
+
+ // now wrap it in an event callback
+ etherListener->ether_event = metisDispatcher_CreateNetworkEvent(
+ metisForwarder_GetDispatcher(etherListener->metis),
+ persistent,
+ _metisEtherListener_ReadCallback,
+ etherListener,
+ etherSocket);
+
+ assertNotNull(etherListener->ether_event, "got null event from metisDispatcher_CreateNetworkEvent: %s", strerror(errno));
+
+ // Setup the destination and source ethernet addresses we want to use
+ _metisEtherListener_FillInEthernetAddresses(etherListener);
+
+ // Finished all initialization, so start the network event.
+ metisDispatcher_StartNetworkEvent(metisForwarder_GetDispatcher(etherListener->metis), etherListener->ether_event);
+
+ // Construct an instance of the MetisListenerOps particular to this context.
+ ops = parcMemory_Allocate(sizeof(MetisListenerOps));
+ assertNotNull(ops, "Got null from parc_memory_new");
+
+ memcpy(ops, &_etherTemplate, sizeof(MetisListenerOps));
+ ops->context = etherListener;
+
+ if (metisLogger_IsLoggable(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) {
+ char *str = cpiAddress_ToString(etherListener->localAddress);
+ metisLogger_Log(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "Create Ethernet Listener id %d on %s addr %s ethertype 0x%04x ether socket %d",
+ etherListener->id,
+ deviceName,
+ str,
+ ethertype,
+ etherSocket);
+ parcMemory_Deallocate((void **) &str);
+ }
+ } else {
+ // failed to setup an Ethernet device
+ parcMemory_Deallocate((void **) &etherListener);
+ }
+
+ return ops;
+}
+
+MetisGenericEther *
+metisEtherListener_GetGenericEtherFromListener(MetisListenerOps *listenerOps)
+{
+ assertNotNull(listenerOps, "Parameter listenerOps must be non-null");
+ assertTrue(listenerOps->getEncapType(listenerOps) == METIS_ENCAP_ETHER, "Can only call on a METIS_ENCAP_ETHER listener");
+
+ _MetisEtherListener *etherListener = (_MetisEtherListener *) listenerOps->context;
+ return etherListener->genericEther;
+}
+
+static void
+_metisEtherListener_Destroy(_MetisEtherListener **listenerPtr)
+{
+ assertNotNull(listenerPtr, "Parameter must be non-null double pointer");
+ assertNotNull(*listenerPtr, "Parameter must derefernce to non-null pointer");
+
+ _MetisEtherListener *etherListener = *listenerPtr;
+ parcEventBuffer_Destroy(&(etherListener->nextReadBuffer));
+ metisDispatcher_DestroyNetworkEvent(metisForwarder_GetDispatcher(etherListener->metis), &etherListener->ether_event);
+
+ metisLogger_Release(&etherListener->logger);
+
+ metisGenericEther_Release(&etherListener->genericEther);
+ _metisEtherListener_ReleaseEthernetAddresses(etherListener);
+ parcMemory_Deallocate((void **) &etherListener);
+ *listenerPtr = NULL;
+}
+
+static void
+_metisEtherListener_OpsDestroy(MetisListenerOps **listenerOpsPtr)
+{
+ MetisListenerOps *ops = *listenerOpsPtr;
+ _MetisEtherListener *etherListener = (_MetisEtherListener *) ops->context;
+ _metisEtherListener_Destroy(&etherListener);
+ parcMemory_Deallocate((void **) &ops);
+ *listenerOpsPtr = NULL;
+}
+
+static unsigned
+_metisEtherListener_OpsGetInterfaceIndex(const MetisListenerOps *ops)
+{
+ _MetisEtherListener *etherListener = (_MetisEtherListener *) ops->context;
+ return etherListener->id;
+}
+
+static const CPIAddress *
+_metisEtherListener_OpsGetListenAddress(const MetisListenerOps *ops)
+{
+ _MetisEtherListener *etherListener = (_MetisEtherListener *) ops->context;
+ return etherListener->localAddress;
+}
+
+static MetisEncapType
+_metisEtherListener_OpsGetEncapType(const MetisListenerOps *ops)
+{
+ return METIS_ENCAP_ETHER;
+}
+
+static int
+_metisEtherListener_OpsGetSocket(const MetisListenerOps *ops)
+{
+ _MetisEtherListener *etherListener = (_MetisEtherListener *) ops->context;
+ return etherListener->ether_fd;
+}
+
+
+// =============================================
+// Internal functions
+
+/**
+ * Construct an address pair to match the remote
+ *
+ * The pair will always be (ourMacAddress, header->sourceAddress), even if the
+ * packet was received via a group or broadcast dmac.
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @retval <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+static MetisAddressPair *
+_metisEtherListener_ConstructAddressPair(_MetisEtherListener *etherListener, PARCEventBuffer *buffer)
+{
+ struct ether_header *header = (struct ether_header *) parcEventBuffer_Pullup(buffer, ETHER_HDR_LEN);
+
+ CPIAddress *remoteAddress = cpiAddress_CreateFromLink(header->ether_shost, ETHER_ADDR_LEN);
+
+ MetisAddressPair *pair = metisAddressPair_Create(etherListener->localAddress, remoteAddress);
+ cpiAddress_Destroy(&remoteAddress);
+
+ return pair;
+}
+
+/**
+ * Lookup a connection in the connection table based on an address pair
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [in] etherListener An allocated MetisEtherListener
+ * @param [in] pair The Address pair to lookup
+ *
+ * @return null Not found
+ * @return non-null The connection
+ *
+ * Example:
+ * @code
+ * {
+ * <#example#>
+ * }
+ * @endcode
+ */
+static const MetisConnection *
+_metisEtherListener_LookupConnectionId(_MetisEtherListener *etherListener, MetisAddressPair *pair)
+{
+ MetisConnectionTable *connTable = metisForwarder_GetConnectionTable(etherListener->metis);
+
+ const MetisConnection *conn = metisConnectionTable_FindByAddressPair(connTable, pair);
+ return conn;
+}
+
+/**
+ * @function _metisEtherListener_CreateNewConnection
+ * @abstract Creates a new Metis connection for the peer
+ * @discussion
+ * PRECONDITION: you know there's not an existing connection with the address pair
+ *
+ * Creates a new connection and adds it to the connection table.
+ *
+ * @param <#param1#>
+ * @return The connection id for the new connection
+ */
+static const MetisConnection *
+_metisEtherListener_CreateNewConnection(_MetisEtherListener *etherListener, MetisAddressPair *pair)
+{
+ // metisEtherConnection_Create takes ownership of the pair
+ MetisIoOperations *ops = metisEtherConnection_Create(etherListener->metis, etherListener->genericEther, pair);
+ MetisConnection *conn = metisConnection_Create(ops);
+
+ metisConnectionTable_Add(metisForwarder_GetConnectionTable(etherListener->metis), conn);
+
+ return conn;
+}
+
+/**
+ * Read an ethernet frame and return its buffer.
+ *
+ * Will use ether->nextReadBuffer. If we read a frame, will allocate a new nextReadBuffer.
+ *
+ * @param [in] fd The ethernet frame socket
+ *
+ * @return NULL could not read a frame
+ * @return non-null Ethernet frame in the buffer
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+static PARCEventBuffer *
+_metisEtherListener_ReadEtherFrame(_MetisEtherListener *etherListener)
+{
+ PARCEventBuffer *readBuffer = NULL;
+
+ bool success = metisGenericEther_ReadNextFrame(etherListener->genericEther, etherListener->nextReadBuffer);
+
+ if (success) {
+ readBuffer = etherListener->nextReadBuffer;
+ etherListener->nextReadBuffer = parcEventBuffer_Create();
+
+ if (metisLogger_IsLoggable(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) {
+ metisLogger_Log(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "read %zu bytes",
+ parcEventBuffer_GetLength(readBuffer));
+ }
+ }
+
+ return readBuffer;
+}
+
+/**
+ * Compares source MAC address to our address
+ *
+ * buffer points to the start of the Ethernet frame. Checks if the source address
+ * is our address.
+ *
+ * The check is done against the array of addresses in ether->sourceAddressList
+ *
+ * @param [in] header The Ethernet header
+ *
+ * @return true The Ethernet source address is our MAC address
+ * @return false It is not our MAC address
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+static bool
+_metisEtherListener_IsOurSourceAddress(_MetisEtherListener *ether, struct ether_header *header)
+{
+ // this copies the source address, then masks our copy
+ uint64_t u64_shost;
+ memcpy(&u64_shost, header->ether_shost, ETHER_ADDR_LEN);
+ u64_shost &= MAC_MASK;
+
+ for (int i = 0; i < ether->sourceAddressSize; i++) {
+ if (u64_shost == ether->sourceAddressList[i]) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Compares destination MAC address to our receive addresses
+ *
+ * The check is done against the array of addresses in ether->destinationAddressList
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @return <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+static bool
+_metisEtherListener_IsOurDestinationAddress(_MetisEtherListener *ether, struct ether_header *header)
+{
+ // this copies the source address, then masks our copy
+ uint64_t u64_dhost;
+ memcpy(&u64_dhost, header->ether_dhost, ETHER_ADDR_LEN);
+ u64_dhost &= MAC_MASK;
+
+ for (int i = 0; i < ether->destinationAddressSize; i++) {
+ if (u64_dhost == ether->destinationAddressList[i]) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * <#One Line Description#>
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @return <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+static bool
+_metisEtherListener_IsOurProtocol(_MetisEtherListener *ether, struct ether_header *header)
+{
+ // TODO: check the ethertype
+ return true;
+}
+
+typedef enum {
+ ParseResult_Accept,
+ ParseResult_Reject,
+ ParseResult_Error
+} _ParseResult;
+
+/**
+ * Processes an ethernet frame to make sure its for us
+ *
+ * Ensures that the frame is for us, and not from our source address.
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @return ParseResult_Accept We should recieve and process the frame
+ * @return ParseResult_Reject Do not receive the frame
+ * @return ParseResult_Error There was an error looking at the Ethernet header
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+static _ParseResult
+_metisEtherListener_ParseEtherFrame(_MetisEtherListener *etherListener, PARCEventBuffer *buffer)
+{
+ _ParseResult result = ParseResult_Error;
+
+ struct ether_header *header = (struct ether_header *) parcEventBuffer_Pullup(buffer, ETHER_HDR_LEN);
+
+ if (header) {
+ result = ParseResult_Reject;
+ if (_metisEtherListener_IsOurProtocol(etherListener, header)) {
+ if (_metisEtherListener_IsOurDestinationAddress(etherListener, header)) {
+ if (!_metisEtherListener_IsOurSourceAddress(etherListener, header)) {
+ // ok, it is the right protocol, a good destination address
+ // and not our source address. We should ccept this.
+
+ result = ParseResult_Accept;
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+static const MetisConnection *
+_metisEtherListener_LookupOrCreateConnection(_MetisEtherListener *etherListener, PARCEventBuffer *buffer)
+{
+ MetisAddressPair *pair = _metisEtherListener_ConstructAddressPair(etherListener, buffer);
+
+ const MetisConnection *conn = _metisEtherListener_LookupConnectionId(etherListener, pair);
+
+ if (!conn) {
+ conn = _metisEtherListener_CreateNewConnection(etherListener, pair);
+
+ if (metisLogger_IsLoggable(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) {
+ char *str = metisAddressPair_ToString(pair);
+ metisLogger_Log(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "Create connid %u address pair %s", metisConnection_GetConnectionId(conn), str);
+ free(str);
+ }
+ }
+ metisAddressPair_Release(&pair);
+
+ return conn;
+}
+
+/**
+ * Accept a fragment, put it in rassembler, and pass reassembled frames up stack.
+ *
+ * precondition: message is not null
+ *
+ * @param [<#in#> | <#out#> | <#in,out#>] <#name#> <#description#>
+ *
+ * @return <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * {
+ * <#example#>
+ * }
+ * @endcode
+ */
+static void
+_acceptFragment(_MetisEtherListener *etherListener, const MetisConnection *conn, MetisMessage *message)
+{
+ MetisHopByHopFragmenter *fragmenter = metisEtherConnection_GetFragmenter(conn);
+ assertNotNull(fragmenter, "Could not get fragmenter from the underlying connection");
+
+ bool receiveQueueNotEmpty = metisHopByHopFragmenter_Receive(fragmenter, message);
+ if (receiveQueueNotEmpty) {
+ MetisMessage *assembled = NULL;
+ while ((assembled = metisHopByHopFragmenter_PopReceiveQueue(fragmenter)) != NULL) {
+ etherListener->stats.framesReassembled++;
+ metisForwarder_Receive(etherListener->metis, assembled);
+ }
+ }
+}
+
+static void
+_acceptFrame(_MetisEtherListener *etherListener, PARCEventBuffer *buffer, int fd)
+{
+ const MetisConnection *conn = _metisEtherListener_LookupOrCreateConnection(etherListener, buffer);
+
+ // remove the ethernet header
+ parcEventBuffer_Read(buffer, NULL, ETHER_HDR_LEN);
+
+ // takes ownership of buffer (will destroy it if there's an error)
+ MetisMessage *message = metisMessage_CreateFromBuffer(metisConnection_GetConnectionId(conn), metisForwarder_GetTicks(etherListener->metis), buffer, etherListener->logger);
+
+ size_t readLength = parcEventBuffer_GetLength(buffer);
+ if (message) {
+ etherListener->stats.framesReceived++;
+
+ if (metisLogger_IsLoggable(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) {
+ metisLogger_Log(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "read %zu bytes from fd %d connid %d",
+ readLength,
+ fd,
+ metisConnection_GetConnectionId(conn));
+ _logStats(etherListener, PARCLogLevel_Debug);
+ }
+
+ _acceptFragment(etherListener, conn, message);
+ metisMessage_Release(&message);
+
+ } else {
+ etherListener->stats.framesError++;
+
+ if (metisLogger_IsLoggable(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning)) {
+ metisLogger_Log(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__,
+ "read %zu bytes from fd %d connid %d: Error parsing skeleton",
+ readLength,
+ fd,
+ metisConnection_GetConnectionId(conn));
+ _logStats(etherListener, PARCLogLevel_Warning);
+ }
+ }
+}
+
+static void
+_rejectFrame(_MetisEtherListener *etherListener, PARCEventBuffer *buffer, int fd)
+{
+ etherListener->stats.framesNotForUs++;
+
+ if (metisLogger_IsLoggable(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning)) {
+ metisLogger_Log(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__,
+ "read %zu bytes from fd %d: reject frame",
+ parcEventBuffer_GetLength(buffer),
+ fd);
+ _logStats(etherListener, PARCLogLevel_Warning);
+ }
+}
+
+static void
+_errorFrame(_MetisEtherListener *etherListener, PARCEventBuffer *buffer, int fd)
+{
+ etherListener->stats.framesError++;
+
+ if (metisLogger_IsLoggable(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning)) {
+ metisLogger_Log(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__,
+ "read %zu bytes from fd %d: error parsing Ethernet header",
+ parcEventBuffer_GetLength(buffer),
+ fd);
+ _logStats(etherListener, PARCLogLevel_Warning);
+ }
+}
+
+static void
+_metisEtherListener_ReadCallback(int fd, PARCEventType what, void *user_data)
+{
+ // ether is datagram based, we don't have a connection
+ _MetisEtherListener *etherListener = (_MetisEtherListener *) user_data;
+
+ if (metisLogger_IsLoggable(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) {
+ metisLogger_Log(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "socket %d what %s%s%s%s data %p",
+ fd,
+ (what & PARCEventType_Timeout) ? " timeout" : "",
+ (what & PARCEventType_Read) ? " read" : "",
+ (what & PARCEventType_Write) ? " write" : "",
+ (what & PARCEventType_Signal) ? " signal" : "",
+ user_data);
+ }
+
+ if (what & PARCEventType_Read) {
+ while (true) {
+ PARCEventBuffer *buffer = _metisEtherListener_ReadEtherFrame(etherListener);
+
+ if (buffer == NULL) {
+ break;
+ }
+
+ etherListener->stats.framesIn++;
+
+ _ParseResult result = _metisEtherListener_ParseEtherFrame(etherListener, buffer);
+
+ switch (result) {
+ case ParseResult_Accept:
+ _acceptFrame(etherListener, buffer, fd);
+ break;
+
+ case ParseResult_Reject:
+ _rejectFrame(etherListener, buffer, fd);
+ parcEventBuffer_Destroy(&buffer);
+ break;
+
+ case ParseResult_Error:
+ _errorFrame(etherListener, buffer, fd);
+ parcEventBuffer_Destroy(&buffer);
+ break;
+
+ default:
+ trapUnexpectedState("Do not understand parse result %d", result);
+ }
+ }
+ }
+}
diff --git a/metis/ccnx/forwarder/metis/io/metis_EtherListener.h b/metis/ccnx/forwarder/metis/io/metis_EtherListener.h
new file mode 100644
index 00000000..12aee973
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_EtherListener.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file metis_EtherListener.h
+ * @brief Listen for raw ethernet frames on an interface
+ *
+ * <#Detailed Description#>
+ *
+ */
+
+#ifndef Metis_metis_EtherListener_h
+#define Metis_metis_EtherListener_h
+
+#include <ccnx/forwarder/metis/io/metis_GenericEther.h>
+
+struct metis_listener_ether;
+typedef struct metis_listener_ether MetisListenerEther;
+
+/**
+ * @function metisListenerEther_Create
+ * @abstract Create a L2 listener on a raw ethertype
+ * @discussion
+ * Requires root, will send/receive ethernet frames on the specified device.
+ * The exact mechanism varies by system.
+ *
+ * @param deviceName is the system name of the interface (e.g. "en0")
+ * @return <#return#>
+ */
+MetisListenerOps *metisEtherListener_Create(MetisForwarder *metis, const char *deviceName, uint16_t ethertype);
+
+/**
+ * Return the underlying GenericEther of the listener
+ *
+ * The MetisGenericEther wraps the platform-specific IO operations of the ethernet connection.
+ * Will assert if the listenerOps is not of type METIS_ENCAP_ETHER.
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @retval non-null The underlying generic ethernet
+ * @retval null An error
+ *
+ * Example:
+ * @code
+ * {
+ * MetisListenerSet *listenerSet = metisForwarder_GetListenerSet(metis);
+ * MetisListenerOps *listenerOps = metisListenerSet_Find(listenerSet, METIS_ENCAP_ETHER, linkAddress);
+ * if (listenerOps) {
+ * MetisGenericEther *ether = metisEtherListener_GetGenericEtherFromListener(listenerOps);
+ * }
+ * }
+ * @endcode
+ */
+MetisGenericEther *metisEtherListener_GetGenericEtherFromListener(MetisListenerOps *listenerOps);
+#endif // Metis_metis_EtherListener_h
diff --git a/metis/ccnx/forwarder/metis/io/metis_Ethernet.h b/metis/ccnx/forwarder/metis/io/metis_Ethernet.h
new file mode 100644
index 00000000..9c560e94
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_Ethernet.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file metis_Ethernet.h
+ * @brief Helpers for Ethernet frames
+ *
+ */
+
+#ifndef Metis_metis_Ethernet_h
+#define Metis_metis_Ethernet_h
+
+/**
+ * returns true is the ethertype is at least 0x0600 indicating
+ * a type II frame (IEEE 802.3x-1997)
+ *
+ * @param [in] ethertype The ethertype in host byte order
+ *
+ * @retval true if the ethertype is at least 0x0600
+ * @retval false Otherwise
+ */
+#define metisEthernet_IsValidEthertype(ethertype) ((ethertype) >= 0x0600)
+
+#endif
diff --git a/metis/ccnx/forwarder/metis/io/metis_GenericEther.h b/metis/ccnx/forwarder/metis/io/metis_GenericEther.h
new file mode 100644
index 00000000..248a55ec
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_GenericEther.h
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file generic_ether.h
+ * @brief Generic interface to working with Ethernet frames.
+ *
+ * Wraps platform-specific code. The implementation is found in the metis/platforms directory.
+ *
+ */
+
+#ifndef Metis_metis_GenericEther_h
+#define Metis_metis_GenericEther_h
+
+#include <stdbool.h>
+#include <parc/algol/parc_EventBuffer.h>
+#include <parc/algol/parc_Buffer.h>
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+
+struct metis_generic_ether;
+typedef struct metis_generic_ether MetisGenericEther;
+
+/**
+ * Create a generic ethernet object
+ *
+ * Hides system dependent ethernet. Creates an ethernet object that is ready to send and
+ * receive ethernet frames on a given device and ether type. There may be system limits
+ * to the number of these you can open (i.e. 4 BPF devices on Mac OS).
+ *
+ * If the device name is NULL, it will not bind to a specific interface.
+ *
+ * You generally need elevated permissions to access an Ethernet device. This function
+ * may return NULL due to a permissions error.
+ *
+ * @param [in] deviceName The name of the device, e.g. "eth0" or "en1"
+ * @param [in] etherType The host-byte-order ethertype (i.e. 0x0801)
+ * @param [in] logger The MetisLogger to use
+ *
+ * @retval non-null An allocated ethernet object
+ * @retval null An error
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisGenericEther *metisGenericEther_Create(MetisForwarder *metis, const char *deviceName, uint16_t etherType);
+
+/**
+ * Acquire a reference counted copy
+ *
+ * Returns a reference counted copy of the generic Ethernet object
+ *
+ * @param [in] ether An allocated Ethernet object
+ *
+ * @retval non-null A reference counted copy
+ * @retval null An error
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisGenericEther *metisGenericEther_Acquire(const MetisGenericEther *ether);
+
+/**
+ * <#One Line Description#>
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @retval <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+void metisGenericEther_Release(MetisGenericEther **etherPtr);
+
+/**
+ * Returns the descriptor for i/o
+ *
+ * Returns a system descriptor that can be used to select, poll, etc.
+ *
+ * Do not close the socket. It will be closed when metisGenericEther_Release() is called.
+ *
+ * @param [in] ether An allocated generic ethernet
+ *
+ * @retval non-negative The system descriptor
+ * @retval negative An error (use errno)
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+int metisGenericEther_GetDescriptor(const MetisGenericEther *ether);
+
+
+/**
+ * Reads the next ethernet frame in to the provided buffer
+ *
+ * The frame will have the Ethernet header
+ *
+ * @param [in] ether An allocated generic ethernet
+ * @param [in] buffer The allocated buffer to put the packet in
+ *
+ * @retval true A frame was ready and put in the buffer
+ * @retval false No frame is ready
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+bool metisGenericEther_ReadNextFrame(MetisGenericEther *ether, PARCEventBuffer *buffer);
+
+/**
+ * Sends an Ethernet frame out the device
+ *
+ * The frame must have an Ethernet header filled in with all values.
+ *
+ * @param [in] ether An allocated GenericEther object
+ * @param [in] buffer The buffer to send, including the Ethernet header
+ *
+ * @retval true The frame was sent
+ * @retval false An error
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+bool metisGenericEther_SendFrame(MetisGenericEther *ether, PARCEventBuffer *buffer);
+
+/**
+ * Return the MAC address the object is bound to
+ *
+ * Returns a PARCBuffer with the 6-byte mac address of the interface
+ *
+ * @param [in] ether An allocated GenericEther object
+ *
+ * @retval non-null The MAC address of the interface
+ * @retval null An error
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+PARCBuffer *metisGenericEther_GetMacAddress(const MetisGenericEther *ether);
+
+/**
+ * Returns the ethertype associated with this object
+ *
+ * Returns the ethertype, in host byte order.
+ *
+ * @param [in] ether An allocated GenericEther object
+ *
+ * @retval number Ethertype (host byte order)
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+uint16_t metisGenericEther_GetEtherType(const MetisGenericEther *ether);
+
+/**
+ * Returns the maximum transmission unit (MTU)
+ *
+ * The MTU is the largest user payload allowed in a frame
+ *
+ * @param [<#in#> | <#out#> | <#in,out#>] <#name#> <#description#>
+ *
+ * @return <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * {
+ * <#example#>
+ * }
+ * @endcode
+ */
+unsigned metisGenericEther_GetMTU(const MetisGenericEther *ether);
+#endif // Metis_metis_GenericEther_h
diff --git a/metis/ccnx/forwarder/metis/io/metis_HopByHopFragmenter.c b/metis/ccnx/forwarder/metis/io/metis_HopByHopFragmenter.c
new file mode 100644
index 00000000..ce8f5956
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_HopByHopFragmenter.c
@@ -0,0 +1,660 @@
+/*
+ * 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.
+ */
+
+/**
+ * See https://www.ietf.org/proceedings/interim/2015/03/22/icnrg/slides/slides-interim-2015-icnrg-2-3.pptx
+ *
+ * B flag - indicates the start of a fragment
+ * E flag - indicates the end of a fragment (may be in same frame as B frame)
+ * I flag - an Idle frame (may only occur between E and B frames).
+ * X flag - extended format (not supported)
+ *
+ * in the basic protocol that we implement, there is a 20-bit sequence number
+ *
+ */
+
+#include <config.h>
+#include <stdio.h>
+
+#include <LongBow/runtime.h>
+
+#include <ccnx/forwarder/metis/io/metis_HopByHopFragmenter.h>
+
+#include <parc/algol/parc_EventBuffer.h>
+
+#include <parc/concurrent/parc_RingBuffer_1x1.h>
+
+/*
+ * Complete header for the Basic Encoding in a V1 FixedHeader. The blob[3] array
+ * holds the protocol header fields. See the macros below for accessing specific fields.
+ * The tlvType and tlvLength begin the container to hold the fragment payload.
+ */
+typedef struct hopbyhop_header {
+ uint8_t version;
+ uint8_t packetType;
+ uint16_t packetLength;
+ uint8_t blob[3];
+ uint8_t headerLength;
+ uint16_t tlvType;
+ uint16_t tlvLength;
+} __attribute__((packed)) _HopByHopHeader;
+
+// These two values are also defined in metis_TlvShcemaV1.c
+#define METIS_PACKET_TYPE_HOPFRAG 4
+#define T_HOPFRAG_PAYLOAD 0x0005
+
+/*
+ * Mask a uint32_t down to the 20-bit sequence number
+ */
+#define SEQNUM_MASK ((uint32_t) (0x000FFFFF))
+
+/*
+ * This will right-pad the seqnum out to 32 bits. By filling up a uint32_t it allows
+ * us to use 2's compliment math to compare two sequence numbers rather than the cumbersome
+ * multiple branches required by the method outlined in RFC 1982.
+ * We use a 20-bit sequence number, so need to shift 12 bits to the left.
+ */
+#define SEQNUM_SHIFT 12
+
+/*
+ * The X bit value in the top byte of the header
+ */
+#define XMASK 0x80
+
+/*
+ * The B bit value in the top byte of the header
+ */
+#define BMASK 0x40
+
+/*
+ * The E bit value in the top byte of the header
+ */
+#define EMASK 0x20
+
+/*
+ * The I bit value in the top byte of the header
+ */
+#define IMASK 0x10
+
+/*
+ * Mask out the flags from the top byte of the header
+ */
+#define _hopByHopHeader_GetFlags(header) ((header)->blob[0] & 0xF0)
+
+/*
+ * non-zero if the X flag is set
+ */
+#define _hopByHopHeader_GetXFlag(header) ((header)->blob[0] & XMASK)
+
+/*
+ * non-zero if the B flag is set
+ */
+#define _hopByHopHeader_GetBFlag(header) ((header)->blob[0] & BMASK)
+
+/*
+ * non-zero if the E flag is set
+ */
+#define _hopByHopHeader_GetEFlag(header) ((header)->blob[0] & EMASK)
+
+/*
+ * non-zero if the I flag is set
+ */
+#define _hopByHopHeader_GetIFlag(header) ((header)->blob[0] & IMASK)
+
+/*
+ * Sets the X flag in the header
+ */
+#define _hopByHopHeader_SetXFlag(header) ((header)->blob[0] |= XMASK)
+
+/*
+ * Sets the B flag in the header
+ */
+#define _hopByHopHeader_SetBFlag(header) ((header)->blob[0] |= BMASK)
+
+/*
+ * Sets the E flag in the header
+ */
+#define _hopByHopHeader_SetEFlag(header) ((header)->blob[0] |= EMASK)
+
+/*
+ * Sets the I flag in the header
+ */
+#define _hopByHopHeader_SetIFlag(header) ((header)->blob[0] |= IMASK)
+
+typedef enum {
+ _ParserState_Idle, // not parsing anything
+ _ParserState_Busy, // we have received a B but not an E
+} _ParserState;
+
+struct metis_hopbyhop_fragment {
+ MetisLogger *logger;
+ unsigned mtu;
+
+ // The next expected sequence number (i.e. compare then increment)
+ uint32_t nextReceiveFragSequenceNumber;
+
+ // The next seqnum to use in out-going message (i.e. use then increment)
+ uint32_t nextSendFragSequenceNumber;
+
+ unsigned receiveQueueCapacity;
+ unsigned sendQueueCapacity;
+ PARCRingBuffer1x1 *receiveQueue;
+ PARCRingBuffer1x1 *sendQueue;
+
+ // We are only ever reassembling one packet at a time
+ PARCEventBuffer *currentReceiveBuffer;
+
+ // these two are set from the "B" fragment so a reassembled frame
+ // will have the time and ingress port of the first fragment.
+ MetisTicks currentReceiveBufferStartTicks;
+ unsigned currentReceiveBufferIngressId;
+
+ // Determines if we are currently reassembling a fragment
+ _ParserState parserState;
+};
+
+static uint32_t
+_hopByHopHeader_GetSeqnum(const _HopByHopHeader *header)
+{
+ uint32_t seqnum = ((uint32_t) header->blob[0] & 0x0F) << 16 | (uint32_t) header->blob[1] << 8 | header->blob[2];
+ return seqnum;
+}
+
+static void __attribute__((unused))
+_hopByHopHeader_SetSeqnum(_HopByHopHeader *header, uint32_t seqnum)
+{
+ header->blob[2] = seqnum & 0xFF;
+ header->blob[1] = (seqnum >> 8) & 0xFF;
+
+ header->blob[0] &= 0xF0;
+ header->blob[0] |= (seqnum >> 16) & 0x0F;
+}
+
+static void
+_ringBufferDestroyer(void **ptr)
+{
+ MetisMessage *message = *ptr;
+ metisMessage_Release(&message);
+ *ptr = NULL;
+}
+
+/**
+ * Compares sequence numbers as per RFC 1982
+ *
+ * Handles wrap-around using the 1/2 buffer rule as per RFC 1982. The indefinate state
+ * at exactly the middle is handled by having 2^(N-1)-1 greater than and 2^(N-1) less than.
+ *
+ * @param [in] a The first sequence number
+ * @param [in] b The second sequence number
+ *
+ * @return negative If a < b
+ * @return 0 If a == b
+ * @return positive if a > b
+ *
+ * Example:
+ * @code
+ * {
+ * <#example#>
+ * }
+ * @endcode
+ */
+static int
+_compareSequenceNumbers(uint32_t a, uint32_t b)
+{
+ // shift the numbers so they take up a full 32-bits and then use 2's compliment
+ // arithmatic to determine the ordering
+
+ a <<= SEQNUM_SHIFT;
+ b <<= SEQNUM_SHIFT;
+
+ int32_t c = (int32_t) (a - b);
+ return c;
+}
+
+static uint32_t
+_incrementSequenceNumber(const uint32_t seqnum, const uint32_t mask)
+{
+ uint32_t result = (seqnum + 1) & mask;
+ return result;
+}
+
+static uint32_t
+_nextSendSequenceNumber(MetisHopByHopFragmenter *fragmenter)
+{
+ uint32_t seqnum = fragmenter->nextSendFragSequenceNumber;
+ fragmenter->nextSendFragSequenceNumber = _incrementSequenceNumber(fragmenter->nextSendFragSequenceNumber, SEQNUM_MASK);
+ return seqnum;
+}
+
+// ===================================================
+// RECEIVE PROCESS
+
+static void
+_resetParser(MetisHopByHopFragmenter *fragmenter)
+{
+ // throw away the re-assembly buffer and reset state to Idle
+ parcEventBuffer_Read(fragmenter->currentReceiveBuffer, NULL, UINT32_MAX);
+ fragmenter->parserState = _ParserState_Idle;
+}
+
+/**
+ * Apply the sequence number rules
+ *
+ * a) If the sequence number is in order, no action.
+ * b) If the sequence number is out of order, reset the parser.
+ * c) Update the next expected sequence number to be this packet's seqnum + 1.
+ *
+ * @param [in] fragmenter An allocated MetisHopByHopFragmenter
+ * @param [in] fixedHeader The packet's fixed header
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+static void
+_applySequenceNumberRules(MetisHopByHopFragmenter *fragmenter, const _HopByHopHeader *fixedHeader)
+{
+ uint32_t segnum = _hopByHopHeader_GetSeqnum(fixedHeader);
+
+ int compare = _compareSequenceNumbers(segnum, fragmenter->nextReceiveFragSequenceNumber);
+
+ if (compare == 0) {
+ // In order
+ metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "Fragmenter %p in-order seqnum %u",
+ (void *) fragmenter, segnum);
+ } else if (compare < 0) {
+ // it is an old sequence number
+ metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__,
+ "Fragmenter %p out-of-order seqnum %u expecting %u",
+ (void *) fragmenter, segnum, fragmenter->nextReceiveFragSequenceNumber);
+ _resetParser(fragmenter);
+ } else if (compare > 0) {
+ // lost packets
+ metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__,
+ "Fragmenter %p out-of-order seqnum %u expecting %u",
+ (void *) fragmenter, segnum, fragmenter->nextReceiveFragSequenceNumber);
+ _resetParser(fragmenter);
+ }
+
+ // the next seqnum we expect will be 1 after what we just received. For example, if we lost packets
+ // this will put us back in line with the new series.
+ fragmenter->nextReceiveFragSequenceNumber = _incrementSequenceNumber(segnum, SEQNUM_MASK);
+}
+
+/*
+ * We've reach the END fragment of the reassembly buffer.
+ * 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
+ */
+static void
+_finalizeReassemblyBuffer(MetisHopByHopFragmenter *fragmenter)
+{
+ // This takes ownership of fragmenter->currentReceiveBuffer
+ MetisMessage *reassembled = metisMessage_CreateFromBuffer(fragmenter->currentReceiveBufferIngressId,
+ fragmenter->currentReceiveBufferStartTicks,
+ fragmenter->currentReceiveBuffer,
+ fragmenter->logger);
+
+ if (reassembled) {
+ bool success = parcRingBuffer1x1_Put(fragmenter->receiveQueue, reassembled);
+ if (success) {
+ metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "Fragmenter %p putting reassembed message %p in receive queue",
+ (void *) fragmenter, (void *) reassembled);
+ } else {
+ metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__,
+ "Fragmenter %p failed to put reassembled message in receive queue, dropping",
+ (void *) fragmenter);
+
+ metisMessage_Release(&reassembled);
+ }
+
+ fragmenter->currentReceiveBuffer = parcEventBuffer_Create();
+ } else {
+ metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__,
+ "Fragmenter %p failed to parse reassembled packet to MetisMessage, dropping",
+ (void *) fragmenter);
+ }
+
+ _resetParser(fragmenter);
+}
+
+static void
+_appendFragmentToReassemblyBuffer(MetisHopByHopFragmenter *fragmenter, const MetisMessage *message)
+{
+ size_t length = metisMessage_AppendFragmentPayload(message, fragmenter->currentReceiveBuffer);
+ metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "Fragmenter %p append %zu bytes to reassembly buffer",
+ (void *) fragmenter, length);
+}
+
+/*
+ * Parser is in Idle state. We can only accept a B or BE frame.
+ * 1) If B frame:
+ * 1a) append to current receive buffer
+ * 1b) set parser state to Busy
+ * 1c) set the currentReceiveBufferStartTicks
+ * 1d) set the currentReceiveBufferIngressId
+ * 2) If BE frame, do B actions and finalize it (side effect: will reset state to Idle)
+ * 3) Otherwise ignore it
+ *
+ * Precondition: You know that the parser is in the Idle state
+ */
+static void
+_receiveInIdleState(MetisHopByHopFragmenter *fragmenter, const MetisMessage *message, const _HopByHopHeader *fixedHeader)
+{
+ trapUnexpectedStateIf(fragmenter->parserState != _ParserState_Idle,
+ "Parser in wrong state, expected %d got %d",
+ _ParserState_Idle, fragmenter->parserState);
+
+ if (_hopByHopHeader_GetBFlag(fixedHeader)) {
+ // start a new packet
+ fragmenter->currentReceiveBufferStartTicks = metisMessage_GetReceiveTime(message);
+ fragmenter->currentReceiveBufferIngressId = metisMessage_GetIngressConnectionId(message);
+ fragmenter->parserState = _ParserState_Busy;
+
+ _appendFragmentToReassemblyBuffer(fragmenter, message);
+
+ if (_hopByHopHeader_GetEFlag(fixedHeader)) {
+ // it is also the last fragment
+ _finalizeReassemblyBuffer(fragmenter);
+ }
+ } else if (_hopByHopHeader_GetIFlag(fixedHeader)) {
+ // nothing to do
+ metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "Fragmenter %p idle frame, ignorning",
+ (void *) fragmenter);
+ } else {
+ // nothing we can do with this frame
+ metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__,
+ "Fragmenter %p received bad header flags (0x%02X), ignorning",
+ (void *) fragmenter, _hopByHopHeader_GetFlags(fixedHeader));
+ }
+}
+
+/*
+ * In the Busy state, we can only accept a packet with no flag (middle) or end flag (end of packet).
+ * Anything else is an error and will cause the parser to be reset.
+ *
+ * 1) If no flags
+ * 1a) append to reassembly buffer
+ * 2) If E flag
+ * 2a) append to reassembly buffer
+ * 2b) finalize the buffer (side effect: will reset the parser and place in receive queue)
+ * 3) Otherwise, its an error, reset the parser
+ *
+ * PRECONDITION: you know the packet is in-order relative to the assembly buffer.
+ * This is handled by calling _applySequenceNumberRules() before this function.
+ * PRECONDITION: you know the parser is in the Busy state.
+ */
+static void
+_receiveInBusyState(MetisHopByHopFragmenter *fragmenter, const MetisMessage *message, const _HopByHopHeader *fixedHeader)
+{
+ trapUnexpectedStateIf(fragmenter->parserState != _ParserState_Busy,
+ "Parser in wrong state, expected %d got %d",
+ _ParserState_Busy, fragmenter->parserState);
+
+ if (_hopByHopHeader_GetFlags(fixedHeader) == 0) {
+ // It's a middle packet
+
+ _appendFragmentToReassemblyBuffer(fragmenter, message);
+ } else if (_hopByHopHeader_GetEFlag(fixedHeader)) {
+ // It is the last fragment
+
+ _appendFragmentToReassemblyBuffer(fragmenter, message);
+
+ _finalizeReassemblyBuffer(fragmenter);
+ } else {
+ // nothing we can do with this frame, and it's a state machine error
+
+ metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__,
+ "Fragmenter %p received invalid headers (0x%02X) in Busy state, resetting",
+ (void *) fragmenter, _hopByHopHeader_GetFlags(fixedHeader));
+ _resetParser(fragmenter);
+ }
+}
+
+/**
+ * Receives a fragment and applies the protocol algorithm
+ *
+ * 1) A receiver maintains one reassembly queue per peer.
+ * 2) Discard Idle fragments.
+ * 3) Discard fragments until a 'B' fragment is received. Store the received sequence number for this sender.
+ * 4) If an out-of-order fragment is received next, discard the reassembly buffer and go to step (2).
+ * 5) Continue receiving in-order fragments until the first 'E’ fragment. At this time, the fragmented
+ * packet is fully re-assembled and may be passed on to the next layer.
+ * 6) The receiver cannot assume it will receive the 'E' fragment or a subsequence 'I' frame, so it should
+ * use a timeout mechanism appropriate to the link to release allocated memory resources.
+ *
+ * @param [in] fragmenter An allocated MetisHopByHopFragmenter
+ * @param [in] message The fragment packet
+ *
+ * Example:
+ * @code
+ * {
+ * <#example#>
+ * }
+ * @endcode
+ */
+static void
+_receiveFragment(MetisHopByHopFragmenter *fragmenter, const MetisMessage *message)
+{
+ const _HopByHopHeader *fixedHeader = (const _HopByHopHeader *) metisMessage_FixedHeader(message);
+
+ _applySequenceNumberRules(fragmenter, fixedHeader);
+
+ // ======
+ // Now apply the receiver rules
+
+
+ switch (fragmenter->parserState) {
+ case _ParserState_Idle:
+ _receiveInIdleState(fragmenter, message, fixedHeader);
+ break;
+
+ case _ParserState_Busy:
+ _receiveInBusyState(fragmenter, message, fixedHeader);
+ break;
+
+ default:
+ metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__,
+ "Fragmenter %p Invalid parser state, discarding current buffer and resetting to Idle: %d",
+ (void *) fragmenter, fragmenter->parserState);
+ break;
+ }
+}
+
+// ===================================================
+// SEND PROCESS
+
+/**
+ * Fragments a message and puts all the fragments in the send queue
+ *
+ * Splits up the message in to fragments. The frist fragment will have the B flag and
+ * the last fragment will have the E flag. If the message fits in one fragment, it will
+ * have both the BE flags. Middle messages have no flags.
+ *
+ * @param [in] fragmenter An allocated MetisHopByHopFragmenter
+ * @param [in] message The message to fragment down to MTU size
+ *
+ * @return true Message was fragmented and all fragments put on send queue
+ * @return false Error durring fragmentation (likley full send queue)
+ *
+ * Example:
+ * @code
+ * {
+ * <#example#>
+ * }
+ * @endcode
+ */
+static bool
+_sendFragments(MetisHopByHopFragmenter *fragmenter, const MetisMessage *message)
+{
+ const size_t length = metisMessage_Length(message);
+ size_t offset = 0;
+
+ const size_t maxPayload = fragmenter->mtu - sizeof(_HopByHopHeader);
+
+ _HopByHopHeader header;
+ memset(&header, 0, sizeof(header));
+ _hopByHopHeader_SetBFlag(&header);
+
+ while (offset < length) {
+ size_t payloadLength = maxPayload;
+ const size_t remaining = length - offset;
+
+ if (remaining < maxPayload) {
+ payloadLength = remaining;
+ _hopByHopHeader_SetEFlag(&header);
+ }
+
+ const size_t packetLength = sizeof(_HopByHopHeader) + payloadLength;
+
+ header.version = 1;
+ header.packetType = METIS_PACKET_TYPE_HOPFRAG;
+ header.packetLength = htons(packetLength);
+ header.headerLength = 8;
+ header.tlvType = htons(T_HOPFRAG_PAYLOAD);
+ header.tlvLength = htons(payloadLength);
+
+ uint32_t seqnum = _nextSendSequenceNumber(fragmenter);
+ _hopByHopHeader_SetSeqnum(&header, seqnum);
+
+ MetisMessage *fragment = metisMessage_Slice(message, offset, payloadLength, sizeof(header), (uint8_t *) &header);
+ bool goodput = parcRingBuffer1x1_Put(fragmenter->sendQueue, fragment);
+ if (!goodput) {
+ metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__,
+ "Fragmenter %p message %p send queue full offset %zu length %zu",
+ (void *) fragmenter, (void *) message, offset, payloadLength);
+ metisMessage_Release(&fragment);
+ break;
+ }
+
+ metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "Fragmenter %p message %p send queue fragment %p offset %zu length %zu",
+ (void *) fragmenter, (void *) message, (void *) fragment, offset, payloadLength);
+
+ offset += payloadLength;
+
+ memset(&header, 0, sizeof(header));
+ }
+
+ return (offset == length);
+}
+
+// ===================================================
+
+MetisHopByHopFragmenter *
+metisHopByHopFragmenter_Create(MetisLogger *logger, unsigned mtu)
+{
+ MetisHopByHopFragmenter *fragmenter = parcMemory_Allocate(sizeof(MetisHopByHopFragmenter));
+ if (fragmenter) {
+ fragmenter->nextReceiveFragSequenceNumber = 0;
+ fragmenter->nextSendFragSequenceNumber = 0;
+
+ fragmenter->logger = metisLogger_Acquire(logger);
+ fragmenter->mtu = mtu;
+ fragmenter->receiveQueueCapacity = 128; // this is a many-to-one queue, so not too big
+ fragmenter->sendQueueCapacity = 2048; // this is a one-to-many queue, so bigger (e.g. 64 KB in to 1KB payloads)
+ fragmenter->receiveQueue = parcRingBuffer1x1_Create(fragmenter->receiveQueueCapacity, _ringBufferDestroyer);
+ fragmenter->sendQueue = parcRingBuffer1x1_Create(fragmenter->sendQueueCapacity, _ringBufferDestroyer);
+ fragmenter->currentReceiveBuffer = parcEventBuffer_Create();
+ fragmenter->parserState = _ParserState_Idle;
+ }
+ return fragmenter;
+}
+
+void
+metisHopByHopFragmenter_Release(MetisHopByHopFragmenter **fragmenterPtr)
+{
+ MetisHopByHopFragmenter *fragmenter = *fragmenterPtr;
+ parcEventBuffer_Destroy(&fragmenter->currentReceiveBuffer);
+ parcRingBuffer1x1_Release(&fragmenter->sendQueue);
+ parcRingBuffer1x1_Release(&fragmenter->receiveQueue);
+ metisLogger_Release(&fragmenter->logger);
+ parcMemory_Deallocate((void **) fragmenterPtr);
+}
+
+bool
+metisHopByHopFragmenter_Receive(MetisHopByHopFragmenter *fragmenter, const MetisMessage *message)
+{
+ if (metisMessage_GetType(message) == MetisMessagePacketType_HopByHopFrag) {
+ _receiveFragment(fragmenter, message);
+ } else {
+ // put the whole thing on the output queue
+ MetisMessage *copy = metisMessage_Acquire(message);
+ bool putSuccess = parcRingBuffer1x1_Put(fragmenter->receiveQueue, copy);
+ if (!putSuccess) {
+ metisMessage_Release(&copy);
+ metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__,
+ "Failed to add message %p to receive queue", (void *) message);
+ } else {
+ if (metisLogger_IsLoggable(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) {
+ metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "Add message %p to receive queue", (void *) message);
+ }
+ }
+ }
+
+ // the maximum remaining is its capacity - 1
+ return (parcRingBuffer1x1_Remaining(fragmenter->receiveQueue) < fragmenter->receiveQueueCapacity - 1);
+}
+
+MetisMessage *
+metisHopByHopFragmenter_PopReceiveQueue(MetisHopByHopFragmenter *fragmenter)
+{
+ MetisMessage *message = NULL;
+ parcRingBuffer1x1_Get(fragmenter->receiveQueue, (void **) &message);
+ return message;
+}
+
+bool
+metisHopByHopFragmenter_Send(MetisHopByHopFragmenter *fragmenter, MetisMessage *message)
+{
+ bool success = false;
+
+ // If the packet will fit in the MTU without fragmentation, do not use fragmentation
+ if (metisMessage_Length(message) > fragmenter->mtu) {
+ success = _sendFragments(fragmenter, message);
+ } else {
+ MetisMessage *copy = metisMessage_Acquire(message);
+ success = parcRingBuffer1x1_Put(fragmenter->sendQueue, copy);
+ if (!success) {
+ metisMessage_Release(&copy);
+ metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__,
+ "Failed to add message %p to send queue", (void *) message);
+ } else {
+ if (metisLogger_IsLoggable(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) {
+ metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "Add message %p to send queue", (void *) message);
+ }
+ }
+ }
+
+ return success;
+}
+
+MetisMessage *
+metisHopByHopFragmenter_PopSendQueue(MetisHopByHopFragmenter *fragmenter)
+{
+ MetisMessage *message = NULL;
+ parcRingBuffer1x1_Get(fragmenter->sendQueue, (void **) &message);
+ return message;
+}
+
diff --git a/metis/ccnx/forwarder/metis/io/metis_HopByHopFragmenter.h b/metis/ccnx/forwarder/metis/io/metis_HopByHopFragmenter.h
new file mode 100644
index 00000000..6fd83f19
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_HopByHopFragmenter.h
@@ -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.
+ */
+
+/**
+ * @file metis_HopByHopFragmenterer.h
+ * @brief Implements a fragmenter for a hop-by-hop protocol
+ *
+ * The hop-by-hop fragmenter can be though of as a bi-directional queue.
+ *
+ * The receive process takes fragmented packets from the wire and reassembles them. Once a packet is
+ * reassembed, it is put on end of the receive queue. The user can then pop a message of the top of
+ * the receive queue and process it normally.
+ *
+ * The send process takes a potentially large input packet and fragments it in to pieces that are places
+ * in order on the send queue. The caller can then pop messages of the head of the send queue and
+ * put them on the wire.
+ *
+ * In the next update, we probalby want to make the send queue and receive queue external so I/O and
+ * message processing threads can access them directly.
+ *
+ */
+
+#ifndef __Metis__metis_HopByHopFragmenter__
+#define __Metis__metis_HopByHopFragmenter__
+
+#include <stdbool.h>
+#include <ccnx/forwarder/metis/core/metis_Logger.h>
+#include <ccnx/forwarder/metis/core/metis_Message.h>
+
+struct metis_hopbyhop_fragment;
+typedef struct metis_hopbyhop_fragment MetisHopByHopFragmenter;
+
+/**
+ * Creates a fragmentation class for a specific session
+ *
+ * The fragmenter is specific to a given flow (i.e. source-destination-ethertype tuple).
+ * It is the responsibility of the caller to create the appropriate number of fragmenters
+ * and classify packets in to the right fragmenter.
+ *
+ * @param [in] logger The logger to use for output
+ * @param [in] mtu The MTU to use for send operations
+ *
+ * @return non-null An allocted MetisHopByHopFragmenter
+ * @return null An error
+ *
+ * Example:
+ * @code
+ * {
+ * MetisHopByHopFragmenter *fragmenter = metisHopByHopFragmenter_Create(logger, mtu);
+ * metisHopByHopFragmenter_Release(&fragmenter);
+ * }
+ * @endcode
+ */
+MetisHopByHopFragmenter *metisHopByHopFragmenter_Create(MetisLogger *logger, unsigned mtu);
+
+/**
+ * Release a reference to the fragmenter
+ *
+ * Will destroy any packets in the receive and send queues.
+ *
+ * @param [in,out] fragmenterPtr A pointer to an allocated MetisHopByHopFragmenter
+ *
+ * Example:
+ * @code
+ * {
+ * MetisHopByHopFragmenter *fragmenter = metisHopByHopFragmenter_Create(logger, mtu);
+ * metisHopByHopFragmenter_Release(&fragmenter);
+ * }
+ * @endcode
+ */
+void metisHopByHopFragmenter_Release(MetisHopByHopFragmenter **fragmenterPtr);
+
+/**
+ * Receives a message as part of the fragmentation session
+ *
+ * Receives a fragment. If this causes a reassembly to complete, the completed packet
+ * will be placed in the receive queue and may be accessed by metisHopByHopFragmenter_PopReceiveQueue().
+ * The caller is reponsible for releasing message.
+ *
+ * If a non-fragment packet is received, it is placed directly on the receive queue.
+ *
+ * The caller is responsible for releasing the message.
+ *
+ * @param [in] fragmenter An allocated MetisHopByHopFragmenter
+ * @param [in] message An allocated MetisMessage
+ *
+ * @return true The receive buffer has an assembled packet ready for read
+ * @return false the receive buffer does not have a complete packet ready.
+ *
+ * Example:
+ * @code
+ * {
+ * void
+ * acceptFragment(MetisHopByHopFragmenter *fragmenter, MetisMessage *message)
+ * {
+ * bool receiveQueueNotEmpty = metisHopByHopFragmenter_Receive(fragmenter, message);
+ * if (receiveQueueNotEmpty) {
+ * MetisMessage *assembled = NULL;
+ * while ((assembled = metisHopByHopFragmenter_PopReceiveQueue(fragmenter)) != NULL) {
+ * etherListener->stats.framesReassembled++;
+ * metisForwarder_Receive(etherListener->metis, assembled);
+ * }
+ * }
+ * }
+ * }
+ * @endcode
+ */
+bool metisHopByHopFragmenter_Receive(MetisHopByHopFragmenter *fragmenter, const MetisMessage *message);
+
+/**
+ * Pops the top assembed message from the receive queue
+ *
+ * Reads the top reassembled packet from the receive queue. The caller must
+ * release the returned message.
+ *
+ * @param [in] fragmenter An allocated MetisHopByHopFragmenter
+ *
+ * @return NULL The receive queue is empty (i.e. the current reassembly is not complete)
+ * @return non-null A re-assembed message
+ *
+ * Example:
+ * @code
+ * {
+ * void
+ * acceptFragment(MetisHopByHopFragmenter *fragmenter, MetisMessage *message)
+ * {
+ * bool receiveQueueNotEmpty = metisHopByHopFragmenter_Receive(fragmenter, message);
+ * if (receiveQueueNotEmpty) {
+ * MetisMessage *assembled = NULL;
+ * while ((assembled = metisHopByHopFragmenter_PopReceiveQueue(fragmenter)) != NULL) {
+ * etherListener->stats.framesReassembled++;
+ * metisForwarder_Receive(etherListener->metis, assembled);
+ * }
+ * }
+ * }
+ * }
+ * @endcode
+ */
+MetisMessage *metisHopByHopFragmenter_PopReceiveQueue(MetisHopByHopFragmenter *fragmenter);
+
+/**
+ * Adds a message to the send buffer
+ *
+ * This may make multiple references to the original message where each fragment is
+ * pointing as an extent in to the original message.
+ *
+ * @param [in] fragmenter An allocated MetisHopByHopFragmenter
+ * @param [in] message An allocated MetisMessage
+ *
+ * @return true The message was fragmented and put on the send queue
+ * @return false An error
+ *
+ * Example:
+ * @code
+ * {
+ * void sendFragments(MetisHopByHopFragmenter *fragmenter, const MetisMessage *message) {
+ * bool success = metisHopByHopFragmenter_Send(fragmenter, message);
+ *
+ * MetisMessage *fragment;
+ * while (success && (fragment = metisHopByHopFragmenter_PopSendQueue(fragmenter)) != NULL) {
+ * success = _sendFrame(fragment);
+ * metisMessage_Release(&fragment);
+ * }
+ *
+ * // if we failed, drain the other fragments
+ * if (!success) {
+ * while ((fragment = metisHopByHopFragmenter_PopSendQueue(fragmenter)) != NULL) {
+ * metisMessage_Release(&fragment);
+ * }
+ * }
+ * // caller must release message
+ * }
+ * @endcode
+ */
+bool metisHopByHopFragmenter_Send(MetisHopByHopFragmenter *fragmenter, MetisMessage *message);
+
+/**
+ * Pops the next message to send to the wire from the send queue
+ *
+ * Returns the front of the Send FIFO queue of fragments that should be
+ * sent on the wire.
+ *
+ * @param [in] fragmenter An allocated MetisHopByHopFragmenter
+ *
+ * @return null there is no message awaiting transmit
+ * @return non-null A message to send
+ *
+ * Example:
+ * @code
+ * {
+ * void sendIdleFragment(MetisHopByHopFragmenter *fragmenter) {
+ * bool success = metisHopByHopFragmenter_SendIdle(fragmenter);
+ *
+ * MetisMessage *fragment;
+ * while (success && (fragment = metisHopByHopFragmenter_PopSendQueue(fragmenter)) != NULL) {
+ * success = _sendFrame(fragment);
+ * metisMessage_Release(&fragment);
+ * }
+ * }
+ * }
+ * @endcode
+ */
+MetisMessage *metisHopByHopFragmenter_PopSendQueue(MetisHopByHopFragmenter *fragmenter);
+#endif /* defined(__Metis__metis_HopByHopFragmenter__) */
diff --git a/metis/ccnx/forwarder/metis/io/metis_IPMulticastListener.c b/metis/ccnx/forwarder/metis/io/metis_IPMulticastListener.c
new file mode 100644
index 00000000..0dc5606d
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_IPMulticastListener.c
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <arpa/inet.h>
+
+#include <LongBow/runtime.h>
+
+#include <parc/algol/parc_Memory.h>
+
+#include <ccnx/forwarder/metis/io/metis_IPMulticastListener.h>
+
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+#include <ccnx/forwarder/metis/core/metis_Connection.h>
+#include <ccnx/forwarder/metis/tlv/metis_Tlv.h>
+#include <ccnx/forwarder/metis/core/metis_Message.h>
+
+MetisListenerOps *metisIPMulticastListener_CreateInet6(MetisForwarder *metis, struct sockaddr_in6 sin6);
+MetisListenerOps *metisIPMulticastListener_CreateInet(MetisForwarder *metis, struct sockaddr_in sin);
diff --git a/metis/ccnx/forwarder/metis/io/metis_IPMulticastListener.h b/metis/ccnx/forwarder/metis/io/metis_IPMulticastListener.h
new file mode 100644
index 00000000..ca1134e2
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_IPMulticastListener.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef Metis_metis_IPMulticastListener_h
+#define Metis_metis_IPMulticastListener_h
+
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+#include <ccnx/forwarder/metis/io/metis_Listener.h>
+
+MetisListenerOps *metisIPMulticastListener_CreateInet6(MetisForwarder *metis, struct sockaddr_in6 sin6);
+MetisListenerOps *metisIPMulticastListener_CreateInet(MetisForwarder *metis, struct sockaddr_in sin);
+#endif // Metis_metis_IPMulticastListener_h
diff --git a/metis/ccnx/forwarder/metis/io/metis_IoOperations.c b/metis/ccnx/forwarder/metis/io/metis_IoOperations.c
new file mode 100644
index 00000000..03feb737
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_IoOperations.c
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include <config.h>
+#include <stdio.h>
+
+#include <ccnx/forwarder/metis/io/metis_IoOperations.h>
+
+#include <LongBow/runtime.h>
+
+void *
+metisIoOperations_GetClosure(const MetisIoOperations *ops)
+{
+ assertNotNull(ops, "Parameter ops must be non-null");
+ return ops->closure;
+}
+
+bool
+metisIoOperations_Send(MetisIoOperations *ops, const CPIAddress *nexthop, MetisMessage *message)
+{
+ return ops->send(ops, nexthop, message);
+}
+
+const CPIAddress *
+metisIoOperations_GetRemoteAddress(const MetisIoOperations *ops)
+{
+ return ops->getRemoteAddress(ops);
+}
+
+const MetisAddressPair *
+metisIoOperations_GetAddressPair(const MetisIoOperations *ops)
+{
+ return ops->getAddressPair(ops);
+}
+
+bool
+metisIoOperations_IsUp(const MetisIoOperations *ops)
+{
+ return ops->isUp(ops);
+}
+
+bool
+metisIoOperations_IsLocal(const MetisIoOperations *ops)
+{
+ return ops->isLocal(ops);
+}
+
+unsigned
+metisIoOperations_GetConnectionId(const MetisIoOperations *ops)
+{
+ return ops->getConnectionId(ops);
+}
+
+void
+metisIoOperations_Release(MetisIoOperations **opsPtr)
+{
+ MetisIoOperations *ops = *opsPtr;
+ ops->destroy(opsPtr);
+}
+
+const void *
+metisIoOperations_Class(const MetisIoOperations *ops)
+{
+ return ops->class(ops);
+}
+
+CPIConnectionType
+metisIoOperations_GetConnectionType(const MetisIoOperations *ops)
+{
+ return ops->getConnectionType(ops);
+}
+
+MetisTicks
+metisIoOperations_SendProbe(MetisIoOperations *ops, unsigned probeType)
+{
+ return ops->sendProbe(ops, probeType);
+}
+
diff --git a/metis/ccnx/forwarder/metis/io/metis_IoOperations.h b/metis/ccnx/forwarder/metis/io/metis_IoOperations.h
new file mode 100644
index 00000000..86129865
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_IoOperations.h
@@ -0,0 +1,369 @@
+/*
+ * 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.
+ */
+
+/**
+ * Defines the interface all connections use to communicate with the forwarder.
+ *
+ * @code
+ *
+ * static MetisIoOperations _template = {
+ * .closure = NULL,
+ * .send = &_metisEtherConnection_Send,
+ * .getRemoteAddress = &_metisEtherConnection_GetRemoteAddress,
+ * .getAddressPair = &_metisEtherConnection_GetAddressPair,
+ * .getConnectionId = &_metisEtherConnection_GetConnectionId,
+ * .isUp = &_metisEtherConnection_IsUp,
+ * .isLocal = &_metisEtherConnection_IsLocal,
+ * .destroy = &_metisEtherConnection_DestroyOperations,
+ * .class = &_metisEtherConnection_Class,
+ * .getConnectionType = &_metisEtherConnection_getConnectionType
+ * };
+ *
+ * MetisIoOperations *
+ * metisEtherConnection_Create(MetisForwarder *metis, MetisGenericEther *ether, MetisAddressPair *pair)
+ * {
+ * _MetisEtherState *etherConnState = parcMemory_Allocate(sizeof(_MetisEtherState));
+ * // Fill in etherConnState with instance variables
+ *
+ * MetisIoOperations *io_ops = parcMemory_Allocate(sizeof(MetisIoOperations));
+ * memcpy(io_ops, &_template, sizeof(MetisIoOperations));
+ * io_ops->closure = etherConnState;
+ * // Add to connection table, send missives about connection state
+ *
+ * return op_ops;
+ * }
+ * @endcode
+ *
+ */
+
+/**
+ * I/O is built around a callback structure. The connection table contains an operations
+ * structure built around function pointers. These allow the connection table to be
+ * agnostic about underlying connections.
+ */
+
+#ifndef Metis_metis_io_h
+#define Metis_metis_io_h
+
+#include <ccnx/api/control/cpi_Connection.h>
+#include <ccnx/api/control/cpi_Address.h>
+#include <ccnx/forwarder/metis/io/metis_AddressPair.h>
+#include <ccnx/forwarder/metis/core/metis_Message.h>
+#include <ccnx/forwarder/metis/core/metis_Ticks.h>
+
+//packet types for probing
+#define METIS_PACKET_TYPE_PROBE_REQUEST 5
+#define METIS_PACKET_TYPE_PROBE_REPLY 6
+
+struct metis_io_ops;
+typedef struct metis_io_ops MetisIoOperations;
+
+/**
+ * @typedef MetisIoOperations
+ * @abstract The IO Operations structure abstracts an connection's properties and send() method
+ * @constant context Implementation specific opaque data, passed back on each call
+ * @constant send function pointer to send a message, does not destroy the message
+ * @constant getRemoteAddress function pointer to return the "to" address associated with the connection.
+ * Some connections might not have a specific peer, such as multicast, where its the group address.
+ * @constant isUp test if the connection is up, ready to send a message.
+ * @constant isLocal test if the connection is local to the host.
+ * @constant getConnectionId returns the Metis id for the connection.
+ * @constant destroy releases a refernce count on the connection and possibly destroys the connection.
+ * @constant class A unique identifier for each class that instantiates MetisIoOperations.
+ * @constant getConnectionType Returns the type of connection (TCP, UDP, L2, etc.) of the underlying connection.
+ * @discussion <#Discussion#>
+ */
+struct metis_io_ops {
+ void *closure;
+ bool (*send)(MetisIoOperations *ops, const CPIAddress *nexthop, MetisMessage *message);
+ const CPIAddress * (*getRemoteAddress)(const MetisIoOperations *ops);
+ const MetisAddressPair * (*getAddressPair)(const MetisIoOperations *ops);
+ bool (*isUp)(const MetisIoOperations *ops);
+ bool (*isLocal)(const MetisIoOperations *ops);
+ unsigned (*getConnectionId)(const MetisIoOperations *ops);
+ void (*destroy)(MetisIoOperations **opsPtr);
+ const void * (*class)(const MetisIoOperations *ops);
+ CPIConnectionType (*getConnectionType)(const MetisIoOperations *ops);
+ MetisTicks (*sendProbe)(MetisIoOperations *ops, unsigned probeType);
+};
+
+/**
+ * Returns the closure of the interface
+ *
+ * The creator of the closure sets this parameter to store its state.
+ *
+ * @param [in] ops A concrete instance of the interface
+ *
+ * @return The value set by the concrete instance of the interface.
+ *
+ * Example:
+ * @clode
+ * {
+
+ * }
+ * @endcode
+ */
+void *metisIoOperations_GetClosure(const MetisIoOperations *ops);
+
+/**
+ * Release all memory related to the interface and implementation
+ *
+ * This function must release all referenced memory in the concrete implementation and
+ * memory related to the MetisIoOperations. It should NULL the input parameter.
+ *
+ * @param [in,out] opsPtr Pointer to interface. Will be NULLed.
+ *
+ * Example:
+ * @code
+ *
+ * static void
+ * _metisEtherConnection_InternalRelease(_MetisEtherState *etherConnState)
+ * {
+ * // release internal state of _MetisEtherState
+ * }
+ *
+ * static void
+ * _metisEtherConnection_Release(MetisIoOperations **opsPtr)
+ * {
+ * MetisIoOperations *ops = *opsPtr;
+ *
+ * _MetisEtherState *etherConnState = (_MetisEtherState *) metisIoOperations_GetClosure(ops);
+ * _metisEtherConnection_InternalRelease(etherConnState);
+ *
+ * parcMemory_Deallocate((void **) &ops);
+ * }
+ *
+ * MetisIoOperations *
+ * metisEtherConnection_Create(MetisForwarder *metis, MetisGenericEther *ether, MetisAddressPair *pair)
+ * {
+ * size_t allocationSize = sizeof(_MetisEtherState) + sizeof(MetisIoOperations);
+ * MetisIoOperations *ops = parcMemory_AllocateAndClear(allocationSize);
+ * if (ops) {
+ * // fill in other interface functions
+ * ops->destroy = &_metisEtherConnection_Release;
+ * ops->closure = (uint8_t *) ops + sizeof(MetisIoOperations);
+ *
+ * _MetisEtherState *etherConnState = metisIoOperations_GetClosure(ops);
+ * // fill in Ethernet state
+ * }
+ * return ops;
+ * }
+ * @endcode
+ */
+void metisIoOperations_Release(MetisIoOperations **opsPtr);
+
+/**
+ * Sends the specified MetisMessage out this connection
+ *
+ * The the implementation of send may queue the message, it must acquire a reference to it.
+ *
+ * @param [in] ops The connection implementation.
+ * @param [in] nexthop On multiple access networks, this parameter might be used, usually NULL.
+ * @param [in] message The message to send. If the message will be queued, it will be acquired.
+ *
+ * @return true The message was sent or queued
+ * @retrun false An error occured and the message will not be sent or queued
+ *
+ * Example:
+ * @code
+ * {
+ * if (metisIoOperations_IsUp(conn->ops)) {
+ * return metisIoOperations_Send(conn->ops, NULL, message);
+ * }
+ * }
+ * @endcode
+ */
+bool metisIoOperations_Send(MetisIoOperations *ops, const CPIAddress *nexthop, MetisMessage *message);
+
+/**
+ * A connection is made up of a local and a remote address. This function returns the remote address.
+ *
+ * Represents the destination endpoint of the communication.
+ *
+ * @param [in] ops The connection implementation.
+ *
+ * @return non-null The remote address
+ * @return null The connection does not have a remote address
+ *
+ * Example:
+ * @code
+ * {
+ * CPIAddress *local = cpiAddress_CreateFromLink((uint8_t []) { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }, 6);
+ * CPIAddress *remote = cpiAddress_CreateFromLink((uint8_t []) { 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }, 6);
+ * MetisAddressPair *pair = metisAddressPair_Create(local, remote);
+ * MetisIoOperations *ops = metisEtherConnection_Create(metis, ether, pair);
+ *
+ * const CPIAddress *test = metisIoOperations_GetRemoteAddress(ops);
+ * assertTrue(cpiAddress_Equals(test, remote), "Wrong remote address");
+ * metisIoOperations_Release(&ops);
+ * metisAddressPair_Release(&pair);
+ * cpiAddress_Destroy(&local);
+ * cpiAddress_Destroy(&remote);
+ * }
+ * @endcode
+ */
+const CPIAddress *metisIoOperations_GetRemoteAddress(const MetisIoOperations *ops);
+
+/**
+ * A connection is made up of a local and a remote address. This function returns the address pair.
+ *
+ * Represents the destination endpoint of the communication.
+ *
+ * @param [in] ops The connection implementation.
+ *
+ * @return non-null The address pair
+ * @return null An error.
+ *
+ * Example:
+ * @code
+ * {
+ * CPIAddress *local = cpiAddress_CreateFromLink((uint8_t []) { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }, 6);
+ * CPIAddress *remote = cpiAddress_CreateFromLink((uint8_t []) { 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }, 6);
+ * MetisAddressPair *pair = metisAddressPair_Create(local, remote);
+ * MetisIoOperations *ops = metisEtherConnection_Create(metis, ether, pair);
+ *
+ * const MetisAddressPair *test = metisIoOperations_GetAddressPair(ops);
+ * assertTrue(metisAddressPair(test, pair), "Wrong address pair");
+ * metisIoOperations_Release(&ops);
+ * metisAddressPair_Release(&pair);
+ * cpiAddress_Destroy(&local);
+ * cpiAddress_Destroy(&remote);
+ * }
+ * @endcode
+ */
+const MetisAddressPair *metisIoOperations_GetAddressPair(const MetisIoOperations *ops);
+
+/**
+ * Returns true if the underlying connection is in operation
+ *
+ * An UP connection is able to send and receive packets. If a subsystem needs to take actions
+ * when a connection goes UP or DOWN, it should subscribe as a MetisMissive listener.
+ *
+ * @param [in] ops The connection implementation.
+ *
+ * @return true The connection is UP
+ * @return false The connection is not UP
+ *
+ * Example:
+ * @code
+ * {
+ * if (metisIoOperations_IsUp(conn->ops)) {
+ * return metisIoOperations_Send(conn->ops, NULL, message);
+ * }
+ * }
+ * @endcode
+ */
+bool metisIoOperations_IsUp(const MetisIoOperations *ops);
+
+/**
+ * If the remote address is local to this system, returns true
+ *
+ * Will return true if an INET or INET6 connection is on localhost. Will return
+ * true for AF_UNIX. An Ethernet connection is not local.
+ *
+ * @param [in] ops The connection implementation.
+ *
+ * @return true The remote address is local to the system
+ * @return false The remote address is not local
+ *
+ * Example:
+ * @code
+ * {
+ * // Is the ingress connection remote? If so check for non-zero and decrement
+ * if (!metisIoOperations(ingressConnectionOps) {
+ * uint8_t hoplimit = metisMessage_GetHopLimit(interestMessage);
+ * if (hoplimit == 0) {
+ * // error
+ * } else {
+ * hoplimit--;
+ * }
+ * // take actions on hoplimit
+ * }
+ * }
+ * @endcode
+ */
+bool metisIoOperations_IsLocal(const MetisIoOperations *ops);
+
+/**
+ * Returns the connection ID represented by this MetisIoOperations in the ConnectionTable.
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [in] ops The connection implementation.
+ *
+ * @return number The connection ID in the connection table.
+ *
+ * Example:
+ * @code
+ * {
+ * unsigned id = metisIoOperations_GetConnectionId(ingressIoOps);
+ * const MetisConnection *conn = metisConnectionTable_FindById(metis->connectionTable, id);
+ * }
+ * @endcode
+ */
+unsigned metisIoOperations_GetConnectionId(const MetisIoOperations *ops);
+
+/**
+ * A pointer that represents the class of the connection
+ *
+ * Each concrete implementation has a class pointer that is unique to the implementation (not instance).
+ * Each implementation is free to choose how to determine the value, so long as it is unique on the system.
+ * This is a system-local value.
+ *
+ * @param [in] ops The connection implementation.
+ *
+ * @return non-null A pointer value unique to the implementation (not instance).
+ *
+ * Example:
+ * @code
+ * bool
+ * metisEtherConnection_IsInstanceOf(const MetisConnection *conn)
+ * {
+ * bool result = false;
+ * if (conn != NULL) {
+ * MetisIoOperations *ops = metisConnection_GetIoOperations(conn);
+ * const void *class = metisIoOperations_Class(ops);
+ * result = (class == _metisEtherConnection_Class(ops));
+ * }
+ * return result;
+ * }
+ * @endcode
+ */
+const void *metisIoOperations_Class(const MetisIoOperations *ops);
+
+/**
+ * Returns the transport type of the connection (TCP, UDP, L2, etc.).
+ *
+ * TCP and AF_UNIX are both stream connections and will both return "cpiConnection_TCP".
+ * Ethernet will return "cpiConnection_L2".
+ *
+ * @param [in] ops The connection implementation.
+ *
+ * @return cpiConnection_TCP A TCP4, TCP6, or AF_UNIX connection
+ * @return cpiConnection_UDP A UDP4 or UDP6 connection
+ * @return cpiConnection_L2 An Ethernet connection
+ *
+ * Example:
+ * @code
+ * {
+ * CPIConnectionType type = metisIoOperations_GetConnectionType(metisConnection_GetIoOperations(connection));
+ * CPIConnection *cpiConn = cpiConnection_Create(metisConnection_GetConnectionId(connection), localAddress, remoteAddress, type);
+ * }
+ * @endcode
+ */
+CPIConnectionType metisIoOperations_GetConnectionType(const MetisIoOperations *ops);
+
+MetisTicks metisIoOperations_SendProbe(MetisIoOperations *ops, unsigned probeType);
+#endif // Metis_metis_io_h
diff --git a/metis/ccnx/forwarder/metis/io/metis_Listener.h b/metis/ccnx/forwarder/metis/io/metis_Listener.h
new file mode 100644
index 00000000..056f76e6
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_Listener.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file metis_Listener.h
+ * @brief Provides the function abstraction of all Listeners.
+ *
+ * A listener accepts in coming packets. A Stream listener will accept the connection
+ * then pass it off to the {@link MetisStreamConnection} class. A datagram listener
+ * will have to have its own way to multiplex packets.
+ *
+ */
+
+#ifndef Metis_metis_Listener_h
+#define Metis_metis_Listener_h
+
+#include <ccnx/api/control/cpi_Address.h>
+
+struct metis_listener_ops;
+typedef struct metis_listener_ops MetisListenerOps;
+
+typedef enum {
+ METIS_ENCAP_TCP, /**< TCP encapsulation type */
+ METIS_ENCAP_UDP, /**< UDP encapsulation type */
+ METIS_ENCAP_ETHER, /**< Ethernet encapsulation type */
+ METIS_ENCAP_LOCAL /**< A connection to a local protocol stack */
+} MetisEncapType;
+
+struct metis_listener_ops {
+ /**
+ * A user-defined parameter
+ */
+ void *context;
+
+ /**
+ * Called to destroy the Listener.
+ *
+ * @param [in] listenerOpsPtr Double pointer to this structure
+ */
+ void (*destroy)(MetisListenerOps **listenerOpsPtr);
+
+ /**
+ * Returns the interface index of the listener.
+ *
+ * @param [in] ops Pointer to this structure
+ *
+ * @return the interface index of the listener
+ */
+ unsigned (*getInterfaceIndex)(const MetisListenerOps *ops);
+
+ /**
+ * Returns the address pair that defines the listener (local, remote)
+ *
+ * @param [in] ops Pointer to this structure
+ *
+ * @return the (local, remote) pair of addresses
+ */
+ const CPIAddress * (*getListenAddress)(const MetisListenerOps *ops);
+
+ /**
+ * Returns the encapsulation type of the listener (e.g. TCP, UDP, Ethernet)
+ *
+ * @param [in] ops Pointer to this structure
+ *
+ * @return the listener encapsulation type
+ */
+ MetisEncapType (*getEncapType)(const MetisListenerOps *ops);
+
+ /**
+ * Returns the underlying socket associated with the listener
+ *
+ * Not all listeners are capable of returning a useful socket. In those
+ * cases, this function pointer is NULL.
+ *
+ * TCP does not support this operation (function is NULL). UDP returns its local socket.
+ *
+ * The caller should never close this socket, the listener will do that when its
+ * destroy method is called.
+ *
+ * @param [in] ops Pointer to this structure
+ *
+ * @retval integer The socket descriptor
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+ int (*getSocket)(const MetisListenerOps *ops);
+};
+#endif // Metis_metis_Listener_h
diff --git a/metis/ccnx/forwarder/metis/io/metis_ListenerSet.c b/metis/ccnx/forwarder/metis/io/metis_ListenerSet.c
new file mode 100644
index 00000000..0a0d32fd
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_ListenerSet.c
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include <config.h>
+#include <stdio.h>
+
+#include <parc/algol/parc_Memory.h>
+#include <parc/algol/parc_ArrayList.h>
+
+#include <ccnx/forwarder/metis/io/metis_ListenerSet.h>
+
+#include <LongBow/runtime.h>
+
+struct metis_listener_set {
+ PARCArrayList *listOfListeners;
+};
+
+static void
+metisListenerSet_DestroyListenerOps(void **opsPtr)
+{
+ MetisListenerOps *ops = *((MetisListenerOps **) opsPtr);
+ ops->destroy(&ops);
+}
+
+MetisListenerSet *
+metisListenerSet_Create()
+{
+ MetisListenerSet *set = parcMemory_AllocateAndClear(sizeof(MetisListenerSet));
+ assertNotNull(set, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisListenerSet));
+ set->listOfListeners = parcArrayList_Create(metisListenerSet_DestroyListenerOps);
+
+ return set;
+}
+
+void
+metisListenerSet_Destroy(MetisListenerSet **setPtr)
+{
+ assertNotNull(setPtr, "Parameter must be non-null double pointer");
+ assertNotNull(*setPtr, "Parameter must dereference to non-null pointer");
+
+ MetisListenerSet *set = *setPtr;
+ parcArrayList_Destroy(&set->listOfListeners);
+ parcMemory_Deallocate((void **) &set);
+ *setPtr = NULL;
+}
+
+/**
+ * @function metisListenerSet_Add
+ * @abstract Adds the listener to the set
+ * @discussion
+ * Unique set based on pair (MetisEncapType, localAddress)
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+bool
+metisListenerSet_Add(MetisListenerSet *set, MetisListenerOps *ops)
+{
+ assertNotNull(set, "Parameter set must be non-null");
+ assertNotNull(ops, "Parameter ops must be non-null");
+
+ int opsEncap = ops->getEncapType(ops);
+ const CPIAddress *opsAddress = ops->getListenAddress(ops);
+
+ // make sure its not in the set
+ size_t length = parcArrayList_Size(set->listOfListeners);
+ for (size_t i = 0; i < length; i++) {
+ MetisListenerOps *entry = parcArrayList_Get(set->listOfListeners, i);
+
+ int entryEncap = entry->getEncapType(entry);
+ const CPIAddress *entryAddress = entry->getListenAddress(entry);
+
+ if (opsEncap == entryEncap && cpiAddress_Equals(opsAddress, entryAddress)) {
+ // duplicate
+ return false;
+ }
+ }
+
+ parcArrayList_Add(set->listOfListeners, ops);
+ return true;
+}
+
+size_t
+metisListenerSet_Length(const MetisListenerSet *set)
+{
+ assertNotNull(set, "Parameter set must be non-null");
+ return parcArrayList_Size(set->listOfListeners);
+}
+
+/**
+ * Returns the listener at the given index
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [in] set An allocated listener set
+ * @param [in] index The index position (0 <= index < metisListenerSet_Count)
+ *
+ * @retval non-null The listener at index
+ * @retval null An error
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisListenerOps *
+metisListenerSet_Get(const MetisListenerSet *set, size_t index)
+{
+ assertNotNull(set, "Parameter set must be non-null");
+ return parcArrayList_Get(set->listOfListeners, index);
+}
+
+MetisListenerOps *
+metisListenerSet_Find(const MetisListenerSet *set, MetisEncapType encapType, const CPIAddress *localAddress)
+{
+ assertNotNull(set, "Parameter set must be non-null");
+ assertNotNull(localAddress, "Parameter localAddress must be non-null");
+
+ MetisListenerOps *match = NULL;
+
+ for (size_t i = 0; i < parcArrayList_Size(set->listOfListeners) && !match; i++) {
+ MetisListenerOps *ops = parcArrayList_Get(set->listOfListeners, i);
+ assertNotNull(ops, "Got null listener ops at index %zu", i);
+
+ if (ops->getEncapType(ops) == encapType) {
+ if (cpiAddress_Equals(localAddress, ops->getListenAddress(ops))) {
+ match = ops;
+ }
+ }
+ }
+
+ return match;
+}
diff --git a/metis/ccnx/forwarder/metis/io/metis_ListenerSet.h b/metis/ccnx/forwarder/metis/io/metis_ListenerSet.h
new file mode 100644
index 00000000..dad876dd
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_ListenerSet.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file metis_ListenerSet.h
+ * @brief A listener set is unique on (MetisEncapType, localAddress)
+ *
+ * Keeps track of all the running listeners. The set is unique on the
+ * encapsulation type and the local address. For example, with TCP encapsulation and
+ * local address 127.0.0.1 or Ethernet encapsulation and MAC address 00:11:22:33:44:55.
+ *
+ * NOTE: This does not allow multiple EtherType on the same interface because the CPIAddress for
+ * a LINK address does not include an EtherType.
+ *
+ */
+
+#ifndef Metis_metis_ListenerSet_h
+#define Metis_metis_ListenerSet_h
+
+#include <ccnx/forwarder/metis/io/metis_Listener.h>
+
+struct metis_listener_set;
+typedef struct metis_listener_set MetisListenerSet;
+
+/**
+ * <#One Line Description#>
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @retval <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisListenerSet *metisListenerSet_Create(void);
+
+/**
+ * <#One Line Description#>
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @retval <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+void metisListenerSet_Destroy(MetisListenerSet **setPtr);
+
+/**
+ * @function metisListenerSet_Add
+ * @abstract Adds the listener to the set
+ * @discussion
+ * Unique set based on pair (MetisEncapType, localAddress).
+ * Takes ownership of the ops memory if added.
+ *
+ * @param <#param1#>
+ * @return true if added, false if not
+ */
+bool metisListenerSet_Add(MetisListenerSet *set, MetisListenerOps *ops);
+
+/**
+ * The number of listeners in the set
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [in] set An allocated listener set
+ *
+ * @retval <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+size_t metisListenerSet_Length(const MetisListenerSet *set);
+
+/**
+ * Returns the listener at the given index
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [in] set An allocated listener set
+ * @param [in] index The index position (0 <= index < metisListenerSet_Lenght)
+ *
+ * @retval non-null The listener at index
+ * @retval null An error
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisListenerOps *metisListenerSet_Get(const MetisListenerSet *set, size_t index);
+
+/**
+ * Looks up a listener by its key (EncapType, LocalAddress)
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [in] set An allocated listener set
+ * @param [in] encapType the listener type
+ * @param [in] localAddress The local bind address (e.g. MAC address or TCP socket)
+ *
+ * @retval non-null The listener matching the query
+ * @retval null Does not exist
+ *
+ * Example:
+ * @code
+ *
+ * @endcode
+ */
+MetisListenerOps *metisListenerSet_Find(const MetisListenerSet *set, MetisEncapType encapType, const CPIAddress *localAddress);
+#endif
diff --git a/metis/ccnx/forwarder/metis/io/metis_LocalListener.c b/metis/ccnx/forwarder/metis/io/metis_LocalListener.c
new file mode 100644
index 00000000..6e188966
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_LocalListener.c
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Implements a listener that works with stream connections over a named pipe.
+ *
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include <ccnx/forwarder/metis/core/metis_ConnectionTable.h>
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+#include <ccnx/forwarder/metis/io/metis_Listener.h>
+#include <ccnx/forwarder/metis/io/metis_LocalListener.h>
+#include <ccnx/forwarder/metis/io/metis_StreamConnection.h>
+
+#include <LongBow/runtime.h>
+#include <parc/algol/parc_Memory.h>
+
+struct metis_local_listener {
+ MetisForwarder *metis;
+ MetisLogger *logger;
+ PARCEventSocket *listener;
+ CPIAddress *localAddress;
+ unsigned id;
+};
+
+static void _metisLocalListener_OpsDestroy(MetisListenerOps **listenerOpsPtr);
+static unsigned _metisLocalListener_OpsGetInterfaceIndex(const MetisListenerOps *ops);
+static const CPIAddress *_metisLocalListener_OpsGetListenAddress(const MetisListenerOps *ops);
+static MetisEncapType _metisLocalListener_OpsGetEncapType(const MetisListenerOps *ops);
+
+static MetisListenerOps localTemplate = {
+ .context = NULL,
+ .destroy = &_metisLocalListener_OpsDestroy,
+ .getInterfaceIndex = &_metisLocalListener_OpsGetInterfaceIndex,
+ .getListenAddress = &_metisLocalListener_OpsGetListenAddress,
+ .getEncapType = &_metisLocalListener_OpsGetEncapType,
+};
+
+// STREAM daemon listener callback
+static void metisListenerLocal_Listen(int, struct sockaddr *, int socklen, void *localVoid);
+
+MetisListenerOps *
+metisLocalListener_Create(MetisForwarder *metis, const char *path)
+{
+ MetisLocalListener *local = parcMemory_AllocateAndClear(sizeof(MetisLocalListener));
+ assertNotNull(local, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisLocalListener));
+ local->metis = metis;
+ local->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis));
+
+ struct sockaddr_un addr_unix;
+ memset(&addr_unix, 0, sizeof(addr_unix));
+
+ addr_unix.sun_family = PF_UNIX;
+ strcpy(addr_unix.sun_path, path);
+
+ unlink(path);
+
+ local->listener = metisDispatcher_CreateListener(metisForwarder_GetDispatcher(metis), metisListenerLocal_Listen,
+ (void *) local, -1, (struct sockaddr*) &addr_unix, sizeof(addr_unix));
+
+ assertNotNull(local->listener, "Got null listener from metisDispatcher_CreateListener: (%d) %s", errno, strerror(errno));
+
+ struct sockaddr_un addr_un;
+ memset(&addr_un, 0, sizeof(addr_un));
+ addr_un.sun_family = AF_UNIX;
+ strcpy(addr_un.sun_path, path);
+
+ local->localAddress = cpiAddress_CreateFromUnix(&addr_un);
+ local->id = metisForwarder_GetNextConnectionId(metis);
+
+ MetisListenerOps *ops = parcMemory_AllocateAndClear(sizeof(MetisListenerOps));
+ assertNotNull(ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisListenerOps));
+ memcpy(ops, &localTemplate, sizeof(MetisListenerOps));
+ ops->context = local;
+
+ return ops;
+}
+
+void
+metisLocalListener_Destroy(MetisLocalListener **listenerPtr)
+{
+ assertNotNull(listenerPtr, "Parameter must be non-null double pointer");
+ assertNotNull(*listenerPtr, "Parameter must dereference to non-null pointer");
+
+ MetisLocalListener *local = *listenerPtr;
+
+ metisLogger_Release(&local->logger);
+
+ cpiAddress_Destroy(&local->localAddress);
+ metisDispatcher_DestroyListener(metisForwarder_GetDispatcher(local->metis), &local->listener);
+
+ parcMemory_Deallocate((void **) &local);
+ *listenerPtr = NULL;
+}
+
+// ==================================================
+
+/**
+ * @function metisListenerLocal_Listen
+ * @abstract Called when a client connects to the server socket
+ * @discussion
+ * Accepts a client connection. Creates a new Stream connection and adds it
+ * to the connection table.
+ *
+ * @param fd the remote client socket (it will be AF_UNIX type)
+ * @param sa the remote client address
+ * @param socklen the bytes of sa
+ * @param localVoid a void point to the MetisLocalListener that owns the server socket
+ */
+static void
+metisListenerLocal_Listen(int fd,
+ struct sockaddr *sa, int socklen, void *localVoid)
+{
+ MetisLocalListener *local = (MetisLocalListener *) localVoid;
+ assertTrue(sa->sa_family == AF_UNIX, "Got wrong address family, expected %d got %d", AF_UNIX, sa->sa_family);
+
+ CPIAddress *remote = cpiAddress_CreateFromUnix((struct sockaddr_un *) sa);
+ MetisAddressPair *pair = metisAddressPair_Create(local->localAddress, remote);
+
+ MetisIoOperations *ops = metisStreamConnection_AcceptConnection(local->metis, fd, pair, true);
+ MetisConnection *conn = metisConnection_Create(ops);
+
+ metisConnectionTable_Add(metisForwarder_GetConnectionTable(local->metis), conn);
+
+ if (metisLogger_IsLoggable(local->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) {
+ char *str = metisAddressPair_ToString(pair);
+ metisLogger_Log(local->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "Listener %p started on address pair %s", (void *) local, str);
+ free(str);
+ }
+
+ cpiAddress_Destroy(&remote);
+}
+
+static void
+_metisLocalListener_OpsDestroy(MetisListenerOps **listenerOpsPtr)
+{
+ MetisListenerOps *ops = *listenerOpsPtr;
+ MetisLocalListener *local = (MetisLocalListener *) ops->context;
+
+ if (metisLogger_IsLoggable(local->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) {
+ metisLogger_Log(local->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "Listener %p destroyed", (void *) local);
+ }
+
+ metisLocalListener_Destroy(&local);
+ parcMemory_Deallocate((void **) &ops);
+ *listenerOpsPtr = NULL;
+}
+
+static unsigned
+_metisLocalListener_OpsGetInterfaceIndex(const MetisListenerOps *ops)
+{
+ MetisLocalListener *local = (MetisLocalListener *) ops->context;
+ return local->id;
+}
+
+static const CPIAddress *
+_metisLocalListener_OpsGetListenAddress(const MetisListenerOps *ops)
+{
+ MetisLocalListener *local = (MetisLocalListener *) ops->context;
+ return local->localAddress;
+}
+
+static MetisEncapType
+_metisLocalListener_OpsGetEncapType(const MetisListenerOps *ops)
+{
+ return METIS_ENCAP_LOCAL;
+}
diff --git a/metis/ccnx/forwarder/metis/io/metis_LocalListener.h b/metis/ccnx/forwarder/metis/io/metis_LocalListener.h
new file mode 100644
index 00000000..1e497a18
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_LocalListener.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+#ifndef Metis_metis_ListenerLocal_h
+#define Metis_metis_ListenerLocal_h
+
+#include <stdlib.h>
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+
+struct metis_local_listener;
+typedef struct metis_local_listener MetisLocalListener;
+
+MetisListenerOps *metisLocalListener_Create(MetisForwarder *metis, const char *path);
+void metisLocalListener_Destroy(MetisLocalListener **listenerPtr);
+#endif // Metis_metis_ListenerLocal_h
diff --git a/metis/ccnx/forwarder/metis/io/metis_StreamConnection.c b/metis/ccnx/forwarder/metis/io/metis_StreamConnection.c
new file mode 100644
index 00000000..225e6d0d
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_StreamConnection.c
@@ -0,0 +1,570 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Common activity for STREAM based listeners.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+
+#include <ccnx/forwarder/metis/io/metis_StreamConnection.h>
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+#include <ccnx/forwarder/metis/core/metis_Connection.h>
+#include <ccnx/forwarder/metis/core/metis_Message.h>
+#include <parc/algol/parc_Hash.h>
+
+#include <LongBow/runtime.h>
+#include <parc/algol/parc_Memory.h>
+#include <ccnx/forwarder/metis/tlv/metis_Tlv.h>
+
+// 128 KB output queue
+#define OUTPUT_QUEUE_BYTES (128 * 1024)
+
+static void
+_conn_readcb(PARCEventQueue *bufferEventVector, PARCEventType type, void *ioOpsVoid);
+
+static void
+_conn_eventcb(PARCEventQueue *bufferEventVector, PARCEventQueueEventType events, void *ioOpsVoid);
+
+typedef struct metis_stream_state {
+ MetisForwarder *metis;
+ MetisLogger *logger;
+
+ int fd;
+
+ MetisAddressPair *addressPair;
+ PARCEventQueue *bufferEventVector;
+
+ bool isLocal;
+ bool isUp;
+ bool isClosed;
+ unsigned id;
+
+ size_t nextMessageLength;
+} _MetisStreamState;
+
+// Prototypes
+static bool _metisStreamConnection_Send(MetisIoOperations *ops, const CPIAddress *nexthop, MetisMessage *message);
+static const CPIAddress *_metisStreamConnection_GetRemoteAddress(const MetisIoOperations *ops);
+static const MetisAddressPair *_metisStreamConnection_GetAddressPair(const MetisIoOperations *ops);
+static unsigned _metisStreamConnection_GetConnectionId(const MetisIoOperations *ops);
+static bool _metisStreamConnection_IsUp(const MetisIoOperations *ops);
+static bool _metisStreamConnection_IsLocal(const MetisIoOperations *ops);
+static void _metisStreamConnection_DestroyOperations(MetisIoOperations **opsPtr);
+
+static void _setConnectionState(_MetisStreamState *stream, bool isUp);
+static CPIConnectionType _metisStreamConnection_GetConnectionType(const MetisIoOperations *ops);
+static MetisTicks _sendProbe(MetisIoOperations *ops, unsigned probeType);
+
+/*
+ * This assigns a unique pointer to the void * which we use
+ * as a GUID for this class.
+ */
+static const void *_metisIoOperationsGuid = __FILE__;
+
+/*
+ * Return our GUID
+ */
+static const void *
+_metisStreamConnection_Class(const MetisIoOperations *ops)
+{
+ return _metisIoOperationsGuid;
+}
+
+static MetisIoOperations _template = {
+ .closure = NULL,
+ .send = &_metisStreamConnection_Send,
+ .getRemoteAddress = &_metisStreamConnection_GetRemoteAddress,
+ .getAddressPair = &_metisStreamConnection_GetAddressPair,
+ .getConnectionId = &_metisStreamConnection_GetConnectionId,
+ .isUp = &_metisStreamConnection_IsUp,
+ .isLocal = &_metisStreamConnection_IsLocal,
+ .destroy = &_metisStreamConnection_DestroyOperations,
+ .class = &_metisStreamConnection_Class,
+ .getConnectionType = &_metisStreamConnection_GetConnectionType,
+ .sendProbe = &_sendProbe,
+};
+
+MetisIoOperations *
+metisStreamConnection_AcceptConnection(MetisForwarder *metis, int fd, MetisAddressPair *pair, bool isLocal)
+{
+ _MetisStreamState *stream = parcMemory_AllocateAndClear(sizeof(_MetisStreamState));
+ assertNotNull(stream, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisStreamState));
+
+ MetisDispatcher *dispatcher = metisForwarder_GetDispatcher(metis);
+ PARCEventScheduler *eventBase = metisDispatcher_GetEventScheduler(dispatcher);
+ stream->bufferEventVector = parcEventQueue_Create(eventBase, fd, PARCEventQueueOption_CloseOnFree | PARCEventQueueOption_DeferCallbacks);
+
+ stream->metis = metis;
+ stream->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis));
+ stream->fd = fd;
+ stream->id = metisForwarder_GetNextConnectionId(metis);
+ stream->addressPair = pair;
+ stream->isClosed = false;
+
+ // allocate a connection
+ MetisIoOperations *io_ops = parcMemory_AllocateAndClear(sizeof(MetisIoOperations));
+ assertNotNull(io_ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisIoOperations));
+ memcpy(io_ops, &_template, sizeof(MetisIoOperations));
+ io_ops->closure = stream;
+ stream->isLocal = isLocal;
+
+ parcEventQueue_SetCallbacks(stream->bufferEventVector, _conn_readcb, NULL, _conn_eventcb, (void *) io_ops);
+ parcEventQueue_Enable(stream->bufferEventVector, PARCEventType_Read);
+
+ metisMessenger_Send(metisForwarder_GetMessenger(stream->metis), metisMissive_Create(MetisMissiveType_ConnectionCreate, stream->id));
+
+ // As we are acceting a connection, we begin in the UP state
+ _setConnectionState(stream, true);
+
+ if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) {
+ char *pair_str = metisAddressPair_ToString(pair);
+ metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "StreamConnection %p accept for address pair %s", (void *) stream, pair_str);
+ free(pair_str);
+ }
+
+ return io_ops;
+}
+
+MetisIoOperations *
+metisStreamConnection_OpenConnection(MetisForwarder *metis, MetisAddressPair *pair, bool isLocal)
+{
+ assertNotNull(metis, "Parameter metis must be non-null");
+ assertNotNull(pair, "Parameter pair must be non-null");
+
+ // if there's an error on the bind or connect, will return NULL
+ PARCEventQueue *bufferEventVector = metisDispatcher_StreamBufferConnect(metisForwarder_GetDispatcher(metis), pair);
+ if (bufferEventVector == NULL) {
+ // error opening connection
+ return NULL;
+ }
+
+ _MetisStreamState *stream = parcMemory_AllocateAndClear(sizeof(_MetisStreamState));
+ assertNotNull(stream, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisStreamState));
+
+ stream->metis = metis;
+ stream->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis));
+ stream->fd = parcEventQueue_GetFileDescriptor(bufferEventVector);
+ stream->bufferEventVector = bufferEventVector;
+ stream->id = metisForwarder_GetNextConnectionId(metis);
+ stream->addressPair = pair;
+ stream->isClosed = false;
+
+ // allocate a connection
+ MetisIoOperations *io_ops = parcMemory_AllocateAndClear(sizeof(MetisIoOperations));
+ assertNotNull(io_ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisIoOperations));
+ memcpy(io_ops, &_template, sizeof(MetisIoOperations));
+ io_ops->closure = stream;
+ stream->isLocal = isLocal;
+
+ parcEventQueue_SetCallbacks(stream->bufferEventVector, _conn_readcb, NULL, _conn_eventcb, (void *) io_ops);
+ parcEventQueue_Enable(stream->bufferEventVector, PARCEventType_Read);
+
+ // we start in DOWN state, until remote side answers
+ metisMessenger_Send(metisForwarder_GetMessenger(stream->metis), metisMissive_Create(MetisMissiveType_ConnectionCreate, stream->id));
+ _setConnectionState(stream, false);
+
+ if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Info)) {
+ char *pair_str = metisAddressPair_ToString(pair);
+ metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__,
+ "StreamConnection %p connect for address pair %s", (void *) stream, pair_str);
+ free(pair_str);
+ }
+
+ return io_ops;
+}
+
+static void
+_metisStreamConnection_DestroyOperations(MetisIoOperations **opsPtr)
+{
+ assertNotNull(opsPtr, "Parameter opsPtr must be non-null double pointer");
+ assertNotNull(*opsPtr, "Parameter opsPtr must dereference to non-null pointer");
+
+ MetisIoOperations *ops = *opsPtr;
+ assertNotNull(metisIoOperations_GetClosure(ops), "ops->context must not be null");
+
+ _MetisStreamState *stream = (_MetisStreamState *) metisIoOperations_GetClosure(ops);
+
+ parcEventQueue_Destroy(&stream->bufferEventVector);
+
+ metisAddressPair_Release(&stream->addressPair);
+
+ if (!stream->isClosed) {
+ stream->isClosed = true;
+ metisMessenger_Send(metisForwarder_GetMessenger(stream->metis), metisMissive_Create(MetisMissiveType_ConnectionClosed, stream->id));
+ }
+
+ metisMessenger_Send(metisForwarder_GetMessenger(stream->metis), metisMissive_Create(MetisMissiveType_ConnectionDestroyed, stream->id));
+
+ if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Info)) {
+ metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__,
+ "StreamConnection %p destroyed", (void *) stream);
+ }
+
+ metisLogger_Release(&stream->logger);
+ parcMemory_Deallocate((void **) &stream);
+ parcMemory_Deallocate((void **) &ops);
+
+ *opsPtr = NULL;
+}
+
+static bool
+_metisStreamConnection_IsUp(const MetisIoOperations *ops)
+{
+ assertNotNull(ops, "Parameter must be non-null");
+ const _MetisStreamState *stream = (const _MetisStreamState *) metisIoOperations_GetClosure(ops);
+ return stream->isUp;
+}
+
+static bool
+_metisStreamConnection_IsLocal(const MetisIoOperations *ops)
+{
+ assertNotNull(ops, "Parameter must be non-null");
+ const _MetisStreamState *stream = (const _MetisStreamState *) metisIoOperations_GetClosure(ops);
+ return stream->isLocal;
+}
+
+static const CPIAddress *
+_metisStreamConnection_GetRemoteAddress(const MetisIoOperations *ops)
+{
+ assertNotNull(ops, "Parameter must be non-null");
+ const _MetisStreamState *stream = (const _MetisStreamState *) metisIoOperations_GetClosure(ops);
+ return metisAddressPair_GetRemote(stream->addressPair);
+}
+
+static const MetisAddressPair *
+_metisStreamConnection_GetAddressPair(const MetisIoOperations *ops)
+{
+ assertNotNull(ops, "Parameter must be non-null");
+ const _MetisStreamState *stream = (const _MetisStreamState *) metisIoOperations_GetClosure(ops);
+ return stream->addressPair;
+}
+
+static unsigned
+_metisStreamConnection_GetConnectionId(const MetisIoOperations *ops)
+{
+ assertNotNull(ops, "Parameter must be non-null");
+ const _MetisStreamState *stream = (const _MetisStreamState *) metisIoOperations_GetClosure(ops);
+ return stream->id;
+}
+
+/**
+ * @function metisStreamConnection_Send
+ * @abstract Non-destructive send of the message.
+ * @discussion
+ * Send uses metisMessage_CopyToStreamBuffer, which is a non-destructive write.
+ * The send may fail if there's no buffer space in the output queue.
+ *
+ * @param dummy is ignored. A stream has only one peer.
+ * @return <#return#>
+ */
+static bool
+_metisStreamConnection_Send(MetisIoOperations *ops, const CPIAddress *dummy, MetisMessage *message)
+{
+ assertNotNull(ops, "Parameter ops must be non-null");
+ assertNotNull(message, "Parameter message must be non-null");
+ _MetisStreamState *stream = (_MetisStreamState *) metisIoOperations_GetClosure(ops);
+
+ bool success = false;
+ if (stream->isUp) {
+ PARCEventBuffer *buffer = parcEventBuffer_GetQueueBufferOutput(stream->bufferEventVector);
+ size_t buffer_backlog = parcEventBuffer_GetLength(buffer);
+ parcEventBuffer_Destroy(&buffer);
+
+ if (buffer_backlog < OUTPUT_QUEUE_BYTES) {
+ if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) {
+ metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "connid %u Writing %zu bytes to buffer with backlog %zu bytes",
+ stream->id,
+ metisMessage_Length(message),
+ buffer_backlog);
+ }
+
+ int failure = metisMessage_Write(stream->bufferEventVector, message);
+ if (failure == 0) {
+ success = true;
+ }
+ } else {
+ if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning)) {
+ metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__,
+ "connid %u Writing to buffer backlog %zu bytes DROP MESSAGE",
+ stream->id,
+ buffer_backlog);
+ }
+ }
+ } else {
+ if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) {
+ metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__,
+ "connid %u tried to send to down connection (isUp %d isClosed %d)",
+ stream->id,
+ stream->isUp,
+ stream->isClosed);
+ }
+ }
+
+ return success;
+}
+
+static CPIConnectionType
+_metisStreamConnection_GetConnectionType(const MetisIoOperations *ops)
+{
+ return cpiConnection_TCP;
+}
+
+static MetisTicks
+_sendProbe(MetisIoOperations *ops, unsigned probeType)
+{
+ //TODO
+ return 0;
+}
+
+// =================================================================
+// the actual I/O functions
+
+/**
+ * @function startNewMessage
+ * @abstract Peek at the fixed header and set the stream->nextMessageLength
+ * @discussion
+ * This function manipulates the stream->nextMessageLength. After reading a FixedHeader, set nextMessageLength
+ * to the total length of the message.
+ *
+ * PRECONDITION: stream->nextMessageLength == 0
+ * PRECONDITION: inputBytesAvailable >= FIXED_HEADER_LEN
+ *
+ * @param stream is the stream begin parsed.
+ * @param input is the input PARCEventBuffer (corresponds to the buffer event's input)
+ * @param inputBytesAvailable is the number of bytes available in the input PARCEventBuffer.
+ * @return <#return#>
+ */
+static void
+_startNewMessage(_MetisStreamState *stream, PARCEventBuffer *input, size_t inputBytesAvailable)
+{
+ assertTrue(stream->nextMessageLength == 0, "Invalid state, nextMessageLength not zero: %zu", stream->nextMessageLength);
+ assertTrue(inputBytesAvailable >= metisTlv_FixedHeaderLength(), "read_length not a whole fixed header!: %zd", inputBytesAvailable);
+
+ // this linearizes the first FIXED_HEADER_LEN bytes of the input buffer's iovecs and
+ // returns a pointer to it.
+ uint8_t *fh = parcEventBuffer_Pullup(input, metisTlv_FixedHeaderLength());
+
+ // Calculate the total message size based on the fixed header
+ stream->nextMessageLength = metisTlv_TotalPacketLength(fh);
+}
+
+/**
+ * @function readMessage
+ * @abstract Read the complete message from the input
+ * @discussion
+ * Called to read a complete message from the input and return a MetisMessage
+ *
+ * PRECONDITION: There are at least <code>stream->nextMessageLength</code>
+ * bytes available on the input PARCEventBuffer.
+ *
+ * @param stream is the stream being parsed
+ * @param time is the current forwarder time (metisForwarder_GetTicks(stream->metis))
+ * @param input is the input PARCEventBuffer to readessage bytes.
+ * @return <#return#>
+ */
+static MetisMessage *
+_readMessage(_MetisStreamState *stream, MetisTicks time, PARCEventBuffer *input)
+{
+ MetisMessage *message = metisMessage_ReadFromBuffer(stream->id, time, input, stream->nextMessageLength, stream->logger);
+
+ return message;
+}
+
+/**
+ * @function single_read
+ * @abstract Read at most 1 message from the network
+ * @discussion
+ * If a complete message is ready on the input buffer, will allocate and return it.
+ *
+ * This function manipulates the stream->nextMessageLength. (1) Initializes with nextMessageLength = 0,
+ * which means we have not started parsing a packet. (2) After reading a FixedHeader, set nextMessageLength
+ * to the total length of the message. (3) After reading nextMessageLength bytes, return the outputBuffer
+ * and reset nextMessageLength to 0.
+ *
+ * @param input is the PARCEventBuffer to read
+ * @param stream is the related stream state for the input
+ * @return true if there's more to read after this message.
+ */
+static MetisMessage *
+_single_read(PARCEventBuffer *input, _MetisStreamState *stream)
+{
+ size_t bytesAvailable = parcEventBuffer_GetLength(input);
+
+ assertTrue(bytesAvailable >= metisTlv_FixedHeaderLength(), "Called with too short an input: %zu", bytesAvailable);
+
+ if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) {
+ metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "connid %u read %zu bytes",
+ stream->id, bytesAvailable);
+ }
+
+ if (stream->nextMessageLength == 0) {
+ _startNewMessage(stream, input, bytesAvailable);
+ }
+
+ // This is not an ELSE statement. We can both start a new message then
+ // check if there's enough bytes to read the whole thing.
+
+ if (bytesAvailable >= stream->nextMessageLength) {
+ MetisMessage *message = _readMessage(stream, metisForwarder_GetTicks(stream->metis), input);
+
+ if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) {
+ metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "connid %u msg_length %zu read_length %zu, resetting parser",
+ stream->id,
+ stream->nextMessageLength,
+ bytesAvailable);
+ }
+
+ // now reset message length for next packet
+ stream->nextMessageLength = 0;
+
+ return message;
+ }
+
+ return NULL;
+}
+
+/**
+ * @function conn_readcb
+ * @abstract Event callback for reads
+ * @discussion
+ * Will read messages off the input. Continues reading as long as we
+ * can get a header to determine the next message length or as long as we
+ * can read a complete message.
+ *
+ * This function manipulates the read low water mark. (1) read a fixed header plus complete message,
+ * then set the low water mark to FIXED_HEADER_LEN. (2) read a fixed header, but not a complete
+ * message, then set low water mark to the total mesage length. Using the low water mark like this
+ * means the buffer event will only trigger on meaningful byte boundaries when we can get actual
+ * work done.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+static void
+_conn_readcb(PARCEventQueue *event, PARCEventType type, void *ioOpsVoid)
+{
+ MetisIoOperations *ops = (MetisIoOperations *) ioOpsVoid;
+ _MetisStreamState *stream = (_MetisStreamState *) metisIoOperations_GetClosure(ops);
+
+ PARCEventBuffer *input = parcEventBuffer_GetQueueBufferInput(event);
+
+ // drain the input buffer
+ while (parcEventBuffer_GetLength(input) >= metisTlv_FixedHeaderLength() && parcEventBuffer_GetLength(input) >= stream->nextMessageLength) {
+ // this may set the stream->nextMessageLength
+ MetisMessage *message = _single_read(input, stream);
+
+ if (message) {
+ metisForwarder_Receive(stream->metis, message);
+ }
+ }
+
+ if (stream->nextMessageLength == 0) {
+ // we don't have the next header, so set it to the header length
+ metisStreamBuffer_SetWatermark(event, true, false, metisTlv_FixedHeaderLength(), 0);
+ } else {
+ // set it to the packet length
+ metisStreamBuffer_SetWatermark(event, true, false, stream->nextMessageLength, 0);
+ }
+ parcEventBuffer_Destroy(&input);
+}
+
+static void
+_setConnectionState(_MetisStreamState *stream, bool isUp)
+{
+ assertNotNull(stream, "Parameter stream must be non-null");
+
+ MetisMessenger *messenger = metisForwarder_GetMessenger(stream->metis);
+
+ bool oldStateIsUp = stream->isUp;
+ stream->isUp = isUp;
+
+ if (oldStateIsUp && !isUp) {
+ // bring connection DOWN
+ MetisMissive *missive = metisMissive_Create(MetisMissiveType_ConnectionDown, stream->id);
+ metisMessenger_Send(messenger, missive);
+ return;
+ }
+
+
+ if (!oldStateIsUp && isUp) {
+ // bring connection UP
+ MetisMissive *missive = metisMissive_Create(MetisMissiveType_ConnectionUp, stream->id);
+ metisMessenger_Send(messenger, missive);
+ return;
+ }
+}
+
+static void
+_conn_eventcb(PARCEventQueue *event, PARCEventQueueEventType events, void *ioOpsVoid)
+{
+ MetisIoOperations *ops = (MetisIoOperations *) ioOpsVoid;
+ _MetisStreamState *stream = (_MetisStreamState *) metisIoOperations_GetClosure(ops);
+
+ if (events & PARCEventQueueEventType_Connected) {
+ if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Info)) {
+ metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__,
+ "Connection %u is connected", stream->id);
+ }
+
+ // if the stream was closed, do not transition to an UP state
+ if (!stream->isClosed) {
+ _setConnectionState(stream, true);
+ }
+ } else
+ if (events & PARCEventQueueEventType_EOF) {
+ if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Info)) {
+ metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__,
+ "connid %u closed.",
+ stream->id);
+ }
+
+ parcEventQueue_Disable(stream->bufferEventVector, PARCEventType_Read);
+
+ _setConnectionState(stream, false);
+
+ if (!stream->isClosed) {
+ stream->isClosed = true;
+ // this will cause the connection manager to destroy the connection later
+ metisMessenger_Send(metisForwarder_GetMessenger(stream->metis), metisMissive_Create(MetisMissiveType_ConnectionClosed, stream->id));
+ }
+ } else
+ if (events & PARCEventQueueEventType_Error) {
+ if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) {
+ metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__,
+ "Got an error on the connection %u: %s", stream->id, strerror(errno));
+ }
+
+ parcEventQueue_Disable(stream->bufferEventVector, PARCEventType_Read | PARCEventType_Write);
+
+ _setConnectionState(stream, false);
+
+ if (!stream->isClosed) {
+ stream->isClosed = true;
+ // this will cause the connection manager to destroy the connection later
+ metisMessenger_Send(metisForwarder_GetMessenger(stream->metis), metisMissive_Create(MetisMissiveType_ConnectionClosed, stream->id));
+ }
+ }
+ /* None of the other events can happen here, since we haven't enabled
+ * timeouts */
+}
diff --git a/metis/ccnx/forwarder/metis/io/metis_StreamConnection.h b/metis/ccnx/forwarder/metis/io/metis_StreamConnection.h
new file mode 100644
index 00000000..ee614862
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_StreamConnection.h
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+
+/**
+ * Methods common to TCP and PF_LOCAL stream-based listeners
+ */
+
+#ifndef Metis_metis_StreamConnection_h
+#define Metis_metis_StreamConnection_h
+
+#include <ccnx/forwarder/metis/io/metis_IoOperations.h>
+#include <ccnx/forwarder/metis/io/metis_AddressPair.h>
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+#include <ccnx/api/control/cpi_Address.h>
+
+/**
+ * @function metisStreamConnection_AcceptConnection
+ * @abstract Receive a connection from a remote peer
+ * @discussion
+ * We are the "server side" of the stream connection, so we need to accept the client connection
+ * and setup state for her.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+MetisIoOperations *metisStreamConnection_AcceptConnection(MetisForwarder *metis, int fd, MetisAddressPair *pair, bool isLocal);
+
+/**
+ * @function metisStreamConnection_OpenConnection
+ * @abstract Initiate a connection to a remote peer
+ * @discussion
+ * We are the "client side" of the stream connection. We'll create state for the peer, but it will
+ * be in the "down" state until the connection establishes.
+ *
+ * For TCP, both address pairs need to be the same address family: both INET or both INET6. The remote
+ * address must have the complete socket information (address, port). The local socket could be wildcarded or
+ * may specify down to the (address, port) pair.
+ *
+ * If the local address is IPADDR_ANY and the port is 0, then it is a normal call to "connect" that will use
+ * whatever local IP address and whatever local port for the connection. If either the address or port is
+ * set, the local socket will first be bound (via bind(2)), and then call connect().
+ *
+ * AF_UNIX is not yet supported
+ *
+ * If there's an error binding to the specified address or connecting to the remote address,
+ * will return NULL.
+ *
+ * @param pair (takes ownership of this) is the complete socket pair of (address, port) for each end, if INET or INET6.
+ * @return NULL on error, otherwise the connections IO operations.
+ */
+MetisIoOperations *metisStreamConnection_OpenConnection(MetisForwarder *metis, MetisAddressPair *pair, bool isLocal);
+#endif // Metis_metis_StreamConnection_h
diff --git a/metis/ccnx/forwarder/metis/io/metis_TcpListener.c b/metis/ccnx/forwarder/metis/io/metis_TcpListener.c
new file mode 100644
index 00000000..e9d232ce
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_TcpListener.c
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+
+#include <ccnx/forwarder/metis/io/metis_TcpListener.h>
+#include <ccnx/forwarder/metis/io/metis_StreamConnection.h>
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+#include <ccnx/forwarder/metis/core/metis_ConnectionTable.h>
+#include <ccnx/forwarder/metis/io/metis_Listener.h>
+
+#include <LongBow/runtime.h>
+#include <parc/algol/parc_Memory.h>
+#include <parc/algol/parc_Network.h>
+
+typedef struct metis_tcp_listener {
+ MetisForwarder *metis;
+ MetisLogger *logger;
+
+ PARCEventSocket *listener;
+
+ CPIAddress *localAddress;
+
+ unsigned id;
+
+ // is the localAddress as 127.0.0.0 address?
+ bool isLocalAddressLocal;
+} _MetisTcpListener;
+
+static void _metisTcpListener_Destroy(_MetisTcpListener **listenerPtr);
+
+static void _metisTcpListener_OpsDestroy(MetisListenerOps **listenerOpsPtr);
+static unsigned _metisTcpListener_OpsGetInterfaceIndex(const MetisListenerOps *ops);
+static const CPIAddress *_metisTcpListener_OpsGetListenAddress(const MetisListenerOps *ops);
+static MetisEncapType _metisTcpListener_OpsGetEncapType(const MetisListenerOps *ops);
+
+static MetisListenerOps _tcpTemplate = {
+ .context = NULL,
+ .destroy = &_metisTcpListener_OpsDestroy,
+ .getInterfaceIndex = &_metisTcpListener_OpsGetInterfaceIndex,
+ .getListenAddress = &_metisTcpListener_OpsGetListenAddress,
+ .getEncapType = &_metisTcpListener_OpsGetEncapType,
+ .getSocket = NULL
+};
+
+// STREAM daemon listener callback
+static void _metisTcpListener_Listen(int, struct sockaddr *, int socklen, void *tcpVoid);
+
+
+MetisListenerOps *
+metisTcpListener_CreateInet6(MetisForwarder *metis, struct sockaddr_in6 sin6)
+{
+ _MetisTcpListener *tcp = parcMemory_AllocateAndClear(sizeof(_MetisTcpListener));
+ assertNotNull(tcp, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisTcpListener));
+
+ tcp->metis = metis;
+ tcp->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis));
+
+ tcp->listener = metisDispatcher_CreateListener(metisForwarder_GetDispatcher(metis), _metisTcpListener_Listen,
+ (void *) tcp, -1, (struct sockaddr*) &sin6, sizeof(sin6));
+
+ if (tcp->listener == NULL) {
+ metisLogger_Log(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__,
+ "metisDispatcher_CreateListener failed to create listener (%d) %s",
+ errno, strerror(errno));
+ metisLogger_Release(&tcp->logger);
+ parcMemory_Deallocate((void **) &tcp);
+ return NULL;
+ }
+
+ tcp->localAddress = cpiAddress_CreateFromInet6(&sin6);
+ tcp->id = metisForwarder_GetNextConnectionId(metis);
+ tcp->isLocalAddressLocal = parcNetwork_IsSocketLocal((struct sockaddr *) &sin6);
+
+ MetisListenerOps *ops = parcMemory_AllocateAndClear(sizeof(MetisListenerOps));
+ assertNotNull(ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisListenerOps));
+
+ memcpy(ops, &_tcpTemplate, sizeof(MetisListenerOps));
+ ops->context = tcp;
+
+ if (metisLogger_IsLoggable(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) {
+ char *str = cpiAddress_ToString(tcp->localAddress);
+ metisLogger_Log(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "TcpListener %p created for address %s (isLocal %d)",
+ (void *) tcp, str, tcp->isLocalAddressLocal);
+ parcMemory_Deallocate((void **) &str);
+ }
+
+ return ops;
+}
+
+MetisListenerOps *
+metisTcpListener_CreateInet(MetisForwarder *metis, struct sockaddr_in sin)
+{
+ _MetisTcpListener *tcp = parcMemory_AllocateAndClear(sizeof(_MetisTcpListener));
+ assertNotNull(tcp, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisTcpListener));
+
+ tcp->metis = metis;
+ tcp->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis));
+ tcp->listener = metisDispatcher_CreateListener(metisForwarder_GetDispatcher(metis), _metisTcpListener_Listen,
+ (void *) tcp, -1, (struct sockaddr*) &sin, sizeof(sin));
+
+ if (tcp->listener == NULL) {
+ metisLogger_Log(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__,
+ "metisDispatcher_CreateListener failed to create listener (%d) %s",
+ errno, strerror(errno));
+
+ metisLogger_Release(&tcp->logger);
+ parcMemory_Deallocate((void **) &tcp);
+ return NULL;
+ }
+
+ tcp->localAddress = cpiAddress_CreateFromInet(&sin);
+ tcp->id = metisForwarder_GetNextConnectionId(metis);
+ tcp->isLocalAddressLocal = parcNetwork_IsSocketLocal((struct sockaddr *) &sin);
+
+ MetisListenerOps *ops = parcMemory_AllocateAndClear(sizeof(MetisListenerOps));
+ assertNotNull(ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisListenerOps));
+
+ memcpy(ops, &_tcpTemplate, sizeof(MetisListenerOps));
+ ops->context = tcp;
+
+ if (metisLogger_IsLoggable(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) {
+ char *str = cpiAddress_ToString(tcp->localAddress);
+ metisLogger_Log(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "TcpListener %p created for address %s (isLocal %d)",
+ (void *) tcp, str, tcp->isLocalAddressLocal);
+ parcMemory_Deallocate((void **) &str);
+ }
+
+ return ops;
+}
+
+static void
+_metisTcpListener_Destroy(_MetisTcpListener **listenerPtr)
+{
+ assertNotNull(listenerPtr, "Parameter must be non-null double pointer");
+ assertNotNull(*listenerPtr, "Parameter must derefernce to non-null pointer");
+ _MetisTcpListener *tcp = *listenerPtr;
+
+ if (metisLogger_IsLoggable(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) {
+ char *str = cpiAddress_ToString(tcp->localAddress);
+ metisLogger_Log(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "TcpListener %p destroyed", (void *) tcp);
+ parcMemory_Deallocate((void **) &str);
+ }
+
+ metisLogger_Release(&tcp->logger);
+ metisDispatcher_DestroyListener(metisForwarder_GetDispatcher(tcp->metis), &tcp->listener);
+ cpiAddress_Destroy(&tcp->localAddress);
+ parcMemory_Deallocate((void **) &tcp);
+ *listenerPtr = NULL;
+}
+
+// ==================================================
+
+static void
+_metisTcpListener_Listen(int fd, struct sockaddr *sa, int socklen, void *tcpVoid)
+{
+ _MetisTcpListener *tcp = (_MetisTcpListener *) tcpVoid;
+
+ CPIAddress *remote;
+
+ switch (sa->sa_family) {
+ case AF_INET:
+ remote = cpiAddress_CreateFromInet((struct sockaddr_in *) sa);
+ break;
+
+ case AF_INET6:
+ remote = cpiAddress_CreateFromInet6((struct sockaddr_in6 *) sa);
+ break;
+
+ default:
+ trapIllegalValue(sa, "Expected INET or INET6, got %d", sa->sa_family);
+ abort();
+ }
+
+ MetisAddressPair *pair = metisAddressPair_Create(tcp->localAddress, remote);
+
+ MetisIoOperations *ops = metisStreamConnection_AcceptConnection(tcp->metis, fd, pair, tcp->isLocalAddressLocal);
+ MetisConnection *conn = metisConnection_Create(ops);
+
+ metisConnectionTable_Add(metisForwarder_GetConnectionTable(tcp->metis), conn);
+
+ if (metisLogger_IsLoggable(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) {
+ metisLogger_Log(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "TcpListener %p listen started", (void *) tcp);
+ }
+
+ cpiAddress_Destroy(&remote);
+}
+
+static void
+_metisTcpListener_OpsDestroy(MetisListenerOps **listenerOpsPtr)
+{
+ MetisListenerOps *ops = *listenerOpsPtr;
+ _MetisTcpListener *tcp = (_MetisTcpListener *) ops->context;
+ _metisTcpListener_Destroy(&tcp);
+ parcMemory_Deallocate((void **) &ops);
+ *listenerOpsPtr = NULL;
+}
+
+static unsigned
+_metisTcpListener_OpsGetInterfaceIndex(const MetisListenerOps *ops)
+{
+ _MetisTcpListener *tcp = (_MetisTcpListener *) ops->context;
+ return tcp->id;
+}
+
+static const CPIAddress *
+_metisTcpListener_OpsGetListenAddress(const MetisListenerOps *ops)
+{
+ _MetisTcpListener *tcp = (_MetisTcpListener *) ops->context;
+ return tcp->localAddress;
+}
+
+static MetisEncapType
+_metisTcpListener_OpsGetEncapType(const MetisListenerOps *ops)
+{
+ return METIS_ENCAP_TCP;
+}
diff --git a/metis/ccnx/forwarder/metis/io/metis_TcpListener.h b/metis/ccnx/forwarder/metis/io/metis_TcpListener.h
new file mode 100644
index 00000000..e62edba9
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_TcpListener.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file metis_TcpListener.h
+ * @brief Listens for in coming TCP connections
+ *
+ * This is the "server socket" of metis for TCP connections. The actual I/O is
+ * handled by {@link MetisStreamConnection}.
+ *
+ */
+
+#ifndef Metis_metis_TcpListener_h
+#define Metis_metis_TcpListener_h
+
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+#include <ccnx/forwarder/metis/io/metis_Listener.h>
+
+MetisListenerOps *metisTcpListener_CreateInet6(MetisForwarder *metis, struct sockaddr_in6 sin6);
+MetisListenerOps *metisTcpListener_CreateInet(MetisForwarder *metis, struct sockaddr_in sin);
+#endif // Metis_metis_TcpListener_h
diff --git a/metis/ccnx/forwarder/metis/io/metis_TcpTunnel.c b/metis/ccnx/forwarder/metis/io/metis_TcpTunnel.c
new file mode 100644
index 00000000..b0182240
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_TcpTunnel.c
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+/**
+ * The TCP out-bound tunnel is almost identical to the in-bound tunnel.
+ * We use MetisStreamConneciton for the out-bound tunnels too. We call a different
+ * constructor than the in-bound so the MetisStreamConneciton knows that it is starting
+ * unconnected and needs to wait for the Connected event before putting it in the UP state.
+ *
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+
+#include <ccnx/forwarder/metis/io/metis_TcpTunnel.h>
+#include <ccnx/forwarder/metis/io/metis_StreamConnection.h>
+
+#include <LongBow/runtime.h>
+
+MetisIoOperations *
+metisTcpTunnel_Create(MetisForwarder *metis, const CPIAddress *localAddress, const CPIAddress *remoteAddress)
+{
+ MetisAddressPair *pair = metisAddressPair_Create(localAddress, remoteAddress);
+
+ bool isLocal = false;
+
+ // this takes ownership of the address pair
+ return metisStreamConnection_OpenConnection(metis, pair, isLocal);
+}
diff --git a/metis/ccnx/forwarder/metis/io/metis_TcpTunnel.h b/metis/ccnx/forwarder/metis/io/metis_TcpTunnel.h
new file mode 100644
index 00000000..ec53c7cd
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_TcpTunnel.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file metis_TcpTunnel.h
+ * @brief Creates a TCP Tunnel to a remote address
+ *
+ * The connection will be established in "connecting" state and once the remote accepts, it will
+ * be promoted to "up" state.
+ *
+ */
+
+#ifndef Metis_metis_TcpTunnel_h
+#define Metis_metis_TcpTunnel_h
+
+#include <ccnx/api/control/cpi_Address.h>
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+#include <ccnx/forwarder/metis/io/metis_IoOperations.h>
+
+/**
+ * @function metisTcpTunnel_Create
+ * @abstract Creates a TCP tunnel to a remote system.
+ * @discussion
+ * The two addresses must be the same type (i.e. both INET or INET6) and cannot point to the same system.
+ *
+ * The tunnel will look just like an in-bound connection after its built. It exposes the standard
+ * MetisIoOperations so it can be put in the MetisConnectionTable.
+ *
+ * The connection will go in the table immediately, but will be in the "down" state until the
+ * connection is established.
+ *
+ *
+ * @param <#param1#>
+ * @return The I/O ops for the tunnel.
+ */
+MetisIoOperations *metisTcpTunnel_Create(MetisForwarder *metis, const CPIAddress *localAddress, const CPIAddress *remoteAddress);
+#endif // Metis_metis_TcpTunnel_h
diff --git a/metis/ccnx/forwarder/metis/io/metis_UdpConnection.c b/metis/ccnx/forwarder/metis/io/metis_UdpConnection.c
new file mode 100644
index 00000000..aecd68fa
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_UdpConnection.c
@@ -0,0 +1,396 @@
+/*
+ * 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.
+ */
+
+/**
+ * Embodies the reader/writer for a UDP connection
+ *
+ * NB The Send() function may overflow the output buffer
+ *
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include <ccnx/forwarder/metis/io/metis_UdpConnection.h>
+
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+#include <ccnx/forwarder/metis/core/metis_Connection.h>
+#include <ccnx/forwarder/metis/core/metis_Message.h>
+#include <parc/algol/parc_Hash.h>
+
+#include <LongBow/runtime.h>
+#include <parc/algol/parc_Memory.h>
+#include <ccnx/forwarder/metis/tlv/metis_Tlv.h>
+
+typedef struct metis_udp_state {
+ MetisForwarder *metis;
+ MetisLogger *logger;
+
+ // the udp listener socket we receive packets on
+ int udpListenerSocket;
+
+ MetisAddressPair *addressPair;
+
+ // We need to access this all the time, so grab it out
+ // of the addressPair;
+ struct sockaddr *peerAddress;
+ socklen_t peerAddressLength;
+
+ bool isLocal;
+ bool isUp;
+ unsigned id;
+
+ unsigned delay;
+} _MetisUdpState;
+
+// Prototypes
+static bool _send(MetisIoOperations *ops, const CPIAddress *nexthop, MetisMessage *message);
+static const CPIAddress *_getRemoteAddress(const MetisIoOperations *ops);
+static const MetisAddressPair *_getAddressPair(const MetisIoOperations *ops);
+static unsigned _getConnectionId(const MetisIoOperations *ops);
+static bool _isUp(const MetisIoOperations *ops);
+static bool _isLocal(const MetisIoOperations *ops);
+static void _destroy(MetisIoOperations **opsPtr);
+static CPIConnectionType _getConnectionType(const MetisIoOperations *ops);
+static MetisTicks _sendProbe(MetisIoOperations *ops, unsigned probeType);
+/*
+ * This assigns a unique pointer to the void * which we use
+ * as a GUID for this class.
+ */
+static const void *_metisIoOperationsGuid = __FILE__;
+
+/*
+ * Return our GUID
+ */
+static const void *
+_metisStreamConnection_Class(const MetisIoOperations *ops)
+{
+ return _metisIoOperationsGuid;
+}
+
+static MetisIoOperations _template = {
+ .closure = NULL,
+ .send = &_send,
+ .getRemoteAddress = &_getRemoteAddress,
+ .getAddressPair = &_getAddressPair,
+ .getConnectionId = &_getConnectionId,
+ .isUp = &_isUp,
+ .isLocal = &_isLocal,
+ .destroy = &_destroy,
+ .class = &_metisStreamConnection_Class,
+ .getConnectionType = &_getConnectionType,
+ .sendProbe = &_sendProbe
+};
+
+// =================================================================
+
+static void _setConnectionState(_MetisUdpState *Udp, bool isUp);
+static bool _saveSockaddr(_MetisUdpState *udpConnState, const MetisAddressPair *pair);
+
+MetisIoOperations *
+metisUdpConnection_Create(MetisForwarder *metis, int fd, const MetisAddressPair *pair, bool isLocal)
+{
+ MetisIoOperations *io_ops = NULL;
+
+ _MetisUdpState *udpConnState = parcMemory_AllocateAndClear(sizeof(_MetisUdpState));
+ assertNotNull(udpConnState, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisUdpState));
+
+ udpConnState->metis = metis;
+ udpConnState->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis));
+
+ bool saved = _saveSockaddr(udpConnState, pair);
+ if (saved) {
+ udpConnState->udpListenerSocket = fd;
+ udpConnState->id = metisForwarder_GetNextConnectionId(metis);
+ udpConnState->addressPair = metisAddressPair_Acquire(pair);
+ udpConnState->isLocal = isLocal;
+
+ // allocate a connection
+ io_ops = parcMemory_AllocateAndClear(sizeof(MetisIoOperations));
+ assertNotNull(io_ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisIoOperations));
+ memcpy(io_ops, &_template, sizeof(MetisIoOperations));
+ io_ops->closure = udpConnState;
+
+ _setConnectionState(udpConnState, true);
+
+ if (metisLogger_IsLoggable(udpConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Info)) {
+ char *str = metisAddressPair_ToString(udpConnState->addressPair);
+ metisLogger_Log(udpConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__,
+ "UdpConnection %p created for address %s (isLocal %d)",
+ (void *) udpConnState, str, udpConnState->isLocal);
+ free(str);
+ }
+
+ metisMessenger_Send(metisForwarder_GetMessenger(metis), metisMissive_Create(MetisMissiveType_ConnectionCreate, udpConnState->id));
+ metisMessenger_Send(metisForwarder_GetMessenger(metis), metisMissive_Create(MetisMissiveType_ConnectionUp, udpConnState->id));
+ } else {
+ // _saveSockaddr will already log an error, no need for extra log message here
+ metisLogger_Release(&udpConnState->logger);
+ parcMemory_Deallocate((void **) &udpConnState);
+ }
+
+ return io_ops;
+}
+
+// =================================================================
+// I/O Operations implementation
+
+static void
+_destroy(MetisIoOperations **opsPtr)
+{
+ assertNotNull(opsPtr, "Parameter opsPtr must be non-null double pointer");
+ assertNotNull(*opsPtr, "Parameter opsPtr must dereference to non-null pointer");
+
+ MetisIoOperations *ops = *opsPtr;
+ assertNotNull(metisIoOperations_GetClosure(ops), "ops->context must not be null");
+
+ _MetisUdpState *udpConnState = (_MetisUdpState *) metisIoOperations_GetClosure(ops);
+ metisAddressPair_Release(&udpConnState->addressPair);
+ parcMemory_Deallocate((void **) &(udpConnState->peerAddress));
+
+ metisMessenger_Send(metisForwarder_GetMessenger(udpConnState->metis), metisMissive_Create(MetisMissiveType_ConnectionDestroyed, udpConnState->id));
+
+ if (metisLogger_IsLoggable(udpConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Info)) {
+ metisLogger_Log(udpConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__,
+ "UdpConnection %p destroyed",
+ (void *) udpConnState);
+ }
+
+ // do not close udp->udpListenerSocket, the listener will close
+ // that when its done
+
+ metisLogger_Release(&udpConnState->logger);
+ parcMemory_Deallocate((void **) &udpConnState);
+ parcMemory_Deallocate((void **) &ops);
+
+ *opsPtr = NULL;
+}
+
+static bool
+_isUp(const MetisIoOperations *ops)
+{
+ assertNotNull(ops, "Parameter must be non-null");
+ const _MetisUdpState *udpConnState = (const _MetisUdpState *) metisIoOperations_GetClosure(ops);
+ return udpConnState->isUp;
+}
+
+static bool
+_isLocal(const MetisIoOperations *ops)
+{
+ assertNotNull(ops, "Parameter must be non-null");
+ const _MetisUdpState *udpConnState = (const _MetisUdpState *) metisIoOperations_GetClosure(ops);
+ return udpConnState->isLocal;
+}
+
+static const CPIAddress *
+_getRemoteAddress(const MetisIoOperations *ops)
+{
+ assertNotNull(ops, "Parameter must be non-null");
+ const _MetisUdpState *udpConnState = (const _MetisUdpState *) metisIoOperations_GetClosure(ops);
+ return metisAddressPair_GetRemote(udpConnState->addressPair);
+}
+
+static const MetisAddressPair *
+_getAddressPair(const MetisIoOperations *ops)
+{
+ assertNotNull(ops, "Parameter must be non-null");
+ const _MetisUdpState *udpConnState = (const _MetisUdpState *) metisIoOperations_GetClosure(ops);
+ return udpConnState->addressPair;
+}
+
+static unsigned
+_getConnectionId(const MetisIoOperations *ops)
+{
+ assertNotNull(ops, "Parameter must be non-null");
+ const _MetisUdpState *udpConnState = (const _MetisUdpState *) metisIoOperations_GetClosure(ops);
+ return udpConnState->id;
+}
+
+/**
+ * @function metisUdpConnection_Send
+ * @abstract Non-destructive send of the message.
+ * @discussion
+ * sends a message to the peer.
+ *
+ * @param dummy is ignored. A udp connection has only one peer.
+ * @return <#return#>
+ */
+static bool
+_send(MetisIoOperations *ops, const CPIAddress *dummy, MetisMessage *message)
+{
+ assertNotNull(ops, "Parameter ops must be non-null");
+ assertNotNull(message, "Parameter message must be non-null");
+ _MetisUdpState *udpConnState = (_MetisUdpState *) metisIoOperations_GetClosure(ops);
+
+ PARCEventBuffer *writeBuffer = parcEventBuffer_Create();
+ metisMessage_Append(writeBuffer, message);
+
+ const uint8_t *buffer = parcEventBuffer_Pullup(writeBuffer, -1);
+ size_t bufferLength = parcEventBuffer_GetLength(writeBuffer);
+
+ ssize_t writeLength = sendto(udpConnState->udpListenerSocket, buffer, bufferLength, 0, udpConnState->peerAddress, udpConnState->peerAddressLength);
+
+ if (writeLength < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ parcEventBuffer_Destroy(&writeBuffer);
+ return false;
+ } else {
+ //this print is for debugging
+ printf("Incorrect write length %zd, expected %zd: (%d) %s\n", writeLength, bufferLength, errno, strerror(errno));
+ parcEventBuffer_Destroy(&writeBuffer);
+ return false;
+ }
+ }
+
+ parcEventBuffer_Destroy(&writeBuffer);
+ return true;
+}
+
+static CPIConnectionType
+_getConnectionType(const MetisIoOperations *ops)
+{
+ return cpiConnection_UDP;
+}
+
+static MetisTicks
+_sendProbe(MetisIoOperations *ops, unsigned probeType)
+{
+ assertNotNull(ops, "Parameter ops must be non-null");
+ _MetisUdpState *udpConnState = (_MetisUdpState *) metisIoOperations_GetClosure(ops);
+
+
+ uint8_t *pkt;
+ size_t pkt_size = 8;
+ pkt = (uint8_t *) malloc(sizeof(uint8_t) * pkt_size);
+ for (unsigned i = 0; i < pkt_size; i++) {
+ pkt[i] = 0;
+ }
+ pkt[0] = 1; //tlv type
+ pkt[1] = probeType; //packet type
+ pkt[6] = 8; //header len (16bit, network order)
+
+ ssize_t writeLen = sendto(udpConnState->udpListenerSocket, pkt, pkt_size, 0, udpConnState->peerAddress, udpConnState->peerAddressLength);
+
+ if (writeLen < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ free(pkt);
+ return 0;
+ } else {
+ //this print is for debugging
+ printf("Incorrect write length %zd, expected %zd: (%d) %s\n", writeLen, pkt_size, errno, strerror(errno));
+ free(pkt);
+ return 0;
+ }
+ }
+
+ free(pkt);
+ return metisForwarder_GetTicks(udpConnState->metis);
+}
+
+/*static MetisTicks
+ * _handleProbe(MetisIoOperations *ops, MetisTicks last_sent, uint8_t *pkt)
+ * {
+ * assertNotNull(ops, "Parameter ops must be non-null");
+ * assertNotNull(pkt, "Parameter pkt must be non-null");
+ *
+ * _MetisUdpState *udpConnState = (_MetisUdpState *) metisIoOperations_GetClosure(ops);
+ *
+ * MetisTicks delay = 0;
+ *
+ * if(pkt[1] == METIS_PACKET_TYPE_PROBE_REQUEST){
+ * _sendProbeType(ops, METIS_PACKET_TYPE_PROBE_REPLY);
+ * } else if (pkt[1] == METIS_PACKET_TYPE_PROBE_REPLY) {
+ * MetisTicks now = metisForwarder_GetTicks(udpConnState->metis);
+ * delay = now - last_sent;
+ * } else {
+ * printf("receivde unkwon probe type\n");
+ * }
+ *
+ * return delay;
+ * }*/
+
+// =================================================================
+// Internal API
+
+static bool
+_saveSockaddr(_MetisUdpState *udpConnState, const MetisAddressPair *pair)
+{
+ bool success = false;
+ const CPIAddress *remoteAddress = metisAddressPair_GetRemote(pair);
+
+ switch (cpiAddress_GetType(remoteAddress)) {
+ case cpiAddressType_INET: {
+ size_t bytes = sizeof(struct sockaddr_in);
+ udpConnState->peerAddress = parcMemory_Allocate(bytes);
+ assertNotNull(udpConnState->peerAddress, "parcMemory_Allocate(%zu) returned NULL", bytes);
+
+ cpiAddress_GetInet(remoteAddress, (struct sockaddr_in *) udpConnState->peerAddress);
+ udpConnState->peerAddressLength = (socklen_t) bytes;
+
+ success = true;
+ break;
+ }
+
+ case cpiAddressType_INET6: {
+ size_t bytes = sizeof(struct sockaddr_in6);
+ udpConnState->peerAddress = parcMemory_Allocate(bytes);
+ assertNotNull(udpConnState->peerAddress, "parcMemory_Allocate(%zu) returned NULL", bytes);
+
+ cpiAddress_GetInet6(remoteAddress, (struct sockaddr_in6 *) udpConnState->peerAddress);
+ udpConnState->peerAddressLength = (socklen_t) bytes;
+
+ success = true;
+ break;
+ }
+
+ default:
+ if (metisLogger_IsLoggable(udpConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) {
+ char *str = cpiAddress_ToString(remoteAddress);
+ metisLogger_Log(udpConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__,
+ "Remote address is not INET or INET6: %s", str);
+ parcMemory_Deallocate((void **) &str);
+ }
+ break;
+ }
+ return success;
+}
+
+static void
+_setConnectionState(_MetisUdpState *udpConnState, bool isUp)
+{
+ assertNotNull(udpConnState, "Parameter Udp must be non-null");
+
+ MetisMessenger *messenger = metisForwarder_GetMessenger(udpConnState->metis);
+
+ bool oldStateIsUp = udpConnState->isUp;
+ udpConnState->isUp = isUp;
+
+ if (oldStateIsUp && !isUp) {
+ // bring connection DOWN
+ MetisMissive *missive = metisMissive_Create(MetisMissiveType_ConnectionDown, udpConnState->id);
+ metisMessenger_Send(messenger, missive);
+ return;
+ }
+
+
+ if (!oldStateIsUp && isUp) {
+ // bring connection UP
+ MetisMissive *missive = metisMissive_Create(MetisMissiveType_ConnectionUp, udpConnState->id);
+ metisMessenger_Send(messenger, missive);
+ return;
+ }
+}
diff --git a/metis/ccnx/forwarder/metis/io/metis_UdpConnection.h b/metis/ccnx/forwarder/metis/io/metis_UdpConnection.h
new file mode 100644
index 00000000..86af9296
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_UdpConnection.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file metis_UdpConnection.h
+ * @brief Represents a UDP connection (socket) for the connection table
+ *
+ * <#Detailed Description#>
+ *
+ */
+
+#ifndef Metis_metis_UdpConnection_h
+#define Metis_metis_UdpConnection_h
+
+#include <ccnx/forwarder/metis/io/metis_IoOperations.h>
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+#include <ccnx/forwarder/metis/io/metis_AddressPair.h>
+#include <ccnx/api/control/cpi_Address.h>
+
+/**
+ * Creates a UDP connection that can send to the remote address
+ *
+ * The address pair must both be same type (i.e. INET or INET6).
+ *
+ * @param [in] metis An allocated MetisForwarder (saves reference)
+ * @param [in] fd The socket to use
+ * @param [in] pair An allocated address pair for the connection (saves reference)
+ * @param [in] isLocal determines if the remote address is on the current system
+ *
+ * @retval non-null An allocated Io operations
+ * @retval null An error
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisIoOperations *metisUdpConnection_Create(MetisForwarder *metis, int fd, const MetisAddressPair *pair, bool isLocal);
+#endif // Metis_metis_UdpConnection_h
diff --git a/metis/ccnx/forwarder/metis/io/metis_UdpController.h b/metis/ccnx/forwarder/metis/io/metis_UdpController.h
new file mode 100644
index 00000000..db27a0e1
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_UdpController.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+#ifndef Metis_metis_UdpController_h
+#define Metis_metis_UdpController_h
+#endif // Metis_metis_UdpController_h
diff --git a/metis/ccnx/forwarder/metis/io/metis_UdpListener.c b/metis/ccnx/forwarder/metis/io/metis_UdpListener.c
new file mode 100644
index 00000000..1bd54187
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_UdpListener.c
@@ -0,0 +1,599 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include <config.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+
+#include <ccnx/forwarder/metis/io/metis_UdpListener.h>
+#include <ccnx/forwarder/metis/io/metis_UdpConnection.h>
+
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+#include <ccnx/forwarder/metis/core/metis_Connection.h>
+#include <ccnx/forwarder/metis/tlv/metis_Tlv.h>
+#include <ccnx/forwarder/metis/core/metis_Message.h>
+#include <ccnx/forwarder/metis/messenger/metis_Messenger.h>
+
+#include <ccnx/forwarder/metis/core/metis_Wldr.h>
+
+#include <LongBow/runtime.h>
+#include <parc/algol/parc_Memory.h>
+
+typedef struct metis_udp_stats {
+ uint64_t framesIn;
+ uint64_t framesError;
+ uint64_t framesReceived;
+} _MetisUdpStats;
+
+struct metis_udp_listener {
+ MetisForwarder *metis;
+ MetisLogger *logger;
+
+ //MetisNetworkEvent *udp_event;
+ PARCEvent *udp_event;
+ MetisSocketType udp_socket;
+ uint16_t port;
+
+ unsigned id;
+ CPIAddress *localAddress;
+
+ _MetisUdpStats stats;
+};
+
+static void _destroy(MetisListenerOps **listenerOpsPtr);
+static unsigned _getInterfaceIndex(const MetisListenerOps *ops);
+static const CPIAddress *_getListenAddress(const MetisListenerOps *ops);
+static MetisEncapType _getEncapType(const MetisListenerOps *ops);
+static int _getSocket(const MetisListenerOps *ops);
+
+static MetisListenerOps udpTemplate = {
+ .context = NULL,
+ .destroy = &_destroy,
+ .getInterfaceIndex = &_getInterfaceIndex,
+ .getListenAddress = &_getListenAddress,
+ .getEncapType = &_getEncapType,
+ .getSocket = &_getSocket
+};
+
+static void _readcb(int fd, PARCEventType what, void *udpVoid);
+
+MetisListenerOps *
+metisUdpListener_CreateInet6(MetisForwarder *metis, struct sockaddr_in6 sin6)
+{
+ MetisListenerOps *ops = NULL;
+
+ MetisUdpListener *udp = parcMemory_AllocateAndClear(sizeof(MetisUdpListener));
+ assertNotNull(udp, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisUdpListener));
+ udp->metis = metis;
+ udp->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis));
+ udp->localAddress = cpiAddress_CreateFromInet6(&sin6);
+ udp->id = metisForwarder_GetNextConnectionId(metis);
+
+ udp->udp_socket = socket(AF_INET6, SOCK_DGRAM, 0);
+ assertFalse(udp->udp_socket < 0, "Error opening UDP socket: (%d) %s", errno, strerror(errno));
+
+ // Set non-blocking flag
+ int flags = fcntl(udp->udp_socket, F_GETFL, NULL);
+ assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)", errno);
+ int failure = fcntl(udp->udp_socket, F_SETFL, flags | O_NONBLOCK);
+ assertFalse(failure, "fcntl failed to set file descriptor flags (%d)", errno);
+
+ int one = 1;
+ // don't hang onto address after listener has closed
+ failure = setsockopt(udp->udp_socket, SOL_SOCKET, SO_REUSEADDR, (void *) &one, (socklen_t) sizeof(one));
+ assertFalse(failure, "failed to set REUSEADDR on socket(%d)", errno);
+
+ failure = bind(udp->udp_socket, (struct sockaddr *) &sin6, sizeof(sin6));
+ if (failure == 0) {
+ udp->udp_event = metisDispatcher_CreateNetworkEvent(metisForwarder_GetDispatcher(metis), true, _readcb, (void *) udp, udp->udp_socket);
+ metisDispatcher_StartNetworkEvent(metisForwarder_GetDispatcher(metis), udp->udp_event);
+
+ ops = parcMemory_AllocateAndClear(sizeof(MetisListenerOps));
+ assertNotNull(ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisListenerOps));
+ memcpy(ops, &udpTemplate, sizeof(MetisListenerOps));
+ ops->context = udp;
+
+ if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) {
+ char *str = cpiAddress_ToString(udp->localAddress);
+ metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "UdpListener %p created for address %s",
+ (void *) udp, str);
+ parcMemory_Deallocate((void **) &str);
+ }
+ } else {
+ if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) {
+ int myerrno = errno;
+ char *str = cpiAddress_ToString(udp->localAddress);
+ metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__,
+ "Error binding UDP socket to address %s: (%d) %s", str, myerrno, strerror(myerrno));
+ parcMemory_Deallocate((void **) &str);
+ }
+
+ close(udp->udp_socket);
+ cpiAddress_Destroy(&udp->localAddress);
+ metisLogger_Release(&udp->logger);
+ parcMemory_Deallocate((void **) &udp);
+ }
+
+ return ops;
+}
+
+MetisListenerOps *
+metisUdpListener_CreateInet(MetisForwarder *metis, struct sockaddr_in sin)
+{
+ MetisListenerOps *ops = NULL;
+
+ MetisUdpListener *udp = parcMemory_AllocateAndClear(sizeof(MetisUdpListener));
+ assertNotNull(udp, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisUdpListener));
+ udp->metis = metis;
+ udp->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis));
+ udp->localAddress = cpiAddress_CreateFromInet(&sin);
+ udp->id = metisForwarder_GetNextConnectionId(metis);
+
+ udp->udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
+ assertFalse(udp->udp_socket < 0, "Error opening UDP socket: (%d) %s", errno, strerror(errno));
+
+ // Set non-blocking flag
+ int flags = fcntl(udp->udp_socket, F_GETFL, NULL);
+ assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)", errno);
+ int failure = fcntl(udp->udp_socket, F_SETFL, flags | O_NONBLOCK);
+ assertFalse(failure, "fcntl failed to set file descriptor flags (%d)", errno);
+
+ int one = 1;
+ // don't hang onto address after listener has closed
+ failure = setsockopt(udp->udp_socket, SOL_SOCKET, SO_REUSEADDR, (void *) &one, (socklen_t) sizeof(one));
+ assertFalse(failure, "failed to set REUSEADDR on socket(%d)", errno);
+
+ failure = bind(udp->udp_socket, (struct sockaddr *) &sin, sizeof(sin));
+ if (failure == 0) {
+ udp->udp_event = metisDispatcher_CreateNetworkEvent(metisForwarder_GetDispatcher(metis), true, _readcb, (void *) udp, udp->udp_socket);
+ metisDispatcher_StartNetworkEvent(metisForwarder_GetDispatcher(metis), udp->udp_event);
+
+ ops = parcMemory_AllocateAndClear(sizeof(MetisListenerOps));
+ assertNotNull(ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisListenerOps));
+ memcpy(ops, &udpTemplate, sizeof(MetisListenerOps));
+ ops->context = udp;
+
+ if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) {
+ char *str = cpiAddress_ToString(udp->localAddress);
+ metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "UdpListener %p created for address %s",
+ (void *) udp, str);
+ parcMemory_Deallocate((void **) &str);
+ }
+ } else {
+ if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) {
+ int myerrno = errno;
+ char *str = cpiAddress_ToString(udp->localAddress);
+ metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__,
+ "Error binding UDP socket to address %s: (%d) %s", str, myerrno, strerror(myerrno));
+ parcMemory_Deallocate((void **) &str);
+ }
+
+ close(udp->udp_socket);
+ cpiAddress_Destroy(&udp->localAddress);
+ metisLogger_Release(&udp->logger);
+ parcMemory_Deallocate((void **) &udp);
+ }
+
+ return ops;
+}
+
+static void
+metisUdpListener_Destroy(MetisUdpListener **listenerPtr)
+{
+ assertNotNull(listenerPtr, "Parameter must be non-null double pointer");
+ assertNotNull(*listenerPtr, "Parameter must derefernce to non-null pointer");
+
+ MetisUdpListener *udp = *listenerPtr;
+
+ if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) {
+ metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "UdpListener %p destroyed",
+ (void *) udp);
+ }
+
+ close(udp->udp_socket);
+ cpiAddress_Destroy(&udp->localAddress);
+ metisDispatcher_DestroyNetworkEvent(metisForwarder_GetDispatcher(udp->metis), &udp->udp_event);
+ metisLogger_Release(&udp->logger);
+ parcMemory_Deallocate((void **) &udp);
+ *listenerPtr = NULL;
+}
+
+static void
+_destroy(MetisListenerOps **listenerOpsPtr)
+{
+ MetisListenerOps *ops = *listenerOpsPtr;
+ MetisUdpListener *udp = (MetisUdpListener *) ops->context;
+ metisUdpListener_Destroy(&udp);
+ parcMemory_Deallocate((void **) &ops);
+ *listenerOpsPtr = NULL;
+}
+
+static unsigned
+_getInterfaceIndex(const MetisListenerOps *ops)
+{
+ MetisUdpListener *udp = (MetisUdpListener *) ops->context;
+ return udp->id;
+}
+
+static const CPIAddress *
+_getListenAddress(const MetisListenerOps *ops)
+{
+ MetisUdpListener *udp = (MetisUdpListener *) ops->context;
+ return udp->localAddress;
+}
+
+static MetisEncapType
+_getEncapType(const MetisListenerOps *ops)
+{
+ return METIS_ENCAP_UDP;
+}
+
+static int
+_getSocket(const MetisListenerOps *ops)
+{
+ MetisUdpListener *udp = (MetisUdpListener *) ops->context;
+ return (int) udp->udp_socket;
+}
+
+static void
+_logStats(MetisUdpListener *udp, PARCLogLevel level)
+{
+ if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, level)) {
+ metisLogger_Log(udp->logger, MetisLoggerFacility_IO, level, __func__,
+ "UdpListener %p frames in %" PRIu64 ", errors %" PRIu64 " ok %" PRIu64,
+ (void *) udp,
+ udp->stats.framesIn,
+ udp->stats.framesError,
+ udp->stats.framesReceived);
+ }
+}
+
+
+// =====================================================================
+
+
+static void _receiveProbeMessage(MetisUdpListener *udp, int fd, uint8_t *pkt, struct sockaddr *peerIpAddress, socklen_t peerIpAddressLength);
+
+
+/**
+ * @function peekMesageLength
+ * @abstract Peek at the next packet to learn its length by reading the fixed header
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+static size_t
+_peekMessageLength(MetisUdpListener *udp, int fd, struct sockaddr *peerIpAddress, socklen_t *peerIpAddressLengthPtr)
+{
+ size_t packetLength = 0;
+
+ uint8_t fixedHeader[metisTlv_FixedHeaderLength()];
+
+ // peek at the UDP packet and read in the fixed header.
+ // Also returns the socket information for the remote peer
+
+ uint8_t wldr_flag[1];
+ ssize_t checkWldrHeader = recvfrom(fd, wldr_flag, 1, MSG_PEEK, (struct sockaddr *) peerIpAddress, peerIpAddressLengthPtr);
+ if (checkWldrHeader == -1) {
+ return -1;
+ }
+ ssize_t readLength;
+ if (wldr_flag[0] == WLDR_HEADER) {
+ //the message contains wldr header
+ uint8_t tmp[metisTlv_FixedHeaderLength() + WLDR_HEADER_SIZE];
+ readLength = recvfrom(fd, tmp, metisTlv_FixedHeaderLength() + WLDR_HEADER_SIZE, MSG_PEEK, (struct sockaddr *) peerIpAddress, peerIpAddressLengthPtr);
+ if (readLength == (metisTlv_FixedHeaderLength() + WLDR_HEADER_SIZE)) {
+ for (int i = 6; i < metisTlv_FixedHeaderLength() + WLDR_HEADER_SIZE; ++i) {
+ fixedHeader[i - WLDR_HEADER_SIZE] = tmp[i];
+ }
+ readLength = metisTlv_FixedHeaderLength();
+ } else {
+ readLength = 0;
+ }
+ } else {
+ readLength = recvfrom(fd, fixedHeader, metisTlv_FixedHeaderLength(), MSG_PEEK, (struct sockaddr *) peerIpAddress, peerIpAddressLengthPtr);
+ }
+
+ if (readLength == -1) {
+ return -1;
+ }
+
+ if (readLength == metisTlv_FixedHeaderLength()) {
+ packetLength = metisTlv_TotalPacketLength(fixedHeader);
+ if (packetLength == 0) {
+ uint8_t *pkt;
+ pkt = (uint8_t *) malloc(sizeof(uint8_t) * metisTlv_FixedHeaderLength());
+ for (unsigned i = 0; i < metisTlv_FixedHeaderLength(); i++) {
+ pkt[i] = fixedHeader[i];
+ }
+
+ _receiveProbeMessage(udp, fd, pkt, peerIpAddress, *peerIpAddressLengthPtr);
+
+ free(pkt);
+ }
+ } else {
+ if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning)) {
+ metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__,
+ "read %zu bytes from fd %d, wrong size for a FixedHeader",
+ readLength,
+ fd);
+ }
+
+ if (readLength < 0) {
+ if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) {
+ metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__,
+ "Error reading fd %d: (%d) %s", fd, errno, strerror(errno));
+ }
+ }
+ }
+
+ if (wldr_flag[0] == WLDR_HEADER) {
+ return packetLength + WLDR_HEADER_SIZE;
+ } else {
+ return packetLength;
+ }
+}
+
+static MetisMessage *
+_readMessage(MetisForwarder *metis, unsigned connid, int fd, size_t packetLength)
+{
+ PARCEventBuffer *readbuffer = parcEventBuffer_Create();
+ int readLength = parcEventBuffer_ReadFromFileDescriptor(readbuffer, fd, packetLength);
+
+ MetisMessage *message = NULL;
+ if (readLength == packetLength) {
+ message = metisMessage_CreateFromBuffer(connid, metisForwarder_GetTicks(metis), readbuffer, metisForwarder_GetLogger(metis));
+
+ // because metisMessage_CreateFromBuffer takes ownership of readbuffer, if there is
+ // an error and it returns null, metisMessage_CreateFromBuffer will destroy the readbuffer.
+ } else {
+ parcEventBuffer_Destroy(&readbuffer);
+
+ if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Warning)) {
+ metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__,
+ "read %d bytes from fd %d, expected %zu",
+ readLength,
+ fd,
+ packetLength);
+ }
+
+ if (readLength < 0) {
+ if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error)) {
+ metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error, __func__,
+ "Error reading fd %d: (%d) %s", fd, errno, strerror(errno));
+ }
+ }
+ }
+
+ return message;
+}
+
+/**
+ * @function _constructAddressPair
+ * @abstract Creates the address pair that uniquely identifies the connection
+ * @discussion
+ * The peerIpAddress must be of AF_INET or AF_INET6 family.
+ *
+ * @param <#param1#>
+ * @return Allocated MetisAddressPair, must be destroyed
+ */
+static MetisAddressPair *
+_constructAddressPair(MetisUdpListener *udp, struct sockaddr *peerIpAddress, socklen_t peerIpAddressLength)
+{
+ CPIAddress *remoteAddress;
+
+ switch (peerIpAddress->sa_family) {
+ case AF_INET:
+ remoteAddress = cpiAddress_CreateFromInet((struct sockaddr_in *) peerIpAddress);
+ break;
+
+ case AF_INET6:
+ remoteAddress = cpiAddress_CreateFromInet6((struct sockaddr_in6 *) peerIpAddress);
+ break;
+
+ default:
+ trapIllegalValue(peerIpAddress, "Peer address unrecognized family for IP: %d", peerIpAddress->sa_family);
+ }
+
+ MetisAddressPair *pair = metisAddressPair_Create(udp->localAddress, remoteAddress);
+ cpiAddress_Destroy(&remoteAddress);
+
+ return pair;
+}
+
+/**
+ * @function _lookupConnectionId
+ * @abstract Lookup a connection in the connection table
+ * @discussion
+ * Looks up the connection in the connection table and returns the connection id if it exists.
+ *
+ * @param outputConnectionIdPtr is the output parameter
+ * @return true if connection found and outputConnectionIdPtr set
+ */
+static bool
+_lookupConnectionId(MetisUdpListener *udp, MetisAddressPair *pair, unsigned *outputConnectionIdPtr)
+{
+ MetisConnectionTable *connTable = metisForwarder_GetConnectionTable(udp->metis);
+
+ const MetisConnection *conn = metisConnectionTable_FindByAddressPair(connTable, pair);
+ if (conn) {
+ *outputConnectionIdPtr = metisConnection_GetConnectionId(conn);
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * @function _createNewConnection
+ * @abstract Creates a new Metis connection for the peer
+ * @discussion
+ * PRECONDITION: you know there's not an existing connection with the address pair
+ *
+ * Creates a new connection and adds it to the connection table.
+ *
+ * @param <#param1#>
+ * @return The connection id for the new connection
+ */
+
+//for the moment this is not used anymore
+//we need to handle the connection tables in a better way in order to avoid multiple connections
+//with the same address pair.
+/*static unsigned
+ * _createNewConnection(MetisUdpListener *udp, int fd, const MetisAddressPair *pair)
+ * {
+ * bool isLocal = false;
+ *
+ * // metisUdpConnection_Create takes ownership of the pair
+ * MetisIoOperations *ops = metisUdpConnection_Create(udp->metis, fd, pair, isLocal);
+ * MetisConnection *conn = metisConnection_Create(ops);
+ *
+ * metisConnectionTable_Add(metisForwarder_GetConnectionTable(udp->metis), conn);
+ * unsigned connid = metisIoOperations_GetConnectionId(ops);
+ *
+ * return connid;
+ * }*/
+
+static void
+_receivePacket(MetisUdpListener *udp, int fd, size_t packetLength, struct sockaddr_storage *peerIpAddress, socklen_t peerIpAddressLength)
+{
+ unsigned connid = 0;
+ MetisAddressPair *pair = _constructAddressPair(udp, (struct sockaddr *) peerIpAddress, peerIpAddressLength);
+ bool foundConnection = _lookupConnectionId(udp, pair, &connid);
+
+ if (!foundConnection) {
+ PARCEventBuffer *readbuffer = parcEventBuffer_Create();
+ parcEventBuffer_ReadFromFileDescriptor(readbuffer, fd, packetLength);
+ parcEventBuffer_Destroy(&readbuffer);
+ metisAddressPair_Release(&pair);
+ return;
+ //connid = _createNewConnection(udp, fd, pair);
+ }
+
+ metisAddressPair_Release(&pair);
+
+ MetisMessage *message = _readMessage(udp->metis, connid, fd, packetLength);
+
+ if (message) {
+ udp->stats.framesReceived++;
+
+ if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) {
+ metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "read %zu bytes from fd %d sa %s:%d connid %d",
+ metisMessage_Length(message),
+ fd,
+ inet_ntoa(((struct sockaddr_in *) peerIpAddress)->sin_addr),
+ ntohs(((struct sockaddr_in *) peerIpAddress)->sin_port),
+ connid);
+ }
+
+ _logStats(udp, PARCLogLevel_Debug);
+
+ metisForwarder_Receive(udp->metis, message);
+ } else {
+ udp->stats.framesError++;
+ if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning)) {
+ metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__,
+ "Could not parse frame from fd %d, discarding", fd);
+ }
+ _logStats(udp, PARCLogLevel_Warning);
+ }
+}
+
+
+static void
+_readFrameToDiscard(MetisUdpListener *udp, int fd)
+{
+ // we need to discard the frame. Read 1 byte. This will clear it off the stack.
+ uint8_t buffer;
+ ssize_t nread = read(fd, &buffer, 1);
+
+ udp->stats.framesError++;
+
+ if (nread == 1) {
+ if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) {
+ metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "Discarded frame from fd %d", fd);
+ }
+ _logStats(udp, PARCLogLevel_Debug);
+ } else if (nread < 0) {
+ if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) {
+ metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__,
+ "Error trying to discard frame from fd %d: (%d) %s", fd, errno, strerror(errno));
+ }
+ _logStats(udp, PARCLogLevel_Error);
+ }
+}
+
+static void
+_receiveProbeMessage(MetisUdpListener *udp, int fd, uint8_t *pkt, struct sockaddr *peerIpAddress, socklen_t peerIpAddressLength)
+{
+ MetisAddressPair *pair = _constructAddressPair(udp, peerIpAddress, peerIpAddressLength);
+ MetisConnectionTable *connTable = metisForwarder_GetConnectionTable(udp->metis);
+ const MetisConnection *conn = metisConnectionTable_FindByAddressPair(connTable, pair);
+
+ if (conn == NULL) {
+ metisAddressPair_Release(&pair);
+ return; //we discard probes coming from connections that we don't know. this should never happen.
+ }
+
+ metisAddressPair_Release(&pair);
+
+ //handle the probe.
+ metisConnection_HandleProbe((MetisConnection *) conn, pkt, metisForwarder_GetTicks(udp->metis));
+}
+
+
+static void
+_readcb(int fd, PARCEventType what, void *udpVoid)
+{
+ MetisUdpListener *udp = (MetisUdpListener *) udpVoid;
+
+ if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) {
+ metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__,
+ "%s socket %d what %s%s%s%s data %p",
+ __func__, fd,
+ (what & PARCEventType_Timeout) ? " timeout" : "",
+ (what & PARCEventType_Read) ? " read" : "",
+ (what & PARCEventType_Write) ? " write" : "",
+ (what & PARCEventType_Signal) ? " signal" : "",
+ udpVoid);
+ }
+
+ if (what & PARCEventType_Read) {
+ udp->stats.framesIn++;
+ struct sockaddr_storage peerIpAddress;
+ socklen_t peerIpAddressLength = sizeof(peerIpAddress);
+
+ size_t packetLength = _peekMessageLength(udp, fd, (struct sockaddr *) &peerIpAddress, &peerIpAddressLength);
+
+ if (packetLength > 0) {
+ _receivePacket(udp, fd, packetLength, &peerIpAddress, peerIpAddressLength);
+ } else {
+ _readFrameToDiscard(udp, fd);
+ }
+ }
+}
diff --git a/metis/ccnx/forwarder/metis/io/metis_UdpListener.h b/metis/ccnx/forwarder/metis/io/metis_UdpListener.h
new file mode 100644
index 00000000..d4dc129d
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_UdpListener.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+#ifndef Metis_metis_UdpListener_h
+#define Metis_metis_UdpListener_h
+
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+#include <ccnx/forwarder/metis/io/metis_Listener.h>
+
+struct metis_udp_listener;
+typedef struct metis_udp_listener MetisUdpListener;
+
+MetisListenerOps *metisUdpListener_CreateInet6(MetisForwarder *metis, struct sockaddr_in6 sin6);
+MetisListenerOps *metisUdpListener_CreateInet(MetisForwarder *metis, struct sockaddr_in sin);
+#endif // Metis_metis_UdpListener_h
diff --git a/metis/ccnx/forwarder/metis/io/metis_UdpTunnel.c b/metis/ccnx/forwarder/metis/io/metis_UdpTunnel.c
new file mode 100644
index 00000000..77424dce
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_UdpTunnel.c
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include <config.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+
+#include <ccnx/forwarder/metis/io/metis_UdpTunnel.h>
+#include <ccnx/forwarder/metis/io/metis_UdpConnection.h>
+
+#include <LongBow/runtime.h>
+
+MetisIoOperations *
+metisUdpTunnel_CreateOnListener(MetisForwarder *metis, MetisListenerOps *localListener, const CPIAddress *remoteAddress)
+{
+ assertNotNull(metis, "Parameter metis must be non-null");
+ assertNotNull(localListener, "Parameter localListener must be non-null");
+ assertNotNull(remoteAddress, "Parameter remoteAddress must be non-null");
+
+ MetisLogger *logger = metisForwarder_GetLogger(metis);
+
+ MetisIoOperations *ops = NULL;
+ if (localListener->getEncapType(localListener) == METIS_ENCAP_UDP) {
+ const CPIAddress *localAddress = localListener->getListenAddress(localListener);
+ CPIAddressType localType = cpiAddress_GetType(localAddress);
+ CPIAddressType remoteType = cpiAddress_GetType(remoteAddress);
+
+ if (localType == remoteType) {
+ MetisAddressPair *pair = metisAddressPair_Create(localAddress, remoteAddress);
+ bool isLocal = false;
+ int fd = localListener->getSocket(localListener);
+ ops = metisUdpConnection_Create(metis, fd, pair, isLocal);
+
+ metisAddressPair_Release(&pair);
+ } else {
+ if (metisLogger_IsLoggable(logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) {
+ metisLogger_Log(logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__,
+ "Local listener of type %s and remote type %s, cannot establish tunnel",
+ cpiAddress_TypeToString(localType),
+ cpiAddress_TypeToString(remoteType));
+ }
+ }
+ } else {
+ if (metisLogger_IsLoggable(logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) {
+ metisLogger_Log(logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__,
+ "Local listener %p is not type UDP, cannot establish tunnel", (void *) localListener);
+ }
+ }
+
+ return ops;
+}
+
+/*
+ * wrapper for metisUdpTunnel_CreateOnListener. Lookup to see if we have a listener on the local address.
+ * If so, call metisUdpTunnel_CreateOnListener, otherwise return NULL
+ */
+MetisIoOperations *
+metisUdpTunnel_Create(MetisForwarder *metis, const CPIAddress *localAddress, const CPIAddress *remoteAddress)
+{
+ MetisListenerSet *set = metisForwarder_GetListenerSet(metis);
+ MetisListenerOps *listener = metisListenerSet_Find(set, METIS_ENCAP_UDP, localAddress);
+ MetisIoOperations *ops = NULL;
+ if (listener) {
+ ops = metisUdpTunnel_CreateOnListener(metis, listener, remoteAddress);
+ } else {
+ if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error)) {
+ char *str = cpiAddress_ToString(localAddress);
+ metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error, __func__,
+ "Could not find listener to match address %s", str);
+ parcMemory_Deallocate((void **) &str);
+ }
+ }
+ return ops;
+}
+
diff --git a/metis/ccnx/forwarder/metis/io/metis_UdpTunnel.h b/metis/ccnx/forwarder/metis/io/metis_UdpTunnel.h
new file mode 100644
index 00000000..8043da7c
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/io/metis_UdpTunnel.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file metis_UdpTunnel.h
+ * @brief Establish a tunnel to a remote system
+ *
+ * Creates a "udp tunnel" to a remote system. There must already be a local UDP listener for the
+ * local side of the connection. Because UDP is connectionless and we do not have a link protocol,
+ * the udp tunnel will go in the connection table immediately in the "up" state.
+ *
+ */
+
+#ifndef Metis_metis_UdpTunnel_h
+#define Metis_metis_UdpTunnel_h
+
+#include <ccnx/api/control/cpi_Address.h>
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+#include <ccnx/forwarder/metis/io/metis_Listener.h>
+#include <ccnx/forwarder/metis/io/metis_IoOperations.h>
+
+/**
+ * Establishes a connection to a remote system over UDP
+ *
+ * The remoteAddress must be of the same type (i.e. v4 or v6) as the localListener.
+ *
+ * The connection will go in the table immediately, and will be in the "up" state.
+ *
+ *
+ * @param [in] metis An allocated MetisForwarder
+ * @param [in] localListener The local receiver for UDP messages
+ * @param [in] remote Address the remote IP address for the connection, must include a destination port.
+ *
+ * @retval non-null An allocated Io Operations structure for the connection
+ * @retval null An error
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisIoOperations *metisUdpTunnel_CreateOnListener(MetisForwarder *metis, MetisListenerOps *localListener, const CPIAddress *remoteAddress);
+
+/**
+ * Establishes a connection to a remote system over UDP
+ *
+ * The remoteAddress must be of the same type (i.e. v4 or v6) as the localAddress. There must be an existing UDP listener
+ * on the local address. If either of these are not true, will return NULL.
+ *
+ * The connection will go in the table immediately, and will be in the "up" state.
+ *
+ * This function will lookup the appropraite listener, then use metisUdpTunnel_CreateOnListener().
+ *
+ * @param [in] metis An allocated MetisForwarder
+ * @param [in] localAddress The local IP address and port to use for the connection
+ * @param [in] remote Address the remote IP address for the connection, must include a destination port.
+ *
+ * @retval non-null An allocated Io Operations structure for the connection
+ * @retval null An error
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisIoOperations *metisUdpTunnel_Create(MetisForwarder *metis, const CPIAddress *localAddress, const CPIAddress *remoteAddress);
+
+#endif // Metis_metis_UdpTunnel_h
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(&copy);
+}
+
+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(&ether->logger);
+ parcDeque_Release(&ether->inputQueue);
+ parcBuffer_Release(&ether->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;
+}