// Copyright (C) 2018 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "host-common/HostGoldfishPipe.h"

#include "aemu/base/containers/Lookup.h"
#include "aemu/base/Result.h"
#include "android/crashreport/crash-handler.h"
#include "host-common/android_pipe_device.h"
#include "host-common/AndroidPipe.h"
#include "host-common/crash-handler.h"
#include "host-common/testing/TestVmLock.h"
#include "host-common/VmLock.h"

#include <algorithm>
#include <unordered_map>
#include <unordered_set>
#include <vector>

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

#ifdef _MSC_VER
#ifdef ERROR
#undef ERROR
#endif
#endif

#define HOST_PIPE_DEBUG 0

#if HOST_PIPE_DEBUG

#define HOST_PIPE_DLOG(fmt,...) fprintf(stderr, "%s:%d " fmt "\n", __func__, __LINE__, ##__VA_ARGS__);

#else

#define HOST_PIPE_DLOG(fmt,...)

#endif

using android::base::Result;
using android::base::Ok;
using android::base::Err;

namespace android {

const AndroidPipeHwFuncs HostGoldfishPipeDevice::HostHwPipe::vtbl = {
    &closeFromHostCallback,
    &signalWakeCallback,
    &getPipeIdCallback,
};

HostGoldfishPipeDevice::HostHwPipe::HostHwPipe(int fd) : vtblPtr(&vtbl), mFd(fd) {}

HostGoldfishPipeDevice::HostHwPipe::~HostHwPipe() {}

std::unique_ptr<HostGoldfishPipeDevice::HostHwPipe>
HostGoldfishPipeDevice::HostHwPipe::create(int fd) {
    return std::make_unique<HostHwPipe>(fd);
}

HostGoldfishPipeDevice::HostGoldfishPipeDevice() {}

HostGoldfishPipeDevice::~HostGoldfishPipeDevice() {
    clearLocked();  // no lock required in dctor
}

int HostGoldfishPipeDevice::getErrno() const {
    ScopedVmLock lock;
    return mErrno;
}

// Also opens.
int HostGoldfishPipeDevice::connect(const char* name) {
    const auto handshake = std::string("pipe:") + name;

    ScopedVmLock lock;
    const int hwPipeFd = ++mFdGenerator;
    std::unique_ptr<HostHwPipe> hwPipe = HostHwPipe::create(hwPipeFd);

    InternalPipe* hostPipe = static_cast<InternalPipe*>(
        android_pipe_guest_open(hwPipe.get(), nullptr));
    if (!hostPipe) {
        mErrno = ENOENT;
        return kNoFd;
    }

    const ssize_t len = static_cast<ssize_t>(handshake.size()) + 1;
    const ssize_t ret = writeInternal(&hostPipe, handshake.c_str(), len);

    if (ret == len) {
        HostHwPipe* hwPipeWeak = associatePipes(std::move(hwPipe), hostPipe);
        if (hwPipeWeak) {
            HOST_PIPE_DLOG("New pipe: service: for '%s', hwpipe: %p hostpipe: %p",
                           name, hwPipeWeak, hostPipe);

            return hwPipeWeak->getFd();
        } else {
            mErrno = ENOENT;
            return kNoFd;
        }
    } else {
        LOG(ERROR) << "Could not connect to goldfish pipe name: " << name;
        android_pipe_guest_close(hostPipe, PIPE_CLOSE_GRACEFUL);
        mErrno = EIO;
        return kNoFd;
    }
}

void HostGoldfishPipeDevice::close(const int fd) {
    HOST_PIPE_DLOG("Close fd=%d", fd);

    ScopedVmLock lock;
    if (!eraseFdInfo(fd)) {
        LOG(INFO) << "Could not close pipe, ENOENT.";
        mErrno = ENOENT;
    }
}

// Read/write/poll but for a particular pipe.
ssize_t HostGoldfishPipeDevice::read(const int fd, void* buffer, size_t len) {
    ScopedVmLock lock;

    FdInfo* fdInfo = lookupFdInfo(fd);
    if (!fdInfo) {
        LOG(ERROR) << "fd=" << fd << " is not a valid pipe fd";
        mErrno = EINVAL;
        return PIPE_ERROR_INVAL;
    }

    AndroidPipeBuffer buf = { static_cast<uint8_t*>(buffer), len };
    ssize_t res = android_pipe_guest_recv(fdInfo->hostPipe, &buf, 1);
    setErrno(res);
    return res;
}

HostGoldfishPipeDevice::ReadResult HostGoldfishPipeDevice::read(int fd, size_t maxLength) {
    std::vector<uint8_t> resultBuffer(maxLength);
    ssize_t read_size = read(fd, resultBuffer.data(), maxLength);

    if (read_size < 0) {
        return Err(mErrno);
    } else {
        if (read_size < maxLength) {
            resultBuffer.resize(read_size);
        }
        return Ok(resultBuffer);
    }
}

ssize_t HostGoldfishPipeDevice::write(const int fd, const void* buffer, size_t len) {
    ScopedVmLock lock;

    FdInfo* fdInfo = lookupFdInfo(fd);
    if (!fdInfo) {
        LOG(ERROR) << "fd=" << fd << " is not a valid pipe fd";
        mErrno = EINVAL;
        return PIPE_ERROR_INVAL;
    }

    return writeInternal(&fdInfo->hostPipe, buffer, len);
}

HostGoldfishPipeDevice::WriteResult
HostGoldfishPipeDevice::write(const int fd, const std::vector<uint8_t>& data) {
    ssize_t res = write(fd, data.data(), data.size());

    if (res < 0) {
        return Err(mErrno);
    } else {
        return Ok(res);
    }
}

unsigned HostGoldfishPipeDevice::poll(const int fd) const {
    ScopedVmLock lock;

    const FdInfo* fdInfo = lookupFdInfo(fd);
    if (!fdInfo) {
        LOG(ERROR) << "fd=" << fd << " is not a valid pipe fd";
        return 0;
    }

    return android_pipe_guest_poll(fdInfo->hostPipe);
}

void HostGoldfishPipeDevice::setWakeCallback(const int fd,
                                             std::function<void(int)> callback) {
    ScopedVmLock lock;

    FdInfo* fdInfo = lookupFdInfo(fd);
    if (fdInfo) {
        fdInfo->wakeCallback = std::move(callback);
    }
}

void* HostGoldfishPipeDevice::getHostPipe(const int fd) const {
    ScopedVmLock lock;

    const FdInfo* fdInfo = lookupFdInfo(fd);
    return fdInfo ? fdInfo->hostPipe : nullptr;
}

void HostGoldfishPipeDevice::saveSnapshot(base::Stream* stream) {
    HOST_PIPE_DLOG("Saving snapshot");

    auto cStream = reinterpret_cast<::Stream*>(stream);

    ScopedVmLock lock;

    android_pipe_guest_pre_save(cStream);
    stream_put_be32(cStream, mFdInfo.size());
    for (const auto& kv : mFdInfo) {
        HOST_PIPE_DLOG("save pipe: fd=%d hwPipe=%p hostPipe=%p",
                       kv.first, kv.second.hwPipe.get(), kv.second.hostPipe);
        stream_put_be32(cStream, kv.first);
        android_pipe_guest_save(kv.second.hostPipe, cStream);
    }
    android_pipe_guest_post_save(cStream);
}

void HostGoldfishPipeDevice::loadSnapshot(base::Stream* stream) {
    auto cStream = reinterpret_cast<::Stream*>(stream);

    ScopedVmLock lock;
    clearLocked();

    android_pipe_guest_pre_load(cStream);
    const uint32_t pipeCount = stream_get_be32(cStream);

    for (uint32_t i = 0; i < pipeCount; ++i) {
        const int fd = stream_get_be32(cStream);
        mFdGenerator = std::max(mFdGenerator, fd);

        std::unique_ptr<HostHwPipe> hwPipe = HostHwPipe::create(fd);

        HOST_PIPE_DLOG("attempt to load a host pipe");

        char forceClose = 0;
        InternalPipe* hostPipe = static_cast<InternalPipe*>(
            android_pipe_guest_load(cStream, hwPipe.get(), &forceClose));

        if (!forceClose) {
            HostHwPipe* hwPipePtr = associatePipes(std::move(hwPipe), hostPipe);
            HOST_PIPE_DLOG("Successfully loaded host pipe %p for hw pipe %p",
                           hostPipe, hwPipePtr);
        } else {
            HOST_PIPE_DLOG("Failed to load host pipe for hw pipe %p", hwpipe);
            LOG(ERROR) << "Could not load goldfish pipe";
            mErrno = EIO;
            return;
        }
    }

    android_pipe_guest_post_load(cStream);
}

void HostGoldfishPipeDevice::saveSnapshot(base::Stream* stream, const int fd) {
    HOST_PIPE_DLOG("Saving snapshot for fd=%d", fd);

    ScopedVmLock lock;
    FdInfo* fdInfo = lookupFdInfo(fd);
    if (!fdInfo) {
        crashhandler_die_format("%s:%d fd=%d is a valid pipe fd",
                                __func__, __LINE__, fd);
    }

    auto cStream = reinterpret_cast<::Stream*>(stream);

    android_pipe_guest_pre_save(cStream);

    stream_put_be32(cStream, 1);
    stream_put_be32(cStream, fd);
    android_pipe_guest_save(fdInfo->hostPipe, cStream);
    android_pipe_guest_post_save(cStream);
}

int HostGoldfishPipeDevice::loadSnapshotSinglePipe(base::Stream* stream) {
    HOST_PIPE_DLOG("Loading snapshot for a single pipe");

    auto cStream = reinterpret_cast<::Stream*>(stream);

    ScopedVmLock lock;

    android_pipe_guest_pre_load(cStream);
    uint32_t pipeCount = stream_get_be32(cStream);

    if (pipeCount != 1) {
        LOG(ERROR) << "Invalid pipe count from stream";
        mErrno = EIO;
        return kNoFd;
    }

    const int fd = stream_get_be32(cStream);
    eraseFdInfo(fd);

    HOST_PIPE_DLOG("attempt to load a host pipe");

    std::unique_ptr<HostHwPipe> hwPipe = HostHwPipe::create(fd);

    char forceClose = 0;
    InternalPipe* hostPipe = static_cast<InternalPipe*>(
        android_pipe_guest_load(cStream, hwPipe.get(), &forceClose));
    if (!forceClose) {
        associatePipes(std::move(hwPipe), hostPipe);
        HOST_PIPE_DLOG("Successfully loaded host pipe %p for fd=%d", hostPipe, fd);
    } else {
        HOST_PIPE_DLOG("Failed to load host pipe for hw pipe %p", hwPipeFromStream);
        LOG(ERROR) << "Could not load goldfish pipe";
        mErrno = EIO;
    }

    android_pipe_guest_post_load(cStream);

    return fd;
}

void HostGoldfishPipeDevice::clear() {
    ScopedVmLock lock;
    clearLocked();
}

void HostGoldfishPipeDevice::initialize() {
    if (mInitialized) return;
    AndroidPipe::Service::resetAll();
    AndroidPipe::initThreading(android::HostVmLock::getInstance());
    mInitialized = true;
}

// locked
void HostGoldfishPipeDevice::clearLocked() {
    while (true) {
        const auto i = mFdInfo.begin();
        if (i == mFdInfo.end()) {
            break;
        } else {
            eraseFdInfo(i->first);
        }
    }
}

// locked
HostGoldfishPipeDevice::FdInfo* HostGoldfishPipeDevice::lookupFdInfo(int fd) {
    const auto i = mFdInfo.find(fd);
    return (i == mFdInfo.end()) ? nullptr : &i->second;
}

// locked
const HostGoldfishPipeDevice::FdInfo* HostGoldfishPipeDevice::lookupFdInfo(int fd) const {
    const auto i = mFdInfo.find(fd);
    return (i == mFdInfo.end()) ? nullptr : &i->second;
}

// locked
HostGoldfishPipeDevice::HostHwPipe*
HostGoldfishPipeDevice::associatePipes(std::unique_ptr<HostGoldfishPipeDevice::HostHwPipe> hwPipe,
                                       HostGoldfishPipeDevice::InternalPipe* hostPipe) {
    HostHwPipe* hwPipePtr = hwPipe.get();
    const int hwPipeFd = hwPipePtr->getFd();

    mPipeToHwPipe[hostPipe] = hwPipePtr;

    FdInfo info;
    info.hwPipe = std::move(hwPipe);
    info.hostPipe = hostPipe;

    if (!mFdInfo.insert({hwPipeFd, std::move(info)}).second) {
        crashhandler_die_format("%s:%d hwPipeFd=%d already exists",
                        __func__, __LINE__, hwPipeFd);
    }

    return hwPipePtr;
}

// locked
bool HostGoldfishPipeDevice::eraseFdInfo(const int fd) {
    const auto i = mFdInfo.find(fd);
    if (i == mFdInfo.end()) {
        return false;
    }

    if (mPipeToHwPipe.erase(i->second.hostPipe) == 0) {
        crashhandler_die_format("%s:%d hostPipe=%p does not exit while fd=%d exists",
                                __func__, __LINE__, i->second.hostPipe, fd);
    }

    android_pipe_guest_close(i->second.hostPipe, PIPE_CLOSE_GRACEFUL);
    mFdInfo.erase(i);

    return true;
}

ssize_t HostGoldfishPipeDevice::writeInternal(InternalPipe** ppipe,
                                              const void* buffer,
                                              size_t len) {
    AndroidPipeBuffer buf = {(uint8_t*)buffer, len};
    ssize_t res = android_pipe_guest_send((void**)ppipe, &buf, 1);
    setErrno(res);
    return res;
}

void HostGoldfishPipeDevice::setErrno(ssize_t res) {
    if (res >= 0) return;
    switch (res) {
        case PIPE_ERROR_INVAL:
            mErrno = EINVAL;
            break;
        case PIPE_ERROR_AGAIN:
            mErrno = EAGAIN;
            break;
        case PIPE_ERROR_NOMEM:
            mErrno = ENOMEM;
            break;
        case PIPE_ERROR_IO:
            mErrno = EIO;
            break;
    }
}

void HostGoldfishPipeDevice::signalWake(const int fd, const int wakes) {
    ScopedVmLock lock;
    const FdInfo* fdInfo = lookupFdInfo(fd);
    if (fdInfo) {
        fdInfo->wakeCallback(wakes);
    }
}

static HostGoldfishPipeDevice* sDevice() {
    static HostGoldfishPipeDevice* d = new HostGoldfishPipeDevice;
    return d;
}

// static
HostGoldfishPipeDevice* HostGoldfishPipeDevice::get() {
    auto res = sDevice();
    // Must be separate from construction
    // as some initialization routines require
    // the instance to be constructed.
    res->initialize();
    return res;
}

// Callbacks for AndroidPipeHwFuncs.
// static
void HostGoldfishPipeDevice::closeFromHostCallback(void* hwPipeRaw) {
    const int fd = static_cast<const HostHwPipe*>(hwPipeRaw)->getFd();

    // PIPE_WAKE_CLOSED gets translated to closeFromHostCallback.
    // To simplify detecting a close-from-host, signal a wake callback so that
    // the event can be detected.
    HostGoldfishPipeDevice::get()->signalWake(fd, PIPE_WAKE_CLOSED);
    HostGoldfishPipeDevice::get()->close(fd);
}

// static
void HostGoldfishPipeDevice::signalWakeCallback(void* hwPipeRaw, unsigned wakes) {
    const int fd = static_cast<const HostHwPipe*>(hwPipeRaw)->getFd();
    HostGoldfishPipeDevice::get()->signalWake(fd, wakes);
}

// static
int HostGoldfishPipeDevice::getPipeIdCallback(void* hwPipeRaw) {
    return static_cast<const HostHwPipe*>(hwPipeRaw)->getFd();
}

} // namespace android
