/*
 * Copyright (C) 2010 Apple Inc. All rights reserved.
 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "Connection.h"

#include "ArgumentEncoder.h"
#include "ProcessLauncher.h"
#include "WorkItem.h"
#include "SharedMemory.h"
#include "WebProcessProxy.h"
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <wtf/Assertions.h>

#if PLATFORM(QT)
#include <QApplication>
#include <QSocketNotifier>
#elif PLATFORM(GTK)
#include <glib.h>
#endif

using namespace std;

namespace CoreIPC {

static const size_t messageMaxSize = 4096;
static const size_t attachmentMaxAmount = 255;

enum {
    MessageBodyIsOOL = 1U << 31
};

class MessageInfo {
public:
    MessageInfo() { }

    MessageInfo(MessageID messageID, size_t bodySize, size_t initialAttachmentCount)
        : m_messageID(messageID.toInt())
        , m_bodySize(bodySize)
        , m_attachmentCount(initialAttachmentCount)
    {
        ASSERT(!(m_messageID & MessageBodyIsOOL));
    }

    void setMessageBodyOOL()
    {
        ASSERT(!isMessageBodyOOL());

        m_messageID |= MessageBodyIsOOL;
        m_attachmentCount++;
    }

    bool isMessageBodyOOL() const { return m_messageID & MessageBodyIsOOL; }

    size_t bodySize() const { return m_bodySize; }

    MessageID messageID() const { return MessageID::fromInt(m_messageID & ~MessageBodyIsOOL); }

    size_t attachmentCount() const { return m_attachmentCount; }

private:
    uint32_t m_messageID;
    size_t m_bodySize;
    size_t m_attachmentCount;
};

void Connection::platformInitialize(Identifier identifier)
{
    m_socketDescriptor = identifier;
    m_readBuffer.resize(messageMaxSize);
    m_currentMessageSize = 0;

#if PLATFORM(QT)
    m_socketNotifier = 0;
#endif
}

void Connection::platformInvalidate()
{
    if (m_socketDescriptor != -1)
        while (close(m_socketDescriptor) == -1 && errno == EINTR) { }

    if (!m_isConnected)
        return;

#if PLATFORM(GTK)
    m_connectionQueue.unregisterEventSourceHandler(m_socketDescriptor);
#endif

#if PLATFORM(QT)
    delete m_socketNotifier;
    m_socketNotifier = 0;
#endif

    m_socketDescriptor = -1;
    m_isConnected = false;
}

#if PLATFORM(QT)
class SocketNotifierResourceGuard {
public:
    SocketNotifierResourceGuard(QSocketNotifier* socketNotifier)
        : m_socketNotifier(socketNotifier)
    {
        m_socketNotifier->setEnabled(false);
    }

    ~SocketNotifierResourceGuard()
    {
        m_socketNotifier->setEnabled(true);
    }

private:
    QSocketNotifier* const m_socketNotifier;
};
#endif

template<class T, class iterator>
class AttachmentResourceGuard {
public:
    AttachmentResourceGuard(T& attachments)
        : m_attachments(attachments)
    {
    }
    ~AttachmentResourceGuard()
    {
        iterator end = m_attachments.end();
        for (iterator i = m_attachments.begin(); i != end; ++i)
            i->dispose();
    }
private:
    T& m_attachments;
};

void Connection::readyReadHandler()
{
    Deque<Attachment> attachments;
#if PLATFORM(QT)
    SocketNotifierResourceGuard socketNotifierEnabler(m_socketNotifier);
#endif
    AttachmentResourceGuard<Deque<Attachment>, Deque<Attachment>::iterator> attachementDisposer(attachments);

    OwnArrayPtr<char> attachmentDescriptorBuffer = adoptArrayPtr(new char[CMSG_SPACE(sizeof(int) * (attachmentMaxAmount))]);
    struct msghdr message;
    memset(&message, 0, sizeof(message));

    struct iovec iov[1];
    memset(&iov, 0, sizeof(iov));

    message.msg_control = attachmentDescriptorBuffer.get();
    message.msg_controllen = CMSG_SPACE(sizeof(int) * (attachmentMaxAmount));

    iov[0].iov_base = m_readBuffer.data();
    iov[0].iov_len = m_readBuffer.size();

    message.msg_iov = iov;
    message.msg_iovlen = 1;


    int messageLength = 0;
    while ((messageLength = recvmsg(m_socketDescriptor, &message, 0)) == -1) {
        if (errno != EINTR)
            return;
    }

    struct cmsghdr* controlMessage = CMSG_FIRSTHDR(&message);

    MessageInfo messageInfo;
    unsigned char* messageData = m_readBuffer.data();

    memcpy(&messageInfo, messageData, sizeof(messageInfo));
    ASSERT(messageLength == sizeof(messageInfo) + messageInfo.attachmentCount() * sizeof(size_t) + (messageInfo.isMessageBodyOOL() ? 0 : messageInfo.bodySize()));

    messageData += sizeof(messageInfo);

    RefPtr<WebKit::SharedMemory> oolMessageBody;

    if (messageInfo.attachmentCount()) {
        if (controlMessage && controlMessage->cmsg_level == SOL_SOCKET && controlMessage->cmsg_type == SCM_RIGHTS) {
            OwnArrayPtr<size_t> attachmentSizes = adoptArrayPtr(new size_t[messageInfo.attachmentCount()]);
            memcpy(attachmentSizes.get(), messageData, sizeof(size_t) * messageInfo.attachmentCount());

            messageData += sizeof(attachmentSizes);

            OwnArrayPtr<int> fileDescriptors = adoptArrayPtr(new int[messageInfo.attachmentCount()]);
            memcpy(fileDescriptors.get(), CMSG_DATA(controlMessage), sizeof(int) * messageInfo.attachmentCount());

            int attachmentCount = messageInfo.attachmentCount();

            if (messageInfo.isMessageBodyOOL())
                attachmentCount--;

            for (int i = 0; i < attachmentCount; ++i) {
                while (fcntl(fileDescriptors[i], F_SETFL, FD_CLOEXEC) == -1) {
                    if (errno != EINTR) {
                        ASSERT_NOT_REACHED();
                        return;
                    }
                }
            }

            for (int i = 0; i < attachmentCount; ++i)
                attachments.append(Attachment(fileDescriptors[i], attachmentSizes[i]));

            if (messageInfo.isMessageBodyOOL()) {
                ASSERT(messageInfo.bodySize());

                WebKit::SharedMemory::Handle handle;
                handle.adoptFromAttachment(fileDescriptors[attachmentCount], attachmentSizes[attachmentCount]);
                if (handle.isNull()) {
                    ASSERT_NOT_REACHED();
                    return;
                }

                oolMessageBody = WebKit::SharedMemory::create(handle, WebKit::SharedMemory::ReadOnly);
                if (!oolMessageBody) {
                    ASSERT_NOT_REACHED();
                    return;
                }
            }

            controlMessage = CMSG_NXTHDR(&message, controlMessage);
        } else {
            ASSERT_NOT_REACHED();
            return;
        }
    }

    ASSERT(attachments.size() == messageInfo.isMessageBodyOOL() ? messageInfo.attachmentCount() - 1 : messageInfo.attachmentCount());

    unsigned char* messageBody = messageData;

    if (messageInfo.isMessageBodyOOL())
        messageBody = reinterpret_cast<unsigned char*>(oolMessageBody->data());

    ArgumentDecoder* argumentDecoder;
    if (attachments.isEmpty())
        argumentDecoder = new ArgumentDecoder(messageBody, messageInfo.bodySize());
    else
        argumentDecoder = new ArgumentDecoder(messageBody, messageInfo.bodySize(), attachments);

    processIncomingMessage(messageInfo.messageID(), adoptPtr(argumentDecoder));

    ASSERT(!controlMessage);
}

bool Connection::open()
{
#if PLATFORM(QT)
    ASSERT(!m_socketNotifier);
#endif

    int flags = fcntl(m_socketDescriptor, F_GETFL, 0);
    while (fcntl(m_socketDescriptor, F_SETFL, flags | O_NONBLOCK) == -1) {
        if (errno != EINTR) {
            ASSERT_NOT_REACHED();
            return false;
        }
    }

    m_isConnected = true;
#if PLATFORM(QT)
    m_socketNotifier = m_connectionQueue.registerSocketEventHandler(m_socketDescriptor, QSocketNotifier::Read, WorkItem::create(this, &Connection::readyReadHandler));
#elif PLATFORM(GTK)
    m_connectionQueue.registerEventSourceHandler(m_socketDescriptor, (G_IO_HUP | G_IO_ERR), WorkItem::create(this, &Connection::connectionDidClose));
    m_connectionQueue.registerEventSourceHandler(m_socketDescriptor, G_IO_IN, WorkItem::create(this, &Connection::readyReadHandler));
#endif

    // Schedule a call to readyReadHandler. Data may have arrived before installation of the signal
    // handler.
    m_connectionQueue.scheduleWork(WorkItem::create(this, &Connection::readyReadHandler));

    return true;
}

bool Connection::platformCanSendOutgoingMessages() const
{
    return m_isConnected;
}

bool Connection::sendOutgoingMessage(MessageID messageID, PassOwnPtr<ArgumentEncoder> arguments)
{
#if PLATFORM(QT)
    ASSERT(m_socketNotifier);
#endif

    COMPILE_ASSERT(sizeof(MessageInfo) + attachmentMaxAmount * sizeof(size_t) <= messageMaxSize, AttachmentsFitToMessageInline);

    Vector<Attachment> attachments = arguments->releaseAttachments();
    AttachmentResourceGuard<Vector<Attachment>, Vector<Attachment>::iterator> attachementDisposer(attachments);

    if (attachments.size() > (attachmentMaxAmount - 1)) {
        ASSERT_NOT_REACHED();
        return false;
    }

    MessageInfo messageInfo(messageID, arguments->bufferSize(), attachments.size());
    size_t messageSizeWithBodyInline = sizeof(messageInfo) + (attachments.size() * sizeof(size_t)) + arguments->bufferSize();
    if (messageSizeWithBodyInline > messageMaxSize && arguments->bufferSize()) {
        RefPtr<WebKit::SharedMemory> oolMessageBody = WebKit::SharedMemory::create(arguments->bufferSize());
        if (!oolMessageBody)
            return false;

        WebKit::SharedMemory::Handle handle;
        if (!oolMessageBody->createHandle(handle, WebKit::SharedMemory::ReadOnly))
            return false;

        messageInfo.setMessageBodyOOL();

        memcpy(oolMessageBody->data(), arguments->buffer(), arguments->bufferSize());

        attachments.append(handle.releaseToAttachment());
    }

    struct msghdr message;
    memset(&message, 0, sizeof(message));

    struct iovec iov[3];
    memset(&iov, 0, sizeof(iov));

    message.msg_iov = iov;
    int iovLength = 1;

    iov[0].iov_base = reinterpret_cast<void*>(&messageInfo);
    iov[0].iov_len = sizeof(messageInfo);

    OwnArrayPtr<char> attachmentFDBuffer = adoptArrayPtr(new char[CMSG_SPACE(sizeof(int) * attachments.size())]);
    OwnArrayPtr<size_t> attachmentSizes = adoptArrayPtr(new size_t[attachments.size()]);

    if (!attachments.isEmpty()) {
        message.msg_control = attachmentFDBuffer.get();
        message.msg_controllen = sizeof(char) * CMSG_SPACE(sizeof(int) * attachments.size());

        struct cmsghdr* cmsg = CMSG_FIRSTHDR(&message);
        cmsg->cmsg_level = SOL_SOCKET;
        cmsg->cmsg_type = SCM_RIGHTS;
        cmsg->cmsg_len = CMSG_LEN(sizeof(int) * attachments.size());

        int* fdptr = reinterpret_cast<int*>(CMSG_DATA(cmsg));
        for (size_t i = 0; i < attachments.size(); ++i) {
            attachmentSizes[i] = attachments[i].size();
            fdptr[i] = attachments[i].fileDescriptor();
        }

        message.msg_controllen = cmsg->cmsg_len;

        iov[iovLength].iov_base = attachmentSizes.get();
        iov[iovLength].iov_len = sizeof(size_t) * attachments.size();
        ++iovLength;
    }

    if (!messageInfo.isMessageBodyOOL() && arguments->bufferSize()) {
        iov[iovLength].iov_base = reinterpret_cast<void*>(arguments->buffer());
        iov[iovLength].iov_len = arguments->bufferSize();
        ++iovLength;
    }

    message.msg_iovlen = iovLength;

    int bytesSent = 0;
    while ((bytesSent = sendmsg(m_socketDescriptor, &message, 0)) == -1) {
        if (errno != EINTR)
            return false;
    }
    return true;
}

#if PLATFORM(QT) || PLATFORM(GTK)
void Connection::setShouldCloseConnectionOnProcessTermination(WebKit::PlatformProcessIdentifier process)
{
    m_connectionQueue.scheduleWorkOnTermination(process, WorkItem::create(this, &Connection::connectionDidClose));
}
#endif

} // namespace CoreIPC
