Skip to content

Commit 2484083

Browse files
jasnellMoLow
authored andcommittedJul 6, 2023
quic: add more QUIC impl
* add BindingData * add LogStream * add TransportParams PR-URL: #47348 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
1 parent d0bda90 commit 2484083

10 files changed

+1082
-0
lines changed
 

‎src/async_wrap.h

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ namespace node {
6060
V(PROCESSWRAP) \
6161
V(PROMISE) \
6262
V(QUERYWRAP) \
63+
V(QUIC_LOGSTREAM) \
6364
V(SHUTDOWNWRAP) \
6465
V(SIGNALWRAP) \
6566
V(STATWATCHER) \

‎src/base_object_types.h

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#ifndef SRC_BASE_OBJECT_TYPES_H_
2+
#define SRC_BASE_OBJECT_TYPES_H_
3+
4+
#include <cinttypes>
5+
6+
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
7+
8+
namespace node {
9+
// List of internalBinding() data wrappers. The first argument should match
10+
// what the class passes to SET_BINDING_ID(), the second argument should match
11+
// the C++ class name.
12+
#define SERIALIZABLE_BINDING_TYPES(V) \
13+
V(encoding_binding_data, encoding_binding::BindingData) \
14+
V(fs_binding_data, fs::BindingData) \
15+
V(v8_binding_data, v8_utils::BindingData) \
16+
V(blob_binding_data, BlobBindingData) \
17+
V(process_binding_data, process::BindingData) \
18+
V(timers_binding_data, timers::BindingData) \
19+
V(url_binding_data, url::BindingData)
20+
21+
#define UNSERIALIZABLE_BINDING_TYPES(V) \
22+
V(http2_binding_data, http2::BindingData) \
23+
V(http_parser_binding_data, http_parser::BindingData) \
24+
V(quic_binding_data, quic::BindingData)
25+
26+
// List of (non-binding) BaseObjects that are serializable in the snapshot.
27+
// The first argument should match what the type passes to
28+
// SET_OBJECT_ID(), the second argument should match the C++ class
29+
// name.
30+
#define SERIALIZABLE_NON_BINDING_TYPES(V) \
31+
V(util_weak_reference, util::WeakReference)
32+
33+
// Helper list of all binding data wrapper types.
34+
#define BINDING_TYPES(V) \
35+
SERIALIZABLE_BINDING_TYPES(V) \
36+
UNSERIALIZABLE_BINDING_TYPES(V)
37+
38+
// Helper list of all BaseObjects that implement snapshot support.
39+
#define SERIALIZABLE_OBJECT_TYPES(V) \
40+
SERIALIZABLE_BINDING_TYPES(V) \
41+
SERIALIZABLE_NON_BINDING_TYPES(V)
42+
43+
#define V(TypeId, NativeType) k_##TypeId,
44+
enum class BindingDataType : uint8_t { BINDING_TYPES(V) kBindingDataTypeCount };
45+
// Make sure that we put the bindings first so that we can also use the enums
46+
// for the bindings as index to the binding data store.
47+
enum class EmbedderObjectType : uint8_t {
48+
BINDING_TYPES(V) SERIALIZABLE_NON_BINDING_TYPES(V)
49+
// We do not need to know about all the unserializable non-binding types for
50+
// now so we do not list them.
51+
kEmbedderObjectTypeCount
52+
};
53+
#undef V
54+
55+
// For now, BaseObjects only need to call this when they implement snapshot
56+
// support.
57+
#define SET_OBJECT_ID(TypeId) \
58+
static constexpr EmbedderObjectType type_int = EmbedderObjectType::k_##TypeId;
59+
60+
// Binding data should call this so that they can be looked up from the binding
61+
// data store.
62+
#define SET_BINDING_ID(TypeId) \
63+
static constexpr BindingDataType binding_type_int = \
64+
BindingDataType::k_##TypeId; \
65+
SET_OBJECT_ID(TypeId) \
66+
static_assert(static_cast<uint8_t>(type_int) == \
67+
static_cast<uint8_t>(binding_type_int));
68+
69+
} // namespace node
70+
71+
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
72+
73+
#endif // SRC_BASE_OBJECT_TYPES_H_

‎src/quic/bindingdata.cc

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
2+
#include "bindingdata.h"
3+
#include <base_object-inl.h>
4+
#include <env-inl.h>
5+
#include <memory_tracker-inl.h>
6+
#include <nghttp3/nghttp3.h>
7+
#include <ngtcp2/ngtcp2.h>
8+
#include <node.h>
9+
#include <node_errors.h>
10+
#include <node_external_reference.h>
11+
#include <node_mem-inl.h>
12+
#include <node_realm-inl.h>
13+
#include <v8.h>
14+
15+
namespace node {
16+
17+
using v8::Function;
18+
using v8::FunctionCallbackInfo;
19+
using v8::FunctionTemplate;
20+
using v8::Local;
21+
using v8::Object;
22+
using v8::String;
23+
using v8::Value;
24+
25+
namespace quic {
26+
27+
BindingData& BindingData::Get(Environment* env) {
28+
return *Realm::GetBindingData<BindingData>(env->context());
29+
}
30+
31+
BindingData::operator ngtcp2_mem() {
32+
return MakeAllocator();
33+
}
34+
35+
BindingData::operator nghttp3_mem() {
36+
ngtcp2_mem allocator = *this;
37+
nghttp3_mem http3_allocator = {
38+
allocator.user_data,
39+
allocator.malloc,
40+
allocator.free,
41+
allocator.calloc,
42+
allocator.realloc,
43+
};
44+
return http3_allocator;
45+
}
46+
47+
void BindingData::CheckAllocatedSize(size_t previous_size) const {
48+
CHECK_GE(current_ngtcp2_memory_, previous_size);
49+
}
50+
51+
void BindingData::IncreaseAllocatedSize(size_t size) {
52+
current_ngtcp2_memory_ += size;
53+
}
54+
55+
void BindingData::DecreaseAllocatedSize(size_t size) {
56+
current_ngtcp2_memory_ -= size;
57+
}
58+
59+
void BindingData::Initialize(Environment* env, Local<Object> target) {
60+
SetMethod(env->context(), target, "setCallbacks", SetCallbacks);
61+
Realm::GetCurrent(env->context())
62+
->AddBindingData<BindingData>(env->context(), target);
63+
}
64+
65+
void BindingData::RegisterExternalReferences(
66+
ExternalReferenceRegistry* registry) {
67+
registry->Register(SetCallbacks);
68+
}
69+
70+
BindingData::BindingData(Realm* realm, Local<Object> object)
71+
: BaseObject(realm, object) {
72+
MakeWeak();
73+
}
74+
75+
void BindingData::MemoryInfo(MemoryTracker* tracker) const {
76+
#define V(name, _) tracker->TrackField(#name, name##_callback());
77+
78+
QUIC_JS_CALLBACKS(V)
79+
80+
#undef V
81+
82+
#define V(name, _) tracker->TrackField(#name, name##_string());
83+
84+
QUIC_STRINGS(V)
85+
86+
#undef V
87+
}
88+
89+
#define V(name) \
90+
void BindingData::set_##name##_constructor_template( \
91+
Local<FunctionTemplate> tmpl) { \
92+
name##_constructor_template_.Reset(env()->isolate(), tmpl); \
93+
} \
94+
Local<FunctionTemplate> BindingData::name##_constructor_template() const { \
95+
return PersistentToLocal::Default(env()->isolate(), \
96+
name##_constructor_template_); \
97+
}
98+
99+
QUIC_CONSTRUCTORS(V)
100+
101+
#undef V
102+
103+
#define V(name, _) \
104+
void BindingData::set_##name##_callback(Local<Function> fn) { \
105+
name##_callback_.Reset(env()->isolate(), fn); \
106+
} \
107+
Local<Function> BindingData::name##_callback() const { \
108+
return PersistentToLocal::Default(env()->isolate(), name##_callback_); \
109+
}
110+
111+
QUIC_JS_CALLBACKS(V)
112+
113+
#undef V
114+
115+
#define V(name, value) \
116+
Local<String> BindingData::name##_string() const { \
117+
if (name##_string_.IsEmpty()) \
118+
name##_string_.Set(env()->isolate(), \
119+
OneByteString(env()->isolate(), value)); \
120+
return name##_string_.Get(env()->isolate()); \
121+
}
122+
123+
QUIC_STRINGS(V)
124+
125+
#undef V
126+
127+
#define V(name, value) \
128+
Local<String> BindingData::on_##name##_string() const { \
129+
if (on_##name##_string_.IsEmpty()) \
130+
on_##name##_string_.Set( \
131+
env()->isolate(), \
132+
FIXED_ONE_BYTE_STRING(env()->isolate(), "on" #value)); \
133+
return on_##name##_string_.Get(env()->isolate()); \
134+
}
135+
136+
QUIC_JS_CALLBACKS(V)
137+
138+
#undef V
139+
140+
void BindingData::SetCallbacks(const FunctionCallbackInfo<Value>& args) {
141+
auto env = Environment::GetCurrent(args);
142+
auto isolate = env->isolate();
143+
BindingData& state = BindingData::Get(env);
144+
CHECK(args[0]->IsObject());
145+
Local<Object> obj = args[0].As<Object>();
146+
147+
#define V(name, key) \
148+
do { \
149+
Local<Value> val; \
150+
if (!obj->Get(env->context(), state.on_##name##_string()).ToLocal(&val) || \
151+
!val->IsFunction()) { \
152+
return THROW_ERR_MISSING_ARGS(isolate, "Missing Callback: on" #key); \
153+
} \
154+
state.set_##name##_callback(val.As<Function>()); \
155+
} while (0);
156+
157+
QUIC_JS_CALLBACKS(V)
158+
159+
#undef V
160+
}
161+
162+
} // namespace quic
163+
} // namespace node
164+
165+
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC

‎src/quic/bindingdata.h

+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
#pragma once
2+
3+
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
4+
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
5+
6+
#include <base_object.h>
7+
#include <env.h>
8+
#include <memory_tracker.h>
9+
#include <nghttp3/nghttp3.h>
10+
#include <ngtcp2/ngtcp2.h>
11+
#include <ngtcp2/ngtcp2_crypto.h>
12+
#include <node.h>
13+
#include <node_mem.h>
14+
#include <v8.h>
15+
16+
namespace node {
17+
namespace quic {
18+
19+
class Endpoint;
20+
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+
28+
// ============================================================================
29+
30+
// The FunctionTemplates the BindingData will store for us.
31+
#define QUIC_CONSTRUCTORS(V) \
32+
V(endpoint) \
33+
V(logstream) \
34+
V(packet) \
35+
V(session) \
36+
V(stream) \
37+
V(udp)
38+
39+
// The callbacks are persistent v8::Function references that are set in the
40+
// quic::BindingState used to communicate data and events back out to the JS
41+
// environment. They are set once from the JavaScript side when the
42+
// internalBinding('quic') is first loaded.
43+
#define QUIC_JS_CALLBACKS(V) \
44+
V(endpoint_close, EndpointClose) \
45+
V(endpoint_error, EndpointError) \
46+
V(session_new, SessionNew) \
47+
V(session_close, SessionClose) \
48+
V(session_error, SessionError) \
49+
V(session_datagram, SessionDatagram) \
50+
V(session_datagram_status, SessionDatagramStatus) \
51+
V(session_handshake, SessionHandshake) \
52+
V(session_ticket, SessionTicket) \
53+
V(session_version_negotiation, SessionVersionNegotiation) \
54+
V(session_path_validation, SessionPathValidation) \
55+
V(stream_close, StreamClose) \
56+
V(stream_error, StreamError) \
57+
V(stream_created, StreamCreated) \
58+
V(stream_reset, StreamReset) \
59+
V(stream_headers, StreamHeaders) \
60+
V(stream_blocked, StreamBlocked) \
61+
V(stream_trailers, StreamTrailers)
62+
63+
// The various JS strings the implementation uses.
64+
#define QUIC_STRINGS(V) \
65+
V(ack_delay_exponent, "ackDelayExponent") \
66+
V(active_connection_id_limit, "activeConnectionIDLimit") \
67+
V(disable_active_migration, "disableActiveMigration") \
68+
V(endpoint, "Endpoint") \
69+
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") \
77+
V(logstream, "LogStream") \
78+
V(max_ack_delay, "maxAckDelay") \
79+
V(max_datagram_frame_size, "maxDatagramFrameSize") \
80+
V(max_idle_timeout, "maxIdleTimeout") \
81+
V(packetwrap, "PacketWrap") \
82+
V(session, "Session") \
83+
V(stream, "Stream")
84+
85+
// =============================================================================
86+
// The BindingState object holds state for the internalBinding('quic') binding
87+
// instance. It is mostly used to hold the persistent constructors, strings, and
88+
// callback references used for the rest of the implementation.
89+
//
90+
// TODO(@jasnell): Make this snapshotable?
91+
class BindingData final
92+
: public BaseObject,
93+
public mem::NgLibMemoryManager<BindingData, ngtcp2_mem> {
94+
public:
95+
SET_BINDING_ID(quic_binding_data)
96+
static void Initialize(Environment* env, v8::Local<v8::Object> target);
97+
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);
98+
99+
static BindingData& Get(Environment* env);
100+
101+
BindingData(Realm* realm, v8::Local<v8::Object> object);
102+
103+
void MemoryInfo(MemoryTracker* tracker) const override;
104+
SET_MEMORY_INFO_NAME(BindingData)
105+
SET_SELF_SIZE(BindingData)
106+
107+
// NgLibMemoryManager
108+
operator ngtcp2_mem();
109+
operator nghttp3_mem();
110+
void CheckAllocatedSize(size_t previous_size) const;
111+
void IncreaseAllocatedSize(size_t size);
112+
void DecreaseAllocatedSize(size_t size);
113+
114+
// Installs the set of JavaScript callback functions that are used to
115+
// bridge out to the JS API.
116+
static void SetCallbacks(const v8::FunctionCallbackInfo<v8::Value>& args);
117+
118+
// TODO(@jasnell) This will be added when Endpoint is implemented.
119+
// // A set of listening Endpoints. We maintain this to ensure that the
120+
// Endpoint
121+
// // cannot be gc'd while it is still listening and there are active
122+
// // connections.
123+
// std::unordered_map<Endpoint*, BaseObjectPtr<Endpoint>> listening_endpoints;
124+
125+
// The following set up various storage and accessors for common strings,
126+
// construction templates, and callbacks stored on the BindingData. These
127+
// are all defined in defs.h
128+
129+
#define V(name) \
130+
void set_##name##_constructor_template( \
131+
v8::Local<v8::FunctionTemplate> tmpl); \
132+
v8::Local<v8::FunctionTemplate> name##_constructor_template() const;
133+
QUIC_CONSTRUCTORS(V)
134+
#undef V
135+
136+
#define V(name, _) \
137+
void set_##name##_callback(v8::Local<v8::Function> fn); \
138+
v8::Local<v8::Function> name##_callback() const;
139+
QUIC_JS_CALLBACKS(V)
140+
#undef V
141+
142+
#define V(name, _) v8::Local<v8::String> name##_string() const;
143+
QUIC_STRINGS(V)
144+
#undef V
145+
146+
#define V(name, _) v8::Local<v8::String> on_##name##_string() const;
147+
QUIC_JS_CALLBACKS(V)
148+
#undef V
149+
150+
size_t current_ngtcp2_memory_ = 0;
151+
152+
#define V(name) v8::Global<v8::FunctionTemplate> name##_constructor_template_;
153+
QUIC_CONSTRUCTORS(V)
154+
#undef V
155+
156+
#define V(name, _) v8::Global<v8::Function> name##_callback_;
157+
QUIC_JS_CALLBACKS(V)
158+
#undef V
159+
160+
#define V(name, _) mutable v8::Eternal<v8::String> name##_string_;
161+
QUIC_STRINGS(V)
162+
#undef V
163+
164+
#define V(name, _) mutable v8::Eternal<v8::String> on_##name##_string_;
165+
QUIC_JS_CALLBACKS(V)
166+
#undef V
167+
};
168+
169+
} // namespace quic
170+
} // namespace node
171+
172+
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
173+
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

‎src/quic/defs.h

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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, ("options." + label.ToString() + " is out of range").c_str());
43+
return false;
44+
}
45+
} else {
46+
val = static_cast<int64_t>(value.As<v8::Number>()->Value());
47+
}
48+
options->*member = val;
49+
}
50+
return true;
51+
}
52+
53+
} // namespace quic
54+
} // namespace node

‎src/quic/logstream.cc

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
2+
3+
#include "logstream.h"
4+
#include <async_wrap-inl.h>
5+
#include <base_object-inl.h>
6+
#include <env-inl.h>
7+
#include <memory_tracker-inl.h>
8+
#include <node_external_reference.h>
9+
#include <stream_base-inl.h>
10+
#include <uv.h>
11+
#include <v8.h>
12+
#include "bindingdata.h"
13+
14+
namespace node {
15+
16+
using v8::FunctionTemplate;
17+
using v8::Local;
18+
using v8::Object;
19+
20+
namespace quic {
21+
22+
Local<FunctionTemplate> LogStream::GetConstructorTemplate(Environment* env) {
23+
auto& state = BindingData::Get(env);
24+
auto tmpl = state.logstream_constructor_template();
25+
if (tmpl.IsEmpty()) {
26+
tmpl = FunctionTemplate::New(env->isolate());
27+
tmpl->Inherit(AsyncWrap::GetConstructorTemplate(env));
28+
tmpl->InstanceTemplate()->SetInternalFieldCount(
29+
StreamBase::kInternalFieldCount);
30+
tmpl->SetClassName(state.logstream_string());
31+
StreamBase::AddMethods(env, tmpl);
32+
state.set_logstream_constructor_template(tmpl);
33+
}
34+
return tmpl;
35+
}
36+
37+
BaseObjectPtr<LogStream> LogStream::Create(Environment* env) {
38+
v8::Local<v8::Object> obj;
39+
if (!GetConstructorTemplate(env)
40+
->InstanceTemplate()
41+
->NewInstance(env->context())
42+
.ToLocal(&obj)) {
43+
return BaseObjectPtr<LogStream>();
44+
}
45+
return MakeDetachedBaseObject<LogStream>(env, obj);
46+
}
47+
48+
LogStream::LogStream(Environment* env, Local<Object> obj)
49+
: AsyncWrap(env, obj, AsyncWrap::PROVIDER_QUIC_LOGSTREAM), StreamBase(env) {
50+
MakeWeak();
51+
StreamBase::AttachToObject(GetObject());
52+
}
53+
54+
void LogStream::Emit(const uint8_t* data, size_t len, EmitOption option) {
55+
if (fin_seen_) return;
56+
fin_seen_ = option == EmitOption::FIN;
57+
58+
size_t remaining = len;
59+
// If the len is greater than the size of the buffer returned by
60+
// EmitAlloc then EmitRead will be called multiple times.
61+
while (remaining != 0) {
62+
uv_buf_t buf = EmitAlloc(len);
63+
size_t len = std::min<size_t>(remaining, buf.len);
64+
memcpy(buf.base, data, len);
65+
remaining -= len;
66+
data += len;
67+
// If we are actively reading from the stream, we'll call emit
68+
// read immediately. Otherwise we buffer the chunk and will push
69+
// the chunks out the next time ReadStart() is called.
70+
if (reading_) {
71+
EmitRead(len, buf);
72+
} else {
73+
// The total measures the total memory used so we always
74+
// increment but buf.len and not chunk len.
75+
ensure_space(buf.len);
76+
total_ += buf.len;
77+
buffer_.push_back(Chunk{len, buf});
78+
}
79+
}
80+
81+
if (ended_ && reading_) {
82+
EmitRead(UV_EOF);
83+
}
84+
}
85+
86+
void LogStream::Emit(const std::string_view line, EmitOption option) {
87+
Emit(reinterpret_cast<const uint8_t*>(line.data()), line.length(), option);
88+
}
89+
90+
void LogStream::End() {
91+
ended_ = true;
92+
}
93+
94+
int LogStream::ReadStart() {
95+
if (reading_) return 0;
96+
// Flush any chunks that have already been buffered.
97+
for (const auto& chunk : buffer_) EmitRead(chunk.len, chunk.buf);
98+
total_ = 0;
99+
buffer_.clear();
100+
if (fin_seen_) {
101+
// If we've already received the fin, there's nothing else to wait for.
102+
EmitRead(UV_EOF);
103+
return ReadStop();
104+
}
105+
// Otherwise, we're going to wait for more chunks to be written.
106+
reading_ = true;
107+
return 0;
108+
}
109+
110+
int LogStream::ReadStop() {
111+
reading_ = false;
112+
return 0;
113+
}
114+
115+
// We do not use either of these.
116+
int LogStream::DoShutdown(ShutdownWrap* req_wrap) {
117+
UNREACHABLE();
118+
}
119+
int LogStream::DoWrite(WriteWrap* w,
120+
uv_buf_t* bufs,
121+
size_t count,
122+
uv_stream_t* send_handle) {
123+
UNREACHABLE();
124+
}
125+
126+
bool LogStream::IsAlive() {
127+
return !ended_;
128+
}
129+
130+
bool LogStream::IsClosing() {
131+
return ended_;
132+
}
133+
134+
AsyncWrap* LogStream::GetAsyncWrap() {
135+
return this;
136+
}
137+
138+
void LogStream::MemoryInfo(MemoryTracker* tracker) const {
139+
tracker->TrackFieldWithSize("buffer", total_);
140+
}
141+
142+
// The LogStream buffer enforces a maximum size of kMaxLogStreamBuffer.
143+
void LogStream::ensure_space(size_t amt) {
144+
while (total_ + amt > kMaxLogStreamBuffer) {
145+
total_ -= buffer_.front().buf.len;
146+
buffer_.pop_front();
147+
}
148+
}
149+
} // namespace quic
150+
} // namespace node
151+
152+
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC

‎src/quic/logstream.h

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#pragma once
2+
3+
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
4+
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
5+
6+
#include <async_wrap.h>
7+
#include <base_object.h>
8+
#include <env.h>
9+
#include <stream_base.h>
10+
#include <deque>
11+
12+
namespace node {
13+
namespace quic {
14+
15+
// The LogStream is a utility that the QUIC impl uses to publish both QLog
16+
// and Keylog diagnostic data (one instance for each).
17+
class LogStream : public AsyncWrap, public StreamBase {
18+
public:
19+
static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
20+
Environment* env);
21+
22+
static BaseObjectPtr<LogStream> Create(Environment* env);
23+
24+
LogStream(Environment* env, v8::Local<v8::Object> obj);
25+
26+
enum class EmitOption {
27+
NONE,
28+
FIN,
29+
};
30+
31+
void Emit(const uint8_t* data,
32+
size_t len,
33+
EmitOption option = EmitOption::NONE);
34+
35+
void Emit(const std::string_view line, EmitOption option = EmitOption::NONE);
36+
37+
void End();
38+
39+
int ReadStart() override;
40+
41+
int ReadStop() override;
42+
43+
// We do not use either of these.
44+
int DoShutdown(ShutdownWrap* req_wrap) override;
45+
int DoWrite(WriteWrap* w,
46+
uv_buf_t* bufs,
47+
size_t count,
48+
uv_stream_t* send_handle) override;
49+
50+
bool IsAlive() override;
51+
bool IsClosing() override;
52+
AsyncWrap* GetAsyncWrap() override;
53+
54+
void MemoryInfo(MemoryTracker* tracker) const override;
55+
SET_MEMORY_INFO_NAME(LogStream)
56+
SET_SELF_SIZE(LogStream)
57+
58+
private:
59+
struct Chunk {
60+
// len will be <= buf.len
61+
size_t len;
62+
uv_buf_t buf;
63+
};
64+
size_t total_ = 0;
65+
bool fin_seen_ = false;
66+
bool ended_ = false;
67+
bool reading_ = false;
68+
std::deque<Chunk> buffer_;
69+
70+
// The value here is fairly arbitrary. Once we get everything
71+
// fully implemented and start working with this, we might
72+
// tune this number further.
73+
static constexpr size_t kMaxLogStreamBuffer = 1024 * 10;
74+
75+
// The LogStream buffer enforces a maximum size of kMaxLogStreamBuffer.
76+
void ensure_space(size_t amt);
77+
};
78+
79+
} // namespace quic
80+
} // namespace node
81+
82+
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
83+
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

‎src/quic/transportparams.cc

+218
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
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(ptr_ == &params_);
182+
// DCHECK(pscid);
183+
// TODO(@jasnell): To be implemented when Session is implemented
184+
// *pscid = session->cid_factory_.Generate();
185+
// params_.preferred_address.cid = *pscid;
186+
// session->endpoint_->AssociateStatelessResetToken(
187+
// session->endpoint().GenerateNewStatelessResetToken(
188+
// params_.preferred_address.stateless_reset_token, *pscid),
189+
// session);
190+
return CID::kInvalid;
191+
}
192+
193+
TransportParams::Type TransportParams::type() const {
194+
return type_;
195+
}
196+
197+
TransportParams::operator const ngtcp2_transport_params&() const {
198+
DCHECK_NOT_NULL(ptr_);
199+
return *ptr_;
200+
}
201+
202+
TransportParams::operator const ngtcp2_transport_params*() const {
203+
DCHECK_NOT_NULL(ptr_);
204+
return ptr_;
205+
}
206+
207+
TransportParams::operator bool() const {
208+
return ptr_ != nullptr;
209+
}
210+
211+
const QuicError& TransportParams::error() const {
212+
return error_;
213+
}
214+
215+
} // namespace quic
216+
} // namespace node
217+
218+
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC

‎src/quic/transportparams.h

+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
#pragma once
2+
3+
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
4+
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
5+
6+
#include <env.h>
7+
#include <ngtcp2/ngtcp2.h>
8+
#include <node_sockaddr.h>
9+
#include <optional>
10+
#include "bindingdata.h"
11+
#include "cid.h"
12+
#include "data.h"
13+
#include "tokens.h"
14+
15+
namespace node {
16+
namespace quic {
17+
18+
class Endpoint;
19+
class Session;
20+
21+
// The Transport Params are the set of configuration options that are sent to
22+
// the remote peer. They communicate the protocol options the other peer
23+
// should use when communicating with this session.
24+
class TransportParams final {
25+
public:
26+
enum class Type {
27+
CLIENT_HELLO = NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO,
28+
ENCRYPTED_EXTENSIONS = NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS,
29+
};
30+
31+
static constexpr uint64_t DEFAULT_MAX_STREAM_DATA_BIDI_LOCAL = 256 * 1024;
32+
static constexpr uint64_t DEFAULT_MAX_STREAM_DATA_BIDI_REMOTE = 256 * 1024;
33+
static constexpr uint64_t DEFAULT_MAX_STREAM_DATA_UNI = 256 * 1024;
34+
static constexpr uint64_t DEFAULT_MAX_DATA = 1 * 1024 * 1024;
35+
static constexpr uint64_t DEFAULT_MAX_IDLE_TIMEOUT = 10; // seconds
36+
static constexpr uint64_t DEFAULT_MAX_STREAMS_BIDI = 100;
37+
static constexpr uint64_t DEFAULT_MAX_STREAMS_UNI = 3;
38+
static constexpr uint64_t DEFAULT_ACTIVE_CONNECTION_ID_LIMIT = 2;
39+
40+
struct Config {
41+
Side side;
42+
const CID& ocid;
43+
const CID& retry_scid;
44+
Config(Side side,
45+
const CID& ocid = CID::kInvalid,
46+
const CID& retry_scid = CID::kInvalid);
47+
};
48+
49+
struct Options {
50+
// Set only on server Sessions, the preferred address communicates the IP
51+
// address and port that the server would prefer the client to use when
52+
// communicating with it. See the QUIC specification for more detail on how
53+
// the preferred address mechanism works.
54+
std::optional<SocketAddress> preferred_address_ipv4{};
55+
std::optional<SocketAddress> preferred_address_ipv6{};
56+
57+
// The initial size of the flow control window of locally initiated streams.
58+
// This is the maximum number of bytes that the *remote* endpoint can send
59+
// when the connection is started.
60+
uint64_t initial_max_stream_data_bidi_local =
61+
DEFAULT_MAX_STREAM_DATA_BIDI_LOCAL;
62+
63+
// The initial size of the flow control window of remotely initiated
64+
// streams. This is the maximum number of bytes that the remote endpoint can
65+
// send when the connection is started.
66+
uint64_t initial_max_stream_data_bidi_remote =
67+
DEFAULT_MAX_STREAM_DATA_BIDI_REMOTE;
68+
69+
// The initial size of the flow control window of remotely initiated
70+
// unidirectional streams. This is the maximum number of bytes that the
71+
// remote endpoint can send when the connection is started.
72+
uint64_t initial_max_stream_data_uni = DEFAULT_MAX_STREAM_DATA_UNI;
73+
74+
// The initial size of the session-level flow control window.
75+
uint64_t initial_max_data = DEFAULT_MAX_DATA;
76+
77+
// The initial maximum number of concurrent bidirectional streams the remote
78+
// endpoint is permitted to open.
79+
uint64_t initial_max_streams_bidi = DEFAULT_MAX_STREAMS_BIDI;
80+
81+
// The initial maximum number of concurrent unidirectional streams the
82+
// remote endpoint is permitted to open.
83+
uint64_t initial_max_streams_uni = DEFAULT_MAX_STREAMS_UNI;
84+
85+
// The maximum amount of time that a Session is permitted to remain idle
86+
// before it is silently closed and state is discarded.
87+
uint64_t max_idle_timeout = DEFAULT_MAX_IDLE_TIMEOUT;
88+
89+
// The maximum number of Connection IDs that the peer can store. A single
90+
// Session may have several connection IDs over it's lifetime.
91+
uint64_t active_connection_id_limit = DEFAULT_ACTIVE_CONNECTION_ID_LIMIT;
92+
93+
// Establishes the exponent used in ACK Delay field in the ACK frame. See
94+
// the QUIC specification for details. This is an advanced option that
95+
// should rarely be modified and only if there is really good reason.
96+
uint64_t ack_delay_exponent = NGTCP2_DEFAULT_ACK_DELAY_EXPONENT;
97+
98+
// The maximum amount of time by which the endpoint will delay sending
99+
// acknowledgements. This is an advanced option that should rarely be
100+
// modified and only if there is a really good reason. It is used to
101+
// determine how long a Session will wait to determine that a packet has
102+
// been lost.
103+
uint64_t max_ack_delay = NGTCP2_DEFAULT_MAX_ACK_DELAY;
104+
105+
// The maximum size of DATAGRAM frames that the endpoint will accept.
106+
// Setting the value to 0 will disable DATAGRAM support.
107+
uint64_t max_datagram_frame_size = kDefaultMaxPacketLength;
108+
109+
// When true, communicates that the Session does not support active
110+
// connection migration. See the QUIC specification for more details on
111+
// connection migration.
112+
bool disable_active_migration = false;
113+
114+
static v8::Maybe<const Options> From(Environment* env,
115+
v8::Local<v8::Value> value);
116+
};
117+
118+
explicit TransportParams(Type type);
119+
120+
// Creates an instance of TransportParams wrapping the existing const
121+
// ngtcp2_transport_params pointer.
122+
TransportParams(Type type, const ngtcp2_transport_params* ptr);
123+
124+
TransportParams(const Config& config, const Options& options);
125+
126+
// Creates an instance of TransportParams by decoding the given buffer.
127+
// If the parameters cannot be successfully decoded, the error()
128+
// property will be set with an appropriate QuicError and the bool()
129+
// operator will return false.
130+
TransportParams(Type type, const ngtcp2_vec& buf);
131+
132+
void GenerateStatelessResetToken(const TokenSecret& token_secret,
133+
const CID& cid);
134+
CID GeneratePreferredAddressToken(const Session& session);
135+
void SetPreferredAddress(const SocketAddress& address);
136+
137+
Type type() const;
138+
139+
operator const ngtcp2_transport_params&() const;
140+
operator const ngtcp2_transport_params*() const;
141+
142+
operator bool() const;
143+
144+
const QuicError& error() const;
145+
146+
// Returns an ArrayBuffer containing the encoded transport parameters.
147+
// If an error occurs during encoding, an empty shared_ptr will be returned
148+
// and the error() property will be set to an appropriate QuicError.
149+
Store Encode(Environment* env);
150+
151+
private:
152+
Type type_;
153+
ngtcp2_transport_params params_{};
154+
const ngtcp2_transport_params* ptr_;
155+
QuicError error_ = QuicError::TRANSPORT_NO_ERROR;
156+
};
157+
158+
} // namespace quic
159+
} // namespace node
160+
161+
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
162+
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

‎test/sequential/test-async-wrap-getasyncid.js

+1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ const { getSystemErrorName } = require('util');
6666
delete providers.FIXEDSIZEBLOBCOPY;
6767
delete providers.RANDOMPRIMEREQUEST;
6868
delete providers.CHECKPRIMEREQUEST;
69+
delete providers.QUIC_LOGSTREAM;
6970

7071
const objKeys = Object.keys(providers);
7172
if (objKeys.length > 0)

0 commit comments

Comments
 (0)
Please sign in to comment.