diff --git a/lib/internal/quic/quic.js b/lib/internal/quic/quic.js index 0d5d0bbb56deb2..7e1ca81ffae63a 100644 --- a/lib/internal/quic/quic.js +++ b/lib/internal/quic/quic.js @@ -5,28 +5,21 @@ /* c8 ignore start */ const { - ArrayBuffer, ArrayIsArray, ArrayPrototypePush, - BigUint64Array, - DataView, - DataViewPrototypeGetBigInt64, - DataViewPrototypeGetBigUint64, - DataViewPrototypeGetUint8, - DataViewPrototypeSetUint8, ObjectDefineProperties, - Symbol, Uint8Array, } = primordials; +// QUIC requires that Node.js be compiled with crypto support. const { assertCrypto, - customInspectSymbol: kInspect, SymbolAsyncDispose, } = require('internal/util'); -const { inspect } = require('internal/util/inspect'); assertCrypto(); +const { inspect } = require('internal/util/inspect'); + const { Endpoint: Endpoint_, setCallbacks, @@ -54,99 +47,6 @@ const { CLOSECONTEXT_RECEIVE_FAILURE: kCloseContextReceiveFailure, CLOSECONTEXT_SEND_FAILURE: kCloseContextSendFailure, CLOSECONTEXT_START_FAILURE: kCloseContextStartFailure, - - // 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, - - 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'); const { @@ -180,34 +80,48 @@ const { isCryptoKey, } = require('internal/crypto/keys'); -const { - kHandle: kKeyObjectHandle, - kKeyObject: kKeyObjectInner, -} = require('internal/crypto/util'); - const { validateFunction, validateObject, + validateString, + validateBoolean, } = require('internal/validators'); const kEmptyObject = { __proto__: null }; -const kEnumerable = { __proto__: null, enumerable: true }; - -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 { + kBlocked, + kDatagram, + kDatagramStatus, + kError, + kFinishClose, + kHandshake, + kHeaders, + kOwner, + kNewSession, + kNewStream, + kPathValidation, + kReset, + kSessionTicket, + kTrailers, + kVersionNegotiation, + kInspect, + kKeyObjectHandle, + kKeyObjectInner, + kPrivateConstructor, +} = require('internal/quic/symbols'); + +const { + QuicEndpointStats, + QuicStreamStats, + QuicSessionStats, +} = require('internal/quic/stats'); + +const { + QuicEndpointState, + QuicSessionState, + QuicStreamState, +} = require('internal/quic/state'); /** * @typedef {import('../socketaddress.js').SocketAddress} SocketAddress @@ -317,22 +231,22 @@ const kVersionNegotiation = Symbol('kVersionNegotiation'); /** * Called when the Endpoint receives a new server-side Session. * @callback OnSessionCallback - * @param {Session} session - * @param {Endpoint} endpoint + * @param {QuicSession} session + * @param {QuicEndpoint} endpoint * @returns {void} */ /** * @callback OnStreamCallback * @param {QuicStream} stream - * @param {Session} session + * @param {QuicSession} session * @returns {void} */ /** * @callback OnDatagramCallback * @param {Uint8Array} datagram - * @param {Session} session + * @param {QuicSession} session * @param {boolean} early * @returns {void} */ @@ -341,7 +255,7 @@ const kVersionNegotiation = Symbol('kVersionNegotiation'); * @callback OnDatagramStatusCallback * @param {bigint} id * @param {'lost'|'acknowledged'} status - * @param {Session} session + * @param {QuicSession} session * @returns {void} */ @@ -353,14 +267,14 @@ const kVersionNegotiation = Symbol('kVersionNegotiation'); * @param {SocketAddress} oldLocalAddress * @param {SocketAddress} oldRemoteAddress * @param {boolean} preferredAddress - * @param {Session} session + * @param {QuicSession} session * @returns {void} */ /** * @callback OnSessionTicketCallback * @param {object} ticket - * @param {Session} session + * @param {QuicSession} session * @returns {void} */ @@ -369,7 +283,7 @@ const kVersionNegotiation = Symbol('kVersionNegotiation'); * @param {number} version * @param {number[]} requestedVersions * @param {number[]} supportedVersions - * @param {Session} session + * @param {QuicSession} session * @returns {void} */ @@ -382,7 +296,7 @@ const kVersionNegotiation = Symbol('kVersionNegotiation'); * @param {string} validationErrorReason * @param {number} validationErrorCode * @param {boolean} earlyDataAccepted - * @param {Session} session + * @param {QuicSession} session * @returns {void} */ @@ -526,18 +440,65 @@ function processTlsOptions(tls) { const { sni, alpn, - ciphers, - groups, - keylog, - verifyClient, - tlsTrace, - verifyPrivateKey, + ciphers = DEFAULT_CIPHERS, + groups = DEFAULT_GROUPS, + keylog = false, + verifyClient = false, + tlsTrace = false, + verifyPrivateKey = false, keys, certs, ca, crl, } = tls; + if (sni !== undefined) { + validateString(sni, 'options.tls.sni'); + } + if (alpn !== undefined) { + validateString(alpn, 'options.tls.alpn'); + } + if (ciphers !== undefined) { + validateString(ciphers, 'options.tls.ciphers'); + } + if (groups !== undefined) { + validateString(groups, 'options.tls.groups'); + } + validateBoolean(keylog, 'options.tls.keylog'); + validateBoolean(verifyClient, 'options.tls.verifyClient'); + validateBoolean(tlsTrace, 'options.tls.tlsTrace'); + validateBoolean(verifyPrivateKey, 'options.tls.verifyPrivateKey'); + + if (certs !== undefined) { + const certInputs = ArrayIsArray(certs) ? certs : [certs]; + for (const cert of certInputs) { + if (!isArrayBufferView(cert) && !isArrayBuffer(cert)) { + throw new ERR_INVALID_ARG_TYPE('options.tls.certs', + ['ArrayBufferView', 'ArrayBuffer'], cert); + } + } + } + + if (ca !== undefined) { + const caInputs = ArrayIsArray(ca) ? ca : [ca]; + for (const caCert of caInputs) { + if (!isArrayBufferView(caCert) && !isArrayBuffer(caCert)) { + throw new ERR_INVALID_ARG_TYPE('options.tls.ca', + ['ArrayBufferView', 'ArrayBuffer'], caCert); + } + } + } + + if (crl !== undefined) { + const crlInputs = ArrayIsArray(crl) ? crl : [crl]; + for (const crlCert of crlInputs) { + if (!isArrayBufferView(crlCert) && !isArrayBuffer(crlCert)) { + throw new ERR_INVALID_ARG_TYPE('options.tls.crl', + ['ArrayBufferView', 'ArrayBuffer'], crlCert); + } + } + } + const keyHandles = []; if (keys !== undefined) { const keyInputs = ArrayIsArray(keys) ? keys : [keys]; @@ -574,277 +535,10 @@ function processTlsOptions(tls) { }; } -class QuicStreamStats { - /** @type {BigUint64Array} */ - #handle; - - /** - * @param {ArrayBuffer} buffer - */ - constructor(buffer) { - 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]; - } - - toJSON() { - return { - __proto__: null, - 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({ - 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)}`; - } - - [kFinishClose]() { - // Snapshot the stats into a new BigUint64Array since the underlying - // buffer will be destroyed. - this.#handle = new BigUint64Array(this.#handle); - } -} - -class QuicStreamState { - /** @type {DataView} */ - #handle; - - /** - * @param {ArrayBuffer} buffer - */ - constructor(buffer) { - 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); - } - - toJSON() { - 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; - - const opts = { - ...options, - depth: options.depth == null ? null : options.depth - 1, - }; - - return `StreamState ${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]() { - // When the stream is destroyed, the state is no longer available. - this.#handle = new DataView(new ArrayBuffer(0)); - } -} - class QuicStream { /** @type {object} */ #handle; - /** @type {Session} */ + /** @type {QuicSession} */ #session; /** @type {QuicStreamStats} */ #stats; @@ -866,12 +560,12 @@ class QuicStream { /** * @param {StreamCallbackConfiguration} config * @param {object} handle - * @param {Session} session + * @param {QuicSession} session */ constructor(config, handle, session, direction) { validateObject(config, 'config'); - this.#stats = new QuicStreamStats(handle.stats); - this.#state = new QuicStreamState(handle.stats); + this.#stats = new QuicStreamStats(kPrivateConstructor, handle.stats); + this.#state = new QuicStreamState(kPrivateConstructor, handle.stats); const { onblocked, onerror, @@ -913,7 +607,7 @@ class QuicStream { /** @type {QuicStreamState} */ get state() { return this.#state; } - /** @type {Session} */ + /** @type {QuicSession} */ get session() { return this.#session; } /** @type {bigint} */ @@ -963,395 +657,8 @@ class QuicStream { } } -class SessionStats { - /** @type {BigUint64Array} */ - #handle; - - /** - * @param {BigUint64Array} buffer - */ - constructor(buffer) { - 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]; - } - - toJSON() { - return { - __proto__: null, - 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 `SessionStats ${inspect({ - 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)}`; - } - - [kFinishClose]() { - // Snapshot the stats into a new BigUint64Array since the underlying - // buffer will be destroyed. - this.#handle = new BigUint64Array(this.#handle); - } -} - -class SessionState { - /** @type {DataView} */ - #handle; - - /** - * @param {ArrayBuffer} buffer - */ - constructor(buffer) { - if (!isArrayBuffer(buffer)) { - throw new ERR_INVALID_ARG_TYPE('buffer', ['ArrayBuffer'], buffer); - } - this.#handle = new DataView(buffer); - } - - /** @type {boolean} */ - get hasPathValidationListener() { - return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_PATH_VALIDATION); - } - - /** @type {boolean} */ - set hasPathValidationListener(val) { - DataViewPrototypeSetUint8(this.#handle, IDX_STATE_SESSION_PATH_VALIDATION, val ? 1 : 0); - } - - /** @type {boolean} */ - get hasVersionNegotiationListener() { - return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_VERSION_NEGOTIATION); - } - - /** @type {boolean} */ - set hasVersionNegotiationListener(val) { - DataViewPrototypeSetUint8(this.#handle, IDX_STATE_SESSION_VERSION_NEGOTIATION, val ? 1 : 0); - } - - /** @type {boolean} */ - get hasDatagramListener() { - return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_DATAGRAM); - } - - /** @type {boolean} */ - set hasDatagramListener(val) { - DataViewPrototypeSetUint8(this.#handle, IDX_STATE_SESSION_DATAGRAM, val ? 1 : 0); - } - - /** @type {boolean} */ - get hasSessionTicketListener() { - return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_SESSION_TICKET); - } - - /** @type {boolean} */ - set hasSessionTicketListener(val) { - DataViewPrototypeSetUint8(this.#handle, IDX_STATE_SESSION_SESSION_TICKET, val ? 1 : 0); - } - - /** @type {boolean} */ - get isClosing() { - return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_CLOSING); - } - - /** @type {boolean} */ - get isGracefulClose() { - return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_GRACEFUL_CLOSE); - } - - /** @type {boolean} */ - get isSilentClose() { - return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_SILENT_CLOSE); - } - - /** @type {boolean} */ - get isStatelessReset() { - return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_STATELESS_RESET); - } - - /** @type {boolean} */ - get isDestroyed() { - return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_DESTROYED); - } - - /** @type {boolean} */ - get isHandshakeCompleted() { - return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_HANDSHAKE_COMPLETED); - } - - /** @type {boolean} */ - get isHandshakeConfirmed() { - return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_HANDSHAKE_CONFIRMED); - } - - /** @type {boolean} */ - get isStreamOpenAllowed() { - return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_STREAM_OPEN_ALLOWED); - } - - /** @type {boolean} */ - get isPrioritySupported() { - return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_PRIORITY_SUPPORTED); - } - - /** @type {boolean} */ - get isWrapped() { - return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_WRAPPED); - } - - /** @type {bigint} */ - get lastDatagramId() { - return DataViewPrototypeGetBigUint64(this.#handle, IDX_STATE_SESSION_LAST_DATAGRAM_ID); - } - - toJSON() { - 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; - - const opts = { - ...options, - depth: options.depth == null ? null : options.depth - 1, - }; - - return `SessionState ${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. - this.#handle = new DataView(new ArrayBuffer(0)); - } -} - -class Session { - /** @type {Endpoint} */ +class QuicSession { + /** @type {QuicEndpoint} */ #endpoint = undefined; /** @type {boolean} */ #isPendingClose = false; @@ -1361,9 +668,9 @@ class Session { #pendingClose; /** @type {SocketAddress|undefined} */ #remoteAddress = undefined; - /** @type {SessionState} */ + /** @type {QuicSessionState} */ #state; - /** @type {SessionStats} */ + /** @type {QuicSessionStats} */ #stats; /** @type {QuicStream[]} */ #streams = []; @@ -1388,12 +695,12 @@ class Session { * @param {SessionCallbackConfiguration} config * @param {StreamCallbackConfiguration} streamConfig * @param {object} [handle] - * @param {Endpoint} [endpoint] + * @param {QuicEndpoint} [endpoint] */ constructor(config, streamConfig, handle, endpoint) { validateObject(config, 'config'); - this.#stats = new SessionStats(handle.stats); - this.#state = new SessionState(handle.state); + this.#stats = new QuicSessionStats(kPrivateConstructor, handle.stats); + this.#state = new QuicSessionState(kPrivateConstructor, handle.state); const { ondatagram, ondatagramstatus, @@ -1440,13 +747,13 @@ class Session { return this.#handle === undefined || this.#isPendingClose; } - /** @type {SessionStats} */ + /** @type {QuicSessionStats} */ get stats() { return this.#stats; } - /** @type {SessionState} */ + /** @type {QuicSessionState} */ get state() { return this.#state; } - /** @type {Endpoint} */ + /** @type {QuicEndpoint} */ get endpoint() { return this.#endpoint; } /** @type {Path} */ @@ -1669,7 +976,7 @@ class Session { depth: options.depth == null ? null : options.depth - 1, }; - return `Session ${inspect({ + return `QuicSession ${inspect({ closed: this.closed, closing: this.#isPendingClose, destroyed: this.destroyed, @@ -1688,242 +995,6 @@ class Session { async [SymbolAsyncDispose]() { await this.close(); } } -class EndpointStats { - /** @type {BigUint64Array} */ - #handle; - - /** - * @param {ArrayBuffer} buffer - */ - constructor(buffer) { - 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]; - } - - toJSON() { - return { - __proto__: null, - 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 `EndpointStats ${inspect({ - 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)}`; - } - - [kFinishClose]() { - // Snapshot the stats into a new BigUint64Array since the underlying - // buffer will be destroyed. - this.#handle = new BigUint64Array(this.#handle); - } -} - -class EndpointState { - /** @type {DataView} */ - #handle; - - /** - * @param {ArrayBuffer} buffer - */ - constructor(buffer) { - 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); - } - - 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 'EndpointState { }'; - } - - const opts = { - ...options, - depth: options.depth == null ? null : options.depth - 1, - }; - - return `EndpointState ${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)); - } -} - function validateStreamConfig(config, name = 'config') { validateObject(config, name); if (config.onerror !== undefined) @@ -1968,7 +1039,7 @@ function validateEndpointConfig(config) { return config; } -class Endpoint { +class QuicEndpoint { /** @type {SocketAddress|undefined} */ #address = undefined; /** @type {boolean} */ @@ -1983,11 +1054,11 @@ class Endpoint { #pendingClose; /** @type {any} */ #pendingError = undefined; - /** @type {Session[]} */ + /** @type {QuicSession[]} */ #sessions = []; - /** @type {EndpointState} */ + /** @type {QuicEndpointState} */ #state; - /** @type {EndpointStats} */ + /** @type {QuicEndpointStats} */ #stats; /** @type {OnSessionCallback} */ #onsession; @@ -2074,14 +1145,14 @@ class Endpoint { tokenSecret, }); this.#handle[kOwner] = this; - this.#stats = new EndpointStats(this.#handle.stats); - this.#state = new EndpointState(this.#handle.state); + this.#stats = new QuicEndpointStats(kPrivateConstructor, this.#handle.stats); + this.#state = new QuicEndpointState(kPrivateConstructor, this.#handle.state); } - /** @type {EndpointStats} */ + /** @type {QuicEndpointStats} */ get stats() { return this.#stats; } - /** @type {EndpointState} */ + /** @type {QuicEndpointState} */ get state() { return this.#state; } get #isClosedOrClosing() { @@ -2164,7 +1235,7 @@ class Endpoint { * Initiates a session with a remote endpoint. * @param {SocketAddress} address * @param {SessionOptions} [options] - * @returns {Session} + * @returns {QuicSession} */ connect(address, options = kEmptyObject) { if (this.#isClosedOrClosing) { @@ -2207,7 +1278,7 @@ class Endpoint { if (handle === undefined) { throw new ERR_QUIC_CONNECTION_FAILED(); } - const session = new Session(this.#sessionConfig, this.#streamConfig, handle, this); + const session = new QuicSession(this.#sessionConfig, this.#streamConfig, handle, this); ArrayPrototypePush(this.#sessions, session); return session; } @@ -2322,7 +1393,7 @@ class Endpoint { } [kNewSession](handle) { - const session = new Session(this.#sessionConfig, this.#streamConfig, handle, this); + const session = new QuicSession(this.#sessionConfig, this.#streamConfig, handle, this); ArrayPrototypePush(this.#sessions, session); this.#onsession(session, this); } @@ -2338,7 +1409,7 @@ class Endpoint { depth: options.depth == null ? null : options.depth - 1, }; - return `Endpoint ${inspect({ + return `QuicEndpoint ${inspect({ address: this.address, busy: this.busy, closed: this.closed, @@ -2352,125 +1423,7 @@ class Endpoint { } }; -ObjectDefineProperties(QuicStreamStats.prototype, { - createdAt: kEnumerable, - receivedAt: kEnumerable, - ackedAt: kEnumerable, - closingAt: kEnumerable, - destroyedAt: kEnumerable, - bytesReceived: kEnumerable, - bytesSent: kEnumerable, - maxOffset: kEnumerable, - maxOffsetAcknowledged: kEnumerable, - maxOffsetReceived: kEnumerable, - finalSize: kEnumerable, -}); -ObjectDefineProperties(QuicStreamState.prototype, { - id: kEnumerable, - finSent: kEnumerable, - finReceived: kEnumerable, - readEnded: kEnumerable, - writeEnded: kEnumerable, - destroyed: kEnumerable, - paused: kEnumerable, - reset: kEnumerable, - hasReader: kEnumerable, - wantsBlock: kEnumerable, - wantsHeaders: kEnumerable, - wantsReset: kEnumerable, - wantsTrailers: kEnumerable, -}); -ObjectDefineProperties(QuicStream.prototype, { - stats: kEnumerable, - state: kEnumerable, - session: kEnumerable, - id: kEnumerable, -}); -ObjectDefineProperties(SessionStats.prototype, { - createdAt: kEnumerable, - closingAt: kEnumerable, - destroyedAt: kEnumerable, - handshakeCompletedAt: kEnumerable, - handshakeConfirmedAt: kEnumerable, - gracefulClosingAt: kEnumerable, - bytesReceived: kEnumerable, - bytesSent: kEnumerable, - bidiInStreamCount: kEnumerable, - bidiOutStreamCount: kEnumerable, - uniInStreamCount: kEnumerable, - uniOutStreamCount: kEnumerable, - lossRetransmitCount: kEnumerable, - maxBytesInFlights: kEnumerable, - bytesInFlight: kEnumerable, - blockCount: kEnumerable, - cwnd: kEnumerable, - latestRtt: kEnumerable, - minRtt: kEnumerable, - rttVar: kEnumerable, - smoothedRtt: kEnumerable, - ssthresh: kEnumerable, - datagramsReceived: kEnumerable, - datagramsSent: kEnumerable, - datagramsAcknowledged: kEnumerable, - datagramsLost: kEnumerable, -}); -ObjectDefineProperties(SessionState.prototype, { - hasPathValidationListener: kEnumerable, - hasVersionNegotiationListener: kEnumerable, - hasDatagramListener: kEnumerable, - hasSessionTicketListener: kEnumerable, - isClosing: kEnumerable, - isGracefulClose: kEnumerable, - isSilentClose: kEnumerable, - isStatelessReset: kEnumerable, - isDestroyed: kEnumerable, - isHandshakeCompleted: kEnumerable, - isHandshakeConfirmed: kEnumerable, - isStreamOpenAllowed: kEnumerable, - isPrioritySupported: kEnumerable, - isWrapped: kEnumerable, - lastDatagramId: kEnumerable, -}); -ObjectDefineProperties(Session.prototype, { - closed: kEnumerable, - destroyed: kEnumerable, - endpoint: kEnumerable, - path: kEnumerable, - state: kEnumerable, - stats: kEnumerable, -}); -ObjectDefineProperties(EndpointStats.prototype, { - createdAt: kEnumerable, - destroyedAt: kEnumerable, - bytesReceived: kEnumerable, - bytesSent: kEnumerable, - packetsReceived: kEnumerable, - packetsSent: kEnumerable, - serverSessions: kEnumerable, - clientSessions: kEnumerable, - serverBusyCount: kEnumerable, - retryCount: kEnumerable, - versionNegotiationCount: kEnumerable, - statelessResetCount: kEnumerable, - immediateCloseCount: kEnumerable, -}); -ObjectDefineProperties(EndpointState.prototype, { - isBound: kEnumerable, - isReceiving: kEnumerable, - isListening: kEnumerable, - isClosing: kEnumerable, - isBusy: kEnumerable, - pendingCallbacks: kEnumerable, -}); -ObjectDefineProperties(Endpoint.prototype, { - address: kEnumerable, - busy: kEnumerable, - closed: kEnumerable, - destroyed: kEnumerable, - state: kEnumerable, - stats: kEnumerable, -}); -ObjectDefineProperties(Endpoint, { +ObjectDefineProperties(QuicEndpoint, { CC_ALGO_RENO: { __proto__: null, value: CC_ALGO_RENO, @@ -2514,7 +1467,7 @@ ObjectDefineProperties(Endpoint, { enumerable: true, }, }); -ObjectDefineProperties(Session, { +ObjectDefineProperties(QuicSession, { DEFAULT_CIPHERS: { __proto__: null, value: DEFAULT_CIPHERS, @@ -2532,17 +1485,15 @@ ObjectDefineProperties(Session, { }); module.exports = { - Endpoint, - Session, + QuicEndpoint, + QuicSession, QuicStream, - // Exported for testing - kFinishClose, - SessionState, - SessionStats, + QuicSessionState, + QuicSessionStats, QuicStreamState, QuicStreamStats, - EndpointState, - EndpointStats, + QuicEndpointState, + QuicEndpointStats, }; /* c8 ignore stop */ diff --git a/lib/internal/quic/state.js b/lib/internal/quic/state.js new file mode 100644 index 00000000000000..8bfb2ac83302fb --- /dev/null +++ b/lib/internal/quic/state.js @@ -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 { }'; + } + + 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 { }'; + } + + 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 { }'; + } + + 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, +}; diff --git a/lib/internal/quic/stats.js b/lib/internal/quic/stats.js new file mode 100644 index 00000000000000..3ac9523d9aeca4 --- /dev/null +++ b/lib/internal/quic/stats.js @@ -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, +}; diff --git a/lib/internal/quic/symbols.js b/lib/internal/quic/symbols.js new file mode 100644 index 00000000000000..fa2c98320b860e --- /dev/null +++ b/lib/internal/quic/symbols.js @@ -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, +}; diff --git a/src/node_builtins.cc b/src/node_builtins.cc index 1bec44f6f29b0b..9aaf5626fcfe4a 100644 --- a/src/node_builtins.cc +++ b/src/node_builtins.cc @@ -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. diff --git a/test/parallel/test-quic-internal-endpoint-listen-defaults.js b/test/parallel/test-quic-internal-endpoint-listen-defaults.js index 7b073104a148ce..987e191b759cdd 100644 --- a/test/parallel/test-quic-internal-endpoint-listen-defaults.js +++ b/test/parallel/test-quic-internal-endpoint-listen-defaults.js @@ -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: {}, diff --git a/test/parallel/test-quic-internal-endpoint-options.js b/test/parallel/test-quic-internal-endpoint-options.js index 88dc42c4dd7ade..91c1e6d5b2d312 100644 --- a/test/parallel/test-quic-internal-endpoint-options.js +++ b/test/parallel/test-quic-internal-endpoint-options.js @@ -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', }); }); diff --git a/test/parallel/test-quic-internal-endpoint-stats-state.js b/test/parallel/test-quic-internal-endpoint-stats-state.js index 18a6ed6b138048..6992e4ac09df08 100644 --- a/test/parallel/test-quic-internal-endpoint-stats-state.js +++ b/test/parallel/test-quic-internal-endpoint-stats-state.js @@ -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);