diff options
Diffstat (limited to 'metis/ccnx/forwarder/metis/io')
50 files changed, 11762 insertions, 0 deletions
diff --git a/metis/ccnx/forwarder/metis/io/metis_AddressPair.c b/metis/ccnx/forwarder/metis/io/metis_AddressPair.c new file mode 100644 index 00000000..7725281f --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_AddressPair.c @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <config.h> +#include <stdio.h> + +#include <ccnx/forwarder/metis/io/metis_AddressPair.h> +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_Hash.h> +#include <parc/algol/parc_Object.h> + +#include <LongBow/runtime.h> + +struct metis_address_pair { + CPIAddress *local; + CPIAddress *remote; +}; + +static void +_metisAddressPair_Destroy(MetisAddressPair **addressPairPtr) +{ + MetisAddressPair *pair = *addressPairPtr; + + cpiAddress_Destroy(&pair->local); + cpiAddress_Destroy(&pair->remote); +} + +parcObject_ExtendPARCObject(MetisAddressPair, _metisAddressPair_Destroy, + NULL, metisAddressPair_ToString, metisAddressPair_Equals, NULL, metisAddressPair_HashCode, NULL); + +parcObject_ImplementAcquire(metisAddressPair, MetisAddressPair); + +parcObject_ImplementRelease(metisAddressPair, MetisAddressPair); + +MetisAddressPair * +metisAddressPair_Create(const CPIAddress *local, const CPIAddress *remote) +{ + assertNotNull(local, "Parameter local must be non-null"); + assertNotNull(remote, "Parameter remote must be non-null"); + + MetisAddressPair *pair = parcObject_CreateInstance(MetisAddressPair); + assertNotNull(pair, "Got null from parcObject_Create()"); + + pair->local = cpiAddress_Copy(local); + pair->remote = cpiAddress_Copy(remote); + + return pair; +} + +bool +metisAddressPair_Equals(const MetisAddressPair *a, const MetisAddressPair *b) +{ + if (a == b) { + return true; + } + if (a == NULL || b == NULL) { + return false; + } + + if (cpiAddress_Equals(a->local, b->local)) { + if (cpiAddress_Equals(a->remote, b->remote)) { + return true; + } + } + + return false; +} + +bool +metisAddressPair_EqualsAddresses(const MetisAddressPair *a, const CPIAddress *local, const CPIAddress *remote) +{ + if (a == NULL || local == NULL || remote == NULL) { + return false; + } + + if (cpiAddress_Equals(a->local, local)) { + if (cpiAddress_Equals(a->remote, remote)) { + return true; + } + } + + return false; +} + +char * +metisAddressPair_ToString(const MetisAddressPair *pair) +{ + assertNotNull(pair, "Parameter pair must be non-null"); + + char *local = cpiAddress_ToString(pair->local); + char *remote = cpiAddress_ToString(pair->remote); + + char *output; + int failure = asprintf(&output, "{ .local=%s, .remote=%s }", local, remote); + assertTrue(failure > -1, "Error on asprintf"); + + parcMemory_Deallocate((void **) &local); + parcMemory_Deallocate((void **) &remote); + + return output; +} + +const CPIAddress * +metisAddressPair_GetLocal(const MetisAddressPair *pair) +{ + assertNotNull(pair, "Parameter pair must be non-null"); + return pair->local; +} + +const CPIAddress * +metisAddressPair_GetRemote(const MetisAddressPair *pair) +{ + assertNotNull(pair, "Parameter pair must be non-null"); + return pair->remote; +} + +/** + * @function metisAddressPair_HashCode + * @abstract Hash useful for tables. Consistent with Equals. + * @discussion + * Returns a non-cryptographic hash that is consistent with equals. That is, + * if a == b, then hash(a) == hash(b). + * + * @param <#param1#> + * @return <#return#> + */ +PARCHashCode +metisAddressPair_HashCode(const MetisAddressPair *pair) +{ + PARCHashCode hashpair[2]; + hashpair[0] = cpiAddress_HashCode(pair->local); + hashpair[1] = cpiAddress_HashCode(pair->remote); + return parcHashCode_Hash((const uint8_t *) hashpair, sizeof(hashpair)); +// return parcHash32_Data(hashpair, sizeof(hashpair)); +} diff --git a/metis/ccnx/forwarder/metis/io/metis_AddressPair.h b/metis/ccnx/forwarder/metis/io/metis_AddressPair.h new file mode 100644 index 00000000..a6678f23 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_AddressPair.h @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +/** + * Used to identify a connection between a specific local address and + * a specific remote address. + */ + +#ifndef Metis_metis_AddressPair_h +#define Metis_metis_AddressPair_h + +#include <ccnx/api/control/cpi_Address.h> + +struct metis_address_pair; +typedef struct metis_address_pair MetisAddressPair; + +/** + * @function metisAddressPair_Create + * @abstract Creates and address pair. There is no restriction on the address types. + * @discussion + * Creates an ordered pair of addresses, where the first is considered the "local" address + * and the second is the "remote" address. Those designations are purely a convention used + * to name them, and does not imply any specifici types of operations. + * + * The two addresses may be of any address types (e.g. IPv4, IPv6, Local, Ethernet). + * However, some functions that use an AddressPair may require that the local and remote + * addresses be the same type. + * + * @param <#param1#> + * @return <#return#> + */ +MetisAddressPair *metisAddressPair_Create(const CPIAddress *local, const CPIAddress *remote); + +/** + * Returns a reference counted copy of the address pair + * + * Increments the reference count and returns the same address pair + * + * @param [in] addressPair An allocated address pair + * + * @retval non-null A reference counted copy + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisAddressPair *metisAddressPair_Acquire(const MetisAddressPair *addressPair); + + +/** + * Releases a reference count to the object + * + * Decrements the reference count and destroys the object when it reaches 0. + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisAddressPair_Release(MetisAddressPair **pairPtr); + +/** + * Determine if two MetisAddressPair instances are equal. + * + * Two MetisAddressPair instances are equal if, and only if, the local and remote addresses are identical. + * Equality is determined by cpiAddress_Equals(a->local, b->local) and + * cpiAdress_Equals(a->remote, b->remote). + * + * The following equivalence relations on non-null `MetisAddressPair` instances are maintained: + * + * * It is reflexive: for any non-null reference value x, `MetisAddressPair_Equals(x, x)` + * must return true. + * + * * It is symmetric: for any non-null reference values x and y, + * `metisAddressPair_Equals(x, y)` must return true if and only if + * `metisAddressPair_Equals(y, x)` returns true. + * + * * It is transitive: for any non-null reference values x, y, and z, if + * `metisAddressPair_Equals(x, y)` returns true and + * `metisAddressPair_Equals(y, z)` returns true, + * then `metisAddressPair_Equals(x, z)` must return true. + * + * * It is consistent: for any non-null reference values x and y, multiple + * invocations of `metisAddressPair_Equals(x, y)` consistently return true or + * consistently return false. + * + * * For any non-null reference value x, `metisAddressPair_Equals(x, NULL)` must + * return false. + * + * @param a A pointer to a `MetisAddressPair` instance. + * @param b A pointer to a `MetisAddressPair` instance. + * @return true if the two `MetisAddressPair` instances are equal. + * + * Example: + * @code + * { + * MetisAddressPair *a = metisAddressPair_Create(); + * MetisAddressPair *b = metisAddressPair_Create(); + * + * if (metisAddressPair_Equals(a, b)) { + * // true + * } else { + * // false + * } + * } + * @endcode + */ +bool metisAddressPair_Equals(const MetisAddressPair *a, const MetisAddressPair *b); + +/** + * @function metisAddressPair_EqualsAddresses + * @abstract As MetisAddressEquals, but "b" is broken out + * @discussion + * Equality is determined by cpiAddress_Equals(a->local, local) and + * cpiAdress_Equals(a->remote, remote). + * + * @param <#param1#> + * @return <#return#> + */ +bool metisAddressPair_EqualsAddresses(const MetisAddressPair *a, const CPIAddress *local, const CPIAddress *remote); + +const CPIAddress *metisAddressPair_GetLocal(const MetisAddressPair *pair); +const CPIAddress *metisAddressPair_GetRemote(const MetisAddressPair *pair); + +/** + * @function metisAddressPair_HashCode + * @abstract Hash useful for tables. Consistent with Equals. + * @discussion + * Returns a non-cryptographic hash that is consistent with equals. That is, + * if a == b, then hash(a) == hash(b). + * + * @param <#param1#> + * @return <#return#> + */ +PARCHashCode metisAddressPair_HashCode(const MetisAddressPair *pair); + +/** + * @function metisAddressPair_ToString + * @abstract Human readable string representation. Caller must use free(3). + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +char *metisAddressPair_ToString(const MetisAddressPair *pair); +#endif // Metis_metis_AddressPair_h diff --git a/metis/ccnx/forwarder/metis/io/metis_EtherConnection.c b/metis/ccnx/forwarder/metis/io/metis_EtherConnection.c new file mode 100644 index 00000000..63e9ab83 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_EtherConnection.c @@ -0,0 +1,388 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Embodies the reader/writer for an Ethernet connection + * + * TODO: Put in a maintenance timer to timeout MAC entries if not used. Need to add a function + * the listener can call to bump up activity when a packet is received. + * + */ + +#include <config.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <net/ethernet.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Hash.h> +#include <parc/algol/parc_Memory.h> + + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_Connection.h> +#include <ccnx/forwarder/metis/core/metis_Message.h> +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> +#include <ccnx/forwarder/metis/io/metis_EtherConnection.h> +#include <ccnx/forwarder/metis/messenger/metis_MissiveType.h> +#include <ccnx/forwarder/metis/io/metis_HopByHopFragmenter.h> + +typedef struct metis_ether_state { + MetisForwarder *metis; + MetisLogger *logger; + + // the udp listener socket we receive packets on + MetisGenericEther *ether; + + MetisAddressPair *addressPair; + + MetisHopByHopFragmenter *fragmenter; + + // We need to access this all the time, so grab it out + // of the addressPair; + uint8_t myAddress[ETHER_ADDR_LEN]; + uint8_t peerAddress[ETHER_ADDR_LEN]; + uint16_t networkOrderEtherType; + + bool isUp; + unsigned id; +} _MetisEtherState; + +// Prototypes +static bool _metisEtherConnection_Send(MetisIoOperations *ops, const CPIAddress *nexthop, MetisMessage *message); +static const CPIAddress *_metisEtherConnection_GetRemoteAddress(const MetisIoOperations *ops); +static const MetisAddressPair *_metisEtherConnection_GetAddressPair(const MetisIoOperations *ops); +static unsigned _metisEtherConnection_GetConnectionId(const MetisIoOperations *ops); +static bool _metisEtherConnection_IsUp(const MetisIoOperations *ops); +static bool _metisEtherConnection_IsLocal(const MetisIoOperations *ops); +static void _metisEtherConnection_Release(MetisIoOperations **opsPtr); + +static void _setConnectionState(_MetisEtherState *ether, bool isUp); +static CPIConnectionType _metisEtherConnection_getConnectionType(const MetisIoOperations *ops); +static MetisTicks _sendProbe(MetisIoOperations *ops, unsigned probeType); + +/* + * This assigns a unique pointer to the void * which we use + * as a GUID for this class. + */ +static const void *_metisIoOperationsGuid = __FILE__; + +/* + * Return our GUID + */ +static const void * +_metisEtherConnection_Class(const MetisIoOperations *ops) +{ + return _metisIoOperationsGuid; +} + +static MetisIoOperations _template = { + .closure = NULL, + .send = &_metisEtherConnection_Send, + .getRemoteAddress = &_metisEtherConnection_GetRemoteAddress, + .getAddressPair = &_metisEtherConnection_GetAddressPair, + .getConnectionId = &_metisEtherConnection_GetConnectionId, + .isUp = &_metisEtherConnection_IsUp, + .isLocal = &_metisEtherConnection_IsLocal, + .destroy = &_metisEtherConnection_Release, + .class = &_metisEtherConnection_Class, + .getConnectionType = &_metisEtherConnection_getConnectionType, + .sendProbe = &_sendProbe, +}; + +// ================================================================= + +static bool +_metisEtherConnection_FillInMacAddress(uint8_t *mac, const CPIAddress *source) +{ + PARCBuffer *macAddress = cpiAddress_GetLinkAddress(source); + if (macAddress) { + parcBuffer_GetBytes(macAddress, ETHER_ADDR_LEN, mac); + parcBuffer_Rewind(macAddress); + return true; + } + return false; +} + +MetisIoOperations * +metisEtherConnection_Create(MetisForwarder *metis, MetisGenericEther *ether, MetisAddressPair *pair) +{ + bool success = false; + size_t allocationSize = sizeof(_MetisEtherState) + sizeof(MetisIoOperations); + MetisIoOperations *ops = parcMemory_AllocateAndClear(allocationSize); + if (ops) { + memcpy(ops, &_template, sizeof(MetisIoOperations)); + ops->closure = (uint8_t *) ops + sizeof(MetisIoOperations); + + _MetisEtherState *etherConnState = metisIoOperations_GetClosure(ops); + + if (_metisEtherConnection_FillInMacAddress(etherConnState->myAddress, metisAddressPair_GetLocal(pair))) { + if (_metisEtherConnection_FillInMacAddress(etherConnState->peerAddress, metisAddressPair_GetRemote(pair))) { + etherConnState->metis = metis; + etherConnState->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + etherConnState->ether = metisGenericEther_Acquire(ether); + etherConnState->id = metisForwarder_GetNextConnectionId(metis); + etherConnState->addressPair = metisAddressPair_Acquire(pair); + etherConnState->networkOrderEtherType = htons(metisGenericEther_GetEtherType(ether)); + etherConnState->fragmenter = metisHopByHopFragmenter_Create(etherConnState->logger, metisGenericEther_GetMTU(ether)); + + _setConnectionState(etherConnState, true); + + if (metisLogger_IsLoggable(etherConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + char *str = metisAddressPair_ToString(pair); + metisLogger_Log(etherConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "EtherConnection %p created address pair %s", + (void *) etherConnState, str); + free(str); + } + + metisMessenger_Send(metisForwarder_GetMessenger(metis), metisMissive_Create(MetisMissiveType_ConnectionCreate, etherConnState->id)); + metisMessenger_Send(metisForwarder_GetMessenger(metis), metisMissive_Create(MetisMissiveType_ConnectionUp, etherConnState->id)); + + success = true; + } + } + } + + if (ops && !success) { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error)) { + char *str = metisAddressPair_ToString(pair); + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Error creating EtherConnection address pair %s", + str); + free(str); + } + + parcMemory_Deallocate(&ops); + } + + return ops; +} + +// ================================================================= +// I/O Operations implementation + +/* + * We only do one allocation of the combined MetisIoOperations and _MetisEtherState, so this + * function only needs to destroy the internal state + */ +static void +_metisEtherConnection_InternalRelease(_MetisEtherState *etherConnState) +{ + if (metisLogger_IsLoggable(etherConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(etherConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "EtherConnection %p destroyed", + (void *) etherConnState); + } + + metisMessenger_Send(metisForwarder_GetMessenger(etherConnState->metis), metisMissive_Create(MetisMissiveType_ConnectionDestroyed, etherConnState->id)); + + metisAddressPair_Release(ðerConnState->addressPair); + metisGenericEther_Release(ðerConnState->ether); + metisHopByHopFragmenter_Release(ðerConnState->fragmenter); + metisLogger_Release(ðerConnState->logger); +} + +static void +_metisEtherConnection_Release(MetisIoOperations **opsPtr) +{ + assertNotNull(opsPtr, "Parameter opsPtr must be non-null double pointer"); + assertNotNull(*opsPtr, "Parameter opsPtr must dereference to non-null pointer"); + + MetisIoOperations *ops = *opsPtr; + assertNotNull(metisIoOperations_GetClosure(ops), "ops->context must not be null"); + + _MetisEtherState *etherConnState = (_MetisEtherState *) metisIoOperations_GetClosure(ops); + _metisEtherConnection_InternalRelease(etherConnState); + + // do not close udp->udpListenerSocket, the listener will close + // that when its done + + parcMemory_Deallocate((void **) &ops); +} + +static bool +_metisEtherConnection_IsUp(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisEtherState *etherConnState = (const _MetisEtherState *) metisIoOperations_GetClosure(ops); + return etherConnState->isUp; +} + +static bool +_metisEtherConnection_IsLocal(const MetisIoOperations *ops) +{ + return false; +} + +static const CPIAddress * +_metisEtherConnection_GetRemoteAddress(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisEtherState *etherConnState = (const _MetisEtherState *) metisIoOperations_GetClosure(ops); + return metisAddressPair_GetRemote(etherConnState->addressPair); +} + +static const MetisAddressPair * +_metisEtherConnection_GetAddressPair(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisEtherState *etherConnState = (const _MetisEtherState *) metisIoOperations_GetClosure(ops); + return etherConnState->addressPair; +} + + +static unsigned +_metisEtherConnection_GetConnectionId(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisEtherState *etherConnState = (const _MetisEtherState *) metisIoOperations_GetClosure(ops); + return etherConnState->id; +} + +static bool +_sendFrame(_MetisEtherState *etherConnState, MetisMessage *message) +{ + PARCEventBuffer *writeBuffer = parcEventBuffer_Create(); + + int failure = metisMessage_Append(writeBuffer, message); + if (failure) { + parcEventBuffer_Destroy(&writeBuffer); + return false; + } + + // Add an Ethernet header + struct ether_header header; + + // Fill in the ethernet header + header.ether_type = etherConnState->networkOrderEtherType; + memcpy(header.ether_dhost, etherConnState->peerAddress, ETHER_ADDR_LEN); + memcpy(header.ether_shost, etherConnState->myAddress, ETHER_ADDR_LEN); + + // and put it at the front of the output buffer + parcEventBuffer_Prepend(writeBuffer, &header, sizeof(header)); + + bool success = metisGenericEther_SendFrame(etherConnState->ether, writeBuffer); + + // we're done with the buffer + parcEventBuffer_Destroy(&writeBuffer); + + // BugzID: 3343 - close the connection on certain errors?? + return success; +} + +/** + * @function metisEtherConnection_Send + * @abstract Non-destructive send of the message. + * @discussion + * sends a message to the peer. + * + * @param dummy is ignored. A udp connection has only one peer. + * @return <#return#> + */ +static bool +_metisEtherConnection_Send(MetisIoOperations *ops, const CPIAddress *dummy, MetisMessage *message) +{ + assertNotNull(ops, "Parameter ops must be non-null"); + assertNotNull(message, "Parameter message must be non-null"); + _MetisEtherState *etherConnState = (_MetisEtherState *) metisIoOperations_GetClosure(ops); + + bool success = metisHopByHopFragmenter_Send(etherConnState->fragmenter, message); + + MetisMessage *fragment; + while (success && (fragment = metisHopByHopFragmenter_PopSendQueue(etherConnState->fragmenter)) != NULL) { + success = _sendFrame(etherConnState, fragment); + metisMessage_Release(&fragment); + } + + // if we failed, drain the other fragments + if (!success) { + while ((fragment = metisHopByHopFragmenter_PopSendQueue(etherConnState->fragmenter)) != NULL) { + metisMessage_Release(&fragment); + } + } + + return success; +} + +static void +_setConnectionState(_MetisEtherState *etherConnState, bool isUp) +{ + assertNotNull(etherConnState, "Parameter Udp must be non-null"); + + MetisMessenger *messenger = metisForwarder_GetMessenger(etherConnState->metis); + + bool oldStateIsUp = etherConnState->isUp; + etherConnState->isUp = isUp; + + if (oldStateIsUp && !isUp) { + // bring connection DOWN + MetisMissive *missive = metisMissive_Create(MetisMissiveType_ConnectionDown, etherConnState->id); + metisMessenger_Send(messenger, missive); + return; + } + + + if (!oldStateIsUp && isUp) { + // bring connection UP + MetisMissive *missive = metisMissive_Create(MetisMissiveType_ConnectionUp, etherConnState->id); + metisMessenger_Send(messenger, missive); + return; + } +} + +static CPIConnectionType +_metisEtherConnection_getConnectionType(const MetisIoOperations *ops) +{ + return cpiConnection_L2; +} + + +bool +metisEtherConnection_IsInstanceOf(const MetisConnection *conn) +{ + bool result = false; + if (conn != NULL) { + const void *class = metisConnection_Class(conn); + if (class == _metisIoOperationsGuid) { + result = true; + } + } + return result; +} + + +MetisHopByHopFragmenter * +metisEtherConnection_GetFragmenter(const MetisConnection *conn) +{ + MetisHopByHopFragmenter *fragmenter = NULL; + + if (metisEtherConnection_IsInstanceOf(conn)) { + MetisIoOperations *ops = metisConnection_GetIoOperations(conn); + _MetisEtherState *state = (_MetisEtherState *) metisIoOperations_GetClosure(ops); + fragmenter = state->fragmenter; + } + return fragmenter; +} + +static MetisTicks +_sendProbe(MetisIoOperations *ops, unsigned probeType) +{ + //TODO + return 0; +} + diff --git a/metis/ccnx/forwarder/metis/io/metis_EtherConnection.h b/metis/ccnx/forwarder/metis/io/metis_EtherConnection.h new file mode 100644 index 00000000..c92587f1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_EtherConnection.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metis_EtherConnection.h + * @brief Represents an ethernet pair (source address, destination address) in the connection table + * + * Ethernet connections are never local. + * + */ + +#ifndef Metis_metis_EtherConnection_h +#define Metis_metis_EtherConnection_h + +#include <ccnx/forwarder/metis/io/metis_IoOperations.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/io/metis_AddressPair.h> +#include <ccnx/api/control/cpi_Address.h> +#include <ccnx/forwarder/metis/io/metis_GenericEther.h> +#include <ccnx/forwarder/metis/io/metis_HopByHopFragmenter.h> +#include <ccnx/forwarder/metis/core/metis_Connection.h> + +/** + * @function metisEtherConnection_Create + * @abstract <#OneLineDescription#> + * @discussion + * <#Discussion#> + * + * @param pair the address pair that uniquely identifies the connection. Takes ownership of this memory. + * @return <#return#> + */ +MetisIoOperations *metisEtherConnection_Create(MetisForwarder *metis, MetisGenericEther *ether, MetisAddressPair *pair); + +/** + * If the IO Operats are of type MetisEtherConnection, return its fragmenter + * + * <#Paragraphs Of Explanation#> + * + * @param [in] conn An allocated MetisConnection + * + * @return non-null The fragmenter associated with this conneciton + * @return null There is no such fragmenter or the ops is not a MetisEtherConnection + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +MetisHopByHopFragmenter *metisEtherConnection_GetFragmenter(const MetisConnection *conn); + +/** + * Tests if MetisEtherConnection is the underlying implementation of the connection + * + * <#Paragraphs Of Explanation#> + * + * @param [in] conn An allocated MetisConnection + * + * @return true The connection is of type MetisEtherConnection + * @return false The connection is of other type + * + * Example: + * @code + * { + * MetisHopByHopFragmenter * + * metisEtherConnection_GetFragmenter(const MetisConnection *conn) + * { + * MetisHopByHopFragmenter *fragmenter = NULL; + * + * if (metisEtherConnection_IsInstanceOf(conn)) { + * MetisIoOperations *ops = metisConnection_GetIoOperations(conn); + * _MetisEtherState *state = (_MetisEtherState *) ops->context; + * fragmenter = state->fragmenter; + * } + * return fragmenter; + * } + * } + * @endcode + */ +bool metisEtherConnection_IsInstanceOf(const MetisConnection *conn); + +#endif // Metis_metis_EtherConnection_h diff --git a/metis/ccnx/forwarder/metis/io/metis_EtherListener.c b/metis/ccnx/forwarder/metis/io/metis_EtherListener.c new file mode 100644 index 00000000..58ac07d1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_EtherListener.c @@ -0,0 +1,728 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Implements the listener over Ethernet. + * + * Right now only supports non-VLAN frames Ethernet (not 802.3/802.2 LLC) frames. + * + */ + +#include <config.h> +#include <stdio.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> +#include <arpa/inet.h> + +#include <net/ethernet.h> + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/ioctl.h> + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_Connection.h> +#include <ccnx/forwarder/metis/core/metis_Message.h> +#include <ccnx/forwarder/metis/messenger/metis_Messenger.h> + +#include <ccnx/forwarder/metis/core/metis_Dispatcher.h> + +#include <ccnx/forwarder/metis/io/metis_GenericEther.h> +#include <ccnx/forwarder/metis/io/metis_EtherConnection.h> + +#ifndef ntohll +#define ntohll(x) (((uint64_t) (ntohl((uint32_t) (x & 0x00000000FFFFFFFF))) << 32) | ntohl(((uint32_t) ((((uint64_t) x) >> 32))))) +#define htonll(x) ntohll(x) +#endif + +#include <LongBow/runtime.h> +#include <parc/algol/parc_Memory.h> + +typedef struct metis_ether_stats { + uint64_t framesIn; + uint64_t framesError; + uint64_t framesReceived; + uint64_t framesReassembled; + uint64_t framesNotForUs; +} _MetisEtherStats; + +typedef struct metis_ether_listener { + MetisForwarder *metis; + MetisLogger *logger; + + MetisGenericEther *genericEther; + + unsigned id; + CPIAddress *localAddress; + + uint16_t ethertype; + + int ether_fd; + PARCEvent *ether_event; + + // what size do the read buffers need to be? + size_t ether_buffer_length; + + // buffer to read next packet in to + PARCEventBuffer *nextReadBuffer; + + // We store MAC addresses in uint64 and mask them down to 6 bytes. + // this means all our address comparisons are simple "==" operations + + uint64_t *destinationAddressList; + size_t destinationAddressSize; + + uint64_t *sourceAddressList; + size_t sourceAddressSize; + + _MetisEtherStats stats; +} _MetisEtherListener; + +// network byte order mask to go from 8-bytes to 6-bytes +#define MAC_MASK (htonll(0xFFFFFFFFFFFF0000ULL)) + +static void _metisEtherListener_OpsDestroy(MetisListenerOps **listenerOpsPtr); +static unsigned _metisEtherListener_OpsGetInterfaceIndex(const MetisListenerOps *ops); +static const CPIAddress *_metisEtherListener_OpsGetListenAddress(const MetisListenerOps *ops); +static MetisEncapType _metisEtherListener_OpsGetEncapType(const MetisListenerOps *ops); +static int _metisEtherListener_OpsGetSocket(const MetisListenerOps *ops); + +static MetisListenerOps _etherTemplate = { + .context = NULL, + .destroy = &_metisEtherListener_OpsDestroy, + .getInterfaceIndex = &_metisEtherListener_OpsGetInterfaceIndex, + .getListenAddress = &_metisEtherListener_OpsGetListenAddress, + .getEncapType = &_metisEtherListener_OpsGetEncapType, + .getSocket = &_metisEtherListener_OpsGetSocket +}; + +// Called by Libevent +static void _metisEtherListener_ReadCallback(int fd, PARCEventType what, void *user_data); + +static void +_logStats(_MetisEtherListener *listener, PARCLogLevel level) +{ + if (metisLogger_IsLoggable(listener->logger, MetisLoggerFacility_IO, level)) { + metisLogger_Log(listener->logger, MetisLoggerFacility_IO, level, __func__, + "EtherListener %p frames in %" PRIu64 ", errors %" PRIu64 " ok %" PRIu64 " reassemble %" PRIu64 " reject %" PRIu64, + (void *) listener, + listener->stats.framesIn, + listener->stats.framesError, + listener->stats.framesReceived, + listener->stats.framesReassembled, + listener->stats.framesNotForUs); + } +} + + +// ============================================= +// Public API + +static void +_metisEtherListener_FillInEthernetAddresses(_MetisEtherListener *etherListener) +{ + // this may be null + PARCBuffer *myAddress = metisGenericEther_GetMacAddress(etherListener->genericEther); + uint64_t macAsUint64 = 0; + + if (myAddress) { + while (parcBuffer_Remaining(myAddress) > 0) { + uint8_t b = parcBuffer_GetUint8(myAddress); + macAsUint64 <<= 8; + macAsUint64 |= b; + } + // the mac address is only 6 bytes, so shift two more + macAsUint64 <<= 16; + parcBuffer_Rewind(myAddress); + + // loopback interface as a 0-length link address + if (parcBuffer_Remaining(myAddress) > 0) { + etherListener->localAddress = cpiAddress_CreateFromLink(parcBuffer_Overlay(myAddress, 0), parcBuffer_Remaining(myAddress)); + } + } + + etherListener->destinationAddressList = parcMemory_AllocateAndClear(sizeof(uint64_t) * 3); + etherListener->destinationAddressSize = 3; + etherListener->destinationAddressList[0] = htonll(macAsUint64); // our address + etherListener->destinationAddressList[1] = htonll(0x01005E0017AA0000); // CCN address + etherListener->destinationAddressList[2] = htonll(0xFFFFFFFFFFFF0000); // broadcast + + etherListener->sourceAddressList = parcMemory_AllocateAndClear(sizeof(uint64_t)); + etherListener->sourceAddressSize = 1; + etherListener->sourceAddressList[0] = htonll(macAsUint64); // our address +} + +static void +_metisEtherListener_ReleaseEthernetAddresses(_MetisEtherListener *etherListener) +{ + parcMemory_Deallocate((void **) ðerListener->destinationAddressList); + parcMemory_Deallocate((void **) ðerListener->sourceAddressList); + + if (etherListener->localAddress) { + cpiAddress_Destroy(ðerListener->localAddress); + } +} + +MetisListenerOps * +metisEtherListener_Create(MetisForwarder *metis, const char *deviceName, uint16_t ethertype) +{ + assertNotNull(metis, "Parameter metis must be non-null"); + assertNotNull(deviceName, "Parameter deviceName must be non-null"); + + _MetisEtherListener *etherListener = parcMemory_AllocateAndClear(sizeof(_MetisEtherListener)); + etherListener->ethertype = ethertype; + etherListener->genericEther = metisGenericEther_Create(metis, deviceName, etherListener->ethertype); + + MetisListenerOps *ops = NULL; + + if (etherListener->genericEther != NULL) { + etherListener->metis = metis; + etherListener->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + etherListener->nextReadBuffer = parcEventBuffer_Create(); + etherListener->id = metisForwarder_GetNextConnectionId(metis); + + int etherSocket = metisGenericEther_GetDescriptor(etherListener->genericEther); + bool persistent = true; + + // now wrap it in an event callback + etherListener->ether_event = metisDispatcher_CreateNetworkEvent( + metisForwarder_GetDispatcher(etherListener->metis), + persistent, + _metisEtherListener_ReadCallback, + etherListener, + etherSocket); + + assertNotNull(etherListener->ether_event, "got null event from metisDispatcher_CreateNetworkEvent: %s", strerror(errno)); + + // Setup the destination and source ethernet addresses we want to use + _metisEtherListener_FillInEthernetAddresses(etherListener); + + // Finished all initialization, so start the network event. + metisDispatcher_StartNetworkEvent(metisForwarder_GetDispatcher(etherListener->metis), etherListener->ether_event); + + // Construct an instance of the MetisListenerOps particular to this context. + ops = parcMemory_Allocate(sizeof(MetisListenerOps)); + assertNotNull(ops, "Got null from parc_memory_new"); + + memcpy(ops, &_etherTemplate, sizeof(MetisListenerOps)); + ops->context = etherListener; + + if (metisLogger_IsLoggable(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + char *str = cpiAddress_ToString(etherListener->localAddress); + metisLogger_Log(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Create Ethernet Listener id %d on %s addr %s ethertype 0x%04x ether socket %d", + etherListener->id, + deviceName, + str, + ethertype, + etherSocket); + parcMemory_Deallocate((void **) &str); + } + } else { + // failed to setup an Ethernet device + parcMemory_Deallocate((void **) ðerListener); + } + + return ops; +} + +MetisGenericEther * +metisEtherListener_GetGenericEtherFromListener(MetisListenerOps *listenerOps) +{ + assertNotNull(listenerOps, "Parameter listenerOps must be non-null"); + assertTrue(listenerOps->getEncapType(listenerOps) == METIS_ENCAP_ETHER, "Can only call on a METIS_ENCAP_ETHER listener"); + + _MetisEtherListener *etherListener = (_MetisEtherListener *) listenerOps->context; + return etherListener->genericEther; +} + +static void +_metisEtherListener_Destroy(_MetisEtherListener **listenerPtr) +{ + assertNotNull(listenerPtr, "Parameter must be non-null double pointer"); + assertNotNull(*listenerPtr, "Parameter must derefernce to non-null pointer"); + + _MetisEtherListener *etherListener = *listenerPtr; + parcEventBuffer_Destroy(&(etherListener->nextReadBuffer)); + metisDispatcher_DestroyNetworkEvent(metisForwarder_GetDispatcher(etherListener->metis), ðerListener->ether_event); + + metisLogger_Release(ðerListener->logger); + + metisGenericEther_Release(ðerListener->genericEther); + _metisEtherListener_ReleaseEthernetAddresses(etherListener); + parcMemory_Deallocate((void **) ðerListener); + *listenerPtr = NULL; +} + +static void +_metisEtherListener_OpsDestroy(MetisListenerOps **listenerOpsPtr) +{ + MetisListenerOps *ops = *listenerOpsPtr; + _MetisEtherListener *etherListener = (_MetisEtherListener *) ops->context; + _metisEtherListener_Destroy(ðerListener); + parcMemory_Deallocate((void **) &ops); + *listenerOpsPtr = NULL; +} + +static unsigned +_metisEtherListener_OpsGetInterfaceIndex(const MetisListenerOps *ops) +{ + _MetisEtherListener *etherListener = (_MetisEtherListener *) ops->context; + return etherListener->id; +} + +static const CPIAddress * +_metisEtherListener_OpsGetListenAddress(const MetisListenerOps *ops) +{ + _MetisEtherListener *etherListener = (_MetisEtherListener *) ops->context; + return etherListener->localAddress; +} + +static MetisEncapType +_metisEtherListener_OpsGetEncapType(const MetisListenerOps *ops) +{ + return METIS_ENCAP_ETHER; +} + +static int +_metisEtherListener_OpsGetSocket(const MetisListenerOps *ops) +{ + _MetisEtherListener *etherListener = (_MetisEtherListener *) ops->context; + return etherListener->ether_fd; +} + + +// ============================================= +// Internal functions + +/** + * Construct an address pair to match the remote + * + * The pair will always be (ourMacAddress, header->sourceAddress), even if the + * packet was received via a group or broadcast dmac. + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +static MetisAddressPair * +_metisEtherListener_ConstructAddressPair(_MetisEtherListener *etherListener, PARCEventBuffer *buffer) +{ + struct ether_header *header = (struct ether_header *) parcEventBuffer_Pullup(buffer, ETHER_HDR_LEN); + + CPIAddress *remoteAddress = cpiAddress_CreateFromLink(header->ether_shost, ETHER_ADDR_LEN); + + MetisAddressPair *pair = metisAddressPair_Create(etherListener->localAddress, remoteAddress); + cpiAddress_Destroy(&remoteAddress); + + return pair; +} + +/** + * Lookup a connection in the connection table based on an address pair + * + * <#Paragraphs Of Explanation#> + * + * @param [in] etherListener An allocated MetisEtherListener + * @param [in] pair The Address pair to lookup + * + * @return null Not found + * @return non-null The connection + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +static const MetisConnection * +_metisEtherListener_LookupConnectionId(_MetisEtherListener *etherListener, MetisAddressPair *pair) +{ + MetisConnectionTable *connTable = metisForwarder_GetConnectionTable(etherListener->metis); + + const MetisConnection *conn = metisConnectionTable_FindByAddressPair(connTable, pair); + return conn; +} + +/** + * @function _metisEtherListener_CreateNewConnection + * @abstract Creates a new Metis connection for the peer + * @discussion + * PRECONDITION: you know there's not an existing connection with the address pair + * + * Creates a new connection and adds it to the connection table. + * + * @param <#param1#> + * @return The connection id for the new connection + */ +static const MetisConnection * +_metisEtherListener_CreateNewConnection(_MetisEtherListener *etherListener, MetisAddressPair *pair) +{ + // metisEtherConnection_Create takes ownership of the pair + MetisIoOperations *ops = metisEtherConnection_Create(etherListener->metis, etherListener->genericEther, pair); + MetisConnection *conn = metisConnection_Create(ops); + + metisConnectionTable_Add(metisForwarder_GetConnectionTable(etherListener->metis), conn); + + return conn; +} + +/** + * Read an ethernet frame and return its buffer. + * + * Will use ether->nextReadBuffer. If we read a frame, will allocate a new nextReadBuffer. + * + * @param [in] fd The ethernet frame socket + * + * @return NULL could not read a frame + * @return non-null Ethernet frame in the buffer + * + * Example: + * @code + * <#example#> + * @endcode + */ +static PARCEventBuffer * +_metisEtherListener_ReadEtherFrame(_MetisEtherListener *etherListener) +{ + PARCEventBuffer *readBuffer = NULL; + + bool success = metisGenericEther_ReadNextFrame(etherListener->genericEther, etherListener->nextReadBuffer); + + if (success) { + readBuffer = etherListener->nextReadBuffer; + etherListener->nextReadBuffer = parcEventBuffer_Create(); + + if (metisLogger_IsLoggable(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "read %zu bytes", + parcEventBuffer_GetLength(readBuffer)); + } + } + + return readBuffer; +} + +/** + * Compares source MAC address to our address + * + * buffer points to the start of the Ethernet frame. Checks if the source address + * is our address. + * + * The check is done against the array of addresses in ether->sourceAddressList + * + * @param [in] header The Ethernet header + * + * @return true The Ethernet source address is our MAC address + * @return false It is not our MAC address + * + * Example: + * @code + * <#example#> + * @endcode + */ +static bool +_metisEtherListener_IsOurSourceAddress(_MetisEtherListener *ether, struct ether_header *header) +{ + // this copies the source address, then masks our copy + uint64_t u64_shost; + memcpy(&u64_shost, header->ether_shost, ETHER_ADDR_LEN); + u64_shost &= MAC_MASK; + + for (int i = 0; i < ether->sourceAddressSize; i++) { + if (u64_shost == ether->sourceAddressList[i]) { + return true; + } + } + return false; +} + +/** + * Compares destination MAC address to our receive addresses + * + * The check is done against the array of addresses in ether->destinationAddressList + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +static bool +_metisEtherListener_IsOurDestinationAddress(_MetisEtherListener *ether, struct ether_header *header) +{ + // this copies the source address, then masks our copy + uint64_t u64_dhost; + memcpy(&u64_dhost, header->ether_dhost, ETHER_ADDR_LEN); + u64_dhost &= MAC_MASK; + + for (int i = 0; i < ether->destinationAddressSize; i++) { + if (u64_dhost == ether->destinationAddressList[i]) { + return true; + } + } + return false; +} + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +static bool +_metisEtherListener_IsOurProtocol(_MetisEtherListener *ether, struct ether_header *header) +{ + // TODO: check the ethertype + return true; +} + +typedef enum { + ParseResult_Accept, + ParseResult_Reject, + ParseResult_Error +} _ParseResult; + +/** + * Processes an ethernet frame to make sure its for us + * + * Ensures that the frame is for us, and not from our source address. + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @return ParseResult_Accept We should recieve and process the frame + * @return ParseResult_Reject Do not receive the frame + * @return ParseResult_Error There was an error looking at the Ethernet header + * + * Example: + * @code + * <#example#> + * @endcode + */ +static _ParseResult +_metisEtherListener_ParseEtherFrame(_MetisEtherListener *etherListener, PARCEventBuffer *buffer) +{ + _ParseResult result = ParseResult_Error; + + struct ether_header *header = (struct ether_header *) parcEventBuffer_Pullup(buffer, ETHER_HDR_LEN); + + if (header) { + result = ParseResult_Reject; + if (_metisEtherListener_IsOurProtocol(etherListener, header)) { + if (_metisEtherListener_IsOurDestinationAddress(etherListener, header)) { + if (!_metisEtherListener_IsOurSourceAddress(etherListener, header)) { + // ok, it is the right protocol, a good destination address + // and not our source address. We should ccept this. + + result = ParseResult_Accept; + } + } + } + } + + return result; +} + +static const MetisConnection * +_metisEtherListener_LookupOrCreateConnection(_MetisEtherListener *etherListener, PARCEventBuffer *buffer) +{ + MetisAddressPair *pair = _metisEtherListener_ConstructAddressPair(etherListener, buffer); + + const MetisConnection *conn = _metisEtherListener_LookupConnectionId(etherListener, pair); + + if (!conn) { + conn = _metisEtherListener_CreateNewConnection(etherListener, pair); + + if (metisLogger_IsLoggable(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + char *str = metisAddressPair_ToString(pair); + metisLogger_Log(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Create connid %u address pair %s", metisConnection_GetConnectionId(conn), str); + free(str); + } + } + metisAddressPair_Release(&pair); + + return conn; +} + +/** + * Accept a fragment, put it in rassembler, and pass reassembled frames up stack. + * + * precondition: message is not null + * + * @param [<#in#> | <#out#> | <#in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +static void +_acceptFragment(_MetisEtherListener *etherListener, const MetisConnection *conn, MetisMessage *message) +{ + MetisHopByHopFragmenter *fragmenter = metisEtherConnection_GetFragmenter(conn); + assertNotNull(fragmenter, "Could not get fragmenter from the underlying connection"); + + bool receiveQueueNotEmpty = metisHopByHopFragmenter_Receive(fragmenter, message); + if (receiveQueueNotEmpty) { + MetisMessage *assembled = NULL; + while ((assembled = metisHopByHopFragmenter_PopReceiveQueue(fragmenter)) != NULL) { + etherListener->stats.framesReassembled++; + metisForwarder_Receive(etherListener->metis, assembled); + } + } +} + +static void +_acceptFrame(_MetisEtherListener *etherListener, PARCEventBuffer *buffer, int fd) +{ + const MetisConnection *conn = _metisEtherListener_LookupOrCreateConnection(etherListener, buffer); + + // remove the ethernet header + parcEventBuffer_Read(buffer, NULL, ETHER_HDR_LEN); + + // takes ownership of buffer (will destroy it if there's an error) + MetisMessage *message = metisMessage_CreateFromBuffer(metisConnection_GetConnectionId(conn), metisForwarder_GetTicks(etherListener->metis), buffer, etherListener->logger); + + size_t readLength = parcEventBuffer_GetLength(buffer); + if (message) { + etherListener->stats.framesReceived++; + + if (metisLogger_IsLoggable(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "read %zu bytes from fd %d connid %d", + readLength, + fd, + metisConnection_GetConnectionId(conn)); + _logStats(etherListener, PARCLogLevel_Debug); + } + + _acceptFragment(etherListener, conn, message); + metisMessage_Release(&message); + + } else { + etherListener->stats.framesError++; + + if (metisLogger_IsLoggable(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning)) { + metisLogger_Log(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "read %zu bytes from fd %d connid %d: Error parsing skeleton", + readLength, + fd, + metisConnection_GetConnectionId(conn)); + _logStats(etherListener, PARCLogLevel_Warning); + } + } +} + +static void +_rejectFrame(_MetisEtherListener *etherListener, PARCEventBuffer *buffer, int fd) +{ + etherListener->stats.framesNotForUs++; + + if (metisLogger_IsLoggable(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning)) { + metisLogger_Log(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "read %zu bytes from fd %d: reject frame", + parcEventBuffer_GetLength(buffer), + fd); + _logStats(etherListener, PARCLogLevel_Warning); + } +} + +static void +_errorFrame(_MetisEtherListener *etherListener, PARCEventBuffer *buffer, int fd) +{ + etherListener->stats.framesError++; + + if (metisLogger_IsLoggable(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning)) { + metisLogger_Log(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "read %zu bytes from fd %d: error parsing Ethernet header", + parcEventBuffer_GetLength(buffer), + fd); + _logStats(etherListener, PARCLogLevel_Warning); + } +} + +static void +_metisEtherListener_ReadCallback(int fd, PARCEventType what, void *user_data) +{ + // ether is datagram based, we don't have a connection + _MetisEtherListener *etherListener = (_MetisEtherListener *) user_data; + + if (metisLogger_IsLoggable(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(etherListener->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "socket %d what %s%s%s%s data %p", + fd, + (what & PARCEventType_Timeout) ? " timeout" : "", + (what & PARCEventType_Read) ? " read" : "", + (what & PARCEventType_Write) ? " write" : "", + (what & PARCEventType_Signal) ? " signal" : "", + user_data); + } + + if (what & PARCEventType_Read) { + while (true) { + PARCEventBuffer *buffer = _metisEtherListener_ReadEtherFrame(etherListener); + + if (buffer == NULL) { + break; + } + + etherListener->stats.framesIn++; + + _ParseResult result = _metisEtherListener_ParseEtherFrame(etherListener, buffer); + + switch (result) { + case ParseResult_Accept: + _acceptFrame(etherListener, buffer, fd); + break; + + case ParseResult_Reject: + _rejectFrame(etherListener, buffer, fd); + parcEventBuffer_Destroy(&buffer); + break; + + case ParseResult_Error: + _errorFrame(etherListener, buffer, fd); + parcEventBuffer_Destroy(&buffer); + break; + + default: + trapUnexpectedState("Do not understand parse result %d", result); + } + } + } +} diff --git a/metis/ccnx/forwarder/metis/io/metis_EtherListener.h b/metis/ccnx/forwarder/metis/io/metis_EtherListener.h new file mode 100644 index 00000000..12aee973 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_EtherListener.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metis_EtherListener.h + * @brief Listen for raw ethernet frames on an interface + * + * <#Detailed Description#> + * + */ + +#ifndef Metis_metis_EtherListener_h +#define Metis_metis_EtherListener_h + +#include <ccnx/forwarder/metis/io/metis_GenericEther.h> + +struct metis_listener_ether; +typedef struct metis_listener_ether MetisListenerEther; + +/** + * @function metisListenerEther_Create + * @abstract Create a L2 listener on a raw ethertype + * @discussion + * Requires root, will send/receive ethernet frames on the specified device. + * The exact mechanism varies by system. + * + * @param deviceName is the system name of the interface (e.g. "en0") + * @return <#return#> + */ +MetisListenerOps *metisEtherListener_Create(MetisForwarder *metis, const char *deviceName, uint16_t ethertype); + +/** + * Return the underlying GenericEther of the listener + * + * The MetisGenericEther wraps the platform-specific IO operations of the ethernet connection. + * Will assert if the listenerOps is not of type METIS_ENCAP_ETHER. + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval non-null The underlying generic ethernet + * @retval null An error + * + * Example: + * @code + * { + * MetisListenerSet *listenerSet = metisForwarder_GetListenerSet(metis); + * MetisListenerOps *listenerOps = metisListenerSet_Find(listenerSet, METIS_ENCAP_ETHER, linkAddress); + * if (listenerOps) { + * MetisGenericEther *ether = metisEtherListener_GetGenericEtherFromListener(listenerOps); + * } + * } + * @endcode + */ +MetisGenericEther *metisEtherListener_GetGenericEtherFromListener(MetisListenerOps *listenerOps); +#endif // Metis_metis_EtherListener_h diff --git a/metis/ccnx/forwarder/metis/io/metis_Ethernet.h b/metis/ccnx/forwarder/metis/io/metis_Ethernet.h new file mode 100644 index 00000000..9c560e94 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_Ethernet.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metis_Ethernet.h + * @brief Helpers for Ethernet frames + * + */ + +#ifndef Metis_metis_Ethernet_h +#define Metis_metis_Ethernet_h + +/** + * returns true is the ethertype is at least 0x0600 indicating + * a type II frame (IEEE 802.3x-1997) + * + * @param [in] ethertype The ethertype in host byte order + * + * @retval true if the ethertype is at least 0x0600 + * @retval false Otherwise + */ +#define metisEthernet_IsValidEthertype(ethertype) ((ethertype) >= 0x0600) + +#endif diff --git a/metis/ccnx/forwarder/metis/io/metis_GenericEther.h b/metis/ccnx/forwarder/metis/io/metis_GenericEther.h new file mode 100644 index 00000000..248a55ec --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_GenericEther.h @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file generic_ether.h + * @brief Generic interface to working with Ethernet frames. + * + * Wraps platform-specific code. The implementation is found in the metis/platforms directory. + * + */ + +#ifndef Metis_metis_GenericEther_h +#define Metis_metis_GenericEther_h + +#include <stdbool.h> +#include <parc/algol/parc_EventBuffer.h> +#include <parc/algol/parc_Buffer.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +struct metis_generic_ether; +typedef struct metis_generic_ether MetisGenericEther; + +/** + * Create a generic ethernet object + * + * Hides system dependent ethernet. Creates an ethernet object that is ready to send and + * receive ethernet frames on a given device and ether type. There may be system limits + * to the number of these you can open (i.e. 4 BPF devices on Mac OS). + * + * If the device name is NULL, it will not bind to a specific interface. + * + * You generally need elevated permissions to access an Ethernet device. This function + * may return NULL due to a permissions error. + * + * @param [in] deviceName The name of the device, e.g. "eth0" or "en1" + * @param [in] etherType The host-byte-order ethertype (i.e. 0x0801) + * @param [in] logger The MetisLogger to use + * + * @retval non-null An allocated ethernet object + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisGenericEther *metisGenericEther_Create(MetisForwarder *metis, const char *deviceName, uint16_t etherType); + +/** + * Acquire a reference counted copy + * + * Returns a reference counted copy of the generic Ethernet object + * + * @param [in] ether An allocated Ethernet object + * + * @retval non-null A reference counted copy + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisGenericEther *metisGenericEther_Acquire(const MetisGenericEther *ether); + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisGenericEther_Release(MetisGenericEther **etherPtr); + +/** + * Returns the descriptor for i/o + * + * Returns a system descriptor that can be used to select, poll, etc. + * + * Do not close the socket. It will be closed when metisGenericEther_Release() is called. + * + * @param [in] ether An allocated generic ethernet + * + * @retval non-negative The system descriptor + * @retval negative An error (use errno) + * + * Example: + * @code + * <#example#> + * @endcode + */ +int metisGenericEther_GetDescriptor(const MetisGenericEther *ether); + + +/** + * Reads the next ethernet frame in to the provided buffer + * + * The frame will have the Ethernet header + * + * @param [in] ether An allocated generic ethernet + * @param [in] buffer The allocated buffer to put the packet in + * + * @retval true A frame was ready and put in the buffer + * @retval false No frame is ready + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisGenericEther_ReadNextFrame(MetisGenericEther *ether, PARCEventBuffer *buffer); + +/** + * Sends an Ethernet frame out the device + * + * The frame must have an Ethernet header filled in with all values. + * + * @param [in] ether An allocated GenericEther object + * @param [in] buffer The buffer to send, including the Ethernet header + * + * @retval true The frame was sent + * @retval false An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +bool metisGenericEther_SendFrame(MetisGenericEther *ether, PARCEventBuffer *buffer); + +/** + * Return the MAC address the object is bound to + * + * Returns a PARCBuffer with the 6-byte mac address of the interface + * + * @param [in] ether An allocated GenericEther object + * + * @retval non-null The MAC address of the interface + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +PARCBuffer *metisGenericEther_GetMacAddress(const MetisGenericEther *ether); + +/** + * Returns the ethertype associated with this object + * + * Returns the ethertype, in host byte order. + * + * @param [in] ether An allocated GenericEther object + * + * @retval number Ethertype (host byte order) + * + * Example: + * @code + * <#example#> + * @endcode + */ +uint16_t metisGenericEther_GetEtherType(const MetisGenericEther *ether); + +/** + * Returns the maximum transmission unit (MTU) + * + * The MTU is the largest user payload allowed in a frame + * + * @param [<#in#> | <#out#> | <#in,out#>] <#name#> <#description#> + * + * @return <#value#> <#explanation#> + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +unsigned metisGenericEther_GetMTU(const MetisGenericEther *ether); +#endif // Metis_metis_GenericEther_h diff --git a/metis/ccnx/forwarder/metis/io/metis_HopByHopFragmenter.c b/metis/ccnx/forwarder/metis/io/metis_HopByHopFragmenter.c new file mode 100644 index 00000000..ce8f5956 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_HopByHopFragmenter.c @@ -0,0 +1,660 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * See https://www.ietf.org/proceedings/interim/2015/03/22/icnrg/slides/slides-interim-2015-icnrg-2-3.pptx + * + * B flag - indicates the start of a fragment + * E flag - indicates the end of a fragment (may be in same frame as B frame) + * I flag - an Idle frame (may only occur between E and B frames). + * X flag - extended format (not supported) + * + * in the basic protocol that we implement, there is a 20-bit sequence number + * + */ + +#include <config.h> +#include <stdio.h> + +#include <LongBow/runtime.h> + +#include <ccnx/forwarder/metis/io/metis_HopByHopFragmenter.h> + +#include <parc/algol/parc_EventBuffer.h> + +#include <parc/concurrent/parc_RingBuffer_1x1.h> + +/* + * Complete header for the Basic Encoding in a V1 FixedHeader. The blob[3] array + * holds the protocol header fields. See the macros below for accessing specific fields. + * The tlvType and tlvLength begin the container to hold the fragment payload. + */ +typedef struct hopbyhop_header { + uint8_t version; + uint8_t packetType; + uint16_t packetLength; + uint8_t blob[3]; + uint8_t headerLength; + uint16_t tlvType; + uint16_t tlvLength; +} __attribute__((packed)) _HopByHopHeader; + +// These two values are also defined in metis_TlvShcemaV1.c +#define METIS_PACKET_TYPE_HOPFRAG 4 +#define T_HOPFRAG_PAYLOAD 0x0005 + +/* + * Mask a uint32_t down to the 20-bit sequence number + */ +#define SEQNUM_MASK ((uint32_t) (0x000FFFFF)) + +/* + * This will right-pad the seqnum out to 32 bits. By filling up a uint32_t it allows + * us to use 2's compliment math to compare two sequence numbers rather than the cumbersome + * multiple branches required by the method outlined in RFC 1982. + * We use a 20-bit sequence number, so need to shift 12 bits to the left. + */ +#define SEQNUM_SHIFT 12 + +/* + * The X bit value in the top byte of the header + */ +#define XMASK 0x80 + +/* + * The B bit value in the top byte of the header + */ +#define BMASK 0x40 + +/* + * The E bit value in the top byte of the header + */ +#define EMASK 0x20 + +/* + * The I bit value in the top byte of the header + */ +#define IMASK 0x10 + +/* + * Mask out the flags from the top byte of the header + */ +#define _hopByHopHeader_GetFlags(header) ((header)->blob[0] & 0xF0) + +/* + * non-zero if the X flag is set + */ +#define _hopByHopHeader_GetXFlag(header) ((header)->blob[0] & XMASK) + +/* + * non-zero if the B flag is set + */ +#define _hopByHopHeader_GetBFlag(header) ((header)->blob[0] & BMASK) + +/* + * non-zero if the E flag is set + */ +#define _hopByHopHeader_GetEFlag(header) ((header)->blob[0] & EMASK) + +/* + * non-zero if the I flag is set + */ +#define _hopByHopHeader_GetIFlag(header) ((header)->blob[0] & IMASK) + +/* + * Sets the X flag in the header + */ +#define _hopByHopHeader_SetXFlag(header) ((header)->blob[0] |= XMASK) + +/* + * Sets the B flag in the header + */ +#define _hopByHopHeader_SetBFlag(header) ((header)->blob[0] |= BMASK) + +/* + * Sets the E flag in the header + */ +#define _hopByHopHeader_SetEFlag(header) ((header)->blob[0] |= EMASK) + +/* + * Sets the I flag in the header + */ +#define _hopByHopHeader_SetIFlag(header) ((header)->blob[0] |= IMASK) + +typedef enum { + _ParserState_Idle, // not parsing anything + _ParserState_Busy, // we have received a B but not an E +} _ParserState; + +struct metis_hopbyhop_fragment { + MetisLogger *logger; + unsigned mtu; + + // The next expected sequence number (i.e. compare then increment) + uint32_t nextReceiveFragSequenceNumber; + + // The next seqnum to use in out-going message (i.e. use then increment) + uint32_t nextSendFragSequenceNumber; + + unsigned receiveQueueCapacity; + unsigned sendQueueCapacity; + PARCRingBuffer1x1 *receiveQueue; + PARCRingBuffer1x1 *sendQueue; + + // We are only ever reassembling one packet at a time + PARCEventBuffer *currentReceiveBuffer; + + // these two are set from the "B" fragment so a reassembled frame + // will have the time and ingress port of the first fragment. + MetisTicks currentReceiveBufferStartTicks; + unsigned currentReceiveBufferIngressId; + + // Determines if we are currently reassembling a fragment + _ParserState parserState; +}; + +static uint32_t +_hopByHopHeader_GetSeqnum(const _HopByHopHeader *header) +{ + uint32_t seqnum = ((uint32_t) header->blob[0] & 0x0F) << 16 | (uint32_t) header->blob[1] << 8 | header->blob[2]; + return seqnum; +} + +static void __attribute__((unused)) +_hopByHopHeader_SetSeqnum(_HopByHopHeader *header, uint32_t seqnum) +{ + header->blob[2] = seqnum & 0xFF; + header->blob[1] = (seqnum >> 8) & 0xFF; + + header->blob[0] &= 0xF0; + header->blob[0] |= (seqnum >> 16) & 0x0F; +} + +static void +_ringBufferDestroyer(void **ptr) +{ + MetisMessage *message = *ptr; + metisMessage_Release(&message); + *ptr = NULL; +} + +/** + * Compares sequence numbers as per RFC 1982 + * + * Handles wrap-around using the 1/2 buffer rule as per RFC 1982. The indefinate state + * at exactly the middle is handled by having 2^(N-1)-1 greater than and 2^(N-1) less than. + * + * @param [in] a The first sequence number + * @param [in] b The second sequence number + * + * @return negative If a < b + * @return 0 If a == b + * @return positive if a > b + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +static int +_compareSequenceNumbers(uint32_t a, uint32_t b) +{ + // shift the numbers so they take up a full 32-bits and then use 2's compliment + // arithmatic to determine the ordering + + a <<= SEQNUM_SHIFT; + b <<= SEQNUM_SHIFT; + + int32_t c = (int32_t) (a - b); + return c; +} + +static uint32_t +_incrementSequenceNumber(const uint32_t seqnum, const uint32_t mask) +{ + uint32_t result = (seqnum + 1) & mask; + return result; +} + +static uint32_t +_nextSendSequenceNumber(MetisHopByHopFragmenter *fragmenter) +{ + uint32_t seqnum = fragmenter->nextSendFragSequenceNumber; + fragmenter->nextSendFragSequenceNumber = _incrementSequenceNumber(fragmenter->nextSendFragSequenceNumber, SEQNUM_MASK); + return seqnum; +} + +// =================================================== +// RECEIVE PROCESS + +static void +_resetParser(MetisHopByHopFragmenter *fragmenter) +{ + // throw away the re-assembly buffer and reset state to Idle + parcEventBuffer_Read(fragmenter->currentReceiveBuffer, NULL, UINT32_MAX); + fragmenter->parserState = _ParserState_Idle; +} + +/** + * Apply the sequence number rules + * + * a) If the sequence number is in order, no action. + * b) If the sequence number is out of order, reset the parser. + * c) Update the next expected sequence number to be this packet's seqnum + 1. + * + * @param [in] fragmenter An allocated MetisHopByHopFragmenter + * @param [in] fixedHeader The packet's fixed header + * + * Example: + * @code + * <#example#> + * @endcode + */ +static void +_applySequenceNumberRules(MetisHopByHopFragmenter *fragmenter, const _HopByHopHeader *fixedHeader) +{ + uint32_t segnum = _hopByHopHeader_GetSeqnum(fixedHeader); + + int compare = _compareSequenceNumbers(segnum, fragmenter->nextReceiveFragSequenceNumber); + + if (compare == 0) { + // In order + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Fragmenter %p in-order seqnum %u", + (void *) fragmenter, segnum); + } else if (compare < 0) { + // it is an old sequence number + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__, + "Fragmenter %p out-of-order seqnum %u expecting %u", + (void *) fragmenter, segnum, fragmenter->nextReceiveFragSequenceNumber); + _resetParser(fragmenter); + } else if (compare > 0) { + // lost packets + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__, + "Fragmenter %p out-of-order seqnum %u expecting %u", + (void *) fragmenter, segnum, fragmenter->nextReceiveFragSequenceNumber); + _resetParser(fragmenter); + } + + // the next seqnum we expect will be 1 after what we just received. For example, if we lost packets + // this will put us back in line with the new series. + fragmenter->nextReceiveFragSequenceNumber = _incrementSequenceNumber(segnum, SEQNUM_MASK); +} + +/* + * We've reach the END fragment of the reassembly buffer. + * 1) Make a metis message out of the reassembly buffer, + * 2) put the message in the receive queue (discard if queue full) + * 3) allocate a new reassembly buffer + * 4) reset the parser + */ +static void +_finalizeReassemblyBuffer(MetisHopByHopFragmenter *fragmenter) +{ + // This takes ownership of fragmenter->currentReceiveBuffer + MetisMessage *reassembled = metisMessage_CreateFromBuffer(fragmenter->currentReceiveBufferIngressId, + fragmenter->currentReceiveBufferStartTicks, + fragmenter->currentReceiveBuffer, + fragmenter->logger); + + if (reassembled) { + bool success = parcRingBuffer1x1_Put(fragmenter->receiveQueue, reassembled); + if (success) { + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Fragmenter %p putting reassembed message %p in receive queue", + (void *) fragmenter, (void *) reassembled); + } else { + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Fragmenter %p failed to put reassembled message in receive queue, dropping", + (void *) fragmenter); + + metisMessage_Release(&reassembled); + } + + fragmenter->currentReceiveBuffer = parcEventBuffer_Create(); + } else { + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "Fragmenter %p failed to parse reassembled packet to MetisMessage, dropping", + (void *) fragmenter); + } + + _resetParser(fragmenter); +} + +static void +_appendFragmentToReassemblyBuffer(MetisHopByHopFragmenter *fragmenter, const MetisMessage *message) +{ + size_t length = metisMessage_AppendFragmentPayload(message, fragmenter->currentReceiveBuffer); + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Fragmenter %p append %zu bytes to reassembly buffer", + (void *) fragmenter, length); +} + +/* + * Parser is in Idle state. We can only accept a B or BE frame. + * 1) If B frame: + * 1a) append to current receive buffer + * 1b) set parser state to Busy + * 1c) set the currentReceiveBufferStartTicks + * 1d) set the currentReceiveBufferIngressId + * 2) If BE frame, do B actions and finalize it (side effect: will reset state to Idle) + * 3) Otherwise ignore it + * + * Precondition: You know that the parser is in the Idle state + */ +static void +_receiveInIdleState(MetisHopByHopFragmenter *fragmenter, const MetisMessage *message, const _HopByHopHeader *fixedHeader) +{ + trapUnexpectedStateIf(fragmenter->parserState != _ParserState_Idle, + "Parser in wrong state, expected %d got %d", + _ParserState_Idle, fragmenter->parserState); + + if (_hopByHopHeader_GetBFlag(fixedHeader)) { + // start a new packet + fragmenter->currentReceiveBufferStartTicks = metisMessage_GetReceiveTime(message); + fragmenter->currentReceiveBufferIngressId = metisMessage_GetIngressConnectionId(message); + fragmenter->parserState = _ParserState_Busy; + + _appendFragmentToReassemblyBuffer(fragmenter, message); + + if (_hopByHopHeader_GetEFlag(fixedHeader)) { + // it is also the last fragment + _finalizeReassemblyBuffer(fragmenter); + } + } else if (_hopByHopHeader_GetIFlag(fixedHeader)) { + // nothing to do + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Fragmenter %p idle frame, ignorning", + (void *) fragmenter); + } else { + // nothing we can do with this frame + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "Fragmenter %p received bad header flags (0x%02X), ignorning", + (void *) fragmenter, _hopByHopHeader_GetFlags(fixedHeader)); + } +} + +/* + * In the Busy state, we can only accept a packet with no flag (middle) or end flag (end of packet). + * Anything else is an error and will cause the parser to be reset. + * + * 1) If no flags + * 1a) append to reassembly buffer + * 2) If E flag + * 2a) append to reassembly buffer + * 2b) finalize the buffer (side effect: will reset the parser and place in receive queue) + * 3) Otherwise, its an error, reset the parser + * + * PRECONDITION: you know the packet is in-order relative to the assembly buffer. + * This is handled by calling _applySequenceNumberRules() before this function. + * PRECONDITION: you know the parser is in the Busy state. + */ +static void +_receiveInBusyState(MetisHopByHopFragmenter *fragmenter, const MetisMessage *message, const _HopByHopHeader *fixedHeader) +{ + trapUnexpectedStateIf(fragmenter->parserState != _ParserState_Busy, + "Parser in wrong state, expected %d got %d", + _ParserState_Busy, fragmenter->parserState); + + if (_hopByHopHeader_GetFlags(fixedHeader) == 0) { + // It's a middle packet + + _appendFragmentToReassemblyBuffer(fragmenter, message); + } else if (_hopByHopHeader_GetEFlag(fixedHeader)) { + // It is the last fragment + + _appendFragmentToReassemblyBuffer(fragmenter, message); + + _finalizeReassemblyBuffer(fragmenter); + } else { + // nothing we can do with this frame, and it's a state machine error + + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "Fragmenter %p received invalid headers (0x%02X) in Busy state, resetting", + (void *) fragmenter, _hopByHopHeader_GetFlags(fixedHeader)); + _resetParser(fragmenter); + } +} + +/** + * Receives a fragment and applies the protocol algorithm + * + * 1) A receiver maintains one reassembly queue per peer. + * 2) Discard Idle fragments. + * 3) Discard fragments until a 'B' fragment is received. Store the received sequence number for this sender. + * 4) If an out-of-order fragment is received next, discard the reassembly buffer and go to step (2). + * 5) Continue receiving in-order fragments until the first 'E’ fragment. At this time, the fragmented + * packet is fully re-assembled and may be passed on to the next layer. + * 6) The receiver cannot assume it will receive the 'E' fragment or a subsequence 'I' frame, so it should + * use a timeout mechanism appropriate to the link to release allocated memory resources. + * + * @param [in] fragmenter An allocated MetisHopByHopFragmenter + * @param [in] message The fragment packet + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +static void +_receiveFragment(MetisHopByHopFragmenter *fragmenter, const MetisMessage *message) +{ + const _HopByHopHeader *fixedHeader = (const _HopByHopHeader *) metisMessage_FixedHeader(message); + + _applySequenceNumberRules(fragmenter, fixedHeader); + + // ====== + // Now apply the receiver rules + + + switch (fragmenter->parserState) { + case _ParserState_Idle: + _receiveInIdleState(fragmenter, message, fixedHeader); + break; + + case _ParserState_Busy: + _receiveInBusyState(fragmenter, message, fixedHeader); + break; + + default: + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Fragmenter %p Invalid parser state, discarding current buffer and resetting to Idle: %d", + (void *) fragmenter, fragmenter->parserState); + break; + } +} + +// =================================================== +// SEND PROCESS + +/** + * Fragments a message and puts all the fragments in the send queue + * + * Splits up the message in to fragments. The frist fragment will have the B flag and + * the last fragment will have the E flag. If the message fits in one fragment, it will + * have both the BE flags. Middle messages have no flags. + * + * @param [in] fragmenter An allocated MetisHopByHopFragmenter + * @param [in] message The message to fragment down to MTU size + * + * @return true Message was fragmented and all fragments put on send queue + * @return false Error durring fragmentation (likley full send queue) + * + * Example: + * @code + * { + * <#example#> + * } + * @endcode + */ +static bool +_sendFragments(MetisHopByHopFragmenter *fragmenter, const MetisMessage *message) +{ + const size_t length = metisMessage_Length(message); + size_t offset = 0; + + const size_t maxPayload = fragmenter->mtu - sizeof(_HopByHopHeader); + + _HopByHopHeader header; + memset(&header, 0, sizeof(header)); + _hopByHopHeader_SetBFlag(&header); + + while (offset < length) { + size_t payloadLength = maxPayload; + const size_t remaining = length - offset; + + if (remaining < maxPayload) { + payloadLength = remaining; + _hopByHopHeader_SetEFlag(&header); + } + + const size_t packetLength = sizeof(_HopByHopHeader) + payloadLength; + + header.version = 1; + header.packetType = METIS_PACKET_TYPE_HOPFRAG; + header.packetLength = htons(packetLength); + header.headerLength = 8; + header.tlvType = htons(T_HOPFRAG_PAYLOAD); + header.tlvLength = htons(payloadLength); + + uint32_t seqnum = _nextSendSequenceNumber(fragmenter); + _hopByHopHeader_SetSeqnum(&header, seqnum); + + MetisMessage *fragment = metisMessage_Slice(message, offset, payloadLength, sizeof(header), (uint8_t *) &header); + bool goodput = parcRingBuffer1x1_Put(fragmenter->sendQueue, fragment); + if (!goodput) { + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "Fragmenter %p message %p send queue full offset %zu length %zu", + (void *) fragmenter, (void *) message, offset, payloadLength); + metisMessage_Release(&fragment); + break; + } + + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Fragmenter %p message %p send queue fragment %p offset %zu length %zu", + (void *) fragmenter, (void *) message, (void *) fragment, offset, payloadLength); + + offset += payloadLength; + + memset(&header, 0, sizeof(header)); + } + + return (offset == length); +} + +// =================================================== + +MetisHopByHopFragmenter * +metisHopByHopFragmenter_Create(MetisLogger *logger, unsigned mtu) +{ + MetisHopByHopFragmenter *fragmenter = parcMemory_Allocate(sizeof(MetisHopByHopFragmenter)); + if (fragmenter) { + fragmenter->nextReceiveFragSequenceNumber = 0; + fragmenter->nextSendFragSequenceNumber = 0; + + fragmenter->logger = metisLogger_Acquire(logger); + fragmenter->mtu = mtu; + fragmenter->receiveQueueCapacity = 128; // this is a many-to-one queue, so not too big + fragmenter->sendQueueCapacity = 2048; // this is a one-to-many queue, so bigger (e.g. 64 KB in to 1KB payloads) + fragmenter->receiveQueue = parcRingBuffer1x1_Create(fragmenter->receiveQueueCapacity, _ringBufferDestroyer); + fragmenter->sendQueue = parcRingBuffer1x1_Create(fragmenter->sendQueueCapacity, _ringBufferDestroyer); + fragmenter->currentReceiveBuffer = parcEventBuffer_Create(); + fragmenter->parserState = _ParserState_Idle; + } + return fragmenter; +} + +void +metisHopByHopFragmenter_Release(MetisHopByHopFragmenter **fragmenterPtr) +{ + MetisHopByHopFragmenter *fragmenter = *fragmenterPtr; + parcEventBuffer_Destroy(&fragmenter->currentReceiveBuffer); + parcRingBuffer1x1_Release(&fragmenter->sendQueue); + parcRingBuffer1x1_Release(&fragmenter->receiveQueue); + metisLogger_Release(&fragmenter->logger); + parcMemory_Deallocate((void **) fragmenterPtr); +} + +bool +metisHopByHopFragmenter_Receive(MetisHopByHopFragmenter *fragmenter, const MetisMessage *message) +{ + if (metisMessage_GetType(message) == MetisMessagePacketType_HopByHopFrag) { + _receiveFragment(fragmenter, message); + } else { + // put the whole thing on the output queue + MetisMessage *copy = metisMessage_Acquire(message); + bool putSuccess = parcRingBuffer1x1_Put(fragmenter->receiveQueue, copy); + if (!putSuccess) { + metisMessage_Release(©); + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "Failed to add message %p to receive queue", (void *) message); + } else { + if (metisLogger_IsLoggable(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Add message %p to receive queue", (void *) message); + } + } + } + + // the maximum remaining is its capacity - 1 + return (parcRingBuffer1x1_Remaining(fragmenter->receiveQueue) < fragmenter->receiveQueueCapacity - 1); +} + +MetisMessage * +metisHopByHopFragmenter_PopReceiveQueue(MetisHopByHopFragmenter *fragmenter) +{ + MetisMessage *message = NULL; + parcRingBuffer1x1_Get(fragmenter->receiveQueue, (void **) &message); + return message; +} + +bool +metisHopByHopFragmenter_Send(MetisHopByHopFragmenter *fragmenter, MetisMessage *message) +{ + bool success = false; + + // If the packet will fit in the MTU without fragmentation, do not use fragmentation + if (metisMessage_Length(message) > fragmenter->mtu) { + success = _sendFragments(fragmenter, message); + } else { + MetisMessage *copy = metisMessage_Acquire(message); + success = parcRingBuffer1x1_Put(fragmenter->sendQueue, copy); + if (!success) { + metisMessage_Release(©); + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "Failed to add message %p to send queue", (void *) message); + } else { + if (metisLogger_IsLoggable(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(fragmenter->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Add message %p to send queue", (void *) message); + } + } + } + + return success; +} + +MetisMessage * +metisHopByHopFragmenter_PopSendQueue(MetisHopByHopFragmenter *fragmenter) +{ + MetisMessage *message = NULL; + parcRingBuffer1x1_Get(fragmenter->sendQueue, (void **) &message); + return message; +} + diff --git a/metis/ccnx/forwarder/metis/io/metis_HopByHopFragmenter.h b/metis/ccnx/forwarder/metis/io/metis_HopByHopFragmenter.h new file mode 100644 index 00000000..6fd83f19 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_HopByHopFragmenter.h @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metis_HopByHopFragmenterer.h + * @brief Implements a fragmenter for a hop-by-hop protocol + * + * The hop-by-hop fragmenter can be though of as a bi-directional queue. + * + * The receive process takes fragmented packets from the wire and reassembles them. Once a packet is + * reassembed, it is put on end of the receive queue. The user can then pop a message of the top of + * the receive queue and process it normally. + * + * The send process takes a potentially large input packet and fragments it in to pieces that are places + * in order on the send queue. The caller can then pop messages of the head of the send queue and + * put them on the wire. + * + * In the next update, we probalby want to make the send queue and receive queue external so I/O and + * message processing threads can access them directly. + * + */ + +#ifndef __Metis__metis_HopByHopFragmenter__ +#define __Metis__metis_HopByHopFragmenter__ + +#include <stdbool.h> +#include <ccnx/forwarder/metis/core/metis_Logger.h> +#include <ccnx/forwarder/metis/core/metis_Message.h> + +struct metis_hopbyhop_fragment; +typedef struct metis_hopbyhop_fragment MetisHopByHopFragmenter; + +/** + * Creates a fragmentation class for a specific session + * + * The fragmenter is specific to a given flow (i.e. source-destination-ethertype tuple). + * It is the responsibility of the caller to create the appropriate number of fragmenters + * and classify packets in to the right fragmenter. + * + * @param [in] logger The logger to use for output + * @param [in] mtu The MTU to use for send operations + * + * @return non-null An allocted MetisHopByHopFragmenter + * @return null An error + * + * Example: + * @code + * { + * MetisHopByHopFragmenter *fragmenter = metisHopByHopFragmenter_Create(logger, mtu); + * metisHopByHopFragmenter_Release(&fragmenter); + * } + * @endcode + */ +MetisHopByHopFragmenter *metisHopByHopFragmenter_Create(MetisLogger *logger, unsigned mtu); + +/** + * Release a reference to the fragmenter + * + * Will destroy any packets in the receive and send queues. + * + * @param [in,out] fragmenterPtr A pointer to an allocated MetisHopByHopFragmenter + * + * Example: + * @code + * { + * MetisHopByHopFragmenter *fragmenter = metisHopByHopFragmenter_Create(logger, mtu); + * metisHopByHopFragmenter_Release(&fragmenter); + * } + * @endcode + */ +void metisHopByHopFragmenter_Release(MetisHopByHopFragmenter **fragmenterPtr); + +/** + * Receives a message as part of the fragmentation session + * + * Receives a fragment. If this causes a reassembly to complete, the completed packet + * will be placed in the receive queue and may be accessed by metisHopByHopFragmenter_PopReceiveQueue(). + * The caller is reponsible for releasing message. + * + * If a non-fragment packet is received, it is placed directly on the receive queue. + * + * The caller is responsible for releasing the message. + * + * @param [in] fragmenter An allocated MetisHopByHopFragmenter + * @param [in] message An allocated MetisMessage + * + * @return true The receive buffer has an assembled packet ready for read + * @return false the receive buffer does not have a complete packet ready. + * + * Example: + * @code + * { + * void + * acceptFragment(MetisHopByHopFragmenter *fragmenter, MetisMessage *message) + * { + * bool receiveQueueNotEmpty = metisHopByHopFragmenter_Receive(fragmenter, message); + * if (receiveQueueNotEmpty) { + * MetisMessage *assembled = NULL; + * while ((assembled = metisHopByHopFragmenter_PopReceiveQueue(fragmenter)) != NULL) { + * etherListener->stats.framesReassembled++; + * metisForwarder_Receive(etherListener->metis, assembled); + * } + * } + * } + * } + * @endcode + */ +bool metisHopByHopFragmenter_Receive(MetisHopByHopFragmenter *fragmenter, const MetisMessage *message); + +/** + * Pops the top assembed message from the receive queue + * + * Reads the top reassembled packet from the receive queue. The caller must + * release the returned message. + * + * @param [in] fragmenter An allocated MetisHopByHopFragmenter + * + * @return NULL The receive queue is empty (i.e. the current reassembly is not complete) + * @return non-null A re-assembed message + * + * Example: + * @code + * { + * void + * acceptFragment(MetisHopByHopFragmenter *fragmenter, MetisMessage *message) + * { + * bool receiveQueueNotEmpty = metisHopByHopFragmenter_Receive(fragmenter, message); + * if (receiveQueueNotEmpty) { + * MetisMessage *assembled = NULL; + * while ((assembled = metisHopByHopFragmenter_PopReceiveQueue(fragmenter)) != NULL) { + * etherListener->stats.framesReassembled++; + * metisForwarder_Receive(etherListener->metis, assembled); + * } + * } + * } + * } + * @endcode + */ +MetisMessage *metisHopByHopFragmenter_PopReceiveQueue(MetisHopByHopFragmenter *fragmenter); + +/** + * Adds a message to the send buffer + * + * This may make multiple references to the original message where each fragment is + * pointing as an extent in to the original message. + * + * @param [in] fragmenter An allocated MetisHopByHopFragmenter + * @param [in] message An allocated MetisMessage + * + * @return true The message was fragmented and put on the send queue + * @return false An error + * + * Example: + * @code + * { + * void sendFragments(MetisHopByHopFragmenter *fragmenter, const MetisMessage *message) { + * bool success = metisHopByHopFragmenter_Send(fragmenter, message); + * + * MetisMessage *fragment; + * while (success && (fragment = metisHopByHopFragmenter_PopSendQueue(fragmenter)) != NULL) { + * success = _sendFrame(fragment); + * metisMessage_Release(&fragment); + * } + * + * // if we failed, drain the other fragments + * if (!success) { + * while ((fragment = metisHopByHopFragmenter_PopSendQueue(fragmenter)) != NULL) { + * metisMessage_Release(&fragment); + * } + * } + * // caller must release message + * } + * @endcode + */ +bool metisHopByHopFragmenter_Send(MetisHopByHopFragmenter *fragmenter, MetisMessage *message); + +/** + * Pops the next message to send to the wire from the send queue + * + * Returns the front of the Send FIFO queue of fragments that should be + * sent on the wire. + * + * @param [in] fragmenter An allocated MetisHopByHopFragmenter + * + * @return null there is no message awaiting transmit + * @return non-null A message to send + * + * Example: + * @code + * { + * void sendIdleFragment(MetisHopByHopFragmenter *fragmenter) { + * bool success = metisHopByHopFragmenter_SendIdle(fragmenter); + * + * MetisMessage *fragment; + * while (success && (fragment = metisHopByHopFragmenter_PopSendQueue(fragmenter)) != NULL) { + * success = _sendFrame(fragment); + * metisMessage_Release(&fragment); + * } + * } + * } + * @endcode + */ +MetisMessage *metisHopByHopFragmenter_PopSendQueue(MetisHopByHopFragmenter *fragmenter); +#endif /* defined(__Metis__metis_HopByHopFragmenter__) */ diff --git a/metis/ccnx/forwarder/metis/io/metis_IPMulticastListener.c b/metis/ccnx/forwarder/metis/io/metis_IPMulticastListener.c new file mode 100644 index 00000000..0dc5606d --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_IPMulticastListener.c @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <config.h> +#include <stdio.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> +#include <arpa/inet.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> + +#include <ccnx/forwarder/metis/io/metis_IPMulticastListener.h> + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_Connection.h> +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> +#include <ccnx/forwarder/metis/core/metis_Message.h> + +MetisListenerOps *metisIPMulticastListener_CreateInet6(MetisForwarder *metis, struct sockaddr_in6 sin6); +MetisListenerOps *metisIPMulticastListener_CreateInet(MetisForwarder *metis, struct sockaddr_in sin); diff --git a/metis/ccnx/forwarder/metis/io/metis_IPMulticastListener.h b/metis/ccnx/forwarder/metis/io/metis_IPMulticastListener.h new file mode 100644 index 00000000..ca1134e2 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_IPMulticastListener.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#ifndef Metis_metis_IPMulticastListener_h +#define Metis_metis_IPMulticastListener_h + +#include <netinet/in.h> +#include <stdlib.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/io/metis_Listener.h> + +MetisListenerOps *metisIPMulticastListener_CreateInet6(MetisForwarder *metis, struct sockaddr_in6 sin6); +MetisListenerOps *metisIPMulticastListener_CreateInet(MetisForwarder *metis, struct sockaddr_in sin); +#endif // Metis_metis_IPMulticastListener_h diff --git a/metis/ccnx/forwarder/metis/io/metis_IoOperations.c b/metis/ccnx/forwarder/metis/io/metis_IoOperations.c new file mode 100644 index 00000000..03feb737 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_IoOperations.c @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <config.h> +#include <stdio.h> + +#include <ccnx/forwarder/metis/io/metis_IoOperations.h> + +#include <LongBow/runtime.h> + +void * +metisIoOperations_GetClosure(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter ops must be non-null"); + return ops->closure; +} + +bool +metisIoOperations_Send(MetisIoOperations *ops, const CPIAddress *nexthop, MetisMessage *message) +{ + return ops->send(ops, nexthop, message); +} + +const CPIAddress * +metisIoOperations_GetRemoteAddress(const MetisIoOperations *ops) +{ + return ops->getRemoteAddress(ops); +} + +const MetisAddressPair * +metisIoOperations_GetAddressPair(const MetisIoOperations *ops) +{ + return ops->getAddressPair(ops); +} + +bool +metisIoOperations_IsUp(const MetisIoOperations *ops) +{ + return ops->isUp(ops); +} + +bool +metisIoOperations_IsLocal(const MetisIoOperations *ops) +{ + return ops->isLocal(ops); +} + +unsigned +metisIoOperations_GetConnectionId(const MetisIoOperations *ops) +{ + return ops->getConnectionId(ops); +} + +void +metisIoOperations_Release(MetisIoOperations **opsPtr) +{ + MetisIoOperations *ops = *opsPtr; + ops->destroy(opsPtr); +} + +const void * +metisIoOperations_Class(const MetisIoOperations *ops) +{ + return ops->class(ops); +} + +CPIConnectionType +metisIoOperations_GetConnectionType(const MetisIoOperations *ops) +{ + return ops->getConnectionType(ops); +} + +MetisTicks +metisIoOperations_SendProbe(MetisIoOperations *ops, unsigned probeType) +{ + return ops->sendProbe(ops, probeType); +} + diff --git a/metis/ccnx/forwarder/metis/io/metis_IoOperations.h b/metis/ccnx/forwarder/metis/io/metis_IoOperations.h new file mode 100644 index 00000000..86129865 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_IoOperations.h @@ -0,0 +1,369 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Defines the interface all connections use to communicate with the forwarder. + * + * @code + * + * static MetisIoOperations _template = { + * .closure = NULL, + * .send = &_metisEtherConnection_Send, + * .getRemoteAddress = &_metisEtherConnection_GetRemoteAddress, + * .getAddressPair = &_metisEtherConnection_GetAddressPair, + * .getConnectionId = &_metisEtherConnection_GetConnectionId, + * .isUp = &_metisEtherConnection_IsUp, + * .isLocal = &_metisEtherConnection_IsLocal, + * .destroy = &_metisEtherConnection_DestroyOperations, + * .class = &_metisEtherConnection_Class, + * .getConnectionType = &_metisEtherConnection_getConnectionType + * }; + * + * MetisIoOperations * + * metisEtherConnection_Create(MetisForwarder *metis, MetisGenericEther *ether, MetisAddressPair *pair) + * { + * _MetisEtherState *etherConnState = parcMemory_Allocate(sizeof(_MetisEtherState)); + * // Fill in etherConnState with instance variables + * + * MetisIoOperations *io_ops = parcMemory_Allocate(sizeof(MetisIoOperations)); + * memcpy(io_ops, &_template, sizeof(MetisIoOperations)); + * io_ops->closure = etherConnState; + * // Add to connection table, send missives about connection state + * + * return op_ops; + * } + * @endcode + * + */ + +/** + * I/O is built around a callback structure. The connection table contains an operations + * structure built around function pointers. These allow the connection table to be + * agnostic about underlying connections. + */ + +#ifndef Metis_metis_io_h +#define Metis_metis_io_h + +#include <ccnx/api/control/cpi_Connection.h> +#include <ccnx/api/control/cpi_Address.h> +#include <ccnx/forwarder/metis/io/metis_AddressPair.h> +#include <ccnx/forwarder/metis/core/metis_Message.h> +#include <ccnx/forwarder/metis/core/metis_Ticks.h> + +//packet types for probing +#define METIS_PACKET_TYPE_PROBE_REQUEST 5 +#define METIS_PACKET_TYPE_PROBE_REPLY 6 + +struct metis_io_ops; +typedef struct metis_io_ops MetisIoOperations; + +/** + * @typedef MetisIoOperations + * @abstract The IO Operations structure abstracts an connection's properties and send() method + * @constant context Implementation specific opaque data, passed back on each call + * @constant send function pointer to send a message, does not destroy the message + * @constant getRemoteAddress function pointer to return the "to" address associated with the connection. + * Some connections might not have a specific peer, such as multicast, where its the group address. + * @constant isUp test if the connection is up, ready to send a message. + * @constant isLocal test if the connection is local to the host. + * @constant getConnectionId returns the Metis id for the connection. + * @constant destroy releases a refernce count on the connection and possibly destroys the connection. + * @constant class A unique identifier for each class that instantiates MetisIoOperations. + * @constant getConnectionType Returns the type of connection (TCP, UDP, L2, etc.) of the underlying connection. + * @discussion <#Discussion#> + */ +struct metis_io_ops { + void *closure; + bool (*send)(MetisIoOperations *ops, const CPIAddress *nexthop, MetisMessage *message); + const CPIAddress * (*getRemoteAddress)(const MetisIoOperations *ops); + const MetisAddressPair * (*getAddressPair)(const MetisIoOperations *ops); + bool (*isUp)(const MetisIoOperations *ops); + bool (*isLocal)(const MetisIoOperations *ops); + unsigned (*getConnectionId)(const MetisIoOperations *ops); + void (*destroy)(MetisIoOperations **opsPtr); + const void * (*class)(const MetisIoOperations *ops); + CPIConnectionType (*getConnectionType)(const MetisIoOperations *ops); + MetisTicks (*sendProbe)(MetisIoOperations *ops, unsigned probeType); +}; + +/** + * Returns the closure of the interface + * + * The creator of the closure sets this parameter to store its state. + * + * @param [in] ops A concrete instance of the interface + * + * @return The value set by the concrete instance of the interface. + * + * Example: + * @clode + * { + + * } + * @endcode + */ +void *metisIoOperations_GetClosure(const MetisIoOperations *ops); + +/** + * Release all memory related to the interface and implementation + * + * This function must release all referenced memory in the concrete implementation and + * memory related to the MetisIoOperations. It should NULL the input parameter. + * + * @param [in,out] opsPtr Pointer to interface. Will be NULLed. + * + * Example: + * @code + * + * static void + * _metisEtherConnection_InternalRelease(_MetisEtherState *etherConnState) + * { + * // release internal state of _MetisEtherState + * } + * + * static void + * _metisEtherConnection_Release(MetisIoOperations **opsPtr) + * { + * MetisIoOperations *ops = *opsPtr; + * + * _MetisEtherState *etherConnState = (_MetisEtherState *) metisIoOperations_GetClosure(ops); + * _metisEtherConnection_InternalRelease(etherConnState); + * + * parcMemory_Deallocate((void **) &ops); + * } + * + * MetisIoOperations * + * metisEtherConnection_Create(MetisForwarder *metis, MetisGenericEther *ether, MetisAddressPair *pair) + * { + * size_t allocationSize = sizeof(_MetisEtherState) + sizeof(MetisIoOperations); + * MetisIoOperations *ops = parcMemory_AllocateAndClear(allocationSize); + * if (ops) { + * // fill in other interface functions + * ops->destroy = &_metisEtherConnection_Release; + * ops->closure = (uint8_t *) ops + sizeof(MetisIoOperations); + * + * _MetisEtherState *etherConnState = metisIoOperations_GetClosure(ops); + * // fill in Ethernet state + * } + * return ops; + * } + * @endcode + */ +void metisIoOperations_Release(MetisIoOperations **opsPtr); + +/** + * Sends the specified MetisMessage out this connection + * + * The the implementation of send may queue the message, it must acquire a reference to it. + * + * @param [in] ops The connection implementation. + * @param [in] nexthop On multiple access networks, this parameter might be used, usually NULL. + * @param [in] message The message to send. If the message will be queued, it will be acquired. + * + * @return true The message was sent or queued + * @retrun false An error occured and the message will not be sent or queued + * + * Example: + * @code + * { + * if (metisIoOperations_IsUp(conn->ops)) { + * return metisIoOperations_Send(conn->ops, NULL, message); + * } + * } + * @endcode + */ +bool metisIoOperations_Send(MetisIoOperations *ops, const CPIAddress *nexthop, MetisMessage *message); + +/** + * A connection is made up of a local and a remote address. This function returns the remote address. + * + * Represents the destination endpoint of the communication. + * + * @param [in] ops The connection implementation. + * + * @return non-null The remote address + * @return null The connection does not have a remote address + * + * Example: + * @code + * { + * CPIAddress *local = cpiAddress_CreateFromLink((uint8_t []) { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }, 6); + * CPIAddress *remote = cpiAddress_CreateFromLink((uint8_t []) { 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }, 6); + * MetisAddressPair *pair = metisAddressPair_Create(local, remote); + * MetisIoOperations *ops = metisEtherConnection_Create(metis, ether, pair); + * + * const CPIAddress *test = metisIoOperations_GetRemoteAddress(ops); + * assertTrue(cpiAddress_Equals(test, remote), "Wrong remote address"); + * metisIoOperations_Release(&ops); + * metisAddressPair_Release(&pair); + * cpiAddress_Destroy(&local); + * cpiAddress_Destroy(&remote); + * } + * @endcode + */ +const CPIAddress *metisIoOperations_GetRemoteAddress(const MetisIoOperations *ops); + +/** + * A connection is made up of a local and a remote address. This function returns the address pair. + * + * Represents the destination endpoint of the communication. + * + * @param [in] ops The connection implementation. + * + * @return non-null The address pair + * @return null An error. + * + * Example: + * @code + * { + * CPIAddress *local = cpiAddress_CreateFromLink((uint8_t []) { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }, 6); + * CPIAddress *remote = cpiAddress_CreateFromLink((uint8_t []) { 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }, 6); + * MetisAddressPair *pair = metisAddressPair_Create(local, remote); + * MetisIoOperations *ops = metisEtherConnection_Create(metis, ether, pair); + * + * const MetisAddressPair *test = metisIoOperations_GetAddressPair(ops); + * assertTrue(metisAddressPair(test, pair), "Wrong address pair"); + * metisIoOperations_Release(&ops); + * metisAddressPair_Release(&pair); + * cpiAddress_Destroy(&local); + * cpiAddress_Destroy(&remote); + * } + * @endcode + */ +const MetisAddressPair *metisIoOperations_GetAddressPair(const MetisIoOperations *ops); + +/** + * Returns true if the underlying connection is in operation + * + * An UP connection is able to send and receive packets. If a subsystem needs to take actions + * when a connection goes UP or DOWN, it should subscribe as a MetisMissive listener. + * + * @param [in] ops The connection implementation. + * + * @return true The connection is UP + * @return false The connection is not UP + * + * Example: + * @code + * { + * if (metisIoOperations_IsUp(conn->ops)) { + * return metisIoOperations_Send(conn->ops, NULL, message); + * } + * } + * @endcode + */ +bool metisIoOperations_IsUp(const MetisIoOperations *ops); + +/** + * If the remote address is local to this system, returns true + * + * Will return true if an INET or INET6 connection is on localhost. Will return + * true for AF_UNIX. An Ethernet connection is not local. + * + * @param [in] ops The connection implementation. + * + * @return true The remote address is local to the system + * @return false The remote address is not local + * + * Example: + * @code + * { + * // Is the ingress connection remote? If so check for non-zero and decrement + * if (!metisIoOperations(ingressConnectionOps) { + * uint8_t hoplimit = metisMessage_GetHopLimit(interestMessage); + * if (hoplimit == 0) { + * // error + * } else { + * hoplimit--; + * } + * // take actions on hoplimit + * } + * } + * @endcode + */ +bool metisIoOperations_IsLocal(const MetisIoOperations *ops); + +/** + * Returns the connection ID represented by this MetisIoOperations in the ConnectionTable. + * + * <#Paragraphs Of Explanation#> + * + * @param [in] ops The connection implementation. + * + * @return number The connection ID in the connection table. + * + * Example: + * @code + * { + * unsigned id = metisIoOperations_GetConnectionId(ingressIoOps); + * const MetisConnection *conn = metisConnectionTable_FindById(metis->connectionTable, id); + * } + * @endcode + */ +unsigned metisIoOperations_GetConnectionId(const MetisIoOperations *ops); + +/** + * A pointer that represents the class of the connection + * + * Each concrete implementation has a class pointer that is unique to the implementation (not instance). + * Each implementation is free to choose how to determine the value, so long as it is unique on the system. + * This is a system-local value. + * + * @param [in] ops The connection implementation. + * + * @return non-null A pointer value unique to the implementation (not instance). + * + * Example: + * @code + * bool + * metisEtherConnection_IsInstanceOf(const MetisConnection *conn) + * { + * bool result = false; + * if (conn != NULL) { + * MetisIoOperations *ops = metisConnection_GetIoOperations(conn); + * const void *class = metisIoOperations_Class(ops); + * result = (class == _metisEtherConnection_Class(ops)); + * } + * return result; + * } + * @endcode + */ +const void *metisIoOperations_Class(const MetisIoOperations *ops); + +/** + * Returns the transport type of the connection (TCP, UDP, L2, etc.). + * + * TCP and AF_UNIX are both stream connections and will both return "cpiConnection_TCP". + * Ethernet will return "cpiConnection_L2". + * + * @param [in] ops The connection implementation. + * + * @return cpiConnection_TCP A TCP4, TCP6, or AF_UNIX connection + * @return cpiConnection_UDP A UDP4 or UDP6 connection + * @return cpiConnection_L2 An Ethernet connection + * + * Example: + * @code + * { + * CPIConnectionType type = metisIoOperations_GetConnectionType(metisConnection_GetIoOperations(connection)); + * CPIConnection *cpiConn = cpiConnection_Create(metisConnection_GetConnectionId(connection), localAddress, remoteAddress, type); + * } + * @endcode + */ +CPIConnectionType metisIoOperations_GetConnectionType(const MetisIoOperations *ops); + +MetisTicks metisIoOperations_SendProbe(MetisIoOperations *ops, unsigned probeType); +#endif // Metis_metis_io_h diff --git a/metis/ccnx/forwarder/metis/io/metis_Listener.h b/metis/ccnx/forwarder/metis/io/metis_Listener.h new file mode 100644 index 00000000..056f76e6 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_Listener.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metis_Listener.h + * @brief Provides the function abstraction of all Listeners. + * + * A listener accepts in coming packets. A Stream listener will accept the connection + * then pass it off to the {@link MetisStreamConnection} class. A datagram listener + * will have to have its own way to multiplex packets. + * + */ + +#ifndef Metis_metis_Listener_h +#define Metis_metis_Listener_h + +#include <ccnx/api/control/cpi_Address.h> + +struct metis_listener_ops; +typedef struct metis_listener_ops MetisListenerOps; + +typedef enum { + METIS_ENCAP_TCP, /**< TCP encapsulation type */ + METIS_ENCAP_UDP, /**< UDP encapsulation type */ + METIS_ENCAP_ETHER, /**< Ethernet encapsulation type */ + METIS_ENCAP_LOCAL /**< A connection to a local protocol stack */ +} MetisEncapType; + +struct metis_listener_ops { + /** + * A user-defined parameter + */ + void *context; + + /** + * Called to destroy the Listener. + * + * @param [in] listenerOpsPtr Double pointer to this structure + */ + void (*destroy)(MetisListenerOps **listenerOpsPtr); + + /** + * Returns the interface index of the listener. + * + * @param [in] ops Pointer to this structure + * + * @return the interface index of the listener + */ + unsigned (*getInterfaceIndex)(const MetisListenerOps *ops); + + /** + * Returns the address pair that defines the listener (local, remote) + * + * @param [in] ops Pointer to this structure + * + * @return the (local, remote) pair of addresses + */ + const CPIAddress * (*getListenAddress)(const MetisListenerOps *ops); + + /** + * Returns the encapsulation type of the listener (e.g. TCP, UDP, Ethernet) + * + * @param [in] ops Pointer to this structure + * + * @return the listener encapsulation type + */ + MetisEncapType (*getEncapType)(const MetisListenerOps *ops); + + /** + * Returns the underlying socket associated with the listener + * + * Not all listeners are capable of returning a useful socket. In those + * cases, this function pointer is NULL. + * + * TCP does not support this operation (function is NULL). UDP returns its local socket. + * + * The caller should never close this socket, the listener will do that when its + * destroy method is called. + * + * @param [in] ops Pointer to this structure + * + * @retval integer The socket descriptor + * + * Example: + * @code + * <#example#> + * @endcode + */ + int (*getSocket)(const MetisListenerOps *ops); +}; +#endif // Metis_metis_Listener_h diff --git a/metis/ccnx/forwarder/metis/io/metis_ListenerSet.c b/metis/ccnx/forwarder/metis/io/metis_ListenerSet.c new file mode 100644 index 00000000..0a0d32fd --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_ListenerSet.c @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <config.h> +#include <stdio.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_ArrayList.h> + +#include <ccnx/forwarder/metis/io/metis_ListenerSet.h> + +#include <LongBow/runtime.h> + +struct metis_listener_set { + PARCArrayList *listOfListeners; +}; + +static void +metisListenerSet_DestroyListenerOps(void **opsPtr) +{ + MetisListenerOps *ops = *((MetisListenerOps **) opsPtr); + ops->destroy(&ops); +} + +MetisListenerSet * +metisListenerSet_Create() +{ + MetisListenerSet *set = parcMemory_AllocateAndClear(sizeof(MetisListenerSet)); + assertNotNull(set, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisListenerSet)); + set->listOfListeners = parcArrayList_Create(metisListenerSet_DestroyListenerOps); + + return set; +} + +void +metisListenerSet_Destroy(MetisListenerSet **setPtr) +{ + assertNotNull(setPtr, "Parameter must be non-null double pointer"); + assertNotNull(*setPtr, "Parameter must dereference to non-null pointer"); + + MetisListenerSet *set = *setPtr; + parcArrayList_Destroy(&set->listOfListeners); + parcMemory_Deallocate((void **) &set); + *setPtr = NULL; +} + +/** + * @function metisListenerSet_Add + * @abstract Adds the listener to the set + * @discussion + * Unique set based on pair (MetisEncapType, localAddress) + * + * @param <#param1#> + * @return <#return#> + */ +bool +metisListenerSet_Add(MetisListenerSet *set, MetisListenerOps *ops) +{ + assertNotNull(set, "Parameter set must be non-null"); + assertNotNull(ops, "Parameter ops must be non-null"); + + int opsEncap = ops->getEncapType(ops); + const CPIAddress *opsAddress = ops->getListenAddress(ops); + + // make sure its not in the set + size_t length = parcArrayList_Size(set->listOfListeners); + for (size_t i = 0; i < length; i++) { + MetisListenerOps *entry = parcArrayList_Get(set->listOfListeners, i); + + int entryEncap = entry->getEncapType(entry); + const CPIAddress *entryAddress = entry->getListenAddress(entry); + + if (opsEncap == entryEncap && cpiAddress_Equals(opsAddress, entryAddress)) { + // duplicate + return false; + } + } + + parcArrayList_Add(set->listOfListeners, ops); + return true; +} + +size_t +metisListenerSet_Length(const MetisListenerSet *set) +{ + assertNotNull(set, "Parameter set must be non-null"); + return parcArrayList_Size(set->listOfListeners); +} + +/** + * Returns the listener at the given index + * + * <#Paragraphs Of Explanation#> + * + * @param [in] set An allocated listener set + * @param [in] index The index position (0 <= index < metisListenerSet_Count) + * + * @retval non-null The listener at index + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisListenerOps * +metisListenerSet_Get(const MetisListenerSet *set, size_t index) +{ + assertNotNull(set, "Parameter set must be non-null"); + return parcArrayList_Get(set->listOfListeners, index); +} + +MetisListenerOps * +metisListenerSet_Find(const MetisListenerSet *set, MetisEncapType encapType, const CPIAddress *localAddress) +{ + assertNotNull(set, "Parameter set must be non-null"); + assertNotNull(localAddress, "Parameter localAddress must be non-null"); + + MetisListenerOps *match = NULL; + + for (size_t i = 0; i < parcArrayList_Size(set->listOfListeners) && !match; i++) { + MetisListenerOps *ops = parcArrayList_Get(set->listOfListeners, i); + assertNotNull(ops, "Got null listener ops at index %zu", i); + + if (ops->getEncapType(ops) == encapType) { + if (cpiAddress_Equals(localAddress, ops->getListenAddress(ops))) { + match = ops; + } + } + } + + return match; +} diff --git a/metis/ccnx/forwarder/metis/io/metis_ListenerSet.h b/metis/ccnx/forwarder/metis/io/metis_ListenerSet.h new file mode 100644 index 00000000..dad876dd --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_ListenerSet.h @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metis_ListenerSet.h + * @brief A listener set is unique on (MetisEncapType, localAddress) + * + * Keeps track of all the running listeners. The set is unique on the + * encapsulation type and the local address. For example, with TCP encapsulation and + * local address 127.0.0.1 or Ethernet encapsulation and MAC address 00:11:22:33:44:55. + * + * NOTE: This does not allow multiple EtherType on the same interface because the CPIAddress for + * a LINK address does not include an EtherType. + * + */ + +#ifndef Metis_metis_ListenerSet_h +#define Metis_metis_ListenerSet_h + +#include <ccnx/forwarder/metis/io/metis_Listener.h> + +struct metis_listener_set; +typedef struct metis_listener_set MetisListenerSet; + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisListenerSet *metisListenerSet_Create(void); + +/** + * <#One Line Description#> + * + * <#Paragraphs Of Explanation#> + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void metisListenerSet_Destroy(MetisListenerSet **setPtr); + +/** + * @function metisListenerSet_Add + * @abstract Adds the listener to the set + * @discussion + * Unique set based on pair (MetisEncapType, localAddress). + * Takes ownership of the ops memory if added. + * + * @param <#param1#> + * @return true if added, false if not + */ +bool metisListenerSet_Add(MetisListenerSet *set, MetisListenerOps *ops); + +/** + * The number of listeners in the set + * + * <#Paragraphs Of Explanation#> + * + * @param [in] set An allocated listener set + * + * @retval <#value#> <#explanation#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +size_t metisListenerSet_Length(const MetisListenerSet *set); + +/** + * Returns the listener at the given index + * + * <#Paragraphs Of Explanation#> + * + * @param [in] set An allocated listener set + * @param [in] index The index position (0 <= index < metisListenerSet_Lenght) + * + * @retval non-null The listener at index + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisListenerOps *metisListenerSet_Get(const MetisListenerSet *set, size_t index); + +/** + * Looks up a listener by its key (EncapType, LocalAddress) + * + * <#Paragraphs Of Explanation#> + * + * @param [in] set An allocated listener set + * @param [in] encapType the listener type + * @param [in] localAddress The local bind address (e.g. MAC address or TCP socket) + * + * @retval non-null The listener matching the query + * @retval null Does not exist + * + * Example: + * @code + * + * @endcode + */ +MetisListenerOps *metisListenerSet_Find(const MetisListenerSet *set, MetisEncapType encapType, const CPIAddress *localAddress); +#endif diff --git a/metis/ccnx/forwarder/metis/io/metis_LocalListener.c b/metis/ccnx/forwarder/metis/io/metis_LocalListener.c new file mode 100644 index 00000000..6e188966 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_LocalListener.c @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Implements a listener that works with stream connections over a named pipe. + * + */ + +#include <config.h> +#include <stdio.h> +#include <stdbool.h> +#include <string.h> +#include <sys/un.h> +#include <errno.h> +#include <unistd.h> + +#include <ccnx/forwarder/metis/core/metis_ConnectionTable.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/io/metis_Listener.h> +#include <ccnx/forwarder/metis/io/metis_LocalListener.h> +#include <ccnx/forwarder/metis/io/metis_StreamConnection.h> + +#include <LongBow/runtime.h> +#include <parc/algol/parc_Memory.h> + +struct metis_local_listener { + MetisForwarder *metis; + MetisLogger *logger; + PARCEventSocket *listener; + CPIAddress *localAddress; + unsigned id; +}; + +static void _metisLocalListener_OpsDestroy(MetisListenerOps **listenerOpsPtr); +static unsigned _metisLocalListener_OpsGetInterfaceIndex(const MetisListenerOps *ops); +static const CPIAddress *_metisLocalListener_OpsGetListenAddress(const MetisListenerOps *ops); +static MetisEncapType _metisLocalListener_OpsGetEncapType(const MetisListenerOps *ops); + +static MetisListenerOps localTemplate = { + .context = NULL, + .destroy = &_metisLocalListener_OpsDestroy, + .getInterfaceIndex = &_metisLocalListener_OpsGetInterfaceIndex, + .getListenAddress = &_metisLocalListener_OpsGetListenAddress, + .getEncapType = &_metisLocalListener_OpsGetEncapType, +}; + +// STREAM daemon listener callback +static void metisListenerLocal_Listen(int, struct sockaddr *, int socklen, void *localVoid); + +MetisListenerOps * +metisLocalListener_Create(MetisForwarder *metis, const char *path) +{ + MetisLocalListener *local = parcMemory_AllocateAndClear(sizeof(MetisLocalListener)); + assertNotNull(local, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisLocalListener)); + local->metis = metis; + local->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + + struct sockaddr_un addr_unix; + memset(&addr_unix, 0, sizeof(addr_unix)); + + addr_unix.sun_family = PF_UNIX; + strcpy(addr_unix.sun_path, path); + + unlink(path); + + local->listener = metisDispatcher_CreateListener(metisForwarder_GetDispatcher(metis), metisListenerLocal_Listen, + (void *) local, -1, (struct sockaddr*) &addr_unix, sizeof(addr_unix)); + + assertNotNull(local->listener, "Got null listener from metisDispatcher_CreateListener: (%d) %s", errno, strerror(errno)); + + struct sockaddr_un addr_un; + memset(&addr_un, 0, sizeof(addr_un)); + addr_un.sun_family = AF_UNIX; + strcpy(addr_un.sun_path, path); + + local->localAddress = cpiAddress_CreateFromUnix(&addr_un); + local->id = metisForwarder_GetNextConnectionId(metis); + + MetisListenerOps *ops = parcMemory_AllocateAndClear(sizeof(MetisListenerOps)); + assertNotNull(ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisListenerOps)); + memcpy(ops, &localTemplate, sizeof(MetisListenerOps)); + ops->context = local; + + return ops; +} + +void +metisLocalListener_Destroy(MetisLocalListener **listenerPtr) +{ + assertNotNull(listenerPtr, "Parameter must be non-null double pointer"); + assertNotNull(*listenerPtr, "Parameter must dereference to non-null pointer"); + + MetisLocalListener *local = *listenerPtr; + + metisLogger_Release(&local->logger); + + cpiAddress_Destroy(&local->localAddress); + metisDispatcher_DestroyListener(metisForwarder_GetDispatcher(local->metis), &local->listener); + + parcMemory_Deallocate((void **) &local); + *listenerPtr = NULL; +} + +// ================================================== + +/** + * @function metisListenerLocal_Listen + * @abstract Called when a client connects to the server socket + * @discussion + * Accepts a client connection. Creates a new Stream connection and adds it + * to the connection table. + * + * @param fd the remote client socket (it will be AF_UNIX type) + * @param sa the remote client address + * @param socklen the bytes of sa + * @param localVoid a void point to the MetisLocalListener that owns the server socket + */ +static void +metisListenerLocal_Listen(int fd, + struct sockaddr *sa, int socklen, void *localVoid) +{ + MetisLocalListener *local = (MetisLocalListener *) localVoid; + assertTrue(sa->sa_family == AF_UNIX, "Got wrong address family, expected %d got %d", AF_UNIX, sa->sa_family); + + CPIAddress *remote = cpiAddress_CreateFromUnix((struct sockaddr_un *) sa); + MetisAddressPair *pair = metisAddressPair_Create(local->localAddress, remote); + + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(local->metis, fd, pair, true); + MetisConnection *conn = metisConnection_Create(ops); + + metisConnectionTable_Add(metisForwarder_GetConnectionTable(local->metis), conn); + + if (metisLogger_IsLoggable(local->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + char *str = metisAddressPair_ToString(pair); + metisLogger_Log(local->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Listener %p started on address pair %s", (void *) local, str); + free(str); + } + + cpiAddress_Destroy(&remote); +} + +static void +_metisLocalListener_OpsDestroy(MetisListenerOps **listenerOpsPtr) +{ + MetisListenerOps *ops = *listenerOpsPtr; + MetisLocalListener *local = (MetisLocalListener *) ops->context; + + if (metisLogger_IsLoggable(local->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(local->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Listener %p destroyed", (void *) local); + } + + metisLocalListener_Destroy(&local); + parcMemory_Deallocate((void **) &ops); + *listenerOpsPtr = NULL; +} + +static unsigned +_metisLocalListener_OpsGetInterfaceIndex(const MetisListenerOps *ops) +{ + MetisLocalListener *local = (MetisLocalListener *) ops->context; + return local->id; +} + +static const CPIAddress * +_metisLocalListener_OpsGetListenAddress(const MetisListenerOps *ops) +{ + MetisLocalListener *local = (MetisLocalListener *) ops->context; + return local->localAddress; +} + +static MetisEncapType +_metisLocalListener_OpsGetEncapType(const MetisListenerOps *ops) +{ + return METIS_ENCAP_LOCAL; +} diff --git a/metis/ccnx/forwarder/metis/io/metis_LocalListener.h b/metis/ccnx/forwarder/metis/io/metis_LocalListener.h new file mode 100644 index 00000000..1e497a18 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_LocalListener.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + + +#ifndef Metis_metis_ListenerLocal_h +#define Metis_metis_ListenerLocal_h + +#include <stdlib.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +struct metis_local_listener; +typedef struct metis_local_listener MetisLocalListener; + +MetisListenerOps *metisLocalListener_Create(MetisForwarder *metis, const char *path); +void metisLocalListener_Destroy(MetisLocalListener **listenerPtr); +#endif // Metis_metis_ListenerLocal_h diff --git a/metis/ccnx/forwarder/metis/io/metis_StreamConnection.c b/metis/ccnx/forwarder/metis/io/metis_StreamConnection.c new file mode 100644 index 00000000..225e6d0d --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_StreamConnection.c @@ -0,0 +1,570 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Common activity for STREAM based listeners. + */ + +#include <config.h> +#include <stdio.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> + +#include <ccnx/forwarder/metis/io/metis_StreamConnection.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_Connection.h> +#include <ccnx/forwarder/metis/core/metis_Message.h> +#include <parc/algol/parc_Hash.h> + +#include <LongBow/runtime.h> +#include <parc/algol/parc_Memory.h> +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> + +// 128 KB output queue +#define OUTPUT_QUEUE_BYTES (128 * 1024) + +static void +_conn_readcb(PARCEventQueue *bufferEventVector, PARCEventType type, void *ioOpsVoid); + +static void +_conn_eventcb(PARCEventQueue *bufferEventVector, PARCEventQueueEventType events, void *ioOpsVoid); + +typedef struct metis_stream_state { + MetisForwarder *metis; + MetisLogger *logger; + + int fd; + + MetisAddressPair *addressPair; + PARCEventQueue *bufferEventVector; + + bool isLocal; + bool isUp; + bool isClosed; + unsigned id; + + size_t nextMessageLength; +} _MetisStreamState; + +// Prototypes +static bool _metisStreamConnection_Send(MetisIoOperations *ops, const CPIAddress *nexthop, MetisMessage *message); +static const CPIAddress *_metisStreamConnection_GetRemoteAddress(const MetisIoOperations *ops); +static const MetisAddressPair *_metisStreamConnection_GetAddressPair(const MetisIoOperations *ops); +static unsigned _metisStreamConnection_GetConnectionId(const MetisIoOperations *ops); +static bool _metisStreamConnection_IsUp(const MetisIoOperations *ops); +static bool _metisStreamConnection_IsLocal(const MetisIoOperations *ops); +static void _metisStreamConnection_DestroyOperations(MetisIoOperations **opsPtr); + +static void _setConnectionState(_MetisStreamState *stream, bool isUp); +static CPIConnectionType _metisStreamConnection_GetConnectionType(const MetisIoOperations *ops); +static MetisTicks _sendProbe(MetisIoOperations *ops, unsigned probeType); + +/* + * This assigns a unique pointer to the void * which we use + * as a GUID for this class. + */ +static const void *_metisIoOperationsGuid = __FILE__; + +/* + * Return our GUID + */ +static const void * +_metisStreamConnection_Class(const MetisIoOperations *ops) +{ + return _metisIoOperationsGuid; +} + +static MetisIoOperations _template = { + .closure = NULL, + .send = &_metisStreamConnection_Send, + .getRemoteAddress = &_metisStreamConnection_GetRemoteAddress, + .getAddressPair = &_metisStreamConnection_GetAddressPair, + .getConnectionId = &_metisStreamConnection_GetConnectionId, + .isUp = &_metisStreamConnection_IsUp, + .isLocal = &_metisStreamConnection_IsLocal, + .destroy = &_metisStreamConnection_DestroyOperations, + .class = &_metisStreamConnection_Class, + .getConnectionType = &_metisStreamConnection_GetConnectionType, + .sendProbe = &_sendProbe, +}; + +MetisIoOperations * +metisStreamConnection_AcceptConnection(MetisForwarder *metis, int fd, MetisAddressPair *pair, bool isLocal) +{ + _MetisStreamState *stream = parcMemory_AllocateAndClear(sizeof(_MetisStreamState)); + assertNotNull(stream, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisStreamState)); + + MetisDispatcher *dispatcher = metisForwarder_GetDispatcher(metis); + PARCEventScheduler *eventBase = metisDispatcher_GetEventScheduler(dispatcher); + stream->bufferEventVector = parcEventQueue_Create(eventBase, fd, PARCEventQueueOption_CloseOnFree | PARCEventQueueOption_DeferCallbacks); + + stream->metis = metis; + stream->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + stream->fd = fd; + stream->id = metisForwarder_GetNextConnectionId(metis); + stream->addressPair = pair; + stream->isClosed = false; + + // allocate a connection + MetisIoOperations *io_ops = parcMemory_AllocateAndClear(sizeof(MetisIoOperations)); + assertNotNull(io_ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisIoOperations)); + memcpy(io_ops, &_template, sizeof(MetisIoOperations)); + io_ops->closure = stream; + stream->isLocal = isLocal; + + parcEventQueue_SetCallbacks(stream->bufferEventVector, _conn_readcb, NULL, _conn_eventcb, (void *) io_ops); + parcEventQueue_Enable(stream->bufferEventVector, PARCEventType_Read); + + metisMessenger_Send(metisForwarder_GetMessenger(stream->metis), metisMissive_Create(MetisMissiveType_ConnectionCreate, stream->id)); + + // As we are acceting a connection, we begin in the UP state + _setConnectionState(stream, true); + + if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + char *pair_str = metisAddressPair_ToString(pair); + metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "StreamConnection %p accept for address pair %s", (void *) stream, pair_str); + free(pair_str); + } + + return io_ops; +} + +MetisIoOperations * +metisStreamConnection_OpenConnection(MetisForwarder *metis, MetisAddressPair *pair, bool isLocal) +{ + assertNotNull(metis, "Parameter metis must be non-null"); + assertNotNull(pair, "Parameter pair must be non-null"); + + // if there's an error on the bind or connect, will return NULL + PARCEventQueue *bufferEventVector = metisDispatcher_StreamBufferConnect(metisForwarder_GetDispatcher(metis), pair); + if (bufferEventVector == NULL) { + // error opening connection + return NULL; + } + + _MetisStreamState *stream = parcMemory_AllocateAndClear(sizeof(_MetisStreamState)); + assertNotNull(stream, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisStreamState)); + + stream->metis = metis; + stream->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + stream->fd = parcEventQueue_GetFileDescriptor(bufferEventVector); + stream->bufferEventVector = bufferEventVector; + stream->id = metisForwarder_GetNextConnectionId(metis); + stream->addressPair = pair; + stream->isClosed = false; + + // allocate a connection + MetisIoOperations *io_ops = parcMemory_AllocateAndClear(sizeof(MetisIoOperations)); + assertNotNull(io_ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisIoOperations)); + memcpy(io_ops, &_template, sizeof(MetisIoOperations)); + io_ops->closure = stream; + stream->isLocal = isLocal; + + parcEventQueue_SetCallbacks(stream->bufferEventVector, _conn_readcb, NULL, _conn_eventcb, (void *) io_ops); + parcEventQueue_Enable(stream->bufferEventVector, PARCEventType_Read); + + // we start in DOWN state, until remote side answers + metisMessenger_Send(metisForwarder_GetMessenger(stream->metis), metisMissive_Create(MetisMissiveType_ConnectionCreate, stream->id)); + _setConnectionState(stream, false); + + if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Info)) { + char *pair_str = metisAddressPair_ToString(pair); + metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__, + "StreamConnection %p connect for address pair %s", (void *) stream, pair_str); + free(pair_str); + } + + return io_ops; +} + +static void +_metisStreamConnection_DestroyOperations(MetisIoOperations **opsPtr) +{ + assertNotNull(opsPtr, "Parameter opsPtr must be non-null double pointer"); + assertNotNull(*opsPtr, "Parameter opsPtr must dereference to non-null pointer"); + + MetisIoOperations *ops = *opsPtr; + assertNotNull(metisIoOperations_GetClosure(ops), "ops->context must not be null"); + + _MetisStreamState *stream = (_MetisStreamState *) metisIoOperations_GetClosure(ops); + + parcEventQueue_Destroy(&stream->bufferEventVector); + + metisAddressPair_Release(&stream->addressPair); + + if (!stream->isClosed) { + stream->isClosed = true; + metisMessenger_Send(metisForwarder_GetMessenger(stream->metis), metisMissive_Create(MetisMissiveType_ConnectionClosed, stream->id)); + } + + metisMessenger_Send(metisForwarder_GetMessenger(stream->metis), metisMissive_Create(MetisMissiveType_ConnectionDestroyed, stream->id)); + + if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Info)) { + metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__, + "StreamConnection %p destroyed", (void *) stream); + } + + metisLogger_Release(&stream->logger); + parcMemory_Deallocate((void **) &stream); + parcMemory_Deallocate((void **) &ops); + + *opsPtr = NULL; +} + +static bool +_metisStreamConnection_IsUp(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisStreamState *stream = (const _MetisStreamState *) metisIoOperations_GetClosure(ops); + return stream->isUp; +} + +static bool +_metisStreamConnection_IsLocal(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisStreamState *stream = (const _MetisStreamState *) metisIoOperations_GetClosure(ops); + return stream->isLocal; +} + +static const CPIAddress * +_metisStreamConnection_GetRemoteAddress(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisStreamState *stream = (const _MetisStreamState *) metisIoOperations_GetClosure(ops); + return metisAddressPair_GetRemote(stream->addressPair); +} + +static const MetisAddressPair * +_metisStreamConnection_GetAddressPair(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisStreamState *stream = (const _MetisStreamState *) metisIoOperations_GetClosure(ops); + return stream->addressPair; +} + +static unsigned +_metisStreamConnection_GetConnectionId(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisStreamState *stream = (const _MetisStreamState *) metisIoOperations_GetClosure(ops); + return stream->id; +} + +/** + * @function metisStreamConnection_Send + * @abstract Non-destructive send of the message. + * @discussion + * Send uses metisMessage_CopyToStreamBuffer, which is a non-destructive write. + * The send may fail if there's no buffer space in the output queue. + * + * @param dummy is ignored. A stream has only one peer. + * @return <#return#> + */ +static bool +_metisStreamConnection_Send(MetisIoOperations *ops, const CPIAddress *dummy, MetisMessage *message) +{ + assertNotNull(ops, "Parameter ops must be non-null"); + assertNotNull(message, "Parameter message must be non-null"); + _MetisStreamState *stream = (_MetisStreamState *) metisIoOperations_GetClosure(ops); + + bool success = false; + if (stream->isUp) { + PARCEventBuffer *buffer = parcEventBuffer_GetQueueBufferOutput(stream->bufferEventVector); + size_t buffer_backlog = parcEventBuffer_GetLength(buffer); + parcEventBuffer_Destroy(&buffer); + + if (buffer_backlog < OUTPUT_QUEUE_BYTES) { + if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "connid %u Writing %zu bytes to buffer with backlog %zu bytes", + stream->id, + metisMessage_Length(message), + buffer_backlog); + } + + int failure = metisMessage_Write(stream->bufferEventVector, message); + if (failure == 0) { + success = true; + } + } else { + if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning)) { + metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "connid %u Writing to buffer backlog %zu bytes DROP MESSAGE", + stream->id, + buffer_backlog); + } + } + } else { + if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "connid %u tried to send to down connection (isUp %d isClosed %d)", + stream->id, + stream->isUp, + stream->isClosed); + } + } + + return success; +} + +static CPIConnectionType +_metisStreamConnection_GetConnectionType(const MetisIoOperations *ops) +{ + return cpiConnection_TCP; +} + +static MetisTicks +_sendProbe(MetisIoOperations *ops, unsigned probeType) +{ + //TODO + return 0; +} + +// ================================================================= +// the actual I/O functions + +/** + * @function startNewMessage + * @abstract Peek at the fixed header and set the stream->nextMessageLength + * @discussion + * This function manipulates the stream->nextMessageLength. After reading a FixedHeader, set nextMessageLength + * to the total length of the message. + * + * PRECONDITION: stream->nextMessageLength == 0 + * PRECONDITION: inputBytesAvailable >= FIXED_HEADER_LEN + * + * @param stream is the stream begin parsed. + * @param input is the input PARCEventBuffer (corresponds to the buffer event's input) + * @param inputBytesAvailable is the number of bytes available in the input PARCEventBuffer. + * @return <#return#> + */ +static void +_startNewMessage(_MetisStreamState *stream, PARCEventBuffer *input, size_t inputBytesAvailable) +{ + assertTrue(stream->nextMessageLength == 0, "Invalid state, nextMessageLength not zero: %zu", stream->nextMessageLength); + assertTrue(inputBytesAvailable >= metisTlv_FixedHeaderLength(), "read_length not a whole fixed header!: %zd", inputBytesAvailable); + + // this linearizes the first FIXED_HEADER_LEN bytes of the input buffer's iovecs and + // returns a pointer to it. + uint8_t *fh = parcEventBuffer_Pullup(input, metisTlv_FixedHeaderLength()); + + // Calculate the total message size based on the fixed header + stream->nextMessageLength = metisTlv_TotalPacketLength(fh); +} + +/** + * @function readMessage + * @abstract Read the complete message from the input + * @discussion + * Called to read a complete message from the input and return a MetisMessage + * + * PRECONDITION: There are at least <code>stream->nextMessageLength</code> + * bytes available on the input PARCEventBuffer. + * + * @param stream is the stream being parsed + * @param time is the current forwarder time (metisForwarder_GetTicks(stream->metis)) + * @param input is the input PARCEventBuffer to readessage bytes. + * @return <#return#> + */ +static MetisMessage * +_readMessage(_MetisStreamState *stream, MetisTicks time, PARCEventBuffer *input) +{ + MetisMessage *message = metisMessage_ReadFromBuffer(stream->id, time, input, stream->nextMessageLength, stream->logger); + + return message; +} + +/** + * @function single_read + * @abstract Read at most 1 message from the network + * @discussion + * If a complete message is ready on the input buffer, will allocate and return it. + * + * This function manipulates the stream->nextMessageLength. (1) Initializes with nextMessageLength = 0, + * which means we have not started parsing a packet. (2) After reading a FixedHeader, set nextMessageLength + * to the total length of the message. (3) After reading nextMessageLength bytes, return the outputBuffer + * and reset nextMessageLength to 0. + * + * @param input is the PARCEventBuffer to read + * @param stream is the related stream state for the input + * @return true if there's more to read after this message. + */ +static MetisMessage * +_single_read(PARCEventBuffer *input, _MetisStreamState *stream) +{ + size_t bytesAvailable = parcEventBuffer_GetLength(input); + + assertTrue(bytesAvailable >= metisTlv_FixedHeaderLength(), "Called with too short an input: %zu", bytesAvailable); + + if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "connid %u read %zu bytes", + stream->id, bytesAvailable); + } + + if (stream->nextMessageLength == 0) { + _startNewMessage(stream, input, bytesAvailable); + } + + // This is not an ELSE statement. We can both start a new message then + // check if there's enough bytes to read the whole thing. + + if (bytesAvailable >= stream->nextMessageLength) { + MetisMessage *message = _readMessage(stream, metisForwarder_GetTicks(stream->metis), input); + + if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "connid %u msg_length %zu read_length %zu, resetting parser", + stream->id, + stream->nextMessageLength, + bytesAvailable); + } + + // now reset message length for next packet + stream->nextMessageLength = 0; + + return message; + } + + return NULL; +} + +/** + * @function conn_readcb + * @abstract Event callback for reads + * @discussion + * Will read messages off the input. Continues reading as long as we + * can get a header to determine the next message length or as long as we + * can read a complete message. + * + * This function manipulates the read low water mark. (1) read a fixed header plus complete message, + * then set the low water mark to FIXED_HEADER_LEN. (2) read a fixed header, but not a complete + * message, then set low water mark to the total mesage length. Using the low water mark like this + * means the buffer event will only trigger on meaningful byte boundaries when we can get actual + * work done. + * + * @param <#param1#> + * @return <#return#> + */ +static void +_conn_readcb(PARCEventQueue *event, PARCEventType type, void *ioOpsVoid) +{ + MetisIoOperations *ops = (MetisIoOperations *) ioOpsVoid; + _MetisStreamState *stream = (_MetisStreamState *) metisIoOperations_GetClosure(ops); + + PARCEventBuffer *input = parcEventBuffer_GetQueueBufferInput(event); + + // drain the input buffer + while (parcEventBuffer_GetLength(input) >= metisTlv_FixedHeaderLength() && parcEventBuffer_GetLength(input) >= stream->nextMessageLength) { + // this may set the stream->nextMessageLength + MetisMessage *message = _single_read(input, stream); + + if (message) { + metisForwarder_Receive(stream->metis, message); + } + } + + if (stream->nextMessageLength == 0) { + // we don't have the next header, so set it to the header length + metisStreamBuffer_SetWatermark(event, true, false, metisTlv_FixedHeaderLength(), 0); + } else { + // set it to the packet length + metisStreamBuffer_SetWatermark(event, true, false, stream->nextMessageLength, 0); + } + parcEventBuffer_Destroy(&input); +} + +static void +_setConnectionState(_MetisStreamState *stream, bool isUp) +{ + assertNotNull(stream, "Parameter stream must be non-null"); + + MetisMessenger *messenger = metisForwarder_GetMessenger(stream->metis); + + bool oldStateIsUp = stream->isUp; + stream->isUp = isUp; + + if (oldStateIsUp && !isUp) { + // bring connection DOWN + MetisMissive *missive = metisMissive_Create(MetisMissiveType_ConnectionDown, stream->id); + metisMessenger_Send(messenger, missive); + return; + } + + + if (!oldStateIsUp && isUp) { + // bring connection UP + MetisMissive *missive = metisMissive_Create(MetisMissiveType_ConnectionUp, stream->id); + metisMessenger_Send(messenger, missive); + return; + } +} + +static void +_conn_eventcb(PARCEventQueue *event, PARCEventQueueEventType events, void *ioOpsVoid) +{ + MetisIoOperations *ops = (MetisIoOperations *) ioOpsVoid; + _MetisStreamState *stream = (_MetisStreamState *) metisIoOperations_GetClosure(ops); + + if (events & PARCEventQueueEventType_Connected) { + if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Info)) { + metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__, + "Connection %u is connected", stream->id); + } + + // if the stream was closed, do not transition to an UP state + if (!stream->isClosed) { + _setConnectionState(stream, true); + } + } else + if (events & PARCEventQueueEventType_EOF) { + if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Info)) { + metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__, + "connid %u closed.", + stream->id); + } + + parcEventQueue_Disable(stream->bufferEventVector, PARCEventType_Read); + + _setConnectionState(stream, false); + + if (!stream->isClosed) { + stream->isClosed = true; + // this will cause the connection manager to destroy the connection later + metisMessenger_Send(metisForwarder_GetMessenger(stream->metis), metisMissive_Create(MetisMissiveType_ConnectionClosed, stream->id)); + } + } else + if (events & PARCEventQueueEventType_Error) { + if (metisLogger_IsLoggable(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(stream->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Got an error on the connection %u: %s", stream->id, strerror(errno)); + } + + parcEventQueue_Disable(stream->bufferEventVector, PARCEventType_Read | PARCEventType_Write); + + _setConnectionState(stream, false); + + if (!stream->isClosed) { + stream->isClosed = true; + // this will cause the connection manager to destroy the connection later + metisMessenger_Send(metisForwarder_GetMessenger(stream->metis), metisMissive_Create(MetisMissiveType_ConnectionClosed, stream->id)); + } + } + /* None of the other events can happen here, since we haven't enabled + * timeouts */ +} diff --git a/metis/ccnx/forwarder/metis/io/metis_StreamConnection.h b/metis/ccnx/forwarder/metis/io/metis_StreamConnection.h new file mode 100644 index 00000000..ee614862 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_StreamConnection.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +/** + * Methods common to TCP and PF_LOCAL stream-based listeners + */ + +#ifndef Metis_metis_StreamConnection_h +#define Metis_metis_StreamConnection_h + +#include <ccnx/forwarder/metis/io/metis_IoOperations.h> +#include <ccnx/forwarder/metis/io/metis_AddressPair.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/api/control/cpi_Address.h> + +/** + * @function metisStreamConnection_AcceptConnection + * @abstract Receive a connection from a remote peer + * @discussion + * We are the "server side" of the stream connection, so we need to accept the client connection + * and setup state for her. + * + * @param <#param1#> + * @return <#return#> + */ +MetisIoOperations *metisStreamConnection_AcceptConnection(MetisForwarder *metis, int fd, MetisAddressPair *pair, bool isLocal); + +/** + * @function metisStreamConnection_OpenConnection + * @abstract Initiate a connection to a remote peer + * @discussion + * We are the "client side" of the stream connection. We'll create state for the peer, but it will + * be in the "down" state until the connection establishes. + * + * For TCP, both address pairs need to be the same address family: both INET or both INET6. The remote + * address must have the complete socket information (address, port). The local socket could be wildcarded or + * may specify down to the (address, port) pair. + * + * If the local address is IPADDR_ANY and the port is 0, then it is a normal call to "connect" that will use + * whatever local IP address and whatever local port for the connection. If either the address or port is + * set, the local socket will first be bound (via bind(2)), and then call connect(). + * + * AF_UNIX is not yet supported + * + * If there's an error binding to the specified address or connecting to the remote address, + * will return NULL. + * + * @param pair (takes ownership of this) is the complete socket pair of (address, port) for each end, if INET or INET6. + * @return NULL on error, otherwise the connections IO operations. + */ +MetisIoOperations *metisStreamConnection_OpenConnection(MetisForwarder *metis, MetisAddressPair *pair, bool isLocal); +#endif // Metis_metis_StreamConnection_h diff --git a/metis/ccnx/forwarder/metis/io/metis_TcpListener.c b/metis/ccnx/forwarder/metis/io/metis_TcpListener.c new file mode 100644 index 00000000..e9d232ce --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_TcpListener.c @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <config.h> +#include <stdio.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> + +#include <ccnx/forwarder/metis/io/metis_TcpListener.h> +#include <ccnx/forwarder/metis/io/metis_StreamConnection.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_ConnectionTable.h> +#include <ccnx/forwarder/metis/io/metis_Listener.h> + +#include <LongBow/runtime.h> +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_Network.h> + +typedef struct metis_tcp_listener { + MetisForwarder *metis; + MetisLogger *logger; + + PARCEventSocket *listener; + + CPIAddress *localAddress; + + unsigned id; + + // is the localAddress as 127.0.0.0 address? + bool isLocalAddressLocal; +} _MetisTcpListener; + +static void _metisTcpListener_Destroy(_MetisTcpListener **listenerPtr); + +static void _metisTcpListener_OpsDestroy(MetisListenerOps **listenerOpsPtr); +static unsigned _metisTcpListener_OpsGetInterfaceIndex(const MetisListenerOps *ops); +static const CPIAddress *_metisTcpListener_OpsGetListenAddress(const MetisListenerOps *ops); +static MetisEncapType _metisTcpListener_OpsGetEncapType(const MetisListenerOps *ops); + +static MetisListenerOps _tcpTemplate = { + .context = NULL, + .destroy = &_metisTcpListener_OpsDestroy, + .getInterfaceIndex = &_metisTcpListener_OpsGetInterfaceIndex, + .getListenAddress = &_metisTcpListener_OpsGetListenAddress, + .getEncapType = &_metisTcpListener_OpsGetEncapType, + .getSocket = NULL +}; + +// STREAM daemon listener callback +static void _metisTcpListener_Listen(int, struct sockaddr *, int socklen, void *tcpVoid); + + +MetisListenerOps * +metisTcpListener_CreateInet6(MetisForwarder *metis, struct sockaddr_in6 sin6) +{ + _MetisTcpListener *tcp = parcMemory_AllocateAndClear(sizeof(_MetisTcpListener)); + assertNotNull(tcp, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisTcpListener)); + + tcp->metis = metis; + tcp->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + + tcp->listener = metisDispatcher_CreateListener(metisForwarder_GetDispatcher(metis), _metisTcpListener_Listen, + (void *) tcp, -1, (struct sockaddr*) &sin6, sizeof(sin6)); + + if (tcp->listener == NULL) { + metisLogger_Log(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "metisDispatcher_CreateListener failed to create listener (%d) %s", + errno, strerror(errno)); + metisLogger_Release(&tcp->logger); + parcMemory_Deallocate((void **) &tcp); + return NULL; + } + + tcp->localAddress = cpiAddress_CreateFromInet6(&sin6); + tcp->id = metisForwarder_GetNextConnectionId(metis); + tcp->isLocalAddressLocal = parcNetwork_IsSocketLocal((struct sockaddr *) &sin6); + + MetisListenerOps *ops = parcMemory_AllocateAndClear(sizeof(MetisListenerOps)); + assertNotNull(ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisListenerOps)); + + memcpy(ops, &_tcpTemplate, sizeof(MetisListenerOps)); + ops->context = tcp; + + if (metisLogger_IsLoggable(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + char *str = cpiAddress_ToString(tcp->localAddress); + metisLogger_Log(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "TcpListener %p created for address %s (isLocal %d)", + (void *) tcp, str, tcp->isLocalAddressLocal); + parcMemory_Deallocate((void **) &str); + } + + return ops; +} + +MetisListenerOps * +metisTcpListener_CreateInet(MetisForwarder *metis, struct sockaddr_in sin) +{ + _MetisTcpListener *tcp = parcMemory_AllocateAndClear(sizeof(_MetisTcpListener)); + assertNotNull(tcp, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisTcpListener)); + + tcp->metis = metis; + tcp->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + tcp->listener = metisDispatcher_CreateListener(metisForwarder_GetDispatcher(metis), _metisTcpListener_Listen, + (void *) tcp, -1, (struct sockaddr*) &sin, sizeof(sin)); + + if (tcp->listener == NULL) { + metisLogger_Log(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "metisDispatcher_CreateListener failed to create listener (%d) %s", + errno, strerror(errno)); + + metisLogger_Release(&tcp->logger); + parcMemory_Deallocate((void **) &tcp); + return NULL; + } + + tcp->localAddress = cpiAddress_CreateFromInet(&sin); + tcp->id = metisForwarder_GetNextConnectionId(metis); + tcp->isLocalAddressLocal = parcNetwork_IsSocketLocal((struct sockaddr *) &sin); + + MetisListenerOps *ops = parcMemory_AllocateAndClear(sizeof(MetisListenerOps)); + assertNotNull(ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisListenerOps)); + + memcpy(ops, &_tcpTemplate, sizeof(MetisListenerOps)); + ops->context = tcp; + + if (metisLogger_IsLoggable(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + char *str = cpiAddress_ToString(tcp->localAddress); + metisLogger_Log(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "TcpListener %p created for address %s (isLocal %d)", + (void *) tcp, str, tcp->isLocalAddressLocal); + parcMemory_Deallocate((void **) &str); + } + + return ops; +} + +static void +_metisTcpListener_Destroy(_MetisTcpListener **listenerPtr) +{ + assertNotNull(listenerPtr, "Parameter must be non-null double pointer"); + assertNotNull(*listenerPtr, "Parameter must derefernce to non-null pointer"); + _MetisTcpListener *tcp = *listenerPtr; + + if (metisLogger_IsLoggable(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + char *str = cpiAddress_ToString(tcp->localAddress); + metisLogger_Log(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "TcpListener %p destroyed", (void *) tcp); + parcMemory_Deallocate((void **) &str); + } + + metisLogger_Release(&tcp->logger); + metisDispatcher_DestroyListener(metisForwarder_GetDispatcher(tcp->metis), &tcp->listener); + cpiAddress_Destroy(&tcp->localAddress); + parcMemory_Deallocate((void **) &tcp); + *listenerPtr = NULL; +} + +// ================================================== + +static void +_metisTcpListener_Listen(int fd, struct sockaddr *sa, int socklen, void *tcpVoid) +{ + _MetisTcpListener *tcp = (_MetisTcpListener *) tcpVoid; + + CPIAddress *remote; + + switch (sa->sa_family) { + case AF_INET: + remote = cpiAddress_CreateFromInet((struct sockaddr_in *) sa); + break; + + case AF_INET6: + remote = cpiAddress_CreateFromInet6((struct sockaddr_in6 *) sa); + break; + + default: + trapIllegalValue(sa, "Expected INET or INET6, got %d", sa->sa_family); + abort(); + } + + MetisAddressPair *pair = metisAddressPair_Create(tcp->localAddress, remote); + + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(tcp->metis, fd, pair, tcp->isLocalAddressLocal); + MetisConnection *conn = metisConnection_Create(ops); + + metisConnectionTable_Add(metisForwarder_GetConnectionTable(tcp->metis), conn); + + if (metisLogger_IsLoggable(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(tcp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "TcpListener %p listen started", (void *) tcp); + } + + cpiAddress_Destroy(&remote); +} + +static void +_metisTcpListener_OpsDestroy(MetisListenerOps **listenerOpsPtr) +{ + MetisListenerOps *ops = *listenerOpsPtr; + _MetisTcpListener *tcp = (_MetisTcpListener *) ops->context; + _metisTcpListener_Destroy(&tcp); + parcMemory_Deallocate((void **) &ops); + *listenerOpsPtr = NULL; +} + +static unsigned +_metisTcpListener_OpsGetInterfaceIndex(const MetisListenerOps *ops) +{ + _MetisTcpListener *tcp = (_MetisTcpListener *) ops->context; + return tcp->id; +} + +static const CPIAddress * +_metisTcpListener_OpsGetListenAddress(const MetisListenerOps *ops) +{ + _MetisTcpListener *tcp = (_MetisTcpListener *) ops->context; + return tcp->localAddress; +} + +static MetisEncapType +_metisTcpListener_OpsGetEncapType(const MetisListenerOps *ops) +{ + return METIS_ENCAP_TCP; +} diff --git a/metis/ccnx/forwarder/metis/io/metis_TcpListener.h b/metis/ccnx/forwarder/metis/io/metis_TcpListener.h new file mode 100644 index 00000000..e62edba9 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_TcpListener.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metis_TcpListener.h + * @brief Listens for in coming TCP connections + * + * This is the "server socket" of metis for TCP connections. The actual I/O is + * handled by {@link MetisStreamConnection}. + * + */ + +#ifndef Metis_metis_TcpListener_h +#define Metis_metis_TcpListener_h + +#include <netinet/in.h> +#include <stdlib.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/io/metis_Listener.h> + +MetisListenerOps *metisTcpListener_CreateInet6(MetisForwarder *metis, struct sockaddr_in6 sin6); +MetisListenerOps *metisTcpListener_CreateInet(MetisForwarder *metis, struct sockaddr_in sin); +#endif // Metis_metis_TcpListener_h diff --git a/metis/ccnx/forwarder/metis/io/metis_TcpTunnel.c b/metis/ccnx/forwarder/metis/io/metis_TcpTunnel.c new file mode 100644 index 00000000..b0182240 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_TcpTunnel.c @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * The TCP out-bound tunnel is almost identical to the in-bound tunnel. + * We use MetisStreamConneciton for the out-bound tunnels too. We call a different + * constructor than the in-bound so the MetisStreamConneciton knows that it is starting + * unconnected and needs to wait for the Connected event before putting it in the UP state. + * + */ + +#include <config.h> +#include <stdio.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> + +#include <ccnx/forwarder/metis/io/metis_TcpTunnel.h> +#include <ccnx/forwarder/metis/io/metis_StreamConnection.h> + +#include <LongBow/runtime.h> + +MetisIoOperations * +metisTcpTunnel_Create(MetisForwarder *metis, const CPIAddress *localAddress, const CPIAddress *remoteAddress) +{ + MetisAddressPair *pair = metisAddressPair_Create(localAddress, remoteAddress); + + bool isLocal = false; + + // this takes ownership of the address pair + return metisStreamConnection_OpenConnection(metis, pair, isLocal); +} diff --git a/metis/ccnx/forwarder/metis/io/metis_TcpTunnel.h b/metis/ccnx/forwarder/metis/io/metis_TcpTunnel.h new file mode 100644 index 00000000..ec53c7cd --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_TcpTunnel.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metis_TcpTunnel.h + * @brief Creates a TCP Tunnel to a remote address + * + * The connection will be established in "connecting" state and once the remote accepts, it will + * be promoted to "up" state. + * + */ + +#ifndef Metis_metis_TcpTunnel_h +#define Metis_metis_TcpTunnel_h + +#include <ccnx/api/control/cpi_Address.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/io/metis_IoOperations.h> + +/** + * @function metisTcpTunnel_Create + * @abstract Creates a TCP tunnel to a remote system. + * @discussion + * The two addresses must be the same type (i.e. both INET or INET6) and cannot point to the same system. + * + * The tunnel will look just like an in-bound connection after its built. It exposes the standard + * MetisIoOperations so it can be put in the MetisConnectionTable. + * + * The connection will go in the table immediately, but will be in the "down" state until the + * connection is established. + * + * + * @param <#param1#> + * @return The I/O ops for the tunnel. + */ +MetisIoOperations *metisTcpTunnel_Create(MetisForwarder *metis, const CPIAddress *localAddress, const CPIAddress *remoteAddress); +#endif // Metis_metis_TcpTunnel_h diff --git a/metis/ccnx/forwarder/metis/io/metis_UdpConnection.c b/metis/ccnx/forwarder/metis/io/metis_UdpConnection.c new file mode 100644 index 00000000..aecd68fa --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_UdpConnection.c @@ -0,0 +1,396 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Embodies the reader/writer for a UDP connection + * + * NB The Send() function may overflow the output buffer + * + */ + +#include <config.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include <ccnx/forwarder/metis/io/metis_UdpConnection.h> + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_Connection.h> +#include <ccnx/forwarder/metis/core/metis_Message.h> +#include <parc/algol/parc_Hash.h> + +#include <LongBow/runtime.h> +#include <parc/algol/parc_Memory.h> +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> + +typedef struct metis_udp_state { + MetisForwarder *metis; + MetisLogger *logger; + + // the udp listener socket we receive packets on + int udpListenerSocket; + + MetisAddressPair *addressPair; + + // We need to access this all the time, so grab it out + // of the addressPair; + struct sockaddr *peerAddress; + socklen_t peerAddressLength; + + bool isLocal; + bool isUp; + unsigned id; + + unsigned delay; +} _MetisUdpState; + +// Prototypes +static bool _send(MetisIoOperations *ops, const CPIAddress *nexthop, MetisMessage *message); +static const CPIAddress *_getRemoteAddress(const MetisIoOperations *ops); +static const MetisAddressPair *_getAddressPair(const MetisIoOperations *ops); +static unsigned _getConnectionId(const MetisIoOperations *ops); +static bool _isUp(const MetisIoOperations *ops); +static bool _isLocal(const MetisIoOperations *ops); +static void _destroy(MetisIoOperations **opsPtr); +static CPIConnectionType _getConnectionType(const MetisIoOperations *ops); +static MetisTicks _sendProbe(MetisIoOperations *ops, unsigned probeType); +/* + * This assigns a unique pointer to the void * which we use + * as a GUID for this class. + */ +static const void *_metisIoOperationsGuid = __FILE__; + +/* + * Return our GUID + */ +static const void * +_metisStreamConnection_Class(const MetisIoOperations *ops) +{ + return _metisIoOperationsGuid; +} + +static MetisIoOperations _template = { + .closure = NULL, + .send = &_send, + .getRemoteAddress = &_getRemoteAddress, + .getAddressPair = &_getAddressPair, + .getConnectionId = &_getConnectionId, + .isUp = &_isUp, + .isLocal = &_isLocal, + .destroy = &_destroy, + .class = &_metisStreamConnection_Class, + .getConnectionType = &_getConnectionType, + .sendProbe = &_sendProbe +}; + +// ================================================================= + +static void _setConnectionState(_MetisUdpState *Udp, bool isUp); +static bool _saveSockaddr(_MetisUdpState *udpConnState, const MetisAddressPair *pair); + +MetisIoOperations * +metisUdpConnection_Create(MetisForwarder *metis, int fd, const MetisAddressPair *pair, bool isLocal) +{ + MetisIoOperations *io_ops = NULL; + + _MetisUdpState *udpConnState = parcMemory_AllocateAndClear(sizeof(_MetisUdpState)); + assertNotNull(udpConnState, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisUdpState)); + + udpConnState->metis = metis; + udpConnState->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + + bool saved = _saveSockaddr(udpConnState, pair); + if (saved) { + udpConnState->udpListenerSocket = fd; + udpConnState->id = metisForwarder_GetNextConnectionId(metis); + udpConnState->addressPair = metisAddressPair_Acquire(pair); + udpConnState->isLocal = isLocal; + + // allocate a connection + io_ops = parcMemory_AllocateAndClear(sizeof(MetisIoOperations)); + assertNotNull(io_ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisIoOperations)); + memcpy(io_ops, &_template, sizeof(MetisIoOperations)); + io_ops->closure = udpConnState; + + _setConnectionState(udpConnState, true); + + if (metisLogger_IsLoggable(udpConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Info)) { + char *str = metisAddressPair_ToString(udpConnState->addressPair); + metisLogger_Log(udpConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__, + "UdpConnection %p created for address %s (isLocal %d)", + (void *) udpConnState, str, udpConnState->isLocal); + free(str); + } + + metisMessenger_Send(metisForwarder_GetMessenger(metis), metisMissive_Create(MetisMissiveType_ConnectionCreate, udpConnState->id)); + metisMessenger_Send(metisForwarder_GetMessenger(metis), metisMissive_Create(MetisMissiveType_ConnectionUp, udpConnState->id)); + } else { + // _saveSockaddr will already log an error, no need for extra log message here + metisLogger_Release(&udpConnState->logger); + parcMemory_Deallocate((void **) &udpConnState); + } + + return io_ops; +} + +// ================================================================= +// I/O Operations implementation + +static void +_destroy(MetisIoOperations **opsPtr) +{ + assertNotNull(opsPtr, "Parameter opsPtr must be non-null double pointer"); + assertNotNull(*opsPtr, "Parameter opsPtr must dereference to non-null pointer"); + + MetisIoOperations *ops = *opsPtr; + assertNotNull(metisIoOperations_GetClosure(ops), "ops->context must not be null"); + + _MetisUdpState *udpConnState = (_MetisUdpState *) metisIoOperations_GetClosure(ops); + metisAddressPair_Release(&udpConnState->addressPair); + parcMemory_Deallocate((void **) &(udpConnState->peerAddress)); + + metisMessenger_Send(metisForwarder_GetMessenger(udpConnState->metis), metisMissive_Create(MetisMissiveType_ConnectionDestroyed, udpConnState->id)); + + if (metisLogger_IsLoggable(udpConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Info)) { + metisLogger_Log(udpConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__, + "UdpConnection %p destroyed", + (void *) udpConnState); + } + + // do not close udp->udpListenerSocket, the listener will close + // that when its done + + metisLogger_Release(&udpConnState->logger); + parcMemory_Deallocate((void **) &udpConnState); + parcMemory_Deallocate((void **) &ops); + + *opsPtr = NULL; +} + +static bool +_isUp(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisUdpState *udpConnState = (const _MetisUdpState *) metisIoOperations_GetClosure(ops); + return udpConnState->isUp; +} + +static bool +_isLocal(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisUdpState *udpConnState = (const _MetisUdpState *) metisIoOperations_GetClosure(ops); + return udpConnState->isLocal; +} + +static const CPIAddress * +_getRemoteAddress(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisUdpState *udpConnState = (const _MetisUdpState *) metisIoOperations_GetClosure(ops); + return metisAddressPair_GetRemote(udpConnState->addressPair); +} + +static const MetisAddressPair * +_getAddressPair(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisUdpState *udpConnState = (const _MetisUdpState *) metisIoOperations_GetClosure(ops); + return udpConnState->addressPair; +} + +static unsigned +_getConnectionId(const MetisIoOperations *ops) +{ + assertNotNull(ops, "Parameter must be non-null"); + const _MetisUdpState *udpConnState = (const _MetisUdpState *) metisIoOperations_GetClosure(ops); + return udpConnState->id; +} + +/** + * @function metisUdpConnection_Send + * @abstract Non-destructive send of the message. + * @discussion + * sends a message to the peer. + * + * @param dummy is ignored. A udp connection has only one peer. + * @return <#return#> + */ +static bool +_send(MetisIoOperations *ops, const CPIAddress *dummy, MetisMessage *message) +{ + assertNotNull(ops, "Parameter ops must be non-null"); + assertNotNull(message, "Parameter message must be non-null"); + _MetisUdpState *udpConnState = (_MetisUdpState *) metisIoOperations_GetClosure(ops); + + PARCEventBuffer *writeBuffer = parcEventBuffer_Create(); + metisMessage_Append(writeBuffer, message); + + const uint8_t *buffer = parcEventBuffer_Pullup(writeBuffer, -1); + size_t bufferLength = parcEventBuffer_GetLength(writeBuffer); + + ssize_t writeLength = sendto(udpConnState->udpListenerSocket, buffer, bufferLength, 0, udpConnState->peerAddress, udpConnState->peerAddressLength); + + if (writeLength < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + parcEventBuffer_Destroy(&writeBuffer); + return false; + } else { + //this print is for debugging + printf("Incorrect write length %zd, expected %zd: (%d) %s\n", writeLength, bufferLength, errno, strerror(errno)); + parcEventBuffer_Destroy(&writeBuffer); + return false; + } + } + + parcEventBuffer_Destroy(&writeBuffer); + return true; +} + +static CPIConnectionType +_getConnectionType(const MetisIoOperations *ops) +{ + return cpiConnection_UDP; +} + +static MetisTicks +_sendProbe(MetisIoOperations *ops, unsigned probeType) +{ + assertNotNull(ops, "Parameter ops must be non-null"); + _MetisUdpState *udpConnState = (_MetisUdpState *) metisIoOperations_GetClosure(ops); + + + uint8_t *pkt; + size_t pkt_size = 8; + pkt = (uint8_t *) malloc(sizeof(uint8_t) * pkt_size); + for (unsigned i = 0; i < pkt_size; i++) { + pkt[i] = 0; + } + pkt[0] = 1; //tlv type + pkt[1] = probeType; //packet type + pkt[6] = 8; //header len (16bit, network order) + + ssize_t writeLen = sendto(udpConnState->udpListenerSocket, pkt, pkt_size, 0, udpConnState->peerAddress, udpConnState->peerAddressLength); + + if (writeLen < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + free(pkt); + return 0; + } else { + //this print is for debugging + printf("Incorrect write length %zd, expected %zd: (%d) %s\n", writeLen, pkt_size, errno, strerror(errno)); + free(pkt); + return 0; + } + } + + free(pkt); + return metisForwarder_GetTicks(udpConnState->metis); +} + +/*static MetisTicks + * _handleProbe(MetisIoOperations *ops, MetisTicks last_sent, uint8_t *pkt) + * { + * assertNotNull(ops, "Parameter ops must be non-null"); + * assertNotNull(pkt, "Parameter pkt must be non-null"); + * + * _MetisUdpState *udpConnState = (_MetisUdpState *) metisIoOperations_GetClosure(ops); + * + * MetisTicks delay = 0; + * + * if(pkt[1] == METIS_PACKET_TYPE_PROBE_REQUEST){ + * _sendProbeType(ops, METIS_PACKET_TYPE_PROBE_REPLY); + * } else if (pkt[1] == METIS_PACKET_TYPE_PROBE_REPLY) { + * MetisTicks now = metisForwarder_GetTicks(udpConnState->metis); + * delay = now - last_sent; + * } else { + * printf("receivde unkwon probe type\n"); + * } + * + * return delay; + * }*/ + +// ================================================================= +// Internal API + +static bool +_saveSockaddr(_MetisUdpState *udpConnState, const MetisAddressPair *pair) +{ + bool success = false; + const CPIAddress *remoteAddress = metisAddressPair_GetRemote(pair); + + switch (cpiAddress_GetType(remoteAddress)) { + case cpiAddressType_INET: { + size_t bytes = sizeof(struct sockaddr_in); + udpConnState->peerAddress = parcMemory_Allocate(bytes); + assertNotNull(udpConnState->peerAddress, "parcMemory_Allocate(%zu) returned NULL", bytes); + + cpiAddress_GetInet(remoteAddress, (struct sockaddr_in *) udpConnState->peerAddress); + udpConnState->peerAddressLength = (socklen_t) bytes; + + success = true; + break; + } + + case cpiAddressType_INET6: { + size_t bytes = sizeof(struct sockaddr_in6); + udpConnState->peerAddress = parcMemory_Allocate(bytes); + assertNotNull(udpConnState->peerAddress, "parcMemory_Allocate(%zu) returned NULL", bytes); + + cpiAddress_GetInet6(remoteAddress, (struct sockaddr_in6 *) udpConnState->peerAddress); + udpConnState->peerAddressLength = (socklen_t) bytes; + + success = true; + break; + } + + default: + if (metisLogger_IsLoggable(udpConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + char *str = cpiAddress_ToString(remoteAddress); + metisLogger_Log(udpConnState->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Remote address is not INET or INET6: %s", str); + parcMemory_Deallocate((void **) &str); + } + break; + } + return success; +} + +static void +_setConnectionState(_MetisUdpState *udpConnState, bool isUp) +{ + assertNotNull(udpConnState, "Parameter Udp must be non-null"); + + MetisMessenger *messenger = metisForwarder_GetMessenger(udpConnState->metis); + + bool oldStateIsUp = udpConnState->isUp; + udpConnState->isUp = isUp; + + if (oldStateIsUp && !isUp) { + // bring connection DOWN + MetisMissive *missive = metisMissive_Create(MetisMissiveType_ConnectionDown, udpConnState->id); + metisMessenger_Send(messenger, missive); + return; + } + + + if (!oldStateIsUp && isUp) { + // bring connection UP + MetisMissive *missive = metisMissive_Create(MetisMissiveType_ConnectionUp, udpConnState->id); + metisMessenger_Send(messenger, missive); + return; + } +} diff --git a/metis/ccnx/forwarder/metis/io/metis_UdpConnection.h b/metis/ccnx/forwarder/metis/io/metis_UdpConnection.h new file mode 100644 index 00000000..86af9296 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_UdpConnection.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metis_UdpConnection.h + * @brief Represents a UDP connection (socket) for the connection table + * + * <#Detailed Description#> + * + */ + +#ifndef Metis_metis_UdpConnection_h +#define Metis_metis_UdpConnection_h + +#include <ccnx/forwarder/metis/io/metis_IoOperations.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/io/metis_AddressPair.h> +#include <ccnx/api/control/cpi_Address.h> + +/** + * Creates a UDP connection that can send to the remote address + * + * The address pair must both be same type (i.e. INET or INET6). + * + * @param [in] metis An allocated MetisForwarder (saves reference) + * @param [in] fd The socket to use + * @param [in] pair An allocated address pair for the connection (saves reference) + * @param [in] isLocal determines if the remote address is on the current system + * + * @retval non-null An allocated Io operations + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisIoOperations *metisUdpConnection_Create(MetisForwarder *metis, int fd, const MetisAddressPair *pair, bool isLocal); +#endif // Metis_metis_UdpConnection_h diff --git a/metis/ccnx/forwarder/metis/io/metis_UdpController.h b/metis/ccnx/forwarder/metis/io/metis_UdpController.h new file mode 100644 index 00000000..db27a0e1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_UdpController.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + + +#ifndef Metis_metis_UdpController_h +#define Metis_metis_UdpController_h +#endif // Metis_metis_UdpController_h diff --git a/metis/ccnx/forwarder/metis/io/metis_UdpListener.c b/metis/ccnx/forwarder/metis/io/metis_UdpListener.c new file mode 100644 index 00000000..1bd54187 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_UdpListener.c @@ -0,0 +1,599 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <config.h> +#include <stdio.h> +#include <fcntl.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> +#include <arpa/inet.h> +#include <unistd.h> + +#include <ccnx/forwarder/metis/io/metis_UdpListener.h> +#include <ccnx/forwarder/metis/io/metis_UdpConnection.h> + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_Connection.h> +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> +#include <ccnx/forwarder/metis/core/metis_Message.h> +#include <ccnx/forwarder/metis/messenger/metis_Messenger.h> + +#include <ccnx/forwarder/metis/core/metis_Wldr.h> + +#include <LongBow/runtime.h> +#include <parc/algol/parc_Memory.h> + +typedef struct metis_udp_stats { + uint64_t framesIn; + uint64_t framesError; + uint64_t framesReceived; +} _MetisUdpStats; + +struct metis_udp_listener { + MetisForwarder *metis; + MetisLogger *logger; + + //MetisNetworkEvent *udp_event; + PARCEvent *udp_event; + MetisSocketType udp_socket; + uint16_t port; + + unsigned id; + CPIAddress *localAddress; + + _MetisUdpStats stats; +}; + +static void _destroy(MetisListenerOps **listenerOpsPtr); +static unsigned _getInterfaceIndex(const MetisListenerOps *ops); +static const CPIAddress *_getListenAddress(const MetisListenerOps *ops); +static MetisEncapType _getEncapType(const MetisListenerOps *ops); +static int _getSocket(const MetisListenerOps *ops); + +static MetisListenerOps udpTemplate = { + .context = NULL, + .destroy = &_destroy, + .getInterfaceIndex = &_getInterfaceIndex, + .getListenAddress = &_getListenAddress, + .getEncapType = &_getEncapType, + .getSocket = &_getSocket +}; + +static void _readcb(int fd, PARCEventType what, void *udpVoid); + +MetisListenerOps * +metisUdpListener_CreateInet6(MetisForwarder *metis, struct sockaddr_in6 sin6) +{ + MetisListenerOps *ops = NULL; + + MetisUdpListener *udp = parcMemory_AllocateAndClear(sizeof(MetisUdpListener)); + assertNotNull(udp, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisUdpListener)); + udp->metis = metis; + udp->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + udp->localAddress = cpiAddress_CreateFromInet6(&sin6); + udp->id = metisForwarder_GetNextConnectionId(metis); + + udp->udp_socket = socket(AF_INET6, SOCK_DGRAM, 0); + assertFalse(udp->udp_socket < 0, "Error opening UDP socket: (%d) %s", errno, strerror(errno)); + + // Set non-blocking flag + int flags = fcntl(udp->udp_socket, F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)", errno); + int failure = fcntl(udp->udp_socket, F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)", errno); + + int one = 1; + // don't hang onto address after listener has closed + failure = setsockopt(udp->udp_socket, SOL_SOCKET, SO_REUSEADDR, (void *) &one, (socklen_t) sizeof(one)); + assertFalse(failure, "failed to set REUSEADDR on socket(%d)", errno); + + failure = bind(udp->udp_socket, (struct sockaddr *) &sin6, sizeof(sin6)); + if (failure == 0) { + udp->udp_event = metisDispatcher_CreateNetworkEvent(metisForwarder_GetDispatcher(metis), true, _readcb, (void *) udp, udp->udp_socket); + metisDispatcher_StartNetworkEvent(metisForwarder_GetDispatcher(metis), udp->udp_event); + + ops = parcMemory_AllocateAndClear(sizeof(MetisListenerOps)); + assertNotNull(ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisListenerOps)); + memcpy(ops, &udpTemplate, sizeof(MetisListenerOps)); + ops->context = udp; + + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + char *str = cpiAddress_ToString(udp->localAddress); + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "UdpListener %p created for address %s", + (void *) udp, str); + parcMemory_Deallocate((void **) &str); + } + } else { + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + int myerrno = errno; + char *str = cpiAddress_ToString(udp->localAddress); + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Error binding UDP socket to address %s: (%d) %s", str, myerrno, strerror(myerrno)); + parcMemory_Deallocate((void **) &str); + } + + close(udp->udp_socket); + cpiAddress_Destroy(&udp->localAddress); + metisLogger_Release(&udp->logger); + parcMemory_Deallocate((void **) &udp); + } + + return ops; +} + +MetisListenerOps * +metisUdpListener_CreateInet(MetisForwarder *metis, struct sockaddr_in sin) +{ + MetisListenerOps *ops = NULL; + + MetisUdpListener *udp = parcMemory_AllocateAndClear(sizeof(MetisUdpListener)); + assertNotNull(udp, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisUdpListener)); + udp->metis = metis; + udp->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + udp->localAddress = cpiAddress_CreateFromInet(&sin); + udp->id = metisForwarder_GetNextConnectionId(metis); + + udp->udp_socket = socket(AF_INET, SOCK_DGRAM, 0); + assertFalse(udp->udp_socket < 0, "Error opening UDP socket: (%d) %s", errno, strerror(errno)); + + // Set non-blocking flag + int flags = fcntl(udp->udp_socket, F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)", errno); + int failure = fcntl(udp->udp_socket, F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)", errno); + + int one = 1; + // don't hang onto address after listener has closed + failure = setsockopt(udp->udp_socket, SOL_SOCKET, SO_REUSEADDR, (void *) &one, (socklen_t) sizeof(one)); + assertFalse(failure, "failed to set REUSEADDR on socket(%d)", errno); + + failure = bind(udp->udp_socket, (struct sockaddr *) &sin, sizeof(sin)); + if (failure == 0) { + udp->udp_event = metisDispatcher_CreateNetworkEvent(metisForwarder_GetDispatcher(metis), true, _readcb, (void *) udp, udp->udp_socket); + metisDispatcher_StartNetworkEvent(metisForwarder_GetDispatcher(metis), udp->udp_event); + + ops = parcMemory_AllocateAndClear(sizeof(MetisListenerOps)); + assertNotNull(ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisListenerOps)); + memcpy(ops, &udpTemplate, sizeof(MetisListenerOps)); + ops->context = udp; + + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + char *str = cpiAddress_ToString(udp->localAddress); + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "UdpListener %p created for address %s", + (void *) udp, str); + parcMemory_Deallocate((void **) &str); + } + } else { + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + int myerrno = errno; + char *str = cpiAddress_ToString(udp->localAddress); + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Error binding UDP socket to address %s: (%d) %s", str, myerrno, strerror(myerrno)); + parcMemory_Deallocate((void **) &str); + } + + close(udp->udp_socket); + cpiAddress_Destroy(&udp->localAddress); + metisLogger_Release(&udp->logger); + parcMemory_Deallocate((void **) &udp); + } + + return ops; +} + +static void +metisUdpListener_Destroy(MetisUdpListener **listenerPtr) +{ + assertNotNull(listenerPtr, "Parameter must be non-null double pointer"); + assertNotNull(*listenerPtr, "Parameter must derefernce to non-null pointer"); + + MetisUdpListener *udp = *listenerPtr; + + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "UdpListener %p destroyed", + (void *) udp); + } + + close(udp->udp_socket); + cpiAddress_Destroy(&udp->localAddress); + metisDispatcher_DestroyNetworkEvent(metisForwarder_GetDispatcher(udp->metis), &udp->udp_event); + metisLogger_Release(&udp->logger); + parcMemory_Deallocate((void **) &udp); + *listenerPtr = NULL; +} + +static void +_destroy(MetisListenerOps **listenerOpsPtr) +{ + MetisListenerOps *ops = *listenerOpsPtr; + MetisUdpListener *udp = (MetisUdpListener *) ops->context; + metisUdpListener_Destroy(&udp); + parcMemory_Deallocate((void **) &ops); + *listenerOpsPtr = NULL; +} + +static unsigned +_getInterfaceIndex(const MetisListenerOps *ops) +{ + MetisUdpListener *udp = (MetisUdpListener *) ops->context; + return udp->id; +} + +static const CPIAddress * +_getListenAddress(const MetisListenerOps *ops) +{ + MetisUdpListener *udp = (MetisUdpListener *) ops->context; + return udp->localAddress; +} + +static MetisEncapType +_getEncapType(const MetisListenerOps *ops) +{ + return METIS_ENCAP_UDP; +} + +static int +_getSocket(const MetisListenerOps *ops) +{ + MetisUdpListener *udp = (MetisUdpListener *) ops->context; + return (int) udp->udp_socket; +} + +static void +_logStats(MetisUdpListener *udp, PARCLogLevel level) +{ + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, level)) { + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, level, __func__, + "UdpListener %p frames in %" PRIu64 ", errors %" PRIu64 " ok %" PRIu64, + (void *) udp, + udp->stats.framesIn, + udp->stats.framesError, + udp->stats.framesReceived); + } +} + + +// ===================================================================== + + +static void _receiveProbeMessage(MetisUdpListener *udp, int fd, uint8_t *pkt, struct sockaddr *peerIpAddress, socklen_t peerIpAddressLength); + + +/** + * @function peekMesageLength + * @abstract Peek at the next packet to learn its length by reading the fixed header + * @discussion + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + */ +static size_t +_peekMessageLength(MetisUdpListener *udp, int fd, struct sockaddr *peerIpAddress, socklen_t *peerIpAddressLengthPtr) +{ + size_t packetLength = 0; + + uint8_t fixedHeader[metisTlv_FixedHeaderLength()]; + + // peek at the UDP packet and read in the fixed header. + // Also returns the socket information for the remote peer + + uint8_t wldr_flag[1]; + ssize_t checkWldrHeader = recvfrom(fd, wldr_flag, 1, MSG_PEEK, (struct sockaddr *) peerIpAddress, peerIpAddressLengthPtr); + if (checkWldrHeader == -1) { + return -1; + } + ssize_t readLength; + if (wldr_flag[0] == WLDR_HEADER) { + //the message contains wldr header + uint8_t tmp[metisTlv_FixedHeaderLength() + WLDR_HEADER_SIZE]; + readLength = recvfrom(fd, tmp, metisTlv_FixedHeaderLength() + WLDR_HEADER_SIZE, MSG_PEEK, (struct sockaddr *) peerIpAddress, peerIpAddressLengthPtr); + if (readLength == (metisTlv_FixedHeaderLength() + WLDR_HEADER_SIZE)) { + for (int i = 6; i < metisTlv_FixedHeaderLength() + WLDR_HEADER_SIZE; ++i) { + fixedHeader[i - WLDR_HEADER_SIZE] = tmp[i]; + } + readLength = metisTlv_FixedHeaderLength(); + } else { + readLength = 0; + } + } else { + readLength = recvfrom(fd, fixedHeader, metisTlv_FixedHeaderLength(), MSG_PEEK, (struct sockaddr *) peerIpAddress, peerIpAddressLengthPtr); + } + + if (readLength == -1) { + return -1; + } + + if (readLength == metisTlv_FixedHeaderLength()) { + packetLength = metisTlv_TotalPacketLength(fixedHeader); + if (packetLength == 0) { + uint8_t *pkt; + pkt = (uint8_t *) malloc(sizeof(uint8_t) * metisTlv_FixedHeaderLength()); + for (unsigned i = 0; i < metisTlv_FixedHeaderLength(); i++) { + pkt[i] = fixedHeader[i]; + } + + _receiveProbeMessage(udp, fd, pkt, peerIpAddress, *peerIpAddressLengthPtr); + + free(pkt); + } + } else { + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning)) { + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "read %zu bytes from fd %d, wrong size for a FixedHeader", + readLength, + fd); + } + + if (readLength < 0) { + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Error reading fd %d: (%d) %s", fd, errno, strerror(errno)); + } + } + } + + if (wldr_flag[0] == WLDR_HEADER) { + return packetLength + WLDR_HEADER_SIZE; + } else { + return packetLength; + } +} + +static MetisMessage * +_readMessage(MetisForwarder *metis, unsigned connid, int fd, size_t packetLength) +{ + PARCEventBuffer *readbuffer = parcEventBuffer_Create(); + int readLength = parcEventBuffer_ReadFromFileDescriptor(readbuffer, fd, packetLength); + + MetisMessage *message = NULL; + if (readLength == packetLength) { + message = metisMessage_CreateFromBuffer(connid, metisForwarder_GetTicks(metis), readbuffer, metisForwarder_GetLogger(metis)); + + // because metisMessage_CreateFromBuffer takes ownership of readbuffer, if there is + // an error and it returns null, metisMessage_CreateFromBuffer will destroy the readbuffer. + } else { + parcEventBuffer_Destroy(&readbuffer); + + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Warning)) { + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "read %d bytes from fd %d, expected %zu", + readLength, + fd, + packetLength); + } + + if (readLength < 0) { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Error reading fd %d: (%d) %s", fd, errno, strerror(errno)); + } + } + } + + return message; +} + +/** + * @function _constructAddressPair + * @abstract Creates the address pair that uniquely identifies the connection + * @discussion + * The peerIpAddress must be of AF_INET or AF_INET6 family. + * + * @param <#param1#> + * @return Allocated MetisAddressPair, must be destroyed + */ +static MetisAddressPair * +_constructAddressPair(MetisUdpListener *udp, struct sockaddr *peerIpAddress, socklen_t peerIpAddressLength) +{ + CPIAddress *remoteAddress; + + switch (peerIpAddress->sa_family) { + case AF_INET: + remoteAddress = cpiAddress_CreateFromInet((struct sockaddr_in *) peerIpAddress); + break; + + case AF_INET6: + remoteAddress = cpiAddress_CreateFromInet6((struct sockaddr_in6 *) peerIpAddress); + break; + + default: + trapIllegalValue(peerIpAddress, "Peer address unrecognized family for IP: %d", peerIpAddress->sa_family); + } + + MetisAddressPair *pair = metisAddressPair_Create(udp->localAddress, remoteAddress); + cpiAddress_Destroy(&remoteAddress); + + return pair; +} + +/** + * @function _lookupConnectionId + * @abstract Lookup a connection in the connection table + * @discussion + * Looks up the connection in the connection table and returns the connection id if it exists. + * + * @param outputConnectionIdPtr is the output parameter + * @return true if connection found and outputConnectionIdPtr set + */ +static bool +_lookupConnectionId(MetisUdpListener *udp, MetisAddressPair *pair, unsigned *outputConnectionIdPtr) +{ + MetisConnectionTable *connTable = metisForwarder_GetConnectionTable(udp->metis); + + const MetisConnection *conn = metisConnectionTable_FindByAddressPair(connTable, pair); + if (conn) { + *outputConnectionIdPtr = metisConnection_GetConnectionId(conn); + return true; + } + + return false; +} + +/** + * @function _createNewConnection + * @abstract Creates a new Metis connection for the peer + * @discussion + * PRECONDITION: you know there's not an existing connection with the address pair + * + * Creates a new connection and adds it to the connection table. + * + * @param <#param1#> + * @return The connection id for the new connection + */ + +//for the moment this is not used anymore +//we need to handle the connection tables in a better way in order to avoid multiple connections +//with the same address pair. +/*static unsigned + * _createNewConnection(MetisUdpListener *udp, int fd, const MetisAddressPair *pair) + * { + * bool isLocal = false; + * + * // metisUdpConnection_Create takes ownership of the pair + * MetisIoOperations *ops = metisUdpConnection_Create(udp->metis, fd, pair, isLocal); + * MetisConnection *conn = metisConnection_Create(ops); + * + * metisConnectionTable_Add(metisForwarder_GetConnectionTable(udp->metis), conn); + * unsigned connid = metisIoOperations_GetConnectionId(ops); + * + * return connid; + * }*/ + +static void +_receivePacket(MetisUdpListener *udp, int fd, size_t packetLength, struct sockaddr_storage *peerIpAddress, socklen_t peerIpAddressLength) +{ + unsigned connid = 0; + MetisAddressPair *pair = _constructAddressPair(udp, (struct sockaddr *) peerIpAddress, peerIpAddressLength); + bool foundConnection = _lookupConnectionId(udp, pair, &connid); + + if (!foundConnection) { + PARCEventBuffer *readbuffer = parcEventBuffer_Create(); + parcEventBuffer_ReadFromFileDescriptor(readbuffer, fd, packetLength); + parcEventBuffer_Destroy(&readbuffer); + metisAddressPair_Release(&pair); + return; + //connid = _createNewConnection(udp, fd, pair); + } + + metisAddressPair_Release(&pair); + + MetisMessage *message = _readMessage(udp->metis, connid, fd, packetLength); + + if (message) { + udp->stats.framesReceived++; + + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "read %zu bytes from fd %d sa %s:%d connid %d", + metisMessage_Length(message), + fd, + inet_ntoa(((struct sockaddr_in *) peerIpAddress)->sin_addr), + ntohs(((struct sockaddr_in *) peerIpAddress)->sin_port), + connid); + } + + _logStats(udp, PARCLogLevel_Debug); + + metisForwarder_Receive(udp->metis, message); + } else { + udp->stats.framesError++; + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning)) { + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "Could not parse frame from fd %d, discarding", fd); + } + _logStats(udp, PARCLogLevel_Warning); + } +} + + +static void +_readFrameToDiscard(MetisUdpListener *udp, int fd) +{ + // we need to discard the frame. Read 1 byte. This will clear it off the stack. + uint8_t buffer; + ssize_t nread = read(fd, &buffer, 1); + + udp->stats.framesError++; + + if (nread == 1) { + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Discarded frame from fd %d", fd); + } + _logStats(udp, PARCLogLevel_Debug); + } else if (nread < 0) { + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Error trying to discard frame from fd %d: (%d) %s", fd, errno, strerror(errno)); + } + _logStats(udp, PARCLogLevel_Error); + } +} + +static void +_receiveProbeMessage(MetisUdpListener *udp, int fd, uint8_t *pkt, struct sockaddr *peerIpAddress, socklen_t peerIpAddressLength) +{ + MetisAddressPair *pair = _constructAddressPair(udp, peerIpAddress, peerIpAddressLength); + MetisConnectionTable *connTable = metisForwarder_GetConnectionTable(udp->metis); + const MetisConnection *conn = metisConnectionTable_FindByAddressPair(connTable, pair); + + if (conn == NULL) { + metisAddressPair_Release(&pair); + return; //we discard probes coming from connections that we don't know. this should never happen. + } + + metisAddressPair_Release(&pair); + + //handle the probe. + metisConnection_HandleProbe((MetisConnection *) conn, pkt, metisForwarder_GetTicks(udp->metis)); +} + + +static void +_readcb(int fd, PARCEventType what, void *udpVoid) +{ + MetisUdpListener *udp = (MetisUdpListener *) udpVoid; + + if (metisLogger_IsLoggable(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(udp->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "%s socket %d what %s%s%s%s data %p", + __func__, fd, + (what & PARCEventType_Timeout) ? " timeout" : "", + (what & PARCEventType_Read) ? " read" : "", + (what & PARCEventType_Write) ? " write" : "", + (what & PARCEventType_Signal) ? " signal" : "", + udpVoid); + } + + if (what & PARCEventType_Read) { + udp->stats.framesIn++; + struct sockaddr_storage peerIpAddress; + socklen_t peerIpAddressLength = sizeof(peerIpAddress); + + size_t packetLength = _peekMessageLength(udp, fd, (struct sockaddr *) &peerIpAddress, &peerIpAddressLength); + + if (packetLength > 0) { + _receivePacket(udp, fd, packetLength, &peerIpAddress, peerIpAddressLength); + } else { + _readFrameToDiscard(udp, fd); + } + } +} diff --git a/metis/ccnx/forwarder/metis/io/metis_UdpListener.h b/metis/ccnx/forwarder/metis/io/metis_UdpListener.h new file mode 100644 index 00000000..d4dc129d --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_UdpListener.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + + +#ifndef Metis_metis_UdpListener_h +#define Metis_metis_UdpListener_h + +#include <netinet/in.h> +#include <stdlib.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/io/metis_Listener.h> + +struct metis_udp_listener; +typedef struct metis_udp_listener MetisUdpListener; + +MetisListenerOps *metisUdpListener_CreateInet6(MetisForwarder *metis, struct sockaddr_in6 sin6); +MetisListenerOps *metisUdpListener_CreateInet(MetisForwarder *metis, struct sockaddr_in sin); +#endif // Metis_metis_UdpListener_h diff --git a/metis/ccnx/forwarder/metis/io/metis_UdpTunnel.c b/metis/ccnx/forwarder/metis/io/metis_UdpTunnel.c new file mode 100644 index 00000000..77424dce --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_UdpTunnel.c @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <config.h> +#include <stdio.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> + +#include <ccnx/forwarder/metis/io/metis_UdpTunnel.h> +#include <ccnx/forwarder/metis/io/metis_UdpConnection.h> + +#include <LongBow/runtime.h> + +MetisIoOperations * +metisUdpTunnel_CreateOnListener(MetisForwarder *metis, MetisListenerOps *localListener, const CPIAddress *remoteAddress) +{ + assertNotNull(metis, "Parameter metis must be non-null"); + assertNotNull(localListener, "Parameter localListener must be non-null"); + assertNotNull(remoteAddress, "Parameter remoteAddress must be non-null"); + + MetisLogger *logger = metisForwarder_GetLogger(metis); + + MetisIoOperations *ops = NULL; + if (localListener->getEncapType(localListener) == METIS_ENCAP_UDP) { + const CPIAddress *localAddress = localListener->getListenAddress(localListener); + CPIAddressType localType = cpiAddress_GetType(localAddress); + CPIAddressType remoteType = cpiAddress_GetType(remoteAddress); + + if (localType == remoteType) { + MetisAddressPair *pair = metisAddressPair_Create(localAddress, remoteAddress); + bool isLocal = false; + int fd = localListener->getSocket(localListener); + ops = metisUdpConnection_Create(metis, fd, pair, isLocal); + + metisAddressPair_Release(&pair); + } else { + if (metisLogger_IsLoggable(logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Local listener of type %s and remote type %s, cannot establish tunnel", + cpiAddress_TypeToString(localType), + cpiAddress_TypeToString(remoteType)); + } + } + } else { + if (metisLogger_IsLoggable(logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Local listener %p is not type UDP, cannot establish tunnel", (void *) localListener); + } + } + + return ops; +} + +/* + * wrapper for metisUdpTunnel_CreateOnListener. Lookup to see if we have a listener on the local address. + * If so, call metisUdpTunnel_CreateOnListener, otherwise return NULL + */ +MetisIoOperations * +metisUdpTunnel_Create(MetisForwarder *metis, const CPIAddress *localAddress, const CPIAddress *remoteAddress) +{ + MetisListenerSet *set = metisForwarder_GetListenerSet(metis); + MetisListenerOps *listener = metisListenerSet_Find(set, METIS_ENCAP_UDP, localAddress); + MetisIoOperations *ops = NULL; + if (listener) { + ops = metisUdpTunnel_CreateOnListener(metis, listener, remoteAddress); + } else { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error)) { + char *str = cpiAddress_ToString(localAddress); + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Could not find listener to match address %s", str); + parcMemory_Deallocate((void **) &str); + } + } + return ops; +} + diff --git a/metis/ccnx/forwarder/metis/io/metis_UdpTunnel.h b/metis/ccnx/forwarder/metis/io/metis_UdpTunnel.h new file mode 100644 index 00000000..8043da7c --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/metis_UdpTunnel.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file metis_UdpTunnel.h + * @brief Establish a tunnel to a remote system + * + * Creates a "udp tunnel" to a remote system. There must already be a local UDP listener for the + * local side of the connection. Because UDP is connectionless and we do not have a link protocol, + * the udp tunnel will go in the connection table immediately in the "up" state. + * + */ + +#ifndef Metis_metis_UdpTunnel_h +#define Metis_metis_UdpTunnel_h + +#include <ccnx/api/control/cpi_Address.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/io/metis_Listener.h> +#include <ccnx/forwarder/metis/io/metis_IoOperations.h> + +/** + * Establishes a connection to a remote system over UDP + * + * The remoteAddress must be of the same type (i.e. v4 or v6) as the localListener. + * + * The connection will go in the table immediately, and will be in the "up" state. + * + * + * @param [in] metis An allocated MetisForwarder + * @param [in] localListener The local receiver for UDP messages + * @param [in] remote Address the remote IP address for the connection, must include a destination port. + * + * @retval non-null An allocated Io Operations structure for the connection + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisIoOperations *metisUdpTunnel_CreateOnListener(MetisForwarder *metis, MetisListenerOps *localListener, const CPIAddress *remoteAddress); + +/** + * Establishes a connection to a remote system over UDP + * + * The remoteAddress must be of the same type (i.e. v4 or v6) as the localAddress. There must be an existing UDP listener + * on the local address. If either of these are not true, will return NULL. + * + * The connection will go in the table immediately, and will be in the "up" state. + * + * This function will lookup the appropraite listener, then use metisUdpTunnel_CreateOnListener(). + * + * @param [in] metis An allocated MetisForwarder + * @param [in] localAddress The local IP address and port to use for the connection + * @param [in] remote Address the remote IP address for the connection, must include a destination port. + * + * @retval non-null An allocated Io Operations structure for the connection + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +MetisIoOperations *metisUdpTunnel_Create(MetisForwarder *metis, const CPIAddress *localAddress, const CPIAddress *remoteAddress); + +#endif // Metis_metis_UdpTunnel_h diff --git a/metis/ccnx/forwarder/metis/io/test/CMakeLists.txt b/metis/ccnx/forwarder/metis/io/test/CMakeLists.txt new file mode 100644 index 00000000..1e22e32e --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/CMakeLists.txt @@ -0,0 +1,25 @@ +# Enable gcov output for the tests +add_definitions(--coverage) +set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} " --coverage") + +set(TestsExpectedToPass + test_metis_AddressPair + test_metis_EtherConnection + test_metis_EtherListener + test_metis_HopByHopFragmenter + test_metis_IPMulticastListener + test_metis_ListenerSet + test_metis_LocalListener + test_metis_StreamConnection + test_metis_TcpListener + test_metis_TcpTunnel + test_metis_UdpConnection + test_metis_UdpListener + test_metis_UdpTunnel +) + + +foreach(test ${TestsExpectedToPass}) + AddTest(${test}) +endforeach() + diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_AddressPair.c b/metis/ccnx/forwarder/metis/io/test/test_metis_AddressPair.c new file mode 100644 index 00000000..34ff9d9d --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_AddressPair.c @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_AddressPair.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +LONGBOW_TEST_RUNNER(metis_AddressPair) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_AddressPair) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_AddressPair) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisAddressPair_Create_Destroy); + LONGBOW_RUN_TEST_CASE(Global, metisAddressPair_Equals); + LONGBOW_RUN_TEST_CASE(Global, metisAddressPair_Equals_NotEqual); + LONGBOW_RUN_TEST_CASE(Global, metisAddressPair_EqualsAddresses); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisAddressPair_Create_Destroy) +{ + CPIAddress *a = cpiAddress_CreateFromInterface(1); + CPIAddress *b = cpiAddress_CreateFromInterface(2); + + size_t allocbase = parcSafeMemory_Outstanding(); + MetisAddressPair *pair = metisAddressPair_Create(a, b); + metisAddressPair_Release(&pair); + + assertTrue(parcSafeMemory_Outstanding() == allocbase, + "Memory out of balance, expected %zu got %u", + allocbase, + parcSafeMemory_Outstanding()); + + cpiAddress_Destroy(&a); + cpiAddress_Destroy(&b); +} + +LONGBOW_TEST_CASE(Global, metisAddressPair_Equals) +{ + CPIAddress *a = cpiAddress_CreateFromInterface(1); + CPIAddress *b = cpiAddress_CreateFromInterface(2); + + MetisAddressPair *pair_a = metisAddressPair_Create(a, b); + MetisAddressPair *pair_b = metisAddressPair_Create(a, b); + + assertTrue(metisAddressPair_Equals(pair_a, pair_b), "Two equal address pairs did not compare equal"); + + metisAddressPair_Release(&pair_a); + metisAddressPair_Release(&pair_b); + + cpiAddress_Destroy(&a); + cpiAddress_Destroy(&b); +} + +LONGBOW_TEST_CASE(Global, metisAddressPair_Equals_NotEqual) +{ + CPIAddress *a = cpiAddress_CreateFromInterface(1); + CPIAddress *b = cpiAddress_CreateFromInterface(2); + + MetisAddressPair *pair_a = metisAddressPair_Create(a, b); + MetisAddressPair *pair_b = metisAddressPair_Create(b, a); + + assertFalse(metisAddressPair_Equals(pair_a, pair_b), "Two unequal address pairs compared equal"); + + metisAddressPair_Release(&pair_a); + metisAddressPair_Release(&pair_b); + cpiAddress_Destroy(&a); + cpiAddress_Destroy(&b); +} + +LONGBOW_TEST_CASE(Global, metisAddressPair_EqualsAddresses) +{ + CPIAddress *a = cpiAddress_CreateFromInterface(1); + CPIAddress *b = cpiAddress_CreateFromInterface(2); + + MetisAddressPair *pair_a = metisAddressPair_Create(a, b); + + assertTrue(metisAddressPair_EqualsAddresses(pair_a, a, b), "Two equal address pairs did not compare equal"); + + metisAddressPair_Release(&pair_a); + cpiAddress_Destroy(&a); + cpiAddress_Destroy(&b); +} + +LONGBOW_TEST_FIXTURE(Local) +{ +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_AddressPair); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_EtherConnection.c b/metis/ccnx/forwarder/metis/io/test/test_metis_EtherConnection.c new file mode 100644 index 00000000..081903b1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_EtherConnection.c @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include "testrig_GenericEther.c" +#include "../metis_EtherConnection.c" + +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h> + +typedef struct test_data { + MetisForwarder *metis; + MetisGenericEther *ether; + MetisAddressPair *pair; + MetisIoOperations *io_ops; +} TestData; + +static void +commonSetup(const LongBowTestCase *testCase, uint16_t ethertype) +{ + TestData *data = parcMemory_AllocateAndClear(sizeof(TestData)); + + data->metis = metisForwarder_Create(NULL); + data->ether = metisGenericEther_Create(data->metis, "foo", ethertype); + data->io_ops = NULL; + + // crank the libevent handle + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis), &((struct timeval) {0, 10000})); + + PARCBuffer *localMacBuffer = metisGenericEther_GetMacAddress(data->ether); + CPIAddress *local = cpiAddress_CreateFromLink(parcBuffer_Overlay(localMacBuffer, 0), parcBuffer_Remaining(localMacBuffer)); + CPIAddress *remote = cpiAddress_CreateFromLink((uint8_t []) { 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }, 6); + + data->pair = metisAddressPair_Create(local, remote); + + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); + + longBowTestCase_SetClipBoardData(testCase, data); +} + +static void +commonTeardown(const LongBowTestCase *testCase) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis), &((struct timeval) {0, 10000})); + metisGenericEther_Release(&data->ether); + metisAddressPair_Release(&data->pair); + + if (data->io_ops) { + data->io_ops->destroy(&data->io_ops); + } + + // destroy metis last + metisForwarder_Destroy(&data->metis); + parcMemory_Deallocate((void **) &data); +} + + +LONGBOW_TEST_RUNNER(metis_EtherConnection) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_EtherConnection) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_EtherConnection) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// =========================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisEtherConnection_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + commonSetup(testCase, 0x0801); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + commonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisEtherConnection_Create) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + data->io_ops = metisEtherConnection_Create(data->metis, data->ether, data->pair); + assertNotNull(data->io_ops, "Got null MetisIoOperations from metisEtherConnection_Create"); +} + +// =========================================== + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_DestroyOperations); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_FillInMacAddress); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_GetAddressPair); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_GetConnectionId); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_GetRemoteAddress); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_IsLocal); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_IsUp); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_Send); + LONGBOW_RUN_TEST_CASE(Local, _setConnectionState); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherConnection_getConnectionType); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + commonSetup(testCase, 0x0801); + + // for the local tests we also pre-create the EtherConnection + TestData *data = longBowTestCase_GetClipBoardData(testCase); + data->io_ops = metisEtherConnection_Create(data->metis, data->ether, data->pair); + + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + commonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_DestroyOperations) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_FillInMacAddress) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_GetAddressPair) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherState *etherConn = (_MetisEtherState *) metisIoOperations_GetClosure(data->io_ops); + + const MetisAddressPair *test = _metisEtherConnection_GetAddressPair(data->io_ops); + + assertTrue(metisAddressPair_Equals(test, etherConn->addressPair), "Address pair did not compare"); +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_GetConnectionId) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherState *etherConn = (_MetisEtherState *) metisIoOperations_GetClosure(data->io_ops); + unsigned connid = _metisEtherConnection_GetConnectionId(data->io_ops); + + assertTrue(connid == etherConn->id, "Wrong connection id, got %u exepected %u", connid, etherConn->id); +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_GetRemoteAddress) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + const CPIAddress *test = _metisEtherConnection_GetRemoteAddress(data->io_ops); + + _MetisEtherState *etherConn = (_MetisEtherState *) metisIoOperations_GetClosure(data->io_ops); + + assertTrue(cpiAddress_Equals(test, metisAddressPair_GetRemote(etherConn->addressPair)), "Remote addresses did not compare"); +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_IsLocal) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + bool isLocal = _metisEtherConnection_IsLocal(data->io_ops); + assertFalse(isLocal, "Ethernet should always be remote"); +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_IsUp) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + bool isUp = _metisEtherConnection_IsUp(data->io_ops); + assertTrue(isUp, "Ethernet should be up"); +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_Send) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest), 1, 2, metisForwarder_GetLogger(data->metis)); + + bool success = _metisEtherConnection_Send(data->io_ops, NULL, message); + assertTrue(success, "Failed to write message to ethernet"); + + // we should now be able to read the ethernet frame from the test socket + int testSocket = mockGenericEther_GetTestDescriptor(data->ether); + assertTrue(testSocket > 0, "Error getting test socket from mock ethernet"); + + uint32_t testBufferSize = 2048; + uint8_t testBuffer[testBufferSize]; + ssize_t bytesRead = read(testSocket, testBuffer, testBufferSize); + + size_t expectedRead = sizeof(struct ether_header) + sizeof(metisTestDataV0_EncodedInterest); + + assertTrue(bytesRead == expectedRead, "Wrong read size, got %zd expected %zu", bytesRead, expectedRead); + + uint8_t *frame = testBuffer + sizeof(struct ether_header); + assertTrue(memcmp(frame, metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest)) == 0, "Buffers do not match"); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Local, _setConnectionState) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherState *etherConn = (_MetisEtherState *) metisIoOperations_GetClosure(data->io_ops); + + _setConnectionState(etherConn, false); +} + +LONGBOW_TEST_CASE(Local, _metisEtherConnection_getConnectionType) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + CPIConnectionType connType = _metisEtherConnection_getConnectionType(data->io_ops); + + assertTrue(connType == cpiConnection_L2, "Wrong connection type expected %d got %d", cpiConnection_L2, connType); +} + +// =========================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_EtherConnection); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_EtherListener.c b/metis/ccnx/forwarder/metis/io/test/test_metis_EtherListener.c new file mode 100644 index 00000000..2ae7596c --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_EtherListener.c @@ -0,0 +1,387 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// force the use of the generic ethernet mockup +#include "testrig_GenericEther.c" +#include "../metis_EtherListener.c" +#include "testrig_GenericEther.h" + +#include <parc/algol/parc_SafeMemory.h> + +#include <LongBow/unit-test.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h> + + +typedef struct test_data { + MetisForwarder *metis; + MetisListenerOps *ops; +} TestData; + +static void +commonSetup(const LongBowTestCase *testCase, uint16_t ethertype) +{ + TestData *data = parcMemory_AllocateAndClear(sizeof(TestData)); + + data->metis = metisForwarder_Create(NULL); + data->ops = metisEtherListener_Create(data->metis, "test0", ethertype); + + // crank the libevent handle + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis), &((struct timeval) {0, 10000})); + + longBowTestCase_SetClipBoardData(testCase, data); +} + +static void +commonTeardown(const LongBowTestCase *testCase) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis), &((struct timeval) {0, 10000})); + data->ops->destroy(&data->ops); + metisForwarder_Destroy(&data->metis); + + parcMemory_Deallocate((void **) &data); +} + +// ============================================================ + +LONGBOW_TEST_RUNNER(metis_EtherListener) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_EtherListener) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_EtherListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ============================================================ + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisEtherListener_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + commonSetup(testCase, 0x0801); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + commonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisEtherListener_Create) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + assertNotNull(data->ops, "Null return from metisEtherListener_Create"); +} + +// ============================================================ + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisEtherListener_Destroy); + LONGBOW_RUN_TEST_CASE(Local, metisEtherListener_OpsDestroy); + LONGBOW_RUN_TEST_CASE(Local, metisEtherListener_OpsGetInterfaceIndex); + LONGBOW_RUN_TEST_CASE(Local, metisEtherListener_OpsGetListenAddress); + LONGBOW_RUN_TEST_CASE(Local, metisEtherListener_OpsGetEncapType); + + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_ReadCallback); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_ReadCallback_FragmentBegin); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_ReadCallback_FragmentEnd); + + + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_ReadEtherFrame_EmptyQueue); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_ReadEtherFrame_PacketWaiting); + + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_IsOurSourceAddress_True); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_IsOurSourceAddress_False); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_IsOurDestinationAddress_Unicast); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_IsOurDestinationAddress_Group); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_IsOurDestinationAddress_Broadcast); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_IsOurDestinationAddress_False); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_IsOurProtocol); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_ParseEtherFrame); + + + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_FillInEthernetAddresses); + LONGBOW_RUN_TEST_CASE(Local, _metisEtherListener_ReleaseEthernetAddresses); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + commonSetup(testCase, 0x0801); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + commonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, metisEtherListener_Destroy) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisListenerOps *ops = metisEtherListener_Create(data->metis, "fake0", 0x0801); + + _metisEtherListener_Destroy((_MetisEtherListener **) &ops->context); + assertNull(ops->context, "Destory did not null context"); + parcMemory_Deallocate((void **) &ops); +} + +LONGBOW_TEST_CASE(Local, metisEtherListener_OpsDestroy) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + MetisListenerOps *ops = metisEtherListener_Create(data->metis, "fake1", 0x0801); + _metisEtherListener_OpsDestroy(&ops); + assertNull(ops, "OpsDestroy did not null ops"); +} + +LONGBOW_TEST_CASE(Local, metisEtherListener_OpsGetInterfaceIndex) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, metisEtherListener_OpsGetListenAddress) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, metisEtherListener_OpsGetEncapType) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_ReadCallback) +{ + testUnimplemented(""); +} + +/* + * Read only a B frame, so its not a complete reassembly + */ +LONGBOW_TEST_CASE(Local, _metisEtherListener_ReadCallback_FragmentBegin) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + uint8_t headerArray[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x08, 0x01 }; + + PARCBuffer *frameBuffer = parcBuffer_Allocate(sizeof(struct ether_header) + sizeof(metisTestDataV1_HopByHopFrag_Begin)); + parcBuffer_PutArray(frameBuffer, sizeof(struct ether_header), headerArray); + parcBuffer_PutArray(frameBuffer, sizeof(metisTestDataV1_HopByHopFrag_Begin), metisTestDataV1_HopByHopFrag_Begin); + + parcBuffer_Flip(frameBuffer); + + mockGenericEther_QueueFrame(etherListener->genericEther, frameBuffer); + + _metisEtherListener_ReadCallback(0, PARCEventType_Read, data->ops->context); + + assertTrue(etherListener->stats.framesIn == 1, "Wrong framesIn count, expected 1 got %" PRIu64, etherListener->stats.framesIn); + assertTrue(etherListener->stats.framesReceived == 1, "Wrong framesReceived count, expected 1 got %" PRIu64, etherListener->stats.framesReceived); + assertTrue(etherListener->stats.framesReassembled == 0, "Wrong framesReassembled count, expected 0 got %" PRIu64, etherListener->stats.framesReassembled); + + parcBuffer_Release(&frameBuffer); +} + +/* + * Read a B and middle and E frame, so it is complete reassembly + */ +LONGBOW_TEST_CASE(Local, _metisEtherListener_ReadCallback_FragmentEnd) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + uint8_t headerArray[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x08, 0x01 }; + + PARCBuffer *frameBuffer = parcBuffer_Allocate(sizeof(struct ether_header) + sizeof(metisTestDataV1_HopByHopFrag_BeginEnd)); + parcBuffer_PutArray(frameBuffer, sizeof(struct ether_header), headerArray); + parcBuffer_PutArray(frameBuffer, sizeof(metisTestDataV1_HopByHopFrag_BeginEnd), metisTestDataV1_HopByHopFrag_BeginEnd); + + parcBuffer_Flip(frameBuffer); + + mockGenericEther_QueueFrame(etherListener->genericEther, frameBuffer); + + _metisEtherListener_ReadCallback(0, PARCEventType_Read, data->ops->context); + + assertTrue(etherListener->stats.framesIn == 1, "Wrong framesIn count, expected 1 got %" PRIu64, etherListener->stats.framesIn); + assertTrue(etherListener->stats.framesReceived == 1, "Wrong framesReceived count, expected 1 got %" PRIu64, etherListener->stats.framesReceived); + assertTrue(etherListener->stats.framesReassembled == 1, "Wrong framesReassembled count, expected 1 got %" PRIu64, etherListener->stats.framesReassembled); + + parcBuffer_Release(&frameBuffer); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_ReadEtherFrame_PacketWaiting) +{ + // create a frame and queue it + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + // Ethernet frame addressed to us with a 0-length CCNx TLV packet (i.e. just the fixed header) + uint8_t frame[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 't', 'e', 's', 't', '0', 0xA0, 0x08, 0x01, + 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 }; + + PARCBuffer *frameBuffer = parcBuffer_Wrap(frame, sizeof(frame), 0, sizeof(frame)); + + mockGenericEther_QueueFrame(etherListener->genericEther, frameBuffer); + + PARCEventBuffer *buffer = _metisEtherListener_ReadEtherFrame(etherListener); + assertNotNull(buffer, "Got null buffer from ReadEtherFrame with a frame queued"); + + assertTrue(parcEventBuffer_GetLength(buffer) == sizeof(frame), "Wrong size, got %zu expected %zu", parcEventBuffer_GetLength(buffer), sizeof(frame)); + + parcEventBuffer_Destroy(&buffer); + parcBuffer_Release(&frameBuffer); +} + + +LONGBOW_TEST_CASE(Local, _metisEtherListener_ReadEtherFrame_EmptyQueue) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + PARCEventBuffer *buffer = _metisEtherListener_ReadEtherFrame(etherListener); + assertNull(buffer, "Should get null buffer from ReadEtherFrame without a frame queued"); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_IsOurSourceAddress_True) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + uint8_t frame[] = { 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 't', 'e', 's', 't', '0', 0x06, 0x08, 0x01 }; + struct ether_header *header = (struct ether_header *) frame; + + bool success = _metisEtherListener_IsOurSourceAddress(etherListener, header); + assertTrue(success, "Did not match our source address."); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_IsOurSourceAddress_False) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + uint8_t frame[] = { 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x11, 0x22, 0x33, 0x44, 0x05, 0x06, 0x08, 0x01 }; + struct ether_header *header = (struct ether_header *) frame; + + bool success = _metisEtherListener_IsOurSourceAddress(etherListener, header); + assertFalse(success, "Should not match our address."); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_IsOurDestinationAddress_Unicast) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + uint8_t frame[] = { 't', 'e', 's', 't', '0', 0x06, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x08, 0x01 }; + struct ether_header *header = (struct ether_header *) frame; + + bool success = _metisEtherListener_IsOurDestinationAddress(etherListener, header); + assertTrue(success, "Did not match our destination address."); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_IsOurDestinationAddress_Group) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + uint8_t frame[] = { 0x01, 0x00, 0x5e, 0x00, 0x17, 0xaa, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x08, 0x01 }; + struct ether_header *header = (struct ether_header *) frame; + + bool success = _metisEtherListener_IsOurDestinationAddress(etherListener, header); + assertTrue(success, "Did not match group address."); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_IsOurDestinationAddress_Broadcast) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + uint8_t frame[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x08, 0x01 }; + struct ether_header *header = (struct ether_header *) frame; + + bool success = _metisEtherListener_IsOurDestinationAddress(etherListener, header); + assertTrue(success, "Did not match broadcast address."); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_IsOurDestinationAddress_False) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _MetisEtherListener *etherListener = data->ops->context; + + uint8_t frame[] = { 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x08, 0x01 }; + struct ether_header *header = (struct ether_header *) frame; + + bool success = _metisEtherListener_IsOurDestinationAddress(etherListener, header); + assertFalse(success, "Should not match one of our addresses."); +} + + +LONGBOW_TEST_CASE(Local, _metisEtherListener_IsOurProtocol) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_ParseEtherFrame) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_FillInEthernetAddresses) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _metisEtherListener_ReleaseEthernetAddresses) +{ + testUnimplemented(""); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_EtherListener); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_HopByHopFragmenter.c b/metis/ccnx/forwarder/metis/io/test/test_metis_HopByHopFragmenter.c new file mode 100644 index 00000000..5be165c5 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_HopByHopFragmenter.c @@ -0,0 +1,1087 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../metis_HopByHopFragmenter.c" + +#include <parc/algol/parc_SafeMemory.h> +#include <parc/logging/parc_LogReporterTextStdout.h> + +#include <LongBow/unit-test.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h> + +typedef struct test_data { + unsigned mtu; + MetisLogger *logger; + MetisHopByHopFragmenter *fragmenter; +} TestData; + +static TestData * +_createTestData(void) +{ + TestData *data = parcMemory_Allocate(sizeof(TestData)); + + data->mtu = 2000; + + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + data->logger = metisLogger_Create(reporter, parcClock_Wallclock()); + metisLogger_SetLogLevel(data->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug); + parcLogReporter_Release(&reporter); + data->fragmenter = metisHopByHopFragmenter_Create(data->logger, data->mtu); + return data; +} + +static void +_destroyTestData(TestData **dataPtr) +{ + TestData *data = *dataPtr; + metisHopByHopFragmenter_Release(&data->fragmenter); + metisLogger_Release(&data->logger); + parcMemory_Deallocate((void **) dataPtr); +} + +/* + * Create a well-formed packet with the given length. + * length is the total packet length, including fixed header. + */ +static uint8_t * +_conjurePacket(size_t length) +{ + _HopByHopHeader header; + memset(&header, 0, sizeof(header)); + + size_t payloadLength = length - sizeof(header); + + header.version = 1; + header.packetType = 2; // interest return -- does not require a name. + header.packetLength = htons(length); + header.headerLength = 8; + header.tlvType = 0; + header.tlvLength = htons(payloadLength); + + uint8_t *packet = parcMemory_Allocate(length); + memcpy(packet, &header, sizeof(header)); + return packet; +} + +// ============================================================ + +LONGBOW_TEST_RUNNER(metis_HopByHopFragmenter) +{ + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_HopByHopFragmenter) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_HopByHopFragmenter) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ============================================================ + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisHopByHopFragmenter_Create); + LONGBOW_RUN_TEST_CASE(Global, metisHopByHopFragmenter_Receive_NotHopByHop); + LONGBOW_RUN_TEST_CASE(Global, metisHopByHopFragmenter_Receive_ReceiveQueueFull); + LONGBOW_RUN_TEST_CASE(Global, metisHopByHopFragmenter_Receive_Ok); + + LONGBOW_RUN_TEST_CASE(Global, metisHopByHopFragmenter_Send_OneMtu); + LONGBOW_RUN_TEST_CASE(Global, metisHopByHopFragmenter_Send_ReceiveQueueFull); + LONGBOW_RUN_TEST_CASE(Global, metisHopByHopFragmenter_Send_Ok); +} + + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + TestData *data = _createTestData(); + longBowTestCase_SetClipBoardData(testCase, data); + + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _destroyTestData(&data); + + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisHopByHopFragmenter_Create) +{ + // nothing really to do here, just need to make sure there's no memory leak in teardown + TestData *data = longBowTestCase_GetClipBoardData(testCase); + assertNotNull(data->fragmenter, "Got null fragmenter"); +} + +/* + * Receive a non-hop-by-hop packet. Should go straight in to the receive queue + */ +LONGBOW_TEST_CASE(Global, metisHopByHopFragmenter_Receive_NotHopByHop) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + MetisTicks startTicks = 1111111; + unsigned ingressId = 77; + + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV1_Interest_AllFields, sizeof(metisTestDataV1_Interest_AllFields), ingressId, startTicks, data->logger); + metisHopByHopFragmenter_Receive(data->fragmenter, message); + + /* + * 1) Make a metis message out of the reassembly buffer, + * 2) put the message in the receive queue (discard if queue full) + * 3) allocate a new reassembly buffer + * 4) reset the parser + */ + + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Got null reassembled message"); + + assertTrue(test == message, "Message not in receive queue, expected %p got %p", (void *) message, (void *) test); + + metisMessage_Release(&message); + metisMessage_Release(&test); +} + +LONGBOW_TEST_CASE(Global, metisHopByHopFragmenter_Receive_ReceiveQueueFull) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // create a full recieve queue + parcRingBuffer1x1_Release(&data->fragmenter->receiveQueue); + data->fragmenter->receiveQueue = parcRingBuffer1x1_Create(2, _ringBufferDestroyer); + + void *fakeData = (void *) 1; + parcRingBuffer1x1_Put(data->fragmenter->receiveQueue, fakeData); + + assertTrue(parcRingBuffer1x1_Remaining(data->fragmenter->receiveQueue) == 0, "expected queue to be full"); + + // === run test + MetisTicks startTicks = 1111111; + unsigned ingressId = 77; + + data->fragmenter->nextReceiveFragSequenceNumber = 1; + + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV1_Interest_AllFields, sizeof(metisTestDataV1_Interest_AllFields), ingressId, startTicks, data->logger); + metisHopByHopFragmenter_Receive(data->fragmenter, message); + + // should still only be the fake data in the queue + void *test = NULL; + parcRingBuffer1x1_Get(data->fragmenter->receiveQueue, &test); + assertTrue(test == fakeData, "Wrong pointer, expected %p got %p", fakeData, test); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisHopByHopFragmenter_Receive_Ok) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + MetisTicks startTicks = 1111111; + unsigned ingressId = 77; + + data->fragmenter->nextReceiveFragSequenceNumber = 1; + + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Begin, sizeof(metisTestDataV1_HopByHopFrag_Begin), ingressId, startTicks, data->logger); + metisHopByHopFragmenter_Receive(data->fragmenter, message); + + /* + * We should now be in the Busy state + */ + assertTrue(data->fragmenter->parserState == _ParserState_Busy, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + + metisMessage_Release(&message); +} + +LONGBOW_TEST_CASE(Global, metisHopByHopFragmenter_Send_OneMtu) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // make a packet shorter than one MTU (so it will fit with the fragment overhead) + size_t length = data->fragmenter->mtu - 100; + uint8_t *packet = _conjurePacket(length); + MetisMessage *message = metisMessage_CreateFromArray(packet, length, 1, 2, data->logger); + assertNotNull(message, "Could not conjure packet"); + + bool success = metisHopByHopFragmenter_Send(data->fragmenter, message); + + assertTrue(success, "Failed to send fragments"); + MetisMessage *fragment = metisHopByHopFragmenter_PopSendQueue(data->fragmenter); + assertNotNull(fragment, "Did not find a fragment in the send queue"); + + // === + // defragment it + + metisHopByHopFragmenter_Receive(data->fragmenter, fragment); + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Should have gotten the original message back"); + assertTrue(metisMessage_Length(test) == metisMessage_Length(message), + "Reconstructed message length wrong expected %zu got %zu", + metisMessage_Length(message), + metisMessage_Length(test)); + + // === + // cleanup + + metisMessage_Release(&fragment); + metisMessage_Release(&message); + metisMessage_Release(&test); + parcMemory_Deallocate((void **) &packet); +} + +LONGBOW_TEST_CASE(Global, metisHopByHopFragmenter_Send_ReceiveQueueFull) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // create a full send queue + parcRingBuffer1x1_Release(&data->fragmenter->sendQueue); + data->fragmenter->sendQueue = parcRingBuffer1x1_Create(2, _ringBufferDestroyer); + + void *fakeData = (void *) 1; + parcRingBuffer1x1_Put(data->fragmenter->sendQueue, fakeData); + + + // less than 1 MTU + size_t length = data->fragmenter->mtu - 100; + uint8_t *packet = _conjurePacket(length); + MetisMessage *message = metisMessage_CreateFromArray(packet, length, 1, 2, data->logger); + assertNotNull(message, "Could not conjure packet"); + + + bool success = metisHopByHopFragmenter_Send(data->fragmenter, message); + assertFalse(success, "Should have failed to send fragments"); + + // === + // cleanup + + // manually pop this off as it is not a proper MetisMessage + parcRingBuffer1x1_Get(data->fragmenter->sendQueue, &fakeData); + metisMessage_Release(&message); + parcMemory_Deallocate((void **) &packet); +} + +LONGBOW_TEST_CASE(Global, metisHopByHopFragmenter_Send_Ok) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // Take up 2 MTUs (minus a little for fragmentation overhead) + size_t length = 2 * data->fragmenter->mtu - 100; + uint8_t *packet = _conjurePacket(length); + MetisMessage *message = metisMessage_CreateFromArray(packet, length, 1, 2, data->logger); + assertNotNull(message, "Could not conjure packet"); + + + bool success = metisHopByHopFragmenter_Send(data->fragmenter, message); + assertTrue(success, "Failed to send fragments"); + + // === + // defragment it + + MetisMessage *fragment; + while ((fragment = metisHopByHopFragmenter_PopSendQueue(data->fragmenter)) != NULL) { + metisHopByHopFragmenter_Receive(data->fragmenter, fragment); + metisMessage_Release(&fragment); + } + ; + + + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Should have gotten the original message back"); + assertTrue(metisMessage_Length(test) == metisMessage_Length(message), + "Reconstructed message length wrong expected %zu got %zu", + metisMessage_Length(message), + metisMessage_Length(test)); + + // === + // cleanup + + metisMessage_Release(&message); + metisMessage_Release(&test); + parcMemory_Deallocate((void **) &packet); +} + +// ============================================================ + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _compareSequenceNumbers); + LONGBOW_RUN_TEST_CASE(Local, _incrementSequenceNumber); + LONGBOW_RUN_TEST_CASE(Local, _resetParser); + LONGBOW_RUN_TEST_CASE(Local, _applySequenceNumberRules_InOrder); + LONGBOW_RUN_TEST_CASE(Local, _applySequenceNumberRules_Early); + LONGBOW_RUN_TEST_CASE(Local, _applySequenceNumberRules_Late); + LONGBOW_RUN_TEST_CASE(Local, _finalizeReassemblyBuffer_NotFull); + LONGBOW_RUN_TEST_CASE(Local, _finalizeReassemblyBuffer_Full); + LONGBOW_RUN_TEST_CASE(Local, _appendFragmentToReassemblyBuffer_Once); + LONGBOW_RUN_TEST_CASE(Local, _appendFragmentToReassemblyBuffer_Multiple); + + LONGBOW_RUN_TEST_CASE(Local, _receiveInIdleState_BFrame); + LONGBOW_RUN_TEST_CASE(Local, _receiveInIdleState_BEFrame); + LONGBOW_RUN_TEST_CASE(Local, _receiveInIdleState_OtherFrame); + + LONGBOW_RUN_TEST_CASE(Local, _receiveInBusyState_EFrame); + LONGBOW_RUN_TEST_CASE(Local, _receiveInBusyState_NoFlagFrame); + LONGBOW_RUN_TEST_CASE(Local, _receiveInBusyState_OtherFrame); + + LONGBOW_RUN_TEST_CASE(Local, _receiveFragment_IdleState); + LONGBOW_RUN_TEST_CASE(Local, _receiveFragment_BusyState); + + LONGBOW_RUN_TEST_CASE(Local, _sendFragments_OneFragment); + LONGBOW_RUN_TEST_CASE(Local, _sendFragments_TwoFragments); + LONGBOW_RUN_TEST_CASE(Local, _sendFragments_ThreeFragments); + LONGBOW_RUN_TEST_CASE(Local, _sendFragments_SendQueueFull); + + LONGBOW_RUN_TEST_CASE(Local, _ringBufferDestroyer); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + TestData *data = _createTestData(); + longBowTestCase_SetClipBoardData(testCase, data); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _destroyTestData(&data); + + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, _compareSequenceNumbers) +{ + struct test_vector { + uint32_t a; + uint32_t b; + int signum; + bool sentinel; + } testVectors[] = { + // compared to b = 0, then a = {1 ... 0x07FFFF} is greater than b + // compared to b = 0, then a = {0x080000 ... 0x0FFFFF} is less than b + + { .a = 0x00000000, .b = 0x00000000, .signum = 0, .sentinel = false }, + { .a = 0x00000001, .b = 0x00000000, .signum = +1, .sentinel = false }, + { .a = 0x0007FFFF, .b = 0x00000000, .signum = +1, .sentinel = false }, + { .a = 0x00080000, .b = 0x00000000, .signum = -1, .sentinel = false }, + { .a = 0x000FFFFF, .b = 0x00000000, .signum = -1, .sentinel = false }, + + // now do the same thing but use b = 0x00040000 + { .a = 0x00040000, .b = 0x00040000, .signum = 0, .sentinel = false }, + { .a = 0x00040001, .b = 0x00040000, .signum = +1, .sentinel = false }, + { .a = 0x000BFFFF, .b = 0x00040000, .signum = +1, .sentinel = false }, + { .a = 0x000C0000, .b = 0x00040000, .signum = -1, .sentinel = false }, + { .a = 0x0003FFFF, .b = 0x00040000, .signum = -1, .sentinel = false }, + + // end test set + { .a = 0x00000000, .b = 0x00000000, .signum = 0, .sentinel = true }, + }; + + for (int i = 0; !testVectors[i].sentinel; i++) { + int result = _compareSequenceNumbers(testVectors[i].a, testVectors[i].b); + + if (testVectors[i].signum == 0) { + assertTrue(result == 0, "Wrong result, expected 0 got %d index %d a 0x%08x b 0x%08x", + result, i, testVectors[i].a, testVectors[i].b); + } + + if (testVectors[i].signum < 0) { + assertTrue(result < 0, "Wrong result, expected negative got %d index %d a 0x%08x b 0x%08x", + result, i, testVectors[i].a, testVectors[i].b); + } + + if (testVectors[i].signum > 0) { + assertTrue(result > 0, "Wrong result, expected positive got %d index %d a 0x%08x b 0x%08x", + result, i, testVectors[i].a, testVectors[i].b); + } + } +} + +LONGBOW_TEST_CASE(Local, _incrementSequenceNumber) +{ + struct test_vector { + uint32_t a; + uint32_t b; + bool sentinel; + } testVectors[] = { + { .a = 0x00000000, .b = 0x00000001, .sentinel = false }, + { .a = 0x00000001, .b = 0x00000002, .sentinel = false }, + { .a = 0x0007FFFF, .b = 0x00080000, .sentinel = false }, + { .a = 0x000FFFFF, .b = 0x00000000, .sentinel = false }, + // end test set + { .a = 0x00000000, .b = 0x00000000, .sentinel = true }, + }; + + for (int i = 0; !testVectors[i].sentinel; i++) { + uint32_t result = _incrementSequenceNumber(testVectors[i].a, 0x000FFFFF); + + assertTrue(result == testVectors[i].b, "Wrong result 0x%08X index %d, got for a 0x%08x b 0x%08x", + result, i, testVectors[i].a, testVectors[i].b); + } +} + +LONGBOW_TEST_CASE(Local, _resetParser) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // put something in the buffer and set the parser state to Busy + data->fragmenter->parserState = _ParserState_Busy; + parcEventBuffer_Append(data->fragmenter->currentReceiveBuffer, data, sizeof(data)); + + _resetParser(data->fragmenter); + assertTrue(data->fragmenter->parserState == _ParserState_Idle, "Wrong parser state, exepcted %d got %d", _ParserState_Idle, data->fragmenter->parserState); + + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == 0, "Wrong length, expected 0 bytes, got %zu", length); +} + +LONGBOW_TEST_CASE(Local, _applySequenceNumberRules_InOrder) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + data->fragmenter->parserState = _ParserState_Busy; + data->fragmenter->nextReceiveFragSequenceNumber = 1000; + + _HopByHopHeader header; + memset(&header, 0, sizeof(header)); + + _hopByHopHeader_SetSeqnum(&header, 1000); + + _applySequenceNumberRules(data->fragmenter, &header); + + // should still be in Busy mode and expecting 1001 + assertTrue(data->fragmenter->parserState == _ParserState_Busy, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + assertTrue(data->fragmenter->nextReceiveFragSequenceNumber == 1001, "Wrong next seqnum, expected 1001 got %u", data->fragmenter->nextReceiveFragSequenceNumber); +} + +LONGBOW_TEST_CASE(Local, _applySequenceNumberRules_Early) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + data->fragmenter->parserState = _ParserState_Busy; + data->fragmenter->nextReceiveFragSequenceNumber = 1000; + + _HopByHopHeader header; + memset(&header, 0, sizeof(header)); + + _hopByHopHeader_SetSeqnum(&header, 998); + + _applySequenceNumberRules(data->fragmenter, &header); + + // should reset state and set next to 999 + assertTrue(data->fragmenter->parserState == _ParserState_Idle, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + assertTrue(data->fragmenter->nextReceiveFragSequenceNumber == 999, "Wrong next seqnum, expected 999 got %u", data->fragmenter->nextReceiveFragSequenceNumber); +} + +LONGBOW_TEST_CASE(Local, _applySequenceNumberRules_Late) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + data->fragmenter->parserState = _ParserState_Busy; + data->fragmenter->nextReceiveFragSequenceNumber = 1000; + + _HopByHopHeader header; + memset(&header, 0, sizeof(header)); + + _hopByHopHeader_SetSeqnum(&header, 1001); + + _applySequenceNumberRules(data->fragmenter, &header); + + // should reset state and set next to 1002 + assertTrue(data->fragmenter->parserState == _ParserState_Idle, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + assertTrue(data->fragmenter->nextReceiveFragSequenceNumber == 1002, "Wrong next seqnum, expected 1002 got %u", data->fragmenter->nextReceiveFragSequenceNumber); +} + +LONGBOW_TEST_CASE(Local, _finalizeReassemblyBuffer_NotFull) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + MetisTicks startTicks = 1111111; + unsigned ingressId = 77; + + // set up as just finished with a message, so the currentReceiveBuffer has + // a complete CCNx message array in it + data->fragmenter->parserState = _ParserState_Busy; + data->fragmenter->currentReceiveBufferIngressId = ingressId; + data->fragmenter->currentReceiveBufferStartTicks = startTicks; + parcEventBuffer_Append(data->fragmenter->currentReceiveBuffer, metisTestDataV1_Interest_AllFields, sizeof(metisTestDataV1_Interest_AllFields)); + + _finalizeReassemblyBuffer(data->fragmenter); + + /* + * 1) Make a metis message out of the reassembly buffer, + * 2) put the message in the receive queue (discard if queue full) + * 3) allocate a new reassembly buffer + * 4) reset the parser + */ + + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Got null reassembled message"); + assertTrue(data->fragmenter->parserState == _ParserState_Idle, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + assertNotNull(data->fragmenter->currentReceiveBuffer, "Current receive buffer should not be null"); + assertTrue(parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer) == 0, "Current receive buffer should be empty, got %zu bytes", parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer)); + + assertTrue(metisMessage_GetIngressConnectionId(test) == ingressId, "Wrong ingress id expected %u got %u", ingressId, metisMessage_GetIngressConnectionId(test)); + assertTrue(metisMessage_GetReceiveTime(test) == startTicks, "Wrong receive time expected %" PRIu64 " got %" PRIu64, + startTicks, metisMessage_GetReceiveTime(test)); + + metisMessage_Release(&test); +} + +LONGBOW_TEST_CASE(Local, _finalizeReassemblyBuffer_Full) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + MetisTicks startTicks = 1111111; + unsigned ingressId = 77; + + // set up as just finished with a message, so the currentReceiveBuffer has + // a complete CCNx message array in it + data->fragmenter->parserState = _ParserState_Busy; + data->fragmenter->currentReceiveBufferIngressId = ingressId; + data->fragmenter->currentReceiveBufferStartTicks = startTicks; + parcEventBuffer_Append(data->fragmenter->currentReceiveBuffer, metisTestDataV1_Interest_AllFields, sizeof(metisTestDataV1_Interest_AllFields)); + + // create a full recieve queue + parcRingBuffer1x1_Release(&data->fragmenter->receiveQueue); + data->fragmenter->receiveQueue = parcRingBuffer1x1_Create(2, _ringBufferDestroyer); + + void *fakeData = (void *) 1; + parcRingBuffer1x1_Put(data->fragmenter->receiveQueue, fakeData); + + assertTrue(parcRingBuffer1x1_Remaining(data->fragmenter->receiveQueue) == 0, "expected queue to be full"); + + /* + * Call with a full receive queue + */ + _finalizeReassemblyBuffer(data->fragmenter); + + void *test = NULL; + parcRingBuffer1x1_Get(data->fragmenter->receiveQueue, &test); + assertTrue(test == fakeData, "Wrong pointer, expected %p got %p", fakeData, test); + + // teardown should show no memory leak +} + +LONGBOW_TEST_CASE(Local, _appendFragmentToReassemblyBuffer_Once) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + unsigned connid = 7; + MetisTicks receiveTime = 9999; + + MetisMessage *fragment = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Begin, sizeof(metisTestDataV1_HopByHopFrag_Begin), connid, receiveTime, data->logger); + _appendFragmentToReassemblyBuffer(data->fragmenter, fragment); + + int fragmentLength = sizeof(metisTestDataV1_HopByHopFrag_Begin_Fragment); + + assertTrue(parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer) == fragmentLength, + "currentReceiveBuffer wrong lenth, expected %d got %zu", + fragmentLength, + parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer)); + + uint8_t *test = parcEventBuffer_Pullup(data->fragmenter->currentReceiveBuffer, -1); + assertTrue(memcmp(test, metisTestDataV1_HopByHopFrag_Begin_Fragment, fragmentLength) == 0, "Fragment payload did not match"); + + metisMessage_Release(&fragment); +} + +LONGBOW_TEST_CASE(Local, _appendFragmentToReassemblyBuffer_Multiple) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + unsigned connid = 7; + MetisTicks receiveTime = 9999; + + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Begin, sizeof(metisTestDataV1_HopByHopFrag_Begin), connid, receiveTime, data->logger); + _appendFragmentToReassemblyBuffer(data->fragmenter, fragment1); + + MetisMessage *fragment2 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Middle, sizeof(metisTestDataV1_HopByHopFrag_Middle), connid, receiveTime, data->logger); + _appendFragmentToReassemblyBuffer(data->fragmenter, fragment2); + + MetisMessage *fragment3 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_End, sizeof(metisTestDataV1_HopByHopFrag_End), connid, receiveTime, data->logger); + _appendFragmentToReassemblyBuffer(data->fragmenter, fragment3); + + int fragmentLength = sizeof(metisTestDataV1_HopByHopFrag_BeginEnd_Fragment); + + assertTrue(parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer) == fragmentLength, + "currentReceiveBuffer wrong lenth, expected %d got %zu", + fragmentLength, + parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer)); + + uint8_t *test = parcEventBuffer_Pullup(data->fragmenter->currentReceiveBuffer, -1); + + // compares against the fragment metisTestDataV1_HopByHopFrag_BeginEnd which has the whole payload + assertTrue(memcmp(test, metisTestDataV1_HopByHopFrag_BeginEnd_Fragment, fragmentLength) == 0, "Fragment payload did not match"); + + metisMessage_Release(&fragment1); + metisMessage_Release(&fragment2); + metisMessage_Release(&fragment3); +} + + +/* + * B frame should be added to currentReceiveBuffer and state should become Busy. + * Also, the currentReceiveBufferIngressId and currentReceiveBufferReceiveTime should be set. + */ +LONGBOW_TEST_CASE(Local, _receiveInIdleState_BFrame) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // ensure we're in Idle state + _resetParser(data->fragmenter); + + unsigned connid = 7; + MetisTicks receiveTime = 9999; + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Begin, sizeof(metisTestDataV1_HopByHopFrag_Begin), connid, receiveTime, data->logger); + + const _HopByHopHeader *header = (const _HopByHopHeader *) metisTestDataV1_HopByHopFrag_Begin; + _receiveInIdleState(data->fragmenter, fragment1, header); + + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == sizeof(metisTestDataV1_HopByHopFrag_Begin_Fragment), "Wrong reassembly buffer length expected %zu got %zu", sizeof(metisTestDataV1_HopByHopFrag_Begin_Fragment), length); + assertTrue(data->fragmenter->parserState == _ParserState_Busy, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + assertTrue(data->fragmenter->currentReceiveBufferIngressId == connid, "Wrong ingress id expected %u got %u", connid, data->fragmenter->currentReceiveBufferIngressId); + assertTrue(data->fragmenter->currentReceiveBufferStartTicks == receiveTime, "Wrong receive time expected %" PRIu64 " got %" PRIu64, + receiveTime, data->fragmenter->currentReceiveBufferStartTicks); + metisMessage_Release(&fragment1); +} + +/* + * BE frame should be added to currentReceiveBuffer and finalized. + * State should stay in Idle but the receiveQueue should have the frame in it. + */ +LONGBOW_TEST_CASE(Local, _receiveInIdleState_BEFrame) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // ensure we're in Idle state + _resetParser(data->fragmenter); + + unsigned connid = 7; + MetisTicks receiveTime = 9999; + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_BeginEnd, sizeof(metisTestDataV1_HopByHopFrag_BeginEnd), connid, receiveTime, data->logger); + + const _HopByHopHeader *header = (const _HopByHopHeader *) metisTestDataV1_HopByHopFrag_BeginEnd; + _receiveInIdleState(data->fragmenter, fragment1, header); + + // should not be in the reassembly buffer + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == 0, "Wrong reassembly buffer length expected 0 got %zu", length); + + // it should be in the receive queue + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Message was not in receive queue"); + metisMessage_Release(&test); + + assertTrue(data->fragmenter->parserState == _ParserState_Idle, "Wrong parser state, exepcted %d got %d", _ParserState_Idle, data->fragmenter->parserState); + + + assertTrue(data->fragmenter->currentReceiveBufferIngressId == connid, "Wrong ingress id expected %u got %u", connid, data->fragmenter->currentReceiveBufferIngressId); + assertTrue(data->fragmenter->currentReceiveBufferStartTicks == receiveTime, "Wrong receive time expected %" PRIu64 " got %" PRIu64, + receiveTime, data->fragmenter->currentReceiveBufferStartTicks); + metisMessage_Release(&fragment1); +} + +/* + * Not B and Not BE frames should be ignored + */ +LONGBOW_TEST_CASE(Local, _receiveInIdleState_OtherFrame) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + struct test_vector { + uint8_t flags; + bool sentinel; + } testVectors[] = { + // All combinations except 0x40 and 0x60 + { .flags = 0x00, .sentinel = false }, + { .flags = 0x10, .sentinel = false }, + { .flags = 0x20, .sentinel = false }, + { .flags = 0x30, .sentinel = false }, + { .flags = 0x80, .sentinel = false }, + { .flags = 0x90, .sentinel = false }, + { .flags = 0xA0, .sentinel = false }, + { .flags = 0xB0, .sentinel = false }, + { .flags = 0x00, .sentinel = true }, + }; + + for (int i = 0; !testVectors[i].sentinel; i++) { + _HopByHopHeader header; + memset(&header, 0, sizeof(header)); + + header.blob[0] |= testVectors[i].flags; + + unsigned connid = 7; + MetisTicks receiveTime = 9999; + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_BeginEnd, sizeof(metisTestDataV1_HopByHopFrag_BeginEnd), connid, receiveTime, data->logger); + + _receiveInIdleState(data->fragmenter, fragment1, &header); + + metisMessage_Release(&fragment1); + + // should not be in the reassembly buffer + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == 0, "Wrong reassembly buffer length expected 0 got %zu", length); + + assertTrue(data->fragmenter->parserState == _ParserState_Idle, "Wrong parser state, exepcted %d got %d", _ParserState_Idle, data->fragmenter->parserState); + } +} + +/* + * 2) If E flag + * 2a) append to reassembly buffer + * 2b) finalize the buffer (side effect: will reset the parser and place in receive queue) + */ +LONGBOW_TEST_CASE(Local, _receiveInBusyState_EFrame) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + unsigned connid = 7; + MetisTicks receiveTime = 9999; + + // ensure we're in Busy state (the precondition of this test) + _resetParser(data->fragmenter); + data->fragmenter->parserState = _ParserState_Busy; + + // and put the Begin and Middle fragments in the reassembly buffer so the packet will decode properly + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Begin, sizeof(metisTestDataV1_HopByHopFrag_Begin), connid, receiveTime, data->logger); + _appendFragmentToReassemblyBuffer(data->fragmenter, fragment1); + + MetisMessage *fragment2 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Middle, sizeof(metisTestDataV1_HopByHopFrag_Middle), connid, receiveTime, data->logger); + _appendFragmentToReassemblyBuffer(data->fragmenter, fragment2); + + // ==== + // Now do the test + + MetisMessage *fragment3 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_End, sizeof(metisTestDataV1_HopByHopFrag_End), connid, receiveTime, data->logger); + + const _HopByHopHeader *header = (const _HopByHopHeader *) metisTestDataV1_HopByHopFrag_End; + _receiveInBusyState(data->fragmenter, fragment3, header); + + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == 0, "Wrong reassembly buffer length expected 0 got %zu", length); + + assertTrue(data->fragmenter->parserState == _ParserState_Idle, "Wrong parser state, exepcted %d got %d", _ParserState_Idle, data->fragmenter->parserState); + + // it should be in the receive queue + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Message was not in receive queue"); + metisMessage_Release(&test); + + metisMessage_Release(&fragment1); + metisMessage_Release(&fragment2); + metisMessage_Release(&fragment3); +} + +/* + * 1) If no flags + * 1a) append to reassembly buffer + */ +LONGBOW_TEST_CASE(Local, _receiveInBusyState_NoFlagFrame) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // ensure we're in Busy state (the precondition of this test) + _resetParser(data->fragmenter); + data->fragmenter->parserState = _ParserState_Busy; + + unsigned connid = 7; + MetisTicks receiveTime = 9999; + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Middle, sizeof(metisTestDataV1_HopByHopFrag_Middle), connid, receiveTime, data->logger); + + const _HopByHopHeader *header = (const _HopByHopHeader *) metisTestDataV1_HopByHopFrag_Middle; + _receiveInBusyState(data->fragmenter, fragment1, header); + + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == sizeof(metisTestDataV1_HopByHopFrag_Middle_Fragment), "Wrong reassembly buffer length expected %zu got %zu", sizeof(metisTestDataV1_HopByHopFrag_Middle_Fragment), length); + + assertTrue(data->fragmenter->parserState == _ParserState_Busy, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + metisMessage_Release(&fragment1); +} + +LONGBOW_TEST_CASE(Local, _receiveInBusyState_OtherFrame) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + struct test_vector { + uint8_t flags; + bool sentinel; + } testVectors[] = { + // All combinations except 0x00 and 0x20 + { .flags = 0x10, .sentinel = false }, + { .flags = 0x40, .sentinel = false }, + { .flags = 0x80, .sentinel = false }, + { .flags = 0x50, .sentinel = false }, + { .flags = 0x90, .sentinel = false }, + { .flags = 0xC0, .sentinel = false }, + { .flags = 0x00, .sentinel = true }, + }; + + for (int i = 0; !testVectors[i].sentinel; i++) { + _HopByHopHeader header; + memset(&header, 0, sizeof(header)); + + header.blob[0] |= testVectors[i].flags; + + unsigned connid = 7; + MetisTicks receiveTime = 9999; + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_BeginEnd, sizeof(metisTestDataV1_HopByHopFrag_BeginEnd), connid, receiveTime, data->logger); + + // ensure we're in Busy state (the precondition of this test) + _resetParser(data->fragmenter); + data->fragmenter->parserState = _ParserState_Busy; + + _receiveInBusyState(data->fragmenter, fragment1, &header); + + metisMessage_Release(&fragment1); + + // should not be in the reassembly buffer + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == 0, "Wrong reassembly buffer length expected 0 got %zu", length); + + assertTrue(data->fragmenter->parserState == _ParserState_Idle, "Wrong parser state, exepcted %d got %d", _ParserState_Idle, data->fragmenter->parserState); + } +} + +/* + * Receive a B frame in Idle state + */ +LONGBOW_TEST_CASE(Local, _receiveFragment_IdleState) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + unsigned connid = 7; + MetisTicks receiveTime = 9999; + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Begin, sizeof(metisTestDataV1_HopByHopFrag_Begin), connid, receiveTime, data->logger); + + _receiveFragment(data->fragmenter, fragment1); + + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == sizeof(metisTestDataV1_HopByHopFrag_Begin_Fragment), "Wrong reassembly buffer length expected %zu got %zu", sizeof(metisTestDataV1_HopByHopFrag_Begin_Fragment), length); + assertTrue(data->fragmenter->parserState == _ParserState_Busy, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + assertTrue(data->fragmenter->currentReceiveBufferIngressId == connid, "Wrong ingress id expected %u got %u", connid, data->fragmenter->currentReceiveBufferIngressId); + assertTrue(data->fragmenter->currentReceiveBufferStartTicks == receiveTime, "Wrong receive time expected %" PRIu64 " got %" PRIu64, + receiveTime, data->fragmenter->currentReceiveBufferStartTicks); + metisMessage_Release(&fragment1); +} + +LONGBOW_TEST_CASE(Local, _receiveFragment_BusyState) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // ensure we're in Busy state (the precondition of this test) + // Make sure the packet will be in-order by setting the next expected seqnum. + _resetParser(data->fragmenter); + data->fragmenter->parserState = _ParserState_Busy; + data->fragmenter->nextReceiveFragSequenceNumber = 2; + + unsigned connid = 7; + MetisTicks receiveTime = 9999; + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Middle, sizeof(metisTestDataV1_HopByHopFrag_Middle), connid, receiveTime, data->logger); + + _receiveFragment(data->fragmenter, fragment1); + + size_t length = parcEventBuffer_GetLength(data->fragmenter->currentReceiveBuffer); + assertTrue(length == sizeof(metisTestDataV1_HopByHopFrag_Middle_Fragment), "Wrong reassembly buffer length expected %zu got %zu", sizeof(metisTestDataV1_HopByHopFrag_Middle_Fragment), length); + + assertTrue(data->fragmenter->parserState == _ParserState_Busy, "Wrong parser state, exepcted %d got %d", _ParserState_Busy, data->fragmenter->parserState); + metisMessage_Release(&fragment1); +} + +LONGBOW_TEST_CASE(Local, _sendFragments_OneFragment) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // make a packet shorter than one MTU (so it will fit with the fragment overhead) + size_t length = data->fragmenter->mtu - 100; + uint8_t *packet = _conjurePacket(length); + MetisMessage *message = metisMessage_CreateFromArray(packet, length, 1, 2, data->logger); + assertNotNull(message, "Could not conjure packet"); + + + bool success = _sendFragments(data->fragmenter, message); + assertTrue(success, "Failed to send fragments"); + MetisMessage *fragment = metisHopByHopFragmenter_PopSendQueue(data->fragmenter); + assertNotNull(fragment, "Did not find a fragment in the send queue"); + + // === + // defragment it + + _receiveFragment(data->fragmenter, fragment); + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Should have gotten the original message back"); + assertTrue(metisMessage_Length(test) == metisMessage_Length(message), + "Reconstructed message length wrong expected %zu got %zu", + metisMessage_Length(message), + metisMessage_Length(test)); + + // === + // cleanup + + metisMessage_Release(&message); + metisMessage_Release(&fragment); + metisMessage_Release(&test); + parcMemory_Deallocate((void **) &packet); +} + +LONGBOW_TEST_CASE(Local, _sendFragments_TwoFragments) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // Take up 2 MTUs (minus a little for fragmentation overhead) + size_t length = 2 * data->fragmenter->mtu - 100; + uint8_t *packet = _conjurePacket(length); + MetisMessage *message = metisMessage_CreateFromArray(packet, length, 1, 2, data->logger); + assertNotNull(message, "Could not conjure packet"); + + + bool success = _sendFragments(data->fragmenter, message); + assertTrue(success, "Failed to send fragments"); + + // === + // defragment it + + MetisMessage *fragment; + while ((fragment = metisHopByHopFragmenter_PopSendQueue(data->fragmenter)) != NULL) { + _receiveFragment(data->fragmenter, fragment); + metisMessage_Release(&fragment); + } + ; + + + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Should have gotten the original message back"); + assertTrue(metisMessage_Length(test) == metisMessage_Length(message), + "Reconstructed message length wrong expected %zu got %zu", + metisMessage_Length(message), + metisMessage_Length(test)); + + // === + // cleanup + + metisMessage_Release(&message); + metisMessage_Release(&test); + parcMemory_Deallocate((void **) &packet); +} + +LONGBOW_TEST_CASE(Local, _sendFragments_ThreeFragments) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // Take up 2 MTUs (minus a little for fragmentation overhead) + size_t length = 3 * data->fragmenter->mtu - 100; + uint8_t *packet = _conjurePacket(length); + MetisMessage *message = metisMessage_CreateFromArray(packet, length, 1, 2, data->logger); + assertNotNull(message, "Could not conjure packet"); + + + bool success = _sendFragments(data->fragmenter, message); + assertTrue(success, "Failed to send fragments"); + + // === + // defragment it + + MetisMessage *fragment; + while ((fragment = metisHopByHopFragmenter_PopSendQueue(data->fragmenter)) != NULL) { + _receiveFragment(data->fragmenter, fragment); + metisMessage_Release(&fragment); + } + ; + + + MetisMessage *test = metisHopByHopFragmenter_PopReceiveQueue(data->fragmenter); + assertNotNull(test, "Should have gotten the original message back"); + assertTrue(metisMessage_Length(test) == metisMessage_Length(message), + "Reconstructed message length wrong expected %zu got %zu", + metisMessage_Length(message), + metisMessage_Length(test)); + + // === + // cleanup + + metisMessage_Release(&message); + metisMessage_Release(&test); + parcMemory_Deallocate((void **) &packet); +} + +LONGBOW_TEST_CASE(Local, _sendFragments_SendQueueFull) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // create a full send queue + parcRingBuffer1x1_Release(&data->fragmenter->sendQueue); + data->fragmenter->sendQueue = parcRingBuffer1x1_Create(2, _ringBufferDestroyer); + + void *fakeData = (void *) 1; + parcRingBuffer1x1_Put(data->fragmenter->sendQueue, fakeData); + + + // Take up 2 MTUs (minus a little for fragmentation overhead) + size_t length = 3 * data->fragmenter->mtu - 100; + uint8_t *packet = _conjurePacket(length); + MetisMessage *message = metisMessage_CreateFromArray(packet, length, 1, 2, data->logger); + assertNotNull(message, "Could not conjure packet"); + + + bool success = _sendFragments(data->fragmenter, message); + assertFalse(success, "Should have failed to send fragments"); + // === + // cleanup + + // manually pop this off as it is not a proper MetisMessage + parcRingBuffer1x1_Get(data->fragmenter->sendQueue, &fakeData); + + metisMessage_Release(&message); + parcMemory_Deallocate((void **) &packet); +} + +LONGBOW_TEST_CASE(Local, _ringBufferDestroyer) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + unsigned connid = 7; + MetisTicks receiveTime = 9999; + MetisMessage *fragment1 = metisMessage_CreateFromArray(metisTestDataV1_HopByHopFrag_Middle, sizeof(metisTestDataV1_HopByHopFrag_Middle), connid, receiveTime, data->logger); + + bool success = parcRingBuffer1x1_Put(data->fragmenter->receiveQueue, fragment1); + assertTrue(success, "Failed to put test message in queue"); + + // nothing to do here. When the fragmenter is destroyed it should destroy the message + // and we will not trip a memory imbalance +} + +// ============================================================ +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_HopByHopFragmenter); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_IPMulticastListener.c b/metis/ccnx/forwarder/metis/io/test/test_metis_IPMulticastListener.c new file mode 100644 index 00000000..12dc17dc --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_IPMulticastListener.c @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_IPMulticastListener.c" +#include <parc/algol/parc_SafeMemory.h> + +#include <LongBow/unit-test.h> + +LONGBOW_TEST_RUNNER(metis_IPMulticastListener) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_IPMulticastListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_IPMulticastListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Local) +{ +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_IPMulticastListener); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_IoOperations.c b/metis/ccnx/forwarder/metis/io/test/test_metis_IoOperations.c new file mode 100644 index 00000000..a78715c1 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_IoOperations.c @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_IoOperations.c" +#include <parc/algol/parc_SafeMemory.h> + +#include <LongBow/unit-test.h> + +#include <ccnx/forwarder/metis/core/test/testrig_MetisIoOperations.h> + +// =========================================== + +LONGBOW_TEST_RUNNER(metis_IoOperations) +{ + LONGBOW_RUN_TEST_FIXTURE(Global); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_IoOperations) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_IoOperations) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_GetClosure); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_Send); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_GetRemoteAddress); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_GetAddressPair); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_IsUp); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_IsLocal); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_GetConnectionId); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_Release); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_Class); + LONGBOW_RUN_TEST_CASE(Global, metisIoOperations_GetConnectionType); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_GetClosure) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + void *closure = metisIoOperations_GetClosure(ops); + assertTrue(closure == ops->closure, "Wrong closure, expected %p got %p", ops->closure, closure); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_Send) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_Send(ops, NULL, NULL); + assertTrue(data->sendCount == 1, "Wrong metisIoOperations_Send count expected 1 got %u", data->sendCount); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_GetRemoteAddress) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_GetRemoteAddress(ops); + assertTrue(data->getRemoteAddressCount == 1, "Wrong metisIoOperations_GetRemoteAddress count expected 1 got %u", data->getRemoteAddressCount); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_GetAddressPair) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_GetAddressPair(ops); + assertTrue(data->getAddressPairCount == 1, "Wrong metisIoOperations_GetAddressPairexpected count 1 got %u", data->getAddressPairCount); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_IsUp) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_IsUp(ops); + assertTrue(data->isUpCount == 1, "Wrong metisIoOperations_IsUp count expected 1 got %u", data->isUpCount); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_IsLocal) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_IsLocal(ops); + assertTrue(data->isLocalCount == 1, "Wrong metisIoOperations_IsLocal count expected 1 got %u", data->isLocalCount); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_GetConnectionId) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_GetConnectionId(ops); + assertTrue(data->getConnectionIdCount == 1, "Wrong metisIoOperations_GetConnectionId count expected 1 got %u", data->getConnectionIdCount); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_Release) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MetisIoOperations *copy = ops; + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_Release(&ops); + assertTrue(data->destroyCount == 1, "Wrong metisIoOperations_Release count expected 1 got %u", data->destroyCount); + mockIoOperationsData_Destroy(©); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_Class) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_Class(ops); + assertTrue(data->classCount == 1, "Wrong metisIoOperations_Class count expected 1 got %u", data->classCount); + mockIoOperationsData_Destroy(&ops); +} + +LONGBOW_TEST_CASE(Global, metisIoOperations_GetConnectionType) +{ + MetisIoOperations *ops = mockIoOperationsData_CreateSimple(1, 2, 3, true, true, true); + MockIoOperationsData *data = metisIoOperations_GetClosure(ops); + + metisIoOperations_GetConnectionType(ops); + assertTrue(data->getConnectionTypeCount == 1, "Wrong getConnectionTypeCount count expected 1 got %u", data->getConnectionTypeCount); + mockIoOperationsData_Destroy(&ops); +} + + + +// =========================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_IoOperations); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_ListenerSet.c b/metis/ccnx/forwarder/metis/io/test/test_metis_ListenerSet.c new file mode 100644 index 00000000..b786d9b3 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_ListenerSet.c @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_ListenerSet.c" +#include <parc/algol/parc_SafeMemory.h> + +#include <LongBow/unit-test.h> + +#include "testrig_MetisListenerOps.c" + + +LONGBOW_TEST_RUNNER(metis_ListenerSet) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_ListenerSet) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_ListenerSet) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisListenerSet_Add_Single); + LONGBOW_RUN_TEST_CASE(Global, metisListenerSet_Add_Unique); + LONGBOW_RUN_TEST_CASE(Global, metisListenerSet_Add_Duplicate); + LONGBOW_RUN_TEST_CASE(Global, metisListenerSet_Create_Destroy); + + + LONGBOW_RUN_TEST_CASE(Global, metisListenerSet_Length); + LONGBOW_RUN_TEST_CASE(Global, metisListenerSet_Get); + LONGBOW_RUN_TEST_CASE(Global, metisListenerSet_Find_InSet); + LONGBOW_RUN_TEST_CASE(Global, metisListenerSet_Find_NotInSet); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +// Adds a single MockListenerData to the listener set. +static MockListenerData * +addSingle(MetisListenerSet *set) +{ + CPIAddress *listenAddress = cpiAddress_CreateFromInterface(44); + MockListenerData *data = mockListenData_Create(1, listenAddress, METIS_ENCAP_ETHER); + MetisListenerOps *listenerOps = mockListener_Create(data); + + bool success = metisListenerSet_Add(set, listenerOps); + assertTrue(success, "Got failure adding one listener to the set"); + assertTrue(parcArrayList_Size(set->listOfListeners) == 1, + "Got wrong list length, got %zu expected %u", + parcArrayList_Size(set->listOfListeners), 1); + + cpiAddress_Destroy(&listenAddress); + return data; +} + +LONGBOW_TEST_CASE(Global, metisListenerSet_Add_Single) +{ + MetisListenerSet *set = metisListenerSet_Create(); + + MockListenerData *data = addSingle(set); + + metisListenerSet_Destroy(&set); + assertTrue(data->destroyCount == 1, + "Wrong destroy count, got %u expected %u", + data->destroyCount, 1); + + mockListenerData_Destroy(&data); +} + +LONGBOW_TEST_CASE(Global, metisListenerSet_Add_Unique) +{ + CPIAddress *listenAddress_A = cpiAddress_CreateFromInterface(44); + MockListenerData *data_A = mockListenData_Create(1, listenAddress_A, METIS_ENCAP_ETHER); + MetisListenerOps *listenerOps_A = mockListener_Create(data_A); + + CPIAddress *listenAddress_B = cpiAddress_CreateFromInterface(55); + MockListenerData *data_B = mockListenData_Create(1, listenAddress_B, METIS_ENCAP_ETHER); + MetisListenerOps *listenerOps_B = mockListener_Create(data_B); + + + MetisListenerSet *set = metisListenerSet_Create(); + bool success_A = metisListenerSet_Add(set, listenerOps_A); + assertTrue(success_A, "Got failure adding listener A to the set"); + + bool success_B = metisListenerSet_Add(set, listenerOps_B); + assertTrue(success_B, "Got failure adding listener B to the set"); + + cpiAddress_Destroy(&listenAddress_A); + cpiAddress_Destroy(&listenAddress_B); + metisListenerSet_Destroy(&set); + + mockListenerData_Destroy(&data_A); + mockListenerData_Destroy(&data_B); +} + +LONGBOW_TEST_CASE(Global, metisListenerSet_Add_Duplicate) +{ + CPIAddress *listenAddress_A = cpiAddress_CreateFromInterface(44); + MockListenerData *data_A = mockListenData_Create(1, listenAddress_A, METIS_ENCAP_ETHER); + MetisListenerOps *listenerOps_A = mockListener_Create(data_A); + + CPIAddress *listenAddress_B = cpiAddress_CreateFromInterface(44); + MockListenerData *data_B = mockListenData_Create(1, listenAddress_B, METIS_ENCAP_ETHER); + MetisListenerOps *listenerOps_B = mockListener_Create(data_B); + + + MetisListenerSet *set = metisListenerSet_Create(); + bool success_A = metisListenerSet_Add(set, listenerOps_A); + assertTrue(success_A, "Got failure adding listener A to the set"); + + bool success_B = metisListenerSet_Add(set, listenerOps_B); + assertFalse(success_B, "Got success adding listener B to the set, duplicate should have failed"); + + cpiAddress_Destroy(&listenAddress_A); + cpiAddress_Destroy(&listenAddress_B); + metisListenerSet_Destroy(&set); + + mockListener_Destroy(&listenerOps_B); + mockListenerData_Destroy(&data_A); + mockListenerData_Destroy(&data_B); +} + + +LONGBOW_TEST_CASE(Global, metisListenerSet_Create_Destroy) +{ + MetisListenerSet *set = metisListenerSet_Create(); + assertNotNull(set, "Got null from Create"); + + metisListenerSet_Destroy(&set); + assertNull(set, "Destroy did not null parameter"); +} + +LONGBOW_TEST_CASE(Global, metisListenerSet_Length) +{ + MetisListenerSet *set = metisListenerSet_Create(); + MockListenerData *data = addSingle(set); + + size_t length = metisListenerSet_Length(set); + + metisListenerSet_Destroy(&set); + mockListenerData_Destroy(&data); + + assertTrue(length == 1, + "Wrong length, got %zu expected %u", + length, 1); +} + +LONGBOW_TEST_CASE(Global, metisListenerSet_Get) +{ + MetisListenerSet *set = metisListenerSet_Create(); + MockListenerData *data = addSingle(set); + + MetisListenerOps *ops = metisListenerSet_Get(set, 0); + + assertNotNull(ops, "Did not fetch the listener ops"); + + metisListenerSet_Destroy(&set); + mockListenerData_Destroy(&data); +} + +LONGBOW_TEST_CASE(Global, metisListenerSet_Find_InSet) +{ + MetisListenerSet *set = metisListenerSet_Create(); + MockListenerData *data = addSingle(set); + + MetisListenerOps *ops = metisListenerSet_Find(set, data->encapType, data->listenAddress); + assertNotNull(ops, "Did not retrieve the listener that is in the set"); + + metisListenerSet_Destroy(&set); + mockListenerData_Destroy(&data); +} + +LONGBOW_TEST_CASE(Global, metisListenerSet_Find_NotInSet) +{ + MetisListenerSet *set = metisListenerSet_Create(); + MockListenerData *data = addSingle(set); + + // use wrong encap type + MetisListenerOps *ops = metisListenerSet_Find(set, data->encapType + 1, data->listenAddress); + assertNull(ops, "Should not have found anything with wrong encap type"); + + metisListenerSet_Destroy(&set); + mockListenerData_Destroy(&data); +} + + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisListenerSet_DestroyListenerOps); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, metisListenerSet_DestroyListenerOps) +{ + testUnimplemented(""); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_ListenerSet); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_LocalListener.c b/metis/ccnx/forwarder/metis/io/test/test_metis_LocalListener.c new file mode 100644 index 00000000..2852d253 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_LocalListener.c @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_LocalListener.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +LONGBOW_TEST_RUNNER(metis_LocalListener) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_LocalListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_LocalListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisListenerLocal_Create_Destroy); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisListenerLocal_Create_Destroy) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisListenerLocal_Listen); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, metisListenerLocal_Listen) +{ + testUnimplemented("This test is unimplemented"); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_LocalListener); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_StreamConnection.c b/metis/ccnx/forwarder/metis/io/test/test_metis_StreamConnection.c new file mode 100644 index 00000000..5ae50bcd --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_StreamConnection.c @@ -0,0 +1,767 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_StreamConnection.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <parc/logging/parc_LogReporterTextStdout.h> + +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> +#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h> + +// inet_pton +#include <arpa/inet.h> + +#include <fcntl.h> + +#include <stdio.h> + +#ifndef INPORT_ANY +#define INPORT_ANY 0 +#endif + +// we hand-code some packets in the unit tests +typedef struct __attribute__ ((__packed__)) metis_tlv_fixed_header { + uint8_t version; + uint8_t packetType; + uint16_t payloadLength; + uint16_t reserved; + uint16_t headerLength; +} _MetisTlvFixedHeaderV0; + + +LONGBOW_TEST_RUNNER(metis_StreamConnection) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_StreamConnection) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_StreamConnection) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ================================================================================ +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisStreamConnection_Create); + LONGBOW_RUN_TEST_CASE(Global, metisStreamConnection_OpenConnection); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisStreamConnection_Create) +{ + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fd, pair, false); + + ops->destroy(&ops); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + + metisForwarder_Destroy(&metis); + close(fd); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); + assertTrue(parcSafeMemory_Outstanding() == 0, "Got memory imbalance: %u", parcSafeMemory_Outstanding()); +} + +static int +listenToInet(struct sockaddr_in *server) +{ + int fd = socket(PF_INET, SOCK_STREAM, 0); + assertFalse(fd < 0, "error on bind: (%d) %s", errno, strerror(errno)); + + // Set non-blocking flag + int flags = fcntl(fd, F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)\n", errno); + int failure = fcntl(fd, F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)\n", errno); + + failure = bind(fd, (struct sockaddr *) server, sizeof(struct sockaddr_in)); + assertFalse(failure, "error on bind: (%d) %s", errno, strerror(errno)); + + failure = listen(fd, 16); + assertFalse(failure, "error on listen: (%d) %s", errno, strerror(errno)); + + return fd; +} + +LONGBOW_TEST_CASE(Global, metisStreamConnection_OpenConnection) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + + struct sockaddr_in serverAddr; + memset(&serverAddr, 0, sizeof(serverAddr)); + serverAddr.sin_family = PF_INET; + serverAddr.sin_port = INPORT_ANY; + inet_pton(AF_INET, "127.0.0.1", &(serverAddr.sin_addr)); + + int serverSocket = listenToInet(&serverAddr); + socklen_t x = sizeof(serverAddr); + int failure = getsockname(serverSocket, (struct sockaddr *) &serverAddr, &x); + assertFalse(failure, "error on getsockname: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in localAddr; + memset(&localAddr, 0, sizeof(localAddr)); + localAddr.sin_family = PF_INET; + localAddr.sin_addr.s_addr = INADDR_ANY; + localAddr.sin_port = INPORT_ANY; + + CPIAddress *local = cpiAddress_CreateFromInet(&localAddr); + + // change from 0.0.0.0 to 127.0.0.1 + inet_pton(AF_INET, "127.0.0.1", &(serverAddr.sin_addr)); + + CPIAddress *remote = cpiAddress_CreateFromInet(&serverAddr); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); + + MetisIoOperations *ops = metisStreamConnection_OpenConnection(metis, pair, false); + assertNotNull(ops, "Got null ops from metisStreamConnection_OpenConnection"); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); +} + +// ======================================================================= + +// ================================================================================== + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, conn_eventcb_Connected); + LONGBOW_RUN_TEST_CASE(Local, conn_eventcb_EOF); + LONGBOW_RUN_TEST_CASE(Local, conn_eventcb_ERROR); + + LONGBOW_RUN_TEST_CASE(Local, conn_readcb); + LONGBOW_RUN_TEST_CASE(Local, metisStreamConnection_Equals); + LONGBOW_RUN_TEST_CASE(Local, metisStreamConnection_GetAddress); + LONGBOW_RUN_TEST_CASE(Local, metisStreamConnection_GetAddressPair); + LONGBOW_RUN_TEST_CASE(Local, metisStreamConnection_GetConnectionId); + LONGBOW_RUN_TEST_CASE(Local, metisStreamConnection_HashCode); + LONGBOW_RUN_TEST_CASE(Local, metisStreamConnection_IsUp); + LONGBOW_RUN_TEST_CASE(Local, metisStreamConnection_Send); + LONGBOW_RUN_TEST_CASE(Local, metisStreamConnection_GetConnectionType); + LONGBOW_RUN_TEST_CASE(Local, printConnection); + LONGBOW_RUN_TEST_CASE(Local, readMessage); + LONGBOW_RUN_TEST_CASE(Local, setConnectionState); + LONGBOW_RUN_TEST_CASE(Local, single_read_ZeroNextMessageLength); + LONGBOW_RUN_TEST_CASE(Local, single_read_PartialRead); + LONGBOW_RUN_TEST_CASE(Local, single_read_FullRead); + LONGBOW_RUN_TEST_CASE(Local, startNewMessage); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, conn_eventcb_Connected) +{ + int fds[2]; + int failure = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds); + assertFalse(failure, "Error socketpair: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fds[0], pair, false); + _MetisStreamState *stream = (_MetisStreamState *) metisIoOperations_GetClosure(ops); + + stream->isUp = false; + + // ---- the actual test + _conn_eventcb(stream->bufferEventVector, PARCEventQueueEventType_Connected, ops); + assertTrue(stream->isUp, "PARCEventQueueEventType_Connected did not trigger stream to up state"); + // ---- + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + close(fds[0]); + close(fds[1]); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); +} + +LONGBOW_TEST_CASE(Local, conn_eventcb_EOF) +{ + int fds[2]; + int failure = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds); + assertFalse(failure, "Error socketpair: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fds[0], pair, false); + _MetisStreamState *stream = (_MetisStreamState *) metisIoOperations_GetClosure(ops); + + stream->isUp = true; + + // ---- the actual test + _conn_eventcb(stream->bufferEventVector, PARCEventQueueEventType_EOF, ops); + assertFalse(stream->isUp, "PARCEventQueueEventType_EOF did not trigger stream to down state"); + // ---- + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + close(fds[0]); + close(fds[1]); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); +} + +LONGBOW_TEST_CASE(Local, conn_eventcb_ERROR) +{ + int fds[2]; + int failure = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds); + assertFalse(failure, "Error socketpair: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fds[0], pair, false); + _MetisStreamState *stream = (_MetisStreamState *) metisIoOperations_GetClosure(ops); + + stream->isUp = true; + + // ---- the actual test + _conn_eventcb(stream->bufferEventVector, PARCEventQueueEventType_Error, ops); + assertFalse(stream->isUp, "PARCEventQueueEventType_Error did not trigger stream to down state"); + // ---- + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + close(fds[0]); + close(fds[1]); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); +} + +LONGBOW_TEST_CASE(Local, conn_readcb) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, metisStreamConnection_Equals) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, metisStreamConnection_GetAddress) +{ + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fd, pair, false); + + const CPIAddress *test_addr = ops->getRemoteAddress(ops); + + assertTrue(cpiAddress_Equals(remote, test_addr), "ops->getAddress incorrect"); + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); + + close(fd); +} + +LONGBOW_TEST_CASE(Local, metisStreamConnection_GetAddressPair) +{ + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fd, pair, false); + + const MetisAddressPair *test_pair = ops->getAddressPair(ops); + + assertTrue(metisAddressPair_Equals(pair, test_pair), "ops->getRemoteAddress incorrect"); + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); + + close(fd); +} + +LONGBOW_TEST_CASE(Local, metisStreamConnection_GetConnectionId) +{ + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + unsigned truth_connid = metisForwarder_GetNextConnectionId(metis) + 1; + + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fd, pair, false); + + assertTrue(ops->getConnectionId(ops) == truth_connid, "Got wrong connection id, expected %u got %u", truth_connid, ops->getConnectionId(ops)); + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); + + close(fd); +} + +LONGBOW_TEST_CASE(Local, metisStreamConnection_HashCode) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, metisStreamConnection_IsUp) +{ + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fd, pair, false); + + assertTrue(ops->isUp(ops), "isUp incorrect, expected true, got false"); + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); + close(fd); +} + +LONGBOW_TEST_CASE(Local, metisStreamConnection_Send) +{ + // StreamConnection_Create needs a socket and address to represent the peer + // we use a socket pair so we can actaully read from it and verify what is sent. + + int fds[2]; + int failure = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds); + assertFalse(failure, "Error socketpair: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fds[0], pair, false); + + // ---------- + // Create a fake message. Send does not care what the message is, it just writes it out. + // We include a real header, but it is not needed. + + char message_str[] = "\x00Once upon a jiffie, in a stack far away, a dangling pointer found its way to the top of the heap."; + _MetisTlvFixedHeaderV0 *hdr = (_MetisTlvFixedHeaderV0 *) message_str; + hdr->payloadLength = htons(92); + hdr->headerLength = htons(0); + + MetisMessage *sendmessage = metisMessage_CreateFromArray((uint8_t *) message_str, sizeof(message_str), 1, 2, metisForwarder_GetLogger(metis)); + + // ---------- + // actually send it + ops->send(ops, NULL, sendmessage); + metisMessage_Release(&sendmessage); + + // ---------- + // turn the handleto crank + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(metis), &((struct timeval) { 0, 10000 })); + + // ---------- + // Now read the result from our end of the socket pair. + + uint8_t read_buffer[1024]; + + // read it and verify + ssize_t read_length = read(fds[1], read_buffer, 1024); + assertTrue(read_length == sizeof(message_str), + "Incorrect read length, expected %zu got %zd: (%d) %s", + sizeof(message_str), read_length, errno, strerror(errno)); + + assertTrue(memcmp(read_buffer, message_str, sizeof(message_str)) == 0, "read_buffer does not match message_str"); + + // ---------- + // hurray, no messages where harmed in this experiment + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + close(fds[0]); + close(fds[1]); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); +} + +LONGBOW_TEST_CASE(Local, metisStreamConnection_GetConnectionType) +{ + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr_local; + addr_local.sin_addr.s_addr = htonl(0x01020304); + addr_local.sin_family = AF_INET; + addr_local.sin_port = htons(56); + + struct sockaddr_in addr_remote; + addr_remote.sin_addr.s_addr = htonl(0x0708090A); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(12); + + CPIAddress *local = cpiAddress_CreateFromInet(&addr_local); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(local, remote); + + MetisForwarder *metis = metisForwarder_Create(NULL); + MetisIoOperations *ops = metisStreamConnection_AcceptConnection(metis, fd, pair, false); + + CPIConnectionType connType = _metisStreamConnection_GetConnectionType(ops); + assertTrue(connType == cpiConnection_TCP, "Wrong connection type expected %d got %d", cpiConnection_TCP, connType); + + ops->destroy(&ops); + metisForwarder_Destroy(&metis); + cpiAddress_Destroy(&local); + cpiAddress_Destroy(&remote); + close(fd); +} + +LONGBOW_TEST_CASE(Local, printConnection) +{ + testUnimplemented("This test is unimplemented"); +} + +LONGBOW_TEST_CASE(Local, readMessage) +{ + char message_str[] = "\x00Once upon a jiffie, in a stack far away, a dangling pointer found its way to the top of the heap."; + _MetisTlvFixedHeaderV0 *hdr = (_MetisTlvFixedHeaderV0 *) message_str; + hdr->payloadLength = htons(92); + hdr->headerLength = htons(0); + + PARCEventBuffer *buff = parcEventBuffer_Create(); + parcEventBuffer_Append(buff, message_str, sizeof(message_str)); + + _MetisStreamState *stream = parcMemory_AllocateAndClear(sizeof(_MetisStreamState)); + assertNotNull(stream, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisStreamState)); + stream->nextMessageLength = parcEventBuffer_GetLength(buff); + stream->id = 77; + + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + stream->logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + + MetisMessage *message = _readMessage(stream, 444, buff); + + assertNotNull(message, "Got null message from readMessage"); + assertTrue(parcEventBuffer_GetLength(buff) == 0, "Did not drain input buffer, expected 0 got %zu", parcEventBuffer_GetLength(buff)); + //assertTrue(metisMessage_Length(message) == sizeof(message_str), + //"Message length wrong, expected %zu got %zu", + //sizeof(message_str), + //metisMessage_Length(message)); + + metisMessage_Release(&message); + parcEventBuffer_Destroy(&buff); + metisLogger_Release(&stream->logger); + parcMemory_Deallocate((void **) &stream); +} + +LONGBOW_TEST_CASE(Local, setConnectionState) +{ + testUnimplemented("This test is unimplemented"); +} + +/** + * Call like the beignning of a new packet, with stream->nextMessageLength set to 0 + */ +LONGBOW_TEST_CASE(Local, single_read_ZeroNextMessageLength) +{ + PARCEventBuffer *buff = parcEventBuffer_Create(); + + // do it like a short read, only 12 bytes + parcEventBuffer_Append(buff, metisTestDataV0_EncodedInterest, 12); + + MetisForwarder *metis = metisForwarder_Create(NULL); + _MetisStreamState *stream = parcMemory_AllocateAndClear(sizeof(_MetisStreamState)); + assertNotNull(stream, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisStreamState)); + stream->metis = metis; + stream->nextMessageLength = 0; + stream->id = 77; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + stream->logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = _single_read(buff, stream); + + assertNull(message, "message should be null, its a short read"); + assertTrue(parcEventBuffer_GetLength(buff) == 12, "Should not have drained buffer, expected %d got %zu", 12, parcEventBuffer_GetLength(buff)); + assertTrue(stream->nextMessageLength == sizeof(metisTestDataV0_EncodedInterest), + "NextMessageLength not set correctly, expected %zu got %zu", + sizeof(metisTestDataV0_EncodedInterest), + stream->nextMessageLength); + + parcEventBuffer_Destroy(&buff); + metisLogger_Release(&stream->logger); + parcMemory_Deallocate((void **) &stream); + metisForwarder_Destroy(&metis); +} + +/** + * Call with stream->nextMessageLength set correctly, but not enough bytes in the buffer + */ +LONGBOW_TEST_CASE(Local, single_read_PartialRead) +{ + PARCEventBuffer *buff = parcEventBuffer_Create(); + + // do it like a short read, only 12 bytes + parcEventBuffer_Append(buff, metisTestDataV0_EncodedInterest, 12); + + MetisForwarder *metis = metisForwarder_Create(NULL); + _MetisStreamState *stream = parcMemory_AllocateAndClear(sizeof(_MetisStreamState)); + assertNotNull(stream, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisStreamState)); + stream->metis = metis; + stream->nextMessageLength = sizeof(metisTestDataV0_EncodedInterest); + stream->id = 77; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + stream->logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = _single_read(buff, stream); + + assertNull(message, "message should be null, its a short read"); + assertTrue(parcEventBuffer_GetLength(buff) == 12, "Should not have drained buffer, expected %d got %zu", 12, parcEventBuffer_GetLength(buff)); + assertTrue(stream->nextMessageLength == sizeof(metisTestDataV0_EncodedInterest), + "NextMessageLength not set correctly, expected %zu got %zu", + sizeof(metisTestDataV0_EncodedInterest), + stream->nextMessageLength); + + parcEventBuffer_Destroy(&buff); + metisLogger_Release(&stream->logger); + parcMemory_Deallocate((void **) &stream); + metisForwarder_Destroy(&metis); +} + +/** + * Call with enough bytes in the buffer to read the whole message + */ +LONGBOW_TEST_CASE(Local, single_read_FullRead) +{ + PARCEventBuffer *buff = parcEventBuffer_Create(); + + // do it like a full read + parcEventBuffer_Append(buff, metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest)); + + MetisForwarder *metis = metisForwarder_Create(NULL); + _MetisStreamState *stream = parcMemory_AllocateAndClear(sizeof(_MetisStreamState)); + assertNotNull(stream, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisStreamState)); + stream->metis = metis; + stream->nextMessageLength = sizeof(metisTestDataV0_EncodedInterest); + stream->id = 77; + PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); + stream->logger = metisLogger_Create(reporter, parcClock_Wallclock()); + parcLogReporter_Release(&reporter); + MetisMessage *message = _single_read(buff, stream); + + assertNotNull(message, "message should not be null, its a short read"); + assertTrue(parcEventBuffer_GetLength(buff) == 0, "Should have drained buffer, expected %d got %zu", 0, parcEventBuffer_GetLength(buff)); + + // should reset the next message length after reading a whole packet + assertTrue(stream->nextMessageLength == 0, + "NextMessageLength not set correctly, expected %u got %zu", + 0, + stream->nextMessageLength); + + metisMessage_Release(&message); + parcEventBuffer_Destroy(&buff); + metisLogger_Release(&stream->logger); + parcMemory_Deallocate((void **) &stream); + metisForwarder_Destroy(&metis); +} + +LONGBOW_TEST_CASE(Local, startNewMessage) +{ + _MetisStreamState *stream = parcMemory_AllocateAndClear(sizeof(_MetisStreamState)); + assertNotNull(stream, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisStreamState)); + + // add data to the buffer to fake out having read from the network + PARCEventBuffer *buff = parcEventBuffer_Create(); + uint8_t *truth_message = parcMemory_Allocate(100); + assertNotNull(truth_message, "parcMemory_Allocate(%u) returned NULL", 100); + + _MetisTlvFixedHeaderV0 *hdr = (_MetisTlvFixedHeaderV0 *) truth_message; + hdr->version = 0; + hdr->payloadLength = htons(92); + hdr->headerLength = htons(0); + + parcEventBuffer_Append(buff, truth_message, 100); + + stream->nextMessageLength = 0; + + _startNewMessage(stream, buff, 100); + + assertTrue(stream->nextMessageLength == 100, "nextMessageLength wrong, expected %d got %zu", 100, stream->nextMessageLength); + + parcEventBuffer_Destroy(&buff); + parcMemory_Deallocate((void **) &stream); + parcMemory_Deallocate((void **) &truth_message); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_StreamConnection); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_TcpListener.c b/metis/ccnx/forwarder/metis/io/test/test_metis_TcpListener.c new file mode 100644 index 00000000..87e51ae2 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_TcpListener.c @@ -0,0 +1,364 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * hard-coded in port 49009 on localhost + */ + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_TcpListener.c" +#include <LongBow/unit-test.h> +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> +#include <parc/algol/parc_SafeMemory.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h> + +#include <parc/algol/parc_Network.h> + +// for inet_pton +#include <arpa/inet.h> + +#include <signal.h> + +struct test_set { + CPIAddress *listenAddress; + MetisForwarder *metis; + MetisListenerOps *ops; +} TestSet; + +static void +setupInetListener() +{ + struct sockaddr_in addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(49009); + inet_pton(AF_INET, "127.0.0.1", &(addr.sin_addr)); + + TestSet.metis = metisForwarder_Create(NULL); + TestSet.ops = metisTcpListener_CreateInet(TestSet.metis, addr); + TestSet.listenAddress = cpiAddress_CreateFromInet(&addr); + + // crank the event scheduler once + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) {0, 10000})); +} + +static void +setupInet6Listener() +{ + struct sockaddr_in6 addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(49009); + + // "::1" is the ipv6 loopback address + inet_pton(AF_INET6, "::1", &(addr.sin6_addr)); + + TestSet.metis = metisForwarder_Create(NULL); + TestSet.ops = metisTcpListener_CreateInet6(TestSet.metis, addr); + TestSet.listenAddress = cpiAddress_CreateFromInet6(&addr); + + // crank the event scheduler once + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) {0, 10000})); +} + +static void +teardownListener() +{ + cpiAddress_Destroy(&TestSet.listenAddress); + TestSet.ops->destroy(&TestSet.ops); + metisForwarder_Destroy(&TestSet.metis); +} + +struct sigaction save_sigchld; +struct sigaction save_sigpipe; + +static void +blockSigChild() +{ + struct sigaction ignore_action; + ignore_action.sa_handler = SIG_IGN; + sigemptyset(&ignore_action.sa_mask); + ignore_action.sa_flags = 0; + + sigaction(SIGCHLD, NULL, &save_sigchld); + sigaction(SIGPIPE, NULL, &save_sigpipe); + + sigaction(SIGCHLD, &ignore_action, NULL); + sigaction(SIGPIPE, &ignore_action, NULL); +} + +static void +unblockSigChild() +{ + sigaction(SIGCHLD, &save_sigchld, NULL); + sigaction(SIGPIPE, &save_sigpipe, NULL); +} + +LONGBOW_TEST_RUNNER(metis_TcpListener) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global_Inet); + LONGBOW_RUN_TEST_FIXTURE(Global_Inet6); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_TcpListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_TcpListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// =========================================================================== + +LONGBOW_TEST_FIXTURE(Global_Inet) +{ + LONGBOW_RUN_TEST_CASE(Global_Inet, metisListenerTcp_CreateInet); + LONGBOW_RUN_TEST_CASE(Global_Inet, metisListenerTcp_Connect); + LONGBOW_RUN_TEST_CASE(Global_Inet, metisListenerTcp_SendPacket); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global_Inet) +{ + setupInetListener(); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global_Inet) +{ + teardownListener(); + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global_Inet, metisListenerTcp_CreateInet) +{ + // now verify that we are listening + // tcp4 0 0 127.0.0.1.49009 *.* LISTEN + + blockSigChild(); + FILE *fp = popen("netstat -an -p tcp", "r"); + assertNotNull(fp, "Got null opening netstat for reading"); + + char str[1035]; + bool found = false; + while (fgets(str, sizeof(str) - 1, fp) != NULL) { + if (strstr(str, "127.0.0.1.49009") != NULL) { + found = true; + break; + } + if (strstr(str, "127.0.0.1:49009") != NULL) { + found = true; + break; + } + } + + pclose(fp); + unblockSigChild(); + + if (!found) { + int ret = system("netstat -an -p tcp"); + assertTrue(ret > -1, "Error on system call"); + } + + assertTrue(found, "Did not find 127.0.0.1.49009 in netstat output"); +} + +LONGBOW_TEST_CASE(Global_Inet, metisListenerTcp_Connect) +{ + int fd = socket(PF_INET, SOCK_STREAM, 0); + assertFalse(fd < 0, "Error on socket: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in serverAddress; + cpiAddress_GetInet(TestSet.listenAddress, &serverAddress); + + int failure = connect(fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)); + assertFalse(failure, "Error on connect: (%d) %s", errno, strerror(errno)); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) {0, 10000})); + + struct sockaddr_in connectAddress; + socklen_t connectAddressLength = sizeof(connectAddress); + failure = getsockname(fd, (struct sockaddr *) &connectAddress, &connectAddressLength); + assertFalse(failure, "Error on getsockname: (%d) %s", errno, strerror(errno)); + assertTrue(connectAddressLength == sizeof(struct sockaddr_in), + "connectAddressLength wrong size, expected %zu got %u", + sizeof(struct sockaddr_in), connectAddressLength); + + // make sure its in the connection table + MetisConnectionTable *table = metisForwarder_GetConnectionTable(TestSet.metis); + CPIAddress *remote = cpiAddress_CreateFromInet(&connectAddress); + MetisAddressPair *pair = metisAddressPair_Create(TestSet.listenAddress, remote); + const MetisConnection *conn = metisConnectionTable_FindByAddressPair(table, pair); + assertNotNull(conn, "Did not find connection in connection table"); + + cpiAddress_Destroy(&remote); + metisAddressPair_Release(&pair); + + close(fd); +} + +LONGBOW_TEST_CASE(Global_Inet, metisListenerTcp_SendPacket) +{ + int fd = socket(PF_INET, SOCK_STREAM, 0); + assertFalse(fd < 0, "Error on socket: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in serverAddress; + cpiAddress_GetInet(TestSet.listenAddress, &serverAddress); + + int failure = connect(fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)); + assertFalse(failure, "Error on connect: (%d) %s", errno, strerror(errno)); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) {0, 10000})); + + ssize_t write_length = write(fd, metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName)); + assertFalse(write_length < 0, "Error on write: (%d) %s", errno, strerror(errno)); + assertTrue(write_length == sizeof(metisTestDataV0_InterestWithName), "Got partial write, expected %zu got %zd", sizeof(metisTestDataV0_InterestWithName), write_length); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) {0, 10000})); + + close(fd); +} + +// =========================================================================== + +LONGBOW_TEST_FIXTURE(Global_Inet6) +{ + LONGBOW_RUN_TEST_CASE(Global_Inet6, metisListenerTcp_CreateInet6); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global_Inet6) +{ + setupInet6Listener(); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global_Inet6) +{ + teardownListener(); + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global_Inet6, metisListenerTcp_CreateInet6) +{ + // now verify that we are listening + // tcp6 0 0 ::1.49009 *.* LISTEN + + blockSigChild(); + FILE *fp = popen("netstat -an -p tcp", "r"); + assertNotNull(fp, "Got null opening netstat for reading"); + + char str[1035]; + bool found = false; + while (fgets(str, sizeof(str) - 1, fp) != NULL) { + if (strstr(str, "::1.49009") != NULL) { + found = true; + break; + } + if (strstr(str, "::1:49009") != NULL) { + found = true; + break; + } + } + + pclose(fp); + unblockSigChild(); + + if (!found) { + int ret = system("netstat -an -p tcp"); + assertTrue(ret > -1, "Error on system call"); + } + + assertTrue(found, "Did not find ::1.49009 in netstat output"); +} + +// =========================================================================== + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, metisListenerTcp_Listen); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +/** + * Create a TCP INET listener then connect to it. + */ +LONGBOW_TEST_CASE(Local, metisListenerTcp_Listen) +{ + setupInetListener(); + + struct sockaddr_in addr_remote; + + memset(&addr_remote, 0, sizeof(addr_remote)); + addr_remote.sin_family = AF_INET; + addr_remote.sin_port = htons(49010); + inet_pton(AF_INET, "127.0.0.1", &(addr_remote.sin_addr)); + + _MetisTcpListener *tcp = (_MetisTcpListener *) TestSet.ops->context; + + int fds[2]; + int failure = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds); + assertFalse(failure, "Failed with socketpair: (%d) %s", errno, strerror(errno)); + + _metisTcpListener_Listen(fds[0], (struct sockaddr *) &addr_remote, sizeof(addr_remote), tcp); + + // now verify the connection is in the connection table + MetisConnectionTable *table = metisForwarder_GetConnectionTable(TestSet.metis); + CPIAddress *remote = cpiAddress_CreateFromInet(&addr_remote); + MetisAddressPair *pair = metisAddressPair_Create(tcp->localAddress, remote); + const MetisConnection *conn = metisConnectionTable_FindByAddressPair(table, pair); + assertNotNull(conn, "Did not find connection in connection table"); + + cpiAddress_Destroy(&remote); + metisAddressPair_Release(&pair); + teardownListener(); +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_TcpListener); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_TcpTunnel.c b/metis/ccnx/forwarder/metis/io/test/test_metis_TcpTunnel.c new file mode 100644 index 00000000..2d86fb17 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_TcpTunnel.c @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_TcpTunnel.c" +#include <LongBow/unit-test.h> + +#include <parc/algol/parc_SafeMemory.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h> + +// so we can see packet events +#include "../../processor/test/testrig_MockTap.h" + +// inet_pton +#include <arpa/inet.h> + +#include <fcntl.h> + +#ifndef INPORT_ANY +#define INPORT_ANY 0 +#endif + +LONGBOW_TEST_RUNNER(metis_TcpTunnel) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_TcpTunnel) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_TcpTunnel) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +typedef struct test_data { + MetisForwarder *metis; + MetisDispatcher *dispatcher; + + int serverSocket; + struct sockaddr_in serverAddr; + struct sockaddr_in localAddr; + + CPIAddress *localCpiAddress; + CPIAddress *remoteCpiAddress; + + MetisIoOperations *tunnelOps; +} TestData; + +static void +listenToInet(TestData *data) +{ + int fd = socket(PF_INET, SOCK_STREAM, 0); + assertFalse(fd < 0, "error on bind: (%d) %s", errno, strerror(errno)); + + // Set non-blocking flag + int flags = fcntl(fd, F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)\n", errno); + int failure = fcntl(fd, F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)\n", errno); + + failure = bind(fd, (struct sockaddr *) &data->serverAddr, sizeof(struct sockaddr_in)); + assertFalse(failure, "error on bind: (%d) %s", errno, strerror(errno)); + + failure = listen(fd, 16); + assertFalse(failure, "error on listen: (%d) %s", errno, strerror(errno)); + + data->serverSocket = fd; + socklen_t x = sizeof(data->serverAddr); + getsockname(fd, (struct sockaddr *) &data->serverAddr, &x); +} + + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisTcpTunnel_Create); + LONGBOW_RUN_TEST_CASE(Global, metisTcpTunnel_Create_ConnectionStartsDown); + LONGBOW_RUN_TEST_CASE(Global, metisTcpTunnel_Create_UpStateAfterAccept); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + memset(&testTap, 0, sizeof(testTap)); + + TestData *data = malloc(sizeof(TestData)); + memset(data, 0, sizeof(TestData)); + + data->metis = metisForwarder_Create(NULL); + data->serverAddr.sin_family = PF_INET; + data->serverAddr.sin_port = INPORT_ANY; + inet_pton(AF_INET, "127.0.0.1", &(data->serverAddr.sin_addr)); + + data->localAddr.sin_family = PF_INET; + data->localAddr.sin_addr.s_addr = INADDR_ANY; + data->localAddr.sin_port = INPORT_ANY; + + listenToInet(data); + + data->localCpiAddress = cpiAddress_CreateFromInet(&data->localAddr); + data->remoteCpiAddress = cpiAddress_CreateFromInet(&data->serverAddr); + + data->dispatcher = metisForwarder_GetDispatcher(data->metis); + + longBowTestCase_SetClipBoardData(testCase, data); + + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + cpiAddress_Destroy(&data->localCpiAddress); + cpiAddress_Destroy(&data->remoteCpiAddress); + + close(data->serverSocket); + metisForwarder_Destroy(&data->metis); + free(data); + + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisTcpTunnel_Create) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + data->tunnelOps = metisTcpTunnel_Create(data->metis, data->localCpiAddress, data->remoteCpiAddress); + assertNotNull(data->tunnelOps, "Got null IO operations for the tunnel"); + data->tunnelOps->destroy(&data->tunnelOps); +} + + +LONGBOW_TEST_CASE(Global, metisTcpTunnel_Create_ConnectionStartsDown) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + data->tunnelOps = metisTcpTunnel_Create(data->metis, data->localCpiAddress, data->remoteCpiAddress); + assertFalse(data->tunnelOps->isUp(data->tunnelOps), "Connection is not down on start"); + data->tunnelOps->destroy(&data->tunnelOps); +} + +LONGBOW_TEST_CASE(Global, metisTcpTunnel_Create_UpStateAfterAccept) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + data->tunnelOps = metisTcpTunnel_Create(data->metis, data->localCpiAddress, data->remoteCpiAddress); + + // run for a milli second + metisDispatcher_RunDuration(data->dispatcher, &((struct timeval) { 0, 1000 })); + + // we should be able to accept + struct sockaddr_in clientAddr; + socklen_t clientAddrLength = sizeof(clientAddr); + + int clientSocket = accept(data->serverSocket, (struct sockaddr *) &clientAddr, &clientAddrLength); + assertFalse(clientSocket < 0, "error on accept: (%d) %s", errno, strerror(errno)); + + // run for a milli second + metisDispatcher_RunDuration(data->dispatcher, &((struct timeval) { 0, 1000 })); + + assertTrue(data->tunnelOps->isUp(data->tunnelOps), "Connection is not up after accept"); + data->tunnelOps->destroy(&data->tunnelOps); +} + + +LONGBOW_TEST_FIXTURE(Local) +{ +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_TcpTunnel); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_UdpConnection.c b/metis/ccnx/forwarder/metis/io/test/test_metis_UdpConnection.c new file mode 100644 index 00000000..db1f9c97 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_UdpConnection.c @@ -0,0 +1,535 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../metis_UdpConnection.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +#include <ccnx/forwarder/metis/io/metis_UdpListener.h> +#include <ccnx/forwarder/metis/config/metis_Configuration.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h> + +// for inet_pton +#include <arpa/inet.h> + +#define ALICE_PORT 49018 +#define BOB_PORT 49019 + +// ---- Used to monitor Missive messages so we know when a connection is up +typedef struct test_notifier_data { + MetisMissiveType type; + unsigned connectionid; +} TestNotifierData; + +static void +testNotifier(MetisMessengerRecipient *recipient, MetisMissive *missive) +{ + struct test_notifier_data *data = metisMessengerRecipient_GetRecipientContext(recipient); + data->type = metisMissive_GetType(missive); + data->connectionid = metisMissive_GetConnectionId(missive); + metisMissive_Release(&missive); +} + +// ---- + +typedef struct test_tap_data { + unsigned onReceiveCount; + MetisMessage *message; +} TestTapData; + +static bool +testTap_IsTapOnReceive(const MetisTap *tap) +{ + return true; +} + +static void +testTap_TapOnReceive(MetisTap *tap, const MetisMessage *message) +{ + TestTapData *mytap = (TestTapData *) tap->context; + mytap->onReceiveCount++; + if (mytap->message) { + metisMessage_Release(&mytap->message); + } + + mytap->message = metisMessage_Acquire(message); +} + +static MetisTap testTapTemplate = { + .context = NULL, + .isTapOnReceive = &testTap_IsTapOnReceive, + .isTapOnSend = NULL, + .isTapOnDrop = NULL, + .tapOnReceive = &testTap_TapOnReceive, + .tapOnSend = NULL, + .tapOnDrop = NULL +}; + +// --- Used to inspect packets received + + +typedef struct test_data { + int remoteSocket; + +#define ALICE 0 +#define BOB 1 + + MetisForwarder *metis[2]; + MetisListenerOps *listener[2]; + MetisMessengerRecipient *recipient[2]; + TestNotifierData notifierData[2]; + MetisTap taps[2]; + TestTapData tapData[2]; +} TestData; + + + +static void +_crankHandle(TestData *data) +{ + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis[ALICE]), &((struct timeval) {0, 10000})); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis[BOB]), &((struct timeval) {0, 10000})); +} + +static void +_setup(TestData *data, int side, uint16_t port) +{ + data->metis[side] = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(data->metis[side]), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + struct sockaddr_in addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + inet_pton(AF_INET, "127.0.0.1", &(addr.sin_addr)); + + data->listener[side] = metisUdpListener_CreateInet(data->metis[side], addr); + + // snoop events + data->recipient[side] = metisMessengerRecipient_Create(&data->notifierData[side], testNotifier); + MetisMessenger *messenger = metisForwarder_GetMessenger(data->metis[side]); + metisMessenger_Register(messenger, data->recipient[side]); + + // snoop packets + memcpy(&data->taps[side], &testTapTemplate, sizeof(testTapTemplate)); + data->taps[side].context = &data->tapData[side]; + metisForwarder_AddTap(data->metis[side], &data->taps[side]); + + // save in Metis + metisListenerSet_Add(metisForwarder_GetListenerSet(data->metis[side]), data->listener[side]); +} + +/* + * Create a UDP socket pair + */ +static void +_commonSetup(const LongBowTestCase *testCase) +{ + TestData *data = parcMemory_AllocateAndClear(sizeof(TestData)); + + _setup(data, ALICE, ALICE_PORT); + _setup(data, BOB, BOB_PORT); + + _crankHandle(data); + + longBowTestCase_SetClipBoardData(testCase, data); +} + +static void +_commonTeardown(const LongBowTestCase *testCase) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // the listeners are stored in the respectie Metis, so we don't need to + // destroy those separately. + + metisMessengerRecipient_Destroy(&data->recipient[ALICE]); + metisMessengerRecipient_Destroy(&data->recipient[BOB]); + + if (data->tapData[ALICE].message) { + metisMessage_Release(&data->tapData[ALICE].message); + } + + if (data->tapData[BOB].message) { + metisMessage_Release(&data->tapData[BOB].message); + } + + metisForwarder_Destroy(&data->metis[ALICE]); + metisForwarder_Destroy(&data->metis[BOB]); + + parcMemory_Deallocate((void **) &data); +} + +LONGBOW_TEST_RUNNER(metis_UdpConnection) +{ + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_UdpConnection) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_UdpConnection) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + //XXX: this test does not work anymore because we do not create the connection in metis + //LONGBOW_RUN_TEST_CASE(Global, metisUdpConnection_Create); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + _commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + _commonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +/* + * Create connection from ALICE to BOB + */ +LONGBOW_TEST_CASE(Global, metisUdpConnection_Create) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + const CPIAddress *bobAddress = data->listener[BOB]->getListenAddress(data->listener[BOB]); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + int fd = data->listener[ALICE]->getSocket(data->listener[ALICE]); + MetisIoOperations *ops = metisUdpConnection_Create(data->metis[ALICE], fd, pair, false); + metisAddressPair_Release(&pair); + + _crankHandle(data); + + // send a data packet to bring it up on BOB + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV1_Interest_NameA_Crc32c, sizeof(metisTestDataV1_Interest_NameA_Crc32c), 2, 3, metisForwarder_GetLogger(data->metis[ALICE])); + + ops->send(ops, NULL, message); + + metisMessage_Release(&message); + + // wait until we indicate that the connection is up on Bob's side + while (data->notifierData[BOB].type == MetisMissiveType_ConnectionDestroyed) { + _crankHandle(data); + } + + // and verify that the message was sent in to the message processor on Bob's side + assertTrue(data->tapData[BOB].onReceiveCount == 1, "Wrong receive count, expected 1 got %u", data->tapData[BOB].onReceiveCount); + + ops->destroy(&ops); +} + +// =========================================================== + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _saveSockaddr_INET); + LONGBOW_RUN_TEST_CASE(Local, _saveSockaddr_INET6); + LONGBOW_RUN_TEST_CASE(Local, _send); + LONGBOW_RUN_TEST_CASE(Local, _getRemoteAddress); + LONGBOW_RUN_TEST_CASE(Local, _getAddressPair); + LONGBOW_RUN_TEST_CASE(Local, _getConnectionId); + LONGBOW_RUN_TEST_CASE(Local, _isUp); + LONGBOW_RUN_TEST_CASE(Local, _isLocal_True); + LONGBOW_RUN_TEST_CASE(Local, _setConnectionState); + LONGBOW_RUN_TEST_CASE(Local, _getConnectionType); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + _commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + _commonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, _saveSockaddr_INET) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + _MetisUdpState *udpConnState = parcMemory_AllocateAndClear(sizeof(_MetisUdpState)); + assertNotNull(udpConnState, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisUdpState)); + + udpConnState->metis = data->metis[ALICE]; + udpConnState->logger = metisLogger_Acquire(metisForwarder_GetLogger(udpConnState->metis)); + + struct sockaddr_in sin1, sin2; + + memset(&sin1, 0, sizeof(sin1)); + sin1.sin_family = AF_INET; + sin1.sin_port = htons(ALICE_PORT); + inet_pton(AF_INET, "127.0.0.1", &(sin1.sin_addr)); + + memset(&sin2, 0, sizeof(sin2)); + sin2.sin_family = AF_INET; + sin2.sin_port = htons(BOB_PORT); + inet_pton(AF_INET, "127.0.0.1", &(sin2.sin_addr)); + + CPIAddress *aliceAddress = cpiAddress_CreateFromInet(&sin1); + CPIAddress *bobAddress = cpiAddress_CreateFromInet(&sin2); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + + bool saved = _saveSockaddr(udpConnState, pair); + assertTrue(saved, "Failed to save address"); + assertTrue(udpConnState->peerAddressLength == sizeof(sin1), "Wrong length, expected %zu got %u", sizeof(sin1), udpConnState->peerAddressLength); + + cpiAddress_Destroy(&aliceAddress); + cpiAddress_Destroy(&bobAddress); + + metisAddressPair_Release(&pair); + metisLogger_Release(&udpConnState->logger); + parcMemory_Deallocate((void **) &udpConnState->peerAddress); + parcMemory_Deallocate((void **) &udpConnState); +} + +LONGBOW_TEST_CASE(Local, _saveSockaddr_INET6) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + _MetisUdpState *udpConnState = parcMemory_AllocateAndClear(sizeof(_MetisUdpState)); + assertNotNull(udpConnState, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisUdpState)); + + udpConnState->metis = data->metis[ALICE]; + udpConnState->logger = metisLogger_Acquire(metisForwarder_GetLogger(udpConnState->metis)); + + struct sockaddr_in6 sin1, sin2; + + memset(&sin1, 0, sizeof(sin1)); + sin1.sin6_family = AF_INET6; + sin1.sin6_port = htons(ALICE_PORT); + int ok = inet_pton(AF_INET6, "::1", &(sin1.sin6_addr)); + + if (ok) { + memset(&sin2, 0, sizeof(sin2)); + sin2.sin6_family = AF_INET6; + sin2.sin6_port = htons(BOB_PORT); + inet_pton(AF_INET6, "::1", &(sin2.sin6_addr)); + + CPIAddress *aliceAddress = cpiAddress_CreateFromInet6(&sin1); + CPIAddress *bobAddress = cpiAddress_CreateFromInet6(&sin2); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + + bool saved = _saveSockaddr(udpConnState, pair); + assertTrue(saved, "Failed to save address"); + assertTrue(udpConnState->peerAddressLength == sizeof(sin1), "Wrong length, expected %zu got %u", sizeof(sin1), udpConnState->peerAddressLength); + + cpiAddress_Destroy(&aliceAddress); + cpiAddress_Destroy(&bobAddress); + + metisAddressPair_Release(&pair); + } else { + testSkip("Skipping inet6 test"); + } + + metisLogger_Release(&udpConnState->logger); + parcMemory_Deallocate((void **) &udpConnState->peerAddress); + parcMemory_Deallocate((void **) &udpConnState); +} + +LONGBOW_TEST_CASE(Local, _send) +{ +} + +LONGBOW_TEST_CASE(Local, _getRemoteAddress) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + const CPIAddress *bobAddress = data->listener[BOB]->getListenAddress(data->listener[BOB]); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + int fd = data->listener[ALICE]->getSocket(data->listener[ALICE]); + MetisIoOperations *ops = metisUdpConnection_Create(data->metis[ALICE], fd, pair, false); + metisAddressPair_Release(&pair); + + // now run test + const CPIAddress *test = _getRemoteAddress(ops); + assertNotNull(test, "Got null remote address"); + assertTrue(cpiAddress_Equals(test, bobAddress), "Addresses do not match"); + ops->destroy(&ops); +} + +LONGBOW_TEST_CASE(Local, _getAddressPair) +{ +} + +LONGBOW_TEST_CASE(Local, _getConnectionId) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + const CPIAddress *bobAddress = data->listener[BOB]->getListenAddress(data->listener[BOB]); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + int fd = data->listener[ALICE]->getSocket(data->listener[ALICE]); + MetisIoOperations *ops = metisUdpConnection_Create(data->metis[ALICE], fd, pair, false); + metisAddressPair_Release(&pair); + + // now run test + unsigned connid = _getConnectionId(ops); + + assertTrue(connid > 0, "Expected positive connid, got 0"); + ops->destroy(&ops); +} + +LONGBOW_TEST_CASE(Local, _isUp) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + const CPIAddress *bobAddress = data->listener[BOB]->getListenAddress(data->listener[BOB]); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + int fd = data->listener[ALICE]->getSocket(data->listener[ALICE]); + MetisIoOperations *ops = metisUdpConnection_Create(data->metis[ALICE], fd, pair, false); + metisAddressPair_Release(&pair); + + // now run test + bool isup = _isUp(ops); + + assertTrue(isup, "Expected connection to be up"); + ops->destroy(&ops); +} + +LONGBOW_TEST_CASE(Local, _isLocal_True) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + const CPIAddress *bobAddress = data->listener[BOB]->getListenAddress(data->listener[BOB]); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + int fd = data->listener[ALICE]->getSocket(data->listener[ALICE]); + MetisIoOperations *ops = metisUdpConnection_Create(data->metis[ALICE], fd, pair, true); + metisAddressPair_Release(&pair); + + // now run test + bool islocal = _isLocal(ops); + + assertTrue(islocal, "Expected connection to be local"); + ops->destroy(&ops); +} + +LONGBOW_TEST_CASE(Local, _setConnectionState) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + const CPIAddress *bobAddress = data->listener[BOB]->getListenAddress(data->listener[BOB]); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + int fd = data->listener[ALICE]->getSocket(data->listener[ALICE]); + MetisIoOperations *ops = metisUdpConnection_Create(data->metis[ALICE], fd, pair, true); + metisAddressPair_Release(&pair); + + // now run test + _MetisUdpState *udpConnState = (_MetisUdpState *) metisIoOperations_GetClosure(ops); + + _setConnectionState(udpConnState, false); + bool isup = _isUp(ops); + + assertFalse(isup, "Expected connection to be down"); + ops->destroy(&ops); +} + +LONGBOW_TEST_CASE(Local, _getConnectionType) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + const CPIAddress *bobAddress = data->listener[BOB]->getListenAddress(data->listener[BOB]); + + MetisAddressPair *pair = metisAddressPair_Create(aliceAddress, bobAddress); + int fd = data->listener[ALICE]->getSocket(data->listener[ALICE]); + MetisIoOperations *ops = metisUdpConnection_Create(data->metis[ALICE], fd, pair, false); + metisAddressPair_Release(&pair); + + // now run test + CPIConnectionType connType = _getConnectionType(ops); + + assertTrue(connType == cpiConnection_UDP, "Expected connection to be %d got %d", cpiConnection_UDP, connType); + ops->destroy(&ops); +} + + +// =========================================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_UdpConnection); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_UdpListener.c b/metis/ccnx/forwarder/metis/io/test/test_metis_UdpListener.c new file mode 100644 index 00000000..96dfc4fd --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_UdpListener.c @@ -0,0 +1,458 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + */ + +/* + * hard-coded in port 49009 on localhost + */ + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_UdpListener.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> +#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h> +#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h> + +// for inet_pton +#include <arpa/inet.h> + +#include <signal.h> + +// ======================================================== + +struct test_set { + CPIAddress *listenAddress; + MetisForwarder *metis; + MetisListenerOps *ops; +} TestSet; + +static void +setupInetListener() +{ + struct sockaddr_in addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(49009); + inet_pton(AF_INET, "127.0.0.1", &(addr.sin_addr)); + + TestSet.metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(TestSet.metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + TestSet.ops = metisUdpListener_CreateInet(TestSet.metis, addr); + TestSet.listenAddress = cpiAddress_CreateFromInet(&addr); + + // crank the handle + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); +} + +static void +teardownListener() +{ + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + cpiAddress_Destroy(&TestSet.listenAddress); + TestSet.ops->destroy(&TestSet.ops); + metisForwarder_Destroy(&TestSet.metis); +} + +static bool +setupInet6Listener() +{ + struct sockaddr_in6 addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(49009); + + // "::1" is the ipv6 loopback address + int ok = inet_pton(AF_INET6, "::1", &(addr.sin6_addr)); + if (ok > 0) { + TestSet.metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(TestSet.metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + + TestSet.ops = metisUdpListener_CreateInet6(TestSet.metis, addr); + TestSet.listenAddress = cpiAddress_CreateFromInet6(&addr); + + // crank the handle + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + return true; + } + return false; +} + +struct sigaction save_sigchld; +struct sigaction save_sigpipe; + +static void +blockSigChild() +{ + struct sigaction ignore_action; + ignore_action.sa_handler = SIG_IGN; + sigemptyset(&ignore_action.sa_mask); + ignore_action.sa_flags = 0; + + sigaction(SIGCHLD, NULL, &save_sigchld); + sigaction(SIGPIPE, NULL, &save_sigpipe); + + sigaction(SIGCHLD, &ignore_action, NULL); + sigaction(SIGPIPE, &ignore_action, NULL); +} + +static void +unblockSigChild() +{ + sigaction(SIGCHLD, &save_sigchld, NULL); + sigaction(SIGPIPE, &save_sigpipe, NULL); +} + +// ======================================================== + +LONGBOW_TEST_RUNNER(metis_UdpListener) +{ + LONGBOW_RUN_TEST_FIXTURE(Global_Inet); + // XXX: Udp code has issues. It should check return values from calls. + // There are bugs in the UDP code that need to be fixed. These are shown in + // this test. The code needs to be fixed first. + //LONGBOW_RUN_TEST_FIXTURE(Global_Inet6); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_UdpListener) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_UdpListener) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ================================================================================ + +LONGBOW_TEST_FIXTURE(Global_Inet) +{ + LONGBOW_RUN_TEST_CASE(Global_Inet, metisListenerUdp_CreateInet); + //XXX: this does not work anymore because we do not create the udp connection + //LONGBOW_RUN_TEST_CASE(Global_Inet, metisListenerUdp_Connect); + LONGBOW_RUN_TEST_CASE(Global_Inet, metisListenerUdp_SendPacket); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global_Inet) +{ + setupInetListener(); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global_Inet) +{ + teardownListener(); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global_Inet, metisListenerUdp_CreateInet) +{ + // now verify that we are listening + // udp4 0 0 127.0.0.1.49009 *.* LISTEN + + blockSigChild(); + FILE *fp = popen("netstat -an -p udp", "r"); + assertNotNull(fp, "Got null opening netstat for reading"); + + char str[1035]; + bool found = false; + while (fgets(str, sizeof(str) - 1, fp) != NULL) { + if (strstr(str, "127.0.0.1.49009") != NULL) { + found = true; + break; + } + if (strstr(str, "127.0.0.1:49009") != NULL) { + found = true; + break; + } + } + + pclose(fp); + unblockSigChild(); + + if (!found) { + int ret = system("netstat -an -p udp"); + assertTrue(ret > -1, "Error on system call"); + } + + assertTrue(found, "Did not find 127.0.0.1.49009 in netstat output"); +} + +LONGBOW_TEST_CASE(Global_Inet, metisListenerUdp_Connect) +{ + int fd = socket(PF_INET, SOCK_DGRAM, 0); + assertFalse(fd < 0, "Error on socket: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in serverAddress; + cpiAddress_GetInet(TestSet.listenAddress, &serverAddress); + + int failure = connect(fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)); + assertFalse(failure, "Error on connect: (%d) %s", errno, strerror(errno)); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + + struct sockaddr_in connectAddress; + socklen_t connectAddressLength = sizeof(connectAddress); + failure = getsockname(fd, (struct sockaddr *) &connectAddress, &connectAddressLength); + assertFalse(failure, "Error on getsockname: (%d) %s", errno, strerror(errno)); + assertTrue(connectAddressLength == sizeof(struct sockaddr_in), + "connectAddressLength wrong size, expected %zu got %u", + sizeof(struct sockaddr_in), connectAddressLength); + + // Unlike TCP, we need to actually send something + ssize_t nwritten = write(fd, metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest)); + assertTrue(nwritten == sizeof(metisTestDataV0_EncodedInterest), "Error on write expected %zu got %zd", sizeof(metisTestDataV0_EncodedInterest), nwritten); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + + // make sure its in the connection table + MetisConnectionTable *table = metisForwarder_GetConnectionTable(TestSet.metis); + CPIAddress *remote = cpiAddress_CreateFromInet(&connectAddress); + MetisAddressPair *pair = metisAddressPair_Create(TestSet.listenAddress, remote); + const MetisConnection *conn = metisConnectionTable_FindByAddressPair(table, pair); + assertNotNull(conn, "Did not find connection in connection table"); + + cpiAddress_Destroy(&remote); + metisAddressPair_Release(&pair); + + close(fd); +} + +LONGBOW_TEST_CASE(Global_Inet, metisListenerUdp_SendPacket) +{ + int fd = socket(PF_INET, SOCK_DGRAM, 0); + assertFalse(fd < 0, "Error on socket: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in serverAddress; + cpiAddress_GetInet(TestSet.listenAddress, &serverAddress); + + int failure = connect(fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)); + assertFalse(failure, "Error on connect socket %d: (%d) %s\n", fd, errno, strerror(errno)); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + + ssize_t write_length = write(fd, metisTestDataV0_InterestWithName, sizeof(metisTestDataV0_InterestWithName)); + assertFalse(write_length < 0, "Error on write: (%d) %s", errno, strerror(errno)); + assertTrue(write_length == sizeof(metisTestDataV0_InterestWithName), "Got partial write, expected %zu got %zd", sizeof(metisTestDataV0_InterestWithName), write_length); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + + close(fd); +} + +// ================================================================================ + +LONGBOW_TEST_FIXTURE(Global_Inet6) +{ + LONGBOW_RUN_TEST_CASE(Global_Inet6, metisListenerUdp_CreateInet6); + LONGBOW_RUN_TEST_CASE(Global_Inet6, metisListenerUdp_Connect); + LONGBOW_RUN_TEST_CASE(Global_Inet6, metisListenerUdp_SendPacket); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global_Inet6) +{ + bool success = setupInet6Listener(); + if (!success) { + return LONGBOW_STATUS_SKIPPED; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global_Inet6) +{ + teardownListener(); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global_Inet6, metisListenerUdp_CreateInet6) +{ + // now verify that we are listening + // udp6 0 0 ::1.49009 *.* + + blockSigChild(); + FILE *fp = popen("netstat -an -p udp", "r"); + assertNotNull(fp, "Got null opening netstat for reading"); + + char str[1035]; + bool found = false; + while (fgets(str, sizeof(str) - 1, fp) != NULL) { + //printf("%s\n", str); + if (strstr(str, "::1.49009") != NULL) { + found = true; + break; + } + if (strstr(str, "::1:49009") != NULL) { + found = true; + break; + } + } + + pclose(fp); + unblockSigChild(); + + if (!found) { + int ret = system("netstat -an -p udp"); + assertTrue(ret != -1, "Error on system() call"); + } + + assertTrue(found, "Did not find 127.0.0.1.49009 in netstat output"); +} + +LONGBOW_TEST_CASE(Global_Inet6, metisListenerUdp_Connect) +{ + int fd = socket(PF_INET6, SOCK_DGRAM, 0); + assertFalse(fd < 0, "Error on socket: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in6 serverAddress; + cpiAddress_GetInet6(TestSet.listenAddress, &serverAddress); + + int failure = connect(fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)); + assertFalse(failure, "Error on connect: (%d) %s", errno, strerror(errno)); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + + struct sockaddr_in6 connectAddress; + socklen_t connectAddressLength = sizeof(connectAddress); + failure = getsockname(fd, (struct sockaddr *) &connectAddress, &connectAddressLength); + assertFalse(failure, "Error on getsockname: (%d) %s", errno, strerror(errno)); + assertTrue(connectAddressLength == sizeof(struct sockaddr_in6), + "connectAddressLength wrong size, expected %zu got %u", + sizeof(struct sockaddr_in6), connectAddressLength); + + // Unlike TCP, we need to actually send something + ssize_t nwritten = write(fd, metisTestDataV1_Interest_NameA_Crc32c, sizeof(metisTestDataV1_Interest_NameA_Crc32c)); + assertTrue(nwritten == sizeof(metisTestDataV1_Interest_NameA_Crc32c), "Write failed, expected %zu got %zd", sizeof(metisTestDataV1_Interest_NameA_Crc32c), nwritten); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + + // make sure its in the connection table + MetisConnectionTable *table = metisForwarder_GetConnectionTable(TestSet.metis); + CPIAddress *remote = cpiAddress_CreateFromInet6(&connectAddress); + MetisAddressPair *pair = metisAddressPair_Create(TestSet.listenAddress, remote); + const MetisConnection *conn = metisConnectionTable_FindByAddressPair(table, pair); + assertNotNull(conn, "Did not find connection in connection table"); + + cpiAddress_Destroy(&remote); + metisAddressPair_Release(&pair); + + close(fd); +} + +LONGBOW_TEST_CASE(Global_Inet6, metisListenerUdp_SendPacket) +{ + int fd = socket(PF_INET6, SOCK_DGRAM, 0); + assertFalse(fd < 0, "Error on socket: (%d) %s", errno, strerror(errno)); + + struct sockaddr_in6 serverAddress; + cpiAddress_GetInet6(TestSet.listenAddress, &serverAddress); + + int failure = connect(fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)); + assertFalse(failure, "Error on connect socket %d: (%d) %s\n", fd, errno, strerror(errno)); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + + ssize_t write_length = write(fd, metisTestDataV1_Interest_NameA_Crc32c, sizeof(metisTestDataV1_Interest_NameA_Crc32c)); + assertFalse(write_length < 0, "Error on write: (%d) %s", errno, strerror(errno)); + assertTrue(write_length == sizeof(metisTestDataV1_Interest_NameA_Crc32c), + "Got partial write, expected %zu got %zd", + sizeof(metisTestDataV1_Interest_NameA_Crc32c), write_length); + + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(TestSet.metis), &((struct timeval) { 0, 10000 })); + + close(fd); +} + +// ================================================================================ + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _getInterfaceIndex); + LONGBOW_RUN_TEST_CASE(Local, _getListenAddress); + LONGBOW_RUN_TEST_CASE(Local, _getEncapType); + LONGBOW_RUN_TEST_CASE(Local, _getSocket); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + setupInetListener(); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + teardownListener(); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, _getInterfaceIndex) +{ + unsigned test = _getInterfaceIndex(TestSet.ops); + assertTrue(test > 0, "Unexpected interface index: %u", test); +} + +LONGBOW_TEST_CASE(Local, _getListenAddress) +{ + const CPIAddress *listenAddr = _getListenAddress(TestSet.ops); + assertNotNull(listenAddr, "Got null listen address"); +} + +LONGBOW_TEST_CASE(Local, _getEncapType) +{ + MetisEncapType type = _getEncapType(TestSet.ops); + assertTrue(type == METIS_ENCAP_UDP, "Unexpected address type, got %d expected %d", type, METIS_ENCAP_UDP); +} + +LONGBOW_TEST_CASE(Local, _getSocket) +{ + int fd = _getSocket(TestSet.ops); + assertTrue(fd > 0, "Unexpected socket, got %d, expected positive", fd); +} + + +// ================================================================================ + + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_UdpListener); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/test_metis_UdpTunnel.c b/metis/ccnx/forwarder/metis/io/test/test_metis_UdpTunnel.c new file mode 100644 index 00000000..cba18ed3 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/test_metis_UdpTunnel.c @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Need a solution to avoid hard-coding port numbers + * + */ + +#include "../metis_UdpTunnel.c" +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> + +#include <ccnx/forwarder/metis/io/metis_UdpListener.h> +#include <ccnx/forwarder/metis/config/metis_Configuration.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h> + +// for inet_pton +#include <arpa/inet.h> + +#define ALICE_PORT 49028 +#define BOB_PORT 49029 + +// ---- Used to monitor Missive messages so we know when a connection is up +typedef struct test_notifier_data { + MetisMissiveType type; + unsigned connectionid; +} TestNotifierData; + +static void +testNotifier(MetisMessengerRecipient *recipient, MetisMissive *missive) +{ + struct test_notifier_data *data = metisMessengerRecipient_GetRecipientContext(recipient); + data->type = metisMissive_GetType(missive); + data->connectionid = metisMissive_GetConnectionId(missive); + metisMissive_Release(&missive); +} + +// ---- + +typedef struct test_tap_data { + unsigned onReceiveCount; + MetisMessage *message; +} TestTapData; + +static bool +testTap_IsTapOnReceive(const MetisTap *tap) +{ + return true; +} + +static void +testTap_TapOnReceive(MetisTap *tap, const MetisMessage *message) +{ + TestTapData *mytap = (TestTapData *) tap->context; + mytap->onReceiveCount++; + if (mytap->message) { + metisMessage_Release(&mytap->message); + } + + mytap->message = metisMessage_Acquire(message); +} + +static MetisTap testTapTemplate = { + .context = NULL, + .isTapOnReceive = &testTap_IsTapOnReceive, + .isTapOnSend = NULL, + .isTapOnDrop = NULL, + .tapOnReceive = &testTap_TapOnReceive, + .tapOnSend = NULL, + .tapOnDrop = NULL +}; + +// --- Used to inspect packets received + + +typedef struct test_data { + int remoteSocket; + +#define ALICE 0 +#define BOB 1 + + MetisForwarder *metis[2]; + MetisListenerOps *listener[2]; + MetisMessengerRecipient *recipient[2]; + TestNotifierData notifierData[2]; + MetisTap taps[2]; + TestTapData tapData[2]; +} TestData; + + + +static void +_crankHandle(TestData *data) +{ + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis[ALICE]), &((struct timeval) {0, 10000})); + metisDispatcher_RunDuration(metisForwarder_GetDispatcher(data->metis[BOB]), &((struct timeval) {0, 10000})); +} + +static void +_setup(TestData *data, int side, uint16_t port) +{ + data->metis[side] = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(data->metis[side]), MetisLoggerFacility_IO, PARCLogLevel_Error); + + struct sockaddr_in addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + inet_pton(AF_INET, "127.0.0.1", &(addr.sin_addr)); + + data->listener[side] = metisUdpListener_CreateInet(data->metis[side], addr); + + // snoop events + data->recipient[side] = metisMessengerRecipient_Create(&data->notifierData[side], testNotifier); + MetisMessenger *messenger = metisForwarder_GetMessenger(data->metis[side]); + metisMessenger_Register(messenger, data->recipient[side]); + + // snoop packets + memcpy(&data->taps[side], &testTapTemplate, sizeof(testTapTemplate)); + data->taps[side].context = &data->tapData[side]; + metisForwarder_AddTap(data->metis[side], &data->taps[side]); + + // save in Metis + metisListenerSet_Add(metisForwarder_GetListenerSet(data->metis[side]), data->listener[side]); +} + +/* + * Create a UDP socket pair + */ +static void +_commonSetup(const LongBowTestCase *testCase) +{ + TestData *data = parcMemory_AllocateAndClear(sizeof(TestData)); + + _setup(data, ALICE, ALICE_PORT); + _setup(data, BOB, BOB_PORT); + + _crankHandle(data); + + longBowTestCase_SetClipBoardData(testCase, data); +} + +static void +_commonTeardown(const LongBowTestCase *testCase) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // the listeners are stored in the respectie Metis, so we don't need to + // destroy those separately. + + metisMessengerRecipient_Destroy(&data->recipient[ALICE]); + metisMessengerRecipient_Destroy(&data->recipient[BOB]); + + if (data->tapData[ALICE].message) { + metisMessage_Release(&data->tapData[ALICE].message); + } + + if (data->tapData[BOB].message) { + metisMessage_Release(&data->tapData[BOB].message); + } + + metisForwarder_Destroy(&data->metis[ALICE]); + metisForwarder_Destroy(&data->metis[BOB]); + + parcMemory_Deallocate((void **) &data); +} + +// ================================== + +LONGBOW_TEST_RUNNER(metis_UdpTunnel) +{ + LONGBOW_RUN_TEST_FIXTURE(Global); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(metis_UdpTunnel) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(metis_UdpTunnel) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisUdpTunnel_CreateOnListener); + //XXX: this test does not work anymore beacuase we don't create the connection in metis + //LONGBOW_RUN_TEST_CASE(Global, metisUdpTunnel_Create); + LONGBOW_RUN_TEST_CASE(Global, metisUdpTunnel_Create_MismatchedTypes); + LONGBOW_RUN_TEST_CASE(Global, metisUdpTunnel_Create_NotFound); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + _commonSetup(testCase); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + _commonTeardown(testCase); + if (parcSafeMemory_ReportAllocation(STDOUT_FILENO) != 0) { + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + + +LONGBOW_TEST_CASE(Global, metisUdpTunnel_CreateOnListener) +{ +} + +/* + * Create from Alice to Bob + */ +LONGBOW_TEST_CASE(Global, metisUdpTunnel_Create) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + const CPIAddress *bobAddress = data->listener[BOB]->getListenAddress(data->listener[BOB]); + + MetisIoOperations *ops = metisUdpTunnel_Create(data->metis[ALICE], aliceAddress, bobAddress); + + _crankHandle(data); + + // send a data packet to bring it up on BOB + MetisMessage *message = metisMessage_CreateFromArray(metisTestDataV1_Interest_NameA_Crc32c, sizeof(metisTestDataV1_Interest_NameA_Crc32c), 2, 3, metisForwarder_GetLogger(data->metis[ALICE])); + + ops->send(ops, NULL, message); + + metisMessage_Release(&message); + + // wait until we indicate that the connection is up on Bob's side + while (data->notifierData[BOB].type == MetisMissiveType_ConnectionDestroyed) { + _crankHandle(data); + } + + // and verify that the message was sent in to the message processor on Bob's side + assertTrue(data->tapData[BOB].onReceiveCount == 1, "Wrong receive count, expected 1 got %u", data->tapData[BOB].onReceiveCount); + + ops->destroy(&ops); +} + +/* + * sockets not same address family + */ +LONGBOW_TEST_CASE(Global, metisUdpTunnel_Create_MismatchedTypes) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + const CPIAddress *aliceAddress = data->listener[ALICE]->getListenAddress(data->listener[ALICE]); + + struct sockaddr_in6 sin6; + memset(&sin6, 0, sizeof(sin6)); + CPIAddress *bobAddress = cpiAddress_CreateFromInet6(&sin6); + + MetisIoOperations *ops = metisUdpTunnel_Create(data->metis[ALICE], aliceAddress, bobAddress); + assertNull(ops, "Should have gotten null return for mismatched address types"); + + cpiAddress_Destroy(&bobAddress); +} + +/* + * Listener not found + */ +LONGBOW_TEST_CASE(Global, metisUdpTunnel_Create_NotFound) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // set Bob's notifier to a known bad state + data->notifierData[BOB].type = MetisMissiveType_ConnectionDestroyed; + + // Create a connection from Alice to Bob + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + CPIAddress *aliceAddress = cpiAddress_CreateFromInet(&sin); + CPIAddress *bobAddress = cpiAddress_CreateFromInet(&sin); + + MetisIoOperations *ops = metisUdpTunnel_Create(data->metis[ALICE], aliceAddress, bobAddress); + assertNull(ops, "Should have gotten null return for mismatched address types"); + + cpiAddress_Destroy(&aliceAddress); + cpiAddress_Destroy(&bobAddress); +} + + + +// ================================== + + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(metis_UdpTunnel); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/io/test/testrig_GenericEther.c b/metis/ccnx/forwarder/metis/io/test/testrig_GenericEther.c new file mode 100644 index 00000000..d5c641d2 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/testrig_GenericEther.c @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * GenericEther mockup for testing metis_EtherListener. + * + */ + +#include <config.h> +#include <unistd.h> +#include <errno.h> +#include <stdio.h> +#include <fcntl.h> +#include <net/ethernet.h> +#include <arpa/inet.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_Deque.h> +#include <parc/algol/parc_Buffer.h> +#include <parc/algol/parc_EventBuffer.h> + +#include <ccnx/forwarder/metis/core/metis_System.h> +#include <ccnx/forwarder/metis/io/metis_GenericEther.h> + +#include <LongBow/runtime.h> + +struct metis_generic_ether { + PARCDeque *inputQueue; + int testSocket; + int etherSocket; + PARCBuffer *macAddress; + uint16_t ethertype; + unsigned refcount; + MetisLogger *logger; + unsigned mtu; +}; + +void +_metisGenericEther_Destroy(MetisGenericEther **etherPtr) +{ + MetisGenericEther *ether = *etherPtr; + PARCBuffer *buffer; + + // the input queue is simple byte arrays of ethernet frames + while ((buffer = parcDeque_RemoveFirst(ether->inputQueue))) { + parcBuffer_Release(&buffer); + } + + metisLogger_Release(ðer->logger); + parcDeque_Release(ðer->inputQueue); + parcBuffer_Release(ðer->macAddress); + + close(ether->testSocket); + close(ether->etherSocket); +} + +parcObject_ExtendPARCObject(MetisGenericEther, _metisGenericEther_Destroy, NULL, NULL, NULL, NULL, NULL, NULL); + +parcObject_ImplementAcquire(metisGenericEther, MetisGenericEther); + +parcObject_ImplementRelease(metisGenericEther, MetisGenericEther); + +MetisGenericEther * +metisGenericEther_Create(MetisForwarder *metis, const char *deviceName, uint16_t etherType) +{ + assertNotNull(deviceName, "Parameter deviceName must be non-null"); + assertTrue(etherType >= 0x0600, "EtherType must be greater than or equal to 0x0600"); + + MetisGenericEther *ether = parcObject_CreateInstance(MetisGenericEther); + + int fd[2]; + int failure = socketpair(PF_LOCAL, SOCK_DGRAM, 0, fd); + assertFalse(failure, "Error on socketpair"); + + ether->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + ether->testSocket = fd[0]; + ether->etherSocket = fd[1]; + ether->ethertype = etherType; + ether->inputQueue = parcDeque_Create(); + ether->mtu = 4000; + + // Set non-blocking flag + int flags = fcntl(ether->testSocket, F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)\n", errno); + failure = fcntl(ether->testSocket, F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)\n", errno); + flags = fcntl(ether->etherSocket, F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)\n", errno); + failure = fcntl(ether->etherSocket, F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)\n", errno); + + // If we are passed a real interface name, use its MAC address otherwise make up something + CPIAddress *realMacAddress = metisSystem_GetMacAddressByName(metis, deviceName); + + if (realMacAddress) { + PARCBuffer *realMac = cpiAddress_GetLinkAddress(realMacAddress); + ether->macAddress = parcBuffer_Copy(realMac); + cpiAddress_Destroy(&realMacAddress); + } else { + uint8_t macAddress[] = { 1, 2, 3, 4, 5, 6 }; + + // copy up to 6 bytes of the deviceName in to the mac address, for debugging + size_t maxbytes = (strlen(deviceName) > 6) ? 6 : strlen(deviceName); + memcpy(macAddress, deviceName, maxbytes); + ether->macAddress = parcBuffer_Allocate(sizeof(macAddress)); + parcBuffer_PutArray(ether->macAddress, sizeof(macAddress), macAddress); + parcBuffer_Flip(ether->macAddress); + } + + return ether; +} + +int +metisGenericEther_GetDescriptor(const MetisGenericEther *ether) +{ + return ether->etherSocket; +} + +bool +metisGenericEther_ReadNextFrame(MetisGenericEther *ether, PARCEventBuffer *buffer) +{ + // read a byte off the etherSocket if available to clear the notification. + // its non-blocking so no harm if nothing there. + uint8_t one; + ssize_t nread = read(ether->etherSocket, &one, 1); + assertTrue(errno == EWOULDBLOCK || nread > -1, "Error on read"); + + if (!parcDeque_IsEmpty(ether->inputQueue)) { + PARCBuffer *frame = parcDeque_RemoveFirst(ether->inputQueue); + uint8_t *bytes = parcBuffer_Overlay(frame, 0); + size_t length = parcBuffer_Remaining(frame); + + parcEventBuffer_Append(buffer, bytes, length); + parcBuffer_Release(&frame); + return true; + } + return false; +} + +bool +metisGenericEther_SendFrame(MetisGenericEther *ether, PARCEventBuffer *buffer) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + + // cannot use parcEventBuffer_WriteToFileDescriptor because we need to write the length in one go, not use the + // iovec approach in parcEventBuffer_WriteToFileDescriptor. It can cause problems on some platforms. + + uint8_t *linear = parcEventBuffer_Pullup(buffer, -1); + size_t length = parcEventBuffer_GetLength(buffer); + + ssize_t written = write(ether->etherSocket, linear, length); + if (written == length) { + return true; + } + + return false; +} + +PARCBuffer * +metisGenericEther_GetMacAddress(const MetisGenericEther *ether) +{ + return ether->macAddress; +} + +uint16_t +metisGenericEther_GetEtherType(const MetisGenericEther *ether) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + return ether->ethertype; +} + +unsigned +metisGenericEther_GetMTU(const MetisGenericEther *ether) +{ + return ether->mtu; +} + +// ========= +// Extra functions for testing + +int +mockGenericEther_GetTestDescriptor(MetisGenericEther *ether) +{ + return ether->testSocket; +} + +void +mockGenericEther_QueueFrame(MetisGenericEther *ether, PARCBuffer *ethernetFrame) +{ + parcDeque_Append(ether->inputQueue, parcBuffer_Acquire(ethernetFrame)); +} + +void +mockGenericEther_Notify(MetisGenericEther *ether) +{ + // notify the etherSocket by writing to the testSocket + uint8_t one = 1; + ssize_t nwritten = write(ether->testSocket, &one, 1); + assertTrue(nwritten == 1, "Error on write, expected 1, got %zd", nwritten); +} + +/* + * Create an Ethernet frame enclosing the ccnxPacket. does not include the FCS. + */ +PARCBuffer * +mockGenericEther_createFrame(size_t length, const uint8_t *ccnxPacket, const uint8_t dmac[ETHER_ADDR_LEN], const uint8_t smac[ETHER_ADDR_LEN], uint16_t ethertype) +{ + size_t totalLength = sizeof(struct ether_header) + length; + PARCBuffer *buffer = parcBuffer_Allocate(totalLength); + + struct ether_header hdr; + + memcpy(hdr.ether_dhost, dmac, ETHER_ADDR_LEN); + memcpy(hdr.ether_shost, smac, ETHER_ADDR_LEN); + hdr.ether_type = htons(ethertype); + + parcBuffer_PutArray(buffer, sizeof(hdr), (uint8_t *) &hdr); + + parcBuffer_PutArray(buffer, length, ccnxPacket); + + parcBuffer_Flip(buffer); + return buffer; +} + diff --git a/metis/ccnx/forwarder/metis/io/test/testrig_GenericEther.h b/metis/ccnx/forwarder/metis/io/test/testrig_GenericEther.h new file mode 100644 index 00000000..dc89841e --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/testrig_GenericEther.h @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file testrig_GenericEther.h + * @brief A mockup of a platform Ethernet + * + * The mockup is connected to a socketpair, so you can read frames that the metis_EtherListener sends. + * It also has an input queue so you can queue frames to be read by metis_EtherListener. + * + * This mockup implements the metis_GenericEther.h API plus two additional functions for the mockup. + * + */ + +#ifndef Metis_testrig_GenericEther_h +#define Metis_testrig_GenericEther_h + +#include <ccnx/forwarder/metis/io/metis_GenericEther.h> +#include <parc/algol/parc_Buffer.h> + +/** + * Returns the other end of a socketpair that mocks up the ethernet wire + * + * The mockup does not connect to a RAW or BPF socket, it connects to a socketpair. + * This function gets the remote end of the socket pair, which is where you can read + * frames that you send. + * + * DO NOT WRITE PACKETS HERE. To queue packets for input, use mockGenericEther_QueueFrame(). + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * @retval number System socketpair + * + * Example: + * @code + * <#example#> + * @endcode + */ +int mockGenericEther_GetTestDescriptor(MetisGenericEther *ether); + +/** + * Queue an Ethernet frame to be read + * + * The mockup maintains an input queue (deque) for input frames. These should be full + * Ethernet frames (not including the frame check sequence). + * + * This stores a reference, so caller must also release the PARCBuffer. + * + * This function will not notify the etherSocket being watched by Libevent in Metis. + * To notify Libevent, use mockGenericEther_Notify() after queuing packets. + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void mockGenericEther_QueueFrame(MetisGenericEther *ether, PARCBuffer *ethernetFrame); + +/** + * Writes a byte to the etherSocket + * + * Tickles Libevent by writing a byte to the etherSocket. + * + * @param [<#in out in,out#>] <#name#> <#description#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +void mockGenericEther_Notify(MetisGenericEther *ether); + +/** + * Convenience function to encapsulate a packet in an ethernet frame + * + * Creates a PARCBuffer that has an Ethernet header followed by a user-provided byte array. + * Does not include the frame check sequence. + * + * @param [in] length The length of the ccnxPacket to be encapsulated + * @param [in] ccnxPacket the byte array to put after the ethernet header + * @param [in] dmac[6] The destination mac + * @param [in] smac[6] The source mac + * @param [in] ethertype The ethertype in host byte order + * + * @retval non-null An allocated PARCBuffer wrapping an ethernet frame, ready to read + * @retval null An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +PARCBuffer *mockGenericEther_createFrame(size_t length, const uint8_t ccnxPacket[length], const uint8_t dmac[ETHER_ADDR_LEN], const uint8_t smac[ETHER_ADDR_LEN], uint16_t ethertype); + +#endif // Metis_testrig_GenericEther_h diff --git a/metis/ccnx/forwarder/metis/io/test/testrig_MetisListenerOps.c b/metis/ccnx/forwarder/metis/io/test/testrig_MetisListenerOps.c new file mode 100644 index 00000000..d0a1f4c0 --- /dev/null +++ b/metis/ccnx/forwarder/metis/io/test/testrig_MetisListenerOps.c @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This is a mock for MetisListenerOps + * + */ + +/** + * You should #include this C file in your unit test code + */ + +// =============================== +// Setup a mock for the MetisListenerOps + +typedef struct mock_listener_data { + unsigned destroyCount; + unsigned getInterfaceIndexCount; + unsigned getListenAddressCount; + unsigned getEncapTypeCount; + + // These values will be returned by the appropriate getter + unsigned interfaceIndex; + CPIAddress *listenAddress; + MetisEncapType encapType; +} MockListenerData; + +static void +mockListener_Destroy(MetisListenerOps **opsPtr) +{ + // Don't actually destroy the data, we want to keep the counts + MetisListenerOps *ops = *opsPtr; + MockListenerData *data = ops->context; + data->destroyCount++; + parcMemory_Deallocate((void **) &ops); + *opsPtr = NULL; +} + +static unsigned +mockListener_GetInterfaceIndex(const MetisListenerOps *ops) +{ + MockListenerData *data = ops->context; + data->getInterfaceIndexCount++; + return data->interfaceIndex; +} + +static const CPIAddress * +mockListener_GetListenAddress(const MetisListenerOps *ops) +{ + MockListenerData *data = ops->context; + data->getListenAddressCount++; + return data->listenAddress; +} + +static MetisEncapType +mockListener_GetEncapType(const MetisListenerOps *ops) +{ + MockListenerData *data = ops->context; + data->getEncapTypeCount++; + return data->encapType; +} + +static MetisListenerOps + mockListenerTemplate = { + .context = NULL, + .destroy = &mockListener_Destroy, + .getInterfaceIndex = &mockListener_GetInterfaceIndex, + .getListenAddress = &mockListener_GetListenAddress, + .getEncapType = &mockListener_GetEncapType +}; + +MockListenerData * +mockListenData_Create(unsigned interfaceIndex, CPIAddress *listenAddress, MetisEncapType encapType) +{ + MockListenerData *data = parcMemory_AllocateAndClear(sizeof(MockListenerData)); + assertNotNull(data, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MockListenerData)); + memset(data, 0, sizeof(MockListenerData)); + data->encapType = encapType; + data->interfaceIndex = interfaceIndex; + data->listenAddress = cpiAddress_Copy(listenAddress); + return data; +} + +MetisListenerOps * +mockListener_Create(MockListenerData *data) +{ + MetisListenerOps *ops = parcMemory_AllocateAndClear(sizeof(MetisListenerOps)); + assertNotNull(ops, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisListenerOps)); + memcpy(ops, &mockListenerTemplate, sizeof(MetisListenerOps)); + ops->context = data; + return ops; +} + +void +mockListenerData_Destroy(MockListenerData **dataPtr) +{ + MockListenerData *data = *dataPtr; + cpiAddress_Destroy(&data->listenAddress); + parcMemory_Deallocate((void **) &data); + *dataPtr = NULL; +} |