LONGBOW_TEST_CASE(Global, metisMessage_ReadFromBuffer) { char message_str[] = "\x00Once upon a time, in a stack far away, a dangling pointer found its way to the top of the heap."; PARCEventBuffer *buff = parcEventBuffer_Create(); parcEventBuffer_Append(buff, message_str, sizeof(message_str)); PARCLogReporter *reporter = parcLogReporterTextStdout_Create(); MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); parcLogReporter_Release(&reporter); MetisMessage *message = metisMessage_ReadFromBuffer(1, 2, buff, sizeof(message_str), logger); metisLogger_Release(&logger); assertNotNull(message, "Got null from metisMessage_CreateFromBuffer"); assertTrue(parcEventBuffer_GetLength(message->messageBytes) == sizeof(message_str), "Length of internal buffer wrong, expected %zu got %zu", sizeof(message_str), parcEventBuffer_GetLength(message->messageBytes)); uint8_t *p = parcEventBuffer_Pullup(message->messageBytes, sizeof(message_str)); assertTrue(memcmp(p, message_str, sizeof(message_str)) == 0, "Internal buffer contents does not match test"); assertTrue(message->ingressConnectionId == 1, "IngressConnectionId wrong, expected %d got %u", 1, message->ingressConnectionId); assertTrue(message->receiveTime == 2, "receiveTime wrong, expected %u got %" PRIu64, 2, message->receiveTime); assertTrue(parcEventBuffer_GetLength(buff) == 0, "Origin buffer not drained, expected 0, got %zu", parcEventBuffer_GetLength(buff)); metisMessage_Release(&message); parcEventBuffer_Destroy(&buff); }
/** * @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 _metisCliSession_ReadCallback(PARCEventQueue *event, PARCEventType type, void *cliSessionVoid) { assertTrue(type == PARCEventType_Read, "Illegal type: expected read event, got %d\n", type); _MetisCommandLineInterface_Session *session = (_MetisCommandLineInterface_Session *) cliSessionVoid; PARCEventBuffer *input = parcEventBuffer_GetQueueBufferInput(event); while (parcEventBuffer_GetLength(input) > 0) { size_t readLength = 0; char *cmdline = parcEventBuffer_ReadLine(input, &readLength); if (cmdline == NULL) { // we have run out of input, we're done parcEventBuffer_Destroy(&input); return; } // we have a whole command line bool success = _metisCliSession_ProcessCommand(session, cmdline); parcEventBuffer_FreeLine(input, &cmdline); if (!success) { // the session is dead parcEventBuffer_Destroy(&input); return; } _metisCliSession_DisplayPrompt(session); } parcEventBuffer_Destroy(&input); }
/** * Parses the work buffer to extract packets * * The work buffer should be filled in with a set of tuples (bh_hdrlen, frame, pad). * The pad extends each packet out to BPF_WORDALIGN. * * If the CCNxMessage PacketLength says it is larger than the read capture length (caplen), * then this is an invalid packet and it will be discarded. This error will result in a * ReadWorkBufferResult_TryAgain condition. * * struct bpf_hdr { * struct BPF_TIMEVAL bh_tstamp; // time stamp * bpf_u_int32 bh_caplen; // length of captured portion * bpf_u_int32 bh_datalen; // original length of packet * u_short bh_hdrlen; // length of bpf header (this struct plus alignment padding) * } * * @param [in] ether An allocated Darwin ethernet. * @param [in] readbuffer A user-provided read buffer. * * @retval ReadWorkBufferResult_Ok A frame was moved from workbuffer to readbuffer * @retval ReadWorkBufferResult_Empty There's not enough bytes in the workbuffer * @retval ReadWorkBufferResult_TryAgain (likely discard) caused this call to fail, but you should try again * * Example: * @code * <#example#> * @endcode */ static _ReadWorkBufferResult _darwinEthernet_ReadWorkBuffer(MetisGenericEther *ether, PARCEventBuffer *readbuffer) { _ReadWorkBufferResult result = ReadWorkBufferResult_Empty; // Make sure we have linear memory for the BPF header struct bpf_hdr *bpfHeader = (struct bpf_hdr *) parcEventBuffer_Pullup(ether->workBuffer, sizeof(struct bpf_hdr)); // make sure we have enough bytes to process the frame // bpfHeader may be NULL if there are not sizeof(struct bpf_hdr) bytes available. if (bpfHeader && parcEventBuffer_GetLength(ether->workBuffer) >= bpfHeader->bh_hdrlen + bpfHeader->bh_caplen) { // (0) Save the needed fields from the bpf header // (1) pop off the bpf header // (2) move the iovec from work buffer to readBuffer. // (3) remove any BPF_WORDALIGN padding to the start of the next packet // (0) Save the needed fields from the bpf header uint16_t hdrlen = bpfHeader->bh_hdrlen; uint32_t caplen = bpfHeader->bh_caplen; // (1) pop off the bpf header parcEventBuffer_Read(ether->workBuffer, NULL, hdrlen); // (1a) Determine the packet length from the fixed header and only transfer that many bytes uint16_t packetlen = _getFrameLengthFromWorkBuffer(ether); if (packetlen <= caplen) { // (2) move the iovec from work buffer to readBuffer. parcEventBuffer_ReadIntoBuffer(ether->workBuffer, readbuffer, packetlen); // (2a) drain off any trailer (i.e. FCS) parcEventBuffer_Read(ether->workBuffer, NULL, caplen - packetlen); result = ReadWorkBufferResult_Ok; } else { if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning)) { metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, "%s reading fd %d discard packetlen %u greater than caplen %u", __func__, ether->etherSocket, packetlen, caplen); } // discard all of caplen parcEventBuffer_Read(ether->workBuffer, NULL, caplen); // tell the caller that this read failed, but they could try again. result = ReadWorkBufferResult_TryAgain; } // (3) remove any BPF_WORDALIGN padding to the start of the next packet size_t alignedLength = BPF_WORDALIGN(hdrlen + caplen); size_t pad = alignedLength - hdrlen - caplen; parcEventBuffer_Read(ether->workBuffer, NULL, pad); } return result; }
bool metisGenericEther_SendFrame(MetisGenericEther *ether, PARCEventBuffer *buffer) { assertNotNull(ether, "Parameter ether must be non-null"); size_t length = parcEventBuffer_GetLength(buffer); int written = parcEventBuffer_WriteToFileDescriptor(buffer, ether->etherSocket, -1); if (written == length) { return true; } return false; }
/* * Reading a BPF packet will include the BPF header. Frame may include the FCS. */ bool metisGenericEther_ReadNextFrame(MetisGenericEther *ether, PARCEventBuffer *readbuffer) { assertNotNull(ether, "Parameter ether must be non-null"); assertNotNull(readbuffer, "Parameter readbuffer must be non-null"); bool success = false; if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, "Workbuffer length %zu", __func__, parcEventBuffer_GetLength(ether->workBuffer)); } // Do we already have something in our work buffer? If not, try to read something. if (parcEventBuffer_GetLength(ether->workBuffer) == 0) { _darwinEthernet_ReadSocket(ether); } success = _darwinEthernet_WorkBufferToReadBuffer(ether, readbuffer); return success; }
TransportMessage * rtaComponent_GetMessage(PARCEventQueue *queue) { PARCEventBuffer *in = parcEventBuffer_GetQueueBufferInput(queue); while (parcEventBuffer_GetLength(in) >= sizeof(TransportMessage *)) { ssize_t len; TransportMessage *tm; RtaConnection *conn; len = parcEventBuffer_Read(in, (void *)&tm, sizeof(&tm)); assertTrue(len == sizeof(TransportMessage *), "parcEventBuffer_Read returned error"); // Is the transport message for an open connection? conn = rtaConnection_GetFromTransport(tm); assertNotNull(conn, "%s GetInfo returnd null connection\n", __func__); if (DEBUG_OUTPUT) { printf("%s queue %-12s tm %p\n", __func__, rtaProtocolStack_GetQueueName(rtaConnection_GetStack(conn), queue), (void *) tm); } (void) rtaConnection_DecrementMessagesInQueue(conn); if (rtaConnection_GetState(conn) != CONN_CLOSED) { parcEventBuffer_Destroy(&in); return tm; } // it's a closed connection if (DEBUG_OUTPUT) { printf("%s clearing connection %p reference in transport\n", __func__, (void *) conn); } // should increment a drop counter (case 908) transportMessage_Destroy(&tm); } parcEventBuffer_Destroy(&in); return NULL; }
/** * @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; }
/** * @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; }