%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/src/quic/
Upload File :
Create Path :
Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/src/quic/session.cc

#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC

#include "session.h"
#include <aliased_struct-inl.h>
#include <async_wrap-inl.h>
#include <crypto/crypto_util.h>
#include <debug_utils-inl.h>
#include <env-inl.h>
#include <memory_tracker-inl.h>
#include <ngtcp2/ngtcp2.h>
#include <node_bob-inl.h>
#include <node_errors.h>
#include <node_http_common-inl.h>
#include <node_sockaddr-inl.h>
#include <req_wrap-inl.h>
#include <timer_wrap-inl.h>
#include <util-inl.h>
#include <uv.h>
#include <v8.h>
#include "application.h"
#include "bindingdata.h"
#include "cid.h"
#include "data.h"
#include "defs.h"
#include "endpoint.h"
#include "logstream.h"
#include "packet.h"
#include "preferredaddress.h"
#include "sessionticket.h"
#include "streams.h"
#include "tlscontext.h"
#include "transportparams.h"

namespace node {

using v8::Array;
using v8::ArrayBuffer;
using v8::ArrayBufferView;
using v8::BigInt;
using v8::Boolean;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::HandleScope;
using v8::Integer;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::Nothing;
using v8::Object;
using v8::PropertyAttribute;
using v8::String;
using v8::Uint32;
using v8::Undefined;
using v8::Value;

namespace quic {

#define SESSION_STATE(V)                                                       \
  /* Set if the JavaScript wrapper has a path-validation event listener */     \
  V(PATH_VALIDATION, path_validation, uint8_t)                                 \
  /* Set if the JavaScript wrapper has a version-negotiation event listener */ \
  V(VERSION_NEGOTIATION, version_negotiation, uint8_t)                         \
  /* Set if the JavaScript wrapper has a datagram event listener */            \
  V(DATAGRAM, datagram, uint8_t)                                               \
  /* Set if the JavaScript wrapper has a session-ticket event listener */      \
  V(SESSION_TICKET, session_ticket, uint8_t)                                   \
  V(CLOSING, closing, uint8_t)                                                 \
  V(GRACEFUL_CLOSE, graceful_close, uint8_t)                                   \
  V(SILENT_CLOSE, silent_close, uint8_t)                                       \
  V(STATELESS_RESET, stateless_reset, uint8_t)                                 \
  V(DESTROYED, destroyed, uint8_t)                                             \
  V(HANDSHAKE_COMPLETED, handshake_completed, uint8_t)                         \
  V(HANDSHAKE_CONFIRMED, handshake_confirmed, uint8_t)                         \
  V(STREAM_OPEN_ALLOWED, stream_open_allowed, uint8_t)                         \
  V(PRIORITY_SUPPORTED, priority_supported, uint8_t)                           \
  /* A Session is wrapped if it has been passed out to JS */                   \
  V(WRAPPED, wrapped, uint8_t)                                                 \
  V(LAST_DATAGRAM_ID, last_datagram_id, uint64_t)

#define SESSION_STATS(V)                                                       \
  V(CREATED_AT, created_at)                                                    \
  V(CLOSING_AT, closing_at)                                                    \
  V(DESTROYED_AT, destroyed_at)                                                \
  V(HANDSHAKE_COMPLETED_AT, handshake_completed_at)                            \
  V(HANDSHAKE_CONFIRMED_AT, handshake_confirmed_at)                            \
  V(GRACEFUL_CLOSING_AT, graceful_closing_at)                                  \
  V(BYTES_RECEIVED, bytes_received)                                            \
  V(BYTES_SENT, bytes_sent)                                                    \
  V(BIDI_IN_STREAM_COUNT, bidi_in_stream_count)                                \
  V(BIDI_OUT_STREAM_COUNT, bidi_out_stream_count)                              \
  V(UNI_IN_STREAM_COUNT, uni_in_stream_count)                                  \
  V(UNI_OUT_STREAM_COUNT, uni_out_stream_count)                                \
  V(KEYUPDATE_COUNT, keyupdate_count)                                          \
  V(LOSS_RETRANSMIT_COUNT, loss_retransmit_count)                              \
  V(MAX_BYTES_IN_FLIGHT, max_bytes_in_flight)                                  \
  V(BYTES_IN_FLIGHT, bytes_in_flight)                                          \
  V(BLOCK_COUNT, block_count)                                                  \
  V(CWND, cwnd)                                                                \
  V(LATEST_RTT, latest_rtt)                                                    \
  V(MIN_RTT, min_rtt)                                                          \
  V(RTTVAR, rttvar)                                                            \
  V(SMOOTHED_RTT, smoothed_rtt)                                                \
  V(SSTHRESH, ssthresh)                                                        \
  V(DATAGRAMS_RECEIVED, datagrams_received)                                    \
  V(DATAGRAMS_SENT, datagrams_sent)                                            \
  V(DATAGRAMS_ACKNOWLEDGED, datagrams_acknowledged)                            \
  V(DATAGRAMS_LOST, datagrams_lost)

#define SESSION_JS_METHODS(V)                                                  \
  V(DoDestroy, destroy, false)                                                 \
  V(GetRemoteAddress, getRemoteAddress, true)                                  \
  V(GetCertificate, getCertificate, true)                                      \
  V(GetEphemeralKeyInfo, getEphemeralKey, true)                                \
  V(GetPeerCertificate, getPeerCertificate, true)                              \
  V(GracefulClose, gracefulClose, false)                                       \
  V(SilentClose, silentClose, false)                                           \
  V(UpdateKey, updateKey, false)                                               \
  V(DoOpenStream, openStream, false)                                           \
  V(DoSendDatagram, sendDatagram, false)

struct Session::State {
#define V(_, name, type) type name;
  SESSION_STATE(V)
#undef V
};

STAT_STRUCT(Session, SESSION)

// ============================================================================
// Used to conditionally trigger sending an explicit connection
// close. If there are multiple MaybeCloseConnectionScope in the
// stack, the determination of whether to send the close will be
// done once the final scope is closed.
struct Session::MaybeCloseConnectionScope final {
  Session* session;
  bool silent = false;
  MaybeCloseConnectionScope(Session* session_, bool silent_)
      : session(session_),
        silent(silent_ || session->connection_close_depth_ > 0) {
    Debug(session_,
          "Entering maybe close connection scope. Silent? %s",
          silent ? "yes" : "no");
    session->connection_close_depth_++;
  }
  MaybeCloseConnectionScope(const MaybeCloseConnectionScope&) = delete;
  MaybeCloseConnectionScope(MaybeCloseConnectionScope&&) = delete;
  MaybeCloseConnectionScope& operator=(const MaybeCloseConnectionScope&) =
      delete;
  MaybeCloseConnectionScope& operator=(MaybeCloseConnectionScope&&) = delete;
  ~MaybeCloseConnectionScope() {
    // We only want to trigger the sending the connection close if ...
    // a) Silent is not explicitly true at this scope.
    // b) We're not within the scope of an ngtcp2 callback, and
    // c) We are not already in a closing or draining period.
    if (--session->connection_close_depth_ == 0 && !silent &&
        session->can_send_packets()) {
      session->SendConnectionClose();
    }
  }
};

// ============================================================================
// Used to conditionally trigger sending of any pending data the session may
// be holding onto. If there are multiple SendPendingDataScope in the stack,
// the determination of whether to send the data will be done once the final
// scope is closed.

Session::SendPendingDataScope::SendPendingDataScope(Session* session)
    : session(session) {
  Debug(session, "Entering send pending data scope");
  session->send_scope_depth_++;
}

Session::SendPendingDataScope::SendPendingDataScope(
    const BaseObjectPtr<Session>& session)
    : SendPendingDataScope(session.get()) {}

Session::SendPendingDataScope::~SendPendingDataScope() {
  if (--session->send_scope_depth_ == 0 && session->can_send_packets()) {
    session->application().SendPendingData();
  }
}

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

namespace {

inline const char* getEncryptionLevelName(ngtcp2_encryption_level level) {
  switch (level) {
    case NGTCP2_ENCRYPTION_LEVEL_1RTT:
      return "1rtt";
    case NGTCP2_ENCRYPTION_LEVEL_0RTT:
      return "0rtt";
    case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE:
      return "handshake";
    case NGTCP2_ENCRYPTION_LEVEL_INITIAL:
      return "initial";
  }
  return "<unknown>";
}

// Qlog is a JSON-based logging format that is being standardized for low-level
// debug logging of QUIC connections and dataflows. The qlog output is generated
// optionally by ngtcp2 for us. The on_qlog_write callback is registered with
// ngtcp2 to emit the qlog information. Every Session will have it's own qlog
// stream.
void on_qlog_write(void* user_data,
                   uint32_t flags,
                   const void* data,
                   size_t len) {
  static_cast<Session*>(user_data)->HandleQlog(flags, data, len);
}

// Forwards detailed(verbose) debugging information from ngtcp2. Enabled using
// the NODE_DEBUG_NATIVE=NGTCP2_DEBUG category.
void ngtcp2_debug_log(void* user_data, const char* fmt, ...) {
  va_list ap;
  va_start(ap, fmt);
  std::string format(fmt, strlen(fmt) + 1);
  format[strlen(fmt)] = '\n';
  // Debug() does not work with the va_list here. So we use vfprintf
  // directly instead. Ngtcp2DebugLog is only enabled when the debug
  // category is enabled.
  vfprintf(stderr, format.c_str(), ap);
  va_end(ap);
}

template <typename Opt, PreferredAddress::Policy Opt::*member>
bool SetOption(Environment* env,
               Opt* options,
               const v8::Local<Object>& object,
               const v8::Local<String>& name) {
  Local<Value> value;
  PreferredAddress::Policy policy =
      PreferredAddress::Policy::USE_PREFERRED_ADDRESS;
  if (!object->Get(env->context(), name).ToLocal(&value) ||
      !PreferredAddress::tryGetPolicy(env, value).To(&policy)) {
    return false;
  }
  options->*member = policy;
  return true;
}

template <typename Opt, TLSContext::Options Opt::*member>
bool SetOption(Environment* env,
               Opt* options,
               const v8::Local<Object>& object,
               const v8::Local<String>& name) {
  Local<Value> value;
  TLSContext::Options opts;
  if (!object->Get(env->context(), name).ToLocal(&value) ||
      !TLSContext::Options::From(env, value).To(&opts)) {
    return false;
  }
  options->*member = opts;
  return true;
}

template <typename Opt, Session::Application_Options Opt::*member>
bool SetOption(Environment* env,
               Opt* options,
               const v8::Local<Object>& object,
               const v8::Local<String>& name) {
  Local<Value> value;
  Session::Application_Options opts;
  if (!object->Get(env->context(), name).ToLocal(&value) ||
      !Session::Application_Options::From(env, value).To(&opts)) {
    return false;
  }
  options->*member = opts;
  return true;
}

template <typename Opt, TransportParams::Options Opt::*member>
bool SetOption(Environment* env,
               Opt* options,
               const v8::Local<Object>& object,
               const v8::Local<String>& name) {
  Local<Value> value;
  TransportParams::Options opts;
  if (!object->Get(env->context(), name).ToLocal(&value) ||
      !TransportParams::Options::From(env, value).To(&opts)) {
    return false;
  }
  options->*member = opts;
  return true;
}

}  // namespace

// ============================================================================
Session::Config::Config(Side side,
                        const Endpoint& endpoint,
                        const Options& options,
                        uint32_t version,
                        const SocketAddress& local_address,
                        const SocketAddress& remote_address,
                        const CID& dcid,
                        const CID& scid,
                        std::optional<SessionTicket> session_ticket,
                        const CID& ocid)
    : side(side),
      options(options),
      version(version),
      local_address(local_address),
      remote_address(remote_address),
      dcid(dcid),
      scid(scid),
      ocid(ocid),
      session_ticket(session_ticket) {
  ngtcp2_settings_default(&settings);
  settings.initial_ts = uv_hrtime();

  // We currently do not support Path MTU Discovery. Once we do, unset this.
  settings.no_pmtud = 1;

  settings.tokenlen = 0;
  settings.token = nullptr;

  if (options.qlog) {
    settings.qlog_write = on_qlog_write;
  }

  if (endpoint.env()->enabled_debug_list()->enabled(
          DebugCategory::NGTCP2_DEBUG)) {
    settings.log_printf = ngtcp2_debug_log;
  }

  // We pull parts of the settings for the session from the endpoint options.
  auto& config = endpoint.options();
  settings.no_tx_udp_payload_size_shaping = config.no_udp_payload_size_shaping;
  settings.handshake_timeout = config.handshake_timeout;
  settings.max_stream_window = config.max_stream_window;
  settings.max_window = config.max_window;
  settings.cc_algo = config.cc_algorithm;
  settings.max_tx_udp_payload_size = config.max_payload_size;
  if (config.unacknowledged_packet_threshold > 0) {
    settings.ack_thresh = config.unacknowledged_packet_threshold;
  }
}

Session::Config::Config(const Endpoint& endpoint,
                        const Options& options,
                        const SocketAddress& local_address,
                        const SocketAddress& remote_address,
                        std::optional<SessionTicket> session_ticket,
                        const CID& ocid)
    : Config(Side::CLIENT,
             endpoint,
             options,
             options.version,
             local_address,
             remote_address,
             CID::Factory::random().Generate(NGTCP2_MIN_INITIAL_DCIDLEN),
             options.cid_factory->Generate(),
             session_ticket,
             ocid) {}

void Session::Config::MemoryInfo(MemoryTracker* tracker) const {
  tracker->TrackField("options", options);
  tracker->TrackField("local_address", local_address);
  tracker->TrackField("remote_address", remote_address);
  tracker->TrackField("dcid", dcid);
  tracker->TrackField("scid", scid);
  tracker->TrackField("ocid", ocid);
  tracker->TrackField("retry_scid", retry_scid);
  if (session_ticket.has_value())
    tracker->TrackField("session_ticket", session_ticket.value());
}

void Session::Config::set_token(const uint8_t* token,
                                size_t len,
                                ngtcp2_token_type type) {
  settings.token = token;
  settings.tokenlen = len;
  settings.token_type = type;
}

void Session::Config::set_token(const RetryToken& token) {
  ngtcp2_vec vec = token;
  set_token(vec.base, vec.len, NGTCP2_TOKEN_TYPE_RETRY);
}

void Session::Config::set_token(const RegularToken& token) {
  ngtcp2_vec vec = token;
  set_token(vec.base, vec.len, NGTCP2_TOKEN_TYPE_NEW_TOKEN);
}

std::string Session::Config::ToString() const {
  DebugIndentScope indent;
  auto prefix = indent.Prefix();
  std::string res("{");

  auto sidestr = ([&] {
    switch (side) {
      case Side::CLIENT:
        return "client";
      case Side::SERVER:
        return "server";
    }
    return "<unknown>";
  })();
  res += prefix + "side: " + std::string(sidestr);
  res += prefix + "options: " + options.ToString();
  res += prefix + "version: " + std::to_string(version);
  res += prefix + "local address: " + local_address.ToString();
  res += prefix + "remote address: " + remote_address.ToString();
  res += prefix + "dcid: " + dcid.ToString();
  res += prefix + "scid: " + scid.ToString();
  res += prefix + "ocid: " + ocid.ToString();
  res += prefix + "retry scid: " + retry_scid.ToString();
  res += prefix + "preferred address cid: " + preferred_address_cid.ToString();

  if (session_ticket.has_value()) {
    res += prefix + "session ticket: yes";
  } else {
    res += prefix + "session ticket: <none>";
  }

  res += indent.Close();
  return res;
}

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

Maybe<Session::Options> Session::Options::From(Environment* env,
                                               Local<Value> value) {
  if (value.IsEmpty() || !value->IsObject()) {
    THROW_ERR_INVALID_ARG_TYPE(env, "options must be an object");
    return Nothing<Options>();
  }

  auto& state = BindingData::Get(env);
  auto params = value.As<Object>();
  Options options;

#define SET(name)                                                              \
  SetOption<Session::Options, &Session::Options::name>(                        \
      env, &options, params, state.name##_string())

  if (!SET(version) || !SET(min_version) || !SET(preferred_address_strategy) ||
      !SET(transport_params) || !SET(tls_options) ||
      !SET(application_options) || !SET(qlog)) {
    return Nothing<Options>();
  }

#undef SET

  // TODO(@jasnell): Later we will also support setting the CID::Factory.
  // For now, we're just using the default random factory.

  return Just<Options>(options);
}

void Session::Options::MemoryInfo(MemoryTracker* tracker) const {
  tracker->TrackField("transport_params", transport_params);
  tracker->TrackField("crypto_options", tls_options);
  tracker->TrackField("application_options", application_options);
  tracker->TrackField("cid_factory_ref", cid_factory_ref);
}

std::string Session::Options::ToString() const {
  DebugIndentScope indent;
  auto prefix = indent.Prefix();
  std::string res("{");
  res += prefix + "version: " + std::to_string(version);
  res += prefix + "min version: " + std::to_string(min_version);

  auto policy = ([&] {
    switch (preferred_address_strategy) {
      case PreferredAddress::Policy::USE_PREFERRED_ADDRESS:
        return "use";
      case PreferredAddress::Policy::IGNORE_PREFERRED_ADDRESS:
        return "ignore";
    }
    return "<unknown>";
  })();
  res += prefix + "preferred address policy: " + std::string(policy);
  res += prefix + "transport params: " + transport_params.ToString();
  res += prefix + "crypto options: " + tls_options.ToString();
  res += prefix + "application options: " + application_options.ToString();
  res += prefix + "qlog: " + (qlog ? std::string("yes") : std::string("no"));
  res += indent.Close();
  return res;
}

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

bool Session::HasInstance(Environment* env, Local<Value> value) {
  return GetConstructorTemplate(env)->HasInstance(value);
}

BaseObjectPtr<Session> Session::Create(Endpoint* endpoint,
                                       const Config& config) {
  Local<Object> obj;
  if (!GetConstructorTemplate(endpoint->env())
           ->InstanceTemplate()
           ->NewInstance(endpoint->env()->context())
           .ToLocal(&obj)) {
    return BaseObjectPtr<Session>();
  }

  return MakeDetachedBaseObject<Session>(endpoint, obj, config);
}

Session::Session(Endpoint* endpoint,
                 v8::Local<v8::Object> object,
                 const Config& config)
    : AsyncWrap(endpoint->env(), object, AsyncWrap::PROVIDER_QUIC_SESSION),
      stats_(env()->isolate()),
      state_(env()->isolate()),
      allocator_(BindingData::Get(env())),
      endpoint_(BaseObjectWeakPtr<Endpoint>(endpoint)),
      config_(config),
      local_address_(config.local_address),
      remote_address_(config.remote_address),
      connection_(InitConnection()),
      tls_context_(env(), config_.side, this, config_.options.tls_options),
      application_(select_application()),
      timer_(env(),
             [this, self = BaseObjectPtr<Session>(this)] { OnTimeout(); }) {
  MakeWeak();

  Debug(this, "Session created.");

  timer_.Unref();

  application().ExtendMaxStreams(EndpointLabel::LOCAL,
                                 Direction::BIDIRECTIONAL,
                                 TransportParams::DEFAULT_MAX_STREAMS_BIDI);
  application().ExtendMaxStreams(EndpointLabel::LOCAL,
                                 Direction::UNIDIRECTIONAL,
                                 TransportParams::DEFAULT_MAX_STREAMS_UNI);

  const auto defineProperty = [&](auto name, auto value) {
    object
        ->DefineOwnProperty(
            env()->context(), name, value, PropertyAttribute::ReadOnly)
        .Check();
  };

  defineProperty(env()->state_string(), state_.GetArrayBuffer());
  defineProperty(env()->stats_string(), stats_.GetArrayBuffer());

  auto& state = BindingData::Get(env());

  if (UNLIKELY(config_.options.qlog)) {
    qlog_stream_ = LogStream::Create(env());
    if (qlog_stream_)
      defineProperty(state.qlog_string(), qlog_stream_->object());
  }

  if (UNLIKELY(config_.options.tls_options.keylog)) {
    keylog_stream_ = LogStream::Create(env());
    if (keylog_stream_)
      defineProperty(state.keylog_string(), keylog_stream_->object());
  }

  // We index the Session by our local CID (the scid) and dcid (the peer's cid)
  endpoint_->AddSession(config_.scid, BaseObjectPtr<Session>(this));
  endpoint_->AssociateCID(config_.dcid, config_.scid);

  tls_context_.Start();

  UpdateDataStats();
}

Session::~Session() {
  Debug(this, "Session destroyed.");
  if (conn_closebuf_) {
    conn_closebuf_->Done(0);
  }
  if (qlog_stream_) {
    Debug(this, "Closing the qlog stream for this session");
    env()->SetImmediate(
        [ptr = std::move(qlog_stream_)](Environment*) { ptr->End(); });
  }
  if (keylog_stream_) {
    Debug(this, "Closing the keylog stream for this session");
    env()->SetImmediate(
        [ptr = std::move(keylog_stream_)](Environment*) { ptr->End(); });
  }
  DCHECK(streams_.empty());
}

Session::operator ngtcp2_conn*() const {
  return connection_.get();
}

uint32_t Session::version() const {
  return config_.version;
}

Endpoint& Session::endpoint() const {
  return *endpoint_;
}

TLSContext& Session::tls_context() {
  return tls_context_;
}

Session::Application& Session::application() {
  return *application_;
}

const SocketAddress& Session::remote_address() const {
  return remote_address_;
}

const SocketAddress& Session::local_address() const {
  return local_address_;
}

bool Session::is_closing() const {
  return state_->closing;
}

bool Session::is_graceful_closing() const {
  return state_->graceful_close;
}

bool Session::is_silent_closing() const {
  return state_->silent_close;
}

bool Session::is_destroyed() const {
  return state_->destroyed;
}

bool Session::is_server() const {
  return config_.side == Side::SERVER;
}

std::string Session::diagnostic_name() const {
  const auto get_type = [&] { return is_server() ? "server" : "client"; };

  return std::string("Session (") + get_type() + "," +
         std::to_string(env()->thread_id()) + ":" +
         std::to_string(static_cast<int64_t>(get_async_id())) + ")";
}

const Session::Config& Session::config() const {
  return config_;
}

const Session::Options& Session::options() const {
  return config_.options;
}

void Session::HandleQlog(uint32_t flags, const void* data, size_t len) {
  if (qlog_stream_) {
    // Fun fact... ngtcp2 does not emit the final qlog statement until the
    // ngtcp2_conn object is destroyed. Ideally, destroying is explicit, but
    // sometimes the Session object can be garbage collected without being
    // explicitly destroyed. During those times, we cannot call out to
    // JavaScript. Because we don't know for sure if we're in in a GC when this
    // is called, it is safer to just defer writes to immediate, and to keep it
    // consistent, let's just always defer (this is not performance sensitive so
    // the deferring is fine).
    std::vector<uint8_t> buffer(len);
    memcpy(buffer.data(), data, len);
    Debug(this, "Emitting qlog data to the qlog stream");
    env()->SetImmediate(
        [ptr = qlog_stream_, buffer = std::move(buffer), flags](Environment*) {
          ptr->Emit(buffer.data(),
                    buffer.size(),
                    flags & NGTCP2_QLOG_WRITE_FLAG_FIN
                        ? LogStream::EmitOption::FIN
                        : LogStream::EmitOption::NONE);
        });
  }
}

TransportParams Session::GetLocalTransportParams() const {
  DCHECK(!is_destroyed());
  return TransportParams(ngtcp2_conn_get_local_transport_params(*this));
}

TransportParams Session::GetRemoteTransportParams() const {
  DCHECK(!is_destroyed());
  return TransportParams(ngtcp2_conn_get_remote_transport_params(*this));
}

void Session::SetLastError(QuicError&& error) {
  Debug(this, "Setting last error to %s", error);
  last_error_ = std::move(error);
}

void Session::Close(Session::CloseMethod method) {
  if (is_destroyed()) return;
  switch (method) {
    case CloseMethod::DEFAULT: {
      Debug(this, "Closing session");
      DoClose(false);
      break;
    }
    case CloseMethod::SILENT: {
      Debug(this, "Closing session silently");
      DoClose(true);
      break;
    }
    case CloseMethod::GRACEFUL: {
      if (is_graceful_closing()) return;
      Debug(this, "Closing session gracefully");
      // If there are no open streams, then we can close just immediately and
      // not worry about waiting around for the right moment.
      if (streams_.empty()) {
        DoClose(false);
      } else {
        state_->graceful_close = 1;
        STAT_RECORD_TIMESTAMP(Stats, graceful_closing_at);
      }
      break;
    }
  }
}

void Session::Destroy() {
  if (is_destroyed()) return;
  Debug(this, "Session destroyed");

  // The DoClose() method should have already been called.
  DCHECK(state_->closing);

  // We create a copy of the streams because they will remove themselves
  // from streams_ as they are cleaning up, causing the iterator to be
  // invalidated.
  auto streams = streams_;
  for (auto& stream : streams) stream.second->Destroy(last_error_);
  DCHECK(streams_.empty());

  STAT_RECORD_TIMESTAMP(Stats, destroyed_at);
  state_->closing = 0;
  state_->graceful_close = 0;

  timer_.Stop();

  // The Session instances are kept alive using a in the Endpoint. Removing the
  // Session from the Endpoint will free that pointer, allowing the Session to
  // be deconstructed once the stack unwinds and any remaining
  // BaseObjectPtr<Session> instances fall out of scope.

  MaybeStackBuffer<ngtcp2_cid, 10> cids(ngtcp2_conn_get_scid(*this, nullptr));
  ngtcp2_conn_get_scid(*this, cids.out());

  MaybeStackBuffer<ngtcp2_cid_token, 10> tokens(
      ngtcp2_conn_get_active_dcid(*this, nullptr));
  ngtcp2_conn_get_active_dcid(*this, tokens.out());

  endpoint_->DisassociateCID(config_.dcid);
  endpoint_->DisassociateCID(config_.preferred_address_cid);

  for (size_t n = 0; n < cids.length(); n++) {
    endpoint_->DisassociateCID(CID(cids[n]));
  }

  for (size_t n = 0; n < tokens.length(); n++) {
    if (tokens[n].token_present) {
      endpoint_->DisassociateStatelessResetToken(
          StatelessResetToken(tokens[n].token));
    }
  }

  state_->destroyed = 1;

  // Removing the session from the endpoint may cause the endpoint to be
  // destroyed if it is waiting on the last session to be destroyed. Let's grab
  // a reference just to be safe for the rest of the function.
  BaseObjectPtr<Endpoint> endpoint = std::move(endpoint_);
  endpoint->RemoveSession(config_.scid);
}

bool Session::Receive(Store&& store,
                      const SocketAddress& local_address,
                      const SocketAddress& remote_address) {
  if (is_destroyed()) return false;

  const auto receivePacket = [&](ngtcp2_path* path, ngtcp2_vec vec) {
    DCHECK(!is_destroyed());

    uint64_t now = uv_hrtime();
    ngtcp2_pkt_info pi{};  // Not used but required.
    int err = ngtcp2_conn_read_pkt(*this, path, &pi, vec.base, vec.len, now);
    switch (err) {
      case 0: {
        // Return true so we send after receiving.
        Debug(this, "Session successfully received packet");
        return true;
      }
      case NGTCP2_ERR_DRAINING: {
        // Connection has entered the draining state, no further data should be
        // sent. This happens when the remote peer has sent a CONNECTION_CLOSE.
        Debug(this, "Session is draining");
        return false;
      }
      case NGTCP2_ERR_CLOSING: {
        // Connection has entered the closing state, no further data should be
        // sent. This happens when the local peer has called
        // ngtcp2_conn_write_connection_close.
        Debug(this, "Session is closing");
        return false;
      }
      case NGTCP2_ERR_CRYPTO: {
        // Crypto error happened! Set the last error to the tls alert
        last_error_ = QuicError::ForTlsAlert(ngtcp2_conn_get_tls_alert(*this));
        Debug(this, "Crypto error while receiving packet: %s", last_error_);
        Close();
        return false;
      }
      case NGTCP2_ERR_RETRY: {
        // This should only ever happen on the server. We have to send a path
        // validation challenge in the form of a RETRY packet to the peer and
        // drop the connection.
        DCHECK(is_server());
        Debug(this, "Server must send a retry packet");
        endpoint_->SendRetry(PathDescriptor{
            version(),
            config_.dcid,
            config_.scid,
            local_address_,
            remote_address_,
        });
        Close(CloseMethod::SILENT);
        return false;
      }
      case NGTCP2_ERR_DROP_CONN: {
        // There's nothing else to do but drop the connection state.
        Debug(this, "Session must drop the connection");
        Close(CloseMethod::SILENT);
        return false;
      }
    }
    // Shouldn't happen but just in case.
    last_error_ = QuicError::ForNgtcp2Error(err);
    Debug(this, "Error while receiving packet: %s (%d)", last_error_, err);
    Close();
    return false;
  };

  auto update_stats = OnScopeLeave([&] { UpdateDataStats(); });
  remote_address_ = remote_address;
  Path path(local_address, remote_address_);
  Debug(this, "Session is receiving packet received along path %s", path);
  STAT_INCREMENT_N(Stats, bytes_received, store.length());
  if (receivePacket(&path, store)) application().SendPendingData();

  if (!is_destroyed()) UpdateTimer();

  return true;
}

void Session::Send(Packet* packet) {
  // Sending a Packet is generally best effort. If we're not in a state
  // where we can send a packet, it's ok to drop it on the floor. The
  // packet loss mechanisms will cause the packet data to be resent later
  // if appropriate (and possible).
  DCHECK(!is_destroyed());
  DCHECK(!is_in_draining_period());

  if (can_send_packets() && packet->length() > 0) {
    Debug(this, "Session is sending %s", packet->ToString());
    STAT_INCREMENT_N(Stats, bytes_sent, packet->length());
    endpoint_->Send(packet);
    return;
  }

  Debug(this, "Session could not send %s", packet->ToString());
  packet->Done(packet->length() > 0 ? UV_ECANCELED : 0);
}

void Session::Send(Packet* packet, const PathStorage& path) {
  UpdatePath(path);
  Send(packet);
}

uint64_t Session::SendDatagram(Store&& data) {
  auto tp = ngtcp2_conn_get_remote_transport_params(*this);
  uint64_t max_datagram_size = tp->max_datagram_frame_size;
  if (max_datagram_size == 0 || data.length() > max_datagram_size) {
    // Datagram is too large.
    Debug(this, "Data is too large to send as a datagram");
    return 0;
  }

  Debug(this, "Session is sending datagram");
  Packet* packet = nullptr;
  uint8_t* pos = nullptr;
  int accepted = 0;
  ngtcp2_vec vec = data;
  PathStorage path;
  int flags = NGTCP2_WRITE_DATAGRAM_FLAG_MORE;
  uint64_t did = state_->last_datagram_id + 1;

  // Let's give it a max number of attempts to send the datagram
  static const int kMaxAttempts = 16;
  int attempts = 0;

  for (;;) {
    // We may have to make several attempts at encoding and sending the
    // datagram packet. On each iteration here we'll try to encode the
    // datagram. It's entirely up to ngtcp2 whether to include the datagram
    // in the packet on each call to ngtcp2_conn_writev_datagram.
    if (packet == nullptr) {
      packet = Packet::Create(env(),
                              endpoint_.get(),
                              remote_address_,
                              ngtcp2_conn_get_max_tx_udp_payload_size(*this),
                              "datagram");
      // Typically sending datagrams is best effort, but if we cannot create
      // the packet, then we handle it as a fatal error.
      if (packet == nullptr) {
        last_error_ = QuicError::ForNgtcp2Error(NGTCP2_ERR_INTERNAL);
        Close(CloseMethod::SILENT);
        return 0;
      }
      pos = ngtcp2_vec(*packet).base;
    }

    ssize_t nwrite = ngtcp2_conn_writev_datagram(*this,
                                                 &path.path,
                                                 nullptr,
                                                 pos,
                                                 packet->length(),
                                                 &accepted,
                                                 flags,
                                                 did,
                                                 &vec,
                                                 1,
                                                 uv_hrtime());
    ngtcp2_conn_update_pkt_tx_time(*this, uv_hrtime());

    if (nwrite <= 0) {
      // Nothing was written to the packet.
      switch (nwrite) {
        case 0: {
          // We cannot send data because of congestion control or the data will
          // not fit. Since datagrams are best effort, we are going to abandon
          // the attempt and just return.
          CHECK_EQ(accepted, 0);
          packet->Done(UV_ECANCELED);
          return 0;
        }
        case NGTCP2_ERR_WRITE_MORE: {
          // We keep on looping! Keep on sending!
          continue;
        }
        case NGTCP2_ERR_INVALID_STATE: {
          // The remote endpoint does not want to accept datagrams. That's ok,
          // just return 0.
          packet->Done(UV_ECANCELED);
          return 0;
        }
        case NGTCP2_ERR_INVALID_ARGUMENT: {
          // The datagram is too large. That should have been caught above but
          // that's ok. We'll just abandon the attempt and return.
          packet->Done(UV_ECANCELED);
          return 0;
        }
        case NGTCP2_ERR_PKT_NUM_EXHAUSTED: {
          // We've exhausted the packet number space. Sadly we have to treat it
          // as a fatal condition.
          break;
        }
        case NGTCP2_ERR_CALLBACK_FAILURE: {
          // There was an internal failure. Sadly we have to treat it as a fatal
          // condition.
          break;
        }
      }
      packet->Done(UV_ECANCELED);
      last_error_ = QuicError::ForNgtcp2Error(nwrite);
      Close(CloseMethod::SILENT);
      return 0;
    }

    // In this case, a complete packet was written and we need to send it along.
    // Note that this doesn't mean that the packet actually contains the
    // datagram! We'll check that next by checking the accepted value.
    packet->Truncate(nwrite);
    Send(std::move(packet));

    if (accepted != 0) {
      // Yay! The datagram was accepted into the packet we just sent and we can
      // return the datagram ID.
      Debug(this, "Session successfully encoded datagram");
      STAT_INCREMENT(Stats, datagrams_sent);
      STAT_INCREMENT_N(Stats, bytes_sent, vec.len);
      state_->last_datagram_id = did;
      return did;
    }

    // We sent a packet, but it wasn't the datagram packet. That can happen.
    // Let's loop around and try again.
    if (++attempts == kMaxAttempts) {
      Debug(this, "Too many attempts to send the datagram");
      // Too many attempts to send the datagram.
      break;
    }
  }

  return 0;
}

void Session::UpdatePath(const PathStorage& storage) {
  remote_address_.Update(storage.path.remote.addr, storage.path.remote.addrlen);
  local_address_.Update(storage.path.local.addr, storage.path.local.addrlen);
  Debug(this,
        "path updated. local %s, remote %s",
        local_address_,
        remote_address_);
}

BaseObjectPtr<Stream> Session::FindStream(int64_t id) const {
  auto it = streams_.find(id);
  return it == std::end(streams_) ? BaseObjectPtr<Stream>() : it->second;
}

BaseObjectPtr<Stream> Session::CreateStream(int64_t id) {
  if (!can_create_streams()) return BaseObjectPtr<Stream>();
  auto stream = Stream::Create(this, id);
  if (stream) AddStream(stream);
  return stream;
}

BaseObjectPtr<Stream> Session::OpenStream(Direction direction) {
  if (!can_create_streams()) return BaseObjectPtr<Stream>();
  int64_t id;
  switch (direction) {
    case Direction::BIDIRECTIONAL: {
      Debug(this, "Opening bidirectional stream");
      if (ngtcp2_conn_open_bidi_stream(*this, &id, nullptr) == 0)
        return CreateStream(id);
      break;
    }
    case Direction::UNIDIRECTIONAL: {
      Debug(this, "Opening uni-directional stream");
      if (ngtcp2_conn_open_uni_stream(*this, &id, nullptr) == 0)
        return CreateStream(id);
      break;
    }
  }
  return BaseObjectPtr<Stream>();
}

void Session::AddStream(const BaseObjectPtr<Stream>& stream) {
  Debug(this, "Adding stream %" PRIi64 " to session", stream->id());
  ngtcp2_conn_set_stream_user_data(*this, stream->id(), stream.get());
  streams_[stream->id()] = stream;

  // Update tracking statistics for the number of streams associated with this
  // session.
  switch (stream->origin()) {
    case Side::CLIENT: {
      if (is_server()) {
        switch (stream->direction()) {
          case Direction::BIDIRECTIONAL:
            STAT_INCREMENT(Stats, bidi_in_stream_count);
            break;
          case Direction::UNIDIRECTIONAL:
            STAT_INCREMENT(Stats, uni_in_stream_count);
            break;
        }
      } else {
        switch (stream->direction()) {
          case Direction::BIDIRECTIONAL:
            STAT_INCREMENT(Stats, bidi_out_stream_count);
            break;
          case Direction::UNIDIRECTIONAL:
            STAT_INCREMENT(Stats, uni_out_stream_count);
            break;
        }
      }
      break;
    }
    case Side::SERVER: {
      if (is_server()) {
        switch (stream->direction()) {
          case Direction::BIDIRECTIONAL:
            STAT_INCREMENT(Stats, bidi_out_stream_count);
            break;
          case Direction::UNIDIRECTIONAL:
            STAT_INCREMENT(Stats, uni_out_stream_count);
            break;
        }
      } else {
        switch (stream->direction()) {
          case Direction::BIDIRECTIONAL:
            STAT_INCREMENT(Stats, bidi_in_stream_count);
            break;
          case Direction::UNIDIRECTIONAL:
            STAT_INCREMENT(Stats, uni_in_stream_count);
            break;
        }
      }
      break;
    }
  }
}

void Session::RemoveStream(int64_t id) {
  // ngtcp2 does not extend the max streams count automatically except in very
  // specific conditions, none of which apply once we've gotten this far. We
  // need to manually extend when a remote peer initiated stream is removed.
  Debug(this, "Removing stream %" PRIi64 " from session", id);
  if (!is_in_draining_period() && !is_in_closing_period() &&
      !state_->silent_close &&
      !ngtcp2_conn_is_local_stream(connection_.get(), id)) {
    if (ngtcp2_is_bidi_stream(id))
      ngtcp2_conn_extend_max_streams_bidi(connection_.get(), 1);
    else
      ngtcp2_conn_extend_max_streams_uni(connection_.get(), 1);
  }

  // Frees the persistent reference to the Stream object, allowing it to be gc'd
  // any time after the JS side releases it's own reference.
  streams_.erase(id);
  ngtcp2_conn_set_stream_user_data(*this, id, nullptr);
}

void Session::ResumeStream(int64_t id) {
  Debug(this, "Resuming stream %" PRIi64, id);
  SendPendingDataScope send_scope(this);
  application_->ResumeStream(id);
}

void Session::ShutdownStream(int64_t id, QuicError error) {
  Debug(this, "Shutting down stream %" PRIi64 " with error %s", id, error);
  SendPendingDataScope send_scope(this);
  ngtcp2_conn_shutdown_stream(*this,
                              0,
                              id,
                              error.type() == QuicError::Type::APPLICATION
                                  ? error.code()
                                  : NGTCP2_APP_NOERROR);
}

void Session::StreamDataBlocked(int64_t id) {
  Debug(this, "Stream %" PRIi64 " is blocked", id);
  STAT_INCREMENT(Stats, block_count);
  application_->BlockStream(id);
}

void Session::ShutdownStreamWrite(int64_t id, QuicError code) {
  Debug(this, "Shutting down stream %" PRIi64 " write with error %s", id, code);
  SendPendingDataScope send_scope(this);
  ngtcp2_conn_shutdown_stream_write(*this,
                                    0,
                                    id,
                                    code.type() == QuicError::Type::APPLICATION
                                        ? code.code()
                                        : NGTCP2_APP_NOERROR);
}

void Session::CollectSessionTicketAppData(
    SessionTicket::AppData* app_data) const {
  application_->CollectSessionTicketAppData(app_data);
}

SessionTicket::AppData::Status Session::ExtractSessionTicketAppData(
    const SessionTicket::AppData& app_data,
    SessionTicket::AppData::Source::Flag flag) {
  return application_->ExtractSessionTicketAppData(app_data, flag);
}

void Session::MemoryInfo(MemoryTracker* tracker) const {
  tracker->TrackField("config", config_);
  tracker->TrackField("endpoint", endpoint_);
  tracker->TrackField("streams", streams_);
  tracker->TrackField("local_address", local_address_);
  tracker->TrackField("remote_address", remote_address_);
  tracker->TrackField("application", application_);
  tracker->TrackField("tls_context", tls_context_);
  tracker->TrackField("timer", timer_);
  tracker->TrackField("conn_closebuf", conn_closebuf_);
  tracker->TrackField("qlog_stream", qlog_stream_);
  tracker->TrackField("keylog_stream", keylog_stream_);
}

bool Session::is_in_closing_period() const {
  return ngtcp2_conn_in_closing_period(*this) != 0;
}

bool Session::is_in_draining_period() const {
  return ngtcp2_conn_in_draining_period(*this) != 0;
}

bool Session::wants_session_ticket() const {
  return state_->session_ticket == 1;
}

void Session::SetStreamOpenAllowed() {
  // TODO(@jasnell): Might remove this. May not be needed
  state_->stream_open_allowed = 1;
}

bool Session::can_send_packets() const {
  // We can send packets if we're not in the middle of a ngtcp2 callback,
  // we're not destroyed, we're not in a draining or closing period, and
  // endpoint is set.
  return !NgTcp2CallbackScope::in_ngtcp2_callback(env()) && !is_destroyed() &&
         !is_in_draining_period() && !is_in_closing_period() && endpoint_;
}

bool Session::can_create_streams() const {
  return !state_->destroyed && !state_->graceful_close && !state_->closing &&
         !is_in_closing_period() && !is_in_draining_period();
}

uint64_t Session::max_data_left() const {
  return ngtcp2_conn_get_max_data_left(*this);
}

uint64_t Session::max_local_streams_uni() const {
  return ngtcp2_conn_get_streams_uni_left(*this);
}

uint64_t Session::max_local_streams_bidi() const {
  return ngtcp2_conn_get_local_transport_params(*this)
      ->initial_max_streams_bidi;
}

void Session::set_wrapped() {
  state_->wrapped = 1;
}

void Session::set_priority_supported(bool on) {
  state_->priority_supported = on ? 1 : 0;
}

void Session::DoClose(bool silent) {
  DCHECK(!is_destroyed());
  Debug(this, "Session is closing. Silently %s", silent ? "yes" : "no");
  // Once Close has been called, we cannot re-enter
  if (state_->closing == 1) return;
  state_->closing = 1;
  state_->silent_close = silent ? 1 : 0;
  STAT_RECORD_TIMESTAMP(Stats, closing_at);

  // Iterate through all of the known streams and close them. The streams
  // will remove themselves from the Session as soon as they are closed.
  // Note: we create a copy because the streams will remove themselves
  // while they are cleaning up which will invalidate the iterator.
  auto streams = streams_;
  for (auto& stream : streams) stream.second->Destroy(last_error_);
  DCHECK(streams.empty());

  // If the state has not been passed out to JavaScript yet, we can skip closing
  // entirely and drop directly out to Destroy.
  if (!state_->wrapped) return Destroy();

  // If we're not running within a ngtcp2 callback scope, schedule a
  // CONNECTION_CLOSE to be sent. If we are within a ngtcp2 callback scope,
  // sending the CONNECTION_CLOSE will be deferred.
  { MaybeCloseConnectionScope close_scope(this, silent); }

  // We emit a close callback so that the JavaScript side can clean up anything
  // it needs to clean up before destroying. It's the JavaScript side's
  // responsibility to call destroy() when ready.
  EmitClose();
}

void Session::ExtendStreamOffset(int64_t id, size_t amount) {
  Debug(this, "Extending stream %" PRIi64 " offset by %zu", id, amount);
  ngtcp2_conn_extend_max_stream_offset(*this, id, amount);
}

void Session::ExtendOffset(size_t amount) {
  Debug(this, "Extending offset by %zu", amount);
  ngtcp2_conn_extend_max_offset(*this, amount);
}

void Session::UpdateDataStats() {
  if (state_->destroyed) return;
  Debug(this, "Updating data stats");
  ngtcp2_conn_info info;
  ngtcp2_conn_get_conn_info(*this, &info);
  STAT_SET(Stats, bytes_in_flight, info.bytes_in_flight);
  STAT_SET(Stats, cwnd, info.cwnd);
  STAT_SET(Stats, latest_rtt, info.latest_rtt);
  STAT_SET(Stats, min_rtt, info.min_rtt);
  STAT_SET(Stats, rttvar, info.rttvar);
  STAT_SET(Stats, smoothed_rtt, info.smoothed_rtt);
  STAT_SET(Stats, ssthresh, info.ssthresh);
  STAT_SET(
      Stats,
      max_bytes_in_flight,
      std::max(STAT_GET(Stats, max_bytes_in_flight), info.bytes_in_flight));
}

void Session::SendConnectionClose() {
  DCHECK(!NgTcp2CallbackScope::in_ngtcp2_callback(env()));
  if (is_destroyed() || is_in_draining_period() || state_->silent_close) return;

  Debug(this, "Sending connection close");
  auto on_exit = OnScopeLeave([this] { UpdateTimer(); });

  switch (config_.side) {
    case Side::SERVER: {
      if (!is_in_closing_period() && !StartClosingPeriod()) {
        Close(CloseMethod::SILENT);
      } else {
        DCHECK(conn_closebuf_);
        Send(conn_closebuf_->Clone());
      }
      return;
    }
    case Side::CLIENT: {
      Path path(local_address_, remote_address_);
      auto packet = Packet::Create(env(),
                                   endpoint_.get(),
                                   remote_address_,
                                   kDefaultMaxPacketLength,
                                   "immediate connection close (client)");
      ngtcp2_vec vec = *packet;
      ssize_t nwrite = ngtcp2_conn_write_connection_close(
          *this, &path, nullptr, vec.base, vec.len, last_error_, uv_hrtime());

      if (UNLIKELY(nwrite < 0)) {
        packet->Done(UV_ECANCELED);
        last_error_ = QuicError::ForNgtcp2Error(NGTCP2_INTERNAL_ERROR);
        Close(CloseMethod::SILENT);
      } else {
        packet->Truncate(nwrite);
        Send(std::move(packet));
      }
      return;
    }
  }
  UNREACHABLE();
}

void Session::OnTimeout() {
  HandleScope scope(env()->isolate());
  if (is_destroyed()) return;

  int ret = ngtcp2_conn_handle_expiry(*this, uv_hrtime());
  if (NGTCP2_OK(ret) && !is_in_closing_period() && !is_in_draining_period() &&
      env()->can_call_into_js()) {
    SendPendingDataScope send_scope(this);
    return;
  }

  Debug(this, "Session timed out");
  last_error_ = QuicError::ForNgtcp2Error(ret);
  Close(CloseMethod::SILENT);
}

void Session::UpdateTimer() {
  // Both uv_hrtime and ngtcp2_conn_get_expiry return nanosecond units.
  uint64_t expiry = ngtcp2_conn_get_expiry(*this);
  uint64_t now = uv_hrtime();
  Debug(
      this, "Updating timer. Expiry: %" PRIu64 ", now: %" PRIu64, expiry, now);

  if (expiry <= now) {
    // The timer has already expired.
    return OnTimeout();
  }

  auto timeout = (expiry - now) / NGTCP2_MILLISECONDS;

  // If timeout is zero here, it means our timer is less than a millisecond
  // off from expiry. Let's bump the timer to 1.
  timer_.Update(timeout == 0 ? 1 : timeout);
}

bool Session::StartClosingPeriod() {
  if (is_in_closing_period()) return true;
  if (is_destroyed()) return false;

  Debug(this, "Session is entering closing period");

  conn_closebuf_ = Packet::CreateConnectionClosePacket(
      env(), endpoint_.get(), remote_address_, *this, last_error_);

  // If we were unable to create a connection close packet, we're in trouble.
  // Set the internal error and return false so that the session will be
  // silently closed.
  if (!conn_closebuf_) {
    last_error_ = QuicError::ForNgtcp2Error(NGTCP2_INTERNAL_ERROR);
    return false;
  }

  return true;
}

void Session::DatagramStatus(uint64_t datagramId, quic::DatagramStatus status) {
  switch (status) {
    case quic::DatagramStatus::ACKNOWLEDGED: {
      Debug(this, "Datagram %" PRIu64 " was acknowledged", datagramId);
      STAT_INCREMENT(Stats, datagrams_acknowledged);
      break;
    }
    case quic::DatagramStatus::LOST: {
      Debug(this, "Datagram %" PRIu64 " was lost", datagramId);
      STAT_INCREMENT(Stats, datagrams_lost);
      break;
    }
  }
  EmitDatagramStatus(datagramId, status);
}

void Session::DatagramReceived(const uint8_t* data,
                               size_t datalen,
                               DatagramReceivedFlags flag) {
  // If there is nothing watching for the datagram on the JavaScript side,
  // we just drop it on the floor.
  if (state_->datagram == 0 || datalen == 0) return;

  auto backing = ArrayBuffer::NewBackingStore(env()->isolate(), datalen);
  Debug(this, "Session is receiving datagram of size %zu", datalen);
  memcpy(backing->Data(), data, datalen);
  STAT_INCREMENT(Stats, datagrams_received);
  STAT_INCREMENT_N(Stats, bytes_received, datalen);
  EmitDatagram(Store(std::move(backing), datalen), flag);
}

bool Session::GenerateNewConnectionId(ngtcp2_cid* cid,
                                      size_t len,
                                      uint8_t* token) {
  CID cid_ = config_.options.cid_factory->Generate(len);
  Debug(this, "Generated new connection id %s", cid_);
  StatelessResetToken new_token(
      token, endpoint_->options().reset_token_secret, cid_);
  endpoint_->AssociateCID(cid_, config_.scid);
  endpoint_->AssociateStatelessResetToken(new_token, this);
  return true;
}

bool Session::HandshakeCompleted() {
  if (state_->handshake_completed) return false;
  state_->handshake_completed = true;

  Debug(this, "Session handshake completed");
  STAT_RECORD_TIMESTAMP(Stats, handshake_completed_at);

  if (!tls_context_.early_data_was_accepted())
    ngtcp2_conn_tls_early_data_rejected(*this);

  // When in a server session, handshake completed == handshake confirmed.
  if (is_server()) {
    HandshakeConfirmed();

    if (!endpoint().is_closed() && !endpoint().is_closing()) {
      auto token = endpoint().GenerateNewToken(version(), remote_address_);
      ngtcp2_vec vec = token;
      if (NGTCP2_ERR(ngtcp2_conn_submit_new_token(*this, vec.base, vec.len))) {
        // Submitting the new token failed... In this case we're going to
        // fail because submitting the new token should only fail if we
        // ran out of memory or some other unrecoverable state.
        return false;
      }
    }
  }

  EmitHandshakeComplete();

  return true;
}

void Session::HandshakeConfirmed() {
  if (state_->handshake_confirmed) return;

  Debug(this, "Session handshake confirmed");

  state_->handshake_confirmed = true;
  STAT_RECORD_TIMESTAMP(Stats, handshake_confirmed_at);
}

void Session::SelectPreferredAddress(PreferredAddress* preferredAddress) {
  if (config_.options.preferred_address_strategy ==
      PreferredAddress::Policy::IGNORE_PREFERRED_ADDRESS) {
    Debug(this, "Ignoring preferred address");
    return;
  }

  auto local_address = endpoint_->local_address();
  int family = local_address.family();

  switch (family) {
    case AF_INET: {
      Debug(this, "Selecting preferred address for AF_INET");
      auto ipv4 = preferredAddress->ipv4();
      if (ipv4.has_value()) {
        if (ipv4->address.empty() || ipv4->port == 0) return;
        CHECK(SocketAddress::New(AF_INET,
                                 std::string(ipv4->address).c_str(),
                                 ipv4->port,
                                 &remote_address_));
        preferredAddress->Use(ipv4.value());
      }
      break;
    }
    case AF_INET6: {
      Debug(this, "Selecting preferred address for AF_INET6");
      auto ipv6 = preferredAddress->ipv6();
      if (ipv6.has_value()) {
        if (ipv6->address.empty() || ipv6->port == 0) return;
        CHECK(SocketAddress::New(AF_INET,
                                 std::string(ipv6->address).c_str(),
                                 ipv6->port,
                                 &remote_address_));
        preferredAddress->Use(ipv6.value());
      }
      break;
    }
  }
}

CID Session::new_cid(size_t len) const {
  return config_.options.cid_factory->Generate(len);
}

// JavaScript callouts

void Session::EmitClose(const QuicError& error) {
  DCHECK(!is_destroyed());
  if (!env()->can_call_into_js()) return Destroy();

  CallbackScope<Session> cb_scope(this);
  Local<Value> argv[] = {
      Integer::New(env()->isolate(), static_cast<int>(error.type())),
      BigInt::NewFromUnsigned(env()->isolate(), error.code()),
      Undefined(env()->isolate()),
  };
  if (error.reason().length() > 0 &&
      !ToV8Value(env()->context(), error.reason()).ToLocal(&argv[2])) {
    return;
  }
  Debug(this, "Notifying JavaScript of session close");
  MakeCallback(
      BindingData::Get(env()).session_close_callback(), arraysize(argv), argv);
}

void Session::EmitDatagram(Store&& datagram, DatagramReceivedFlags flag) {
  DCHECK(!is_destroyed());
  if (!env()->can_call_into_js()) return;

  CallbackScope cbv_scope(this);

  Local<Value> argv[] = {datagram.ToUint8Array(env()),
                         v8::Boolean::New(env()->isolate(), flag.early)};

  Debug(this, "Notifying JavaScript of datagram");
  MakeCallback(BindingData::Get(env()).session_datagram_callback(),
               arraysize(argv),
               argv);
}

void Session::EmitDatagramStatus(uint64_t id, quic::DatagramStatus status) {
  DCHECK(!is_destroyed());
  if (!env()->can_call_into_js()) return;

  CallbackScope<Session> cb_scope(this);
  auto& state = BindingData::Get(env());

  const auto status_to_string = ([&] {
    switch (status) {
      case quic::DatagramStatus::ACKNOWLEDGED:
        return state.acknowledged_string();
      case quic::DatagramStatus::LOST:
        return state.lost_string();
    }
    UNREACHABLE();
  })();

  Local<Value> argv[] = {BigInt::NewFromUnsigned(env()->isolate(), id),
                         status_to_string};
  Debug(this, "Notifying JavaScript of datagram status");
  MakeCallback(state.session_datagram_status_callback(), arraysize(argv), argv);
}

void Session::EmitHandshakeComplete() {
  DCHECK(!is_destroyed());
  if (!env()->can_call_into_js()) return;

  CallbackScope<Session> cb_scope(this);

  auto isolate = env()->isolate();

  static constexpr auto kServerName = 0;
  static constexpr auto kSelectedAlpn = 1;
  static constexpr auto kCipherName = 2;
  static constexpr auto kCipherVersion = 3;
  static constexpr auto kValidationErrorReason = 4;
  static constexpr auto kValidationErrorCode = 5;

  Local<Value> argv[] = {
      Undefined(isolate),  // The negotiated server name
      Undefined(isolate),  // The selected alpn
      Undefined(isolate),  // Cipher name
      Undefined(isolate),  // Cipher version
      Undefined(isolate),  // Validation error reason
      Undefined(isolate),  // Validation error code
      v8::Boolean::New(isolate, tls_context_.early_data_was_accepted())};

  int err = tls_context_.VerifyPeerIdentity();

  if (err != X509_V_OK && (!crypto::GetValidationErrorReason(env(), err)
                                .ToLocal(&argv[kValidationErrorReason]) ||
                           !crypto::GetValidationErrorCode(env(), err)
                                .ToLocal(&argv[kValidationErrorCode]))) {
    return;
  }

  if (!ToV8Value(env()->context(), tls_context_.servername())
           .ToLocal(&argv[kServerName]) ||
      !ToV8Value(env()->context(), tls_context_.alpn())
           .ToLocal(&argv[kSelectedAlpn]) ||
      tls_context_.cipher_name(env()).ToLocal(&argv[kCipherName]) ||
      !tls_context_.cipher_version(env()).ToLocal(&argv[kCipherVersion])) {
    return;
  }

  Debug(this, "Notifying JavaScript of handshake complete");
  MakeCallback(BindingData::Get(env()).session_handshake_callback(),
               arraysize(argv),
               argv);
}

void Session::EmitPathValidation(PathValidationResult result,
                                 PathValidationFlags flags,
                                 const ValidatedPath& newPath,
                                 const std::optional<ValidatedPath>& oldPath) {
  DCHECK(!is_destroyed());
  if (!env()->can_call_into_js()) return;
  if (LIKELY(state_->path_validation == 0)) return;

  auto isolate = env()->isolate();
  CallbackScope<Session> cb_scope(this);
  auto& state = BindingData::Get(env());

  const auto resultToString = ([&] {
    switch (result) {
      case PathValidationResult::ABORTED:
        return state.aborted_string();
      case PathValidationResult::FAILURE:
        return state.failure_string();
      case PathValidationResult::SUCCESS:
        return state.success_string();
    }
    UNREACHABLE();
  })();

  Local<Value> argv[] = {
      resultToString,
      SocketAddressBase::Create(env(), newPath.local)->object(),
      SocketAddressBase::Create(env(), newPath.remote)->object(),
      Undefined(isolate),
      Undefined(isolate),
      Boolean::New(isolate, flags.preferredAddress)};

  if (oldPath.has_value()) {
    argv[3] = SocketAddressBase::Create(env(), oldPath->local)->object();
    argv[4] = SocketAddressBase::Create(env(), oldPath->remote)->object();
  }

  Debug(this, "Notifying JavaScript of path validation");
  MakeCallback(state.session_path_validation_callback(), arraysize(argv), argv);
}

void Session::EmitSessionTicket(Store&& ticket) {
  DCHECK(!is_destroyed());
  if (!env()->can_call_into_js()) return;

  // If there is nothing listening for the session ticket, don't bother
  // emitting.
  if (LIKELY(state_->session_ticket == 0)) return;

  CallbackScope<Session> cb_scope(this);

  auto remote_transport_params = GetRemoteTransportParams();
  Store transport_params;
  if (remote_transport_params)
    transport_params = remote_transport_params.Encode(env());

  SessionTicket session_ticket(std::move(ticket), std::move(transport_params));
  Local<Value> argv;
  if (session_ticket.encode(env()).ToLocal(&argv)) {
    Debug(this, "Notifying JavaScript of session ticket");
    MakeCallback(BindingData::Get(env()).session_ticket_callback(), 1, &argv);
  }
}

void Session::EmitStream(BaseObjectPtr<Stream> stream) {
  if (is_destroyed()) return;
  if (!env()->can_call_into_js()) return;
  CallbackScope<Session> cb_scope(this);
  Local<Value> arg = stream->object();

  Debug(this, "Notifying JavaScript of stream created");
  MakeCallback(BindingData::Get(env()).stream_created_callback(), 1, &arg);
}

void Session::EmitVersionNegotiation(const ngtcp2_pkt_hd& hd,
                                     const uint32_t* sv,
                                     size_t nsv) {
  DCHECK(!is_destroyed());
  DCHECK(!is_server());
  if (!env()->can_call_into_js()) return;

  auto isolate = env()->isolate();
  const auto to_integer = [&](uint32_t version) {
    return Integer::NewFromUnsigned(isolate, version);
  };

  CallbackScope<Session> cb_scope(this);

  // version() is the version that was actually configured for this session.

  // versions are the versions requested by the peer.
  MaybeStackBuffer<Local<Value>, 5> versions;
  versions.AllocateSufficientStorage(nsv);
  for (size_t n = 0; n < nsv; n++) versions[n] = to_integer(sv[n]);

  // supported are the versons we acutually support expressed as a range.
  // The first value is the minimum version, the second is the maximum.
  Local<Value> supported[] = {to_integer(config_.options.min_version),
                              to_integer(config_.options.version)};

  Local<Value> argv[] = {// The version configured for this session.
                         to_integer(version()),
                         // The versions requested.
                         Array::New(isolate, versions.out(), nsv),
                         // The versions we actually support.
                         Array::New(isolate, supported, arraysize(supported))};

  Debug(this, "Notifying JavaScript of version negotiation");
  MakeCallback(BindingData::Get(env()).session_version_negotiation_callback(),
               arraysize(argv),
               argv);
}

void Session::EmitKeylog(const char* line) {
  if (!env()->can_call_into_js()) return;
  if (keylog_stream_) {
    Debug(this, "Emitting keylog line");
    env()->SetImmediate([ptr = keylog_stream_, data = std::string(line) + "\n"](
                            Environment* env) { ptr->Emit(data); });
  }
}

// ============================================================================
// ngtcp2 static callback functions

#define NGTCP2_CALLBACK_SCOPE(name)                                            \
  auto name = Impl::From(conn, user_data);                                     \
  if (UNLIKELY(name->is_destroyed())) return NGTCP2_ERR_CALLBACK_FAILURE;      \
  NgTcp2CallbackScope scope(session->env());

struct Session::Impl {
  static Session* From(ngtcp2_conn* conn, void* user_data) {
    DCHECK_NOT_NULL(user_data);
    auto session = static_cast<Session*>(user_data);
    DCHECK_EQ(conn, session->connection_.get());
    return session;
  }

  static void DoDestroy(const FunctionCallbackInfo<Value>& args) {
    Session* session;
    ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
    session->Destroy();
  }

  static void GetRemoteAddress(const FunctionCallbackInfo<Value>& args) {
    auto env = Environment::GetCurrent(args);
    Session* session;
    ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
    auto address = session->remote_address();
    args.GetReturnValue().Set(
        SocketAddressBase::Create(env, std::make_shared<SocketAddress>(address))
            ->object());
  }

  static void GetCertificate(const FunctionCallbackInfo<Value>& args) {
    auto env = Environment::GetCurrent(args);
    Session* session;
    ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
    Local<Value> ret;
    if (session->tls_context().cert(env).ToLocal(&ret))
      args.GetReturnValue().Set(ret);
  }

  static void GetEphemeralKeyInfo(const FunctionCallbackInfo<Value>& args) {
    auto env = Environment::GetCurrent(args);
    Session* session;
    ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
    Local<Object> ret;
    if (!session->is_server() &&
        session->tls_context().ephemeral_key(env).ToLocal(&ret))
      args.GetReturnValue().Set(ret);
  }

  static void GetPeerCertificate(const FunctionCallbackInfo<Value>& args) {
    auto env = Environment::GetCurrent(args);
    Session* session;
    ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
    Local<Value> ret;
    if (session->tls_context().peer_cert(env).ToLocal(&ret))
      args.GetReturnValue().Set(ret);
  }

  static void GracefulClose(const FunctionCallbackInfo<Value>& args) {
    Session* session;
    ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
    session->Close(Session::CloseMethod::GRACEFUL);
  }

  static void SilentClose(const FunctionCallbackInfo<Value>& args) {
    // This is exposed for testing purposes only!
    Session* session;
    ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
    session->Close(Session::CloseMethod::SILENT);
  }

  static void UpdateKey(const FunctionCallbackInfo<Value>& args) {
    Session* session;
    ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
    // Initiating a key update may fail if it is done too early (either
    // before the TLS handshake has been confirmed or while a previous
    // key update is being processed). When it fails, InitiateKeyUpdate()
    // will return false.
    args.GetReturnValue().Set(session->tls_context().InitiateKeyUpdate());
  }

  static void DoOpenStream(const FunctionCallbackInfo<Value>& args) {
    Session* session;
    ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
    DCHECK(args[0]->IsUint32());
    auto direction = static_cast<Direction>(args[0].As<Uint32>()->Value());
    BaseObjectPtr<Stream> stream = session->OpenStream(direction);

    if (stream) args.GetReturnValue().Set(stream->object());
  }

  static void DoSendDatagram(const FunctionCallbackInfo<Value>& args) {
    auto env = Environment::GetCurrent(args);
    Session* session;
    ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
    DCHECK(args[0]->IsArrayBufferView());
    args.GetReturnValue().Set(BigInt::New(
        env->isolate(),
        session->SendDatagram(Store(args[0].As<ArrayBufferView>()))));
  }

  static int on_acknowledge_stream_data_offset(ngtcp2_conn* conn,
                                               int64_t stream_id,
                                               uint64_t offset,
                                               uint64_t datalen,
                                               void* user_data,
                                               void* stream_user_data) {
    NGTCP2_CALLBACK_SCOPE(session)
    session->application().AcknowledgeStreamData(Stream::From(stream_user_data),
                                                 datalen);
    return NGTCP2_SUCCESS;
  }

  static int on_acknowledge_datagram(ngtcp2_conn* conn,
                                     uint64_t dgram_id,
                                     void* user_data) {
    NGTCP2_CALLBACK_SCOPE(session)
    session->DatagramStatus(dgram_id, quic::DatagramStatus::ACKNOWLEDGED);
    return NGTCP2_SUCCESS;
  }

  static int on_cid_status(ngtcp2_conn* conn,
                           ngtcp2_connection_id_status_type type,
                           uint64_t seq,
                           const ngtcp2_cid* cid,
                           const uint8_t* token,
                           void* user_data) {
    NGTCP2_CALLBACK_SCOPE(session)
    std::optional<StatelessResetToken> maybe_reset_token;
    if (token != nullptr) maybe_reset_token.emplace(token);
    auto& endpoint = session->endpoint();
    switch (type) {
      case NGTCP2_CONNECTION_ID_STATUS_TYPE_ACTIVATE: {
        endpoint.AssociateCID(session->config_.scid, CID(cid));
        if (token != nullptr) {
          endpoint.AssociateStatelessResetToken(StatelessResetToken(token),
                                                session);
        }
        break;
      }
      case NGTCP2_CONNECTION_ID_STATUS_TYPE_DEACTIVATE: {
        endpoint.DisassociateCID(CID(cid));
        if (token != nullptr) {
          endpoint.DisassociateStatelessResetToken(StatelessResetToken(token));
        }
        break;
      }
    }
    return NGTCP2_SUCCESS;
  }

  static int on_extend_max_remote_streams_bidi(ngtcp2_conn* conn,
                                               uint64_t max_streams,
                                               void* user_data) {
    NGTCP2_CALLBACK_SCOPE(session)
    session->application().ExtendMaxStreams(
        EndpointLabel::REMOTE, Direction::BIDIRECTIONAL, max_streams);
    return NGTCP2_SUCCESS;
  }

  static int on_extend_max_remote_streams_uni(ngtcp2_conn* conn,
                                              uint64_t max_streams,
                                              void* user_data) {
    NGTCP2_CALLBACK_SCOPE(session)
    session->application().ExtendMaxStreams(
        EndpointLabel::REMOTE, Direction::UNIDIRECTIONAL, max_streams);
    return NGTCP2_SUCCESS;
  }

  static int on_extend_max_streams_bidi(ngtcp2_conn* conn,
                                        uint64_t max_streams,
                                        void* user_data) {
    NGTCP2_CALLBACK_SCOPE(session)
    session->application().ExtendMaxStreams(
        EndpointLabel::LOCAL, Direction::BIDIRECTIONAL, max_streams);
    return NGTCP2_SUCCESS;
  }

  static int on_extend_max_streams_uni(ngtcp2_conn* conn,
                                       uint64_t max_streams,
                                       void* user_data) {
    NGTCP2_CALLBACK_SCOPE(session)
    session->application().ExtendMaxStreams(
        EndpointLabel::LOCAL, Direction::UNIDIRECTIONAL, max_streams);
    return NGTCP2_SUCCESS;
  }

  static int on_extend_max_stream_data(ngtcp2_conn* conn,
                                       int64_t stream_id,
                                       uint64_t max_data,
                                       void* user_data,
                                       void* stream_user_data) {
    NGTCP2_CALLBACK_SCOPE(session)
    session->application().ExtendMaxStreamData(Stream::From(stream_user_data),
                                               max_data);
    return NGTCP2_SUCCESS;
  }

  static int on_get_new_cid(ngtcp2_conn* conn,
                            ngtcp2_cid* cid,
                            uint8_t* token,
                            size_t cidlen,
                            void* user_data) {
    NGTCP2_CALLBACK_SCOPE(session)
    return session->GenerateNewConnectionId(cid, cidlen, token)
               ? NGTCP2_SUCCESS
               : NGTCP2_ERR_CALLBACK_FAILURE;
  }

  static int on_handshake_completed(ngtcp2_conn* conn, void* user_data) {
    NGTCP2_CALLBACK_SCOPE(session)
    return session->HandshakeCompleted() ? NGTCP2_SUCCESS
                                         : NGTCP2_ERR_CALLBACK_FAILURE;
  }

  static int on_handshake_confirmed(ngtcp2_conn* conn, void* user_data) {
    NGTCP2_CALLBACK_SCOPE(session)
    session->HandshakeConfirmed();
    return NGTCP2_SUCCESS;
  }

  static int on_lost_datagram(ngtcp2_conn* conn,
                              uint64_t dgram_id,
                              void* user_data) {
    NGTCP2_CALLBACK_SCOPE(session)
    session->DatagramStatus(dgram_id, quic::DatagramStatus::LOST);
    return NGTCP2_SUCCESS;
  }

  static int on_path_validation(ngtcp2_conn* conn,
                                uint32_t flags,
                                const ngtcp2_path* path,
                                const ngtcp2_path* old_path,
                                ngtcp2_path_validation_result res,
                                void* user_data) {
    NGTCP2_CALLBACK_SCOPE(session)
    bool flag_preferred_address =
        flags & NGTCP2_PATH_VALIDATION_FLAG_PREFERRED_ADDR;
    ValidatedPath newValidatedPath{
        std::make_shared<SocketAddress>(path->local.addr),
        std::make_shared<SocketAddress>(path->remote.addr)};
    std::optional<ValidatedPath> oldValidatedPath = std::nullopt;
    if (old_path != nullptr) {
      oldValidatedPath =
          ValidatedPath{std::make_shared<SocketAddress>(old_path->local.addr),
                        std::make_shared<SocketAddress>(old_path->remote.addr)};
    }
    session->EmitPathValidation(static_cast<PathValidationResult>(res),
                                PathValidationFlags{flag_preferred_address},
                                newValidatedPath,
                                oldValidatedPath);
    return NGTCP2_SUCCESS;
  }

  static int on_receive_datagram(ngtcp2_conn* conn,
                                 uint32_t flags,
                                 const uint8_t* data,
                                 size_t datalen,
                                 void* user_data) {
    NGTCP2_CALLBACK_SCOPE(session)
    DatagramReceivedFlags f;
    f.early = flags & NGTCP2_DATAGRAM_FLAG_0RTT;
    session->DatagramReceived(data, datalen, f);
    return NGTCP2_SUCCESS;
  }

  static int on_receive_new_token(ngtcp2_conn* conn,
                                  const uint8_t* token,
                                  size_t tokenlen,
                                  void* user_data) {
    NGTCP2_CALLBACK_SCOPE(session)
    // We currently do nothing with this callback.
    return NGTCP2_SUCCESS;
  }

  static int on_receive_rx_key(ngtcp2_conn* conn,
                               ngtcp2_encryption_level level,
                               void* user_data) {
    auto session = Impl::From(conn, user_data);
    if (UNLIKELY(session->is_destroyed())) return NGTCP2_ERR_CALLBACK_FAILURE;

    Debug(session,
          "Receiving RX key for level %d for dcid %s",
          getEncryptionLevelName(level),
          session->config().dcid);

    if (!session->is_server() && (level == NGTCP2_ENCRYPTION_LEVEL_0RTT ||
                                  level == NGTCP2_ENCRYPTION_LEVEL_1RTT)) {
      if (!session->application().Start()) return NGTCP2_ERR_CALLBACK_FAILURE;
    }
    return NGTCP2_SUCCESS;
  }

  static int on_receive_stateless_reset(ngtcp2_conn* conn,
                                        const ngtcp2_pkt_stateless_reset* sr,
                                        void* user_data) {
    NGTCP2_CALLBACK_SCOPE(session)
    session->state_->stateless_reset = 1;
    return NGTCP2_SUCCESS;
  }

  static int on_receive_stream_data(ngtcp2_conn* conn,
                                    uint32_t flags,
                                    int64_t stream_id,
                                    uint64_t offset,
                                    const uint8_t* data,
                                    size_t datalen,
                                    void* user_data,
                                    void* stream_user_data) {
    NGTCP2_CALLBACK_SCOPE(session)
    Stream::ReceiveDataFlags f;
    f.early = flags & NGTCP2_STREAM_DATA_FLAG_0RTT;
    f.fin = flags & NGTCP2_STREAM_DATA_FLAG_FIN;

    if (stream_user_data == nullptr) {
      // We have an implicitly created stream.
      auto stream = session->CreateStream(stream_id);
      if (stream) {
        session->EmitStream(stream);
        session->application().ReceiveStreamData(
            stream.get(), data, datalen, f);
      } else {
        return ngtcp2_conn_shutdown_stream(
                   *session, 0, stream_id, NGTCP2_APP_NOERROR) == 0
                   ? NGTCP2_SUCCESS
                   : NGTCP2_ERR_CALLBACK_FAILURE;
      }
    } else {
      session->application().ReceiveStreamData(
          Stream::From(stream_user_data), data, datalen, f);
    }
    return NGTCP2_SUCCESS;
  }

  static int on_receive_tx_key(ngtcp2_conn* conn,
                               ngtcp2_encryption_level level,
                               void* user_data) {
    auto session = Impl::From(conn, user_data);
    if (UNLIKELY(session->is_destroyed())) return NGTCP2_ERR_CALLBACK_FAILURE;

    Debug(session,
          "Receiving TX key for level %d for dcid %s",
          getEncryptionLevelName(level),
          session->config().dcid);

    if (session->is_server() && (level == NGTCP2_ENCRYPTION_LEVEL_0RTT ||
                                 level == NGTCP2_ENCRYPTION_LEVEL_1RTT)) {
      if (!session->application().Start()) return NGTCP2_ERR_CALLBACK_FAILURE;
    }
    return NGTCP2_SUCCESS;
  }

  static int on_receive_version_negotiation(ngtcp2_conn* conn,
                                            const ngtcp2_pkt_hd* hd,
                                            const uint32_t* sv,
                                            size_t nsv,
                                            void* user_data) {
    NGTCP2_CALLBACK_SCOPE(session)
    session->EmitVersionNegotiation(*hd, sv, nsv);
    return NGTCP2_SUCCESS;
  }

  static int on_remove_connection_id(ngtcp2_conn* conn,
                                     const ngtcp2_cid* cid,
                                     void* user_data) {
    NGTCP2_CALLBACK_SCOPE(session)
    session->endpoint().DisassociateCID(CID(cid));
    return NGTCP2_SUCCESS;
  }

  static int on_select_preferred_address(ngtcp2_conn* conn,
                                         ngtcp2_path* dest,
                                         const ngtcp2_preferred_addr* paddr,
                                         void* user_data) {
    NGTCP2_CALLBACK_SCOPE(session)
    PreferredAddress preferred_address(dest, paddr);
    session->SelectPreferredAddress(&preferred_address);
    return NGTCP2_SUCCESS;
  }

  static int on_stream_close(ngtcp2_conn* conn,
                             uint32_t flags,
                             int64_t stream_id,
                             uint64_t app_error_code,
                             void* user_data,
                             void* stream_user_data) {
    NGTCP2_CALLBACK_SCOPE(session)
    if (flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET) {
      session->application().StreamClose(
          Stream::From(stream_user_data),
          QuicError::ForApplication(app_error_code));
    } else {
      session->application().StreamClose(Stream::From(stream_user_data));
    }
    return NGTCP2_SUCCESS;
  }

  static int on_stream_reset(ngtcp2_conn* conn,
                             int64_t stream_id,
                             uint64_t final_size,
                             uint64_t app_error_code,
                             void* user_data,
                             void* stream_user_data) {
    NGTCP2_CALLBACK_SCOPE(session)
    session->application().StreamReset(
        Stream::From(stream_user_data),
        final_size,
        QuicError::ForApplication(app_error_code));
    return NGTCP2_SUCCESS;
  }

  static int on_stream_stop_sending(ngtcp2_conn* conn,
                                    int64_t stream_id,
                                    uint64_t app_error_code,
                                    void* user_data,
                                    void* stream_user_data) {
    NGTCP2_CALLBACK_SCOPE(session)
    session->application().StreamStopSending(
        Stream::From(stream_user_data),
        QuicError::ForApplication(app_error_code));
    return NGTCP2_SUCCESS;
  }

  static void on_rand(uint8_t* dest,
                      size_t destlen,
                      const ngtcp2_rand_ctx* rand_ctx) {
    CHECK(crypto::CSPRNG(dest, destlen).is_ok());
  }

  static int on_early_data_rejected(ngtcp2_conn* conn, void* user_data) {
    // TODO(@jasnell): Called when early data was rejected by server during the
    // TLS handshake or client decided not to attempt early data.
    return NGTCP2_SUCCESS;
  }

  static constexpr ngtcp2_callbacks CLIENT = {
      ngtcp2_crypto_client_initial_cb,
      nullptr,
      ngtcp2_crypto_recv_crypto_data_cb,
      on_handshake_completed,
      on_receive_version_negotiation,
      ngtcp2_crypto_encrypt_cb,
      ngtcp2_crypto_decrypt_cb,
      ngtcp2_crypto_hp_mask_cb,
      on_receive_stream_data,
      on_acknowledge_stream_data_offset,
      nullptr,
      on_stream_close,
      on_receive_stateless_reset,
      ngtcp2_crypto_recv_retry_cb,
      on_extend_max_streams_bidi,
      on_extend_max_streams_uni,
      on_rand,
      on_get_new_cid,
      on_remove_connection_id,
      ngtcp2_crypto_update_key_cb,
      on_path_validation,
      on_select_preferred_address,
      on_stream_reset,
      on_extend_max_remote_streams_bidi,
      on_extend_max_remote_streams_uni,
      on_extend_max_stream_data,
      on_cid_status,
      on_handshake_confirmed,
      on_receive_new_token,
      ngtcp2_crypto_delete_crypto_aead_ctx_cb,
      ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
      on_receive_datagram,
      on_acknowledge_datagram,
      on_lost_datagram,
      ngtcp2_crypto_get_path_challenge_data_cb,
      on_stream_stop_sending,
      ngtcp2_crypto_version_negotiation_cb,
      on_receive_rx_key,
      on_receive_tx_key,
      on_early_data_rejected};

  static constexpr ngtcp2_callbacks SERVER = {
      nullptr,
      ngtcp2_crypto_recv_client_initial_cb,
      ngtcp2_crypto_recv_crypto_data_cb,
      on_handshake_completed,
      nullptr,
      ngtcp2_crypto_encrypt_cb,
      ngtcp2_crypto_decrypt_cb,
      ngtcp2_crypto_hp_mask_cb,
      on_receive_stream_data,
      on_acknowledge_stream_data_offset,
      nullptr,
      on_stream_close,
      on_receive_stateless_reset,
      nullptr,
      on_extend_max_streams_bidi,
      on_extend_max_streams_uni,
      on_rand,
      on_get_new_cid,
      on_remove_connection_id,
      ngtcp2_crypto_update_key_cb,
      on_path_validation,
      nullptr,
      on_stream_reset,
      on_extend_max_remote_streams_bidi,
      on_extend_max_remote_streams_uni,
      on_extend_max_stream_data,
      on_cid_status,
      nullptr,
      nullptr,
      ngtcp2_crypto_delete_crypto_aead_ctx_cb,
      ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
      on_receive_datagram,
      on_acknowledge_datagram,
      on_lost_datagram,
      ngtcp2_crypto_get_path_challenge_data_cb,
      on_stream_stop_sending,
      ngtcp2_crypto_version_negotiation_cb,
      on_receive_rx_key,
      on_receive_tx_key,
      on_early_data_rejected};
};

#undef NGTCP2_CALLBACK_SCOPE

Local<FunctionTemplate> Session::GetConstructorTemplate(Environment* env) {
  auto& state = BindingData::Get(env);
  auto tmpl = state.session_constructor_template();
  if (tmpl.IsEmpty()) {
    auto isolate = env->isolate();
    tmpl = NewFunctionTemplate(isolate, IllegalConstructor);
    tmpl->SetClassName(state.session_string());
    tmpl->Inherit(AsyncWrap::GetConstructorTemplate(env));
    tmpl->InstanceTemplate()->SetInternalFieldCount(
        Session::kInternalFieldCount);
#define V(name, key, no_side_effect)                                           \
  if (no_side_effect) {                                                        \
    SetProtoMethodNoSideEffect(isolate, tmpl, #key, Impl::name);               \
  } else {                                                                     \
    SetProtoMethod(isolate, tmpl, #key, Impl::name);                           \
  }

    SESSION_JS_METHODS(V)

#undef V
    state.set_session_constructor_template(tmpl);
  }
  return tmpl;
}

void Session::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
#define V(name, _, __) registry->Register(Impl::name);
  SESSION_JS_METHODS(V)
#undef V
}

Session::QuicConnectionPointer Session::InitConnection() {
  ngtcp2_conn* conn;
  Path path(local_address_, remote_address_);
  Debug(this, "Initializing session for path %s", path);
  TransportParams::Config tp_config(
      config_.side, config_.ocid, config_.retry_scid);
  TransportParams transport_params(tp_config, config_.options.transport_params);
  transport_params.GenerateSessionTokens(this);

  switch (config_.side) {
    case Side::SERVER: {
      CHECK_EQ(ngtcp2_conn_server_new(&conn,
                                      config_.dcid,
                                      config_.scid,
                                      path,
                                      config_.version,
                                      &Impl::SERVER,
                                      &config_.settings,
                                      transport_params,
                                      &allocator_,
                                      this),
               0);
      return QuicConnectionPointer(conn);
    }
    case Side::CLIENT: {
      CHECK_EQ(ngtcp2_conn_client_new(&conn,
                                      config_.dcid,
                                      config_.scid,
                                      path,
                                      config_.version,
                                      &Impl::CLIENT,
                                      &config_.settings,
                                      transport_params,
                                      &allocator_,
                                      this),
               0);
      if (config_.session_ticket.has_value())
        tls_context_.MaybeSetEarlySession(config_.session_ticket.value());
      return QuicConnectionPointer(conn);
    }
  }
  UNREACHABLE();
}

void Session::InitPerIsolate(IsolateData* data,
                             v8::Local<v8::ObjectTemplate> target) {
  // TODO(@jasnell): Implement the per-isolate state
}

void Session::InitPerContext(Realm* realm, Local<Object> target) {
  // Make sure the Session constructor template is initialized.
  USE(GetConstructorTemplate(realm->env()));

  TransportParams::Initialize(realm->env(), target);
  PreferredAddress::Initialize(realm->env(), target);

  static constexpr auto STREAM_DIRECTION_BIDIRECTIONAL =
      static_cast<uint32_t>(Direction::BIDIRECTIONAL);
  static constexpr auto STREAM_DIRECTION_UNIDIRECTIONAL =
      static_cast<uint32_t>(Direction::UNIDIRECTIONAL);
  static constexpr auto QUIC_PROTO_MAX = NGTCP2_PROTO_VER_MAX;
  static constexpr auto QUIC_PROTO_MIN = NGTCP2_PROTO_VER_MIN;

  NODE_DEFINE_CONSTANT(target, STREAM_DIRECTION_BIDIRECTIONAL);
  NODE_DEFINE_CONSTANT(target, STREAM_DIRECTION_UNIDIRECTIONAL);
  NODE_DEFINE_CONSTANT(target, DEFAULT_MAX_HEADER_LIST_PAIRS);
  NODE_DEFINE_CONSTANT(target, DEFAULT_MAX_HEADER_LENGTH);
  NODE_DEFINE_CONSTANT(target, QUIC_PROTO_MAX);
  NODE_DEFINE_CONSTANT(target, QUIC_PROTO_MIN);

#define V(name, _) IDX_STATS_SESSION_##name,
  enum SessionStatsIdx { SESSION_STATS(V) IDX_STATS_SESSION_COUNT };
#undef V

#define V(name, key, __)                                                       \
  auto IDX_STATE_SESSION_##name = offsetof(Session::State, key);
  SESSION_STATE(V)
#undef V

#define V(name, _) NODE_DEFINE_CONSTANT(target, IDX_STATS_SESSION_##name);
  SESSION_STATS(V)
  NODE_DEFINE_CONSTANT(target, IDX_STATS_SESSION_COUNT);
#undef V
#define V(name, _, __) NODE_DEFINE_CONSTANT(target, IDX_STATE_SESSION_##name);
  SESSION_STATE(V)
#undef V
}

}  // namespace quic
}  // namespace node

#endif  // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC

Zerion Mini Shell 1.0