current commit
260917.10 · quic: improve stream cleanup & lookup
#pragma once

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include "base_object.h"
#include "bindingdata.h"
#include "defs.h"
#include "session.h"
#include "sessionticket.h"
#include "streams.h"

namespace node::quic {

enum class HeadersKind : uint8_t {
  HINTS,
  INITIAL,
  TRAILING,
};

enum class HeadersFlags : uint8_t {
  NONE,
  TERMINAL,
};

// An Application implements the ALPN-protocol specific semantics on behalf
// of a QUIC Session.
class Session::Application : public MemoryRetainer {
 public:
  using Options = Session::Application_Options;

  Application(Session* session, const Options& options);
  DISALLOW_COPY_AND_MOVE(Application)

  // Get the active options for this application. These may differ from the
  // options passed at construction time since some options can be negotiated.
  virtual const Options& options() const = 0;

  // The type of Application, exposed via the session state so JS
  // can observe which Application was selected after ALPN negotiation.
  // This is used primarily for testing/debugging.
  enum class Type : uint8_t {
    NONE = 0,     // Not yet selected (server pre-negotiation)
    DEFAULT = 1,  // DefaultApplication (non-h3 ALPN)
    HTTP3 = 2,    // Http3ApplicationImpl (h3 / h3-XX ALPN)
  };
  virtual Type type() const = 0;

  virtual bool Start();

  // Returns true if Start() has been called successfully.
  virtual bool is_started() const { return false; }

  // Called when the server rejects 0-RTT early data. The application
  // must destroy all streams that were opened during the 0-RTT phase
  // since ngtcp2 has already discarded their internal state.
  virtual void EarlyDataRejected() = 0;

  // The "no error code" is the application-level error code that signals
  // "no error". Per the QUIC spec, this can vary by application protocol
  // and is not necessarily 0.
  virtual error_code GetNoErrorCode() const = 0;

  // The "internal error code" is the application-level error code used
  // to signal a non-specific failure when no more specific code has
  // been provided by the caller. For example, `writer.fail(reason)` on
  // the JS side uses this code when `reason` is not a `QuicError`
  // carrying an explicit code. For HTTP/3 this is
  // `NGHTTP3_H3_INTERNAL_ERROR` (0x102); for raw QUIC applications
  // there is no defined application code so we fall back to
  // NGTCP2_INTERNAL_ERROR (0x1).
  virtual error_code GetInternalErrorCode() const = 0;

  // The "request rejected" code is sent on RESET_STREAM when an incoming
  // request stream is rejected without any application processing (e.g.
  // the session has no consumer for it), so the peer learns the request
  // was not processed. For HTTP/3 this is NGHTTP3_H3_REQUEST_REJECTED
  // (0x10b); other applications have no such semantic and reuse the
  // "no error" code.
  virtual error_code GetRequestRejectedCode() const = 0;

  // Called after Session::Receive processes a packet, outside all callback
  // scopes. Applications can use this to handle deferred operations that
  // require calling into JS (e.g., HTTP/3 GOAWAY processing).
  virtual void PostReceive() {}

  // Called when ngtcp2 notifies us that a new remote stream has been
  // opened. The Application decides whether to create a Stream object
  // (and fire the JS onstream callback) based on the stream type. For
  // example, HTTP/3 only creates Stream objects for bidi streams since
  // uni streams are managed internally by nghttp3.
  virtual bool ReceiveStreamOpen(stream_id id) = 0;

  // Session will forward all received stream data immediately on to the
  // Application without any additional processing. Every byte delivered here
  // is charged against both the session-level and the stream-level receive
  // window, and it is up to the Application to return that credit (see
  // ReturnConnectionCredit and Stream::ReturnFlowControlCredit) once the
  // bytes have been consumed or discarded.
  virtual bool ReceiveStreamData(stream_id id,
                                 const uint8_t* data,
                                 size_t datalen,
                                 const Stream::ReceiveDataFlags& flags,
                                 void* stream_user_data) = 0;

  // Session will forward all data acknowledgements for a stream to the
  // Application.
  virtual bool AcknowledgeStreamData(stream_id id, size_t datalen);

  // Called when a pending transport stream receives its stream ID. Protocols
  // can use this to flush operations that require an opened stream. Returns
  // false if deferred application data could not be submitted.
  virtual bool StreamOpened(Stream& stream) { return true; }

  // Called when ngtcp2 reports NGTCP2_ERR_STREAM_SHUT_WR for a stream.
  // Applications that manage their own framing (e.g., HTTP/3) must inform
  // their protocol layer that the stream's write side is shut so it stops
  // queuing data for that stream. The default is a no-op.
  virtual void StreamWriteShut(stream_id id) {}

  // Called to mark the identified stream as being blocked. Not all
  // Application types will support blocked streams, and those that do will do
  // so differently.
  virtual void BlockStream(stream_id id) {}

  // Called when the session determines that there is outbound data available
  // to send for the given stream.
  virtual void ResumeStream(stream_id id) {}

  // Returns true if the application manages stream FIN internally (e.g.,
  // HTTP/3 uses nghttp3 which sends FIN via the fin flag in writev_stream).
  // When true, the stream infrastructure must NOT call
  // ngtcp2_conn_shutdown_stream_write when the JS write side ends —
  // the application protocol layer handles it.
  virtual bool stream_fin_managed_by_application() const { return false; }

  // Called when the Session determines that the flow control window for the
  // given stream has been expanded. Not all Application types will require
  // this notification so the default is to do nothing.
  virtual void ExtendMaxStreamData(Stream* stream, uint64_t max_data) {
    Debug(session_, "Application extending max stream data");
    // By default do nothing.
  }

  // Different Applications may wish to set some application data in the
  // session ticket (e.g. http/3 would set server settings in the application
  // data). The first byte written MUST be the Application::Type enum value.
  // By default, writes just the type byte.
  virtual void CollectSessionTicketAppData(
      SessionTicket::AppData* app_data) const;

  // Validates the application data embedded in a session ticket offered by
  // a resuming client, and decides whether the ticket may be used. The
  // Application is always installed by the time a ticket can be decrypted,
  // so each Application checks its own data here. By default, there's
  // nothing to get.
  virtual SessionTicket::AppData::Status ExtractSessionTicketAppData(
      const SessionTicket::AppData& app_data,
      SessionTicket::AppData::Source::Flag flag);

  // Notifies the Application that the identified stream has been closed.
  virtual void ReceiveStreamClose(stream_id id,
                                  Stream* stream,
                                  QuicError&& error = QuicError());

  // Notifies the Application that the Stream for the identified stream has
  // been removed from the session and may be freed immediately afterwards.
  // Applications caching the Stream pointer must drop it here.
  virtual void StreamRemoved(stream_id id) {}

  // Notifies the Application that the identified stream has been reset.
  virtual void ReceiveStreamReset(Stream* stream,
                                  uint64_t final_size,
                                  QuicError&& error = QuicError());

  // Notifies the Application that the identified stream should stop sending.
  virtual void ReceiveStreamStopSending(Stream* stream,
                                        QuicError&& error = QuicError());

  // Submits an outbound block of headers for the given stream. Not all
  // Application types will support headers, in which case this function
  // should return false.
  virtual bool SendHeaders(Stream& stream,
                           HeadersKind kind,
                           const v8::Local<v8::Array>& headers,
                           HeadersFlags flags = HeadersFlags::NONE) {
    return false;
  }

  // Updates JavaScript callback interest for an application's stream header
  // events. Applications without header semantics ignore this.
  virtual void SetHeadersInterest(Stream& stream,
                                  bool wants_headers,
                                  bool wants_trailers) {}

  // Returns true if the application protocol supports sending and
  // receiving headers on streams (e.g. HTTP/3). Applications that
  // do not support headers should return false (the default).
  virtual bool SupportsHeaders() const { return false; }

  // True if this application dispatches the session-level stream
  // callbacks (onheaders et al) for incoming streams when they are
  // registered on the session.
  virtual bool SupportsStreamCallbacks() const { return false; }

  // Initiates application-level graceful shutdown signaling (e.g.,
  // HTTP/3 GOAWAY). Called when Session::Close(GRACEFUL) is invoked.
  virtual void BeginShutdown() {}

  // Completes the application-level graceful shutdown. Called from
  // FinishClose() before CONNECTION_CLOSE is sent. For HTTP/3, this
  // sends the final GOAWAY with the actual last accepted stream ID.
  virtual void CompleteShutdown() {}

  // Set the priority level of the stream if supported by the application. Not
  // all applications support priorities, in which case this function is a
  // non-op.
  virtual void SetStreamPriority(
      const Stream& stream,
      StreamPriority priority = StreamPriority::DEFAULT,
      StreamPriorityFlags flags = StreamPriorityFlags::NON_INCREMENTAL) {}

  struct StreamPriorityResult {
    StreamPriority priority;
    StreamPriorityFlags flags;
  };

  // Get the priority level of the stream if supported by the application. Not
  // all applications support priorities, in which case this function returns
  // the default stream priority.
  virtual StreamPriorityResult GetStreamPriority(const Stream& stream) {
    return {StreamPriority::DEFAULT, StreamPriorityFlags::NON_INCREMENTAL};
  }

  virtual int GetStreamData(StreamData* data) = 0;
  virtual bool StreamCommit(StreamData* data, size_t datalen) = 0;

  inline Environment* env() const { return session().env(); }

  inline Session& session() {
    CHECK_NOT_NULL(session_);
    return *session_;
  }
  inline const Session& session() const {
    CHECK_NOT_NULL(session_);
    return *session_;
  }

  // Returns the connection-level flow control credit for `datalen` bytes that
  // were delivered to the Application but discarded without ever reaching a
  // Stream. Dropping them silently would permanently shrink the session's
  // shared receive window.
  void ReturnConnectionCredit(size_t datalen);

 private:
  Session* session_ = nullptr;
};

// Create a DefaultApplication for the given session.
std::unique_ptr<Session::Application> CreateDefaultApplication(
    Session* session, const Session::Application_Options& options);

}  // namespace node::quic

#endif  // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS