Skip to content

Commit

Permalink
quic: start adding in the internal quic js api
Browse files Browse the repository at this point in the history
While the external API for QUIC is expected to be
the WebTransport API primarily, this provides the
internal API for QUIC that aligns with the native
C++ QUIC components.
  • Loading branch information
jasnell committed Jun 2, 2024
1 parent 3ab0499 commit f716980
Show file tree
Hide file tree
Showing 9 changed files with 2,348 additions and 69 deletions.
36 changes: 36 additions & 0 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -3571,6 +3571,42 @@ removed: v10.0.0

The `node:repl` module was unable to parse data from the REPL history file.

<a id="ERR_QUIC_CONNECTION_FAILED"></a>

### `ERR_QUIC_CONNECTION_FAILED`

<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental
Establishing a QUIC connection failed.

<a id="ERR_QUIC_ENDPOINT_CLOSED"></a>

### `ERR_QUIC_ENDPOINT_CLOSED`

<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental
A QUIC Endpoint closed with an error.

<a id="ERR_QUIC_OPEN_STREAM_FAILED"></a>

### `ERR_QUIC_OPEN_STREAM_FAILED`

<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental
Opening a QUIC stream failed.

<a id="ERR_SOCKET_CANNOT_SEND"></a>

### `ERR_SOCKET_CANNOT_SEND`
Expand Down
3 changes: 3 additions & 0 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1645,6 +1645,9 @@ E('ERR_PARSE_ARGS_UNKNOWN_OPTION', (option, allowPositionals) => {
E('ERR_PERFORMANCE_INVALID_TIMESTAMP',
'%d is not a valid timestamp', TypeError);
E('ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS', '%s', TypeError);
E('ERR_QUIC_CONNECTION_FAILED', 'QUIC connection failed', Error);
E('ERR_QUIC_ENDPOINT_CLOSED', 'QUIC endpoint closed: %s (%d)', Error);
E('ERR_QUIC_OPEN_STREAM_FAILED', 'Failed to open QUIC stream', Error);
E('ERR_REQUIRE_CYCLE_MODULE', '%s', Error);
E('ERR_REQUIRE_ESM',
function(filename, hasEsmSyntax, parentPath = null, packageJsonPath = null) {
Expand Down
142 changes: 142 additions & 0 deletions lib/internal/quic/datagrams.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
'use strict';

const {
Uint8Array,
} = primordials;

const {
ReadableStream,
} = require('internal/webstreams/readablestream');

const {
WritableStream,
} = require('internal/webstreams/writablestream');

const {
isArrayBufferView,
} = require('util/types');

const {
codes: {
ERR_INVALID_ARG_TYPE,
},
} = require('internal/errors');

// QUIC datagrams are unordered, unreliable packets of data exchanged over
// a session. They are unrelated to any specific QUIC stream. Our implementation
// uses a ReadableStream to receive datagrams and a WritableStream to send them.
// Any ArrayBufferView can be used when sending a datagram. Received datagrams
// will always be Uint8Array.
// The DatagramReadableStream and DatagramWritableStream instances are created
// and held internally by the QUIC Session object. Only the readable and writable
// properties will be exposed.

class DatagramReadableStream {
#readable = undefined;

/** @type {ReadableStreamDefaultController} */
#controller = undefined;

constructor() {
let controller;
this.#readable = new ReadableStream({
start(c) { controller = c; },
});
this.#controller = controller;
}

get readable() { return this.#readable; }

// Close the ReadableStream. The underlying source will be closed. Any
// datagrams already in the queue will be preserved and will be read.
close() {
this.#controller.close();
}

// Errors the readable stream
error(reason) {
this.#controller.error(reason);
}

// Enqueue a datagram to be read by the stream. This will always be
// a Uint8Array.
enqueue(datagram) {
this.#controller.enqueue(datagram);
}
}

class DatagramWritableStream {
#writable = undefined;
/** @type {WritableStreamDefaultController} */
#controller = undefined;

/**
* @callback DatagramWrittenCallback
* @param {Uint8Array} chunk
* @returns {Promise<void>}
*/

/**
* @callback DatagramClosedCallback
* @returns {Promise<void>}
*/

/**
* @callback DatagramAbortedCallback
* @param {any} reason
* @returns {Promise<void>}
*/

/**
* @param {DatagramWrittenCallback} written
* @param {DatagramClosedCallback} closed
* @param {DatagramAbortedCallback} aborted
*/
constructor(written, closed, aborted) {
let controller;
this.#writable = new WritableStream({
start(c) { controller = c; },
async close(controller) {
try {
await closed(undefined);
} catch (err) {
controller.error(err);
}
},
async abort(reason) {
try {
await aborted(reason);
} catch {
// There's nothing to do in this case
}
},
async write(chunk, controller) {
if (!isArrayBufferView(chunk)) {
throw new ERR_INVALID_ARG_TYPE('chunk', ['ArrayBufferView'], chunk);
}
const {
byteOffset,
byteLength,
} = chunk;
chunk = new Uint8Array(chunk.buffer.transfer(), byteOffset, byteLength);
try {
await written(chunk);
} catch (err) {
controller.error(err);
}
},
});
this.#controller = controller;
}

get writable() { return this.#writable; }

error(reason) {
this.#controller.error(reason);
}
}

module.exports = {
DatagramReadableStream,
DatagramWritableStream,
};
Loading

0 comments on commit f716980

Please sign in to comment.