Skip to content

Commit

Permalink
quic: refactor QuicSession shared state to use AliasedStruct
Browse files Browse the repository at this point in the history
PR-URL: #34160
Reviewed-By: Anna Henningsen <anna@addaleax.net>
  • Loading branch information
jasnell committed Jul 5, 2020
1 parent f9c2245 commit 3acdd6a
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 141 deletions.
49 changes: 24 additions & 25 deletions lib/internal/quic/core.js
Expand Up @@ -39,6 +39,7 @@ const {
validateQuicEndpointOptions,
validateCreateSecureContextOptions,
validateQuicSocketConnectOptions,
QuicSessionSharedState,
} = require('internal/quic/util');
const util = require('util');
const assert = require('internal/assert');
Expand Down Expand Up @@ -131,12 +132,6 @@ const {
AF_INET,
AF_INET6,
NGTCP2_DEFAULT_MAX_PKTLEN,
IDX_QUIC_SESSION_STATE_MAX_STREAMS_BIDI,
IDX_QUIC_SESSION_STATE_MAX_STREAMS_UNI,
IDX_QUIC_SESSION_STATE_MAX_DATA_LEFT,
IDX_QUIC_SESSION_STATE_HANDSHAKE_CONFIRMED,
IDX_QUIC_SESSION_STATE_IDLE_TIMEOUT,
IDX_QUIC_SESSION_STATE_BYTES_IN_FLIGHT,
IDX_QUIC_SESSION_STATS_CREATED_AT,
IDX_QUIC_SESSION_STATS_HANDSHAKE_START_AT,
IDX_QUIC_SESSION_STATS_BYTES_RECEIVED,
Expand Down Expand Up @@ -605,11 +600,11 @@ function createSecureContext(options, init_cb) {
}

function onNewListener(event) {
toggleListeners(this[kHandle], event, true);
toggleListeners(this[kInternalState].state, event, true);
}

function onRemoveListener(event) {
toggleListeners(this[kHandle], event, false);
toggleListeners(this[kInternalState].state, event, false);
}

function getStats(obj, idx) {
Expand Down Expand Up @@ -1651,6 +1646,7 @@ class QuicSession extends EventEmitter {
handshakeContinuationHistogram: undefined,
highWaterMark: undefined,
defaultEncoding: undefined,
state: undefined,
};

constructor(socket, options) {
Expand Down Expand Up @@ -1693,6 +1689,7 @@ class QuicSession extends EventEmitter {
this[kHandle] = handle;
if (handle !== undefined) {
handle[owner_symbol] = this;
state.state = new QuicSessionSharedState(handle.state);
state.handshakeAckHistogram = new Histogram(handle.ack);
state.handshakeContinuationHistogram = new Histogram(handle.rate);
} else {
Expand Down Expand Up @@ -1849,10 +1846,10 @@ class QuicSession extends EventEmitter {
return false;
}

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

// Destroy the underlying QuicSession handle
handle.destroy(state.closeCode, state.closeFamily);
Expand Down Expand Up @@ -1950,8 +1946,8 @@ class QuicSession extends EventEmitter {
let bidi = 0;
let uni = 0;
if (this[kHandle]) {
bidi = this[kHandle].state[IDX_QUIC_SESSION_STATE_MAX_STREAMS_BIDI];
uni = this[kHandle].state[IDX_QUIC_SESSION_STATE_MAX_STREAMS_UNI];
bidi = this[kInternalState].state.maxStreamsBidi;
uni = this[kInternalState].state.maxStreamsUni;
}
return { bidi, uni };
}
Expand All @@ -1961,15 +1957,15 @@ class QuicSession extends EventEmitter {
}

get maxDataLeft() {
return this[kHandle]?.state[IDX_QUIC_SESSION_STATE_MAX_DATA_LEFT] || 0;
return this[kHandle] ? this[kInternalState].state.maxDataLeft : 0;
}

get bytesInFlight() {
return this[kHandle]?.state[IDX_QUIC_SESSION_STATE_BYTES_IN_FLIGHT] || 0;
return this[kHandle] ? this[kInternalState].state.bytesInFlight : 0;
}

get blockCount() {
return this[kHandle]?.state[IDX_QUIC_SESSION_STATS_BLOCK_COUNT] || 0;
return this[kHandle]?.stats[IDX_QUIC_SESSION_STATS_BLOCK_COUNT] || 0;
}

get authenticated() {
Expand Down Expand Up @@ -2003,8 +1999,9 @@ class QuicSession extends EventEmitter {
}

get handshakeConfirmed() {
return Boolean(
this[kHandle]?.state[IDX_QUIC_SESSION_STATE_HANDSHAKE_CONFIRMED]);
return this[kHandle] ?
this[kInternalState].state.handshakeConfirmed :
false;
}

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

if (this.listenerCount('keylog') > 0)
toggleListeners(handle, 'keylog', true);
const internalState = this[kInternalState];
if (this.listenerCount('keylog') > 0) {
toggleListeners(internalState.state, 'keylog', true);
}

if (this.listenerCount('pathValidation') > 0)
toggleListeners(handle, 'pathValidation', true);
toggleListeners(internalState.state, 'pathValidation', true);

if (this.listenerCount('usePreferredAddress') > 0)
toggleListeners(handle, 'usePreferredAddress', true);
toggleListeners(internalState.state, 'usePreferredAddress', true);

this[kMaybeReady](0x2);
}
Expand Down
133 changes: 120 additions & 13 deletions lib/internal/quic/util.js
Expand Up @@ -15,6 +15,12 @@ const {
},
} = require('internal/errors');

const {
kHandle,
} = require('internal/stream_base_commons');

const endianness = require('os').endianness();

const assert = require('internal/assert');
assert(process.versions.ngtcp2 !== undefined);

Expand Down Expand Up @@ -52,11 +58,19 @@ const {
IDX_QUIC_SESSION_MAX_UDP_PAYLOAD_SIZE,
IDX_QUIC_SESSION_CC_ALGO,
IDX_QUIC_SESSION_CONFIG_COUNT,
IDX_QUIC_SESSION_STATE_CERT_ENABLED,
IDX_QUIC_SESSION_STATE_CLIENT_HELLO_ENABLED,
IDX_QUIC_SESSION_STATE_KEYLOG_ENABLED,
IDX_QUIC_SESSION_STATE_PATH_VALIDATED_ENABLED,
IDX_QUIC_SESSION_STATE_USE_PREFERRED_ADDRESS_ENABLED,

IDX_QUICSESSION_STATE_KEYLOG_ENABLED,
IDX_QUICSESSION_STATE_CLIENT_HELLO_ENABLED,
IDX_QUICSESSION_STATE_CERT_ENABLED,
IDX_QUICSESSION_STATE_PATH_VALIDATED_ENABLED,
IDX_QUICSESSION_STATE_USE_PREFERRED_ADDRESS_ENABLED,
IDX_QUICSESSION_STATE_HANDSHAKE_CONFIRMED,
IDX_QUICSESSION_STATE_IDLE_TIMEOUT,
IDX_QUICSESSION_STATE_MAX_STREAMS_BIDI,
IDX_QUICSESSION_STATE_MAX_STREAMS_UNI,
IDX_QUICSESSION_STATE_MAX_DATA_LEFT,
IDX_QUICSESSION_STATE_BYTES_IN_FLIGHT,

IDX_HTTP3_QPACK_MAX_TABLE_CAPACITY,
IDX_HTTP3_QPACK_BLOCKED_STREAMS,
IDX_HTTP3_MAX_HEADER_LIST_SIZE,
Expand Down Expand Up @@ -756,29 +770,121 @@ function setTransportParams(config) {
// communicate that a handler has been added for the optional events
// so that the C++ internals know there is an actual listener. The event
// will not be emitted if there is no handler.
function toggleListeners(handle, event, on) {
if (handle === undefined)
function toggleListeners(state, event, on) {
if (state === undefined)
return;
const val = on ? 1 : 0;
switch (event) {
case 'keylog':
handle.state[IDX_QUIC_SESSION_STATE_KEYLOG_ENABLED] = val;
state.keylogEnabled = on;
break;
case 'clientHello':
handle.state[IDX_QUIC_SESSION_STATE_CLIENT_HELLO_ENABLED] = val;
state.clientHelloEnabled = on;
break;
case 'pathValidation':
handle.state[IDX_QUIC_SESSION_STATE_PATH_VALIDATED_ENABLED] = val;
state.pathValidatedEnabled = on;
break;
case 'OCSPRequest':
handle.state[IDX_QUIC_SESSION_STATE_CERT_ENABLED] = val;
state.certEnabled = on;
break;
case 'usePreferredAddress':
handle.state[IDX_QUIC_SESSION_STATE_USE_PREFERRED_ADDRESS_ENABLED] = on;
state.usePreferredAddressEnabled = on;
break;
}
}

// A utility class used to handle reading / modifying shared JS/C++
// state associated with a QuicSession
class QuicSessionSharedState {
constructor(state) {
this[kHandle] = Buffer.from(state);
}

get keylogEnabled() {
return Boolean(this[kHandle]
.readUInt8(IDX_QUICSESSION_STATE_KEYLOG_ENABLED));
}

set keylogEnabled(on) {
this[kHandle]
.writeUInt8(on ? 1 : 0, IDX_QUICSESSION_STATE_KEYLOG_ENABLED);
}

get clientHelloEnabled() {
return Boolean(this[kHandle]
.readUInt8(IDX_QUICSESSION_STATE_CLIENT_HELLO_ENABLED));
}

set clientHelloEnabled(on) {
this[kHandle]
.writeUInt8(on ? 1 : 0, IDX_QUICSESSION_STATE_CLIENT_HELLO_ENABLED);
}

get certEnabled() {
return Boolean(this[kHandle]
.readUInt8(IDX_QUICSESSION_STATE_CERT_ENABLED));
}

set certEnabled(on) {
this[kHandle]
.writeUInt8(on ? 1 : 0, IDX_QUICSESSION_STATE_CERT_ENABLED);
}

get pathValidatedEnabled() {
return Boolean(this[kHandle]
.readUInt8(IDX_QUICSESSION_STATE_PATH_VALIDATED_ENABLED));
}

set pathValidatedEnabled(on) {
this[kHandle]
.writeUInt8(on ? 1 : 0, IDX_QUICSESSION_STATE_PATH_VALIDATED_ENABLED);
}

get usePreferredAddressEnabled() {
return Boolean(this[kHandle]
.readUInt8(IDX_QUICSESSION_STATE_USE_PREFERRED_ADDRESS_ENABLED));
}

set usePreferredAddressEnabled(on) {
this[kHandle]
.writeUInt8(on ? 1 : 0,
IDX_QUICSESSION_STATE_USE_PREFERRED_ADDRESS_ENABLED);
}

get handshakeConfirmed() {
return Boolean(this[kHandle]
.readUInt8(IDX_QUICSESSION_STATE_HANDSHAKE_CONFIRMED));
}

get idleTimeout() {
return Boolean(this[kHandle]
.readUInt8(IDX_QUICSESSION_STATE_IDLE_TIMEOUT));
}

get maxStreamsBidi() {
return Number(endianness === 'BE' ?
this[kHandle].readBigInt64BE(IDX_QUICSESSION_STATE_MAX_STREAMS_BIDI) :
this[kHandle].readBigInt64LE(IDX_QUICSESSION_STATE_MAX_STREAMS_BIDI));
}

get maxStreamsUni() {
return Number(endianness === 'BE' ?
this[kHandle].readBigInt64BE(IDX_QUICSESSION_STATE_MAX_STREAMS_UNI) :
this[kHandle].readBigInt64LE(IDX_QUICSESSION_STATE_MAX_STREAMS_UNI));
}

get maxDataLeft() {
return Number(endianness === 'BE' ?
this[kHandle].readBigInt64BE(IDX_QUICSESSION_STATE_MAX_DATA_LEFT) :
this[kHandle].readBigInt64LE(IDX_QUICSESSION_STATE_MAX_DATA_LEFT));
}

get bytesInFlight() {
return Number(endianness === 'BE' ?
this[kHandle].readBigInt64BE(IDX_QUICSESSION_STATE_BYTES_IN_FLIGHT) :
this[kHandle].readBigInt64LE(IDX_QUICSESSION_STATE_BYTES_IN_FLIGHT));
}
}

module.exports = {
getAllowUnauthorized,
getSocketType,
Expand All @@ -796,4 +902,5 @@ module.exports = {
validateQuicEndpointOptions,
validateCreateSecureContextOptions,
validateQuicSocketConnectOptions,
QuicSessionSharedState,
};
16 changes: 5 additions & 11 deletions src/quic/node_quic.cc
Expand Up @@ -173,17 +173,6 @@ void Initialize(Local<Object> target,
V(IDX_QUIC_SESSION_MAX_ACK_DELAY) \
V(IDX_QUIC_SESSION_CC_ALGO) \
V(IDX_QUIC_SESSION_CONFIG_COUNT) \
V(IDX_QUIC_SESSION_STATE_CERT_ENABLED) \
V(IDX_QUIC_SESSION_STATE_CLIENT_HELLO_ENABLED) \
V(IDX_QUIC_SESSION_STATE_USE_PREFERRED_ADDRESS_ENABLED) \
V(IDX_QUIC_SESSION_STATE_PATH_VALIDATED_ENABLED) \
V(IDX_QUIC_SESSION_STATE_KEYLOG_ENABLED) \
V(IDX_QUIC_SESSION_STATE_MAX_STREAMS_BIDI) \
V(IDX_QUIC_SESSION_STATE_MAX_STREAMS_UNI) \
V(IDX_QUIC_SESSION_STATE_MAX_DATA_LEFT) \
V(IDX_QUIC_SESSION_STATE_BYTES_IN_FLIGHT) \
V(IDX_QUIC_SESSION_STATE_HANDSHAKE_CONFIRMED) \
V(IDX_QUIC_SESSION_STATE_IDLE_TIMEOUT) \
V(MAX_RETRYTOKEN_EXPIRATION) \
V(MIN_RETRYTOKEN_EXPIRATION) \
V(NGTCP2_APP_NOERROR) \
Expand Down Expand Up @@ -212,6 +201,11 @@ void Initialize(Local<Object> target,
V(ERR_FAILED_TO_CREATE_SESSION) \
V(UV_EBADF)

#define V(id, _, __) \
NODE_DEFINE_CONSTANT(constants, IDX_QUICSESSION_STATE_##id);
QUICSESSION_SHARED_STATE(V)
#undef V

#define V(name, _, __) \
NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATS_##name);
SESSION_STATS(V)
Expand Down
14 changes: 6 additions & 8 deletions src/quic/node_quic_session-inl.h
Expand Up @@ -105,7 +105,7 @@ ngtcp2_crypto_level QuicCryptoContext::write_crypto_level() const {
// to a keylog file that can be consumed by tools like Wireshark to intercept
// and decrypt QUIC network traffic.
void QuicCryptoContext::Keylog(const char* line) {
if (UNLIKELY(session_->state_[IDX_QUIC_SESSION_STATE_KEYLOG_ENABLED] == 1))
if (UNLIKELY(session_->state_->keylog_enabled))
session_->listener()->OnKeylog(line, strlen(line));
}

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

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

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

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

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

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

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

0 comments on commit 3acdd6a

Please sign in to comment.