Skip to content

Commit 7b0fc29

Browse files
committed
quic: add TransportParams
1 parent 0a1703d commit 7b0fc29

5 files changed

Lines changed: 459 additions & 1 deletion

File tree

‎node.gyp‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,13 +342,15 @@
342342
'src/quic/preferredaddress.cc',
343343
'src/quic/sessionticket.cc',
344344
'src/quic/tokens.cc',
345+
'src/quic/transportparams.cc',
345346
'src/quic/bindingdata.h',
346347
'src/quic/cid.h',
347348
'src/quic/data.h',
348349
'src/quic/logstream.h',
349350
'src/quic/preferredaddress.h',
350351
'src/quic/sessionticket.h',
351352
'src/quic/tokens.h',
353+
'src/quic/transportparams.h',
352354
],
353355
'node_mksnapshot_exec': '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)node_mksnapshot<(EXECUTABLE_SUFFIX)',
354356
'conditions': [

‎src/quic/bindingdata.h‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <memory_tracker.h>
99
#include <nghttp3/nghttp3.h>
1010
#include <ngtcp2/ngtcp2.h>
11+
#include <ngtcp2/ngtcp2_crypto.h>
1112
#include <node.h>
1213
#include <node_mem.h>
1314
#include <v8.h>
@@ -17,6 +18,13 @@ namespace quic {
1718

1819
class Endpoint;
1920

21+
enum class Side {
22+
CLIENT = NGTCP2_CRYPTO_SIDE_CLIENT,
23+
SERVER = NGTCP2_CRYPTO_SIDE_SERVER,
24+
};
25+
26+
constexpr size_t kDefaultMaxPacketLength = NGTCP2_MAX_UDP_PAYLOAD_SIZE;
27+
2028
// ============================================================================
2129

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

5563
// The various JS strings the implementation uses.
5664
#define QUIC_STRINGS(V) \
57-
V(http3_alpn, &NGHTTP3_ALPN_H3[1]) \
65+
V(ack_delay_exponent, "ackDelayExponent") \
66+
V(active_connection_id_limit, "activeConnectionIDLimit") \
67+
V(disable_active_migration, "disableActiveMigration") \
5868
V(endpoint, "Endpoint") \
5969
V(endpoint_udp, "Endpoint::UDP") \
70+
V(http3_alpn, &NGHTTP3_ALPN_H3[1]) \
71+
V(initial_max_data, "initialMaxData") \
72+
V(initial_max_stream_data_bidi_local, "initialMaxStreamDataBidiLocal") \
73+
V(initial_max_stream_data_bidi_remote, "initialMaxStreamDataBidiRemote") \
74+
V(initial_max_stream_data_uni, "initialMaxStreamDataUni") \
75+
V(initial_max_streams_bidi, "initialMaxStreamsBidi") \
76+
V(initial_max_streams_uni, "initialMaxStreamsUni") \
6077
V(logstream, "LogStream") \
78+
V(max_ack_delay, "maxAckDelay") \
79+
V(max_datagram_frame_size, "maxDatagramFrameSize") \
80+
V(max_idle_timeout, "maxIdleTimeout") \
6181
V(packetwrap, "PacketWrap") \
6282
V(session, "Session") \
6383
V(stream, "Stream")

‎src/quic/defs.h‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#pragma once
2+
3+
#include <env.h>
4+
#include <node_errors.h>
5+
#include <v8.h>
6+
7+
namespace node {
8+
namespace quic {
9+
10+
template <typename Opt, bool Opt::*member>
11+
bool SetOption(Environment* env,
12+
Opt* options,
13+
const v8::Local<v8::Object>& object,
14+
const v8::Local<v8::String>& name) {
15+
v8::Local<v8::Value> value;
16+
if (!object->Get(env->context(), name).ToLocal(&value)) return false;
17+
if (!value->IsUndefined()) {
18+
CHECK(value->IsBoolean());
19+
options->*member = value->IsTrue();
20+
}
21+
return true;
22+
}
23+
24+
template <typename Opt, uint64_t Opt::*member>
25+
bool SetOption(Environment* env,
26+
Opt* options,
27+
const v8::Local<v8::Object>& object,
28+
const v8::Local<v8::String>& name) {
29+
v8::Local<v8::Value> value;
30+
if (!object->Get(env->context(), name).ToLocal(&value)) return false;
31+
32+
if (!value->IsUndefined()) {
33+
CHECK_IMPLIES(!value->IsBigInt(), value->IsNumber());
34+
35+
uint64_t val = 0;
36+
if (value->IsBigInt()) {
37+
bool lossless = true;
38+
val = value.As<v8::BigInt>()->Uint64Value(&lossless);
39+
if (!lossless) {
40+
Utf8Value label(env->isolate(), name);
41+
THROW_ERR_OUT_OF_RANGE(
42+
env,
43+
(std::string("options.") + (*label) + " is out of range").c_str());
44+
return false;
45+
}
46+
} else {
47+
val = static_cast<int64_t>(value.As<v8::Number>()->Value());
48+
}
49+
options->*member = val;
50+
}
51+
return true;
52+
}
53+
54+
} // namespace quic
55+
} // namespace node

‎src/quic/transportparams.cc‎

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
2+
3+
#include "transportparams.h"
4+
#include <env-inl.h>
5+
#include <memory_tracker-inl.h>
6+
#include <node_sockaddr-inl.h>
7+
#include <util-inl.h>
8+
#include <v8.h>
9+
#include "bindingdata.h"
10+
#include "defs.h"
11+
#include "tokens.h"
12+
13+
namespace node {
14+
15+
using v8::ArrayBuffer;
16+
using v8::Just;
17+
using v8::Local;
18+
using v8::Maybe;
19+
using v8::Nothing;
20+
using v8::Object;
21+
using v8::Value;
22+
23+
namespace quic {
24+
TransportParams::Config::Config(Side side,
25+
const CID& ocid,
26+
const CID& retry_scid)
27+
: side(side), ocid(ocid), retry_scid(retry_scid) {}
28+
29+
Maybe<const TransportParams::Options> TransportParams::Options::From(
30+
Environment* env, Local<Value> value) {
31+
if (value.IsEmpty() || !value->IsObject()) {
32+
return Nothing<const Options>();
33+
}
34+
35+
auto& state = BindingData::Get(env);
36+
auto params = value.As<Object>();
37+
Options options;
38+
39+
#define SET(name) \
40+
SetOption<TransportParams::Options, &TransportParams::Options::name>( \
41+
env, &options, params, state.name##_string())
42+
43+
if (!SET(initial_max_stream_data_bidi_local) ||
44+
!SET(initial_max_stream_data_bidi_remote) ||
45+
!SET(initial_max_stream_data_uni) || !SET(initial_max_data) ||
46+
!SET(initial_max_streams_bidi) || !SET(initial_max_streams_uni) ||
47+
!SET(max_idle_timeout) || !SET(active_connection_id_limit) ||
48+
!SET(ack_delay_exponent) || !SET(max_ack_delay) ||
49+
!SET(max_datagram_frame_size) || !SET(disable_active_migration)) {
50+
return Nothing<const Options>();
51+
}
52+
53+
#undef SET
54+
55+
return Just<const Options>(options);
56+
}
57+
58+
TransportParams::TransportParams(Type type) : type_(type), ptr_(&params_) {}
59+
60+
TransportParams::TransportParams(Type type, const ngtcp2_transport_params* ptr)
61+
: type_(type), ptr_(ptr) {}
62+
63+
TransportParams::TransportParams(const Config& config, const Options& options)
64+
: TransportParams(Type::ENCRYPTED_EXTENSIONS) {
65+
ngtcp2_transport_params_default(&params_);
66+
params_.active_connection_id_limit = options.active_connection_id_limit;
67+
params_.initial_max_stream_data_bidi_local =
68+
options.initial_max_stream_data_bidi_local;
69+
params_.initial_max_stream_data_bidi_remote =
70+
options.initial_max_stream_data_bidi_remote;
71+
params_.initial_max_stream_data_uni = options.initial_max_stream_data_uni;
72+
params_.initial_max_streams_bidi = options.initial_max_streams_bidi;
73+
params_.initial_max_streams_uni = options.initial_max_streams_uni;
74+
params_.initial_max_data = options.initial_max_data;
75+
params_.max_idle_timeout = options.max_idle_timeout * NGTCP2_SECONDS;
76+
params_.max_ack_delay = options.max_ack_delay;
77+
params_.ack_delay_exponent = options.ack_delay_exponent;
78+
params_.max_datagram_frame_size = options.max_datagram_frame_size;
79+
params_.disable_active_migration = options.disable_active_migration ? 1 : 0;
80+
params_.preferred_address_present = 0;
81+
params_.stateless_reset_token_present = 0;
82+
params_.retry_scid_present = 0;
83+
84+
if (config.side == Side::SERVER) {
85+
// For the server side, the original dcid is always set.
86+
CHECK(config.ocid);
87+
params_.original_dcid = config.ocid;
88+
89+
// The retry_scid is only set if the server validated a retry token.
90+
if (config.retry_scid) {
91+
params_.retry_scid = config.retry_scid;
92+
params_.retry_scid_present = 1;
93+
}
94+
}
95+
96+
if (options.preferred_address_ipv4.has_value())
97+
SetPreferredAddress(options.preferred_address_ipv4.value());
98+
99+
if (options.preferred_address_ipv6.has_value())
100+
SetPreferredAddress(options.preferred_address_ipv6.value());
101+
}
102+
103+
TransportParams::TransportParams(Type type, const ngtcp2_vec& vec)
104+
: TransportParams(type) {
105+
int ret = ngtcp2_decode_transport_params(
106+
&params_,
107+
static_cast<ngtcp2_transport_params_type>(type),
108+
vec.base,
109+
vec.len);
110+
111+
if (ret != 0) {
112+
ptr_ = nullptr;
113+
error_ = QuicError::ForNgtcp2Error(ret);
114+
}
115+
}
116+
117+
Store TransportParams::Encode(Environment* env) {
118+
if (ptr_ == nullptr) {
119+
error_ = QuicError::ForNgtcp2Error(NGTCP2_INTERNAL_ERROR);
120+
return Store();
121+
}
122+
123+
// Preflight to see how much storage we'll need.
124+
ssize_t size = ngtcp2_encode_transport_params(
125+
nullptr, 0, static_cast<ngtcp2_transport_params_type>(type_), &params_);
126+
127+
DCHECK_GT(size, 0);
128+
129+
auto result = ArrayBuffer::NewBackingStore(env->isolate(), size);
130+
131+
auto ret = ngtcp2_encode_transport_params(
132+
static_cast<uint8_t*>(result->Data()),
133+
size,
134+
static_cast<ngtcp2_transport_params_type>(type_),
135+
&params_);
136+
137+
if (ret != 0) {
138+
error_ = QuicError::ForNgtcp2Error(ret);
139+
return Store();
140+
}
141+
142+
return Store(std::move(result), static_cast<size_t>(size));
143+
}
144+
145+
void TransportParams::SetPreferredAddress(const SocketAddress& address) {
146+
DCHECK(ptr_ == &params_);
147+
params_.preferred_address_present = 1;
148+
switch (address.family()) {
149+
case AF_INET: {
150+
const sockaddr_in* src =
151+
reinterpret_cast<const sockaddr_in*>(address.data());
152+
memcpy(params_.preferred_address.ipv4_addr,
153+
&src->sin_addr,
154+
sizeof(params_.preferred_address.ipv4_addr));
155+
params_.preferred_address.ipv4_port = address.port();
156+
return;
157+
}
158+
case AF_INET6: {
159+
const sockaddr_in6* src =
160+
reinterpret_cast<const sockaddr_in6*>(address.data());
161+
memcpy(params_.preferred_address.ipv6_addr,
162+
&src->sin6_addr,
163+
sizeof(params_.preferred_address.ipv6_addr));
164+
params_.preferred_address.ipv6_port = address.port();
165+
return;
166+
}
167+
}
168+
UNREACHABLE();
169+
}
170+
171+
void TransportParams::GenerateStatelessResetToken(
172+
const TokenSecret& token_secret, const CID& cid) {
173+
DCHECK(ptr_ == &params_);
174+
DCHECK(cid);
175+
params_.stateless_reset_token_present = 1;
176+
177+
StatelessResetToken token(params_.stateless_reset_token, token_secret, cid);
178+
}
179+
180+
CID TransportParams::GeneratePreferredAddressToken(const Session& session) {
181+
DCHECK_NOT_NULL(session);
182+
DCHECK(ptr_ == &params_);
183+
DCHECK(pscid);
184+
// TODO(@jasnell): To be implemented when Session is implemented
185+
// *pscid = session->cid_factory_.Generate();
186+
// params_.preferred_address.cid = *pscid;
187+
// session->endpoint_->AssociateStatelessResetToken(
188+
// session->endpoint().GenerateNewStatelessResetToken(
189+
// params_.preferred_address.stateless_reset_token, *pscid),
190+
// session);
191+
return CID::kInvalid;
192+
}
193+
194+
TransportParams::Type TransportParams::type() const {
195+
return type_;
196+
}
197+
198+
TransportParams::operator const ngtcp2_transport_params&() const {
199+
DCHECK_NOT_NULL(ptr_);
200+
return *ptr_;
201+
}
202+
203+
TransportParams::operator const ngtcp2_transport_params*() const {
204+
DCHECK_NOT_NULL(ptr_);
205+
return ptr_;
206+
}
207+
208+
TransportParams::operator bool() const {
209+
return ptr_ != nullptr;
210+
}
211+
212+
const QuicError& TransportParams::error() const {
213+
return error_;
214+
}
215+
216+
} // namespace quic
217+
} // namespace node
218+
219+
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC

0 commit comments

Comments
 (0)