Skip to content
Closed
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
Prev Previous commit
Next Next commit
quic: add TransportParams
  • Loading branch information
jasnell committed Apr 10, 2023
commit 7b0fc290b46fb233183b2f01be78553d68ae3b73
2 changes: 2 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -342,13 +342,15 @@
'src/quic/preferredaddress.cc',
'src/quic/sessionticket.cc',
'src/quic/tokens.cc',
'src/quic/transportparams.cc',
'src/quic/bindingdata.h',
'src/quic/cid.h',
'src/quic/data.h',
'src/quic/logstream.h',
'src/quic/preferredaddress.h',
'src/quic/sessionticket.h',
'src/quic/tokens.h',
'src/quic/transportparams.h',
],
'node_mksnapshot_exec': '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)node_mksnapshot<(EXECUTABLE_SUFFIX)',
'conditions': [
Expand Down
22 changes: 21 additions & 1 deletion src/quic/bindingdata.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <memory_tracker.h>
#include <nghttp3/nghttp3.h>
#include <ngtcp2/ngtcp2.h>
#include <ngtcp2/ngtcp2_crypto.h>
#include <node.h>
#include <node_mem.h>
#include <v8.h>
Expand All @@ -17,6 +18,13 @@ namespace quic {

class Endpoint;

enum class Side {
CLIENT = NGTCP2_CRYPTO_SIDE_CLIENT,
SERVER = NGTCP2_CRYPTO_SIDE_SERVER,
};

constexpr size_t kDefaultMaxPacketLength = NGTCP2_MAX_UDP_PAYLOAD_SIZE;

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

// The FunctionTemplates the BindingData will store for us.
Expand Down Expand Up @@ -54,10 +62,22 @@ class Endpoint;

// The various JS strings the implementation uses.
#define QUIC_STRINGS(V) \
V(http3_alpn, &NGHTTP3_ALPN_H3[1]) \
V(ack_delay_exponent, "ackDelayExponent") \
V(active_connection_id_limit, "activeConnectionIDLimit") \
V(disable_active_migration, "disableActiveMigration") \
V(endpoint, "Endpoint") \
V(endpoint_udp, "Endpoint::UDP") \
V(http3_alpn, &NGHTTP3_ALPN_H3[1]) \
V(initial_max_data, "initialMaxData") \
V(initial_max_stream_data_bidi_local, "initialMaxStreamDataBidiLocal") \
V(initial_max_stream_data_bidi_remote, "initialMaxStreamDataBidiRemote") \
V(initial_max_stream_data_uni, "initialMaxStreamDataUni") \
V(initial_max_streams_bidi, "initialMaxStreamsBidi") \
V(initial_max_streams_uni, "initialMaxStreamsUni") \
V(logstream, "LogStream") \
V(max_ack_delay, "maxAckDelay") \
V(max_datagram_frame_size, "maxDatagramFrameSize") \
V(max_idle_timeout, "maxIdleTimeout") \
V(packetwrap, "PacketWrap") \
V(session, "Session") \
V(stream, "Stream")
Expand Down
55 changes: 55 additions & 0 deletions src/quic/defs.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#pragma once

#include <env.h>
#include <node_errors.h>
#include <v8.h>

namespace node {
namespace quic {

template <typename Opt, bool Opt::*member>
bool SetOption(Environment* env,
Opt* options,
const v8::Local<v8::Object>& object,
const v8::Local<v8::String>& name) {
v8::Local<v8::Value> value;
if (!object->Get(env->context(), name).ToLocal(&value)) return false;
if (!value->IsUndefined()) {
CHECK(value->IsBoolean());
Comment thread
anonrig marked this conversation as resolved.
options->*member = value->IsTrue();
}
return true;
}

template <typename Opt, uint64_t Opt::*member>
bool SetOption(Environment* env,
Opt* options,
const v8::Local<v8::Object>& object,
const v8::Local<v8::String>& name) {
v8::Local<v8::Value> value;
if (!object->Get(env->context(), name).ToLocal(&value)) return false;

if (!value->IsUndefined()) {
CHECK_IMPLIES(!value->IsBigInt(), value->IsNumber());

uint64_t val = 0;
if (value->IsBigInt()) {
bool lossless = true;
val = value.As<v8::BigInt>()->Uint64Value(&lossless);
if (!lossless) {
Utf8Value label(env->isolate(), name);
THROW_ERR_OUT_OF_RANGE(
env,
(std::string("options.") + (*label) + " is out of range").c_str());
Comment thread
jasnell marked this conversation as resolved.
Outdated
Comment thread
jasnell marked this conversation as resolved.
Outdated
return false;
}
} else {
val = static_cast<int64_t>(value.As<v8::Number>()->Value());
}
options->*member = val;
}
return true;
}

} // namespace quic
} // namespace node
219 changes: 219 additions & 0 deletions src/quic/transportparams.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC

#include "transportparams.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 "tokens.h"

namespace node {

using v8::ArrayBuffer;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::Nothing;
using v8::Object;
using v8::Value;

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

Maybe<const TransportParams::Options> TransportParams::Options::From(
Environment* env, Local<Value> value) {
if (value.IsEmpty() || !value->IsObject()) {
return Nothing<const Options>();
}

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

#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) || !SET(disable_active_migration)) {
return Nothing<const Options>();
}

#undef SET

return Just<const Options>(options);
}

TransportParams::TransportParams(Type type) : type_(type), ptr_(&params_) {}

TransportParams::TransportParams(Type type, const ngtcp2_transport_params* ptr)
: type_(type), ptr_(ptr) {}

TransportParams::TransportParams(const Config& config, const Options& options)
: TransportParams(Type::ENCRYPTED_EXTENSIONS) {
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_address_present = 0;
params_.stateless_reset_token_present = 0;
params_.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;

// 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;
}
}

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(Type type, const ngtcp2_vec& vec)
: TransportParams(type) {
int ret = ngtcp2_decode_transport_params(
&params_,
static_cast<ngtcp2_transport_params_type>(type),
vec.base,
vec.len);

if (ret != 0) {
ptr_ = nullptr;
error_ = QuicError::ForNgtcp2Error(ret);
}
}

Store TransportParams::Encode(Environment* env) {
if (ptr_ == nullptr) {
error_ = QuicError::ForNgtcp2Error(NGTCP2_INTERNAL_ERROR);
return Store();
}

// Preflight to see how much storage we'll need.
ssize_t size = ngtcp2_encode_transport_params(
nullptr, 0, static_cast<ngtcp2_transport_params_type>(type_), &params_);

DCHECK_GT(size, 0);

auto result = ArrayBuffer::NewBackingStore(env->isolate(), size);

auto ret = ngtcp2_encode_transport_params(
static_cast<uint8_t*>(result->Data()),
size,
static_cast<ngtcp2_transport_params_type>(type_),
&params_);

if (ret != 0) {
error_ = QuicError::ForNgtcp2Error(ret);
return Store();
}

return Store(std::move(result), static_cast<size_t>(size));
}

void TransportParams::SetPreferredAddress(const SocketAddress& address) {
DCHECK(ptr_ == &params_);
params_.preferred_address_present = 1;
switch (address.family()) {
case AF_INET: {
const sockaddr_in* src =
reinterpret_cast<const sockaddr_in*>(address.data());
memcpy(params_.preferred_address.ipv4_addr,
&src->sin_addr,
sizeof(params_.preferred_address.ipv4_addr));
params_.preferred_address.ipv4_port = address.port();
return;
}
case AF_INET6: {
const sockaddr_in6* src =
reinterpret_cast<const sockaddr_in6*>(address.data());
memcpy(params_.preferred_address.ipv6_addr,
&src->sin6_addr,
sizeof(params_.preferred_address.ipv6_addr));
params_.preferred_address.ipv6_port = address.port();
return;
}
}
UNREACHABLE();
}

void TransportParams::GenerateStatelessResetToken(
const TokenSecret& token_secret, const CID& cid) {
DCHECK(ptr_ == &params_);
DCHECK(cid);
params_.stateless_reset_token_present = 1;

StatelessResetToken token(params_.stateless_reset_token, token_secret, cid);
}

CID TransportParams::GeneratePreferredAddressToken(const Session& session) {
DCHECK_NOT_NULL(session);
DCHECK(ptr_ == &params_);
DCHECK(pscid);
// TODO(@jasnell): To be implemented when Session is implemented
// *pscid = session->cid_factory_.Generate();
// params_.preferred_address.cid = *pscid;
// session->endpoint_->AssociateStatelessResetToken(
// session->endpoint().GenerateNewStatelessResetToken(
// params_.preferred_address.stateless_reset_token, *pscid),
// session);
return CID::kInvalid;
}

TransportParams::Type TransportParams::type() const {
return type_;
}

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;
}

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

} // namespace quic
} // namespace node

#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
Loading