Skip to content

Commit 3acdd6a

Browse files
committed
quic: refactor QuicSession shared state to use AliasedStruct
PR-URL: #34160 Reviewed-By: Anna Henningsen <[email protected]>
1 parent f9c2245 commit 3acdd6a

File tree

6 files changed

+196
-141
lines changed

6 files changed

+196
-141
lines changed

Diff for: lib/internal/quic/core.js

+24-25
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const {
3939
validateQuicEndpointOptions,
4040
validateCreateSecureContextOptions,
4141
validateQuicSocketConnectOptions,
42+
QuicSessionSharedState,
4243
} = require('internal/quic/util');
4344
const util = require('util');
4445
const assert = require('internal/assert');
@@ -131,12 +132,6 @@ const {
131132
AF_INET,
132133
AF_INET6,
133134
NGTCP2_DEFAULT_MAX_PKTLEN,
134-
IDX_QUIC_SESSION_STATE_MAX_STREAMS_BIDI,
135-
IDX_QUIC_SESSION_STATE_MAX_STREAMS_UNI,
136-
IDX_QUIC_SESSION_STATE_MAX_DATA_LEFT,
137-
IDX_QUIC_SESSION_STATE_HANDSHAKE_CONFIRMED,
138-
IDX_QUIC_SESSION_STATE_IDLE_TIMEOUT,
139-
IDX_QUIC_SESSION_STATE_BYTES_IN_FLIGHT,
140135
IDX_QUIC_SESSION_STATS_CREATED_AT,
141136
IDX_QUIC_SESSION_STATS_HANDSHAKE_START_AT,
142137
IDX_QUIC_SESSION_STATS_BYTES_RECEIVED,
@@ -605,11 +600,11 @@ function createSecureContext(options, init_cb) {
605600
}
606601

607602
function onNewListener(event) {
608-
toggleListeners(this[kHandle], event, true);
603+
toggleListeners(this[kInternalState].state, event, true);
609604
}
610605

611606
function onRemoveListener(event) {
612-
toggleListeners(this[kHandle], event, false);
607+
toggleListeners(this[kInternalState].state, event, false);
613608
}
614609

615610
function getStats(obj, idx) {
@@ -1651,6 +1646,7 @@ class QuicSession extends EventEmitter {
16511646
handshakeContinuationHistogram: undefined,
16521647
highWaterMark: undefined,
16531648
defaultEncoding: undefined,
1649+
state: undefined,
16541650
};
16551651

16561652
constructor(socket, options) {
@@ -1693,6 +1689,7 @@ class QuicSession extends EventEmitter {
16931689
this[kHandle] = handle;
16941690
if (handle !== undefined) {
16951691
handle[owner_symbol] = this;
1692+
state.state = new QuicSessionSharedState(handle.state);
16961693
state.handshakeAckHistogram = new Histogram(handle.ack);
16971694
state.handshakeContinuationHistogram = new Histogram(handle.rate);
16981695
} else {
@@ -1849,10 +1846,10 @@ class QuicSession extends EventEmitter {
18491846
return false;
18501847
}
18511848

1852-
// Closing allows any existing QuicStream's to complete
1853-
// normally but disallows any new QuicStreams from being
1854-
// opened. Calls to openStream() will fail, and new streams
1855-
// from the peer will be rejected/ignored.
1849+
// Closing allows any existing QuicStream's to gracefully
1850+
// complete while disallowing any new QuicStreams from being
1851+
// opened (in either direction). Calls to openStream() will
1852+
// fail, and new streams from the peer will be rejected/ignored.
18561853
close(callback) {
18571854
const state = this[kInternalState];
18581855
if (state.destroyed)
@@ -1921,8 +1918,7 @@ class QuicSession extends EventEmitter {
19211918
if (handle !== undefined) {
19221919
// Copy the stats for use after destruction
19231920
state.stats = new BigInt64Array(handle.stats);
1924-
state.idleTimeout =
1925-
Boolean(handle.state[IDX_QUIC_SESSION_STATE_IDLE_TIMEOUT]);
1921+
state.idleTimeout = this[kInternalState].state.idleTimeout;
19261922

19271923
// Destroy the underlying QuicSession handle
19281924
handle.destroy(state.closeCode, state.closeFamily);
@@ -1950,8 +1946,8 @@ class QuicSession extends EventEmitter {
19501946
let bidi = 0;
19511947
let uni = 0;
19521948
if (this[kHandle]) {
1953-
bidi = this[kHandle].state[IDX_QUIC_SESSION_STATE_MAX_STREAMS_BIDI];
1954-
uni = this[kHandle].state[IDX_QUIC_SESSION_STATE_MAX_STREAMS_UNI];
1949+
bidi = this[kInternalState].state.maxStreamsBidi;
1950+
uni = this[kInternalState].state.maxStreamsUni;
19551951
}
19561952
return { bidi, uni };
19571953
}
@@ -1961,15 +1957,15 @@ class QuicSession extends EventEmitter {
19611957
}
19621958

19631959
get maxDataLeft() {
1964-
return this[kHandle]?.state[IDX_QUIC_SESSION_STATE_MAX_DATA_LEFT] || 0;
1960+
return this[kHandle] ? this[kInternalState].state.maxDataLeft : 0;
19651961
}
19661962

19671963
get bytesInFlight() {
1968-
return this[kHandle]?.state[IDX_QUIC_SESSION_STATE_BYTES_IN_FLIGHT] || 0;
1964+
return this[kHandle] ? this[kInternalState].state.bytesInFlight : 0;
19691965
}
19701966

19711967
get blockCount() {
1972-
return this[kHandle]?.state[IDX_QUIC_SESSION_STATS_BLOCK_COUNT] || 0;
1968+
return this[kHandle]?.stats[IDX_QUIC_SESSION_STATS_BLOCK_COUNT] || 0;
19731969
}
19741970

19751971
get authenticated() {
@@ -2003,8 +1999,9 @@ class QuicSession extends EventEmitter {
20031999
}
20042000

20052001
get handshakeConfirmed() {
2006-
return Boolean(
2007-
this[kHandle]?.state[IDX_QUIC_SESSION_STATE_HANDSHAKE_CONFIRMED]);
2002+
return this[kHandle] ?
2003+
this[kInternalState].state.handshakeConfirmed :
2004+
false;
20082005
}
20092006

20102007
get idleTimeout() {
@@ -2449,14 +2446,16 @@ class QuicClientSession extends QuicSession {
24492446
// Listeners may have been added before the handle was created.
24502447
// Ensure that we toggle those listeners in the handle state.
24512448

2452-
if (this.listenerCount('keylog') > 0)
2453-
toggleListeners(handle, 'keylog', true);
2449+
const internalState = this[kInternalState];
2450+
if (this.listenerCount('keylog') > 0) {
2451+
toggleListeners(internalState.state, 'keylog', true);
2452+
}
24542453

24552454
if (this.listenerCount('pathValidation') > 0)
2456-
toggleListeners(handle, 'pathValidation', true);
2455+
toggleListeners(internalState.state, 'pathValidation', true);
24572456

24582457
if (this.listenerCount('usePreferredAddress') > 0)
2459-
toggleListeners(handle, 'usePreferredAddress', true);
2458+
toggleListeners(internalState.state, 'usePreferredAddress', true);
24602459

24612460
this[kMaybeReady](0x2);
24622461
}

Diff for: lib/internal/quic/util.js

+120-13
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ const {
1515
},
1616
} = require('internal/errors');
1717

18+
const {
19+
kHandle,
20+
} = require('internal/stream_base_commons');
21+
22+
const endianness = require('os').endianness();
23+
1824
const assert = require('internal/assert');
1925
assert(process.versions.ngtcp2 !== undefined);
2026

@@ -52,11 +58,19 @@ const {
5258
IDX_QUIC_SESSION_MAX_UDP_PAYLOAD_SIZE,
5359
IDX_QUIC_SESSION_CC_ALGO,
5460
IDX_QUIC_SESSION_CONFIG_COUNT,
55-
IDX_QUIC_SESSION_STATE_CERT_ENABLED,
56-
IDX_QUIC_SESSION_STATE_CLIENT_HELLO_ENABLED,
57-
IDX_QUIC_SESSION_STATE_KEYLOG_ENABLED,
58-
IDX_QUIC_SESSION_STATE_PATH_VALIDATED_ENABLED,
59-
IDX_QUIC_SESSION_STATE_USE_PREFERRED_ADDRESS_ENABLED,
61+
62+
IDX_QUICSESSION_STATE_KEYLOG_ENABLED,
63+
IDX_QUICSESSION_STATE_CLIENT_HELLO_ENABLED,
64+
IDX_QUICSESSION_STATE_CERT_ENABLED,
65+
IDX_QUICSESSION_STATE_PATH_VALIDATED_ENABLED,
66+
IDX_QUICSESSION_STATE_USE_PREFERRED_ADDRESS_ENABLED,
67+
IDX_QUICSESSION_STATE_HANDSHAKE_CONFIRMED,
68+
IDX_QUICSESSION_STATE_IDLE_TIMEOUT,
69+
IDX_QUICSESSION_STATE_MAX_STREAMS_BIDI,
70+
IDX_QUICSESSION_STATE_MAX_STREAMS_UNI,
71+
IDX_QUICSESSION_STATE_MAX_DATA_LEFT,
72+
IDX_QUICSESSION_STATE_BYTES_IN_FLIGHT,
73+
6074
IDX_HTTP3_QPACK_MAX_TABLE_CAPACITY,
6175
IDX_HTTP3_QPACK_BLOCKED_STREAMS,
6276
IDX_HTTP3_MAX_HEADER_LIST_SIZE,
@@ -756,29 +770,121 @@ function setTransportParams(config) {
756770
// communicate that a handler has been added for the optional events
757771
// so that the C++ internals know there is an actual listener. The event
758772
// will not be emitted if there is no handler.
759-
function toggleListeners(handle, event, on) {
760-
if (handle === undefined)
773+
function toggleListeners(state, event, on) {
774+
if (state === undefined)
761775
return;
762-
const val = on ? 1 : 0;
763776
switch (event) {
764777
case 'keylog':
765-
handle.state[IDX_QUIC_SESSION_STATE_KEYLOG_ENABLED] = val;
778+
state.keylogEnabled = on;
766779
break;
767780
case 'clientHello':
768-
handle.state[IDX_QUIC_SESSION_STATE_CLIENT_HELLO_ENABLED] = val;
781+
state.clientHelloEnabled = on;
769782
break;
770783
case 'pathValidation':
771-
handle.state[IDX_QUIC_SESSION_STATE_PATH_VALIDATED_ENABLED] = val;
784+
state.pathValidatedEnabled = on;
772785
break;
773786
case 'OCSPRequest':
774-
handle.state[IDX_QUIC_SESSION_STATE_CERT_ENABLED] = val;
787+
state.certEnabled = on;
775788
break;
776789
case 'usePreferredAddress':
777-
handle.state[IDX_QUIC_SESSION_STATE_USE_PREFERRED_ADDRESS_ENABLED] = on;
790+
state.usePreferredAddressEnabled = on;
778791
break;
779792
}
780793
}
781794

795+
// A utility class used to handle reading / modifying shared JS/C++
796+
// state associated with a QuicSession
797+
class QuicSessionSharedState {
798+
constructor(state) {
799+
this[kHandle] = Buffer.from(state);
800+
}
801+
802+
get keylogEnabled() {
803+
return Boolean(this[kHandle]
804+
.readUInt8(IDX_QUICSESSION_STATE_KEYLOG_ENABLED));
805+
}
806+
807+
set keylogEnabled(on) {
808+
this[kHandle]
809+
.writeUInt8(on ? 1 : 0, IDX_QUICSESSION_STATE_KEYLOG_ENABLED);
810+
}
811+
812+
get clientHelloEnabled() {
813+
return Boolean(this[kHandle]
814+
.readUInt8(IDX_QUICSESSION_STATE_CLIENT_HELLO_ENABLED));
815+
}
816+
817+
set clientHelloEnabled(on) {
818+
this[kHandle]
819+
.writeUInt8(on ? 1 : 0, IDX_QUICSESSION_STATE_CLIENT_HELLO_ENABLED);
820+
}
821+
822+
get certEnabled() {
823+
return Boolean(this[kHandle]
824+
.readUInt8(IDX_QUICSESSION_STATE_CERT_ENABLED));
825+
}
826+
827+
set certEnabled(on) {
828+
this[kHandle]
829+
.writeUInt8(on ? 1 : 0, IDX_QUICSESSION_STATE_CERT_ENABLED);
830+
}
831+
832+
get pathValidatedEnabled() {
833+
return Boolean(this[kHandle]
834+
.readUInt8(IDX_QUICSESSION_STATE_PATH_VALIDATED_ENABLED));
835+
}
836+
837+
set pathValidatedEnabled(on) {
838+
this[kHandle]
839+
.writeUInt8(on ? 1 : 0, IDX_QUICSESSION_STATE_PATH_VALIDATED_ENABLED);
840+
}
841+
842+
get usePreferredAddressEnabled() {
843+
return Boolean(this[kHandle]
844+
.readUInt8(IDX_QUICSESSION_STATE_USE_PREFERRED_ADDRESS_ENABLED));
845+
}
846+
847+
set usePreferredAddressEnabled(on) {
848+
this[kHandle]
849+
.writeUInt8(on ? 1 : 0,
850+
IDX_QUICSESSION_STATE_USE_PREFERRED_ADDRESS_ENABLED);
851+
}
852+
853+
get handshakeConfirmed() {
854+
return Boolean(this[kHandle]
855+
.readUInt8(IDX_QUICSESSION_STATE_HANDSHAKE_CONFIRMED));
856+
}
857+
858+
get idleTimeout() {
859+
return Boolean(this[kHandle]
860+
.readUInt8(IDX_QUICSESSION_STATE_IDLE_TIMEOUT));
861+
}
862+
863+
get maxStreamsBidi() {
864+
return Number(endianness === 'BE' ?
865+
this[kHandle].readBigInt64BE(IDX_QUICSESSION_STATE_MAX_STREAMS_BIDI) :
866+
this[kHandle].readBigInt64LE(IDX_QUICSESSION_STATE_MAX_STREAMS_BIDI));
867+
}
868+
869+
get maxStreamsUni() {
870+
return Number(endianness === 'BE' ?
871+
this[kHandle].readBigInt64BE(IDX_QUICSESSION_STATE_MAX_STREAMS_UNI) :
872+
this[kHandle].readBigInt64LE(IDX_QUICSESSION_STATE_MAX_STREAMS_UNI));
873+
}
874+
875+
get maxDataLeft() {
876+
return Number(endianness === 'BE' ?
877+
this[kHandle].readBigInt64BE(IDX_QUICSESSION_STATE_MAX_DATA_LEFT) :
878+
this[kHandle].readBigInt64LE(IDX_QUICSESSION_STATE_MAX_DATA_LEFT));
879+
}
880+
881+
get bytesInFlight() {
882+
return Number(endianness === 'BE' ?
883+
this[kHandle].readBigInt64BE(IDX_QUICSESSION_STATE_BYTES_IN_FLIGHT) :
884+
this[kHandle].readBigInt64LE(IDX_QUICSESSION_STATE_BYTES_IN_FLIGHT));
885+
}
886+
}
887+
782888
module.exports = {
783889
getAllowUnauthorized,
784890
getSocketType,
@@ -796,4 +902,5 @@ module.exports = {
796902
validateQuicEndpointOptions,
797903
validateCreateSecureContextOptions,
798904
validateQuicSocketConnectOptions,
905+
QuicSessionSharedState,
799906
};

Diff for: src/quic/node_quic.cc

+5-11
Original file line numberDiff line numberDiff line change
@@ -173,17 +173,6 @@ void Initialize(Local<Object> target,
173173
V(IDX_QUIC_SESSION_MAX_ACK_DELAY) \
174174
V(IDX_QUIC_SESSION_CC_ALGO) \
175175
V(IDX_QUIC_SESSION_CONFIG_COUNT) \
176-
V(IDX_QUIC_SESSION_STATE_CERT_ENABLED) \
177-
V(IDX_QUIC_SESSION_STATE_CLIENT_HELLO_ENABLED) \
178-
V(IDX_QUIC_SESSION_STATE_USE_PREFERRED_ADDRESS_ENABLED) \
179-
V(IDX_QUIC_SESSION_STATE_PATH_VALIDATED_ENABLED) \
180-
V(IDX_QUIC_SESSION_STATE_KEYLOG_ENABLED) \
181-
V(IDX_QUIC_SESSION_STATE_MAX_STREAMS_BIDI) \
182-
V(IDX_QUIC_SESSION_STATE_MAX_STREAMS_UNI) \
183-
V(IDX_QUIC_SESSION_STATE_MAX_DATA_LEFT) \
184-
V(IDX_QUIC_SESSION_STATE_BYTES_IN_FLIGHT) \
185-
V(IDX_QUIC_SESSION_STATE_HANDSHAKE_CONFIRMED) \
186-
V(IDX_QUIC_SESSION_STATE_IDLE_TIMEOUT) \
187176
V(MAX_RETRYTOKEN_EXPIRATION) \
188177
V(MIN_RETRYTOKEN_EXPIRATION) \
189178
V(NGTCP2_APP_NOERROR) \
@@ -212,6 +201,11 @@ void Initialize(Local<Object> target,
212201
V(ERR_FAILED_TO_CREATE_SESSION) \
213202
V(UV_EBADF)
214203

204+
#define V(id, _, __) \
205+
NODE_DEFINE_CONSTANT(constants, IDX_QUICSESSION_STATE_##id);
206+
QUICSESSION_SHARED_STATE(V)
207+
#undef V
208+
215209
#define V(name, _, __) \
216210
NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATS_##name);
217211
SESSION_STATS(V)

Diff for: src/quic/node_quic_session-inl.h

+6-8
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ ngtcp2_crypto_level QuicCryptoContext::write_crypto_level() const {
105105
// to a keylog file that can be consumed by tools like Wireshark to intercept
106106
// and decrypt QUIC network traffic.
107107
void QuicCryptoContext::Keylog(const char* line) {
108-
if (UNLIKELY(session_->state_[IDX_QUIC_SESSION_STATE_KEYLOG_ENABLED] == 1))
108+
if (UNLIKELY(session_->state_->keylog_enabled))
109109
session_->listener()->OnKeylog(line, strlen(line));
110110
}
111111

@@ -117,7 +117,7 @@ void QuicCryptoContext::OnClientHelloDone() {
117117
[&]() { set_in_client_hello(false); });
118118

119119
// Disable the callback at this point so we don't loop continuously
120-
session_->state_[IDX_QUIC_SESSION_STATE_CLIENT_HELLO_ENABLED] = 0;
120+
session_->state_->client_hello_enabled = 0;
121121
}
122122

123123
// Following a pause in the handshake for OCSP or client hello, we kickstart
@@ -274,14 +274,12 @@ void QuicSession::ExtendMaxStreamsRemoteBidi(uint64_t max_streams) {
274274

275275
void QuicSession::ExtendMaxStreamsUni(uint64_t max_streams) {
276276
Debug(this, "Setting max unidirectional streams to %" PRIu64, max_streams);
277-
state_[IDX_QUIC_SESSION_STATE_MAX_STREAMS_UNI] =
278-
static_cast<double>(max_streams);
277+
state_->max_streams_uni = max_streams;
279278
}
280279

281280
void QuicSession::ExtendMaxStreamsBidi(uint64_t max_streams) {
282281
Debug(this, "Setting max bidirectional streams to %" PRIu64, max_streams);
283-
state_[IDX_QUIC_SESSION_STATE_MAX_STREAMS_BIDI] =
284-
static_cast<double>(max_streams);
282+
state_->max_streams_bidi = max_streams;
285283
}
286284

287285
// Extends the stream-level flow control by the given number of bytes.
@@ -327,7 +325,7 @@ void QuicSession::HandshakeCompleted() {
327325
void QuicSession::HandshakeConfirmed() {
328326
Debug(this, "Handshake is confirmed");
329327
RecordTimestamp(&QuicSessionStats::handshake_confirmed_at);
330-
state_[IDX_QUIC_SESSION_STATE_HANDSHAKE_CONFIRMED] = 1;
328+
state_->handshake_confirmed = 1;
331329
}
332330

333331
bool QuicSession::is_handshake_completed() const {
@@ -346,7 +344,7 @@ void QuicSession::InitApplication() {
346344
// the peer. All existing streams are abandoned and closed.
347345
void QuicSession::OnIdleTimeout() {
348346
if (!is_destroyed()) {
349-
state_[IDX_QUIC_SESSION_STATE_IDLE_TIMEOUT] = 1;
347+
state_->idle_timeout = 1;
350348
Debug(this, "Idle timeout");
351349
Close(QuicSessionListener::SESSION_CLOSE_FLAG_SILENT);
352350
}

0 commit comments

Comments
 (0)