/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#pragma once

#include <memory>
#include <vector>

#include <folly/ThreadLocal.h>
#include <folly/container/F14Map.h>
#include <folly/io/SocketOptionMap.h>
#include <folly/io/async/ScopedEventBaseThread.h>

#include <quic/QuicConstants.h>
#include <quic/codec/ConnectionIdAlgo.h>
#include <quic/congestion_control/ServerCongestionControllerFactory.h>
#include <quic/server/QuicServerTransportFactory.h>
#include <quic/server/QuicServerWorker.h>
#include <quic/server/QuicUDPSocketFactory.h>
#include <quic/state/QuicConnectionStats.h>
#include <quic/state/QuicTransportStatsCallback.h>

FOLLY_GFLAGS_DECLARE_int32(qs_conn_id_version);

namespace quic {

class QuicServer : public QuicServerWorker::WorkerCallback,
                   public std::enable_shared_from_this<QuicServer> {
 public:
  using TransportSettingsOverrideFn =
      std::function<void(quic::TransportSettings&, const folly::IPAddress&)>;

  static std::shared_ptr<QuicServer> createQuicServer(
      TransportSettings transportSettings = TransportSettings()) {
    return std::shared_ptr<QuicServer>(
        new QuicServer(std::move(transportSettings)));
  }

  ~QuicServer() override;

  // Initialize and start the quic server where the quic server manages
  // the eventbases for workers
  void start(const folly::SocketAddress& address, size_t maxWorkers);

  // Initialize quic server worker per evb.
  void initialize(
      const folly::SocketAddress& address,
      const std::vector<folly::EventBase*>& evbs,
      bool useDefaultTransport = false);

  /**
   * start reading from sockets
   */
  void start();

  /*
   * Pause reading from the listening socket the server workers are bound to
   */
  void pauseRead();

  /*
   * Take in a function to supply overrides for transport parameters, given
   * the client address as input. This can be useful if we are running
   * experiments.
   */
  void setTransportSettingsOverrideFn(TransportSettingsOverrideFn fn);

  /*
   * Transport factory to create server-transport.
   * QuicServer calls 'make()' on the supplied transport factory for *each* new
   * connection.
   * This is useful to do proper set-up on the callers side for each new
   * established connection, such as transport settings and setup sessions.
   */
  void setQuicServerTransportFactory(
      std::unique_ptr<QuicServerTransportFactory> factory);

  /*
   * The socket factory used to create sockets for client connections.  These
   * will end up backing QuicServerTransports and managing per connection state.
   */
  void setQuicUDPSocketFactory(std::unique_ptr<QuicUDPSocketFactory> factory);

  /*
   * The socket factory used to create acceptor sockets.  The sockets created
   * from this factory will listen for udp packets and create new connections
   * via the factory specified in setQuicUDPSocketFactory.
   */
  void setListenerSocketFactory(std::unique_ptr<QuicUDPSocketFactory> factory);

  /**
   * Set factory to create specific congestion controller instances
   * for a given connection
   * This must be set before the server is started.
   */
  void setCongestionControllerFactory(
      std::shared_ptr<CongestionControllerFactory> ccFactory);

  void setRateLimit(
      std::function<uint64_t()> count,
      std::chrono::seconds window);

  void setUnfinishedHandshakeLimit(std::function<int()> limitFn);

  /**
   * Set list of supported QUICVersion for this server. These versions will be
   * used during the 'Version-Negotiation' phase with the client.
   */
  void setSupportedVersion(const std::vector<QuicVersion>& versions);

  /**
   * A token to use for health checking VIPs. When a UDP packet is sent to the
   * server with the exact contents of the health check token, the server will
   * respond with an "OK".
   */
  void setHealthCheckToken(const std::string& healthCheckToken);

  /**
   * Set server TLS context.
   */
  void setFizzContext(
      std::shared_ptr<const fizz::server::FizzServerContext> ctx);

  /**
   * Set server TLS context for a worker associated with the given eventbase.
   */
  void setFizzContext(
      folly::EventBase* evb,
      std::shared_ptr<const fizz::server::FizzServerContext> ctx);

  /**
   * Set socket options for the underlying socket.
   * Options are being set before and after bind, and not at the time of
   * invoking this function.
   */
  void setSocketOptions(const folly::SocketOptionMap& options) noexcept;

  /**
   * Sets whether the underlying socket should set the IPV6_ONLY socket option
   * or not. If set to false, IPv4-mapped IPv6 addresses will be enabled on the
   * socket.
   */
  void setBindV6Only(bool bindV6Only);

  /**
   * Set the server id of the quic server.
   * Note that this function must be called before initialize(..)
   */
  void setProcessId(ProcessId id) noexcept;

  ProcessId getProcessId() const noexcept;

  /**
   * Set the id of the host where this server is running.
   * It is used to make routing decision by setting this id in the ConnectionId
   */
  void setHostId(uint32_t hostId) noexcept;

  /**
   * Set version of connection ID used by quic server.
   * Note that this function must be called before initialize(..)
   */
  void setConnectionIdVersion(ConnectionIdVersion cidVersion) noexcept;

  /**
   * Get transport settings.
   */
  const TransportSettings& getTransportSettings() const noexcept;

  /**
   * Tells the server to start rejecting any new connection. The parameter
   * function is stored and evaluated on each new connection before being
   * accepted.
   */
  void rejectNewConnections(std::function<bool()> rejectFn);

  /**
   * Set callback to determine if MVFST_PRIMING version handling is
   * enabled.
   */
  void setPrimingEnabledCallback(std::function<bool()> isPrimingEnabled);

  /**
   * Tells the server to begin rejecting any new connections with block listed
   * source ports. Like rejectNewConnections above, the parameter function is
   * stored and evaluated on each new connection before being accepted.
   */
  void blockListedSrcPort(std::function<bool(uint16_t)> isBlockListedSrcPort);

  /**
   * Returns listening address of this server
   */
  const folly::SocketAddress& getAddress() const;

  /**
   * Returns true iff the server is fully initialized
   */
  bool isInitialized() const noexcept;

  /**
   * Shutdown the sever (and all the workers)
   */
  void shutdown(LocalErrorCode error = LocalErrorCode::SHUTTING_DOWN);

  /**
   * Returns true if the server has begun the termination process or if it has
   * not been initialized
   */
  bool hasShutdown() const noexcept;

  /**
   * Blocks the calling thread until isInitialized() is true
   */
  void waitUntilInitialized();

  /**
   * Set the transport factory for the worker associated with the given
   * eventbase.
   * This is relevant if the QuicServer is initialized with the vector of
   * event-bases supplied by the caller.
   * Typically, this is useful when the server is already running fixed pool of
   * thread ('workers'), and want to run QuicServer within those workers.
   * In such scenario, the supplied factory's make() will be called (lock-free)
   * upon each new connection establishment within each worker.
   */
  void addTransportFactory(
      folly::EventBase*,
      QuicServerTransportFactory* acceptor);

  /**
   * Initialize necessary steps to enable being taken over of this server by
   * another server, such as binding to a local port so that once another
   * process starts to takeover the port this server is listening to, the other
   * server can forward packets belonging to this server
   * Note that this method cannot be called on a worker's thread.
   * Note that this should also be called after initialize(..),
   * calling this before initialize is undefined.
   */
  void allowBeingTakenOver(const folly::SocketAddress& addr);

  folly::SocketAddress overrideTakeoverHandlerAddress(
      const folly::SocketAddress& addr);

  /*
   * Setup and initialize the listening socket of the old server from the given
   * address to forward misrouted packets belonging to that server during
   * the takeover process
   */
  void startPacketForwarding(const folly::SocketAddress& destAddr);

  /*
   * Disable packet forwarding, even if the packet has no connection id
   * associated with it after the 'delayMS' milliseconds
   */
  void stopPacketForwarding(std::chrono::milliseconds delay);

  /**
   * Set takenover socket fds for the quic server from another process.
   * Quic server calls ::dup for each fd and will not bind to the address for
   * all the valid fds (i.e. not -1) in the given vector
   * NOTE: it must be called before calling 'start()'
   */
  void setListeningFDs(const std::vector<int>& fds);

  /*
   * Returns the File Descriptor of the listening socket for this server.
   */
  int getListeningSocketFD() const;

  /*
   * Returns all the File Descriptor of the listening sockets for each
   * worker for this server.
   */
  std::vector<int> getAllListeningSocketFDs() const noexcept;

  /*
   * Once this server is notified that another server has initiated the takeover
   * it opens a new communication channel so that new server can forward
   * misrouted packets to this server.
   * This method returns the File Descriptor of a local port that this server
   * is listening to.
   */
  int getTakeoverHandlerSocketFD() const;

  TakeoverProtocolVersion getTakeoverProtocolVersion() const noexcept;

  /**
   * Factory to create per worker callback for various transport stats (such as
   * packet received, dropped etc). QuicServer calls 'make' during the
   * initialization _for each worker_.
   * Also, 'make' is called from the worker's eventbase.
   *
   * NOTE: Since the callback is invoked very frequently and per thread,
   * it is important that the implementation of QuicTransportStatsCallback is
   * efficient.
   * NOTE: Quic does not synchronize across threads before calling
   * callbacks for various stats.
   */
  void setTransportStatsCallbackFactory(
      std::unique_ptr<QuicTransportStatsCallbackFactory> statsFactory);

  /**
   * Factory to create per worker ConnectionIdAlgo instance
   * NOTE: it must be set before calling 'start()' or 'initialize(..)'
   */
  void setConnectionIdAlgoFactory(
      std::unique_ptr<ConnectionIdAlgoFactory> connIdAlgoFactory);

  /**
   * Returns vector of running eventbases.
   * This is useful if QuicServer is initialized with a 'default' mode by just
   * specifying number of workers.
   */
  std::vector<folly::EventBase*> getWorkerEvbs() const noexcept;

  /**
   * Adds observer for accept events.
   *
   * Adds for the worker associated with the given EventBase. This is relevant
   * if the QuicServer is initialized with a vector of EventBase supplied by
   * the caller. With this approach, each worker thread can (but is not
   * required to) have its own observer, removing the need for the observer
   * implementation to be thread safe.
   *
   * Can be used to install socket observers and instrumentation without
   * changing / interfering with application-specific acceptor logic.
   *
   * See AcceptObserver class for details.
   *
   * @param evb           Worker EventBase for which we want to add observer.
   * @param observer      Observer to add (implements AcceptObserver).
   * @return              Whether worker found and observer added.
   */
  bool addAcceptObserver(folly::EventBase* evb, AcceptObserver* observer);

  /**
   * Remove observer for accept events.
   *
   * Removes for the worker associated with the given EventBase.
   *
   * @param evb           Worker EventBase for which we want to remove observer.
   * @param observer      Observer to remove.
   * @return              Whether worker + observer found and observer removed.
   */
  bool removeAcceptObserver(folly::EventBase* evb, AcceptObserver* observer);

  void getAllConnectionsStats(std::vector<QuicConnectionStats>& stats);

 private:
  explicit QuicServer(TransportSettings transportSettings);

  static TransportSettings validateTransportSettings(
      TransportSettings transportSettings);

  /**
   * Routes the given data for the given client to the correct worker that may
   * have the state for the connection associated with the given data and client
   */
  void routeDataToWorker(
      const folly::SocketAddress& client,
      RoutingData&& routingData,
      NetworkData&& networkData,
      Optional<QuicVersion> quicVersion,
      folly::EventBase* workerEvb,
      bool isForwardedData = false) override;

  void handleWorkerError(LocalErrorCode error) override;

  using MaybeOwnedEvbPtr =
      std::unique_ptr<folly::IOExecutor, void (*)(folly::IOExecutor*)>;

  // initializes a QuicServerWorker per evb.
  void initializeImpl(
      const folly::SocketAddress& address,
      std::vector<MaybeOwnedEvbPtr> evbs,
      bool useDefaultTransport = false);

  struct EventBaseBackendDetails {
    std::unique_ptr<folly::EventBaseBackendBase> (*factory)();
    bool supportsRecvmsgMultishot = false;
  };

  static EventBaseBackendDetails getEventBaseBackendDetails();

  // helper function to initialize workers
  void initializeWorkers(bool useDefaultTransport);

  std::unique_ptr<QuicServerWorker> newWorkerWithoutSocket();

  // helper method to run the given function in all worker asynchronously
  void runOnAllWorkers(const std::function<void(QuicServerWorker*)>& func);

  // helper method to run the given function in all worker synchronously
  void runOnAllWorkersSync(const std::function<void(QuicServerWorker*)>& func);

  void bindWorkersToSocket(const folly::SocketAddress& address);

  std::vector<QuicVersion> supportedVersions_{{
      QuicVersion::MVFST,
      QuicVersion::MVFST_EXPERIMENTAL,
      QuicVersion::MVFST_EXPERIMENTAL2,
      QuicVersion::MVFST_EXPERIMENTAL3,
      QuicVersion::MVFST_EXPERIMENTAL4,
      QuicVersion::MVFST_EXPERIMENTAL5,
      QuicVersion::MVFST_ALIAS,
      QuicVersion::QUIC_V1,
      QuicVersion::QUIC_V1_ALIAS,
      QuicVersion::QUIC_V1_ALIAS2,
      QuicVersion::MVFST_PRIMING,
  }};

  std::atomic<bool> shutdown_{true};
  std::shared_ptr<const fizz::server::FizzServerContext> ctx_;
  const TransportSettings transportSettings_;
  std::mutex startMutex_;
  std::atomic<bool> initialized_{false};
  std::atomic<bool> takeoverHandlerInitialized_{false};

  // stores all the worker evbs which may be owned (i.e. ScopedEventBaseThread)
  // or unowned (i.e. folly::EventBase*)
  folly::Synchronized<std::vector<MaybeOwnedEvbPtr>> workerEvbs_;

  std::vector<std::unique_ptr<QuicServerWorker>> workers_;
  // Thread local pointer to QuicServerWorker. This is useful to avoid
  // looking up the worker to route to.
  // NOTE: QuicServer still maintains ownership of all the workers and manages
  // their destruction
  folly::ThreadLocalPtr<QuicServerWorker> workerPtr_;
  folly::F14FastMap<folly::EventBase*, QuicServerWorker*> evbToWorkers_;
  std::unique_ptr<QuicServerTransportFactory> transportFactory_;
  // factory used for workers to create their listening / bound sockets
  std::unique_ptr<QuicUDPSocketFactory> listenerSocketFactory_;
  // factory used by workers to create sockets for connection transports
  std::unique_ptr<QuicUDPSocketFactory> socketFactory_;
  // factory used to create specific instance of Congestion control algorithm
  std::shared_ptr<CongestionControllerFactory> ccFactory_;

  Optional<std::string> healthCheckToken_;
  // vector of all the listening fds on each quic server worker
  std::vector<int> listeningFDs_;
  ProcessId processId_{ProcessId::ZERO};
  uint32_t hostId_{0};
  ConnectionIdVersion cidVersion_{ConnectionIdVersion::V1};
  std::function<bool()> rejectNewConnections_{[]() { return false; }};
  std::function<bool()> isPrimingEnabled_{[]() { return false; }};
  std::function<bool(uint16_t)> isBlockListedSrcPort_{
      [](uint16_t) { return false; }};
  // factory to create per worker QuicTransportStatsCallback
  std::unique_ptr<QuicTransportStatsCallbackFactory> transportStatsFactory_;
  // factory to create per worker ConnectionIdAlgo
  std::unique_ptr<ConnectionIdAlgoFactory> connIdAlgoFactory_;
  // Impl of ConnectionIdAlgo to make routing decisions from ConnectionId
  std::unique_ptr<ConnectionIdAlgo> connIdAlgo_;
  // Used to override certain transport parameters, given the client address
  TransportSettingsOverrideFn transportSettingsOverrideFn_;
  // address that the server is bound to
  folly::SocketAddress boundAddress_;
  folly::SocketOptionMap socketOptions_;

  // Rate limits
  struct RateLimit {
    RateLimit(std::function<uint64_t()> c, std::chrono::seconds w)
        : count(std::move(c)), window(w) {}

    std::function<uint64_t()> count;
    std::chrono::seconds window;
  };

  Optional<RateLimit> rateLimit_;

  std::function<int()> unfinishedHandshakeLimitFn_{[]() { return 1048576; }};

  // Options to AsyncUDPSocket::bind, only controls IPV6_ONLY currently.
  FollyAsyncUDPSocketAlias::BindOptions bindOptions_;

  // set by getEventBaseBackend if multishot callback is
  // supported
  bool backendSupportsMultishotCallback_{false};

  // flag to ensure baton is only posted once
  folly::once_flag startDone_;
  // baton used to block in ::waitUntilInitialized – posted via shutdown path or
  // last worker to have been initialized
  folly::Baton<> startDoneBaton_;

  /**
   * QuicServer does not support multi-threaded/concurrent access. mainThreadId_
   * is initialized to the id of the thread that constructed the QuicServer and
   * all member methods check the invariant that they are invoked in
   * mainThreadId_. This is temporary until we move all member methods that
   * shouldn't be invoked after initialization into the constructor.
   */
  const std::thread::id mainThreadId_{};
};

} // namespace quic
