@fails-components/webtransport 1.0.8 → 1.0.9

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 (154) hide show
  1. package/dist/lib/client.d.ts.map +1 -0
  2. package/dist/{main/lib → lib}/dom.d.ts +10 -2
  3. package/dist/lib/dom.d.ts.map +1 -0
  4. package/dist/lib/error.d.ts.map +1 -0
  5. package/dist/{main/lib → lib}/features.d.ts.map +1 -1
  6. package/dist/{main/lib → lib}/http2/browser/browser.d.ts +2 -0
  7. package/dist/lib/http2/browser/browser.d.ts.map +1 -0
  8. package/dist/lib/http2/browser/browserparser.d.ts.map +1 -0
  9. package/dist/{main/lib → lib}/http2/flowcontroller.d.ts +1 -1
  10. package/dist/lib/http2/flowcontroller.d.ts.map +1 -0
  11. package/dist/{main/lib → lib}/http2/node/capsuleparser.d.ts +1 -1
  12. package/dist/lib/http2/node/capsuleparser.d.ts.map +1 -0
  13. package/dist/{main/lib → lib}/http2/node/client.d.ts +2 -0
  14. package/dist/lib/http2/node/client.d.ts.map +1 -0
  15. package/dist/{main/test → lib/http2/node}/index.d.ts.map +1 -1
  16. package/dist/{main/lib → lib}/http2/node/server.d.ts +3 -0
  17. package/dist/lib/http2/node/server.d.ts.map +1 -0
  18. package/dist/{main/lib → lib}/http2/node/websocketparser.d.ts +4 -2
  19. package/dist/lib/http2/node/websocketparser.d.ts.map +1 -0
  20. package/dist/{main/lib → lib}/http2/parserbase.d.ts +26 -9
  21. package/dist/lib/http2/parserbase.d.ts.map +1 -0
  22. package/dist/lib/http2/parserbasehttp2.d.ts.map +1 -0
  23. package/dist/{main/lib → lib}/http2/session.d.ts +43 -6
  24. package/dist/lib/http2/session.d.ts.map +1 -0
  25. package/dist/{main/lib → lib}/http2/stream.d.ts +29 -7
  26. package/dist/lib/http2/stream.d.ts.map +1 -0
  27. package/dist/lib/http2/streamidmanager.d.ts +72 -0
  28. package/dist/lib/http2/streamidmanager.d.ts.map +1 -0
  29. package/dist/lib/http2/websocketcommon.d.ts.map +1 -0
  30. package/dist/lib/index.browser.d.ts.map +1 -0
  31. package/dist/lib/index.node.d.ts.map +1 -0
  32. package/dist/lib/server.d.ts.map +1 -0
  33. package/dist/{main/lib → lib}/session.d.ts +10 -3
  34. package/dist/lib/session.d.ts.map +1 -0
  35. package/dist/lib/stream.d.ts.map +1 -0
  36. package/dist/{main/lib → lib}/types.d.ts +26 -4
  37. package/dist/lib/types.d.ts.map +1 -0
  38. package/dist/lib/utils.d.ts.map +1 -0
  39. package/dist/lib/webstreams.browser.d.ts.map +1 -0
  40. package/dist/lib/webstreams.d.ts.map +1 -0
  41. package/dist/{main/lib → lib}/webtransport.browser.d.ts +12 -2
  42. package/dist/lib/webtransport.browser.d.ts.map +1 -0
  43. package/dist/lib/webtransport.node.d.ts.map +1 -0
  44. package/dist/{main/lib → lib}/webtransportbase.d.ts +14 -2
  45. package/dist/lib/webtransportbase.d.ts.map +1 -0
  46. package/lib/dom.ts +12 -2
  47. package/lib/http2/browser/browser.js +12 -3
  48. package/lib/http2/browser/browserparser.js +36 -36
  49. package/lib/http2/flowcontroller.js +2 -2
  50. package/lib/http2/node/capsuleparser.js +58 -51
  51. package/lib/http2/node/client.js +12 -3
  52. package/lib/http2/node/server.js +53 -16
  53. package/lib/http2/node/websocketparser.js +69 -59
  54. package/lib/http2/parserbase.js +87 -12
  55. package/lib/http2/session.js +109 -34
  56. package/lib/http2/stream.js +65 -10
  57. package/lib/http2/streamidmanager.js +316 -0
  58. package/lib/session.js +31 -5
  59. package/lib/types.ts +20 -4
  60. package/lib/webtransport.browser.js +14 -4
  61. package/lib/webtransportbase.js +14 -4
  62. package/package.json +1 -1
  63. package/readme.md +4 -3
  64. package/test/fixtures/certificate.js +1 -2
  65. package/test/fixtures/server.js +112 -7
  66. package/test/stream-limits.spec.js +247 -0
  67. package/tsconfig.json +2 -7
  68. package/dist/main/lib/client.d.ts.map +0 -1
  69. package/dist/main/lib/dom.d.ts.map +0 -1
  70. package/dist/main/lib/error.d.ts.map +0 -1
  71. package/dist/main/lib/http2/browser/browser.d.ts.map +0 -1
  72. package/dist/main/lib/http2/browser/browserparser.d.ts.map +0 -1
  73. package/dist/main/lib/http2/flowcontroller.d.ts.map +0 -1
  74. package/dist/main/lib/http2/node/capsuleparser.d.ts.map +0 -1
  75. package/dist/main/lib/http2/node/client.d.ts.map +0 -1
  76. package/dist/main/lib/http2/node/index.d.ts.map +0 -1
  77. package/dist/main/lib/http2/node/server.d.ts.map +0 -1
  78. package/dist/main/lib/http2/node/websocketparser.d.ts.map +0 -1
  79. package/dist/main/lib/http2/parserbase.d.ts.map +0 -1
  80. package/dist/main/lib/http2/parserbasehttp2.d.ts.map +0 -1
  81. package/dist/main/lib/http2/session.d.ts.map +0 -1
  82. package/dist/main/lib/http2/stream.d.ts.map +0 -1
  83. package/dist/main/lib/http2/websocketcommon.d.ts.map +0 -1
  84. package/dist/main/lib/index.browser.d.ts.map +0 -1
  85. package/dist/main/lib/index.node.d.ts.map +0 -1
  86. package/dist/main/lib/server.d.ts.map +0 -1
  87. package/dist/main/lib/session.d.ts.map +0 -1
  88. package/dist/main/lib/stream.d.ts.map +0 -1
  89. package/dist/main/lib/types.d.ts.map +0 -1
  90. package/dist/main/lib/utils.d.ts.map +0 -1
  91. package/dist/main/lib/webstreams.browser.d.ts.map +0 -1
  92. package/dist/main/lib/webstreams.d.ts.map +0 -1
  93. package/dist/main/lib/webtransport.browser.d.ts.map +0 -1
  94. package/dist/main/lib/webtransport.node.d.ts.map +0 -1
  95. package/dist/main/lib/webtransportbase.d.ts.map +0 -1
  96. package/dist/main/test/bidirectional-streams.spec.d.ts +0 -5
  97. package/dist/main/test/bidirectional-streams.spec.d.ts.map +0 -1
  98. package/dist/main/test/datagrams.spec.d.ts +0 -5
  99. package/dist/main/test/datagrams.spec.d.ts.map +0 -1
  100. package/dist/main/test/fixtures/certificate.d.ts +0 -21
  101. package/dist/main/test/fixtures/certificate.d.ts.map +0 -1
  102. package/dist/main/test/fixtures/chai.d.ts +0 -2
  103. package/dist/main/test/fixtures/chai.d.ts.map +0 -1
  104. package/dist/main/test/fixtures/known-bytes.d.ts +0 -8
  105. package/dist/main/test/fixtures/known-bytes.d.ts.map +0 -1
  106. package/dist/main/test/fixtures/p-timeout.d.ts +0 -8
  107. package/dist/main/test/fixtures/p-timeout.d.ts.map +0 -1
  108. package/dist/main/test/fixtures/read-cert-hash.d.ts +0 -6
  109. package/dist/main/test/fixtures/read-cert-hash.d.ts.map +0 -1
  110. package/dist/main/test/fixtures/read-stream.d.ts +0 -18
  111. package/dist/main/test/fixtures/read-stream.d.ts.map +0 -1
  112. package/dist/main/test/fixtures/reader-value.d.ts +0 -21
  113. package/dist/main/test/fixtures/reader-value.d.ts.map +0 -1
  114. package/dist/main/test/fixtures/server.d.ts +0 -5
  115. package/dist/main/test/fixtures/server.d.ts.map +0 -1
  116. package/dist/main/test/fixtures/webtransport.browser.d.ts +0 -4
  117. package/dist/main/test/fixtures/webtransport.browser.d.ts.map +0 -1
  118. package/dist/main/test/fixtures/webtransport.d.ts +0 -3
  119. package/dist/main/test/fixtures/webtransport.d.ts.map +0 -1
  120. package/dist/main/test/fixtures/write-stream.d.ts +0 -10
  121. package/dist/main/test/fixtures/write-stream.d.ts.map +0 -1
  122. package/dist/main/test/index.d.ts +0 -2
  123. package/dist/main/test/session.spec.d.ts +0 -5
  124. package/dist/main/test/session.spec.d.ts.map +0 -1
  125. package/dist/main/test/unidirectional-streams.spec.d.ts +0 -2
  126. package/dist/main/test/unidirectional-streams.spec.d.ts.map +0 -1
  127. package/dist/main/tsconfig.tsbuildinfo +0 -1
  128. package/dist/transports/http3-quiche/lib/clientsocket.d.ts +0 -19
  129. package/dist/transports/http3-quiche/lib/clientsocket.d.ts.map +0 -1
  130. package/dist/transports/http3-quiche/lib/index.d.ts +0 -11
  131. package/dist/transports/http3-quiche/lib/index.d.ts.map +0 -1
  132. package/dist/transports/http3-quiche/lib/serversocket.d.ts +0 -12
  133. package/dist/transports/http3-quiche/lib/serversocket.d.ts.map +0 -1
  134. package/dist/transports/http3-quiche/lib/socket.d.ts +0 -19
  135. package/dist/transports/http3-quiche/lib/socket.d.ts.map +0 -1
  136. package/dist/transports/http3-quiche/lib/types.d.ts +0 -21
  137. package/dist/transports/http3-quiche/lib/types.d.ts.map +0 -1
  138. package/dist/transports/http3-quiche/lib/utils.d.ts +0 -6
  139. package/dist/transports/http3-quiche/lib/utils.d.ts.map +0 -1
  140. /package/dist/{main/lib → lib}/client.d.ts +0 -0
  141. /package/dist/{main/lib → lib}/error.d.ts +0 -0
  142. /package/dist/{main/lib → lib}/features.d.ts +0 -0
  143. /package/dist/{main/lib → lib}/http2/browser/browserparser.d.ts +0 -0
  144. /package/dist/{main/lib → lib}/http2/node/index.d.ts +0 -0
  145. /package/dist/{main/lib → lib}/http2/parserbasehttp2.d.ts +0 -0
  146. /package/dist/{main/lib → lib}/http2/websocketcommon.d.ts +0 -0
  147. /package/dist/{main/lib → lib}/index.browser.d.ts +0 -0
  148. /package/dist/{main/lib → lib}/index.node.d.ts +0 -0
  149. /package/dist/{main/lib → lib}/server.d.ts +0 -0
  150. /package/dist/{main/lib → lib}/stream.d.ts +0 -0
  151. /package/dist/{main/lib → lib}/utils.d.ts +0 -0
  152. /package/dist/{main/lib → lib}/webstreams.browser.d.ts +0 -0
  153. /package/dist/{main/lib → lib}/webstreams.d.ts +0 -0
  154. /package/dist/{main/lib → lib}/webtransport.node.d.ts +0 -0
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * @typedef {import('http2').Http2Stream} Http2Stream
3
+ * @typedef {import('../dom.js').WebTransportSendGroup} WebTransportSendGroup
3
4
  */
4
5
  import { ParserBase } from './parserbase.js'
5
6
  import { FlowController } from './flowcontroller.js'
6
7
  import { logger } from '../utils.js'
8
+ import { StreamIdManager } from './streamidmanager.js'
7
9
 
8
10
  const pid = typeof process !== 'undefined' ? process.pid : 0
9
11
  const log = logger(`webtransport:http2webtransportsession(${pid})`)
@@ -20,7 +22,11 @@ export class Http2WebTransportSession {
20
22
  * sendWindowOffset: Number,
21
23
  * receiveWindowOffset: Number,
22
24
  * shouldAutoTuneReceiveWindow: boolean
23
- * receiveWindowSizeLimit: Number}} args
25
+ * receiveWindowSizeLimit: Number,
26
+ * initialBidirectionalSendStreams: Number,
27
+ * initialBidirectionalReceiveStreams: Number,
28
+ * initialUnidirectionalSendStreams: Number,
29
+ * initialUnidirectionalReceiveStreams: Number}} args
24
30
  * */
25
31
  constructor({
26
32
  stream,
@@ -30,7 +36,11 @@ export class Http2WebTransportSession {
30
36
  sendWindowOffset,
31
37
  receiveWindowOffset,
32
38
  shouldAutoTuneReceiveWindow,
33
- receiveWindowSizeLimit
39
+ receiveWindowSizeLimit,
40
+ initialBidirectionalSendStreams,
41
+ initialBidirectionalReceiveStreams,
42
+ initialUnidirectionalSendStreams,
43
+ initialUnidirectionalReceiveStreams
34
44
  }) {
35
45
  // @ts-ignore
36
46
  this.jsobj = undefined // the creator will set this
@@ -40,8 +50,6 @@ export class Http2WebTransportSession {
40
50
  this.ws = ws
41
51
  } else throw new Error('Neither stream or websocket supplied')
42
52
  this.capsParser = createParser(this)
43
- this.unidiId = 0
44
- this.bidiId = 0
45
53
  this.isclient = isclient
46
54
  this.flowController = new FlowController({
47
55
  tocontrol: this,
@@ -50,6 +58,22 @@ export class Http2WebTransportSession {
50
58
  shouldAutoTuneReceiveWindow,
51
59
  receiveWindowSizeLimit
52
60
  })
61
+ this.streamIdMngrUni = new StreamIdManager({
62
+ delegate: this,
63
+ unidirectional: true,
64
+ isclient,
65
+ maxAllowedIncomingStreams: initialUnidirectionalReceiveStreams,
66
+ maxAllowedOutgoingStreams: initialUnidirectionalSendStreams
67
+ })
68
+ this.streamIdMngrBi = new StreamIdManager({
69
+ delegate: this,
70
+ unidirectional: false,
71
+ isclient,
72
+ maxAllowedIncomingStreams: initialBidirectionalReceiveStreams,
73
+ maxAllowedOutgoingStreams: initialBidirectionalSendStreams
74
+ })
75
+ this.orderUniStreams = 0
76
+ this.orderBiStreams = 0
53
77
  if (stream) {
54
78
  if (isclient) {
55
79
  stream.on('response', (headers) => {
@@ -75,16 +99,8 @@ export class Http2WebTransportSession {
75
99
 
76
100
  sendInitialParameters() {
77
101
  this.flowController.sendWindowUpdate()
78
- this.capsParser.writeCapsule({
79
- type: ParserBase.WT_MAX_STREAMS_BIDI,
80
- headerVints: [0xffffff],
81
- payload: undefined
82
- })
83
- this.capsParser.writeCapsule({
84
- type: ParserBase.WT_MAX_STREAMS_UNIDI,
85
- headerVints: [0xffffff],
86
- payload: undefined
87
- })
102
+ this.streamIdMngrBi.sendMaxStreamsFrameInitial()
103
+ this.streamIdMngrUni.sendMaxStreamsFrameInitial()
88
104
  }
89
105
 
90
106
  /**
@@ -101,28 +117,66 @@ export class Http2WebTransportSession {
101
117
  })
102
118
  }
103
119
 
104
- orderUnidiStream() {
105
- let streamid = 0x2 | (this.unidiId << 2)
106
- if (this.isclient) streamid = streamid | 0x1
107
- this.capsParser.writeCapsule({
108
- type: ParserBase.WT_STREAM_WOFIN,
109
- headerVints: [streamid],
110
- payload: undefined
111
- })
112
- this.capsParser.newStream(streamid)
113
- this.unidiId++
120
+ trySendingUnidirectionalStreams() {
121
+ while (
122
+ this.orderUniStreams > 0 &&
123
+ this.streamIdMngrUni.canOpenNextOutgoingStream()
124
+ ) {
125
+ const streamid = this.streamIdMngrUni.getNextOutgoingStreamId()
126
+ this.capsParser.writeCapsule({
127
+ type: ParserBase.WT_STREAM_WOFIN,
128
+ headerVints: [streamid],
129
+ payload: undefined
130
+ })
131
+ this.capsParser.newStream(streamid)
132
+ this.orderUniStreams--
133
+ }
114
134
  }
115
135
 
116
- orderBidiStream() {
117
- let streamid = 0x0 | (this.bidiId << 2)
118
- if (this.isclient) streamid = streamid | 0x1
119
- this.capsParser.writeCapsule({
120
- type: ParserBase.WT_STREAM_WOFIN,
121
- headerVints: [streamid],
122
- payload: undefined
123
- })
124
- this.capsParser.newStream(streamid)
125
- this.bidiId++
136
+ /**
137
+ * @param {{sendGroup: WebTransportSendGroup|null, sendOrder: number, waitUntilAvailable: boolean}} opts
138
+ */
139
+ orderUnidiStream({ sendGroup, sendOrder, waitUntilAvailable }) {
140
+ const canopen = this.streamIdMngrUni.canOpenNextOutgoingStream()
141
+ const maxset = this.streamIdMngrUni.isMaxStreamSet() // we block if the maxsetting did not arrive
142
+
143
+ if (canopen || waitUntilAvailable || !maxset) {
144
+ this.orderUniStreams++
145
+ this.trySendingUnidirectionalStreams()
146
+ return true
147
+ }
148
+ return false
149
+ }
150
+
151
+ trySendingBidirectionalStreams() {
152
+ while (
153
+ this.orderBiStreams > 0 &&
154
+ this.streamIdMngrBi.canOpenNextOutgoingStream()
155
+ ) {
156
+ const streamid = this.streamIdMngrBi.getNextOutgoingStreamId()
157
+ this.capsParser.writeCapsule({
158
+ type: ParserBase.WT_STREAM_WOFIN,
159
+ headerVints: [streamid],
160
+ payload: undefined
161
+ })
162
+ this.capsParser.newStream(streamid)
163
+ this.orderBiStreams--
164
+ }
165
+ }
166
+
167
+ /**
168
+ * @param {{sendGroup: WebTransportSendGroup|null, sendOrder: number, waitUntilAvailable: boolean}} opts
169
+ */
170
+ orderBidiStream({ sendGroup, sendOrder, waitUntilAvailable }) {
171
+ const canopen = this.streamIdMngrBi.canOpenNextOutgoingStream()
172
+ const maxset = this.streamIdMngrBi.isMaxStreamSet() // we block if the maxsetting did not arrive
173
+
174
+ if (canopen || waitUntilAvailable || !maxset) {
175
+ this.orderBiStreams++
176
+ this.trySendingBidirectionalStreams()
177
+ return true
178
+ }
179
+ return false
126
180
  }
127
181
 
128
182
  orderSessionStats() {
@@ -218,4 +272,25 @@ export class Http2WebTransportSession {
218
272
  return navigator?.connection?.rtt || 26
219
273
  }
220
274
  }
275
+
276
+ /**
277
+ * @returns {boolean}
278
+ */
279
+ canSendMaxStreams() {
280
+ return true
281
+ }
282
+
283
+ /**
284
+ * @param {bigint} maxStreams
285
+ * @param {boolean} unidirectional
286
+ */
287
+ sendMaxStreams(maxStreams, unidirectional) {
288
+ this.capsParser.writeCapsule({
289
+ type: unidirectional
290
+ ? ParserBase.WT_MAX_STREAMS_UNIDI
291
+ : ParserBase.WT_MAX_STREAMS_BIDI,
292
+ headerVints: [maxStreams],
293
+ payload: undefined
294
+ })
295
+ }
221
296
  }
@@ -12,7 +12,8 @@ const log = logger(`webtransport:http2webtransportstream(${pid})`)
12
12
  * @typedef {import('../types').StreamWriteEvent} StreamWriteEvent
13
13
  * @typedef {import('../types').StreamNetworkFinishEvent} StreamNetworkFinishEvent
14
14
  *
15
- * @typedef {import('../types').ReadDataInt} ReadDataInt
15
+ * @typedef {import('../types').ReadDataInt} ReadDataInt
16
+ * @typedef {import('./streamidmanager.js').StreamIdManager} StreamIdManager
16
17
  */
17
18
 
18
19
  let processnextTick = (/** @type {{ (): void; }} */ func) =>
@@ -22,21 +23,28 @@ if (typeof process !== 'undefined') processnextTick = process.nextTick
22
23
 
23
24
  export class Http2WebTransportStream {
24
25
  /**
25
- * @param {{streamid: Number, capsuleParser: ParserBase
26
+ * @param {{streamid: bigint,
27
+ * unidirectional: boolean,
28
+ * incoming: boolean,
29
+ * capsuleParser: ParserBase
26
30
  * sendWindowOffset: Number,
27
31
  * receiveWindowOffset: Number,
28
32
  * shouldAutoTuneReceiveWindow: boolean
29
33
  * receiveWindowSizeLimit: Number,
30
- * sessionFlowController: FlowController}} args
34
+ * sessionFlowController: FlowController,
35
+ * streamIdManager: StreamIdManager}} args
31
36
  * */
32
37
  constructor({
33
38
  streamid,
39
+ unidirectional,
40
+ incoming,
34
41
  capsuleParser,
35
42
  sendWindowOffset,
36
43
  receiveWindowOffset,
37
44
  shouldAutoTuneReceiveWindow,
38
45
  receiveWindowSizeLimit,
39
- sessionFlowController
46
+ sessionFlowController,
47
+ streamIdManager
40
48
  }) {
41
49
  /** @type {import('../stream').HttpWTStream} */
42
50
  // @ts-ignore
@@ -58,11 +66,22 @@ export class Http2WebTransportStream {
58
66
  sessionFlowController
59
67
  })
60
68
  this.sessionFlowController = sessionFlowController
69
+ this.streamIdManager = streamIdManager
61
70
 
62
71
  this.final = false
63
72
  this.stopReading_ = true
64
73
  this.drainReads_ = true
65
74
  this.recvBytes = 0
75
+
76
+ this.outgoingClosed_ = false
77
+ this.incomingClosed_ = false
78
+ if (unidirectional) {
79
+ if (incoming) {
80
+ this.outgoingClosed_ = true
81
+ } else {
82
+ this.incomingClosed_ = true
83
+ }
84
+ }
66
85
  }
67
86
 
68
87
  sendInitialParameters() {
@@ -71,12 +90,12 @@ export class Http2WebTransportStream {
71
90
 
72
91
  /**
73
92
  * @param {Object} obj
74
- * @param {Uint8Array} obj.data
93
+ * @param {Uint8Array|undefined} obj.data
75
94
  * @param {Boolean} obj.fin
76
95
  */
77
96
  recvData({ data, fin }) {
78
97
  this.incomdata.push({ data, fin })
79
- if (data?.byteLength > 0) {
98
+ if (data && data?.byteLength > 0) {
80
99
  const checkstream = this.flowController.updateHighestReceivedOffset(
81
100
  data?.byteLength
82
101
  )
@@ -115,13 +134,13 @@ export class Http2WebTransportStream {
115
134
  ) {
116
135
  if (!buffer) {
117
136
  const bytes = this.incomdata.reduce(
118
- (prevVal, val) => prevVal + val.data?.byteLength,
137
+ (prevVal, val) => prevVal + ((val && val.data?.byteLength) || 0),
119
138
  0
120
139
  )
121
140
  if (bytes > 0) {
122
141
  buffer = this.jsobj.getReadBuffer({
123
142
  byteSize: this.incomdata.reduce(
124
- (prevVal, val) => prevVal + val.data?.byteLength,
143
+ (prevVal, val) => prevVal + ((val && val.data?.byteLength) || 0),
125
144
  0
126
145
  )
127
146
  })
@@ -194,10 +213,40 @@ export class Http2WebTransportStream {
194
213
  this.stopReading_ = true
195
214
  }
196
215
 
216
+ /**
217
+ * @param {'resetStream'|'stopSending'} type
218
+ */
219
+ onStreamSignal(type) {
220
+ switch (type) {
221
+ case 'resetStream':
222
+ this.closeIncoming()
223
+ break
224
+ case 'stopSending':
225
+ this.closeOutgoing()
226
+ break
227
+ }
228
+ }
229
+
230
+ closeIncoming() {
231
+ this.incomingClosed_ = true
232
+ if (this.outgoingClosed_) this.onClose()
233
+ }
234
+
235
+ closeOutgoing() {
236
+ this.outgoingClosed_ = true
237
+ if (this.incomingClosed_) this.onClose()
238
+ }
239
+
240
+ onClose() {
241
+ // ok, inform our session id manager
242
+ this.streamIdManager.onStreamClosed(this.streamid)
243
+ }
244
+
197
245
  /**
198
246
  * @param {Number} code
199
247
  */
200
248
  stopSending(code) {
249
+ this.closeIncoming()
201
250
  this.capsuleParser.writeCapsule({
202
251
  type: ParserBase.WT_STOP_SENDING,
203
252
  headerVints: [this.streamid, code],
@@ -210,10 +259,15 @@ export class Http2WebTransportStream {
210
259
  )
211
260
  }
212
261
 
262
+ onFin() {
263
+ this.closeIncoming()
264
+ }
265
+
213
266
  /**
214
267
  * @param {Number} code
215
268
  */
216
269
  resetStream(code) {
270
+ this.closeOutgoing()
217
271
  this.capsuleParser.writeCapsule({
218
272
  type: ParserBase.WT_RESET_STREAM,
219
273
  headerVints: [this.streamid, code],
@@ -238,8 +292,8 @@ export class Http2WebTransportStream {
238
292
  while (
239
293
  this.outgochunks.length > 0 &&
240
294
  (!this.capsuleParser.blocked || this.final) &&
241
- this.flowController.sendWindowSize() > 0 &&
242
- this.sessionFlowController.sendWindowSize() > 0
295
+ this.flowController.sendWindowSize() > 0n &&
296
+ this.sessionFlowController.sendWindowSize() > 0n
243
297
  ) {
244
298
  const cur = this.outgochunks.shift()
245
299
  if (cur) {
@@ -308,6 +362,7 @@ export class Http2WebTransportStream {
308
362
  * @param {bigint} windowOffset
309
363
  */
310
364
  sendWindowUpdate(windowOffset) {
365
+ log('sendwindow offset stream:', windowOffset)
311
366
  this.capsuleParser.writeCapsule({
312
367
  type: ParserBase.WT_MAX_STREAM_DATA,
313
368
  headerVints: [this.streamid, windowOffset],
@@ -0,0 +1,316 @@
1
+ import { logger } from '../utils.js'
2
+
3
+ /**
4
+ * @typedef {import('../types').StreamIdClient} StreamIdClient
5
+ */
6
+
7
+ function GetMaxStreamCount() {
8
+ return (0xffffffffn >> 2n) + 1n
9
+ }
10
+
11
+ const pid = typeof process !== 'undefined' ? process.pid : 0
12
+ const log = logger(`webtransport:streamidmanager(${pid})`)
13
+ // Ported from libquiche, QuicStreamIdManager so their license applies to the original in C++ and this javascript translation
14
+ // Copyright (c) 2018 The Chromium Authors. All rights reserved.
15
+ // Use of this source code is governed by a BSD-style license that can be
16
+ // found in the LICENSE file.
17
+
18
+ export class StreamIdManager {
19
+ /**
20
+ * @param {{delegate: StreamIdClient
21
+ * unidirectional: boolean,
22
+ * isclient: boolean,
23
+ * maxAllowedOutgoingStreams: number,
24
+ * maxAllowedIncomingStreams: number}} arg
25
+ */
26
+ constructor({
27
+ delegate,
28
+ unidirectional,
29
+ isclient,
30
+ maxAllowedOutgoingStreams,
31
+ maxAllowedIncomingStreams
32
+ }) {
33
+ this.delegate = delegate
34
+ this.unidirectional = unidirectional
35
+ this.isclient = isclient
36
+
37
+ this.outgoingMaxStreams = BigInt(maxAllowedOutgoingStreams)
38
+
39
+ // The ID to use for the next outgoing stream.
40
+ this.nextOutgoingStreamId = this.getFirstOutgoingStreamId()
41
+
42
+ // The number of outgoing streams that have ever been opened, including those
43
+ // that have been closed. This number must never be larger than
44
+ // outgoing_max_streams_.
45
+ this.outgoingStreamCount = 0n
46
+
47
+ // FOR INCOMING STREAMS
48
+
49
+ // The actual maximum number of streams that can be opened by the peer.
50
+ this.incomingActualMaxStreams = BigInt(maxAllowedIncomingStreams)
51
+ // Max incoming stream number that has been advertised to the peer and is <=
52
+ // incoming_actual_max_streams_. It is set to incoming_actual_max_streams_
53
+ // when a MAX_STREAMS is sent.
54
+ this.incomingAdvertisedMaxStreams = BigInt(maxAllowedIncomingStreams)
55
+
56
+ // Initial maximum on the number of open streams allowed.
57
+ this.incomingInitialMaxOpenStreams = BigInt(maxAllowedIncomingStreams)
58
+
59
+ // The number of streams that have been created, including open ones and
60
+ // closed ones.
61
+ this.incomingStreamCount = 0n
62
+
63
+ // Set of stream ids that are less than the largest stream id that has been
64
+ // received, but are nonetheless available to be created.
65
+ this.availableStreams = new Set()
66
+
67
+ this.largestPeerCreatedStreamId = BigInt(Number.MAX_SAFE_INTEGER)
68
+
69
+ // If true, then the stream limit will never be increased.
70
+ this.stopIncreasingIncomingMaxStreams = false
71
+ }
72
+
73
+ /**
74
+ * @param {bigint} streamCount
75
+ */
76
+ onStreamsBlockedFrame(streamCount) {
77
+ if (streamCount > this.incomingAdvertisedMaxStreams) {
78
+ // Peer thinks it can send more streams that we've told it.
79
+ return {
80
+ error:
81
+ "StreamsBlockedFrame's stream count " +
82
+ streamCount +
83
+ ' exceeds incoming max stream ' +
84
+ this.incomingAdvertisedMaxStreams
85
+ }
86
+ }
87
+ if (this.incomingAdvertisedMaxStreams === this.incomingActualMaxStreams) {
88
+ // We have told peer about current max.
89
+ return { success: true }
90
+ }
91
+ if (
92
+ streamCount < this.incomingActualMaxStreams &&
93
+ this.delegate.canSendMaxStreams()
94
+ ) {
95
+ // Peer thinks it's blocked on a stream count that is less than our current
96
+ // max. Inform the peer of the correct stream count.
97
+ this.sendMaxStreamsFrame()
98
+ }
99
+ return { success: true }
100
+ }
101
+
102
+ /**
103
+ * @param {bigint} maxOpenStreams
104
+ */
105
+ maybeAllowNewOutgoingStreams(maxOpenStreams) {
106
+ if (maxOpenStreams <= this.outgoingMaxStreams) {
107
+ // Only update the stream count if it would increase the limit.
108
+ return false
109
+ }
110
+
111
+ // This implementation only supports 32 bit Stream IDs, so limit max streams
112
+ // if it would exceed the max 32 bits can express.
113
+ const maxStreamCount = GetMaxStreamCount()
114
+ if (maxOpenStreams < maxStreamCount)
115
+ this.outgoingMaxStreams = maxOpenStreams
116
+ else this.outgoingMaxStreams = maxStreamCount
117
+
118
+ return true
119
+ }
120
+
121
+ /**
122
+ * @param {bigint} maxOpenStreams
123
+ */
124
+ setMaxOpenIncomingStreams(maxOpenStreams) {
125
+ if (this.incomingStreamCount > 0)
126
+ throw new Error(
127
+ 'non-zero incoming stream count ' +
128
+ this.incomingStreamCount +
129
+ +' when setting max incoming stream to ' +
130
+ maxOpenStreams
131
+ )
132
+ if (this.incomingInitialMaxOpenStreams !== maxOpenStreams)
133
+ log(
134
+ this.unidirectional ? 'unidirectional ' : 'bidirectional: ',
135
+ 'incoming stream limit changed from ',
136
+ this.incomingInitialMaxOpenStreams,
137
+ ' to ',
138
+ maxOpenStreams
139
+ )
140
+ this.incomingActualMaxStreams = maxOpenStreams
141
+ this.incomingAdvertisedMaxStreams = maxOpenStreams
142
+ this.incomingInitialMaxOpenStreams = maxOpenStreams
143
+ }
144
+
145
+ maybeSendMaxStreamsFrame() {
146
+ const divisor = 2n // may be modify
147
+
148
+ if (divisor > 0n) {
149
+ if (
150
+ this.incomingAdvertisedMaxStreams - this.incomingStreamCount >
151
+ this.incomingInitialMaxOpenStreams / divisor
152
+ ) {
153
+ // window too large, no advertisement
154
+ return
155
+ }
156
+ }
157
+ if (
158
+ this.delegate.canSendMaxStreams() &&
159
+ this.incomingAdvertisedMaxStreams < this.incomingActualMaxStreams
160
+ ) {
161
+ this.sendMaxStreamsFrame()
162
+ }
163
+ }
164
+
165
+ sendMaxStreamsFrame() {
166
+ if (this.incomingAdvertisedMaxStreams >= this.incomingActualMaxStreams)
167
+ throw new Error(
168
+ 'this.incomingAdvertisedMaxStreams >= this.incomingActualMaxStreams' +
169
+ this.incomingAdvertisedMaxStreams +
170
+ 'vs.' +
171
+ this.incomingActualMaxStreams
172
+ )
173
+ this.incomingAdvertisedMaxStreams = this.incomingActualMaxStreams
174
+ this.delegate.sendMaxStreams(
175
+ this.incomingAdvertisedMaxStreams,
176
+ this.unidirectional
177
+ )
178
+ }
179
+
180
+ sendMaxStreamsFrameInitial() {
181
+ this.delegate.sendMaxStreams(
182
+ this.incomingAdvertisedMaxStreams,
183
+ this.unidirectional
184
+ )
185
+ }
186
+
187
+ /**
188
+ * @param {bigint} streamId
189
+ */
190
+ onStreamClosed(streamId) {
191
+ // Nothing to do for outgoing streams.
192
+ if (
193
+ (this.isclient && streamId & 0x1n) ||
194
+ (!this.isclient && !(streamId & 0x1n))
195
+ )
196
+ return
197
+
198
+ // If the stream is inbound, we can increase the actual stream limit and maybe
199
+ // advertise the new limit to the peer.
200
+ if (this.incomingActualMaxStreams === GetMaxStreamCount()) {
201
+ // Reached the maximum stream id value that the implementation
202
+ // supports. Nothing can be done here.
203
+ return
204
+ }
205
+ if (!this.stopIncreasingIncomingMaxStreams) {
206
+ // One stream closed, and another one can be opened.
207
+ this.incomingActualMaxStreams++
208
+ this.maybeSendMaxStreamsFrame()
209
+ }
210
+ }
211
+
212
+ getNextOutgoingStreamId() {
213
+ if (this.outgoingStreamCount >= this.outgoingMaxStreams)
214
+ throw new Error(
215
+ 'Attempt to allocate a new outgoing stream that would exceed the ' +
216
+ 'limit (' +
217
+ +Number(this.outgoingMaxStreams) +
218
+ ')'
219
+ )
220
+ const id = this.nextOutgoingStreamId
221
+ this.nextOutgoingStreamId += 1n << 2n
222
+ this.outgoingStreamCount++
223
+ return id
224
+ }
225
+
226
+ canOpenNextOutgoingStream() {
227
+ return this.outgoingStreamCount < this.outgoingMaxStreams
228
+ }
229
+
230
+ isMaxStreamSet() {
231
+ return this.outgoingMaxStreams > 0n
232
+ }
233
+
234
+ /**
235
+ * @param {bigint} streamId
236
+ */
237
+ maybeIncreaseLargestPeerStreamId(streamId) {
238
+ // |stream_id| must be an incoming stream of the right directionality.
239
+
240
+ if (this.availableStreams.has(streamId)) {
241
+ this.availableStreams.delete(streamId)
242
+ // stream_id is available.
243
+ return true
244
+ }
245
+
246
+ // Calculate increment of incoming_stream_count_ by creating stream_id.
247
+ const delta = 1n << 2n
248
+ const leastNewStreamId =
249
+ this.largestPeerCreatedStreamId === BigInt(Number.MAX_SAFE_INTEGER)
250
+ ? this.getFirstIncomingStreamId()
251
+ : this.largestPeerCreatedStreamId + delta
252
+ const streamCountIncrement = (streamId - leastNewStreamId) / delta + 1n
253
+
254
+ if (
255
+ this.incomingStreamCount + streamCountIncrement >
256
+ this.incomingAdvertisedMaxStreams
257
+ ) {
258
+ log(
259
+ 'Failed to create a new incoming stream with id:' +
260
+ streamId +
261
+ ', reaching MAX_STREAMS limit: ' +
262
+ this.incomingAdvertisedMaxStreams +
263
+ '.'
264
+ )
265
+ return {
266
+ error:
267
+ 'Stream id ' +
268
+ streamId +
269
+ ' would exceed stream count limit ' +
270
+ this.incomingAdvertisedMaxStreams
271
+ }
272
+ }
273
+
274
+ for (let id = leastNewStreamId; id < streamId; id += delta) {
275
+ this.availableStreams.add(id)
276
+ }
277
+ this.incomingStreamCount += streamCountIncrement
278
+ this.largestPeerCreatedStreamId = streamId
279
+ return true
280
+ }
281
+
282
+ /**
283
+ * @param {number} id
284
+ */
285
+ isAvailableStream(id) {
286
+ if ((this.isclient && id & 0x1) || (!this.isclient && !(id & 0x1))) {
287
+ // Stream IDs under next_ougoing_stream_id_ are either open or previously
288
+ // open but now closed.
289
+ return id >= this.nextOutgoingStreamId
290
+ }
291
+ // For peer created streams, we also need to consider available streams.
292
+ return (
293
+ this.largestPeerCreatedStreamId === BigInt(Number.MAX_SAFE_INTEGER) ||
294
+ id > this.largestPeerCreatedStreamId ||
295
+ this.availableStreams.has(id)
296
+ )
297
+ }
298
+
299
+ getFirstOutgoingStreamId() {
300
+ let streamid = 0n
301
+ if (this.isclient) streamid |= 0x1n
302
+ if (this.unidirectional) streamid |= 0x2n
303
+ return streamid
304
+ }
305
+
306
+ getFirstIncomingStreamId() {
307
+ let streamid = 0n
308
+ if (!this.isclient) streamid |= 0x1n
309
+ if (this.unidirectional) streamid |= 0x2n
310
+ return streamid
311
+ }
312
+
313
+ get availableIncomingStreams() {
314
+ return this.incomingAdvertisedMaxStreams - this.incomingStreamCount
315
+ }
316
+ }