static CCNxMetaMessage *
_TemplateReceive(AthenaTransportLink *athenaTransportLink)
{
    struct _TemplateLinkData *linkData = athenaTransportLink_GetPrivateData(athenaTransportLink);
    CCNxMetaMessage *ccnxMetaMessage = NULL;

    PARCBuffer *wireFormatBuffer = _internalRECEIVE(linkData);

    // On error, just return and retry.
    if (wireFormatBuffer == NULL) {
        parcLog_Debug(athenaTransportLink_GetLogger(athenaTransportLink), "read error (%s)", strerror(errno));
        return NULL;
    }

    // Construct, and return a ccnxMetaMessage from the wire format buffer.
    ccnxMetaMessage = ccnxMetaMessage_CreateFromWireFormatBuffer(wireFormatBuffer);
    if (ccnxMetaMessage == NULL) {
        linkData->_stats.receive_DecodeFailed++;
        parcLog_Error(athenaTransportLink_GetLogger(athenaTransportLink), "Failed to decode message from received packet.");
    }
    parcBuffer_Release(&wireFormatBuffer);

    if (parcDeque_Size(linkData->queue) > 0) { // if there's another message, mark an event.
        athenaTransportLink_SetEvent(athenaTransportLink, AthenaTransportLinkEvent_Receive);
    }
    return ccnxMetaMessage;
}
//
// Receive a message from the specified link.
//
static CCNxMetaMessage *
_ETHReceiveMessage(AthenaTransportLink *athenaTransportLink, struct ether_addr *peerAddress, socklen_t *peerAddressLength)
{
    struct _ETHLinkData *linkData = athenaTransportLink_GetPrivateData(athenaTransportLink);
    CCNxMetaMessage *ccnxMetaMessage = NULL;
    AthenaTransportLinkEvent events = 0;

    PARCBuffer *message = athenaEthernet_Receive(linkData->athenaEthernet, -1, &events);
    if (message == NULL) {
        return NULL;
    }
    // Mark any pending events
    if (events) {
        athenaTransportLink_SetEvent(athenaTransportLink, events);
    }

    // Map the header
    struct ether_header *header = parcBuffer_Overlay(message, sizeof(struct ether_header));

    // If the destination does not match my address, drop the message
    if (memcmp(&linkData->link.myAddress, header->ether_dhost, ETHER_ADDR_LEN * sizeof(uint8_t)) != 0) {
        linkData->_stats.receive_NoLinkDestination++;
        parcBuffer_Release(&message);
        return NULL;
    }
    assertTrue(header->ether_type == htons(CCNX_ETHERTYPE), "Unexpected ether type %x", header->ether_type);

    // Set peerAddress from header source address
    *peerAddressLength = ETHER_ADDR_LEN * sizeof(uint8_t);
    memcpy(peerAddress, header->ether_shost, *peerAddressLength);

    parcBuffer_SetPosition(message, sizeof(struct ether_header));
    PARCBuffer *wireFormatBuffer = parcBuffer_Slice(message);
    parcBuffer_Release(&message);
    parcBuffer_SetPosition(wireFormatBuffer, 0);

    // Construct, and return a ccnxMetaMessage from the wire format buffer.
    ccnxMetaMessage = ccnxMetaMessage_CreateFromWireFormatBuffer(wireFormatBuffer);
    if (ccnxMetaMessage == NULL) {
        linkData->_stats.receive_DecodeFailed++;
        parcLog_Error(athenaTransportLink_GetLogger(athenaTransportLink), "Failed to decode message from received packet.");
    } else if (ccnxTlvDictionary_GetSchemaVersion(ccnxMetaMessage) == CCNxTlvDictionary_SchemaVersion_V0) {
        parcLog_Debug(athenaTransportLink_GetLogger(athenaTransportLink),
                      "received deprecated version %d message\n", ccnxTlvDictionary_GetSchemaVersion(ccnxMetaMessage));
    }
    parcBuffer_Release(&wireFormatBuffer);

    return ccnxMetaMessage;
}
LONGBOW_TEST_CASE(Global, ccnxMetaMessage_EncodeDecode)
{
    CCNxName *name = ccnxName_CreateFromCString("lci:/foo/bar");
    CCNxInterest *interest = ccnxInterest_CreateSimple(name);
    ccnxName_Release(&name);

    PARCSigner *signer = ccnxValidationCRC32C_CreateSigner(); // Would really be SHA256 or something.

    // Encode it to wire format.
    PARCBuffer *wireFormatBuffer = ccnxMetaMessage_CreateWireFormatBuffer(interest, signer);

    // Now decode it from wireformat.
    CCNxMetaMessage *decodedMessage = ccnxMetaMessage_CreateFromWireFormatBuffer(wireFormatBuffer);

    // At this point, the unpacked dictionary should be equivalent to the original interest.
    assertTrue(ccnxInterest_Equals(interest, decodedMessage), "Expected an equivalent interest to be unpacked");

    parcBuffer_Release(&wireFormatBuffer);
    ccnxInterest_Release(&interest);
    ccnxMetaMessage_Release(&decodedMessage);
    parcSigner_Release(&signer);
}
//
// Receive a message from the specified link.
//
static CCNxMetaMessage *
_UDPReceiveMessage(AthenaTransportLink *athenaTransportLink, struct sockaddr_in *peerAddress, socklen_t *peerAddressLength)
{
    struct _UDPLinkData *linkData = athenaTransportLink_GetPrivateData(athenaTransportLink);
    CCNxMetaMessage *ccnxMetaMessage = NULL;
    size_t messageLength;

    // If an MTU has been set, allocate a buffer of that size to avoid having to peek at the message,
    // othersize derive the link from the header and allocate a buffer based on the message size.

    if ((messageLength = linkData->link.mtu) == 0) {
        messageLength = _messageLengthFromHeader(athenaTransportLink, linkData);
        if (messageLength <= 0) {
            return NULL;
        }
    }

    PARCBuffer *wireFormatBuffer = parcBuffer_Allocate(messageLength);

    char *buffer = parcBuffer_Overlay(wireFormatBuffer, 0);
    *peerAddressLength = (socklen_t) sizeof(struct sockaddr_in);
    ssize_t readCount = recvfrom(linkData->fd, buffer, messageLength, 0, (struct sockaddr *) peerAddress, peerAddressLength);

    // On error, just return and retry.
    if (readCount == -1) {
        linkData->_stats.receive_ReadError++;
        parcLog_Debug(athenaTransportLink_GetLogger(athenaTransportLink), "read error (%s)", strerror(errno));
        parcBuffer_Release(&wireFormatBuffer);
        return NULL;
    }

    // A zero read means either no more data is currently available or our peer hungup.
    // Just return to retry as we'll detect EOF when we come back at the top of UDPReceive
    if (readCount == 0) {
        parcBuffer_Release(&wireFormatBuffer);
        return NULL;
    }

    // If it was it a short read just return to retry later.
    while (readCount < messageLength) {
        linkData->_stats.receive_ShortRead++;
        parcLog_Debug(athenaTransportLink_GetLogger(athenaTransportLink), "short read error (%s)", strerror(errno));
        parcBuffer_Release(&wireFormatBuffer);
        return NULL;
    }

    parcLog_Debug(athenaTransportLink_GetLogger(athenaTransportLink), "received message (size=%d)", readCount);
    parcBuffer_SetPosition(wireFormatBuffer, parcBuffer_Position(wireFormatBuffer) + readCount);
    parcBuffer_Flip(wireFormatBuffer);

    // Construct, and return a ccnxMetaMessage from the wire format buffer.
    ccnxMetaMessage = ccnxMetaMessage_CreateFromWireFormatBuffer(wireFormatBuffer);
    if (ccnxTlvDictionary_GetSchemaVersion(ccnxMetaMessage) == CCNxTlvDictionary_SchemaVersion_V0) {
        parcLog_Debug(athenaTransportLink_GetLogger(athenaTransportLink),
                      "received deprecated version %d message\n", ccnxTlvDictionary_GetSchemaVersion(ccnxMetaMessage));
    }
    if (ccnxMetaMessage == NULL) {
        linkData->_stats.receive_DecodeFailed++;
        parcLog_Error(athenaTransportLink_GetLogger(athenaTransportLink), "Failed to decode message from received packet.");
    }
    parcBuffer_Release(&wireFormatBuffer);

    return ccnxMetaMessage;
}