Commit 2f749dd

Browse files
martenrichteraduh95
authored andcommitted
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 Backport-PR-URL: #64675 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 5ca7ef3 commit 2f749dd

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
@@ -914,6 +914,8 @@ added: REPLACEME
914914
The current application-level options for this session. These include settings
915915
that are specific to the negotiated application protocol (e.g. HTTP/3) and may
916916
be negotiated separately from the transport parameters. Read only.
917+
You can use the callback [`session.onapplication`][] to be informed, when settings
918+
from the remote arrive.
917919

918920
### `session.close([options])`
919921

@@ -1046,6 +1048,16 @@ added: v23.8.0
10461048
The endpoint that created this session. Returns `null` if the session
10471049
has been destroyed. Read only.
10481050

1051+
### `session.onapplication`
1052+
1053+
<!-- YAML
1054+
added: REPLACEME
1055+
-->
1056+
1057+
* Type: {quic.OnApplicationCallback}
1058+
1059+
The callback to invoke when new application options, e.g. HTTP/3 settings arrived.
1060+
10491061
### `session.onerror`
10501062

10511063
<!-- YAML
@@ -3499,11 +3511,11 @@ with that error:
34993511

35003512
* Stream callbacks (`onblocked`, `onreset`, `onheaders`, `ontrailers`,
35013513
`oninfo`, `onwanttrailers`): the stream is destroyed.
3502-
* Session callbacks (`onstream`, `ondatagram`, `ondatagramstatus`,
3503-
`onpathvalidation`, `onsessionticket`, `onnewtoken`,
3504-
`onversionnegotiation`, `onorigin`, `ongoaway`, `onhandshake`,
3505-
`onkeylog`, `onqlog`): the session is destroyed along with all of its
3506-
streams.
3514+
* Session callbacks (`onapplication`, `onstream`, `ondatagram`,
3515+
`ondatagramstatus`, `onpathvalidation`, `onsessionticket`,
3516+
`onnewtoken`, `onversionnegotiation`, `onorigin`, `ongoaway`,
3517+
`onhandshake`, `onkeylog`, `onqlog`): the session is destroyed along
3518+
with all of its streams.
35073519

35083520
Before destruction, the optional [`session.onerror`][] or
35093521
[`stream.onerror`][] callback is invoked (if set), giving the application a
@@ -3557,6 +3569,19 @@ added: v23.8.0
35573569
datagram was never sent on the wire (dropped due to queue overflow,
35583570
send attempt limit exceeded, or frame size rejection).
35593571

3572+
### Callback: `OnApplicationCallback`
3573+
3574+
<!-- YAML
3575+
added: v23.8.0
3576+
-->
3577+
3578+
*`this` {quic.QuicSession}
3579+
*`applicationoption` {quic.QuicSession}
3580+
3581+
The callback function that is invoked when application options change.
3582+
E.g. for http/3 settings are included in applications options and
3583+
may arrive after the connection is established.
3584+
35603585
### Callback: `OnPathValidationCallback`
35613586

35623587
<!-- YAML
@@ -4031,6 +4056,17 @@ added: v23.8.0
40314056
40324057
Published when an endpoint's busy state changes.
40334058
4059+
### Channel: `quic.session.application`
4060+
4061+
<!-- YAML
4062+
added: v23.8.0
4063+
-->
4064+
4065+
* `applicationoptions` {quic.ApplicationOptions} Current application options.
4066+
* `session` {quic.QuicSession}
4067+
4068+
Published when a locally-initiated stream is opened.
4069+
40344070
### Channel: `quic.session.created.client`
40354071
40364072
<!-- YAML
@@ -4412,6 +4448,7 @@ throughput issues caused by flow control.
44124448
[`session.createUnidirectionalStream()`]: #sessioncreateunidirectionalstreamoptions
44134449
[`session.destroy()`]: #sessiondestroyerror-options
44144450
[`session.maxPendingDatagrams`]: #sessionmaxpendingdatagrams
4451+
[`session.onapplication`]: #sessiononapplication
44154452
[`session.ondatagram`]: #sessionondatagram
44164453
[`session.ondatagramstatus`]: #sessionondatagramstatus
44174454
[`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
constonEndpointBusyChangeChannel=dc.channel('quic.endpoint.busy.change');
1515
constonEndpointClientSessionChannel=dc.channel('quic.session.created.client');
1616
constonEndpointServerSessionChannel=dc.channel('quic.session.created.server');
17+
constonSessionApplicationChannel=dc.channel('quic.session.application');
1718
constonSessionOpenStreamChannel=dc.channel('quic.session.open.stream');
1819
constonSessionReceivedStreamChannel=dc.channel('quic.session.received.stream');
1920
constonSessionSendDatagramChannel=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+
getonapplication(){
2998+
assertIsQuicSession(this);
2999+
returnthis.#inner.onapplication;
3000+
}
3001+
3002+
setonapplication(fn){
3003+
assertIsQuicSession(this);
3004+
constinner=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
getonversionnegotiation(){
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+
constinner=this.#inner;
3844+
if(typeofinner.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
consthandle=this.#handle;
@@ -367,6 +368,14 @@ class QuicSessionState {
367368
val ? (current|flag) : (current&~flag),kIsLittleEndian);
368369
}
369370

371+
/** @type {boolean} */
372+
gethasApplicationListener(){
373+
returnthis.#getListenerFlag(QuicSessionState.#LISTENER_APPLICATION);
374+
}
375+
sethasApplicationListener(val){
376+
this.#setListenerFlag(QuicSessionState.#LISTENER_APPLICATION,val);
377+
}
378+
370379
/** @type {boolean} */
371380
gethasPathValidationListener(){
372381
returnthis.#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
constkRemoveStream=Symbol('kRemoveStream');
5656
constkReset=Symbol('kReset');
5757
constkSendHeaders=Symbol('kSendHeaders');
58+
constkSessionApplication=Symbol('kSessionApplication');
5859
constkSessionTicket=Symbol('kSessionTicket');
5960
constkTrailers=Symbol('kTrailers');
6061
constkVersionNegotiation=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
#defineQUIC_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
@@ -1014,6 +1014,8 @@ class Http3ApplicationImpl final : public Session::Application {
10141014
Debug(&session(),
10151015
"HTTP/3 application received updated settings: %s",
10161016
options_);
1017+
// The settings are part of the application
1018+
session().EmitApplication();
10171019
}
10181020

10191021
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,
@@ -3677,6 +3678,33 @@ void Session::EmitSessionTicket(Store&& ticket) {
36773678
}
36783679
}
36793680

3681+
voidSession::EmitApplication() {
3682+
if (is_destroyed()) return;
3683+
if (!env()->can_call_into_js()) return;
3684+
3685+
if (!has_application()) {
3686+
// The application has not yet been selected (ALPN negotiation is not
3687+
// yet complete on the server) or the session has been destroyed. In
3688+
// either case, the application options are not available.
3689+
// Should not happen, but we bail out
3690+
return;
3691+
}
3692+
3693+
if (!HasListenerFlag(impl_->state()->listener_flags,
3694+
SessionListenerFlags::APPLICATION)) [[likely]] {
3695+
return;
3696+
}
3697+
3698+
CallbackScope<Session> cb_scope(this);
3699+
3700+
Local<Value> argv;
3701+
auto& options = application().options();
3702+
if (options.ToObject(env()).ToLocal(&argv)) {
3703+
MakeCallback(
3704+
BindingData::Get(env()).session_application_callback(), 1, &argv);
3705+
}
3706+
}
3707+
36803708
voidSession::DestroyAllStreams(const QuicError& error) {
36813709
DCHECK(!is_destroyed());
36823710
// 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
voidEmitVersionNegotiation(const ngtcp2_pkt_hd& hd,
616616
constuint32_t* sv,
617617
size_t nsv);
618+
voidEmitApplication();
618619
voidDatagramStatus(datagram_id datagramId, DatagramStatus status);
619620
voidDatagramReceived(constuint8_t* data,
620621
size_t datalen,

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Commit 2f749dd

Browse files
martenrichteraduh95
authored andcommitted
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 Backport-PR-URL: #64675 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 5ca7ef3 commit 2f749dd

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
@@ -914,6 +914,8 @@ added: REPLACEME
914914
The current application-level options for this session. These include settings
915915
that are specific to the negotiated application protocol (e.g. HTTP/3) and may
916916
be negotiated separately from the transport parameters. Read only.
917+
You can use the callback [`session.onapplication`][] to be informed, when settings
918+
from the remote arrive.
917919

918920
### `session.close([options])`
919921

@@ -1046,6 +1048,16 @@ added: v23.8.0
10461048
The endpoint that created this session. Returns `null` if the session
10471049
has been destroyed. Read only.
10481050

1051+
### `session.onapplication`
1052+
1053+
<!-- YAML
1054+
added: REPLACEME
1055+
-->
1056+
1057+
* Type: {quic.OnApplicationCallback}
1058+
1059+
The callback to invoke when new application options, e.g. HTTP/3 settings arrived.
1060+
10491061
### `session.onerror`
10501062

10511063
<!-- YAML
@@ -3499,11 +3511,11 @@ with that error:
34993511

35003512
* Stream callbacks (`onblocked`, `onreset`, `onheaders`, `ontrailers`,
35013513
`oninfo`, `onwanttrailers`): the stream is destroyed.
3502-
* Session callbacks (`onstream`, `ondatagram`, `ondatagramstatus`,
3503-
`onpathvalidation`, `onsessionticket`, `onnewtoken`,
3504-
`onversionnegotiation`, `onorigin`, `ongoaway`, `onhandshake`,
3505-
`onkeylog`, `onqlog`): the session is destroyed along with all of its
3506-
streams.
3514+
* Session callbacks (`onapplication`, `onstream`, `ondatagram`,
3515+
`ondatagramstatus`, `onpathvalidation`, `onsessionticket`,
3516+
`onnewtoken`, `onversionnegotiation`, `onorigin`, `ongoaway`,
3517+
`onhandshake`, `onkeylog`, `onqlog`): the session is destroyed along
3518+
with all of its streams.
35073519

35083520
Before destruction, the optional [`session.onerror`][] or
35093521
[`stream.onerror`][] callback is invoked (if set), giving the application a
@@ -3557,6 +3569,19 @@ added: v23.8.0
35573569
datagram was never sent on the wire (dropped due to queue overflow,
35583570
send attempt limit exceeded, or frame size rejection).
35593571

3572+
### Callback: `OnApplicationCallback`
3573+
3574+
<!-- YAML
3575+
added: v23.8.0
3576+
-->
3577+
3578+
*`this` {quic.QuicSession}
3579+
*`applicationoption` {quic.QuicSession}
3580+
3581+
The callback function that is invoked when application options change.
3582+
E.g. for http/3 settings are included in applications options and
3583+
may arrive after the connection is established.
3584+
35603585
### Callback: `OnPathValidationCallback`
35613586

35623587
<!-- YAML
@@ -4031,6 +4056,17 @@ added: v23.8.0
40314056
40324057
Published when an endpoint's busy state changes.
40334058
4059+
### Channel: `quic.session.application`
4060+
4061+
<!-- YAML
4062+
added: v23.8.0
4063+
-->
4064+
4065+
* `applicationoptions` {quic.ApplicationOptions} Current application options.
4066+
* `session` {quic.QuicSession}
4067+
4068+
Published when a locally-initiated stream is opened.
4069+
40344070
### Channel: `quic.session.created.client`
40354071
40364072
<!-- YAML
@@ -4412,6 +4448,7 @@ throughput issues caused by flow control.
44124448
[`session.createUnidirectionalStream()`]: #sessioncreateunidirectionalstreamoptions
44134449
[`session.destroy()`]: #sessiondestroyerror-options
44144450
[`session.maxPendingDatagrams`]: #sessionmaxpendingdatagrams
4451+
[`session.onapplication`]: #sessiononapplication
44154452
[`session.ondatagram`]: #sessionondatagram
44164453
[`session.ondatagramstatus`]: #sessionondatagramstatus
44174454
[`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
constonEndpointBusyChangeChannel=dc.channel('quic.endpoint.busy.change');
1515
constonEndpointClientSessionChannel=dc.channel('quic.session.created.client');
1616
constonEndpointServerSessionChannel=dc.channel('quic.session.created.server');
17+
constonSessionApplicationChannel=dc.channel('quic.session.application');
1718
constonSessionOpenStreamChannel=dc.channel('quic.session.open.stream');
1819
constonSessionReceivedStreamChannel=dc.channel('quic.session.received.stream');
1920
constonSessionSendDatagramChannel=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+
getonapplication(){
2998+
assertIsQuicSession(this);
2999+
returnthis.#inner.onapplication;
3000+
}
3001+
3002+
setonapplication(fn){
3003+
assertIsQuicSession(this);
3004+
constinner=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
getonversionnegotiation(){
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+
constinner=this.#inner;
3844+
if(typeofinner.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
consthandle=this.#handle;
@@ -367,6 +368,14 @@ class QuicSessionState {
367368
val ? (current|flag) : (current&~flag),kIsLittleEndian);
368369
}
369370

371+
/** @type {boolean} */
372+
gethasApplicationListener(){
373+
returnthis.#getListenerFlag(QuicSessionState.#LISTENER_APPLICATION);
374+
}
375+
sethasApplicationListener(val){
376+
this.#setListenerFlag(QuicSessionState.#LISTENER_APPLICATION,val);
377+
}
378+
370379
/** @type {boolean} */
371380
gethasPathValidationListener(){
372381
returnthis.#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
constkRemoveStream=Symbol('kRemoveStream');
5656
constkReset=Symbol('kReset');
5757
constkSendHeaders=Symbol('kSendHeaders');
58+
constkSessionApplication=Symbol('kSessionApplication');
5859
constkSessionTicket=Symbol('kSessionTicket');
5960
constkTrailers=Symbol('kTrailers');
6061
constkVersionNegotiation=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
#defineQUIC_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
@@ -1014,6 +1014,8 @@ class Http3ApplicationImpl final : public Session::Application {
10141014
Debug(&session(),
10151015
"HTTP/3 application received updated settings: %s",
10161016
options_);
1017+
// The settings are part of the application
1018+
session().EmitApplication();
10171019
}
10181020

10191021
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,
@@ -3677,6 +3678,33 @@ void Session::EmitSessionTicket(Store&& ticket) {
36773678
}
36783679
}
36793680

3681+
voidSession::EmitApplication() {
3682+
if (is_destroyed()) return;
3683+
if (!env()->can_call_into_js()) return;
3684+
3685+
if (!has_application()) {
3686+
// The application has not yet been selected (ALPN negotiation is not
3687+
// yet complete on the server) or the session has been destroyed. In
3688+
// either case, the application options are not available.
3689+
// Should not happen, but we bail out
3690+
return;
3691+
}
3692+
3693+
if (!HasListenerFlag(impl_->state()->listener_flags,
3694+
SessionListenerFlags::APPLICATION)) [[likely]] {
3695+
return;
3696+
}
3697+
3698+
CallbackScope<Session> cb_scope(this);
3699+
3700+
Local<Value> argv;
3701+
auto& options = application().options();
3702+
if (options.ToObject(env()).ToLocal(&argv)) {
3703+
MakeCallback(
3704+
BindingData::Get(env()).session_application_callback(), 1, &argv);
3705+
}
3706+
}
3707+
36803708
voidSession::DestroyAllStreams(const QuicError& error) {
36813709
DCHECK(!is_destroyed());
36823710
// 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
voidEmitVersionNegotiation(const ngtcp2_pkt_hd& hd,
616616
constuint32_t* sv,
617617
size_t nsv);
618+
voidEmitApplication();
618619
voidDatagramStatus(datagram_id datagramId, DatagramStatus status);
619620
voidDatagramReceived(constuint8_t* data,
620621
size_t datalen,

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 2f749dd

Browse files
martenrichteraduh95
authored andcommitted
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 Backport-PR-URL: #64675 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 5ca7ef3 commit 2f749dd

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
@@ -914,6 +914,8 @@ added: REPLACEME
914914
The current application-level options for this session. These include settings
915915
that are specific to the negotiated application protocol (e.g. HTTP/3) and may
916916
be negotiated separately from the transport parameters. Read only.
917+
You can use the callback [`session.onapplication`][] to be informed, when settings
918+
from the remote arrive.
917919

918920
### `session.close([options])`
919921

@@ -1046,6 +1048,16 @@ added: v23.8.0
10461048
The endpoint that created this session. Returns `null` if the session
10471049
has been destroyed. Read only.
10481050

1051+
### `session.onapplication`
1052+
1053+
<!-- YAML
1054+
added: REPLACEME
1055+
-->
1056+
1057+
* Type: {quic.OnApplicationCallback}
1058+
1059+
The callback to invoke when new application options, e.g. HTTP/3 settings arrived.
1060+
10491061
### `session.onerror`
10501062

10511063
<!-- YAML
@@ -3499,11 +3511,11 @@ with that error:
34993511

35003512
* Stream callbacks (`onblocked`, `onreset`, `onheaders`, `ontrailers`,
35013513
`oninfo`, `onwanttrailers`): the stream is destroyed.
3502-
* Session callbacks (`onstream`, `ondatagram`, `ondatagramstatus`,
3503-
`onpathvalidation`, `onsessionticket`, `onnewtoken`,
3504-
`onversionnegotiation`, `onorigin`, `ongoaway`, `onhandshake`,
3505-
`onkeylog`, `onqlog`): the session is destroyed along with all of its
3506-
streams.
3514+
* Session callbacks (`onapplication`, `onstream`, `ondatagram`,
3515+
`ondatagramstatus`, `onpathvalidation`, `onsessionticket`,
3516+
`onnewtoken`, `onversionnegotiation`, `onorigin`, `ongoaway`,
3517+
`onhandshake`, `onkeylog`, `onqlog`): the session is destroyed along
3518+
with all of its streams.
35073519

35083520
Before destruction, the optional [`session.onerror`][] or
35093521
[`stream.onerror`][] callback is invoked (if set), giving the application a
@@ -3557,6 +3569,19 @@ added: v23.8.0
35573569
datagram was never sent on the wire (dropped due to queue overflow,
35583570
send attempt limit exceeded, or frame size rejection).
35593571

3572+
### Callback: `OnApplicationCallback`
3573+
3574+
<!-- YAML
3575+
added: v23.8.0
3576+
-->
3577+
3578+
*`this` {quic.QuicSession}
3579+
*`applicationoption` {quic.QuicSession}
3580+
3581+
The callback function that is invoked when application options change.
3582+
E.g. for http/3 settings are included in applications options and
3583+
may arrive after the connection is established.
3584+
35603585
### Callback: `OnPathValidationCallback`
35613586

35623587
<!-- YAML
@@ -4031,6 +4056,17 @@ added: v23.8.0
40314056
40324057
Published when an endpoint's busy state changes.
40334058
4059+
### Channel: `quic.session.application`
4060+
4061+
<!-- YAML
4062+
added: v23.8.0
4063+
-->
4064+
4065+
* `applicationoptions` {quic.ApplicationOptions} Current application options.
4066+
* `session` {quic.QuicSession}
4067+
4068+
Published when a locally-initiated stream is opened.
4069+
40344070
### Channel: `quic.session.created.client`
40354071
40364072
<!-- YAML
@@ -4412,6 +4448,7 @@ throughput issues caused by flow control.
44124448
[`session.createUnidirectionalStream()`]: #sessioncreateunidirectionalstreamoptions
44134449
[`session.destroy()`]: #sessiondestroyerror-options
44144450
[`session.maxPendingDatagrams`]: #sessionmaxpendingdatagrams
4451+
[`session.onapplication`]: #sessiononapplication
44154452
[`session.ondatagram`]: #sessionondatagram
44164453
[`session.ondatagramstatus`]: #sessionondatagramstatus
44174454
[`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
constonEndpointBusyChangeChannel=dc.channel('quic.endpoint.busy.change');
1515
constonEndpointClientSessionChannel=dc.channel('quic.session.created.client');
1616
constonEndpointServerSessionChannel=dc.channel('quic.session.created.server');
17+
constonSessionApplicationChannel=dc.channel('quic.session.application');
1718
constonSessionOpenStreamChannel=dc.channel('quic.session.open.stream');
1819
constonSessionReceivedStreamChannel=dc.channel('quic.session.received.stream');
1920
constonSessionSendDatagramChannel=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+
getonapplication(){
2998+
assertIsQuicSession(this);
2999+
returnthis.#inner.onapplication;
3000+
}
3001+
3002+
setonapplication(fn){
3003+
assertIsQuicSession(this);
3004+
constinner=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
getonversionnegotiation(){
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+
constinner=this.#inner;
3844+
if(typeofinner.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
consthandle=this.#handle;
@@ -367,6 +368,14 @@ class QuicSessionState {
367368
val ? (current|flag) : (current&~flag),kIsLittleEndian);
368369
}
369370

371+
/** @type {boolean} */
372+
gethasApplicationListener(){
373+
returnthis.#getListenerFlag(QuicSessionState.#LISTENER_APPLICATION);
374+
}
375+
sethasApplicationListener(val){
376+
this.#setListenerFlag(QuicSessionState.#LISTENER_APPLICATION,val);
377+
}
378+
370379
/** @type {boolean} */
371380
gethasPathValidationListener(){
372381
returnthis.#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
constkRemoveStream=Symbol('kRemoveStream');
5656
constkReset=Symbol('kReset');
5757
constkSendHeaders=Symbol('kSendHeaders');
58+
constkSessionApplication=Symbol('kSessionApplication');
5859
constkSessionTicket=Symbol('kSessionTicket');
5960
constkTrailers=Symbol('kTrailers');
6061
constkVersionNegotiation=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
#defineQUIC_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
@@ -1014,6 +1014,8 @@ class Http3ApplicationImpl final : public Session::Application {
10141014
Debug(&session(),
10151015
"HTTP/3 application received updated settings: %s",
10161016
options_);
1017+
// The settings are part of the application
1018+
session().EmitApplication();
10171019
}
10181020

10191021
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,
@@ -3677,6 +3678,33 @@ void Session::EmitSessionTicket(Store&& ticket) {
36773678
}
36783679
}
36793680

3681+
voidSession::EmitApplication() {
3682+
if (is_destroyed()) return;
3683+
if (!env()->can_call_into_js()) return;
3684+
3685+
if (!has_application()) {
3686+
// The application has not yet been selected (ALPN negotiation is not
3687+
// yet complete on the server) or the session has been destroyed. In
3688+
// either case, the application options are not available.
3689+
// Should not happen, but we bail out
3690+
return;
3691+
}
3692+
3693+
if (!HasListenerFlag(impl_->state()->listener_flags,
3694+
SessionListenerFlags::APPLICATION)) [[likely]] {
3695+
return;
3696+
}
3697+
3698+
CallbackScope<Session> cb_scope(this);
3699+
3700+
Local<Value> argv;
3701+
auto& options = application().options();
3702+
if (options.ToObject(env()).ToLocal(&argv)) {
3703+
MakeCallback(
3704+
BindingData::Get(env()).session_application_callback(), 1, &argv);
3705+
}
3706+
}
3707+
36803708
voidSession::DestroyAllStreams(const QuicError& error) {
36813709
DCHECK(!is_destroyed());
36823710
// 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
voidEmitVersionNegotiation(const ngtcp2_pkt_hd& hd,
616616
constuint32_t* sv,
617617
size_t nsv);
618+
voidEmitApplication();
618619
voidDatagramStatus(datagram_id datagramId, DatagramStatus status);
619620
voidDatagramReceived(constuint8_t* data,
620621
size_t datalen,

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 2f749dd

Browse files
martenrichteraduh95
authored andcommitted
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 Backport-PR-URL: #64675 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 5ca7ef3 commit 2f749dd

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
@@ -914,6 +914,8 @@ added: REPLACEME
914914
The current application-level options for this session. These include settings
915915
that are specific to the negotiated application protocol (e.g. HTTP/3) and may
916916
be negotiated separately from the transport parameters. Read only.
917+
You can use the callback [`session.onapplication`][] to be informed, when settings
918+
from the remote arrive.
917919

918920
### `session.close([options])`
919921

@@ -1046,6 +1048,16 @@ added: v23.8.0
10461048
The endpoint that created this session. Returns `null` if the session
10471049
has been destroyed. Read only.
10481050

1051+
### `session.onapplication`
1052+
1053+
<!-- YAML
1054+
added: REPLACEME
1055+
-->
1056+
1057+
* Type: {quic.OnApplicationCallback}
1058+
1059+
The callback to invoke when new application options, e.g. HTTP/3 settings arrived.
1060+
10491061
### `session.onerror`
10501062

10511063
<!-- YAML
@@ -3499,11 +3511,11 @@ with that error:
34993511

35003512
* Stream callbacks (`onblocked`, `onreset`, `onheaders`, `ontrailers`,
35013513
`oninfo`, `onwanttrailers`): the stream is destroyed.
3502-
* Session callbacks (`onstream`, `ondatagram`, `ondatagramstatus`,
3503-
`onpathvalidation`, `onsessionticket`, `onnewtoken`,
3504-
`onversionnegotiation`, `onorigin`, `ongoaway`, `onhandshake`,
3505-
`onkeylog`, `onqlog`): the session is destroyed along with all of its
3506-
streams.
3514+
* Session callbacks (`onapplication`, `onstream`, `ondatagram`,
3515+
`ondatagramstatus`, `onpathvalidation`, `onsessionticket`,
3516+
`onnewtoken`, `onversionnegotiation`, `onorigin`, `ongoaway`,
3517+
`onhandshake`, `onkeylog`, `onqlog`): the session is destroyed along
3518+
with all of its streams.
35073519

35083520
Before destruction, the optional [`session.onerror`][] or
35093521
[`stream.onerror`][] callback is invoked (if set), giving the application a
@@ -3557,6 +3569,19 @@ added: v23.8.0
35573569
datagram was never sent on the wire (dropped due to queue overflow,
35583570
send attempt limit exceeded, or frame size rejection).
35593571

3572+
### Callback: `OnApplicationCallback`
3573+
3574+
<!-- YAML
3575+
added: v23.8.0
3576+
-->
3577+
3578+
*`this` {quic.QuicSession}
3579+
*`applicationoption` {quic.QuicSession}
3580+
3581+
The callback function that is invoked when application options change.
3582+
E.g. for http/3 settings are included in applications options and
3583+
may arrive after the connection is established.
3584+
35603585
### Callback: `OnPathValidationCallback`
35613586

35623587
<!-- YAML
@@ -4031,6 +4056,17 @@ added: v23.8.0
40314056
40324057
Published when an endpoint's busy state changes.
40334058
4059+
### Channel: `quic.session.application`
4060+
4061+
<!-- YAML
4062+
added: v23.8.0
4063+
-->
4064+
4065+
* `applicationoptions` {quic.ApplicationOptions} Current application options.
4066+
* `session` {quic.QuicSession}
4067+
4068+
Published when a locally-initiated stream is opened.
4069+
40344070
### Channel: `quic.session.created.client`
40354071
40364072
<!-- YAML
@@ -4412,6 +4448,7 @@ throughput issues caused by flow control.
44124448
[`session.createUnidirectionalStream()`]: #sessioncreateunidirectionalstreamoptions
44134449
[`session.destroy()`]: #sessiondestroyerror-options
44144450
[`session.maxPendingDatagrams`]: #sessionmaxpendingdatagrams
4451+
[`session.onapplication`]: #sessiononapplication
44154452
[`session.ondatagram`]: #sessionondatagram
44164453
[`session.ondatagramstatus`]: #sessionondatagramstatus
44174454
[`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
constonEndpointBusyChangeChannel=dc.channel('quic.endpoint.busy.change');
1515
constonEndpointClientSessionChannel=dc.channel('quic.session.created.client');
1616
constonEndpointServerSessionChannel=dc.channel('quic.session.created.server');
17+
constonSessionApplicationChannel=dc.channel('quic.session.application');
1718
constonSessionOpenStreamChannel=dc.channel('quic.session.open.stream');
1819
constonSessionReceivedStreamChannel=dc.channel('quic.session.received.stream');
1920
constonSessionSendDatagramChannel=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+
getonapplication(){
2998+
assertIsQuicSession(this);
2999+
returnthis.#inner.onapplication;
3000+
}
3001+
3002+
setonapplication(fn){
3003+
assertIsQuicSession(this);
3004+
constinner=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
getonversionnegotiation(){
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+
constinner=this.#inner;
3844+
if(typeofinner.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
consthandle=this.#handle;
@@ -367,6 +368,14 @@ class QuicSessionState {
367368
val ? (current|flag) : (current&~flag),kIsLittleEndian);
368369
}
369370

371+
/** @type {boolean} */
372+
gethasApplicationListener(){
373+
returnthis.#getListenerFlag(QuicSessionState.#LISTENER_APPLICATION);
374+
}
375+
sethasApplicationListener(val){
376+
this.#setListenerFlag(QuicSessionState.#LISTENER_APPLICATION,val);
377+
}
378+
370379
/** @type {boolean} */
371380
gethasPathValidationListener(){
372381
returnthis.#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
constkRemoveStream=Symbol('kRemoveStream');
5656
constkReset=Symbol('kReset');
5757
constkSendHeaders=Symbol('kSendHeaders');
58+
constkSessionApplication=Symbol('kSessionApplication');
5859
constkSessionTicket=Symbol('kSessionTicket');
5960
constkTrailers=Symbol('kTrailers');
6061
constkVersionNegotiation=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
#defineQUIC_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
@@ -1014,6 +1014,8 @@ class Http3ApplicationImpl final : public Session::Application {
10141014
Debug(&session(),
10151015
"HTTP/3 application received updated settings: %s",
10161016
options_);
1017+
// The settings are part of the application
1018+
session().EmitApplication();
10171019
}
10181020

10191021
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,
@@ -3677,6 +3678,33 @@ void Session::EmitSessionTicket(Store&& ticket) {
36773678
}
36783679
}
36793680

3681+
voidSession::EmitApplication() {
3682+
if (is_destroyed()) return;
3683+
if (!env()->can_call_into_js()) return;
3684+
3685+
if (!has_application()) {
3686+
// The application has not yet been selected (ALPN negotiation is not
3687+
// yet complete on the server) or the session has been destroyed. In
3688+
// either case, the application options are not available.
3689+
// Should not happen, but we bail out
3690+
return;
3691+
}
3692+
3693+
if (!HasListenerFlag(impl_->state()->listener_flags,
3694+
SessionListenerFlags::APPLICATION)) [[likely]] {
3695+
return;
3696+
}
3697+
3698+
CallbackScope<Session> cb_scope(this);
3699+
3700+
Local<Value> argv;
3701+
auto& options = application().options();
3702+
if (options.ToObject(env()).ToLocal(&argv)) {
3703+
MakeCallback(
3704+
BindingData::Get(env()).session_application_callback(), 1, &argv);
3705+
}
3706+
}
3707+
36803708
voidSession::DestroyAllStreams(const QuicError& error) {
36813709
DCHECK(!is_destroyed());
36823710
// 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
voidEmitVersionNegotiation(const ngtcp2_pkt_hd& hd,
616616
constuint32_t* sv,
617617
size_t nsv);
618+
voidEmitApplication();
618619
voidDatagramStatus(datagram_id datagramId, DatagramStatus status);
619620
voidDatagramReceived(constuint8_t* data,
620621
size_t datalen,

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Commit 2f749dd

Browse files
martenrichteraduh95
authored andcommitted
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 Backport-PR-URL: #64675 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 5ca7ef3 commit 2f749dd

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
@@ -914,6 +914,8 @@ added: REPLACEME
914914
The current application-level options for this session. These include settings
915915
that are specific to the negotiated application protocol (e.g. HTTP/3) and may
916916
be negotiated separately from the transport parameters. Read only.
917+
You can use the callback [`session.onapplication`][] to be informed, when settings
918+
from the remote arrive.
917919

918920
### `session.close([options])`
919921

@@ -1046,6 +1048,16 @@ added: v23.8.0
10461048
The endpoint that created this session. Returns `null` if the session
10471049
has been destroyed. Read only.
10481050

1051+
### `session.onapplication`
1052+
1053+
<!-- YAML
1054+
added: REPLACEME
1055+
-->
1056+
1057+
* Type: {quic.OnApplicationCallback}
1058+
1059+
The callback to invoke when new application options, e.g. HTTP/3 settings arrived.
1060+
10491061
### `session.onerror`
10501062

10511063
<!-- YAML
@@ -3499,11 +3511,11 @@ with that error:
34993511

35003512
* Stream callbacks (`onblocked`, `onreset`, `onheaders`, `ontrailers`,
35013513
`oninfo`, `onwanttrailers`): the stream is destroyed.
3502-
* Session callbacks (`onstream`, `ondatagram`, `ondatagramstatus`,
3503-
`onpathvalidation`, `onsessionticket`, `onnewtoken`,
3504-
`onversionnegotiation`, `onorigin`, `ongoaway`, `onhandshake`,
3505-
`onkeylog`, `onqlog`): the session is destroyed along with all of its
3506-
streams.
3514+
* Session callbacks (`onapplication`, `onstream`, `ondatagram`,
3515+
`ondatagramstatus`, `onpathvalidation`, `onsessionticket`,
3516+
`onnewtoken`, `onversionnegotiation`, `onorigin`, `ongoaway`,
3517+
`onhandshake`, `onkeylog`, `onqlog`): the session is destroyed along
3518+
with all of its streams.
35073519

35083520
Before destruction, the optional [`session.onerror`][] or
35093521
[`stream.onerror`][] callback is invoked (if set), giving the application a
@@ -3557,6 +3569,19 @@ added: v23.8.0
35573569
datagram was never sent on the wire (dropped due to queue overflow,
35583570
send attempt limit exceeded, or frame size rejection).
35593571

3572+
### Callback: `OnApplicationCallback`
3573+
3574+
<!-- YAML
3575+
added: v23.8.0
3576+
-->
3577+
3578+
*`this` {quic.QuicSession}
3579+
*`applicationoption` {quic.QuicSession}
3580+
3581+
The callback function that is invoked when application options change.
3582+
E.g. for http/3 settings are included in applications options and
3583+
may arrive after the connection is established.
3584+
35603585
### Callback: `OnPathValidationCallback`
35613586

35623587
<!-- YAML
@@ -4031,6 +4056,17 @@ added: v23.8.0
40314056
40324057
Published when an endpoint's busy state changes.
40334058
4059+
### Channel: `quic.session.application`
4060+
4061+
<!-- YAML
4062+
added: v23.8.0
4063+
-->
4064+
4065+
* `applicationoptions` {quic.ApplicationOptions} Current application options.
4066+
* `session` {quic.QuicSession}
4067+
4068+
Published when a locally-initiated stream is opened.
4069+
40344070
### Channel: `quic.session.created.client`
40354071
40364072
<!-- YAML
@@ -4412,6 +4448,7 @@ throughput issues caused by flow control.
44124448
[`session.createUnidirectionalStream()`]: #sessioncreateunidirectionalstreamoptions
44134449
[`session.destroy()`]: #sessiondestroyerror-options
44144450
[`session.maxPendingDatagrams`]: #sessionmaxpendingdatagrams
4451+
[`session.onapplication`]: #sessiononapplication
44154452
[`session.ondatagram`]: #sessionondatagram
44164453
[`session.ondatagramstatus`]: #sessionondatagramstatus
44174454
[`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
constonEndpointBusyChangeChannel=dc.channel('quic.endpoint.busy.change');
1515
constonEndpointClientSessionChannel=dc.channel('quic.session.created.client');
1616
constonEndpointServerSessionChannel=dc.channel('quic.session.created.server');
17+
constonSessionApplicationChannel=dc.channel('quic.session.application');
1718
constonSessionOpenStreamChannel=dc.channel('quic.session.open.stream');
1819
constonSessionReceivedStreamChannel=dc.channel('quic.session.received.stream');
1920
constonSessionSendDatagramChannel=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+
getonapplication(){
2998+
assertIsQuicSession(this);
2999+
returnthis.#inner.onapplication;
3000+
}
3001+
3002+
setonapplication(fn){
3003+
assertIsQuicSession(this);
3004+
constinner=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
getonversionnegotiation(){
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+
constinner=this.#inner;
3844+
if(typeofinner.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
consthandle=this.#handle;
@@ -367,6 +368,14 @@ class QuicSessionState {
367368
val ? (current|flag) : (current&~flag),kIsLittleEndian);
368369
}
369370

371+
/** @type {boolean} */
372+
gethasApplicationListener(){
373+
returnthis.#getListenerFlag(QuicSessionState.#LISTENER_APPLICATION);
374+
}
375+
sethasApplicationListener(val){
376+
this.#setListenerFlag(QuicSessionState.#LISTENER_APPLICATION,val);
377+
}
378+
370379
/** @type {boolean} */
371380
gethasPathValidationListener(){
372381
returnthis.#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
constkRemoveStream=Symbol('kRemoveStream');
5656
constkReset=Symbol('kReset');
5757
constkSendHeaders=Symbol('kSendHeaders');
58+
constkSessionApplication=Symbol('kSessionApplication');
5859
constkSessionTicket=Symbol('kSessionTicket');
5960
constkTrailers=Symbol('kTrailers');
6061
constkVersionNegotiation=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
#defineQUIC_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
@@ -1014,6 +1014,8 @@ class Http3ApplicationImpl final : public Session::Application {
10141014
Debug(&session(),
10151015
"HTTP/3 application received updated settings: %s",
10161016
options_);
1017+
// The settings are part of the application
1018+
session().EmitApplication();
10171019
}
10181020

10191021
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,
@@ -3677,6 +3678,33 @@ void Session::EmitSessionTicket(Store&& ticket) {
36773678
}
36783679
}
36793680

3681+
voidSession::EmitApplication() {
3682+
if (is_destroyed()) return;
3683+
if (!env()->can_call_into_js()) return;
3684+
3685+
if (!has_application()) {
3686+
// The application has not yet been selected (ALPN negotiation is not
3687+
// yet complete on the server) or the session has been destroyed. In
3688+
// either case, the application options are not available.
3689+
// Should not happen, but we bail out
3690+
return;
3691+
}
3692+
3693+
if (!HasListenerFlag(impl_->state()->listener_flags,
3694+
SessionListenerFlags::APPLICATION)) [[likely]] {
3695+
return;
3696+
}
3697+
3698+
CallbackScope<Session> cb_scope(this);
3699+
3700+
Local<Value> argv;
3701+
auto& options = application().options();
3702+
if (options.ToObject(env()).ToLocal(&argv)) {
3703+
MakeCallback(
3704+
BindingData::Get(env()).session_application_callback(), 1, &argv);
3705+
}
3706+
}
3707+
36803708
voidSession::DestroyAllStreams(const QuicError& error) {
36813709
DCHECK(!is_destroyed());
36823710
// 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
voidEmitVersionNegotiation(const ngtcp2_pkt_hd& hd,
616616
constuint32_t* sv,
617617
size_t nsv);
618+
voidEmitApplication();
618619
voidDatagramStatus(datagram_id datagramId, DatagramStatus status);
619620
voidDatagramReceived(constuint8_t* data,
620621
size_t datalen,

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 2f749dd

Browse files
martenrichteraduh95
authored andcommitted
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 Backport-PR-URL: #64675 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 5ca7ef3 commit 2f749dd

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
@@ -914,6 +914,8 @@ added: REPLACEME
914914
The current application-level options for this session. These include settings
915915
that are specific to the negotiated application protocol (e.g. HTTP/3) and may
916916
be negotiated separately from the transport parameters. Read only.
917+
You can use the callback [`session.onapplication`][] to be informed, when settings
918+
from the remote arrive.
917919

918920
### `session.close([options])`
919921

@@ -1046,6 +1048,16 @@ added: v23.8.0
10461048
The endpoint that created this session. Returns `null` if the session
10471049
has been destroyed. Read only.
10481050

1051+
### `session.onapplication`
1052+
1053+
<!-- YAML
1054+
added: REPLACEME
1055+
-->
1056+
1057+
* Type: {quic.OnApplicationCallback}
1058+
1059+
The callback to invoke when new application options, e.g. HTTP/3 settings arrived.
1060+
10491061
### `session.onerror`
10501062

10511063
<!-- YAML
@@ -3499,11 +3511,11 @@ with that error:
34993511

35003512
* Stream callbacks (`onblocked`, `onreset`, `onheaders`, `ontrailers`,
35013513
`oninfo`, `onwanttrailers`): the stream is destroyed.
3502-
* Session callbacks (`onstream`, `ondatagram`, `ondatagramstatus`,
3503-
`onpathvalidation`, `onsessionticket`, `onnewtoken`,
3504-
`onversionnegotiation`, `onorigin`, `ongoaway`, `onhandshake`,
3505-
`onkeylog`, `onqlog`): the session is destroyed along with all of its
3506-
streams.
3514+
* Session callbacks (`onapplication`, `onstream`, `ondatagram`,
3515+
`ondatagramstatus`, `onpathvalidation`, `onsessionticket`,
3516+
`onnewtoken`, `onversionnegotiation`, `onorigin`, `ongoaway`,
3517+
`onhandshake`, `onkeylog`, `onqlog`): the session is destroyed along
3518+
with all of its streams.
35073519

35083520
Before destruction, the optional [`session.onerror`][] or
35093521
[`stream.onerror`][] callback is invoked (if set), giving the application a
@@ -3557,6 +3569,19 @@ added: v23.8.0
35573569
datagram was never sent on the wire (dropped due to queue overflow,
35583570
send attempt limit exceeded, or frame size rejection).
35593571

3572+
### Callback: `OnApplicationCallback`
3573+
3574+
<!-- YAML
3575+
added: v23.8.0
3576+
-->
3577+
3578+
*`this` {quic.QuicSession}
3579+
*`applicationoption` {quic.QuicSession}
3580+
3581+
The callback function that is invoked when application options change.
3582+
E.g. for http/3 settings are included in applications options and
3583+
may arrive after the connection is established.
3584+
35603585
### Callback: `OnPathValidationCallback`
35613586

35623587
<!-- YAML
@@ -4031,6 +4056,17 @@ added: v23.8.0
40314056
40324057
Published when an endpoint's busy state changes.
40334058
4059+
### Channel: `quic.session.application`
4060+
4061+
<!-- YAML
4062+
added: v23.8.0
4063+
-->
4064+
4065+
* `applicationoptions` {quic.ApplicationOptions} Current application options.
4066+
* `session` {quic.QuicSession}
4067+
4068+
Published when a locally-initiated stream is opened.
4069+
40344070
### Channel: `quic.session.created.client`
40354071
40364072
<!-- YAML
@@ -4412,6 +4448,7 @@ throughput issues caused by flow control.
44124448
[`session.createUnidirectionalStream()`]: #sessioncreateunidirectionalstreamoptions
44134449
[`session.destroy()`]: #sessiondestroyerror-options
44144450
[`session.maxPendingDatagrams`]: #sessionmaxpendingdatagrams
4451+
[`session.onapplication`]: #sessiononapplication
44154452
[`session.ondatagram`]: #sessionondatagram
44164453
[`session.ondatagramstatus`]: #sessionondatagramstatus
44174454
[`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
constonEndpointBusyChangeChannel=dc.channel('quic.endpoint.busy.change');
1515
constonEndpointClientSessionChannel=dc.channel('quic.session.created.client');
1616
constonEndpointServerSessionChannel=dc.channel('quic.session.created.server');
17+
constonSessionApplicationChannel=dc.channel('quic.session.application');
1718
constonSessionOpenStreamChannel=dc.channel('quic.session.open.stream');
1819
constonSessionReceivedStreamChannel=dc.channel('quic.session.received.stream');
1920
constonSessionSendDatagramChannel=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+
getonapplication(){
2998+
assertIsQuicSession(this);
2999+
returnthis.#inner.onapplication;
3000+
}
3001+
3002+
setonapplication(fn){
3003+
assertIsQuicSession(this);
3004+
constinner=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
getonversionnegotiation(){
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+
constinner=this.#inner;
3844+
if(typeofinner.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
consthandle=this.#handle;
@@ -367,6 +368,14 @@ class QuicSessionState {
367368
val ? (current|flag) : (current&~flag),kIsLittleEndian);
368369
}
369370

371+
/** @type {boolean} */
372+
gethasApplicationListener(){
373+
returnthis.#getListenerFlag(QuicSessionState.#LISTENER_APPLICATION);
374+
}
375+
sethasApplicationListener(val){
376+
this.#setListenerFlag(QuicSessionState.#LISTENER_APPLICATION,val);
377+
}
378+
370379
/** @type {boolean} */
371380
gethasPathValidationListener(){
372381
returnthis.#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
constkRemoveStream=Symbol('kRemoveStream');
5656
constkReset=Symbol('kReset');
5757
constkSendHeaders=Symbol('kSendHeaders');
58+
constkSessionApplication=Symbol('kSessionApplication');
5859
constkSessionTicket=Symbol('kSessionTicket');
5960
constkTrailers=Symbol('kTrailers');
6061
constkVersionNegotiation=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
#defineQUIC_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
@@ -1014,6 +1014,8 @@ class Http3ApplicationImpl final : public Session::Application {
10141014
Debug(&session(),
10151015
"HTTP/3 application received updated settings: %s",
10161016
options_);
1017+
// The settings are part of the application
1018+
session().EmitApplication();
10171019
}
10181020

10191021
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,
@@ -3677,6 +3678,33 @@ void Session::EmitSessionTicket(Store&& ticket) {
36773678
}
36783679
}
36793680

3681+
voidSession::EmitApplication() {
3682+
if (is_destroyed()) return;
3683+
if (!env()->can_call_into_js()) return;
3684+
3685+
if (!has_application()) {
3686+
// The application has not yet been selected (ALPN negotiation is not
3687+
// yet complete on the server) or the session has been destroyed. In
3688+
// either case, the application options are not available.
3689+
// Should not happen, but we bail out
3690+
return;
3691+
}
3692+
3693+
if (!HasListenerFlag(impl_->state()->listener_flags,
3694+
SessionListenerFlags::APPLICATION)) [[likely]] {
3695+
return;
3696+
}
3697+
3698+
CallbackScope<Session> cb_scope(this);
3699+
3700+
Local<Value> argv;
3701+
auto& options = application().options();
3702+
if (options.ToObject(env()).ToLocal(&argv)) {
3703+
MakeCallback(
3704+
BindingData::Get(env()).session_application_callback(), 1, &argv);
3705+
}
3706+
}
3707+
36803708
voidSession::DestroyAllStreams(const QuicError& error) {
36813709
DCHECK(!is_destroyed());
36823710
// 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
voidEmitVersionNegotiation(const ngtcp2_pkt_hd& hd,
616616
constuint32_t* sv,
617617
size_t nsv);
618+
voidEmitApplication();
618619
voidDatagramStatus(datagram_id datagramId, DatagramStatus status);
619620
voidDatagramReceived(constuint8_t* data,
620621
size_t datalen,

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 2f749dd

Browse files
martenrichteraduh95
authored andcommitted
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 Backport-PR-URL: #64675 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 5ca7ef3 commit 2f749dd

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
@@ -914,6 +914,8 @@ added: REPLACEME
914914
The current application-level options for this session. These include settings
915915
that are specific to the negotiated application protocol (e.g. HTTP/3) and may
916916
be negotiated separately from the transport parameters. Read only.
917+
You can use the callback [`session.onapplication`][] to be informed, when settings
918+
from the remote arrive.
917919

918920
### `session.close([options])`
919921

@@ -1046,6 +1048,16 @@ added: v23.8.0
10461048
The endpoint that created this session. Returns `null` if the session
10471049
has been destroyed. Read only.
10481050

1051+
### `session.onapplication`
1052+
1053+
<!-- YAML
1054+
added: REPLACEME
1055+
-->
1056+
1057+
* Type: {quic.OnApplicationCallback}
1058+
1059+
The callback to invoke when new application options, e.g. HTTP/3 settings arrived.
1060+
10491061
### `session.onerror`
10501062

10511063
<!-- YAML
@@ -3499,11 +3511,11 @@ with that error:
34993511

35003512
* Stream callbacks (`onblocked`, `onreset`, `onheaders`, `ontrailers`,
35013513
`oninfo`, `onwanttrailers`): the stream is destroyed.
3502-
* Session callbacks (`onstream`, `ondatagram`, `ondatagramstatus`,
3503-
`onpathvalidation`, `onsessionticket`, `onnewtoken`,
3504-
`onversionnegotiation`, `onorigin`, `ongoaway`, `onhandshake`,
3505-
`onkeylog`, `onqlog`): the session is destroyed along with all of its
3506-
streams.
3514+
* Session callbacks (`onapplication`, `onstream`, `ondatagram`,
3515+
`ondatagramstatus`, `onpathvalidation`, `onsessionticket`,
3516+
`onnewtoken`, `onversionnegotiation`, `onorigin`, `ongoaway`,
3517+
`onhandshake`, `onkeylog`, `onqlog`): the session is destroyed along
3518+
with all of its streams.
35073519

35083520
Before destruction, the optional [`session.onerror`][] or
35093521
[`stream.onerror`][] callback is invoked (if set), giving the application a
@@ -3557,6 +3569,19 @@ added: v23.8.0
35573569
datagram was never sent on the wire (dropped due to queue overflow,
35583570
send attempt limit exceeded, or frame size rejection).
35593571

3572+
### Callback: `OnApplicationCallback`
3573+
3574+
<!-- YAML
3575+
added: v23.8.0
3576+
-->
3577+
3578+
*`this` {quic.QuicSession}
3579+
*`applicationoption` {quic.QuicSession}
3580+
3581+
The callback function that is invoked when application options change.
3582+
E.g. for http/3 settings are included in applications options and
3583+
may arrive after the connection is established.
3584+
35603585
### Callback: `OnPathValidationCallback`
35613586

35623587
<!-- YAML
@@ -4031,6 +4056,17 @@ added: v23.8.0
40314056
40324057
Published when an endpoint's busy state changes.
40334058
4059+
### Channel: `quic.session.application`
4060+
4061+
<!-- YAML
4062+
added: v23.8.0
4063+
-->
4064+
4065+
* `applicationoptions` {quic.ApplicationOptions} Current application options.
4066+
* `session` {quic.QuicSession}
4067+
4068+
Published when a locally-initiated stream is opened.
4069+
40344070
### Channel: `quic.session.created.client`
40354071
40364072
<!-- YAML
@@ -4412,6 +4448,7 @@ throughput issues caused by flow control.
44124448
[`session.createUnidirectionalStream()`]: #sessioncreateunidirectionalstreamoptions
44134449
[`session.destroy()`]: #sessiondestroyerror-options
44144450
[`session.maxPendingDatagrams`]: #sessionmaxpendingdatagrams
4451+
[`session.onapplication`]: #sessiononapplication
44154452
[`session.ondatagram`]: #sessionondatagram
44164453
[`session.ondatagramstatus`]: #sessionondatagramstatus
44174454
[`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
constonEndpointBusyChangeChannel=dc.channel('quic.endpoint.busy.change');
1515
constonEndpointClientSessionChannel=dc.channel('quic.session.created.client');
1616
constonEndpointServerSessionChannel=dc.channel('quic.session.created.server');
17+
constonSessionApplicationChannel=dc.channel('quic.session.application');
1718
constonSessionOpenStreamChannel=dc.channel('quic.session.open.stream');
1819
constonSessionReceivedStreamChannel=dc.channel('quic.session.received.stream');
1920
constonSessionSendDatagramChannel=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+
getonapplication(){
2998+
assertIsQuicSession(this);
2999+
returnthis.#inner.onapplication;
3000+
}
3001+
3002+
setonapplication(fn){
3003+
assertIsQuicSession(this);
3004+
constinner=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
getonversionnegotiation(){
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+
constinner=this.#inner;
3844+
if(typeofinner.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
consthandle=this.#handle;
@@ -367,6 +368,14 @@ class QuicSessionState {
367368
val ? (current|flag) : (current&~flag),kIsLittleEndian);
368369
}
369370

371+
/** @type {boolean} */
372+
gethasApplicationListener(){
373+
returnthis.#getListenerFlag(QuicSessionState.#LISTENER_APPLICATION);
374+
}
375+
sethasApplicationListener(val){
376+
this.#setListenerFlag(QuicSessionState.#LISTENER_APPLICATION,val);
377+
}
378+
370379
/** @type {boolean} */
371380
gethasPathValidationListener(){
372381
returnthis.#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
constkRemoveStream=Symbol('kRemoveStream');
5656
constkReset=Symbol('kReset');
5757
constkSendHeaders=Symbol('kSendHeaders');
58+
constkSessionApplication=Symbol('kSessionApplication');
5859
constkSessionTicket=Symbol('kSessionTicket');
5960
constkTrailers=Symbol('kTrailers');
6061
constkVersionNegotiation=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
#defineQUIC_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
@@ -1014,6 +1014,8 @@ class Http3ApplicationImpl final : public Session::Application {
10141014
Debug(&session(),
10151015
"HTTP/3 application received updated settings: %s",
10161016
options_);
1017+
// The settings are part of the application
1018+
session().EmitApplication();
10171019
}
10181020

10191021
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,
@@ -3677,6 +3678,33 @@ void Session::EmitSessionTicket(Store&& ticket) {
36773678
}
36783679
}
36793680

3681+
voidSession::EmitApplication() {
3682+
if (is_destroyed()) return;
3683+
if (!env()->can_call_into_js()) return;
3684+
3685+
if (!has_application()) {
3686+
// The application has not yet been selected (ALPN negotiation is not
3687+
// yet complete on the server) or the session has been destroyed. In
3688+
// either case, the application options are not available.
3689+
// Should not happen, but we bail out
3690+
return;
3691+
}
3692+
3693+
if (!HasListenerFlag(impl_->state()->listener_flags,
3694+
SessionListenerFlags::APPLICATION)) [[likely]] {
3695+
return;
3696+
}
3697+
3698+
CallbackScope<Session> cb_scope(this);
3699+
3700+
Local<Value> argv;
3701+
auto& options = application().options();
3702+
if (options.ToObject(env()).ToLocal(&argv)) {
3703+
MakeCallback(
3704+
BindingData::Get(env()).session_application_callback(), 1, &argv);
3705+
}
3706+
}
3707+
36803708
voidSession::DestroyAllStreams(const QuicError& error) {
36813709
DCHECK(!is_destroyed());
36823710
// 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
voidEmitVersionNegotiation(const ngtcp2_pkt_hd& hd,
616616
constuint32_t* sv,
617617
size_t nsv);
618+
voidEmitApplication();
618619
voidDatagramStatus(datagram_id datagramId, DatagramStatus status);
619620
voidDatagramReceived(constuint8_t* data,
620621
size_t datalen,

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Commit 2f749dd

Browse files
martenrichteraduh95
authored andcommitted
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 Backport-PR-URL: #64675 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 5ca7ef3 commit 2f749dd

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
@@ -914,6 +914,8 @@ added: REPLACEME
914914
The current application-level options for this session. These include settings
915915
that are specific to the negotiated application protocol (e.g. HTTP/3) and may
916916
be negotiated separately from the transport parameters. Read only.
917+
You can use the callback [`session.onapplication`][] to be informed, when settings
918+
from the remote arrive.
917919

918920
### `session.close([options])`
919921

@@ -1046,6 +1048,16 @@ added: v23.8.0
10461048
The endpoint that created this session. Returns `null` if the session
10471049
has been destroyed. Read only.
10481050

1051+
### `session.onapplication`
1052+
1053+
<!-- YAML
1054+
added: REPLACEME
1055+
-->
1056+
1057+
* Type: {quic.OnApplicationCallback}
1058+
1059+
The callback to invoke when new application options, e.g. HTTP/3 settings arrived.
1060+
10491061
### `session.onerror`
10501062

10511063
<!-- YAML
@@ -3499,11 +3511,11 @@ with that error:
34993511

35003512
* Stream callbacks (`onblocked`, `onreset`, `onheaders`, `ontrailers`,
35013513
`oninfo`, `onwanttrailers`): the stream is destroyed.
3502-
* Session callbacks (`onstream`, `ondatagram`, `ondatagramstatus`,
3503-
`onpathvalidation`, `onsessionticket`, `onnewtoken`,
3504-
`onversionnegotiation`, `onorigin`, `ongoaway`, `onhandshake`,
3505-
`onkeylog`, `onqlog`): the session is destroyed along with all of its
3506-
streams.
3514+
* Session callbacks (`onapplication`, `onstream`, `ondatagram`,
3515+
`ondatagramstatus`, `onpathvalidation`, `onsessionticket`,
3516+
`onnewtoken`, `onversionnegotiation`, `onorigin`, `ongoaway`,
3517+
`onhandshake`, `onkeylog`, `onqlog`): the session is destroyed along
3518+
with all of its streams.
35073519

35083520
Before destruction, the optional [`session.onerror`][] or
35093521
[`stream.onerror`][] callback is invoked (if set), giving the application a
@@ -3557,6 +3569,19 @@ added: v23.8.0
35573569
datagram was never sent on the wire (dropped due to queue overflow,
35583570
send attempt limit exceeded, or frame size rejection).
35593571

3572+
### Callback: `OnApplicationCallback`
3573+
3574+
<!-- YAML
3575+
added: v23.8.0
3576+
-->
3577+
3578+
*`this` {quic.QuicSession}
3579+
*`applicationoption` {quic.QuicSession}
3580+
3581+
The callback function that is invoked when application options change.
3582+
E.g. for http/3 settings are included in applications options and
3583+
may arrive after the connection is established.
3584+
35603585
### Callback: `OnPathValidationCallback`
35613586

35623587
<!-- YAML
@@ -4031,6 +4056,17 @@ added: v23.8.0
40314056
40324057
Published when an endpoint's busy state changes.
40334058
4059+
### Channel: `quic.session.application`
4060+
4061+
<!-- YAML
4062+
added: v23.8.0
4063+
-->
4064+
4065+
* `applicationoptions` {quic.ApplicationOptions} Current application options.
4066+
* `session` {quic.QuicSession}
4067+
4068+
Published when a locally-initiated stream is opened.
4069+
40344070
### Channel: `quic.session.created.client`
40354071
40364072
<!-- YAML
@@ -4412,6 +4448,7 @@ throughput issues caused by flow control.
44124448
[`session.createUnidirectionalStream()`]: #sessioncreateunidirectionalstreamoptions
44134449
[`session.destroy()`]: #sessiondestroyerror-options
44144450
[`session.maxPendingDatagrams`]: #sessionmaxpendingdatagrams
4451+
[`session.onapplication`]: #sessiononapplication
44154452
[`session.ondatagram`]: #sessionondatagram
44164453
[`session.ondatagramstatus`]: #sessionondatagramstatus
44174454
[`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
constonEndpointBusyChangeChannel=dc.channel('quic.endpoint.busy.change');
1515
constonEndpointClientSessionChannel=dc.channel('quic.session.created.client');
1616
constonEndpointServerSessionChannel=dc.channel('quic.session.created.server');
17+
constonSessionApplicationChannel=dc.channel('quic.session.application');
1718
constonSessionOpenStreamChannel=dc.channel('quic.session.open.stream');
1819
constonSessionReceivedStreamChannel=dc.channel('quic.session.received.stream');
1920
constonSessionSendDatagramChannel=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+
getonapplication(){
2998+
assertIsQuicSession(this);
2999+
returnthis.#inner.onapplication;
3000+
}
3001+
3002+
setonapplication(fn){
3003+
assertIsQuicSession(this);
3004+
constinner=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
getonversionnegotiation(){
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+
constinner=this.#inner;
3844+
if(typeofinner.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
consthandle=this.#handle;
@@ -367,6 +368,14 @@ class QuicSessionState {
367368
val ? (current|flag) : (current&~flag),kIsLittleEndian);
368369
}
369370

371+
/** @type {boolean} */
372+
gethasApplicationListener(){
373+
returnthis.#getListenerFlag(QuicSessionState.#LISTENER_APPLICATION);
374+
}
375+
sethasApplicationListener(val){
376+
this.#setListenerFlag(QuicSessionState.#LISTENER_APPLICATION,val);
377+
}
378+
370379
/** @type {boolean} */
371380
gethasPathValidationListener(){
372381
returnthis.#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
constkRemoveStream=Symbol('kRemoveStream');
5656
constkReset=Symbol('kReset');
5757
constkSendHeaders=Symbol('kSendHeaders');
58+
constkSessionApplication=Symbol('kSessionApplication');
5859
constkSessionTicket=Symbol('kSessionTicket');
5960
constkTrailers=Symbol('kTrailers');
6061
constkVersionNegotiation=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
#defineQUIC_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
@@ -1014,6 +1014,8 @@ class Http3ApplicationImpl final : public Session::Application {
10141014
Debug(&session(),
10151015
"HTTP/3 application received updated settings: %s",
10161016
options_);
1017+
// The settings are part of the application
1018+
session().EmitApplication();
10171019
}
10181020

10191021
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,
@@ -3677,6 +3678,33 @@ void Session::EmitSessionTicket(Store&& ticket) {
36773678
}
36783679
}
36793680

3681+
voidSession::EmitApplication() {
3682+
if (is_destroyed()) return;
3683+
if (!env()->can_call_into_js()) return;
3684+
3685+
if (!has_application()) {
3686+
// The application has not yet been selected (ALPN negotiation is not
3687+
// yet complete on the server) or the session has been destroyed. In
3688+
// either case, the application options are not available.
3689+
// Should not happen, but we bail out
3690+
return;
3691+
}
3692+
3693+
if (!HasListenerFlag(impl_->state()->listener_flags,
3694+
SessionListenerFlags::APPLICATION)) [[likely]] {
3695+
return;
3696+
}
3697+
3698+
CallbackScope<Session> cb_scope(this);
3699+
3700+
Local<Value> argv;
3701+
auto& options = application().options();
3702+
if (options.ToObject(env()).ToLocal(&argv)) {
3703+
MakeCallback(
3704+
BindingData::Get(env()).session_application_callback(), 1, &argv);
3705+
}
3706+
}
3707+
36803708
voidSession::DestroyAllStreams(const QuicError& error) {
36813709
DCHECK(!is_destroyed());
36823710
// 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
voidEmitVersionNegotiation(const ngtcp2_pkt_hd& hd,
616616
constuint32_t* sv,
617617
size_t nsv);
618+
voidEmitApplication();
618619
voidDatagramStatus(datagram_id datagramId, DatagramStatus status);
619620
voidDatagramReceived(constuint8_t* data,
620621
size_t datalen,

0 commit comments

Comments
 (0)