Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions doc/api/quic.md
Original file line number Diff line number Diff line change
Expand Up @@ -974,7 +974,8 @@ added: v23.8.0

* `options` {Object}
* `code` {bigint|number} The error code to include in the `CONNECTION_CLOSE`
frame sent to the peer. **Default:** `0` (no error).
frame sent to the peer. Must be a non-negative 62-bit unsigned varint
(`0n <= code <= 2n ** 62n - 1n`). **Default:** `0` (no error).
* `type` {string} Either `'transport'` or `'application'`. Determines the
error code namespace used in the `CONNECTION_CLOSE` frame. When `'transport'`
(the default), the frame type is `0x1c` and the code is interpreted as a QUIC
Expand Down Expand Up @@ -1056,7 +1057,8 @@ added: v23.8.0
* `error` {any}
* `options` {Object}
* `code` {bigint|number} The error code to include in the `CONNECTION_CLOSE`
frame sent to the peer. **Default:** `0`.
frame sent to the peer. Must be a non-negative 62-bit unsigned varint
(`0n <= code <= 2n ** 62n - 1n`). **Default:** `0`.
* `type` {string} Either `'transport'` or `'application'`. **Default:**
`'transport'`.
* `reason` {string} An optional human-readable reason string included in
Expand Down
8 changes: 8 additions & 0 deletions lib/internal/quic/quic.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const {
ErrorCaptureStackTrace,
FunctionPrototypeBind,
FunctionPrototypeCall,
NumberIsInteger,
ObjectDefineProperties,
ObjectKeys,
PromisePrototypeThen,
Expand Down Expand Up @@ -5478,6 +5479,13 @@ function validateCloseOptions(options) {
throw new ERR_INVALID_ARG_TYPE('options.code',
['bigint', 'number'], code);
}
if (typeof code === 'number' && !NumberIsInteger(code)) {
throw new ERR_OUT_OF_RANGE('options.code', 'an integer', code);
}
if (code < 0 || code > kMaxQuicErrorCode) {
throw new ERR_OUT_OF_RANGE('options.code',
`>= 0 and <= ${kMaxQuicErrorCode}`, code);
}
}
validateOneOf(type, 'options.type', ['transport', 'application']);
if (reason !== undefined) {
Expand Down
23 changes: 20 additions & 3 deletions test/parallel/test-quic-session-destroy-validate-options.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,20 @@ assert.throws(() => clientSession.destroy(goodError, { reason: 42 }), {
assert.strictEqual(clientSession.destroyed, false);
assert.strictEqual(stream.destroyed, false);

// 5. options.code does not fit in a QUIC varint -> throws ERR_OUT_OF_RANGE,
// from both destroy() and close().
for (const code of [-1, 1.5, NaN, Infinity, 2 ** 62, -1n, 2n ** 62n]) {
assert.throws(() => clientSession.destroy(goodError, { code }), {
code: 'ERR_OUT_OF_RANGE',
});
assert.throws(() => clientSession.close({ code }), {
code: 'ERR_OUT_OF_RANGE',
});
}
assert.strictEqual(clientSession.destroyed, false);
assert.strictEqual(clientSession.closing, false);
assert.strictEqual(stream.destroyed, false);

// Now switch the handlers to expect the real teardown so the final
// destroy with valid options can run cleanly.
diagnostics_channel.unsubscribe('quic.session.error', errSub);
Expand All @@ -107,19 +121,22 @@ stream.onerror = mustCall((err) => { assert.strictEqual(err, goodError); });
// final destroy, so the rejections do not race ahead of any awaits in
// the test body. The client rejects with the original `goodError`;
// the server decodes the CONNECTION_CLOSE frame transport code into
// an `ERR_QUIC_TRANSPORT_ERROR`.
// an `ERR_QUIC_TRANSPORT_ERROR`. The code is the largest one that fits
// in a QUIC varint, so it must be accepted and arrive unchanged.
const maxCode = 2n ** 62n - 1n;
const clientClosedAssertion = assert.rejects(clientSession.closed, goodError);
const serverClosedAssertion = assert.rejects(serverSession.closed, mustCall((err) => {
assert.strictEqual(err.code, 'ERR_QUIC_TRANSPORT_ERROR');
assert.strictEqual(err.errorCode, maxCode);
return true;
}));

// 5. Valid options after the failed attempts -> session destroys
// 6. Valid options after the failed attempts -> session destroys
// normally, the underlying handle sends CONNECTION_CLOSE with the
// supplied transport code, and the local closed promise rejects
// with the original error.
clientSession.destroy(goodError, {
code: 1n,
code: maxCode,
type: 'transport',
reason: 'after validation throw',
});
Expand Down
Loading