//
// Copyright 2005 The Android Open Source Project
//
// Message stream abstraction.
//
#include "MessageStream.h"
#include "LogBundle.h"

#include "utils/Log.h"

#include <stdint.h>
#include <string.h>
#include <assert.h>

using namespace android;

/*
 * ===========================================================================
 *      Message
 * ===========================================================================
 */

/*
 * Send a blob of raw data.
 */
void Message::setRaw(const unsigned char* data, int len, Cleanup cleanup)
{
    reset();

    mData = const_cast<unsigned char*>(data);
    mLength = len;
    mCleanup = cleanup;
    mType = kTypeRaw;
}

/*
 * Send a "name=value" config pair.
 */
void Message::setConfig(const char* name, const char* value)
{
    reset();

    assert(name != NULL && value != NULL);

    int nlen = strlen(name) +1;
    int vlen = strlen(value) +1;
    mData = new unsigned char[nlen+vlen];
    mCleanup = kCleanupDelete;
    mLength = nlen + vlen;
    mType = kTypeConfig;

    memcpy(mData, name, nlen);
    memcpy(mData + nlen, value, vlen);
}

/*
 * Try to return the contents of the message as if it were a name/value pair.
 */
bool Message::getConfig(const char** pName, const char** pValue)
{
    if (mLength < 2)
        return false;
    assert(mData != NULL);

    *pName = (const char*) mData;
    *pValue = (const char*) (mData + strlen((char*)mData) +1);
    return true;
}

/*
 * Send a command/arg pair.
 */
void Message::setCommand(int cmd, int arg)
{
    reset();

    mData = new unsigned char[sizeof(int) * 2];
    mCleanup = kCleanupDelete;
    mLength = sizeof(int) * 2;
    mType = kTypeCommand;

    int* pInt = (int*) mData;
    pInt[0] = cmd;
    pInt[1] = arg;
}

/*
 * Send a command with 3 args instead of just one.
 */
void Message::setCommandExt(int cmd, int arg0, int arg1, int arg2)
{
    reset();

    mData = new unsigned char[sizeof(int) * 4];
    mCleanup = kCleanupDelete;
    mLength = sizeof(int) * 4;
    mType = kTypeCommandExt;

    int* pInt = (int*) mData;
    pInt[0] = cmd;
    pInt[1] = arg0;
    pInt[2] = arg1;
    pInt[3] = arg2;
}

/*
 * Try to return the contents of the message as if it were a "command".
 */
bool Message::getCommand(int* pCmd, int* pArg)
{
    if (mLength != sizeof(int) * 2) {
        LOG(LOG_WARN, "", "type is %d, len is %d\n", mType, mLength);
        return false;
    }
    assert(mData != NULL);

    const int* pInt = (const int*) mData;
    *pCmd = pInt[0];
    *pArg = pInt[1];

    return true;
}

/*
 * Serialize a log message.
 *
 * DO NOT call LOG() from here.
 */
void Message::setLogBundle(const android_LogBundle* pBundle)
{
    reset();

    /* get string lengths; we add one here to include the '\0' */
    int tagLen, msgLen;
    tagLen = strlen(pBundle->tag) + 1;
    size_t i;
    msgLen = 0;
    for (i=0; i<pBundle->msgCount; i++) msgLen += pBundle->msgVec[i].iov_len;
    msgLen += 1;

    /* set up the structure */
    mCleanup = kCleanupDelete;
    mLength =   sizeof(pBundle->when) +
                sizeof(pBundle->priority) +
                sizeof(pBundle->pid) +
                tagLen +
                msgLen;
    mData = new unsigned char[mLength];
    mType = kTypeLogBundle;

    unsigned char* pCur = mData;

    /* copy the stuff over */
    *((time_t*)pCur) = pBundle->when;
    pCur += sizeof(pBundle->when);
    *((android_LogPriority*)pCur) = pBundle->priority;
    pCur += sizeof(pBundle->priority);
    *((pid_t*)pCur) = pBundle->pid;
    pCur += sizeof(pBundle->pid);
    memcpy(pCur, pBundle->tag, tagLen);
    pCur += tagLen;
    for (i=0; i<pBundle->msgCount; i++) {
        memcpy(pCur, pBundle->msgVec[i].iov_base, pBundle->msgVec[i].iov_len);
        pCur += pBundle->msgVec[i].iov_len;
    }
    *pCur++ = 0;

    assert(pCur - mData == mLength);
}

/*
 * Extract the components of a log bundle.
 *
 * We're just returning points inside the message buffer, so the caller
 * will need to copy them out before the next reset().
 */
bool Message::getLogBundle(android_LogBundle* pBundle)
{
    if (mLength < (int)(sizeof(time_t) + sizeof(int)*2 + 4)) {
        LOG(LOG_WARN, "", "type is %d, len is %d, too small\n",
            mType, mLength);
        return false;
    }
    assert(mData != NULL);

    unsigned char* pCur = mData;

    pBundle->when = *((time_t*) pCur);
    pCur += sizeof(pBundle->when);
    pBundle->priority = *((android_LogPriority*) pCur);
    pCur += sizeof(pBundle->priority);
    pBundle->pid = *((pid_t*) pCur);
    pCur += sizeof(pBundle->pid);
    pBundle->tag = (const char*) pCur;
    pCur += strlen((const char*) pCur) +1;
    mVec.iov_base = (char*) pCur;
    mVec.iov_len = strlen((const char*) pCur);
    pBundle->msgVec = &mVec;
    pBundle->msgCount = 1;
    pCur += mVec.iov_len +1;

    if (pCur - mData != mLength) {
        LOG(LOG_WARN, "", "log bundle rcvd %d, used %d\n", mLength,
            (int) (pCur - mData));
        return false;
    }

    return true;
}

/*
 * Read the next event from the pipe.
 *
 * This is not expected to work well when multiple threads are reading.
 */
bool Message::read(Pipe* pPipe, bool wait)
{
    if (pPipe == NULL)
        return false;
    assert(pPipe->isCreated());

    if (!wait) {
        if (!pPipe->readReady())
            return false;
    }

    reset();

    unsigned char header[4];
    if (pPipe->read(header, 4) != 4)
        return false;

    mType = (MessageType) header[2];
    mLength = header[0] | header[1] << 8;
    mLength -= 2;   // we already read two of them in the header

    if (mLength > 0) {
        int actual;

        mData = new unsigned char[mLength];
        if (mData == NULL) {
            LOG(LOG_ERROR, "", "alloc failed\n");
            return false;
        }
        mCleanup = kCleanupDelete;

        actual = pPipe->read(mData, mLength);
        if (actual != mLength) {
            LOG(LOG_WARN, "", "failed reading message body (%d of %d bytes)\n",
                actual, mLength);
            return false;
        }
    }

    return true;
}

/*
 * Write this event to a pipe.
 *
 * It would be easiest to write the header and message body with two
 * separate calls, but that will occasionally fail on multithreaded
 * systems when the writes are interleaved.  We have to allocate a
 * temporary buffer, copy the data, and write it all at once.  This
 * would be easier with writev(), but we can't rely on having that.
 *
 * DO NOT call LOG() from here, as we could be in the process of sending
 * a log message.
 */
bool Message::write(Pipe* pPipe) const
{
    char tmpBuf[128];
    char* writeBuf = tmpBuf;
    bool result = false;
    int kHeaderLen = 4;

    if (pPipe == NULL)
        return false;
    assert(pPipe->isCreated());

    if (mData == NULL || mLength < 0)
        return false;

    /* if it doesn't fit in stack buffer, allocate space */
    if (mLength + kHeaderLen > (int) sizeof(tmpBuf)) {
        writeBuf = new char[mLength + kHeaderLen];
        if (writeBuf == NULL)
            goto bail;
    }

    /*
     * The current value of "mLength" does not include the 4-byte header.
     * Two of the 4 header bytes are included in the length we output
     * (the type byte and the pad byte), so we adjust mLength.
     */
    writeBuf[0] = (unsigned char) (mLength + kHeaderLen -2);
    writeBuf[1] = (unsigned char) ((mLength + kHeaderLen -2) >> 8);
    writeBuf[2] = (unsigned char) mType;
    writeBuf[3] = 0;
    if (mLength > 0)
        memcpy(writeBuf + kHeaderLen, mData, mLength);

    int actual;

    actual = pPipe->write(writeBuf, mLength + kHeaderLen);
    if (actual != mLength + kHeaderLen) {
        fprintf(stderr,
            "Message::write failed writing message body (%d of %d bytes)\n",
            actual, mLength + kHeaderLen);
        goto bail;
    }

    result = true;

bail:
    if (writeBuf != tmpBuf)
        delete[] writeBuf;
    return result;
}


/*
 * ===========================================================================
 *      MessageStream
 * ===========================================================================
 */

/*
 * Get ready to go.
 */
bool MessageStream::init(Pipe* readPipe, Pipe* writePipe, bool initiateHello)
{
    assert(mReadPipe == NULL && mWritePipe == NULL);    // only once

    /*
     * Swap "hello" messages.
     *
     * In a more robust implementation, this would include version numbers
     * and capability flags.
     */
    if (initiateHello) {
        int32_t data = kHelloMsg;
        Message msg;

        /* send hello */
        msg.setRaw((unsigned char*) &data, sizeof(data),
            Message::kCleanupNoDelete);
        if (!msg.write(writePipe)) {
            LOG(LOG_WARN, "", "hello write failed in stream init\n");
            return false;
        }

        LOG(LOG_DEBUG, "", "waiting for peer to ack my hello\n");

        /* wait for the ack */
        if (!msg.read(readPipe, true)) {
            LOG(LOG_WARN, "", "hello ack read failed in stream init\n");
            return false;
        }

        const int32_t* pAck;
        pAck = (const int32_t*) msg.getData();
        if (pAck == NULL || *pAck != kHelloAckMsg) {
            LOG(LOG_WARN, "", "hello ack was bad (%08x vs %08x)\n",
                *pAck, kHelloAckMsg);
            return false;
        }
    } else {
        int32_t data = kHelloAckMsg;
        Message msg;

        LOG(LOG_DEBUG, "", "waiting for hello from peer\n");

        /* wait for the hello */
        if (!msg.read(readPipe, true)) {
            LOG(LOG_WARN, "", "hello read failed in stream init\n");
            return false;
        }

        const int32_t* pAck;
        pAck = (const int32_t*) msg.getData();
        if (pAck == NULL || *pAck != kHelloMsg) {
            LOG(LOG_WARN, "", "hello was bad\n");
            return false;
        }

        /* send hello ack */
        msg.setRaw((unsigned char*) &data, sizeof(data),
            Message::kCleanupNoDelete);
        if (!msg.write(writePipe)) {
            LOG(LOG_WARN, "", "hello ack write failed in stream init\n");
            return false;
        }
    }

    /* success, set up our local stuff */
    mReadPipe = readPipe;
    mWritePipe = writePipe;

    //LOG(LOG_DEBUG, "", "init success\n");

    return true;
}

