current commit
261007.7 · quic: give each BindingData its own allocator state
#pragma once

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include <base_object.h>
#include <env.h>
#include <memory_tracker.h>
#include <nghttp3/nghttp3.h>
#include <ngtcp2/ngtcp2.h>
#include <ngtcp2/ngtcp2_crypto.h>
#include <node.h>
#include <node_mem.h>
#include <uv.h>
#include <v8.h>
#include <functional>
#include <memory>
#include <unordered_map>
#include <vector>
#include "defs.h"

namespace node::quic {

class Endpoint;
class Packet;
class Session;
class SessionManager;
struct QuicAllocState;

// ============================================================================

// The FunctionTemplates the BindingData will store for us.
#define QUIC_CONSTRUCTORS(V)                                                   \
  V(endpoint)                                                                  \
  V(session)                                                                   \
  V(stream)                                                                    \
  V(udp)

// The callbacks are persistent v8::Function references that are set in the
// quic::BindingState used to communicate data and events back out to the JS
// environment. They are set once from the JavaScript side when the
// internalBinding('quic') is first loaded.
#define QUIC_JS_CALLBACKS(V)                                                   \
  V(endpoint_close, EndpointClose)                                             \
  V(session_close, SessionClose)                                               \
  V(session_application, SessionApplication)                                   \
  V(session_early_data_rejected, SessionEarlyDataRejected)                     \
  V(session_goaway, SessionGoaway)                                             \
  V(session_datagram, SessionDatagram)                                         \
  V(session_datagram_status, SessionDatagramStatus)                            \
  V(session_handshake, SessionHandshake)                                       \
  V(session_keylog, SessionKeyLog)                                             \
  V(session_qlog, SessionQlog)                                                 \
  V(session_new, SessionNew)                                                   \
  V(session_new_token, SessionNewToken)                                        \
  V(session_origin, SessionOrigin)                                             \
  V(session_path_validation, SessionPathValidation)                            \
  V(session_ticket, SessionTicket)                                             \
  V(session_version_negotiation, SessionVersionNegotiation)                    \
  V(stream_available, StreamAvailable)                                         \
  V(stream_blocked, StreamBlocked)                                             \
  V(stream_close, StreamClose)                                                 \
  V(stream_created, StreamCreated)                                             \
  V(stream_drain, StreamDrain)                                                 \
  V(stream_headers, StreamHeaders)                                             \
  V(stream_reset, StreamReset)                                                 \
  V(stream_stop_sending, StreamStopSending)                                    \
  V(stream_trailers, StreamTrailers)

// The various JS strings the implementation uses.
#define QUIC_STRINGS(V)                                                        \
  V(abandoned, "abandoned")                                                    \
  V(aborted, "aborted")                                                        \
  V(acknowledged, "acknowledged")                                              \
  V(ack_delay_exponent, "ackDelayExponent")                                    \
  V(active_connection_id_limit, "activeConnectionIDLimit")                     \
  V(address_lru_size, "addressLRUSize")                                        \
  V(allow, "allow")                                                            \
  V(application, "application")                                                \
  V(authoritative, "authoritative")                                            \
  V(bbr, "bbr")                                                                \
  V(ca, "ca")                                                                  \
  V(cc_algorithm, "cc")                                                        \
  V(certificate_compression, "certificateCompression")                         \
  V(certs, "certs")                                                            \
  V(code, "code")                                                              \
  V(ciphers, "ciphers")                                                        \
  V(crl, "crl")                                                                \
  V(cubic, "cubic")                                                            \
  V(datagram_drop_policy, "datagramDropPolicy")                                \
  V(deny, "deny")                                                              \
  V(disable_stateless_reset, "disableStatelessReset")                          \
  V(draining_period_multiplier, "drainingPeriodMultiplier")                    \
  V(enable_connect_protocol, "enableConnectProtocol")                          \
  V(enable_early_data, "enableEarlyData")                                      \
  V(enable_datagrams, "enableDatagrams")                                       \
  V(enable_tls_trace, "tlsTrace")                                              \
  V(endpoint, "Endpoint")                                                      \
  V(endpoint_udp, "Endpoint::UDP")                                             \
  V(failure, "failure")                                                        \
  V(groups, "groups")                                                          \
  V(handshake_timeout, "handshakeTimeout")                                     \
  V(http3_alpn, &NGHTTP3_ALPN_H3[1])                                           \
  V(initial_rtt, "initialRtt")                                                 \
  V(keep_alive_timeout, "keepAlive")                                           \
  V(initial_max_data, "initialMaxData")                                        \
  V(initial_max_stream_data_bidi_local, "initialMaxStreamDataBidiLocal")       \
  V(initial_max_stream_data_bidi_remote, "initialMaxStreamDataBidiRemote")     \
  V(initial_max_stream_data_uni, "initialMaxStreamDataUni")                    \
  V(initial_max_streams_bidi, "initialMaxStreamsBidi")                         \
  V(initial_max_streams_uni, "initialMaxStreamsUni")                           \
  V(ipv6_only, "ipv6Only")                                                     \
  V(reuse_port, "reusePort")                                                   \
  V(keylog, "keylog")                                                          \
  V(keys, "keys")                                                              \
  V(lost, "lost")                                                              \
  V(max_ack_delay, "maxAckDelay")                                              \
  V(max_connections_per_host, "maxConnectionsPerHost")                         \
  V(max_connections_total, "maxConnectionsTotal")                              \
  V(max_datagram_frame_size, "maxDatagramFrameSize")                           \
  V(max_datagram_send_attempts, "maxDatagramSendAttempts")                     \
  V(stream_idle_timeout, "streamIdleTimeout")                                  \
  V(max_field_section_size, "maxFieldSectionSize")                             \
  V(max_header_length, "maxHeaderLength")                                      \
  V(max_header_pairs, "maxHeaderPairs")                                        \
  V(idle_timeout, "idleTimeout")                                               \
  V(max_idle_timeout, "maxIdleTimeout")                                        \
  V(max_payload_size, "maxPayloadSize")                                        \
  V(retry_rate, "retryRate")                                                   \
  V(retry_burst, "retryBurst")                                                 \
  V(stateless_reset_rate, "statelessResetRate")                                \
  V(stateless_reset_burst, "statelessResetBurst")                              \
  V(version_negotiation_rate, "versionNegotiationRate")                        \
  V(version_negotiation_burst, "versionNegotiationBurst")                      \
  V(immediate_close_rate, "immediateCloseRate")                                \
  V(immediate_close_burst, "immediateCloseBurst")                              \
  V(session_creation_rate, "sessionCreationRate")                              \
  V(session_creation_burst, "sessionCreationBurst")                            \
  V(block_list, "blockList")                                                   \
  V(block_list_policy, "blockListPolicy")                                      \
  V(max_stream_window, "maxStreamWindow")                                      \
  V(max_window, "maxWindow")                                                   \
  V(min_version, "minVersion")                                                 \
  V(port, "port")                                                              \
  V(preferred_address_ipv4, "preferredAddressIpv4")                            \
  V(preferred_address_ipv6, "preferredAddressIpv6")                            \
  V(preferred_address_strategy, "preferredAddressPolicy")                      \
  V(alpn, "alpn")                                                              \
  V(qlog, "qlog")                                                              \
  V(qpack_blocked_streams, "qpackBlockedStreams")                              \
  V(qpack_encoder_max_dtable_capacity, "qpackEncoderMaxDTableCapacity")        \
  V(qpack_max_dtable_capacity, "qpackMaxDTableCapacity")                       \
  V(reason, "reason")                                                          \
  V(reject_unauthorized, "rejectUnauthorized")                                 \
  V(reno, "reno")                                                              \
  V(reset_token_secret, "resetTokenSecret")                                    \
  V(retry_token_expiration, "retryTokenExpiration")                            \
  V(rx_loss, "rxDiagnosticLoss")                                               \
  V(servername, "servername")                                                  \
  V(session, "Session")                                                        \
  V(sni, "sni")                                                                \
  V(stream, "Stream")                                                          \
  V(success, "success")                                                        \
  V(tls_options, "tls")                                                        \
  V(token, "token")                                                            \
  V(token_expiration, "tokenExpiration")                                       \
  V(token_secret, "tokenSecret")                                               \
  V(transport, "transport")                                                    \
  V(transport_params, "transportParams")                                       \
  V(type, "type")                                                              \
  V(tx_loss, "txDiagnosticLoss")                                               \
  V(udp_receive_buffer_size, "udpReceiveBufferSize")                           \
  V(udp_send_buffer_size, "udpSendBufferSize")                                 \
  V(udp_ttl, "udpTTL")                                                         \
  V(unacknowledged_packet_threshold, "unacknowledgedPacketThreshold")          \
  V(validate_address, "validateAddress")                                       \
  V(verify_client, "verifyClient")                                             \
  V(verify_hostname, "verifyHostname")                                         \
  V(verify_peer_strict, "verifyPeerStrict")                                    \
  V(verify_private_key, "verifyPrivateKey")                                    \
  V(version, "version")

// =============================================================================
// Lightweight wrappers around uv_check_t that ensure safe handle closure.
// The check handle is embedded in a heap-allocated CheckWrap whose destruction
// is deferred until the uv_close callback fires, preventing use-after-free
// when the owning object is destroyed before libuv finishes closing the handle.
// Follows the same two-layer pattern as TimerWrap / TimerWrapHandle
// (see timer_wrap.h).
// TODO(@jasnell): Consider moving it out to a separate file like timer_wrap.h.
class CheckWrap final : public MemoryRetainer {
 public:
  using CheckCb = std::function<void()>;

  template <typename... Args>
  explicit CheckWrap(Environment* env, Args&&... args)
      : env_(env), fn_(std::forward<Args>(args)...) {
    uv_check_init(env->event_loop(), &check_);
    check_.data = this;
  }

  DISALLOW_COPY_AND_MOVE(CheckWrap)

  inline Environment* env() const { return env_; }

  void Start();
  void Stop();
  void Close();
  void Ref();
  void Unref();

  SET_NO_MEMORY_INFO()
  SET_MEMORY_INFO_NAME(CheckWrap)
  SET_SELF_SIZE(CheckWrap)

 private:
  static void OnCheck(uv_check_t* check);
  static void CheckClosedCb(uv_handle_t* handle);
  ~CheckWrap() = default;

  Environment* env_;
  CheckCb fn_;
  uv_check_t check_;

  friend std::unique_ptr<CheckWrap>::deleter_type;
};

class CheckWrapHandle : public MemoryRetainer {
 public:
  template <typename... Args>
  explicit CheckWrapHandle(Environment* env, Args&&... args)
      : check_(new CheckWrap(env, std::forward<Args>(args)...)) {
    env->AddCleanupHook(CleanupHook, this);
  }

  DISALLOW_COPY_AND_MOVE(CheckWrapHandle)

  ~CheckWrapHandle() { Close(); }

  inline operator bool() const { return check_ != nullptr; }

  void Start();
  void Stop();
  void Close();
  void Ref();
  void Unref();

  void MemoryInfo(node::MemoryTracker* tracker) const override;

  SET_MEMORY_INFO_NAME(CheckWrapHandle)
  SET_SELF_SIZE(CheckWrapHandle)

 private:
  static void CleanupHook(void* data);
  CheckWrap* check_;
};

// =============================================================================
// The BindingState object holds state for the internalBinding('quic') binding
// instance. It is mostly used to hold the persistent constructors, strings, and
// callback references used for the rest of the implementation.
//
// TODO(@jasnell): Make this snapshotable?
class BindingData final
    : public BaseObject,
      public Cleanable,
      public mem::NgLibMemoryManager<BindingData, ngtcp2_mem> {
 public:
  SET_BINDING_ID(quic_binding_data)
  static void InitPerContext(Realm* realm, v8::Local<v8::Object> target);
  static void RegisterExternalReferences(ExternalReferenceRegistry* registry);

  static BindingData& Get(Environment* env);
  static inline BindingData& Get(Realm* realm) { return Get(realm->env()); }

  BindingData(Realm* realm, v8::Local<v8::Object> object);
  ~BindingData() override;
  DISALLOW_COPY_AND_MOVE(BindingData)

  void MemoryInfo(MemoryTracker* tracker) const override;
  SET_MEMORY_INFO_NAME(BindingData)
  SET_SELF_SIZE(BindingData)

  // NgLibMemoryManager — the base class provides CheckAllocatedSize,
  // IncreaseAllocatedSize, DecreaseAllocatedSize, and StopTrackingMemory.
  // Actual allocations go through the allocators below.
  void CheckAllocatedSize(size_t previous_size) const;
  void IncreaseAllocatedSize(size_t size);
  void DecreaseAllocatedSize(size_t size);

  // The allocators can outlive the BindingData; see QuicAllocState.
  ngtcp2_mem* ngtcp2_allocator();
  nghttp3_mem* nghttp3_allocator();

  // Installs the set of JavaScript callback functions that are used to
  // bridge out to the JS API.
  JS_METHOD(SetCallbacks);
  JS_METHOD(SendHeaders);
  JS_METHOD(SetHeadersInterest);

  // Lazily-created per-Realm SessionManager. Centralizes CID -> Session
  // routing so that any endpoint can route packets to any session.
  SessionManager& session_manager();

  // Schedule a session for deferred SendPendingData. Sessions are accumulated
  // during the I/O poll phase (via Endpoint::Receive -> Session::ReadPacket)
  // and flushed in a uv_check callback immediately after poll completes.
  // This batches multiple received packets before generating responses,
  // allowing ngtcp2 to make better ACK coalescing decisions.
  void ScheduleSessionFlush(const BaseObjectPtr<Session>& session);

  std::unordered_map<Endpoint*, BaseObjectPtr<BaseObject>> listening_endpoints;

  v8::Local<v8::String> error_name_string(const char* name);

  size_t current_ngtcp2_memory_ = 0;

  // The following set up various storage and accessors for common strings,
  // construction templates, and callbacks stored on the BindingData. These
  // are all defined in defs.h

#define V(name)                                                                \
  void set_##name##_constructor_template(                                      \
      v8::Local<v8::FunctionTemplate> tmpl);                                   \
  v8::Local<v8::FunctionTemplate> name##_constructor_template() const;
  QUIC_CONSTRUCTORS(V)
#undef V

  void set_transport_params_template(v8::Local<v8::DictionaryTemplate> tmpl);
  v8::Local<v8::DictionaryTemplate> transport_params_template() const;

  void set_application_options_template(v8::Local<v8::DictionaryTemplate> tmpl);
  v8::Local<v8::DictionaryTemplate> application_options_template() const;

#define V(name, _)                                                             \
  void set_##name##_callback(v8::Local<v8::Function> fn);                      \
  v8::Local<v8::Function> name##_callback() const;
  QUIC_JS_CALLBACKS(V)
#undef V

#define V(name, _) v8::Local<v8::String> name##_string() const;
  QUIC_STRINGS(V)
#undef V

#define V(name, _) v8::Local<v8::String> on_##name##_string() const;
  QUIC_JS_CALLBACKS(V)
#undef V

#define V(name) v8::Global<v8::FunctionTemplate> name##_constructor_template_;
  QUIC_CONSTRUCTORS(V)
#undef V

  v8::Global<v8::DictionaryTemplate> transport_params_template_;
  v8::Global<v8::DictionaryTemplate> application_options_template_;

#define V(name, _) v8::Global<v8::Function> name##_callback_;
  QUIC_JS_CALLBACKS(V)
#undef V

#define V(name, _) mutable v8::Eternal<v8::String> name##_string_;
  QUIC_STRINGS(V)
#undef V

#define V(name, _) mutable v8::Eternal<v8::String> on_##name##_string_;
  QUIC_JS_CALLBACKS(V)
#undef V

  // Lazy cache backing error_name_string()
  std::unordered_map<const char*, v8::Eternal<v8::String>> error_name_strings_;

  std::unique_ptr<SessionManager> session_manager_;

  // Type-erased arena storage. The concrete AliasedStructArena<T> types
  // are only complete in the .cc files where Stream::State etc. are defined.
  // Each .cc file provides typed accessor methods. The deleters are set
  // when the arenas are created so that ~BindingData destroys them correctly.
  using ArenaDeleter = void (*)(void*);
  using ArenaPtr = std::unique_ptr<void, ArenaDeleter>;
  ArenaPtr stream_state_arena_{nullptr, +[](void*) {}};
  ArenaPtr stream_stats_arena_{nullptr, +[](void*) {}};
  ArenaPtr session_state_arena_{nullptr, +[](void*) {}};
  ArenaPtr session_stats_arena_{nullptr, +[](void*) {}};
  ArenaPtr endpoint_state_arena_{nullptr, +[](void*) {}};
  ArenaPtr endpoint_stats_arena_{nullptr, +[](void*) {}};

  QuicAllocState* alloc_state_;

  // Deferred send flush state. The CheckWrapHandle fires immediately after
  // the I/O poll phase in the same event loop tick, allowing batched
  // receive processing: all packets are read during poll, then
  // SendPendingData is called once per dirty session in the check callback.
  CheckWrapHandle flush_check_;
  std::vector<BaseObjectPtr<Session>> pending_flush_sessions_;
  bool flush_check_started_ = false;

  void OnFlushCheck();

 private:
  void Clean() override;
};

JS_METHOD_IMPL(IllegalConstructor);

// The ngtcp2 and nghttp3 callbacks have certain restrictions
// that forbid re-entry. We provide the following scopes for
// use in those to help protect against it.
// These callback scopes are per-session, not per-environment. This ensures
// that one session's ngtcp2/nghttp3 callback does not block an unrelated
// session from sending packets. A BaseObjectPtr prevents the Session from
// being prematurely freed while the scope is alive on the stack.
struct NgTcp2CallbackScope final {
  BaseObjectPtr<Session> session;
  explicit NgTcp2CallbackScope(Session* session);
  DISALLOW_COPY_AND_MOVE(NgTcp2CallbackScope)
  ~NgTcp2CallbackScope();
};

struct NgHttp3CallbackScope final {
  BaseObjectPtr<Session> session;
  explicit NgHttp3CallbackScope(Session* session);
  DISALLOW_COPY_AND_MOVE(NgHttp3CallbackScope)
  ~NgHttp3CallbackScope();
};

struct CallbackScopeBase {
  Environment* env;
  v8::Context::Scope context_scope;
  v8::TryCatch try_catch;

  explicit CallbackScopeBase(Environment* env);
  DISALLOW_COPY_AND_MOVE(CallbackScopeBase)
  ~CallbackScopeBase();
};

// Maintains a strong reference to BaseObject type ptr to keep it alive during
// a MakeCallback during which it might be destroyed.
template <typename T>
struct CallbackScope final : public CallbackScopeBase {
  BaseObjectPtr<T> ref;
  explicit CallbackScope(const T* ptr)
      : CallbackScopeBase(ptr->env()), ref(ptr) {}
  DISALLOW_COPY_AND_MOVE(CallbackScope)
  explicit CallbackScope(T* ptr) : CallbackScopeBase(ptr->env()), ref(ptr) {}
};

}  // namespace node::quic

#endif  // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS