root/src/kits/debug/DebugEventStream.cpp
/*
 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */

#include <DebugEventStream.h>

#include <stdlib.h>
#include <string.h>

#include <DataIO.h>

#include <system_profiler_defs.h>


#define INPUT_BUFFER_SIZE       (128 * 1024)


BDebugEventInputStream::BDebugEventInputStream()
        :
        fStream(NULL),
        fFlags(0),
        fEventMask(0),
        fBuffer(NULL),
        fBufferCapacity(0),
        fBufferSize(0),
        fBufferPosition(0),
        fStreamPosition(0),
        fOwnsBuffer(false)
{
}


BDebugEventInputStream::~BDebugEventInputStream()
{
        Unset();

        if (fOwnsBuffer)
                free(fBuffer);
}


status_t
BDebugEventInputStream::SetTo(BDataIO* stream)
{
        Unset();

        // set the new values
        if (stream == NULL)
                return B_BAD_VALUE;

        fStream = stream;

        // allocate a buffer
        if (fBuffer == NULL) {
                fBuffer = (uint8*)malloc(INPUT_BUFFER_SIZE);
                if (fBuffer == NULL) {
                        Unset();
                        return B_NO_MEMORY;
                }

                fOwnsBuffer = true;
                fBufferCapacity = INPUT_BUFFER_SIZE;
                fBufferSize = 0;
        }

        return _Init();
}


status_t
BDebugEventInputStream::SetTo(const void* data, size_t size,
        bool takeOverOwnership)
{
        Unset();

        if (data == NULL || size == 0)
                return B_BAD_VALUE;

        if (fBuffer != NULL) {
                if (fOwnsBuffer)
                        free(fBuffer);
                fBuffer = NULL;
                fBufferCapacity = 0;
                fBufferSize = 0;
        }

        fBuffer = (uint8*)data;
        fBufferCapacity = fBufferSize = size;
        fOwnsBuffer = takeOverOwnership;

        return _Init();
}


void
BDebugEventInputStream::Unset()
{
        fStream = NULL;
        fFlags = 0;
        fEventMask = 0;

        // If we have a buffer that we own and has the right size, we keep it.
        if (fOwnsBuffer) {
                if (fBuffer != NULL && fBufferSize != INPUT_BUFFER_SIZE) {
                        free(fBuffer);
                        fBuffer = NULL;
                        fBufferCapacity = 0;
                        fBufferSize = 0;
                }
        } else {
                fBuffer = NULL;
                fBufferCapacity = 0;
                fBufferSize = 0;
        }
}


status_t
BDebugEventInputStream::Seek(off_t streamOffset)
{
        // TODO: Support for streams, at least for BPositionIOs.
        if (fStream != NULL)
                return B_UNSUPPORTED;

        if (streamOffset < 0 || streamOffset > (off_t)fBufferCapacity)
                return B_BUFFER_OVERFLOW;

        fStreamPosition = 0;
        fBufferPosition = streamOffset;
        fBufferSize = fBufferCapacity - streamOffset;

        return B_OK;
}


/*!     \brief Returns the next event in the stream.

        At the end of the stream \c 0 is returned and \c *_buffer is set to \c NULL.
        For events that don't have data associated with them, \c *_buffer will still
        be non-NULL, even if dereferencing that address is not allowed.

        \param _event Pointer to a pre-allocated location where the event ID shall
                be stored.
        \param _cpu Pointer to a pre-allocated location where the CPU index shall
                be stored.
        \param _buffer Pointer to a pre-allocated location where the pointer to the
                event data shall be stored.
        \param _streamOffset Pointer to a pre-allocated location where the event
                header's offset relative to the beginning of the stream shall be stored.
                May be \c NULL.
        \return A negative error code in case an error occurred while trying to read
                the info, the size of the data associated with the event otherwise.
*/
ssize_t
BDebugEventInputStream::ReadNextEvent(uint32* _event, uint32* _cpu,
        const void** _buffer, off_t* _streamOffset)
{
        // get the next header
        status_t error = _GetData(sizeof(system_profiler_event_header));
        if (error != B_OK) {
                if (error == B_BAD_DATA && fBufferSize == 0) {
                        *_buffer = NULL;
                        return 0;
                }
                return error;
        }

        system_profiler_event_header header
                = *(system_profiler_event_header*)(fBuffer + fBufferPosition);

        off_t streamOffset = fStreamPosition + fBufferPosition;

        // skip the header in the buffer
        fBufferSize -= sizeof(system_profiler_event_header);
        fBufferPosition += sizeof(system_profiler_event_header);

        // get the data
        if (header.size > 0) {
                error = _GetData(header.size);
                if (error != B_OK)
                        return error;
        }

        *_event = header.event;
        *_cpu = header.cpu;
        *_buffer = fBuffer + fBufferPosition;
        if (_streamOffset)
                *_streamOffset = streamOffset;

        // skip the event in the buffer
        fBufferSize -= header.size;
        fBufferPosition += header.size;

        return header.size;
}


status_t
BDebugEventInputStream::_Init()
{
        fStreamPosition = 0;
        fBufferPosition = 0;

        // get the header
        status_t error = _GetData(sizeof(debug_event_stream_header));
        if (error != B_OK) {
                Unset();
                return error;
        }
        const debug_event_stream_header& header
                = *(const debug_event_stream_header*)(fBuffer + fBufferPosition);

        fBufferPosition += sizeof(debug_event_stream_header);
        fBufferSize -= sizeof(debug_event_stream_header);

        // check the header
        if (strncmp(header.signature, B_DEBUG_EVENT_STREAM_SIGNATURE,
                        sizeof(header.signature)) != 0
                || header.version != B_DEBUG_EVENT_STREAM_VERSION
                || (header.flags & B_DEBUG_EVENT_STREAM_FLAG_HOST_ENDIAN) == 0) {
                // TODO: Support non-host endianess!
                Unset();
                return B_BAD_DATA;
        }

        fFlags = header.flags;
        fEventMask = header.event_mask;

        return B_OK;
}


ssize_t
BDebugEventInputStream::_Read(void* _buffer, size_t size)
{
        uint8* buffer = (uint8*)_buffer;
        size_t totalBytesRead = 0;
        ssize_t bytesRead = 0;

        while (size > 0 && (bytesRead = fStream->Read(buffer, size)) > 0) {
                totalBytesRead += bytesRead;
                buffer += bytesRead;
                size -= bytesRead;
        }

        if (bytesRead < 0)
                return bytesRead;

        return totalBytesRead;
}


status_t
BDebugEventInputStream::_GetData(size_t size)
{
        if (fBufferSize >= size)
                return B_OK;

        if (size > fBufferCapacity)
                return B_BUFFER_OVERFLOW;

        // move remaining data to the start of the buffer
        if (fBufferSize > 0 && fBufferPosition > 0)
                memmove(fBuffer, fBuffer + fBufferPosition, fBufferSize);
        fStreamPosition += fBufferPosition;
        fBufferPosition = 0;

        // read more data
        if (fStream != NULL) {
                ssize_t bytesRead = _Read(fBuffer + fBufferSize,
                        fBufferCapacity - fBufferSize);
                if (bytesRead < 0)
                        return bytesRead;

                fBufferSize += bytesRead;
        }

        return fBufferSize >= size ? B_OK : B_BAD_DATA;
}


// #pragma mark - BDebugEventOutputStream


BDebugEventOutputStream::BDebugEventOutputStream()
        :
        fStream(NULL),
        fFlags(0)
{
}


BDebugEventOutputStream::~BDebugEventOutputStream()
{
        Unset();
}


status_t
BDebugEventOutputStream::SetTo(BDataIO* stream, uint32 flags, uint32 eventMask)
{
        Unset();

        // set the new values
        if (stream == NULL)
                return B_BAD_VALUE;

        fStream = stream;
        fFlags = /*(flags & B_DEBUG_EVENT_STREAM_FLAG_ZIPPED)
                |*/ B_DEBUG_EVENT_STREAM_FLAG_HOST_ENDIAN;
                // TODO: Support zipped data!

        // init and write the header
        debug_event_stream_header header;
        memset(header.signature, 0, sizeof(header.signature));
        strlcpy(header.signature, B_DEBUG_EVENT_STREAM_SIGNATURE,
                sizeof(header.signature));
        header.version = B_DEBUG_EVENT_STREAM_VERSION;
        header.flags = fFlags;
        header.event_mask = eventMask;

        ssize_t written = fStream->Write(&header, sizeof(header));
        if (written < 0) {
                Unset();
                return written;
        }
        if ((size_t)written != sizeof(header)) {
                Unset();
                return B_FILE_ERROR;
        }

        return B_OK;
}


void
BDebugEventOutputStream::Unset()
{
        Flush();
        fStream = NULL;
        fFlags = 0;
}


status_t
BDebugEventOutputStream::Write(const void* buffer, size_t size)
{
        if (size == 0)
                return B_OK;
        if (buffer == NULL)
                return B_BAD_VALUE;

        ssize_t written = fStream->Write(buffer, size);
        if (written < 0)
                return written;
        if ((size_t)written != size)
                return B_FILE_ERROR;

        return B_OK;
}


status_t
BDebugEventOutputStream::Flush()
{
        return B_OK;
}