@fails-components/webtransport 1.0.10 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/lib/http2/browser/browser.d.ts.map +1 -1
  2. package/dist/lib/http2/browser/browserparser.d.ts +4 -3
  3. package/dist/lib/http2/browser/browserparser.d.ts.map +1 -1
  4. package/dist/lib/http2/node/capsuleparser.d.ts +3 -2
  5. package/dist/lib/http2/node/capsuleparser.d.ts.map +1 -1
  6. package/dist/lib/http2/node/client.d.ts.map +1 -1
  7. package/dist/lib/http2/node/server.d.ts +4 -4
  8. package/dist/lib/http2/node/server.d.ts.map +1 -1
  9. package/dist/lib/http2/node/websocketparser.d.ts +7 -6
  10. package/dist/lib/http2/node/websocketparser.d.ts.map +1 -1
  11. package/dist/lib/http2/parserbase.d.ts +16 -4
  12. package/dist/lib/http2/parserbase.d.ts.map +1 -1
  13. package/dist/lib/http2/parserbasehttp2.d.ts +9 -1
  14. package/dist/lib/http2/parserbasehttp2.d.ts.map +1 -1
  15. package/dist/lib/http2/session.d.ts.map +1 -1
  16. package/dist/lib/index.node.d.ts +1 -1
  17. package/dist/lib/index.node.d.ts.map +1 -1
  18. package/dist/lib/index.types.d.ts +1 -1
  19. package/dist/lib/index.types.d.ts.map +1 -1
  20. package/dist/lib/server.d.ts.map +1 -1
  21. package/dist/lib/session.d.ts +8 -4
  22. package/dist/lib/session.d.ts.map +1 -1
  23. package/dist/lib/types.d.ts +18 -6
  24. package/dist/lib/types.d.ts.map +1 -1
  25. package/dist/lib/webtransport.browser.d.ts.map +1 -1
  26. package/dist/lib/webtransport.node.d.ts +1 -0
  27. package/dist/lib/webtransport.node.d.ts.map +1 -1
  28. package/lib/http2/browser/browser.js +2 -1
  29. package/lib/http2/browser/browserparser.js +43 -7
  30. package/lib/http2/node/capsuleparser.js +41 -10
  31. package/lib/http2/node/client.js +28 -7
  32. package/lib/http2/node/server.js +117 -55
  33. package/lib/http2/node/websocketparser.js +42 -20
  34. package/lib/http2/parserbase.js +44 -7
  35. package/lib/http2/parserbasehttp2.js +15 -2
  36. package/lib/http2/session.js +1 -5
  37. package/lib/index.node.js +1 -1
  38. package/lib/index.types.js +1 -1
  39. package/lib/server.js +7 -4
  40. package/lib/session.js +5 -2
  41. package/lib/types.ts +19 -6
  42. package/lib/webtransport.browser.js +3 -2
  43. package/lib/webtransport.node.js +16 -5
  44. package/lib/webtransportbase.js +1 -1
  45. package/old_test/test.js +13 -1
  46. package/package.json +3 -2
  47. package/readme.md +4 -1
  48. package/test/bidirectional-streams.spec.js +31 -0
  49. package/test/datagrams.spec.js +12 -0
  50. package/test/fixtures/quiche.browser.js +2 -0
  51. package/test/fixtures/quiche.js +1 -0
  52. package/test/fixtures/read-stream.js +20 -7
  53. package/test/fixtures/reader-value.js +0 -5
  54. package/test/fixtures/server.js +70 -0
  55. package/test/session.spec.js +29 -3
  56. package/test/stream-limits.spec.js +12 -0
  57. package/test/unidirectional-streams.spec.js +12 -0
@@ -1,4 +1,9 @@
1
- import { ParserBaseHttp2, readVarInt, writeVarInt } from '../parserbasehttp2.js'
1
+ import {
2
+ ParserBaseHttp2,
3
+ readVarInt,
4
+ readUint32,
5
+ writeVarInt
6
+ } from '../parserbasehttp2.js'
2
7
  import { lengthVarInt } from '../parserbase.js'
3
8
 
4
9
  export class Http2CapsuleParser extends ParserBaseHttp2 {
@@ -9,7 +14,8 @@ export class Http2CapsuleParser extends ParserBaseHttp2 {
9
14
  stream,
10
15
  nativesession,
11
16
  isclient,
12
- initialStreamSendWindowOffset,
17
+ initialStreamSendWindowOffsetBidi,
18
+ initialStreamSendWindowOffsetUnidi,
13
19
  initialStreamReceiveWindowOffset,
14
20
  streamShouldAutoTuneReceiveWindow,
15
21
  streamReceiveWindowSizeLimit
@@ -18,7 +24,8 @@ export class Http2CapsuleParser extends ParserBaseHttp2 {
18
24
  stream,
19
25
  nativesession,
20
26
  isclient,
21
- initialStreamSendWindowOffset,
27
+ initialStreamSendWindowOffsetBidi,
28
+ initialStreamSendWindowOffsetUnidi,
22
29
  initialStreamReceiveWindowOffset,
23
30
  streamShouldAutoTuneReceiveWindow,
24
31
  streamReceiveWindowSizeLimit
@@ -211,6 +218,23 @@ export class Http2CapsuleParser extends ParserBaseHttp2 {
211
218
  case Http2CapsuleParser.WT_STREAMS_BLOCKED_BIDI:
212
219
  this.onStreamsBlockedBidi(readVarInt(bufferstate))
213
220
  break
221
+ case Http2CapsuleParser.CLOSE_WEBTRANSPORT_SESSION:
222
+ {
223
+ const code = readUint32(bufferstate) || 0
224
+ const decoder = new TextDecoder()
225
+ const reason = decoder.decode(
226
+ new Uint8Array(
227
+ bufferstate.buffer.buffer,
228
+ bufferstate.buffer.byteOffset + bufferstate.offset,
229
+ offsetend - bufferstate.offset
230
+ )
231
+ )
232
+ this.onCloseWebTransportSession({ code, reason })
233
+ }
234
+ break
235
+ case Http2CapsuleParser.DRAIN_WEBTRANSPORT_SESSION:
236
+ this.onDrain()
237
+ break
214
238
  case Http2CapsuleParser.DATAGRAM:
215
239
  this.session.jsobj.onDatagramReceived({
216
240
  datagram: new Uint8Array(
@@ -275,9 +299,9 @@ export class Http2CapsuleParser extends ParserBaseHttp2 {
275
299
  }
276
300
 
277
301
  /**
278
- * @param{{type: Number, headerVints: Array<Number>, payload: Uint8Array|undefined}} bs
302
+ * @param{{type: Number, headerVints: Array<Number>, payload: Uint8Array|undefined, end?: () => void}} bs
279
303
  */
280
- writeCapsule({ type, headerVints, payload }) {
304
+ writeCapsule({ type, headerVints, payload, end }) {
281
305
  let length = 0
282
306
  for (const ind in headerVints) length += lengthVarInt(headerVints[ind])
283
307
  let headlength = length
@@ -289,11 +313,18 @@ export class Http2CapsuleParser extends ParserBaseHttp2 {
289
313
  writeVarInt(bufferstate, length)
290
314
  for (const ind in headerVints) writeVarInt(bufferstate, headerVints[ind])
291
315
  if (!this.stream) return
292
- let blocked = !this.stream.write(cdata)
293
- if (payload) blocked = !this.stream.write(payload) || blocked
294
- // do something if blocked
295
- if (blocked) this.blocked = true
296
- return blocked
316
+ // note it might be illegal to split the write
317
+ if (!end) {
318
+ let blocked = !this.stream.write(cdata)
319
+ if (payload) blocked = !this.stream.write(payload) || blocked
320
+ // do something if blocked
321
+ if (blocked) this.blocked = true
322
+ return blocked
323
+ } else {
324
+ if (!payload) this.stream.end(cdata, end)
325
+ else this.stream.write(cdata)
326
+ if (payload) this.stream.end(payload, end)
327
+ }
297
328
  }
298
329
 
299
330
  /**
@@ -44,9 +44,10 @@ export class Http2WebTransportClient {
44
44
 
45
45
  createTransport() {
46
46
  /**
47
+ * @param {string} hostname
47
48
  * @param {import('node:tls').PeerCertificate} cert
48
49
  * */
49
- const webTransportVerifier = (cert) => {
50
+ const webTransportVerifier = (hostname, cert) => {
50
51
  if (
51
52
  this.serverCertificateHashes &&
52
53
  this.serverCertificateHashes.some((el) => {
@@ -91,7 +92,8 @@ export class Http2WebTransportClient {
91
92
  0x2b63: this.initialStreamFlowControlWindow, // SETTINGS_WEBTRANSPORT_INITIAL_MAX_STREAM_DATA_BIDI
92
93
  0x2b64: this.initialUnidirectionalStreams, // SETTINGS_WEBTRANSPORT_INITIAL_MAX_STREAMS_UNI
93
94
  0x2b65: this.initialBidirectionalStreams // SETTINGS_WEBTRANSPORT_INITIAL_MAX_STREAMS_BIDI
94
- }
95
+ },
96
+ remoteCustomSettings: [0x2b60, 0x2b61, 0x2b62, 0x2b63, 0x2b64, 0x2b65]
95
97
  },
96
98
  localPort: this.localPort,
97
99
  // TODO: REMOVE BEFORE RELEASE; UNSAFE SETTING
@@ -141,7 +143,9 @@ export class Http2WebTransportClient {
141
143
  if (!oursocket.authorized) {
142
144
  // ok last hope we have hashes
143
145
  if (this.serverCertificateHashes) {
144
- if (!webTransportVerifier(oursocket.getPeerCertificate())) {
146
+ if (
147
+ !webTransportVerifier(this.hostname, oursocket.getPeerCertificate())
148
+ ) {
145
149
  this.clientInt?.destroy(
146
150
  undefined,
147
151
  http2constants.NGHTTP2_REFUSED_STREAM
@@ -210,6 +214,14 @@ export class Http2WebTransportClient {
210
214
  authority: this.hostname,
211
215
  origin: this.hostname
212
216
  })
217
+ const {
218
+ 0x2b65: remoteBidirectionalStreams = undefined,
219
+ 0x2b64: remoteUnidirectionalStreams = undefined,
220
+ 0x2b63: remoteBidirectionalStreamFlowControlWindow = undefined,
221
+ 0x2b62: remoteUnidirectionalStreamFlowControlWindow = undefined,
222
+ 0x2b61: remoteSessionFlowControlWindow = undefined
223
+ // @ts-ignore
224
+ } = this.clientInt.remoteSettings?.customSettings || {}
213
225
 
214
226
  const retObj = {
215
227
  header: stream.sentHeaders,
@@ -221,18 +233,27 @@ export class Http2WebTransportClient {
221
233
  stream,
222
234
  nativesession,
223
235
  isclient: true,
224
- initialStreamSendWindowOffset: this.initialStreamFlowControlWindow, // TODO, once supported by node, use initial settings
236
+ initialStreamSendWindowOffsetBidi:
237
+ remoteBidirectionalStreamFlowControlWindow ||
238
+ this.initialStreamFlowControlWindow,
239
+ initialStreamSendWindowOffsetUnidi:
240
+ remoteUnidirectionalStreamFlowControlWindow ||
241
+ this.initialStreamFlowControlWindow,
225
242
  initialStreamReceiveWindowOffset:
226
243
  this.initialStreamFlowControlWindow,
227
244
  streamShouldAutoTuneReceiveWindow:
228
245
  this.streamShouldAutoTuneReceiveWindow,
229
246
  streamReceiveWindowSizeLimit: this.streamFlowControlWindowSizeLimit
230
247
  }),
231
- initialBidirectionalSendStreams: this.initialBidirectionalStreams, // TODO, once supported by node, use initial settings
248
+ initialBidirectionalSendStreams:
249
+ remoteBidirectionalStreams || this.initialBidirectionalStreams,
232
250
  initialBidirectionalReceiveStreams: this.initialBidirectionalStreams,
233
- initialUnidirectionalSendStreams: this.initialUnidirectionalStreams, // TODO, once supported by node, use initial settings
251
+ initialUnidirectionalSendStreams:
252
+ remoteUnidirectionalStreams || this.initialUnidirectionalStreams,
234
253
  initialUnidirectionalReceiveStreams: this.initialUnidirectionalStreams,
235
- sendWindowOffset: this.sessionFlowControlWindowSizeLimit,
254
+ sendWindowOffset:
255
+ remoteSessionFlowControlWindow ||
256
+ this.sessionFlowControlWindowSizeLimit,
236
257
  receiveWindowOffset: this.sessionFlowControlWindowSizeLimit,
237
258
  shouldAutoTuneReceiveWindow: this.sessionShouldAutoTuneReceiveWindow,
238
259
  receiveWindowSizeLimit: this.sessionFlowControlWindowSizeLimit
@@ -65,7 +65,8 @@ export class Http2WebTransportServer {
65
65
  0x2b63: this.initialStreamFlowControlWindow, // SETTINGS_WEBTRANSPORT_INITIAL_MAX_STREAM_DATA_BIDI
66
66
  0x2b64: this.initialUnidirectionalStreams, // SETTINGS_WEBTRANSPORT_INITIAL_MAX_STREAMS_UNI
67
67
  0x2b65: this.initialBidirectionalStreams // SETTINGS_WEBTRANSPORT_INITIAL_MAX_STREAMS_BIDI
68
- }
68
+ },
69
+ remoteCustomSettings: [0x2b60, 0x2b61, 0x2b62, 0x2b63, 0x2b64, 0x2b65]
69
70
  }
70
71
  })
71
72
 
@@ -126,20 +127,31 @@ export class Http2WebTransportServer {
126
127
  'upgrade',
127
128
  (request, stream /* actually a socket */, head) => {
128
129
  let path = request.url
129
- const header = request.headers
130
130
  if (!path) {
131
131
  stream.destroy()
132
132
  return
133
133
  }
134
+ while (path.length > 1 && path[0] === '/' && path[1] === '/') {
135
+ path = path?.slice(1)
136
+ }
137
+ const header = { ...request.headers, ':path': path }
134
138
  const websocketProt = this.checkProtocolHeader(header)
135
139
  if (!websocketProt) {
136
140
  stream.destroy()
137
141
  return
138
142
  }
139
- while (path.length > 1 && path[0] === '/' && path[1] === '/') {
140
- path = path?.slice(1)
141
- }
142
- if (this.paths[path]) {
143
+ if (this.hasrequesthandler) {
144
+ stream.head = head
145
+
146
+ const retObj = {
147
+ header,
148
+ session: stream,
149
+ protocol: 'websocketoverhttp1',
150
+ head,
151
+ transportPrivate: { websocketProt }
152
+ }
153
+ this.jsobj.onSessionRequest(retObj)
154
+ } else if (this.paths[path]) {
143
155
  this.sendHttp1Headers({ stream, header, protocol: websocketProt })
144
156
  .then(() => {
145
157
  const retObj = {
@@ -153,7 +165,8 @@ export class Http2WebTransportServer {
153
165
  stream,
154
166
  nativesession,
155
167
  isclient: false,
156
- initialStreamSendWindowOffset: 0,
168
+ initialStreamSendWindowOffsetBidi: 0,
169
+ initialStreamSendWindowOffsetUnidi: 0,
157
170
  initialStreamReceiveWindowOffset:
158
171
  this.initialStreamFlowControlWindow,
159
172
  streamShouldAutoTuneReceiveWindow:
@@ -187,16 +200,6 @@ export class Http2WebTransportServer {
187
200
  log('Problem sendHttp1Header', error)
188
201
  stream.destroy()
189
202
  })
190
- } else if (this.hasrequesthandler) {
191
- stream.head = head
192
-
193
- const retObj = {
194
- header,
195
- session: stream,
196
- protocol: 'websocketoverhttp1',
197
- head
198
- }
199
- this.jsobj.onSessionRequest(retObj)
200
203
  } else {
201
204
  stream.destroy()
202
205
  }
@@ -243,7 +246,24 @@ export class Http2WebTransportServer {
243
246
  while (path.length > 1 && path[0] === '/' && path[1] === '/') {
244
247
  path = path?.slice(1)
245
248
  }
246
- if (this.paths[path]) {
249
+ header[':path'] = path // also adapt it for the middleware
250
+ if (this.hasrequesthandler) {
251
+ const retObj = {
252
+ header,
253
+ session: stream,
254
+ protocol: websocketProt ? 'websocket' : 'capsule',
255
+ transportPrivate: { websocketProt }
256
+ }
257
+ this.jsobj.onSessionRequest(retObj)
258
+ } else if (this.paths[path]) {
259
+ const {
260
+ 0x2b65: remoteBidirectionalStreams = undefined,
261
+ 0x2b64: remoteUnidirectionalStreams = undefined,
262
+ 0x2b63: remoteBidirectionalStreamFlowControlWindow = undefined,
263
+ 0x2b62: remoteUnidirectionalStreamFlowControlWindow = undefined,
264
+ 0x2b61: remoteSessionFlowControlWindow = undefined
265
+ // @ts-ignore
266
+ } = stream?.session?.remoteSettings?.customSettings || {}
247
267
  const retObj = {
248
268
  session: new Http2WebTransportSession({
249
269
  stream,
@@ -256,8 +276,12 @@ export class Http2WebTransportServer {
256
276
  stream,
257
277
  nativesession,
258
278
  isclient: false,
259
- initialStreamSendWindowOffset:
260
- this.initialStreamFlowControlWindow, // TODO, once supported by node, use initial settings
279
+ initialStreamSendWindowOffsetBidi:
280
+ remoteBidirectionalStreamFlowControlWindow ||
281
+ this.initialStreamFlowControlWindow,
282
+ initialStreamSendWindowOffsetUnidi:
283
+ remoteUnidirectionalStreamFlowControlWindow ||
284
+ this.initialStreamFlowControlWindow,
261
285
  initialStreamReceiveWindowOffset:
262
286
  this.initialStreamFlowControlWindow,
263
287
  streamShouldAutoTuneReceiveWindow:
@@ -270,7 +294,8 @@ export class Http2WebTransportServer {
270
294
  stream,
271
295
  nativesession,
272
296
  isclient: false,
273
- initialStreamSendWindowOffset: 0,
297
+ initialStreamSendWindowOffsetBidi: 0,
298
+ initialStreamSendWindowOffsetUnidi: 0,
274
299
  initialStreamReceiveWindowOffset:
275
300
  this.initialStreamFlowControlWindow,
276
301
  streamShouldAutoTuneReceiveWindow:
@@ -280,18 +305,20 @@ export class Http2WebTransportServer {
280
305
  }))
281
306
  }
282
307
  },
283
- initialBidirectionalSendStreams: websocketProt // TODO, once supported by node, use initial settings
308
+ initialBidirectionalSendStreams: websocketProt
284
309
  ? 0
285
- : this.initialBidirectionalStreams,
310
+ : remoteBidirectionalStreams || this.initialBidirectionalStreams,
286
311
  initialBidirectionalReceiveStreams:
287
312
  this.initialBidirectionalStreams,
288
313
  initialUnidirectionalSendStreams: websocketProt
289
314
  ? 0
290
- : this.initialUnidirectionalStreams, // TODO, once supported by node, use initial settings
315
+ : remoteUnidirectionalStreams ||
316
+ this.initialUnidirectionalStreams,
291
317
  initialUnidirectionalReceiveStreams:
292
318
  this.initialUnidirectionalStreams,
293
319
  sendWindowOffset: !websocketProt
294
- ? this.sessionFlowControlWindowSizeLimit
320
+ ? remoteSessionFlowControlWindow ||
321
+ this.sessionFlowControlWindowSizeLimit
295
322
  : 0, // TODO, once supported by node, use initial settings
296
323
  receiveWindowOffset: this.sessionFlowControlWindowSizeLimit,
297
324
  shouldAutoTuneReceiveWindow:
@@ -310,19 +337,6 @@ export class Http2WebTransportServer {
310
337
  if (websocketProt) resp['sec-websocket-protocol'] = websocketProt
311
338
  stream.respond(resp)
312
339
  this.jsobj.onHttpWTSessionVisitor(retObj)
313
- } else if (this.hasrequesthandler) {
314
- const retObj = {
315
- header,
316
- session: stream,
317
- protocol: websocketProt ? 'websocket' : 'capsule'
318
- }
319
- const resp = {
320
- ':status': '200'
321
- }
322
- // @ts-ignore
323
- if (websocketProt) resp['sec-websocket-protocol'] = websocketProt
324
- stream.respond(resp)
325
- this.jsobj.onSessionRequest(retObj)
326
340
  } else {
327
341
  stream.respond({
328
342
  ':status': '404'
@@ -364,8 +378,8 @@ export class Http2WebTransportServer {
364
378
  }
365
379
 
366
380
  /**
367
- * @param {string} cert
368
- * @param {string} privKey
381
+ * @param {string|string[]} cert
382
+ * @param {string|string[]} privKey
369
383
  * @param {boolean} http2only
370
384
  * */
371
385
  updateCert(cert, privKey, http2only) {
@@ -420,17 +434,35 @@ export class Http2WebTransportServer {
420
434
  /**
421
435
  * @param {import('../../types.js').NativeFinishSessionRequest} args
422
436
  */
423
- finishSessionRequest({ header, session: stream, status, protocol, head }) {
437
+ finishSessionRequest({
438
+ header,
439
+ userData,
440
+ session: stream,
441
+ status,
442
+ protocol,
443
+ head,
444
+ path,
445
+ transportPrivate
446
+ }) {
424
447
  if (status !== 200) {
425
448
  if (protocol === 'websocketoverhttp1') {
426
449
  stream.destroy()
427
450
  } else {
428
- stream.close(constants.HTTP_STATUS_NOT_FOUND)
451
+ stream.respond({
452
+ ':status': status.toString()
453
+ })
454
+ stream.end()
455
+ stream.close()
429
456
  }
430
457
  } else {
431
458
  if (protocol === 'websocketoverhttp1') {
432
- // @ts-ignore
433
- this.sendHttp1Headers({ stream, header })
459
+ this.sendHttp1Headers({
460
+ // @ts-ignore
461
+ stream,
462
+ header,
463
+ // @ts-ignore
464
+ protocol: transportPrivate.websocketProt
465
+ })
434
466
  .then(() => {
435
467
  const retObj = {
436
468
  session: new Http2WebTransportSession({
@@ -443,7 +475,8 @@ export class Http2WebTransportServer {
443
475
  stream,
444
476
  nativesession,
445
477
  isclient: false,
446
- initialStreamSendWindowOffset: 0,
478
+ initialStreamSendWindowOffsetBidi: 0,
479
+ initialStreamSendWindowOffsetUnidi: 0,
447
480
  initialStreamReceiveWindowOffset:
448
481
  this.initialStreamFlowControlWindow,
449
482
  streamShouldAutoTuneReceiveWindow:
@@ -466,8 +499,9 @@ export class Http2WebTransportServer {
466
499
  this.sessionShouldAutoTuneReceiveWindow,
467
500
  receiveWindowSizeLimit: this.sessionFlowControlWindowSizeLimit
468
501
  }),
469
- path: header[':path'],
470
- header
502
+ path,
503
+ header,
504
+ userData
471
505
  }
472
506
  // @ts-ignore
473
507
  this.jsobj.onHttpWTSessionVisitor(retObj)
@@ -476,6 +510,22 @@ export class Http2WebTransportServer {
476
510
  log('sendHttp1Headers error', error)
477
511
  })
478
512
  } else {
513
+ const resp = {
514
+ ':status': '200'
515
+ }
516
+ // @ts-ignore
517
+ if (transportPrivate?.websocketProt)
518
+ // @ts-ignore
519
+ resp['sec-websocket-protocol'] = transportPrivate.websocketProt
520
+ stream.respond(resp)
521
+ const {
522
+ 0x2b65: remoteBidirectionalStreams = undefined,
523
+ 0x2b64: remoteUnidirectionalStreams = undefined,
524
+ 0x2b63: remoteBidirectionalStreamFlowControlWindow = undefined,
525
+ 0x2b62: remoteUnidirectionalStreamFlowControlWindow = undefined,
526
+ 0x2b61: remoteSessionFlowControlWindow = undefined
527
+ // @ts-ignore
528
+ } = stream?.session?.remoteSettings?.customSettings || {}
479
529
  const retObj = {
480
530
  session: new Http2WebTransportSession({
481
531
  stream,
@@ -488,8 +538,12 @@ export class Http2WebTransportServer {
488
538
  stream,
489
539
  nativesession,
490
540
  isclient: false,
491
- initialStreamSendWindowOffset:
492
- this.initialStreamFlowControlWindow, // TODO, once supported by node, use initial settings
541
+ initialStreamSendWindowOffsetBidi:
542
+ remoteBidirectionalStreamFlowControlWindow ||
543
+ this.initialStreamFlowControlWindow,
544
+ initialStreamSendWindowOffsetUnidi:
545
+ remoteUnidirectionalStreamFlowControlWindow ||
546
+ this.initialStreamFlowControlWindow,
493
547
  initialStreamReceiveWindowOffset:
494
548
  this.initialStreamFlowControlWindow,
495
549
  streamShouldAutoTuneReceiveWindow:
@@ -502,7 +556,8 @@ export class Http2WebTransportServer {
502
556
  stream,
503
557
  nativesession,
504
558
  isclient: false,
505
- initialStreamSendWindowOffset: 0,
559
+ initialStreamSendWindowOffsetBidi: 0,
560
+ initialStreamSendWindowOffsetUnidi: 0,
506
561
  initialStreamReceiveWindowOffset:
507
562
  this.initialStreamFlowControlWindow,
508
563
  streamShouldAutoTuneReceiveWindow:
@@ -513,24 +568,31 @@ export class Http2WebTransportServer {
513
568
  }
514
569
  },
515
570
  initialBidirectionalSendStreams:
516
- protocol !== 'websocket' ? this.initialBidirectionalStreams : 0, // TODO, once supported by node, use initial settings
571
+ protocol !== 'websocket'
572
+ ? remoteBidirectionalStreams || this.initialBidirectionalStreams
573
+ : 0, // TODO, once supported by node, use initial settings
517
574
  initialBidirectionalReceiveStreams:
518
575
  this.initialBidirectionalStreams,
519
576
  initialUnidirectionalSendStreams:
520
- protocol !== 'websocket' ? this.initialUnidirectionalStreams : 0, // TODO, once supported by node, use initial settings
577
+ protocol !== 'websocket'
578
+ ? remoteUnidirectionalStreams ||
579
+ this.initialUnidirectionalStreams
580
+ : 0, // TODO, once supported by node, use initial settings
521
581
  initialUnidirectionalReceiveStreams:
522
582
  this.initialUnidirectionalStreams,
523
583
  sendWindowOffset:
524
584
  protocol !== 'websocket'
525
- ? this.sessionFlowControlWindowSizeLimit
585
+ ? remoteSessionFlowControlWindow ||
586
+ this.sessionFlowControlWindowSizeLimit
526
587
  : 0, // TODO, once supported by node, use initial settings
527
588
  receiveWindowOffset: this.sessionFlowControlWindowSizeLimit,
528
589
  shouldAutoTuneReceiveWindow:
529
590
  this.sessionShouldAutoTuneReceiveWindow,
530
591
  receiveWindowSizeLimit: this.sessionFlowControlWindowSizeLimit
531
592
  }),
532
- path: header[':path'],
533
- header
593
+ path,
594
+ header,
595
+ userData
534
596
  }
535
597
  // @ts-ignore
536
598
  this.jsobj.onHttpWTSessionVisitor(retObj)
@@ -1,6 +1,11 @@
1
1
  import { randomBytes } from 'node:crypto'
2
2
  import { ParserBase, lengthVarInt } from '../parserbase.js'
3
- import { ParserBaseHttp2, readVarInt, writeVarInt } from '../parserbasehttp2.js'
3
+ import {
4
+ ParserBaseHttp2,
5
+ readVarInt,
6
+ writeVarInt,
7
+ readUint32
8
+ } from '../parserbasehttp2.js'
4
9
  import { logger } from '../../utils.js'
5
10
 
6
11
  const log = logger(`webtransport:http2:node:websocketparser(${process?.pid})`)
@@ -174,7 +179,8 @@ export class WebSocketParser extends ParserBaseHttp2 {
174
179
  stream,
175
180
  nativesession,
176
181
  isclient,
177
- initialStreamSendWindowOffset,
182
+ initialStreamSendWindowOffsetUnidi,
183
+ initialStreamSendWindowOffsetBidi,
178
184
  initialStreamReceiveWindowOffset,
179
185
  streamShouldAutoTuneReceiveWindow,
180
186
  streamReceiveWindowSizeLimit
@@ -183,7 +189,8 @@ export class WebSocketParser extends ParserBaseHttp2 {
183
189
  stream,
184
190
  nativesession,
185
191
  isclient,
186
- initialStreamSendWindowOffset,
192
+ initialStreamSendWindowOffsetUnidi,
193
+ initialStreamSendWindowOffsetBidi,
187
194
  initialStreamReceiveWindowOffset,
188
195
  streamShouldAutoTuneReceiveWindow,
189
196
  streamReceiveWindowSizeLimit
@@ -598,6 +605,23 @@ export class WebSocketParser extends ParserBaseHttp2 {
598
605
  case ParserBase.WT_STREAMS_BLOCKED_BIDI:
599
606
  this.onStreamsBlockedBidi(readVarInt(bufferstate))
600
607
  break
608
+ case ParserBase.CLOSE_WEBTRANSPORT_SESSION:
609
+ {
610
+ const code = readUint32(bufferstate) || 0
611
+ const decoder = new TextDecoder()
612
+ const reason = decoder.decode(
613
+ new Uint8Array(
614
+ bufferstate.buffer.buffer,
615
+ bufferstate.buffer.byteOffset + bufferstate.offset,
616
+ offsetend - bufferstate.offset
617
+ )
618
+ )
619
+ this.onCloseWebTransportSession({ code, reason })
620
+ }
621
+ break
622
+ case ParserBase.DRAIN_WEBTRANSPORT_SESSION:
623
+ this.onDrain()
624
+ break
601
625
  case ParserBase.DATAGRAM:
602
626
  if (wbufferstate) {
603
627
  this.session.jsobj.onDatagramReceived({
@@ -688,19 +712,12 @@ export class WebSocketParser extends ParserBaseHttp2 {
688
712
  * @param{{code: Number, reason: string}}arg
689
713
  */
690
714
  sendClose({ code, reason }) {
691
- const reasBuf = Buffer.from(
692
- (code || 0).toString() + ':' + (reason || ''),
693
- 'utf8'
694
- )
695
- const wbuf = Buffer.allocUnsafe(2 + reasBuf.byteLength)
696
- const errorcode = code === 0 ? 1000 : 1003
697
- wbuf.writeUint16BE(errorcode, 0)
698
- reasBuf.copy(wbuf, 2)
699
- this.sendCloseInt(wbuf)
715
+ super.sendClose({ code, reason })
716
+ this.sendCloseInt()
700
717
  }
701
718
 
702
719
  /**
703
- * @param {Uint8Array} payload
720
+ * @param {Uint8Array} [payload]
704
721
  */
705
722
  sendCloseInt(payload) {
706
723
  this.writeWSFrame({ opcode: WebSocketParser.WS_CLOSE, payload })
@@ -715,9 +732,9 @@ export class WebSocketParser extends ParserBaseHttp2 {
715
732
  }
716
733
 
717
734
  /**
718
- * @param{{type: Number, headerVints: Array<Number|bigint>, payload: Uint8Array|undefined}} bs
735
+ * @param{{type: Number, headerVints: Array<Number|bigint>, payload: Uint8Array|undefined, end?: () => void}} bs
719
736
  */
720
- writeCapsule({ type, headerVints, payload }) {
737
+ writeCapsule({ type, headerVints, payload, end }) {
721
738
  let plength = 0
722
739
  for (const ind in headerVints) plength += lengthVarInt(headerVints[ind])
723
740
  plength += lengthVarInt(type)
@@ -762,8 +779,13 @@ export class WebSocketParser extends ParserBaseHttp2 {
762
779
  payload.byteLength
763
780
  )
764
781
  }
765
- let blocked = !this.stream.write(cdata)
766
- if (payload) blocked = !this.stream.write(payload) || blocked
782
+ let blocked = false
783
+ if (payload) {
784
+ blocked = !this.stream.write(cdata)
785
+ blocked = !this.stream.write(payload, end) || blocked
786
+ } else {
787
+ blocked = !this.stream.write(cdata, end)
788
+ }
767
789
  // do something if blocked
768
790
  if (blocked) this.blocked = true
769
791
  return blocked
@@ -802,10 +824,10 @@ export class WebSocketParser extends ParserBaseHttp2 {
802
824
  }
803
825
 
804
826
  /**
805
- * @param {{ opcode: number; payload: Uint8Array; }} args
827
+ * @param {{ opcode: number; payload: Uint8Array|undefined; }} args
806
828
  */
807
829
  writeWSFrame({ opcode, payload }) {
808
- const plength = payload.byteLength
830
+ const plength = payload?.byteLength || 0
809
831
  let headlength = 2
810
832
  let mask = 0
811
833
  if (this.isclient) {
@@ -825,7 +847,7 @@ export class WebSocketParser extends ParserBaseHttp2 {
825
847
  throw new Error('Headlength does not match pos')
826
848
 
827
849
  let blocked = !this.stream.write(cdata)
828
- if (maskstate)
850
+ if (maskstate && payload)
829
851
  applyMask(
830
852
  maskstate,
831
853
  Buffer.from(payload.buffer, payload.byteOffset, payload.byteLength),