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
54 changes: 41 additions & 13 deletions lib/internal/quic/quic.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ let debug = require('internal/util/debuglog').debuglog('quic', (fn) => {

const {
Endpoint: Endpoint_,
sendHeaders,
setCallbacks,
setHeadersInterest,

// The constants to be exposed to end users for various options.
CC_ALGO_RENO_STR: CC_ALGO_RENO,
Expand Down Expand Up @@ -1303,6 +1305,17 @@ function parseHeaderPairs(pairs) {
return block;
}

function updateHeaderInterest(handle, inner) {
if (handle === undefined) return;
setHeadersInterest(
handle,
inner.onheaders !== undefined ||
inner.ontrailers !== undefined ||
inner.oninfo !== undefined,
inner.onwanttrailers !== undefined || inner.pendingTrailers !== undefined,
);
}

/**
* Applies session and stream callbacks from an options object to a session.
* @param {QuicSession} session
Expand Down Expand Up @@ -1827,13 +1840,12 @@ class QuicStream {
const inner = this.#inner;
if (fn === undefined) {
inner.onheaders = undefined;
inner.state.wantsHeaders = false;
} else {
validateFunction(fn, 'onheaders');
assertHeadersSupported(inner.session);
inner.onheaders = FunctionPrototypeBind(fn, this);
inner.state.wantsHeaders = true;
}
updateHeaderInterest(this.#handle, inner);
}

/** @type {Function|undefined} */
Expand All @@ -1852,6 +1864,7 @@ class QuicStream {
assertHeadersSupported(inner.session);
inner.oninfo = FunctionPrototypeBind(fn, this);
}
updateHeaderInterest(this.#handle, inner);
}

/** @type {Function|undefined} */
Expand All @@ -1870,6 +1883,7 @@ class QuicStream {
assertHeadersSupported(inner.session);
inner.ontrailers = FunctionPrototypeBind(fn, this);
}
updateHeaderInterest(this.#handle, inner);
}

/** @type {Function|undefined} */
Expand All @@ -1883,13 +1897,12 @@ class QuicStream {
const inner = this.#inner;
if (fn === undefined) {
inner.onwanttrailers = undefined;
inner.state.wantsTrailers = false;
} else {
validateFunction(fn, 'onwanttrailers');
assertHeadersSupported(inner.session);
inner.onwanttrailers = FunctionPrototypeBind(fn, this);
inner.state.wantsTrailers = true;
}
updateHeaderInterest(this.#handle, inner);
}

/**
Expand Down Expand Up @@ -1918,10 +1931,12 @@ class QuicStream {
assertHeadersSupported(inner.session);
if (headers === undefined) {
inner.pendingTrailers = undefined;
updateHeaderInterest(this.#handle, inner);
return;
}
validateObject(headers, 'headers');
inner.pendingTrailers = headers;
updateHeaderInterest(this.#handle, inner);
}

/**
Expand Down Expand Up @@ -2104,7 +2119,8 @@ class QuicStream {
const headerString = buildNgHeaderString(
headers, assertValidPseudoHeader, true /* strictSingleValueFields */);
const flags = terminal ? kHeadersFlagsTerminal : kHeadersFlagsNone;
return this.#handle.sendHeaders(kHeadersKindInitial, headerString, flags);
return sendHeaders(
this.#handle, kHeadersKindInitial, headerString, flags);
}

/**
Expand All @@ -2123,8 +2139,8 @@ class QuicStream {
validateObject(headers, 'headers');
const headerString = buildNgHeaderString(
headers, assertValidPseudoHeader, true);
return this.#handle.sendHeaders(
kHeadersKindHints, headerString, kHeadersFlagsNone);
return sendHeaders(
this.#handle, kHeadersKindHints, headerString, kHeadersFlagsNone);
}

/**
Expand All @@ -2143,8 +2159,8 @@ class QuicStream {
}
validateObject(headers, 'headers');
const headerString = buildNgHeaderString(headers);
return this.#handle.sendHeaders(
kHeadersKindTrailing, headerString, kHeadersFlagsNone);
return sendHeaders(
this.#handle, kHeadersKindTrailing, headerString, kHeadersFlagsNone);
}

/**
Expand Down Expand Up @@ -2562,7 +2578,7 @@ class QuicStream {
assertValidPseudoHeader,
true, // This could become an option in future
);
return this.#handle.sendHeaders(kind, headerString, flags);
return sendHeaders(this.#handle, kind, headerString, flags);
}

[kFinishClose](error) {
Expand Down Expand Up @@ -2670,7 +2686,6 @@ class QuicStream {

switch (kindName) {
case 'initial':
assert(inner.onheaders, 'Unexpected stream headers event');
inner.headers ??= block;
if (onStreamHeadersChannel.hasSubscribers) {
onStreamHeadersChannel.publish({
Expand All @@ -2680,7 +2695,8 @@ class QuicStream {
headers: block,
});
}
safeCallbackInvoke(inner.onheaders, this, block);
if (inner.onheaders)
safeCallbackInvoke(inner.onheaders, this, block);
break;
case 'trailing':
if (onStreamTrailersChannel.hasSubscribers) {
Expand Down Expand Up @@ -2716,8 +2732,20 @@ class QuicStream {
// nghttp3 is asking us to provide trailers to send.
// Check for pre-set pendingTrailers first, then the callback.
if (inner.pendingTrailers) {
this.sendTrailers(inner.pendingTrailers);
let sent;
try {
sent = this.sendTrailers(inner.pendingTrailers);
} catch (error) {
this.destroy(error);
return;
}
if (!sent) {
this.destroy(new ERR_QUIC_STREAM_ABORTED(
'Failed to submit trailing headers'));
return;
}
inner.pendingTrailers = undefined;
updateHeaderInterest(this.#handle, inner);
} else if (typeof inner.onwanttrailers === 'function') {
safeCallbackInvoke(inner.onwanttrailers, this);
}
Expand Down
40 changes: 0 additions & 40 deletions lib/internal/quic/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,8 @@ const {
IDX_STATE_STREAM_HAS_OUTBOUND,
IDX_STATE_STREAM_HAS_READER,
IDX_STATE_STREAM_WANTS_BLOCK,
IDX_STATE_STREAM_WANTS_HEADERS,
IDX_STATE_STREAM_WANTS_RESET,
IDX_STATE_STREAM_WANTS_STOP_SENDING,
IDX_STATE_STREAM_WANTS_TRAILERS,
IDX_STATE_STREAM_RECEIVED_EARLY_DATA,
IDX_STATE_STREAM_WRITE_DESIRED_SIZE,
IDX_STATE_STREAM_BUDGET,
Expand Down Expand Up @@ -145,10 +143,8 @@ assert(IDX_STATE_STREAM_RESET !== undefined);
assert(IDX_STATE_STREAM_HAS_OUTBOUND !== undefined);
assert(IDX_STATE_STREAM_HAS_READER !== undefined);
assert(IDX_STATE_STREAM_WANTS_BLOCK !== undefined);
assert(IDX_STATE_STREAM_WANTS_HEADERS !== undefined);
assert(IDX_STATE_STREAM_WANTS_RESET !== undefined);
assert(IDX_STATE_STREAM_WANTS_STOP_SENDING !== undefined);
assert(IDX_STATE_STREAM_WANTS_TRAILERS !== undefined);
assert(IDX_STATE_STREAM_WRITE_DESIRED_SIZE !== undefined);
assert(IDX_STATE_STREAM_RESET_CODE !== undefined);

Expand Down Expand Up @@ -824,20 +820,6 @@ class QuicStreamState {
DataViewPrototypeSetUint8(handle, this.#offset + IDX_STATE_STREAM_WANTS_BLOCK, val ? 1 : 0);
}

/** @type {boolean} */
get wantsHeaders() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_STREAM_WANTS_HEADERS) !== 0;
}

/** @type {boolean} */
set wantsHeaders(val) {
const handle = this.#handle;
if (handle === undefined) return;
DataViewPrototypeSetUint8(handle, this.#offset + IDX_STATE_STREAM_WANTS_HEADERS, val ? 1 : 0);
}

/** @type {boolean} */
get wantsReset() {
const handle = this.#handle;
Expand Down Expand Up @@ -870,20 +852,6 @@ class QuicStreamState {
val ? 1 : 0);
}

/** @type {boolean} */
get wantsTrailers() {
const handle = this.#handle;
if (handle === undefined) return undefined;
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_STREAM_WANTS_TRAILERS) !== 0;
}

/** @type {boolean} */
set wantsTrailers(val) {
const handle = this.#handle;
if (handle === undefined) return;
DataViewPrototypeSetUint8(handle, this.#offset + IDX_STATE_STREAM_WANTS_TRAILERS, val ? 1 : 0);
}

/** @type {boolean} */
get early() {
const handle = this.#handle;
Expand Down Expand Up @@ -948,8 +916,6 @@ class QuicStreamState {
wantsBlock,
wantsReset,
wantsStopSending,
wantsHeaders,
wantsTrailers,
early,
resetCode,
writeDesiredSize,
Expand All @@ -969,8 +935,6 @@ class QuicStreamState {
wantsBlock,
wantsReset,
wantsStopSending,
wantsHeaders,
wantsTrailers,
early,
resetCode: `${resetCode}`,
writeDesiredSize,
Expand Down Expand Up @@ -1007,8 +971,6 @@ class QuicStreamState {
wantsBlock,
wantsReset,
wantsStopSending,
wantsHeaders,
wantsTrailers,
early,
resetCode,
writeDesiredSize,
Expand All @@ -1028,8 +990,6 @@ class QuicStreamState {
wantsBlock,
wantsReset,
wantsStopSending,
wantsHeaders,
wantsTrailers,
early,
resetCode,
writeDesiredSize,
Expand Down
2 changes: 1 addition & 1 deletion src/quic/application.cc
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ Maybe<Session::Application_Options> Session::Application_Options::From(

// Ensure the advertised max_field_section_size in SETTINGS is at least
// as large as max_header_length. Otherwise the peer would be told to
// restrict headers to a smaller size than what CanAddHeader accepts.
// restrict headers to a smaller size than what the HTTP/3 stream accepts.
if (options.max_field_section_size < options.max_header_length) {
options.max_field_section_size = options.max_header_length;
}
Expand Down
30 changes: 22 additions & 8 deletions src/quic/application.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@

namespace node::quic {

enum class HeadersKind : uint8_t {
HINTS,
INITIAL,
TRAILING,
};

enum class HeadersFlags : uint8_t {
NONE,
TERMINAL,
};

// An Application implements the ALPN-protocol specific semantics on behalf
// of a QUIC Session.
class Session::Application : public MemoryRetainer {
Expand Down Expand Up @@ -95,13 +106,10 @@ class Session::Application : public MemoryRetainer {
// Application.
virtual bool AcknowledgeStreamData(stream_id id, size_t datalen);

// Called to determine if a Header can be added to this application.
// Applications that do not support headers will always return false.
virtual bool CanAddHeader(size_t current_count,
size_t current_headers_length,
size_t this_header_length) {
return false;
}
// Called when a pending transport stream receives its stream ID. Protocols
// can use this to flush operations that require an opened stream. Returns
// false if deferred application data could not be submitted.
virtual bool StreamOpened(Stream& stream) { return true; }

// Called when ngtcp2 reports NGTCP2_ERR_STREAM_SHUT_WR for a stream.
// Applications that manage their own framing (e.g., HTTP/3) must inform
Expand Down Expand Up @@ -173,13 +181,19 @@ class Session::Application : public MemoryRetainer {
// Submits an outbound block of headers for the given stream. Not all
// Application types will support headers, in which case this function
// should return false.
virtual bool SendHeaders(const Stream& stream,
virtual bool SendHeaders(Stream& stream,
HeadersKind kind,
const v8::Local<v8::Array>& headers,
HeadersFlags flags = HeadersFlags::NONE) {
return false;
}

// Updates JavaScript callback interest for an application's stream header
// events. Applications without header semantics ignore this.
virtual void SetHeadersInterest(Stream& stream,
bool wants_headers,
bool wants_trailers) {}

// Returns true if the application protocol supports sending and
// receiving headers on streams (e.g. HTTP/3). Applications that
// do not support headers should return false (the default).
Expand Down
Loading
Loading