/* vim:set ts=4 sw=2 sts=2 et cin: */
/* 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 ZeroRttHandle_h_
#define ZeroRttHandle_h_

#include "mozilla/Maybe.h"
#include "nsIWeakReferenceUtils.h"
#include "nsISupportsImpl.h"
#include "nscore.h"

namespace mozilla::net {

class HappyEyeballsConnectionAttempt;
class HappyEyeballsTransaction;
class nsAHttpSegmentReader;
class nsHttpTransaction;

// Shared 0-RTT coordinator for a single Happy Eyeballs race. One
// ZeroRttHandle is created per HappyEyeballsConnectionAttempt and is
// shared by every HappyEyeballsTransaction racer. The per-attempt
// 0-RTT request-stream offset lives on each HappyEyeballsTransaction
// (m0RttRequestStreamOffset); the handle holds only race-wide state
// (winner identity, started/rejected flags).
//
// Design constraints:
//  * We NEVER touch the real transaction's 0-RTT flags during the race.
//    The handle reads directly from the real txn's request stream at
//    the caller's recorded offset using nsISeekableStream.
//  * The handle resolves the real txn lazily through its weak ref to
//    the owning HE (mHet). If that ref can't resolve (HE gone, txn
//    closed) Do0RTT returns false and the other methods no-op.
//  * When any attempt's Do0RTT returns true, the handle sets a one-way
//    mAny0RttStarted flag. Non-0-RTT racers are then disqualified —
//    a successful Close on one is converted to a failure so HE drops
//    it from the race. Rationale: 0-RTT PSK tickets are single-use.
//    The guard doesn't fire on non-NS_OK closes (e.g. Http2Session
//    cleanup with NS_BASE_STREAM_WOULD_BLOCK); the HET::OnSucceeded
//    rewind block is the fallback for that case.
//  * First Finish0RTT call wins. The handle seeks the real txn's
//    request stream — to the winner's offset on accept (so the real
//    txn sees EOF and doesn't duplicate bytes) or to 0 on reject (so
//    the real txn resends from the start) — and synchronously invokes
//    the winner's connected callback.
//  * Once a winner is declared, subsequent losing-racer ReadSegments
//    return NS_BASE_STREAM_CLOSED so they can't advance the request
//    stream past where the real txn needs to read.
//  * Response bytes can't land on HT post-winner: Adopt's carrier
//    swap runs synchronously inside Finish0RTT, so the carrier hands
//    response bytes directly to the real txn from then on.
class ZeroRttHandle {
 public:
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ZeroRttHandle)

  explicit ZeroRttHandle(HappyEyeballsConnectionAttempt* aHet);

  // Corresponds to nsAHttpTransaction::Do0RTT. Returns true if the
  // caller is eligible and has been accepted into the 0-RTT flow —
  // the caller's m0RttRequestStreamOffset is set to Some(0).
  bool Do0RTT(HappyEyeballsTransaction* aCaller, bool aCanSendEarlyData);

  // Corresponds to nsAHttpTransaction::ReadSegments. Seeks the real
  // transaction's request stream to aOffset, forwards bytes to aReader,
  // and advances aOffset on success. The caller's offset slot
  // (HappyEyeballsTransaction::Request0RttStreamOffset) is read and
  // updated through this reference; we don't need the HET itself here.
  nsresult ReadSegments(mozilla::Maybe<uint64_t>& aOffset,
                        nsAHttpSegmentReader* aReader, uint32_t aCount,
                        uint32_t* aCountRead);

  // Corresponds to nsAHttpTransaction::Finish0RTT. First call wins:
  // adopts the real txn onto the winning HT (via HET::AdoptWinner),
  // seeks the real txn's request stream (to winner-offset on accept,
  // 0 on reject), and invokes the winner's connected callback so the
  // establisher chain fires.
  nsresult Finish0RTT(HappyEyeballsTransaction* aCaller, bool aRestart,
                      bool aAlpnChanged);

  // True if the caller should be disqualified from the race because
  // some racer started 0-RTT but the caller did not. Consulted from
  // HappyEyeballsTransaction::Close to convert a successful Close
  // reason into NS_ERROR_FAILURE, so the non-0-RTT attempt drops out
  // and HE can keep waiting for the 0-RTT racer. Fires only on
  // NS_SUCCEEDED closes — HET::OnSucceeded has a rewind fallback for
  // the non-NS_OK close paths.
  bool ShouldDisqualify(const HappyEyeballsTransaction* aCaller) const;

  // True if any racer attempt entered the 0-RTT flow (Do0RTT returned
  // true) during the race. Consulted by HCA::Abandon() to detect an
  // abandoned 0-RTT attempt whose txn was locked from the pending queue,
  // and by HCA::OnSucceeded() to detect the "0-RTT racer advanced the
  // stream but a non-0-RTT conn won" case and rewind the request stream.
  bool AnyStarted() const { return mAny0RttStarted; }

  // True if a winner was ever declared via Finish0RTT. Outlives mWinner:
  // Cleanup() clears mWinner to break the RefPtr cycle but mHadWinner
  // remains set so callers can still distinguish "no winner yet" from
  // "winner declared and then cleaned up".
  bool HadWinner() const { return mHadWinner; }

  // Resolve the real nsHttpTransaction via the HET weak ref. Returns
  // nullptr once Cleanup() has run or if the real txn is closed.
  // HappyEyeballsTransaction::RequestHead uses this so Http3Stream /
  // Http2Stream read :method/:authority/:path/:scheme from the real
  // txn's head — NullHttpTransaction's stub only has Host set to
  // connect-target:port and no path, which is what the HT shim would
  // otherwise return before Adopt.
  nsHttpTransaction* RealTxn() const;

  // Test-only: force the race-wide 0-RTT started flag directly.
  void SetAnyStartedForTesting() { mAny0RttStarted = true; }

  void Cleanup();

 private:
  ~ZeroRttHandle() = default;

  // Lifecycle state.
  //   Open           : default. Race in progress. Do0RTT and Finish0RTT
  //                    can promote the handle out of this state.
  //   WinnerDeclared : first Finish0RTT call has run; mWinner and
  //                    mHadWinner are set. Subsequent Finish0RTT calls
  //                    are loser no-ops.
  //   CleanedUp      : Cleanup() has run; mHet is null and the handle's
  //                    weak ref to HE is gone. Any 0-RTT method that
  //                    requires resolving the real txn becomes a no-op.
  //
  // Legal transitions:
  //   Open           -> WinnerDeclared | CleanedUp
  //   WinnerDeclared -> CleanedUp
  //   CleanedUp      -> CleanedUp (idempotent)
  enum class State : uint8_t {
    Open,
    WinnerDeclared,
    CleanedUp,
  };

  // Single dispatcher for state transitions. Switches on the target
  // state, asserts the move is legal from the current state, commits
  // mState, and runs the actions tied to entering that state.
  //
  // aWinner / aRejected are consumed by the WinnerDeclared entry;
  // ignored by other cases.
  void Transition(State aNext, HappyEyeballsTransaction* aWinner = nullptr,
                  bool aRejected = false);

  // Weak ref to the owning HET. We look up the real nsHttpTransaction
  // through HET->RealHttpTransaction() each time because the HT may
  // have been created (and this handle constructed) while HET's
  // mTransaction was still a speculative NullHttpTransaction — a
  // later Claim() replaces it with the real txn, and we need to pick
  // that change up without explicit notifications.
  nsWeakPtr mHet;

  // First attempt to reach Finish0RTT wins.  RefPtr — Cleanup() clears
  // mWinner to break the RefPtr cycle with HET::mZeroRttHandle.
  RefPtr<HappyEyeballsTransaction> mWinner;

  bool mHadWinner = false;

  // Any attempt has ever successfully entered the 0-RTT flow.
  bool mAny0RttStarted = false;

  // Set to true in Finish0RTT when called with aRestart=true. For H1
  // this means the server rejected early data; for H2 it means ALPN
  // changed (so H2 can't reuse its frame cache).
  bool mRejected = false;

  State mState = State::Open;
};

}  // namespace mozilla::net

#endif
