/*
 * Copyright (C) 2021 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.
 */

// Wraps the transport layer of RPC. Implementation may use plain sockets or TLS.

#pragma once

#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <variant>
#include <vector>

#include <android-base/function_ref.h>
#include <android-base/unique_fd.h>
#include <utils/Errors.h>

#include <binder/RpcCertificateFormat.h>
#include <binder/RpcThreads.h>

#include <sys/uio.h>

namespace android {

class FdTrigger;
struct RpcTransportFd;

// for 'friend'
class RpcTransportRaw;
class RpcTransportTls;
class RpcTransportTipcAndroid;
class RpcTransportTipcTrusty;
class RpcTransportCtxRaw;
class RpcTransportCtxTls;
class RpcTransportCtxTipcAndroid;
class RpcTransportCtxTipcTrusty;

// Represents a socket connection.
// No thread-safety is guaranteed for these APIs.
class RpcTransport {
public:
    virtual ~RpcTransport() = default;

    /**
     * Poll the transport to check whether there is any data ready to read.
     *
     * Return:
     *   OK - There is data available on this transport
     *   WOULDBLOCK - No data is available
     *   error - any other error
     */
    [[nodiscard]] virtual status_t pollRead(void) = 0;

    /**
     * Read (or write), but allow to be interrupted by a trigger.
     *
     * iovs - array of iovecs to perform the operation on. The elements
     * of the array may be modified by this method.
     *
     * altPoll - function to be called instead of polling, when needing to wait
     * to read/write data. If this returns an error, that error is returned from
     * this function.
     *
     * ancillaryFds - FDs to be sent via UNIX domain dockets or Trusty IPC. When
     * reading, if `ancillaryFds` is null, any received FDs will be silently
     * dropped and closed (by the OS). Appended values will always be unique_fd,
     * the variant type is used to avoid extra copies elsewhere.
     *
     * Return:
     *   OK - succeeded in completely processing 'size'
     *   error - interrupted (failure or trigger)
     */
    [[nodiscard]] virtual status_t interruptableWriteFully(
            FdTrigger *fdTrigger, iovec *iovs, int niovs,
            const std::optional<android::base::function_ref<status_t()>> &altPoll,
            const std::vector<std::variant<base::unique_fd, base::borrowed_fd>> *ancillaryFds) = 0;
    [[nodiscard]] virtual status_t interruptableReadFully(
            FdTrigger *fdTrigger, iovec *iovs, int niovs,
            const std::optional<android::base::function_ref<status_t()>> &altPoll,
            std::vector<std::variant<base::unique_fd, base::borrowed_fd>> *ancillaryFds) = 0;

    /**
     *  Check whether any threads are blocked while polling the transport
     *  for read operations
     *  Return:
     *    True - Specifies that there is active polling on transport.
     *    False - No active polling on transport
     */
    [[nodiscard]] virtual bool isWaiting() = 0;

private:
    // limit the classes which can implement RpcTransport. Being able to change this
    // interface is important to allow development of RPC binder. In the past, we
    // changed this interface to use iovec for efficiency, and we added FDs to the
    // interface. If another transport is needed, it should be added directly here.
    // non-socket FDs likely also need changes in RpcSession in order to get
    // connected, and similarly to how addrinfo was type-erased from RPC binder
    // interfaces when RpcTransportTipc* was added, other changes may be needed
    // to add more transports.

    friend class ::android::RpcTransportRaw;
    friend class ::android::RpcTransportTls;
    friend class ::android::RpcTransportTipcAndroid;
    friend class ::android::RpcTransportTipcTrusty;

    RpcTransport() = default;
};

// Represents the context that generates the socket connection.
// All APIs are thread-safe. See RpcTransportCtxRaw and RpcTransportCtxTls for details.
class RpcTransportCtx {
public:
    virtual ~RpcTransportCtx() = default;

    // Create a new RpcTransport object.
    //
    // Implementation details: for TLS, this function may incur I/O. |fdTrigger| may be used
    // to interrupt I/O. This function blocks until handshake is finished.
    [[nodiscard]] virtual std::unique_ptr<RpcTransport> newTransport(
            android::RpcTransportFd fd, FdTrigger *fdTrigger) const = 0;

    // Return the preconfigured certificate of this context.
    //
    // Implementation details:
    // - For raw sockets, this always returns empty string.
    // - For TLS, this returns the certificate. See RpcTransportTls for details.
    [[nodiscard]] virtual std::vector<uint8_t> getCertificate(
            RpcCertificateFormat format) const = 0;

private:
    // see comment on RpcTransport
    friend class ::android::RpcTransportCtxRaw;
    friend class ::android::RpcTransportCtxTls;
    friend class ::android::RpcTransportCtxTipcAndroid;
    friend class ::android::RpcTransportCtxTipcTrusty;

    RpcTransportCtx() = default;
};

// A factory class that generates RpcTransportCtx.
// All APIs are thread-safe.
class RpcTransportCtxFactory {
public:
    virtual ~RpcTransportCtxFactory() = default;
    // Creates server context.
    [[nodiscard]] virtual std::unique_ptr<RpcTransportCtx> newServerCtx() const = 0;

    // Creates client context.
    [[nodiscard]] virtual std::unique_ptr<RpcTransportCtx> newClientCtx() const = 0;

    // Return a short description of this transport (e.g. "raw"). For logging / debugging / testing
    // only.
    [[nodiscard]] virtual const char *toCString() const = 0;

protected:
    RpcTransportCtxFactory() = default;
};

struct RpcTransportFd final {
private:
    mutable bool isPolling{false};

    void setPollingState(bool state) const { isPolling = state; }

public:
    base::unique_fd fd;

    RpcTransportFd() = default;
    explicit RpcTransportFd(base::unique_fd &&descriptor)
          : isPolling(false), fd(std::move(descriptor)) {}

    RpcTransportFd(RpcTransportFd &&transportFd) noexcept
          : isPolling(transportFd.isPolling), fd(std::move(transportFd.fd)) {}

    RpcTransportFd &operator=(RpcTransportFd &&transportFd) noexcept {
        fd = std::move(transportFd.fd);
        isPolling = transportFd.isPolling;
        return *this;
    }

    RpcTransportFd &operator=(base::unique_fd &&descriptor) noexcept {
        fd = std::move(descriptor);
        isPolling = false;
        return *this;
    }

    bool isInPollingState() const { return isPolling; }
    friend class FdTrigger;
};

} // namespace android
