2024-12-31 14:12:39 -05:00
|
|
|
// Copyright 2018-2025 the Deno authors. MIT license.
|
2024-12-20 07:48:48 -05:00
|
|
|
import { core, primordials } from "ext:core/mod.js";
|
|
|
|
import {
|
2025-01-06 09:24:59 -05:00
|
|
|
op_quic_connecting_0rtt,
|
|
|
|
op_quic_connecting_1rtt,
|
|
|
|
op_quic_connection_accept_bi,
|
|
|
|
op_quic_connection_accept_uni,
|
|
|
|
op_quic_connection_close,
|
2024-12-20 07:48:48 -05:00
|
|
|
op_quic_connection_closed,
|
2025-01-06 09:24:59 -05:00
|
|
|
op_quic_connection_get_max_datagram_size,
|
2024-12-20 07:48:48 -05:00
|
|
|
op_quic_connection_get_protocol,
|
|
|
|
op_quic_connection_get_remote_addr,
|
2025-01-06 09:24:59 -05:00
|
|
|
op_quic_connection_get_server_name,
|
|
|
|
op_quic_connection_handshake,
|
|
|
|
op_quic_connection_open_bi,
|
|
|
|
op_quic_connection_open_uni,
|
|
|
|
op_quic_connection_read_datagram,
|
|
|
|
op_quic_connection_send_datagram,
|
|
|
|
op_quic_endpoint_close,
|
|
|
|
op_quic_endpoint_connect,
|
|
|
|
op_quic_endpoint_create,
|
2024-12-20 07:48:48 -05:00
|
|
|
op_quic_endpoint_get_addr,
|
2025-01-06 09:24:59 -05:00
|
|
|
op_quic_endpoint_listen,
|
2024-12-20 07:48:48 -05:00
|
|
|
op_quic_incoming_accept,
|
2025-01-06 09:24:59 -05:00
|
|
|
op_quic_incoming_accept_0rtt,
|
2024-12-20 07:48:48 -05:00
|
|
|
op_quic_incoming_ignore,
|
|
|
|
op_quic_incoming_local_ip,
|
|
|
|
op_quic_incoming_refuse,
|
|
|
|
op_quic_incoming_remote_addr,
|
|
|
|
op_quic_incoming_remote_addr_validated,
|
2025-01-06 09:24:59 -05:00
|
|
|
op_quic_listener_accept,
|
|
|
|
op_quic_listener_stop,
|
|
|
|
op_quic_recv_stream_get_id,
|
|
|
|
op_quic_send_stream_get_id,
|
|
|
|
op_quic_send_stream_get_priority,
|
|
|
|
op_quic_send_stream_set_priority,
|
2024-12-20 07:48:48 -05:00
|
|
|
} from "ext:core/ops";
|
|
|
|
import {
|
2025-01-06 09:24:59 -05:00
|
|
|
getReadableStreamResourceBacking,
|
2024-12-20 07:48:48 -05:00
|
|
|
getWritableStreamResourceBacking,
|
|
|
|
ReadableStream,
|
|
|
|
readableStreamForRid,
|
|
|
|
WritableStream,
|
|
|
|
writableStreamForRid,
|
|
|
|
} from "ext:deno_web/06_streams.js";
|
|
|
|
import { loadTlsKeyPair } from "ext:deno_net/02_tls.js";
|
|
|
|
const {
|
|
|
|
BadResourcePrototype,
|
|
|
|
} = core;
|
|
|
|
const {
|
2025-01-06 09:24:59 -05:00
|
|
|
ObjectPrototypeIsPrototypeOf,
|
|
|
|
PromisePrototypeThen,
|
|
|
|
Symbol,
|
2024-12-20 07:48:48 -05:00
|
|
|
SymbolAsyncIterator,
|
|
|
|
SafePromisePrototypeFinally,
|
|
|
|
} = primordials;
|
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
let getEndpointResource;
|
|
|
|
|
|
|
|
function transportOptions({
|
|
|
|
keepAliveInterval,
|
|
|
|
maxIdleTimeout,
|
|
|
|
maxConcurrentBidirectionalStreams,
|
|
|
|
maxConcurrentUnidirectionalStreams,
|
|
|
|
preferredAddressV4,
|
|
|
|
preferredAddressV6,
|
|
|
|
congestionControl,
|
|
|
|
}) {
|
|
|
|
return {
|
|
|
|
keepAliveInterval,
|
|
|
|
maxIdleTimeout,
|
|
|
|
maxConcurrentBidirectionalStreams,
|
|
|
|
maxConcurrentUnidirectionalStreams,
|
|
|
|
preferredAddressV4,
|
|
|
|
preferredAddressV6,
|
|
|
|
congestionControl,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const kRid = Symbol("rid");
|
|
|
|
|
|
|
|
class QuicEndpoint {
|
|
|
|
#endpoint;
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
{ hostname = "::", port = 0, [kRid]: rid } = { __proto__: null },
|
|
|
|
) {
|
|
|
|
this.#endpoint = rid ?? op_quic_endpoint_create({ hostname, port }, true);
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
get addr() {
|
|
|
|
return op_quic_endpoint_get_addr(this.#endpoint);
|
|
|
|
}
|
|
|
|
|
|
|
|
listen(options) {
|
|
|
|
const keyPair = loadTlsKeyPair("Deno.QuicEndpoint.listen", {
|
|
|
|
cert: options.cert,
|
|
|
|
key: options.key,
|
|
|
|
});
|
|
|
|
const listener = op_quic_endpoint_listen(
|
|
|
|
this.#endpoint,
|
|
|
|
{ alpnProtocols: options.alpnProtocols },
|
|
|
|
transportOptions(options),
|
|
|
|
keyPair,
|
2024-12-20 07:48:48 -05:00
|
|
|
);
|
2025-01-06 09:24:59 -05:00
|
|
|
return new QuicListener(listener, this);
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
close({ closeCode = 0, reason = "" } = { __proto__: null }) {
|
|
|
|
op_quic_endpoint_close(this.#endpoint, closeCode, reason);
|
|
|
|
}
|
2024-12-20 07:48:48 -05:00
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
static {
|
|
|
|
getEndpointResource = (e) => e.#endpoint;
|
|
|
|
}
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
class QuicListener {
|
|
|
|
#listener;
|
|
|
|
#endpoint;
|
2024-12-20 07:48:48 -05:00
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
constructor(listener, endpoint) {
|
|
|
|
this.#listener = listener;
|
|
|
|
this.#endpoint = endpoint;
|
|
|
|
}
|
2024-12-20 07:48:48 -05:00
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
get endpoint() {
|
|
|
|
return this.#endpoint;
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
async incoming() {
|
|
|
|
const incoming = await op_quic_listener_accept(this.#listener);
|
|
|
|
return new QuicIncoming(incoming, this.#endpoint);
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
async accept() {
|
|
|
|
const incoming = await this.incoming();
|
|
|
|
const connection = await incoming.accept();
|
|
|
|
return connection;
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
async next() {
|
|
|
|
try {
|
|
|
|
const connection = await this.accept();
|
|
|
|
return { value: connection, done: false };
|
|
|
|
} catch (error) {
|
|
|
|
if (ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error)) {
|
|
|
|
return { value: undefined, done: true };
|
|
|
|
}
|
|
|
|
throw error;
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
2025-01-06 09:24:59 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
[SymbolAsyncIterator]() {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
stop() {
|
|
|
|
op_quic_listener_stop(this.#listener);
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
class QuicIncoming {
|
|
|
|
#incoming;
|
|
|
|
#endpoint;
|
|
|
|
|
|
|
|
constructor(incoming, endpoint) {
|
|
|
|
this.#incoming = incoming;
|
|
|
|
this.#endpoint = endpoint;
|
|
|
|
}
|
|
|
|
|
|
|
|
get localIp() {
|
|
|
|
return op_quic_incoming_local_ip(this.#incoming);
|
|
|
|
}
|
|
|
|
|
|
|
|
get remoteAddr() {
|
|
|
|
return op_quic_incoming_remote_addr(this.#incoming);
|
|
|
|
}
|
|
|
|
|
|
|
|
get remoteAddressValidated() {
|
|
|
|
return op_quic_incoming_remote_addr_validated(this.#incoming);
|
|
|
|
}
|
|
|
|
|
|
|
|
accept(options) {
|
|
|
|
const tOptions = options ? transportOptions(options) : null;
|
|
|
|
if (options?.zeroRtt) {
|
|
|
|
const conn = op_quic_incoming_accept_0rtt(
|
|
|
|
this.#incoming,
|
|
|
|
tOptions,
|
|
|
|
);
|
|
|
|
return new QuicConn(conn, this.#endpoint);
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
2025-01-06 09:24:59 -05:00
|
|
|
return PromisePrototypeThen(
|
|
|
|
op_quic_incoming_accept(this.#incoming, tOptions),
|
|
|
|
(conn) => new QuicConn(conn, this.#endpoint),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
refuse() {
|
|
|
|
op_quic_incoming_refuse(this.#incoming);
|
|
|
|
}
|
|
|
|
|
|
|
|
ignore() {
|
|
|
|
op_quic_incoming_ignore(this.#incoming);
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class QuicConn {
|
|
|
|
#resource;
|
|
|
|
#bidiStream = null;
|
|
|
|
#uniStream = null;
|
|
|
|
#closed;
|
2025-01-06 09:24:59 -05:00
|
|
|
#handshake;
|
|
|
|
#endpoint;
|
2024-12-20 07:48:48 -05:00
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
constructor(resource, endpoint) {
|
2024-12-20 07:48:48 -05:00
|
|
|
this.#resource = resource;
|
2025-01-06 09:24:59 -05:00
|
|
|
this.#endpoint = endpoint;
|
2024-12-20 07:48:48 -05:00
|
|
|
|
|
|
|
this.#closed = op_quic_connection_closed(this.#resource);
|
|
|
|
core.unrefOpPromise(this.#closed);
|
|
|
|
}
|
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
get endpoint() {
|
|
|
|
return this.#endpoint;
|
|
|
|
}
|
|
|
|
|
2024-12-20 07:48:48 -05:00
|
|
|
get protocol() {
|
|
|
|
return op_quic_connection_get_protocol(this.#resource);
|
|
|
|
}
|
|
|
|
|
|
|
|
get remoteAddr() {
|
|
|
|
return op_quic_connection_get_remote_addr(this.#resource);
|
|
|
|
}
|
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
get serverName() {
|
|
|
|
return op_quic_connection_get_server_name(this.#resource);
|
|
|
|
}
|
|
|
|
|
2024-12-20 07:48:48 -05:00
|
|
|
async createBidirectionalStream(
|
|
|
|
{ sendOrder, waitUntilAvailable } = { __proto__: null },
|
|
|
|
) {
|
2025-01-06 09:24:59 -05:00
|
|
|
const { 0: txRid, 1: rxRid } = await op_quic_connection_open_bi(
|
2024-12-20 07:48:48 -05:00
|
|
|
this.#resource,
|
|
|
|
waitUntilAvailable ?? false,
|
|
|
|
);
|
|
|
|
if (sendOrder !== null && sendOrder !== undefined) {
|
2025-01-06 09:24:59 -05:00
|
|
|
op_quic_send_stream_set_priority(txRid, sendOrder);
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
|
|
|
return new QuicBidirectionalStream(txRid, rxRid, this.#closed);
|
|
|
|
}
|
|
|
|
|
|
|
|
async createUnidirectionalStream(
|
|
|
|
{ sendOrder, waitUntilAvailable } = { __proto__: null },
|
|
|
|
) {
|
2025-01-06 09:24:59 -05:00
|
|
|
const rid = await op_quic_connection_open_uni(
|
2024-12-20 07:48:48 -05:00
|
|
|
this.#resource,
|
|
|
|
waitUntilAvailable ?? false,
|
|
|
|
);
|
|
|
|
if (sendOrder !== null && sendOrder !== undefined) {
|
2025-01-06 09:24:59 -05:00
|
|
|
op_quic_send_stream_set_priority(rid, sendOrder);
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
|
|
|
return writableStream(rid, this.#closed);
|
|
|
|
}
|
|
|
|
|
|
|
|
get incomingBidirectionalStreams() {
|
|
|
|
if (this.#bidiStream === null) {
|
|
|
|
this.#bidiStream = ReadableStream.from(
|
|
|
|
bidiStream(this.#resource, this.#closed),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return this.#bidiStream;
|
|
|
|
}
|
|
|
|
|
|
|
|
get incomingUnidirectionalStreams() {
|
|
|
|
if (this.#uniStream === null) {
|
|
|
|
this.#uniStream = ReadableStream.from(
|
|
|
|
uniStream(this.#resource, this.#closed),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return this.#uniStream;
|
|
|
|
}
|
|
|
|
|
|
|
|
get maxDatagramSize() {
|
2025-01-06 09:24:59 -05:00
|
|
|
return op_quic_connection_get_max_datagram_size(this.#resource);
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
async readDatagram() {
|
|
|
|
const buffer = await op_quic_connection_read_datagram(this.#resource);
|
|
|
|
return buffer;
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
async sendDatagram(data) {
|
2025-01-06 09:24:59 -05:00
|
|
|
await op_quic_connection_send_datagram(this.#resource, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
get handshake() {
|
|
|
|
if (!this.#handshake) {
|
|
|
|
this.#handshake = op_quic_connection_handshake(this.#resource);
|
|
|
|
}
|
|
|
|
return this.#handshake;
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
get closed() {
|
|
|
|
core.refOpPromise(this.#closed);
|
|
|
|
return this.#closed;
|
|
|
|
}
|
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
close({ closeCode = 0, reason = "" } = { __proto__: null }) {
|
|
|
|
op_quic_connection_close(this.#resource, closeCode, reason);
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
class QuicSendStream extends WritableStream {
|
|
|
|
get sendOrder() {
|
|
|
|
return op_quic_send_stream_get_priority(
|
|
|
|
getWritableStreamResourceBacking(this).rid,
|
|
|
|
);
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
set sendOrder(p) {
|
|
|
|
op_quic_send_stream_set_priority(
|
|
|
|
getWritableStreamResourceBacking(this).rid,
|
|
|
|
p,
|
|
|
|
);
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
get id() {
|
|
|
|
return op_quic_send_stream_get_id(
|
|
|
|
getWritableStreamResourceBacking(this).rid,
|
|
|
|
);
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
2025-01-06 09:24:59 -05:00
|
|
|
}
|
2024-12-20 07:48:48 -05:00
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
class QuicReceiveStream extends ReadableStream {
|
|
|
|
get id() {
|
|
|
|
return op_quic_recv_stream_get_id(
|
|
|
|
getReadableStreamResourceBacking(this).rid,
|
|
|
|
);
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
2025-01-06 09:24:59 -05:00
|
|
|
}
|
2024-12-20 07:48:48 -05:00
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
function readableStream(rid, closed) {
|
|
|
|
// stream can be indirectly closed by closing connection.
|
|
|
|
SafePromisePrototypeFinally(closed, () => {
|
|
|
|
core.tryClose(rid);
|
|
|
|
});
|
|
|
|
return readableStreamForRid(rid, true, QuicReceiveStream);
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
function writableStream(rid, closed) {
|
|
|
|
// stream can be indirectly closed by closing connection.
|
|
|
|
SafePromisePrototypeFinally(closed, () => {
|
|
|
|
core.tryClose(rid);
|
|
|
|
});
|
|
|
|
return writableStreamForRid(rid, true, QuicSendStream);
|
|
|
|
}
|
2024-12-20 07:48:48 -05:00
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
class QuicBidirectionalStream {
|
|
|
|
#readable;
|
|
|
|
#writable;
|
2024-12-20 07:48:48 -05:00
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
constructor(txRid, rxRid, closed) {
|
|
|
|
this.#readable = readableStream(rxRid, closed);
|
|
|
|
this.#writable = writableStream(txRid, closed);
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
get readable() {
|
|
|
|
return this.#readable;
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
get writable() {
|
|
|
|
return this.#writable;
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
2025-01-06 09:24:59 -05:00
|
|
|
}
|
2024-12-20 07:48:48 -05:00
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
async function* bidiStream(conn, closed) {
|
|
|
|
try {
|
|
|
|
while (true) {
|
|
|
|
const r = await op_quic_connection_accept_bi(conn);
|
|
|
|
yield new QuicBidirectionalStream(r[0], r[1], closed);
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
2025-01-06 09:24:59 -05:00
|
|
|
} catch (error) {
|
|
|
|
if (ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
throw error;
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
2025-01-06 09:24:59 -05:00
|
|
|
}
|
2024-12-20 07:48:48 -05:00
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
async function* uniStream(conn, closed) {
|
|
|
|
try {
|
|
|
|
while (true) {
|
|
|
|
const uniRid = await op_quic_connection_accept_uni(conn);
|
|
|
|
yield readableStream(uniRid, closed);
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
if (ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
throw error;
|
2024-12-20 07:48:48 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
function connectQuic(options) {
|
|
|
|
const endpoint = options.endpoint ??
|
|
|
|
new QuicEndpoint({
|
|
|
|
[kRid]: op_quic_endpoint_create({ hostname: "::", port: 0 }, 0, false),
|
|
|
|
});
|
|
|
|
const keyPair = loadTlsKeyPair("Deno.connectQuic", {
|
|
|
|
cert: options.cert,
|
|
|
|
key: options.key,
|
|
|
|
});
|
|
|
|
const connecting = op_quic_endpoint_connect(
|
|
|
|
getEndpointResource(endpoint),
|
2024-12-20 07:48:48 -05:00
|
|
|
{
|
2025-01-06 09:24:59 -05:00
|
|
|
addr: {
|
|
|
|
hostname: options.hostname,
|
|
|
|
port: options.port,
|
|
|
|
},
|
|
|
|
caCerts: options.caCerts,
|
|
|
|
alpnProtocols: options.alpnProtocols,
|
|
|
|
serverName: options.serverName,
|
2024-12-20 07:48:48 -05:00
|
|
|
},
|
2025-01-06 09:24:59 -05:00
|
|
|
transportOptions(options),
|
2024-12-20 07:48:48 -05:00
|
|
|
keyPair,
|
|
|
|
);
|
|
|
|
|
2025-01-06 09:24:59 -05:00
|
|
|
if (options.zeroRtt) {
|
|
|
|
const conn = op_quic_connecting_0rtt(connecting);
|
|
|
|
if (conn) {
|
|
|
|
return new QuicConn(conn, endpoint);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return PromisePrototypeThen(
|
|
|
|
op_quic_connecting_1rtt(connecting),
|
|
|
|
(conn) => new QuicConn(conn, endpoint),
|
2024-12-20 07:48:48 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export {
|
|
|
|
connectQuic,
|
|
|
|
QuicBidirectionalStream,
|
|
|
|
QuicConn,
|
2025-01-06 09:24:59 -05:00
|
|
|
QuicEndpoint,
|
2024-12-20 07:48:48 -05:00
|
|
|
QuicIncoming,
|
|
|
|
QuicListener,
|
|
|
|
QuicReceiveStream,
|
|
|
|
QuicSendStream,
|
|
|
|
};
|