mirror of
https://github.com/denoland/deno.git
synced 2025-01-21 04:52:26 -05:00
Merge remote-tracking branch 'upstream/main' into check-workspace-member-compiler-options
This commit is contained in:
commit
2eae9a99ed
25 changed files with 1162 additions and 777 deletions
2
.github/workflows/ci.generate.ts
vendored
2
.github/workflows/ci.generate.ts
vendored
|
@ -360,7 +360,7 @@ const ci = {
|
|||
needs: ["pre_build"],
|
||||
if: "${{ needs.pre_build.outputs.skip_build != 'true' }}",
|
||||
"runs-on": "${{ matrix.runner }}",
|
||||
"timeout-minutes": 180,
|
||||
"timeout-minutes": 240,
|
||||
defaults: {
|
||||
run: {
|
||||
// GH actions does not fail fast by default on
|
||||
|
|
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -48,7 +48,7 @@ jobs:
|
|||
- pre_build
|
||||
if: '${{ needs.pre_build.outputs.skip_build != ''true'' }}'
|
||||
runs-on: '${{ matrix.runner }}'
|
||||
timeout-minutes: 180
|
||||
timeout-minutes: 240
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
|
11
cli/tsc/dts/lib.deno.unstable.d.ts
vendored
11
cli/tsc/dts/lib.deno.unstable.d.ts
vendored
|
@ -1268,16 +1268,15 @@ declare namespace Deno {
|
|||
* OpenTelemetry API. This is done using the official OpenTelemetry package
|
||||
* for JavaScript:
|
||||
* [`npm:@opentelemetry/api`](https://opentelemetry.io/docs/languages/js/).
|
||||
* Deno integrates with this package to provide trace context propagation
|
||||
* between native Deno APIs (like `Deno.serve` or `fetch`) and custom user
|
||||
* code. Deno also provides APIs that allow exporting custom telemetry data
|
||||
* via the same OTLP channel used by the Deno runtime. This is done using the
|
||||
* [`jsr:@deno/otel`](https://jsr.io/@deno/otel) package.
|
||||
* Deno integrates with this package to provide tracing, metrics, and trace
|
||||
* context propagation between native Deno APIs (like `Deno.serve` or `fetch`)
|
||||
* and custom user code. Deno automatically registers the providers with the
|
||||
* OpenTelemetry API, so users can start creating custom traces, metrics, and
|
||||
* logs without any additional setup.
|
||||
*
|
||||
* @example Using OpenTelemetry API to create custom traces
|
||||
* ```ts,ignore
|
||||
* import { trace } from "npm:@opentelemetry/api@1";
|
||||
* import "jsr:@deno/otel@0.0.2/register";
|
||||
*
|
||||
* const tracer = trace.getTracer("example-tracer");
|
||||
*
|
||||
|
|
|
@ -1,33 +1,42 @@
|
|||
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||
import { core, primordials } from "ext:core/mod.js";
|
||||
import {
|
||||
op_quic_accept,
|
||||
op_quic_accept_bi,
|
||||
op_quic_accept_incoming,
|
||||
op_quic_accept_uni,
|
||||
op_quic_close_connection,
|
||||
op_quic_close_endpoint,
|
||||
op_quic_connect,
|
||||
op_quic_connecting_0rtt,
|
||||
op_quic_connecting_1rtt,
|
||||
op_quic_connection_accept_bi,
|
||||
op_quic_connection_accept_uni,
|
||||
op_quic_connection_close,
|
||||
op_quic_connection_closed,
|
||||
op_quic_connection_get_max_datagram_size,
|
||||
op_quic_connection_get_protocol,
|
||||
op_quic_connection_get_remote_addr,
|
||||
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,
|
||||
op_quic_endpoint_get_addr,
|
||||
op_quic_get_send_stream_priority,
|
||||
op_quic_endpoint_listen,
|
||||
op_quic_incoming_accept,
|
||||
op_quic_incoming_accept_0rtt,
|
||||
op_quic_incoming_ignore,
|
||||
op_quic_incoming_local_ip,
|
||||
op_quic_incoming_refuse,
|
||||
op_quic_incoming_remote_addr,
|
||||
op_quic_incoming_remote_addr_validated,
|
||||
op_quic_listen,
|
||||
op_quic_max_datagram_size,
|
||||
op_quic_open_bi,
|
||||
op_quic_open_uni,
|
||||
op_quic_read_datagram,
|
||||
op_quic_send_datagram,
|
||||
op_quic_set_send_stream_priority,
|
||||
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,
|
||||
} from "ext:core/ops";
|
||||
import {
|
||||
getReadableStreamResourceBacking,
|
||||
getWritableStreamResourceBacking,
|
||||
ReadableStream,
|
||||
readableStreamForRid,
|
||||
|
@ -39,29 +48,297 @@ const {
|
|||
BadResourcePrototype,
|
||||
} = core;
|
||||
const {
|
||||
Uint8Array,
|
||||
TypedArrayPrototypeSubarray,
|
||||
ObjectPrototypeIsPrototypeOf,
|
||||
PromisePrototypeThen,
|
||||
Symbol,
|
||||
SymbolAsyncIterator,
|
||||
SafePromisePrototypeFinally,
|
||||
ObjectPrototypeIsPrototypeOf,
|
||||
} = primordials;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
return new QuicListener(listener, this);
|
||||
}
|
||||
|
||||
close({ closeCode = 0, reason = "" } = { __proto__: null }) {
|
||||
op_quic_endpoint_close(this.#endpoint, closeCode, reason);
|
||||
}
|
||||
|
||||
static {
|
||||
getEndpointResource = (e) => e.#endpoint;
|
||||
}
|
||||
}
|
||||
|
||||
class QuicListener {
|
||||
#listener;
|
||||
#endpoint;
|
||||
|
||||
constructor(listener, endpoint) {
|
||||
this.#listener = listener;
|
||||
this.#endpoint = endpoint;
|
||||
}
|
||||
|
||||
get endpoint() {
|
||||
return this.#endpoint;
|
||||
}
|
||||
|
||||
async incoming() {
|
||||
const incoming = await op_quic_listener_accept(this.#listener);
|
||||
return new QuicIncoming(incoming, this.#endpoint);
|
||||
}
|
||||
|
||||
async accept() {
|
||||
const incoming = await this.incoming();
|
||||
const connection = await incoming.accept();
|
||||
return connection;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
[SymbolAsyncIterator]() {
|
||||
return this;
|
||||
}
|
||||
|
||||
stop() {
|
||||
op_quic_listener_stop(this.#listener);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
class QuicConn {
|
||||
#resource;
|
||||
#bidiStream = null;
|
||||
#uniStream = null;
|
||||
#closed;
|
||||
#handshake;
|
||||
#endpoint;
|
||||
|
||||
constructor(resource, endpoint) {
|
||||
this.#resource = resource;
|
||||
this.#endpoint = endpoint;
|
||||
|
||||
this.#closed = op_quic_connection_closed(this.#resource);
|
||||
core.unrefOpPromise(this.#closed);
|
||||
}
|
||||
|
||||
get endpoint() {
|
||||
return this.#endpoint;
|
||||
}
|
||||
|
||||
get protocol() {
|
||||
return op_quic_connection_get_protocol(this.#resource);
|
||||
}
|
||||
|
||||
get remoteAddr() {
|
||||
return op_quic_connection_get_remote_addr(this.#resource);
|
||||
}
|
||||
|
||||
get serverName() {
|
||||
return op_quic_connection_get_server_name(this.#resource);
|
||||
}
|
||||
|
||||
async createBidirectionalStream(
|
||||
{ sendOrder, waitUntilAvailable } = { __proto__: null },
|
||||
) {
|
||||
const { 0: txRid, 1: rxRid } = await op_quic_connection_open_bi(
|
||||
this.#resource,
|
||||
waitUntilAvailable ?? false,
|
||||
);
|
||||
if (sendOrder !== null && sendOrder !== undefined) {
|
||||
op_quic_send_stream_set_priority(txRid, sendOrder);
|
||||
}
|
||||
return new QuicBidirectionalStream(txRid, rxRid, this.#closed);
|
||||
}
|
||||
|
||||
async createUnidirectionalStream(
|
||||
{ sendOrder, waitUntilAvailable } = { __proto__: null },
|
||||
) {
|
||||
const rid = await op_quic_connection_open_uni(
|
||||
this.#resource,
|
||||
waitUntilAvailable ?? false,
|
||||
);
|
||||
if (sendOrder !== null && sendOrder !== undefined) {
|
||||
op_quic_send_stream_set_priority(rid, sendOrder);
|
||||
}
|
||||
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() {
|
||||
return op_quic_connection_get_max_datagram_size(this.#resource);
|
||||
}
|
||||
|
||||
async readDatagram() {
|
||||
const buffer = await op_quic_connection_read_datagram(this.#resource);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
async sendDatagram(data) {
|
||||
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;
|
||||
}
|
||||
|
||||
get closed() {
|
||||
core.refOpPromise(this.#closed);
|
||||
return this.#closed;
|
||||
}
|
||||
|
||||
close({ closeCode = 0, reason = "" } = { __proto__: null }) {
|
||||
op_quic_connection_close(this.#resource, closeCode, reason);
|
||||
}
|
||||
}
|
||||
|
||||
class QuicSendStream extends WritableStream {
|
||||
get sendOrder() {
|
||||
return op_quic_get_send_stream_priority(
|
||||
return op_quic_send_stream_get_priority(
|
||||
getWritableStreamResourceBacking(this).rid,
|
||||
);
|
||||
}
|
||||
|
||||
set sendOrder(p) {
|
||||
op_quic_set_send_stream_priority(
|
||||
op_quic_send_stream_set_priority(
|
||||
getWritableStreamResourceBacking(this).rid,
|
||||
p,
|
||||
);
|
||||
}
|
||||
|
||||
get id() {
|
||||
return op_quic_send_stream_get_id(
|
||||
getWritableStreamResourceBacking(this).rid,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class QuicReceiveStream extends ReadableStream {}
|
||||
class QuicReceiveStream extends ReadableStream {
|
||||
get id() {
|
||||
return op_quic_recv_stream_get_id(
|
||||
getReadableStreamResourceBacking(this).rid,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function readableStream(rid, closed) {
|
||||
// stream can be indirectly closed by closing connection.
|
||||
|
@ -100,7 +377,7 @@ class QuicBidirectionalStream {
|
|||
async function* bidiStream(conn, closed) {
|
||||
try {
|
||||
while (true) {
|
||||
const r = await op_quic_accept_bi(conn);
|
||||
const r = await op_quic_connection_accept_bi(conn);
|
||||
yield new QuicBidirectionalStream(r[0], r[1], closed);
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -114,7 +391,7 @@ async function* bidiStream(conn, closed) {
|
|||
async function* uniStream(conn, closed) {
|
||||
try {
|
||||
while (true) {
|
||||
const uniRid = await op_quic_accept_uni(conn);
|
||||
const uniRid = await op_quic_connection_accept_uni(conn);
|
||||
yield readableStream(uniRid, closed);
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -125,241 +402,48 @@ async function* uniStream(conn, closed) {
|
|||
}
|
||||
}
|
||||
|
||||
class QuicConn {
|
||||
#resource;
|
||||
#bidiStream = null;
|
||||
#uniStream = null;
|
||||
#closed;
|
||||
|
||||
constructor(resource) {
|
||||
this.#resource = resource;
|
||||
|
||||
this.#closed = op_quic_connection_closed(this.#resource);
|
||||
core.unrefOpPromise(this.#closed);
|
||||
}
|
||||
|
||||
get protocol() {
|
||||
return op_quic_connection_get_protocol(this.#resource);
|
||||
}
|
||||
|
||||
get remoteAddr() {
|
||||
return op_quic_connection_get_remote_addr(this.#resource);
|
||||
}
|
||||
|
||||
async createBidirectionalStream(
|
||||
{ sendOrder, waitUntilAvailable } = { __proto__: null },
|
||||
) {
|
||||
const { 0: txRid, 1: rxRid } = await op_quic_open_bi(
|
||||
this.#resource,
|
||||
waitUntilAvailable ?? false,
|
||||
);
|
||||
if (sendOrder !== null && sendOrder !== undefined) {
|
||||
op_quic_set_send_stream_priority(txRid, sendOrder);
|
||||
}
|
||||
return new QuicBidirectionalStream(txRid, rxRid, this.#closed);
|
||||
}
|
||||
|
||||
async createUnidirectionalStream(
|
||||
{ sendOrder, waitUntilAvailable } = { __proto__: null },
|
||||
) {
|
||||
const rid = await op_quic_open_uni(
|
||||
this.#resource,
|
||||
waitUntilAvailable ?? false,
|
||||
);
|
||||
if (sendOrder !== null && sendOrder !== undefined) {
|
||||
op_quic_set_send_stream_priority(rid, sendOrder);
|
||||
}
|
||||
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() {
|
||||
return op_quic_max_datagram_size(this.#resource);
|
||||
}
|
||||
|
||||
async readDatagram(p) {
|
||||
const view = p || new Uint8Array(this.maxDatagramSize);
|
||||
const nread = await op_quic_read_datagram(this.#resource, view);
|
||||
return TypedArrayPrototypeSubarray(view, 0, nread);
|
||||
}
|
||||
|
||||
async sendDatagram(data) {
|
||||
await op_quic_send_datagram(this.#resource, data);
|
||||
}
|
||||
|
||||
get closed() {
|
||||
core.refOpPromise(this.#closed);
|
||||
return this.#closed;
|
||||
}
|
||||
|
||||
close({ closeCode, reason }) {
|
||||
op_quic_close_connection(this.#resource, closeCode, reason);
|
||||
}
|
||||
}
|
||||
|
||||
class QuicIncoming {
|
||||
#incoming;
|
||||
|
||||
constructor(incoming) {
|
||||
this.#incoming = incoming;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
async accept() {
|
||||
const conn = await op_quic_incoming_accept(this.#incoming);
|
||||
return new QuicConn(conn);
|
||||
}
|
||||
|
||||
refuse() {
|
||||
op_quic_incoming_refuse(this.#incoming);
|
||||
}
|
||||
|
||||
ignore() {
|
||||
op_quic_incoming_ignore(this.#incoming);
|
||||
}
|
||||
}
|
||||
|
||||
class QuicListener {
|
||||
#endpoint;
|
||||
|
||||
constructor(endpoint) {
|
||||
this.#endpoint = endpoint;
|
||||
}
|
||||
|
||||
get addr() {
|
||||
return op_quic_endpoint_get_addr(this.#endpoint);
|
||||
}
|
||||
|
||||
async accept() {
|
||||
const conn = await op_quic_accept(this.#endpoint);
|
||||
return new QuicConn(conn);
|
||||
}
|
||||
|
||||
async incoming() {
|
||||
const incoming = await op_quic_accept_incoming(this.#endpoint);
|
||||
return new QuicIncoming(incoming);
|
||||
}
|
||||
|
||||
async next() {
|
||||
let conn;
|
||||
try {
|
||||
conn = await this.accept();
|
||||
} catch (error) {
|
||||
if (ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error)) {
|
||||
return { value: undefined, done: true };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return { value: conn, done: false };
|
||||
}
|
||||
|
||||
[SymbolAsyncIterator]() {
|
||||
return this;
|
||||
}
|
||||
|
||||
close({ closeCode, reason }) {
|
||||
op_quic_close_endpoint(this.#endpoint, closeCode, reason);
|
||||
}
|
||||
}
|
||||
|
||||
async function listenQuic(
|
||||
{
|
||||
hostname,
|
||||
port,
|
||||
cert,
|
||||
key,
|
||||
alpnProtocols,
|
||||
keepAliveInterval,
|
||||
maxIdleTimeout,
|
||||
maxConcurrentBidirectionalStreams,
|
||||
maxConcurrentUnidirectionalStreams,
|
||||
},
|
||||
) {
|
||||
hostname = hostname || "0.0.0.0";
|
||||
const keyPair = loadTlsKeyPair("Deno.listenQuic", { cert, key });
|
||||
const endpoint = await op_quic_listen(
|
||||
{ hostname, port },
|
||||
{ alpnProtocols },
|
||||
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),
|
||||
{
|
||||
keepAliveInterval,
|
||||
maxIdleTimeout,
|
||||
maxConcurrentBidirectionalStreams,
|
||||
maxConcurrentUnidirectionalStreams,
|
||||
addr: {
|
||||
hostname: options.hostname,
|
||||
port: options.port,
|
||||
},
|
||||
caCerts: options.caCerts,
|
||||
alpnProtocols: options.alpnProtocols,
|
||||
serverName: options.serverName,
|
||||
},
|
||||
transportOptions(options),
|
||||
keyPair,
|
||||
);
|
||||
return new QuicListener(endpoint);
|
||||
}
|
||||
|
||||
async function connectQuic(
|
||||
{
|
||||
hostname,
|
||||
port,
|
||||
serverName,
|
||||
caCerts,
|
||||
cert,
|
||||
key,
|
||||
alpnProtocols,
|
||||
keepAliveInterval,
|
||||
maxIdleTimeout,
|
||||
maxConcurrentBidirectionalStreams,
|
||||
maxConcurrentUnidirectionalStreams,
|
||||
congestionControl,
|
||||
},
|
||||
) {
|
||||
const keyPair = loadTlsKeyPair("Deno.connectQuic", { cert, key });
|
||||
const conn = await op_quic_connect(
|
||||
{ hostname, port },
|
||||
{
|
||||
caCerts,
|
||||
alpnProtocols,
|
||||
serverName,
|
||||
},
|
||||
{
|
||||
keepAliveInterval,
|
||||
maxIdleTimeout,
|
||||
maxConcurrentBidirectionalStreams,
|
||||
maxConcurrentUnidirectionalStreams,
|
||||
congestionControl,
|
||||
},
|
||||
keyPair,
|
||||
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),
|
||||
);
|
||||
return new QuicConn(conn);
|
||||
}
|
||||
|
||||
export {
|
||||
connectQuic,
|
||||
listenQuic,
|
||||
QuicBidirectionalStream,
|
||||
QuicConn,
|
||||
QuicEndpoint,
|
||||
QuicIncoming,
|
||||
QuicListener,
|
||||
QuicReceiveStream,
|
||||
|
|
325
ext/net/lib.deno_net.d.ts
vendored
325
ext/net/lib.deno_net.d.ts
vendored
|
@ -450,6 +450,24 @@ declare namespace Deno {
|
|||
options?: StartTlsOptions,
|
||||
): Promise<TlsConn>;
|
||||
|
||||
/**
|
||||
* **UNSTABLE**: New API, yet to be vetted.
|
||||
* @experimental
|
||||
* @category Network
|
||||
*/
|
||||
export interface QuicEndpointOptions {
|
||||
/**
|
||||
* A literal IP address or host name that can be resolved to an IP address.
|
||||
* @default {"::"}
|
||||
*/
|
||||
hostname?: string;
|
||||
/**
|
||||
* The port to bind to.
|
||||
* @default {0}
|
||||
*/
|
||||
port?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* **UNSTABLE**: New API, yet to be vetted.
|
||||
* @experimental
|
||||
|
@ -479,53 +497,20 @@ declare namespace Deno {
|
|||
* @default {100}
|
||||
*/
|
||||
maxConcurrentUnidirectionalStreams?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* **UNSTABLE**: New API, yet to be vetted.
|
||||
* @experimental
|
||||
* @category Network
|
||||
*/
|
||||
export interface ListenQuicOptions extends QuicTransportOptions {
|
||||
/** The port to connect to. */
|
||||
port: number;
|
||||
/**
|
||||
* A literal IP address or host name that can be resolved to an IP address.
|
||||
* @default {"0.0.0.0"}
|
||||
* The congestion control algorithm used when sending data over this connection.
|
||||
* @default {"default"}
|
||||
*/
|
||||
hostname?: string;
|
||||
/** Server private key in PEM format */
|
||||
key: string;
|
||||
/** Cert chain in PEM format */
|
||||
cert: string;
|
||||
/** Application-Layer Protocol Negotiation (ALPN) protocols to announce to
|
||||
* the client. QUIC requires the use of ALPN.
|
||||
*/
|
||||
alpnProtocols: string[];
|
||||
congestionControl?: "throughput" | "low-latency" | "default";
|
||||
}
|
||||
|
||||
/**
|
||||
* **UNSTABLE**: New API, yet to be vetted.
|
||||
* Listen announces on the local transport address over QUIC.
|
||||
*
|
||||
* ```ts
|
||||
* const lstnr = await Deno.listenQuic({ port: 443, cert: "...", key: "...", alpnProtocols: ["h3"] });
|
||||
* ```
|
||||
*
|
||||
* Requires `allow-net` permission.
|
||||
*
|
||||
* @experimental
|
||||
* @tags allow-net
|
||||
* @category Network
|
||||
*/
|
||||
export function listenQuic(options: ListenQuicOptions): Promise<QuicListener>;
|
||||
|
||||
/**
|
||||
* **UNSTABLE**: New API, yet to be vetted.
|
||||
* @experimental
|
||||
* @category Network
|
||||
*/
|
||||
export interface ConnectQuicOptions extends QuicTransportOptions {
|
||||
export interface ConnectQuicOptions<ZRTT extends boolean>
|
||||
extends QuicTransportOptions {
|
||||
/** The port to connect to. */
|
||||
port: number;
|
||||
/** A literal IP address or host name that can be resolved to an IP address. */
|
||||
|
@ -543,30 +528,73 @@ declare namespace Deno {
|
|||
* Must be in PEM format. */
|
||||
caCerts?: string[];
|
||||
/**
|
||||
* The congestion control algorithm used when sending data over this connection.
|
||||
* If no endpoint is provided, a new one is bound on an ephemeral port.
|
||||
*/
|
||||
congestionControl?: "throughput" | "low-latency";
|
||||
endpoint?: QuicEndpoint;
|
||||
/**
|
||||
* Attempt to convert the connection into 0-RTT. Any data sent before
|
||||
* the TLS handshake completes is vulnerable to replay attacks.
|
||||
* @default {false}
|
||||
*/
|
||||
zeroRtt?: ZRTT;
|
||||
}
|
||||
|
||||
/**
|
||||
* **UNSTABLE**: New API, yet to be vetted.
|
||||
* Establishes a secure connection over QUIC using a hostname and port. The
|
||||
* cert file is optional and if not included Mozilla's root certificates will
|
||||
* be used. See also https://github.com/ctz/webpki-roots for specifics.
|
||||
*
|
||||
* ```ts
|
||||
* const caCert = await Deno.readTextFile("./certs/my_custom_root_CA.pem");
|
||||
* const conn1 = await Deno.connectQuic({ hostname: "example.com", port: 443, alpnProtocols: ["h3"] });
|
||||
* const conn2 = await Deno.connectQuic({ caCerts: [caCert], hostname: "example.com", port: 443, alpnProtocols: ["h3"] });
|
||||
* ```
|
||||
*
|
||||
* Requires `allow-net` permission.
|
||||
*
|
||||
* @experimental
|
||||
* @tags allow-net
|
||||
* @category Network
|
||||
*/
|
||||
export function connectQuic(options: ConnectQuicOptions): Promise<QuicConn>;
|
||||
export interface QuicServerTransportOptions extends QuicTransportOptions {
|
||||
/**
|
||||
* Preferred IPv4 address to be communicated to the client during
|
||||
* handshaking. If the client is able to reach this address it will switch
|
||||
* to it.
|
||||
* @default {undefined}
|
||||
*/
|
||||
preferredAddressV4?: string;
|
||||
/**
|
||||
* Preferred IPv6 address to be communicated to the client during
|
||||
* handshaking. If the client is able to reach this address it will switch
|
||||
* to it.
|
||||
* @default {undefined}
|
||||
*/
|
||||
preferredAddressV6?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* **UNSTABLE**: New API, yet to be vetted.
|
||||
* @experimental
|
||||
* @category Network
|
||||
*/
|
||||
export interface QuicListenOptions extends QuicServerTransportOptions {
|
||||
/** Application-Layer Protocol Negotiation (ALPN) protocols to announce to
|
||||
* the client. QUIC requires the use of ALPN.
|
||||
*/
|
||||
alpnProtocols: string[];
|
||||
/** Server private key in PEM format */
|
||||
key: string;
|
||||
/** Cert chain in PEM format */
|
||||
cert: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* **UNSTABLE**: New API, yet to be vetted.
|
||||
* @experimental
|
||||
* @category Network
|
||||
*/
|
||||
export interface QuicAcceptOptions<ZRTT extends boolean>
|
||||
extends QuicServerTransportOptions {
|
||||
/** Application-Layer Protocol Negotiation (ALPN) protocols to announce to
|
||||
* the client. QUIC requires the use of ALPN.
|
||||
*/
|
||||
alpnProtocols?: string[];
|
||||
/**
|
||||
* Convert this connection into 0.5-RTT at the cost of weakened security, as
|
||||
* 0.5-RTT data may be sent before TLS client authentication has occurred.
|
||||
* @default {false}
|
||||
*/
|
||||
zeroRtt?: ZRTT;
|
||||
}
|
||||
|
||||
/**
|
||||
* **UNSTABLE**: New API, yet to be vetted.
|
||||
|
@ -582,14 +610,93 @@ declare namespace Deno {
|
|||
|
||||
/**
|
||||
* **UNSTABLE**: New API, yet to be vetted.
|
||||
* An incoming connection for which the server has not yet begun its part of the handshake.
|
||||
*
|
||||
* @experimental
|
||||
* @category Network
|
||||
*/
|
||||
export interface QuicSendStreamOptions {
|
||||
/** Indicates the send priority of this stream relative to other streams for
|
||||
* which the value has been set.
|
||||
* @default {0}
|
||||
*/
|
||||
sendOrder?: number;
|
||||
/** Wait until there is sufficient flow credit to create the stream.
|
||||
* @default {false}
|
||||
*/
|
||||
waitUntilAvailable?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* **UNSTABLE**: New API, yet to be vetted.
|
||||
* @experimental
|
||||
* @category Network
|
||||
*/
|
||||
export class QuicEndpoint {
|
||||
/**
|
||||
* Create a QUIC endpoint which may be used for client or server connections.
|
||||
*
|
||||
* Requires `allow-net` permission.
|
||||
*
|
||||
* @experimental
|
||||
* @tags allow-net
|
||||
* @category Network
|
||||
*/
|
||||
constructor(options?: QuicEndpointOptions);
|
||||
|
||||
/** Return the address of the `QuicListener`. */
|
||||
readonly addr: NetAddr;
|
||||
|
||||
/**
|
||||
* **UNSTABLE**: New API, yet to be vetted.
|
||||
* Listen announces on the local transport address over QUIC.
|
||||
*
|
||||
* @experimental
|
||||
* @category Network
|
||||
*/
|
||||
listen(options: QuicListenOptions): QuicListener;
|
||||
|
||||
/**
|
||||
* Closes the endpoint. All associated connections will be closed and incoming
|
||||
* connections will be rejected.
|
||||
*/
|
||||
close(info?: QuicCloseInfo): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* **UNSTABLE**: New API, yet to be vetted.
|
||||
* Specialized listener that accepts QUIC connections.
|
||||
*
|
||||
* @experimental
|
||||
* @category Network
|
||||
*/
|
||||
export interface QuicListener extends AsyncIterable<QuicConn> {
|
||||
/** Waits for and resolves to the next incoming connection. */
|
||||
incoming(): Promise<QuicIncoming>;
|
||||
|
||||
/** Wait for the next incoming connection and accepts it. */
|
||||
accept(): Promise<QuicConn>;
|
||||
|
||||
/** Stops the listener. This does not close the endpoint. */
|
||||
stop(): void;
|
||||
|
||||
[Symbol.asyncIterator](): AsyncIterableIterator<QuicConn>;
|
||||
|
||||
/** The endpoint for this listener. */
|
||||
readonly endpoint: QuicEndpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* **UNSTABLE**: New API, yet to be vetted.
|
||||
* An incoming connection for which the server has not yet begun its part of
|
||||
* the handshake.
|
||||
*
|
||||
* @experimental
|
||||
* @category Network
|
||||
*/
|
||||
export interface QuicIncoming {
|
||||
/**
|
||||
* The local IP address which was used when the peer established the connection.
|
||||
* The local IP address which was used when the peer established the
|
||||
* connection.
|
||||
*/
|
||||
readonly localIp: string;
|
||||
|
||||
|
@ -599,14 +706,17 @@ declare namespace Deno {
|
|||
readonly remoteAddr: NetAddr;
|
||||
|
||||
/**
|
||||
* Whether the socket address that is initiating this connection has proven that they can receive traffic.
|
||||
* Whether the socket address that is initiating this connection has proven
|
||||
* that they can receive traffic.
|
||||
*/
|
||||
readonly remoteAddressValidated: boolean;
|
||||
|
||||
/**
|
||||
* Accept this incoming connection.
|
||||
*/
|
||||
accept(): Promise<QuicConn>;
|
||||
accept<ZRTT extends boolean>(
|
||||
options?: QuicAcceptOptions<ZRTT>,
|
||||
): ZRTT extends true ? QuicConn : Promise<QuicConn>;
|
||||
|
||||
/**
|
||||
* Refuse this incoming connection.
|
||||
|
@ -619,48 +729,6 @@ declare namespace Deno {
|
|||
ignore(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* **UNSTABLE**: New API, yet to be vetted.
|
||||
* Specialized listener that accepts QUIC connections.
|
||||
*
|
||||
* @experimental
|
||||
* @category Network
|
||||
*/
|
||||
export interface QuicListener extends AsyncIterable<QuicConn> {
|
||||
/** Return the address of the `QuicListener`. */
|
||||
readonly addr: NetAddr;
|
||||
|
||||
/** Waits for and resolves to the next connection to the `QuicListener`. */
|
||||
accept(): Promise<QuicConn>;
|
||||
|
||||
/** Waits for and resolves to the next incoming request to the `QuicListener`. */
|
||||
incoming(): Promise<QuicIncoming>;
|
||||
|
||||
/** Close closes the listener. Any pending accept promises will be rejected
|
||||
* with errors. */
|
||||
close(info: QuicCloseInfo): void;
|
||||
|
||||
[Symbol.asyncIterator](): AsyncIterableIterator<QuicConn>;
|
||||
}
|
||||
|
||||
/**
|
||||
* **UNSTABLE**: New API, yet to be vetted.
|
||||
*
|
||||
* @experimental
|
||||
* @category Network
|
||||
*/
|
||||
export interface QuicSendStreamOptions {
|
||||
/** Indicates the send priority of this stream relative to other streams for
|
||||
* which the value has been set.
|
||||
* @default {undefined}
|
||||
*/
|
||||
sendOrder?: number;
|
||||
/** Wait until there is sufficient flow credit to create the stream.
|
||||
* @default {false}
|
||||
*/
|
||||
waitUntilAvailable?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* **UNSTABLE**: New API, yet to be vetted.
|
||||
*
|
||||
|
@ -670,7 +738,7 @@ declare namespace Deno {
|
|||
export interface QuicConn {
|
||||
/** Close closes the listener. Any pending accept promises will be rejected
|
||||
* with errors. */
|
||||
close(info: QuicCloseInfo): void;
|
||||
close(info?: QuicCloseInfo): void;
|
||||
/** Opens and returns a bidirectional stream. */
|
||||
createBidirectionalStream(
|
||||
options?: QuicSendStreamOptions,
|
||||
|
@ -682,17 +750,25 @@ declare namespace Deno {
|
|||
/** Send a datagram. The provided data cannot be larger than
|
||||
* `maxDatagramSize`. */
|
||||
sendDatagram(data: Uint8Array): Promise<void>;
|
||||
/** Receive a datagram. If no buffer is provider, one will be allocated.
|
||||
* The size of the provided buffer should be at least `maxDatagramSize`. */
|
||||
readDatagram(buffer?: Uint8Array): Promise<Uint8Array>;
|
||||
/** Receive a datagram. */
|
||||
readDatagram(): Promise<Uint8Array>;
|
||||
|
||||
/** The endpoint for this connection. */
|
||||
readonly endpoint: QuicEndpoint;
|
||||
/** Returns a promise that resolves when the TLS handshake is complete. */
|
||||
readonly handshake: Promise<void>;
|
||||
/** Return the remote address for the connection. Clients may change
|
||||
* addresses at will, for example when switching to a cellular internet
|
||||
* connection.
|
||||
*/
|
||||
readonly remoteAddr: NetAddr;
|
||||
/** The negotiated ALPN protocol, if provided. */
|
||||
/**
|
||||
* The negotiated ALPN protocol, if provided. Only available after the
|
||||
* handshake is complete. */
|
||||
readonly protocol: string | undefined;
|
||||
/** The negotiated server name. Only available on the server after the
|
||||
* handshake is complete. */
|
||||
readonly serverName: string | undefined;
|
||||
/** Returns a promise that resolves when the connection is closed. */
|
||||
readonly closed: Promise<QuicCloseInfo>;
|
||||
/** A stream of bidirectional streams opened by the peer. */
|
||||
|
@ -728,6 +804,11 @@ declare namespace Deno {
|
|||
/** Indicates the send priority of this stream relative to other streams for
|
||||
* which the value has been set. */
|
||||
sendOrder: number;
|
||||
|
||||
/**
|
||||
* 62-bit stream ID, unique within this connection.
|
||||
*/
|
||||
readonly id: bigint;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -736,7 +817,39 @@ declare namespace Deno {
|
|||
* @experimental
|
||||
* @category Network
|
||||
*/
|
||||
export interface QuicReceiveStream extends ReadableStream<Uint8Array> {}
|
||||
export interface QuicReceiveStream extends ReadableStream<Uint8Array> {
|
||||
/**
|
||||
* 62-bit stream ID, unique within this connection.
|
||||
*/
|
||||
readonly id: bigint;
|
||||
}
|
||||
|
||||
/**
|
||||
* **UNSTABLE**: New API, yet to be vetted.
|
||||
* Establishes a secure connection over QUIC using a hostname and port. The
|
||||
* cert file is optional and if not included Mozilla's root certificates will
|
||||
* be used. See also https://github.com/ctz/webpki-roots for specifics.
|
||||
*
|
||||
* ```ts
|
||||
* const caCert = await Deno.readTextFile("./certs/my_custom_root_CA.pem");
|
||||
* const conn1 = await Deno.connectQuic({ hostname: "example.com", port: 443, alpnProtocols: ["h3"] });
|
||||
* const conn2 = await Deno.connectQuic({ caCerts: [caCert], hostname: "example.com", port: 443, alpnProtocols: ["h3"] });
|
||||
* ```
|
||||
*
|
||||
* If an endpoint is shared among many connections, 0-RTT can be enabled.
|
||||
* When 0-RTT is successful, a QuicConn will be synchronously returned
|
||||
* and data can be sent immediately with it. **Any data sent before the
|
||||
* TLS handshake completes is vulnerable to replay attacks.**
|
||||
*
|
||||
* Requires `allow-net` permission.
|
||||
*
|
||||
* @experimental
|
||||
* @tags allow-net
|
||||
* @category Network
|
||||
*/
|
||||
export function connectQuic<ZRTT extends boolean>(
|
||||
options: ConnectQuicOptions<ZRTT>,
|
||||
): ZRTT extends true ? (QuicConn | Promise<QuicConn>) : Promise<QuicConn>;
|
||||
|
||||
export {}; // only export exports
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ use deno_core::OpState;
|
|||
use deno_permissions::PermissionCheckError;
|
||||
use deno_tls::rustls::RootCertStore;
|
||||
use deno_tls::RootCertStoreProvider;
|
||||
pub use quic::QuicError;
|
||||
|
||||
pub const UNSTABLE_FEATURE_NAME: &str = "net";
|
||||
|
||||
|
@ -161,33 +162,42 @@ deno_core::extension!(deno_net,
|
|||
ops_unix::op_net_recv_unixpacket,
|
||||
ops_unix::op_net_send_unixpacket<P>,
|
||||
|
||||
quic::op_quic_accept,
|
||||
quic::op_quic_accept_bi,
|
||||
quic::op_quic_accept_incoming,
|
||||
quic::op_quic_accept_uni,
|
||||
quic::op_quic_close_connection,
|
||||
quic::op_quic_close_endpoint,
|
||||
quic::op_quic_connecting_0rtt,
|
||||
quic::op_quic_connecting_1rtt,
|
||||
quic::op_quic_connection_accept_bi,
|
||||
quic::op_quic_connection_accept_uni,
|
||||
quic::op_quic_connection_close,
|
||||
quic::op_quic_connection_closed,
|
||||
quic::op_quic_connection_get_protocol,
|
||||
quic::op_quic_connection_get_remote_addr,
|
||||
quic::op_quic_connect<P>,
|
||||
quic::op_quic_connection_get_server_name,
|
||||
quic::op_quic_connection_handshake,
|
||||
quic::op_quic_connection_open_bi,
|
||||
quic::op_quic_connection_open_uni,
|
||||
quic::op_quic_connection_get_max_datagram_size,
|
||||
quic::op_quic_connection_read_datagram,
|
||||
quic::op_quic_connection_send_datagram,
|
||||
quic::op_quic_endpoint_close,
|
||||
quic::op_quic_endpoint_connect<P>,
|
||||
quic::op_quic_endpoint_create<P>,
|
||||
quic::op_quic_endpoint_get_addr,
|
||||
quic::op_quic_get_send_stream_priority,
|
||||
quic::op_quic_endpoint_listen,
|
||||
quic::op_quic_incoming_accept,
|
||||
quic::op_quic_incoming_refuse,
|
||||
quic::op_quic_incoming_accept_0rtt,
|
||||
quic::op_quic_incoming_ignore,
|
||||
quic::op_quic_incoming_local_ip,
|
||||
quic::op_quic_incoming_refuse,
|
||||
quic::op_quic_incoming_remote_addr,
|
||||
quic::op_quic_incoming_remote_addr_validated,
|
||||
quic::op_quic_listen<P>,
|
||||
quic::op_quic_max_datagram_size,
|
||||
quic::op_quic_open_bi,
|
||||
quic::op_quic_open_uni,
|
||||
quic::op_quic_read_datagram,
|
||||
quic::op_quic_send_datagram,
|
||||
quic::op_quic_set_send_stream_priority,
|
||||
quic::op_quic_listener_accept,
|
||||
quic::op_quic_listener_stop,
|
||||
quic::op_quic_recv_stream_get_id,
|
||||
quic::op_quic_send_stream_get_id,
|
||||
quic::op_quic_send_stream_get_priority,
|
||||
quic::op_quic_send_stream_set_priority,
|
||||
],
|
||||
esm = [ "01_net.js", "02_tls.js", "03_quic.js" ],
|
||||
esm = [ "01_net.js", "02_tls.js" ],
|
||||
lazy_loaded_esm = [ "03_quic.js" ],
|
||||
options = {
|
||||
root_cert_store_provider: Option<Arc<dyn RootCertStoreProvider>>,
|
||||
unsafely_ignore_certificate_errors: Option<Vec<String>>,
|
||||
|
|
596
ext/net/quic.rs
596
ext/net/quic.rs
|
@ -4,24 +4,24 @@ use std::borrow::Cow;
|
|||
use std::cell::RefCell;
|
||||
use std::future::Future;
|
||||
use std::net::IpAddr;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::net::Ipv6Addr;
|
||||
use std::net::SocketAddr;
|
||||
use std::net::SocketAddrV4;
|
||||
use std::net::SocketAddrV6;
|
||||
use std::pin::pin;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::AtomicI32;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::task::Context;
|
||||
use std::task::Poll;
|
||||
use std::time::Duration;
|
||||
|
||||
use deno_core::error::bad_resource;
|
||||
use deno_core::error::generic_error;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::futures::task::noop_waker_ref;
|
||||
use deno_core::op2;
|
||||
use deno_core::AsyncRefCell;
|
||||
use deno_core::AsyncResult;
|
||||
use deno_core::BufMutView;
|
||||
use deno_core::BufView;
|
||||
use deno_core::GarbageCollected;
|
||||
use deno_core::JsBuffer;
|
||||
|
@ -30,20 +30,68 @@ use deno_core::RcRef;
|
|||
use deno_core::Resource;
|
||||
use deno_core::ResourceId;
|
||||
use deno_core::WriteOutcome;
|
||||
use deno_permissions::PermissionCheckError;
|
||||
use deno_tls::create_client_config;
|
||||
use deno_tls::SocketUse;
|
||||
use deno_tls::TlsError;
|
||||
use deno_tls::TlsKeys;
|
||||
use deno_tls::TlsKeysHolder;
|
||||
use quinn::crypto::rustls::QuicClientConfig;
|
||||
use quinn::crypto::rustls::QuicServerConfig;
|
||||
use quinn::rustls::client::ClientSessionMemoryCache;
|
||||
use quinn::rustls::client::ClientSessionStore;
|
||||
use quinn::rustls::client::Resumption;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::resolve_addr::resolve_addr;
|
||||
use crate::resolve_addr::resolve_addr_sync;
|
||||
use crate::DefaultTlsOptions;
|
||||
use crate::NetPermissions;
|
||||
use crate::UnsafelyIgnoreCertificateErrors;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum QuicError {
|
||||
#[error("Endpoint created by 'connectQuic' cannot be used for listening")]
|
||||
CannotListen,
|
||||
#[error("key and cert are required")]
|
||||
MissingTlsKey,
|
||||
#[error("Duration is invalid")]
|
||||
InvalidDuration,
|
||||
#[error("Unable to resolve hostname")]
|
||||
UnableToResolve,
|
||||
#[error("{0}")]
|
||||
StdIo(#[from] std::io::Error),
|
||||
#[error("{0}")]
|
||||
PermissionCheck(#[from] PermissionCheckError),
|
||||
#[error("{0}")]
|
||||
VarIntBoundsExceeded(#[from] quinn::VarIntBoundsExceeded),
|
||||
#[error("{0}")]
|
||||
Rustls(#[from] quinn::rustls::Error),
|
||||
#[error("{0}")]
|
||||
Tls(#[from] TlsError),
|
||||
#[error("{0}")]
|
||||
ConnectionError(#[from] quinn::ConnectionError),
|
||||
#[error("{0}")]
|
||||
ConnectError(#[from] quinn::ConnectError),
|
||||
#[error("{0}")]
|
||||
SendDatagramError(#[from] quinn::SendDatagramError),
|
||||
#[error("{0}")]
|
||||
ClosedStream(#[from] quinn::ClosedStream),
|
||||
#[error("Invalid {0} resource")]
|
||||
BadResource(&'static str),
|
||||
#[error("Connection has reached the maximum number of concurrent outgoing {0} streams")]
|
||||
MaxStreams(&'static str),
|
||||
#[error("{0}")]
|
||||
Core(#[from] deno_core::error::AnyError),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct CloseInfo {
|
||||
close_code: u64,
|
||||
reason: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct Addr {
|
||||
hostname: String,
|
||||
|
@ -56,7 +104,7 @@ struct ListenArgs {
|
|||
alpn_protocols: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, Default, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct TransportConfig {
|
||||
keep_alive_interval: Option<u64>,
|
||||
|
@ -69,9 +117,9 @@ struct TransportConfig {
|
|||
}
|
||||
|
||||
impl TryInto<quinn::TransportConfig> for TransportConfig {
|
||||
type Error = AnyError;
|
||||
type Error = QuicError;
|
||||
|
||||
fn try_into(self) -> Result<quinn::TransportConfig, AnyError> {
|
||||
fn try_into(self) -> Result<quinn::TransportConfig, Self::Error> {
|
||||
let mut cfg = quinn::TransportConfig::default();
|
||||
|
||||
if let Some(interval) = self.keep_alive_interval {
|
||||
|
@ -79,7 +127,11 @@ impl TryInto<quinn::TransportConfig> for TransportConfig {
|
|||
}
|
||||
|
||||
if let Some(timeout) = self.max_idle_timeout {
|
||||
cfg.max_idle_timeout(Some(Duration::from_millis(timeout).try_into()?));
|
||||
cfg.max_idle_timeout(Some(
|
||||
Duration::from_millis(timeout)
|
||||
.try_into()
|
||||
.map_err(|_| QuicError::InvalidDuration)?,
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(max) = self.max_concurrent_bidirectional_streams {
|
||||
|
@ -111,34 +163,119 @@ impl TryInto<quinn::TransportConfig> for TransportConfig {
|
|||
}
|
||||
}
|
||||
|
||||
struct EndpointResource(quinn::Endpoint, Arc<QuicServerConfig>);
|
||||
fn apply_server_transport_config(
|
||||
config: &mut quinn::ServerConfig,
|
||||
transport_config: TransportConfig,
|
||||
) -> Result<(), QuicError> {
|
||||
config.preferred_address_v4(transport_config.preferred_address_v4);
|
||||
config.preferred_address_v6(transport_config.preferred_address_v6);
|
||||
config.transport_config(Arc::new(transport_config.try_into()?));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct EndpointResource {
|
||||
endpoint: quinn::Endpoint,
|
||||
can_listen: bool,
|
||||
session_store: Arc<dyn ClientSessionStore>,
|
||||
}
|
||||
|
||||
impl GarbageCollected for EndpointResource {}
|
||||
|
||||
#[op2(async)]
|
||||
#[op2]
|
||||
#[cppgc]
|
||||
pub(crate) async fn op_quic_listen<NP>(
|
||||
pub(crate) fn op_quic_endpoint_create<NP>(
|
||||
state: Rc<RefCell<OpState>>,
|
||||
#[serde] addr: Addr,
|
||||
#[serde] args: ListenArgs,
|
||||
#[serde] transport_config: TransportConfig,
|
||||
#[cppgc] keys: &TlsKeysHolder,
|
||||
) -> Result<EndpointResource, AnyError>
|
||||
can_listen: bool,
|
||||
) -> Result<EndpointResource, QuicError>
|
||||
where
|
||||
NP: NetPermissions + 'static,
|
||||
{
|
||||
state
|
||||
.borrow_mut()
|
||||
.borrow_mut::<NP>()
|
||||
.check_net(&(&addr.hostname, Some(addr.port)), "Deno.listenQuic()")?;
|
||||
|
||||
let addr = resolve_addr(&addr.hostname, addr.port)
|
||||
.await?
|
||||
let addr = resolve_addr_sync(&addr.hostname, addr.port)?
|
||||
.next()
|
||||
.ok_or_else(|| generic_error("No resolved address found"))?;
|
||||
.ok_or_else(|| QuicError::UnableToResolve)?;
|
||||
|
||||
if can_listen {
|
||||
state.borrow_mut().borrow_mut::<NP>().check_net(
|
||||
&(&addr.ip().to_string(), Some(addr.port())),
|
||||
"new Deno.QuicEndpoint()",
|
||||
)?;
|
||||
} else {
|
||||
// If this is not a can-listen, assert that we will bind to an ephemeral port.
|
||||
assert_eq!(
|
||||
addr,
|
||||
SocketAddr::from((
|
||||
IpAddr::from(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)),
|
||||
0
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
let config = quinn::EndpointConfig::default();
|
||||
let socket = std::net::UdpSocket::bind(addr)?;
|
||||
let endpoint = quinn::Endpoint::new(
|
||||
config,
|
||||
None,
|
||||
socket,
|
||||
quinn::default_runtime().unwrap(),
|
||||
)?;
|
||||
|
||||
Ok(EndpointResource {
|
||||
endpoint,
|
||||
can_listen,
|
||||
session_store: Arc::new(ClientSessionMemoryCache::new(256)),
|
||||
})
|
||||
}
|
||||
|
||||
#[op2]
|
||||
#[serde]
|
||||
pub(crate) fn op_quic_endpoint_get_addr(
|
||||
#[cppgc] endpoint: &EndpointResource,
|
||||
) -> Result<Addr, QuicError> {
|
||||
let addr = endpoint.endpoint.local_addr()?;
|
||||
let addr = Addr {
|
||||
hostname: format!("{}", addr.ip()),
|
||||
port: addr.port(),
|
||||
};
|
||||
Ok(addr)
|
||||
}
|
||||
|
||||
#[op2(fast)]
|
||||
pub(crate) fn op_quic_endpoint_close(
|
||||
#[cppgc] endpoint: &EndpointResource,
|
||||
#[bigint] close_code: u64,
|
||||
#[string] reason: String,
|
||||
) -> Result<(), QuicError> {
|
||||
endpoint
|
||||
.endpoint
|
||||
.close(quinn::VarInt::from_u64(close_code)?, reason.as_bytes());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct ListenerResource(quinn::Endpoint, Arc<QuicServerConfig>);
|
||||
|
||||
impl Drop for ListenerResource {
|
||||
fn drop(&mut self) {
|
||||
self.0.set_server_config(None);
|
||||
}
|
||||
}
|
||||
|
||||
impl GarbageCollected for ListenerResource {}
|
||||
|
||||
#[op2]
|
||||
#[cppgc]
|
||||
pub(crate) fn op_quic_endpoint_listen(
|
||||
#[cppgc] endpoint: &EndpointResource,
|
||||
#[serde] args: ListenArgs,
|
||||
#[serde] transport_config: TransportConfig,
|
||||
#[cppgc] keys: &TlsKeysHolder,
|
||||
) -> Result<ListenerResource, QuicError> {
|
||||
if !endpoint.can_listen {
|
||||
return Err(QuicError::CannotListen);
|
||||
}
|
||||
|
||||
let TlsKeys::Static(deno_tls::TlsKey(cert, key)) = keys.take() else {
|
||||
unreachable!()
|
||||
return Err(QuicError::MissingTlsKey);
|
||||
};
|
||||
|
||||
let mut crypto =
|
||||
|
@ -148,6 +285,9 @@ where
|
|||
.with_no_client_auth()
|
||||
.with_single_cert(cert.clone(), key.clone_key())?;
|
||||
|
||||
// required by QUIC spec.
|
||||
crypto.max_early_data_size = u32::MAX;
|
||||
|
||||
if let Some(alpn_protocols) = args.alpn_protocols {
|
||||
crypto.alpn_protocols = alpn_protocols
|
||||
.into_iter()
|
||||
|
@ -155,66 +295,24 @@ where
|
|||
.collect();
|
||||
}
|
||||
|
||||
let server_config = Arc::new(QuicServerConfig::try_from(crypto)?);
|
||||
let server_config = Arc::new(
|
||||
QuicServerConfig::try_from(crypto).expect("TLS13 is explicitly configured"),
|
||||
);
|
||||
let mut config = quinn::ServerConfig::with_crypto(server_config.clone());
|
||||
config.preferred_address_v4(transport_config.preferred_address_v4);
|
||||
config.preferred_address_v6(transport_config.preferred_address_v6);
|
||||
config.transport_config(Arc::new(transport_config.try_into()?));
|
||||
let endpoint = quinn::Endpoint::server(config, addr)?;
|
||||
apply_server_transport_config(&mut config, transport_config)?;
|
||||
|
||||
Ok(EndpointResource(endpoint, server_config))
|
||||
endpoint.endpoint.set_server_config(Some(config));
|
||||
|
||||
Ok(ListenerResource(endpoint.endpoint.clone(), server_config))
|
||||
}
|
||||
|
||||
#[op2]
|
||||
#[serde]
|
||||
pub(crate) fn op_quic_endpoint_get_addr(
|
||||
#[cppgc] endpoint: &EndpointResource,
|
||||
) -> Result<Addr, AnyError> {
|
||||
let addr = endpoint.0.local_addr()?;
|
||||
let addr = Addr {
|
||||
hostname: format!("{}", addr.ip()),
|
||||
port: addr.port(),
|
||||
};
|
||||
Ok(addr)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct CloseInfo {
|
||||
close_code: u64,
|
||||
reason: String,
|
||||
}
|
||||
|
||||
#[op2(fast)]
|
||||
pub(crate) fn op_quic_close_endpoint(
|
||||
#[cppgc] endpoint: &EndpointResource,
|
||||
#[bigint] close_code: u64,
|
||||
#[string] reason: String,
|
||||
) -> Result<(), AnyError> {
|
||||
endpoint
|
||||
.0
|
||||
.close(quinn::VarInt::from_u64(close_code)?, reason.as_bytes());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct ConnectionResource(quinn::Connection);
|
||||
struct ConnectionResource(
|
||||
quinn::Connection,
|
||||
RefCell<Option<quinn::ZeroRttAccepted>>,
|
||||
);
|
||||
|
||||
impl GarbageCollected for ConnectionResource {}
|
||||
|
||||
#[op2(async)]
|
||||
#[cppgc]
|
||||
pub(crate) async fn op_quic_accept(
|
||||
#[cppgc] endpoint: &EndpointResource,
|
||||
) -> Result<ConnectionResource, AnyError> {
|
||||
match endpoint.0.accept().await {
|
||||
Some(incoming) => {
|
||||
let conn = incoming.accept()?.await?;
|
||||
Ok(ConnectionResource(conn))
|
||||
}
|
||||
None => Err(bad_resource("QuicListener is closed")),
|
||||
}
|
||||
}
|
||||
|
||||
struct IncomingResource(
|
||||
RefCell<Option<quinn::Incoming>>,
|
||||
Arc<QuicServerConfig>,
|
||||
|
@ -224,25 +322,30 @@ impl GarbageCollected for IncomingResource {}
|
|||
|
||||
#[op2(async)]
|
||||
#[cppgc]
|
||||
pub(crate) async fn op_quic_accept_incoming(
|
||||
#[cppgc] endpoint: &EndpointResource,
|
||||
) -> Result<IncomingResource, AnyError> {
|
||||
match endpoint.0.accept().await {
|
||||
pub(crate) async fn op_quic_listener_accept(
|
||||
#[cppgc] resource: &ListenerResource,
|
||||
) -> Result<IncomingResource, QuicError> {
|
||||
match resource.0.accept().await {
|
||||
Some(incoming) => Ok(IncomingResource(
|
||||
RefCell::new(Some(incoming)),
|
||||
endpoint.1.clone(),
|
||||
resource.1.clone(),
|
||||
)),
|
||||
None => Err(bad_resource("QuicListener is closed")),
|
||||
None => Err(QuicError::BadResource("QuicListener")),
|
||||
}
|
||||
}
|
||||
|
||||
#[op2(fast)]
|
||||
pub(crate) fn op_quic_listener_stop(#[cppgc] resource: &ListenerResource) {
|
||||
resource.0.set_server_config(None);
|
||||
}
|
||||
|
||||
#[op2]
|
||||
#[string]
|
||||
pub(crate) fn op_quic_incoming_local_ip(
|
||||
#[cppgc] incoming_resource: &IncomingResource,
|
||||
) -> Result<Option<String>, AnyError> {
|
||||
) -> Result<Option<String>, QuicError> {
|
||||
let Some(incoming) = incoming_resource.0.borrow_mut().take() else {
|
||||
return Err(bad_resource("QuicIncoming already used"));
|
||||
return Err(QuicError::BadResource("QuicIncoming"));
|
||||
};
|
||||
Ok(incoming.local_ip().map(|ip| ip.to_string()))
|
||||
}
|
||||
|
@ -251,9 +354,9 @@ pub(crate) fn op_quic_incoming_local_ip(
|
|||
#[serde]
|
||||
pub(crate) fn op_quic_incoming_remote_addr(
|
||||
#[cppgc] incoming_resource: &IncomingResource,
|
||||
) -> Result<Addr, AnyError> {
|
||||
) -> Result<Addr, QuicError> {
|
||||
let Some(incoming) = incoming_resource.0.borrow_mut().take() else {
|
||||
return Err(bad_resource("QuicIncoming already used"));
|
||||
return Err(QuicError::BadResource("QuicIncoming"));
|
||||
};
|
||||
let addr = incoming.remote_address();
|
||||
Ok(Addr {
|
||||
|
@ -265,43 +368,66 @@ pub(crate) fn op_quic_incoming_remote_addr(
|
|||
#[op2(fast)]
|
||||
pub(crate) fn op_quic_incoming_remote_addr_validated(
|
||||
#[cppgc] incoming_resource: &IncomingResource,
|
||||
) -> Result<bool, AnyError> {
|
||||
) -> Result<bool, QuicError> {
|
||||
let Some(incoming) = incoming_resource.0.borrow_mut().take() else {
|
||||
return Err(bad_resource("QuicIncoming already used"));
|
||||
return Err(QuicError::BadResource("QuicIncoming"));
|
||||
};
|
||||
Ok(incoming.remote_address_validated())
|
||||
}
|
||||
|
||||
fn quic_incoming_accept(
|
||||
incoming_resource: &IncomingResource,
|
||||
transport_config: Option<TransportConfig>,
|
||||
) -> Result<quinn::Connecting, QuicError> {
|
||||
let Some(incoming) = incoming_resource.0.borrow_mut().take() else {
|
||||
return Err(QuicError::BadResource("QuicIncoming"));
|
||||
};
|
||||
match transport_config {
|
||||
Some(transport_config) if transport_config != Default::default() => {
|
||||
let mut config =
|
||||
quinn::ServerConfig::with_crypto(incoming_resource.1.clone());
|
||||
apply_server_transport_config(&mut config, transport_config)?;
|
||||
Ok(incoming.accept_with(Arc::new(config))?)
|
||||
}
|
||||
_ => Ok(incoming.accept()?),
|
||||
}
|
||||
}
|
||||
|
||||
#[op2(async)]
|
||||
#[cppgc]
|
||||
pub(crate) async fn op_quic_incoming_accept(
|
||||
#[cppgc] incoming_resource: &IncomingResource,
|
||||
#[serde] transport_config: Option<TransportConfig>,
|
||||
) -> Result<ConnectionResource, AnyError> {
|
||||
let Some(incoming) = incoming_resource.0.borrow_mut().take() else {
|
||||
return Err(bad_resource("QuicIncoming already used"));
|
||||
};
|
||||
let conn = match transport_config {
|
||||
Some(transport_config) => {
|
||||
let mut config =
|
||||
quinn::ServerConfig::with_crypto(incoming_resource.1.clone());
|
||||
config.preferred_address_v4(transport_config.preferred_address_v4);
|
||||
config.preferred_address_v6(transport_config.preferred_address_v6);
|
||||
config.transport_config(Arc::new(transport_config.try_into()?));
|
||||
incoming.accept_with(Arc::new(config))?.await?
|
||||
) -> Result<ConnectionResource, QuicError> {
|
||||
let connecting = quic_incoming_accept(incoming_resource, transport_config)?;
|
||||
let conn = connecting.await?;
|
||||
Ok(ConnectionResource(conn, RefCell::new(None)))
|
||||
}
|
||||
|
||||
#[op2]
|
||||
#[cppgc]
|
||||
pub(crate) fn op_quic_incoming_accept_0rtt(
|
||||
#[cppgc] incoming_resource: &IncomingResource,
|
||||
#[serde] transport_config: Option<TransportConfig>,
|
||||
) -> Result<ConnectionResource, QuicError> {
|
||||
let connecting = quic_incoming_accept(incoming_resource, transport_config)?;
|
||||
match connecting.into_0rtt() {
|
||||
Ok((conn, zrtt_accepted)) => {
|
||||
Ok(ConnectionResource(conn, RefCell::new(Some(zrtt_accepted))))
|
||||
}
|
||||
None => incoming.accept()?.await?,
|
||||
};
|
||||
Ok(ConnectionResource(conn))
|
||||
Err(_connecting) => {
|
||||
unreachable!("0.5-RTT always succeeds");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[op2]
|
||||
#[serde]
|
||||
pub(crate) fn op_quic_incoming_refuse(
|
||||
#[cppgc] incoming: &IncomingResource,
|
||||
) -> Result<(), AnyError> {
|
||||
) -> Result<(), QuicError> {
|
||||
let Some(incoming) = incoming.0.borrow_mut().take() else {
|
||||
return Err(bad_resource("QuicIncoming already used"));
|
||||
return Err(QuicError::BadResource("QuicIncoming"));
|
||||
};
|
||||
incoming.refuse();
|
||||
Ok(())
|
||||
|
@ -311,43 +437,47 @@ pub(crate) fn op_quic_incoming_refuse(
|
|||
#[serde]
|
||||
pub(crate) fn op_quic_incoming_ignore(
|
||||
#[cppgc] incoming: &IncomingResource,
|
||||
) -> Result<(), AnyError> {
|
||||
) -> Result<(), QuicError> {
|
||||
let Some(incoming) = incoming.0.borrow_mut().take() else {
|
||||
return Err(bad_resource("QuicIncoming already used"));
|
||||
return Err(QuicError::BadResource("QuicIncoming"));
|
||||
};
|
||||
incoming.ignore();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct ConnectingResource(RefCell<Option<quinn::Connecting>>);
|
||||
|
||||
impl GarbageCollected for ConnectingResource {}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ConnectArgs {
|
||||
addr: Addr,
|
||||
ca_certs: Option<Vec<String>>,
|
||||
alpn_protocols: Option<Vec<String>>,
|
||||
server_name: Option<String>,
|
||||
}
|
||||
|
||||
#[op2(async)]
|
||||
#[op2]
|
||||
#[cppgc]
|
||||
pub(crate) async fn op_quic_connect<NP>(
|
||||
pub(crate) fn op_quic_endpoint_connect<NP>(
|
||||
state: Rc<RefCell<OpState>>,
|
||||
#[serde] addr: Addr,
|
||||
#[cppgc] endpoint: &EndpointResource,
|
||||
#[serde] args: ConnectArgs,
|
||||
#[serde] transport_config: TransportConfig,
|
||||
#[cppgc] key_pair: &TlsKeysHolder,
|
||||
) -> Result<ConnectionResource, AnyError>
|
||||
) -> Result<ConnectingResource, QuicError>
|
||||
where
|
||||
NP: NetPermissions + 'static,
|
||||
{
|
||||
state
|
||||
.borrow_mut()
|
||||
.borrow_mut::<NP>()
|
||||
.check_net(&(&addr.hostname, Some(addr.port)), "Deno.connectQuic()")?;
|
||||
state.borrow_mut().borrow_mut::<NP>().check_net(
|
||||
&(&args.addr.hostname, Some(args.addr.port)),
|
||||
"Deno.connectQuic()",
|
||||
)?;
|
||||
|
||||
let sock_addr = resolve_addr(&addr.hostname, addr.port)
|
||||
.await?
|
||||
let sock_addr = resolve_addr_sync(&args.addr.hostname, args.addr.port)?
|
||||
.next()
|
||||
.ok_or_else(|| generic_error("No resolved address found"))?;
|
||||
.ok_or_else(|| QuicError::UnableToResolve)?;
|
||||
|
||||
let root_cert_store = state
|
||||
.borrow()
|
||||
|
@ -379,24 +509,50 @@ where
|
|||
alpn_protocols.into_iter().map(|s| s.into_bytes()).collect();
|
||||
}
|
||||
|
||||
let client_config = QuicClientConfig::try_from(tls_config)?;
|
||||
tls_config.enable_early_data = true;
|
||||
tls_config.resumption = Resumption::store(endpoint.session_store.clone());
|
||||
|
||||
let client_config =
|
||||
QuicClientConfig::try_from(tls_config).expect("TLS13 supported");
|
||||
let mut client_config = quinn::ClientConfig::new(Arc::new(client_config));
|
||||
client_config.transport_config(Arc::new(transport_config.try_into()?));
|
||||
|
||||
let local_addr = match sock_addr.ip() {
|
||||
IpAddr::V4(_) => IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)),
|
||||
IpAddr::V6(_) => IpAddr::from(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)),
|
||||
let connecting = endpoint.endpoint.connect_with(
|
||||
client_config,
|
||||
sock_addr,
|
||||
&args.server_name.unwrap_or(args.addr.hostname),
|
||||
)?;
|
||||
|
||||
Ok(ConnectingResource(RefCell::new(Some(connecting))))
|
||||
}
|
||||
|
||||
#[op2(async)]
|
||||
#[cppgc]
|
||||
pub(crate) async fn op_quic_connecting_1rtt(
|
||||
#[cppgc] connecting: &ConnectingResource,
|
||||
) -> Result<ConnectionResource, QuicError> {
|
||||
let Some(connecting) = connecting.0.borrow_mut().take() else {
|
||||
return Err(QuicError::BadResource("QuicConnecting"));
|
||||
};
|
||||
let conn = connecting.await?;
|
||||
Ok(ConnectionResource(conn, RefCell::new(None)))
|
||||
}
|
||||
|
||||
let conn = quinn::Endpoint::client((local_addr, 0).into())?
|
||||
.connect_with(
|
||||
client_config,
|
||||
sock_addr,
|
||||
&args.server_name.unwrap_or(addr.hostname),
|
||||
)?
|
||||
.await?;
|
||||
|
||||
Ok(ConnectionResource(conn))
|
||||
#[op2]
|
||||
#[cppgc]
|
||||
pub(crate) fn op_quic_connecting_0rtt(
|
||||
#[cppgc] connecting_res: &ConnectingResource,
|
||||
) -> Option<ConnectionResource> {
|
||||
let connecting = connecting_res.0.borrow_mut().take()?;
|
||||
match connecting.into_0rtt() {
|
||||
Ok((conn, zrtt_accepted)) => {
|
||||
Some(ConnectionResource(conn, RefCell::new(Some(zrtt_accepted))))
|
||||
}
|
||||
Err(connecting) => {
|
||||
*connecting_res.0.borrow_mut() = Some(connecting);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[op2]
|
||||
|
@ -412,11 +568,23 @@ pub(crate) fn op_quic_connection_get_protocol(
|
|||
.map(|p| String::from_utf8_lossy(&p).into_owned())
|
||||
}
|
||||
|
||||
#[op2]
|
||||
#[string]
|
||||
pub(crate) fn op_quic_connection_get_server_name(
|
||||
#[cppgc] connection: &ConnectionResource,
|
||||
) -> Option<String> {
|
||||
connection
|
||||
.0
|
||||
.handshake_data()
|
||||
.and_then(|h| h.downcast::<quinn::crypto::rustls::HandshakeData>().ok())
|
||||
.and_then(|h| h.server_name)
|
||||
}
|
||||
|
||||
#[op2]
|
||||
#[serde]
|
||||
pub(crate) fn op_quic_connection_get_remote_addr(
|
||||
#[cppgc] connection: &ConnectionResource,
|
||||
) -> Result<Addr, AnyError> {
|
||||
) -> Result<Addr, QuicError> {
|
||||
let addr = connection.0.remote_address();
|
||||
Ok(Addr {
|
||||
hostname: format!("{}", addr.ip()),
|
||||
|
@ -425,11 +593,11 @@ pub(crate) fn op_quic_connection_get_remote_addr(
|
|||
}
|
||||
|
||||
#[op2(fast)]
|
||||
pub(crate) fn op_quic_close_connection(
|
||||
pub(crate) fn op_quic_connection_close(
|
||||
#[cppgc] connection: &ConnectionResource,
|
||||
#[bigint] close_code: u64,
|
||||
#[string] reason: String,
|
||||
) -> Result<(), AnyError> {
|
||||
) -> Result<(), QuicError> {
|
||||
connection
|
||||
.0
|
||||
.close(quinn::VarInt::from_u64(close_code)?, reason.as_bytes());
|
||||
|
@ -440,7 +608,7 @@ pub(crate) fn op_quic_close_connection(
|
|||
#[serde]
|
||||
pub(crate) async fn op_quic_connection_closed(
|
||||
#[cppgc] connection: &ConnectionResource,
|
||||
) -> Result<CloseInfo, AnyError> {
|
||||
) -> Result<CloseInfo, QuicError> {
|
||||
let e = connection.0.closed().await;
|
||||
match e {
|
||||
quinn::ConnectionError::LocallyClosed => Ok(CloseInfo {
|
||||
|
@ -455,11 +623,29 @@ pub(crate) async fn op_quic_connection_closed(
|
|||
}
|
||||
}
|
||||
|
||||
struct SendStreamResource(AsyncRefCell<quinn::SendStream>);
|
||||
#[op2(async)]
|
||||
pub(crate) async fn op_quic_connection_handshake(
|
||||
#[cppgc] connection: &ConnectionResource,
|
||||
) {
|
||||
let Some(zrtt_accepted) = connection.1.borrow_mut().take() else {
|
||||
return;
|
||||
};
|
||||
zrtt_accepted.await;
|
||||
}
|
||||
|
||||
struct SendStreamResource {
|
||||
stream: AsyncRefCell<quinn::SendStream>,
|
||||
stream_id: quinn::StreamId,
|
||||
priority: AtomicI32,
|
||||
}
|
||||
|
||||
impl SendStreamResource {
|
||||
fn new(stream: quinn::SendStream) -> Self {
|
||||
Self(AsyncRefCell::new(stream))
|
||||
Self {
|
||||
stream_id: stream.id(),
|
||||
priority: AtomicI32::new(stream.priority().unwrap_or(0)),
|
||||
stream: AsyncRefCell::new(stream),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -470,18 +656,28 @@ impl Resource for SendStreamResource {
|
|||
|
||||
fn write(self: Rc<Self>, view: BufView) -> AsyncResult<WriteOutcome> {
|
||||
Box::pin(async move {
|
||||
let mut r = RcRef::map(self, |r| &r.0).borrow_mut().await;
|
||||
let nwritten = r.write(&view).await?;
|
||||
let mut stream =
|
||||
RcRef::map(self.clone(), |r| &r.stream).borrow_mut().await;
|
||||
stream.set_priority(self.priority.load(Ordering::Relaxed))?;
|
||||
let nwritten = stream.write(&view).await?;
|
||||
Ok(WriteOutcome::Partial { nwritten, view })
|
||||
})
|
||||
}
|
||||
|
||||
fn close(self: Rc<Self>) {}
|
||||
}
|
||||
|
||||
struct RecvStreamResource(AsyncRefCell<quinn::RecvStream>);
|
||||
struct RecvStreamResource {
|
||||
stream: AsyncRefCell<quinn::RecvStream>,
|
||||
stream_id: quinn::StreamId,
|
||||
}
|
||||
|
||||
impl RecvStreamResource {
|
||||
fn new(stream: quinn::RecvStream) -> Self {
|
||||
Self(AsyncRefCell::new(stream))
|
||||
Self {
|
||||
stream_id: stream.id(),
|
||||
stream: AsyncRefCell::new(stream),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -492,21 +688,40 @@ impl Resource for RecvStreamResource {
|
|||
|
||||
fn read(self: Rc<Self>, limit: usize) -> AsyncResult<BufView> {
|
||||
Box::pin(async move {
|
||||
let mut r = RcRef::map(self, |r| &r.0).borrow_mut().await;
|
||||
let mut r = RcRef::map(self, |r| &r.stream).borrow_mut().await;
|
||||
let mut data = vec![0; limit];
|
||||
let nread = r.read(&mut data).await?.unwrap_or(0);
|
||||
data.truncate(nread);
|
||||
Ok(BufView::from(data))
|
||||
})
|
||||
}
|
||||
|
||||
fn read_byob(
|
||||
self: Rc<Self>,
|
||||
mut buf: BufMutView,
|
||||
) -> AsyncResult<(usize, BufMutView)> {
|
||||
Box::pin(async move {
|
||||
let mut r = RcRef::map(self, |r| &r.stream).borrow_mut().await;
|
||||
let nread = r.read(&mut buf).await?.unwrap_or(0);
|
||||
Ok((nread, buf))
|
||||
})
|
||||
}
|
||||
|
||||
fn shutdown(self: Rc<Self>) -> AsyncResult<()> {
|
||||
Box::pin(async move {
|
||||
let mut r = RcRef::map(self, |r| &r.stream).borrow_mut().await;
|
||||
r.stop(quinn::VarInt::from(0u32))?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[op2(async)]
|
||||
#[serde]
|
||||
pub(crate) async fn op_quic_accept_bi(
|
||||
pub(crate) async fn op_quic_connection_accept_bi(
|
||||
#[cppgc] connection: &ConnectionResource,
|
||||
state: Rc<RefCell<OpState>>,
|
||||
) -> Result<(ResourceId, ResourceId), AnyError> {
|
||||
) -> Result<(ResourceId, ResourceId), QuicError> {
|
||||
match connection.0.accept_bi().await {
|
||||
Ok((tx, rx)) => {
|
||||
let mut state = state.borrow_mut();
|
||||
|
@ -517,7 +732,7 @@ pub(crate) async fn op_quic_accept_bi(
|
|||
Err(e) => match e {
|
||||
quinn::ConnectionError::LocallyClosed
|
||||
| quinn::ConnectionError::ApplicationClosed(..) => {
|
||||
Err(bad_resource("QuicConn is closed"))
|
||||
Err(QuicError::BadResource("QuicConnection"))
|
||||
}
|
||||
_ => Err(e.into()),
|
||||
},
|
||||
|
@ -526,11 +741,11 @@ pub(crate) async fn op_quic_accept_bi(
|
|||
|
||||
#[op2(async)]
|
||||
#[serde]
|
||||
pub(crate) async fn op_quic_open_bi(
|
||||
pub(crate) async fn op_quic_connection_open_bi(
|
||||
#[cppgc] connection: &ConnectionResource,
|
||||
state: Rc<RefCell<OpState>>,
|
||||
wait_for_available: bool,
|
||||
) -> Result<(ResourceId, ResourceId), AnyError> {
|
||||
) -> Result<(ResourceId, ResourceId), QuicError> {
|
||||
let (tx, rx) = if wait_for_available {
|
||||
connection.0.open_bi().await?
|
||||
} else {
|
||||
|
@ -539,7 +754,7 @@ pub(crate) async fn op_quic_open_bi(
|
|||
match pin!(connection.0.open_bi()).poll(&mut cx) {
|
||||
Poll::Ready(r) => r?,
|
||||
Poll::Pending => {
|
||||
return Err(generic_error("Connection has reached the maximum number of outgoing concurrent bidirectional streams"));
|
||||
return Err(QuicError::MaxStreams("bidirectional"));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -551,10 +766,10 @@ pub(crate) async fn op_quic_open_bi(
|
|||
|
||||
#[op2(async)]
|
||||
#[serde]
|
||||
pub(crate) async fn op_quic_accept_uni(
|
||||
pub(crate) async fn op_quic_connection_accept_uni(
|
||||
#[cppgc] connection: &ConnectionResource,
|
||||
state: Rc<RefCell<OpState>>,
|
||||
) -> Result<ResourceId, AnyError> {
|
||||
) -> Result<ResourceId, QuicError> {
|
||||
match connection.0.accept_uni().await {
|
||||
Ok(rx) => {
|
||||
let rid = state
|
||||
|
@ -566,7 +781,7 @@ pub(crate) async fn op_quic_accept_uni(
|
|||
Err(e) => match e {
|
||||
quinn::ConnectionError::LocallyClosed
|
||||
| quinn::ConnectionError::ApplicationClosed(..) => {
|
||||
Err(bad_resource("QuicConn is closed"))
|
||||
Err(QuicError::BadResource("QuicConnection"))
|
||||
}
|
||||
_ => Err(e.into()),
|
||||
},
|
||||
|
@ -575,11 +790,11 @@ pub(crate) async fn op_quic_accept_uni(
|
|||
|
||||
#[op2(async)]
|
||||
#[serde]
|
||||
pub(crate) async fn op_quic_open_uni(
|
||||
pub(crate) async fn op_quic_connection_open_uni(
|
||||
#[cppgc] connection: &ConnectionResource,
|
||||
state: Rc<RefCell<OpState>>,
|
||||
wait_for_available: bool,
|
||||
) -> Result<ResourceId, AnyError> {
|
||||
) -> Result<ResourceId, QuicError> {
|
||||
let tx = if wait_for_available {
|
||||
connection.0.open_uni().await?
|
||||
} else {
|
||||
|
@ -588,7 +803,7 @@ pub(crate) async fn op_quic_open_uni(
|
|||
match pin!(connection.0.open_uni()).poll(&mut cx) {
|
||||
Poll::Ready(r) => r?,
|
||||
Poll::Pending => {
|
||||
return Err(generic_error("Connection has reached the maximum number of outgoing concurrent unidirectional streams"));
|
||||
return Err(QuicError::MaxStreams("unidirectional"));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -600,63 +815,80 @@ pub(crate) async fn op_quic_open_uni(
|
|||
}
|
||||
|
||||
#[op2(async)]
|
||||
pub(crate) async fn op_quic_send_datagram(
|
||||
pub(crate) async fn op_quic_connection_send_datagram(
|
||||
#[cppgc] connection: &ConnectionResource,
|
||||
#[buffer] buf: JsBuffer,
|
||||
) -> Result<(), AnyError> {
|
||||
) -> Result<(), QuicError> {
|
||||
connection.0.send_datagram_wait(buf.to_vec().into()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[op2(async)]
|
||||
pub(crate) async fn op_quic_read_datagram(
|
||||
#[buffer]
|
||||
pub(crate) async fn op_quic_connection_read_datagram(
|
||||
#[cppgc] connection: &ConnectionResource,
|
||||
#[buffer] mut buf: JsBuffer,
|
||||
) -> Result<u32, AnyError> {
|
||||
) -> Result<Vec<u8>, QuicError> {
|
||||
let data = connection.0.read_datagram().await?;
|
||||
buf[0..data.len()].copy_from_slice(&data);
|
||||
Ok(data.len() as _)
|
||||
Ok(data.into())
|
||||
}
|
||||
|
||||
#[op2(fast)]
|
||||
pub(crate) fn op_quic_max_datagram_size(
|
||||
pub(crate) fn op_quic_connection_get_max_datagram_size(
|
||||
#[cppgc] connection: &ConnectionResource,
|
||||
) -> Result<u32, AnyError> {
|
||||
) -> Result<u32, QuicError> {
|
||||
Ok(connection.0.max_datagram_size().unwrap_or(0) as _)
|
||||
}
|
||||
|
||||
#[op2(fast)]
|
||||
pub(crate) fn op_quic_get_send_stream_priority(
|
||||
pub(crate) fn op_quic_send_stream_get_priority(
|
||||
state: Rc<RefCell<OpState>>,
|
||||
#[smi] rid: ResourceId,
|
||||
) -> Result<i32, AnyError> {
|
||||
) -> Result<i32, QuicError> {
|
||||
let resource = state
|
||||
.borrow()
|
||||
.resource_table
|
||||
.get::<SendStreamResource>(rid)?;
|
||||
let r = RcRef::map(resource, |r| &r.0).try_borrow();
|
||||
match r {
|
||||
Some(s) => Ok(s.priority()?),
|
||||
None => Err(generic_error("Unable to get priority")),
|
||||
}
|
||||
Ok(resource.priority.load(Ordering::Relaxed))
|
||||
}
|
||||
|
||||
#[op2(fast)]
|
||||
pub(crate) fn op_quic_set_send_stream_priority(
|
||||
pub(crate) fn op_quic_send_stream_set_priority(
|
||||
state: Rc<RefCell<OpState>>,
|
||||
#[smi] rid: ResourceId,
|
||||
priority: i32,
|
||||
) -> Result<(), AnyError> {
|
||||
) -> Result<(), QuicError> {
|
||||
let resource = state
|
||||
.borrow()
|
||||
.resource_table
|
||||
.get::<SendStreamResource>(rid)?;
|
||||
let r = RcRef::map(resource, |r| &r.0).try_borrow();
|
||||
match r {
|
||||
Some(s) => {
|
||||
s.set_priority(priority)?;
|
||||
Ok(())
|
||||
}
|
||||
None => Err(generic_error("Unable to set priority")),
|
||||
}
|
||||
resource.priority.store(priority, Ordering::Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[op2(fast)]
|
||||
#[bigint]
|
||||
pub(crate) fn op_quic_send_stream_get_id(
|
||||
state: Rc<RefCell<OpState>>,
|
||||
#[smi] rid: ResourceId,
|
||||
) -> Result<u64, QuicError> {
|
||||
let resource = state
|
||||
.borrow()
|
||||
.resource_table
|
||||
.get::<SendStreamResource>(rid)?;
|
||||
let stream_id = quinn::VarInt::from(resource.stream_id).into_inner();
|
||||
Ok(stream_id)
|
||||
}
|
||||
|
||||
#[op2(fast)]
|
||||
#[bigint]
|
||||
pub(crate) fn op_quic_recv_stream_get_id(
|
||||
state: Rc<RefCell<OpState>>,
|
||||
#[smi] rid: ResourceId,
|
||||
) -> Result<u64, QuicError> {
|
||||
let resource = state
|
||||
.borrow()
|
||||
.resource_table
|
||||
.get::<RecvStreamResource>(rid)?;
|
||||
let stream_id = quinn::VarInt::from(resource.stream_id).into_inner();
|
||||
Ok(stream_id)
|
||||
}
|
||||
|
|
|
@ -1075,6 +1075,11 @@ export function builtinTracer(): Tracer {
|
|||
return builtinTracerCache;
|
||||
}
|
||||
|
||||
// We specify a very high version number, to allow any `@opentelemetry/api`
|
||||
// version to load this module. This does cause @opentelemetry/api to not be
|
||||
// able to register anything itself with the global registration methods.
|
||||
const OTEL_API_COMPAT_VERSION = "1.999.999";
|
||||
|
||||
export function bootstrap(
|
||||
config: [
|
||||
0 | 1,
|
||||
|
@ -1102,6 +1107,19 @@ export function bootstrap(
|
|||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (TRACING_ENABLED || METRICS_ENABLED) {
|
||||
const otel = globalThis[SymbolFor("opentelemetry.js.api.1")] ??= {
|
||||
version: OTEL_API_COMPAT_VERSION,
|
||||
};
|
||||
if (TRACING_ENABLED) {
|
||||
otel.trace = TracerProvider;
|
||||
otel.context = ContextManager;
|
||||
}
|
||||
if (METRICS_ENABLED) {
|
||||
otel.metrics = MeterProvider;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const telemetry = {
|
||||
|
|
|
@ -47,6 +47,7 @@ use deno_kv::KvErrorKind;
|
|||
use deno_kv::KvMutationError;
|
||||
use deno_napi::NApiError;
|
||||
use deno_net::ops::NetError;
|
||||
use deno_net::QuicError;
|
||||
use deno_permissions::ChildPermissionError;
|
||||
use deno_permissions::NetDescriptorFromUrlParseError;
|
||||
use deno_permissions::PathResolveError;
|
||||
|
@ -1589,6 +1590,27 @@ fn get_sync_fetch_error(error: &SyncFetchError) -> &'static str {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_quic_error_class(error: &QuicError) -> &'static str {
|
||||
match error {
|
||||
QuicError::CannotListen => "Error",
|
||||
QuicError::MissingTlsKey => "TypeError",
|
||||
QuicError::InvalidDuration => "TypeError",
|
||||
QuicError::UnableToResolve => "Error",
|
||||
QuicError::StdIo(e) => get_io_error_class(e),
|
||||
QuicError::PermissionCheck(e) => get_permission_check_error_class(e),
|
||||
QuicError::VarIntBoundsExceeded(_) => "RangeError",
|
||||
QuicError::Rustls(_) => "Error",
|
||||
QuicError::Tls(e) => get_tls_error_class(e),
|
||||
QuicError::ConnectionError(_) => "Error",
|
||||
QuicError::ConnectError(_) => "Error",
|
||||
QuicError::SendDatagramError(_) => "Error",
|
||||
QuicError::ClosedStream(_) => "BadResource",
|
||||
QuicError::BadResource(_) => "BadResource",
|
||||
QuicError::MaxStreams(_) => "RangeError",
|
||||
QuicError::Core(e) => get_error_class_name(e).unwrap_or("Error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> {
|
||||
deno_core::error::get_custom_error_class(e)
|
||||
.or_else(|| {
|
||||
|
@ -1824,6 +1846,7 @@ pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> {
|
|||
e.downcast_ref::<deno_net::io::MapError>()
|
||||
.map(get_net_map_error)
|
||||
})
|
||||
.or_else(|| e.downcast_ref::<QuicError>().map(get_quic_error_class))
|
||||
.or_else(|| {
|
||||
e.downcast_ref::<BroadcastChannelError>()
|
||||
.map(get_broadcast_channel_error)
|
||||
|
|
|
@ -423,7 +423,7 @@ fn get_suggestions_for_terminal_errors(e: &JsError) -> Vec<FixSuggestion> {
|
|||
"Run again with `--unstable-webgpu` flag to enable this API.",
|
||||
),
|
||||
];
|
||||
} else if msg.contains("listenQuic is not a function") {
|
||||
} else if msg.contains("QuicEndpoint is not a constructor") {
|
||||
return vec![
|
||||
FixSuggestion::info("listenQuic is an unstable API."),
|
||||
FixSuggestion::hint(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||
|
||||
import { core } from "ext:core/mod.js";
|
||||
import { core, primordials } from "ext:core/mod.js";
|
||||
import {
|
||||
op_net_listen_udp,
|
||||
op_net_listen_unixpacket,
|
||||
|
@ -13,7 +13,6 @@ import * as console from "ext:deno_console/01_console.js";
|
|||
import * as ffi from "ext:deno_ffi/00_ffi.js";
|
||||
import * as net from "ext:deno_net/01_net.js";
|
||||
import * as tls from "ext:deno_net/02_tls.js";
|
||||
import * as quic from "ext:deno_net/03_quic.js";
|
||||
import * as serve from "ext:deno_http/00_serve.ts";
|
||||
import * as http from "ext:deno_http/01_http.js";
|
||||
import * as websocket from "ext:deno_http/02_websocket.ts";
|
||||
|
@ -32,6 +31,10 @@ import * as cron from "ext:deno_cron/01_cron.ts";
|
|||
import * as webgpuSurface from "ext:deno_webgpu/02_surface.js";
|
||||
import * as telemetry from "ext:deno_telemetry/telemetry.ts";
|
||||
|
||||
const { ObjectDefineProperties } = primordials;
|
||||
|
||||
const loadQuic = core.createLazyLoader("ext:deno_net/03_quic.js");
|
||||
|
||||
const denoNs = {
|
||||
Process: process.Process,
|
||||
run: process.run,
|
||||
|
@ -175,17 +178,28 @@ denoNsUnstableById[unstableIds.net] = {
|
|||
op_net_listen_udp,
|
||||
op_net_listen_unixpacket,
|
||||
),
|
||||
|
||||
connectQuic: quic.connectQuic,
|
||||
listenQuic: quic.listenQuic,
|
||||
QuicBidirectionalStream: quic.QuicBidirectionalStream,
|
||||
QuicConn: quic.QuicConn,
|
||||
QuicListener: quic.QuicListener,
|
||||
QuicReceiveStream: quic.QuicReceiveStream,
|
||||
QuicSendStream: quic.QuicSendStream,
|
||||
QuicIncoming: quic.QuicIncoming,
|
||||
};
|
||||
|
||||
ObjectDefineProperties(denoNsUnstableById[unstableIds.net], {
|
||||
connectQuic: core.propWritableLazyLoaded((q) => q.connectQuic, loadQuic),
|
||||
QuicEndpoint: core.propWritableLazyLoaded((q) => q.QuicEndpoint, loadQuic),
|
||||
QuicBidirectionalStream: core.propWritableLazyLoaded(
|
||||
(q) => q.QuicBidirectionalStream,
|
||||
loadQuic,
|
||||
),
|
||||
QuicConn: core.propWritableLazyLoaded((q) => q.QuicConn, loadQuic),
|
||||
QuicListener: core.propWritableLazyLoaded((q) => q.QuicListener, loadQuic),
|
||||
QuicReceiveStream: core.propWritableLazyLoaded(
|
||||
(q) => q.QuicReceiveStream,
|
||||
loadQuic,
|
||||
),
|
||||
QuicSendStream: core.propWritableLazyLoaded(
|
||||
(q) => q.QuicSendStream,
|
||||
loadQuic,
|
||||
),
|
||||
QuicIncoming: core.propWritableLazyLoaded((q) => q.QuicIncoming, loadQuic),
|
||||
});
|
||||
|
||||
// denoNsUnstableById[unstableIds.unsafeProto] = { __proto__: null }
|
||||
|
||||
denoNsUnstableById[unstableIds.webgpu] = {
|
||||
|
|
|
@ -368,9 +368,11 @@
|
|||
"test-dgram-send-callback-multi-buffer.js",
|
||||
"test-dgram-send-callback-recursive.js",
|
||||
"test-dgram-send-default-host.js",
|
||||
"test-dgram-send-empty-array.js",
|
||||
"test-dgram-send-empty-buffer.js",
|
||||
"test-dgram-send-empty-packet.js",
|
||||
// TODO(kt3k): These tests are flaky on macOS CI.
|
||||
// https://github.com/denoland/deno/issues/27316
|
||||
// "test-dgram-send-empty-array.js",
|
||||
// "test-dgram-send-empty-buffer.js",
|
||||
// "test-dgram-send-empty-packet.js",
|
||||
"test-dgram-send-error.js",
|
||||
"test-dgram-send-invalid-msg-type.js",
|
||||
"test-dgram-send-multi-buffer-copy.js",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<!-- deno-fmt-ignore-file -->
|
||||
# Remaining Node Tests
|
||||
|
||||
1155 tests out of 3681 have been ported from Node 20.11.1 (31.38% ported, 69.14% remaining).
|
||||
1152 tests out of 3681 have been ported from Node 20.11.1 (31.30% ported, 69.22% remaining).
|
||||
|
||||
NOTE: This file should not be manually edited. Please edit `tests/node_compat/config.json` and run `deno task setup` in `tests/node_compat/runner` dir instead.
|
||||
|
||||
|
@ -499,6 +499,9 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co
|
|||
- [parallel/test-dgram-multicast-set-interface.js](https://github.com/nodejs/node/tree/v20.11.1/test/parallel/test-dgram-multicast-set-interface.js)
|
||||
- [parallel/test-dgram-multicast-setTTL.js](https://github.com/nodejs/node/tree/v20.11.1/test/parallel/test-dgram-multicast-setTTL.js)
|
||||
- [parallel/test-dgram-send-address-types.js](https://github.com/nodejs/node/tree/v20.11.1/test/parallel/test-dgram-send-address-types.js)
|
||||
- [parallel/test-dgram-send-empty-array.js](https://github.com/nodejs/node/tree/v20.11.1/test/parallel/test-dgram-send-empty-array.js)
|
||||
- [parallel/test-dgram-send-empty-buffer.js](https://github.com/nodejs/node/tree/v20.11.1/test/parallel/test-dgram-send-empty-buffer.js)
|
||||
- [parallel/test-dgram-send-empty-packet.js](https://github.com/nodejs/node/tree/v20.11.1/test/parallel/test-dgram-send-empty-packet.js)
|
||||
- [parallel/test-dgram-send-queue-info.js](https://github.com/nodejs/node/tree/v20.11.1/test/parallel/test-dgram-send-queue-info.js)
|
||||
- [parallel/test-dgram-sendto.js](https://github.com/nodejs/node/tree/v20.11.1/test/parallel/test-dgram-sendto.js)
|
||||
- [parallel/test-dgram-setBroadcast.js](https://github.com/nodejs/node/tree/v20.11.1/test/parallel/test-dgram-setBroadcast.js)
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
// deno-fmt-ignore-file
|
||||
// deno-lint-ignore-file
|
||||
|
||||
// Copyright Joyent and Node contributors. All rights reserved. MIT license.
|
||||
// Taken from Node 20.11.1
|
||||
// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually.
|
||||
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
|
||||
const assert = require('assert');
|
||||
const dgram = require('dgram');
|
||||
|
||||
const client = dgram.createSocket('udp4');
|
||||
|
||||
let interval;
|
||||
|
||||
client.on('message', common.mustCall(function onMessage(buf, info) {
|
||||
const expected = Buffer.alloc(0);
|
||||
assert.ok(buf.equals(expected), `Expected empty message but got ${buf}`);
|
||||
clearInterval(interval);
|
||||
client.close();
|
||||
}));
|
||||
|
||||
client.on('listening', common.mustCall(function() {
|
||||
interval = setInterval(function() {
|
||||
client.send([], client.address().port, common.localhostIPv4);
|
||||
}, 10);
|
||||
}));
|
||||
|
||||
client.bind(0);
|
|
@ -1,50 +0,0 @@
|
|||
// deno-fmt-ignore-file
|
||||
// deno-lint-ignore-file
|
||||
|
||||
// Copyright Joyent and Node contributors. All rights reserved. MIT license.
|
||||
// Taken from Node 20.11.1
|
||||
// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually.
|
||||
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
|
||||
const assert = require('assert');
|
||||
const dgram = require('dgram');
|
||||
|
||||
const client = dgram.createSocket('udp4');
|
||||
|
||||
client.bind(0, common.mustCall(function() {
|
||||
const port = this.address().port;
|
||||
|
||||
client.on('message', common.mustCall(function onMessage(buffer) {
|
||||
assert.strictEqual(buffer.length, 0);
|
||||
clearInterval(interval);
|
||||
client.close();
|
||||
}));
|
||||
|
||||
const buf = Buffer.alloc(0);
|
||||
const interval = setInterval(function() {
|
||||
client.send(buf, 0, 0, port, '127.0.0.1', common.mustCall());
|
||||
}, 10);
|
||||
}));
|
|
@ -1,36 +0,0 @@
|
|||
// deno-fmt-ignore-file
|
||||
// deno-lint-ignore-file
|
||||
|
||||
// Copyright Joyent and Node contributors. All rights reserved. MIT license.
|
||||
// Taken from Node 20.11.1
|
||||
// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually.
|
||||
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
|
||||
const assert = require('assert');
|
||||
const dgram = require('dgram');
|
||||
|
||||
const client = dgram.createSocket('udp4');
|
||||
|
||||
client.bind(0, common.mustCall(function() {
|
||||
|
||||
client.on('message', common.mustCall(callback));
|
||||
|
||||
const port = this.address().port;
|
||||
const buf = Buffer.alloc(1);
|
||||
|
||||
const interval = setInterval(function() {
|
||||
client.send(buf, 0, 0, port, '127.0.0.1', common.mustCall(callback));
|
||||
}, 10);
|
||||
|
||||
function callback(firstArg) {
|
||||
// If client.send() callback, firstArg should be null.
|
||||
// If client.on('message') listener, firstArg should be a 0-length buffer.
|
||||
if (firstArg instanceof Buffer) {
|
||||
assert.strictEqual(firstArg.length, 0);
|
||||
clearInterval(interval);
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
}));
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"name": "@deno/otel",
|
||||
"version": "0.0.2",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./register": "./src/register.ts"
|
||||
},
|
||||
"tasks": {
|
||||
"check:license": "deno run -A tools/check_license.ts",
|
||||
"check:docs": "deno doc --lint src/index.ts",
|
||||
"check": "deno task check:license --check",
|
||||
"ok": "deno fmt --check && deno lint && deno task check"
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
// Copyright 2024-2024 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
import { context, trace, metrics } from "npm:@opentelemetry/api@1";
|
||||
|
||||
// @ts-ignore Deno.telemetry is not typed yet
|
||||
const telemetry = Deno.telemetry ?? Deno.tracing;
|
||||
|
||||
/**
|
||||
* Register `Deno.telemetry` with the OpenTelemetry library.
|
||||
*/
|
||||
export function register() {
|
||||
context.setGlobalContextManager(telemetry.contextManager);
|
||||
trace.setGlobalTracerProvider(telemetry.tracerProvider);
|
||||
metrics.setGlobalMeterProvider(telemetry.meterProvider);
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
// Copyright 2024-2024 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
import { register } from "./index.ts";
|
||||
|
||||
register();
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./register": "./src/register.ts"
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"scope": "deno",
|
||||
"name": "otel",
|
||||
"latest": "0.0.2",
|
||||
"versions": {
|
||||
"0.0.2": {}
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,7 +1,6 @@
|
|||
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||
|
||||
import { trace } from "npm:@opentelemetry/api@1.9.0";
|
||||
import "jsr:@deno/otel@0.0.2/register";
|
||||
|
||||
const tracer = trace.getTracer("example-tracer");
|
||||
|
||||
|
|
|
@ -1,28 +1,32 @@
|
|||
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||
|
||||
import { assertEquals } from "./test_util.ts";
|
||||
import { assert, assertEquals } from "./test_util.ts";
|
||||
|
||||
const cert = Deno.readTextFileSync("tests/testdata/tls/localhost.crt");
|
||||
const key = Deno.readTextFileSync("tests/testdata/tls/localhost.key");
|
||||
const caCerts = [Deno.readTextFileSync("tests/testdata/tls/RootCA.pem")];
|
||||
|
||||
async function pair(opt?: Deno.QuicTransportOptions): Promise<
|
||||
[Deno.QuicConn, Deno.QuicConn, Deno.QuicListener]
|
||||
> {
|
||||
const listener = await Deno.listenQuic({
|
||||
hostname: "localhost",
|
||||
port: 0,
|
||||
interface Pair {
|
||||
server: Deno.QuicConn;
|
||||
client: Deno.QuicConn;
|
||||
endpoint: Deno.QuicEndpoint;
|
||||
}
|
||||
|
||||
async function pair(opt?: Deno.QuicTransportOptions): Promise<Pair> {
|
||||
const endpoint = new Deno.QuicEndpoint({ hostname: "localhost" });
|
||||
const listener = endpoint.listen({
|
||||
cert,
|
||||
key,
|
||||
alpnProtocols: ["deno-test"],
|
||||
...opt,
|
||||
});
|
||||
assertEquals(endpoint, listener.endpoint);
|
||||
|
||||
const [server, client] = await Promise.all([
|
||||
listener.accept(),
|
||||
Deno.connectQuic({
|
||||
hostname: "localhost",
|
||||
port: listener.addr.port,
|
||||
port: endpoint.addr.port,
|
||||
caCerts,
|
||||
alpnProtocols: ["deno-test"],
|
||||
...opt,
|
||||
|
@ -31,13 +35,14 @@ async function pair(opt?: Deno.QuicTransportOptions): Promise<
|
|||
|
||||
assertEquals(server.protocol, "deno-test");
|
||||
assertEquals(client.protocol, "deno-test");
|
||||
assertEquals(client.remoteAddr, listener.addr);
|
||||
assertEquals(client.remoteAddr, endpoint.addr);
|
||||
assertEquals(server.serverName, "localhost");
|
||||
|
||||
return [server, client, listener];
|
||||
return { server, client, endpoint };
|
||||
}
|
||||
|
||||
Deno.test("bidirectional stream", async () => {
|
||||
const [server, client, listener] = await pair();
|
||||
const { server, client, endpoint } = await pair();
|
||||
|
||||
const encoded = (new TextEncoder()).encode("hi!");
|
||||
|
||||
|
@ -57,12 +62,12 @@ Deno.test("bidirectional stream", async () => {
|
|||
assertEquals(data, encoded);
|
||||
}
|
||||
|
||||
listener.close({ closeCode: 0, reason: "" });
|
||||
client.close({ closeCode: 0, reason: "" });
|
||||
client.close();
|
||||
endpoint.close();
|
||||
});
|
||||
|
||||
Deno.test("unidirectional stream", async () => {
|
||||
const [server, client, listener] = await pair();
|
||||
const { server, client, endpoint } = await pair();
|
||||
|
||||
const encoded = (new TextEncoder()).encode("hi!");
|
||||
|
||||
|
@ -82,12 +87,12 @@ Deno.test("unidirectional stream", async () => {
|
|||
assertEquals(data, encoded);
|
||||
}
|
||||
|
||||
listener.close({ closeCode: 0, reason: "" });
|
||||
client.close({ closeCode: 0, reason: "" });
|
||||
endpoint.close();
|
||||
client.close();
|
||||
});
|
||||
|
||||
Deno.test("datagrams", async () => {
|
||||
const [server, client, listener] = await pair();
|
||||
const { server, client, endpoint } = await pair();
|
||||
|
||||
const encoded = (new TextEncoder()).encode("hi!");
|
||||
|
||||
|
@ -96,22 +101,20 @@ Deno.test("datagrams", async () => {
|
|||
const data = await client.readDatagram();
|
||||
assertEquals(data, encoded);
|
||||
|
||||
listener.close({ closeCode: 0, reason: "" });
|
||||
client.close({ closeCode: 0, reason: "" });
|
||||
endpoint.close();
|
||||
client.close();
|
||||
});
|
||||
|
||||
Deno.test("closing", async () => {
|
||||
const [server, client, listener] = await pair();
|
||||
const { server, client } = await pair();
|
||||
|
||||
server.close({ closeCode: 42, reason: "hi!" });
|
||||
|
||||
assertEquals(await client.closed, { closeCode: 42, reason: "hi!" });
|
||||
|
||||
listener.close({ closeCode: 0, reason: "" });
|
||||
});
|
||||
|
||||
Deno.test("max concurrent streams", async () => {
|
||||
const [server, client, listener] = await pair({
|
||||
const { server, client, endpoint } = await pair({
|
||||
maxConcurrentBidirectionalStreams: 1,
|
||||
maxConcurrentUnidirectionalStreams: 1,
|
||||
});
|
||||
|
@ -136,15 +139,13 @@ Deno.test("max concurrent streams", async () => {
|
|||
});
|
||||
}
|
||||
|
||||
listener.close({ closeCode: 0, reason: "" });
|
||||
server.close({ closeCode: 0, reason: "" });
|
||||
client.close({ closeCode: 0, reason: "" });
|
||||
endpoint.close();
|
||||
client.close();
|
||||
});
|
||||
|
||||
Deno.test("incoming", async () => {
|
||||
const listener = await Deno.listenQuic({
|
||||
hostname: "localhost",
|
||||
port: 0,
|
||||
const endpoint = new Deno.QuicEndpoint({ hostname: "localhost" });
|
||||
const listener = endpoint.listen({
|
||||
cert,
|
||||
key,
|
||||
alpnProtocols: ["deno-test"],
|
||||
|
@ -153,7 +154,7 @@ Deno.test("incoming", async () => {
|
|||
const connect = () =>
|
||||
Deno.connectQuic({
|
||||
hostname: "localhost",
|
||||
port: listener.addr.port,
|
||||
port: endpoint.addr.port,
|
||||
caCerts,
|
||||
alpnProtocols: ["deno-test"],
|
||||
});
|
||||
|
@ -165,8 +166,63 @@ Deno.test("incoming", async () => {
|
|||
|
||||
assertEquals(server.protocol, "deno-test");
|
||||
assertEquals(client.protocol, "deno-test");
|
||||
assertEquals(client.remoteAddr, listener.addr);
|
||||
assertEquals(client.remoteAddr, endpoint.addr);
|
||||
|
||||
listener.close({ closeCode: 0, reason: "" });
|
||||
client.close({ closeCode: 0, reason: "" });
|
||||
endpoint.close();
|
||||
client.close();
|
||||
});
|
||||
|
||||
Deno.test("0rtt", async () => {
|
||||
const sEndpoint = new Deno.QuicEndpoint({ hostname: "localhost" });
|
||||
const listener = sEndpoint.listen({
|
||||
cert,
|
||||
key,
|
||||
alpnProtocols: ["deno-test"],
|
||||
});
|
||||
|
||||
(async () => {
|
||||
while (true) {
|
||||
let incoming;
|
||||
try {
|
||||
incoming = await listener.incoming();
|
||||
} catch (e) {
|
||||
if (e instanceof Deno.errors.BadResource) {
|
||||
break;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
const conn = incoming.accept({ zeroRtt: true });
|
||||
conn.handshake.then(() => {
|
||||
conn.close();
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
const endpoint = new Deno.QuicEndpoint();
|
||||
|
||||
const c1 = await Deno.connectQuic({
|
||||
hostname: "localhost",
|
||||
port: sEndpoint.addr.port,
|
||||
caCerts,
|
||||
alpnProtocols: ["deno-test"],
|
||||
endpoint,
|
||||
});
|
||||
|
||||
await c1.closed;
|
||||
|
||||
const c2 = Deno.connectQuic({
|
||||
hostname: "localhost",
|
||||
port: sEndpoint.addr.port,
|
||||
caCerts,
|
||||
alpnProtocols: ["deno-test"],
|
||||
zeroRtt: true,
|
||||
endpoint,
|
||||
});
|
||||
|
||||
assert(!(c2 instanceof Promise), "0rtt should be accepted");
|
||||
|
||||
await c2.closed;
|
||||
|
||||
sEndpoint.close();
|
||||
endpoint.close();
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue