current commit
261010.8 · quic: replace removed SetPrototypeV2 calls
#include "guard.h"
#ifndef OPENSSL_NO_QUIC
#include <async_wrap-inl.h>
#include <base_object-inl.h>
#include <env-inl.h>
#include <memory_tracker-inl.h>
#include <node_sockaddr-inl.h>
#include <util-inl.h>
#include <v8.h>
#include "bindingdata.h"
#include "defs.h"
#include "endpoint.h"
#include "session.h"
#include "session_manager.h"
#include "tokens.h"
#include "transportparams.h"

namespace node {

using v8::BigInt;
using v8::Boolean;
using v8::DictionaryTemplate;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Nothing;
using v8::Object;
using v8::Value;

namespace quic {

const TransportParams::Options TransportParams::Options::kDefault = {};

TransportParams::Config::Config(Side side,
                                const CID& ocid,
                                const CID& retry_scid)
    : side(side), ocid(ocid), retry_scid(retry_scid) {}

Maybe<TransportParams::Options> TransportParams::Options::From(
    Environment* env, Local<Value> value) {
  if (value.IsEmpty()) {
    THROW_ERR_INVALID_ARG_TYPE(env, "options must be an object or undefined");
    return Nothing<Options>();
  } else if (value->IsUndefined()) {
    return Just<Options>(kDefault);
  } else if (!value->IsObject()) {
    THROW_ERR_INVALID_ARG_TYPE(env, "options must be an object or undefined");
    return Nothing<Options>();
  }

  Options options;

  // TODO(@jasnell): We currently only support version 1 of the transport
  // parameters, so the options.transportParamsVersion is hardcoded to that.
  // In the future, when we support multiple versions, we will need to
  // expose this via the options object.

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

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

  if (!SET(initial_max_stream_data_bidi_local) ||
      !SET(initial_max_stream_data_bidi_remote) ||
      !SET(initial_max_stream_data_uni) || !SET(initial_max_data) ||
      !SET(initial_max_streams_bidi) || !SET(initial_max_streams_uni) ||
      !SET(max_idle_timeout) || !SET(active_connection_id_limit) ||
      !SET(ack_delay_exponent) || !SET(max_ack_delay) ||
      !SET(max_datagram_frame_size)) {
    return Nothing<Options>();
  }

#undef SET

  // Parse the preferred address options. These are SocketAddress objects
  // (or undefined to skip). Only meaningful for server sessions.
  Local<Value> preferred_ipv4;
  if (!params->Get(env->context(), state.preferred_address_ipv4_string())
           .ToLocal(&preferred_ipv4)) {
    return Nothing<Options>();
  }
  if (!preferred_ipv4->IsUndefined()) {
    if (!SocketAddressBase::HasInstance(env, preferred_ipv4)) {
      THROW_ERR_INVALID_ARG_TYPE(
          env, "transportParams.preferredAddressIpv4 must be a SocketAddress");
      return Nothing<Options>();
    }
    auto* addr = BaseObject::FromJSObject<SocketAddressBase>(
        preferred_ipv4.As<Object>());
    if (addr->address()->family() != AF_INET) {
      THROW_ERR_INVALID_ARG_VALUE(
          env, "transportParams.preferredAddressIpv4 must be an IPv4 address");
      return Nothing<Options>();
    }
    options.preferred_address_ipv4 = *addr->address();
  }

  Local<Value> preferred_ipv6;
  if (!params->Get(env->context(), state.preferred_address_ipv6_string())
           .ToLocal(&preferred_ipv6)) {
    return Nothing<Options>();
  }
  if (!preferred_ipv6->IsUndefined()) {
    if (!SocketAddressBase::HasInstance(env, preferred_ipv6)) {
      THROW_ERR_INVALID_ARG_TYPE(
          env, "transportParams.preferredAddressIpv6 must be a SocketAddress");
      return Nothing<Options>();
    }
    auto* addr = BaseObject::FromJSObject<SocketAddressBase>(
        preferred_ipv6.As<Object>());
    if (addr->address()->family() != AF_INET6) {
      THROW_ERR_INVALID_ARG_VALUE(
          env, "transportParams.preferredAddressIpv6 must be an IPv6 address");
      return Nothing<Options>();
    }
    options.preferred_address_ipv6 = *addr->address();
  }

  return Just<Options>(options);
}

std::string TransportParams::Options::ToString() const {
  DebugIndentScope indent;
  auto prefix = indent.Prefix();
  std::string res("{");
  res += prefix +
         "version: " + std::to_string(static_cast<int>(transportParamsVersion));
  if (preferred_address_ipv4.has_value()) {
    res += prefix + "preferred_address_ipv4: " +
           preferred_address_ipv4.value().ToString();
  } else {
    res += prefix + "preferred_address_ipv4: <none>";
  }
  if (preferred_address_ipv6.has_value()) {
    res += prefix + "preferred_address_ipv6: " +
           preferred_address_ipv6.value().ToString();
  } else {
    res += prefix + "preferred_address_ipv6: <none>";
  }
  res += prefix + "initial max stream data bidi local: " +
         std::to_string(initial_max_stream_data_bidi_local);
  res += prefix + "initial max stream data bidi remote: " +
         std::to_string(initial_max_stream_data_bidi_remote);
  res += prefix + "initial max stream data uni: " +
         std::to_string(initial_max_stream_data_uni);
  res += prefix + "tinitial max data: " + std::to_string(initial_max_data);
  res += prefix + "initial max streams bidi: " +
         std::to_string(initial_max_streams_bidi);
  res += prefix +
         "initial max streams uni: " + std::to_string(initial_max_streams_uni);
  res += prefix + "max idle timeout: " + std::to_string(max_idle_timeout);
  res += prefix + "active connection id limit: " +
         std::to_string(active_connection_id_limit);
  res += prefix + "ack delay exponent: " + std::to_string(ack_delay_exponent);
  res += prefix + "max ack delay: " + std::to_string(max_ack_delay);
  res += prefix +
         "max datagram frame size: " + std::to_string(max_datagram_frame_size);
  res += indent.Close();
  return res;
}

void TransportParams::Options::MemoryInfo(MemoryTracker* tracker) const {
  if (preferred_address_ipv4.has_value()) {
    tracker->TrackField("preferred_address_ipv4",
                        preferred_address_ipv4.value());
  }
  if (preferred_address_ipv6.has_value()) {
    tracker->TrackField("preferred_address_ipv6",
                        preferred_address_ipv6.value());
  }
}

TransportParams::TransportParams() : ptr_(&params_) {}

TransportParams::TransportParams(const ngtcp2_transport_params* ptr)
    : ptr_(ptr) {}

TransportParams::TransportParams(const Config& config, const Options& options)
    : TransportParams() {
  ngtcp2_transport_params_default(&params_);
#define SET_PARAM(name) params_.name = options.name
#define SET_PARAM_V(name, value) params_.name = value
  SET_PARAM(active_connection_id_limit);
  SET_PARAM(initial_max_stream_data_bidi_local);
  SET_PARAM(initial_max_stream_data_bidi_remote);
  SET_PARAM(initial_max_stream_data_uni);
  SET_PARAM(initial_max_streams_bidi);
  SET_PARAM(initial_max_streams_uni);
  SET_PARAM(initial_max_data);
  SET_PARAM(max_ack_delay);
  SET_PARAM(ack_delay_exponent);
  SET_PARAM(max_datagram_frame_size);
  SET_PARAM_V(max_idle_timeout, options.max_idle_timeout * NGTCP2_SECONDS);
  SET_PARAM_V(disable_active_migration, 0);
  SET_PARAM_V(grease_quic_bit, 1);
  SET_PARAM_V(preferred_addr_present, 0);
  SET_PARAM_V(stateless_reset_token_present, 0);
  SET_PARAM_V(retry_scid_present, 0);

  if (config.side == Side::SERVER) {
    // For the server side, the original dcid is always set.
    CHECK(config.ocid);
    SET_PARAM_V(original_dcid, config.ocid);
    SET_PARAM_V(original_dcid_present, 1);

    // The retry_scid is only set if the server validated a retry token.
    if (config.retry_scid) {
      SET_PARAM_V(retry_scid, config.retry_scid);
      SET_PARAM_V(retry_scid_present, 1);
    }
  }
#undef SET_PARAM
#undef SET_PARAM_V

  if (options.preferred_address_ipv4.has_value()) {
    SetPreferredAddress(options.preferred_address_ipv4.value());
  }

  if (options.preferred_address_ipv6.has_value()) {
    SetPreferredAddress(options.preferred_address_ipv6.value());
  }
}

TransportParams::TransportParams(const ngtcp2_vec& vec, Version version)
    : TransportParams() {
  int ret = ngtcp2_transport_params_decode_versioned(
      static_cast<int>(version), &params_, vec.base, vec.len);

  // The only error we should see here is NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM,
  // which indicates that the provided data was not valid transport parameters.
  // In that case, we set ptr_ to nullptr to indicate that the parameters
  // could not be decoded.
  if (ret != 0) {
    ptr_ = nullptr;
  }
}

Store TransportParams::Encode(Environment* env, Version version) const {
  if (ptr_ == nullptr) {
    return {};
  }

  // Preflight to see how much storage we'll need.
  ssize_t size = ngtcp2_transport_params_encode_versioned(
      nullptr, 0, static_cast<int>(version), &params_);
  if (size == 0) {
    return {};
  }

  JS_TRY_ALLOCATE_BACKING_OR_RETURN(env, result, size, {});

  auto ret = ngtcp2_transport_params_encode_versioned(
      static_cast<uint8_t*>(result->Data()),
      size,
      static_cast<int>(version),
      &params_);

  // The ret is the number of bytes written, or a negative error code.
  if (ret < 0) return {};
  CHECK_EQ(ret, size);
  return Store(std::move(result), static_cast<size_t>(size));
}

ssize_t TransportParams::EncodedSize(Version version) const {
  if (ptr_ == nullptr) return 0;
  return ngtcp2_transport_params_encode_versioned(
      nullptr, 0, static_cast<int>(version), &params_);
}

ssize_t TransportParams::EncodeInto(uint8_t* buf,
                                    size_t len,
                                    Version version) const {
  if (ptr_ == nullptr) return -1;
  return ngtcp2_transport_params_encode_versioned(
      buf, len, static_cast<int>(version), &params_);
}

void TransportParams::SetPreferredAddress(const SocketAddress& address) {
  DCHECK(ptr_ == &params_);
  params_.preferred_addr_present = 1;
  switch (address.family()) {
    case AF_INET: {
      const sockaddr_in* src =
          reinterpret_cast<const sockaddr_in*>(address.data());
      memcpy(&params_.preferred_addr.ipv4.sin_addr,
             &src->sin_addr,
             sizeof(params_.preferred_addr.ipv4.sin_addr));
      params_.preferred_addr.ipv4.sin_port = address.port();
      params_.preferred_addr.ipv4_present = 1;
      return;
    }
    case AF_INET6: {
      const sockaddr_in6* src =
          reinterpret_cast<const sockaddr_in6*>(address.data());
      memcpy(&params_.preferred_addr.ipv6.sin6_addr,
             &src->sin6_addr,
             sizeof(params_.preferred_addr.ipv6.sin6_addr));
      params_.preferred_addr.ipv6.sin6_port = address.port();
      params_.preferred_addr.ipv6_present = 1;
      return;
    }
  }
  UNREACHABLE();
}

void TransportParams::GenerateSessionTokens(Session* session) {
  if (session->is_server()) {
    GenerateStatelessResetToken(session->endpoint(), session->config().scid);
    GeneratePreferredAddressToken(session);
  }
}

void TransportParams::GenerateStatelessResetToken(const Endpoint& endpoint,
                                                  const CID& cid) {
  DCHECK(ptr_ == &params_);
  DCHECK(cid);
  params_.stateless_reset_token_present = 1;
  endpoint.GenerateNewStatelessResetToken(params_.stateless_reset_token, cid);
}

void TransportParams::GeneratePreferredAddressToken(Session* session) {
  DCHECK(ptr_ == &params_);
  Session::Config& config = session->config();
  if (params_.preferred_addr_present) {
    config.preferred_address_cid = session->new_cid();
    params_.preferred_addr.cid = config.preferred_address_cid;
    auto& endpoint = session->endpoint();
    endpoint.AssociateStatelessResetToken(
        endpoint.GenerateNewStatelessResetToken(
            params_.preferred_addr.stateless_reset_token,
            config.preferred_address_cid),
        session);
    // Register the preferred address CID with SessionManager for
    // cross-endpoint routing. This is a locally-generated CID that needs
    // to be routable from the preferred address endpoint (which may be
    // different from the primary endpoint).
    auto& mgr = BindingData::Get(session->env()).session_manager();
    mgr.AssociateCID(config.preferred_address_cid, config.scid);
  }
}

v8::MaybeLocal<v8::Object> TransportParams::ToObject(Environment* env) const {
  auto& binding_data = BindingData::Get(env);
  auto tmpl = binding_data.transport_params_template();
  static constexpr std::string_view names[] = {
      "preferredAddressIpv4",
      "preferredAddressIpv6",
      "originalDCID",
      "initialSCID",
      "retrySCID",
      "initialMaxStreamDataBidiLocal",
      "initialMaxStreamDataBidiRemote",
      "initialMaxStreamDataUni",
      "initialMaxData",
      "initialMaxStreamsBidi",
      "initialMaxStreamsUni",
      "maxIdleTimeout",
      "activeConnectionIDLimit",
      "ackDelayExponent",
      "maxAckDelay",
      "maxDatagramFrameSize",
      "disableActiveMigration",
  };
  if (tmpl.IsEmpty()) {
    tmpl = DictionaryTemplate::New(env->isolate(), names);
    binding_data.set_transport_params_template(tmpl);
  }

  MaybeLocal<Value> values[] = {
      Undefined(env->isolate()),  // preferredAddressIpv4
      Undefined(env->isolate()),  // preferredAddressIpv6
      Undefined(env->isolate()),  // originalDCID
      Undefined(env->isolate()),  // initialSCID
      Undefined(env->isolate()),  // retrySCID
      Undefined(env->isolate()),  // initialMaxStreamDataBidiLocal
      Undefined(env->isolate()),  // initialMaxStreamDataBidiRemote
      Undefined(env->isolate()),  // initialMaxStreamDataUni
      Undefined(env->isolate()),  // initialMaxData
      Undefined(env->isolate()),  // initialMaxStreamsBidi
      Undefined(env->isolate()),  // initialMaxStreamsUni
      Undefined(env->isolate()),  // maxIdleTimeout
      Undefined(env->isolate()),  // activeConnectionIDLimit
      Undefined(env->isolate()),  // ackDelayExponent
      Undefined(env->isolate()),  // maxAckDelay
      Undefined(env->isolate()),  // maxDatagramFrameSize
      Undefined(env->isolate()),  // disableActiveMigration
  };

  static_assert(std::size(values) == std::size(names));

  static constexpr size_t kPreferredAddressIpv4Index = 0;
  static constexpr size_t kPreferredAddressIpv6Index = 1;
  static constexpr size_t kOriginalDCIDIndex = 2;
  static constexpr size_t kInitialSCIDIndex = 3;
  static constexpr size_t kRetrySCIDIndex = 4;
  static constexpr size_t kInitialMaxStreamDataBidiLocalIndex = 5;
  static constexpr size_t kInitialMaxStreamDataBidiRemoteIndex = 6;
  static constexpr size_t kInitialMaxStreamDataUniIndex = 7;
  static constexpr size_t kInitialMaxDataIndex = 8;
  static constexpr size_t kInitialMaxStreamsBidiIndex = 9;
  static constexpr size_t kInitialMaxStreamsUniIndex = 10;
  static constexpr size_t kMaxIdleTimeoutIndex = 11;
  static constexpr size_t kActiveConnectionIDLimitIndex = 12;
  static constexpr size_t kAckDelayExponentIndex = 13;
  static constexpr size_t kMaxAckDelayIndex = 14;
  static constexpr size_t kMaxDatagramFrameSizeIndex = 15;
  static constexpr size_t kDisableActiveMigrationIndex = 16;

  if (ptr_ != nullptr) {
    if (ptr_->preferred_addr_present) {
      if (ptr_->preferred_addr.ipv4_present) {
        auto address = std::make_shared<SocketAddress>(
            reinterpret_cast<const sockaddr*>(&ptr_->preferred_addr.ipv4));
        auto addr = SocketAddressBase::Create(env, std::move(address));
        if (!addr) return {};
        values[kPreferredAddressIpv4Index] = addr->object();
      }

      if (ptr_->preferred_addr.ipv6_present) {
        auto address = std::make_shared<SocketAddress>(
            reinterpret_cast<const sockaddr*>(&ptr_->preferred_addr.ipv6));
        auto addr = SocketAddressBase::Create(env, std::move(address));
        if (!addr) return {};
        values[kPreferredAddressIpv6Index] = addr->object();
      }
      // ngtcp2_preferred_addr preferred_addr;
    }

    if (ptr_->original_dcid_present) {
      CID cid(ptr_->original_dcid);
      Local<Value> value;
      if (!ToV8Value(env->context(), cid.ToString()).ToLocal(&value)) {
        return {};
      }
      values[kOriginalDCIDIndex] = value;
    }

    if (ptr_->initial_scid_present) {
      CID cid(ptr_->initial_scid);
      Local<Value> value;
      if (!ToV8Value(env->context(), cid.ToString()).ToLocal(&value)) {
        return {};
      }
      values[kInitialSCIDIndex] = value;
    }

    if (ptr_->retry_scid_present) {
      CID cid(ptr_->retry_scid);
      Local<Value> value;
      if (!ToV8Value(env->context(), cid.ToString()).ToLocal(&value)) {
        return {};
      }
      values[kRetrySCIDIndex] = value;
    }

    values[kInitialMaxStreamDataBidiLocalIndex] = BigInt::NewFromUnsigned(
        env->isolate(), ptr_->initial_max_stream_data_bidi_local);
    values[kInitialMaxStreamDataBidiRemoteIndex] = BigInt::NewFromUnsigned(
        env->isolate(), ptr_->initial_max_stream_data_bidi_remote);
    values[kInitialMaxStreamDataUniIndex] = BigInt::NewFromUnsigned(
        env->isolate(), ptr_->initial_max_stream_data_uni);
    values[kInitialMaxDataIndex] =
        BigInt::NewFromUnsigned(env->isolate(), ptr_->initial_max_data);
    values[kInitialMaxStreamsBidiIndex] =
        BigInt::NewFromUnsigned(env->isolate(), ptr_->initial_max_streams_bidi);
    values[kInitialMaxStreamsUniIndex] =
        BigInt::NewFromUnsigned(env->isolate(), ptr_->initial_max_streams_uni);
    values[kMaxIdleTimeoutIndex] = BigInt::NewFromUnsigned(
        env->isolate(), ptr_->max_idle_timeout / NGTCP2_SECONDS);
    values[kActiveConnectionIDLimitIndex] = BigInt::NewFromUnsigned(
        env->isolate(), ptr_->active_connection_id_limit);
    values[kAckDelayExponentIndex] =
        BigInt::NewFromUnsigned(env->isolate(), ptr_->ack_delay_exponent);
    values[kMaxAckDelayIndex] =
        BigInt::NewFromUnsigned(env->isolate(), ptr_->ack_delay_exponent);
    values[kMaxAckDelayIndex] =
        BigInt::NewFromUnsigned(env->isolate(), ptr_->max_ack_delay);
    values[kMaxDatagramFrameSizeIndex] =
        BigInt::NewFromUnsigned(env->isolate(), ptr_->max_datagram_frame_size);
    values[kDisableActiveMigrationIndex] =
        Boolean::New(env->isolate(), ptr_->disable_active_migration);
  }

  auto obj = tmpl->NewInstance(env->context(), values);
  if (obj->SetPrototype(env->context(), Null(env->isolate())).IsNothing()) {
    return {};
  }
  return obj;
}

TransportParams::operator const ngtcp2_transport_params&() const {
  DCHECK_NOT_NULL(ptr_);
  return *ptr_;
}

TransportParams::operator const ngtcp2_transport_params*() const {
  DCHECK_NOT_NULL(ptr_);
  return ptr_;
}

TransportParams::operator bool() const {
  return ptr_ != nullptr;
}

uint64_t TransportParams::initial_max_streams_bidi() const {
  DCHECK_NOT_NULL(ptr_);
  return ptr_->initial_max_streams_bidi;
}

uint64_t TransportParams::initial_max_streams_uni() const {
  DCHECK_NOT_NULL(ptr_);
  return ptr_->initial_max_streams_uni;
}

void TransportParams::Initialize(Environment* env, Local<Object> target) {
  NODE_DEFINE_CONSTANT(target, DEFAULT_MAX_STREAM_DATA);
  NODE_DEFINE_CONSTANT(target, DEFAULT_MAX_DATA);
  NODE_DEFINE_CONSTANT(target, DEFAULT_MAX_IDLE_TIMEOUT);
  NODE_DEFINE_CONSTANT(target, DEFAULT_MAX_STREAMS_BIDI);
  NODE_DEFINE_CONSTANT(target, DEFAULT_MAX_STREAMS_UNI);
  NODE_DEFINE_CONSTANT(target, DEFAULT_ACTIVE_CONNECTION_ID_LIMIT);
}

}  // namespace quic
}  // namespace node

#endif  // OPENSSL_NO_QUIC