'use strict';
// TODO(@jasnell) Temporarily ignoring c8 coverage for this file while tests
// are still being developed.
/* c8 ignore start */
const {
DataView,
DataViewPrototypeGetBigInt64,
DataViewPrototypeGetBigUint64,
DataViewPrototypeGetBuffer,
DataViewPrototypeGetByteLength,
DataViewPrototypeGetByteOffset,
DataViewPrototypeGetUint16,
DataViewPrototypeGetUint32,
DataViewPrototypeGetUint8,
DataViewPrototypeSetUint16,
DataViewPrototypeSetUint32,
DataViewPrototypeSetUint8,
JSONStringify,
Number,
TypedArrayPrototypeGetBuffer,
TypedArrayPrototypeSlice,
Uint8Array,
} = primordials;
const {
isBigEndian,
} = internalBinding('os');
const kIsLittleEndian = !isBigEndian;
const {
getOptionValue,
} = require('internal/options');
if (!process.features.quic || !getOptionValue('--experimental-quic')) {
return;
}
const {
codes: {
ERR_ILLEGAL_CONSTRUCTOR,
ERR_INVALID_ARG_TYPE,
},
} = require('internal/errors');
const {
isArrayBuffer,
} = require('util/types');
const { inspect } = require('internal/util/inspect');
const assert = require('internal/assert');
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_LISTENER_FLAGS,
IDX_STATE_SESSION_CLOSING,
IDX_STATE_SESSION_GRACEFUL_CLOSE,
IDX_STATE_SESSION_SILENT_CLOSE,
IDX_STATE_SESSION_STATELESS_RESET,
IDX_STATE_SESSION_HANDSHAKE_COMPLETED,
IDX_STATE_SESSION_HANDSHAKE_CONFIRMED,
IDX_STATE_SESSION_STREAM_OPEN_ALLOWED,
IDX_STATE_SESSION_PRIORITY_SUPPORTED,
IDX_STATE_SESSION_HEADERS_SUPPORTED,
IDX_STATE_SESSION_STREAM_CALLBACKS_SUPPORTED,
IDX_STATE_SESSION_WRAPPED,
IDX_STATE_SESSION_APPLICATION_TYPE,
IDX_STATE_SESSION_NO_ERROR_CODE,
IDX_STATE_SESSION_INTERNAL_ERROR_CODE,
IDX_STATE_SESSION_REQUEST_REJECTED_CODE,
IDX_STATE_SESSION_MAX_DATAGRAM_SIZE,
IDX_STATE_SESSION_LAST_DATAGRAM_ID,
IDX_STATE_SESSION_MAX_PENDING_DATAGRAMS,
IDX_STATE_ENDPOINT_BOUND,
IDX_STATE_ENDPOINT_RECEIVING,
IDX_STATE_ENDPOINT_LISTENING,
IDX_STATE_ENDPOINT_CLOSING,
IDX_STATE_ENDPOINT_BUSY,
IDX_STATE_ENDPOINT_MAX_CONNECTIONS_PER_HOST,
IDX_STATE_ENDPOINT_MAX_CONNECTIONS_TOTAL,
IDX_STATE_ENDPOINT_PENDING_CALLBACKS,
IDX_STATE_STREAM_ID,
IDX_STATE_STREAM_PENDING,
IDX_STATE_STREAM_FIN_SENT,
IDX_STATE_STREAM_FIN_RECEIVED,
IDX_STATE_STREAM_READ_ENDED,
IDX_STATE_STREAM_WRITE_ENDED,
IDX_STATE_STREAM_RESET,
IDX_STATE_STREAM_HAS_OUTBOUND,
IDX_STATE_STREAM_HAS_READER,
IDX_STATE_STREAM_WANTS_BLOCK,
IDX_STATE_STREAM_WANTS_RESET,
IDX_STATE_STREAM_WANTS_STOP_SENDING,
IDX_STATE_STREAM_RECEIVED_EARLY_DATA,
IDX_STATE_STREAM_WRITE_DESIRED_SIZE,
IDX_STATE_STREAM_BUDGET,
IDX_STATE_STREAM_RESET_CODE,
IDX_STATE_STREAM_SIZE,
} = internalBinding('quic');
assert(IDX_STATE_SESSION_LISTENER_FLAGS !== undefined);
assert(IDX_STATE_SESSION_CLOSING !== undefined);
assert(IDX_STATE_SESSION_GRACEFUL_CLOSE !== undefined);
assert(IDX_STATE_SESSION_SILENT_CLOSE !== undefined);
assert(IDX_STATE_SESSION_STATELESS_RESET !== undefined);
assert(IDX_STATE_SESSION_HANDSHAKE_COMPLETED !== undefined);
assert(IDX_STATE_SESSION_HANDSHAKE_CONFIRMED !== undefined);
assert(IDX_STATE_SESSION_STREAM_OPEN_ALLOWED !== undefined);
assert(IDX_STATE_SESSION_PRIORITY_SUPPORTED !== undefined);
assert(IDX_STATE_SESSION_HEADERS_SUPPORTED !== undefined);
assert(IDX_STATE_SESSION_STREAM_CALLBACKS_SUPPORTED !== undefined);
assert(IDX_STATE_SESSION_WRAPPED !== undefined);
assert(IDX_STATE_SESSION_APPLICATION_TYPE !== undefined);
assert(IDX_STATE_SESSION_NO_ERROR_CODE !== undefined);
assert(IDX_STATE_SESSION_INTERNAL_ERROR_CODE !== undefined);
assert(IDX_STATE_SESSION_REQUEST_REJECTED_CODE !== undefined);
assert(IDX_STATE_SESSION_MAX_DATAGRAM_SIZE !== undefined);
assert(IDX_STATE_SESSION_LAST_DATAGRAM_ID !== undefined);
assert(IDX_STATE_ENDPOINT_BOUND !== undefined);
assert(IDX_STATE_ENDPOINT_RECEIVING !== undefined);
assert(IDX_STATE_ENDPOINT_LISTENING !== undefined);
assert(IDX_STATE_ENDPOINT_CLOSING !== undefined);
assert(IDX_STATE_ENDPOINT_BUSY !== undefined);
assert(IDX_STATE_ENDPOINT_PENDING_CALLBACKS !== undefined);
assert(IDX_STATE_STREAM_ID !== undefined);
assert(IDX_STATE_STREAM_PENDING !== undefined);
assert(IDX_STATE_STREAM_FIN_SENT !== undefined);
assert(IDX_STATE_STREAM_FIN_RECEIVED !== undefined);
assert(IDX_STATE_STREAM_READ_ENDED !== undefined);
assert(IDX_STATE_STREAM_WRITE_ENDED !== undefined);
assert(IDX_STATE_STREAM_RESET !== undefined);
assert(IDX_STATE_STREAM_HAS_OUTBOUND !== undefined);
assert(IDX_STATE_STREAM_HAS_READER !== undefined);
assert(IDX_STATE_STREAM_WANTS_BLOCK !== undefined);
assert(IDX_STATE_STREAM_WANTS_RESET !== undefined);
assert(IDX_STATE_STREAM_WANTS_STOP_SENDING !== undefined);
assert(IDX_STATE_STREAM_WRITE_DESIRED_SIZE !== undefined);
assert(IDX_STATE_STREAM_RESET_CODE !== undefined);
const kEmptyObject = { __proto__: null };
// The internal state objects for endpoints, sessions, and streams.
// These are not exposed directly to users.
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);
}
/** @type {boolean} */
get isBound() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, IDX_STATE_ENDPOINT_BOUND) !== 0;
}
/** @type {boolean} */
get isReceiving() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, IDX_STATE_ENDPOINT_RECEIVING) !== 0;
}
/** @type {boolean} */
get isListening() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, IDX_STATE_ENDPOINT_LISTENING) !== 0;
}
/** @type {boolean} */
get isClosing() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, IDX_STATE_ENDPOINT_CLOSING) !== 0;
}
/** @type {boolean} */
get isBusy() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, IDX_STATE_ENDPOINT_BUSY) !== 0;
}
/** @type {number} */
get maxConnectionsPerHost() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint16(
handle, IDX_STATE_ENDPOINT_MAX_CONNECTIONS_PER_HOST, kIsLittleEndian);
}
set maxConnectionsPerHost(val) {
const handle = this.#handle;
if (handle === undefined) return;
DataViewPrototypeSetUint16(
handle, IDX_STATE_ENDPOINT_MAX_CONNECTIONS_PER_HOST, val, kIsLittleEndian);
}
/** @type {number} */
get maxConnectionsTotal() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint16(
handle, IDX_STATE_ENDPOINT_MAX_CONNECTIONS_TOTAL, kIsLittleEndian);
}
set maxConnectionsTotal(val) {
const handle = this.#handle;
if (handle === undefined) return;
DataViewPrototypeSetUint16(
handle, IDX_STATE_ENDPOINT_MAX_CONNECTIONS_TOTAL, val, kIsLittleEndian);
}
/**
* The number of underlying callbacks that are pending. If the endpoint
* is closing, these are the number of callbacks that the endpoint is
* waiting on before it can be closed.
* @type {bigint}
*/
get pendingCallbacks() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetBigUint64(handle, IDX_STATE_ENDPOINT_PENDING_CALLBACKS, kIsLittleEndian);
}
toString() {
return JSONStringify(this.toJSON());
}
toJSON() {
if (this.#handle === undefined) return kEmptyObject;
const {
isBound,
isReceiving,
isListening,
isClosing,
isBusy,
maxConnectionsPerHost,
maxConnectionsTotal,
pendingCallbacks,
} = this;
return {
__proto__: null,
isBound,
isReceiving,
isListening,
isClosing,
isBusy,
maxConnectionsPerHost,
maxConnectionsTotal,
pendingCallbacks: Number(pendingCallbacks),
};
}
[kInspect](depth, options) {
if (this.#handle === undefined) {
return 'QuicEndpointState { <Closed> }';
}
if (depth < 0) {
return 'QuicEndpointState { }';
}
const opts = {
__proto__: null,
...options,
depth: options.depth == null ? null : options.depth - 1,
};
const {
isBound,
isReceiving,
isListening,
isClosing,
isBusy,
maxConnectionsPerHost,
maxConnectionsTotal,
pendingCallbacks,
} = this;
return `QuicEndpointState ${inspect({
isBound,
isReceiving,
isListening,
isClosing,
isBusy,
maxConnectionsPerHost,
maxConnectionsTotal,
pendingCallbacks,
}, opts)}`;
}
[kFinishClose]() {
this.#handle = undefined;
}
}
class QuicSessionState {
/** @type {DataView} */
#handle;
/** @type {number} */
#offset = 0;
/**
* @param {symbol} privateSymbol
* @param {DataView|ArrayBuffer} view
* @param {number} [byteOffset]
*/
constructor(privateSymbol, view, byteOffset = 0) {
if (privateSymbol !== kPrivateConstructor) {
throw new ERR_ILLEGAL_CONSTRUCTOR();
}
if (isArrayBuffer(view)) {
this.#handle = new DataView(view);
} else {
this.#handle = view;
}
this.#offset = byteOffset;
}
// Listener flags are packed into a single uint32_t bitfield. The bit
// positions must match the SessionListenerFlags enum in session.cc.
static #LISTENER_PATH_VALIDATION = 1 << 0;
static #LISTENER_DATAGRAM = 1 << 1;
static #LISTENER_DATAGRAM_STATUS = 1 << 2;
static #LISTENER_SESSION_TICKET = 1 << 3;
static #LISTENER_NEW_TOKEN = 1 << 4;
static #LISTENER_ORIGIN = 1 << 5;
static #LISTENER_APPLICATION = 1 << 6;
#getListenerFlag(flag) {
const handle = this.#handle;
if (handle === undefined) return undefined;
return (DataViewPrototypeGetUint32(
handle, this.#offset + IDX_STATE_SESSION_LISTENER_FLAGS, kIsLittleEndian) & flag) !== 0;
}
#setListenerFlag(flag, val) {
const handle = this.#handle;
if (handle === undefined) return;
const current = DataViewPrototypeGetUint32(
handle, this.#offset + IDX_STATE_SESSION_LISTENER_FLAGS, kIsLittleEndian);
DataViewPrototypeSetUint32(
handle, this.#offset + IDX_STATE_SESSION_LISTENER_FLAGS,
val ? (current | flag) : (current & ~flag), kIsLittleEndian);
}
/** @type {boolean} */
get hasApplicationListener() {
return this.#getListenerFlag(QuicSessionState.#LISTENER_APPLICATION);
}
set hasApplicationListener(val) {
this.#setListenerFlag(QuicSessionState.#LISTENER_APPLICATION, val);
}
/** @type {boolean} */
get hasPathValidationListener() {
return this.#getListenerFlag(QuicSessionState.#LISTENER_PATH_VALIDATION);
}
set hasPathValidationListener(val) {
this.#setListenerFlag(QuicSessionState.#LISTENER_PATH_VALIDATION, val);
}
/** @type {boolean} */
get hasDatagramListener() {
return this.#getListenerFlag(QuicSessionState.#LISTENER_DATAGRAM);
}
set hasDatagramListener(val) {
this.#setListenerFlag(QuicSessionState.#LISTENER_DATAGRAM, val);
}
/** @type {boolean} */
get hasDatagramStatusListener() {
return this.#getListenerFlag(QuicSessionState.#LISTENER_DATAGRAM_STATUS);
}
set hasDatagramStatusListener(val) {
this.#setListenerFlag(QuicSessionState.#LISTENER_DATAGRAM_STATUS, val);
}
/** @type {boolean} */
get hasSessionTicketListener() {
return this.#getListenerFlag(QuicSessionState.#LISTENER_SESSION_TICKET);
}
set hasSessionTicketListener(val) {
this.#setListenerFlag(QuicSessionState.#LISTENER_SESSION_TICKET, val);
}
/** @type {boolean} */
get hasNewTokenListener() {
return this.#getListenerFlag(QuicSessionState.#LISTENER_NEW_TOKEN);
}
set hasNewTokenListener(val) {
this.#setListenerFlag(QuicSessionState.#LISTENER_NEW_TOKEN, val);
}
/** @type {boolean} */
get hasOriginListener() {
return this.#getListenerFlag(QuicSessionState.#LISTENER_ORIGIN);
}
set hasOriginListener(val) {
this.#setListenerFlag(QuicSessionState.#LISTENER_ORIGIN, val);
}
/** @type {boolean} */
get isClosing() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_SESSION_CLOSING) !== 0;
}
/** @type {boolean} */
get isGracefulClose() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_SESSION_GRACEFUL_CLOSE) !== 0;
}
/** @type {boolean} */
get isSilentClose() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_SESSION_SILENT_CLOSE) !== 0;
}
/** @type {boolean} */
get isStatelessReset() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_SESSION_STATELESS_RESET) !== 0;
}
/** @type {boolean} */
get isHandshakeCompleted() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_SESSION_HANDSHAKE_COMPLETED) !== 0;
}
/** @type {boolean} */
get isHandshakeConfirmed() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_SESSION_HANDSHAKE_CONFIRMED) !== 0;
}
/** @type {boolean} */
get isStreamOpenAllowed() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_SESSION_STREAM_OPEN_ALLOWED) !== 0;
}
/** @type {boolean} */
get isPrioritySupported() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_SESSION_PRIORITY_SUPPORTED) !== 0;
}
/**
* Whether the negotiated application protocol supports headers.
* Returns 0 (unknown), 1 (supported), or 2 (not supported).
* @type {number}
*/
get headersSupported() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_SESSION_HEADERS_SUPPORTED);
}
/**
* Whether the negotiated application dispatches the session-level
* stream callbacks (onheaders et al) for incoming streams.
* Returns 0 (unknown), 1 (supported), or 2 (not supported).
* @type {number}
*/
get streamCallbacksSupported() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(
handle, this.#offset + IDX_STATE_SESSION_STREAM_CALLBACKS_SUPPORTED);
}
/** @type {boolean} */
get isWrapped() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_SESSION_WRAPPED) !== 0;
}
/** @type {number} */
get applicationType() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_SESSION_APPLICATION_TYPE);
}
/**
* The negotiated application protocol's "no error" code, populated
* by the C++ layer when the application is selected during ALPN
* negotiation. For raw QUIC this is `0n`; for HTTP/3 this is
* `0x100n` (`H3_NO_ERROR`).
* @type {bigint}
*/
get noErrorCode() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetBigUint64(
handle, this.#offset + IDX_STATE_SESSION_NO_ERROR_CODE, kIsLittleEndian);
}
/**
* The negotiated application protocol's "internal error" code,
* populated by the C++ layer when the application is selected
* during ALPN negotiation. Used as the wire code for `RESET_STREAM`
* frames when a stream is aborted without a more specific code.
* For raw QUIC this is `0x1n` (NGTCP2_INTERNAL_ERROR); for HTTP/3
* this is `0x102n` (`H3_INTERNAL_ERROR`).
* @type {bigint}
*/
get internalErrorCode() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetBigUint64(
handle, this.#offset + IDX_STATE_SESSION_INTERNAL_ERROR_CODE, kIsLittleEndian);
}
get requestRejectedCode() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetBigUint64(
handle, this.#offset + IDX_STATE_SESSION_REQUEST_REJECTED_CODE, kIsLittleEndian);
}
/** @type {number} */
get maxDatagramSize() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint16(
handle, this.#offset + IDX_STATE_SESSION_MAX_DATAGRAM_SIZE,
kIsLittleEndian);
}
/** @type {bigint} */
get lastDatagramId() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetBigUint64(
handle, this.#offset + IDX_STATE_SESSION_LAST_DATAGRAM_ID,
kIsLittleEndian);
}
/** @type {number} */
get maxPendingDatagrams() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint16(
handle, this.#offset + IDX_STATE_SESSION_MAX_PENDING_DATAGRAMS, kIsLittleEndian);
}
set maxPendingDatagrams(val) {
const handle = this.#handle;
if (handle === undefined) return;
DataViewPrototypeSetUint16(
handle, this.#offset + IDX_STATE_SESSION_MAX_PENDING_DATAGRAMS, val, kIsLittleEndian);
}
toString() {
return JSONStringify(this.toJSON());
}
toJSON() {
if (this.#handle === undefined) return kEmptyObject;
const {
hasPathValidationListener,
hasDatagramListener,
hasDatagramStatusListener,
hasSessionTicketListener,
hasNewTokenListener,
hasOriginListener,
isClosing,
isGracefulClose,
isSilentClose,
isStatelessReset,
isHandshakeCompleted,
isHandshakeConfirmed,
isStreamOpenAllowed,
isPrioritySupported,
headersSupported,
isWrapped,
applicationType,
noErrorCode,
internalErrorCode,
maxDatagramSize,
lastDatagramId,
maxPendingDatagrams,
} = this;
return {
__proto__: null,
hasPathValidationListener,
hasDatagramListener,
hasDatagramStatusListener,
hasSessionTicketListener,
hasNewTokenListener,
hasOriginListener,
isClosing,
isGracefulClose,
isSilentClose,
isStatelessReset,
isHandshakeCompleted,
isHandshakeConfirmed,
isStreamOpenAllowed,
isPrioritySupported,
headersSupported,
isWrapped,
applicationType,
noErrorCode: `${noErrorCode}`,
internalErrorCode: `${internalErrorCode}`,
maxDatagramSize: `${maxDatagramSize}`,
lastDatagramId: `${lastDatagramId}`,
maxPendingDatagrams,
};
}
[kInspect](depth, options) {
if (this.#handle === undefined) {
return 'QuicSessionState { <Closed> }';
}
if (depth < 0) {
return 'QuicSessionState { }';
}
const opts = {
__proto__: null,
...options,
depth: options.depth == null ? null : options.depth - 1,
};
const {
hasPathValidationListener,
hasDatagramListener,
hasDatagramStatusListener,
hasSessionTicketListener,
hasNewTokenListener,
hasOriginListener,
isClosing,
isGracefulClose,
isSilentClose,
isStatelessReset,
isHandshakeCompleted,
isHandshakeConfirmed,
isStreamOpenAllowed,
isPrioritySupported,
headersSupported,
isWrapped,
applicationType,
noErrorCode,
internalErrorCode,
maxDatagramSize,
lastDatagramId,
maxPendingDatagrams,
} = this;
return `QuicSessionState ${inspect({
hasPathValidationListener,
hasDatagramListener,
hasDatagramStatusListener,
hasSessionTicketListener,
hasNewTokenListener,
hasOriginListener,
isClosing,
isGracefulClose,
isSilentClose,
isStatelessReset,
isHandshakeCompleted,
isHandshakeConfirmed,
isStreamOpenAllowed,
isPrioritySupported,
headersSupported,
isWrapped,
applicationType,
noErrorCode,
internalErrorCode,
maxDatagramSize,
lastDatagramId,
maxPendingDatagrams,
}, opts)}`;
}
[kFinishClose]() {
this.#handle = undefined;
}
}
class QuicStreamState {
/** @type {DataView} */
#handle;
/** @type {number} */
#offset = 0;
/** @type {boolean} */
#disconnected = false;
/**
* @param {symbol} privateSymbol
* @param {DataView|ArrayBuffer} view
* @param {number} [byteOffset]
*/
constructor(privateSymbol, view, byteOffset = 0) {
if (privateSymbol !== kPrivateConstructor) {
throw new ERR_ILLEGAL_CONSTRUCTOR();
}
if (isArrayBuffer(view)) {
this.#handle = new DataView(view);
} else {
this.#handle = view;
}
this.#offset = byteOffset;
}
/** @type {bigint} */
get id() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetBigInt64(handle, this.#offset + IDX_STATE_STREAM_ID, kIsLittleEndian);
}
/** @type {boolean} */
get pending() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_STREAM_PENDING) !== 0;
}
/** @type {boolean} */
get finSent() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_STREAM_FIN_SENT) !== 0;
}
/** @type {boolean} */
get finReceived() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_STREAM_FIN_RECEIVED) !== 0;
}
/** @type {boolean} */
get readEnded() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_STREAM_READ_ENDED) !== 0;
}
/** @type {boolean} */
get writeEnded() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_STREAM_WRITE_ENDED) !== 0;
}
/** @type {boolean} */
get reset() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_STREAM_RESET) !== 0;
}
/** @type {boolean} */
get hasOutbound() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_STREAM_HAS_OUTBOUND) !== 0;
}
/** @type {boolean} */
get hasReader() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_STREAM_HAS_READER) !== 0;
}
/** @type {boolean} */
get wantsBlock() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_STREAM_WANTS_BLOCK) !== 0;
}
/** @type {boolean} */
set wantsBlock(val) {
const handle = this.#handle;
if (handle === undefined) return;
DataViewPrototypeSetUint8(handle, this.#offset + IDX_STATE_STREAM_WANTS_BLOCK, val ? 1 : 0);
}
/** @type {boolean} */
get wantsReset() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_STREAM_WANTS_RESET) !== 0;
}
/** @type {boolean} */
set wantsReset(val) {
const handle = this.#handle;
if (handle === undefined) return;
DataViewPrototypeSetUint8(handle, this.#offset + IDX_STATE_STREAM_WANTS_RESET, val ? 1 : 0);
}
/** @type {boolean} */
get wantsStopSending() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(
handle, this.#offset + IDX_STATE_STREAM_WANTS_STOP_SENDING) !== 0;
}
/** @type {boolean} */
set wantsStopSending(val) {
const handle = this.#handle;
if (handle === undefined) return;
DataViewPrototypeSetUint8(
handle,
this.#offset + IDX_STATE_STREAM_WANTS_STOP_SENDING,
val ? 1 : 0);
}
/** @type {boolean} */
get early() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_STREAM_RECEIVED_EARLY_DATA) !== 0;
}
/** @type {bigint} */
get resetCode() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetBigUint64(
handle, this.#offset + IDX_STATE_STREAM_RESET_CODE, kIsLittleEndian);
}
/** @type {bigint} */
get writeDesiredSize() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint32(
handle, this.#offset + IDX_STATE_STREAM_WRITE_DESIRED_SIZE, kIsLittleEndian);
}
set writeDesiredSize(val) {
const handle = this.#handle;
if (handle === undefined) return;
DataViewPrototypeSetUint32(
handle, this.#offset + IDX_STATE_STREAM_WRITE_DESIRED_SIZE, val, kIsLittleEndian);
}
/** @type {number} */
get budget() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint32(
handle, this.#offset + IDX_STATE_STREAM_BUDGET, kIsLittleEndian);
}
set budget(val) {
const handle = this.#handle;
if (handle === undefined) return;
DataViewPrototypeSetUint32(
handle, this.#offset + IDX_STATE_STREAM_BUDGET, val, kIsLittleEndian);
}
toString() {
return JSONStringify(this.toJSON());
}
toJSON() {
if (this.#handle === undefined) return kEmptyObject;
const {
id,
pending,
finSent,
finReceived,
readEnded,
writeEnded,
reset,
hasOutbound,
hasReader,
wantsBlock,
wantsReset,
wantsStopSending,
early,
resetCode,
writeDesiredSize,
budget,
} = this;
return {
__proto__: null,
id: `${id}`,
pending,
finSent,
finReceived,
readEnded,
writeEnded,
reset,
hasOutbound,
hasReader,
wantsBlock,
wantsReset,
wantsStopSending,
early,
resetCode: `${resetCode}`,
writeDesiredSize,
budget,
};
}
[kInspect](depth, options) {
if (this.#disconnected || this.#handle === undefined ||
DataViewPrototypeGetByteLength(this.#handle) === 0) {
return 'QuicStreamState { <Closed> }';
}
if (depth < 0) {
return 'QuicStreamState { }';
}
const opts = {
__proto__: null,
...options,
depth: options.depth == null ? null : options.depth - 1,
};
const {
id,
pending,
finSent,
finReceived,
readEnded,
writeEnded,
reset,
hasOutbound,
hasReader,
wantsBlock,
wantsReset,
wantsStopSending,
early,
resetCode,
writeDesiredSize,
budget,
} = this;
return `QuicStreamState ${inspect({
id,
pending,
finSent,
finReceived,
readEnded,
writeEnded,
reset,
hasOutbound,
hasReader,
wantsBlock,
wantsReset,
wantsStopSending,
early,
resetCode,
writeDesiredSize,
budget,
}, opts)}`;
}
[kFinishClose]() {
// Copy the final values out of the shared buffer (zeroed when the
// underlying stream goes away) so getters keep reporting the final
// state, the same way QuicStreamStats does.
const handle = this.#handle;
if (handle === undefined ||
DataViewPrototypeGetByteLength(handle) <
this.#offset + IDX_STATE_STREAM_SIZE) {
this.#handle = undefined;
this.#disconnected = true;
return;
}
const copy = TypedArrayPrototypeSlice(new Uint8Array(
DataViewPrototypeGetBuffer(handle),
DataViewPrototypeGetByteOffset(handle) + this.#offset,
IDX_STATE_STREAM_SIZE));
this.#handle = new DataView(TypedArrayPrototypeGetBuffer(copy));
this.#offset = 0;
this.#disconnected = true;
}
}
module.exports = {
QuicEndpointState,
QuicSessionState,
QuicStreamState,
};
/* c8 ignore stop */