aboutsummaryrefslogtreecommitdiffstats
path: root/metis/ccnx/forwarder/metis/core
diff options
context:
space:
mode:
Diffstat (limited to 'metis/ccnx/forwarder/metis/core')
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_Connection.c257
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_Connection.h224
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_ConnectionList.c82
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_ConnectionList.h102
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_ConnectionManager.c293
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_ConnectionManager.h45
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_ConnectionTable.c252
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_ConnectionTable.h134
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_Dispatcher.c425
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_Dispatcher.h303
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_Forwarder.c506
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_Forwarder.h340
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_Logger.c189
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_Logger.h228
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_Message.c992
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_Message.h859
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_MessagePacketType.h36
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_NumberSet.c225
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_NumberSet.h212
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_StreamBuffer.c153
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_StreamBuffer.h141
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_System.h80
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_ThreadedForwarder.c247
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_ThreadedForwarder.h111
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_Ticks.h32
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_Wldr.c174
-rw-r--r--metis/ccnx/forwarder/metis/core/metis_Wldr.h49
-rw-r--r--metis/ccnx/forwarder/metis/core/test/CMakeLists.txt22
-rw-r--r--metis/ccnx/forwarder/metis/core/test/test_metis_Connection.c227
-rw-r--r--metis/ccnx/forwarder/metis/core/test/test_metis_ConnectionList.c159
-rw-r--r--metis/ccnx/forwarder/metis/core/test/test_metis_ConnectionManager.c261
-rw-r--r--metis/ccnx/forwarder/metis/core/test/test_metis_ConnectionTable.c490
-rw-r--r--metis/ccnx/forwarder/metis/core/test/test_metis_Dispatcher.c733
-rw-r--r--metis/ccnx/forwarder/metis/core/test/test_metis_Forwarder.c241
-rw-r--r--metis/ccnx/forwarder/metis/core/test/test_metis_Logger.c222
-rw-r--r--metis/ccnx/forwarder/metis/core/test/test_metis_Message.c946
-rw-r--r--metis/ccnx/forwarder/metis/core/test/test_metis_NumberSet.c557
-rw-r--r--metis/ccnx/forwarder/metis/core/test/test_metis_StreamBuffer.c134
-rw-r--r--metis/ccnx/forwarder/metis/core/test/test_metis_ThreadedForwarder.c145
-rw-r--r--metis/ccnx/forwarder/metis/core/test/testrig_MetisIoOperations.h224
40 files changed, 11052 insertions, 0 deletions
diff --git a/metis/ccnx/forwarder/metis/core/metis_Connection.c b/metis/ccnx/forwarder/metis/core/metis_Connection.c
new file mode 100644
index 00000000..c3f28e14
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_Connection.c
@@ -0,0 +1,257 @@
+/*
+ * 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 <limits.h>
+
+#include <ccnx/forwarder/metis/io/metis_IoOperations.h>
+#include <ccnx/forwarder/metis/core/metis_Connection.h>
+#include <ccnx/forwarder/metis/io/metis_AddressPair.h>
+#include <ccnx/forwarder/metis/core/metis_Wldr.h>
+#include <ccnx/forwarder/metis/core/metis_Ticks.h>
+#include <parc/algol/parc_Memory.h>
+#include <LongBow/runtime.h>
+
+struct metis_connection {
+ const MetisAddressPair *addressPair;
+ MetisIoOperations *ops;
+
+ unsigned refCount;
+
+ bool probing_active;
+ unsigned probing_interval;
+ unsigned counter;
+ MetisTicks last_sent;
+ MetisTicks delay;
+
+ MetisWldr *wldr;
+};
+
+MetisConnection *
+metisConnection_Create(MetisIoOperations *ops)
+{
+ assertNotNull(ops, "Parameter ops must be non-null");
+ MetisConnection *conn = parcMemory_AllocateAndClear(sizeof(MetisConnection));
+ assertNotNull(conn, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisConnection));
+ conn->addressPair = metisIoOperations_GetAddressPair(ops);
+ conn->ops = ops;
+ conn->refCount = 1;
+ conn->wldr = NULL;
+ conn->probing_active = false;
+ conn->probing_interval = 0;
+ conn->counter = 0;
+ conn->last_sent = 0;
+ conn->delay = INT_MAX;
+ return conn;
+}
+
+MetisConnection *
+metisConnection_Acquire(MetisConnection *connection)
+{
+ assertNotNull(connection, "Parameter conn must be non-null");
+ connection->refCount++;
+ return connection;
+}
+
+void
+metisConnection_Release(MetisConnection **connectionPtr)
+{
+ assertNotNull(connectionPtr, "Parameter must be non-null double pointer");
+ assertNotNull(*connectionPtr, "Parameter must dereference to non-null pointer");
+ MetisConnection *conn = *connectionPtr;
+
+ assertTrue(conn->refCount > 0, "Invalid state, connection reference count should be positive, got 0.");
+ conn->refCount--;
+ if (conn->refCount == 0) {
+ // don't destroy addressPair, its part of ops.
+ metisIoOperations_Release(&conn->ops);
+ if (conn->wldr != NULL) {
+ metisWldr_Destroy(&(conn->wldr));
+ }
+ parcMemory_Deallocate((void **) &conn);
+ }
+ *connectionPtr = NULL;
+}
+
+bool
+metisConnection_Send(const MetisConnection *conn, MetisMessage *message)
+{
+ assertNotNull(conn, "Parameter conn must be non-null");
+ assertNotNull(message, "Parameter message must be non-null");
+
+ if (metisIoOperations_IsUp(conn->ops)) {
+ uint8_t connectionId = (uint8_t) metisConnection_GetConnectionId(conn);
+ metisMessage_UpdatePathLabel(message, connectionId);
+ if (conn->wldr != NULL) {
+ metisWldr_SetLabel(conn->wldr, message);
+ }
+ return metisIoOperations_Send(conn->ops, NULL, message);
+ }
+ return false;
+}
+
+
+static void
+_sendProbe(MetisConnection *conn, unsigned probeType)
+{
+ assertNotNull(conn, "Parameter conn must be non-null");
+
+ if (probeType == METIS_PACKET_TYPE_PROBE_REQUEST) {
+ MetisTicks now = metisIoOperations_SendProbe(conn->ops, probeType);
+ if (now != 0) {
+ conn->last_sent = now;
+ }
+ } else {
+ metisIoOperations_SendProbe(conn->ops, probeType);
+ }
+}
+
+
+void
+metisConnection_Probe(MetisConnection *conn)
+{
+ _sendProbe(conn, METIS_PACKET_TYPE_PROBE_REQUEST);
+}
+
+void
+metisConnection_HandleProbe(MetisConnection *conn, uint8_t *pkt, MetisTicks actualTime)
+{
+ assertNotNull(conn, "Parameter conn must be non-null");
+ assertNotNull(pkt, "Parameter pkt must be non-null");
+
+ if (pkt[1] == METIS_PACKET_TYPE_PROBE_REQUEST) {
+ _sendProbe(conn, METIS_PACKET_TYPE_PROBE_REPLY);
+ } else if (pkt[1] == METIS_PACKET_TYPE_PROBE_REPLY) {
+ MetisTicks delay = actualTime - conn->last_sent;
+ if (delay == 0) {
+ delay = 1;
+ }
+ if (delay < conn->delay) {
+ conn->delay = delay;
+ }
+ } else {
+ printf("receivde unkwon probe type\n");
+ }
+}
+
+uint64_t
+metisConnection_GetDelay(MetisConnection *conn)
+{
+ return (uint64_t) conn->delay;
+}
+
+
+MetisIoOperations *
+metisConnection_GetIoOperations(const MetisConnection *conn)
+{
+ return conn->ops;
+}
+
+unsigned
+metisConnection_GetConnectionId(const MetisConnection *conn)
+{
+ assertNotNull(conn, "Parameter conn must be non-null");
+ return metisIoOperations_GetConnectionId(conn->ops);
+}
+
+const MetisAddressPair *
+metisConnection_GetAddressPair(const MetisConnection *conn)
+{
+ assertNotNull(conn, "Parameter conn must be non-null");
+ return metisIoOperations_GetAddressPair(conn->ops);
+}
+
+bool
+metisConnection_IsUp(const MetisConnection *conn)
+{
+ assertNotNull(conn, "Parameter conn must be non-null");
+ return metisIoOperations_IsUp(conn->ops);
+}
+
+bool
+metisConnection_IsLocal(const MetisConnection *conn)
+{
+ assertNotNull(conn, "Parameter conn must be non-null");
+ return metisIoOperations_IsLocal(conn->ops);
+}
+
+const void *
+metisConnection_Class(const MetisConnection *conn)
+{
+ assertNotNull(conn, "Parameter conn must be non-null");
+ return metisIoOperations_Class(conn->ops);
+}
+
+bool
+metisConnection_ReSend(const MetisConnection *conn, MetisMessage *message)
+{
+ assertNotNull(conn, "Parameter conn must be non-null");
+ assertNotNull(message, "Parameter message must be non-null");
+
+ if (metisConnection_IsUp(conn)) {
+ //here the wldr header is alreay set: this message is a retransmission or a notification
+
+ //we don't need to update the path label. In fact the path label was already set in the first
+ //transmission of this packet (in metisConnection_Send). Since we are using pointers this
+ //message has the same path label. However it could be a good idea to remove the path label
+ //so that raaqm will discard this packet for the RTT estimation.
+
+ return metisIoOperations_Send(conn->ops, NULL, message);
+ }
+ return false;
+}
+
+void
+metisConnection_EnableWldr(MetisConnection *conn)
+{
+ if (!metisConnection_IsLocal(conn)) {
+ if (conn->wldr == NULL) {
+ printf("----------------- enable wldr\n");
+ conn->wldr = metisWldr_Init();
+ }
+ }
+}
+
+void
+metisConnection_DisableWldr(MetisConnection *conn)
+{
+ if (!metisConnection_IsLocal(conn)) {
+ if (conn->wldr != NULL) {
+ printf("----------------- disable wldr\n");
+ metisWldr_Destroy(&(conn->wldr));
+ conn->wldr = NULL;
+ }
+ }
+}
+
+
+bool
+metisConnection_HasWldr(const MetisConnection *conn)
+{
+ if (conn->wldr == NULL) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+void
+metisConnection_DetectLosses(MetisConnection *conn, MetisMessage *message)
+{
+ metisWldr_DetectLosses(conn->wldr, conn, message);
+}
+
diff --git a/metis/ccnx/forwarder/metis/core/metis_Connection.h b/metis/ccnx/forwarder/metis/core/metis_Connection.h
new file mode 100644
index 00000000..8dfa119a
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_Connection.h
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file metis_Connection.h
+ * @brief Wrapper for different types of connections
+ *
+ * A connection wraps a specific set of {@link MetisIoOperations}. Those operations
+ * allow for input and output. Connections get stored in the Connection Table.
+ *
+ */
+
+#ifndef Metis_metis_Connection_h
+#define Metis_metis_Connection_h
+#include <config.h>
+#include <ccnx/api/control/cpi_Address.h>
+#include <ccnx/forwarder/metis/core/metis_Message.h>
+#include <ccnx/forwarder/metis/io/metis_IoOperations.h>
+
+//packet types for probing
+#define METIS_PACKET_TYPE_PROBE_REQUEST 5
+#define METIS_PACKET_TYPE_PROBE_REPLY 6
+
+struct metis_connection;
+typedef struct metis_connection MetisConnection;
+
+
+/**
+ * Creates a connection object.
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @return <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisConnection *metisConnection_Create(MetisIoOperations *ops);
+
+/**
+ * @function metisConnection_Release
+ * @abstract Releases a reference count, destroying on last release
+ * @discussion
+ * Only frees the memory on the final reference count. The pointer will
+ * always be NULL'd.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+void metisConnection_Release(MetisConnection **connectionPtr);
+
+/**
+ * @function metisConnection_Acquire
+ * @abstract A reference counted copy.
+ * @discussion
+ * A shallow copy, they share the same memory.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+MetisConnection *metisConnection_Acquire(MetisConnection *connection);
+
+/**
+ * @function metisConnection_Send
+ * @abstract Sends the ccnx message on the connection
+ * @discussion
+ *
+ * @param <#param1#>
+ * @return true if message sent, false if connection not up
+ */
+bool metisConnection_Send(const MetisConnection *conn, MetisMessage *message);
+
+/**
+ * Return the `MetisIoOperations` instance associated with the specified `MetisConnection` instance.
+ *
+ * @param [in] connection The allocated connection
+ *
+ * @return a pointer to the MetisIoOperations instance associated by th specified connection.
+ *
+ * Example:
+ * @code
+ * {
+ * MetisIoOperations *ioOps = metisConnection_GetIoOperations(conn);
+ * }
+ * @endcode
+ */
+MetisIoOperations *metisConnection_GetIoOperations(const MetisConnection *conn);
+
+/**
+ * Returns the unique identifier of the connection
+ *
+ * Calls the underlying MetisIoOperations to fetch the connection id
+ *
+ * @param [in] connection The allocated connection
+ *
+ * @return unsigned The unique connection id
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+unsigned metisConnection_GetConnectionId(const MetisConnection *conn);
+
+/**
+ * Returns the (remote, local) address pair that describes the connection
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [in] connection The allocated connection
+ *
+ * @return non-null The connection's remote and local address
+ * @return null Should never return NULL
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+const MetisAddressPair *metisConnection_GetAddressPair(const MetisConnection *conn);
+
+/**
+ * Tests if the connection is in the "up" state
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [in] connection The allocated connection
+ *
+ * @return true The connection is in the "up" state
+ * @return false The connection is not in the "up" state
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+bool metisConnection_IsUp(const MetisConnection *conn);
+
+/**
+ * Tests if the connection is to a Local/Loopback address
+ *
+ * A local connection is PF_LOCAL (PF_UNIX) and a loopback connection is
+ * 127.0.0.0/8 or ::1 for IPv6.
+ *
+ * @param [in] connection The allocated connection
+ *
+ * @retval true The connection is local or loopback
+ * @retval false The connection is not local or loopback
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+bool metisConnection_IsLocal(const MetisConnection *conn);
+
+/**
+ * Returns an opaque pointer representing the class of the Io Operations
+ *
+ * Returns an opaque pointer that an implementation can use to detect if
+ * the connection is based on that class.
+ *
+ * @param [in] conn The MetisConnection to test
+ *
+ * @return non-null An opaque pointer for each concrete implementation
+ *
+ * Example:
+ * @code
+ * {
+ * bool
+ * metisEtherConnection_IsClass(const MetisConnection *conn)
+ * {
+ * bool result = false;
+ * const void *class = metisConnection_Class(conn);
+ * if (class == _metisIoOperationsGuid) {
+ * result = true;
+ * }
+ * return result;
+ * }
+ *
+ * MetisHopByHopFragmenter *
+ * metisEtherConnection_GetFragmenter(const MetisConnection *conn)
+ * {
+ * MetisHopByHopFragmenter *fragmenter = NULL;
+ *
+ * if (metisEtherConnection_IsClass(conn)) {
+ * MetisIoOperations *ops = metisConnection_GetIoOperations(conn);
+ * _MetisEtherState *state = (_MetisEtherState *) ops->context;
+ * fragmenter = state->fragmenter;
+ * }
+ * return fragmenter;
+ * }
+ * }
+ * @endcode
+ */
+const void *metisConnection_Class(const MetisConnection *conn);
+
+bool metisConnection_ReSend(const MetisConnection *conn, MetisMessage *message);
+
+void metisConnection_Probe(MetisConnection *conn);
+void metisConnection_HandleProbe(MetisConnection *conn, uint8_t *message, MetisTicks actualTime);
+uint64_t metisConnection_GetDelay(MetisConnection *conn);
+void metisConnection_EnableWldr(MetisConnection *conn);
+void metisConnection_DisableWldr(MetisConnection *conn);
+bool metisConnection_HasWldr(const MetisConnection *conn);
+void metisConnection_DetectLosses(MetisConnection *conn, MetisMessage *message);
+
+#endif // Metis_metis_Connection_h
diff --git a/metis/ccnx/forwarder/metis/core/metis_ConnectionList.c b/metis/ccnx/forwarder/metis/core/metis_ConnectionList.c
new file mode 100644
index 00000000..021a6a7d
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_ConnectionList.c
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <stdio.h>
+
+#include <parc/algol/parc_Memory.h>
+#include <parc/algol/parc_ArrayList.h>
+
+#include <ccnx/forwarder/metis/core/metis_ConnectionList.h>
+#include <LongBow/runtime.h>
+
+struct metis_connection_list {
+ PARCArrayList *listOfConnections;
+};
+
+/**
+ * PARCArrayList entry destroyer
+ */
+
+static void
+metisConnectionList_ArrayDestroyer(void **voidPtr)
+{
+ MetisConnection **entryPtr = (MetisConnection **) voidPtr;
+ metisConnection_Release(entryPtr);
+}
+
+MetisConnectionList *
+metisConnectionList_Create()
+{
+ MetisConnectionList *list = parcMemory_AllocateAndClear(sizeof(MetisConnectionList));
+ assertNotNull(list, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisConnectionList));
+ list->listOfConnections = parcArrayList_Create(metisConnectionList_ArrayDestroyer);
+ return list;
+}
+
+void
+metisConnectionList_Destroy(MetisConnectionList **listPtr)
+{
+ assertNotNull(listPtr, "Parameter must be non-null double pointer");
+ assertNotNull(*listPtr, "Parameter must dereference to non-null pointer");
+ MetisConnectionList *list = *listPtr;
+ parcArrayList_Destroy(&list->listOfConnections);
+ parcMemory_Deallocate((void **) &list);
+ *listPtr = NULL;
+}
+
+void
+metisConnectionList_Append(MetisConnectionList *list, MetisConnection *entry)
+{
+ assertNotNull(list, "Parameter list must be non-null");
+ assertNotNull(entry, "Parameter entry must be non-null");
+
+ parcArrayList_Add(list->listOfConnections, metisConnection_Acquire(entry));
+}
+
+size_t
+metisConnectionList_Length(const MetisConnectionList *list)
+{
+ assertNotNull(list, "Parameter list must be non-null");
+ return parcArrayList_Size(list->listOfConnections);
+}
+
+MetisConnection *
+metisConnectionList_Get(MetisConnectionList *list, size_t index)
+{
+ assertNotNull(list, "Parameter list must be non-null");
+ MetisConnection *original = (MetisConnection *) parcArrayList_Get(list->listOfConnections, index);
+ return original;
+}
diff --git a/metis/ccnx/forwarder/metis/core/metis_ConnectionList.h b/metis/ccnx/forwarder/metis/core/metis_ConnectionList.h
new file mode 100644
index 00000000..d84b654a
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_ConnectionList.h
@@ -0,0 +1,102 @@
+/*
+ * 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_ConnectionList.h
+ * @brief A typesafe list of MetisConnection objects
+ *
+ * <#Detailed Description#>
+ *
+ */
+
+#ifndef Metis_metis_ConnectionList_h
+#define Metis_metis_ConnectionList_h
+
+struct metis_connection_list;
+typedef struct metis_connection_list MetisConnectionList;
+
+#include <ccnx/forwarder/metis/core/metis_Connection.h>
+
+/**
+ * Creates a lis of MetisConnection
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @return non-null An allocated list
+ * @return null An error
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisConnectionList *metisConnectionList_Create(void);
+
+/**
+ * Destroys the list and all objects inside it
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @return <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+void metisConnectionList_Destroy(MetisConnectionList **listPtr);
+
+/**
+ * @function metisConnectionList_Append
+ * @abstract Adds a connection entry to the list.
+ * @discussion
+ * Acquires a reference to the passed entry and stores it in the list.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+void metisConnectionList_Append(MetisConnectionList *list, MetisConnection *entry);
+
+/**
+ * Returns the number of items on the list
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [in] list The allocated list to check
+ *
+ * @return number The number of items on the list
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+size_t metisConnectionList_Length(const MetisConnectionList *list);
+
+/**
+ * @function metisConnectionList_Get
+ * @abstract Returns the connection entry.
+ * @discussion
+ * Caller must not destroy the returned value. If you will store the
+ * entry in your own data structure, you should acquire your own reference.
+ * Will assert if you go beyond the end of the list.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+MetisConnection *metisConnectionList_Get(MetisConnectionList *list, size_t index);
+#endif // Metis_metis_ConnectionList_h
diff --git a/metis/ccnx/forwarder/metis/core/metis_ConnectionManager.c b/metis/ccnx/forwarder/metis/core/metis_ConnectionManager.c
new file mode 100644
index 00000000..778aff01
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_ConnectionManager.c
@@ -0,0 +1,293 @@
+/*
+ * 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 Connection Manager sets itself up as a listener to the Messenger so it can take
+ * action based on system events.
+ *
+ * The Connection Manager queues and then processes in a later time slice the messages.
+ *
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <ccnx/forwarder/metis/core/metis_ConnectionManager.h>
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+#include <ccnx/forwarder/metis/messenger/metis_Messenger.h>
+#include <ccnx/forwarder/metis/messenger/metis_MessengerRecipient.h>
+#include <ccnx/forwarder/metis/messenger/metis_MissiveDeque.h>
+
+#include <parc/algol/parc_Memory.h>
+
+#include <LongBow/runtime.h>
+
+struct metis_connection_manager {
+ MetisForwarder *metis;
+ MetisLogger *logger;
+
+ MetisMessengerRecipient *messengerRecipient;
+
+ // we queue missives as they come in to process in our own
+ // event timeslice
+ MetisMissiveDeque *missiveQueue;
+
+ // for deferred queue processing
+ PARCEventTimer *timerEvent;
+};
+
+/**
+ * Receives missives from the messenger, queues them, and schedules our execution
+ *
+ * We defer processing of missives to a later time slice
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+static void metisConnectionManager_MessengerCallback(MetisMessengerRecipient *recipient, MetisMissive *missive);
+
+/**
+ * Event callback
+ *
+ * This is our main run loop to process our queue of messages. It is scheduled
+ * in {@link metisConnectionManager_MessengerCallback} when the queue becomes non-empty.
+ *
+ * When we are called here, we have exclusive use of the system, so we will not create any message loops
+ *
+ * @param [in] fd unused, required for compliance with function prototype
+ * @param [in] which_event unused, required for compliance with function prototype
+ * @param [in] connManagerVoidPtr A void* to MetisConnectionManager
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+static void metisConnectionManager_ProcessQueue(int fd, PARCEventType which_event, void *connManagerVoidPtr);
+
+/**
+ * Process a missive for a connection DOWN
+ *
+ * We've dequeued a missive and are now ready to take action on it
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @return <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+static void metisConnectionManager_ProcessDownMissive(MetisConnectionManager *connManager, const MetisMissive *missive);
+
+/**
+ * Process a missive for a connection UP
+ *
+ * We've dequeued a missive and are now ready to take action on it
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @return <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+static void metisConnectionManager_ProcessUpMissive(MetisConnectionManager *connManager, const MetisMissive *missive);
+
+static void metisConnectionManager_ProcessCreateMissive(MetisConnectionManager *connManager, const MetisMissive *missive);
+static void metisConnectionManager_ProcessClosedMissive(MetisConnectionManager *connManager, const MetisMissive *missive);
+static void metisConnectionManager_ProcessDestroyedMissive(MetisConnectionManager *connManager, const MetisMissive *missive);
+
+/**
+ * Send a notification up to local applications about connection state changes.
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @return <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+static void metisConnectionManager_NotifyApplications(MetisConnectionManager *connManager, const MetisMissive *missive);
+
+// ========================================================
+// Public API
+
+MetisConnectionManager *
+metisConnectionManager_Create(MetisForwarder *metis)
+{
+ MetisConnectionManager *connManager = parcMemory_AllocateAndClear(sizeof(MetisConnectionManager));
+ assertNotNull(connManager, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisConnectionManager));
+ connManager->metis = metis;
+ connManager->missiveQueue = metisMissiveDeque_Create();
+ connManager->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis));
+
+ MetisMessenger *messenger = metisForwarder_GetMessenger(connManager->metis);
+
+ // creates the timer, but does not start it
+ PARCEventScheduler *base = metisDispatcher_GetEventScheduler(metisForwarder_GetDispatcher(metis));
+ connManager->timerEvent = parcEventTimer_Create(base, 0, metisConnectionManager_ProcessQueue, connManager);
+
+ connManager->messengerRecipient = metisMessengerRecipient_Create(connManager, metisConnectionManager_MessengerCallback);
+ metisMessenger_Register(messenger, connManager->messengerRecipient);
+ return connManager;
+}
+
+void
+metisConnectionManager_Destroy(MetisConnectionManager **managerPtr)
+{
+ assertNotNull(managerPtr, "Double pointer must be non-null");
+ assertNotNull(*managerPtr, "Double pointer must dereference to non-null");
+
+ MetisConnectionManager *connManager = *managerPtr;
+
+ MetisMessenger *messenger = metisForwarder_GetMessenger(connManager->metis);
+ parcEventTimer_Destroy(&(connManager->timerEvent));
+ metisMessenger_Unregister(messenger, connManager->messengerRecipient);
+ metisMessengerRecipient_Destroy(&connManager->messengerRecipient);
+ metisMissiveDeque_Release(&connManager->missiveQueue);
+ metisLogger_Release(&connManager->logger);
+
+ parcMemory_Deallocate((void **) &connManager);
+ *managerPtr = NULL;
+}
+
+// ========================================================
+// Internal Functions
+
+static void
+metisConnectionManager_MessengerCallback(MetisMessengerRecipient *recipient, MetisMissive *missive)
+{
+ MetisConnectionManager *connManager = metisMessengerRecipient_GetRecipientContext(recipient);
+
+ // we do not release our reference count, we store it until later
+ // We are called with our own reference, so we do not need to acquire the missive here.
+ metisMissiveDeque_Append(connManager->missiveQueue, missive);
+
+ if (metisMissiveDeque_Size(connManager->missiveQueue) == 1) {
+ // When it becomes non-empty, schedule {@link metisConnectionManager_ProcessQueue}
+ struct timeval immediateTimeout = { 0, 0 };
+ parcEventTimer_Start(connManager->timerEvent, &immediateTimeout);
+ }
+}
+
+static void
+metisConnectionManager_ProcessQueue(int fd, PARCEventType which_event, void *connManagerVoidPtr)
+{
+ MetisConnectionManager *connManager = (MetisConnectionManager *) connManagerVoidPtr;
+
+ MetisMissive *missive;
+ while ((missive = metisMissiveDeque_RemoveFirst(connManager->missiveQueue)) != NULL) {
+ switch (metisMissive_GetType(missive)) {
+ case MetisMissiveType_ConnectionCreate:
+ metisConnectionManager_ProcessCreateMissive(connManager, missive);
+ break;
+ case MetisMissiveType_ConnectionUp:
+ metisConnectionManager_ProcessUpMissive(connManager, missive);
+ break;
+ case MetisMissiveType_ConnectionDown:
+ metisConnectionManager_ProcessDownMissive(connManager, missive);
+ break;
+ case MetisMissiveType_ConnectionClosed:
+ metisConnectionManager_ProcessClosedMissive(connManager, missive);
+ break;
+ case MetisMissiveType_ConnectionDestroyed:
+ metisConnectionManager_ProcessDestroyedMissive(connManager, missive);
+ break;
+ default:
+ trapUnexpectedState("Missive %p of unknown type: %d", (void *) missive, metisMissive_GetType(missive));
+ }
+ metisMissive_Release(&missive);
+ }
+}
+
+static void
+metisConnectionManager_ProcessUpMissive(MetisConnectionManager *connManager, const MetisMissive *missive)
+{
+ metisLogger_Log(connManager->logger, MetisLoggerFacility_Core, PARCLogLevel_Debug, __func__,
+ "Processing UP message for connid %u",
+ metisMissive_GetConnectionId(missive));
+
+ metisConnectionManager_NotifyApplications(connManager, missive);
+}
+
+static void
+metisConnectionManager_ProcessDownMissive(MetisConnectionManager *connManager, const MetisMissive *missive)
+{
+ metisLogger_Log(connManager->logger, MetisLoggerFacility_Core, PARCLogLevel_Debug, __func__,
+ "Processing DOWN message for connid %u",
+ metisMissive_GetConnectionId(missive));
+
+ metisConnectionManager_NotifyApplications(connManager, missive);
+}
+
+static void
+metisConnectionManager_ProcessCreateMissive(MetisConnectionManager *connManager, const MetisMissive *missive)
+{
+ metisLogger_Log(connManager->logger, MetisLoggerFacility_Core, PARCLogLevel_Debug, __func__,
+ "Processing CREATE message for connid %u",
+ metisMissive_GetConnectionId(missive));
+
+ metisConnectionManager_NotifyApplications(connManager, missive);
+}
+
+static void
+metisConnectionManager_ProcessClosedMissive(MetisConnectionManager *connManager, const MetisMissive *missive)
+{
+ metisLogger_Log(connManager->logger, MetisLoggerFacility_Core, PARCLogLevel_Debug, __func__,
+ "Processing CLOSED message for connid %u",
+ metisMissive_GetConnectionId(missive));
+
+ MetisConnectionTable *table = metisForwarder_GetConnectionTable(connManager->metis);
+ const MetisConnection *conn = metisConnectionTable_FindById(table, metisMissive_GetConnectionId(missive));
+
+ if (conn) {
+ // this will destroy the connection if its the last reference count
+ metisConnectionTable_Remove(table, conn);
+
+ // remove from FIB
+ metisForwarder_RemoveConnectionIdFromRoutes(connManager->metis, metisMissive_GetConnectionId(missive));
+
+ // finally tell apps
+ metisConnectionManager_NotifyApplications(connManager, missive);
+ }
+}
+
+static void
+metisConnectionManager_ProcessDestroyedMissive(MetisConnectionManager *connManager, const MetisMissive *missive)
+{
+ metisLogger_Log(connManager->logger, MetisLoggerFacility_Core, PARCLogLevel_Debug, __func__,
+ "Processing DESTROYED message for connid %u",
+ metisMissive_GetConnectionId(missive));
+
+ metisConnectionManager_NotifyApplications(connManager, missive);
+}
+
+
+static void
+metisConnectionManager_NotifyApplications(MetisConnectionManager *connManager, const MetisMissive *missive)
+{
+ //TODO
+}
diff --git a/metis/ccnx/forwarder/metis/core/metis_ConnectionManager.h b/metis/ccnx/forwarder/metis/core/metis_ConnectionManager.h
new file mode 100644
index 00000000..d4cd1f3a
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_ConnectionManager.h
@@ -0,0 +1,45 @@
+/*
+ * 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_ConnectionManager.h
+ * @brief The connection manager handles connection events, such as going down
+ *
+ * The connection manager listens to the event notification system. Based on those
+ * events, the connection manager will take specific actions. This is expected
+ * to be a singleton instantiated by metis_Forwarder.
+ *
+ * METIS_CONN_UP:
+ * - send a notification to appropriate local applications that want to
+ * known when connections come up.
+ *
+ * METIS_CONN_DOWN:
+ * - Tear down the connection
+ * - Send a notification to local applications
+ *
+ */
+
+#ifndef Metis_metis_ConnectionManager_h
+#define Metis_metis_ConnectionManager_h
+
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+
+struct metis_connection_manager;
+typedef struct metis_connection_manager MetisConnectionManager;
+
+MetisConnectionManager *metisConnectionManager_Create(MetisForwarder *metis);
+
+void metisConnectionManager_Destroy(MetisConnectionManager **managerPtr);
+#endif // Metis_metis_ConnectionManager_h
diff --git a/metis/ccnx/forwarder/metis/core/metis_ConnectionTable.c b/metis/ccnx/forwarder/metis/core/metis_ConnectionTable.c
new file mode 100644
index 00000000..9e15b30e
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_ConnectionTable.c
@@ -0,0 +1,252 @@
+/*
+ * 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.
+ */
+
+
+/**
+ * @header MetisConnectionTable
+ * @abstract Records all the current connections and references to them
+ * @discussion
+ *
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <LongBow/runtime.h>
+
+#include <ccnx/forwarder/metis/core/metis_ConnectionTable.h>
+#include <ccnx/forwarder/metis/io/metis_AddressPair.h>
+#include <parc/algol/parc_Memory.h>
+#include <parc/algol/parc_ArrayList.h>
+#include <parc/algol/parc_HashCodeTable.h>
+#include <parc/algol/parc_Hash.h>
+#include <parc/algol/parc_TreeRedBlack.h>
+
+struct metis_connection_table {
+ // The main storage table that has a Destroy method.
+ // The key is an unsigned int pointer. We use an unsigned int pointer
+ // because we want to be able to lookup by the id alone, and not have to
+ // have the MetisIoOperations everywhere.
+ PARCHashCodeTable *storageTableById;
+
+ // The key is a MetisAddressPair
+ // It does not have a destroy method for the data or key,
+ // as they are derived from the storage table.
+ PARCHashCodeTable *indexByAddressPair;
+
+ // An iterable stucture organized by connection id. The keys and
+ // values are the same pointers as in storageTableById, so there
+ // are no destructors in the tree.
+ // The only reason to keep this tree is so we have an iterable list
+ // of connections, which the hash table does not give us.
+ PARCTreeRedBlack *listById;
+};
+
+static bool
+metisConnectionTable_ConnectionIdEquals(const void *keyA, const void *keyB)
+{
+ unsigned idA = *((unsigned *) keyA);
+ unsigned idB = *((unsigned *) keyB);
+ return (idA == idB);
+}
+
+static int
+metisConnectionTable_ConnectionIdCompare(const void *keyA, const void *keyB)
+{
+ unsigned idA = *((unsigned *) keyA);
+ unsigned idB = *((unsigned *) keyB);
+ if (idA < idB) {
+ return -1;
+ }
+ if (idA > idB) {
+ return +1;
+ }
+ return 0;
+}
+
+static bool
+metisConnectionTable_AddressPairEquals(const void *keyA, const void *keyB)
+{
+ const MetisAddressPair *pairA = (const MetisAddressPair *) keyA;
+ const MetisAddressPair *pairB = (const MetisAddressPair *) keyB;
+
+ return metisAddressPair_Equals(pairA, pairB);
+}
+
+static HashCodeType
+metisConnectionTable_ConnectionIdHashCode(const void *keyA)
+{
+ unsigned idA = *((unsigned *) keyA);
+ return parcHash32_Int32(idA);
+}
+
+static HashCodeType
+metisConnectionTable_AddressPairHashCode(const void *keyA)
+{
+ const MetisAddressPair *pairA = (const MetisAddressPair *) keyA;
+ return metisAddressPair_HashCode(pairA);
+}
+
+
+static void
+metisConnectionTable_ConnectionIdDestroyer(void **dataPtr)
+{
+ unsigned *idA = (unsigned *) *dataPtr;
+ parcMemory_Deallocate((void **) &idA);
+ *dataPtr = NULL;
+}
+
+static void
+metisConnectionTable_ConnectionDestroyer(void **dataPtr)
+{
+ metisConnection_Release((MetisConnection **) dataPtr);
+}
+
+MetisConnectionTable *
+metisConnectionTable_Create()
+{
+ size_t initialSize = 16384;
+
+ MetisConnectionTable *conntable = parcMemory_AllocateAndClear(sizeof(MetisConnectionTable));
+ assertNotNull(conntable, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisConnectionTable));
+
+ conntable->storageTableById = parcHashCodeTable_Create_Size(metisConnectionTable_ConnectionIdEquals,
+ metisConnectionTable_ConnectionIdHashCode,
+ metisConnectionTable_ConnectionIdDestroyer,
+ metisConnectionTable_ConnectionDestroyer,
+ initialSize);
+
+ // no key or data destroyer, this is an index into storageByid.
+ conntable->indexByAddressPair = parcHashCodeTable_Create_Size(metisConnectionTable_AddressPairEquals,
+ metisConnectionTable_AddressPairHashCode,
+ NULL,
+ NULL,
+ initialSize);
+
+ conntable->listById = parcTreeRedBlack_Create(metisConnectionTable_ConnectionIdCompare,
+ NULL, // key free
+ NULL, // key copy
+ NULL, // value equals
+ NULL, // value free
+ NULL); // value copy
+
+ return conntable;
+}
+
+
+void
+metisConnectionTable_Destroy(MetisConnectionTable **conntablePtr)
+{
+ assertNotNull(conntablePtr, "Parameter must be non-null double pointer");
+ assertNotNull(*conntablePtr, "Parameter must dereference to non-null pointer");
+
+ MetisConnectionTable *conntable = *conntablePtr;
+
+ parcTreeRedBlack_Destroy(&conntable->listById);
+ parcHashCodeTable_Destroy(&conntable->indexByAddressPair);
+ parcHashCodeTable_Destroy(&conntable->storageTableById);
+ parcMemory_Deallocate((void **) &conntable);
+ *conntablePtr = NULL;
+}
+
+/**
+ * @function metisConnectionTable_Add
+ * @abstract Add a connection, takes ownership of memory
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ */
+void
+metisConnectionTable_Add(MetisConnectionTable *table, MetisConnection *connection)
+{
+ assertNotNull(table, "Parameter table must be non-null");
+ assertNotNull(connection, "Parameter connection must be non-null");
+
+ unsigned *connectionIdKey = parcMemory_Allocate(sizeof(unsigned));
+ assertNotNull(connectionIdKey, "parcMemory_Allocate(%zu) returned NULL", sizeof(unsigned));
+ *connectionIdKey = metisConnection_GetConnectionId(connection);
+
+ if (parcHashCodeTable_Add(table->storageTableById, connectionIdKey, connection)) {
+ parcHashCodeTable_Add(table->indexByAddressPair, (void *) metisConnection_GetAddressPair(connection), connection);
+ parcTreeRedBlack_Insert(table->listById, connectionIdKey, connection);
+ } else {
+ trapUnexpectedState("Could not add connection id %u -- is it a duplicate?", *connectionIdKey);
+ }
+}
+
+/**
+ * @function metisConnectionTable_Remove
+ * @abstract Removes the connection, calling Destroy on our copy
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ */
+void
+metisConnectionTable_Remove(MetisConnectionTable *table, const MetisConnection *connection)
+{
+ assertNotNull(table, "Parameter table must be non-null");
+ assertNotNull(connection, "Parameter connection must be non-null");
+
+ unsigned connid = metisConnection_GetConnectionId(connection);
+
+ parcTreeRedBlack_Remove(table->listById, &connid);
+ parcHashCodeTable_Del(table->indexByAddressPair, metisConnection_GetAddressPair(connection));
+ parcHashCodeTable_Del(table->storageTableById, &connid);
+}
+
+void
+metisConnectionTable_RemoveById(MetisConnectionTable *table, unsigned id)
+{
+ assertNotNull(table, "Parameter table must be non-null");
+ const MetisConnection *connection = metisConnectionTable_FindById(table, id);
+ if (connection) {
+ metisConnectionTable_Remove(table, connection);
+ }
+}
+
+const MetisConnection *
+metisConnectionTable_FindByAddressPair(MetisConnectionTable *table, const MetisAddressPair *pair)
+{
+ assertNotNull(table, "Parameter table must be non-null");
+ return (MetisConnection *) parcHashCodeTable_Get(table->indexByAddressPair, pair);
+}
+
+const MetisConnection *
+metisConnectionTable_FindById(MetisConnectionTable *table, unsigned id)
+{
+ assertNotNull(table, "Parameter table must be non-null");
+ return (MetisConnection *) parcHashCodeTable_Get(table->storageTableById, &id);
+}
+
+
+MetisConnectionList *
+metisConnectionTable_GetEntries(const MetisConnectionTable *table)
+{
+ assertNotNull(table, "Parameter table must be non-null");
+ MetisConnectionList *list = metisConnectionList_Create();
+
+ PARCArrayList *values = parcTreeRedBlack_Values(table->listById);
+ for (size_t i = 0; i < parcArrayList_Size(values); i++) {
+ MetisConnection *original = parcArrayList_Get(values, i);
+ metisConnectionList_Append(list, original);
+ }
+ parcArrayList_Destroy(&values);
+ return list;
+}
diff --git a/metis/ccnx/forwarder/metis/core/metis_ConnectionTable.h b/metis/ccnx/forwarder/metis/core/metis_ConnectionTable.h
new file mode 100644
index 00000000..1f47dafd
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_ConnectionTable.h
@@ -0,0 +1,134 @@
+/*
+ * 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_ConnectionTable_h
+#define Metis_metis_ConnectionTable_h
+
+#include <ccnx/forwarder/metis/core/metis_Connection.h>
+#include <ccnx/forwarder/metis/core/metis_ConnectionList.h>
+#include <ccnx/forwarder/metis/io/metis_IoOperations.h>
+#include <ccnx/forwarder/metis/io/metis_AddressPair.h>
+
+struct metis_connection_table;
+typedef struct metis_connection_table MetisConnectionTable;
+
+/**
+ * Creates an empty connection table
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @retval <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisConnectionTable *metisConnectionTable_Create(void);
+
+/**
+ * Destroys the connection table
+ *
+ * This will release the reference to all connections stored in the connection table.
+ *
+ * @param [in,out] conntablePtr Pointer to the allocated connection table, will be NULL'd
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+void metisConnectionTable_Destroy(MetisConnectionTable **conntablePtr);
+
+/**
+ * @function metisConnectionTable_Add
+ * @abstract Add a connection, takes ownership of memory
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ */
+void metisConnectionTable_Add(MetisConnectionTable *table, MetisConnection *connection);
+
+/**
+ * @function metisConnectionTable_Remove
+ * @abstract Removes the connection, calling Destroy on our copy
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ */
+void metisConnectionTable_Remove(MetisConnectionTable *table, const MetisConnection *connection);
+
+/**
+ * Removes a connection from the connection table
+ *
+ * Looks up a connection by its connection ID and removes it from the connection table.
+ * Removing the connection will call metisConnection_Release() on the connection object.
+ *
+ * @param [in] table The allocated connection table
+ * @param [in] id The connection ID
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+void metisConnectionTable_RemoveById(MetisConnectionTable *table, unsigned id);
+
+/**
+ * Lookup a connection by the (local, remote) addres pair
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [in] table The allocated connection table
+ * @param [in] pair The address pair to match, based on the inner values of the local and remote addresses
+ *
+ * @retval non-null The matched conneciton
+ * @retval null No match found or error
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+const MetisConnection *metisConnectionTable_FindByAddressPair(MetisConnectionTable *table, const MetisAddressPair *pair);
+
+/**
+ * @function metisConnectionTable_FindById
+ * @abstract Find a connection by its numeric id.
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return NULL if not found
+ */
+const MetisConnection *metisConnectionTable_FindById(MetisConnectionTable *table, unsigned id);
+
+/**
+ * @function metisConnectionTable_GetEntries
+ * @abstract Returns a list of connections. They are reference counted copies from the table.
+ * @discussion
+ * An allocated list of connections in the table. Each list entry is a reference counted
+ * copy of the connection in the table, thus they are "live" objects.
+ *
+ * @param <#param1#>
+ * @return An allocated list, which you must destroy
+ */
+MetisConnectionList *metisConnectionTable_GetEntries(const MetisConnectionTable *table);
+#endif // Metis_metis_ConnectionTable_h
diff --git a/metis/ccnx/forwarder/metis/core/metis_Dispatcher.c b/metis/ccnx/forwarder/metis/core/metis_Dispatcher.c
new file mode 100644
index 00000000..cf7283da
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_Dispatcher.c
@@ -0,0 +1,425 @@
+/*
+ * 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.
+ */
+
+
+/**
+ * @header metis_Dispatch.c
+ * @abstract Event dispatcher for Metis. Uses parcEvent
+ * @discussion
+ * Wraps the functions we use in parcEvent, along with mets_StreamBuffer and metis_Message.
+ * The dispatcher is the event loop, so it manages things like signals, timers, and network events.
+ *
+ */
+
+#include <config.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <pthread.h>
+
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+#include <parc/algol/parc_EventTimer.h>
+#include <parc/algol/parc_EventQueue.h>
+
+#include <LongBow/runtime.h>
+
+#include <ccnx/forwarder/metis/core/metis_Dispatcher.h>
+
+#ifndef INPORT_ANY
+#define INPORT_ANY 0
+#endif
+
+struct metis_dispatcher {
+ PARCEventScheduler *Base;
+ MetisLogger *logger;
+};
+
+// ==========================================
+// Public API
+
+PARCEventScheduler *
+metisDispatcher_GetEventScheduler(MetisDispatcher *dispatcher)
+{
+ return dispatcher->Base;
+}
+
+MetisDispatcher *
+metisDispatcher_Create(MetisLogger *logger)
+{
+ MetisDispatcher *dispatcher = parcMemory_AllocateAndClear(sizeof(MetisDispatcher));
+ assertNotNull(dispatcher, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisDispatcher));
+
+ dispatcher->Base = parcEventScheduler_Create();
+ dispatcher->logger = metisLogger_Acquire(logger);
+
+ assertNotNull(dispatcher->Base, "Got NULL from parcEventScheduler_Create()");
+
+ return dispatcher;
+}
+
+void
+metisDispatcher_Destroy(MetisDispatcher **dispatcherPtr)
+{
+ assertNotNull(dispatcherPtr, "Parameter must be non-null double pointer");
+ assertNotNull(*dispatcherPtr, "Parameter must dereference to non-null pointer");
+ MetisDispatcher *dispatcher = *dispatcherPtr;
+
+ metisLogger_Release(&dispatcher->logger);
+ parcEventScheduler_Destroy(&(dispatcher->Base));
+ parcMemory_Deallocate((void **) &dispatcher);
+ *dispatcherPtr = NULL;
+}
+
+void
+metisDispatcher_Stop(MetisDispatcher *dispatcher)
+{
+ struct timeval delay = { 0, 1000 };
+
+ parcEventScheduler_Stop(dispatcher->Base, &delay);
+}
+
+void
+metisDispatcher_Run(MetisDispatcher *dispatcher)
+{
+ assertNotNull(dispatcher, "Parameter must be non-null");
+
+ parcEventScheduler_Start(dispatcher->Base, 0);
+}
+
+
+void
+metisDispatcher_RunDuration(MetisDispatcher *dispatcher, struct timeval *duration)
+{
+ assertNotNull(dispatcher, "Parameter dispatcher must be non-null");
+ assertNotNull(duration, "Parameter duration must be non-null");
+
+ parcEventScheduler_Stop(dispatcher->Base, duration);
+ parcEventScheduler_Start(dispatcher->Base, 0);
+}
+
+
+void
+metisDispatcher_RunCount(MetisDispatcher *dispatcher, unsigned count)
+{
+ assertNotNull(dispatcher, "Parameter must be non-null");
+
+ for (unsigned i = 0; i < count; i++) {
+ parcEventScheduler_Start(dispatcher->Base, PARCEventSchedulerDispatchType_LoopOnce);
+ }
+}
+
+PARCEventSocket *
+metisDispatcher_CreateListener(MetisDispatcher *dispatcher, PARCEventSocket_Callback *callback, void *user_data, int backlog, const struct sockaddr *sa, int socklen)
+{
+ PARCEventSocket *listener = parcEventSocket_Create(dispatcher->Base, callback, NULL, user_data, sa, socklen);
+ if (listener == NULL) {
+ perror("Problem creating listener");
+ }
+ return listener;
+}
+
+void
+metisDispatcher_DestroyListener(MetisDispatcher *dispatcher, PARCEventSocket **listenerPtr)
+{
+ assertNotNull(listenerPtr, "Parameter must be non-null double pointer");
+ assertNotNull(*listenerPtr, "Parameter must dereference to non-null pointer");
+ parcEventSocket_Destroy(listenerPtr);
+}
+
+PARCEventQueue *
+metisDispatcher_CreateStreamBufferFromSocket(MetisDispatcher *dispatcher, MetisSocketType fd)
+{
+ assertNotNull(dispatcher, "Parameter dispatcher must be non-null");
+ PARCEventQueue *buffer = parcEventQueue_Create(dispatcher->Base, fd, PARCEventQueueOption_CloseOnFree | PARCEventQueueOption_DeferCallbacks);
+ assertNotNull(buffer, "Got null from parcEventBufver_Create for socket %d", fd);
+ return buffer;
+}
+
+PARCEventTimer *
+metisDispatcher_CreateTimer(MetisDispatcher *dispatcher, bool isPeriodic, PARCEvent_Callback *callback, void *userData)
+{
+ assertNotNull(dispatcher, "Parameter dispatcher must be non-null");
+ assertNotNull(callback, "Parameter callback must be non-null");
+
+ PARCEventType flags = 0;
+ if (isPeriodic) {
+ flags |= PARCEventType_Persist;
+ }
+ PARCEventTimer *event = parcEventTimer_Create(dispatcher->Base, flags, callback, userData);
+ return event;
+}
+
+void
+metisDispatcher_StartTimer(MetisDispatcher *dispatcher, PARCEventTimer *timerEvent, struct timeval *delay)
+{
+ assertNotNull(dispatcher, "Parameter dispatcher must be non-null");
+ assertNotNull(timerEvent, "Parameter timerEvent must be non-null");
+ int failure = parcEventTimer_Start(timerEvent, delay);
+ assertFalse(failure < 0, "Error starting timer event %p: (%d) %s", (void *) timerEvent, errno, strerror(errno));
+}
+
+void
+metisDispatcher_StopTimer(MetisDispatcher *dispatcher, PARCEventTimer *event)
+{
+ assertNotNull(dispatcher, "Parameter dispatcher must be non-null");
+ assertNotNull(event, "Parameter event must be non-null");
+
+ int failure = parcEventTimer_Stop(event);
+ assertFalse(failure < 0, "Error stopping signal event %p: (%d) %s", (void *) event, errno, strerror(errno));
+}
+
+void
+metisDispatcher_DestroyTimerEvent(MetisDispatcher *dispatcher, PARCEventTimer **eventPtr)
+{
+ assertNotNull(dispatcher, "Parameter dispatcher must be non-null");
+ assertNotNull(eventPtr, "Parameter eventPtr must be non-null double pointer");
+ assertNotNull(*eventPtr, "Paramter eventPtr must dereference to non-null pointer");
+
+ parcEventTimer_Destroy(eventPtr);
+ eventPtr = NULL;
+}
+
+PARCEvent *
+metisDispatcher_CreateNetworkEvent(MetisDispatcher *dispatcher, bool isPersistent, PARCEvent_Callback *callback, void *userData, int fd)
+{
+ short flags = PARCEventType_Timeout | PARCEventType_Read;
+ if (isPersistent) {
+ flags |= PARCEventType_Persist;
+ }
+
+ PARCEvent *event = parcEvent_Create(dispatcher->Base, fd, flags, callback, userData);
+ assertNotNull(event, "Got null from parcEvent_Create for socket %d", fd);
+ return event;
+}
+
+void
+metisDispatcher_DestroyNetworkEvent(MetisDispatcher *dispatcher, PARCEvent **eventPtr)
+{
+ assertNotNull(dispatcher, "Parameter dispatcher must be non-null");
+ assertNotNull(eventPtr, "Parameter eventPtr must be non-null double pointer");
+ assertNotNull(*eventPtr, "Paramter eventPtr must dereference to non-null pointer");
+
+ parcEvent_Destroy(eventPtr);
+ eventPtr = NULL;
+}
+
+void
+metisDispatcher_StartNetworkEvent(MetisDispatcher *dispatcher, PARCEvent *event)
+{
+ assertNotNull(dispatcher, "Parameter dispatcher must be non-null");
+ assertNotNull(event, "Parameter event must be non-null");
+
+ int failure = parcEvent_Start(event);
+ assertFalse(failure < 0, "Error starting signal event %p: (%d) %s", (void *) event, errno, strerror(errno));
+}
+
+void
+metisDispatcher_StopNetworkEvent(MetisDispatcher *dispatcher, PARCEvent *event)
+{
+ assertNotNull(dispatcher, "Parameter dispatcher must be non-null");
+ assertNotNull(event, "Parameter event must be non-null");
+
+ int failure = parcEvent_Stop(event);
+ assertFalse(failure < 0, "Error stopping signal event %p: (%d) %s", (void *) event, errno, strerror(errno));
+}
+
+PARCEventSignal *
+metisDispatcher_CreateSignalEvent(MetisDispatcher *dispatcher, PARCEventSignal_Callback *callback, void *userData, int signal)
+{
+ assertNotNull(dispatcher, "Parameter dispatcher must be non-null");
+ assertNotNull(callback, "Parameter callback must be non-null");
+
+ PARCEventSignal *event = parcEventSignal_Create(dispatcher->Base, signal, PARCEventType_Signal | PARCEventType_Persist, callback, userData);
+ assertNotNull(event, "Got null event when creating signal catcher for signal %d", signal);
+
+ return event;
+}
+
+void
+metisDispatcher_DestroySignalEvent(MetisDispatcher *dispatcher, PARCEventSignal **eventPtr)
+{
+ assertNotNull(dispatcher, "Parameter dispatcher must be non-null");
+ assertNotNull(eventPtr, "Parameter eventPtr must be non-null double pointer");
+ assertNotNull(*eventPtr, "Paramter eventPtr must dereference to non-null pointer");
+
+ parcEventSignal_Destroy(eventPtr);
+ eventPtr = NULL;
+}
+
+void
+metisDispatcher_StartSignalEvent(MetisDispatcher *dispatcher, PARCEventSignal *event)
+{
+ assertNotNull(dispatcher, "Parameter dispatcher must be non-null");
+ assertNotNull(event, "Parameter event must be non-null");
+
+ int failure = parcEventSignal_Start(event);
+ assertFalse(failure < 0, "Error starting signal event %p: (%d) %s", (void *) event, errno, strerror(errno));
+}
+
+void
+metisDispatcher_StopSignalEvent(MetisDispatcher *dispatcher, PARCEventSignal *event)
+{
+ assertNotNull(dispatcher, "Parameter dispatcher must be non-null");
+ assertNotNull(event, "Parameter event must be non-null");
+
+ int failure = parcEventSignal_Stop(event);
+ assertFalse(failure < 0, "Error stopping signal event %p: (%d) %s", (void *) event, errno, strerror(errno));
+}
+
+/**
+ * Bind to a local address/port then connect to peer.
+ */
+static bool
+metisDispatcher_StreamBufferBindAndConnect(MetisDispatcher *dispatcher, PARCEventQueue *buffer,
+ struct sockaddr *localSock, socklen_t localSockLength,
+ struct sockaddr *remoteSock, socklen_t remoteSockLength)
+{
+ // we need to bind, then connect. Special operation, so we make our
+ // own fd then pass it off to the buffer event
+
+ int fd = socket(localSock->sa_family, SOCK_STREAM, 0);
+ if (fd < 0) {
+ perror("socket");
+ return -1;
+ }
+
+ // Set non-blocking flag
+ int flags = fcntl(fd, F_GETFL, NULL);
+ if (flags < 0) {
+ perror("F_GETFL");
+ close(fd);
+ return -1;
+ }
+ int failure = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+ if (failure) {
+ perror("F_SETFL");
+ close(fd);
+ return -1;
+ }
+
+ failure = bind(fd, localSock, localSockLength);
+ if (failure) {
+ perror("bind");
+ close(fd);
+ return false;
+ }
+
+ parcEventQueue_SetFileDescriptor(buffer, fd);
+
+ failure = parcEventQueue_ConnectSocket(buffer, remoteSock, remoteSockLength);
+ if (failure && (errno != EINPROGRESS)) {
+ perror("connect");
+ close(fd);
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Connect to an INET peer
+ * @return NULL on error, otherwise a streambuffer
+ */
+static PARCEventQueue *
+metisDispatcher_StreamBufferConnect_INET(MetisDispatcher *dispatcher, const CPIAddress *localAddress, const CPIAddress *remoteAddress)
+{
+ struct sockaddr_in localSock, remoteSock;
+ cpiAddress_GetInet(localAddress, &localSock);
+ cpiAddress_GetInet(remoteAddress, &remoteSock);
+
+ PARCEventQueue *buffer = parcEventQueue_Create(dispatcher->Base, -1, PARCEventQueueOption_CloseOnFree);
+ assertNotNull(buffer, "got null buffer from parcEventQueue_Create()");
+
+ bool success = metisDispatcher_StreamBufferBindAndConnect(dispatcher, buffer,
+ (struct sockaddr *) &localSock, sizeof(localSock),
+ (struct sockaddr *) &remoteSock, sizeof(remoteSock));
+ if (!success) {
+ parcEventQueue_Destroy(&buffer);
+ buffer = NULL;
+ }
+
+ return buffer;
+}
+
+/**
+ * Connect to an INET peer
+ * @return NULL on error, otherwise a streambuffer
+ */
+static PARCEventQueue *
+//static MetisStreamBuffer *
+metisDispatcher_StreamBufferConnect_INET6(MetisDispatcher *dispatcher, const CPIAddress *localAddress, const CPIAddress *remoteAddress)
+{
+ struct sockaddr_in6 localSock, remoteSock;
+ cpiAddress_GetInet6(localAddress, &localSock);
+ cpiAddress_GetInet6(remoteAddress, &remoteSock);
+
+ PARCEventQueue *buffer = parcEventQueue_Create(dispatcher->Base, -1, PARCEventQueueOption_CloseOnFree);
+ assertNotNull(buffer, "got null buffer from parcEventQueue_Create()");
+
+ bool success = metisDispatcher_StreamBufferBindAndConnect(dispatcher, buffer,
+ (struct sockaddr *) &localSock, sizeof(localSock),
+ (struct sockaddr *) &remoteSock, sizeof(remoteSock));
+ if (!success) {
+ parcEventQueue_Destroy(&buffer);
+ buffer = NULL;
+ }
+
+ return buffer;
+}
+
+PARCEventQueue *
+metisDispatcher_StreamBufferConnect(MetisDispatcher *dispatcher, const MetisAddressPair *pair)
+{
+ const CPIAddress *localAddress = metisAddressPair_GetLocal(pair);
+ const CPIAddress *remoteAddress = metisAddressPair_GetRemote(pair);
+
+
+ // they must be of the same address family
+ if (cpiAddress_GetType(localAddress) != cpiAddress_GetType(remoteAddress)) {
+ char message[2048];
+ char *localAddressString = cpiAddress_ToString(localAddress);
+ char *remoteAddressString = cpiAddress_ToString(remoteAddress);
+ snprintf(message,
+ 2048,
+ "Remote address not same type as local address, expected %d got %d\nlocal %s remote %s",
+ cpiAddress_GetType(localAddress),
+ cpiAddress_GetType(remoteAddress),
+ localAddressString,
+ remoteAddressString);
+
+ parcMemory_Deallocate((void **) &localAddressString);
+ parcMemory_Deallocate((void **) &remoteAddressString);
+
+ assertTrue(cpiAddress_GetType(localAddress) == cpiAddress_GetType(remoteAddress), "%s", message);
+ }
+
+ switch (cpiAddress_GetType(localAddress)) {
+ case cpiAddressType_INET:
+ return metisDispatcher_StreamBufferConnect_INET(dispatcher, localAddress, remoteAddress);
+ break;
+ case cpiAddressType_INET6:
+ return metisDispatcher_StreamBufferConnect_INET6(dispatcher, localAddress, remoteAddress);
+ break;
+ default:
+ trapIllegalValue(pair, "local address unsupported CPI address type: %d", cpiAddress_GetType(localAddress));
+ }
+}
diff --git a/metis/ccnx/forwarder/metis/core/metis_Dispatcher.h b/metis/ccnx/forwarder/metis/core/metis_Dispatcher.h
new file mode 100644
index 00000000..e936df22
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_Dispatcher.h
@@ -0,0 +1,303 @@
+/*
+ * 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.
+ */
+
+/**
+ * @header Metis Dispatcher
+ * @abstract The dispatcher is the event loop run by MetisForwarder.
+ * @discussion
+ * These functions manage listeners, timers, and network events inside
+ * the event loop.
+ *
+ * Curently, it is a thin wrapper around an event so we don't have to
+ * expose that implementation detail to other modules.
+ *
+ */
+
+#ifndef Metis_metis_Dispatcher_h
+#define Metis_metis_Dispatcher_h
+
+#include <sys/socket.h>
+#include <stdbool.h>
+
+struct metis_dispatcher;
+typedef struct metis_dispatcher MetisDispatcher;
+
+#include <parc/algol/parc_Memory.h>
+#include <parc/algol/parc_EventScheduler.h>
+#include <parc/algol/parc_Event.h>
+#include <parc/algol/parc_EventTimer.h>
+#include <parc/algol/parc_EventSignal.h>
+#include <parc/algol/parc_EventQueue.h>
+#include <parc/algol/parc_EventSocket.h>
+
+#include <ccnx/forwarder/metis/core/metis_Logger.h>
+
+PARCEventScheduler *metisDispatcher_GetEventScheduler(MetisDispatcher *dispatcher);
+/**
+ * Creates an event dispatcher
+ *
+ * Event dispatcher based on PARCEvent
+ *
+ * @return non-null Allocated event dispatcher
+ * @return null An error
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisDispatcher *metisDispatcher_Create(MetisLogger *logger);
+
+/**
+ * Destroys event dispatcher
+ *
+ * Caller is responsible for destroying call events before destroying
+ * the event dispatcher.
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @return <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+void metisDispatcher_Destroy(MetisDispatcher **dispatcherPtr);
+
+/**
+ * @function metisDispatcher_Stop
+ * @abstract Called from a different thread, tells the dispatcher to stop
+ * @discussion
+ * Called from a user thread or from an interrupt handler.
+ * Does not block. Use <code>metisDispatcher_WaitForStopped()</code> to
+ * block until stopped after calling this.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+void metisDispatcher_Stop(MetisDispatcher *dispatcher);
+
+/**
+ * @function metisDispatcher_WaitForStopped
+ * @abstract Blocks until dispatcher in stopped state
+ * @discussion
+ * Used after <code>metisDispatcher_Stop()</code> to wait for stop.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+void metisDispatcher_WaitForStopped(MetisDispatcher *dispatcher);
+
+/**
+ * @function metisDispatcher_Run
+ * @abstract Runs the forwarder, blocks.
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ */
+void metisDispatcher_Run(MetisDispatcher *dispatcher);
+
+/**
+ * @function metisDispatcher_RunDuration
+ * @abstract Runs forwarder for at most duration, blocks.
+ * @discussion
+ * Blocks running the forwarder for a duration. May be called
+ * iteratively to keep running. Duration is a minimum, actual
+ * runtime may be slightly longer.
+ *
+ * @param <#param1#>
+ */
+void metisDispatcher_RunDuration(MetisDispatcher *dispatcher, struct timeval *duration);
+
+/**
+ * @header metisDispatcher_RunCount
+ * @abstract Run the event loop for the given count cycles
+ * @discussion
+ * Runs the event loop for the given number of cycles, blocking
+ * until done. May be called sequentially over and over.
+ *
+ */
+void metisDispatcher_RunCount(MetisDispatcher *dispatcher, unsigned count);
+
+typedef int MetisSocketType;
+
+typedef struct evconnlistener MetisListener;
+
+/**
+ * @typedef MetisListenerCallback
+ * @abstract Callback function typedef for a stream listener
+ *
+ * @constant listener is the object created by <code>metisForwarder_NewBind()</code> that received the client connection
+ * @constant client_socket is the client socket
+ * @constant user_data is the user_data passed to <code>metisForwarder_NewBind()</code>
+ * @constant client_addr is the client address
+ * @constant socklen is the length of client_addr
+ * @discussion <#Discussion#>
+ */
+typedef void (MetisListenerCallback)(MetisListener *listener, MetisSocketType client_socket,
+ struct sockaddr *client_addr, int socklen, void *user_data);
+
+/**
+ * @header metisForwarder_NewBind
+ * @abstract Allocate a new stream listener
+ * @discussion
+ * The server socket will be freed when closed and will be reusable.
+ *
+ * @param metis the forwarder that owns the event loop
+ * @param cb is the callback for a new connection
+ * @param user_data is opaque user data passed to the callback
+ * @param backlog is the listen() depth, may use -1 for a default value
+ * @param sa is the socket address to bind to (INET, INET6, LOCAL)
+ * @param socklen is the sizeof the actual sockaddr (e.g. sizeof(sockaddr_un))
+ */
+PARCEventSocket *metisDispatcher_CreateListener(MetisDispatcher *dispatcher,
+ PARCEventSocket_Callback *callback, void *user_data,
+ int backlog, const struct sockaddr *sa, int socklen);
+
+void metisDispatcher_DestroyListener(MetisDispatcher *dispatcher, PARCEventSocket **listenerPtr);
+
+typedef struct event MetisTimerEvent;
+typedef struct event MetisNetworkEvent;
+typedef struct event MetisSignalEvent;
+
+/**
+ * @typedef MetisEventCallback
+ * @abstract A network event or a timer callback
+ * @constant fd The file descriptor associated with the event, may be -1 for timers
+ * @constant which_event is a bitmap of the MetisEventType
+ * @constant user_data is the user_data passed to <code>MetisForwarder_CreateEvent()</code>
+ * @discussion <#Discussion#>
+ */
+typedef void (MetisEventCallback)(MetisSocketType fd, short which_event, void *user_data);
+
+/**
+ * @function metisDispatcher_CreateTimer
+ * @abstract Creates a MetisEvent for use as a timer.
+ * @discussion
+ *
+ * When created, the timer is idle and you need to call <code>metisForwarder_StartTimer()</code>
+ *
+ * @param isPeriodic means the timer will fire repeatidly, otherwise it is a one-shot and
+ * needs to be set again with <code>metisDispatcher_StartTimer()</code>
+ * @return <#return#>
+ */
+PARCEventTimer *metisDispatcher_CreateTimer(MetisDispatcher *dispatcher, bool isPeriodic, PARCEvent_Callback *callback, void *userData);
+
+/**
+ * @function metisDispatcher_StartTimer
+ * @abstract Starts the timer with the given delay.
+ * @discussion
+ * If the timer is periodic, it will keep firing with the given delay
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+void metisDispatcher_StartTimer(MetisDispatcher *dispatcher, PARCEventTimer *timerEvent, struct timeval *delay);
+
+void metisDispatcher_StopTimer(MetisDispatcher *dispatcher, PARCEventTimer *timerEvent);
+
+/**
+ * @function metisDispatcher_DestroyTimerEvent
+ * @abstract Cancels the timer and frees the event
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+void metisDispatcher_DestroyTimerEvent(MetisDispatcher *dispatcher, PARCEventTimer **eventPtr);
+
+/**
+ * @function metisDispatcher_CreateNetworkEvent
+ * @abstract Creates a network event callback on the socket
+ * @discussion
+ * May be used on any sort of file descriptor or socket. The event is edge triggered and non-reentrent.
+ * This means you need to drain the events off the socket, as the callback will not be called again
+ * until a new event arrives.
+ *
+ * When created, the event is idle and you need to call <code>metisForwarder_StartNetworkEvent()</code>
+ *
+ * @param isPersistent means the callback will keep firing with new events, otherwise its a one-shot
+ * @param fd is the socket to monitor
+ * @return <#return#>
+ */
+//MetisNetworkEvent *metisDispatcher_CreateNetworkEvent(MetisDispatcher *dispatcher, bool isPersistent, MetisEventCallback *callback, void *userData, MetisSocketType fd);
+PARCEvent *metisDispatcher_CreateNetworkEvent(MetisDispatcher *dispatcher, bool isPersistent, PARCEvent_Callback *callback, void *userData, int fd);
+
+//void metisDispatcher_StartNetworkEvent(MetisDispatcher *dispatcher, MetisNetworkEvent *event);
+//void metisDispatcher_StopNetworkEvent(MetisDispatcher *dispatcher, MetisNetworkEvent *event);
+void metisDispatcher_StartNetworkEvent(MetisDispatcher *dispatcher, PARCEvent *event);
+void metisDispatcher_StopNetworkEvent(MetisDispatcher *dispatcher, PARCEvent *event);
+
+//void metisDispatcher_DestroyNetworkEvent(MetisDispatcher *dispatcher, MetisNetworkEvent **eventPtr);
+void metisDispatcher_DestroyNetworkEvent(MetisDispatcher *dispatcher, PARCEvent **eventPtr);
+
+/**
+ * @function metisDispatcher_CreateSignalEvent
+ * @abstract Creates a signal trap
+ * @discussion
+ * May be used on catchable signals. The event is edge triggered and non-reentrent. Signal events are persistent.
+ *
+ * When created, the signal trap is idle and you need to call <code>metisForwarder_StartSignalEvent()</code>
+ *
+ * @param signal is the system signal to monitor (e.g. SIGINT).
+ * @return <#return#>
+ */
+PARCEventSignal *metisDispatcher_CreateSignalEvent(MetisDispatcher *dispatcher, PARCEventSignal_Callback *callback, void *userData, int signal);
+
+void metisDispatcher_DestroySignalEvent(MetisDispatcher *dispatcher, PARCEventSignal **eventPtr);
+
+void metisDispatcher_StartSignalEvent(MetisDispatcher *dispatcher, PARCEventSignal *event);
+void metisDispatcher_StopSignalEvent(MetisDispatcher *dispatcher, PARCEventSignal *event);
+
+// =============
+// stream buffers
+
+#include <ccnx/forwarder/metis/core/metis_StreamBuffer.h>
+#include <ccnx/forwarder/metis/io/metis_AddressPair.h>
+
+/**
+ * @function metisDispatcher_CreateStreamBuffer
+ * @abstract Creates a high-function buffer around a stream socket
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+PARCEventQueue *metisDispatcher_CreateStreamBufferFromSocket(MetisDispatcher *dispatcher, MetisSocketType fd);
+
+/**
+ * @function metisDispatcher_StreamBufferConnect
+ * @abstract Create a TCP tunnel to a remote peer
+ * @discussion
+ * 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().
+ *
+ * It is unlikely that the buffer will be connected by the time the function returns. The eventCallback will
+ * fire once the remote system accepts the conneciton.
+ *
+ * @param <#param1#>
+ * @return NULL on error, otherwise a streambuffer.
+ */
+PARCEventQueue *metisDispatcher_StreamBufferConnect(MetisDispatcher *dispatcher, const MetisAddressPair *pair);
+#endif // Metis_metis_Dispatcher_h
diff --git a/metis/ccnx/forwarder/metis/core/metis_Forwarder.c b/metis/ccnx/forwarder/metis/core/metis_Forwarder.c
new file mode 100644
index 00000000..7f3f9b8c
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_Forwarder.c
@@ -0,0 +1,506 @@
+/*
+ * 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.
+ */
+
+/**
+ * Event based router based on TLVs
+ *
+ * This module is the glue around the event scheduler.
+ * Its the packet i/o module.
+ *
+ * Packet processing is done in metis_Dispatcher.c, which is the actual wrapper around the event scheduler
+ *
+ * USAGE:
+ *
+ * MetisForwarder *forwarder = metisForwarder_Create(NULL);
+ *
+ * // do one of these
+ * metisForwarder_SetupAllListeners(forwarder);
+ * or
+ * metisForwarder_SetupFromConfigFile(forwarder, "metis.cfg");
+ *
+ * // now run the event loop via the dispatcher
+ * MetisDispatcher *dispatcher = metisForwarder_GetDispatcher();
+ *
+ * // you can call any of the Run method sequentially.
+ * // chose one of
+ * metisDispatcher_Run(dispatcher);
+ * metisDispatcher_RunCount(dispatcher, 100);
+ * metisDispatcher_RunDuration(dispatcher, &((struct timeval) {30, 0}));
+ *
+ * metisForwarder_Destroy(&forwarder);
+ */
+
+#include <config.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+#define __STDC_FORMAT_MACROS
+#include <inttypes.h>
+
+#include <parc/algol/parc_ArrayList.h>
+#include <parc/algol/parc_Memory.h>
+#include <parc/algol/parc_Object.h>
+#include <parc/logging/parc_LogReporterTextStdout.h>
+
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+#include <ccnx/forwarder/metis/core/metis_ConnectionManager.h>
+#include <ccnx/forwarder/metis/core/metis_ConnectionTable.h>
+#include <ccnx/forwarder/metis/core/metis_Dispatcher.h>
+#include <ccnx/forwarder/metis/config/metis_Configuration.h>
+#include <ccnx/forwarder/metis/config/metis_ConfigurationFile.h>
+#include <ccnx/forwarder/metis/config/metis_ConfigurationListeners.h>
+#include <ccnx/forwarder/metis/config/metis_CommandLineInterface.h>
+#include <ccnx/forwarder/metis/config/metis_WebInterface.h>
+#include <ccnx/forwarder/metis/processor/metis_MessageProcessor.h>
+
+#include <ccnx/forwarder/metis/core/metis_Wldr.h>
+
+#include <LongBow/runtime.h>
+
+// the router's clock frequency (we now use the monotonic clock)
+#define METISHZ 1000
+
+// these will all be a little off because its all integer division
+#define METIS_MSEC_PER_TICK (1000 / METISHZ)
+#define METIS_USEC_PER_TICK (1000000 / METISHZ)
+#define METIS_NSEC_PER_TICK ((1000000000ULL) / METISHZ)
+#define MSEC_TO_TICKS(msec) ((msec < FC_MSEC_PER_TICK) ? 1 : msec / FC_MSEC_PER_TICK)
+#define NSEC_TO_TICKS(nsec) ((nsec < METIS_NSEC_PER_TICK) ? 1 : nsec / METIS_NSEC_PER_TICK)
+
+
+struct metis_forwarder {
+ MetisDispatcher *dispatcher;
+
+ uint16_t server_port;
+
+ PARCEventSignal *signal_int;
+ PARCEventSignal *signal_term;
+ PARCEventSignal *signal_usr1;
+ PARCEventTimer *keepalive_event;
+
+ // This is added to metisForwarder_GetTime(). Some unit tests
+ // will skew the virtual clock forward. In normal operaiton, it is 0.
+ MetisTicks clockOffset;
+
+ unsigned nextConnectionid;
+ MetisMessenger *messenger;
+ MetisConnectionManager *connectionManager;
+ MetisConnectionTable *connectionTable;
+ MetisListenerSet *listenerSet;
+ MetisConfiguration *config;
+
+ // we'll eventually want to setup a threadpool of these
+ MetisMessageProcessor *processor;
+
+ MetisLogger *logger;
+
+ PARCClock *clock;
+
+ // used by seed48 and nrand48
+ unsigned short seed[3];
+};
+
+// signal traps through the event scheduler
+static void _signal_cb(int, PARCEventType, void *);
+
+// A no-op keepalive to prevent Libevent from exiting the dispatch loop
+static void _keepalive_cb(int, PARCEventType, void *);
+
+/**
+ * Reseed our pseudo-random number generator.
+ */
+static void
+metisForwarder_Seed(MetisForwarder *metis)
+{
+ int fd;
+ ssize_t res;
+
+ res = -1;
+ fd = open("/dev/urandom", O_RDONLY);
+ if (fd != -1) {
+ res = read(fd, metis->seed, sizeof(metis->seed));
+ close(fd);
+ }
+ if (res != sizeof(metis->seed)) {
+ metis->seed[1] = (unsigned short) getpid(); /* better than no entropy */
+ metis->seed[2] = (unsigned short) time(NULL);
+ }
+ /*
+ * The call to seed48 is needed by cygwin, and should be harmless
+ * on other platforms.
+ */
+ seed48(metis->seed);
+}
+
+MetisLogger *
+metisForwarder_GetLogger(const MetisForwarder *metis)
+{
+ return metis->logger;
+}
+
+// ============================================================================
+// Setup and destroy section
+
+MetisForwarder *
+metisForwarder_Create(MetisLogger *logger)
+{
+ MetisForwarder *metis = parcMemory_AllocateAndClear(sizeof(MetisForwarder));
+ assertNotNull(metis, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisForwarder));
+ memset(metis, 0, sizeof(MetisForwarder));
+ metisForwarder_Seed(metis);
+
+ metis->clock = parcClock_Monotonic();
+ metis->clockOffset = 0;
+
+ if (logger) {
+ metis->logger = metisLogger_Acquire(logger);
+ metisLogger_SetClock(metis->logger, metis->clock);
+ } else {
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ metis->logger = metisLogger_Create(reporter, metis->clock);
+ parcLogReporter_Release(&reporter);
+ }
+
+ metis->nextConnectionid = 1;
+ metis->dispatcher = metisDispatcher_Create(metis->logger);
+ metis->messenger = metisMessenger_Create(metis->dispatcher);
+ metis->connectionManager = metisConnectionManager_Create(metis);
+ metis->connectionTable = metisConnectionTable_Create();
+ metis->listenerSet = metisListenerSet_Create();
+ metis->config = metisConfiguration_Create(metis);
+ metis->processor = metisMessageProcessor_Create(metis);
+
+ metis->signal_term = metisDispatcher_CreateSignalEvent(metis->dispatcher, _signal_cb, metis, SIGTERM);
+ metisDispatcher_StartSignalEvent(metis->dispatcher, metis->signal_term);
+
+ metis->signal_int = metisDispatcher_CreateSignalEvent(metis->dispatcher, _signal_cb, metis, SIGINT);
+ metisDispatcher_StartSignalEvent(metis->dispatcher, metis->signal_int);
+
+ metis->signal_usr1 = metisDispatcher_CreateSignalEvent(metis->dispatcher, _signal_cb, metis, SIGPIPE);
+ metisDispatcher_StartSignalEvent(metis->dispatcher, metis->signal_usr1);
+
+ /* ignore child */
+ signal(SIGCHLD, SIG_IGN);
+
+ /* ignore tty signals */
+ signal(SIGTSTP, SIG_IGN);
+ signal(SIGTTOU, SIG_IGN);
+ signal(SIGTTIN, SIG_IGN);
+
+ // We no longer use this for ticks, but we need to have at least one event schedule
+ // to keep Libevent happy.
+
+ struct timeval wtnow_timeout;
+ timerclear(&wtnow_timeout);
+
+ wtnow_timeout.tv_sec = 0;
+ wtnow_timeout.tv_usec = 50000; // 20 Hz keepalive
+
+ PARCEventScheduler *base = metisDispatcher_GetEventScheduler(metis->dispatcher);
+ metis->keepalive_event = parcEventTimer_Create(base, PARCEventType_Persist, _keepalive_cb, (void *) metis);
+ parcEventTimer_Start(metis->keepalive_event, &wtnow_timeout);
+
+ return metis;
+}
+
+void
+metisForwarder_Destroy(MetisForwarder **metisPtr)
+{
+ assertNotNull(metisPtr, "Parameter must be non-null double pointer");
+ assertNotNull(*metisPtr, "Parameter must dereference to non-null pointer");
+ MetisForwarder *metis = *metisPtr;
+
+ parcEventTimer_Destroy(&(metis->keepalive_event));
+
+ metisListenerSet_Destroy(&(metis->listenerSet));
+ metisConnectionManager_Destroy(&(metis->connectionManager));
+ metisConnectionTable_Destroy(&(metis->connectionTable));
+ metisMessageProcessor_Destroy(&(metis->processor));
+ metisConfiguration_Destroy(&(metis->config));
+
+ // the messenger is used by many of the other pieces, so destroy it last
+ metisMessenger_Destroy(&(metis->messenger));
+
+ metisDispatcher_DestroySignalEvent(metis->dispatcher, &(metis->signal_int));
+ metisDispatcher_DestroySignalEvent(metis->dispatcher, &(metis->signal_term));
+ metisDispatcher_DestroySignalEvent(metis->dispatcher, &(metis->signal_usr1));
+
+ parcClock_Release(&metis->clock);
+ metisLogger_Release(&metis->logger);
+
+ // do the dispatcher last
+ metisDispatcher_Destroy(&(metis->dispatcher));
+
+ parcMemory_Deallocate((void **) &metis);
+ *metisPtr = NULL;
+}
+
+void
+metisForwarder_SetupAllListeners(MetisForwarder *metis, uint16_t port, const char *localPath)
+{
+ assertNotNull(metis, "Parameter must be non-null");
+
+ metisConfigurationListeners_SetupAll(metis->config, port, localPath);
+}
+
+void
+metisForwarder_SetupFromConfigFile(MetisForwarder *forwarder, const char *filename)
+{
+ MetisConfigurationFile *configFile = metisConfigurationFile_Create(forwarder, filename);
+ if (configFile) {
+ //metisConfigurationFile_ProcessForwardingStrategies(forwarder->config, configFile);
+ metisConfigurationFile_Process(configFile);
+ metisConfigurationFile_Release(&configFile);
+ }
+}
+
+MetisConfiguration *
+metisForwarder_GetConfiguration(MetisForwarder *metis)
+{
+ assertNotNull(metis, "Parameter must be non-null");
+ return metis->config;
+}
+
+// ============================================================================
+
+unsigned
+metisForwarder_GetNextConnectionId(MetisForwarder *metis)
+{
+ assertNotNull(metis, "Parameter must be non-null");
+ return metis->nextConnectionid++;
+}
+
+MetisMessenger *
+metisForwarder_GetMessenger(MetisForwarder *metis)
+{
+ assertNotNull(metis, "Parameter must be non-null");
+ return metis->messenger;
+}
+
+MetisDispatcher *
+metisForwarder_GetDispatcher(MetisForwarder *metis)
+{
+ assertNotNull(metis, "Parameter must be non-null");
+ return metis->dispatcher;
+}
+
+MetisConnectionTable *
+metisForwarder_GetConnectionTable(MetisForwarder *metis)
+{
+ assertNotNull(metis, "Parameter must be non-null");
+ return metis->connectionTable;
+}
+
+MetisListenerSet *
+metisForwarder_GetListenerSet(MetisForwarder *metis)
+{
+ assertNotNull(metis, "Parameter must be non-null");
+ return metis->listenerSet;
+}
+
+void
+metisForwarder_SetChacheStoreFlag(MetisForwarder *metis, bool val)
+{
+ assertNotNull(metis, "Parameter must be non-null");
+ metisMessageProcessor_SetCacheStoreFlag(metis->processor, val);
+}
+
+bool
+metisForwarder_GetChacheStoreFlag(MetisForwarder *metis)
+{
+ assertNotNull(metis, "Parameter must be non-null");
+ return metisMessageProcessor_GetCacheStoreFlag(metis->processor);
+}
+
+void
+metisForwarder_SetChacheServeFlag(MetisForwarder *metis, bool val)
+{
+ assertNotNull(metis, "Parameter must be non-null");
+ metisMessageProcessor_SetCacheServeFlag(metis->processor, val);
+}
+
+bool
+metisForwarder_GetChacheServeFlag(MetisForwarder *metis)
+{
+ assertNotNull(metis, "Parameter must be non-null");
+ return metisMessageProcessor_GetCacheServeFlag(metis->processor);
+}
+
+void
+metisForwarder_Receive(MetisForwarder *metis, MetisMessage *message)
+{
+ assertNotNull(metis, "Parameter metis must be non-null");
+ assertNotNull(message, "Parameter message must be non-null");
+
+ // this takes ownership of the message, so we're done here
+ if (metisMessage_GetType(message) == MetisMessagePacketType_Control) {
+ metisConfiguration_Receive(metis->config, message);
+ } else {
+ const MetisConnection *conn = metisConnectionTable_FindById(metis->connectionTable, metisMessage_GetIngressConnectionId(message));
+ if (metisConnection_HasWldr(conn)) {
+ metisConnection_DetectLosses((MetisConnection *) conn, message);
+ }
+ if (metisMessage_HasWldr(message) && (metisMessage_GetWldrType(message) == WLDR_NOTIFICATION)) {
+ //this is a wldr notification packet. We can discard it
+ metisMessage_Release(&message);
+ return;
+ }
+ metisMessageProcessor_Receive(metis->processor, message);
+ }
+}
+
+MetisTicks
+metisForwarder_GetTicks(const MetisForwarder *metis)
+{
+ assertNotNull(metis, "Parameter must be non-null");
+ return parcClock_GetTime(metis->clock) + metis->clockOffset;
+}
+
+MetisTicks
+metisForwarder_NanosToTicks(uint64_t nanos)
+{
+ return NSEC_TO_TICKS(nanos);
+}
+
+uint64_t
+metisForwarder_TicksToNanos(MetisTicks ticks)
+{
+ return (1000000000ULL) * ticks / METISHZ;
+}
+
+bool
+metisForwarder_AddOrUpdateRoute(MetisForwarder *metis, CPIRouteEntry *route)
+{
+ assertNotNull(metis, "Parameter metis must be non-null");
+ assertNotNull(route, "Parameter route must be non-null");
+
+ // we only have one message processor
+ return metisMessageProcessor_AddOrUpdateRoute(metis->processor, route);
+}
+
+bool
+metisForwarder_RemoveRoute(MetisForwarder *metis, CPIRouteEntry *route)
+{
+ assertNotNull(metis, "Parameter metis must be non-null");
+ assertNotNull(route, "Parameter route must be non-null");
+
+ // we only have one message processor
+ return metisMessageProcessor_RemoveRoute(metis->processor, route);
+}
+
+void
+metisForwarder_RemoveConnectionIdFromRoutes(MetisForwarder *metis, unsigned connectionId)
+{
+ assertNotNull(metis, "Parameter metis must be non-null");
+ metisMessageProcessor_RemoveConnectionIdFromRoutes(metis->processor, connectionId);
+}
+
+void
+metisForwarder_SetStrategy(MetisForwarder *metis, CCNxName *prefix, const char *strategy)
+{
+ assertNotNull(metis, "Parameter metis must be non-null");
+ assertNotNull(prefix, "Parameter route must be non-null");
+
+ if (strategy == NULL) {
+ strategy = "random";
+ }
+
+ metisProcessor_SetStrategy(metis->processor, prefix, strategy);
+}
+
+void
+metisForwarder_AddTap(MetisForwarder *metis, MetisTap *tap)
+{
+ metisMessageProcessor_AddTap(metis->processor, tap);
+}
+
+void
+metisForwarder_RemoveTap(MetisForwarder *metis, MetisTap *tap)
+{
+ metisMessageProcessor_RemoveTap(metis->processor, tap);
+}
+
+MetisFibEntryList *
+metisForwarder_GetFibEntries(MetisForwarder *metis)
+{
+ return metisMessageProcessor_GetFibEntries(metis->processor);
+}
+
+void
+metisForwarder_SetContentObjectStoreSize(MetisForwarder *metis, size_t maximumContentStoreSize)
+{
+ metisMessageProcessor_SetContentObjectStoreSize(metis->processor, maximumContentStoreSize);
+}
+
+void
+metisForwarder_ClearCache(MetisForwarder *metis)
+{
+ metisMessageProcessor_ClearCache(metis->processor);
+}
+
+PARCClock *
+metisForwarder_GetClock(const MetisForwarder *metis)
+{
+ return metis->clock;
+}
+
+// =======================================================
+
+static void
+_signal_cb(int sig, PARCEventType events, void *user_data)
+{
+ MetisForwarder *metis = (MetisForwarder *) user_data;
+
+ metisLogger_Log(metis->logger, MetisLoggerFacility_Core, PARCLogLevel_Warning, __func__,
+ "signal %d events %d", sig, events);
+
+ switch ((int) sig) {
+ case SIGTERM:
+ metisLogger_Log(metis->logger, MetisLoggerFacility_Core, PARCLogLevel_Warning, __func__,
+ "Caught an terminate signal; exiting cleanly.");
+ metisDispatcher_Stop(metis->dispatcher);
+ break;
+
+ case SIGINT:
+ metisLogger_Log(metis->logger, MetisLoggerFacility_Core, PARCLogLevel_Warning, __func__,
+ "Caught an interrupt signal; exiting cleanly.");
+ metisDispatcher_Stop(metis->dispatcher);
+ break;
+
+ case SIGUSR1:
+ // dump stats
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+_keepalive_cb(int fd, PARCEventType what, void *user_data)
+{
+ assertTrue(what & PARCEventType_Timeout, "Got unexpected tick_cb: %d", what);
+ // function is just a keepalive for Metis, does not do anything
+}
diff --git a/metis/ccnx/forwarder/metis/core/metis_Forwarder.h b/metis/ccnx/forwarder/metis/core/metis_Forwarder.h
new file mode 100644
index 00000000..b3240dc6
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_Forwarder.h
@@ -0,0 +1,340 @@
+/*
+ * 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 methods in this header are for the non-threaded forwarder. They should only be called
+ * within the forwarders thread of execution.
+ */
+
+#ifndef Metis_metis_Forwarder_h
+#define Metis_metis_Forwarder_h
+
+#include <sys/time.h>
+#include <stdlib.h>
+
+#include <ccnx/api/control/cpi_RouteEntry.h>
+
+#include <ccnx/forwarder/metis/messenger/metis_Messenger.h>
+#include <ccnx/forwarder/metis/core/metis_Dispatcher.h>
+#include <ccnx/forwarder/metis/core/metis_ConnectionTable.h>
+
+#include <ccnx/forwarder/metis/core/metis_Message.h>
+#include <ccnx/forwarder/metis/core/metis_Ticks.h>
+#include <ccnx/forwarder/metis/core/metis_Logger.h>
+#include <ccnx/forwarder/metis/io/metis_ListenerSet.h>
+
+#include <ccnx/forwarder/metis/processor/metis_FibEntryList.h>
+
+#include <parc/algol/parc_Clock.h>
+
+
+#define PORT_NUMBER 9695
+#define PORT_NUMBER_AS_STRING "9695"
+
+// ==============================================
+
+struct metis_forwarder;
+typedef struct metis_forwarder MetisForwarder;
+
+// needs to be after the definition of MetisForwarder
+#include <ccnx/forwarder/metis/config/metis_Configuration.h>
+
+/**
+ * @function metisForwarder_Create
+ * @abstract Create the forwarder and use the provided logger for diagnostic output
+ * @discussion
+ * If the logger is null, Metis will create a STDOUT logger.
+ *
+ * @param logger may be NULL
+ * @return <#return#>
+ */
+MetisForwarder *metisForwarder_Create(MetisLogger *logger);
+
+/**
+ * @function metisForwarder_Destroy
+ * @abstract Destroys the forwarder, stopping all traffic and freeing all memory
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+void metisForwarder_Destroy(MetisForwarder **metisPtr);
+
+/**
+ * @function metisForwarder_SetupAllListeners
+ * @abstract Setup all listeners (tcp, udp, local, ether, ip multicast) on all interfaces
+ * @discussion
+ * Sets up all listeners on all running interfaces. This provides a quick and easy
+ * startup, rather than providing a configuration file or programmatic commands.
+ *
+ * @param port is used by TCP and UDP listeners, in host byte order
+ * @param localPath is the AF_UNIX path to use, if NULL no AF_UNIX listener is setup
+ */
+void metisForwarder_SetupAllListeners(MetisForwarder *forwarder, uint16_t port, const char *localPath);
+
+/**
+ * Configure Metis via a configuration file
+ *
+ * The configuration file is a set of lines, just like used in metis_control.
+ * You need to have "add listener" lines in the file to receive connections. No default
+ * listeners are configured.
+ *
+ * @param [in] forwarder An alloated MetisForwarder
+ * @param [in] filename The path to the configuration file
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+void metisForwarder_SetupFromConfigFile(MetisForwarder *forwarder, const char *filename);
+
+/**
+ * Returns the logger used by this forwarder
+ *
+ * If you will store the logger, you should acquire a reference to it.
+ *
+ * @param [in] metis An allocated Metis forwarder
+ *
+ * @retval non-null The logger used by Metis
+ * @retval null An error
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisLogger *metisForwarder_GetLogger(const MetisForwarder *metis);
+
+/**
+ * @function metisForwarder_SetLogLevel
+ * @abstract Sets the minimum level to log
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ */
+void metisForwarder_SetLogLevel(MetisForwarder *metis, PARCLogLevel level);
+
+/**
+ * @function metisForwarder_GetNextConnectionId
+ * @abstract Get the next identifier for a new connection
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+unsigned metisForwarder_GetNextConnectionId(MetisForwarder *metis);
+
+MetisMessenger *metisForwarder_GetMessenger(MetisForwarder *metis);
+
+MetisDispatcher *metisForwarder_GetDispatcher(MetisForwarder *metis);
+
+/**
+ * Returns the set of currently active listeners
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [in] metis An allocated Metis forwarder
+ *
+ * @retval non-null The set of active listeners
+ * @retval null An error
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisListenerSet *metisForwarder_GetListenerSet(MetisForwarder *metis);
+
+/**
+ * Returns the forwrder's connection table
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [in] metis An allocated Metis forwarder
+ *
+ * @retval non-null The connection tabler
+ * @retval null An error
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisConnectionTable *metisForwarder_GetConnectionTable(MetisForwarder *metis);
+
+/**
+ * Returns a Tick-based clock
+ *
+ * Runs at approximately 1 msec per tick (see METISHZ in metis_Forwarder.c).
+ * Do not Release this clock. If you save a copy of it, create your own
+ * reference to it with parcClock_Acquire().
+ *
+ * @param [in] metis An allocated Metis forwarder
+ *
+ * @retval non-null An allocated Metis Clock based on the Tick counter
+ * @retval null An error
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+PARCClock *metisForwarder_GetClock(const MetisForwarder *metis);
+
+/**
+ * Direct call to get the Tick clock
+ *
+ * Runs at approximately 1 msec per tick (see METISHZ in metis_Forwarder.c)
+ *
+ * @param [in] metis An allocated Metis forwarder
+ *
+ * @retval <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisTicks metisForwarder_GetTicks(const MetisForwarder *metis);
+
+/**
+ * Convert nano seconds to Ticks
+ *
+ * Converts nano seconds to Ticks, based on METISHZ (in metis_Forwarder.c)
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @retval <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisTicks metisForwarder_NanosToTicks(uint64_t nanos);
+
+
+uint64_t metisForwarder_TicksToNanos(MetisTicks ticks);
+
+void metisForwarder_Receive(MetisForwarder *metis, MetisMessage *mesage);
+
+/**
+ * @function metisForwarder_AddOrUpdateRoute
+ * @abstract Adds or updates a route on all the message processors
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+bool metisForwarder_AddOrUpdateRoute(MetisForwarder *metis, CPIRouteEntry *route);
+
+/**
+ * @function metisForwarder_RemoveRoute
+ * @abstract Removes a route from all the message processors
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+bool metisForwarder_RemoveRoute(MetisForwarder *metis, CPIRouteEntry *route);
+
+/**
+ * Removes a connection id from all routes
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @return <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+void metisForwarder_RemoveConnectionIdFromRoutes(MetisForwarder *metis, unsigned connectionId);
+
+/**
+ * @function metisForwarder_GetConfiguration
+ * @abstract The configuration object
+ * @discussion
+ * The configuration contains all user-issued commands. It does not include dynamic state.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+MetisConfiguration *metisForwarder_GetConfiguration(MetisForwarder *metis);
+
+MetisFibEntryList *metisForwarder_GetFibEntries(MetisForwarder *metis);
+
+/**
+ * Sets the maximum number of content objects in the content store
+ *
+ * Implementation dependent - may wipe the cache.
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+void metisForwarder_SetContentObjectStoreSize(MetisForwarder *metis, size_t maximumContentStoreSize);
+
+// ========================
+// Functions to manipulate the event dispatcher
+
+#include <ccnx/forwarder/metis/processor/metis_Tap.h>
+
+/**
+ * @function metisForwarder_AddTap
+ * @abstract Add a diagnostic tap to see message events.
+ * @discussion
+ * There can only be one tap at a time. The most recent add wins.
+ *
+ * @param <#param1#>
+ */
+void metisForwarder_AddTap(MetisForwarder *metis, MetisTap *tap);
+
+/**
+ * @function metisForwarder_RemoveTap
+ * @abstract Removes a message tap, no effect if it was not in effect
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ */
+void metisForwarder_RemoveTap(MetisForwarder *metis, MetisTap *tap);
+
+void metisForwarder_SetChacheStoreFlag(MetisForwarder *metis, bool val);
+
+bool metisForwarder_GetChacheStoreFlag(MetisForwarder *metis);
+
+void metisForwarder_SetChacheServeFlag(MetisForwarder *metis, bool val);
+
+bool metisForwarder_GetChacheServeFlag(MetisForwarder *metis);
+
+void metisForwarder_ClearCache(MetisForwarder *metis);
+
+void metisForwarder_SetStrategy(MetisForwarder *metis, CCNxName *prefix, const char *strategy);
+
+#endif // Metis_metis_Forwarder_h
diff --git a/metis/ccnx/forwarder/metis/core/metis_Logger.c b/metis/ccnx/forwarder/metis/core/metis_Logger.c
new file mode 100644
index 00000000..cb4d2beb
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_Logger.c
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <LongBow/runtime.h>
+
+#include <parc/algol/parc_Memory.h>
+#include <parc/algol/parc_Object.h>
+
+#include <parc/logging/parc_Log.h>
+
+#include <ccnx/forwarder/metis/core/metis_Logger.h>
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+
+struct metis_logger {
+ PARCClock *clock;
+
+ PARCLogReporter *reporter;
+ PARCLog *loggerArray[MetisLoggerFacility_END];
+};
+
+static const struct facility_to_string {
+ MetisLoggerFacility facility;
+ const char *string;
+} _facilityToString[] = {
+ { .facility = MetisLoggerFacility_Config, .string = "Config" },
+ { .facility = MetisLoggerFacility_Core, .string = "Core" },
+ { .facility = MetisLoggerFacility_IO, .string = "IO" },
+ { .facility = MetisLoggerFacility_Message, .string = "Message" },
+ { .facility = MetisLoggerFacility_Processor, .string = "Processor" },
+ { .facility = 0, .string = NULL }
+};
+
+const char *
+metisLogger_FacilityString(MetisLoggerFacility facility)
+{
+ for (int i = 0; _facilityToString[i].string != NULL; i++) {
+ if (_facilityToString[i].facility == facility) {
+ return _facilityToString[i].string;
+ }
+ }
+ return "Unknown";
+}
+
+static void
+_allocateLoggers(MetisLogger *logger, PARCLogReporter *reporter)
+{
+ trapUnexpectedStateIf(logger->reporter != NULL, "Trying to allocate a reporter when the previous one is not null");
+ logger->reporter = parcLogReporter_Acquire(reporter);
+
+ char hostname[255];
+ int gotHostName = gethostname(hostname, 255);
+ if (gotHostName < 0) {
+ snprintf(hostname, 255, "unknown");
+ }
+
+ for (int i = 0; i < MetisLoggerFacility_END; i++) {
+ logger->loggerArray[i] = parcLog_Create(hostname, metisLogger_FacilityString(i), "metis", logger->reporter);
+ parcLog_SetLevel(logger->loggerArray[i], PARCLogLevel_Error);
+ }
+}
+
+static void
+_releaseLoggers(MetisLogger *logger)
+{
+ for (int i = 0; i < MetisLoggerFacility_END; i++) {
+ parcLog_Release(&logger->loggerArray[i]);
+ }
+ parcLogReporter_Release(&logger->reporter);
+}
+
+static void
+_destroyer(MetisLogger **loggerPtr)
+{
+ MetisLogger *logger = *loggerPtr;
+ _releaseLoggers(logger);
+ parcClock_Release(&(*loggerPtr)->clock);
+}
+
+parcObject_ExtendPARCObject(MetisLogger, _destroyer, NULL, NULL, NULL, NULL, NULL, NULL);
+
+parcObject_ImplementAcquire(metisLogger, MetisLogger);
+
+parcObject_ImplementRelease(metisLogger, MetisLogger);
+
+MetisLogger *
+metisLogger_Create(PARCLogReporter *reporter, const PARCClock *clock)
+{
+ assertNotNull(reporter, "Parameter reporter must be non-null");
+ assertNotNull(clock, "Parameter clock must be non-null");
+
+ MetisLogger *logger = parcObject_CreateAndClearInstance(MetisLogger);
+ if (logger) {
+ logger->clock = parcClock_Acquire(clock);
+ _allocateLoggers(logger, reporter);
+ }
+
+ return logger;
+}
+
+void
+metisLogger_SetReporter(MetisLogger *logger, PARCLogReporter *reporter)
+{
+ assertNotNull(logger, "Parameter logger must be non-null");
+
+ // save the log level state
+ PARCLogLevel savedLevels[MetisLoggerFacility_END];
+ for (int i = 0; i < MetisLoggerFacility_END; i++) {
+ savedLevels[i] = parcLog_GetLevel(logger->loggerArray[i]);
+ }
+
+ _releaseLoggers(logger);
+
+ _allocateLoggers(logger, reporter);
+
+ // restore log level state
+ for (int i = 0; i < MetisLoggerFacility_END; i++) {
+ parcLog_SetLevel(logger->loggerArray[i], savedLevels[i]);
+ }
+}
+
+void
+metisLogger_SetClock(MetisLogger *logger, PARCClock *clock)
+{
+ assertNotNull(logger, "Parameter logger must be non-null");
+ parcClock_Release(&logger->clock);
+ logger->clock = parcClock_Acquire(clock);
+}
+
+static void
+_assertInvariants(const MetisLogger *logger, MetisLoggerFacility facility)
+{
+ assertNotNull(logger, "Parameter logger must be non-null");
+ trapOutOfBoundsIf(facility >= MetisLoggerFacility_END, "Invalid facility %d", facility);
+}
+
+void
+metisLogger_SetLogLevel(MetisLogger *logger, MetisLoggerFacility facility, PARCLogLevel minimumLevel)
+{
+ _assertInvariants(logger, facility);
+ PARCLog *log = logger->loggerArray[facility];
+ parcLog_SetLevel(log, minimumLevel);
+}
+
+bool
+metisLogger_IsLoggable(const MetisLogger *logger, MetisLoggerFacility facility, PARCLogLevel level)
+{
+ _assertInvariants(logger, facility);
+ PARCLog *log = logger->loggerArray[facility];
+ return parcLog_IsLoggable(log, level);
+}
+
+void
+metisLogger_Log(MetisLogger *logger, MetisLoggerFacility facility, PARCLogLevel level, const char *module, const char *format, ...)
+{
+ if (metisLogger_IsLoggable(logger, facility, level)) {
+ // this is logged as the messageid
+ uint64_t logtime = parcClock_GetTime(logger->clock);
+
+ // metisLogger_IsLoggable asserted invariants so we know facility is in bounds
+ PARCLog *log = logger->loggerArray[facility];
+
+ va_list va;
+ va_start(va, format);
+
+ parcLog_MessageVaList(log, level, logtime, format, va);
+
+ va_end(va);
+ }
+}
+
diff --git a/metis/ccnx/forwarder/metis/core/metis_Logger.h b/metis/ccnx/forwarder/metis/core/metis_Logger.h
new file mode 100644
index 00000000..5b462e22
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_Logger.h
@@ -0,0 +1,228 @@
+/*
+ * 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_Logger.h
+ * @brief Logger for the Metis forwarder
+ *
+ * A facility based logger to allow selective logging from different parts of Metis
+ *
+ */
+
+#ifndef Metis_metis_Logger_h
+#define Metis_metis_Logger_h
+
+#include <sys/time.h>
+#include <stdarg.h>
+#include <parc/algol/parc_Buffer.h>
+#include <parc/logging/parc_LogLevel.h>
+#include <parc/logging/parc_LogReporter.h>
+
+#include <parc/algol/parc_Clock.h>
+
+struct metis_logger;
+typedef struct metis_logger MetisLogger;
+
+/**
+ * CONFIG faciilty concerns anything in the /config directory
+ * CORE concerns anything in the /core directory
+ * IO concerns anything in the /io directory (listeners, connectors, tcp, ethernet, etc.)
+ * PROCESSOR concerns FIB, PIT, CS
+ * MESSAGE concerns message events, like parsing
+ */
+typedef enum {
+ MetisLoggerFacility_Config,
+ MetisLoggerFacility_Core,
+ MetisLoggerFacility_IO,
+ MetisLoggerFacility_Processor,
+ MetisLoggerFacility_Message,
+ MetisLoggerFacility_END // sentinel value
+} MetisLoggerFacility;
+
+/**
+ * Returns a string representation of a facility
+ *
+ * Do not free the returned value.
+ *
+ * @param [in] facility The facility to change to a string
+ *
+ * @retval string A string representation of the facility
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+const char * metisLogger_FacilityString(MetisLoggerFacility facility);
+
+/**
+ * Returns a string representation of a log level
+ *
+ * Do not free the returned value.
+ *
+ * @param [in] level The level to change to a string
+ *
+ * @retval string A string representation of the level
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+const char * metisLogger_LevelString(PARCLogLevel level);
+
+/**
+ * Create a logger that uses a given writer and clock
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [in] writer The output writer
+ * @param [in] clock The clock to use for log messages
+ *
+ * @retval non-null An allocated logger
+ * @retval null An error
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisLogger * metisLogger_Create(PARCLogReporter *reporter, const PARCClock *clock);
+
+/**
+ * <#One Line Description#>
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @retval <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+void metisLogger_Release(MetisLogger **loggerPtr);
+
+/**
+ * <#One Line Description#>
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @retval <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisLogger *metisLogger_Acquire(const MetisLogger *logger);
+
+/**
+ * Sets the minimum log level for a facility
+ *
+ * The default log level is ERROR. For a message to be logged, it must be of equal
+ * or higher log level.
+ *
+ * @param [in] logger An allocated logger
+ * @param [in] facility The facility to set the log level for
+ * @param [in] The minimum level to log
+ *
+ * @retval <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * {
+ * PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ * MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ * parcLogReporter_Release(&reporter);
+ * metisLogger_SetLogLevel(logger, MetisLoggerFacility_IO, PARCLogLevel_Warning);
+ * }
+ * @endcode
+ */
+void metisLogger_SetLogLevel(MetisLogger *logger, MetisLoggerFacility facility, PARCLogLevel minimumLevel);
+
+/**
+ * Tests if the log level would be logged
+ *
+ * If the facility would log the given level, returns true. May be used as a
+ * guard around expensive logging functions.
+ *
+ * @param [in] logger An allocated logger
+ * @param [in] facility The facility to test
+ * @param [in] The level to test
+ *
+ * @retval true The given facility would log the given level
+ * @retval false A message of the given level would not be logged
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+bool metisLogger_IsLoggable(const MetisLogger *logger, MetisLoggerFacility facility, PARCLogLevel level);
+
+/**
+ * Log a message
+ *
+ * The message will only be logged if it is loggable (metisLogger_IsLoggable returns true).
+ *
+ * @param [in] logger An allocated MetisLogger
+ * @param [in] facility The facility to log under
+ * @param [in] level The log level of the message
+ * @param [in] module The specific module logging the message
+ * @param [in] format The message with varargs
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+void metisLogger_Log(MetisLogger *logger, MetisLoggerFacility facility, PARCLogLevel level, const char *module, const char *format, ...);
+
+/**
+ * Switch the logger to a new reporter
+ *
+ * Will close the old reporter and re-setup the internal loggers to use the new reporter.
+ * All current log level settings are preserved.
+ *
+ * @param [in] logger An allocated MetisLogger
+ * @param [in] reporter An allocated PARCLogReporter
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+void metisLogger_SetReporter(MetisLogger *logger, PARCLogReporter *reporter);
+
+/**
+ * Set a new clock to use with the logger
+ *
+ * The logger will start getting the time (logged as the messageid) from the specified clock
+ *
+ * @param [in] logger An allocated MetisLogger
+ * @param [in] clock An allocated PARCClock
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+void metisLogger_SetClock(MetisLogger *logger, PARCClock *clock);
+#endif // Metis_metis_Logger_h
diff --git a/metis/ccnx/forwarder/metis/core/metis_Message.c b/metis/ccnx/forwarder/metis/core/metis_Message.c
new file mode 100644
index 00000000..77295476
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_Message.c
@@ -0,0 +1,992 @@
+/*
+ * 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 implementation of metisMessage_Slice() copies data, it needs to do this by reference.
+ *
+ */
+#include <config.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+
+#include <ccnx/forwarder/metis/core/metis_Message.h>
+#include <ccnx/forwarder/metis/core/metis_StreamBuffer.h>
+#include <ccnx/forwarder/metis/tlv/metis_Tlv.h>
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+
+#include <ccnx/forwarder/metis/core/metis_Wldr.h>
+
+#include <parc/algol/parc_Memory.h>
+#include <parc/algol/parc_Hash.h>
+
+#include <LongBow/runtime.h>
+
+#include <parc/algol/parc_EventBuffer.h>
+
+struct metis_message {
+ MetisLogger *logger;
+
+ MetisTicks receiveTime;
+ unsigned ingressConnectionId;
+
+ PARCEventBuffer *messageBytes;
+ uint8_t *messageHead;
+
+ unsigned refcount;
+
+ struct tlv_skeleton skeleton;
+
+ bool hasKeyId;
+ uint32_t keyIdHash;
+ bool isKeyIdVerified;
+
+ bool hasContentObjectHash;
+ // may be null, even if hasContentObjectHash true due to lazy calculation
+ PARCBuffer *contentObjectHash;
+
+ PARCBuffer *certificate;
+
+ PARCBuffer *publicKey;
+
+ bool hasInterestLifetime;
+ uint64_t interestLifetimeTicks;
+
+ bool hasExpiryTimeTicks;
+ uint64_t expiryTimeTicks;
+
+ bool hasRecommendedCacheTimeTicks;
+ uint64_t recommendedCacheTimeTicks;
+
+ bool hasName;
+ MetisTlvName *name;
+
+ bool hasFragmentPayload;
+
+ MetisMessagePacketType packetType;
+
+
+ bool hasPathLabel;
+ uint64_t pathLabel;
+
+ bool hasWldr;
+ //the following fields are valid only if hasWldr is true
+ uint8_t wldrType;
+ uint16_t wldrLbl; //if wldrType == WLDR_LBL this indicates the message label
+ //if wldrType == WLDR_NOTIFICATION this indicates the expected message label
+ uint16_t wldrLastReceived; //this field is valid only when wldrType == WLDR_NOTIFICATION. In this case,
+ //all the messages between wldrLbl (included) and wldrLastReceived (excluded)
+ //are considered lost
+};
+
+static void
+_setupWldr(MetisMessage *message)
+{
+ uint8_t wldr_header = 0;
+ parcEventBuffer_copyOut(message->messageBytes, &wldr_header, 1);
+ if (wldr_header == WLDR_HEADER) {
+ message->hasWldr = true;
+ parcEventBuffer_Read(message->messageBytes, NULL, 1);
+ parcEventBuffer_Read(message->messageBytes, &(message->wldrType), 1);
+ if (message->wldrType == WLDR_LBL) {
+ parcEventBuffer_Read(message->messageBytes, &(message->wldrLbl), 2);
+ parcEventBuffer_Read(message->messageBytes, NULL, 2);
+ } else if (message->wldrType == WLDR_NOTIFICATION) {
+ parcEventBuffer_Read(message->messageBytes, &(message->wldrLbl), 2);
+ parcEventBuffer_Read(message->messageBytes, &(message->wldrLastReceived), 2);
+ } else {
+ //find a better way to exit (look into longBow)
+ printf("Error, Unknown WLDR Type\n");
+ exit(0);
+ }
+ message->messageHead = parcEventBuffer_Pullup(message->messageBytes, -1);
+ } else {
+ message->hasWldr = false;
+ }
+}
+
+static void
+_setupName(MetisMessage *message)
+{
+ MetisTlvExtent extent = metisTlvSkeleton_GetName(&message->skeleton);
+ if (extent.offset > 0) {
+ message->hasName = true;
+ message->name = metisTlvName_Create(&message->messageHead[extent.offset], extent.length);
+ } else {
+ message->hasName = false;
+ message->name = NULL;
+ }
+}
+
+static void
+_setupValidationParams(MetisMessage *message)
+{
+ MetisTlvExtent extent = metisTlvSkeleton_GetKeyId(&message->skeleton);
+ if (extent.offset > 0) {
+ message->hasKeyId = true;
+ message->keyIdHash = parcHash32_Data(&message->messageHead[extent.offset], extent.length);
+ } else {
+ message->hasKeyId = false;
+ message->keyIdHash = 0;
+ }
+ message->isKeyIdVerified = false;
+
+ extent = metisTlvSkeleton_GetCertificate(&message->skeleton);
+ if (extent.offset > 0) {
+ message->certificate = parcBuffer_Flip(parcBuffer_CreateFromArray(&message->messageHead[extent.offset], extent.length));
+ } else {
+ message->certificate = NULL;
+ }
+
+ extent = metisTlvSkeleton_GetPublicKey(&message->skeleton);
+ if (extent.offset > 0) {
+ message->publicKey = parcBuffer_Flip(parcBuffer_CreateFromArray(&message->messageHead[extent.offset], extent.length));
+ } else {
+ message->publicKey = NULL;
+ }
+}
+
+static void
+_setupContentObjectHash(MetisMessage *message)
+{
+ if (metisTlvSkeleton_IsPacketTypeInterest(&message->skeleton)) {
+ MetisTlvExtent extent = metisTlvSkeleton_GetObjectHash(&message->skeleton);
+ // pre-compute it for an interest
+ if (extent.offset > 0) {
+ message->hasContentObjectHash = true;
+ message->contentObjectHash =
+ parcBuffer_Flip(parcBuffer_CreateFromArray(&message->messageHead[extent.offset], extent.length));
+ } else {
+ message->hasContentObjectHash = false;
+ }
+ } else if (metisTlvSkeleton_IsPacketTypeContentObject(&message->skeleton)) {
+ // we will compute this on demand
+ message->hasContentObjectHash = true;
+ message->contentObjectHash = NULL;
+ } else {
+ message->hasContentObjectHash = false;
+ }
+}
+
+static void
+_setupInterestLifetime(MetisMessage *message)
+{
+ MetisTlvExtent extent = metisTlvSkeleton_GetInterestLifetime(&message->skeleton);
+ message->hasInterestLifetime = false;
+ if (metisTlvSkeleton_IsPacketTypeInterest(&message->skeleton) && extent.offset > 0) {
+ message->hasInterestLifetime = true;
+ uint64_t lifetimeMilliseconds;
+ metisTlv_ExtentToVarInt(message->messageHead, &extent, &lifetimeMilliseconds);
+ message->interestLifetimeTicks = metisForwarder_NanosToTicks(lifetimeMilliseconds * 1000000);
+ }
+}
+
+static void
+_setupPathLabel(MetisMessage *message)
+{
+ MetisTlvExtent extent = metisTlvSkeleton_GetPathLabel(&message->skeleton);
+ message->hasPathLabel = false;
+ if (metisTlvSkeleton_IsPacketTypeContentObject(&message->skeleton) && extent.offset > 0) {
+ message->hasPathLabel = true;
+ uint64_t pathLabel;
+ metisTlv_ExtentToVarInt(message->messageHead, &extent, &pathLabel);
+ message->pathLabel = pathLabel;
+ }
+}
+
+
+static void
+_setupFragmentPayload(MetisMessage *message)
+{
+ MetisTlvExtent extent = metisTlvSkeleton_GetFragmentPayload(&message->skeleton);
+ if (extent.offset > 0) {
+ message->hasFragmentPayload = true;
+ } else {
+ message->hasFragmentPayload = true;
+ }
+}
+
+static void
+_setupExpiryTime(MetisMessage *message)
+{
+ MetisTlvExtent expiryTimeExtent = metisTlvSkeleton_GetExpiryTime(&message->skeleton);
+
+ message->hasExpiryTimeTicks = false;
+
+ if (metisTlvSkeleton_IsPacketTypeContentObject(&message->skeleton)) {
+ if (!metisTlvExtent_Equals(&expiryTimeExtent, &metisTlvExtent_NotFound)) {
+ uint64_t expiryTimeUTC = 0;
+ if (metisTlv_ExtentToVarInt(metisTlvSkeleton_GetPacket(&message->skeleton), &expiryTimeExtent, &expiryTimeUTC)) {
+ message->hasExpiryTimeTicks = true;
+
+ // Convert it to ticks that we can use for expiration checking.
+ uint64_t metisWallClockTime = parcClock_GetTime(parcClock_Wallclock());
+ uint64_t currentTimeInTicks = parcClock_GetTime(parcClock_Monotonic());
+
+ message->expiryTimeTicks = expiryTimeUTC - metisWallClockTime + currentTimeInTicks;
+ }
+ }
+ }
+}
+
+static void
+_setupRecommendedCacheTime(MetisMessage *message)
+{
+ MetisTlvExtent cacheTimeExtent = metisTlvSkeleton_GetCacheTimeHeader(&message->skeleton);
+
+ message->hasRecommendedCacheTimeTicks = false;
+
+ if (metisTlvSkeleton_IsPacketTypeContentObject(&message->skeleton)) {
+ if (!metisTlvExtent_Equals(&cacheTimeExtent, &metisTlvExtent_NotFound)) {
+ uint64_t recommendedCacheTime = 0;
+ if (metisTlv_ExtentToVarInt(metisTlvSkeleton_GetPacket(&message->skeleton), &cacheTimeExtent, &recommendedCacheTime)) {
+ message->hasRecommendedCacheTimeTicks = true;
+
+ // Convert it to ticks that we can use for expiration checking.
+ uint64_t metisWallClockTime = parcClock_GetTime(parcClock_Wallclock());
+ uint64_t currentTimeInTicks = parcClock_GetTime(parcClock_Monotonic());
+
+ message->recommendedCacheTimeTicks = recommendedCacheTime - metisWallClockTime + currentTimeInTicks;
+ }
+ }
+ }
+}
+
+/**
+ * Parse the TLV skeleton and setup message pointers
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [in] message An allocated messae with the message->messagaeHead pointer setup.
+ *
+ * @retval false Error parsing message
+ * @retval true Good parse
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+static bool
+_setupInternalData(MetisMessage *message)
+{
+ // -1 means linearize the whole buffer
+ message->messageHead = parcEventBuffer_Pullup(message->messageBytes, -1);
+ message->packetType = MetisMessagePacketType_Unknown;
+
+ _setupWldr(message);
+ if (message->hasWldr == true && message->wldrType == WLDR_NOTIFICATION) {
+ //this is a WLDR notification message, all the other fields are meaningless because the
+ //packet will be dropped immmedialtly after the retransmissions. For this reason we can
+ //immediatly retrun and avoid the parsing of the message
+ return true;
+ }
+ //now the WLDR header is removed from the packet and the parsing shuould work as usual
+
+ bool goodSkeleton = metisTlvSkeleton_Parse(&message->skeleton, message->messageHead, message->logger);
+
+ if (goodSkeleton) {
+ _setupName(message);
+ _setupValidationParams(message);
+ _setupContentObjectHash(message);
+ _setupInterestLifetime(message);
+ _setupPathLabel(message);
+ _setupFragmentPayload(message);
+ _setupExpiryTime(message);
+ _setupRecommendedCacheTime(message);
+
+ // set the packet type
+ bool requiresName = false;
+ if (metisTlvSkeleton_IsPacketTypeInterest(&message->skeleton)) {
+ message->packetType = MetisMessagePacketType_Interest;
+ requiresName = true;
+ } else if (metisTlvSkeleton_IsPacketTypeContentObject(&message->skeleton)) {
+ message->packetType = MetisMessagePacketType_ContentObject;
+ requiresName = true;
+ } else if (metisTlvSkeleton_IsPacketTypeHopByHopFragment(&message->skeleton)) {
+ message->packetType = MetisMessagePacketType_HopByHopFrag;
+ } else if (metisTlvSkeleton_IsPacketTypeControl(&message->skeleton)) {
+ message->packetType = MetisMessagePacketType_Control;
+ } else if (metisTlvSkeleton_IsPacketTypeInterestReturn(&message->skeleton)) {
+ message->packetType = MetisMessagePacketType_InterestReturn;
+ }
+
+ if (requiresName && !metisMessage_HasName(message)) {
+ goodSkeleton = false;
+ }
+ }
+
+ return goodSkeleton;
+}
+
+MetisMessage *
+metisMessage_Acquire(const MetisMessage *message)
+{
+ MetisMessage *copy = (MetisMessage *) message;
+ copy->refcount++;
+ return copy;
+}
+
+MetisMessage *
+metisMessage_CreateFromParcBuffer(PARCBuffer *buffer, unsigned ingressConnectionId, MetisTicks receiveTime, MetisLogger *logger)
+{
+ MetisMessage *message = parcMemory_AllocateAndClear(sizeof(MetisMessage));
+ assertNotNull(message, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisMessage));
+ message->receiveTime = receiveTime;
+ message->ingressConnectionId = ingressConnectionId;
+ message->messageBytes = parcEventBuffer_Create();
+ message->refcount = 1;
+ message->logger = metisLogger_Acquire(logger);
+
+ // this copies the data
+ int failure = parcEventBuffer_Append(message->messageBytes, parcBuffer_Overlay(buffer, 0), parcBuffer_Remaining(buffer));
+ assertFalse(failure, "Got failure copying data into buffer: (%d) %s", errno, strerror(errno));
+
+ bool goodSkeleton = _setupInternalData(message);
+ if (goodSkeleton) {
+ if (metisLogger_IsLoggable(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) {
+ metisLogger_Log(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__,
+ "Message %p created ingress %u",
+ (void *) message, ingressConnectionId);
+ }
+ } else {
+ if (metisLogger_IsLoggable(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Warning)) {
+ metisLogger_Log(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Warning, __func__,
+ "Error setting up skeleton for buffer %p ingress %u",
+ (void *) buffer, ingressConnectionId);
+ }
+
+ metisMessage_Release(&message);
+ }
+ return message;
+}
+
+MetisMessage *
+metisMessage_CreateFromArray(const uint8_t *data, size_t dataLength, unsigned ingressConnectionId, MetisTicks receiveTime, MetisLogger *logger)
+{
+ MetisMessage *message = parcMemory_AllocateAndClear(sizeof(MetisMessage));
+ assertNotNull(message, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisMessage));
+ message->receiveTime = receiveTime;
+ message->ingressConnectionId = ingressConnectionId;
+ message->messageBytes = parcEventBuffer_Create();
+ message->refcount = 1;
+ message->logger = metisLogger_Acquire(logger);
+
+ // this copies the data
+ int failure = parcEventBuffer_Append(message->messageBytes, (void *) data, dataLength);
+ assertFalse(failure, "Got failure copying data into PARCEventBuffer: (%d) %s", errno, strerror(errno));
+
+ bool goodSkeleton = _setupInternalData(message);
+ if (goodSkeleton) {
+ if (metisLogger_IsLoggable(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) {
+ metisLogger_Log(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__,
+ "Message %p created ingress %u",
+ (void *) message, ingressConnectionId);
+ }
+ } else {
+ if (metisLogger_IsLoggable(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Warning)) {
+ metisLogger_Log(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Warning, __func__,
+ "Error setting up skeleton for array %p ingress %u",
+ (void *) data, ingressConnectionId);
+ }
+
+ metisMessage_Release(&message);
+ }
+
+ return message;
+}
+
+MetisMessage *
+metisMessage_ReadFromBuffer(unsigned ingressConnectionId, MetisTicks receiveTime, PARCEventBuffer *input, size_t bytesToRead, MetisLogger *logger)
+{
+ MetisMessage *message = parcMemory_AllocateAndClear(sizeof(MetisMessage));
+ assertNotNull(message, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisMessage));
+ message->receiveTime = receiveTime;
+ message->ingressConnectionId = ingressConnectionId;
+ message->messageBytes = parcEventBuffer_Create();
+ message->refcount = 1;
+ message->logger = metisLogger_Acquire(logger);
+
+ // dequeue into packet buffer. This is a near-zero-copy operation from
+ // one buffer to another. It only copies if the message falls across iovec
+ // boundaries.
+ int bytesRead = parcEventBuffer_ReadIntoBuffer(input, message->messageBytes, bytesToRead);
+ assertTrue(bytesRead == bytesToRead, "Partial read, expected %zu got %d", bytesToRead, bytesRead);
+
+ bool goodSkeleton = _setupInternalData(message);
+ if (goodSkeleton) {
+ if (metisLogger_IsLoggable(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) {
+ metisLogger_Log(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__,
+ "Message %p created ingress %u",
+ (void *) message, ingressConnectionId);
+ }
+ } else {
+ if (metisLogger_IsLoggable(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Warning)) {
+ metisLogger_Log(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Warning, __func__,
+ "Error setting up skeleton for buffer %p ingress %u",
+ (void *) input, ingressConnectionId);
+ }
+
+ metisMessage_Release(&message);
+ }
+ return message;
+}
+
+MetisMessage *
+metisMessage_CreateFromBuffer(unsigned ingressConnectionId, MetisTicks receiveTime, PARCEventBuffer *input, MetisLogger *logger)
+{
+ assertNotNull(input, "Parameter input must be non-null");
+ MetisMessage *message = parcMemory_AllocateAndClear(sizeof(MetisMessage));
+ assertNotNull(message, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisMessage));
+ message->receiveTime = receiveTime;
+ message->ingressConnectionId = ingressConnectionId;
+ message->messageBytes = input;
+ message->refcount = 1;
+ message->logger = metisLogger_Acquire(logger);
+
+ bool goodSkeleton = _setupInternalData(message);
+ if (goodSkeleton) {
+ if (metisLogger_IsLoggable(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) {
+ metisLogger_Log(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__,
+ "Message %p created ingress %u",
+ (void *) message, ingressConnectionId);
+ }
+ } else {
+ if (metisLogger_IsLoggable(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Warning)) {
+ metisLogger_Log(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Warning, __func__,
+ "Error setting up skeleton for buffer %p ingress %u",
+ (void *) input, ingressConnectionId);
+ }
+
+ metisMessage_Release(&message);
+ }
+ return message;
+}
+
+void
+metisMessage_Release(MetisMessage **messagePtr)
+{
+ assertNotNull(messagePtr, "Parameter must be non-null double pointer");
+ assertNotNull(*messagePtr, "Parameter must dereference to non-null pointer");
+
+ MetisMessage *message = *messagePtr;
+ assertTrue(message->refcount > 0, "Invalid state: metisMessage_Release called on message with 0 references %p", (void *) message);
+
+ message->refcount--;
+ if (message->refcount == 0) {
+ if (metisLogger_IsLoggable(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) {
+ metisLogger_Log(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__,
+ "Message %p destroyed",
+ (void *) message);
+ }
+
+ if (message->contentObjectHash) {
+ parcBuffer_Release(&message->contentObjectHash);
+ }
+
+ if (message->name) {
+ metisTlvName_Release(&message->name);
+ }
+
+ if (message->publicKey) {
+ parcBuffer_Release(&message->publicKey);
+ }
+
+ if (message->certificate) {
+ parcBuffer_Release(&message->certificate);
+ }
+
+ metisLogger_Release(&message->logger);
+ parcEventBuffer_Destroy(&(message->messageBytes));
+ parcMemory_Deallocate((void **) &message);
+ }
+ *messagePtr = NULL;
+}
+
+bool
+metisMessage_Write(PARCEventQueue *parcEventQueue, const MetisMessage *message)
+{
+ assertNotNull(message, "Message parameter must be non-null");
+ assertNotNull(parcEventQueue, "Buffer parameter must be non-null");
+
+ return parcEventQueue_Write(parcEventQueue, message->messageHead, parcEventBuffer_GetLength(message->messageBytes));
+}
+
+bool
+metisMessage_Append(PARCEventBuffer *writeBuffer, const MetisMessage *message)
+{
+ assertNotNull(message, "Message parameter must be non-null");
+ assertNotNull(writeBuffer, "Buffer parameter must be non-null");
+
+ if (message->messageBytes == NULL) {
+ //this is an error that we need to handle (just dropping the packet?)
+ //right now we log the event, drop the packet and return false
+ //should I release the message as well?
+ if (message->logger != NULL && metisLogger_IsLoggable(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) {
+ metisLogger_Log(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__,
+ "Message %p has a null message buffer inside!",
+ (void *) message);
+ }
+ return false;
+ }
+
+ return parcEventBuffer_Append(writeBuffer, message->messageHead, metisMessage_Length(message));
+}
+
+size_t
+metisMessage_Length(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter must be non-null");
+ return parcEventBuffer_GetLength(message->messageBytes);
+}
+
+bool
+metisMessage_HasWldr(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter must be non-null");
+ return message->hasWldr;
+}
+
+unsigned
+metisMessage_GetWldrType(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter must be non-null");
+ assertTrue(message->hasWldr == true, "This message does not contains a WLDR header");
+ return message->wldrType;
+}
+
+unsigned
+metisMessage_GetWldrLabel(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter must be non-null");
+ assertTrue(message->hasWldr == true, "This message does not contains a WLDR header");
+ return message->wldrLbl;
+}
+
+unsigned
+metisMessage_GetWldrLastReceived(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter must be non-null");
+ assertTrue(message->hasWldr == true, "This message does not contains a WLDR header");
+ return message->wldrLastReceived;
+}
+
+static void
+_removeOldWldrHeader(MetisMessage *message)
+{
+ uint8_t wldr_header = 0;
+ parcEventBuffer_copyOut(message->messageBytes, &wldr_header, 1);
+ if (wldr_header == WLDR_HEADER) {
+ parcEventBuffer_Read(message->messageBytes, NULL, WLDR_HEADER_SIZE);
+ }
+}
+
+void
+metisMessage_SetWldrLabel(MetisMessage *message, uint16_t label)
+{
+ assertNotNull(message, "Parameter must be non-null");
+ _removeOldWldrHeader(message);
+ message->hasWldr = true;
+ message->wldrType = WLDR_LBL;
+ message->wldrLbl = label;
+ uint8_t wldr_header[6];
+ wldr_header[0] = WLDR_HEADER;
+ wldr_header[1] = WLDR_LBL;
+ wldr_header[2] = label & 0xFF;
+ wldr_header[3] = (label >> 8UL) & 0xFF;
+ wldr_header[4] = 0;
+ wldr_header[5] = 0;
+ parcEventBuffer_Prepend(message->messageBytes, &wldr_header, sizeof(wldr_header));
+ uint8_t *newMessage = parcEventBuffer_Pullup(message->messageBytes, -1);
+ bool goodSkeleton = metisTlvSkeleton_Parse(&message->skeleton, newMessage + WLDR_HEADER_SIZE, message->logger);
+ if (goodSkeleton) {
+ message->messageHead = newMessage;
+ } else {
+ trapNotImplemented("[metis_Message.c] message parsing after WLDR header insertion failed");
+ }
+}
+
+void
+metisMessage_SetWldrNotification(MetisMessage *message, uint16_t expected, uint16_t lastReceived)
+{
+ assertNotNull(message, "Parameter must be non-null");
+ _removeOldWldrHeader(message);
+ message->hasWldr = true;
+ message->wldrType = WLDR_NOTIFICATION;
+ message->wldrLbl = expected;
+ message->wldrLastReceived = lastReceived;
+ uint8_t wldr_header[6];
+ wldr_header[0] = WLDR_HEADER;
+ wldr_header[1] = WLDR_NOTIFICATION;
+ wldr_header[2] = expected & 0xFF;
+ wldr_header[3] = (expected >> 8U) & 0xFF;
+ wldr_header[4] = lastReceived & 0xFF;
+ wldr_header[5] = (lastReceived >> 8U) & 0xFF;
+ parcEventBuffer_Prepend(message->messageBytes, &wldr_header, sizeof(wldr_header));
+ message->messageHead = parcEventBuffer_Pullup(message->messageBytes, -1);
+}
+
+unsigned
+metisMessage_GetIngressConnectionId(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter must be non-null");
+ return message->ingressConnectionId;
+}
+
+MetisTicks
+metisMessage_GetReceiveTime(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter must be non-null");
+ return message->receiveTime;
+}
+
+bool
+metisMessage_HasHopLimit(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter must be non-null");
+ MetisTlvExtent extent = metisTlvSkeleton_GetHopLimit(&message->skeleton);
+
+ if (extent.offset > 0) {
+ return true;
+ }
+ return false;
+}
+
+uint8_t
+metisMessage_GetHopLimit(const MetisMessage *message)
+{
+ bool hasHopLimit = metisMessage_HasHopLimit(message);
+ assertTrue(hasHopLimit, "Message does not have a HopLimit field");
+
+ MetisTlvExtent extent = metisTlvSkeleton_GetHopLimit(&message->skeleton);
+ uint8_t hopLimit = message->messageHead[extent.offset];
+ return hopLimit;
+}
+
+void
+metisMessage_SetHopLimit(MetisMessage *message, uint8_t hoplimit)
+{
+ assertNotNull(message, "Parameter must be non-null");
+ metisTlvSkeleton_UpdateHopLimit(&message->skeleton, hoplimit);
+}
+
+void
+metisMessage_UpdatePathLabel(MetisMessage *message, uint8_t outFace)
+{
+ assertNotNull(message, "Parameter must be non-null");
+ metisTlvSkeleton_UpdatePathLabel(&message->skeleton, outFace);
+}
+void
+metisMessage_ResetPathLabel(MetisMessage *message)
+{
+ assertNotNull(message, "Parameter must be non-null");
+ metisTlvSkeleton_ResetPathLabel(&message->skeleton);
+}
+
+MetisMessagePacketType
+metisMessage_GetType(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter message must be non-null");
+ return message->packetType;
+}
+
+MetisTlvName *
+metisMessage_GetName(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter message must be non-null");
+ return message->name;
+}
+
+bool
+metisMessage_GetKeyIdHash(const MetisMessage *message, uint32_t *hashOutput)
+{
+ assertNotNull(message, "Parameter message must be non-null");
+ if (message->hasKeyId) {
+ *hashOutput = message->keyIdHash;
+ return true;
+ }
+ return false;
+}
+
+PARCBuffer *
+metisMessage_GetCertificate(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter message must be non-null");
+ return message->certificate;
+}
+
+PARCBuffer *
+metisMessage_GetPublicKey(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter message must be non-null");
+ return message->publicKey;
+}
+
+bool
+metisMessage_KeyIdEquals(const MetisMessage *a, const MetisMessage *b)
+{
+ assertNotNull(a, "Parameter a must be non-null");
+ assertNotNull(b, "Parameter b must be non-null");
+
+ if (a->hasKeyId && b->hasKeyId) {
+ MetisTlvExtent ae = metisTlvSkeleton_GetKeyId(&a->skeleton);
+ MetisTlvExtent be = metisTlvSkeleton_GetKeyId(&b->skeleton);
+
+ if (ae.length == be.length) {
+ return memcmp(&a->messageHead[ae.offset], &b->messageHead[be.offset], ae.length) == 0;
+ }
+ }
+ return false;
+}
+
+bool
+metisMessage_ObjectHashEquals(MetisMessage *a, MetisMessage *b)
+{
+ assertNotNull(a, "Parameter a must be non-null");
+ assertNotNull(b, "Parameter b must be non-null");
+
+ if (a->hasContentObjectHash && b->hasContentObjectHash) {
+ if (a->contentObjectHash == NULL) {
+ PARCCryptoHash *hash = metisTlvSkeleton_ComputeContentObjectHash(&a->skeleton);
+ a->contentObjectHash = parcBuffer_Acquire(parcCryptoHash_GetDigest(hash));
+ parcCryptoHash_Release(&hash);
+ }
+
+ if (b->contentObjectHash == NULL) {
+ PARCCryptoHash *hash = metisTlvSkeleton_ComputeContentObjectHash(&b->skeleton);
+ b->contentObjectHash = parcBuffer_Acquire(parcCryptoHash_GetDigest(hash));
+ parcCryptoHash_Release(&hash);
+ }
+
+ return parcBuffer_Equals(a->contentObjectHash, b->contentObjectHash);
+ }
+
+ return false;
+}
+
+bool
+metisMessage_GetContentObjectHashHash(MetisMessage *message, uint32_t *hashOutput)
+{
+ assertNotNull(message, "Parameter message must be non-null");
+ assertNotNull(hashOutput, "Parameter hashOutput must be non-null");
+
+ if (message->hasContentObjectHash) {
+ if (message->contentObjectHash == NULL) {
+ PARCCryptoHash *hash = metisTlvSkeleton_ComputeContentObjectHash(&message->skeleton);
+ message->contentObjectHash = parcBuffer_Acquire(parcCryptoHash_GetDigest(hash));
+ parcCryptoHash_Release(&hash);
+ }
+
+ *hashOutput = (uint32_t) parcBuffer_HashCode(message->contentObjectHash);
+ return true;
+ }
+ return false;
+}
+
+bool
+metisMessage_HasPublicKey(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter message must be non-null");
+ return (message->publicKey != NULL);
+}
+
+bool
+metisMessage_HasCertificate(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter message must be non-null");
+ return (message->certificate != NULL);
+}
+
+bool
+metisMessage_HasName(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter message must be non-null");
+ return message->hasName;
+}
+
+bool
+metisMessage_HasKeyId(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter message must be non-null");
+ return message->hasKeyId;
+}
+
+bool
+metisMessage_IsKeyIdVerified(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter message must be non-null");
+ return message->isKeyIdVerified;
+}
+
+bool
+metisMessage_HasContentObjectHash(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter message must be non-null");
+ return message->hasContentObjectHash;
+}
+
+CCNxControl *
+metisMessage_CreateControlMessage(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter message must be non-null");
+ assertTrue(metisMessage_GetType(message) == MetisMessagePacketType_Control,
+ "Wrong type of message, expected %02X got %02X",
+ MetisMessagePacketType_Control,
+ metisMessage_GetType(message));
+
+ MetisTlvExtent extent = metisTlvSkeleton_GetCPI(&message->skeleton);
+ assertTrue(extent.offset > 0, "Message does not have a CPI TLV field!");
+
+ PARCJSON *json = parcJSON_ParseString((char *) &message->messageHead[ extent.offset ]);
+ CCNxControl *control = ccnxControl_CreateCPIRequest(json);
+ parcJSON_Release(&json);
+ return control;
+}
+
+bool
+metisMessage_HasInterestLifetime(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter message must be non-null");
+ return message->hasInterestLifetime;
+}
+
+uint64_t
+metisMessage_GetInterestLifetimeTicks(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter message must be non-null");
+ return message->interestLifetimeTicks;
+}
+
+bool
+metisMessage_HasFragmentPayload(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter message must be non-null");
+ return message->hasFragmentPayload;
+}
+
+size_t
+metisMessage_AppendFragmentPayload(const MetisMessage *message, PARCEventBuffer *buffer)
+{
+ size_t bytesAppended = 0;
+ if (message->hasFragmentPayload) {
+ MetisTlvExtent extent = metisTlvSkeleton_GetFragmentPayload(&message->skeleton);
+ parcEventBuffer_Append(buffer, message->messageHead + extent.offset, extent.length);
+ bytesAppended = extent.length;
+ }
+ return bytesAppended;
+}
+
+const uint8_t *
+metisMessage_FixedHeader(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter message must be non-null");
+ return message->messageHead;
+}
+
+MetisMessage *
+metisMessage_Slice(const MetisMessage *original, size_t offset, size_t length, size_t headerLength, const uint8_t header[headerLength])
+{
+ assertNotNull(original, "Parameter original must be non-null");
+ assertTrue(length > 0, "Parameter length must be positive");
+ assertTrue(offset + length <= parcEventBuffer_GetLength(original->messageBytes),
+ "Slice extends beyond end, maximum %zu got %zu",
+ parcEventBuffer_GetLength(original->messageBytes),
+ offset + length);
+
+ MetisMessage *message = parcMemory_AllocateAndClear(sizeof(MetisMessage));
+ assertNotNull(message, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisMessage));
+ message->receiveTime = original->receiveTime;
+ message->ingressConnectionId = original->ingressConnectionId;
+ message->messageBytes = parcEventBuffer_Create();
+ message->refcount = 1;
+ message->logger = metisLogger_Acquire(original->logger);
+
+ if (headerLength > 0) {
+ assertNotNull(header, "Cannot have a positive headerLength and NULL header");
+
+ // this copies the data
+ int failure = parcEventBuffer_Append(message->messageBytes, (void *) header, headerLength);
+ assertFalse(failure, "Got failure adding header data into PARCEventBuffer: (%d) %s", errno, strerror(errno));
+ }
+
+ // this copies the data
+ int failure = parcEventBuffer_Append(message->messageBytes, (uint8_t *) original->messageHead + offset, length);
+ assertFalse(failure, "Got failure adding slice data into PARCEventBuffer: (%d) %s", errno, strerror(errno));
+
+ bool goodSkeleton = _setupInternalData(message);
+ if (goodSkeleton) {
+ if (metisLogger_IsLoggable(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug)) {
+ metisLogger_Log(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Debug, __func__,
+ "Message %p created slice(%p, %zu, %zu)",
+ (void *) message, (void *) original, offset, length);
+ }
+ } else {
+ if (metisLogger_IsLoggable(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Warning)) {
+ metisLogger_Log(message->logger, MetisLoggerFacility_Message, PARCLogLevel_Warning, __func__,
+ "Error setting up skeleton for original %p and header %p",
+ (void *) original, (void *) header);
+ }
+
+ metisMessage_Release(&message);
+ }
+
+ return message;
+}
+
+bool
+metisMessage_HasRecommendedCacheTime(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter message must be non-null");
+ return message->hasRecommendedCacheTimeTicks;
+}
+
+uint64_t
+metisMessage_GetRecommendedCacheTimeTicks(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter message must be non-null");
+ assertTrue(message->hasRecommendedCacheTimeTicks, "MetisMessage does not have a RecommendedCacheTime. Call metisMessage_HasRecommendedCacheTime() first.");
+ return message->recommendedCacheTimeTicks;
+}
+
+void
+metisMessage_SetRecommendedCacheTimeTicks(MetisMessage *message, uint64_t recommendedCacheTimeTicks)
+{
+ assertNotNull(message, "Parameter message must be non-null");
+ message->recommendedCacheTimeTicks = recommendedCacheTimeTicks;
+ message->hasRecommendedCacheTimeTicks = true;
+}
+
+bool
+metisMessage_HasExpiryTime(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter message must be non-null");
+ return message->hasExpiryTimeTicks;
+}
+
+uint64_t
+metisMessage_GetExpiryTimeTicks(const MetisMessage *message)
+{
+ assertNotNull(message, "Parameter message must be non-null");
+ assertTrue(message->hasExpiryTimeTicks, "MetisMessage does not have an ExpiryTime. Call metisMessage_HasExpiryTime() first.");
+ return message->expiryTimeTicks;
+}
+
+void
+metisMessage_SetExpiryTimeTicks(MetisMessage *message, uint64_t expiryTimeTicks)
+{
+ assertNotNull(message, "Parameter message must be non-null");
+ message->expiryTimeTicks = expiryTimeTicks;
+ message->hasExpiryTimeTicks = true;
+}
diff --git a/metis/ccnx/forwarder/metis/core/metis_Message.h b/metis/ccnx/forwarder/metis/core/metis_Message.h
new file mode 100644
index 00000000..3b954a4e
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_Message.h
@@ -0,0 +1,859 @@
+/*
+ * 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_Message.h
+ * @brief MetisMessage is the unit of forwarding, i.e. the packets being switched
+ *
+ */
+#ifndef Metis_metis_Message_h
+#define Metis_metis_Message_h
+
+#include <config.h>
+#include <ccnx/forwarder/metis/core/metis_MessagePacketType.h>
+#include <ccnx/forwarder/metis/core/metis_StreamBuffer.h>
+#include <ccnx/forwarder/metis/tlv/metis_TlvName.h>
+#include <ccnx/forwarder/metis/tlv/metis_Tlv.h>
+
+#include <parc/algol/parc_EventQueue.h>
+#include <parc/algol/parc_EventBuffer.h>
+
+#include <ccnx/api/control/cpi_Address.h>
+#include <ccnx/api/control/cpi_ControlMessage.h>
+
+#include <ccnx/forwarder/metis/core/metis_Ticks.h>
+
+struct metis_message;
+typedef struct metis_message MetisMessage;
+
+/**
+ * @function metisMessage_ReadFromBuffer
+ * @abstract Read bytes from the input buffer and create a MetisMessage
+ * @discussion
+ * There must be bytesToRead bytes available.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+MetisMessage *metisMessage_ReadFromBuffer(unsigned ingressConnectionId, MetisTicks receiveTime, PARCEventBuffer *input, size_t bytesToRead, MetisLogger *logger);
+
+/**
+ * @function metisMessage_CreateFromBuffer
+ * @abstract Takes ownership of the input buffer, which comprises one complete message
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+MetisMessage *metisMessage_CreateFromBuffer(unsigned ingressConnectionId, MetisTicks receiveTime, PARCEventBuffer *input, MetisLogger *logger);
+
+/**
+ * @function metisMessage_CreateFromArray
+ * @abstract Copies the input buffer into the message.
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+MetisMessage *metisMessage_CreateFromArray(const uint8_t *data, size_t dataLength, unsigned ingressConnectionId, MetisTicks receiveTime, MetisLogger *logger);
+
+/**
+ * @function metisMessage_CreateFromParcBuffer
+ * @abstract Creates a message from the byte buffer
+ * @discussion
+ * Caller retains owership of the buffer
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+MetisMessage *metisMessage_CreateFromParcBuffer(PARCBuffer *buffer, unsigned ingressConnectionId, MetisTicks receiveTime, MetisLogger *logger);
+
+/**
+ * @function metisMessage_Copy
+ * @abstract Get a reference counted copy
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+MetisMessage *metisMessage_Acquire(const MetisMessage *message);
+
+/**
+ * Releases the message and frees the memory
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @return <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+void metisMessage_Release(MetisMessage **messagePtr);
+
+/**
+ * Writes the message to the queue
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @return <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+bool metisMessage_Write(PARCEventQueue *parcEventQueue, const MetisMessage *message);
+
+/**
+ * Appends the message to the buffer
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @return <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+bool metisMessage_Append(PARCEventBuffer *parcEventBuffer, const MetisMessage *message);
+
+/**
+ * Returns the total byte length of the message
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @return <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+size_t metisMessage_Length(const MetisMessage *message);
+
+
+bool metisMessage_HasWldr(const MetisMessage *message);
+
+unsigned metisMessage_GetWldrType(const MetisMessage *message);
+
+unsigned metisMessage_GetWldrLabel(const MetisMessage *message);
+
+unsigned metisMessage_GetWldrLastReceived(const MetisMessage *message);
+
+void metisMessage_SetWldrLabel(MetisMessage *message, uint16_t label);
+
+void metisMessage_SetWldrNotification(MetisMessage *message, uint16_t expected, uint16_t lastReceived);
+
+/**
+ * Returns the connection id of the packet input
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @return <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+unsigned metisMessage_GetIngressConnectionId(const MetisMessage *message);
+
+/**
+ * Returns the receive time (in router ticks) of the message
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @return <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisTicks metisMessage_GetReceiveTime(const MetisMessage *message);
+
+/**
+ * Returns the PacketType from the FixedHeader
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [in] message A parsed message
+ *
+ * @retval <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+MetisMessagePacketType metisMessage_GetType(const MetisMessage *message);
+
+/**
+ * Determines if the message has a hop limit
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @retval true A hop limit exists
+ * @retval false A hop limit does not exist
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+bool metisMessage_HasHopLimit(const MetisMessage *message);
+
+void metisMessage_UpdatePathLabel(MetisMessage *message, uint8_t outFace);
+void metisMessage_ResetPathLabel(MetisMessage *message);
+
+/**
+ * Returns the hoplimit of the message
+ *
+ * Will assert if the message does not have a hoplimit. Use metisMessage_HasHopLimit() first
+ * to determine if there is a hop limit.
+ *
+ * @param [in] message An allocated MetisMessage
+ *
+ * @retval number The hop limit
+ *
+ * Example:
+ * @code
+ * {
+ * if (metisMessage_HasHopLimit(message)) {
+ * uint8_t hoplimit = metisMessage_GetHopLimit(message);
+ * if (hoplimit == 0) {
+ * // drop packet
+ * } else {
+ * metisMessage_SetHopLimit(message, hoplimit - 1);
+ * }
+ * }
+ * }
+ * @endcode
+ */
+uint8_t metisMessage_GetHopLimit(const MetisMessage *message);
+
+/**
+ * Sets the message hop limit to the specified value
+ *
+ * Will assert if the message does not already have a hop limit. Use metisMessage_HasHopLimit() first
+ * to determine if there is a hop limit.
+ *
+ * @param [in] message An allocated MetisMessage
+ * @param [in] hoplimit The value to set in the packet
+ *
+ * Example:
+ * @code
+ * {
+ * if (metisMessage_HasHopLimit(message)) {
+ * uint8_t hoplimit = metisMessage_GetHopLimit(message);
+ * if (hoplimit == 0) {
+ * // drop packet
+ * } else {
+ * metisMessage_SetHopLimit(message, hoplimit - 1);
+ * }
+ * }
+ * }
+ * @endcode
+ */
+void metisMessage_SetHopLimit(MetisMessage *message, uint8_t hoplimit);
+
+// ===========================================================
+// Accessors used to index and compare messages
+
+/**
+ * @function metisMessage_GetName
+ * @abstract The name in the CCNx message
+ * @discussion
+ * The name of the Interest or Content Object. If the caller will store the
+ * name, he should make a reference counted copy.
+ *
+ * @param <#param1#>
+ * @return The name as stored in the message object.
+ */
+MetisTlvName *metisMessage_GetName(const MetisMessage *message);
+
+/**
+ * @function metisMessage_GetKeyIdHash
+ * @abstract Non-cryptographic hash of the KeyId
+ * @discussion
+ * If the message does not have a KeyId, the output pointer is left unchanged.
+ *
+ * @param hashOutput will be filled in with the hash, if a KeyId exists.
+ * @return true if object has a KeyId
+ */
+bool metisMessage_GetKeyIdHash(const MetisMessage *message, uint32_t *hashOutput);
+
+
+/**
+ * Determine if the KeyIds of two Metis Messages are equal.
+ *
+ * The following equivalence relations on non-null KeyIds in `MetisMessage` instances are maintained:
+ *
+ * * It is reflexive: for any non-null reference value x, `metisMessage_KeyIdEquals(x, x)`
+ * must return true.
+ *
+ * * It is symmetric: for any non-null reference values x and y,
+ * `metisMessage_KeyIdEquals(x, y)` must return true if and only if
+ * `metisMessage_KeyIdEquals(y, x)` returns true.
+ *
+ * * It is transitive: for any non-null reference values x, y, and z, if
+ * `metisMessage_KeyIdEquals(x, y)` returns true and
+ * `metisMessage_KeyIdEquals(y, z)` returns true,
+ * then `metisMessage_KeyIdEquals(x, z)` must return true.
+ *
+ * * It is consistent: for any non-null reference values x and y, multiple
+ * invocations of `metisMessage_KeyIdEquals(x, y)` consistently return true or
+ * consistently return false.
+ *
+ * * For any non-null reference value x, `metisMessage_KeyIdEquals(x, NULL)` must
+ * return false.
+ *
+ * @param a A pointer to a `MetisMessage` instance.
+ * @param b A pointer to a `MetisMessage` instance.
+ * @return true if the KeyIds of the two `MetisMessage` instances are equal.
+ *
+ * Example:
+ * @code
+ * {
+ * MetisMessage *a = metisMessage_Create();
+ * MetisMessage *b = metisMessage_Create();
+ *
+ * if (metisMessage_KeyIdEquals(a, b)) {
+ * // true
+ * } else {
+ * // false
+ * }
+ * }
+ * @endcode
+ */
+bool metisMessage_KeyIdEquals(const MetisMessage *a, const MetisMessage *b);
+
+/**
+ * Determine if the ContentObjectHashes of two `Metis Messages` are equal.
+ *
+ * The ContentObjectHashes of two `MetisMessage` instances are equal if, and only if,
+ * a ContentObjectHash exists in both `MetisMessage` and are equal.
+ *
+ *
+ * The following equivalence relations on non-null KeyIds in `MetisMessage` instances are maintained:
+ *
+ * * It is reflexive: for any non-null reference value x, `metisMessage_ObjectHashEquals(x, x)`
+ * must return true.
+ *
+ * * It is symmetric: for any non-null reference values x and y,
+ * `metisMessage_ObjectHashEquals(x, y)` must return true if and only if
+ * `metisMessage_ObjectHashEquals(y, x)` returns true.
+ *
+ * * It is transitive: for any non-null reference values x, y, and z, if
+ * `metisMessage_ObjectHashEquals(x, y)` returns true and
+ * `metisMessage_ObjectHashEquals(y, z)` returns true,
+ * then `metisMessage_ObjectHashEquals(x, z)` must return true.
+ *
+ * * It is consistent: for any non-null reference values x and y, multiple
+ * invocations of `metisMessage_ObjectHashEquals(x, y)` consistently return true or
+ * consistently return false.
+ *
+ * * For any non-null reference value x, `metisMessage_ObjectHashEquals(x, NULL)` must
+ * return false.
+ *
+ * @param a A pointer to a `MetisMessage` instance.
+ * @param b A pointer to a `MetisMessage` instance.
+ * @return true if the KeyIds of the two `MetisMessage` instances are equal.
+ *
+ * Example:
+ * @code
+ * {
+ * MetisMessage *a = metisMessage_Create();
+ * MetisMessage *b = metisMessage_Create();
+ *
+ * if (metisMessage_ObjectHashEquals(a, b)) {
+ * // true
+ * } else {
+ * // false
+ * }
+ * }
+ * @endcode
+ */
+bool metisMessage_ObjectHashEquals(MetisMessage *a, MetisMessage *b);
+
+/**
+ * @function metisMessage_GetContentObjectHashHash
+ * @abstract Non-cryptographic hash of the ContentObjectHash
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return true if message has a contentobject hash and the output updated.
+ */
+bool metisMessage_GetContentObjectHashHash(MetisMessage *message, uint32_t *hashOutput);
+
+/**
+ * <#One Line Description#>
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @return <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+bool metisMessage_HasName(const MetisMessage *message);
+
+/**
+ * Return true if there is a KeyId associated with this `MetisMessage`. *
+ *
+ * Note that this will return true if either the underlying message is an Interest that contains
+ * a KeyId restriction, or if the underlying message is a ContentObject that contains a KeyId.
+ * In the latter case, the KeyId might have been specified by the creator of the content, or
+ * it may have been calculated when we verified the public key specified by the ContentObject.
+ *
+ *
+ * @param [in] message the `MetisMessage` to query.
+ *
+ * @return true if there is a KeyId (or KeyId restriction) associated with this `MetisMessage`.
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+bool metisMessage_HasKeyId(const MetisMessage *message);
+
+/**
+ * Return true if there is a KeyId associated with this `MetisMessage`, and that KeyId
+ * has been verified.
+ *
+ * This KeyId may have been specified by the sender, or may have been calculated while
+ * verifying the public key associated with this `MetisMessage`s validation data, if any.
+ *
+ * @param [in] message the `MetisMessage` to query.
+ *
+ * @return true if there is a KeyId associated with this `MetisMessage`, and that KeyId has been verified.
+ * @return false if there is no KeyId associated with this `MetisMessage` or it has not yet been verified.
+ * Example:
+ * @code
+ * {
+ * if (metisMessage_IsKeyIdVerified(message)) {
+ * doSomethingWithMssage(message);
+ * } else {
+ * // KeyId was not verified
+ * }
+ * }
+ * @endcode
+ */
+bool metisMessage_IsKeyIdVerified(const MetisMessage *message);
+
+/**
+ * <#One Line Description#>
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [<#in out in,out#>] <#name#> <#description#>
+ *
+ * @return <#value#> <#explanation#>
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+bool metisMessage_HasContentObjectHash(const MetisMessage *message);
+
+/**
+ * @function metisMessage_GetControlMessage
+ * @abstract A TLV_MSG_CPI will return a control message
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+CCNxControl *metisMessage_CreateControlMessage(const MetisMessage *message);
+
+/**
+ * Determines if the message has an Interest Lifetime parameter
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [in] message An allocated and parsed Message
+ *
+ * @retval true If an Intrerest Lifetime field exists
+ * @retval false If no Interest Lifetime exists
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+bool metisMessage_HasInterestLifetime(const MetisMessage *message);
+
+/**
+ * Returns the Interest lifetime
+ *
+ * Will trap if the message does not contain an Interest lifetime
+ *
+ * @param [in] message An allocated and parsed Message
+ *
+ * @retval integer Lifetime in forwarder Ticks
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+uint64_t metisMessage_GetInterestLifetimeTicks(const MetisMessage *message);
+
+/**
+ * Return true if the specified `MetisMessage` instance contains a RecommendedCacheTime.
+ *
+ * The Recommended Cache Time (a millisecond timestamp) is a network byte ordered unsigned integer of the number of
+ * milliseconds since the epoch in UTC of when the payload expires. It is a 64-bit field.
+ *
+ * @param message the `MetisMessage` instance to check for RecommendedCacheTime.
+ * @return true if the specified `MetisMessage` instance has a RecommendedCacheTime.
+ * @return false otherwise.
+ * Example:
+ * @code
+ * {
+ * MetisMessage *metisMessage = <...>;
+ * if (metisMessage_HasRecommendedCacheTime(metisMessage)) {
+ * uint64_t rct = metisMessage_GetRecommendedCacheTimeTicks(metisMessage);
+ * }
+ * }
+ * @endcode
+ * @see metisMessage_GetRecommendedCacheTimeTicks
+ */
+bool metisMessage_HasRecommendedCacheTime(const MetisMessage *message);
+
+/**
+ * Return the RecommendedCacheTime of the specified `MetisMessage`, if available, in Metis ticks. If not, it will trap.
+ * Before calling this function, call {@link metisMessage_HasRecommendedCacheTime} first.
+ *
+ * The Recommended Cache Time (a millisecond timestamp) is a network byte ordered unsigned integer of the number of
+ * milliseconds since the epoch in UTC of when the payload expires. It is a 64-bit field.
+ *
+ * The Recommended Cache Time is initially set from the referenced tlv_skeleton, but may be assigned by calling
+ * {@link metisMessage_SetRecommendedCacheTimeTicks}.
+ *
+ * @param message the `MetisMessage` instance to check for RecommendedCacheTime.
+ * @return the RecommendedCacheTime of the specified `MetisMessage`.
+ *
+ * Example:
+ * @code
+ * {
+ * MetisMessage *metisMessage = <...>;
+ * if (metisMessage_HasRecommendedCacheTime(metisMessage)) {
+ * uint64_t rct = metisMessage_GetRecommendedCacheTimeTicks(metisMessage);
+ * }
+ * }
+ * @endcode
+ *
+ * @see metisMessage_HasRecommendedCacheTime
+ * @see metisMessage_SetRecommendedCacheTimeTicks
+ */
+uint64_t metisMessage_GetRecommendedCacheTimeTicks(const MetisMessage *message);
+
+/**
+ * Return true if the specified `MetisMessage` instance contains an ExpiryTime.
+ *
+ * The ExpiryTime is the time at which the Payload expires, as expressed by a timestamp containing the number of milliseconds
+ * since the epoch in UTC. It is a network byte order unsigned integer in a 64-bit field. A cache or end system should not
+ * respond with a Content Object past its ExpiryTime. Routers forwarding a Content Object do not need to check the ExpiryTime.
+ * If the ExpiryTime field is missing, the Content Object has no expressed expiration and a cache or end system may use the
+ * Content Object for as long as desired.
+ *
+ * @param message the `MetisMessage` instance to check for ExpiryTime.
+ * @return true if the specified `MetisMessage` instance has an ExpiryTime.
+ * @return false otherwise.
+ *
+ * Example:
+ * @code
+ * {
+ * MetisMessage *metisMessage = <...>;
+ * if (metisMessage_HasExpiryTime(metisMessage)) {
+ * uint64_t expiryTime = metisMessage_GetExpiryTimeTicks(metisMessage);
+ * }
+ * }
+ * @endcode
+ *
+ * @see metisMessage_GetExpiryTimeTicks
+ * @see metisMessage_SetExpiryTimeTicks
+ */
+bool metisMessage_HasExpiryTime(const MetisMessage *message);
+
+/**
+ * Return the ExpiryTime of the specified `MetisMessage`, if available, in Metis ticks. If not, it will trap.
+ * Before calling this function, call {@link metisMessage_HasExpiryTime} first.
+ *
+ * The ExpiryTime is the time at which the Payload expires, as expressed by a timestamp containing the number of milliseconds
+ * since the epoch in UTC. It is a network byte order unsigned integer in a 64-bit field. A cache or end system should not
+ * respond with a Content Object past its ExpiryTime. Routers forwarding a Content Object do not need to check the ExpiryTime.
+ * If the ExpiryTime field is missing, the Content Object has no expressed expiration and a cache or end system may use the
+ * Content Object for as long as desired.
+ *
+ * The ExpiryTime is initially set from the referenced tlv_skeleton, but may be assigned by calling
+ * {@link metisMessage_SetExpiryTimeTicks}.
+ *
+ * @param message the `MetisMessage` instance to check for ExpiryTime.
+ * @return the ExpiryTime of the specified `MetisMessage`.
+ *
+ * Example:
+ * @code
+ * {
+ * MetisMessage *metisMessage = <...>;
+ * if (metisMessage_HasExpiryTime(metisMessage)) {
+ * uint64_t rct = metisMessage_GetExpiryTimeTicks(metisMessage);
+ * }
+ * }
+ * @endcode
+ *
+ * @see metisMessage_HasExpiryTime
+ * @see metisMessage_SetExpiryTimeTicks
+ */
+uint64_t metisMessage_GetExpiryTimeTicks(const MetisMessage *message);
+
+/**
+ * Assign the ExpiryTime of the specified `MetisMessage`, in Metis ticks. This will not update the
+ * referenced tlv_skeleton.
+ *
+ * The ExpiryTime is the time at which the Payload expires, as expressed by a timestamp containing the number of milliseconds
+ * since the epoch in UTC. It is a network byte order unsigned integer in a 64-bit field. A cache or end system should not
+ * respond with a Content Object past its ExpiryTime. Routers forwarding a Content Object do not need to check the ExpiryTime.
+ * If the ExpiryTime field is missing, the Content Object has no expressed expiration and a cache or end system may use the
+ * Content Object for as long as desired.
+ *
+ * @param message the `MetisMessage` instance to check for ExpiryTime.
+ * @param expiryTimeTicks the time, in ticks, that this message's payload will expire.
+ *
+ * Example:
+ * @code
+ * {
+ * MetisMessage *metisMessage = <...>;
+ * uint64_t timeInTicks = <...>;
+ * metisMessage_SetExpiryTimeTicks(metisMessage, timeInTicks);
+ * }
+ * @endcode
+ *
+ * @see metisMessage_HasExpiryTime
+ * @see metisMessage_GetExpiryTimeTicks
+ */
+void metisMessage_SetExpiryTimeTicks(MetisMessage *message, uint64_t expiryTimeTicks);
+
+/**
+ * Assign the RecommendedCacheTime of the specified `MetisMessage`, in Metis ticks. This will not update the
+ * referenced tlv_skeleton.
+ *
+ * The Recommended Cache Time (a millisecond timestamp) is a network byte ordered unsigned integer of the number of
+ * milliseconds since the epoch in UTC of when the payload expires. It is a 64-bit field.
+ *
+ * @param message the `MetisMessage` instance to check for ExpiryTime.
+ * @param expiryTimeTicks the time, in ticks, that this message's payload will expire.
+ *
+ * Example:
+ * @code
+ * {
+ * MetisMessage *metisMessage = <...>;
+ * uint64_t timeInTicks = <...>;
+ * metisMessage_SetRecommendedCacheTimeTicks(metisMessage, timeInTicks);
+ * }
+ * @endcode
+ *
+ * @see metisMessage_HasExpiryTime
+ */
+void metisMessage_SetRecommendedCacheTimeTicks(MetisMessage *message, uint64_t recommendedCacheTimeTicks);
+
+/**
+ * Return true if there is a public key associated with the specified `MetisMessage`.
+ *
+ * @param message the `MetisMessage` instance to check for a public key.
+ *
+ * @return true if there is a public key in this `MetisMessage`.
+ * @return false otherwise.
+ */
+bool metisMessage_HasPublicKey(const MetisMessage *message);
+
+/**
+ * Return true if there is a certificate associated with the specified `MetisMessage`.
+ *
+ * @param message the `MetisMessage` instance to check for certificate.
+ *
+ * @return true if there is a certificate in this `MetisMessage`.
+ * @return false otherwise.
+ */
+bool metisMessage_HasCertificate(const MetisMessage *message);
+
+/**
+ * Return the public key associated with the specified `MetisMessage`, if it exists.
+ *
+ * @param message the `MetisMessage` instance to check for a public key.
+ *
+ * @return a pointer to a PARCBuffer containing the public key of this `MetisMessage`.
+ * @return NULL if no public key exists.
+ */
+PARCBuffer *metisMessage_GetPublicKey(const MetisMessage *message);
+
+/**
+ * Return the certificate associated with the specified `MetisMessage`, if it exists.
+ *
+ * @param message the `MetisMessage` instance to check for a certificate.
+ *
+ * @return a pointer to a PARCBuffer containing the certificate of this `MetisMessage`.
+ * @return NULL if no certificate exists.
+ */
+PARCBuffer *metisMessage_GetCertificate(const MetisMessage *message);
+
+/**
+ * Tests if the packet has a fragment payload
+ *
+ * The fragment payload is part of a larger packet
+ *
+ * @param [in] message An allocated and parsed Message
+ *
+ * @return true The packet contains a portion of another packet
+ * @return false There is no fragment payload
+ *
+ * Example:
+ * @code
+ * {
+ * <#example#>
+ * }
+ * @endcode
+ */
+bool metisMessage_HasFragmentPayload(const MetisMessage *message);
+
+/**
+ * Appends the fragment payload to the given buffer
+ *
+ * Will append the fragment payload from the message to the given buffer.
+ * This is a non-destructive copy. If there is no fragment payload in the
+ * message, 0 bytes will be copied and 0 returned.
+ *
+ * @param [in] message An allocated and parsed Message
+ * @param [in] buffer The buffer to append to
+ *
+ * @return number The number of bytes appended
+ *
+ * Example:
+ * @code
+ * {
+ * <#example#>
+ * }
+ * @endcode
+ */
+size_t metisMessage_AppendFragmentPayload(const MetisMessage *message, PARCEventBuffer *buffer);
+
+/**
+ * Returns a pointer to the beginning of the FixedHeader
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [in] message An allocated and parsed Message
+ *
+ * @return non-null The fixed header memory
+ * @return null No fixed header or an error
+ *
+ * Example:
+ * @code
+ * {
+ * <#example#>
+ * }
+ * @endcode
+ */
+const uint8_t *metisMessage_FixedHeader(const MetisMessage *message);
+
+/**
+ * Creates a new MetisMessage from a slice of a first message
+ *
+ * The new MetisMessage will be the header byte array prefix followed by the slice extent.
+ * The resulting MetisMessage must be freed by calling metisMessage_Release().
+ *
+ * Depending on the implementation, this may make a reference to the first message and access the
+ * memory by reference.
+ *
+ * The offset + length must be less than or equal to metisMessage_Length(). It is an error
+ * to call with a 0 length.
+ *
+ * @param [in] message The original message to slice
+ * @param [in] offset The offset within the message to start the slice
+ * @param [in] length The length after the offset to include in the slice (must be positive)
+ * @param [in] headerLength The length of the header to prepend (may be 0)
+ * @param [in] header The header to prepend (may be NULL with 0 length)
+ *
+ * @return non-null A new MetisMessage that has the header plus slice
+ * @return null An error
+ *
+ * Example:
+ * @code
+ * {
+ * uint8_t interestToFragment[] = {
+ * 0x01, 0x00, 0x00, 24, // ver = 1, type = interest, length = 24
+ * 0x20, 0x00, 0x11, 8, // HopLimit = 32, reserved = 0, flags = 0x11, header length = 8
+ * 0x00, 0x01, 0x00, 12, // type = interest, length = 12
+ * 0x00, 0x00, 0x00, 8, // type = name, length = 8
+ * 0x00, 0x02, 0x00, 4, // type = binary, length = 4
+ * 'c', 'o', 'o', 'l', // "cool"
+ * };
+ *
+ * MetisMessage *firstMessage = metisMessage_CreateFromArray(interestToFragment, sizeof(interestToFragment), 1, 100, logger);
+ *
+ * uint8_t fragmentHeader[] = {
+ * 0x01, 0x05, 0x00, 12, // hop-by-hop fragment
+ * 0x40, 0x00, 0x01, 8, // B flag, seqnum = 1
+ * 0x00, 0x05, 0x00, 8, // fragment data, length = 8
+ * };
+ *
+ * MetisMessage *secondMessage = metisMessage_Slice(firstMessage, 0, 8, sizeof(fragmentHeader), fragmentHeader,);
+ * // the secondMessage message contains the fragmentHeader followed by the first 8 bytes
+ * // of the first message, as showin in wireFormat below.
+ *
+ * uint8_t wireFormat[] = {
+ * 0x01, 0x05, 0x00, 12, // hop-by-hop fragment
+ * 0x40, 0x00, 0x01, 8, // B flag, seqnum = 1
+ * 0x00, 0x05, 0x00, 8, // fragment data, length = 8
+ * 0x01, 0x00, 0x00, 24, // ver = 1, type = interest, length = 24
+ * 0x20, 0x00, 0x11, 8, // HopLimit = 32, reserved = 0, flags = 0x11, header length = 8
+ * };
+ *
+ * metisMessage_Release(&firstMessage);
+ * metisMessage_Release(&secondMessage);
+ * }
+ * @endcode
+ */
+MetisMessage *metisMessage_Slice(const MetisMessage *message, size_t offset, size_t length, size_t headerLength, const uint8_t header[headerLength]);
+#endif // Metis_metis_Message_h
diff --git a/metis/ccnx/forwarder/metis/core/metis_MessagePacketType.h b/metis/ccnx/forwarder/metis/core/metis_MessagePacketType.h
new file mode 100644
index 00000000..c2f9b77a
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_MessagePacketType.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_metis_MessagePacketType_h
+ * @brief Defines the packet type for a CCNx message
+ *
+ * Used by MetisMessage to define the packet type of a Fixed Header.
+ *
+ */
+
+#ifndef Metis_metis_MessagePacketType_h
+#define Metis_metis_MessagePacketType_h
+
+typedef enum metis_message_tlv_type {
+ MetisMessagePacketType_Unknown,
+ MetisMessagePacketType_Interest,
+ MetisMessagePacketType_ContentObject,
+ MetisMessagePacketType_Control,
+ MetisMessagePacketType_InterestReturn,
+ MetisMessagePacketType_HopByHopFrag
+} MetisMessagePacketType;
+
+#endif // Metis_metis_MessagePacketType_h
diff --git a/metis/ccnx/forwarder/metis/core/metis_NumberSet.c b/metis/ccnx/forwarder/metis/core/metis_NumberSet.c
new file mode 100644
index 00000000..6828014a
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_NumberSet.c
@@ -0,0 +1,225 @@
+/*
+ * 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.
+ */
+
+/**
+ * Currently uses an unsorted array of numbers.
+ *
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <parc/algol/parc_Memory.h>
+#include <ccnx/forwarder/metis/core/metis_NumberSet.h>
+#include <parc/algol/parc_ArrayList.h>
+
+#include <LongBow/runtime.h>
+
+struct metis_number_set {
+ MetisNumber *arrayOfNumbers;
+ size_t length;
+ size_t limit;
+ unsigned refcount;
+};
+
+static void metisNumberSet_Expand(MetisNumberSet *set);
+
+MetisNumberSet *
+metisNumberSet_Create()
+{
+ MetisNumberSet *set = parcMemory_AllocateAndClear(sizeof(MetisNumberSet));
+ assertNotNull(set, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisNumberSet));
+ set->arrayOfNumbers = parcMemory_AllocateAndClear(sizeof(MetisNumber) * 16);
+ assertNotNull((set->arrayOfNumbers), "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisNumber) * 16);
+ set->length = 0;
+ set->limit = 16;
+ set->refcount = 1;
+ return set;
+}
+
+MetisNumberSet *
+metisNumberSet_Acquire(const MetisNumberSet *original)
+{
+ assertNotNull(original, "Parameter original must be non-null");
+ MetisNumberSet *copy = (MetisNumberSet *) original;
+ copy->refcount++;
+ return copy;
+}
+
+void
+metisNumberSet_Release(MetisNumberSet **setPtr)
+{
+ assertNotNull(setPtr, "Parameter must be non-null double pointer");
+ assertNotNull(*setPtr, "Parameter must dereference to non-null pointer");
+
+ MetisNumberSet *set = *setPtr;
+ assertTrue(set->refcount > 0, "Invalid state: calling destroy on an object with 0 reference count");
+ set->refcount--;
+
+ if (set->refcount == 0) {
+ parcMemory_Deallocate((void **) &(set->arrayOfNumbers));
+ parcMemory_Deallocate((void **) &set);
+ *setPtr = NULL;
+ }
+}
+
+/**
+ * @function metisNumberSet_AddNoChecks
+ * @abstract Add a number we know is not already in the set
+ * @discussion
+ * Used by other functions that already know the number is unique in the set,
+ * Does not do the expensive Contains check.
+ *
+ * @param <#param1#>
+ */
+static void
+metisNumberSet_AddNoChecks(MetisNumberSet *set, MetisNumber number)
+{
+ if (set->length == set->limit) {
+ metisNumberSet_Expand(set);
+ }
+
+ set->arrayOfNumbers[ set->length ] = number;
+ set->length++;
+}
+
+bool
+metisNumberSet_Add(MetisNumberSet *set, MetisNumber number)
+{
+ assertNotNull(set, "Parameter set must be non-null");
+ if (metisNumberSet_Contains(set, number)) {
+ return false;
+ }
+
+ metisNumberSet_AddNoChecks(set, number);
+ return true;
+}
+
+size_t
+metisNumberSet_Length(const MetisNumberSet *set)
+{
+ assertNotNull(set, "Parameter set must be non-null");
+ return set->length;
+}
+
+MetisNumber
+metisNumberSet_GetItem(const MetisNumberSet *set, size_t ordinalIndex)
+{
+ assertNotNull(set, "Parameter set must be non-null");
+ assertTrue(ordinalIndex < set->length, "Limit beyond end of set, length %zu got %zu", set->length, ordinalIndex);
+
+ return set->arrayOfNumbers[ordinalIndex];
+}
+
+bool
+metisNumberSet_Contains(const MetisNumberSet *set, MetisNumber number)
+{
+ assertNotNull(set, "Parameter set must be non-null");
+ for (size_t i = 0; i < set->length; i++) {
+ if (set->arrayOfNumbers[i] == number) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+metisNumberSet_AddSet(MetisNumberSet *destinationSet, const MetisNumberSet *setToAdd)
+{
+ assertNotNull(destinationSet, "Parameter destinationSet must be non-null");
+ assertNotNull(setToAdd, "Parameter setToAdd must be non-null");
+
+ for (size_t i = 0; i < setToAdd->length; i++) {
+ metisNumberSet_Add(destinationSet, setToAdd->arrayOfNumbers[i]);
+ }
+}
+
+MetisNumberSet *
+metisNumberSet_Subtract(const MetisNumberSet *minuend, const MetisNumberSet *subtrahend)
+{
+ // because the underlying ADT is not sorted, this is pretty ineffient, could be O(n^2).
+
+ MetisNumberSet *difference = metisNumberSet_Create();
+
+ for (size_t i = 0; i < minuend->length; i++) {
+ bool unique = true;
+ for (size_t j = 0; j < subtrahend->length && unique; j++) {
+ if (minuend->arrayOfNumbers[i] == subtrahend->arrayOfNumbers[j]) {
+ unique = false;
+ }
+ }
+
+ if (unique) {
+ metisNumberSet_AddNoChecks(difference, minuend->arrayOfNumbers[i]);
+ }
+ }
+ return difference;
+}
+
+bool
+metisNumberSet_Equals(const MetisNumberSet *a, const MetisNumberSet *b)
+{
+ if (a == NULL && b == NULL) {
+ return true;
+ }
+
+ if (a == NULL || b == NULL) {
+ return false;
+ }
+
+ if (a->length == b->length) {
+ for (size_t i = 0; i < a->length; i++) {
+ bool found = false;
+ for (size_t j = 0; j < b->length && !found; j++) {
+ if (a->arrayOfNumbers[i] == b->arrayOfNumbers[j]) {
+ found = true;
+ }
+ }
+ if (!found) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ return false;
+}
+
+void
+metisNumberSet_Remove(MetisNumberSet *set, MetisNumber number)
+{
+ assertNotNull(set, "Parameter set must be non-null");
+ for (size_t i = 0; i < set->length; i++) {
+ if (set->arrayOfNumbers[i] == number) {
+ set->length--;
+ if (set->length > 0) {
+ // move the last element to the removed element to keep the array packed.
+ set->arrayOfNumbers[i] = set->arrayOfNumbers[set->length];
+ }
+ return;
+ }
+ }
+}
+
+// =====================================================
+
+static void
+metisNumberSet_Expand(MetisNumberSet *set)
+{
+ size_t newlimit = set->limit * 2;
+ size_t newbytes = newlimit * sizeof(MetisNumber);
+
+ set->arrayOfNumbers = parcMemory_Reallocate(set->arrayOfNumbers, newbytes);
+ set->limit = newlimit;
+}
diff --git a/metis/ccnx/forwarder/metis/core/metis_NumberSet.h b/metis/ccnx/forwarder/metis/core/metis_NumberSet.h
new file mode 100644
index 00000000..8ca5a9b3
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_NumberSet.h
@@ -0,0 +1,212 @@
+/*
+ * 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+NumberList.h
+ * @brief Stores a set of numbers.
+ *
+ * Useful for things like the reverse path of a PIT
+ * or the forward paths of a FIB. Does not allow duplicates.
+ *
+ */
+
+#ifndef Metis_metis_NumberSet_h
+#define Metis_metis_NumberSet_h
+
+#include <stdlib.h>
+#include <stdbool.h>
+
+struct metis_number_set;
+typedef struct metis_number_set MetisNumberSet;
+
+typedef uint32_t MetisNumber;
+
+/**
+ * @function metisNumberList_Create
+ * @abstract A new list of numbers
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+MetisNumberSet *metisNumberSet_Create(void);
+
+/**
+ * Obtains a reference counted copy of the original
+ *
+ * The reference count is increased by one. It must be released with MetisNumberSet_Release().
+ *
+ * @param [in] original An allocated MetisNumberSet
+ *
+ * @return non-null The reference counted copy
+ *
+ * Example:
+ * @code
+ * {
+ * MetisNumberSet *set = metisNumberSet_Create();
+ * MetisNumberSet *copy = metisNumberSet_Acquire(set);
+ * metisNumberSet_Release(&copy);
+ * metisNumberSet_Release(&set);
+ * }
+ * @endcode
+ */
+MetisNumberSet *metisNumberSet_Acquire(const MetisNumberSet *original);
+
+/**
+ * Releases one reference count and destroys the memory after last release
+ *
+ * The pointer will be NULLed after release regardless if the memory was destroyed.
+ *
+ * @param [in,out] setPtr A pointer to a MetisNumberSet. Will be NULL'd after release.
+ *
+ * Example:
+ * @code
+ * {
+ * MetisNumberSet *set = metisNumberSet_Create();
+ * metisNumberSet_Release(&set);
+ * }
+ * @endcode
+ */
+void metisNumberSet_Release(MetisNumberSet **setPtr);
+
+/**
+ * @function metisNumberList_Append
+ * @abstract Add a number to the end of the list
+ * @discussion
+ * No check for duplicates is done
+ *
+ * @param <#param1#>
+ * @return true if added, false if a duplicate
+ */
+bool metisNumberSet_Add(MetisNumberSet *set, MetisNumber number);
+
+/**
+ * @function metisNumberList_Length
+ * @abstract The count of numbers in the list
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+size_t metisNumberSet_Length(const MetisNumberSet *set);
+
+/**
+ * @function metisNumberSet_GetItem
+ * @abstract Retrieves an item based on the ordinal index
+ * @discussion
+ * Will assert if the ordinalIndex is out of bounds.
+ *
+ * @param <#param1#>
+ * @return the number
+ */
+MetisNumber metisNumberSet_GetItem(const MetisNumberSet *set, size_t ordinalIndex);
+
+/**
+ * @function metisNumberSet_Contains
+ * @abstract Checks for set membership
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return true if the set contains the number, false otherwise
+ */
+bool metisNumberSet_Contains(const MetisNumberSet *set, MetisNumber number);
+
+/**
+ * @function metisNumberSet_AddSet
+ * @abstract Adds one set to another set
+ * @discussion
+ * Adds <code>setToAdd</code> to <code>destinationSet</code>
+ *
+ * @param <#param1#>
+ * @return true if the set contains the number, false otherwise
+ */
+void metisNumberSet_AddSet(MetisNumberSet *destinationSet, const MetisNumberSet *setToAdd);
+
+/**
+ * @function metisNumberSet_Subtract
+ * @abstract Computes set difference <code>difference = minuend - subtrahend</code>, returns a new number set.
+ * @discussion
+ * <code>minuend</code> and <code>subtrahend</code> are not modified. A new difference set is created.
+ *
+ * Returns the elements in <code>minuend</code> that are not in <code>subtrahend</code>.
+ *
+ * @param minuend The set from which to subtract
+ * @param subrahend The set begin removed from minuend
+ * @return The set difference. May be empty, but will not be NULL.
+ */
+MetisNumberSet *metisNumberSet_Subtract(const MetisNumberSet *minuend, const MetisNumberSet *subtrahend);
+
+/**
+ * Determine if two MetisNumberSet instances are equal.
+ *
+ * Two MetisNumberSet instances are equal if, and only if,
+ * they are the same size and contain the same elements. Empty sets are equal.
+ * NULL equals NULL, but does not equal non-NULL.
+ *
+ * The following equivalence relations on non-null `MetisNumberSet` instances are maintained:
+ *
+ * * It is reflexive: for any non-null reference value x, `MetisNumberSet_Equals(x, x)`
+ * must return true.
+ *
+ * * It is symmetric: for any non-null reference values x and y,
+ * `metisNumberSet_Equals(x, y)` must return true if and only if
+ * `metisNumberSet_Equals(y, x)` returns true.
+ *
+ * * It is transitive: for any non-null reference values x, y, and z, if
+ * `metisNumberSet_Equals(x, y)` returns true and
+ * `metisNumberSet_Equals(y, z)` returns true,
+ * then `metisNumberSet_Equals(x, z)` must return true.
+ *
+ * * It is consistent: for any non-null reference values x and y, multiple
+ * invocations of `metisNumberSet_Equals(x, y)` consistently return true or
+ * consistently return false.
+ *
+ * * For any non-null reference value x, `metisNumberSet_Equals(x, NULL)` must
+ * return false.
+ *
+ * @param a A pointer to a `MetisNumberSet` instance.
+ * @param b A pointer to a `MetisNumberSet` instance.
+ * @return true if the two `MetisNumberSet` instances are equal.
+ *
+ * Example:
+ * @code
+ * {
+ * MetisNumberSet *a = metisNumberSet_Create();
+ * MetisNumberSet *b = metisNumberSet_Create();
+ *
+ * if (metisNumberSet_Equals(a, b)) {
+ * // true
+ * } else {
+ * // false
+ * }
+ * }
+ * @endcode
+ */
+bool metisNumberSet_Equals(const MetisNumberSet *a, const MetisNumberSet *b);
+
+/**
+ * @function metisNumberSet_Remove
+ * @abstract Removes the number from the set
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+void metisNumberSet_Remove(MetisNumberSet *set, MetisNumber number);
+#endif // Metis_metis_NumberSet_h
diff --git a/metis/ccnx/forwarder/metis/core/metis_StreamBuffer.c b/metis/ccnx/forwarder/metis/core/metis_StreamBuffer.c
new file mode 100644
index 00000000..12638095
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_StreamBuffer.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 <config.h>
+#include <stdio.h>
+
+#include <LongBow/runtime.h>
+
+#include <ccnx/forwarder/metis/core/metis_StreamBuffer.h>
+
+void
+metisStreamBuffer_Destroy(PARCEventQueue **bufferPtr)
+{
+ assertNotNull(bufferPtr, "Parameter must be non-null double pointer");
+ assertNotNull(*bufferPtr, "Parameter must dereference to non-null pointer");
+ parcEventQueue_Destroy(bufferPtr);
+ *bufferPtr = NULL;
+}
+
+void
+metisStreamBuffer_SetWatermark(PARCEventQueue *buffer, bool setRead, bool setWrite, size_t low, size_t high)
+{
+ assertNotNull(buffer, "Parameter buffer must be non-null");
+
+ short flags = 0;
+ if (setRead) {
+ flags |= PARCEventType_Read;
+ }
+
+ if (setWrite) {
+ flags |= PARCEventType_Write;
+ }
+
+ parcEventQueue_SetWatermark(buffer, flags, low, high);
+}
+
+int
+metisStreamBuffer_Flush(PARCEventQueue *buffer, bool flushRead, bool flushWrite)
+{
+ assertNotNull(buffer, "Parameter buffer must be non-null");
+
+ short flags = 0;
+ if (flushRead) {
+ flags |= PARCEventType_Read;
+ }
+
+ if (flushWrite) {
+ flags |= PARCEventType_Write;
+ }
+
+ return parcEventQueue_Flush(buffer, flags);
+}
+
+// NOT USED!!
+int
+metisStreamBuffer_FlushCheckpoint(PARCEventQueue *buffer, bool flushRead, bool flushWrite)
+{
+ assertNotNull(buffer, "Parameter buffer must be non-null");
+
+ short flags = 0;
+ if (flushRead) {
+ flags |= PARCEventType_Read;
+ }
+
+ if (flushWrite) {
+ flags |= PARCEventType_Write;
+ }
+
+ return parcEventQueue_Flush(buffer, flags);
+}
+
+// NOT USED!!
+int
+metisStreamBuffer_FlushFinished(PARCEventQueue *buffer, bool flushRead, bool flushWrite)
+{
+ assertNotNull(buffer, "Parameter buffer must be non-null");
+
+ short flags = 0;
+ if (flushRead) {
+ flags |= PARCEventType_Read;
+ }
+
+ if (flushWrite) {
+ flags |= PARCEventType_Write;
+ }
+
+ return parcEventQueue_Flush(buffer, flags);
+}
+
+void
+metisStreamBuffer_SetCallbacks(PARCEventQueue *buffer,
+ PARCEventQueue_Callback *readCallback,
+ PARCEventQueue_Callback *writeCallback,
+ PARCEventQueue_EventCallback *eventCallback,
+ void *user_data)
+{
+ assertNotNull(buffer, "Parameter buffer must be non-null");
+
+ parcEventQueue_SetCallbacks(buffer, readCallback, writeCallback, eventCallback, user_data);
+}
+
+void
+metisStreamBuffer_EnableCallbacks(PARCEventQueue *buffer, bool enableRead, bool enableWrite)
+{
+ assertNotNull(buffer, "Parameter buffer must be non-null");
+ short flags = 0;
+ if (enableRead) {
+ flags |= PARCEventType_Read;
+ }
+ if (enableWrite) {
+ flags |= PARCEventType_Write;
+ }
+
+ parcEventQueue_Enable(buffer, flags);
+}
+
+/**
+ * @function MetisStreamBuffer_DisableCallbacks
+ * @abstract Disables specified callbacks. Does not affect others.
+ * @discussion
+ * Disables enabled callbacks. If a callback is already disabled, has no effect.
+ * A "false" value does not enable it.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+void
+metisStreamBuffer_DisableCallbacks(PARCEventQueue *buffer, bool disableRead, bool disableWrite)
+{
+ assertNotNull(buffer, "Parameter buffer must be non-null");
+ short flags = 0;
+ if (disableRead) {
+ flags |= PARCEventType_Read;
+ }
+ if (disableWrite) {
+ flags |= PARCEventType_Write;
+ }
+
+ parcEventQueue_Disable(buffer, flags);
+}
diff --git a/metis/ccnx/forwarder/metis/core/metis_StreamBuffer.h b/metis/ccnx/forwarder/metis/core/metis_StreamBuffer.h
new file mode 100644
index 00000000..bd0fc3c6
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_StreamBuffer.h
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+
+
+/**
+ * Wrapper around event scheduler
+ */
+
+#ifndef Metis_metis_StreamBuffer_h
+#define Metis_metis_StreamBuffer_h
+
+#include <parc/algol/parc_EventQueue.h>
+#include <stdbool.h>
+
+void metisStreamBuffer_Destroy(PARCEventQueue **bufferPtr);
+
+/**
+ * @function metisStreamBuffer_SetWatermark
+ * @abstract Sets the read and/or write watermarks
+ * @discussion
+ * For a read watermark, when there is at least <code>low</code> bytes available to read,
+ * the read callback will be fired. If the bytes in the buffer exceed <code>high</code>,
+ * the stream buffer will stop reading from the network.
+ *
+ * For a write watermark, when the bytes in the buffer fall below <code>low</code>, the
+ * write callback is fired. The <code>high</code> watermark limits stream filters
+ * and shapers from exceeding that threashold on what they write to the buffer.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+void metisStreamBuffer_SetWatermark(PARCEventQueue *buffer, bool setRead, bool setWrite, size_t low, size_t high);
+
+/**
+ * @function metisStreamBuffer_Flush
+ * @abstract The buffer will read/write more data if available
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return -1 error, 0 no more data, 1 more data
+ */
+int metisStreamBuffer_Flush(PARCEventQueue *buffer, bool flushRead, bool flushWrite);
+
+/**
+ * @function metisStreamBuffer_FlushCheckpoint
+ * @abstract Flushes the stream, checkpointing all data in the buffer
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+int metisStreamBuffer_FlushCheckpoint(PARCEventQueue *buffer, bool flushRead, bool flushWrite);
+
+/**
+ * @function metisStreamBuffer_FlushFinished
+ * @abstract Flush the stream and indicate the end of new data
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+int metisStreamBuffer_FlushFinished(PARCEventQueue *buffer, bool flushRead, bool flushWrite);
+
+/**
+ * @typedef MetisStreamBufferReadWriteCallback
+ * @abstract Callback when data is available or write space available
+ * @constant user_data opaque data passed to <code>MetisStreamBuffer_SetCallbacks()</code>
+ * @discussion <#Discussion#>
+ */
+typedef void (MetisStreamBufferReadWriteCallback)(PARCEventQueue *buffer, void *user_data);
+
+/**
+ * @typedef MetisStreamBufferEventCallback
+ * @abstract Callback on error or other event on the stream buffer
+ * @constant what logical or of METIS_STREAM events. METIS_STREAM_READING and METIS_STREAM_WRITING
+ * indicate if the error was on the read or write direction. The conditions
+ * may be METIS_STREAM_ERROR, METIS_STREAM_EOF, METIS_STREAM_TIMEOUT, or METIS_STREAM_CONNECTED.
+ * @constant user_data opaque data passed to <code>MetisStreamBuffer_SetCallbacks()</code>
+ * @discussion <#Discussion#>
+ */
+typedef void (MetisStreamBufferEventCallback)(PARCEventQueue *buffer, short what, void *user_data);
+
+/**
+ * Changes the callbacks for a buffer event.
+ *
+ * @param bufev the buffer event object for which to change callbacks
+ * @param readcb callback to invoke when there is data to be read, or NULL if
+ * no callback is desired
+ * @param writecb callback to invoke when the file descriptor is ready for
+ * writing, or NULL if no callback is desired
+ * @param eventcb callback to invoke when there is an event on the file
+ * descriptor
+ * @param cbarg an argument that will be supplied to each of the callbacks
+ * (readcb, writecb, and errorcb)
+ * @see parcEventQueue_Create()
+ */
+void metisStreamBuffer_SetCallbacks(PARCEventQueue *buffer,
+ PARCEventQueue_Callback *readCallback,
+ PARCEventQueue_Callback *writeCallback,
+ PARCEventQueue_EventCallback *eventCallback,
+ void *user_data);
+
+/**
+ * @function MetisStreamBuffer_EnableCallbacks
+ * @abstract Enables specified callbacks. Does not affect others.
+ * @discussion
+ * Enables disabled callbacks. If a callback is already enabled, has no effect.
+ * A "false" value does not disable it.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+void metisStreamBuffer_EnableCallbacks(PARCEventQueue *buffer, bool enableRead, bool enableWrite);
+
+/**
+ * @function MetisStreamBuffer_DisableCallbacks
+ * @abstract Disables specified callbacks. Does not affect others.
+ * @discussion
+ * Disables enabled callbacks. If a callback is already disabled, has no effect.
+ * A "false" value does not enable it.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+void metisStreamBuffer_DisableCallbacks(PARCEventQueue *buffer, bool disableRead, bool disableWrite);
+#endif // Metis_metis_StreamBuffer_h
diff --git a/metis/ccnx/forwarder/metis/core/metis_System.h b/metis/ccnx/forwarder/metis/core/metis_System.h
new file mode 100644
index 00000000..5775e0fe
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_System.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.
+ */
+
+/**
+ * @header metis_System.h
+ * @abstract System-level properties
+ * @discussion
+ * <#Discussion#>
+ *
+ */
+
+#ifndef Metis_metis_System_h
+#define Metis_metis_System_h
+
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+#include <ccnx/api/control/cpi_InterfaceSet.h>
+
+/**
+ * @function metisSystem_Interfaces
+ * @abstract The system network interfaces
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+CPIInterfaceSet *metisSystem_Interfaces(MetisForwarder *metis);
+
+/**
+ * Returns the MTU of the named interface
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [in] metis An allocated forwarder
+ * @param [in] interfaceName The system interface name, e.g. "eth0"
+ *
+ * @return 0 Interface does not exist
+ * @return positive the MTU the kernel reports
+ *
+ * Example:
+ * @code
+ * {
+ * <#example#>
+ * }
+ * @endcode
+ */
+unsigned metisSystem_InterfaceMtu(MetisForwarder *metis, const char *interfaceName);
+
+/**
+ * Returns the LINK address of the specified interface
+ *
+ * <#Paragraphs Of Explanation#>
+ *
+ * @param [in] metis An allocated forwarder
+ * @param [in] interfaceName The system interface name, e.g. "eth0"
+ *
+ * @retval non-null The MAC address of the interface
+ * @retval null The interface does not exist
+ *
+ * Example:
+ * @code
+ * {
+ * CPIAddress *linkAddress = metisSystem_GetMacAddressByName(metis, "en0");
+ * }
+ * @endcode
+ */
+CPIAddress *metisSystem_GetMacAddressByName(MetisForwarder *metis, const char *interfaceName);
+#endif
diff --git a/metis/ccnx/forwarder/metis/core/metis_ThreadedForwarder.c b/metis/ccnx/forwarder/metis/core/metis_ThreadedForwarder.c
new file mode 100644
index 00000000..db6c0626
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_ThreadedForwarder.c
@@ -0,0 +1,247 @@
+/*
+ * 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.
+ */
+
+/**
+ * @header Metis Threaded Forwarder
+ * @abstract A thread wrapper around metis_Forwarder.
+ * @discussion
+ * Cannot restart a thread after its stopped. I think this should be ok, but
+ * have not had time to test it yet, so dont support it.
+ *
+ * This wrapper does not expose any of the metis_Forwarder calls, as those
+ * are all non-threaded calls. You can only create, start, stop, and destroy
+ * the forwarder. All configuration needs to be via the CLI or via CPI control messages.
+ *
+ * You may run multiple Metis forwarders as long as they are on different ports.
+ *
+ */
+
+#include <config.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <pthread.h>
+
+#define __STDC_FORMAT_MACROS
+#include <inttypes.h>
+
+#include <parc/algol/parc_Memory.h>
+#include <LongBow/runtime.h>
+
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+#include <ccnx/forwarder/metis/core/metis_ThreadedForwarder.h>
+#include <ccnx/forwarder/metis/core/metis_Dispatcher.h>
+#include <ccnx/forwarder/metis/config/metis_Configuration.h>
+#include <ccnx/forwarder/metis/config/metis_CommandLineInterface.h>
+
+struct metis_threaded_forwarder {
+ pthread_t thread;
+ pthread_mutex_t state_mutex;
+ pthread_cond_t state_cond;
+
+ // indicates that the Start function was called
+ bool started;
+
+ // indicates that the thread has entered the Run function and is running
+ bool running;
+
+ MetisForwarder *forwarder;
+ MetisLogger *logger;
+ MetisCommandLineInterface *cli;
+};
+
+static void
+metisThreadedForwarder_LockState(MetisThreadedForwarder *threadedMetis)
+{
+ int res = pthread_mutex_lock(&threadedMetis->state_mutex);
+ assertTrue(res == 0, "error from pthread_mutex_lock: %d", res);
+}
+
+static void
+metisThreadedForwarder_UnlockState(MetisThreadedForwarder *threadedMetis)
+{
+ int res = pthread_mutex_unlock(&threadedMetis->state_mutex);
+ assertTrue(res == 0, "error from pthread_mutex_unlock: %d", res);
+}
+
+static void
+metisThreadedForwarder_WaitStatus(MetisThreadedForwarder *threadedMetis)
+{
+ int res = pthread_cond_wait(&threadedMetis->state_cond, &threadedMetis->state_mutex);
+ assertTrue(res == 0, "error from pthread_mutex_unlock: %d", res);
+}
+
+static void
+metisThreadedForwarder_BroadcastStatus(MetisThreadedForwarder *threadedMetis)
+{
+ int res = pthread_cond_broadcast(&threadedMetis->state_cond);
+ assertTrue(res == 0, "error from pthread_mutex_unlock: %d", res);
+}
+
+static void *
+metisThreadedForwarder_Run(void *arg)
+{
+ MetisThreadedForwarder *threadedMetis = (MetisThreadedForwarder *) arg;
+
+ metisThreadedForwarder_LockState(threadedMetis);
+ assertFalse(threadedMetis->running, "Invalid State: forwarder already in running state");
+ threadedMetis->running = true;
+ metisThreadedForwarder_BroadcastStatus(threadedMetis);
+ metisThreadedForwarder_UnlockState(threadedMetis);
+
+ // --------
+ // Block in the dispatch loop
+ MetisDispatcher *dispatcher = metisForwarder_GetDispatcher(threadedMetis->forwarder);
+ metisDispatcher_Run(dispatcher);
+ // --------
+
+ metisThreadedForwarder_LockState(threadedMetis);
+ assertTrue(threadedMetis->running, "Invalid State: forwarder indicates its not running!");
+ threadedMetis->running = false;
+ metisThreadedForwarder_BroadcastStatus(threadedMetis);
+ metisThreadedForwarder_UnlockState(threadedMetis);
+
+ pthread_exit(NULL);
+}
+
+// ===========================
+
+MetisThreadedForwarder *
+metisThreadedForwarder_Create(MetisLogger *logger)
+{
+ struct sigaction ignore_action;
+ ignore_action.sa_handler = SIG_IGN;
+ sigemptyset(&ignore_action.sa_mask);
+ ignore_action.sa_flags = 0;
+ // sigaction(SIGPIPE, NULL, &save_sigpipe);
+ sigaction(SIGPIPE, &ignore_action, NULL);
+
+
+ MetisThreadedForwarder *threadedMetis = parcMemory_AllocateAndClear(sizeof(MetisThreadedForwarder));
+ assertNotNull(threadedMetis, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisThreadedForwarder));
+ threadedMetis->logger = metisLogger_Acquire(logger);
+ threadedMetis->forwarder = metisForwarder_Create(logger);
+
+ pthread_mutex_init(&threadedMetis->state_mutex, NULL);
+ pthread_cond_init(&threadedMetis->state_cond, NULL);
+
+ threadedMetis->thread = (pthread_t) { 0 };
+ threadedMetis->cli = NULL;
+ threadedMetis->running = false;
+ return threadedMetis;
+}
+
+void
+metisThreadedForwarder_AddCLI(MetisThreadedForwarder *threadedMetis, uint16_t port)
+{
+ assertNotNull(threadedMetis, "Parameter must be non-null");
+ assertFalse(threadedMetis->started, "Must be done prior to starting!");
+ assertNull(threadedMetis->cli, "Can only define one CLI");
+
+ threadedMetis->cli = metisCommandLineInterface_Create(threadedMetis->forwarder, port);
+
+ // this sets up all the network events in the dispatcher so when the thread is
+ // started, the CLI will be ready to go.
+ metisCommandLineInterface_Start(threadedMetis->cli);
+}
+
+void
+metisThreadedForwarder_SetupAllListeners(MetisThreadedForwarder *threadedMetis, uint16_t port, const char *localPath)
+{
+ assertNotNull(threadedMetis, "Parameter must be non-null");
+ assertFalse(threadedMetis->started, "Must be done prior to starting!");
+
+ metisForwarder_SetupAllListeners(threadedMetis->forwarder, port, localPath);
+}
+
+void
+metisThreadedForwarder_Start(MetisThreadedForwarder *threadedMetis)
+{
+ assertNotNull(threadedMetis, "Parameter must be non-null");
+ assertFalse(threadedMetis->started, "Must be done prior to starting!");
+
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+
+ int failure = pthread_create(&threadedMetis->thread, &attr, metisThreadedForwarder_Run, threadedMetis);
+ assertFalse(failure, "Eror creating thread: %d", failure);
+
+ // block until running
+ metisThreadedForwarder_LockState(threadedMetis);
+ while (!threadedMetis->running) {
+ metisThreadedForwarder_WaitStatus(threadedMetis);
+ }
+ metisThreadedForwarder_UnlockState(threadedMetis);
+}
+
+/**
+ * @function metisThreadedForwarder_Stop
+ * @abstract Blocks until stopped
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+void
+metisThreadedForwarder_Stop(MetisThreadedForwarder *threadedMetis)
+{
+ assertNotNull(threadedMetis, "Parameter must be non-null");
+
+ // These are explicitly thread-safe operations inside Metis
+ MetisDispatcher *dispatcher = metisForwarder_GetDispatcher(threadedMetis->forwarder);
+ metisDispatcher_Stop(dispatcher);
+
+ // Equivalently, we could block until joined
+
+ // block until stopped
+ metisThreadedForwarder_LockState(threadedMetis);
+ while (threadedMetis->running) {
+ metisThreadedForwarder_WaitStatus(threadedMetis);
+ }
+ metisThreadedForwarder_UnlockState(threadedMetis);
+}
+
+/**
+ * @function metisThreadedForwarder_Destroy
+ * @abstract Blocks until stopped and destoryed
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+void
+metisThreadedForwarder_Destroy(MetisThreadedForwarder **threadedMetisPtr)
+{
+ assertNotNull(threadedMetisPtr, "Parameter must be non-null double pointer");
+ assertNotNull(*threadedMetisPtr, "Parameter must dereference to non-null pointer");
+
+ MetisThreadedForwarder *threadedMetis = *threadedMetisPtr;
+ metisThreadedForwarder_Stop(threadedMetis);
+
+ pthread_mutex_destroy(&threadedMetis->state_mutex);
+ pthread_cond_destroy(&threadedMetis->state_cond);
+
+ metisLogger_Release(&threadedMetis->logger);
+ metisForwarder_Destroy(&threadedMetis->forwarder);
+ parcMemory_Deallocate((void **) &threadedMetis);
+ *threadedMetisPtr = NULL;
+}
diff --git a/metis/ccnx/forwarder/metis/core/metis_ThreadedForwarder.h b/metis/ccnx/forwarder/metis/core/metis_ThreadedForwarder.h
new file mode 100644
index 00000000..162a6b78
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_ThreadedForwarder.h
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ */
+
+/**
+ * @header Metis Threaded Forwarder
+ * @abstract This is a wrapper around metis_Forwarder to run it as a thread
+ * @discussion
+ * <#Discussion#>
+ *
+ */
+
+#ifndef Metis_metis_ThreadedForwarder_h
+#define Metis_metis_ThreadedForwarder_h
+
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+
+struct metis_threaded_forwarder;
+typedef struct metis_threaded_forwarder MetisThreadedForwarder;
+
+/**
+ * @function metisThreadedForwarder_Create
+ * @abstract Creates a threaded forwarder in the stopped state
+ * @discussion
+ * IMPORTANT: The logger is called from the Metis thread, so it is up to
+ * the user to implement any necessary thread saftey in the logger. There
+ * is only a single metis thread, so it does not need to be re-enterent.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+MetisThreadedForwarder *metisThreadedForwarder_Create(MetisLogger *logger);
+
+/**
+ * @function metisThreadedForwarder_AddCLI
+ * @abstract Add a command line interface (CLI) on the given port
+ * @discussion
+ * MUST BE DONE PRIOR TO START. This function will add a CLI to the forwarder
+ * prior to starting it. Once started, will assert if you try to do this.
+ *
+ * @param <#param1#>
+ */
+void metisThreadedForwarder_AddCLI(MetisThreadedForwarder *metis, uint16_t port);
+
+/**
+ * @function metisThreadedForwarder_AddTcpListener
+ * @abstract Adds a TCP listenener
+ * @discussion
+ * MUST BE DONE PRIOR TO START.
+ * May be IPv4 or IPv6
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+void metisThreadedForwarder_AddTcpListener(MetisThreadedForwarder *metis, struct sockaddr *address);
+
+/**
+ * @function metisThreadedForwarder_SetupAllListeners
+ * @abstract Setup all tcp/udp ipv4/ipv6 listeners on the given port
+ * @discussion
+ * MUST BE DONE PRIOR TO START.
+ *
+ * @param port is the UDP and TCP port
+ * @param localPath is the AF_UNIX path, may be NULL for no AF_UNIX socket.
+ * @return <#return#>
+ */
+void metisThreadedForwarder_SetupAllListeners(MetisThreadedForwarder *metis, uint16_t port, const char *localPath);
+
+/**
+ * @function metisThreadedForwarder_Start
+ * @abstract Blocks until started
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ */
+void metisThreadedForwarder_Start(MetisThreadedForwarder *metis);
+
+/**
+ * @function metisThreadedForwarder_Stop
+ * @abstract Blocks until stopped
+ * @discussion
+ * Currently we do not support re-starting a thread after it is stopped.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+void metisThreadedForwarder_Stop(MetisThreadedForwarder *metis);
+
+/**
+ * @function metisThreadedForwarder_Destroy
+ * @abstract Blocks until stopped and destoryed
+ * @discussion
+ * <#Discussion#>
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+void metisThreadedForwarder_Destroy(MetisThreadedForwarder **metisPtr);
+#endif // Metis_metis_ThreadedForwarder_h
diff --git a/metis/ccnx/forwarder/metis/core/metis_Ticks.h b/metis/ccnx/forwarder/metis/core/metis_Ticks.h
new file mode 100644
index 00000000..6e6bda00
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_Ticks.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file metis_Ticks.h
+ * @brief The router periodically measures time in units of Ticks
+ *
+ * See metis_Forwarder.c METISHZ which specifies the tick rate. metis_Forwarder.h has functions
+ * to convert between ticks and milliseconds.
+ *
+ */
+#ifndef Metis_metis_Ticks_h
+#define Metis_metis_Ticks_h
+
+#define __STDC_FORMAT_MACROS
+#include <stdint.h>
+
+typedef uint64_t MetisTicks;
+
+#endif // Metis_metis_Ticks_h
diff --git a/metis/ccnx/forwarder/metis/core/metis_Wldr.c b/metis/ccnx/forwarder/metis/core/metis_Wldr.c
new file mode 100644
index 00000000..6249bf0a
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_Wldr.c
@@ -0,0 +1,174 @@
+/*
+ * 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 <stdint.h>
+#include <stdio.h>
+#include <ccnx/forwarder/metis/core/metis_Connection.h>
+#include <ccnx/forwarder/metis/core/metis_Wldr.h>
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+#include <parc/logging/parc_LogReporterTextStdout.h>
+
+struct metis_wldr_buffer {
+ MetisMessage *message;
+ uint8_t rtx_counter;
+};
+
+typedef struct metis_wldr_buffer MetisWldrBuffer;
+
+struct metis_wldr_state {
+ uint16_t expected_label;
+ uint16_t next_label;
+ MetisWldrBuffer *buffer[BUFFER_SIZE];
+};
+
+MetisWldr *
+metisWldr_Init()
+{
+ MetisWldr *wldr = parcMemory_AllocateAndClear(sizeof(MetisWldr));
+ assertNotNull(wldr, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisWldr));
+ wldr->expected_label = 1;
+ wldr->next_label = 1;
+ for (int i = 0; i < BUFFER_SIZE; i++) {
+ MetisWldrBuffer *entry = parcMemory_AllocateAndClear(sizeof(MetisWldrBuffer));
+ assertNotNull(entry, "WldrBuffer init: parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisWldrBuffer));
+ entry->message = NULL;
+ entry->rtx_counter = 0;
+ wldr->buffer[i] = entry;
+ }
+ return wldr;
+}
+
+void
+metisWldr_ResetState(MetisWldr *wldr)
+{
+ wldr->expected_label = 1;
+ wldr->next_label = 1;
+ for (int i = 0; i < BUFFER_SIZE; i++) {
+ wldr->buffer[i]->message = NULL;
+ wldr->buffer[i]->rtx_counter = 0;
+ }
+}
+
+void
+metisWldr_Destroy(MetisWldr **wldrPtr)
+{
+ MetisWldr *wldr = *wldrPtr;
+ for (unsigned i = 0; i < BUFFER_SIZE; i++) {
+ if (wldr->buffer[i]->message != NULL) {
+ metisMessage_Release(&(wldr->buffer[i]->message));
+ parcMemory_Deallocate((void **) &(wldr->buffer[i]));
+ }
+ }
+ parcMemory_Deallocate((void **) &wldr);
+ *wldrPtr = NULL;
+}
+
+
+static void
+_metisWldr_RetransmitPacket(MetisWldr *wldr, const MetisConnection *conn, uint16_t label)
+{
+ if (wldr->buffer[label % BUFFER_SIZE]->message == NULL) {
+ printf("the required message for retransmission is not in the buffer\n");
+ return;
+ }
+
+ if (wldr->buffer[label % BUFFER_SIZE]->rtx_counter < MAX_RTX) {
+ MetisMessage *msg = wldr->buffer[label % BUFFER_SIZE]->message;
+
+ //printf("-----retransmit packet label = %d, new label = %d\n", label, wldr->next_label);
+ metisMessage_SetWldrLabel(msg, wldr->next_label);
+
+ if (wldr->buffer[wldr->next_label % BUFFER_SIZE]->message != NULL) {
+ //printf("-------------release message in retransmit packet, %d %d\n",(wldr->next_label % BUFFER_SIZE),wldr->next_label);
+ metisMessage_Release(&(wldr->buffer[wldr->next_label % BUFFER_SIZE]->message));
+ }
+
+ wldr->buffer[wldr->next_label % BUFFER_SIZE]->message = msg;
+ wldr->buffer[wldr->next_label % BUFFER_SIZE]->rtx_counter = wldr->buffer[label % BUFFER_SIZE]->rtx_counter + 1;
+ metisMessage_Acquire(wldr->buffer[wldr->next_label % BUFFER_SIZE]->message);
+ wldr->next_label++;
+ metisConnection_ReSend(conn, msg);
+ }
+}
+
+static void
+_metisWldr_SendWldrNotificaiton(MetisWldr *wldr, const MetisConnection *conn, MetisMessage *message, uint16_t expected_lbl, uint16_t received_lbl)
+{
+ //here we create a copy of the last message received and we use it as a loss notification
+ //this can be made more efficient using a pre-encoded message with a small size
+ MetisMessage *notification = metisMessage_Slice(message, 0, metisMessage_Length(message), 0, NULL);
+ metisMessage_SetWldrNotification(notification, expected_lbl, received_lbl);
+ //printf("------------send notification %u, %u\n",expected_lbl, received_lbl);
+ assertNotNull(notification, "Got null from Slice");
+ metisConnection_ReSend(conn, notification);
+}
+
+
+void
+metisWldr_SetLabel(MetisWldr *wldr, MetisMessage *message)
+{
+ //we send the packet for the first time
+ metisMessage_SetWldrLabel(message, wldr->next_label);
+ if (wldr->buffer[wldr->next_label % BUFFER_SIZE]->message != NULL) {
+ //printf("-------------release message in set label packet, %d %d\n",(wldr->next_label % BUFFER_SIZE),wldr->next_label);
+ metisMessage_Release(&(wldr->buffer[wldr->next_label % BUFFER_SIZE]->message));
+ }
+ metisMessage_Acquire(message);
+ wldr->buffer[wldr->next_label % BUFFER_SIZE]->message = message;
+ wldr->buffer[wldr->next_label % BUFFER_SIZE]->rtx_counter = 0;
+ wldr->next_label++;
+}
+
+void
+metisWldr_DetectLosses(MetisWldr *wldr, const MetisConnection *conn, MetisMessage *message)
+{
+ if (metisMessage_HasWldr(message)) {
+ uint8_t wldr_type = (uint8_t) metisMessage_GetWldrType(message);
+ if (wldr_type == WLDR_LBL) {
+ uint16_t pkt_lbl = (uint16_t) metisMessage_GetWldrLabel(message);
+ //printf("--------------received packet label %u\n", pkt_lbl);
+ if (pkt_lbl != wldr->expected_label) {
+ //if the received packet label is 1 and the expected packet label > pkt_lbl
+ //usually we are in the case where a remove note disconnected for a while
+ //and reconnected on this same connection, so the two nodes are out of synch
+ //for this reason we do not send any notification, we just synch the labels
+
+ if ((pkt_lbl != 1) || (wldr->expected_label < pkt_lbl)) {
+ _metisWldr_SendWldrNotificaiton(wldr, conn, message, wldr->expected_label, pkt_lbl);
+ }
+
+ //here we always synch
+ wldr->expected_label = (uint16_t) (pkt_lbl + 1);
+ } else {
+ wldr->expected_label++;
+ }
+ } else if (wldr_type == WLDR_NOTIFICATION) {
+ uint16_t expected_lbl = (uint16_t) metisMessage_GetWldrLabel(message);
+ uint16_t received_lbl = (uint16_t) metisMessage_GetWldrLastReceived(message);
+ //printf("------------received notification %u, %u\n",expected_lbl, received_lbl);
+ if ((wldr->next_label - expected_lbl) > BUFFER_SIZE) {
+ //printf("--------------the packets are not in the buffer anymore %u %u %u\n", wldr->next_label,
+ // expected_lbl, BUFFER_SIZE);
+ //the packets are not in the buffer anymore
+ return;
+ }
+ while (expected_lbl < received_lbl) {
+ _metisWldr_RetransmitPacket(wldr, conn, expected_lbl);
+ expected_lbl++;
+ }
+ }
+ }
+}
diff --git a/metis/ccnx/forwarder/metis/core/metis_Wldr.h b/metis/ccnx/forwarder/metis/core/metis_Wldr.h
new file mode 100644
index 00000000..9332e9e0
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/metis_Wldr.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.
+ */
+
+#ifndef Metis_metis_Wldr_h
+#define Metis_metis_Wldr_h
+
+#include <ccnx/forwarder/metis/core/metis_Message.h>
+#include <ccnx/forwarder/metis/core/metis_Connection.h>
+#include <config.h>
+
+#define BUFFER_SIZE 8192
+#define MAX_RTX 3
+#define WLDR_HEADER_SIZE 6
+#define WLDR_HEADER 12
+#define WLDR_LBL 13
+#define WLDR_NOTIFICATION 14
+
+//WLDR HEADERS :
+// NORMAL PACKET or RETRASMISSION
+// | WLDR_HEADER | WLDR_LBL | label (1byte) | label (2bytes) | unused | unused |
+// NOTIFICATION
+// | WLDR_HEADER | WLDR_NOTIFICATION | expected_label (1byte) | expected_label (2bytes) | last_received_label (1byte) | last_received_label (2byte) |
+
+struct metis_wldr_state;
+typedef struct metis_wldr_state MetisWldr;
+
+MetisWldr *metisWldr_Init();
+
+void metisWldr_Destroy(MetisWldr **wldrPtr);
+
+void metisWldr_ResetState(MetisWldr *wldr);
+
+void metisWldr_SetLabel(MetisWldr *wldr, MetisMessage *message);
+
+void metisWldr_DetectLosses(MetisWldr *wldr, const MetisConnection *conn, MetisMessage *message);
+
+#endif //Metis_metis_Wldr_h
diff --git a/metis/ccnx/forwarder/metis/core/test/CMakeLists.txt b/metis/ccnx/forwarder/metis/core/test/CMakeLists.txt
new file mode 100644
index 00000000..a1209868
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/test/CMakeLists.txt
@@ -0,0 +1,22 @@
+# Enable gcov output for the tests
+add_definitions(--coverage)
+set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} " --coverage")
+
+set(TestsExpectedToPass
+ test_metis_Connection
+ test_metis_ConnectionTable
+ test_metis_Dispatcher
+ test_metis_Forwarder
+ test_metis_Logger
+ test_metis_Message
+ test_metis_NumberSet
+ test_metis_StreamBuffer
+ test_metis_ConnectionList
+ test_metis_ThreadedForwarder
+)
+
+
+foreach(test ${TestsExpectedToPass})
+ AddTest(${test})
+endforeach()
+
diff --git a/metis/ccnx/forwarder/metis/core/test/test_metis_Connection.c b/metis/ccnx/forwarder/metis/core/test/test_metis_Connection.c
new file mode 100644
index 00000000..1fe904d1
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/test/test_metis_Connection.c
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Include the file(s) containing the functions to be tested.
+// This permits internal static functions to be visible to this Test Framework.
+#include "../metis_Connection.c"
+#include <LongBow/unit-test.h>
+#include <parc/algol/parc_SafeMemory.h>
+#include <parc/logging/parc_LogReporterTextStdout.h>
+
+#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h>
+
+#include "testrig_MetisIoOperations.h"
+
+LONGBOW_TEST_RUNNER(metis_Connection)
+{
+ // 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_Connection)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// The Test Runner calls this function once after all the Test Fixtures are run.
+LONGBOW_TEST_RUNNER_TEARDOWN(metis_Connection)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE(Global)
+{
+ LONGBOW_RUN_TEST_CASE(Global, metisConnection_Acquire);
+ LONGBOW_RUN_TEST_CASE(Global, metisConnection_Create_Destroy);
+ LONGBOW_RUN_TEST_CASE(Global, metisConnection_Send);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisConnection_GetConnectionId);
+ LONGBOW_RUN_TEST_CASE(Global, metisConnection_GetAddressPair);
+ LONGBOW_RUN_TEST_CASE(Global, metisConnection_IsUp);
+ LONGBOW_RUN_TEST_CASE(Global, metisConnection_IsLocal);
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(Global)
+{
+ MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true);
+ longBowTestCase_SetClipBoardData(testCase, ops);
+
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(Global)
+{
+ MetisIoOperations *ops = longBowTestCase_GetClipBoardData(testCase);
+ mockIoOperationsData_Destroy(&ops);
+
+ 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, metisConnection_Acquire)
+{
+ MetisIoOperations *ops = longBowTestCase_GetClipBoardData(testCase);
+ MetisConnection *conn = metisConnection_Create(ops);
+
+ assertTrue(conn->refCount == 1, "Wrong refcount, got %u expected %u", conn->refCount, 1);
+
+ MetisConnection *copy = metisConnection_Acquire(conn);
+ assertTrue(conn->refCount == 2, "Wrong refcount, got %u expected %u", conn->refCount, 2);
+
+ metisConnection_Release(&copy);
+ assertTrue(conn->refCount == 1, "Wrong refcount, got %u expected %u", conn->refCount, 1);
+
+ metisConnection_Release(&conn);
+}
+
+LONGBOW_TEST_CASE(Global, metisConnection_Create_Destroy)
+{
+ MetisIoOperations *ops = longBowTestCase_GetClipBoardData(testCase);
+ MetisConnection *conn = metisConnection_Create(ops);
+ assertNotNull(conn, "Got null connection");
+ assertTrue(conn->refCount == 1, "Wrong refcount, got %u expected %u", conn->refCount, 1);
+
+ metisConnection_Release(&conn);
+ assertNull(conn, "Release did not null pointer");
+
+ // the mock in testrig_MetisIoOperations does not destroy the IoOperations on destroy
+ // so we can still look at the counters
+ assertTrue(((MockIoOperationsData *) metisIoOperations_GetClosure(ops))->destroyCount == 1,
+ "Destroy count is wrong, got %u expected %u",
+ ((MockIoOperationsData *) metisIoOperations_GetClosure(ops))->destroyCount,
+ 1);
+}
+
+LONGBOW_TEST_CASE(Global, metisConnection_Send)
+{
+ MetisIoOperations *ops = longBowTestCase_GetClipBoardData(testCase);
+ MockIoOperationsData *data = metisIoOperations_GetClosure(ops);
+
+ MetisConnection *conn = metisConnection_Create(ops);
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+
+ MetisMessage *message = metisMessage_CreateFromArray((uint8_t *) metisTestDataV1_Interest_AllFields,
+ sizeof(metisTestDataV1_Interest_AllFields), 111, 2, logger);
+
+ metisConnection_Send(conn, message);
+
+ assertTrue(data->sendCount == 1, "Send count wrong, got %u expected %u", data->sendCount, 1);
+ assertTrue(data->lastMessage == message, "Sent wrong message, got %p expected %p", (void *) data->lastMessage, (void *) message);
+
+ metisMessage_Release(&message);
+ metisConnection_Release(&conn);
+ metisLogger_Release(&logger);
+}
+
+LONGBOW_TEST_CASE(Global, metisConnection_GetConnectionId)
+{
+ MetisIoOperations *ops = longBowTestCase_GetClipBoardData(testCase);
+ MockIoOperationsData *data = metisIoOperations_GetClosure(ops);
+
+ MetisConnection *conn = metisConnection_Create(ops);
+ unsigned testid = metisConnection_GetConnectionId(conn);
+
+ assertTrue(testid == data->id, "Got wrong id, got %u expected %u", testid, data->id);
+ assertTrue(data->getConnectionIdCount == 1, "Wrong getConnectionIdCount, got %u expected %u", data->getConnectionIdCount, 1);
+
+ metisConnection_Release(&conn);
+}
+
+LONGBOW_TEST_CASE(Global, metisConnection_GetAddressPair)
+{
+ MetisIoOperations *ops = longBowTestCase_GetClipBoardData(testCase);
+ MockIoOperationsData *data = metisIoOperations_GetClosure(ops);
+
+ MetisConnection *conn = metisConnection_Create(ops);
+
+ unsigned beforeCount = data->getAddressPairCount;
+ const MetisAddressPair *pair = metisConnection_GetAddressPair(conn);
+
+ assertTrue(metisAddressPair_Equals(pair, data->addressPair), "Got wrong address pair");
+ assertTrue(data->getAddressPairCount == beforeCount + 1, "Wrong getAddressPairCount, got %u expected %u", data->getAddressPairCount, beforeCount + 1);
+
+ metisConnection_Release(&conn);
+}
+
+LONGBOW_TEST_CASE(Global, metisConnection_IsUp)
+{
+ MetisIoOperations *ops = longBowTestCase_GetClipBoardData(testCase);
+ MockIoOperationsData *data = metisIoOperations_GetClosure(ops);
+
+ MetisConnection *conn = metisConnection_Create(ops);
+
+ unsigned beforeCount = data->isUpCount;
+ bool isup = metisConnection_IsUp(conn);
+
+ assertTrue(isup == data->isUp, "Got wrong isup, got %d expected %d", isup, data->isUp);
+ assertTrue(data->isUpCount == beforeCount + 1, "Wrong isUpCount, got %u expected %u", data->isUpCount, beforeCount + 1);
+
+ metisConnection_Release(&conn);
+}
+
+LONGBOW_TEST_CASE(Global, metisConnection_IsLocal)
+{
+ MetisIoOperations *ops = longBowTestCase_GetClipBoardData(testCase);
+ MockIoOperationsData *data = metisIoOperations_GetClosure(ops);
+
+ MetisConnection *conn = metisConnection_Create(ops);
+
+ unsigned beforeCount = data->isLocalCount;
+ bool islocal = metisConnection_IsLocal(conn);
+
+ assertTrue(islocal == data->isLocal, "Got wrong islocal, got %d expected %d", islocal, data->isLocal);
+ assertTrue(data->isLocalCount == beforeCount + 1, "Wrong isLocalCount, got %u expected %u", data->isLocalCount, beforeCount + 1);
+
+ metisConnection_Release(&conn);
+}
+
+
+LONGBOW_TEST_FIXTURE(Local)
+{
+}
+
+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;
+}
+
+int
+main(int argc, char *argv[])
+{
+ LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_Connection);
+ int exitStatus = longBowMain(argc, argv, testRunner, NULL);
+ longBowTestRunner_Destroy(&testRunner);
+ exit(exitStatus);
+}
diff --git a/metis/ccnx/forwarder/metis/core/test/test_metis_ConnectionList.c b/metis/ccnx/forwarder/metis/core/test/test_metis_ConnectionList.c
new file mode 100644
index 00000000..94518a9e
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/test/test_metis_ConnectionList.c
@@ -0,0 +1,159 @@
+/*
+ * 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_ConnectionList.c"
+#include <parc/algol/parc_SafeMemory.h>
+#include "testrig_MetisIoOperations.h"
+#include <LongBow/unit-test.h>
+
+LONGBOW_TEST_RUNNER(metis_ConnectionList)
+{
+ // 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_ConnectionList)
+{
+ 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_ConnectionList)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE(Global)
+{
+ LONGBOW_RUN_TEST_CASE(Global, metisConnectionList_Append);
+ LONGBOW_RUN_TEST_CASE(Global, metisConnectionList_Create_Destroy);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisConnectionList_Get);
+ LONGBOW_RUN_TEST_CASE(Global, metisConnectionList_Length);
+}
+
+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, metisConnectionList_Append)
+{
+ MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true);
+ MetisConnection *connection = metisConnection_Create(ops);
+
+ MetisConnectionList *list = metisConnectionList_Create();
+ metisConnectionList_Append(list, connection);
+ metisConnection_Release(&connection);
+
+ assertTrue(parcArrayList_Size(list->listOfConnections) == 1,
+ "Got wrong list size, got %zu expected %u",
+ parcArrayList_Size(list->listOfConnections), 1);
+
+ metisConnectionList_Destroy(&list);
+ mockIoOperationsData_Destroy(&ops);
+}
+
+LONGBOW_TEST_CASE(Global, metisConnectionList_Create_Destroy)
+{
+ MetisConnectionList *list = metisConnectionList_Create();
+ assertNotNull(list, "Got null from Create");
+
+ metisConnectionList_Destroy(&list);
+ assertNull(list, "Destroy did not null the parameter");
+}
+
+LONGBOW_TEST_CASE(Global, metisConnectionList_Get)
+{
+ MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true);
+ MetisConnection *connection = metisConnection_Create(ops);
+
+ MetisConnectionList *list = metisConnectionList_Create();
+ metisConnectionList_Append(list, connection);
+
+ MetisConnection *test = metisConnectionList_Get(list, 0);
+ assertTrue(test == connection,
+ "Got wrong connection, got %p expected %p",
+ (void *) test, (void *) connection);
+
+ metisConnection_Release(&connection);
+ metisConnectionList_Destroy(&list);
+ mockIoOperationsData_Destroy(&ops);
+}
+
+LONGBOW_TEST_CASE(Global, metisConnectionList_Length)
+{
+ MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true);
+ MetisConnection *connection = metisConnection_Create(ops);
+
+ MetisConnectionList *list = metisConnectionList_Create();
+ metisConnectionList_Append(list, connection);
+
+ size_t length = metisConnectionList_Length(list);
+ assertTrue(length == 1,
+ "Got wrong list size, got %zu expected %u",
+ length, 1);
+
+ metisConnection_Release(&connection);
+ metisConnectionList_Destroy(&list);
+ mockIoOperationsData_Destroy(&ops);
+}
+
+LONGBOW_TEST_FIXTURE(Local)
+{
+ LONGBOW_RUN_TEST_CASE(Local, metisConnectionList_ArrayDestroyer);
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(Local)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(Local)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_CASE(Local, metisConnectionList_ArrayDestroyer)
+{
+ testUnimplemented("");
+}
+
+int
+main(int argc, char *argv[])
+{
+ LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_ConnectionList);
+ int exitStatus = longBowMain(argc, argv, testRunner, NULL);
+ longBowTestRunner_Destroy(&testRunner);
+ exit(exitStatus);
+}
diff --git a/metis/ccnx/forwarder/metis/core/test/test_metis_ConnectionManager.c b/metis/ccnx/forwarder/metis/core/test/test_metis_ConnectionManager.c
new file mode 100644
index 00000000..b4550e44
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/test/test_metis_ConnectionManager.c
@@ -0,0 +1,261 @@
+/*
+ * 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_ConnectionManager.c"
+
+#include <LongBow/unit-test.h>
+
+LONGBOW_TEST_RUNNER(metis_ConnectionManager)
+{
+ // 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_ConnectionManager)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// The Test Runner calls this function once after all the Test Fixtures are run.
+LONGBOW_TEST_RUNNER_TEARDOWN(metis_ConnectionManager)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE(Global)
+{
+ LONGBOW_RUN_TEST_CASE(Global, metisConnectionManager_Create);
+ LONGBOW_RUN_TEST_CASE(Global, metisConnectionManager_Destroy);
+}
+
+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, metisConnectionManager_Create)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Global, metisConnectionManager_Destroy)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_FIXTURE(Local)
+{
+ LONGBOW_RUN_TEST_CASE(Local, metisConnectionManager_MessengerCallback);
+ LONGBOW_RUN_TEST_CASE(Local, metisConnectionManager_NotifyApplications);
+ LONGBOW_RUN_TEST_CASE(Local, metisConnectionManager_ProcessDownMissive);
+ LONGBOW_RUN_TEST_CASE(Local, metisConnectionManager_ProcessQueue);
+ LONGBOW_RUN_TEST_CASE(Local, metisConnectionManager_ProcessUpMissive);
+ LONGBOW_RUN_TEST_CASE(Local, metisConnectionManager_ProcessDestroyMissive);
+
+ LONGBOW_RUN_TEST_CASE(Local, metisConnectionManager_ProcessCloseMissive_RemoveConnection);
+ LONGBOW_RUN_TEST_CASE(Local, metisConnectionManager_ProcessCloseMissive_RemoveRoutes);
+}
+
+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, metisConnectionManager_MessengerCallback)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Local, metisConnectionManager_NotifyApplications)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Local, metisConnectionManager_ProcessDownMissive)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Local, metisConnectionManager_ProcessQueue)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Local, metisConnectionManager_ProcessUpMissive)
+{
+ testUnimplemented("");
+}
+
+typedef struct my_connection {
+ MetisAddressPair *addressPair;
+ unsigned connectionId;
+} MyConnection;
+
+static const MetisAddressPair *
+getAddressPair(const MetisIoOperations *ops)
+{
+ MyConnection *myconn = (MyConnection *) ops->context;
+ return myconn->addressPair;
+}
+
+static unsigned
+getConnectionId(const MetisIoOperations *ops)
+{
+ MyConnection *myconn = (MyConnection *) ops->context;
+ return myconn->connectionId;
+}
+
+static void
+myConnectionDestroy(MetisIoOperations **opsPtr)
+{
+ MetisIoOperations *ops = *opsPtr;
+ MyConnection *myconn = (MyConnection *) ops->context;
+ metisAddressPair_Release(&myconn->addressPair);
+ parcMemory_Deallocate((void **) &ops);
+ *opsPtr = NULL;
+}
+
+static MetisConnection *
+createConnection(unsigned connectionId)
+{
+ MyConnection *myconn = parcMemory_AllocateAndClear(sizeof(MyConnection));
+ assertNotNull(myconn, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MyConnection));
+ CPIAddress *a = cpiAddress_CreateFromInterface(1);
+ myconn->addressPair = metisAddressPair_Create(a, a);
+ myconn->connectionId = connectionId;
+
+ cpiAddress_Destroy(&a);
+
+ MetisIoOperations *ops = parcMemory_AllocateAndClear(sizeof(MetisIoOperations));
+ assertNotNull(ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisIoOperations));
+ memset(ops, 0, sizeof(MetisIoOperations));
+ ops->context = myconn;
+ ops->destroy = myConnectionDestroy;
+ ops->getAddressPair = getAddressPair;
+ ops->getConnectionId = getConnectionId;
+
+ MetisConnection *conn = metisConnection_Create(ops);
+ return conn;
+}
+
+static void
+addRoute(MetisForwarder *metis, const char *name, unsigned connectionId)
+{
+ CCNxName *uri = ccnxName_CreateFromURI(name);
+ CPIRouteEntry *route = cpiRouteEntry_Create(uri, connectionId, NULL, cpiNameRouteProtocolType_STATIC, cpiNameRouteType_LONGEST_MATCH, NULL, 1);
+
+ metisForwarder_AddOrUpdateRoute(metis, route);
+ ccnxName_Release(&uri);
+}
+
+/**
+ * We add a connection, then send a CLOSE message, make sure the connection
+ * is no longer in the connection table.
+ */
+LONGBOW_TEST_CASE(Local, metisConnectionManager_ProcessCloseMissive_RemoveConnection)
+{
+ unsigned connectionId = 1000;
+
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 }));
+
+ MetisConnection *conn = createConnection(connectionId);
+ metisConnectionTable_Add(metisForwarder_GetConnectionTable(metis), conn);
+
+ metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 }));
+
+ // send close message
+ metisMessenger_Send(metisForwarder_GetMessenger(metis), metisMissive_Create(MetisMissiveType_ConnectionClosed, connectionId));
+ metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 }));
+
+ // now make sure its gone
+ const MetisConnection *test = metisConnectionTable_FindById(metisForwarder_GetConnectionTable(metis), connectionId);
+ assertNull(test, "Return from connection table should be null, got %p\n", (const void *) test);
+
+ metisForwarder_Destroy(&metis);
+}
+
+/**
+ * We add a connection and a route that uses that connection, then send a CLOSE message,
+ * then make sure the connection is no longer in the routing table.
+ */
+LONGBOW_TEST_CASE(Local, metisConnectionManager_ProcessCloseMissive_RemoveRoutes)
+{
+ unsigned connectionId = 1001;
+
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 }));
+
+ MetisConnection *conn = createConnection(connectionId);
+ metisConnectionTable_Add(metisForwarder_GetConnectionTable(metis), conn);
+
+ addRoute(metis, "lci:/foo/bar", connectionId);
+
+ metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 }));
+
+ // send close message
+ metisMessenger_Send(metisForwarder_GetMessenger(metis), metisMissive_Create(MetisMissiveType_ConnectionClosed, connectionId));
+ metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 }));
+
+ // now make sure its gone
+ MetisFibEntryList *fiblist = metisForwarder_GetFibEntries(metis);
+ for (size_t i = 0; i < metisFibEntryList_Length(fiblist); i++) {
+ MetisFibEntry *fibentry = metisFibEntryList_Get(fiblist, i);
+ assertTrue(metisFibEntry_NexthopCount(fibentry) == 0, "Wrong nexthop count, expected 0 got %zu", metisFibEntry_NexthopCount(fibentry));
+ }
+
+ metisForwarder_Destroy(&metis);
+}
+
+LONGBOW_TEST_CASE(Local, metisConnectionManager_ProcessDestroyMissive)
+{
+ testUnimplemented("");
+}
+
+int
+main(int argc, char *argv[])
+{
+ LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_ConnectionManager);
+ int exitStatus = longBowMain(argc, argv, testRunner, NULL);
+ longBowTestRunner_Destroy(&testRunner);
+ exit(exitStatus);
+}
diff --git a/metis/ccnx/forwarder/metis/core/test/test_metis_ConnectionTable.c b/metis/ccnx/forwarder/metis/core/test/test_metis_ConnectionTable.c
new file mode 100644
index 00000000..b55b6e67
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/test/test_metis_ConnectionTable.c
@@ -0,0 +1,490 @@
+/*
+ * 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_ConnectionTable.c"
+#include <LongBow/unit-test.h>
+#include <parc/algol/parc_SafeMemory.h>
+
+#include "testrig_MetisIoOperations.h"
+
+static struct test_set_s {
+ unsigned localAddr;
+ unsigned remoteAddr;
+ unsigned id;
+ MetisIoOperations *ops;
+ MetisConnection *conn;
+} test_set [] = {
+ { .localAddr = 1, .remoteAddr = 2, .id = 3, .ops = NULL },
+ { .localAddr = 2, .remoteAddr = 1, .id = 4, .ops = NULL },
+ { .localAddr = 7, .remoteAddr = 2, .id = 22, .ops = NULL },
+ { .localAddr = 13, .remoteAddr = 2, .id = 102332, .ops = NULL },
+ { .localAddr = 99, .remoteAddr = 2, .id = 99, .ops = NULL },
+ { .localAddr = 3, .remoteAddr = 5, .id = 0xFFFFFFFF, .ops = NULL },
+ { .localAddr = 0, .remoteAddr = 0, .id = 0, .ops = NULL }
+};
+
+// ============================================
+
+LONGBOW_TEST_RUNNER(metis_ConnectionTable)
+{
+ // 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(CreateDestroy);
+ 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_ConnectionTable)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// The Test Runner calls this function once after all the Test Fixtures are run.
+LONGBOW_TEST_RUNNER_TEARDOWN(metis_ConnectionTable)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// ============================================
+
+LONGBOW_TEST_FIXTURE(CreateDestroy)
+{
+ LONGBOW_RUN_TEST_CASE(CreateDestroy, metisConnectionTable_Create_Destroy);
+ LONGBOW_RUN_TEST_CASE(CreateDestroy, metisConnectionTable_Add);
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(CreateDestroy)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(CreateDestroy)
+{
+ 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(CreateDestroy, metisConnectionTable_Create_Destroy)
+{
+ MetisConnectionTable *table = metisConnectionTable_Create();
+ metisConnectionTable_Destroy(&table);
+
+ assertTrue(parcMemory_Outstanding() == 0, "Got memory imbalance on create/destroy: %u", parcMemory_Outstanding());
+}
+
+LONGBOW_TEST_CASE(CreateDestroy, metisConnectionTable_Add)
+{
+ MetisConnectionTable *table = metisConnectionTable_Create();
+ MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true);
+ MockIoOperationsData *data = metisIoOperations_GetClosure(ops);
+
+ MetisConnection *conn = metisConnection_Create(ops);
+
+ assertTrue(parcHashCodeTable_Length(table->storageTableById) == 0,
+ "storageTableById not empty at start of test, length %zu",
+ parcHashCodeTable_Length(table->storageTableById));
+
+ assertTrue(parcHashCodeTable_Length(table->indexByAddressPair) == 0,
+ "indexByAddressPair not empty at start of test, length %zu",
+ parcHashCodeTable_Length(table->indexByAddressPair));
+
+ metisConnectionTable_Add(table, conn);
+
+ assertTrue(parcHashCodeTable_Length(table->storageTableById) == 1,
+ "Incorrect storage table size, expected %u got %zu",
+ 1,
+ parcHashCodeTable_Length(table->storageTableById));
+
+ assertTrue(parcHashCodeTable_Length(table->indexByAddressPair) == 1,
+ "Incorrect storage table size, expected %u got %zu",
+ 1,
+ parcHashCodeTable_Length(table->indexByAddressPair));
+ metisConnectionTable_Destroy(&table);
+
+ assertTrue(data->destroyCount == 1, "metisConnectionTable_Destroy did not call entry's destroyer");
+
+ mockIoOperationsData_Destroy(&ops);
+ assertTrue(parcMemory_Outstanding() == 0, "Got memory imbalance on create/destroy: %u", parcMemory_Outstanding());
+}
+
+// ============================================
+
+LONGBOW_TEST_FIXTURE(Global)
+{
+ LONGBOW_RUN_TEST_CASE(Global, metisConnectionTable_FindByAddressPair);
+ LONGBOW_RUN_TEST_CASE(Global, metisConnectionTable_FindById);
+ LONGBOW_RUN_TEST_CASE(Global, metisConnectionTable_Remove);
+ LONGBOW_RUN_TEST_CASE(Global, metisConnectionTable_RemoveById);
+
+// LONGBOW_RUN_TEST_CASE(Global, metisConnectionTable_GetEntries);
+}
+
+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, metisConnectionTable_FindByAddressPair)
+{
+ MetisConnectionTable *table = metisConnectionTable_Create();
+
+ // add the test set to the table
+ for (int i = 0; test_set[i].localAddr != 0; i++) {
+ test_set[i].ops = mockIoOperationsData_CreateSimple(test_set[i].localAddr, test_set[i].remoteAddr, test_set[i].id, true, true, true);
+ assertNotNull(test_set[i].ops, "Got null from testdata_CreateSimple index %d", i);
+ test_set[i].conn = metisConnection_Create(test_set[i].ops);
+ assertNotNull(test_set[i].conn, "Got null from metisConnection_Create index %d", i);
+ metisConnectionTable_Add(table, test_set[i].conn);
+ }
+
+
+ // now make sure we can find them all by their address pair
+ for (int i = 0; test_set[i].localAddr != 0; i++) {
+ const MetisAddressPair *pair = test_set[i].ops->getAddressPair(test_set[i].ops);
+ const MetisConnection *conn = metisConnectionTable_FindByAddressPair(table, pair);
+ assertTrue(conn == test_set[i].conn,
+ "id %u returned wrong pointer, expected %p got %p",
+ test_set[i].id,
+ (void *) test_set[i].conn,
+ (void *) conn);
+ }
+
+ // cleanup and verify destructions
+ metisConnectionTable_Destroy(&table);
+
+ for (int i = 0; test_set[i].localAddr != 0; i++) {
+ MockIoOperationsData *data = metisIoOperations_GetClosure(test_set[i].ops);
+ assertTrue(data->destroyCount == 1, "Did not destroy data element %d, count %u", i, data->destroyCount);
+ mockIoOperationsData_Destroy(&test_set[i].ops);
+ }
+}
+
+LONGBOW_TEST_CASE(Global, metisConnectionTable_FindById)
+{
+ MetisConnectionTable *table = metisConnectionTable_Create();
+
+ // add the test set to the table
+ for (int i = 0; test_set[i].localAddr != 0; i++) {
+ test_set[i].ops = mockIoOperationsData_CreateSimple(test_set[i].localAddr, test_set[i].remoteAddr, test_set[i].id, true, true, true);
+ assertNotNull(test_set[i].ops, "Got null from testdata_CreateSimple index %d", i);
+ test_set[i].conn = metisConnection_Create(test_set[i].ops);
+ assertNotNull(test_set[i].conn, "Got null from metisConnection_Create index %d", i);
+ metisConnectionTable_Add(table, test_set[i].conn);
+ }
+
+
+ // now make sure we can find them all by their id
+ for (int i = 0; test_set[i].localAddr != 0; i++) {
+ const MetisConnection *conn = metisConnectionTable_FindById(table, test_set[i].id);
+ assertTrue(conn == test_set[i].conn,
+ "id %u returned wrong pointer, expected %p got %p",
+ test_set[i].id,
+ (void *) test_set[i].conn,
+ (void *) conn);
+ }
+
+ // cleanup and verify destructions
+ metisConnectionTable_Destroy(&table);
+
+ for (int i = 0; test_set[i].localAddr != 0; i++) {
+ MockIoOperationsData *data = metisIoOperations_GetClosure(test_set[i].ops);
+ assertTrue(data->destroyCount == 1, "Did not destroy data element %d, count %u", i, data->destroyCount);
+ mockIoOperationsData_Destroy(&test_set[i].ops);
+ }
+}
+
+LONGBOW_TEST_CASE(Global, metisConnectionTable_Remove)
+{
+ MetisConnectionTable *table = metisConnectionTable_Create();
+
+ MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true);
+ MetisConnection *conn = metisConnection_Create(ops);
+ metisConnectionTable_Add(table, conn);
+
+
+ // Check preconditions
+ assertTrue(parcHashCodeTable_Length(table->storageTableById) == 1,
+ "storageTableById wrong size, expected %u got %zu",
+ 1,
+ parcHashCodeTable_Length(table->storageTableById));
+
+ assertTrue(parcHashCodeTable_Length(table->indexByAddressPair) == 1,
+ "indexByAddressPair wrong size, expected %u got %zu",
+ 1,
+ parcHashCodeTable_Length(table->indexByAddressPair));
+
+ // test the operation
+ metisConnectionTable_Remove(table, conn);
+
+ // check post conditions
+ assertTrue(parcHashCodeTable_Length(table->storageTableById) == 0,
+ "storageTableById wrong size, expected %u got %zu",
+ 0,
+ parcHashCodeTable_Length(table->storageTableById));
+
+ assertTrue(parcHashCodeTable_Length(table->indexByAddressPair) == 0,
+ "indexByAddressPair wrong size, expected %u got %zu",
+ 0,
+ parcHashCodeTable_Length(table->indexByAddressPair));
+
+ // cleanup
+ MockIoOperationsData *data = metisIoOperations_GetClosure(ops);
+ assertTrue(data->destroyCount == 1, "Remove did not destroy data, count %u", data->destroyCount);
+ mockIoOperationsData_Destroy(&ops);
+ metisConnectionTable_Destroy(&table);
+}
+
+LONGBOW_TEST_CASE(Global, metisConnectionTable_RemoveById)
+{
+ MetisConnectionTable *table = metisConnectionTable_Create();
+
+ unsigned connid = 3;
+
+ MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, connid, true, true, true);
+ MetisConnection *conn = metisConnection_Create(ops);
+ metisConnectionTable_Add(table, conn);
+
+ // Check preconditions
+ assertTrue(parcHashCodeTable_Length(table->storageTableById) == 1,
+ "storageTableById wrong size, expected %u got %zu",
+ 1,
+ parcHashCodeTable_Length(table->storageTableById));
+
+ assertTrue(parcHashCodeTable_Length(table->indexByAddressPair) == 1,
+ "indexByAddressPair wrong size, expected %u got %zu",
+ 1,
+ parcHashCodeTable_Length(table->indexByAddressPair));
+
+ // test the operation
+ metisConnectionTable_RemoveById(table, connid);
+
+
+ // check post conditions
+ assertTrue(parcHashCodeTable_Length(table->storageTableById) == 0,
+ "storageTableById wrong size, expected %u got %zu",
+ 0,
+ parcHashCodeTable_Length(table->storageTableById));
+
+ assertTrue(parcHashCodeTable_Length(table->indexByAddressPair) == 0,
+ "indexByAddressPair wrong size, expected %u got %zu",
+ 0,
+ parcHashCodeTable_Length(table->indexByAddressPair));
+
+ // cleanup
+ MockIoOperationsData *data = metisIoOperations_GetClosure(ops);
+ assertTrue(data->destroyCount == 1, "Remove did not destroy data, count %u", data->destroyCount);
+ mockIoOperationsData_Destroy(&ops);
+ metisConnectionTable_Destroy(&table);
+}
+
+LONGBOW_TEST_CASE(Global, metisConnectionTable_GetEntries)
+{
+ MetisConnectionTable *table = metisConnectionTable_Create();
+
+ size_t count = 0;
+ // add the test set to the table
+ for (int i = 0; test_set[i].localAddr != 0; i++) {
+ test_set[i].ops = mockIoOperationsData_CreateSimple(test_set[i].localAddr, test_set[i].remoteAddr, test_set[i].id, true, true, true);
+ assertNotNull(test_set[i].ops, "Got null from testdata_CreateSimple index %d", i);
+ test_set[i].conn = metisConnection_Create(test_set[i].ops);
+ assertNotNull(test_set[i].conn, "Got null from metisConnection_Create index %d", i);
+ metisConnectionTable_Add(table, test_set[i].conn);
+ count++;
+ }
+
+ MetisConnectionList *list = metisConnectionTable_GetEntries(table);
+ assertTrue(metisConnectionList_Length(list) == count, "List wrong size, expected %zu got %zu", count, metisConnectionList_Length(list));
+
+ // now verify each entry. The entries are not necessarily in the same order
+ for (int i = 0; i < count; i++) {
+ MetisConnection *test = metisConnectionList_Get(list, i);
+ const MetisAddressPair *test_pair = metisConnection_GetAddressPair(test);
+ const MetisAddressPair *truth_pair = test_set[i].ops->getAddressPair(test_set[i].ops);
+ assertTrue(metisAddressPair_Equals(test_pair, truth_pair), "Address pairs not equal, index %d", i);
+ }
+
+ metisConnectionList_Destroy(&list);
+ for (int i = 0; test_set[i].localAddr != 0; i++) {
+ MockIoOperationsData *data = metisIoOperations_GetClosure(test_set[i].ops);
+ assertTrue(data->destroyCount == 1, "Did not destroy data element %d, count %u", i, data->destroyCount);
+ mockIoOperationsData_Destroy(&test_set[i].ops);
+ }
+}
+
+// ==============================================================================
+
+LONGBOW_TEST_FIXTURE(Local)
+{
+ LONGBOW_RUN_TEST_CASE(Local, metisConnectionTable_AddressPairEquals_IsEqual);
+ LONGBOW_RUN_TEST_CASE(Local, metisConnectionTable_AddressPairEquals_IsNotEqual);
+ LONGBOW_RUN_TEST_CASE(Local, metisConnectionTable_AddressPairHashCode);
+ LONGBOW_RUN_TEST_CASE(Local, metisConnectionTable_ConnectionDestroyer);
+ LONGBOW_RUN_TEST_CASE(Local, metisConnectionTable_ConnectionIdDestroyer);
+ LONGBOW_RUN_TEST_CASE(Local, metisConnectionTable_ConnectionIdEquals_IsEqual);
+ LONGBOW_RUN_TEST_CASE(Local, metisConnectionTable_ConnectionIdEquals_IsNotEqual);
+ LONGBOW_RUN_TEST_CASE(Local, metisConnectionTable_ConnectionIdHashCode);
+}
+
+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, metisConnectionTable_AddressPairEquals_IsEqual)
+{
+ CPIAddress *a1 = cpiAddress_CreateFromInterface(1);
+ CPIAddress *a2 = cpiAddress_CreateFromInterface(2);
+ MetisAddressPair *pair_a = metisAddressPair_Create(a1, a2);
+
+ CPIAddress *b1 = cpiAddress_CreateFromInterface(1);
+ CPIAddress *b2 = cpiAddress_CreateFromInterface(2);
+ MetisAddressPair *pair_b = metisAddressPair_Create(b1, b2);
+
+ bool success = metisConnectionTable_AddressPairEquals((void *) pair_a, (void *) pair_b);
+ assertTrue(success, "Equal address pairs do not compare");
+
+ metisAddressPair_Release(&pair_a);
+ metisAddressPair_Release(&pair_b);
+ cpiAddress_Destroy(&a1);
+ cpiAddress_Destroy(&a2);
+ cpiAddress_Destroy(&b1);
+ cpiAddress_Destroy(&b2);
+}
+
+LONGBOW_TEST_CASE(Local, metisConnectionTable_AddressPairEquals_IsNotEqual)
+{
+ CPIAddress *a1 = cpiAddress_CreateFromInterface(1);
+ CPIAddress *a2 = cpiAddress_CreateFromInterface(2);
+ MetisAddressPair *pair_a = metisAddressPair_Create(a1, a2);
+
+ CPIAddress *b1 = cpiAddress_CreateFromInterface(1);
+ CPIAddress *b2 = cpiAddress_CreateFromInterface(2);
+ MetisAddressPair *pair_b = metisAddressPair_Create(b2, b1);
+
+ bool success = metisConnectionTable_AddressPairEquals((void *) pair_a, (void *) pair_b);
+ assertFalse(success, "Unequal address pairs compare as equal");
+
+ metisAddressPair_Release(&pair_a);
+ metisAddressPair_Release(&pair_b);
+ cpiAddress_Destroy(&a1);
+ cpiAddress_Destroy(&a2);
+ cpiAddress_Destroy(&b1);
+ cpiAddress_Destroy(&b2);
+}
+
+LONGBOW_TEST_CASE(Local, metisConnectionTable_AddressPairHashCode)
+{
+ CPIAddress *a1 = cpiAddress_CreateFromInterface(1);
+ CPIAddress *a2 = cpiAddress_CreateFromInterface(2);
+ MetisAddressPair *pair_a = metisAddressPair_Create(a1, a2);
+
+ HashCodeType truth = metisAddressPair_HashCode(pair_a);
+ HashCodeType hash = metisConnectionTable_AddressPairHashCode((void *) pair_a);
+
+ assertTrue(truth == hash, "Incorrect hash code, expected %04"PRIX64 "got %04"PRIX64, truth, hash);
+
+ metisAddressPair_Release(&pair_a);
+ cpiAddress_Destroy(&a1);
+ cpiAddress_Destroy(&a2);
+}
+
+LONGBOW_TEST_CASE(Local, metisConnectionTable_ConnectionDestroyer)
+{
+ MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true);
+ MetisConnection *conn = metisConnection_Create(ops);
+ MockIoOperationsData *data = metisIoOperations_GetClosure(ops);
+
+ assertTrue(data->destroyCount == 0, "testdata_CreateSimple did not zero destroyCount");
+
+ metisConnectionTable_ConnectionDestroyer((void **) &conn);
+
+ assertTrue(data->destroyCount == 1, "metisConnectionTable_ConnectionDestroyer did not call destroy on MetisIoOperations");
+ mockIoOperationsData_Destroy(&ops);
+
+ assertTrue(parcMemory_Outstanding() == 0, "Memory imbalance, expected 0 got %u", parcMemory_Outstanding());
+}
+
+LONGBOW_TEST_CASE(Local, metisConnectionTable_ConnectionIdDestroyer)
+{
+ unsigned *aptr = parcMemory_Allocate(sizeof(unsigned));
+ assertNotNull(aptr, "parcMemory_Allocate(%zu) returned NULL", sizeof(unsigned));
+ metisConnectionTable_ConnectionIdDestroyer((void **) &aptr);
+ assertNull(aptr, "destroyer did not null pointer");
+ assertTrue(parcMemory_Outstanding() == 0, "Memory imbalance, expected 0 got %u", parcMemory_Outstanding());
+}
+
+LONGBOW_TEST_CASE(Local, metisConnectionTable_ConnectionIdEquals_IsEqual)
+{
+ unsigned a = 0x01020304;
+ unsigned b = 0x01020304;
+
+ bool success = metisConnectionTable_ConnectionIdEquals(&a, &b);
+ assertTrue(success, "equal unsigned pointers do not compare");
+}
+
+LONGBOW_TEST_CASE(Local, metisConnectionTable_ConnectionIdEquals_IsNotEqual)
+{
+ unsigned a = 0x01020304;
+ unsigned b = 0x01020305;
+
+ bool success = metisConnectionTable_ConnectionIdEquals(&a, &b);
+ assertFalse(success, "Unequal unsigned pointers compare as equal");
+}
+
+LONGBOW_TEST_CASE(Local, metisConnectionTable_ConnectionIdHashCode)
+{
+ unsigned a = 0x01020304;
+
+ HashCodeType truth = parcHash32_Int32(a);
+ HashCodeType hash = metisConnectionTable_ConnectionIdHashCode(&a);
+
+ assertTrue(truth == hash, "Incorrect hash code, expected %04"PRIX64 "got %04"PRIX64, truth, hash);
+}
+
+int
+main(int argc, char *argv[])
+{
+ LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_ConnectionTable);
+ int exitStatus = longBowMain(argc, argv, testRunner, NULL);
+ longBowTestRunner_Destroy(&testRunner);
+ exit(exitStatus);
+}
diff --git a/metis/ccnx/forwarder/metis/core/test/test_metis_Dispatcher.c b/metis/ccnx/forwarder/metis/core/test/test_metis_Dispatcher.c
new file mode 100644
index 00000000..3845d0d5
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/test/test_metis_Dispatcher.c
@@ -0,0 +1,733 @@
+/*
+ * 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_Dispatcher.c"
+#include <LongBow/unit-test.h>
+#include <parc/algol/parc_SafeMemory.h>
+#include <parc/algol/parc_EventQueue.h>
+#include <parc/logging/parc_LogReporterTextStdout.h>
+
+#include <ccnx/forwarder/metis/core/metis_Forwarder.h>
+
+LONGBOW_TEST_RUNNER(metis_Dispatcher)
+{
+ // 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.
+#ifdef PARC_MEMORY
+ // The CreateDestroy fixture diagnoses issues with the debug memory allocator,
+ // which will fail if that allocator is not in use.
+ LONGBOW_RUN_TEST_FIXTURE(CreateDestroy);
+#endif
+ LONGBOW_RUN_TEST_FIXTURE(Global);
+ LONGBOW_RUN_TEST_FIXTURE(StreamBufferConnect);
+ LONGBOW_RUN_TEST_FIXTURE(Local);
+}
+
+// The Test Runner calls this function once before any Test Fixtures are run.
+LONGBOW_TEST_RUNNER_SETUP(metis_Dispatcher)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// The Test Runner calls this function once after all the Test Fixtures are run.
+LONGBOW_TEST_RUNNER_TEARDOWN(metis_Dispatcher)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// =================================================================================
+LONGBOW_TEST_FIXTURE(CreateDestroy)
+{
+ LONGBOW_RUN_TEST_CASE(CreateDestroy, metisDispatcher_Create_Destroy);
+ LONGBOW_RUN_TEST_CASE(CreateDestroy, metisDispatcher_Memory);
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(CreateDestroy)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(CreateDestroy)
+{
+ 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(CreateDestroy, metisDispatcher_Create_Destroy)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisDispatcher *dispatcher = metisDispatcher_Create(logger);
+ metisDispatcher_Destroy(&dispatcher);
+ metisLogger_Release(&logger);
+ assertTrue(parcSafeMemory_ReportAllocation(STDOUT_FILENO) == 0, "Got memory imbalance on create/destroy: %u", parcMemory_Outstanding());
+}
+
+/**
+ * Ensure that the dispatcher is using parc memory inside event scheduler
+ */
+LONGBOW_TEST_CASE(CreateDestroy, metisDispatcher_Memory)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisDispatcher *dispatcher = metisDispatcher_Create(logger);
+ size_t baseline = parcMemory_Outstanding();
+
+ PARCEventBuffer *buffer = parcEventBuffer_Create();
+
+ assertTrue(parcMemory_Outstanding() > baseline,
+ "parcEventBuffer_Create() did not increase parcMemory_Outstanding: baseline %zu now %u",
+ baseline,
+ parcMemory_Outstanding());
+
+ parcEventBuffer_Destroy(&buffer);
+
+ assertTrue(parcMemory_Outstanding() == baseline,
+ "parcEventBuffer_Destroy() did reduce to baseline: baseline %zu now %u",
+ baseline,
+ parcMemory_Outstanding());
+
+ metisDispatcher_Destroy(&dispatcher);
+ metisLogger_Release(&logger);
+
+ assertTrue(parcSafeMemory_ReportAllocation(STDOUT_FILENO) == 0, "Got memory imbalance on create/destroy: %u", parcMemory_Outstanding());
+}
+
+// =================================================================================
+
+LONGBOW_TEST_FIXTURE(Global)
+{
+ LONGBOW_RUN_TEST_CASE(Global, metisDispatcher_CreateTimer_Oneshot);
+ LONGBOW_RUN_TEST_CASE(Global, metisDispatcher_CreateTimer_Periodic);
+ LONGBOW_RUN_TEST_CASE(Global, metisDispatcher_StopTimer);
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(Global)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisDispatcher *dispatcher = metisDispatcher_Create(logger);
+ metisLogger_Release(&logger);
+
+ longBowTestCase_SetClipBoardData(testCase, dispatcher);
+
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(Global)
+{
+ MetisDispatcher *dispatcher = longBowTestCase_GetClipBoardData(testCase);
+ metisDispatcher_Destroy(&dispatcher);
+
+ uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO);
+ if (outstandingAllocations != 0) {
+ printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations);
+ return LONGBOW_STATUS_MEMORYLEAK;
+ }
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+static void
+timerCallback(int fd, PARCEventType which_event, void *user_data)
+{
+ assertTrue(which_event & PARCEventType_Timeout, "Event incorrect, expecting %X set, got %X", PARCEventType_Timeout, which_event);
+ (*(unsigned *) user_data)++;
+}
+
+LONGBOW_TEST_CASE(Global, metisDispatcher_CreateTimer_Oneshot)
+{
+ MetisDispatcher *dispatcher = longBowTestCase_GetClipBoardData(testCase);
+
+ unsigned timerCallbackCount = 0;
+ PARCEventTimer *event = metisDispatcher_CreateTimer(dispatcher, false, timerCallback, &timerCallbackCount);
+ assertNotNull(event, "Got null event from metisDispatcher_CreateTimer");
+
+ // 10 msec
+ struct timeval timeout = { 0, 10000 };
+ metisDispatcher_StartTimer(dispatcher, event, &timeout);
+
+ // run for 250 msec;
+ struct timeval runtime = { 0, 250000 };
+ metisDispatcher_RunDuration(dispatcher, &runtime);
+
+ assertTrue(timerCallbackCount == 1, "Incorrect timer callbacks, expected %u got %u", 1, timerCallbackCount);
+ metisDispatcher_DestroyTimerEvent(dispatcher, &event);
+}
+
+LONGBOW_TEST_CASE(Global, metisDispatcher_CreateTimer_Periodic)
+{
+#ifdef __ARMEL__
+ testUnimplemented("Test not implemented on ARMEL, timers too inaccurate");
+#else
+ MetisDispatcher *dispatcher = longBowTestCase_GetClipBoardData(testCase);
+
+ unsigned timerCallbackCount = 0;
+ PARCEventTimer *event = metisDispatcher_CreateTimer(dispatcher, true, timerCallback, &timerCallbackCount);
+ assertNotNull(event, "Got null event from metisDispatcher_CreateTimer");
+
+ // 10 msec
+ struct timeval timeout = { 0, 10000 };
+ metisDispatcher_StartTimer(dispatcher, event, &timeout);
+
+ // run for 255 msec. Use an offset time to run so its clear what we should be after
+ // the 25th event and before the 26th event.
+
+ struct timeval runtime = { 0, 255000 };
+ metisDispatcher_RunDuration(dispatcher, &runtime);
+
+ // make sure it runs at least twice (is periodic). Could run as many as 25.
+ assertTrue(timerCallbackCount >= 2, "Incorrect timer callbacks, expected at least %u got %u", 2, timerCallbackCount);
+ metisDispatcher_DestroyTimerEvent(dispatcher, &event);
+#endif
+}
+
+LONGBOW_TEST_CASE(Global, metisDispatcher_StopTimer)
+{
+ MetisDispatcher *dispatcher = longBowTestCase_GetClipBoardData(testCase);
+
+ unsigned timerCallbackCount = 0;
+ PARCEventTimer *event = metisDispatcher_CreateTimer(dispatcher, true, timerCallback, &timerCallbackCount);
+ assertNotNull(event, "Got null event from metisDispatcher_CreateTimer");
+
+ // 10 msec
+ struct timeval timeout = { 0, 10000 };
+ metisDispatcher_StartTimer(dispatcher, event, &timeout);
+
+ // run for 55 msec (5 events), then stop the timer and run another 55 msec
+
+ struct timeval runtime = { 0, 55000 };
+ metisDispatcher_RunDuration(dispatcher, &runtime);
+
+ metisDispatcher_StopTimer(dispatcher, event);
+ metisDispatcher_RunDuration(dispatcher, &runtime);
+
+ // not sure how many times it will fire, but it should not fire more than 5 times
+ assertTrue(timerCallbackCount <= 5, "Incorrect timer callbacks, expected no more than %u got %u", 5, timerCallbackCount);
+ metisDispatcher_DestroyTimerEvent(dispatcher, &event);
+}
+
+// =================================================================================
+
+typedef struct open_connection_state {
+// MetisForwarder * metis;
+ MetisDispatcher *dispatcher;
+
+ CPIAddress *a;
+ CPIAddress *b;
+ MetisAddressPair *pair;
+ size_t baselineMemoryBalance;
+
+ // if serverSocket > 0, then its allocated
+ int serverSocket;
+ struct sockaddr *serverAddr;
+ socklen_t serverAddrLength;
+} OpenConnectionState;
+
+static void
+listenToInet(OpenConnectionState *ocs)
+{
+ struct sockaddr_in server;
+ memset(&server, 0, sizeof(server));
+ server.sin_family = PF_INET;
+ server.sin_addr.s_addr = INADDR_ANY;
+ server.sin_port = INPORT_ANY;
+
+ 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(server));
+ assertFalse(failure, "error on bind: (%d) %s", errno, strerror(errno));
+
+ failure = listen(fd, 16);
+ assertFalse(failure, "error on listen: (%d) %s", errno, strerror(errno));
+
+ ocs->serverSocket = fd;
+ ocs->serverAddrLength = sizeof(struct sockaddr_in);
+ ocs->serverAddr = parcMemory_Allocate(ocs->serverAddrLength);
+ assertNotNull(ocs->serverAddr, "parcMemory_Allocate(%u) returned NULL", ocs->serverAddrLength);
+
+ failure = getsockname(fd, ocs->serverAddr, &ocs->serverAddrLength);
+ assertFalse(failure, "error on getsockname: (%d) %s", errno, strerror(errno));
+}
+
+static void
+listenToInet6(OpenConnectionState *ocs)
+{
+ struct sockaddr_in6 server;
+ memset(&server, 0, sizeof(server));
+ server.sin6_family = PF_INET6;
+ server.sin6_addr = in6addr_any;
+ server.sin6_port = INPORT_ANY;
+
+ int fd = socket(PF_INET6, 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(server));
+ assertFalse(failure, "error on bind: (%d) %s", errno, strerror(errno));
+
+ failure = listen(fd, 16);
+ assertFalse(failure, "error on listen: (%d) %s", errno, strerror(errno));
+
+ ocs->serverSocket = fd;
+ ocs->serverAddrLength = sizeof(struct sockaddr_in6);
+ ocs->serverAddr = parcMemory_Allocate(ocs->serverAddrLength);
+ assertNotNull(ocs->serverAddr, "parcMemory_Allocate(%u) returned NULL", ocs->serverAddrLength);
+
+ failure = getsockname(fd, ocs->serverAddr, &ocs->serverAddrLength);
+ assertFalse(failure, "error on getsockname: (%d) %s", errno, strerror(errno));
+}
+
+LONGBOW_TEST_FIXTURE(StreamBufferConnect)
+{
+ // --------
+ // these two tests will cause assertions
+ LONGBOW_RUN_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_Invalid);
+ LONGBOW_RUN_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_DifferentTypes);
+ // --------
+
+ LONGBOW_RUN_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_INET);
+ LONGBOW_RUN_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_INET6);
+
+
+ LONGBOW_RUN_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_INET_Success);
+ LONGBOW_RUN_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_INET_Failure);
+
+ LONGBOW_RUN_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_INET6_Success);
+ LONGBOW_RUN_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_INET6_Failure);
+
+ LONGBOW_RUN_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferBindAndConnect_BindFail);
+ LONGBOW_RUN_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferBindAndConnect_BindOk_ConnectFail);
+ LONGBOW_RUN_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferBindAndConnect_BindOk_ConnectOk);
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(StreamBufferConnect)
+{
+ size_t baselineMemoryBalance = parcMemory_Outstanding();
+
+ OpenConnectionState *ocs = parcMemory_AllocateAndClear(sizeof(OpenConnectionState));
+ assertNotNull(ocs, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(OpenConnectionState));
+ memset(ocs, 0, sizeof(OpenConnectionState));
+
+ longBowTestCase_SetClipBoardData(testCase, ocs);
+
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ ocs->dispatcher = metisDispatcher_Create(logger);
+ metisLogger_Release(&logger);
+ ocs->baselineMemoryBalance = baselineMemoryBalance;
+
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(StreamBufferConnect)
+{
+ OpenConnectionState *ocs = longBowTestCase_GetClipBoardData(testCase);
+ metisDispatcher_Destroy(&ocs->dispatcher);
+
+ if (ocs->a) {
+ cpiAddress_Destroy(&ocs->a);
+ }
+
+ if (ocs->b) {
+ cpiAddress_Destroy(&ocs->b);
+ }
+
+ if (ocs->pair) {
+ metisAddressPair_Release(&ocs->pair);
+ }
+
+ if (ocs->serverSocket > 0) {
+ close(ocs->serverSocket);
+ }
+
+ if (ocs->serverAddr) {
+ parcMemory_Deallocate((void **) &(ocs->serverAddr));
+ }
+
+ size_t baselineMemoryBalance = ocs->baselineMemoryBalance;
+ parcMemory_Deallocate((void **) &ocs);
+
+ if (parcMemory_Outstanding() != baselineMemoryBalance) {
+ parcSafeMemory_ReportAllocation(STDOUT_FILENO);
+ assertTrue(parcMemory_Outstanding() == ocs->baselineMemoryBalance,
+ "memory imbalance in %s: exepcted %zu got %u",
+ longBowTestCase_GetName(testCase),
+ baselineMemoryBalance,
+ parcMemory_Outstanding());
+ }
+
+
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+/**
+ * Tests invalid protocol family
+ */
+LONGBOW_TEST_CASE_EXPECTS(StreamBufferConnect, metisDispatcher_StreamBufferConnect_Invalid, .event = &LongBowTrapIllegalValue)
+{
+ OpenConnectionState *ocs = longBowTestCase_GetClipBoardData(testCase);
+ ocs->a = cpiAddress_CreateFromInterface(1);
+ ocs->b = cpiAddress_CreateFromInterface(2);
+ ocs->pair = metisAddressPair_Create(ocs->a, ocs->b);
+
+ // this will throw a trap
+ fprintf(stderr, "\n\nTHIS IS NOT AN ERROR, EXPECTED TRAP: Assertion Illegal\n\n");
+ metisDispatcher_StreamBufferConnect(ocs->dispatcher, ocs->pair);
+}
+
+/**
+ * Tests different protocol families
+ */
+LONGBOW_TEST_CASE_EXPECTS(StreamBufferConnect, metisDispatcher_StreamBufferConnect_DifferentTypes, .event = &LongBowAssertEvent)
+{
+ OpenConnectionState *ocs = longBowTestCase_GetClipBoardData(testCase);
+ ocs->a = cpiAddress_CreateFromInet(&((struct sockaddr_in) { .sin_family = PF_INET }));
+ ocs->b = cpiAddress_CreateFromInet6(&((struct sockaddr_in6) { .sin6_family = PF_INET6 }));
+ ocs->pair = metisAddressPair_Create(ocs->a, ocs->b);
+
+ // this will throw a trap
+ fprintf(stderr, "\n\nTHIS IS NOT AN ERROR, EXPECTED ASSERTION: Assertion cpiAddress_GetType...\n\n");
+ metisDispatcher_StreamBufferConnect(ocs->dispatcher, ocs->pair);
+}
+
+/**
+ * Use a port that is already in use
+ */
+LONGBOW_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferBindAndConnect_BindFail)
+{
+ OpenConnectionState *ocs = longBowTestCase_GetClipBoardData(testCase);
+ listenToInet(ocs);
+
+ PARCEventQueue *buffer = parcEventQueue_Create(ocs->dispatcher->Base, -1, PARCEventQueueOption_CloseOnFree);
+
+ // use the server address for our bind address. will cause a failure.
+ bool success = metisDispatcher_StreamBufferBindAndConnect(ocs->dispatcher, buffer,
+ ocs->serverAddr, ocs->serverAddrLength,
+ ocs->serverAddr, ocs->serverAddrLength);
+ parcEventQueue_Destroy(&buffer);
+ assertFalse(success, "metisDispatcher_StreamBufferBindAndConnect succedded with bind to in use address");
+}
+
+/**
+ * Good bind address, but bad connect to address
+ */
+LONGBOW_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferBindAndConnect_BindOk_ConnectFail)
+{
+ OpenConnectionState *ocs = longBowTestCase_GetClipBoardData(testCase);
+
+ struct sockaddr_in goodAddress;
+ memset(&goodAddress, 0, sizeof(goodAddress));
+ goodAddress.sin_family = PF_INET;
+ goodAddress.sin_addr.s_addr = INADDR_ANY;
+ goodAddress.sin_port = INPORT_ANY;
+
+ struct sockaddr_in badAddress;
+ memset(&badAddress, 0xFF, sizeof(badAddress));
+ badAddress.sin_family = PF_INET;
+
+ PARCEventQueue *buffer = parcEventQueue_Create(ocs->dispatcher->Base, -1, PARCEventQueueOption_CloseOnFree);
+
+ // use the server address for our bind address. will cause a failure.
+ bool success = metisDispatcher_StreamBufferBindAndConnect(ocs->dispatcher, buffer,
+ (struct sockaddr *) &goodAddress, sizeof(goodAddress),
+ (struct sockaddr *) &badAddress, sizeof(badAddress));
+
+ parcEventQueue_Destroy(&buffer);
+ assertFalse(success, "metisDispatcher_StreamBufferBindAndConnect succedded with bind to in use address");
+}
+
+/**
+ * Everything good, should succeed!
+ */
+LONGBOW_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferBindAndConnect_BindOk_ConnectOk)
+{
+ OpenConnectionState *ocs = longBowTestCase_GetClipBoardData(testCase);
+ listenToInet(ocs);
+
+ struct sockaddr_in goodAddress;
+ memset(&goodAddress, 0, sizeof(goodAddress));
+ goodAddress.sin_family = PF_INET;
+ goodAddress.sin_addr.s_addr = INADDR_ANY;
+ goodAddress.sin_port = INPORT_ANY;
+
+ PARCEventQueue *buffer = parcEventQueue_Create(ocs->dispatcher->Base, -1, PARCEventQueueOption_CloseOnFree);
+
+ // use the server address for our bind address. will cause a failure.
+ bool success = metisDispatcher_StreamBufferBindAndConnect(ocs->dispatcher, buffer,
+ (struct sockaddr *) &goodAddress, sizeof(goodAddress),
+ ocs->serverAddr, ocs->serverAddrLength);
+
+ parcEventQueue_Destroy(&buffer);
+ assertTrue(success, "metisDispatcher_StreamBufferBindAndConnect did not succeed with good addresses");
+}
+
+LONGBOW_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_INET_Success)
+{
+ OpenConnectionState *ocs = longBowTestCase_GetClipBoardData(testCase);
+ listenToInet(ocs);
+
+ uint16_t localPort = 9698;
+ printf("local port = %u\n", localPort);
+
+ // Connection "from" will use localPort as the local port number
+ struct sockaddr_in goodLocalAddress;
+ memset(&goodLocalAddress, 0, sizeof(goodLocalAddress));
+ goodLocalAddress.sin_family = PF_INET;
+ goodLocalAddress.sin_addr.s_addr = INADDR_ANY;
+ goodLocalAddress.sin_port = htons(localPort);
+
+ ocs->a = cpiAddress_CreateFromInet(&goodLocalAddress);
+
+ // ocs->serverAddr will have "0.0.0.0" as the address, so need to create
+ // something to 127.0.0.1
+ struct sockaddr_in goodRemoteAddress;
+ memset(&goodRemoteAddress, 0, sizeof(goodRemoteAddress));
+ goodRemoteAddress.sin_family = PF_INET;
+ goodRemoteAddress.sin_port = ((struct sockaddr_in *) ocs->serverAddr)->sin_port;
+ inet_pton(AF_INET, "127.0.0.1", &(goodRemoteAddress.sin_addr));
+
+ ocs->b = cpiAddress_CreateFromInet(&goodRemoteAddress);
+
+ PARCEventQueue *result = metisDispatcher_StreamBufferConnect_INET(ocs->dispatcher, ocs->a, ocs->b);
+
+ assertNotNull(result, "result buffer should be non-null for good local bind address 0.0.0.0 port %u", localPort) {
+ int res;
+ res = system("netstat -an -p tcp");
+ assertTrue(res != -1, "Error on system call");
+ res = system("ps -el");
+ assertTrue(res != -1, "Error on system call");
+ }
+
+ // turn the crank a few times, then accept and make sure the bind address is correct
+ metisDispatcher_RunDuration(ocs->dispatcher, &((struct timeval) { 0, 1000 }));
+
+ struct sockaddr_in clientAddr;
+ socklen_t clientAddrLen = sizeof(struct sockaddr_in);
+ int clientfd = accept(ocs->serverSocket, (struct sockaddr *) &clientAddr, &clientAddrLen);
+ assertFalse(clientfd < 0, "Error on accept: (%d) %s", errno, strerror(errno));
+
+ assertTrue(clientAddr.sin_port == goodLocalAddress.sin_port,
+ "Ports do not match, expecting %u got %u",
+ htons(goodLocalAddress.sin_port),
+ htons(clientAddr.sin_port));
+
+ close(clientfd);
+ metisDispatcher_RunCount(ocs->dispatcher, 1);
+ metisStreamBuffer_Destroy(&result);
+ metisDispatcher_RunCount(ocs->dispatcher, 1);
+}
+
+/**
+ * Pass in a bad local address for the bind, will cause failure.
+ * should receive NULL back from call
+ */
+LONGBOW_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_INET_Failure)
+{
+ // This test only works on OSX, as linux will accept the 0xFFFFFFF address as localhost
+#if !defined(__APPLE__)
+ testUnimplemented("Platform not supported");
+#else
+ OpenConnectionState *ocs = longBowTestCase_GetClipBoardData(testCase);
+ listenToInet(ocs);
+
+ struct sockaddr_in badAddress;
+ memset(&badAddress, 0xFF, sizeof(badAddress));
+ badAddress.sin_family = PF_INET;
+
+ ocs->a = cpiAddress_CreateFromInet(&badAddress);
+ ocs->b = cpiAddress_CreateFromInet((struct sockaddr_in *) ocs->serverAddr);
+
+ // use the server address for our bind address. will cause a failure.
+ PARCEventQueue *result = metisDispatcher_StreamBufferConnect_INET(ocs->dispatcher, ocs->a, ocs->b);
+
+ assertNull(result, "result buffer should be null for bad local address");
+#endif
+}
+
+LONGBOW_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_INET6_Success)
+{
+ OpenConnectionState *ocs = longBowTestCase_GetClipBoardData(testCase);
+ listenToInet6(ocs);
+
+ struct sockaddr_in6 goodLocalAddress;
+ memset(&goodLocalAddress, 0, sizeof(goodLocalAddress));
+ goodLocalAddress.sin6_family = PF_INET6;
+ goodLocalAddress.sin6_addr = in6addr_any;
+ goodLocalAddress.sin6_port = INPORT_ANY;
+
+ ocs->a = cpiAddress_CreateFromInet6(&goodLocalAddress);
+
+ // ocs->serverAddr will have "0.0.0.0" as the address, so need to create
+ // something to 127.0.0.1
+ struct sockaddr_in6 goodRemoteAddress;
+ memset(&goodRemoteAddress, 0, sizeof(goodRemoteAddress));
+ goodRemoteAddress.sin6_family = PF_INET6;
+ goodRemoteAddress.sin6_port = ((struct sockaddr_in6 *) ocs->serverAddr)->sin6_port;
+ inet_pton(AF_INET6, "::1", &(goodRemoteAddress.sin6_addr));
+
+ ocs->b = cpiAddress_CreateFromInet6(&goodRemoteAddress);
+
+ PARCEventQueue *result = metisDispatcher_StreamBufferConnect_INET6(ocs->dispatcher, ocs->a, ocs->b);
+
+ assertNotNull(result, "result buffer should be non-null for good local address");
+
+
+ metisStreamBuffer_Destroy(&result);
+}
+
+/**
+ * Pass in a bad local address for the bind, will cause failure.
+ * should receive NULL back from call
+ */
+LONGBOW_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_INET6_Failure)
+{
+ OpenConnectionState *ocs = longBowTestCase_GetClipBoardData(testCase);
+ listenToInet6(ocs);
+
+ struct sockaddr_in6 badAddress;
+ memset(&badAddress, 0xFF, sizeof(badAddress));
+ badAddress.sin6_family = PF_INET6;
+
+ ocs->a = cpiAddress_CreateFromInet6(&badAddress);
+ ocs->b = cpiAddress_CreateFromInet6((struct sockaddr_in6 *) ocs->serverAddr);
+
+ // use the server address for our bind address. will cause a failure.
+ PARCEventQueue *result = metisDispatcher_StreamBufferConnect_INET6(ocs->dispatcher, ocs->a, ocs->b);
+
+ assertNull(result, "result buffer should be null for bad local address");
+}
+
+LONGBOW_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_INET)
+{
+ OpenConnectionState *ocs = longBowTestCase_GetClipBoardData(testCase);
+ listenToInet(ocs);
+
+ struct sockaddr_in goodLocalAddress;
+ memset(&goodLocalAddress, 0, sizeof(goodLocalAddress));
+ goodLocalAddress.sin_family = PF_INET;
+ goodLocalAddress.sin_addr.s_addr = INADDR_ANY;
+ goodLocalAddress.sin_port = INPORT_ANY;
+
+ ocs->a = cpiAddress_CreateFromInet(&goodLocalAddress);
+
+ // ocs->serverAddr will have "0.0.0.0" as the address, so need to create
+ // something to 127.0.0.1
+ struct sockaddr_in goodRemoteAddress;
+ memset(&goodRemoteAddress, 0, sizeof(goodRemoteAddress));
+ goodRemoteAddress.sin_family = PF_INET;
+ goodRemoteAddress.sin_port = ((struct sockaddr_in *) ocs->serverAddr)->sin_port;
+ inet_pton(AF_INET, "127.0.0.1", &(goodRemoteAddress.sin_addr));
+
+ ocs->b = cpiAddress_CreateFromInet(&goodRemoteAddress);
+
+ MetisAddressPair *pair = metisAddressPair_Create(ocs->a, ocs->b);
+ PARCEventQueue *result = metisDispatcher_StreamBufferConnect(ocs->dispatcher, pair);
+ metisAddressPair_Release(&pair);
+ assertNotNull(result, "result buffer should be non-null for good local address");
+ metisStreamBuffer_Destroy(&result);
+}
+
+LONGBOW_TEST_CASE(StreamBufferConnect, metisDispatcher_StreamBufferConnect_INET6)
+{
+ OpenConnectionState *ocs = longBowTestCase_GetClipBoardData(testCase);
+ listenToInet6(ocs);
+
+ struct sockaddr_in6 goodLocalAddress;
+ memset(&goodLocalAddress, 0, sizeof(goodLocalAddress));
+ goodLocalAddress.sin6_family = PF_INET6;
+ goodLocalAddress.sin6_addr = in6addr_any;
+ goodLocalAddress.sin6_port = INPORT_ANY;
+
+ ocs->a = cpiAddress_CreateFromInet6(&goodLocalAddress);
+
+ // ocs->serverAddr will have "0.0.0.0" as the address, so need to create
+ // something to 127.0.0.1
+ struct sockaddr_in6 goodRemoteAddress;
+ memset(&goodRemoteAddress, 0, sizeof(goodRemoteAddress));
+ goodRemoteAddress.sin6_family = PF_INET6;
+ goodRemoteAddress.sin6_port = ((struct sockaddr_in6 *) ocs->serverAddr)->sin6_port;
+ inet_pton(AF_INET6, "::1", &(goodRemoteAddress.sin6_addr));
+
+ ocs->b = cpiAddress_CreateFromInet6(&goodRemoteAddress);
+
+ MetisAddressPair *pair = metisAddressPair_Create(ocs->a, ocs->b);
+ PARCEventQueue *result = metisDispatcher_StreamBufferConnect(ocs->dispatcher, pair);
+ metisAddressPair_Release(&pair);
+
+ assertNotNull(result, "result buffer should be non-null for good local address");
+
+ metisStreamBuffer_Destroy(&result);
+}
+
+// =================================================================================
+
+LONGBOW_TEST_FIXTURE(Local)
+{
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(Local)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisDispatcher *dispatcher = metisDispatcher_Create(logger);
+ metisLogger_Release(&logger);
+ longBowTestCase_SetClipBoardData(testCase, dispatcher);
+
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(Local)
+{
+ MetisDispatcher *dispatcher = longBowTestCase_GetClipBoardData(testCase);
+ metisDispatcher_Destroy(&dispatcher);
+
+ 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_Dispatcher);
+ int exitStatus = longBowMain(argc, argv, testRunner, NULL);
+ longBowTestRunner_Destroy(&testRunner);
+ exit(exitStatus);
+}
diff --git a/metis/ccnx/forwarder/metis/core/test/test_metis_Forwarder.c b/metis/ccnx/forwarder/metis/core/test/test_metis_Forwarder.c
new file mode 100644
index 00000000..b5f99594
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/test/test_metis_Forwarder.c
@@ -0,0 +1,241 @@
+/*
+ * 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_Forwarder.c"
+#include <LongBow/unit-test.h>
+#include <parc/algol/parc_SafeMemory.h>
+
+LONGBOW_TEST_RUNNER(metis_Forwarder)
+{
+ // 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_Forwarder)
+{
+ 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_Forwarder)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE(Global)
+{
+ LONGBOW_RUN_TEST_CASE(Global, byteArrayToUnsignedLong);
+ LONGBOW_RUN_TEST_CASE(Global, metisForwarder_Create);
+ LONGBOW_RUN_TEST_CASE(Global, metisForwarder_Destroy);
+ LONGBOW_RUN_TEST_CASE(Global, metisForwarder_GetDispatcher);
+ LONGBOW_RUN_TEST_CASE(Global, metisForwarder_GetMessenger);
+ LONGBOW_RUN_TEST_CASE(Global, metisForwarder_GetNextConnectionId);
+ LONGBOW_RUN_TEST_CASE(Global, metisForwarder_GetTicks);
+ LONGBOW_RUN_TEST_CASE(Global, metisForwarder_Log);
+ LONGBOW_RUN_TEST_CASE(Global, metisForwarder_Receive);
+ LONGBOW_RUN_TEST_CASE(Global, metis_run);
+ LONGBOW_RUN_TEST_CASE(Global, metis_stop);
+
+
+ LONGBOW_RUN_TEST_CASE(Global, metisForwarder_NanosToTicks_1sec);
+ LONGBOW_RUN_TEST_CASE(Global, metisForwarder_NanosToTicks_1msec);
+ LONGBOW_RUN_TEST_CASE(Global, metisForwarder_NanosToTicks_LessThanHz);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisForwarder_TicksToNanos_1sec);
+}
+
+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, byteArrayToUnsignedLong)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+LONGBOW_TEST_CASE(Global, metisForwarder_Create)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+LONGBOW_TEST_CASE(Global, metisForwarder_Destroy)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+LONGBOW_TEST_CASE(Global, metisForwarder_GetDispatcher)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+LONGBOW_TEST_CASE(Global, metisForwarder_GetMessenger)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+LONGBOW_TEST_CASE(Global, metisForwarder_GetNextConnectionId)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+LONGBOW_TEST_CASE(Global, metisForwarder_GetTicks)
+{
+ MetisForwarder *metis = metisForwarder_Create(NULL);
+ MetisDispatcher *dispatcher = metisForwarder_GetDispatcher(metis);
+
+ int msec = 50;
+ struct timeval duration = { 0, msec * 1000 };
+
+ // run for a bit to get things primed
+ metisDispatcher_RunDuration(dispatcher, &duration);
+
+ MetisTicks t0 = metisForwarder_GetTicks(metis);
+ metisDispatcher_RunDuration(dispatcher, &duration);
+ MetisTicks t1 = metisForwarder_GetTicks(metis);
+
+ int64_t tickDelta = (int64_t) (t1 - t0);
+ int64_t tickError = llabs(msec - tickDelta);
+
+ metisForwarder_Destroy(&metis);
+
+ printf("tickError = %" PRId64 "\n", tickError);
+
+ // Test we're somewhere in the ballpark, betwen 40msec - 60msec
+ assertTrue(tickError <= 10, "tickError %" PRId64
+ " (tickDelta %" PRId64 ")",
+ tickError, tickDelta);
+
+}
+
+LONGBOW_TEST_CASE(Global, metisForwarder_Log)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+LONGBOW_TEST_CASE(Global, metisForwarder_Receive)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+LONGBOW_TEST_CASE(Global, metis_run)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+LONGBOW_TEST_CASE(Global, metis_stop)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+LONGBOW_TEST_CASE(Global, metisForwarder_NanosToTicks_1sec)
+{
+ // 1 second
+ uint64_t nanos = 1000000000ULL;
+ MetisTicks t = metisForwarder_NanosToTicks(nanos);
+
+ assertTrue(t == METISHZ, "1 second in nanos did not equal METISHZ, expected %d, got %" PRIu64, METISHZ, t);
+}
+
+LONGBOW_TEST_CASE(Global, metisForwarder_NanosToTicks_1msec)
+{
+ // 1 second
+ uint64_t nanos = 1000000ULL;
+ MetisTicks t = metisForwarder_NanosToTicks(nanos);
+ MetisTicks expected = METISHZ / 1000;
+ if (expected == 0) {
+ expected = 1;
+ }
+
+ assertTrue(t == expected, "1 msec in nanos did not equal METISHZ/1000, expected %" PRIu64 ", got %" PRIu64, expected, t);
+}
+
+LONGBOW_TEST_CASE(Global, metisForwarder_NanosToTicks_LessThanHz)
+{
+ // 1 second
+ uint64_t nanos = 1ULL;
+ MetisTicks t = metisForwarder_NanosToTicks(nanos);
+ MetisTicks expected = 1;
+
+ assertTrue(t == expected, "1 nsec in nanos did not equal 1, expected %" PRIu64 ", got %" PRIu64, expected, t);
+}
+
+LONGBOW_TEST_CASE(Global, metisForwarder_TicksToNanos_1sec)
+{
+ MetisTicks t = METISHZ;
+ uint64_t expected = ((1000000000ULL) * t / METISHZ);
+
+ uint64_t nanos = metisForwarder_TicksToNanos(t);
+ assertTrue(nanos == expected, "METISHZ ticks does not equal 1sec, expected %" PRIu64 ", got %" PRIu64, expected, nanos);
+}
+
+// ======================================================================
+
+LONGBOW_TEST_FIXTURE(Local)
+{
+ LONGBOW_RUN_TEST_CASE(Local, metisForwarder_Seed);
+ LONGBOW_RUN_TEST_CASE(Local, signal_cb);
+}
+
+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, metisForwarder_Seed)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+LONGBOW_TEST_CASE(Local, signal_cb)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+int
+main(int argc, char *argv[])
+{
+ LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_Forwarder);
+ int exitStatus = longBowMain(argc, argv, testRunner, NULL);
+ longBowTestRunner_Destroy(&testRunner);
+ exit(exitStatus);
+}
diff --git a/metis/ccnx/forwarder/metis/core/test/test_metis_Logger.c b/metis/ccnx/forwarder/metis/core/test/test_metis_Logger.c
new file mode 100644
index 00000000..4b62b1d6
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/test/test_metis_Logger.c
@@ -0,0 +1,222 @@
+/*
+ * 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_Logger.c"
+#include <stdio.h>
+#include <LongBow/unit-test.h>
+#include <parc/algol/parc_SafeMemory.h>
+
+LONGBOW_TEST_RUNNER(metis_Logger)
+{
+ LONGBOW_RUN_TEST_FIXTURE(Global);
+}
+
+// The Test Runner calls this function once before any Test Fixtures are run.
+LONGBOW_TEST_RUNNER_SETUP(metis_Logger)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// The Test Runner calls this function once after all the Test Fixtures are run.
+LONGBOW_TEST_RUNNER_TEARDOWN(metis_Logger)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// ==========================================================
+
+/*
+ * _testWritter will vsprintf to this buffer
+ */
+#define _logLength 1024
+static char _lastLogMessage[_logLength];
+
+static int
+_testWriter(const char *message)
+{
+ int written = 0;
+ written = snprintf(_lastLogMessage, _logLength, "%s", message);
+ return written;
+}
+
+static PARCLogReporter *
+_testWriter_Acquire(const PARCLogReporter *reporter)
+{
+ return parcObject_Acquire(reporter);
+}
+
+static void
+_testWriter_Release(PARCLogReporter **reporterPtr)
+{
+ parcObject_Release((void **) reporterPtr);
+}
+
+static void
+_testWriter_Report(PARCLogReporter *reporter, const PARCLogEntry *entry)
+{
+ char *string = parcLogEntry_ToString(entry);
+ _testWriter(string);
+ parcMemory_Deallocate((void **) &string);
+}
+
+static PARCLogReporter *
+_testWriter_Create(void)
+{
+ return parcLogReporter_Create(_testWriter_Acquire, _testWriter_Release, _testWriter_Report, NULL);
+}
+
+// ==========================================================
+
+LONGBOW_TEST_FIXTURE(Global)
+{
+ LONGBOW_RUN_TEST_CASE(Global, metisLogger_FacilityString_Found);
+ LONGBOW_RUN_TEST_CASE(Global, metisLogger_FacilityString_NotFound);
+ LONGBOW_RUN_TEST_CASE(Global, metisLogger_Create);
+ LONGBOW_RUN_TEST_CASE(Global, metisLogger_Acquire);
+ LONGBOW_RUN_TEST_CASE(Global, metisLogger_SetLogLevel);
+ LONGBOW_RUN_TEST_CASE(Global, metisLogger_IsLoggable_True);
+ LONGBOW_RUN_TEST_CASE(Global, metisLogger_IsLoggable_False);
+ LONGBOW_RUN_TEST_CASE(Global, metisLogger_Log_IsLoggable);
+ LONGBOW_RUN_TEST_CASE(Global, metisLogger_Log_IsNotLoggable);
+}
+
+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, metisLogger_FacilityString_Found)
+{
+ for (MetisLoggerFacility i = 0; i < MetisLoggerFacility_END; i++) {
+ const char *test = metisLogger_FacilityString(i);
+ assertNotNull(test, "Got null string for facility %d", i);
+ }
+}
+
+LONGBOW_TEST_CASE(Global, metisLogger_FacilityString_NotFound)
+{
+ const char *test = metisLogger_FacilityString(1000);
+ assertTrue(strcmp(test, "Unknown") == 0, "Got wrong string for unknown facility");
+}
+
+LONGBOW_TEST_CASE(Global, metisLogger_Create)
+{
+ PARCLogReporter *reporter = _testWriter_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+
+ metisLogger_Release(&logger);
+}
+
+LONGBOW_TEST_CASE(Global, metisLogger_Acquire)
+{
+ PARCLogReporter *reporter = _testWriter_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+
+ MetisLogger *copy = metisLogger_Acquire(logger);
+ metisLogger_Release(&logger);
+ metisLogger_Release(&copy);
+}
+
+LONGBOW_TEST_CASE(Global, metisLogger_SetLogLevel)
+{
+ PARCLogReporter *reporter = _testWriter_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_IO, PARCLogLevel_Off);
+
+ PARCLogLevel test = parcLog_GetLevel(logger->loggerArray[MetisLoggerFacility_IO]);
+ assertTrue(test == PARCLogLevel_Off, "wrong log level, expected %d got %d", PARCLogLevel_Off, test);
+ metisLogger_Release(&logger);
+}
+
+LONGBOW_TEST_CASE(Global, metisLogger_IsLoggable_True)
+{
+ PARCLogReporter *reporter = _testWriter_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_IO, PARCLogLevel_Warning);
+ bool isLoggable = metisLogger_IsLoggable(logger, MetisLoggerFacility_IO, PARCLogLevel_Warning);
+ assertTrue(isLoggable, "Did not get true for isLoggable when expecting true");
+ metisLogger_Release(&logger);
+}
+
+LONGBOW_TEST_CASE(Global, metisLogger_IsLoggable_False)
+{
+ PARCLogReporter *reporter = _testWriter_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_IO, PARCLogLevel_Warning);
+ bool isLoggable = metisLogger_IsLoggable(logger, MetisLoggerFacility_IO, PARCLogLevel_Debug);
+ assertFalse(isLoggable, "Logging debug to warning facility should have been false");
+ metisLogger_Release(&logger);
+}
+
+LONGBOW_TEST_CASE(Global, metisLogger_Log_IsLoggable)
+{
+ PARCLogReporter *reporter = _testWriter_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_IO, PARCLogLevel_Warning);
+ memset(_lastLogMessage, 0, _logLength);
+
+ metisLogger_Log(logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, "hello");
+ assertTrue(strlen(_lastLogMessage) > 0, "Did not write to log message");
+ metisLogger_Release(&logger);
+}
+
+LONGBOW_TEST_CASE(Global, metisLogger_Log_IsNotLoggable)
+{
+ PARCLogReporter *reporter = _testWriter_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+
+ metisLogger_SetLogLevel(logger, MetisLoggerFacility_IO, PARCLogLevel_Warning);
+ memset(_lastLogMessage, 0, _logLength);
+
+ metisLogger_Log(logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, "hello");
+ assertTrue(strlen(_lastLogMessage) == 0, "Should not have written to log message");
+ metisLogger_Release(&logger);
+}
+
+
+// ==========================================================
+
+int
+main(int argc, char *argv[])
+{
+ LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_Logger);
+ int exitStatus = longBowMain(argc, argv, testRunner, NULL);
+ longBowTestRunner_Destroy(&testRunner);
+ exit(exitStatus);
+}
+
diff --git a/metis/ccnx/forwarder/metis/core/test/test_metis_Message.c b/metis/ccnx/forwarder/metis/core/test/test_metis_Message.c
new file mode 100644
index 00000000..345a1440
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/test/test_metis_Message.c
@@ -0,0 +1,946 @@
+/*
+ * 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_Message.c"
+#include <LongBow/unit-test.h>
+#include <parc/algol/parc_SafeMemory.h>
+#include <parc/logging/parc_LogReporterTextStdout.h>
+
+#include <parc/logging/parc_LogReporterTextStdout.h>
+#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h>
+#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h>
+
+LONGBOW_TEST_RUNNER(metis_Message)
+{
+ // 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_Message)
+{
+ 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_Message)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE(Global)
+{
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_Create_InterestV0);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_Create_ObjectV0);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_Create_InterestV1);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_Create_ObjectV1);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_CreateFromBuffer);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_CreateFromArray);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_CreateFromElasticBuffer);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_CreateFromBuffer_BadMessage);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_CreateFromArray_BadMessage);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_Length);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_Append);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_Write);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_GetConnectionId);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_GetReceiveTime);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_ReadFromBuffer);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_Copy);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_GetMessageType);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_GetName);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasName_True);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasName_False);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_GetKeyIdHash);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasKeyId_True);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasKeyId_False);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_KeyIdEquals_IsEqual);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_KeyIdEquals_DifferentLength);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_KeyIdEquals_DifferentValue);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_ObjectHashEquals_IsEqual_Precomputed);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_ObjectHashEquals_IsEqual_Lazy);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_ObjectHashEquals_IsNotEqual);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_ObjectHashHashCode_Precomputed);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_ObjectHashHashCode_Lazy);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasContentObjectHash_True);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasContentObjectHash_False);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasHopLimit_True);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasHopLimit_False);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_GetHopLimit);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_SetHopLimit);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasInterestLifetime);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_GetInterestLifetimeTicks);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasExpirationTime);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasRecommendedCacheTime);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_SetGetExpirationTime);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_SetGetRecommendedCacheTime);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasGetPublicKey);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_IsPublicKeyVerified_True);
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_IsPublicKeyVerified_False);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisMessage_HasGetCertificate);
+}
+
+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, metisMessage_Create_InterestV0)
+{
+ PARCEventBuffer *buff = parcEventBuffer_Create();
+ parcEventBuffer_Append(buff, metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest));
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger);
+ metisLogger_Release(&logger);
+
+ assertNotNull(message, "Got null from metisMessage_CreateFromBuffer");
+ assertTrue(message->ingressConnectionId == 1, "IngressConnectionId wrong, expected %d got %u", 1, message->ingressConnectionId);
+ assertTrue(message->receiveTime == 2, "receiveTime wrong, expected %u got %" PRIu64, 2, message->receiveTime);
+
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_Create_ObjectV0)
+{
+ PARCEventBuffer *buff = parcEventBuffer_Create();
+ parcEventBuffer_Append(buff, metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject));
+
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger);
+ metisLogger_Release(&logger);
+
+ assertNotNull(message, "Got null from metisMessage_CreateFromBuffer");
+ assertTrue(message->ingressConnectionId == 1, "IngressConnectionId wrong, expected %d got %u", 1, message->ingressConnectionId);
+ assertTrue(message->receiveTime == 2, "receiveTime wrong, expected %u got %" PRIu64, 2, message->receiveTime);
+
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_Create_InterestV1)
+{
+ PARCEventBuffer *buff = parcEventBuffer_Create();
+ parcEventBuffer_Append(buff, metisTestDataV1_Interest_AllFields, sizeof(metisTestDataV1_Interest_AllFields));
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger);
+ metisLogger_Release(&logger);
+
+ assertNotNull(message, "Got null from metisMessage_CreateFromBuffer");
+ assertTrue(message->ingressConnectionId == 1, "IngressConnectionId wrong, expected %d got %u", 1, message->ingressConnectionId);
+ assertTrue(message->receiveTime == 2, "receiveTime wrong, expected %u got %" PRIu64, 2, message->receiveTime);
+
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_Create_ObjectV1)
+{
+ PARCEventBuffer *buff = parcEventBuffer_Create();
+ parcEventBuffer_Append(buff, metisTestDataV1_ContentObject_NameA_Crc32c, sizeof(metisTestDataV1_ContentObject_NameA_Crc32c));
+
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger);
+ metisLogger_Release(&logger);
+
+ assertNotNull(message, "Got null from metisMessage_CreateFromBuffer");
+ assertTrue(message->ingressConnectionId == 1, "IngressConnectionId wrong, expected %d got %u", 1, message->ingressConnectionId);
+ assertTrue(message->receiveTime == 2, "receiveTime wrong, expected %u got %" PRIu64, 2, message->receiveTime);
+
+ metisMessage_Release(&message);
+}
+
+
+LONGBOW_TEST_CASE(Global, metisMessage_CreateFromArray)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+
+ assertNotNull(message, "Got null from metisMessage_CreateFromArray");
+ assertTrue(message->ingressConnectionId == 1, "IngressConnectionId wrong, expected %d got %u", 1, message->ingressConnectionId);
+ assertTrue(message->receiveTime == 2, "receiveTime wrong, expected %u got %" PRIu64, 2, message->receiveTime);
+
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_CreateFromArray_BadMessage)
+{
+ // Invalid version
+ char message_str[] = "\xFFOnce upon a time, in a stack far away, a dangling pointer found its way to the top of the heap.";
+ printf("metisMessage_CreateFromArray_BadMessage attempting to process a bad message which should report an error:\n");
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromArray((uint8_t *) message_str, sizeof(message_str), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ assertNull(message, "Got null from metisMessage_CreateFromArray");
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_CreateFromBuffer)
+{
+ char message_str[] = "\x00Once upon a time, in a stack far away, a dangling pointer found its way to the top of the heap.";
+
+ PARCEventBuffer *buff = parcEventBuffer_Create();
+ parcEventBuffer_Append(buff, message_str, sizeof(message_str));
+
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger);
+ metisLogger_Release(&logger);
+
+ assertNotNull(message, "Got null from metisMessage_CreateFromBuffer");
+ assertTrue(message->ingressConnectionId == 1, "IngressConnectionId wrong, expected %d got %u", 1, message->ingressConnectionId);
+ assertTrue(message->receiveTime == 2, "receiveTime wrong, expected %u got %" PRIu64, 2, message->receiveTime);
+
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_CreateFromElasticBuffer)
+{
+ char message_str[] = "\0x00Once upon a time, in a stack far away, a dangling pointer found its way to the top of the heap.";
+
+ PARCBuffer *buff = parcBuffer_Wrap(message_str, sizeof(message_str), 0, sizeof(message_str));
+
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromParcBuffer(buff, 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ assertNotNull(message, "Got null from metisMessage_CreateFromBuffer");
+ assertTrue(message->ingressConnectionId == 1, "IngressConnectionId wrong, expected %d got %u", 1, message->ingressConnectionId);
+ assertTrue(message->receiveTime == 2, "receiveTime wrong, expected %u got %" PRIu64, 2, message->receiveTime);
+
+ metisMessage_Release(&message);
+ parcBuffer_Release(&buff);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_CreateFromBuffer_BadMessage)
+{
+ // Bad version
+ char message_str[] = "\xFFOnce upon a time, in a stack far away, a dangling pointer found its way to the top of the heap.";
+
+ printf("metisMessage_CreateFromBuffer_BadMessage attempting to process a bad message which should report an error:\n");
+ PARCEventBuffer *buff = parcEventBuffer_Create();
+ parcEventBuffer_Append(buff, message_str, sizeof(message_str));
+
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger);
+ metisLogger_Release(&logger);
+
+ assertNull(message, "Got null from metisMessage_CreateFromBuffer");
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_ReadFromBuffer)
+{
+ char message_str[] = "\x00Once upon a time, in a stack far away, a dangling pointer found its way to the top of the heap.";
+
+ PARCEventBuffer *buff = parcEventBuffer_Create();
+ parcEventBuffer_Append(buff, message_str, sizeof(message_str));
+
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_ReadFromBuffer(1, 2, buff, sizeof(message_str), logger);
+ metisLogger_Release(&logger);
+
+ assertNotNull(message, "Got null from metisMessage_CreateFromBuffer");
+
+ assertTrue(parcEventBuffer_GetLength(message->messageBytes) == sizeof(message_str),
+ "Length of internal buffer wrong, expected %zu got %zu",
+ sizeof(message_str),
+ parcEventBuffer_GetLength(message->messageBytes));
+
+ uint8_t *p = parcEventBuffer_Pullup(message->messageBytes, sizeof(message_str));
+ assertTrue(memcmp(p, message_str, sizeof(message_str)) == 0, "Internal buffer contents does not match test");
+ assertTrue(message->ingressConnectionId == 1, "IngressConnectionId wrong, expected %d got %u", 1, message->ingressConnectionId);
+ assertTrue(message->receiveTime == 2, "receiveTime wrong, expected %u got %" PRIu64, 2, message->receiveTime);
+ assertTrue(parcEventBuffer_GetLength(buff) == 0, "Origin buffer not drained, expected 0, got %zu", parcEventBuffer_GetLength(buff));
+
+ metisMessage_Release(&message);
+ parcEventBuffer_Destroy(&buff);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_Append)
+{
+ char message_str[] = "\x00Once upon a time ...";
+
+ PARCEventBuffer *buffer = parcEventBuffer_Create();
+ PARCEventBuffer *buff = parcEventBuffer_Create();
+ parcEventBuffer_Append(buff, message_str, sizeof(message_str));
+
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger);
+ int result = metisMessage_Append(buffer, message);
+
+ assertTrue(result == 0, "Got error from metisMessage_Append");
+ metisLogger_Release(&logger);
+ metisMessage_Release(&message);
+ parcEventBuffer_Destroy(&buffer);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_Write)
+{
+ char message_str[] = "\x00Once upon a time ...";
+
+ PARCEventScheduler *scheduler = parcEventScheduler_Create();
+ PARCEventQueue *queue = parcEventQueue_Create(scheduler, -1, PARCEventQueueOption_CloseOnFree);
+
+ PARCEventBuffer *buff = parcEventBuffer_Create();
+ parcEventBuffer_Append(buff, message_str, sizeof(message_str));
+
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger);
+ int result = metisMessage_Write(queue, message);
+
+ assertTrue(result == 0, "Got error from metisMessage_Write");
+
+ // buff is deallocated by metisMessage_Release
+ metisLogger_Release(&logger);
+ metisMessage_Release(&message);
+ parcEventQueue_Destroy(&queue);
+ parcEventScheduler_Destroy(&scheduler);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_Length)
+{
+ char message_str[] = "\x00Once upon a time, in a stack far away, a dangling pointer found its way to the top of the heap.";
+
+ PARCEventBuffer *buff = parcEventBuffer_Create();
+ parcEventBuffer_Append(buff, message_str, sizeof(message_str));
+
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger);
+ metisLogger_Release(&logger);
+
+ assertNotNull(message, "Got null from metisMessage_CreateFromBuffer");
+ size_t length = metisMessage_Length(message);
+ assertTrue(length == sizeof(message_str), "Wrong length, expected %zu got %zu", sizeof(message_str), length);
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_GetConnectionId)
+{
+ char message_str[] = "\x00Once upon a time, in a stack far away, a dangling pointer found its way to the top of the heap.";
+
+ PARCEventBuffer *buff = parcEventBuffer_Create();
+ parcEventBuffer_Append(buff, message_str, sizeof(message_str));
+
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger);
+ metisLogger_Release(&logger);
+
+ assertNotNull(message, "Got null from metisMessage_CreateFromBuffer");
+ unsigned connid = metisMessage_GetIngressConnectionId(message);
+
+ assertTrue(connid == 1, "Wrong length, expected %u got %u", 1, connid);
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_GetReceiveTime)
+{
+ char message_str[] = "\x00Once upon a time, in a stack far away, a dangling pointer found its way to the top of the heap.";
+
+ PARCEventBuffer *buff = parcEventBuffer_Create();
+ parcEventBuffer_Append(buff, message_str, sizeof(message_str));
+
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger);
+ metisLogger_Release(&logger);
+
+ assertNotNull(message, "Got null from metisMessage_CreateFromBuffer");
+ MetisTicks time = metisMessage_GetReceiveTime(message);
+
+ assertTrue(time == 2, "Wrong receive time, expected %u got %" PRIu64, 2, time);
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_Copy)
+{
+ char message_str[] = "\x00Once upon a time, in a stack far away, a dangling pointer found its way to the top of the heap.";
+
+ PARCEventBuffer *buff = parcEventBuffer_Create();
+ parcEventBuffer_Append(buff, message_str, sizeof(message_str));
+
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger);
+ metisLogger_Release(&logger);
+
+ assertNotNull(message, "Got null from metisMessage_CreateFromBuffer");
+ assertTrue(message->refcount == 1, "Incorrect refcount, expected %u got %u", 1, message->refcount);
+
+ MetisMessage *copy = metisMessage_Acquire(message);
+ assertTrue(message->refcount == 2, "Incorrect refcount, expected %u got %u", 2, message->refcount);
+
+ metisMessage_Release(&message);
+ assertTrue(copy->refcount == 1, "Incorrect refcount, expected %u got %u", 1, message->refcount);
+
+ metisMessage_Release(&copy);
+
+ assertTrue(parcMemory_Outstanding() == 0, "Memory balance should be zero after destroying last copy, got %u", parcMemory_Outstanding());
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_GetMessageType)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+ metisLogger_Release(&logger);
+ MetisMessagePacketType type = metisMessage_GetType(message);
+
+ assertTrue(type == MetisMessagePacketType_ContentObject, "wrong type, expected %u got %u", MetisMessagePacketType_ContentObject, type);
+
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_GetName)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+ metisLogger_Release(&logger);
+ MetisTlvName *name = metisMessage_GetName(message);
+ MetisTlvName *truth = metisTlvName_Create(&metisTestDataV0_EncodedObject[metisTestDataV0_EncodedObject_name.offset], metisTestDataV0_EncodedObject_name.length);
+
+ assertTrue(metisTlvName_Equals(truth, name), "Did not get back the right name");
+
+ metisTlvName_Release(&truth);
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_HasName_True)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+ metisLogger_Release(&logger);
+ bool hasName = metisMessage_HasName(message);
+ assertTrue(hasName, "Message with a name says it does not");
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_HasName_False)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_CPIMessage, sizeof(metisTestDataV0_CPIMessage), 1, 2, logger);
+ metisLogger_Release(&logger);
+ bool hasName = metisMessage_HasName(message);
+ assertFalse(hasName, "Message without a name says it does");
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_HasContentObjectHash_True)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+ metisLogger_Release(&logger);
+ bool hasHash = metisMessage_HasContentObjectHash(message);
+ assertTrue(hasHash, "Message with a content object hash says it does not");
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_HasContentObjectHash_False)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest), 1, 2, logger);
+ metisLogger_Release(&logger);
+ bool hasHash = metisMessage_HasContentObjectHash(message);
+ assertTrue(hasHash, "Message without a content object hash says it does");
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_GetKeyIdHash)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ uint32_t truthhash = parcHash32_Data(&metisTestDataV0_EncodedObject[metisTestDataV0_EncodedObject_keyid.offset], metisTestDataV0_EncodedObject_keyid.length);
+ uint32_t testhash;
+ bool success = metisMessage_GetKeyIdHash(message, &testhash);
+
+ assertTrue(success, "Failed metisMessage_GetKeyIdHash, returned false");
+ assertTrue(truthhash == testhash, "Hash compared wrong, expected %08X, got %08X", truthhash, testhash);
+
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_HasKeyId_True)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+ metisLogger_Release(&logger);
+ bool hasKeyId = metisMessage_HasKeyId(message);
+ assertTrue(hasKeyId, "Message with a keyid says it does not");
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_HasKeyId_False)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName), 1, 2, logger);
+ metisLogger_Release(&logger);
+ bool hasKeyId = metisMessage_HasKeyId(message);
+ assertFalse(hasKeyId, "Message without a keyid says it does");
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_KeyIdEquals_IsEqual)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *a = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+ MetisMessage *b = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ assertTrue(metisMessage_KeyIdEquals(a, b), "Messages with equal keyids did not compare");
+ metisMessage_Release(&a);
+ metisMessage_Release(&b);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_KeyIdEquals_DifferentLength)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *a = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+ MetisMessage *b = metisMessage_CreateFromArray(metisTestDataV0_SecondObject, sizeof(metisTestDataV0_SecondObject), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ assertFalse(metisMessage_KeyIdEquals(a, b), "Messages with differnt length keyids did compared equal");
+ metisMessage_Release(&a);
+ metisMessage_Release(&b);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_KeyIdEquals_DifferentValue)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *a = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+ MetisMessage *b = metisMessage_CreateFromArray(metisTestDataV0_SecondInterest, sizeof(metisTestDataV0_SecondInterest), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ assertFalse(metisMessage_KeyIdEquals(a, b), "Messages with differnt keyids did compared equal");
+ metisMessage_Release(&a);
+ metisMessage_Release(&b);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_ObjectHashEquals_IsEqual_Precomputed)
+{
+ // create messages from Interests, as those are precomputed
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *a = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest), 1, 2, logger);
+ MetisMessage *b = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ assertTrue(metisMessage_ObjectHashEquals(a, b), "Messages with equal ContentObjectHash did not compare");
+ metisMessage_Release(&a);
+ metisMessage_Release(&b);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_ObjectHashEquals_IsEqual_Lazy)
+{
+ // create messages from content objects, as those are lazy computed
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *a = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+ MetisMessage *b = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ assertTrue(metisMessage_ObjectHashEquals(a, b), "Messages with equal ContentObjectHash did not compare");
+ metisMessage_Release(&a);
+ metisMessage_Release(&b);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_ObjectHashEquals_IsNotEqual)
+{
+ // create messages from different content objects
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *a = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+ MetisMessage *b = metisMessage_CreateFromArray(metisTestDataV0_SecondObject, sizeof(metisTestDataV0_SecondObject), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ assertFalse(metisMessage_ObjectHashEquals(a, b), "Messages with unequal ContentObjectHash compared as equal");
+ metisMessage_Release(&a);
+ metisMessage_Release(&b);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_ObjectHashHashCode_Precomputed)
+{
+ // create messages from Interests, as those are precomputed
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *a = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ uint32_t hashcode;
+ bool success = metisMessage_GetContentObjectHashHash(a, &hashcode);
+ assertTrue(success, "Returned false trying to get hash of contentobject hash");
+
+ metisMessage_Release(&a);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_ObjectHashHashCode_Lazy)
+{
+ // create messages from content object, as those are precomputed
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *a = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ uint32_t hashcode;
+ bool success = metisMessage_GetContentObjectHashHash(a, &hashcode);
+ assertTrue(success, "Returned false trying to get hash of contentobject hash");
+
+ metisMessage_Release(&a);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_HasHopLimit_True)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ bool hasHopLimit = metisMessage_HasHopLimit(message);
+ assertTrue(hasHopLimit, "Message with a hop limit says it does not.");
+
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_HasHopLimit_False)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest_no_hoplimit, sizeof(metisTestDataV0_EncodedInterest_no_hoplimit), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ bool hasHopLimit = metisMessage_HasHopLimit(message);
+ assertFalse(hasHopLimit, "Message without a hop limit says it does.");
+
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_GetHopLimit)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ uint8_t hoplimit = metisMessage_GetHopLimit(message);
+ assertTrue(hoplimit == 32, "Wrong hop limit, got %u expected %u.", hoplimit, 32);
+
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_SetHopLimit)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ metisMessage_SetHopLimit(message, 99);
+ uint8_t hoplimit = metisMessage_GetHopLimit(message);
+ assertTrue(hoplimit == 99, "Wrong hop limit, got %u expected %u.", hoplimit, 99);
+
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_HasInterestLifetime)
+{
+ PARCEventBuffer *buff = parcEventBuffer_Create();
+ parcEventBuffer_Append(buff, metisTestDataV1_Interest_AllFields, sizeof(metisTestDataV1_Interest_AllFields));
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger);
+ metisLogger_Release(&logger);
+
+ assertTrue(metisMessage_HasInterestLifetime(message), "Should have returned true for interest lifetime");
+
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_GetInterestLifetimeTicks)
+{
+ PARCEventBuffer *buff = parcEventBuffer_Create();
+ parcEventBuffer_Append(buff, metisTestDataV1_Interest_AllFields, sizeof(metisTestDataV1_Interest_AllFields));
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+ MetisMessage *message = metisMessage_CreateFromBuffer(1, 2, buff, logger);
+ metisLogger_Release(&logger);
+
+ // don't check actual value. It will vary based on METISHZ and rouding errors due to integer math
+ MetisTicks ticks = metisMessage_GetInterestLifetimeTicks(message);
+ assertTrue(ticks > 0, "Should have gotten positive value for interest lifetime ticks");
+
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_HasExpirationTime)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+
+ // Note: Assumes metisTestDataV0_EncodedObject doesn't have ExpiryTime.
+ MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ bool hasExpiryTime = metisMessage_HasExpiryTime(message);
+ assertFalse(hasExpiryTime, "Message without ExpiryTime says it has one.");
+
+ metisMessage_SetExpiryTimeTicks(message, 10000);
+ hasExpiryTime = metisMessage_HasExpiryTime(message);
+ assertTrue(hasExpiryTime, "Message with ExpiryTime says it doesn't have one.");
+
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_HasRecommendedCacheTime)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+
+ // Note: Assumes metisTestDataV0_EncodedObject doesn't have RCT.
+ MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ bool hasRCT = metisMessage_HasRecommendedCacheTime(message);
+ assertFalse(hasRCT, "Message without hasRCT says it has one.");
+
+ metisMessage_SetRecommendedCacheTimeTicks(message, 10000);
+ hasRCT = metisMessage_HasRecommendedCacheTime(message);
+ assertTrue(hasRCT, "Message with hasRCT says it doesn't have one.");
+
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_SetGetExpirationTime)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+
+ // Note: Assumes metisTestDataV0_EncodedObject doesn't have RCT.
+ MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ uint64_t time = 12345;
+ metisMessage_SetExpiryTimeTicks(message, time);
+ assertTrue(time == metisMessage_GetExpiryTimeTicks(message), "Retrieved unexpected ExpiryTime");
+
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_SetGetRecommendedCacheTime)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+
+ // Note: Assumes metisTestDataV0_EncodedObject doesn't have RCT.
+ MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedObject, sizeof(metisTestDataV0_EncodedObject), 1, 2, logger);
+ metisLogger_Release(&logger);
+
+ uint64_t time = 12345;
+ metisMessage_SetRecommendedCacheTimeTicks(message, time);
+ assertTrue(time == metisMessage_GetRecommendedCacheTimeTicks(message), "Retrieved unexpected RCT");
+
+ metisMessage_Release(&message);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_HasGetPublicKey)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+
+ MetisMessage *contentWithKey =
+ metisMessage_CreateFromArray(metisTestDataV1_ContentObject_NameA_KeyId1_RsaSha256,
+ sizeof(metisTestDataV1_ContentObject_NameA_KeyId1_RsaSha256), 1, 2, logger);
+
+ MetisMessage *interestWithKeyIdRestriction =
+ metisMessage_CreateFromArray(metisTestDataV1_Interest_NameAAndKeyId,
+ sizeof(metisTestDataV1_Interest_NameAAndKeyId), 1, 2, logger);
+
+ metisLogger_Release(&logger);
+
+ assertTrue(metisMessage_HasPublicKey(contentWithKey), "Expected to see a public key");
+ assertFalse(metisMessage_HasPublicKey(interestWithKeyIdRestriction), "Expected to not see a public key");
+
+ PARCBuffer *key = metisMessage_GetPublicKey(contentWithKey);
+
+ assertNotNull(key, "Expected to retrieve the public key");
+
+ metisMessage_Release(&contentWithKey);
+ metisMessage_Release(&interestWithKeyIdRestriction);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_IsPublicKeyVerified_False)
+{
+ PARCLogReporter *reporter = parcLogReporterTextStdout_Create();
+ MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock());
+ parcLogReporter_Release(&reporter);
+
+ MetisMessage *contentWithKey =
+ metisMessage_CreateFromArray(metisTestDataV1_ContentObject_NameA_KeyId1_RsaSha256,
+ sizeof(metisTestDataV1_ContentObject_NameA_KeyId1_RsaSha256), 1, 2, logger);
+
+ MetisMessage *interestWithKeyIdRestriction =
+ metisMessage_CreateFromArray(metisTestDataV1_Interest_NameAAndKeyId,
+ sizeof(metisTestDataV1_Interest_NameAAndKeyId), 1, 2, logger);
+
+ metisLogger_Release(&logger);
+
+ assertFalse(metisMessage_IsKeyIdVerified(contentWithKey), "Expected key to not be verified.");
+
+ // This is an interest. The keyId is actually a KeyId restriction, so will never be verified.
+ assertFalse(metisMessage_IsKeyIdVerified(interestWithKeyIdRestriction), "Expected key to not be verified.");
+
+ PARCBuffer *key = metisMessage_GetPublicKey(contentWithKey);
+
+ assertNotNull(key, "Expected to retrieve the public key");
+
+ metisMessage_Release(&contentWithKey);
+ metisMessage_Release(&interestWithKeyIdRestriction);
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_IsPublicKeyVerified_True)
+{
+ testUnimplemented("Verification of KeyIds in ContentObjects is not yet implemented.");
+}
+
+LONGBOW_TEST_CASE(Global, metisMessage_HasGetCertificate)
+{
+ testUnimplemented("Need test data with an encoded certificate.");
+}
+
+// ===================================================
+
+LONGBOW_TEST_FIXTURE(Local)
+{
+}
+
+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;
+}
+
+int
+main(int argc, char *argv[])
+{
+ LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_Message);
+ int exitStatus = longBowMain(argc, argv, testRunner, NULL);
+ longBowTestRunner_Destroy(&testRunner);
+ exit(exitStatus);
+}
diff --git a/metis/ccnx/forwarder/metis/core/test/test_metis_NumberSet.c b/metis/ccnx/forwarder/metis/core/test/test_metis_NumberSet.c
new file mode 100644
index 00000000..461e5e62
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/test/test_metis_NumberSet.c
@@ -0,0 +1,557 @@
+/*
+ * 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_NumberSet.c"
+#include <LongBow/unit-test.h>
+
+#include <parc/algol/parc_SafeMemory.h>
+
+LONGBOW_TEST_RUNNER(metis_NumberSet)
+{
+ // 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_NumberSet)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// The Test Runner calls this function once after all the Test Fixtures are run.
+LONGBOW_TEST_RUNNER_TEARDOWN(metis_NumberSet)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE(Global)
+{
+ LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Append_NoExpand);
+ LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Append_Expand);
+ LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Append_Duplicate);
+ LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Contains);
+ LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Copy);
+ LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Create_Destroy);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Equals_IsEqual);
+ LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Equals_BothEmpty);
+ LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Equals_BothNull);
+ LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Equals_OneNull);
+ LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Equals_DifferentLengths);
+ LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Equals_IsNotEqual);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_GetItem);
+ LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Length);
+ LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Subtract_Disjoint);
+ LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Subtract_Equivalent);
+ LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Subtract_Overlap);
+
+ LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Remove_LastElement);
+ LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Remove_AllElements);
+ LONGBOW_RUN_TEST_CASE(Global, metisNumberSet_Remove_FirstElement);
+}
+
+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, metisNumberSet_Append_NoExpand)
+{
+ MetisNumberSet *set = metisNumberSet_Create();
+
+ for (int i = 1; i <= set->limit; i++) {
+ bool result = metisNumberSet_Add(set, i);
+ assertTrue(result, "Got failure on append, i = %d", i);
+ assertTrue(set->length == i, "Set length wrong, expected %d got %zu", i, set->length);
+ }
+
+ metisNumberSet_Release(&set);
+}
+
+LONGBOW_TEST_CASE(Global, metisNumberSet_Append_Expand)
+{
+ MetisNumberSet *set = metisNumberSet_Create();
+
+ size_t limit = set->limit;
+ for (int i = 1; i <= limit + 5; i++) {
+ bool result = metisNumberSet_Add(set, i);
+ assertTrue(result, "Got failure on append, i = %d", i);
+ assertTrue(set->length == i, "Set length wrong, expected %d got %zu", i, set->length);
+ }
+
+ metisNumberSet_Release(&set);
+}
+
+LONGBOW_TEST_CASE(Global, metisNumberSet_Append_Duplicate)
+{
+ MetisNumberSet *set = metisNumberSet_Create();
+
+ for (int i = 1; i <= set->limit; i++) {
+ bool result = metisNumberSet_Add(set, i);
+ assertTrue(result, "Got failure on append, i = %d", i);
+ assertTrue(set->length == i, "Set length wrong, expected %d got %zu", i, set->length);
+ }
+
+ for (int i = 1; i <= set->limit; i++) {
+ bool result = metisNumberSet_Add(set, i);
+ assertFalse(result, "Got success on duplicate append, i = %d", i);
+ assertTrue(set->length == set->limit, "Set length wrong, expected %zu got %zu", set->limit, set->length);
+ }
+
+ metisNumberSet_Release(&set);
+}
+
+LONGBOW_TEST_CASE(Global, metisNumberSet_Contains)
+{
+ MetisNumberSet *set = metisNumberSet_Create();
+
+ int limit = 10;
+ for (int i = 1; i <= limit; i++) {
+ bool result = metisNumberSet_Add(set, i);
+ assertTrue(result, "Got failure on append, i = %d", i);
+ assertTrue(set->length == i, "Set length wrong, expected %d got %zu", i, set->length);
+ }
+
+ for (int i = 1; i <= limit; i++) {
+ bool result = metisNumberSet_Contains(set, i);
+ assertTrue(result, "Got missing member, i = %d", i);
+ }
+
+ for (int i = limit + 1; i <= 2 * limit; i++) {
+ bool result = metisNumberSet_Contains(set, i);
+ assertFalse(result, "Got contains returned true for missing element, i = %d", i);
+ }
+
+ metisNumberSet_Release(&set);
+}
+
+LONGBOW_TEST_CASE(Global, metisNumberSet_Copy)
+{
+ MetisNumberSet *set = metisNumberSet_Create();
+
+ int limit = 10;
+ for (int i = 1; i <= limit; i++) {
+ bool result = metisNumberSet_Add(set, i);
+ assertTrue(result, "Got failure on append, i = %d", i);
+ assertTrue(set->length == i, "Set length wrong, expected %d got %zu", i, set->length);
+ }
+
+ MetisNumberSet *copy = metisNumberSet_Acquire(set);
+ assertTrue(set->refcount == 2, "Set refcount not 2: %u", set->refcount);
+
+ metisNumberSet_Release(&copy);
+ assertTrue(set->refcount == 1, "Set refcount not 1: %u", set->refcount);
+
+ metisNumberSet_Release(&set);
+}
+
+LONGBOW_TEST_CASE(Global, metisNumberSet_Create_Destroy)
+{
+ MetisNumberSet *set = metisNumberSet_Create();
+ assertTrue(set->length == 0, "Set not 0 length on create: %zu", set->length);
+ assertTrue(set->refcount == 1, "Set refcount not 1: %u", set->refcount);
+ metisNumberSet_Release(&set);
+
+ assertTrue(parcSafeMemory_ReportAllocation(STDOUT_FILENO) == 0, "Memory imbalance on create/destroy: %u", parcMemory_Outstanding());
+}
+
+LONGBOW_TEST_CASE(Global, metisNumberSet_Equals_IsEqual)
+{
+ // 0 is the terminator
+ unsigned a_set[] = { 1, 2, 3, 4, 5, 6, 7, 0 };
+ unsigned b_set[] = { 1, 2, 3, 4, 5, 6, 7, 0 };
+
+ MetisNumberSet *a = metisNumberSet_Create();
+ MetisNumberSet *b = metisNumberSet_Create();
+
+ for (int i = 0; a_set[i] != 0; i++) {
+ metisNumberSet_Add(a, a_set[i]);
+ }
+
+ for (int i = 0; b_set[i] != 0; i++) {
+ metisNumberSet_Add(b, b_set[i]);
+ }
+
+ bool equal = metisNumberSet_Equals(a, b);
+
+ metisNumberSet_Release(&a);
+ metisNumberSet_Release(&b);
+
+ assertTrue(equal, "Equal sets did not compare as equal");
+}
+
+LONGBOW_TEST_CASE(Global, metisNumberSet_Equals_BothEmpty)
+{
+ MetisNumberSet *a = metisNumberSet_Create();
+ MetisNumberSet *b = metisNumberSet_Create();
+
+ bool equal = metisNumberSet_Equals(a, b);
+
+ metisNumberSet_Release(&a);
+ metisNumberSet_Release(&b);
+
+ assertTrue(equal, "Two empty sets did not compare as equal");
+}
+
+LONGBOW_TEST_CASE(Global, metisNumberSet_Equals_BothNull)
+{
+ MetisNumberSet *a = NULL;
+ MetisNumberSet *b = NULL;
+
+ bool equal = metisNumberSet_Equals(a, b);
+
+ assertTrue(equal, "Two NULL sets did not compare as equal");
+}
+
+LONGBOW_TEST_CASE(Global, metisNumberSet_Equals_OneNull)
+{
+ MetisNumberSet *a = NULL;
+ MetisNumberSet *b = metisNumberSet_Create();
+
+ bool equal = metisNumberSet_Equals(a, b);
+
+ assertFalse(equal, "One null one allocated sets did compared as equal");
+ metisNumberSet_Release(&b);
+}
+
+LONGBOW_TEST_CASE(Global, metisNumberSet_Equals_DifferentLengths)
+{
+ // 0 is the terminator
+ unsigned a_set[] = { 1, 2, 3, 4, 5, 6, 7, 0 };
+ unsigned b_set[] = { 1, 2, 3, 4, 5, 6, 0 };
+
+ MetisNumberSet *a = metisNumberSet_Create();
+ MetisNumberSet *b = metisNumberSet_Create();
+
+ for (int i = 0; a_set[i] != 0; i++) {
+ metisNumberSet_Add(a, a_set[i]);
+ }
+
+ for (int i = 0; b_set[i] != 0; i++) {
+ metisNumberSet_Add(b, b_set[i]);
+ }
+
+ bool equal = metisNumberSet_Equals(a, b);
+
+ metisNumberSet_Release(&a);
+ metisNumberSet_Release(&b);
+
+ assertFalse(equal, "Sets of different lengths compared as equal");
+}
+
+LONGBOW_TEST_CASE(Global, metisNumberSet_Equals_IsNotEqual)
+{
+ // 0 is the terminator
+ unsigned a_set[] = { 1, 2, 3, 4, 5, 6, 7, 0 };
+ unsigned b_set[] = { 1, 2, 3, 4, 5, 6, 8, 0 };
+
+ MetisNumberSet *a = metisNumberSet_Create();
+ MetisNumberSet *b = metisNumberSet_Create();
+
+ for (int i = 0; a_set[i] != 0; i++) {
+ metisNumberSet_Add(a, a_set[i]);
+ }
+
+ for (int i = 0; b_set[i] != 0; i++) {
+ metisNumberSet_Add(b, b_set[i]);
+ }
+
+ bool equal = metisNumberSet_Equals(a, b);
+
+ metisNumberSet_Release(&a);
+ metisNumberSet_Release(&b);
+
+ assertFalse(equal, "Same length but unequal sets compared as equal");
+}
+
+LONGBOW_TEST_CASE(Global, metisNumberSet_GetItem)
+{
+ MetisNumberSet *set = metisNumberSet_Create();
+
+ int limit = 10;
+ for (int i = 1; i <= limit; i++) {
+ bool result = metisNumberSet_Add(set, i);
+ assertTrue(result, "Got failure on append, i = %d", i);
+ assertTrue(set->length == i, "Set length wrong, expected %d got %zu", i, set->length);
+ }
+
+ for (int i = 0; i < limit; i++) {
+ MetisNumber n = metisNumberSet_GetItem(set, i);
+ assertTrue(n == i + 1, "Got wrong number, i = %d, n = %u", i, n);
+ }
+
+ metisNumberSet_Release(&set);
+}
+
+LONGBOW_TEST_CASE(Global, metisNumberSet_Length)
+{
+ MetisNumberSet *set = metisNumberSet_Create();
+
+ int limit = 10;
+ for (int i = 1; i <= limit; i++) {
+ bool result = metisNumberSet_Add(set, i);
+ assertTrue(result, "Got failure on append, i = %d", i);
+ assertTrue(metisNumberSet_Length(set) == i, "Set length wrong, expected %d got %zu", i, metisNumberSet_Length(set));
+ }
+
+ metisNumberSet_Release(&set);
+}
+
+LONGBOW_TEST_CASE(Global, metisNumberSet_Subtract_Disjoint)
+{
+ // 0 is the terminator
+ unsigned a_set[] = { 1, 2, 3, 4, 5, 6, 7, 0 };
+ unsigned b_set[] = { 11, 12, 13, 14, 15, 0 };
+ unsigned truth_set[] = { 1, 2, 3, 4, 5, 6, 7, 0 };
+
+ MetisNumberSet *a = metisNumberSet_Create();
+ MetisNumberSet *b = metisNumberSet_Create();
+ MetisNumberSet *truth = metisNumberSet_Create();
+
+ for (int i = 0; a_set[i] != 0; i++) {
+ metisNumberSet_Add(a, a_set[i]);
+ }
+
+ for (int i = 0; b_set[i] != 0; i++) {
+ metisNumberSet_Add(b, b_set[i]);
+ }
+
+ for (int i = 0; truth_set[i] != 0; i++) {
+ metisNumberSet_Add(truth, truth_set[i]);
+ }
+
+ MetisNumberSet *test = metisNumberSet_Subtract(a, b);
+
+ bool equal = metisNumberSet_Equals(truth, test);
+
+ metisNumberSet_Release(&a);
+ metisNumberSet_Release(&b);
+ metisNumberSet_Release(&truth);
+ metisNumberSet_Release(&test);
+
+ assertTrue(equal, "subtraction result incorrect for disjoint sets");
+}
+
+LONGBOW_TEST_CASE(Global, metisNumberSet_Subtract_Equivalent)
+{
+ // 0 is the terminator
+ unsigned a_set[] = { 1, 2, 3, 4, 5, 6, 7, 0 };
+ unsigned b_set[] = { 1, 2, 3, 4, 5, 6, 7, 0 };
+ unsigned truth_set[] = { 0 };
+
+ MetisNumberSet *a = metisNumberSet_Create();
+ MetisNumberSet *b = metisNumberSet_Create();
+ MetisNumberSet *truth = metisNumberSet_Create();
+
+ for (int i = 0; a_set[i] != 0; i++) {
+ metisNumberSet_Add(a, a_set[i]);
+ }
+
+ for (int i = 0; b_set[i] != 0; i++) {
+ metisNumberSet_Add(b, b_set[i]);
+ }
+
+ for (int i = 0; truth_set[i] != 0; i++) {
+ metisNumberSet_Add(truth, truth_set[i]);
+ }
+
+ MetisNumberSet *test = metisNumberSet_Subtract(a, b);
+
+ bool equal = metisNumberSet_Equals(truth, test);
+
+ metisNumberSet_Release(&a);
+ metisNumberSet_Release(&b);
+ metisNumberSet_Release(&truth);
+ metisNumberSet_Release(&test);
+
+ assertTrue(equal, "subtraction result incorrect for disjoint sets");
+}
+
+LONGBOW_TEST_CASE(Global, metisNumberSet_Subtract_Overlap)
+{
+ // 0 is the terminator
+ unsigned a_set[] = { 1, 2, 3, 4, 5, 6, 7, 0 };
+ unsigned b_set[] = { 1, 2, 3, 4, 5, 0 };
+ unsigned truth_set[] = { 6, 7, 0 };
+
+ MetisNumberSet *a = metisNumberSet_Create();
+ MetisNumberSet *b = metisNumberSet_Create();
+ MetisNumberSet *truth = metisNumberSet_Create();
+
+ for (int i = 0; a_set[i] != 0; i++) {
+ metisNumberSet_Add(a, a_set[i]);
+ }
+
+ for (int i = 0; b_set[i] != 0; i++) {
+ metisNumberSet_Add(b, b_set[i]);
+ }
+
+ for (int i = 0; truth_set[i] != 0; i++) {
+ metisNumberSet_Add(truth, truth_set[i]);
+ }
+
+ MetisNumberSet *test = metisNumberSet_Subtract(a, b);
+
+ bool equal = metisNumberSet_Equals(truth, test);
+
+ metisNumberSet_Release(&a);
+ metisNumberSet_Release(&b);
+ metisNumberSet_Release(&truth);
+ metisNumberSet_Release(&test);
+
+ assertTrue(equal, "subtraction result incorrect for disjoint sets");
+}
+
+LONGBOW_TEST_CASE(Global, metisNumberSet_Remove_LastElement)
+{
+ // 0 is the terminator
+ unsigned a_set[] = { 1, 2, 3, 4, 5, 6, 7, 0 };
+ unsigned to_remove = 7;
+ unsigned truth_set[] = { 1, 2, 3, 4, 5, 6, 0 };
+
+ MetisNumberSet *a = metisNumberSet_Create();
+ MetisNumberSet *truth = metisNumberSet_Create();
+
+ for (int i = 0; a_set[i] != 0; i++) {
+ metisNumberSet_Add(a, a_set[i]);
+ }
+
+ for (int i = 0; truth_set[i] != 0; i++) {
+ metisNumberSet_Add(truth, truth_set[i]);
+ }
+
+ metisNumberSet_Remove(a, to_remove);
+
+ bool equal = metisNumberSet_Equals(truth, a);
+
+ metisNumberSet_Release(&a);
+ metisNumberSet_Release(&truth);
+
+ assertTrue(equal, "Removing element gives incorrect set");
+}
+
+LONGBOW_TEST_CASE(Global, metisNumberSet_Remove_AllElements)
+{
+ // 0 is the terminator
+ unsigned a_set[] = { 1, 0 };
+ unsigned to_remove = 1;
+ unsigned truth_set[] = { 0 };
+
+ MetisNumberSet *a = metisNumberSet_Create();
+ MetisNumberSet *truth = metisNumberSet_Create();
+
+ for (int i = 0; a_set[i] != 0; i++) {
+ metisNumberSet_Add(a, a_set[i]);
+ }
+
+ for (int i = 0; truth_set[i] != 0; i++) {
+ metisNumberSet_Add(truth, truth_set[i]);
+ }
+
+ metisNumberSet_Remove(a, to_remove);
+
+ bool equal = metisNumberSet_Equals(truth, a);
+
+ metisNumberSet_Release(&a);
+ metisNumberSet_Release(&truth);
+
+ assertTrue(equal, "Removing element gives incorrect set");
+}
+
+LONGBOW_TEST_CASE(Global, metisNumberSet_Remove_FirstElement)
+{
+ // 0 is the terminator
+ unsigned a_set[] = { 1, 2, 3, 4, 5, 6, 7, 0 };
+ unsigned to_remove = 1;
+ unsigned truth_set[] = { 2, 3, 4, 5, 6, 7, 0 };
+
+ MetisNumberSet *a = metisNumberSet_Create();
+ MetisNumberSet *truth = metisNumberSet_Create();
+
+ for (int i = 0; a_set[i] != 0; i++) {
+ metisNumberSet_Add(a, a_set[i]);
+ }
+
+ for (int i = 0; truth_set[i] != 0; i++) {
+ metisNumberSet_Add(truth, truth_set[i]);
+ }
+
+ metisNumberSet_Remove(a, to_remove);
+
+ bool equal = metisNumberSet_Equals(truth, a);
+
+ metisNumberSet_Release(&a);
+ metisNumberSet_Release(&truth);
+
+ assertTrue(equal, "Removing element gives incorrect set");
+}
+
+// ======================================================================================
+
+LONGBOW_TEST_FIXTURE(Local)
+{
+ LONGBOW_RUN_TEST_CASE(Local, metisNumberSet_Expand);
+ LONGBOW_RUN_TEST_CASE(Local, metisNumberSet_AddNoChecks);
+}
+
+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, metisNumberSet_Expand)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+LONGBOW_TEST_CASE(Local, metisNumberSet_AddNoChecks)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+int
+main(int argc, char *argv[])
+{
+ LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_NumberSet);
+ int exitStatus = longBowMain(argc, argv, testRunner, NULL);
+ longBowTestRunner_Destroy(&testRunner);
+ exit(exitStatus);
+}
diff --git a/metis/ccnx/forwarder/metis/core/test/test_metis_StreamBuffer.c b/metis/ccnx/forwarder/metis/core/test/test_metis_StreamBuffer.c
new file mode 100644
index 00000000..09bd9401
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/test/test_metis_StreamBuffer.c
@@ -0,0 +1,134 @@
+/*
+ * 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_StreamBuffer.c"
+#include <LongBow/unit-test.h>
+#include <parc/algol/parc_SafeMemory.h>
+
+LONGBOW_TEST_RUNNER(metis_StreamBuffer)
+{
+ // 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_StreamBuffer)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// The Test Runner calls this function once after all the Test Fixtures are run.
+LONGBOW_TEST_RUNNER_TEARDOWN(metis_StreamBuffer)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE(Global)
+{
+ LONGBOW_RUN_TEST_CASE(Global, metisStreamBuffer_Destroy);
+ LONGBOW_RUN_TEST_CASE(Global, metisStreamBuffer_DisableCallbacks);
+ LONGBOW_RUN_TEST_CASE(Global, metisStreamBuffer_EnableCallbacks);
+ LONGBOW_RUN_TEST_CASE(Global, metisStreamBuffer_Flush);
+ LONGBOW_RUN_TEST_CASE(Global, metisStreamBuffer_FlushCheckpoint);
+ LONGBOW_RUN_TEST_CASE(Global, metisStreamBuffer_FlushFinished);
+ LONGBOW_RUN_TEST_CASE(Global, metisStreamBuffer_SetCallbacks);
+ LONGBOW_RUN_TEST_CASE(Global, metisStreamBuffer_SetWatermark);
+}
+
+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, metisStreamBuffer_Destroy)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+LONGBOW_TEST_CASE(Global, metisStreamBuffer_DisableCallbacks)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+LONGBOW_TEST_CASE(Global, metisStreamBuffer_EnableCallbacks)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+LONGBOW_TEST_CASE(Global, metisStreamBuffer_Flush)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+LONGBOW_TEST_CASE(Global, metisStreamBuffer_FlushCheckpoint)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+LONGBOW_TEST_CASE(Global, metisStreamBuffer_FlushFinished)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+LONGBOW_TEST_CASE(Global, metisStreamBuffer_SetCallbacks)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+LONGBOW_TEST_CASE(Global, metisStreamBuffer_SetWatermark)
+{
+ testUnimplemented("This test is unimplemented");
+}
+
+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_StreamBuffer);
+ int exitStatus = longBowMain(argc, argv, testRunner, NULL);
+ longBowTestRunner_Destroy(&testRunner);
+ exit(exitStatus);
+}
diff --git a/metis/ccnx/forwarder/metis/core/test/test_metis_ThreadedForwarder.c b/metis/ccnx/forwarder/metis/core/test/test_metis_ThreadedForwarder.c
new file mode 100644
index 00000000..75579900
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/test/test_metis_ThreadedForwarder.c
@@ -0,0 +1,145 @@
+/*
+ * 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_ThreadedForwarder.c"
+
+#include <LongBow/unit-test.h>
+
+LONGBOW_TEST_RUNNER(metis_ThreadedForwarder)
+{
+ // 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_ThreadedForwarder)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+// The Test Runner calls this function once after all the Test Fixtures are run.
+LONGBOW_TEST_RUNNER_TEARDOWN(metis_ThreadedForwarder)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE(Global)
+{
+ LONGBOW_RUN_TEST_CASE(Global, metisThreadedForwarder_AddCLI);
+ LONGBOW_RUN_TEST_CASE(Global, metisThreadedForwarder_Create);
+ LONGBOW_RUN_TEST_CASE(Global, metisThreadedForwarder_Destroy);
+ LONGBOW_RUN_TEST_CASE(Global, metisThreadedForwarder_SetupAllListeners);
+ LONGBOW_RUN_TEST_CASE(Global, metisThreadedForwarder_Start);
+ LONGBOW_RUN_TEST_CASE(Global, metisThreadedForwarder_Stop);
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(Global)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(Global)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_CASE(Global, metisThreadedForwarder_AddCLI)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Global, metisThreadedForwarder_Create)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Global, metisThreadedForwarder_Destroy)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Global, metisThreadedForwarder_SetupAllListeners)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Global, metisThreadedForwarder_Start)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Global, metisThreadedForwarder_Stop)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_FIXTURE(Local)
+{
+ LONGBOW_RUN_TEST_CASE(Local, metisThreadedForwarder_BroadcastStatus);
+ LONGBOW_RUN_TEST_CASE(Local, metisThreadedForwarder_LockState);
+ LONGBOW_RUN_TEST_CASE(Local, metisThreadedForwarder_Run);
+ LONGBOW_RUN_TEST_CASE(Local, metisThreadedForwarder_UnlockState);
+ LONGBOW_RUN_TEST_CASE(Local, metisThreadedForwarder_WaitStatus);
+}
+
+LONGBOW_TEST_FIXTURE_SETUP(Local)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_FIXTURE_TEARDOWN(Local)
+{
+ return LONGBOW_STATUS_SUCCEEDED;
+}
+
+LONGBOW_TEST_CASE(Local, metisThreadedForwarder_BroadcastStatus)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Local, metisThreadedForwarder_LockState)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Local, metisThreadedForwarder_Run)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Local, metisThreadedForwarder_UnlockState)
+{
+ testUnimplemented("");
+}
+
+LONGBOW_TEST_CASE(Local, metisThreadedForwarder_WaitStatus)
+{
+ testUnimplemented("");
+}
+
+int
+main(int argc, char *argv[])
+{
+ LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_ThreadedForwarder);
+ int exitStatus = longBowMain(argc, argv, testRunner, NULL);
+ longBowTestRunner_Destroy(&testRunner);
+ exit(exitStatus);
+}
diff --git a/metis/ccnx/forwarder/metis/core/test/testrig_MetisIoOperations.h b/metis/ccnx/forwarder/metis/core/test/testrig_MetisIoOperations.h
new file mode 100644
index 00000000..80735862
--- /dev/null
+++ b/metis/ccnx/forwarder/metis/core/test/testrig_MetisIoOperations.h
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef Metis_testrig_MetisIoOperations_h
+#define Metis_testrig_MetisIoOperations_h
+
+/**
+ * Setup a test rig around a MetisIoOperation so we have visibility in to
+ * what the connection table is doing
+ *
+ * Usage: Use <code>mockIoOperationsData_Create()</code> or <code>mockIoOperationsData_CreateSimple()</code>
+ * to create the MetisIoOperations. You can then inspect the TestData inside the context
+ * by mapping <code>TestData *data = (TestData *) metisIoOperations_GetClosure(ops)</code>.
+ *
+ * IMPORTANT: ops->destroy(&ops) will not destroy the test rig. It will increment a counter.
+ * you must call <code>testdata_Destroy(&ops)</code> yourself. You should call this
+ * as the very last thing, even after <code>metisForwarder_Destroy()</code>, if you put
+ * the MetisIoOpereations in the connection table.
+ */
+static bool mockIoOperations_Send(MetisIoOperations *ops, const CPIAddress *nexthop, MetisMessage *message);
+static const CPIAddress *mockIoOperations_GetRemoteAddress(const MetisIoOperations *ops);
+static const MetisAddressPair *mockIoOperations_GetAddressPair(const MetisIoOperations *ops);
+static bool mockIoOperations_IsUp(const MetisIoOperations *ops);
+static bool mockIoOperations_IsLocal(const MetisIoOperations *ops);
+static unsigned mockIoOperations_GetConnectionId(const MetisIoOperations *ops);
+static void mockIoOperations_Destroy(MetisIoOperations **opsPtr);
+static CPIConnectionType mockIoOperations_GetConnectionType(const MetisIoOperations *ops);
+static const void *mockIoOperations_Class(const MetisIoOperations *ops);
+
+static MetisIoOperations mockIoOperationsTemplate = {
+ .closure = NULL,
+ .send = &mockIoOperations_Send,
+ .getRemoteAddress = &mockIoOperations_GetRemoteAddress,
+ .getAddressPair = &mockIoOperations_GetAddressPair,
+ .isUp = &mockIoOperations_IsUp,
+ .isLocal = &mockIoOperations_IsLocal,
+ .getConnectionId = &mockIoOperations_GetConnectionId,
+ .destroy = &mockIoOperations_Destroy,
+ .getConnectionType = &mockIoOperations_GetConnectionType,
+ .class = &mockIoOperations_Class
+};
+
+typedef struct mock_io_operations_data {
+ // counters for each call
+ unsigned sendCount;
+ unsigned getRemoteAddressCount;
+ unsigned getAddressPairCount;
+ unsigned isUpCount;
+ unsigned isLocalCount;
+ unsigned getConnectionIdCount;
+ unsigned destroyCount;
+ unsigned getConnectionTypeCount;
+ unsigned classCount;
+
+ MetisMessage *lastMessage;
+ MetisAddressPair *addressPair;
+ unsigned id;
+ bool isUp;
+ bool isLocal;
+ bool sendResult; // what to return when send() called
+ CPIConnectionType connType;
+} MockIoOperationsData;
+
+/**
+ * @function testdata_Create
+ * @abstract Creates a data set for testing MetisIoOperations
+ * @discussion
+ * Caller must explicitly use <code>testdata_Destroy()</code> when done. Calling the destroyer through
+ * the io operations only increments counters, it does not destroy the object.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+static MetisIoOperations *
+mockIoOperationsData_Create(MetisAddressPair *pair, unsigned id, bool isUp, bool sendResult, bool isLocal, CPIConnectionType connType)
+{
+ MockIoOperationsData *data = parcMemory_AllocateAndClear(sizeof(MockIoOperationsData));
+ data->addressPair = pair;
+ data->id = id;
+ data->isUp = isUp;
+ data->sendResult = sendResult;
+ data->lastMessage = NULL;
+ data->isLocal = isLocal;
+ data->connType = connType;
+
+ MetisIoOperations *ops = parcMemory_AllocateAndClear(sizeof(MetisIoOperations));
+ memcpy(ops, &mockIoOperationsTemplate, sizeof(MetisIoOperations));
+ ops->closure = data;
+
+ return ops;
+}
+
+/**
+ * @function testdata_CreateSimple
+ * @abstract Creates a data set for testing MetisIoOperations
+ * @discussion
+ * Caller must explicitly use <code>testdata_Destroy()</code> when done. Calling the destroyer through
+ * the io operations only increments counters, it does not destroy the object.
+ *
+ * @param <#param1#>
+ * @return <#return#>
+ */
+static MetisIoOperations *
+mockIoOperationsData_CreateSimple(unsigned addressLocal, unsigned addressRemote, unsigned id, bool isUp, bool sendResult, bool isLocal)
+{
+ CPIAddress *local = cpiAddress_CreateFromInterface(addressLocal);
+ CPIAddress *remote = cpiAddress_CreateFromInterface(addressRemote);
+ MetisAddressPair *pair = metisAddressPair_Create(local, remote);
+ MetisIoOperations *ops = mockIoOperationsData_Create(pair, id, isUp, sendResult, isLocal, cpiConnection_UDP);
+ cpiAddress_Destroy(&local);
+ cpiAddress_Destroy(&remote);
+ return ops;
+}
+
+static void
+mockIoOperationsData_Destroy(MetisIoOperations **opsPtr)
+{
+ MetisIoOperations *ops = *opsPtr;
+ MockIoOperationsData *data = (MockIoOperationsData *) metisIoOperations_GetClosure(ops);
+
+ metisAddressPair_Release(&data->addressPair);
+ if (data->lastMessage) {
+ metisMessage_Release(&data->lastMessage);
+ }
+ parcMemory_Deallocate((void **) &data);
+ ops->closure = NULL;
+ parcMemory_Deallocate((void **) &ops);
+ *opsPtr = NULL;
+}
+
+static bool
+mockIoOperations_Send(MetisIoOperations *ops, const CPIAddress *nexthop, MetisMessage *message)
+{
+ MockIoOperationsData *data = (MockIoOperationsData *) metisIoOperations_GetClosure(ops);
+ data->sendCount++;
+
+ if (message) {
+ if (data->lastMessage) {
+ metisMessage_Release(&data->lastMessage);
+ }
+
+ data->lastMessage = metisMessage_Acquire(message);
+ }
+
+ return data->sendResult;
+}
+
+static const CPIAddress *
+mockIoOperations_GetRemoteAddress(const MetisIoOperations *ops)
+{
+ MockIoOperationsData *data = (MockIoOperationsData *) metisIoOperations_GetClosure(ops);
+ data->getRemoteAddressCount++;
+ return metisAddressPair_GetRemote(data->addressPair);
+}
+
+static const MetisAddressPair *
+mockIoOperations_GetAddressPair(const MetisIoOperations *ops)
+{
+ MockIoOperationsData *data = (MockIoOperationsData *) metisIoOperations_GetClosure(ops);
+ data->getAddressPairCount++;
+ return data->addressPair;
+}
+
+static bool
+mockIoOperations_IsUp(const MetisIoOperations *ops)
+{
+ MockIoOperationsData *data = (MockIoOperationsData *) metisIoOperations_GetClosure(ops);
+ data->isUpCount++;
+ return data->isUp;
+}
+
+static bool
+mockIoOperations_IsLocal(const MetisIoOperations *ops)
+{
+ MockIoOperationsData *data = (MockIoOperationsData *) metisIoOperations_GetClosure(ops);
+ data->isLocalCount++;
+ return data->isLocal;
+}
+
+
+static unsigned
+mockIoOperations_GetConnectionId(const MetisIoOperations *ops)
+{
+ MockIoOperationsData *data = (MockIoOperationsData *) metisIoOperations_GetClosure(ops);
+ data->getConnectionIdCount++;
+ return data->id;
+}
+
+static void
+mockIoOperations_Destroy(MetisIoOperations **opsPtr)
+{
+ MockIoOperationsData *data = (MockIoOperationsData *) (*opsPtr)->closure;
+ data->destroyCount++;
+ *opsPtr = NULL;
+}
+
+static CPIConnectionType
+mockIoOperations_GetConnectionType(const MetisIoOperations *ops)
+{
+ MockIoOperationsData *data = (MockIoOperationsData *) metisIoOperations_GetClosure(ops);
+ data->getConnectionTypeCount++;
+ return data->connType;
+}
+
+static const void *
+mockIoOperations_Class(const MetisIoOperations *ops)
+{
+ MockIoOperationsData *data = (MockIoOperationsData *) metisIoOperations_GetClosure(ops);
+ data->classCount++;
+ return __FILE__;
+}
+#endif // Metis_testrig_MetisIoOperations_h