quic: multiple updates to quic impl

* separate stats and symbols into separate files
* quic: rename `EndpointStats` and `SessionStats` to be consistent
  * s/EndpointStats/QuicEndpointStats/
  * s/SessionStats/QuicSessionStats/
* separate state into separate files and other cleanups
* extend tls options validations
* rename classes for consistency and other cleanups

PR-URL: https://github.com/nodejs/node/pull/55971
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
James M Snell 2024-11-23 09:37:33 -08:00 committed by Antoine du Hamel
parent baed2763df
commit 1fb30d6e86
No known key found for this signature in database
GPG Key ID: 21D900FFDB233756
8 changed files with 1448 additions and 1221 deletions

File diff suppressed because it is too large Load Diff

566
lib/internal/quic/state.js Normal file
View File

@ -0,0 +1,566 @@
'use strict';
const {
ArrayBuffer,
DataView,
DataViewPrototypeGetBigInt64,
DataViewPrototypeGetBigUint64,
DataViewPrototypeGetUint8,
DataViewPrototypeSetUint8,
JSONStringify,
} = primordials;
const {
codes: {
ERR_ILLEGAL_CONSTRUCTOR,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_STATE,
},
} = require('internal/errors');
const {
isArrayBuffer,
} = require('util/types');
const { inspect } = require('internal/util/inspect');
const {
kFinishClose,
kInspect,
kPrivateConstructor,
} = require('internal/quic/symbols');
// This file defines the helper objects for accessing state for
// various QUIC objects. Each of these wraps a DataView.
// Some of the state properties are read only, others are mutable.
// An ArrayBuffer is shared with the C++ level to allow for more
// efficient communication of state across the C++/JS boundary.
// When the state object is no longer needed, it is closed to
// prevent further updates to the buffer.
const {
IDX_STATE_SESSION_PATH_VALIDATION,
IDX_STATE_SESSION_VERSION_NEGOTIATION,
IDX_STATE_SESSION_DATAGRAM,
IDX_STATE_SESSION_SESSION_TICKET,
IDX_STATE_SESSION_CLOSING,
IDX_STATE_SESSION_GRACEFUL_CLOSE,
IDX_STATE_SESSION_SILENT_CLOSE,
IDX_STATE_SESSION_STATELESS_RESET,
IDX_STATE_SESSION_DESTROYED,
IDX_STATE_SESSION_HANDSHAKE_COMPLETED,
IDX_STATE_SESSION_HANDSHAKE_CONFIRMED,
IDX_STATE_SESSION_STREAM_OPEN_ALLOWED,
IDX_STATE_SESSION_PRIORITY_SUPPORTED,
IDX_STATE_SESSION_WRAPPED,
IDX_STATE_SESSION_LAST_DATAGRAM_ID,
IDX_STATE_ENDPOINT_BOUND,
IDX_STATE_ENDPOINT_RECEIVING,
IDX_STATE_ENDPOINT_LISTENING,
IDX_STATE_ENDPOINT_CLOSING,
IDX_STATE_ENDPOINT_BUSY,
IDX_STATE_ENDPOINT_PENDING_CALLBACKS,
IDX_STATE_STREAM_ID,
IDX_STATE_STREAM_FIN_SENT,
IDX_STATE_STREAM_FIN_RECEIVED,
IDX_STATE_STREAM_READ_ENDED,
IDX_STATE_STREAM_WRITE_ENDED,
IDX_STATE_STREAM_DESTROYED,
IDX_STATE_STREAM_PAUSED,
IDX_STATE_STREAM_RESET,
IDX_STATE_STREAM_HAS_READER,
IDX_STATE_STREAM_WANTS_BLOCK,
IDX_STATE_STREAM_WANTS_HEADERS,
IDX_STATE_STREAM_WANTS_RESET,
IDX_STATE_STREAM_WANTS_TRAILERS,
} = internalBinding('quic');
class QuicEndpointState {
/** @type {DataView} */
#handle;
/**
* @param {symbol} privateSymbol
* @param {ArrayBuffer} buffer
*/
constructor(privateSymbol, buffer) {
if (privateSymbol !== kPrivateConstructor) {
throw new ERR_ILLEGAL_CONSTRUCTOR();
}
if (!isArrayBuffer(buffer)) {
throw new ERR_INVALID_ARG_TYPE('buffer', ['ArrayBuffer'], buffer);
}
this.#handle = new DataView(buffer);
}
#assertNotClosed() {
if (this.#handle.byteLength === 0) {
throw new ERR_INVALID_STATE('Endpoint is closed');
}
}
/** @type {boolean} */
get isBound() {
this.#assertNotClosed();
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_ENDPOINT_BOUND);
}
/** @type {boolean} */
get isReceiving() {
this.#assertNotClosed();
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_ENDPOINT_RECEIVING);
}
/** @type {boolean} */
get isListening() {
this.#assertNotClosed();
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_ENDPOINT_LISTENING);
}
/** @type {boolean} */
get isClosing() {
this.#assertNotClosed();
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_ENDPOINT_CLOSING);
}
/** @type {boolean} */
get isBusy() {
this.#assertNotClosed();
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_ENDPOINT_BUSY);
}
/**
* The number of underlying callbacks that are pending. If the session
* is closing, these are the number of callbacks that the session is
* waiting on before it can be closed.
* @type {bigint}
*/
get pendingCallbacks() {
this.#assertNotClosed();
return DataViewPrototypeGetBigUint64(this.#handle, IDX_STATE_ENDPOINT_PENDING_CALLBACKS);
}
toString() {
return JSONStringify(this.toJSON());
}
toJSON() {
if (this.#handle.byteLength === 0) return {};
return {
__proto__: null,
isBound: this.isBound,
isReceiving: this.isReceiving,
isListening: this.isListening,
isClosing: this.isClosing,
isBusy: this.isBusy,
pendingCallbacks: `${this.pendingCallbacks}`,
};
}
[kInspect](depth, options) {
if (depth < 0)
return this;
if (this.#handle.byteLength === 0) {
return 'QuicEndpointState { <Closed> }';
}
const opts = {
...options,
depth: options.depth == null ? null : options.depth - 1,
};
return `QuicEndpointState ${inspect({
isBound: this.isBound,
isReceiving: this.isReceiving,
isListening: this.isListening,
isClosing: this.isClosing,
isBusy: this.isBusy,
pendingCallbacks: this.pendingCallbacks,
}, opts)}`;
}
[kFinishClose]() {
// Snapshot the state into a new DataView since the underlying
// buffer will be destroyed.
if (this.#handle.byteLength === 0) return;
this.#handle = new DataView(new ArrayBuffer(0));
}
}
class QuicSessionState {
/** @type {DataView} */
#handle;
/**
* @param {symbol} privateSymbol
* @param {ArrayBuffer} buffer
*/
constructor(privateSymbol, buffer) {
if (privateSymbol !== kPrivateConstructor) {
throw new ERR_ILLEGAL_CONSTRUCTOR();
}
if (!isArrayBuffer(buffer)) {
throw new ERR_INVALID_ARG_TYPE('buffer', ['ArrayBuffer'], buffer);
}
this.#handle = new DataView(buffer);
}
#assertNotClosed() {
if (this.#handle.byteLength === 0) {
throw new ERR_INVALID_STATE('Session is closed');
}
}
/** @type {boolean} */
get hasPathValidationListener() {
this.#assertNotClosed();
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_PATH_VALIDATION);
}
/** @type {boolean} */
set hasPathValidationListener(val) {
this.#assertNotClosed();
DataViewPrototypeSetUint8(this.#handle, IDX_STATE_SESSION_PATH_VALIDATION, val ? 1 : 0);
}
/** @type {boolean} */
get hasVersionNegotiationListener() {
this.#assertNotClosed();
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_VERSION_NEGOTIATION);
}
/** @type {boolean} */
set hasVersionNegotiationListener(val) {
this.#assertNotClosed();
DataViewPrototypeSetUint8(this.#handle, IDX_STATE_SESSION_VERSION_NEGOTIATION, val ? 1 : 0);
}
/** @type {boolean} */
get hasDatagramListener() {
this.#assertNotClosed();
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_DATAGRAM);
}
/** @type {boolean} */
set hasDatagramListener(val) {
this.#assertNotClosed();
DataViewPrototypeSetUint8(this.#handle, IDX_STATE_SESSION_DATAGRAM, val ? 1 : 0);
}
/** @type {boolean} */
get hasSessionTicketListener() {
this.#assertNotClosed();
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_SESSION_TICKET);
}
/** @type {boolean} */
set hasSessionTicketListener(val) {
this.#assertNotClosed();
DataViewPrototypeSetUint8(this.#handle, IDX_STATE_SESSION_SESSION_TICKET, val ? 1 : 0);
}
/** @type {boolean} */
get isClosing() {
this.#assertNotClosed();
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_CLOSING);
}
/** @type {boolean} */
get isGracefulClose() {
this.#assertNotClosed();
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_GRACEFUL_CLOSE);
}
/** @type {boolean} */
get isSilentClose() {
this.#assertNotClosed();
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_SILENT_CLOSE);
}
/** @type {boolean} */
get isStatelessReset() {
this.#assertNotClosed();
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_STATELESS_RESET);
}
/** @type {boolean} */
get isDestroyed() {
this.#assertNotClosed();
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_DESTROYED);
}
/** @type {boolean} */
get isHandshakeCompleted() {
this.#assertNotClosed();
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_HANDSHAKE_COMPLETED);
}
/** @type {boolean} */
get isHandshakeConfirmed() {
this.#assertNotClosed();
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_HANDSHAKE_CONFIRMED);
}
/** @type {boolean} */
get isStreamOpenAllowed() {
this.#assertNotClosed();
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_STREAM_OPEN_ALLOWED);
}
/** @type {boolean} */
get isPrioritySupported() {
this.#assertNotClosed();
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_PRIORITY_SUPPORTED);
}
/** @type {boolean} */
get isWrapped() {
this.#assertNotClosed();
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_WRAPPED);
}
/** @type {bigint} */
get lastDatagramId() {
this.#assertNotClosed();
return DataViewPrototypeGetBigUint64(this.#handle, IDX_STATE_SESSION_LAST_DATAGRAM_ID);
}
toString() {
return JSONStringify(this.toJSON());
}
toJSON() {
if (this.#handle.byteLength === 0) return {};
return {
__proto__: null,
hasPathValidationListener: this.hasPathValidationListener,
hasVersionNegotiationListener: this.hasVersionNegotiationListener,
hasDatagramListener: this.hasDatagramListener,
hasSessionTicketListener: this.hasSessionTicketListener,
isClosing: this.isClosing,
isGracefulClose: this.isGracefulClose,
isSilentClose: this.isSilentClose,
isStatelessReset: this.isStatelessReset,
isDestroyed: this.isDestroyed,
isHandshakeCompleted: this.isHandshakeCompleted,
isHandshakeConfirmed: this.isHandshakeConfirmed,
isStreamOpenAllowed: this.isStreamOpenAllowed,
isPrioritySupported: this.isPrioritySupported,
isWrapped: this.isWrapped,
lastDatagramId: `${this.lastDatagramId}`,
};
}
[kInspect](depth, options) {
if (depth < 0)
return this;
if (this.#handle.byteLength === 0) {
return 'QuicSessionState { <Closed> }';
}
const opts = {
...options,
depth: options.depth == null ? null : options.depth - 1,
};
return `QuicSessionState ${inspect({
hasPathValidationListener: this.hasPathValidationListener,
hasVersionNegotiationListener: this.hasVersionNegotiationListener,
hasDatagramListener: this.hasDatagramListener,
hasSessionTicketListener: this.hasSessionTicketListener,
isClosing: this.isClosing,
isGracefulClose: this.isGracefulClose,
isSilentClose: this.isSilentClose,
isStatelessReset: this.isStatelessReset,
isDestroyed: this.isDestroyed,
isHandshakeCompleted: this.isHandshakeCompleted,
isHandshakeConfirmed: this.isHandshakeConfirmed,
isStreamOpenAllowed: this.isStreamOpenAllowed,
isPrioritySupported: this.isPrioritySupported,
isWrapped: this.isWrapped,
lastDatagramId: this.lastDatagramId,
}, opts)}`;
}
[kFinishClose]() {
// Snapshot the state into a new DataView since the underlying
// buffer will be destroyed.
if (this.#handle.byteLength === 0) return;
this.#handle = new DataView(new ArrayBuffer(0));
}
}
class QuicStreamState {
/** @type {DataView} */
#handle;
/**
* @param {symbol} privateSymbol
* @param {ArrayBuffer} buffer
*/
constructor(privateSymbol, buffer) {
if (privateSymbol !== kPrivateConstructor) {
throw new ERR_ILLEGAL_CONSTRUCTOR();
}
if (!isArrayBuffer(buffer)) {
throw new ERR_INVALID_ARG_TYPE('buffer', ['ArrayBuffer'], buffer);
}
this.#handle = new DataView(buffer);
}
/** @type {bigint} */
get id() {
return DataViewPrototypeGetBigInt64(this.#handle, IDX_STATE_STREAM_ID);
}
/** @type {boolean} */
get finSent() {
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_STREAM_FIN_SENT);
}
/** @type {boolean} */
get finReceived() {
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_STREAM_FIN_RECEIVED);
}
/** @type {boolean} */
get readEnded() {
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_STREAM_READ_ENDED);
}
/** @type {boolean} */
get writeEnded() {
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_STREAM_WRITE_ENDED);
}
/** @type {boolean} */
get destroyed() {
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_STREAM_DESTROYED);
}
/** @type {boolean} */
get paused() {
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_STREAM_PAUSED);
}
/** @type {boolean} */
get reset() {
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_STREAM_RESET);
}
/** @type {boolean} */
get hasReader() {
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_STREAM_HAS_READER);
}
/** @type {boolean} */
get wantsBlock() {
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_STREAM_WANTS_BLOCK);
}
/** @type {boolean} */
set wantsBlock(val) {
DataViewPrototypeSetUint8(this.#handle, IDX_STATE_STREAM_WANTS_BLOCK, val ? 1 : 0);
}
/** @type {boolean} */
get wantsHeaders() {
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_STREAM_WANTS_HEADERS);
}
/** @type {boolean} */
set wantsHeaders(val) {
DataViewPrototypeSetUint8(this.#handle, IDX_STATE_STREAM_WANTS_HEADERS, val ? 1 : 0);
}
/** @type {boolean} */
get wantsReset() {
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_STREAM_WANTS_RESET);
}
/** @type {boolean} */
set wantsReset(val) {
DataViewPrototypeSetUint8(this.#handle, IDX_STATE_STREAM_WANTS_RESET, val ? 1 : 0);
}
/** @type {boolean} */
get wantsTrailers() {
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_STREAM_WANTS_TRAILERS);
}
/** @type {boolean} */
set wantsTrailers(val) {
DataViewPrototypeSetUint8(this.#handle, IDX_STATE_STREAM_WANTS_TRAILERS, val ? 1 : 0);
}
toString() {
return JSONStringify(this.toJSON());
}
toJSON() {
if (this.#handle.byteLength === 0) return {};
return {
__proto__: null,
id: `${this.id}`,
finSent: this.finSent,
finReceived: this.finReceived,
readEnded: this.readEnded,
writeEnded: this.writeEnded,
destroyed: this.destroyed,
paused: this.paused,
reset: this.reset,
hasReader: this.hasReader,
wantsBlock: this.wantsBlock,
wantsHeaders: this.wantsHeaders,
wantsReset: this.wantsReset,
wantsTrailers: this.wantsTrailers,
};
}
[kInspect](depth, options) {
if (depth < 0)
return this;
if (this.#handle.byteLength === 0) {
return 'QuicStreamState { <Closed> }';
}
const opts = {
...options,
depth: options.depth == null ? null : options.depth - 1,
};
return `QuicStreamState ${inspect({
id: this.id,
finSent: this.finSent,
finReceived: this.finReceived,
readEnded: this.readEnded,
writeEnded: this.writeEnded,
destroyed: this.destroyed,
paused: this.paused,
reset: this.reset,
hasReader: this.hasReader,
wantsBlock: this.wantsBlock,
wantsHeaders: this.wantsHeaders,
wantsReset: this.wantsReset,
wantsTrailers: this.wantsTrailers,
}, opts)}`;
}
[kFinishClose]() {
// Snapshot the state into a new DataView since the underlying
// buffer will be destroyed.
if (this.#handle.byteLength === 0) return;
this.#handle = new DataView(new ArrayBuffer(0));
}
}
module.exports = {
QuicEndpointState,
QuicSessionState,
QuicStreamState,
};

646
lib/internal/quic/stats.js Normal file
View File

@ -0,0 +1,646 @@
'use strict';
const {
BigUint64Array,
JSONStringify,
} = primordials;
const {
isArrayBuffer,
} = require('util/types');
const {
codes: {
ERR_ILLEGAL_CONSTRUCTOR,
ERR_INVALID_ARG_TYPE,
},
} = require('internal/errors');
const { inspect } = require('internal/util/inspect');
const {
kFinishClose,
kInspect,
kPrivateConstructor,
} = require('internal/quic/symbols');
// This file defines the helper objects for accessing statistics collected
// by various QUIC objects. Each of these wraps a BigUint64Array. Every
// stats object is read-only via the API, and the underlying buffer is
// only updated by the QUIC internals. When the stats object is no longer
// needed, it is closed to prevent further updates to the buffer.
const {
// All of the IDX_STATS_* constants are the index positions of the stats
// fields in the relevant BigUint64Array's that underlie the *Stats objects.
// These are not exposed to end users.
IDX_STATS_ENDPOINT_CREATED_AT,
IDX_STATS_ENDPOINT_DESTROYED_AT,
IDX_STATS_ENDPOINT_BYTES_RECEIVED,
IDX_STATS_ENDPOINT_BYTES_SENT,
IDX_STATS_ENDPOINT_PACKETS_RECEIVED,
IDX_STATS_ENDPOINT_PACKETS_SENT,
IDX_STATS_ENDPOINT_SERVER_SESSIONS,
IDX_STATS_ENDPOINT_CLIENT_SESSIONS,
IDX_STATS_ENDPOINT_SERVER_BUSY_COUNT,
IDX_STATS_ENDPOINT_RETRY_COUNT,
IDX_STATS_ENDPOINT_VERSION_NEGOTIATION_COUNT,
IDX_STATS_ENDPOINT_STATELESS_RESET_COUNT,
IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_COUNT,
IDX_STATS_SESSION_CREATED_AT,
IDX_STATS_SESSION_CLOSING_AT,
IDX_STATS_SESSION_DESTROYED_AT,
IDX_STATS_SESSION_HANDSHAKE_COMPLETED_AT,
IDX_STATS_SESSION_HANDSHAKE_CONFIRMED_AT,
IDX_STATS_SESSION_GRACEFUL_CLOSING_AT,
IDX_STATS_SESSION_BYTES_RECEIVED,
IDX_STATS_SESSION_BYTES_SENT,
IDX_STATS_SESSION_BIDI_IN_STREAM_COUNT,
IDX_STATS_SESSION_BIDI_OUT_STREAM_COUNT,
IDX_STATS_SESSION_UNI_IN_STREAM_COUNT,
IDX_STATS_SESSION_UNI_OUT_STREAM_COUNT,
IDX_STATS_SESSION_LOSS_RETRANSMIT_COUNT,
IDX_STATS_SESSION_MAX_BYTES_IN_FLIGHT,
IDX_STATS_SESSION_BYTES_IN_FLIGHT,
IDX_STATS_SESSION_BLOCK_COUNT,
IDX_STATS_SESSION_CWND,
IDX_STATS_SESSION_LATEST_RTT,
IDX_STATS_SESSION_MIN_RTT,
IDX_STATS_SESSION_RTTVAR,
IDX_STATS_SESSION_SMOOTHED_RTT,
IDX_STATS_SESSION_SSTHRESH,
IDX_STATS_SESSION_DATAGRAMS_RECEIVED,
IDX_STATS_SESSION_DATAGRAMS_SENT,
IDX_STATS_SESSION_DATAGRAMS_ACKNOWLEDGED,
IDX_STATS_SESSION_DATAGRAMS_LOST,
IDX_STATS_STREAM_CREATED_AT,
IDX_STATS_STREAM_RECEIVED_AT,
IDX_STATS_STREAM_ACKED_AT,
IDX_STATS_STREAM_CLOSING_AT,
IDX_STATS_STREAM_DESTROYED_AT,
IDX_STATS_STREAM_BYTES_RECEIVED,
IDX_STATS_STREAM_BYTES_SENT,
IDX_STATS_STREAM_MAX_OFFSET,
IDX_STATS_STREAM_MAX_OFFSET_ACK,
IDX_STATS_STREAM_MAX_OFFSET_RECV,
IDX_STATS_STREAM_FINAL_SIZE,
} = internalBinding('quic');
class QuicEndpointStats {
/** @type {BigUint64Array} */
#handle;
/** @type {boolean} */
#disconnected = false;
/**
* @param {symbol} privateSymbol
* @param {ArrayBuffer} buffer
*/
constructor(privateSymbol, buffer) {
// We use the kPrivateConstructor symbol to restrict the ability to
// create new instances of QuicEndpointStats to internal code.
if (privateSymbol !== kPrivateConstructor) {
throw new ERR_ILLEGAL_CONSTRUCTOR();
}
if (!isArrayBuffer(buffer)) {
throw new ERR_INVALID_ARG_TYPE('buffer', ['ArrayBuffer'], buffer);
}
this.#handle = new BigUint64Array(buffer);
}
/** @type {bigint} */
get createdAt() {
return this.#handle[IDX_STATS_ENDPOINT_CREATED_AT];
}
/** @type {bigint} */
get destroyedAt() {
return this.#handle[IDX_STATS_ENDPOINT_DESTROYED_AT];
}
/** @type {bigint} */
get bytesReceived() {
return this.#handle[IDX_STATS_ENDPOINT_BYTES_RECEIVED];
}
/** @type {bigint} */
get bytesSent() {
return this.#handle[IDX_STATS_ENDPOINT_BYTES_SENT];
}
/** @type {bigint} */
get packetsReceived() {
return this.#handle[IDX_STATS_ENDPOINT_PACKETS_RECEIVED];
}
/** @type {bigint} */
get packetsSent() {
return this.#handle[IDX_STATS_ENDPOINT_PACKETS_SENT];
}
/** @type {bigint} */
get serverSessions() {
return this.#handle[IDX_STATS_ENDPOINT_SERVER_SESSIONS];
}
/** @type {bigint} */
get clientSessions() {
return this.#handle[IDX_STATS_ENDPOINT_CLIENT_SESSIONS];
}
/** @type {bigint} */
get serverBusyCount() {
return this.#handle[IDX_STATS_ENDPOINT_SERVER_BUSY_COUNT];
}
/** @type {bigint} */
get retryCount() {
return this.#handle[IDX_STATS_ENDPOINT_RETRY_COUNT];
}
/** @type {bigint} */
get versionNegotiationCount() {
return this.#handle[IDX_STATS_ENDPOINT_VERSION_NEGOTIATION_COUNT];
}
/** @type {bigint} */
get statelessResetCount() {
return this.#handle[IDX_STATS_ENDPOINT_STATELESS_RESET_COUNT];
}
/** @type {bigint} */
get immediateCloseCount() {
return this.#handle[IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_COUNT];
}
toString() {
return JSONStringify(this.toJSON());
}
toJSON() {
return {
__proto__: null,
connected: this.isConnected,
// We need to convert the values to strings because JSON does not
// support BigInts.
createdAt: `${this.createdAt}`,
destroyedAt: `${this.destroyedAt}`,
bytesReceived: `${this.bytesReceived}`,
bytesSent: `${this.bytesSent}`,
packetsReceived: `${this.packetsReceived}`,
packetsSent: `${this.packetsSent}`,
serverSessions: `${this.serverSessions}`,
clientSessions: `${this.clientSessions}`,
serverBusyCount: `${this.serverBusyCount}`,
retryCount: `${this.retryCount}`,
versionNegotiationCount: `${this.versionNegotiationCount}`,
statelessResetCount: `${this.statelessResetCount}`,
immediateCloseCount: `${this.immediateCloseCount}`,
};
}
[kInspect](depth, options) {
if (depth < 0)
return this;
const opts = {
...options,
depth: options.depth == null ? null : options.depth - 1,
};
return `QuicEndpointStats ${inspect({
connected: this.isConnected,
createdAt: this.createdAt,
destroyedAt: this.destroyedAt,
bytesReceived: this.bytesReceived,
bytesSent: this.bytesSent,
packetsReceived: this.packetsReceived,
packetsSent: this.packetsSent,
serverSessions: this.serverSessions,
clientSessions: this.clientSessions,
serverBusyCount: this.serverBusyCount,
retryCount: this.retryCount,
versionNegotiationCount: this.versionNegotiationCount,
statelessResetCount: this.statelessResetCount,
immediateCloseCount: this.immediateCloseCount,
}, opts)}`;
}
/**
* True if this QuicEndpointStats object is still connected to the underlying
* Endpoint stats source. If this returns false, then the stats object is
* no longer being updated and should be considered stale.
* @returns {boolean}
*/
get isConnected() {
return !this.#disconnected;
}
[kFinishClose]() {
// Snapshot the stats into a new BigUint64Array since the underlying
// buffer will be destroyed.
this.#handle = new BigUint64Array(this.#handle);
this.#disconnected = true;
}
}
class QuicSessionStats {
/** @type {BigUint64Array} */
#handle;
/** @type {boolean} */
#disconnected = false;
/**
* @param {symbol} privateSynbol
* @param {BigUint64Array} buffer
*/
constructor(privateSynbol, buffer) {
// We use the kPrivateConstructor symbol to restrict the ability to
// create new instances of QuicSessionStats to internal code.
if (privateSynbol !== kPrivateConstructor) {
throw new ERR_ILLEGAL_CONSTRUCTOR();
}
if (!isArrayBuffer(buffer)) {
throw new ERR_INVALID_ARG_TYPE('buffer', ['ArrayBuffer'], buffer);
}
this.#handle = new BigUint64Array(buffer);
}
/** @type {bigint} */
get createdAt() {
return this.#handle[IDX_STATS_SESSION_CREATED_AT];
}
/** @type {bigint} */
get closingAt() {
return this.#handle[IDX_STATS_SESSION_CLOSING_AT];
}
/** @type {bigint} */
get destroyedAt() {
return this.#handle[IDX_STATS_SESSION_DESTROYED_AT];
}
/** @type {bigint} */
get handshakeCompletedAt() {
return this.#handle[IDX_STATS_SESSION_HANDSHAKE_COMPLETED_AT];
}
/** @type {bigint} */
get handshakeConfirmedAt() {
return this.#handle[IDX_STATS_SESSION_HANDSHAKE_CONFIRMED_AT];
}
/** @type {bigint} */
get gracefulClosingAt() {
return this.#handle[IDX_STATS_SESSION_GRACEFUL_CLOSING_AT];
}
/** @type {bigint} */
get bytesReceived() {
return this.#handle[IDX_STATS_SESSION_BYTES_RECEIVED];
}
/** @type {bigint} */
get bytesSent() {
return this.#handle[IDX_STATS_SESSION_BYTES_SENT];
}
/** @type {bigint} */
get bidiInStreamCount() {
return this.#handle[IDX_STATS_SESSION_BIDI_IN_STREAM_COUNT];
}
/** @type {bigint} */
get bidiOutStreamCount() {
return this.#handle[IDX_STATS_SESSION_BIDI_OUT_STREAM_COUNT];
}
/** @type {bigint} */
get uniInStreamCount() {
return this.#handle[IDX_STATS_SESSION_UNI_IN_STREAM_COUNT];
}
/** @type {bigint} */
get uniOutStreamCount() {
return this.#handle[IDX_STATS_SESSION_UNI_OUT_STREAM_COUNT];
}
/** @type {bigint} */
get lossRetransmitCount() {
return this.#handle[IDX_STATS_SESSION_LOSS_RETRANSMIT_COUNT];
}
/** @type {bigint} */
get maxBytesInFlights() {
return this.#handle[IDX_STATS_SESSION_MAX_BYTES_IN_FLIGHT];
}
/** @type {bigint} */
get bytesInFlight() {
return this.#handle[IDX_STATS_SESSION_BYTES_IN_FLIGHT];
}
/** @type {bigint} */
get blockCount() {
return this.#handle[IDX_STATS_SESSION_BLOCK_COUNT];
}
/** @type {bigint} */
get cwnd() {
return this.#handle[IDX_STATS_SESSION_CWND];
}
/** @type {bigint} */
get latestRtt() {
return this.#handle[IDX_STATS_SESSION_LATEST_RTT];
}
/** @type {bigint} */
get minRtt() {
return this.#handle[IDX_STATS_SESSION_MIN_RTT];
}
/** @type {bigint} */
get rttVar() {
return this.#handle[IDX_STATS_SESSION_RTTVAR];
}
/** @type {bigint} */
get smoothedRtt() {
return this.#handle[IDX_STATS_SESSION_SMOOTHED_RTT];
}
/** @type {bigint} */
get ssthresh() {
return this.#handle[IDX_STATS_SESSION_SSTHRESH];
}
/** @type {bigint} */
get datagramsReceived() {
return this.#handle[IDX_STATS_SESSION_DATAGRAMS_RECEIVED];
}
/** @type {bigint} */
get datagramsSent() {
return this.#handle[IDX_STATS_SESSION_DATAGRAMS_SENT];
}
/** @type {bigint} */
get datagramsAcknowledged() {
return this.#handle[IDX_STATS_SESSION_DATAGRAMS_ACKNOWLEDGED];
}
/** @type {bigint} */
get datagramsLost() {
return this.#handle[IDX_STATS_SESSION_DATAGRAMS_LOST];
}
toString() {
return JSONStringify(this.toJSON());
}
toJSON() {
return {
__proto__: null,
connected: this.isConnected,
// We need to convert the values to strings because JSON does not
// support BigInts.
createdAt: `${this.createdAt}`,
closingAt: `${this.closingAt}`,
destroyedAt: `${this.destroyedAt}`,
handshakeCompletedAt: `${this.handshakeCompletedAt}`,
handshakeConfirmedAt: `${this.handshakeConfirmedAt}`,
gracefulClosingAt: `${this.gracefulClosingAt}`,
bytesReceived: `${this.bytesReceived}`,
bytesSent: `${this.bytesSent}`,
bidiInStreamCount: `${this.bidiInStreamCount}`,
bidiOutStreamCount: `${this.bidiOutStreamCount}`,
uniInStreamCount: `${this.uniInStreamCount}`,
uniOutStreamCount: `${this.uniOutStreamCount}`,
lossRetransmitCount: `${this.lossRetransmitCount}`,
maxBytesInFlights: `${this.maxBytesInFlights}`,
bytesInFlight: `${this.bytesInFlight}`,
blockCount: `${this.blockCount}`,
cwnd: `${this.cwnd}`,
latestRtt: `${this.latestRtt}`,
minRtt: `${this.minRtt}`,
rttVar: `${this.rttVar}`,
smoothedRtt: `${this.smoothedRtt}`,
ssthresh: `${this.ssthresh}`,
datagramsReceived: `${this.datagramsReceived}`,
datagramsSent: `${this.datagramsSent}`,
datagramsAcknowledged: `${this.datagramsAcknowledged}`,
datagramsLost: `${this.datagramsLost}`,
};
}
[kInspect](depth, options) {
if (depth < 0)
return this;
const opts = {
...options,
depth: options.depth == null ? null : options.depth - 1,
};
return `QuicSessionStats ${inspect({
connected: this.isConnected,
createdAt: this.createdAt,
closingAt: this.closingAt,
destroyedAt: this.destroyedAt,
handshakeCompletedAt: this.handshakeCompletedAt,
handshakeConfirmedAt: this.handshakeConfirmedAt,
gracefulClosingAt: this.gracefulClosingAt,
bytesReceived: this.bytesReceived,
bytesSent: this.bytesSent,
bidiInStreamCount: this.bidiInStreamCount,
bidiOutStreamCount: this.bidiOutStreamCount,
uniInStreamCount: this.uniInStreamCount,
uniOutStreamCount: this.uniOutStreamCount,
lossRetransmitCount: this.lossRetransmitCount,
maxBytesInFlights: this.maxBytesInFlights,
bytesInFlight: this.bytesInFlight,
blockCount: this.blockCount,
cwnd: this.cwnd,
latestRtt: this.latestRtt,
minRtt: this.minRtt,
rttVar: this.rttVar,
smoothedRtt: this.smoothedRtt,
ssthresh: this.ssthresh,
datagramsReceived: this.datagramsReceived,
datagramsSent: this.datagramsSent,
datagramsAcknowledged: this.datagramsAcknowledged,
datagramsLost: this.datagramsLost,
}, opts)}`;
}
/**
* True if this QuicSessionStats object is still connected to the underlying
* Session stats source. If this returns false, then the stats object is
* no longer being updated and should be considered stale.
* @returns {boolean}
*/
get isConnected() {
return !this.#disconnected;
}
[kFinishClose]() {
// Snapshot the stats into a new BigUint64Array since the underlying
// buffer will be destroyed.
this.#handle = new BigUint64Array(this.#handle);
this.#disconnected = true;
}
}
class QuicStreamStats {
/** @type {BigUint64Array} */
#handle;
/** type {boolean} */
#disconnected = false;
/**
* @param {symbol} privateSymbol
* @param {ArrayBuffer} buffer
*/
constructor(privateSymbol, buffer) {
// We use the kPrivateConstructor symbol to restrict the ability to
// create new instances of QuicStreamStats to internal code.
if (privateSymbol !== kPrivateConstructor) {
throw new ERR_ILLEGAL_CONSTRUCTOR();
}
if (!isArrayBuffer(buffer)) {
throw new ERR_INVALID_ARG_TYPE('buffer', ['ArrayBuffer'], buffer);
}
this.#handle = new BigUint64Array(buffer);
}
/** @type {bigint} */
get createdAt() {
return this.#handle[IDX_STATS_STREAM_CREATED_AT];
}
/** @type {bigint} */
get receivedAt() {
return this.#handle[IDX_STATS_STREAM_RECEIVED_AT];
}
/** @type {bigint} */
get ackedAt() {
return this.#handle[IDX_STATS_STREAM_ACKED_AT];
}
/** @type {bigint} */
get closingAt() {
return this.#handle[IDX_STATS_STREAM_CLOSING_AT];
}
/** @type {bigint} */
get destroyedAt() {
return this.#handle[IDX_STATS_STREAM_DESTROYED_AT];
}
/** @type {bigint} */
get bytesReceived() {
return this.#handle[IDX_STATS_STREAM_BYTES_RECEIVED];
}
/** @type {bigint} */
get bytesSent() {
return this.#handle[IDX_STATS_STREAM_BYTES_SENT];
}
/** @type {bigint} */
get maxOffset() {
return this.#handle[IDX_STATS_STREAM_MAX_OFFSET];
}
/** @type {bigint} */
get maxOffsetAcknowledged() {
return this.#handle[IDX_STATS_STREAM_MAX_OFFSET_ACK];
}
/** @type {bigint} */
get maxOffsetReceived() {
return this.#handle[IDX_STATS_STREAM_MAX_OFFSET_RECV];
}
/** @type {bigint} */
get finalSize() {
return this.#handle[IDX_STATS_STREAM_FINAL_SIZE];
}
toString() {
return JSONStringify(this.toJSON());
}
toJSON() {
return {
__proto__: null,
connected: this.isConnected,
// We need to convert the values to strings because JSON does not
// support BigInts.
createdAt: `${this.createdAt}`,
receivedAt: `${this.receivedAt}`,
ackedAt: `${this.ackedAt}`,
closingAt: `${this.closingAt}`,
destroyedAt: `${this.destroyedAt}`,
bytesReceived: `${this.bytesReceived}`,
bytesSent: `${this.bytesSent}`,
maxOffset: `${this.maxOffset}`,
maxOffsetAcknowledged: `${this.maxOffsetAcknowledged}`,
maxOffsetReceived: `${this.maxOffsetReceived}`,
finalSize: `${this.finalSize}`,
};
}
[kInspect](depth, options) {
if (depth < 0)
return this;
const opts = {
...options,
depth: options.depth == null ? null : options.depth - 1,
};
return `StreamStats ${inspect({
connected: this.isConnected,
createdAt: this.createdAt,
receivedAt: this.receivedAt,
ackedAt: this.ackedAt,
closingAt: this.closingAt,
destroyedAt: this.destroyedAt,
bytesReceived: this.bytesReceived,
bytesSent: this.bytesSent,
maxOffset: this.maxOffset,
maxOffsetAcknowledged: this.maxOffsetAcknowledged,
maxOffsetReceived: this.maxOffsetReceived,
finalSize: this.finalSize,
}, opts)}`;
}
/**
* True if this QuicStreamStats object is still connected to the underlying
* Stream stats source. If this returns false, then the stats object is
* no longer being updated and should be considered stale.
* @returns {boolean}
*/
get isConnected() {
return !this.#disconnected;
}
[kFinishClose]() {
// Snapshot the stats into a new BigUint64Array since the underlying
// buffer will be destroyed.
this.#handle = new BigUint64Array(this.#handle);
this.#disconnected = true;
}
}
module.exports = {
QuicEndpointStats,
QuicSessionStats,
QuicStreamStats,
};

View File

@ -0,0 +1,56 @@
'use strict';
const {
Symbol,
} = primordials;
const {
customInspectSymbol: kInspect,
} = require('internal/util');
const {
kHandle: kKeyObjectHandle,
kKeyObject: kKeyObjectInner,
} = require('internal/crypto/util');
// Symbols used to hide various private properties and methods from the
// public API.
const kBlocked = Symbol('kBlocked');
const kDatagram = Symbol('kDatagram');
const kDatagramStatus = Symbol('kDatagramStatus');
const kError = Symbol('kError');
const kFinishClose = Symbol('kFinishClose');
const kHandshake = Symbol('kHandshake');
const kHeaders = Symbol('kHeaders');
const kOwner = Symbol('kOwner');
const kNewSession = Symbol('kNewSession');
const kNewStream = Symbol('kNewStream');
const kPathValidation = Symbol('kPathValidation');
const kReset = Symbol('kReset');
const kSessionTicket = Symbol('kSessionTicket');
const kTrailers = Symbol('kTrailers');
const kVersionNegotiation = Symbol('kVersionNegotiation');
const kPrivateConstructor = Symbol('kPrivateConstructor');
module.exports = {
kBlocked,
kDatagram,
kDatagramStatus,
kError,
kFinishClose,
kHandshake,
kHeaders,
kOwner,
kNewSession,
kNewStream,
kPathValidation,
kReset,
kSessionTicket,
kTrailers,
kVersionNegotiation,
kInspect,
kKeyObjectHandle,
kKeyObjectInner,
kPrivateConstructor,
};

View File

@ -133,7 +133,8 @@ BuiltinLoader::BuiltinCategories BuiltinLoader::GetBuiltinCategories() const {
"internal/streams/lazy_transform",
#endif // !HAVE_OPENSSL
#if !NODE_OPENSSL_HAS_QUIC
"internal/quic/quic",
"internal/quic/quic", "internal/quic/symbols", "internal/quic/stats",
"internal/quic/state",
#endif // !NODE_OPENSSL_HAS_QUIC
"sqlite", // Experimental.
"sys", // Deprecated.

View File

@ -20,11 +20,11 @@ describe('quic internal endpoint listen defaults', { skip: !hasQuic }, async ()
} = require('net');
const {
Endpoint,
QuicEndpoint,
} = require('internal/quic/quic');
it('are reasonable and work as expected', async () => {
const endpoint = new Endpoint({
const endpoint = new QuicEndpoint({
onsession() {},
session: {},
stream: {},

View File

@ -15,7 +15,7 @@ describe('quic internal endpoint options', { skip: !hasQuic }, async () => {
} = require('node:assert');
const {
Endpoint,
QuicEndpoint,
} = require('internal/quic/quic');
const {
@ -30,7 +30,7 @@ describe('quic internal endpoint options', { skip: !hasQuic }, async () => {
it('invalid options', async () => {
['a', null, false, NaN].forEach((i) => {
throws(() => new Endpoint(callbackConfig, i), {
throws(() => new QuicEndpoint(callbackConfig, i), {
code: 'ERR_INVALID_ARG_TYPE',
});
});
@ -38,9 +38,9 @@ describe('quic internal endpoint options', { skip: !hasQuic }, async () => {
it('valid options', async () => {
// Just Works... using all defaults
new Endpoint(callbackConfig, {});
new Endpoint(callbackConfig);
new Endpoint(callbackConfig, undefined);
new QuicEndpoint(callbackConfig, {});
new QuicEndpoint(callbackConfig);
new QuicEndpoint(callbackConfig, undefined);
});
it('various cases', async () => {
@ -126,12 +126,12 @@ describe('quic internal endpoint options', { skip: !hasQuic }, async () => {
{
key: 'cc',
valid: [
Endpoint.CC_ALGO_RENO,
Endpoint.CC_ALGO_CUBIC,
Endpoint.CC_ALGO_BBR,
Endpoint.CC_ALGO_RENO_STR,
Endpoint.CC_ALGO_CUBIC_STR,
Endpoint.CC_ALGO_BBR_STR,
QuicEndpoint.CC_ALGO_RENO,
QuicEndpoint.CC_ALGO_CUBIC,
QuicEndpoint.CC_ALGO_BBR,
QuicEndpoint.CC_ALGO_RENO_STR,
QuicEndpoint.CC_ALGO_CUBIC_STR,
QuicEndpoint.CC_ALGO_BBR_STR,
],
invalid: [-1, 4, 1n, 'a', null, false, true, {}, [], () => {}],
},
@ -190,13 +190,13 @@ describe('quic internal endpoint options', { skip: !hasQuic }, async () => {
for (const value of valid) {
const options = {};
options[key] = value;
new Endpoint(callbackConfig, options);
new QuicEndpoint(callbackConfig, options);
}
for (const value of invalid) {
const options = {};
options[key] = value;
throws(() => new Endpoint(callbackConfig, options), {
throws(() => new QuicEndpoint(callbackConfig, options), {
code: 'ERR_INVALID_ARG_VALUE',
});
}
@ -204,7 +204,7 @@ describe('quic internal endpoint options', { skip: !hasQuic }, async () => {
});
it('endpoint can be ref/unrefed without error', async () => {
const endpoint = new Endpoint(callbackConfig, {});
const endpoint = new QuicEndpoint(callbackConfig, {});
endpoint.unref();
endpoint.ref();
endpoint.close();
@ -212,17 +212,17 @@ describe('quic internal endpoint options', { skip: !hasQuic }, async () => {
});
it('endpoint can be inspected', async () => {
const endpoint = new Endpoint(callbackConfig, {});
const endpoint = new QuicEndpoint(callbackConfig, {});
strictEqual(typeof inspect(endpoint), 'string');
endpoint.close();
await endpoint.closed;
});
it('endpoint with object address', () => {
new Endpoint(callbackConfig, {
new QuicEndpoint(callbackConfig, {
address: { host: '127.0.0.1:0' },
});
throws(() => new Endpoint(callbackConfig, { address: '127.0.0.1:0' }), {
throws(() => new QuicEndpoint(callbackConfig, { address: '127.0.0.1:0' }), {
code: 'ERR_INVALID_ARG_TYPE',
});
});

View File

@ -10,14 +10,18 @@ const {
describe('quic internal endpoint stats and state', { skip: !hasQuic }, () => {
const {
Endpoint,
QuicEndpoint,
QuicStreamState,
QuicStreamStats,
SessionState,
SessionStats,
kFinishClose,
QuicSessionState,
QuicSessionStats,
} = require('internal/quic/quic');
const {
kFinishClose,
kPrivateConstructor,
} = require('internal/quic/symbols');
const {
inspect,
} = require('util');
@ -29,7 +33,7 @@ describe('quic internal endpoint stats and state', { skip: !hasQuic }, () => {
} = require('node:assert');
it('endpoint state', () => {
const endpoint = new Endpoint({
const endpoint = new QuicEndpoint({
onsession() {},
session: {},
stream: {},
@ -62,7 +66,7 @@ describe('quic internal endpoint stats and state', { skip: !hasQuic }, () => {
});
it('state is not readable after close', () => {
const endpoint = new Endpoint({
const endpoint = new QuicEndpoint({
onsession() {},
session: {},
stream: {},
@ -74,24 +78,25 @@ describe('quic internal endpoint stats and state', { skip: !hasQuic }, () => {
});
it('state constructor argument is ArrayBuffer', () => {
const endpoint = new Endpoint({
const endpoint = new QuicEndpoint({
onsession() {},
session: {},
stream: {},
}, {});
const Cons = endpoint.state.constructor;
throws(() => new Cons(1), {
throws(() => new Cons(kPrivateConstructor, 1), {
code: 'ERR_INVALID_ARG_TYPE'
});
});
it('endpoint stats', () => {
const endpoint = new Endpoint({
const endpoint = new QuicEndpoint({
onsession() {},
session: {},
stream: {},
});
strictEqual(typeof endpoint.stats.isConnected, 'boolean');
strictEqual(typeof endpoint.stats.createdAt, 'bigint');
strictEqual(typeof endpoint.stats.destroyedAt, 'bigint');
strictEqual(typeof endpoint.stats.bytesReceived, 'bigint');
@ -107,6 +112,7 @@ describe('quic internal endpoint stats and state', { skip: !hasQuic }, () => {
strictEqual(typeof endpoint.stats.immediateCloseCount, 'bigint');
deepStrictEqual(Object.keys(endpoint.stats.toJSON()), [
'connected',
'createdAt',
'destroyedAt',
'bytesReceived',
@ -128,25 +134,26 @@ describe('quic internal endpoint stats and state', { skip: !hasQuic }, () => {
});
it('stats are still readble after close', () => {
const endpoint = new Endpoint({
const endpoint = new QuicEndpoint({
onsession() {},
session: {},
stream: {},
}, {});
strictEqual(typeof endpoint.stats.toJSON(), 'object');
endpoint.stats[kFinishClose]();
strictEqual(endpoint.stats.isConnected, false);
strictEqual(typeof endpoint.stats.destroyedAt, 'bigint');
strictEqual(typeof endpoint.stats.toJSON(), 'object');
});
it('stats constructor argument is ArrayBuffer', () => {
const endpoint = new Endpoint({
const endpoint = new QuicEndpoint({
onsession() {},
session: {},
stream: {},
}, {});
const Cons = endpoint.stats.constructor;
throws(() => new Cons(1), {
throws(() => new Cons(kPrivateConstructor, 1), {
code: 'ERR_INVALID_ARG_TYPE',
});
});
@ -156,8 +163,8 @@ describe('quic internal endpoint stats and state', { skip: !hasQuic }, () => {
// temporarily while the rest of the functionality is being
// implemented.
it('stream and session states', () => {
const streamState = new QuicStreamState(new ArrayBuffer(1024));
const sessionState = new SessionState(new ArrayBuffer(1024));
const streamState = new QuicStreamState(kPrivateConstructor, new ArrayBuffer(1024));
const sessionState = new QuicSessionState(kPrivateConstructor, new ArrayBuffer(1024));
strictEqual(streamState.finSent, false);
strictEqual(streamState.finReceived, false);
@ -195,8 +202,8 @@ describe('quic internal endpoint stats and state', { skip: !hasQuic }, () => {
});
it('stream and session stats', () => {
const streamStats = new QuicStreamStats(new ArrayBuffer(1024));
const sessionStats = new SessionStats(new ArrayBuffer(1024));
const streamStats = new QuicStreamStats(kPrivateConstructor, new ArrayBuffer(1024));
const sessionStats = new QuicSessionStats(kPrivateConstructor, new ArrayBuffer(1024));
strictEqual(streamStats.createdAt, undefined);
strictEqual(streamStats.receivedAt, undefined);
strictEqual(streamStats.ackedAt, undefined);