Skip to content

Commit 5342557

Browse files
committed
quic: start adding in the internal quic js api
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.
1 parent 3ab0499 commit 5342557

File tree

9 files changed

+2347
-69
lines changed

9 files changed

+2347
-69
lines changed

doc/api/errors.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3571,6 +3571,42 @@ removed: v10.0.0
35713571

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

3574+
<a id="ERR_QUIC_CONNECTION_FAILED"></a>
3575+
3576+
### `ERR_QUIC_CONNECTION_FAILED`
3577+
3578+
<!-- YAML
3579+
added: REPLACEME
3580+
-->
3581+
3582+
> Stability: 1 - Experimental
3583+
3584+
Establishing a QUIC connection failed.
3585+
3586+
<a id="ERR_QUIC_ENDPOINT_CLOSED"></a>
3587+
3588+
### `ERR_QUIC_ENDPOINT_CLOSED`
3589+
3590+
<!-- YAML
3591+
added: REPLACEME
3592+
-->
3593+
3594+
> Stability: 1 - Experimental
3595+
3596+
A QUIC Endpoint closed with an error.
3597+
3598+
<a id="ERR_QUIC_OPEN_STREAM_FAILED"></a>
3599+
3600+
### `ERR_QUIC_OPEN_STREAM_FAILED`
3601+
3602+
<!-- YAML
3603+
added: REPLACEME
3604+
-->
3605+
3606+
> Stability: 1 - Experimental
3607+
3608+
Opening a QUIC stream failed.
3609+
35743610
<a id="ERR_SOCKET_CANNOT_SEND"></a>
35753611

35763612
### `ERR_SOCKET_CANNOT_SEND`

lib/internal/errors.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1645,6 +1645,9 @@ E('ERR_PARSE_ARGS_UNKNOWN_OPTION', (option, allowPositionals) => {
16451645
E('ERR_PERFORMANCE_INVALID_TIMESTAMP',
16461646
'%d is not a valid timestamp', TypeError);
16471647
E('ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS', '%s', TypeError);
1648+
E('ERR_QUIC_CONNECTION_FAILED', 'QUIC connection failed', Error);
1649+
E('ERR_QUIC_ENDPOINT_CLOSED', 'QUIC endpoint closed: %s (%d)', Error);
1650+
E('ERR_QUIC_OPEN_STREAM_FAILED', 'Failed to open QUIC stream', Error);
16481651
E('ERR_REQUIRE_CYCLE_MODULE', '%s', Error);
16491652
E('ERR_REQUIRE_ESM',
16501653
function(filename, hasEsmSyntax, parentPath = null, packageJsonPath = null) {

lib/internal/quic/datagrams.js

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
'use strict';
2+
3+
const {
4+
Uint8Array,
5+
} = primordials;
6+
7+
const {
8+
ReadableStream,
9+
} = require('internal/webstreams/readablestream');
10+
11+
const {
12+
WritableStream,
13+
} = require('internal/webstreams/writablestream');
14+
15+
const {
16+
isArrayBufferView,
17+
} = require('util/types');
18+
19+
const {
20+
codes: {
21+
ERR_INVALID_ARG_TYPE,
22+
},
23+
} = require('internal/errors');
24+
25+
// QUIC datagrams are unordered, unreliable packets of data exchanged over
26+
// a session. They are unrelated to any specific QUIC stream. Our implementation
27+
// uses a ReadableStream to receive datagrams and a WritableStream to send them.
28+
// Any ArrayBufferView can be used when sending a datagram. Received datagrams
29+
// will always be Uint8Array.
30+
// The DatagramReadableStream and DatagramWritableStream instances are created
31+
// and held internally by the QUIC Session object. Only the readable and writable
32+
// properties will be exposed.
33+
34+
class DatagramReadableStream {
35+
#readable = undefined;
36+
37+
/** @type {ReadableStreamDefaultController} */
38+
#controller = undefined;
39+
40+
constructor() {
41+
let controller;
42+
this.#readable = new ReadableStream({
43+
start(c) { controller = c; },
44+
});
45+
this.#controller = controller;
46+
}
47+
48+
get readable() { return this.#readable; }
49+
50+
// Close the ReadableStream. The underlying source will be closed. Any
51+
// datagrams already in the queue will be preserved and will be read.
52+
close() {
53+
this.#controller.close();
54+
}
55+
56+
// Errors the readable stream
57+
error(reason) {
58+
this.#controller.error(reason);
59+
}
60+
61+
// Enqueue a datagram to be read by the stream. This will always be
62+
// a Uint8Array.
63+
enqueue(datagram) {
64+
this.#controller.enqueue(datagram);
65+
}
66+
}
67+
68+
class DatagramWritableStream {
69+
#writable = undefined;
70+
/** @type {WritableStreamDefaultController} */
71+
#controller = undefined;
72+
73+
/**
74+
* @callback DatagramWrittenCallback
75+
* @param {Uint8Array} chunk
76+
* @returns {Promise<void>}
77+
*/
78+
79+
/**
80+
* @callback DatagramClosedCallback
81+
* @returns {Promise<void>}
82+
*/
83+
84+
/**
85+
* @callback DatagramAbortedCallback
86+
* @param {any} reason
87+
* @returns {Promise<void>}
88+
*/
89+
90+
/**
91+
* @param {DatagramWrittenCallback} written
92+
* @param {DatagramClosedCallback} closed
93+
* @param {DatagramAbortedCallback} aborted
94+
*/
95+
constructor(written, closed, aborted) {
96+
let controller;
97+
this.#writable = new WritableStream({
98+
start(c) { controller = c; },
99+
async close(controller) {
100+
try {
101+
await closed(undefined);
102+
} catch (err) {
103+
controller.error(err);
104+
}
105+
},
106+
async abort(reason) {
107+
try {
108+
await aborted(reason);
109+
} catch {
110+
// There's nothing to do in this case
111+
}
112+
},
113+
async write(chunk, controller) {
114+
if (!isArrayBufferView(chunk)) {
115+
throw new ERR_INVALID_ARG_TYPE('chunk', ['ArrayBufferView'], chunk);
116+
}
117+
const {
118+
byteOffset,
119+
byteLength,
120+
} = chunk;
121+
chunk = new Uint8Array(chunk.buffer.transfer(), byteOffset, byteLength);
122+
try {
123+
await written(chunk);
124+
} catch (err) {
125+
controller.error(err);
126+
}
127+
},
128+
});
129+
this.#controller = controller;
130+
}
131+
132+
get writable() { return this.#writable; }
133+
134+
error(reason) {
135+
this.#controller.error(reason);
136+
}
137+
}
138+
139+
module.exports = {
140+
DatagramReadableStream,
141+
DatagramWritableStream,
142+
};

0 commit comments

Comments
 (0)