/*
 * Copyright 2020, 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.
 */

#pragma once

#include <TrustyIpc.h>

#include <android-base/logging.h>
#include <android-base/unique_fd.h>
#include <errno.h>
#include <poll.h>
#include <stdio.h>
#include <sys/eventfd.h>
#include <sys/stat.h>
#include <teeui/msg_formatting.h>
#include <trusty/tipc.h>
#include <unistd.h>

#include <fstream>
#include <functional>
#include <future>
#include <iostream>
#include <sstream>
#include <thread>
#include <vector>

#define AT __FILE__ ":" << __LINE__ << ": "

namespace android {
namespace trusty {
namespace confirmationui {

using ::teeui::Message;
using ::teeui::msg2tuple_t;
using ::teeui::ReadStream;
using ::teeui::WriteStream;

#ifndef TEEUI_USE_STD_VECTOR
/*
 * TEEUI_USE_STD_VECTOR makes certain wire types like teeui::MsgString and
 * teeui::MsgVector be aliases for std::vector. This is required for thread safe
 * message serialization. Always compile this with -DTEEUI_USE_STD_VECTOR set in
 * CFLAGS of the HAL service.
 */
#error "Must be compiled with -DTEEUI_USE_STD_VECTOR."
#endif

enum class TrustyAppError : int32_t {
    OK,
    ERROR = -1,
    MSG_TOO_LONG = -2,
};

class TrustyApp {
  private:
    android::base::unique_fd handle_;
    void* shm_base_;
    size_t shm_len_;
    static constexpr const int kInvalidHandle = -1;
    /*
     * This mutex serializes communication with the trusted app, not handle_.
     * Calling issueCmd during construction or deletion is undefined behavior.
     */
    std::mutex mutex_;

  public:
    TrustyApp(const std::string& path, const std::string& appname);
    ~TrustyApp();

    ssize_t TrustyRpc(const uint8_t* obegin, const uint8_t* oend, uint8_t* ibegin, uint8_t* iend);

    template <typename Request, typename Response, typename... T>
    std::tuple<TrustyAppError, msg2tuple_t<Response>> issueCmd(const T&... args) {
        std::lock_guard<std::mutex> lock(mutex_);

        if (handle_ == kInvalidHandle) {
            LOG(ERROR) << "TrustyApp not connected";
            return {TrustyAppError::ERROR, {}};
        }

        uint8_t buffer[CONFIRMATIONUI_MAX_MSG_SIZE];
        WriteStream out(buffer);

        out = write(Request(), out, args...);
        if (!out) {
            LOG(ERROR) << AT << "send command failed: message formatting";
            return {TrustyAppError::MSG_TOO_LONG, {}};
        }

        auto rc = TrustyRpc(&buffer[0], const_cast<const uint8_t*>(out.pos()), &buffer[0],
                            &buffer[CONFIRMATIONUI_MAX_MSG_SIZE]);
        if (rc < 0) return {TrustyAppError::ERROR, {}};

        ReadStream in(&buffer[0], rc);
        auto result = read(Response(), in);
        if (!std::get<0>(result)) {
            LOG(ERROR) << "send command failed: message parsing";
            return {TrustyAppError::ERROR, {}};
        }

        return {std::get<0>(result) ? TrustyAppError::OK : TrustyAppError::ERROR,
                tuple_tail(std::move(result))};
    }

    template <typename Request, typename... T> TrustyAppError issueCmd(const T&... args) {
        std::lock_guard<std::mutex> lock(mutex_);

        if (handle_ == kInvalidHandle) {
            LOG(ERROR) << "TrustyApp not connected";
            return TrustyAppError::ERROR;
        }

        uint8_t buffer[CONFIRMATIONUI_MAX_MSG_SIZE];
        WriteStream out(buffer);

        out = write(Request(), out, args...);
        if (!out) {
            LOG(ERROR) << AT << "send command failed: message formatting";
            return TrustyAppError::MSG_TOO_LONG;
        }

        auto rc = TrustyRpc(&buffer[0], const_cast<const uint8_t*>(out.pos()), &buffer[0],
                            &buffer[CONFIRMATIONUI_MAX_MSG_SIZE]);
        if (rc < 0) {
            LOG(ERROR) << "send command failed: " << strerror(errno) << " (" << errno << ")";
            return TrustyAppError::ERROR;
        }

        if (rc > 0) {
            LOG(ERROR) << "Unexpected non zero length response";
            return TrustyAppError::ERROR;
        }
        return TrustyAppError::OK;
    }

    operator bool() const { return handle_ != kInvalidHandle; }
};

}  // namespace confirmationui
}  // namespace trusty
}  // namespace android
