mirror of
https://github.com/denoland/deno.git
synced 2025-09-25 03:42:30 +00:00
feat(unstable): Implement QUIC (#21942)
Implements a QUIC interface, loosely based on the WebTransport API (a future change could add the WebTransport API, built on top of this one). [quinn](https://docs.rs/quinn/latest/quinn/) is used for the underlying QUIC implementation, for a few reasons: - A cloneable "handle" api which fits quite nicely into deno resources. - Good collaboration with the rust ecosystem, especially rustls. - I like it. <!-- Before submitting a PR, please read https://deno.com/manual/contributing 1. Give the PR a descriptive title. Examples of good title: - fix(std/http): Fix race condition in server - docs(console): Update docstrings - feat(doc): Handle nested reexports Examples of bad title: - fix #7123 - update docs - fix bugs 2. Ensure there is a related issue and it is referenced in the PR text. 3. Ensure there are tests that cover the changes. 4. Ensure `cargo test` passes. 5. Ensure `./tools/format.js` passes without changing files. 6. Ensure `./tools/lint.js` passes. 7. Open as a draft PR if your work is still in progress. The CI won't run all steps, but you can add '[ci]' to a commit message to force it to. 8. If you would like to run the benchmarks on the CI, add the 'ci-bench' label. -->
This commit is contained in:
parent
23f7032d56
commit
65b647909d
12 changed files with 1591 additions and 19 deletions
367
ext/net/03_quic.js
Normal file
367
ext/net/03_quic.js
Normal file
|
@ -0,0 +1,367 @@
|
|||
// Copyright 2018-2024 the Deno authors. All rights reserved. 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_connection_closed,
|
||||
op_quic_connection_get_protocol,
|
||||
op_quic_connection_get_remote_addr,
|
||||
op_quic_endpoint_get_addr,
|
||||
op_quic_get_send_stream_priority,
|
||||
op_quic_incoming_accept,
|
||||
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,
|
||||
} from "ext:core/ops";
|
||||
import {
|
||||
getWritableStreamResourceBacking,
|
||||
ReadableStream,
|
||||
readableStreamForRid,
|
||||
WritableStream,
|
||||
writableStreamForRid,
|
||||
} from "ext:deno_web/06_streams.js";
|
||||
import { loadTlsKeyPair } from "ext:deno_net/02_tls.js";
|
||||
const {
|
||||
BadResourcePrototype,
|
||||
} = core;
|
||||
const {
|
||||
Uint8Array,
|
||||
TypedArrayPrototypeSubarray,
|
||||
SymbolAsyncIterator,
|
||||
SafePromisePrototypeFinally,
|
||||
ObjectPrototypeIsPrototypeOf,
|
||||
} = primordials;
|
||||
|
||||
class QuicSendStream extends WritableStream {
|
||||
get sendOrder() {
|
||||
return op_quic_get_send_stream_priority(
|
||||
getWritableStreamResourceBacking(this).rid,
|
||||
);
|
||||
}
|
||||
|
||||
set sendOrder(p) {
|
||||
op_quic_set_send_stream_priority(
|
||||
getWritableStreamResourceBacking(this).rid,
|
||||
p,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class QuicReceiveStream extends ReadableStream {}
|
||||
|
||||
function readableStream(rid, closed) {
|
||||
// stream can be indirectly closed by closing connection.
|
||||
SafePromisePrototypeFinally(closed, () => {
|
||||
core.tryClose(rid);
|
||||
});
|
||||
return readableStreamForRid(rid, true, QuicReceiveStream);
|
||||
}
|
||||
|
||||
function writableStream(rid, closed) {
|
||||
// stream can be indirectly closed by closing connection.
|
||||
SafePromisePrototypeFinally(closed, () => {
|
||||
core.tryClose(rid);
|
||||
});
|
||||
return writableStreamForRid(rid, true, QuicSendStream);
|
||||
}
|
||||
|
||||
class QuicBidirectionalStream {
|
||||
#readable;
|
||||
#writable;
|
||||
|
||||
constructor(txRid, rxRid, closed) {
|
||||
this.#readable = readableStream(rxRid, closed);
|
||||
this.#writable = writableStream(txRid, closed);
|
||||
}
|
||||
|
||||
get readable() {
|
||||
return this.#readable;
|
||||
}
|
||||
|
||||
get writable() {
|
||||
return this.#writable;
|
||||
}
|
||||
}
|
||||
|
||||
async function* bidiStream(conn, closed) {
|
||||
try {
|
||||
while (true) {
|
||||
const r = await op_quic_accept_bi(conn);
|
||||
yield new QuicBidirectionalStream(r[0], r[1], closed);
|
||||
}
|
||||
} catch (error) {
|
||||
if (ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error)) {
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function* uniStream(conn, closed) {
|
||||
try {
|
||||
while (true) {
|
||||
const uniRid = await op_quic_accept_uni(conn);
|
||||
yield readableStream(uniRid, closed);
|
||||
}
|
||||
} catch (error) {
|
||||
if (ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error)) {
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
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 },
|
||||
{
|
||||
keepAliveInterval,
|
||||
maxIdleTimeout,
|
||||
maxConcurrentBidirectionalStreams,
|
||||
maxConcurrentUnidirectionalStreams,
|
||||
},
|
||||
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,
|
||||
);
|
||||
return new QuicConn(conn);
|
||||
}
|
||||
|
||||
export {
|
||||
connectQuic,
|
||||
listenQuic,
|
||||
QuicBidirectionalStream,
|
||||
QuicConn,
|
||||
QuicIncoming,
|
||||
QuicListener,
|
||||
QuicReceiveStream,
|
||||
QuicSendStream,
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue