mirror of https://github.com/nodejs/node.git
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:
parent
baed2763df
commit
1fb30d6e86
File diff suppressed because it is too large
Load Diff
|
@ -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,
|
||||
};
|
|
@ -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,
|
||||
};
|
|
@ -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,
|
||||
};
|
|
@ -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.
|
||||
|
|
|
@ -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: {},
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue