/*
 * Copyright (C) 2022 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 <gtest/gtest.h>

#include "binderRpcTestCommon.h"

#define EXPECT_OK(status)                        \
    do {                                         \
        android::binder::Status stat = (status); \
        EXPECT_TRUE(stat.isOk()) << stat;        \
    } while (false)

namespace android {

// Abstract base class with a virtual destructor that handles the
// ownership of a process session for BinderRpcTestSession below
class ProcessSession {
public:
    struct SessionInfo {
        sp<RpcSession> session;
        sp<IBinder> root;
// Trusty defines its own socket APIs in trusty_ipc.h but doesn't include
// sockaddr types.
#ifndef __TRUSTY__
        sockaddr_storage addr;
        socklen_t addrLen;
#endif
    };

    // client session objects associated with other process
    // each one represents a separate session
    std::vector<SessionInfo> sessions;

    virtual ~ProcessSession() = 0;

    // If the process exits with a status, run the given callback on that value.
    virtual void setCustomExitStatusCheck(std::function<void(int wstatus)> f) = 0;

    // Kill the process. Avoid if possible. Shutdown gracefully via an RPC instead.
    virtual void terminate() = 0;
};

// Process session where the process hosts IBinderRpcTest, the server used
// for most testing here
struct BinderRpcTestProcessSession {
    std::unique_ptr<ProcessSession> proc;

    // pre-fetched root object (for first session)
    sp<IBinder> rootBinder;

    // pre-casted root object (for first session)
    sp<IBinderRpcTest> rootIface;

    // whether session should be invalidated by end of run
    bool expectAlreadyShutdown = false;

    // TODO(b/271830568): fix this in binderRpcTest, we always use the first session to cause the
    // remote process to shutdown. Normally, when we shutdown, the default in the destructor is to
    // check that there are no leaks and shutdown. However, when there are incoming threadpools,
    // there will be a few extra binder threads there, so we can't shutdown the server. We should
    // consider an alternative way of doing the test so that we don't need this, some ideas, such as
    // program in understanding of incoming threadpool into the destructor so that (e.g.
    // intelligently wait for sessions to shutdown now that they will do this)
    void forceShutdown() {
        if (auto status = rootIface->scheduleShutdown(); !status.isOk()) {
            EXPECT_EQ(DEAD_OBJECT, status.transactionError()) << status;
        }
        EXPECT_TRUE(proc->sessions.at(0).session->shutdownAndWait(true));
        expectAlreadyShutdown = true;
    }

    BinderRpcTestProcessSession(std::unique_ptr<ProcessSession> proc) : proc(std::move(proc)){};
    BinderRpcTestProcessSession(BinderRpcTestProcessSession&&) = default;
    ~BinderRpcTestProcessSession() {
        if (!expectAlreadyShutdown) {
            EXPECT_NE(nullptr, rootIface);
            if (rootIface == nullptr) return;

            std::vector<int32_t> remoteCounts;
            // calling over any sessions counts across all sessions
            EXPECT_OK(rootIface->countBinders(&remoteCounts));
            EXPECT_EQ(remoteCounts.size(), proc->sessions.size());
            for (auto remoteCount : remoteCounts) {
                EXPECT_EQ(remoteCount, 1);
            }

            // even though it is on another thread, shutdown races with
            // the transaction reply being written
            if (auto status = rootIface->scheduleShutdown(); !status.isOk()) {
                EXPECT_EQ(DEAD_OBJECT, status.transactionError()) << status;
            }
        }

        rootIface = nullptr;
        rootBinder = nullptr;
    }
};

struct BinderRpcParam {
    SocketType type;
    RpcSecurity security;
    uint32_t clientVersion;
    uint32_t serverVersion;
    bool singleThreaded;
    bool noKernel;
};
class BinderRpc : public ::testing::TestWithParam<BinderRpcParam> {
public:
    // TODO: avoid unnecessary layer of indirection
    SocketType socketType() const { return GetParam().type; }
    RpcSecurity rpcSecurity() const { return GetParam().security; }
    uint32_t clientVersion() const { return GetParam().clientVersion; }
    uint32_t serverVersion() const { return GetParam().serverVersion; }
    bool serverSingleThreaded() const { return GetParam().singleThreaded; }
    bool noKernel() const { return GetParam().noKernel; }

    bool clientOrServerSingleThreaded() const {
        return !kEnableRpcThreads || serverSingleThreaded();
    }

    // Whether the test params support sending FDs in parcels.
    bool supportsFdTransport() const {
        if (socketType() == SocketType::TIPC) {
            // Trusty does not support file descriptors yet
            return false;
        }
        return clientVersion() >= 1 && serverVersion() >= 1 && rpcSecurity() != RpcSecurity::TLS &&
                (socketType() == SocketType::PRECONNECTED || socketType() == SocketType::UNIX ||
                 socketType() == SocketType::UNIX_BOOTSTRAP ||
                 socketType() == SocketType::UNIX_RAW);
    }

    void SetUp() override {
        if (socketType() == SocketType::UNIX_BOOTSTRAP && rpcSecurity() == RpcSecurity::TLS) {
            GTEST_SKIP() << "Unix bootstrap not supported over a TLS transport";
        }
    }

    BinderRpcTestProcessSession createRpcTestSocketServerProcess(const BinderRpcOptions& options) {
        BinderRpcTestProcessSession ret(createRpcTestSocketServerProcessEtc(options));

        ret.rootBinder = ret.proc->sessions.empty() ? nullptr : ret.proc->sessions.at(0).root;
        ret.rootIface = interface_cast<IBinderRpcTest>(ret.rootBinder);

        return ret;
    }

    static std::string PrintParamInfo(const testing::TestParamInfo<ParamType>& info) {
        auto ret = PrintToString(info.param.type) + "_" +
                newFactory(info.param.security)->toCString() + "_clientV" +
                std::to_string(info.param.clientVersion) + "_serverV" +
                std::to_string(info.param.serverVersion);
        if (info.param.singleThreaded) {
            ret += "_single_threaded";
        } else {
            ret += "_multi_threaded";
        }
        if (info.param.noKernel) {
            ret += "_no_kernel";
        } else {
            ret += "_with_kernel";
        }
        return ret;
    }

protected:
    static std::unique_ptr<RpcTransportCtxFactory> newFactory(RpcSecurity rpcSecurity);

    std::unique_ptr<ProcessSession> createRpcTestSocketServerProcessEtc(
            const BinderRpcOptions& options);
};

} // namespace android
