/* 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 nsHttpConnection_h_
#define nsHttpConnection_h_

#include <functional>
#include "HttpConnectionBase.h"
#include "nsHttpConnectionInfo.h"
#include "nsHttpResponseHead.h"
#include "nsAHttpTransaction.h"
#include "nsCOMPtr.h"
#include "nsProxyRelease.h"
#include "prinrval.h"
#include "mozilla/Mutex.h"
#include "ARefBase.h"
#include "TimingStruct.h"
#include "HttpTrafficAnalyzer.h"
#include "TlsHandshaker.h"

#include "nsIAsyncInputStream.h"
#include "nsIAsyncOutputStream.h"
#include "nsIInterfaceRequestor.h"
#include "nsILoadInfo.h"
#include "nsISocketTransport.h"
#include "nsISupportsPriority.h"
#include "nsITimer.h"
#include "nsITlsHandshakeListener.h"

class nsISocketTransport;
class nsITLSSocketControl;

namespace mozilla {
namespace net {

class nsHttpHandler;
class ASpdySession;

// 1dcc863e-db90-4652-a1fe-13fea0b54e46
#define NS_HTTPCONNECTION_IID \
  {0x1dcc863e, 0xdb90, 0x4652, {0xa1, 0xfe, 0x13, 0xfe, 0xa0, 0xb5, 0x4e, 0x46}}

//-----------------------------------------------------------------------------
// nsHttpConnection - represents a connection to a HTTP server (or proxy)
//
// NOTE: this objects lives on the socket thread only.  it should not be
// accessed from any other thread.
//-----------------------------------------------------------------------------

class nsHttpConnection final : public HttpConnectionBase,
                               public nsAHttpSegmentReader,
                               public nsAHttpSegmentWriter,
                               public nsIInputStreamCallback,
                               public nsIOutputStreamCallback,
                               public nsITransportEventSink,
                               public nsIInterfaceRequestor {
 private:
  virtual ~nsHttpConnection();

 public:
  NS_INLINE_DECL_STATIC_IID(NS_HTTPCONNECTION_IID)
  NS_DECL_HTTPCONNECTIONBASE
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSAHTTPSEGMENTREADER
  NS_DECL_NSAHTTPSEGMENTWRITER
  NS_DECL_NSIINPUTSTREAMCALLBACK
  NS_DECL_NSIOUTPUTSTREAMCALLBACK
  NS_DECL_NSITRANSPORTEVENTSINK
  NS_DECL_NSIINTERFACEREQUESTOR

  nsHttpConnection();

  // Initialize the connection:
  //  info        - specifies the connection parameters.
  //  maxHangTime - limits the amount of time this connection can spend on a
  //                single transaction before it should no longer be kept
  //                alive.  a value of 0xffff indicates no limit.
  [[nodiscard]] virtual nsresult Init(nsHttpConnectionInfo* info,
                                      uint16_t maxHangTime, nsISocketTransport*,
                                      nsIAsyncInputStream*,
                                      nsIAsyncOutputStream*,
                                      bool connectedTransport, nsresult status,
                                      nsIInterfaceRequestor*, PRIntervalTime,
                                      bool forWebSocket);

  //-------------------------------------------------------------------------
  // XXX document when these are ok to call

  bool IsKeepAlive() {
    return (mUsingSpdyVersion != SpdyVersion::NONE) ||
           (mKeepAliveMask && mKeepAlive);
  }

  // Cheap reuse check without the IsAlive() socket probe. Used by
  // AvailableForDispatchNow() to avoid a redundant probe before
  // GetIdleConnection() performs the definitive check.
  bool CanReuseLikely();
  // Returns a short string naming which CanDirectlyActivate() condition is
  // false, for use in diagnostic logs. Returns "ok" when all pass.
  const char* CanDirectlyActivateReason() const;

  // Returns time in seconds for how long connection can be reused.
  uint32_t TimeToLive();

  bool NeedSpdyTunnel() {
    return mConnInfo->UsingHttpsProxy() && !mHasTLSTransportLayer &&
           mConnInfo->UsingConnect();
  }

  // A connection is forced into plaintext when it is intended to be used as a
  // CONNECT tunnel but the setup fails. The plaintext only carries the CONNECT
  // error.
  void ForcePlainText() { mForcePlainText = true; }

  bool IsUrgentStartPreferred() const {
    return mUrgentStartPreferredKnown && mUrgentStartPreferred;
  }
  void SetUrgentStartPreferred(bool urgent);

  void SetIsReusedAfter(uint32_t afterMilliseconds);

  int64_t MaxBytesRead() { return mMaxBytesRead; }
  HttpVersion GetLastHttpResponseVersion() { return mLastHttpResponseVersion; }

  nsresult HandshakeError() const { return mHandshakeError; }

  // Forwarded from TlsHandshaker on CertificateRequest / its resolution;
  // HappyEyeballsTransaction uses these to pause around the cert dialog.
  void OnClientAuthCertificateRequested();
  void OnClientAuthCertificateSelected();

  // Retry ECH config captured at TLS handshake error time. Cached because the
  // NSS SSL state is no longer queryable by HE's failed-connection callback.
  const nsACString& CachedRetryEchConfig() const { return mRetryEchConfig; }

  friend class HttpConnectionForceIO;
  friend class TlsHandshaker;

  // When a persistent connection is in the connection manager idle
  // connection pool, the nsHttpConnection still reads errors and hangups
  // on the socket so that it can be proactively released if the server
  // initiates a termination. Only call on socket thread.
  void BeginIdleMonitoring();
  void EndIdleMonitoring();

  bool UsingSpdy() override { return (mUsingSpdyVersion != SpdyVersion::NONE); }
  SpdyVersion GetSpdyVersion() { return mUsingSpdyVersion; }
  bool EverUsedSpdy() { return mEverUsedSpdy; }
  bool UsingHttp3() override { return false; }

  // true when connection SSL NPN phase is complete and we know
  // authoritatively whether UsingSpdy() or not.
  bool ReportedNPN() { return mReportedSpdy; }

  // When the connection is active this is called up to once every 1 second
  // return the interval (in seconds) that the connection next wants to
  // have this invoked. It might happen sooner depending on the needs of
  // other connections.
  uint32_t ReadTimeoutTick(PRIntervalTime now);

  // For Active and Idle connections, this will be called when
  // mTCPKeepaliveTransitionTimer fires, to check if the TCP keepalive config
  // should move from short-lived (fast-detect) to long-lived.
  static void UpdateTCPKeepalive(nsITimer* aTimer, void* aClosure);

  // When the connection is active this is called every second
  void ReadTimeoutTick();

  int64_t ContentBytesWritten() { return mContentBytesWritten; }

  void SetupSecondaryTLS();
  void SetInTunnel() override;

  // Check active connections for traffic (or not). SPDY connections send a
  // ping, ordinary HTTP connections get some time to get traffic to be
  // considered alive.
  void CheckForTraffic(bool check);

  // NoTraffic() returns true if there's been no traffic on the (non-spdy)
  // connection since CheckForTraffic() was called.
  bool NoTraffic() {
    return mTrafficStamp &&
           (mTrafficCount == (mTotalBytesWritten + mTotalBytesRead));
  }

  // Return true when the socket this connection is using has not been
  // authenticated using a client certificate.  Before SSL negotiation
  // has finished this returns false.
  bool NoClientCertAuth() const override;

  ExtendedCONNECTSupport GetExtendedCONNECTSupport() override;

  int64_t BytesWritten() override { return mTotalBytesWritten; }

  nsISocketTransport* Transport() override { return mSocketTransport; }

  nsresult GetSelfAddr(NetAddr* addr) override;
  nsresult GetPeerAddr(NetAddr* addr) override;
  bool ResolvedByTRR() override;
  bool GetEchConfigUsed() override;
  nsIRequest::TRRMode EffectiveTRRMode() override;
  TRRSkippedReason TRRSkipReason() override;
  bool IsForWebSocket() { return mForWebSocket; }

  // The following functions are related to setting up a tunnel.
  [[nodiscard]] static nsresult MakeConnectString(
      nsAHttpTransaction* trans, nsHttpRequestHead* request, nsACString& result,
      bool aShouldResistFingerprinting);
  [[nodiscard]] static nsresult ReadFromStream(nsIInputStream*, void*,
                                               const char*, uint32_t, uint32_t,
                                               uint32_t*);

  nsresult CreateTunnelStream(nsAHttpTransaction* httpTransaction,
                              HttpConnectionBase** aHttpConnection,
                              bool aIsExtendedCONNECT = false) override;

  // Replace the transaction this H1 connection is currently driving.
  // Analog of Http2Session::SwapTransaction / Http3Session::SwapTransaction:
  // used by the HE / 0-RTT adopt path when a HappyEyeballsTransaction shim
  // is replaced by the real nsHttpTransaction on an already-activated H1
  // connection. No-op if `aOld` isn't the current mTransaction.
  void SwapTransaction(nsAHttpTransaction* aOld, nsAHttpTransaction* aNew);

 private:
  void SetTunnelSetupDone() override;
  nsresult SetupProxyConnectStream() override;
  nsresult SendConnectRequest(void* closure, uint32_t* transactionBytes);

  void HandleTunnelResponse(const nsHttpResponseHead& responseHead,
                            bool* reset);
  void HandleWebSocketResponse(nsHttpRequestHead* requestHead,
                               nsHttpResponseHead* responseHead,
                               uint16_t responseStatus);
  void ResetTransaction(RefPtr<nsAHttpTransaction>&& trans,
                        bool aForH2Proxy = false);

  // Value (set in mTCPKeepaliveConfig) indicates which set of prefs to use.
  enum TCPKeepaliveConfig {
    kTCPKeepaliveDisabled = 0,
    kTCPKeepaliveShortLivedConfig,
    kTCPKeepaliveLongLivedConfig
  };

  [[nodiscard]] nsresult OnTransactionDone(nsresult reason);
  [[nodiscard]] nsresult OnSocketWritable();
  [[nodiscard]] nsresult OnSocketReadable();

  PRIntervalTime IdleTime();
  bool IsAlive();

  // Start the Spdy transaction handler when NPN indicates spdy/*
  void StartSpdy(nsITLSSocketControl* ssl, SpdyVersion spdyVersion);
  // Like the above, but do the bare minimum to do 0RTT data, so we can back
  // it out, if necessary
  void Start0RTTSpdy(SpdyVersion spdyVersion);

  // Helpers for Start*Spdy
  nsresult TryTakeSubTransactions(nsTArray<RefPtr<nsAHttpTransaction> >& list);
  nsresult MoveTransactionsToSpdy(nsresult status,
                                  nsTArray<RefPtr<nsAHttpTransaction> >& list);

  // Directly Add a transaction to an active connection for SPDY
  [[nodiscard]] nsresult AddTransaction(nsAHttpTransaction*, int32_t);

  // Used to set TCP keepalives for fast detection of dead connections during
  // an initial period, and slower detection for long-lived connections.
  [[nodiscard]] nsresult StartShortLivedTCPKeepalives();
  [[nodiscard]] nsresult StartLongLivedTCPKeepalives();
  [[nodiscard]] nsresult DisableTCPKeepalives();

  bool CheckCanWrite0RTTData();
  void PostProcessNPNSetup(bool handshakeSucceeded, bool hasSecurityInfo,
                           bool earlyDataUsed);
  void Reset0RttForSpdy();
  void HandshakeDoneInternal();
  uint32_t TransactionCaps() const { return mTransactionCaps; }

  void MarkAsDontReuse();

  virtual WebTransportSessionBase* GetWebTransportSession(
      nsAHttpTransaction* aTransaction) override;

 private:
  // mTransaction only points to the HTTP Transaction callbacks if the
  // transaction is open, otherwise it is null.
  RefPtr<nsAHttpTransaction> mTransaction;

  RefPtr<TlsHandshaker> mTlsHandshaker;

  nsCOMPtr<nsIAsyncInputStream> mSocketIn;
  nsCOMPtr<nsIAsyncOutputStream> mSocketOut;

  nsresult mSocketInCondition{NS_ERROR_NOT_INITIALIZED};
  nsresult mSocketOutCondition{NS_ERROR_NOT_INITIALIZED};

  RefPtr<nsHttpHandler> mHttpHandler;  // keep gHttpHandler alive

  PRIntervalTime mLastReadTime{0};
  PRIntervalTime mLastWriteTime{0};
  // max download time before dropping keep-alive status
  PRIntervalTime mMaxHangTime{0};
  PRIntervalTime mIdleTimeout;  // value of keep-alive: timeout=
  PRIntervalTime mConsiderReusedAfterInterval{0};
  PRIntervalTime mConsiderReusedAfterEpoch{0};
  TimeStamp mLastTRRResponseTime;   // Time of the last successful TRR response
  int64_t mCurrentBytesRead{0};     // data read per activation
  int64_t mMaxBytesRead{0};         // max read in 1 activation
  int64_t mTotalBytesRead{0};       // total data read
  int64_t mContentBytesWritten{0};  // does not include CONNECT tunnel or TLS

  RefPtr<nsIAsyncInputStream> mInputOverflow;

  // Whether the first non-null transaction dispatched on this connection was
  // urgent-start or not
  bool mUrgentStartPreferred{false};
  // A flag to prevent reset of mUrgentStartPreferred by subsequent transactions
  bool mUrgentStartPreferredKnown{false};
  bool mConnectedTransport{false};
  // assume to keep-alive by default
  bool mKeepAlive{true};
  bool mKeepAliveMask{true};
  bool mDontReuse{false};
  bool mIsReused{false};
  bool mLastTransactionExpectedNoContent{false};
  bool mIdleMonitoring{false};
  bool mInSpdyTunnel{false};
  bool mForcePlainText{false};

  // A snapshot of current number of transfered bytes
  int64_t mTrafficCount{0};
  bool mTrafficStamp{false};  // true then the above is set

  // The number of <= HTTP/1.1 transactions performed on this connection. This
  // excludes spdy transactions.
  uint32_t mHttp1xTransactionCount{0};

  // Keep-Alive: max="mRemainingConnectionUses" provides the number of future
  // transactions (including the current one) that the server expects to allow
  // on this persistent connection.
  uint32_t mRemainingConnectionUses{0xffffffff};

  // version level in use, 0 if unused
  SpdyVersion mUsingSpdyVersion{SpdyVersion::NONE};

  RefPtr<ASpdySession> mSpdySession;
  RefPtr<ASpdySession> mExtendedCONNECTHttp2Session;
  int32_t mPriority{nsISupportsPriority::PRIORITY_NORMAL};
  bool mReportedSpdy{false};

  // mUsingSpdyVersion is cleared when mSpdySession is freed, this is permanent
  bool mEverUsedSpdy{false};

  // mLastHttpResponseVersion stores the last response's http version seen.
  HttpVersion mLastHttpResponseVersion{HttpVersion::v1_1};

  // Set by PostProcessNPNSetup on TLS handshake failure (translated from
  // the security info's PRErrorCode via psm::GetXPCOMFromNSSError). Read
  // by HappyEyeballsTransaction::ReadSegments to surface the cert/TLS
  // error instead of NS_BASE_STREAM_CLOSED.
  nsresult mHandshakeError{NS_OK};

  // Set in PostProcessNPNSetup on SSL_ERROR_ECH_RETRY_WITH_ECH. See
  // CachedRetryEchConfig().
  nsCString mRetryEchConfig;

  // If a large keepalive has been requested for any trans,
  // scale the default by this factor
  uint32_t mDefaultTimeoutFactor{1};

  bool mResponseTimeoutEnabled{false};

  // Flag to indicate connection is in inital keepalive period (fast detect).
  uint32_t mTCPKeepaliveConfig{kTCPKeepaliveDisabled};
  nsCOMPtr<nsITimer> mTCPKeepaliveTransitionTimer;

 private:
  // For ForceSend()
  static void ForceSendIO(nsITimer* aTimer, void* aClosure);
  [[nodiscard]] nsresult MaybeForceSendIO();
  bool mForceSendPending{false};
  nsCOMPtr<nsITimer> mForceSendTimer;

  int64_t mContentBytesWritten0RTT{0};
  bool mDid0RTTSpdy{false};

  nsresult mErrorBeforeConnect = NS_OK;

  nsCOMPtr<nsISocketTransport> mSocketTransport;

  // This flag indicates if the connection is used for WebSocket.
  // - When true and mInSpdyTunnel is also true: WebSocket over HTTP/2.
  // - When true and mInSpdyTunnel is false: WebSocket over HTTP/1.1.
  bool mForWebSocket{false};

  std::function<void()> mContinueHandshakeDone{nullptr};

 private:
  int64_t mTotalBytesWritten = 0;  // does not include CONNECT tunnel

  nsCOMPtr<nsIInputStream> mProxyConnectStream;

  bool mHasTLSTransportLayer{false};
  bool mTransactionDisallowHttp3{false};
};

}  // namespace net
}  // namespace mozilla

#endif  // nsHttpConnection_h_
