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

#ifndef SRC_TRACED_RELAY_SOCKET_RELAY_HANDLER_H_
#define SRC_TRACED_RELAY_SOCKET_RELAY_HANDLER_H_

#include <poll.h>

#include <cstring>
#include <deque>
#include <future>
#include <mutex>
#include <optional>
#include <thread>
#include <tuple>

#include "perfetto/base/platform_handle.h"
#include "perfetto/ext/base/event_fd.h"
#include "perfetto/ext/base/flat_hash_map.h"
#include "perfetto/ext/base/thread_checker.h"
#include "perfetto/ext/base/unix_socket.h"
#include "perfetto/ext/ipc/basic_types.h"

namespace perfetto {

// FdPoller is a utility for waiting for IO events of a set of watched file
// descriptors. It's used for multiplexing non-blocking IO operations.
class FdPoller {
 public:
  // The interface class for observing IO events from the FdPoller class.
  class Watcher {
   public:
    virtual ~Watcher();
    // Called when |fd| can be read from without blocking. For a socket
    // connection, this indicates the socket read buffer has some data.
    virtual void OnFdReadable(base::PlatformHandle fd) = 0;
    // Called when |fd| can be written to without blocking. For a socket
    // connection, this indicates that the socket write buffer has some capacity
    // for writing data into.
    virtual void OnFdWritable(base::PlatformHandle fd) = 0;
  };

  using WatchEvents = decltype(pollfd::events);

  explicit FdPoller(Watcher* watcher);

  // Watch and unwatch IO event for a given file descriptor.
  inline void WatchForRead(base::PlatformHandle fd) { WatchFd(fd, POLLIN); }
  inline void WatchForWrite(base::PlatformHandle fd) { WatchFd(fd, POLLOUT); }
  inline void UnwatchForRead(base::PlatformHandle fd) { UnwatchFd(fd, POLLIN); }
  inline void UnwatchForWrite(base::PlatformHandle fd) {
    UnwatchFd(fd, POLLOUT);
  }

  // Called when |fd| is no longer of interest (e.g. when |fd| is to be closed).
  void RemoveWatch(base::PlatformHandle fd);

  // Poll for all watched events previously added with WatchForRead() and
  // WatchForWrite().
  //
  // Must be called on poller thread.
  void Poll();

  // Notifies the poller for pending updates. Calling Notify() will unblock the
  // poller and make it return from Poll(). It is caller's responsibility to
  // call Poll() again once the updates are complete.
  //
  // This can be (and typically is) called from any thread.
  void Notify();

 private:
  std::vector<pollfd>::iterator FindPollEvent(base::PlatformHandle fd);
  void WatchFd(base::PlatformHandle fd, WatchEvents events);
  void UnwatchFd(base::PlatformHandle fd, WatchEvents events);

  base::ThreadChecker thread_checker_;
  Watcher* const watcher_;
  base::EventFd notify_fd_;
  std::vector<pollfd> poll_fds_;
};

// This class groups a UnixSocketRaw with an associated ring buffer. The ring
// buffer is used as a temporary storage for data *read* from the socket.
class SocketWithBuffer {
 public:
  constexpr static size_t kBuffSize = ipc::kIPCBufferSize;

  base::UnixSocketRaw sock;

  // Points to the beginning of buffered data.
  inline uint8_t* data() { return &buf_[0]; }
  // Size of the buffered data.
  inline size_t data_size() { return data_size_; }

  // Points to the beginning of the free space for buffering new data.
  inline uint8_t* buffer() { return &buf_[data_size_]; }
  // Size of the free space.
  inline size_t available_bytes() { return buf_.size() - data_size_; }

  // Called when |bytes| of data is enqueued to the buffer.
  void EnqueueData(size_t bytes) {
    PERFETTO_CHECK(bytes <= available_bytes());
    data_size_ += bytes;
  }
  // Called when |bytes| of data is dequeued from the buffer.
  void DequeueData(size_t bytes) {
    PERFETTO_CHECK(bytes <= data_size());
    memmove(data(), data() + bytes, data_size() - bytes);
    data_size_ -= bytes;
  }

  SocketWithBuffer() : buf_(kBuffSize) {}

  // Movable only.
  SocketWithBuffer(SocketWithBuffer&& other) = default;
  SocketWithBuffer& operator=(SocketWithBuffer&& other) = default;
  SocketWithBuffer(const SocketWithBuffer& other) = delete;
  SocketWithBuffer& operator=(const SocketWithBuffer& other) = delete;

 private:
  std::vector<uint8_t> buf_;
  size_t data_size_ = 0;
};

using SocketPair = std::pair<SocketWithBuffer, SocketWithBuffer>;

// SocketRelayHandler bidirectionally forwards data between paired sockets.
// Internally it multiplexes IO operations of the sockets using a FdPoller on a
// dedicated thread.
class SocketRelayHandler : public FdPoller::Watcher {
 public:
  SocketRelayHandler();
  SocketRelayHandler(const SocketRelayHandler&) = delete;
  SocketRelayHandler& operator=(const SocketRelayHandler&) = delete;
  ~SocketRelayHandler() override;

  // Transfer a pair of sockets to be relayed. Can be called from any thread.
  void AddSocketPair(std::unique_ptr<SocketPair> socket_pair);

  // The FdPoller::Watcher callbacks.
  void OnFdReadable(base::PlatformHandle fd) override;
  void OnFdWritable(base::PlatformHandle fd) override;

 private:
  void Run();
  void RemoveSocketPair(SocketWithBuffer&, SocketWithBuffer&);

  // A helper for running a callable object on |io_thread_|.
  template <typename Callable>
  void RunOnIOThread(Callable&& c) {
    std::lock_guard<std::mutex> lock(mutex_);
    pending_tasks_.emplace_back(std::forward<Callable>(c));
    fd_poller_.Notify();
  }

  std::optional<std::tuple<SocketWithBuffer&, SocketWithBuffer&>> GetSocketPair(
      base::PlatformHandle fd);

  base::FlatHashMap<base::PlatformHandle, SocketPair*> socket_pairs_by_fd_;
  std::vector<std::unique_ptr<SocketPair>> socket_pairs_;

  FdPoller fd_poller_;

  // The thread that fd_poller_ polls for IO events. Most methods of this class
  // asserts to be running on this thread.
  std::thread io_thread_;
  base::ThreadChecker io_thread_checker_;

  bool exited_ = false;

  //--------------- Member data with multi-thread access ------------------
  std::mutex mutex_;
  std::deque<std::packaged_task<void()>> pending_tasks_;
};

}  // namespace perfetto
#endif  // SRC_TRACED_RELAY_SOCKET_RELAY_HANDLER_H_
