Skip to content

Commit e6a8d06

Browse files
quic: impl. cb for http/3 settings/app. options
Implements a callback that is invoked once http/3 settings are received. Background, http/3 settings usually arrive a bit later than connection establishment, and e.g. for webtransport these settings are used to indicate support. So e.g. the examples for quiche from google, wait for the settings to arrive. (This is different to http/2). The implemented callback mechanism allows to wait for the settings to arrive until connection attempts are made. As settings are stored in the generic applications option object, the callback's name refers to the application rather than the settings. Whether this is a good choice is debatable. Fixes: #63553 Signed-off-by: Marten Richter <marten.richter@freenet.de> PR-URL: #63558 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Tim Perry <pimterry@gmail.com> Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 1778eeb commit e6a8d06

10 files changed

Lines changed: 167 additions & 5 deletions

File tree

doc/api/quic.md

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,8 @@ added: v26.3.0
934934
The current application-level options for this session. These include settings
935935
that are specific to the negotiated application protocol (e.g. HTTP/3) and may
936936
be negotiated separately from the transport parameters. Read only.
937+
You can use the callback [`session.onapplication`][] to be informed, when settings
938+
from the remote arrive.
937939

938940
### `session.close([options])`
939941

@@ -1066,6 +1068,16 @@ added: v23.8.0
10661068
The endpoint that created this session. Returns `null` if the session
10671069
has been destroyed. Read only.
10681070

1071+
### `session.onapplication`
1072+
1073+
<!-- YAML
1074+
added: REPLACEME
1075+
-->
1076+
1077+
* Type: {quic.OnApplicationCallback}
1078+
1079+
The callback to invoke when new application options, e.g. HTTP/3 settings arrived.
1080+
10691081
### `session.onerror`
10701082

10711083
<!-- YAML
@@ -3525,11 +3537,11 @@ with that error:
35253537

35263538
* Stream callbacks (`onblocked`, `onreset`, `onheaders`, `ontrailers`,
35273539
`oninfo`, `onwanttrailers`): the stream is destroyed.
3528-
* Session callbacks (`onstream`, `ondatagram`, `ondatagramstatus`,
3529-
`onpathvalidation`, `onsessionticket`, `onnewtoken`,
3530-
`onversionnegotiation`, `onorigin`, `ongoaway`, `onhandshake`,
3531-
`onkeylog`, `onqlog`): the session is destroyed along with all of its
3532-
streams.
3540+
* Session callbacks (`onapplication`, `onstream`, `ondatagram`,
3541+
`ondatagramstatus`, `onpathvalidation`, `onsessionticket`,
3542+
`onnewtoken`, `onversionnegotiation`, `onorigin`, `ongoaway`,
3543+
`onhandshake`, `onkeylog`, `onqlog`): the session is destroyed along
3544+
with all of its streams.
35333545

35343546
Before destruction, the optional [`session.onerror`][] or
35353547
[`stream.onerror`][] callback is invoked (if set), giving the application a
@@ -3583,6 +3595,19 @@ added: v23.8.0
35833595
datagram was never sent on the wire (dropped due to queue overflow,
35843596
send attempt limit exceeded, or frame size rejection).
35853597

3598+
### Callback: `OnApplicationCallback`
3599+
3600+
<!-- YAML
3601+
added: v23.8.0
3602+
-->
3603+
3604+
* `this` {quic.QuicSession}
3605+
* `applicationoption` {quic.QuicSession}
3606+
3607+
The callback function that is invoked when application options change.
3608+
E.g. for http/3 settings are included in applications options and
3609+
may arrive after the connection is established.
3610+
35863611
### Callback: `OnPathValidationCallback`
35873612

35883613
<!-- YAML
@@ -4057,6 +4082,17 @@ added: v23.8.0
40574082
40584083
Published when an endpoint's busy state changes.
40594084
4085+
### Channel: `quic.session.application`
4086+
4087+
<!-- YAML
4088+
added: v23.8.0
4089+
-->
4090+
4091+
* `applicationoptions` {quic.ApplicationOptions} Current application options.
4092+
* `session` {quic.QuicSession}
4093+
4094+
Published when a locally-initiated stream is opened.
4095+
40604096
### Channel: `quic.session.created.client`
40614097
40624098
<!-- YAML
@@ -4440,6 +4476,7 @@ throughput issues caused by flow control.
44404476
[`session.createUnidirectionalStream()`]: #sessioncreateunidirectionalstreamoptions
44414477
[`session.destroy()`]: #sessiondestroyerror-options
44424478
[`session.maxPendingDatagrams`]: #sessionmaxpendingdatagrams
4479+
[`session.onapplication`]: #sessiononapplication
44434480
[`session.ondatagram`]: #sessionondatagram
44444481
[`session.ondatagramstatus`]: #sessionondatagramstatus
44454482
[`session.onearlyrejected`]: #sessiononearlyrejected

lib/internal/quic/diagnostics.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const onEndpointErrorChannel = dc.channel('quic.endpoint.error');
1414
const onEndpointBusyChangeChannel = dc.channel('quic.endpoint.busy.change');
1515
const onEndpointClientSessionChannel = dc.channel('quic.session.created.client');
1616
const onEndpointServerSessionChannel = dc.channel('quic.session.created.server');
17+
const onSessionApplicationChannel = dc.channel('quic.session.application');
1718
const onSessionOpenStreamChannel = dc.channel('quic.session.open.stream');
1819
const onSessionReceivedStreamChannel = dc.channel('quic.session.received.stream');
1920
const onSessionSendDatagramChannel = dc.channel('quic.session.send.datagram');
@@ -48,6 +49,7 @@ module.exports = {
4849
onEndpointBusyChangeChannel,
4950
onEndpointClientSessionChannel,
5051
onEndpointServerSessionChannel,
52+
onSessionApplicationChannel,
5153
onSessionOpenStreamChannel,
5254
onSessionReceivedStreamChannel,
5355
onSessionSendDatagramChannel,

lib/internal/quic/quic.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ const {
204204
kPrivateConstructor,
205205
kReset,
206206
kSendHeaders,
207+
kSessionApplication,
207208
kSessionTicket,
208209
kTrailers,
209210
kVersionNegotiation,
@@ -252,6 +253,7 @@ const {
252253
onSessionReceiveDatagramStatusChannel,
253254
onSessionPathValidationChannel,
254255
onSessionNewTokenChannel,
256+
onSessionApplicationChannel,
255257
onSessionTicketChannel,
256258
onSessionVersionNegotiationChannel,
257259
onSessionOriginChannel,
@@ -453,6 +455,7 @@ const endpointRegistry = new SafeSet();
453455
* @property {OnGoawayCallback} [ongoaway] GOAWAY frame callback.
454456
* @property {OnKeylogCallback} [onkeylog] TLS key-log callback.
455457
* @property {OnQlogCallback} [onqlog] qlog data callback.
458+
* @property {OnApplicationCallback} [onapplication] application options callback.
456459
* @property {OnHeadersCallback} [onheaders] Default per-stream initial-headers callback.
457460
* @property {OnTrailersCallback} [ontrailers] Default per-stream trailing-headers callback.
458461
* @property {OnInfoCallback} [oninfo] Default per-stream informational-headers callback.
@@ -583,6 +586,13 @@ const endpointRegistry = new SafeSet();
583586
* @returns {void}
584587
*/
585588

589+
/**
590+
* @callback OnApplicationCallback
591+
* @this {QuicSession}
592+
* @param {ApplicationOptions} applicationoptions
593+
* @returns {void}
594+
*/
595+
586596
/**
587597
* @callback OnSessionTicketCallback
588598
* @this {QuicSession}
@@ -660,6 +670,14 @@ const endpointRegistry = new SafeSet();
660670
* @returns {void}
661671
*/
662672

673+
/**
674+
* Called when `ApplicationOptions` are changed, e.g. HTTP/3 settings.
675+
* @callback OnApplicationCallback
676+
* @this {QuicSession}
677+
* @param {ApplicationOptions} applicationoptions ApplicationOptions object
678+
* @returns {void}
679+
*/
680+
663681
/**
664682
* @callback OnBlockedCallback
665683
* @this {QuicStream}
@@ -817,6 +835,16 @@ setCallbacks({
817835
preferredAddress);
818836
},
819837

838+
/**
839+
* Called when the session's application object is updated
840+
* E.g. http/3 session arrived.
841+
* @param {ApplicationOptions} applicationoptions An application object
842+
*/
843+
onSessionApplication(applicationoptions) {
844+
debug('session application callback', this[kOwner]);
845+
this[kOwner][kSessionApplication](applicationoptions);
846+
},
847+
820848
/**
821849
* Called when the session generates a new TLS session ticket
822850
* @param {object} ticket An opaque session ticket
@@ -1271,6 +1299,7 @@ function applyCallbacks(session, cbs) {
12711299
if (cbs.ongoaway) session.ongoaway = cbs.ongoaway;
12721300
if (cbs.onkeylog) session.onkeylog = cbs.onkeylog;
12731301
if (cbs.onqlog) session.onqlog = cbs.onqlog;
1302+
if (cbs.onapplication) session.onapplication = cbs.onapplication;
12741303
if (cbs.onheaders || cbs.ontrailers || cbs.oninfo || cbs.onwanttrailers) {
12751304
session[kStreamCallbacks] = {
12761305
__proto__: null,
@@ -2964,6 +2993,25 @@ class QuicSession {
29642993
}
29652994
}
29662995

2996+
/** @type {Function|undefined} */
2997+
get onapplication() {
2998+
assertIsQuicSession(this);
2999+
return this.#inner.onapplication;
3000+
}
3001+
3002+
set onapplication(fn) {
3003+
assertIsQuicSession(this);
3004+
const inner = this.#inner;
3005+
if (fn === undefined) {
3006+
inner.onapplication = undefined;
3007+
inner.state.hasApplicationListener = false;
3008+
} else {
3009+
validateFunction(fn, 'onapplication');
3010+
inner.onapplication = FunctionPrototypeBind(fn, this);
3011+
inner.state.hasApplicationListener = true;
3012+
}
3013+
}
3014+
29673015
/** @type {Function|undefined} */
29683016
get onversionnegotiation() {
29693017
assertIsQuicSession(this);
@@ -3551,6 +3599,7 @@ class QuicSession {
35513599
inner.ondatagramstatus = undefined;
35523600
inner.onpathvalidation = undefined;
35533601
inner.onsessionticket = undefined;
3602+
inner.onapplication = undefined;
35543603
inner.onkeylog = undefined;
35553604
inner.onversionnegotiation = undefined;
35563605
inner.onhandshake = undefined;
@@ -3779,6 +3828,23 @@ class QuicSession {
37793828
safeCallbackInvoke(inner.onsessionticket, this, ticket);
37803829
}
37813830

3831+
/**
3832+
* @param {ApplicationOptions} applicationoptions
3833+
*/
3834+
[kSessionApplication](applicationoptions) {
3835+
if (this.destroyed) return;
3836+
if (onSessionApplicationChannel.hasSubscribers) {
3837+
onSessionApplicationChannel.publish({
3838+
__proto__: null,
3839+
applicationoptions,
3840+
session: this,
3841+
});
3842+
}
3843+
const inner = this.#inner;
3844+
if (typeof inner.onapplication === 'function')
3845+
safeCallbackInvoke(inner.onapplication, this, applicationoptions);
3846+
}
3847+
37823848
/**
37833849
* @param {Buffer} token
37843850
* @param {SocketAddress} address
@@ -4356,6 +4422,7 @@ class QuicEndpoint {
43564422
ongoaway,
43574423
onkeylog,
43584424
onqlog,
4425+
onapplication,
43594426
// Stream-level callbacks applied to each incoming stream.
43604427
onheaders,
43614428
ontrailers,
@@ -4381,6 +4448,7 @@ class QuicEndpoint {
43814448
ongoaway,
43824449
onkeylog,
43834450
onqlog,
4451+
onapplication,
43844452
onheaders,
43854453
ontrailers,
43864454
oninfo,
@@ -5113,6 +5181,8 @@ function processSessionOptions(options, config = kEmptyObject) {
51135181
ongoaway,
51145182
onkeylog,
51155183
onqlog,
5184+
onapplication,
5185+
// Application level options changed, e.g. HTTP/3 settings related
51165186
// Stream-level callbacks.
51175187
onheaders,
51185188
ontrailers,
@@ -5234,6 +5304,7 @@ function processSessionOptions(options, config = kEmptyObject) {
52345304
ongoaway,
52355305
onkeylog,
52365306
onqlog,
5307+
onapplication,
52375308
onheaders,
52385309
ontrailers,
52395310
oninfo,

lib/internal/quic/state.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ class QuicSessionState {
349349
static #LISTENER_SESSION_TICKET = 1 << 3;
350350
static #LISTENER_NEW_TOKEN = 1 << 4;
351351
static #LISTENER_ORIGIN = 1 << 5;
352+
static #LISTENER_APPLICATION = 1 << 6;
352353

353354
#getListenerFlag(flag) {
354355
const handle = this.#handle;
@@ -367,6 +368,14 @@ class QuicSessionState {
367368
val ? (current | flag) : (current & ~flag), kIsLittleEndian);
368369
}
369370

371+
/** @type {boolean} */
372+
get hasApplicationListener() {
373+
return this.#getListenerFlag(QuicSessionState.#LISTENER_APPLICATION);
374+
}
375+
set hasApplicationListener(val) {
376+
this.#setListenerFlag(QuicSessionState.#LISTENER_APPLICATION, val);
377+
}
378+
370379
/** @type {boolean} */
371380
get hasPathValidationListener() {
372381
return this.#getListenerFlag(QuicSessionState.#LISTENER_PATH_VALIDATION);

lib/internal/quic/symbols.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ const kRemoveSession = Symbol('kRemoveSession');
5555
const kRemoveStream = Symbol('kRemoveStream');
5656
const kReset = Symbol('kReset');
5757
const kSendHeaders = Symbol('kSendHeaders');
58+
const kSessionApplication = Symbol('kSessionApplication');
5859
const kSessionTicket = Symbol('kSessionTicket');
5960
const kTrailers = Symbol('kTrailers');
6061
const kVersionNegotiation = Symbol('kVersionNegotiation');
@@ -90,6 +91,7 @@ module.exports = {
9091
kRemoveStream,
9192
kReset,
9293
kSendHeaders,
94+
kSessionApplication,
9395
kSessionTicket,
9496
kTrailers,
9597
kVersionNegotiation,

src/quic/bindingdata.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class SessionManager;
4141
#define QUIC_JS_CALLBACKS(V) \
4242
V(endpoint_close, EndpointClose) \
4343
V(session_close, SessionClose) \
44+
V(session_application, SessionApplication) \
4445
V(session_early_data_rejected, SessionEarlyDataRejected) \
4546
V(session_goaway, SessionGoaway) \
4647
V(session_datagram, SessionDatagram) \

src/quic/http3.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,6 +1015,8 @@ class Http3ApplicationImpl final : public Session::Application {
10151015
Debug(&session(),
10161016
"HTTP/3 application received updated settings: %s",
10171017
options_);
1018+
// The settings are part of the application
1019+
session().EmitApplication();
10181020
}
10191021

10201022
bool started_ = false;

src/quic/session.cc

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ enum class SessionListenerFlags : uint32_t {
7272
SESSION_TICKET = 1 << 3,
7373
NEW_TOKEN = 1 << 4,
7474
ORIGIN = 1 << 5,
75+
APPLICATION = 1 << 6
7576
};
7677

7778
inline SessionListenerFlags operator|(SessionListenerFlags a,
@@ -3669,6 +3670,33 @@ void Session::EmitSessionTicket(Store&& ticket) {
36693670
}
36703671
}
36713672

3673+
void Session::EmitApplication() {
3674+
if (is_destroyed()) return;
3675+
if (!env()->can_call_into_js()) return;
3676+
3677+
if (!has_application()) {
3678+
// The application has not yet been selected (ALPN negotiation is not
3679+
// yet complete on the server) or the session has been destroyed. In
3680+
// either case, the application options are not available.
3681+
// Should not happen, but we bail out
3682+
return;
3683+
}
3684+
3685+
if (!HasListenerFlag(impl_->state()->listener_flags,
3686+
SessionListenerFlags::APPLICATION)) [[likely]] {
3687+
return;
3688+
}
3689+
3690+
CallbackScope<Session> cb_scope(this);
3691+
3692+
Local<Value> argv;
3693+
auto& options = application().options();
3694+
if (options.ToObject(env()).ToLocal(&argv)) {
3695+
MakeCallback(
3696+
BindingData::Get(env()).session_application_callback(), 1, &argv);
3697+
}
3698+
}
3699+
36723700
void Session::DestroyAllStreams(const QuicError& error) {
36733701
DCHECK(!is_destroyed());
36743702
// Copy the streams map since streams remove themselves during

src/quic/session.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,7 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
615615
void EmitVersionNegotiation(const ngtcp2_pkt_hd& hd,
616616
const uint32_t* sv,
617617
size_t nsv);
618+
void EmitApplication();
618619
void DatagramStatus(datagram_id datagramId, DatagramStatus status);
619620
void DatagramReceived(const uint8_t* data,
620621
size_t datalen,

0 commit comments

Comments
 (0)