diff options
Diffstat (limited to 'metis/ccnx/forwarder/metis/core')
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(©); + * 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(©); + 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(©); +} + +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(©); + + 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(©); + 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 |