Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
src: cleanup quic TransportParams class
  • Loading branch information
jasnell committed Sep 20, 2025
commit e74d9f4033fbe4333dc86496d9ef128f1390e8e9
100 changes: 57 additions & 43 deletions src/quic/transportparams.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,23 @@ TransportParams::Config::Config(Side side,
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");
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;
auto& state = BindingData::Get(env);

if (value->IsUndefined()) {
return Just<Options>(options);
}

if (!value->IsObject()) {
THROW_ERR_INVALID_ARG_TYPE(env, "options must be an object");
return Nothing<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) \
Expand All @@ -68,14 +69,20 @@ Maybe<TransportParams::Options> TransportParams::Options::From(

#undef SET

// TODO(@jasnell): We are not yet exposing the ability to set the preferred
// adddress via the options, tho the underlying support is here in the class.
options.preferred_address_ipv4 = std::nullopt;
options.preferred_address_ipv6 = std::nullopt;

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(transportParamsVersion);
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();
Expand Down Expand Up @@ -131,36 +138,39 @@ TransportParams::TransportParams(const ngtcp2_transport_params* ptr)
TransportParams::TransportParams(const Config& config, const Options& options)
: TransportParams() {
ngtcp2_transport_params_default(&params_);
params_.active_connection_id_limit = options.active_connection_id_limit;
params_.initial_max_stream_data_bidi_local =
options.initial_max_stream_data_bidi_local;
params_.initial_max_stream_data_bidi_remote =
options.initial_max_stream_data_bidi_remote;
params_.initial_max_stream_data_uni = options.initial_max_stream_data_uni;
params_.initial_max_streams_bidi = options.initial_max_streams_bidi;
params_.initial_max_streams_uni = options.initial_max_streams_uni;
params_.initial_max_data = options.initial_max_data;
params_.max_idle_timeout = options.max_idle_timeout * NGTCP2_SECONDS;
params_.max_ack_delay = options.max_ack_delay;
params_.ack_delay_exponent = options.ack_delay_exponent;
params_.max_datagram_frame_size = options.max_datagram_frame_size;
params_.disable_active_migration = options.disable_active_migration ? 1 : 0;
params_.preferred_addr_present = 0;
params_.stateless_reset_token_present = 0;
params_.retry_scid_present = 0;
#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,
options.disable_active_migration ? 1 : 0);
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);
params_.original_dcid = config.ocid;
params_.original_dcid_present = 1;
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) {
params_.retry_scid = config.retry_scid;
params_.retry_scid_present = 1;
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());
Expand All @@ -169,33 +179,39 @@ TransportParams::TransportParams(const Config& config, const Options& options)
SetPreferredAddress(options.preferred_address_ipv6.value());
}

TransportParams::TransportParams(const ngtcp2_vec& vec, int version)
TransportParams::TransportParams(const ngtcp2_vec& vec, Version version)
: TransportParams() {
int ret = ngtcp2_transport_params_decode_versioned(
version, &params_, vec.base, vec.len);
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;
error_ = QuicError::ForNgtcp2Error(ret);
}
}

Store TransportParams::Encode(Environment* env, int version) const {
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, version, &params_);
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, version, &params_);
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 {};
Expand All @@ -214,6 +230,7 @@ void TransportParams::SetPreferredAddress(const SocketAddress& address) {
&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: {
Expand All @@ -223,6 +240,7 @@ void TransportParams::SetPreferredAddress(const SocketAddress& address) {
&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;
}
}
Expand Down Expand Up @@ -273,10 +291,6 @@ TransportParams::operator bool() const {
return ptr_ != nullptr;
}

const QuicError& TransportParams::error() const {
return error_;
}

void TransportParams::Initialize(Environment* env, Local<Object> target) {
NODE_DEFINE_CONSTANT(target, DEFAULT_MAX_STREAM_DATA);
NODE_DEFINE_CONSTANT(target, DEFAULT_MAX_DATA);
Expand Down
57 changes: 34 additions & 23 deletions src/quic/transportparams.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ class TransportParams final {
public:
static void Initialize(Environment* env, v8::Local<v8::Object> target);

static constexpr int QUIC_TRANSPORT_PARAMS_V1 = NGTCP2_TRANSPORT_PARAMS_V1;
static constexpr int QUIC_TRANSPORT_PARAMS_VERSION =
NGTCP2_TRANSPORT_PARAMS_VERSION;
enum class Version : int {
V1 = NGTCP2_TRANSPORT_PARAMS_V1,
};

static constexpr uint64_t DEFAULT_MAX_STREAM_DATA = 256 * 1024;
static constexpr uint64_t DEFAULT_MAX_DATA = 1 * 1024 * 1024;
static constexpr uint64_t DEFAULT_MAX_IDLE_TIMEOUT = 10; // seconds
Expand All @@ -44,69 +45,82 @@ class TransportParams final {
};

struct Options final : public MemoryRetainer {
int transportParamsVersion = QUIC_TRANSPORT_PARAMS_V1;
Version transportParamsVersion = Version::V1;

// Set only on server Sessions, the preferred address communicates the IP
// address and port that the server would prefer the client to use when
// communicating with it. See the QUIC specification for more detail on how
// the preferred address mechanism works.
// the preferred address mechanism works:
// https://www.rfc-editor.org/rfc/rfc9000.html#name-servers-preferred-address
std::optional<SocketAddress> preferred_address_ipv4{};
std::optional<SocketAddress> preferred_address_ipv6{};

// The initial size of the flow control window of locally initiated streams.
// This is the maximum number of bytes that the *remote* endpoint can send
// when the connection is started.
// https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2-4.16.1
uint64_t initial_max_stream_data_bidi_local = DEFAULT_MAX_STREAM_DATA;

// The initial size of the flow control window of remotely initiated
// streams. This is the maximum number of bytes that the remote endpoint can
// send when the connection is started.
// https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2-4.18.1
uint64_t initial_max_stream_data_bidi_remote = DEFAULT_MAX_STREAM_DATA;

// The initial size of the flow control window of remotely initiated
// unidirectional streams. This is the maximum number of bytes that the
// remote endpoint can send when the connection is started.
// https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2-4.20.1
uint64_t initial_max_stream_data_uni = DEFAULT_MAX_STREAM_DATA;

// The initial size of the session-level flow control window.
// https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2-4.14.1
uint64_t initial_max_data = DEFAULT_MAX_DATA;

// The initial maximum number of concurrent bidirectional streams the remote
// endpoint is permitted to open.
// https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2-4.22.1
uint64_t initial_max_streams_bidi = DEFAULT_MAX_STREAMS_BIDI;

// The initial maximum number of concurrent unidirectional streams the
// remote endpoint is permitted to open.
// https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2-4.24.1
uint64_t initial_max_streams_uni = DEFAULT_MAX_STREAMS_UNI;

// The maximum amount of time that a Session is permitted to remain idle
// before it is silently closed and state is discarded.
// https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2-4.4.1
uint64_t max_idle_timeout = DEFAULT_MAX_IDLE_TIMEOUT;

// The maximum number of Connection IDs that the peer can store. A single
// Session may have several connection IDs over it's lifetime.
// https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2-6.2.1
uint64_t active_connection_id_limit = DEFAULT_ACTIVE_CONNECTION_ID_LIMIT;

// Establishes the exponent used in ACK Delay field in the ACK frame. See
// the QUIC specification for details. This is an advanced option that
// should rarely be modified and only if there is really good reason.
// https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2-4.26.1
uint64_t ack_delay_exponent = NGTCP2_DEFAULT_ACK_DELAY_EXPONENT;

// The maximum amount of time by which the endpoint will delay sending
// acknowledgements. This is an advanced option that should rarely be
// modified and only if there is a really good reason. It is used to
// determine how long a Session will wait to determine that a packet has
// been lost.
// https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2-4.28.1
uint64_t max_ack_delay = NGTCP2_DEFAULT_MAX_ACK_DELAY;

// The maximum size of DATAGRAM frames that the endpoint will accept.
// Setting the value to 0 will disable DATAGRAM support.
// https://datatracker.ietf.org/doc/html/rfc9221#section-3
uint64_t max_datagram_frame_size = kDefaultMaxPacketLength;

// When true, communicates that the Session does not support active
// connection migration. See the QUIC specification for more details on
// connection migration.
// TODO(@jasnell): We currently do not implementation active migration.
// https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2-4.30.1
// TODO(@jasnell): We currently do not implement active migration.
bool disable_active_migration = true;

static const Options kDefault;
Expand All @@ -123,40 +137,37 @@ class TransportParams final {

explicit TransportParams();

// Creates an instance of TransportParams wrapping the existing const
// ngtcp2_transport_params pointer.
// Creates an instance of TransportParams wrapping an existing const
// ngtcp2_transport_params pointer. Instances created this way are
// immutable.
TransportParams(const ngtcp2_transport_params* ptr);

TransportParams(const Config& config, const Options& options);

// Creates an instance of TransportParams by decoding the given buffer.
// If the parameters cannot be successfully decoded, the error()
// property will be set with an appropriate QuicError and the bool()
// operator will return false.
TransportParams(const ngtcp2_vec& buf,
int version = QUIC_TRANSPORT_PARAMS_V1);
// If the parameters cannot be successfully decoded, the bool() operator
// will be false.
TransportParams(const ngtcp2_vec& buf, Version version = Version::V1);

void GenerateSessionTokens(Session* session);
void GenerateStatelessResetToken(const Endpoint& endpoint, const CID& cid);
void GeneratePreferredAddressToken(Session* session);
void SetPreferredAddress(const SocketAddress& address);

operator const ngtcp2_transport_params&() const;
operator const ngtcp2_transport_params*() const;

operator bool() const;

const QuicError& error() const;

// Returns an ArrayBuffer containing the encoded transport parameters.
// If an error occurs during encoding, an empty shared_ptr will be returned
// and the error() property will be set to an appropriate QuicError.
Store Encode(Environment* env, int version = QUIC_TRANSPORT_PARAMS_V1) const;
// Returns a Store containing the encoded transport parameters.
// If an error occurs during encoding, or if the parameters could
// not be encoded, an empty Store will be returned.
Store Encode(Environment* env, Version version = Version::V1) const;

private:
void SetPreferredAddress(const SocketAddress& address);
void GeneratePreferredAddressToken(Session* session);
void GenerateStatelessResetToken(const Endpoint& endpoint, const CID& cid);

ngtcp2_transport_params params_{};
const ngtcp2_transport_params* ptr_;
QuicError error_ = QuicError::TRANSPORT_NO_ERROR;
};

} // namespace node::quic
Expand Down