/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef nsHttpConnectionMgr_h_
#define nsHttpConnectionMgr_h_

#include "DnsAndConnectSocket.h"
#include "HttpConnectionMgrShell.h"
#include "nsHttpConnection.h"
#include "nsHttpTransaction.h"
#include "nsTArray.h"
#include "nsTHashSet.h"
#include "nsThreadUtils.h"
#include "nsClassHashtable.h"
#include "mozilla/DataMutex.h"
#include "mozilla/TimeStamp.h"
#include "ARefBase.h"
#include "nsWeakReference.h"
#include "ConnectionEntry.h"

#include "nsINamed.h"
#include "nsIObserver.h"
#include "nsITimer.h"

class nsIHttpUpgradeListener;

namespace mozilla::net {
class EventTokenBucket;
class HttpConnectionBase;
class NullHttpTransaction;
struct HttpRetParams;
struct Http3ConnectionStatsParams;

//-----------------------------------------------------------------------------

// message handlers have this signature
class nsHttpConnectionMgr;
using nsConnEventHandler = void (nsHttpConnectionMgr::*)(int32_t, ARefBase*);

class nsHttpConnectionMgr final : public HttpConnectionMgrShell,
                                  public nsIObserver,
                                  nsINamed {
 public:
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_HTTPCONNECTIONMGRSHELL
  NS_DECL_NSIOBSERVER
  NS_DECL_NSINAMED

  //-------------------------------------------------------------------------
  // NOTE: functions below may only be called on the main thread.
  //-------------------------------------------------------------------------

  nsHttpConnectionMgr();

  //-------------------------------------------------------------------------
  // NOTE: functions below may be called on any thread.
  //-------------------------------------------------------------------------

  [[nodiscard]] nsresult CancelTransactions(nsHttpConnectionInfo*,
                                            nsresult code);

  // The connection manager needs to know the hashes used for a WebTransport
  // connection authenticated with serverCertHashes
  nsresult StoreServerCertHashes(
      nsHttpConnectionInfo* aConnInfo, bool aNoSpdy, bool aNoHttp3,
      nsTArray<RefPtr<nsIWebTransportHash>>&& aServerCertHashes);

  //-------------------------------------------------------------------------
  // NOTE: functions below may be called only on the socket thread.
  //-------------------------------------------------------------------------

  // called to change the connection entry associated with conn from specific
  // into a wildcard (i.e. http2 proxy friendy) mapping
  void MoveToWildCardConnEntry(nsHttpConnectionInfo* specificCI,
                               nsHttpConnectionInfo* wildcardCI,
                               HttpConnectionBase* conn);

  // Remove a transaction from the pendingQ of it's connection entry. Returns
  // true if the transaction is removed successfully, otherwise returns false.
  bool RemoveTransFromConnEntry(nsHttpTransaction* aTrans,
                                const nsACString& aHashKey);

  // Directly dispatch the transaction or insert it in to the pendingQ.
  [[nodiscard]] nsresult ProcessNewTransaction(nsHttpTransaction* aTrans);

  // This is used to force an idle connection to be closed and removed from
  // the idle connection list. It is called when the idle connection detects
  // that the network peer has closed the transport.
  [[nodiscard]] nsresult CloseIdleConnection(nsHttpConnection*);
  [[nodiscard]] nsresult RemoveIdleConnection(nsHttpConnection*);

  // Close a single connection and prevent it from being reused.
  [[nodiscard]] nsresult DoSingleConnectionCleanup(
      nsHttpConnectionInfo*,
      uint32_t aPriority = nsIRunnablePriority::PRIORITY_NORMAL);

  // The connection manager needs to know when a normal HTTP connection has been
  // upgraded to SPDY because the dispatch and idle semantics are a little
  // bit different.
  void ReportSpdyConnection(nsHttpConnection*, bool usingSpdy,
                            bool disallowHttp3);

  void ReportHttp3Connection(HttpConnectionBase* conn,
                             ConnectionEntry* entry = nullptr);

  bool GetConnectionData(nsTArray<HttpRetParams>*);
  bool GetHttp3ConnectionStatsData(nsTArray<Http3ConnectionStatsParams>*);

  void ResetIPFamilyPreference(nsHttpConnectionInfo*);

  uint16_t MaxRequestDelay() { return mMaxRequestDelay; }

  // tracks and untracks active transactions according their throttle status
  void AddActiveTransaction(nsHttpTransaction* aTrans);
  void RemoveActiveTransaction(nsHttpTransaction* aTrans,
                               Maybe<bool> const& aOverride = Nothing());
  void UpdateActiveTransaction(nsHttpTransaction* aTrans);

  // called by nsHttpTransaction::WriteSegments.  decides whether the
  // transaction should limit reading its reponse data.  There are various
  // conditions this methods evaluates.  If called by an active-tab
  // non-throttled transaction, the throttling window time will be prolonged.
  bool ShouldThrottle(nsHttpTransaction* aTrans);

  // prolongs the throttling time window to now + the window preferred delay.
  // called when:
  // - any transaction is activated
  // - or when a currently unthrottled transaction for the active window
  // receives data
  void TouchThrottlingTimeWindow(bool aEnsureTicker = true);

  // return true iff the connection has pending transactions for the active tab.
  // it's mainly used to disallow throttling (limit reading) of a response
  // belonging to the same conn info to free up a connection ASAP.
  // NOTE: relatively expensive to call, there are two hashtable lookups.
  bool IsConnEntryUnderPressure(nsHttpConnectionInfo*);

  uint64_t CurrentBrowserId() { return mCurrentBrowserId; }

  void DoFallbackConnection(SpeculativeTransaction* aTrans, bool aFetchHTTPSRR);
  void DoSpeculativeConnection(SpeculativeTransaction* aTrans,
                               bool aFetchHTTPSRR);

  HttpConnectionBase* GetH2orH3ActiveConn(ConnectionEntry* ent, bool aNoHttp2,
                                          bool aNoHttp3);

  void IncreaseNumDnsAndConnectSockets();
  void DecreaseNumDnsAndConnectSockets();

  // Wen a new idle connection has been added, this function is called to
  // increment mNumIdleConns and update PruneDeadConnections timer.
  void NewIdleConnectionAdded(uint32_t timeToLive);
  void DecrementNumIdleConns();

  const nsTArray<RefPtr<nsIWebTransportHash>>* GetServerCertHashes(
      nsHttpConnectionInfo* aConnInfo);

 private:
  virtual ~nsHttpConnectionMgr();

  //-------------------------------------------------------------------------
  // NOTE: functions below may be called on any thread.
  //-------------------------------------------------------------------------

  // Schedules next pruning of dead connection to happen after
  // given time.
  void PruneDeadConnectionsAfter(uint32_t time);

  // Stops timer scheduled for next pruning of dead connections if
  // there are no more idle connections or active spdy ones
  void ConditionallyStopPruneDeadConnectionsTimer();

  // Stops timer used for the read timeout tick if there are no currently
  // active connections.
  void ConditionallyStopTimeoutTick();

  // called to close active connections with no registered "traffic"
  [[nodiscard]] nsresult PruneNoTraffic();

  //-------------------------------------------------------------------------
  // NOTE: functions below may be called only on the socket thread.
  //-------------------------------------------------------------------------

  [[nodiscard]] bool ProcessPendingQForEntry(nsHttpConnectionInfo*);
  void ProcessPendingQForEntry(ConnectionEntry*);

  // public, so that the SPDY/http2 seesions can activate
  void ActivateTimeoutTick();

  already_AddRefed<PendingTransactionInfo> FindTransactionHelper(
      bool removeWhenFound, ConnectionEntry* aEnt, nsAHttpTransaction* aTrans);

  void DoSpeculativeConnectionInternal(ConnectionEntry* aEnt,
                                       SpeculativeTransaction* aTrans,
                                       bool aFetchHTTPSRR);

  already_AddRefed<ConnectionEntry> FindConnectionEntry(
      const nsHttpConnectionInfo* ci);

  void MaybeRemoveEntryFromPendingSet(ConnectionEntry* ent);

 public:
  void RegisterOriginCoalescingKey(HttpConnectionBase*, const nsACString& host,
                                   int32_t port);
  // A test if be-conservative should be used when proxy is setup for the
  // connection
  bool BeConservativeIfProxied(nsIProxyInfo* proxy);

  bool AllowToRetryDifferentIPFamilyForHttp3(nsHttpConnectionInfo* ci,
                                             nsresult aError);
  void SetRetryDifferentIPFamilyForHttp3(nsHttpConnectionInfo* ci,
                                         uint16_t aIPFamily);

 protected:
  friend class ConnectionEntry;
  void IncrementActiveConnCount();
  void DecrementActiveConnCount(HttpConnectionBase*);

 private:
  friend class ConnectionAttemptPool;
  friend class DnsAndConnectSocket;
  friend class HappyEyeballsConnectionAttempt;
  friend class DefaultHappyEyeballsConnMgrDelegate;
  friend class PendingTransactionInfo;
  friend class ConnectionEstablisher;
  friend class TCPConnectionEstablisher;

  //-------------------------------------------------------------------------
  // NOTE: these members may be accessed from any thread
  //-------------------------------------------------------------------------

  // Null after Shutdown(); used as a flag that we're shut down and no new
  // events should be dispatched.
  DataMutex<nsCOMPtr<nsIEventTarget>> mSocketThreadTarget{
      "nsHttpConnectionMgr.mSocketThreadTarget"};

  Atomic<bool, mozilla::Relaxed> mIsShuttingDown{false};

  //-------------------------------------------------------------------------
  // NOTE: these members are only accessed on the socket transport thread
  //-------------------------------------------------------------------------
  // connection limits
  uint16_t mMaxUrgentExcessiveConns{0};
  uint16_t mMaxConns{0};
  uint16_t mMaxPersistConnsPerHost{0};
  uint16_t mMaxPersistConnsPerProxy{0};
  uint16_t mMaxRequestDelay{0};  // in seconds
  bool mThrottleEnabled{false};
  uint32_t mThrottleSuspendFor{0};
  uint32_t mThrottleResumeFor{0};
  uint32_t mThrottleHoldTime{0};
  TimeDuration mThrottleMaxTime;
  bool mBeConservativeForProxy{true};

  [[nodiscard]] bool ProcessPendingQForEntry(ConnectionEntry*,
                                             bool considerAll);
  bool DispatchPendingQ(nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ,
                        ConnectionEntry* ent, bool considerAll);

  // This function selects transactions from mPendingTransactionTable to
  // dispatch according to the following conditions:
  // 1. When network.http.active_tab_priority is false, only get transactions
  //    from the queue whose window id is 0.
  // 2. If |considerAll| is false, either get transactions from the focused
  //    window queue or non-focused ones.
  // 3. If |considerAll| is true, fill the |pendingQ| with the transactions from
  //    both focused window and non-focused window queues.
  void PreparePendingQForDispatching(
      ConnectionEntry* ent, nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ,
      bool considerAll);

  // Return |mMaxPersistConnsPerProxy| or |mMaxPersistConnsPerHost|,
  // depending whether the proxy is used.
  uint32_t MaxPersistConnections(ConnectionEntry* ent) const;

  bool AtActiveConnectionLimit(ConnectionEntry*, uint32_t caps,
                               bool forInnerConn = false);
  [[nodiscard]] nsresult TryDispatchTransaction(
      ConnectionEntry* ent, bool onlyReusedConnection,
      PendingTransactionInfo* pendingTransInfo);
  [[nodiscard]] nsresult TryDispatchTransactionOnIdleConn(
      ConnectionEntry* ent, PendingTransactionInfo* pendingTransInfo,
      bool respectUrgency, bool* allUrgent = nullptr);
  [[nodiscard]] nsresult DispatchTransaction(ConnectionEntry*,
                                             nsHttpTransaction*,
                                             HttpConnectionBase*);
  [[nodiscard]] nsresult DispatchAbstractTransaction(ConnectionEntry*,
                                                     nsAHttpTransaction*,
                                                     uint32_t,
                                                     HttpConnectionBase*,
                                                     int32_t);
  [[nodiscard]] nsresult EnsureSocketThreadTarget();
  [[nodiscard]] nsresult TryDispatchExtendedCONNECTransaction(
      ConnectionEntry* aEnt, nsHttpTransaction* aTrans,
      nsHttpConnection* aConn);
  void StartedConnect();
  void RecvdConnect();

  ConnectionEntry* GetOrCreateConnectionEntry(
      nsHttpConnectionInfo*, bool prohibitWildCard, bool aNoHttp2,
      bool aNoHttp3, bool* aIsWildcard,
      bool* aAvailableForDispatchNow = nullptr);

  [[nodiscard]] nsresult MakeNewConnection(
      ConnectionEntry* ent, PendingTransactionInfo* pendingTransInfo);

  // Manage h2/3 connection coalescing
  // The hashtable contains arrays of weak pointers to HttpConnectionBases
  nsClassHashtable<nsUint32HashKey, nsTArray<nsWeakPtr>> mCoalescingHash;

  HttpConnectionBase* FindCoalescableConnection(ConnectionEntry* ent,
                                                bool justKidding, bool aNoHttp2,
                                                bool aNoHttp3);
  HttpConnectionBase* FindCoalescableConnectionByHashKey(ConnectionEntry* ent,
                                                         HashNumber key,
                                                         bool justKidding,
                                                         bool aNoHttp2,
                                                         bool aNoHttp3);
  void UpdateCoalescingForNewConn(HttpConnectionBase* conn,
                                  ConnectionEntry* ent, bool aNoHttp3);

  void ProcessSpdyPendingQ(ConnectionEntry* ent);
  void DispatchSpdyPendingQ(nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ,
                            ConnectionEntry* ent, HttpConnectionBase* connH2,
                            HttpConnectionBase* connH3);
  // used to marshall events to the socket transport thread.
  [[nodiscard]] nsresult PostEvent(
      nsConnEventHandler handler, int32_t iparam = 0,
      ARefBase* vparam = nullptr,
      uint32_t priority = nsIRunnablePriority::PRIORITY_NORMAL);

  void OnMsgReclaimConnection(HttpConnectionBase*);

  // message handlers
  void OnMsgShutdown(int32_t, ARefBase*);
  void OnMsgShutdownConfirm(int32_t, ARefBase*);
  void OnMsgNewTransaction(int32_t, ARefBase*);
  void OnMsgNewTransactionWithStickyConn(int32_t, ARefBase*);
  void OnMsgReschedTransaction(int32_t, ARefBase*);
  void OnMsgUpdateClassOfServiceOnTransaction(ClassOfService, ARefBase*);
  void OnMsgCancelTransaction(int32_t, ARefBase*);
  void OnMsgCancelTransactions(int32_t, ARefBase*);
  void OnMsgProcessPendingQ(int32_t, ARefBase*);
  void OnMsgPruneDeadConnections(int32_t, ARefBase*);
  void OnMsgSpeculativeConnect(int32_t, ARefBase*);
  void OnMsgCompleteUpgrade(int32_t, ARefBase*);
  void OnMsgUpdateParam(int32_t, ARefBase*);
  void OnMsgDoShiftReloadConnectionCleanup(int32_t, ARefBase*);
  void OnMsgDoSingleConnectionCleanup(int32_t, ARefBase*);
  void OnMsgProcessFeedback(int32_t, ARefBase*);
  void OnMsgProcessAllSpdyPendingQ(int32_t, ARefBase*);
  void OnMsgUpdateRequestTokenBucket(int32_t, ARefBase*);
  void OnMsgVerifyTraffic(int32_t, ARefBase*);
  void OnMsgPruneNoTraffic(int32_t, ARefBase*);
  void OnMsgUpdateCurrentBrowserId(int32_t, ARefBase*);
  void OnMsgClearConnectionHistory(int32_t, ARefBase*);
  void OnMsgStoreServerCertHashes(int32_t, ARefBase*);

  // Total number of active connections in all of the ConnectionEntry objects
  // that are accessed from mCT connection table.
  uint16_t mNumActiveConns{0};
  // Total number of idle connections in all of the ConnectionEntry objects
  // that are accessed from mCT connection table.
  uint16_t mNumIdleConns{0};
  // Total number of spdy or http3 connections which are a subset of the active
  // conns
  uint16_t mNumSpdyHttp3ActiveConns{0};
  // Total number of connections in DnsAndConnectSockets ConnectionEntry objects
  // that are accessed from mCT connection table
  uint32_t mNumDnsAndConnectSockets{0};

  // Holds time in seconds for next wake-up to prune dead connections.
  uint64_t mTimeOfNextWakeUp{UINT64_MAX};
  // Timer for next pruning of dead connections.
  nsCOMPtr<nsITimer> mTimer;
  // Timer for pruning stalled connections after changed network.
  nsCOMPtr<nsITimer> mTrafficTimer;
  bool mPruningNoTraffic{false};

  // A 1s tick to call nsHttpConnection::ReadTimeoutTick on
  // active http/1 connections and check for orphaned half opens.
  // Disabled when there are no active or half open connections.
  nsCOMPtr<nsITimer> mTimeoutTick;
  bool mTimeoutTickArmed{false};
  uint32_t mTimeoutTickNext{1};

  //
  // the connection table
  //
  // this table is indexed by connection key.  each entry is a
  // ConnectionEntry object. It is unlocked and therefore must only
  // be accessed from the socket thread.
  //
  nsRefPtrHashtable<nsCStringHashKey, ConnectionEntry> mCT;

  // Subset of mCT entries that currently have non-empty pending transaction
  // queues. Only iterated in OnMsgProcessAllSpdyPendingQ.
  nsTHashSet<ConnectionEntry*> mPendingQEntries;

  // Read Timeout Tick handlers
  void TimeoutTick();

  // For diagnostics
  void OnMsgPrintDiagnostics(int32_t, ARefBase*);

  nsCString mLogData;
  uint64_t mCurrentBrowserId{0};

  // Called on a pref change
  void SetThrottlingEnabled(bool aEnable);

  // we only want to throttle for a limited amount of time after a new
  // active transaction is added so that we don't block downloads on comet,
  // socket and any kind of longstanding requests that don't need bandwidth.
  // these methods track this time.
  bool InThrottlingTimeWindow();

  // Two hashtables keeping track of active transactions regarding window id and
  // throttling. Used by the throttling algorithm to obtain number of
  // transactions for the active tab and for inactive tabs according their
  // throttle status. mActiveTransactions[0] are all unthrottled transactions,
  // mActiveTransactions[1] throttled.
  nsClassHashtable<nsUint64HashKey, nsTArray<RefPtr<nsHttpTransaction>>>
      mActiveTransactions[2];

  // V1 specific
  // Whether we are inside the "stop reading" interval, altered by the throttle
  // ticker
  bool mThrottlingInhibitsReading{false};

  TimeStamp mThrottlingWindowEndsAt;

  // ticker for the 'stop reading'/'resume reading' signal
  nsCOMPtr<nsITimer> mThrottleTicker;
  // Checks if the combination of active transactions requires the ticker.
  bool IsThrottleTickerNeeded();
  // The method also unschedules the delayed resume of background tabs timer
  // if the ticker was about to be scheduled.
  void EnsureThrottleTickerIfNeeded();
  // V1:
  // Drops also the mThrottlingInhibitsReading flag.  Immediate or delayed
  // resume of currently throttled transactions is not affected by this method.
  // V2:
  // Immediate or delayed resume of currently throttled transactions is not
  // affected by this method.
  void DestroyThrottleTicker();
  // V1:
  // Handler for the ticker: alters the mThrottlingInhibitsReading flag.
  // V2:
  // Handler for the ticker: calls ResumeReading() for all throttled
  // transactions.
  void ThrottlerTick();

  // mechanism to delay immediate resume of background tabs and chrome initiated
  // throttled transactions after the last transaction blocking their unthrottle
  // has been removed.  Needs to be delayed because during a page load there is
  // a number of intervals when there is no transaction that would cause
  // throttling. Hence, throttling of long standing responses, like downloads,
  // would be mostly ineffective if resumed during every such interval.
  nsCOMPtr<nsITimer> mDelayedResumeReadTimer;
  // Schedule the resume
  void DelayedResumeBackgroundThrottledTransactions();
  // Simply destroys the timer
  void CancelDelayedResumeBackgroundThrottledTransactions();
  // Handler for the timer: resumes all background throttled transactions
  void ResumeBackgroundThrottledTransactions();

  // Simple helpers, iterates the given hash/array and resume.
  // @param excludeActive: skip active tabid transactions.
  void ResumeReadOf(
      nsClassHashtable<nsUint64HashKey, nsTArray<RefPtr<nsHttpTransaction>>>&,
      bool excludeForActiveTab = false);
  void ResumeReadOf(nsTArray<RefPtr<nsHttpTransaction>>*);

  // Cached status of the active tab active transactions existence,
  // saves a lot of hashtable lookups
  bool mActiveTabTransactionsExist{false};
  bool mActiveTabUnthrottledTransactionsExist{false};

  void LogActiveTransactions(char);

  // When current active tab is changed, this function uses
  // |previousId| to select background transactions and
  // |mCurrentBrowserId| to select foreground transactions.
  // Then, it notifies selected transactions' connection of the new active tab
  // id.
  void NotifyConnectionOfBrowserIdChange(uint64_t previousId);

  void CheckTransInPendingQueue(nsHttpTransaction* aTrans);
};

}  // namespace mozilla::net

#endif  // !nsHttpConnectionMgr_h_
