33const {
44 ArrayIsArray,
55 ArrayPrototypePush,
6+ ObjectHasOwn,
67 Symbol,
78 SymbolAsyncIterator,
89} = primordials ;
@@ -40,6 +41,7 @@ const {
4041
4142const {
4243 setHttp3Callbacks,
44+ createHttp3Handle,
4345
4446 QUIC_STREAM_HEADERS_KIND_INITIAL : kHeadersKindInitial ,
4547 QUIC_STREAM_HEADERS_KIND_HINTS : kHeadersKindHints ,
@@ -65,8 +67,10 @@ const {
6567} = require ( 'internal/quic/diagnostics' ) ;
6668
6769const {
70+ validateBoolean,
6871 validateFunction,
6972 validateObject,
73+ validateOneOf,
7074} = require ( 'internal/validators' ) ;
7175
7276const {
@@ -133,14 +137,29 @@ function parseHeaderPairs(pairs) {
133137 * will be sent after these headers.
134138 */
135139
140+ const kGetHttp3Handle = Symbol ( 'kGetHttp3Handle' ) ;
141+ const kSubmitInitialHeaders = Symbol ( 'kSubmitInitialHeaders' ) ;
142+
143+ function priorityFieldValue ( level , incremental ) {
144+ const urgency = level === 'high' ? 0 : level === 'low' ? 7 : 3 ;
145+ if ( urgency === 3 && ! incremental ) return undefined ;
146+ return incremental ? `u=${ urgency } , i` : `u=${ urgency } ` ;
147+ }
148+
136149class Http3Stream {
137150 #stream;
138151 #session;
152+ #h3handle;
139153 #headers = undefined ;
140154 #onheaders = undefined ;
141155 #oninfo = undefined ;
142156 #ontrailers = undefined ;
143157 #onwanttrailers = undefined ;
158+ // Client-side stored priority (the value this side requested). The server
159+ // side reads the peer's requested priority from nghttp3 instead.
160+ #priority = { __proto__ : null , level : 'default' , incremental : false } ;
161+ // Have we submitted an initial header block (request/response) to nghttp3?
162+ #headersSubmitted = false ;
144163
145164 /**
146165 * @param {QuicStream } stream the underlying QUIC stream
@@ -153,6 +172,7 @@ class Http3Stream {
153172 }
154173 this . #stream = stream ;
155174 this . #session = session ;
175+ this . #h3handle = session [ kGetHttp3Handle ] ( ) ;
156176 const handle = stream [ kStreamHandle ] ;
157177 if ( handle !== undefined ) {
158178 handle [ kApplicationOwner ] = this ;
@@ -246,9 +266,57 @@ class Http3Stream {
246266 // True when the stream's data was received as 0-RTT early data.
247267 get early ( ) { return this . #stream. early ; }
248268
249- get priority ( ) { return this . #stream. priority ; }
269+ get #isServer( ) {
270+ return getQuicSessionState ( this . #session. session ) . isServer ;
271+ }
250272
251- setPriority ( options ) { return this . #stream. setPriority ( options ) ; }
273+ get #knownToNgHttp3( ) {
274+ return this . #isServer || this . #headersSubmitted;
275+ }
276+
277+ // The stream's priority. On a client this is the value we requested; on a
278+ // server it is the peer's requested priority read from nghttp3.
279+ get priority ( ) {
280+ const stream = this . #stream;
281+ if ( stream . destroyed || this . #h3handle === undefined ) return null ;
282+ if ( ! this . #isServer) {
283+ return { level : this . #priority. level ,
284+ incremental : this . #priority. incremental } ;
285+ }
286+ const packed = this . #h3handle. getPriority ( stream [ kStreamHandle ] ) ;
287+ if ( packed === undefined ) return null ;
288+ const urgency = packed >> 1 ;
289+ const incremental = ! ! ( packed & 1 ) ;
290+ const level = urgency < 3 ? 'high' : urgency > 3 ? 'low' : 'default' ;
291+ return { level, incremental } ;
292+ }
293+
294+ setPriority ( options = kEmptyObject ) {
295+ const stream = this . #stream;
296+ if ( stream . destroyed ) return ;
297+ validateObject ( options , 'options' ) ;
298+ const { level = 'default' , incremental = false } = options ;
299+ validateOneOf ( level , 'options.level' , [ 'default' , 'low' , 'high' ] ) ;
300+ validateBoolean ( incremental , 'options.incremental' ) ;
301+ this . #priority = { __proto__ : null , level, incremental } ;
302+
303+ // Before the stream is known to nghttp3 (client, pre-submit) the requested
304+ // priority is carried in the initial header block by sendHeaders;
305+ // afterwards a change is signaled with a PRIORITY_UPDATE frame.
306+ if ( this . #knownToNgHttp3 && this . #h3handle !== undefined ) {
307+ const urgency = level === 'high' ? 0 : level === 'low' ? 7 : 3 ;
308+ this . #h3handle. setPriority (
309+ stream [ kStreamHandle ] , ( urgency << 1 ) | ( incremental ? 1 : 0 ) ) ;
310+ }
311+ }
312+
313+ [ kSubmitInitialHeaders ] ( headerString , flags ) {
314+ const stream = this . #stream;
315+ if ( stream . destroyed || this . #h3handle === undefined ) return false ;
316+ this . #headersSubmitted = true ;
317+ return this . #h3handle. sendHeaders (
318+ stream [ kStreamHandle ] , headerString , flags ) ;
319+ }
252320
253321 #updateHeaderInterest( ) {
254322 getQuicStreamState ( this . #stream) . wantsHeaders =
@@ -308,14 +376,29 @@ class Http3Stream {
308376 */
309377 sendHeaders ( headers , options = kEmptyObject ) {
310378 const stream = this . #stream;
311- if ( stream . destroyed ) return false ;
379+ if ( stream . destroyed || this . #h3handle === undefined ) return false ;
312380 validateObject ( headers , 'headers' ) ;
313381 const { terminal = false } = options ;
382+
383+ // A client request carries its requested priority as a priority header
384+ // when set - server responses signal via setPriority instead.
385+ let toSend = headers ;
386+ if ( ! this . #isServer) {
387+ const pri = priorityFieldValue (
388+ this . #priority. level , this . #priority. incremental ) ;
389+ if ( pri !== undefined && ! ObjectHasOwn ( headers , 'priority' ) ) {
390+ toSend = { __proto__ : null , ...headers , priority : pri } ;
391+ }
392+ }
393+
314394 const headerString = buildNgHeaderString (
315- headers , assertValidPseudoHeader , true /* strictSingleValueFields */ ) ;
395+ toSend , assertValidPseudoHeader , true /* strictSingleValueFields */ ) ;
316396 const flags = terminal ? kHeadersFlagsTerminal : kHeadersFlagsNone ;
317- return stream [ kStreamHandle ] . sendHeaders (
318- kHeadersKindInitial , headerString , flags ) ;
397+ // The stream now exists in nghttp3; later priority changes use
398+ // PRIORITY_UPDATE rather than the header block.
399+ this . #headersSubmitted = true ;
400+ return this . #h3handle. sendHeaders (
401+ stream [ kStreamHandle ] , headerString , flags ) ;
319402 }
320403
321404 /**
@@ -326,11 +409,12 @@ class Http3Stream {
326409 sendInformationalHeaders ( headers ) {
327410 const stream = this . #stream;
328411 if ( stream . destroyed ) return false ;
412+ if ( this . #h3handle === undefined ) return false ;
329413 validateObject ( headers , 'headers' ) ;
330414 const headerString = buildNgHeaderString (
331415 headers , assertValidPseudoHeader , true ) ;
332- return stream [ kStreamHandle ] . sendHeaders (
333- kHeadersKindHints , headerString , kHeadersFlagsNone ) ;
416+ return this . #h3handle . sendInformationalHeaders (
417+ stream [ kStreamHandle ] , headerString ) ;
334418 }
335419
336420 /**
@@ -342,11 +426,12 @@ class Http3Stream {
342426 sendTrailers ( headers ) {
343427 const stream = this . #stream;
344428 if ( stream . destroyed ) return false ;
429+ if ( this . #h3handle === undefined ) return false ;
345430 validateObject ( headers , 'headers' ) ;
346431 const headerString =
347432 buildNgHeaderString ( headers , assertValidPseudoHeaderTrailer ) ;
348- return stream [ kStreamHandle ] . sendHeaders (
349- kHeadersKindTrailing , headerString , kHeadersFlagsNone ) ;
433+ return this . #h3handle . sendTrailers (
434+ stream [ kStreamHandle ] , headerString ) ;
350435 }
351436
352437 // Outbound body writer (stream/iter push writer).
@@ -371,6 +456,7 @@ class Http3Stream {
371456
372457class Http3Session {
373458 #session;
459+ #h3handle;
374460 #onstream = undefined ;
375461 #ongoaway = undefined ;
376462 #onorigin = undefined ;
@@ -395,6 +481,11 @@ class Http3Session {
395481 // routed back here.
396482 const handle = session [ kSessionHandle ] ;
397483 if ( handle !== undefined ) {
484+ if ( handle [ kApplicationOwner ] !== undefined ) {
485+ throw new ERR_INVALID_STATE (
486+ 'The QUIC session already has an application attached' ) ;
487+ }
488+ this . #h3handle = createHttp3Handle ( handle ) ;
398489 handle [ kApplicationOwner ] = this ;
399490 }
400491 // Claim the session's incoming streams. This takes the single
@@ -417,6 +508,9 @@ class Http3Session {
417508 if ( onsettings !== undefined ) this . onsettings = onsettings ;
418509 }
419510
511+ // Internal: hands the per-session HTTP/3 handle to this session's streams.
512+ [ kGetHttp3Handle ] ( ) { return this . #h3handle; }
513+
420514 /**
421515 * The peer initiated a graceful shutdown of the session (HTTP/3
422516 * GOAWAY). The session stops allowing new streams.
@@ -584,14 +678,22 @@ class Http3Session {
584678 onwanttrailers,
585679 onreset,
586680 onerror,
681+ priority,
682+ incremental,
587683 ...quicOptions
588684 } = options ;
589685
590686 let headerString ;
591687 if ( headers !== undefined ) {
688+ let toSend = headers ;
689+ const pri = priorityFieldValue ( priority ?? 'default' , incremental ?? false ) ;
690+ if ( pri !== undefined && ! ObjectHasOwn ( headers , 'priority' ) ) {
691+ toSend = { __proto__ : null , ...headers , priority : pri } ;
692+ }
592693 headerString = buildNgHeaderString (
593- headers , assertValidPseudoHeader , true /* strictSingleValueFields */ ) ;
694+ toSend , assertValidPseudoHeader , true /* strictSingleValueFields */ ) ;
594695 }
696+
595697 const stream = await this . #session. createBidirectionalStream ( quicOptions ) ;
596698 const wrapped = new Http3Stream ( stream , this , {
597699 __proto__ : null ,
@@ -602,6 +704,11 @@ class Http3Session {
602704 onreset,
603705 onerror,
604706 } ) ;
707+
708+ if ( priority !== undefined || incremental !== undefined ) {
709+ wrapped . setPriority ( { __proto__ : null , level : priority , incremental } ) ;
710+ }
711+
605712 // Submit the request headers only after the callbacks above are
606713 // attached. Nothing for this stream can hit the wire before the
607714 // headers are submitted: the HTTP/3 application only learns about the
@@ -611,8 +718,7 @@ class Http3Session {
611718 if ( headerString !== undefined ) {
612719 const flags = quicOptions . body === undefined ?
613720 kHeadersFlagsTerminal : kHeadersFlagsNone ;
614- if ( ! stream [ kStreamHandle ] . sendHeaders (
615- kHeadersKindInitial , headerString , flags ) ) {
721+ if ( ! wrapped [ kSubmitInitialHeaders ] ( headerString , flags ) ) {
616722 wrapped . destroy ( ) ;
617723 throw new ERR_QUIC_OPEN_STREAM_FAILED ( ) ;
618724 }
0 commit comments