@@ -637,6 +637,51 @@ function validateBody(body) {
637637 ] , body ) ;
638638}
639639
640+ /**
641+ * Parses an alternating [name, value, name, value, ...] array from C++
642+ * into a plain header object. Multi-value headers become arrays.
643+ * @param {string[] } pairs
644+ * @returns {object }
645+ */
646+ function parseHeaderPairs ( pairs ) {
647+ assert ( ArrayIsArray ( pairs ) ) ;
648+ assert ( pairs . length % 2 === 0 ) ;
649+ const block = { __proto__ : null } ;
650+ for ( let n = 0 ; n + 1 < pairs . length ; n += 2 ) {
651+ if ( block [ pairs [ n ] ] !== undefined ) {
652+ block [ pairs [ n ] ] = [ block [ pairs [ n ] ] , pairs [ n + 1 ] ] ;
653+ } else {
654+ block [ pairs [ n ] ] = pairs [ n + 1 ] ;
655+ }
656+ }
657+ return block ;
658+ }
659+
660+ /**
661+ * Applies session and stream callbacks from an options object to a session.
662+ * @param {QuicSession } session
663+ * @param {object } cbs
664+ */
665+ function applyCallbacks ( session , cbs ) {
666+ if ( cbs . onstream ) session . onstream = cbs . onstream ;
667+ if ( cbs . ondatagram ) session . ondatagram = cbs . ondatagram ;
668+ if ( cbs . ondatagramstatus ) session . ondatagramstatus = cbs . ondatagramstatus ;
669+ if ( cbs . onpathvalidation ) session . onpathvalidation = cbs . onpathvalidation ;
670+ if ( cbs . onsessionticket ) session . onsessionticket = cbs . onsessionticket ;
671+ if ( cbs . onversionnegotiation ) session . onversionnegotiation = cbs . onversionnegotiation ;
672+ if ( cbs . onhandshake ) session . onhandshake = cbs . onhandshake ;
673+ if ( cbs . onnewtoken ) session . onnewtoken = cbs . onnewtoken ;
674+ if ( cbs . onorigin ) session . onorigin = cbs . onorigin ;
675+ if ( cbs . onheaders || cbs . ontrailers || cbs . onwanttrailers ) {
676+ session [ kStreamCallbacks ] = {
677+ __proto__ : null ,
678+ onheaders : cbs . onheaders ,
679+ ontrailers : cbs . ontrailers ,
680+ onwanttrailers : cbs . onwanttrailers ,
681+ } ;
682+ }
683+ }
684+
640685// Functions used specifically for internal or assertion purposes only.
641686let getQuicStreamState ;
642687let getQuicSessionState ;
@@ -713,6 +758,13 @@ class QuicStream {
713758 }
714759 }
715760
761+ #assertHeadersSupported( ) {
762+ if ( getQuicSessionState ( this . #session) . headersSupported === 2 ) {
763+ throw new ERR_INVALID_STATE (
764+ 'The negotiated QUIC application protocol does not support headers' ) ;
765+ }
766+ }
767+
716768 /**
717769 * @param {symbol } privateSymbol
718770 * @param {object } handle
@@ -810,10 +862,7 @@ class QuicStream {
810862 this . #onheaders = undefined ;
811863 this . #state[ kWantsHeaders ] = false ;
812864 } else {
813- if ( getQuicSessionState ( this . #session) . headersSupported === 2 ) {
814- throw new ERR_INVALID_STATE (
815- 'The negotiated QUIC application protocol does not support headers' ) ;
816- }
865+ this . #assertHeadersSupported( ) ;
817866 validateFunction ( fn , 'onheaders' ) ;
818867 this . #onheaders = FunctionPrototypeBind ( fn , this ) ;
819868 this . #state[ kWantsHeaders ] = true ;
@@ -832,10 +881,7 @@ class QuicStream {
832881 this . #ontrailers = undefined ;
833882 this . #state[ kWantsTrailers ] = false ;
834883 } else {
835- if ( getQuicSessionState ( this . #session) . headersSupported === 2 ) {
836- throw new ERR_INVALID_STATE (
837- 'The negotiated QUIC application protocol does not support headers' ) ;
838- }
884+ this . #assertHeadersSupported( ) ;
839885 validateFunction ( fn , 'ontrailers' ) ;
840886 this . #ontrailers = FunctionPrototypeBind ( fn , this ) ;
841887 this . #state[ kWantsTrailers ] = true ;
@@ -853,10 +899,7 @@ class QuicStream {
853899 if ( fn === undefined ) {
854900 this . #onwanttrailers = undefined ;
855901 } else {
856- if ( getQuicSessionState ( this . #session) . headersSupported === 2 ) {
857- throw new ERR_INVALID_STATE (
858- 'The negotiated QUIC application protocol does not support headers' ) ;
859- }
902+ this . #assertHeadersSupported( ) ;
860903 validateFunction ( fn , 'onwanttrailers' ) ;
861904 this . #onwanttrailers = FunctionPrototypeBind ( fn , this ) ;
862905 }
@@ -940,7 +983,7 @@ class QuicStream {
940983
941984 /**
942985 * True if the stream has been destroyed.
943- * @returns {boolean }
986+ * @type {boolean }
944987 */
945988 get destroyed ( ) {
946989 QuicStream . #assertIsQuicStream( this ) ;
@@ -1186,18 +1229,7 @@ class QuicStream {
11861229
11871230 [ kHeaders ] ( headers , kind ) {
11881231 assert ( this . #onheaders, 'Unexpected stream headers event' ) ;
1189- assert ( ArrayIsArray ( headers ) ) ;
1190- assert ( headers . length % 2 === 0 ) ;
1191- const block = {
1192- __proto__ : null ,
1193- } ;
1194- for ( let n = 0 ; n + 1 < headers . length ; n += 2 ) {
1195- if ( block [ headers [ n ] ] !== undefined ) {
1196- block [ headers [ n ] ] = [ block [ headers [ n ] ] , headers [ n + 1 ] ] ;
1197- } else {
1198- block [ headers [ n ] ] = headers [ n + 1 ] ;
1199- }
1200- }
1232+ const block = parseHeaderPairs ( headers ) ;
12011233
12021234 // Buffer initial headers so stream.headers returns them.
12031235 if ( kind === 'initial' && this . #headers === undefined ) {
@@ -1213,17 +1245,7 @@ class QuicStream {
12131245 // If we received trailers from the peer, dispatch them.
12141246 if ( headers !== undefined ) {
12151247 if ( this . #ontrailers) {
1216- assert ( ArrayIsArray ( headers ) ) ;
1217- assert ( headers . length % 2 === 0 ) ;
1218- const block = { __proto__ : null } ;
1219- for ( let n = 0 ; n + 1 < headers . length ; n += 2 ) {
1220- if ( block [ headers [ n ] ] !== undefined ) {
1221- block [ headers [ n ] ] = [ block [ headers [ n ] ] , headers [ n + 1 ] ] ;
1222- } else {
1223- block [ headers [ n ] ] = headers [ n + 1 ] ;
1224- }
1225- }
1226- this . #ontrailers( block ) ;
1248+ this . #ontrailers( parseHeaderPairs ( headers ) ) ;
12271249 }
12281250 return ;
12291251 }
@@ -1248,7 +1270,7 @@ class QuicStream {
12481270 depth : options . depth == null ? null : options . depth - 1 ,
12491271 } ;
12501272
1251- return `Stream ${ inspect ( {
1273+ return `QuicStream ${ inspect ( {
12521274 __proto__ : null ,
12531275 id : this . id ,
12541276 direction : this . direction ,
@@ -1792,7 +1814,7 @@ class QuicSession {
17921814
17931815 debug ( 'gracefully closing the session' ) ;
17941816
1795- this . #handle? .gracefulClose ( ) ;
1817+ this . #handle. gracefulClose ( ) ;
17961818 if ( onSessionClosingChannel . hasSubscribers ) {
17971819 onSessionClosingChannel . publish ( {
17981820 session : this ,
@@ -1890,7 +1912,6 @@ class QuicSession {
18901912 this . #peerCertificate = undefined ;
18911913 this . #ephemeralKeyInfo = undefined ;
18921914
1893-
18941915 // Destroy the underlying C++ handle
18951916 this . #handle. destroy ( ) ;
18961917 this . #handle = undefined ;
@@ -1920,29 +1941,20 @@ class QuicSession {
19201941 debug ( 'finishing closing the session with an error' , errorType , code , reason ) ;
19211942 // Otherwise, errorType indicates the type of error that occurred, code indicates
19221943 // the specific error, and reason is an optional string describing the error.
1944+ // code !== 0n here (the early return above handles code === 0n)
19231945 switch ( errorType ) {
19241946 case 0 : /* Transport Error */
1925- if ( code === 0n ) {
1926- this . destroy ( ) ;
1927- } else {
1928- this . destroy ( new ERR_QUIC_TRANSPORT_ERROR ( code , reason ) ) ;
1929- }
1947+ this . destroy ( new ERR_QUIC_TRANSPORT_ERROR ( code , reason ) ) ;
19301948 break ;
19311949 case 1 : /* Application Error */
1932- if ( code === 0n ) {
1933- this . destroy ( ) ;
1934- } else {
1935- this . destroy ( new ERR_QUIC_APPLICATION_ERROR ( code , reason ) ) ;
1936- }
1950+ this . destroy ( new ERR_QUIC_APPLICATION_ERROR ( code , reason ) ) ;
19371951 break ;
19381952 case 2 : /* Version Negotiation Error */
19391953 this . destroy ( new ERR_QUIC_VERSION_NEGOTIATION_ERROR ( ) ) ;
19401954 break ;
1941- case 3 : /* Idle close */ {
1942- // An idle close is not really an error. We can just destroy.
1955+ case 3 : /* Idle close */
19431956 this . destroy ( ) ;
19441957 break ;
1945- }
19461958 }
19471959 }
19481960
@@ -2069,9 +2081,6 @@ class QuicSession {
20692081 if ( this . #onversionnegotiation) {
20702082 this . #onversionnegotiation( version , requestedVersions , supportedVersions ) ;
20712083 }
2072- // Version negotiation is always a fatal event - the session must be
2073- // destroyed regardless of whether the callback is set.
2074- this . destroy ( new ERR_QUIC_VERSION_NEGOTIATION_ERROR ( ) ) ;
20752084 if ( onSessionVersionNegotiationChannel . hasSubscribers ) {
20762085 onSessionVersionNegotiationChannel . publish ( {
20772086 __proto__ : null ,
@@ -2081,6 +2090,9 @@ class QuicSession {
20812090 session : this ,
20822091 } ) ;
20832092 }
2093+ // Version negotiation is always a fatal event - the session must be
2094+ // destroyed regardless of whether the callback is set.
2095+ this . destroy ( new ERR_QUIC_VERSION_NEGOTIATION_ERROR ( ) ) ;
20842096 }
20852097
20862098 /**
@@ -2515,19 +2527,6 @@ class QuicEndpoint {
25152527 validateObject ( options , 'options' ) ;
25162528 const {
25172529 sessionTicket,
2518- onstream,
2519- ondatagram,
2520- ondatagramstatus,
2521- onpathvalidation,
2522- onsessionticket,
2523- onversionnegotiation,
2524- onhandshake,
2525- onnewtoken,
2526- onorigin,
2527- // Stream-level callbacks.
2528- onheaders,
2529- ontrailers,
2530- onwanttrailers,
25312530 ...rest
25322531 } = options ;
25332532
@@ -2539,24 +2538,7 @@ class QuicEndpoint {
25392538 const session = this . #newSession( handle ) ;
25402539 // Set callbacks before any async work to avoid missing events
25412540 // that fire during or immediately after the handshake.
2542- if ( onstream ) session . onstream = onstream ;
2543- if ( ondatagram ) session . ondatagram = ondatagram ;
2544- if ( ondatagramstatus ) session . ondatagramstatus = ondatagramstatus ;
2545- if ( onpathvalidation ) session . onpathvalidation = onpathvalidation ;
2546- if ( onsessionticket ) session . onsessionticket = onsessionticket ;
2547- if ( onversionnegotiation ) session . onversionnegotiation = onversionnegotiation ;
2548- if ( onhandshake ) session . onhandshake = onhandshake ;
2549- if ( onnewtoken ) session . onnewtoken = onnewtoken ;
2550- if ( onorigin ) session . onorigin = onorigin ;
2551- // Store stream-level callbacks for application to client-created streams.
2552- if ( onheaders || ontrailers || onwanttrailers ) {
2553- session [ kStreamCallbacks ] = {
2554- __proto__ : null ,
2555- onheaders,
2556- ontrailers,
2557- onwanttrailers,
2558- } ;
2559- }
2541+ applyCallbacks ( session , options ) ;
25602542 return session ;
25612543 }
25622544
@@ -2735,26 +2717,7 @@ class QuicEndpoint {
27352717 // the onsession callback, to avoid missing events that fire
27362718 // during or immediately after the handshake.
27372719 if ( this . #sessionCallbacks) {
2738- const cbs = this . #sessionCallbacks;
2739- if ( cbs . onstream ) session . onstream = cbs . onstream ;
2740- if ( cbs . ondatagram ) session . ondatagram = cbs . ondatagram ;
2741- if ( cbs . ondatagramstatus ) session . ondatagramstatus = cbs . ondatagramstatus ;
2742- if ( cbs . onpathvalidation ) session . onpathvalidation = cbs . onpathvalidation ;
2743- if ( cbs . onsessionticket ) session . onsessionticket = cbs . onsessionticket ;
2744- if ( cbs . onversionnegotiation ) session . onversionnegotiation = cbs . onversionnegotiation ;
2745- if ( cbs . onhandshake ) session . onhandshake = cbs . onhandshake ;
2746- if ( cbs . onnewtoken ) session . onnewtoken = cbs . onnewtoken ;
2747- if ( cbs . onorigin ) session . onorigin = cbs . onorigin ;
2748- // Store stream-level callbacks on the session for application
2749- // to each new incoming stream.
2750- if ( cbs . onheaders || cbs . ontrailers || cbs . onwanttrailers ) {
2751- session [ kStreamCallbacks ] = {
2752- __proto__ : null ,
2753- onheaders : cbs . onheaders ,
2754- ontrailers : cbs . ontrailers ,
2755- onwanttrailers : cbs . onwanttrailers ,
2756- } ;
2757- }
2720+ applyCallbacks ( session , this . #sessionCallbacks) ;
27582721 }
27592722 if ( onEndpointServerSessionChannel . hasSubscribers ) {
27602723 onEndpointServerSessionChannel . publish ( {
0 commit comments