js/net/quic/connection
js/net/quic/connection.ts
fino:net/quic/connection — QUIC connection state and stream creation.
A QuicConnection represents one negotiated QUIC connection. It accepts
inbound streams, opens outbound streams, sends datagrams when negotiated, and
exposes connection lifecycle events through EventTarget.
import { QuicEndpoint } from 'fino:net/quic/endpoint';
import type { QuicConnection } from 'fino:net/quic/connection';
const endpoint = new QuicEndpoint({ alpnProtocols: ['h3'] });
const connection: QuicConnection = await endpoint.connect({
address: { family: 'ipv4', ip: '127.0.0.1', port: 4433 },
serverName: 'localhost',
});
const stream = await connection.openStream();
await stream.writer.write(new TextEncoder().encode('ping'));
await connection.close();
await endpoint.close();
Classes
class QuicConnection extends EventTarget {
A single QUIC connection and the streams multiplexed inside it.
Created by QuicEndpoint.connect() (client) or accepted on the server side;
never constructed directly. It wraps one ngtcp2 connection and exposes its
handshake state, negotiated parameters, streams, unreliable DATAGRAMs,
migration, key updates, and close. It extends EventTarget and emits
'stream', 'datagram', 'datagramack'/'datagramlost'/
'datagramabandoned', 'newtoken', 'earlydata', 'keyupdate',
'pathvalidation', 'error', and 'close'.
Open application streams with openBidirectionalStream() /
openUnidirectionalStream() and accept peer streams with acceptStream()
(or the 'stream' event). Both open methods await stream-limit credit when
the peer's stream budget is exhausted. close() starts a graceful close that
flushes pending stream data; destroy() tears the connection down at once.
const conn = await endpoint.connect({ address });
const stream = await conn.openBidirectionalStream();
await stream.writer.write(new TextEncoder().encode('GET /'));
await stream.writer.close();
for await (const chunk of stream.readable) process(chunk);
await conn.close();
Readonly Properties
readonly connectionId: string
Stable per-process identifier for this connection (for logging/routing).
readonly localAddress: QuicAddress
Local address this connection is currently using.
readonly alpnProtocols: string[]
ALPN protocols offered for this connection.
readonly routeCids: string[]
Connection IDs registered in the endpoint routing table for this connection.
Properties
remoteAddress: QuicAddress
Current remote peer address; updated as the connection migrates paths.
Constructors
constructor(
role: 'client' | 'server',
endpoint: QuicEndpoint,
listener: QuicListener | null,
localAddress: QuicAddress,
remoteAddress: QuicAddress,
alpnProtocols: string[],
transport: QuicDatagramTransport,
ctx: QuicTlsContext | null,
tls: QuicTlsSession,
originalDcid: ArrayBuffer | null,
options: ResolvedQuicOptions,
serverName: string | null = null
)
Getters
get alpnProtocol(): string
The ALPN protocol negotiated with the peer, or the empty string if none.
get handshakeComplete(): boolean
True once the connection has reached the 'connected' state.
get version(): QuicVersion
Negotiated QUIC version for this connection.
Returns v1 or v2. Before ngtcp2 reports a negotiated value, this falls
back to the local version used to construct the native connection.
get state(): QuicConnectionState
Lifecycle state: 'connecting', 'connected', 'closing', or 'closed'.
get closing(): boolean
True once a close has been initiated (locally or by the peer).
get closed(): Promise<void>
Promise that resolves when the connection has fully closed.
get closeInfo(): QuicCloseInfo | null
Details of why the connection closed, or null while still open.
get peerCertificate(): Uint8Array | null
The peer's leaf certificate in DER bytes, or null if none was presented.
get peerVerification(): QuicPeerVerification | null
Result of verifying the peer certificate, or null before it is known.
get localTransportParameters(): QuicTransportParameterSnapshot | null
Decoded transport parameters this side advertised, or null before setup.
get remoteTransportParameters(): QuicTransportParameterSnapshot | null
Decoded transport parameters the peer advertised, or null before they arrive.
get stats(): QuicConnectionStats
Frozen snapshot of this connection's counters and live recovery metrics.
get datagrams(): ReadableStream<Uint8Array>
Readable stream of received QUIC DATAGRAM payloads.
The stream is available only when DATAGRAM support was enabled and negotiated. Payloads are unreliable and unordered by protocol design.
get datagramReadable(): ReadableStream<Uint8Array>
Alias for datagrams; the same received-DATAGRAM ReadableStream.
get [quicIncomingStreamHook](): ((stream: QuicStream) => void) | null
Methods
exportKeyingMaterial(label: string, context: Uint8Array, length: number): ArrayBuffer
Export TLS keying material (RFC 5705 exporter) from the connection.
Derives length bytes bound to this connection's TLS secrets under the
given label and context — useful for channel binding or deriving
application keys. Throws if the connection is not yet connected.
const key = conn.exportKeyingMaterial('EXPORTER-my-app', new Uint8Array(), 32);
acceptStream(): Promise<QuicStream>
Wait for and return the next stream the peer opens on this connection.
Resolves in FIFO order as peer-initiated streams arrive; an alternative to
the 'stream' event. The promise rejects if the connection closes while
waiting.
for (;;) {
const stream = await conn.acceptStream();
serve(stream);
}
async openBidirectionalStream(): Promise<QuicStream>
Open a locally initiated bidirectional stream, awaiting credit if needed.
If the peer's bidirectional stream limit is currently exhausted, the promise waits until the peer extends stream credit and then opens the stream, rather than failing. Rejects if the connection is closing or not connected, or if ngtcp2 reports a non-recoverable open error.
const stream = await conn.openBidirectionalStream();
await stream.writer.write(payload);
async openUnidirectionalStream(): Promise<QuicStream>
Open a locally initiated send-only (unidirectional) stream.
Behaves like openBidirectionalStream() but produces a stream whose
readable side is closed — only writer is usable. Awaits unidirectional
stream credit when the peer's limit is exhausted.
const stream = await conn.openUnidirectionalStream();
await stream.writer.write(payload);
await stream.writer.close();
initiateKeyUpdate(): void
Initiate a local QUIC key update.
Throws if the connection is not connected or ngtcp2 reports that the update is not currently legal, for example before enough 1-RTT traffic has flowed.
async migrate(address: QuicAddress): Promise<void>
Actively migrate a client connection to a new local UDP address.
This binds a new UDP socket, asks ngtcp2 to validate migration to the new path, and then keeps both sockets readable while validation completes.
async sendDatagram(
data: QuicDatagramSource,
encoding: QuicDatagramEncoding = 'utf8'
): Promise<number>
Send one unreliable QUIC DATAGRAM payload.
Rejects for local validation failures such as disabled DATAGRAM support,
disconnected state, oversized payloads, or missing peer DATAGRAM
negotiation. Zero-length payloads are valid RFC 9221 DATAGRAM frames.
Transient send pressure is handled by the local
queue; queued DATAGRAMs later surface datagramack, datagramlost, or
datagramabandoned events.
async close(options: QuicCloseOptions = {}): Promise<void>
Gracefully close the connection, flushing pending stream data first.
Marks the connection closing, closes the writable side of local streams, and
once outstanding data is acknowledged sends a CONNECTION_CLOSE with the given
QuicCloseOptions. The returned promise resolves when the connection has
fully closed. Idempotent — repeat calls return the in-flight close promise.
Use destroy() to close immediately without draining.
await conn.close({ errorCode: 0, reason: 'done' });
destroy(error?: Error, options: QuicCloseOptions = {}): void
Immediately tear down the connection.
Sends a CONNECTION_CLOSE where possible and transitions straight to closed
without waiting for streams to drain. When error is given it is attached
as the connection's close cause and surfaces on the 'error' event. This is
the abrupt counterpart to close().
conn.destroy(new Error('protocol violation'), { errorCode: 1, type: 'application' });
addEventListener(type: string, callback: any, options?: any): void
Setters
set [quicIncomingStreamHook](hook: ((stream: QuicStream) => void) | null)
Types
type QuicConnectionOptions = {
handshakeTimeoutMs?: number;
initialRttMs?: number;
keepAliveTimeoutMs?: number;
maxPayloadSize?: number;
maxWindow?: number;
maxStreamWindow?: number;
unacknowledgedPacketThreshold?: number;
congestionControl?: 'cubic' | 'reno' | 'bbr';
drainingPeriodMultiplier?: number;
streamIdleTimeoutMs?: number;
maxIdleTimeoutMs?: number;
initialMaxData?: number;
initialMaxStreamDataBidiLocal?: number;
initialMaxStreamDataBidiRemote?: number;
initialMaxStreamDataUni?: number;
initialMaxStreamsBidi?: number;
initialMaxStreamsUni?: number;
activeConnectionIdLimit?: number;
maxAckDelayMs?: number;
ackDelayExponent?: number;
disableActiveMigration?: boolean;
cidLength?: number;
}
Per-connection QUIC transport tuning.
type QuicConnectionState = 'connecting' | 'connected' | 'closing' | 'closed'
type QuicConnectionStats = {
readonly createdAt: number;
readonly connectedAt: number | null;
readonly handshakeConfirmedAt: number | null;
readonly closingAt: number | null;
readonly destroyedAt: number | null;
readonly bytesReceived: number;
readonly bytesSent: number;
readonly packetsReceived: number;
readonly packetsSent: number;
readonly datagramsReceived: number;
readonly datagramsSent: number;
readonly datagramsAcked: number;
readonly datagramsLost: number;
readonly datagramsAbandoned: number;
readonly streamsOpened: number;
readonly streamsReceived: number;
readonly bidiIncomingStreams: number;
readonly bidiOutgoingStreams: number;
readonly uniIncomingStreams: number;
readonly uniOutgoingStreams: number;
readonly streamsClosed: number;
readonly maxBytesInFlight: number;
readonly bytesInFlight: number;
readonly blockCount: number;
readonly congestionWindow: number;
readonly latestRttMs: number;
readonly minRttMs: number;
readonly rttVarianceMs: number;
readonly smoothedRttMs: number;
readonly slowStartThreshold: number;
readonly packetsLost: number;
readonly bytesLost: number;
readonly pingReceived: number;
readonly packetsDiscarded: number;
readonly streamsIdleTimedOut: number;
readonly pendingWriteBytes: number;
readonly outstandingStreamBytes: number;
readonly qlogOpenFailed: number;
readonly qlogWriteFailed: number;
}
Per-connection counters and recovery metrics, read from QuicConnection.stats.
Each read returns a frozen snapshot refreshed from ngtcp2's live connection
info, so the RTT, congestion-window, bytes-in-flight, and loss fields track
the current recovery state rather than lifetime totals. Timestamps are
millisecond epochs and are null until the corresponding milestone occurs
(connectedAt on handshake completion, closingAt/destroyedAt on close).
Stream and datagram counts are lifetime totals for the connection.
type QuicPeerVerification = {
verified: boolean;
errorCode: number;
reason: string | null;
}
Outcome of TLS certificate verification for the connected peer.
Read from QuicConnection.peerVerification after the handshake. verified
is true when the peer presented a certificate that passed verification;
otherwise errorCode/reason describe the verification failure.
type QuicTransportParameterSnapshot = {
readonly initialMaxStreamDataBidiLocal: number;
readonly initialMaxStreamDataBidiRemote: number;
readonly initialMaxStreamDataUni: number;
readonly initialMaxData: number;
readonly initialMaxStreamsBidi: number;
readonly initialMaxStreamsUni: number;
readonly maxIdleTimeoutMs: number;
readonly maxUdpPayloadSize: number;
readonly activeConnectionIdLimit: number;
readonly ackDelayExponent: number;
readonly maxAckDelayMs: number;
readonly maxDatagramFrameSize: number;
readonly disableActiveMigration: boolean;
readonly preferredAddress: QuicAddress | null;
readonly originalDestinationConnectionId: Uint8Array | null;
readonly initialSourceConnectionId: Uint8Array | null;
readonly retrySourceConnectionId: Uint8Array | null;
readonly statelessResetToken: Uint8Array | null;
}
Decoded QUIC transport parameters for one side of a connection.
Returned by QuicConnection.localTransportParameters and
.remoteTransportParameters (both null before the native connection
exists). It exposes the negotiated flow-control limits, idle timeout, ACK
tuning, migration flag, advertised preferred address, and the connection-ID
fields QUIC uses to bind the handshake — including the byte-array CIDs, each
null when the peer did not send it.