@fails-components/webtransport 0.3.1 → 1.0.1
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.
- package/lib/client.js +81 -40
- package/lib/dom.ts +7 -5
- package/lib/http2/browser/browser.js +168 -0
- package/lib/http2/browser/browserparser.js +248 -0
- package/lib/http2/flowcontroller.js +371 -0
- package/lib/http2/node/capsuleparser.js +291 -0
- package/lib/http2/node/client.js +239 -0
- package/lib/http2/node/index.js +2 -0
- package/lib/http2/node/server.js +499 -0
- package/lib/http2/node/websocketparser.js +829 -0
- package/lib/http2/parserbase.js +139 -0
- package/lib/http2/parserbasehttp2.js +128 -0
- package/lib/http2/session.js +225 -0
- package/lib/http2/stream.js +354 -0
- package/lib/http2/websocketcommon.js +1 -0
- package/lib/{index.js → index.browser.js} +6 -10
- package/lib/index.node.js +27 -0
- package/lib/server.js +252 -69
- package/lib/session.js +31 -68
- package/lib/stream.js +103 -179
- package/lib/types.ts +174 -87
- package/lib/utils.js +2 -1
- package/lib/webstreams.browser.js +6 -0
- package/lib/webstreams.js +7 -0
- package/lib/webtransport.browser.js +284 -0
- package/lib/webtransport.node.js +137 -0
- package/lib/{webtransport.js → webtransportbase.js} +47 -41
- package/old_test/test.js +31 -29
- package/old_test/testsuite.js +1 -1
- package/package.json +18 -17
- package/readme.md +94 -30
- package/test/bidirectional-streams.spec.js +51 -9
- package/test/datagrams.spec.js +8 -2
- package/test/fixtures/known-bytes.js +29 -0
- package/test/fixtures/read-stream.js +1 -1
- package/test/fixtures/reader-value.js +1 -1
- package/test/fixtures/server.js +24 -10
- package/test/fixtures/webtransport.browser.js +20 -2
- package/test/fixtures/webtransport.js +1 -1
- package/test/index.js +33 -6
- package/test/pw-no-https-errors.json +5 -0
- package/test/session.spec.js +71 -52
- package/test/unidirectional-streams.spec.js +20 -4
- package/CMakeLists.txt +0 -991
- package/LICENSE.chromium +0 -27
- package/LICENSE.envoy +0 -207
- package/build.js +0 -313
- package/dist/lib/client.d.ts +0 -78
- package/dist/lib/client.d.ts.map +0 -1
- package/dist/lib/dom.d.ts +0 -87
- package/dist/lib/dom.d.ts.map +0 -1
- package/dist/lib/error.d.ts +0 -8
- package/dist/lib/error.d.ts.map +0 -1
- package/dist/lib/event-loop.d.ts +0 -33
- package/dist/lib/event-loop.d.ts.map +0 -1
- package/dist/lib/index.d.ts +0 -79
- package/dist/lib/index.d.ts.map +0 -1
- package/dist/lib/native.d.ts +0 -2
- package/dist/lib/native.d.ts.map +0 -1
- package/dist/lib/server.d.ts +0 -92
- package/dist/lib/server.d.ts.map +0 -1
- package/dist/lib/session.d.ts +0 -298
- package/dist/lib/session.d.ts.map +0 -1
- package/dist/lib/stream.d.ts +0 -135
- package/dist/lib/stream.d.ts.map +0 -1
- package/dist/lib/transport.d.ts +0 -34
- package/dist/lib/transport.d.ts.map +0 -1
- package/dist/lib/types.d.ts +0 -212
- package/dist/lib/types.d.ts.map +0 -1
- package/dist/lib/utils.d.ts +0 -11
- package/dist/lib/utils.d.ts.map +0 -1
- package/dist/lib/webtransport.d.ts +0 -47
- package/dist/lib/webtransport.d.ts.map +0 -1
- package/dist/test/bidirectional-streams.spec.d.ts +0 -5
- package/dist/test/bidirectional-streams.spec.d.ts.map +0 -1
- package/dist/test/datagrams.spec.d.ts +0 -5
- package/dist/test/datagrams.spec.d.ts.map +0 -1
- package/dist/test/event-loop.node.d.ts +0 -2
- package/dist/test/event-loop.node.d.ts.map +0 -1
- package/dist/test/fixtures/certificate.d.ts +0 -21
- package/dist/test/fixtures/certificate.d.ts.map +0 -1
- package/dist/test/fixtures/chai.d.ts +0 -2
- package/dist/test/fixtures/chai.d.ts.map +0 -1
- package/dist/test/fixtures/known-bytes.d.ts +0 -6
- package/dist/test/fixtures/known-bytes.d.ts.map +0 -1
- package/dist/test/fixtures/p-timeout.d.ts +0 -8
- package/dist/test/fixtures/p-timeout.d.ts.map +0 -1
- package/dist/test/fixtures/read-cert-hash.d.ts +0 -6
- package/dist/test/fixtures/read-cert-hash.d.ts.map +0 -1
- package/dist/test/fixtures/read-stream.d.ts +0 -19
- package/dist/test/fixtures/read-stream.d.ts.map +0 -1
- package/dist/test/fixtures/reader-value.d.ts +0 -22
- package/dist/test/fixtures/reader-value.d.ts.map +0 -1
- package/dist/test/fixtures/server.d.ts +0 -5
- package/dist/test/fixtures/server.d.ts.map +0 -1
- package/dist/test/fixtures/webtransport.browser.d.ts +0 -3
- package/dist/test/fixtures/webtransport.browser.d.ts.map +0 -1
- package/dist/test/fixtures/webtransport.d.ts +0 -3
- package/dist/test/fixtures/webtransport.d.ts.map +0 -1
- package/dist/test/fixtures/write-stream.d.ts +0 -10
- package/dist/test/fixtures/write-stream.d.ts.map +0 -1
- package/dist/test/index.d.ts +0 -2
- package/dist/test/index.d.ts.map +0 -1
- package/dist/test/session.spec.d.ts +0 -5
- package/dist/test/session.spec.d.ts.map +0 -1
- package/dist/test/unidirectional-streams.spec.d.ts +0 -2
- package/dist/test/unidirectional-streams.spec.d.ts.map +0 -1
- package/failslogolicensereadme.txt +0 -4
- package/lib/event-loop.js +0 -88
- package/lib/native.js +0 -28
- package/lib/transport.js +0 -82
- package/platform/icufix/fakedata.cpp +0 -8
- package/platform/quiche/quic/core/io/socket_win.inc +0 -3
- package/platform/quiche_platform_impl/quiche_bug_tracker_impl.h +0 -7
- package/platform/quiche_platform_impl/quiche_client_stats_impl.h +0 -7
- package/platform/quiche_platform_impl/quiche_command_line_flags_impl.h +0 -7
- package/platform/quiche_platform_impl/quiche_containers_impl.h +0 -7
- package/platform/quiche_platform_impl/quiche_event_loop_impl.h +0 -7
- package/platform/quiche_platform_impl/quiche_export_impl.h +0 -6
- package/platform/quiche_platform_impl/quiche_flag_utils_impl.h +0 -11
- package/platform/quiche_platform_impl/quiche_flags_impl.h +0 -8
- package/platform/quiche_platform_impl/quiche_iovec_impl.h +0 -10
- package/platform/quiche_platform_impl/quiche_logging_impl.h +0 -21
- package/platform/quiche_platform_impl/quiche_mem_slice_impl.h +0 -2
- package/platform/quiche_platform_impl/quiche_mutex_impl.h +0 -8
- package/platform/quiche_platform_impl/quiche_prefetch_impl.h +0 -11
- package/platform/quiche_platform_impl/quiche_reference_counted_impl.h +0 -16
- package/platform/quiche_platform_impl/quiche_server_stats_impl.h +0 -7
- package/platform/quiche_platform_impl/quiche_stack_trace_impl.h +0 -12
- package/platform/quiche_platform_impl/quiche_stream_buffer_allocator_impl.h +0 -8
- package/platform/quiche_platform_impl/quiche_testvalue_impl.h +0 -1
- package/platform/quiche_platform_impl/quiche_thread_local_impl.h +0 -6
- package/platform/quiche_platform_impl/quiche_time_utils_impl.h +0 -13
- package/platform/quiche_platform_impl/quiche_udp_socket_platform_impl.h +0 -39
- package/src/http3backendresponse.cc +0 -36
- package/src/http3backendresponse.h +0 -109
- package/src/http3client.cc +0 -1529
- package/src/http3client.h +0 -557
- package/src/http3clientsession.cc +0 -65
- package/src/http3clientsession.h +0 -60
- package/src/http3clientstream.cc +0 -52
- package/src/http3clientstream.h +0 -42
- package/src/http3dispatcher.cc +0 -62
- package/src/http3dispatcher.h +0 -57
- package/src/http3eventloop.cc +0 -1107
- package/src/http3eventloop.h +0 -316
- package/src/http3server.cc +0 -514
- package/src/http3server.h +0 -151
- package/src/http3serverbackend.cc +0 -105
- package/src/http3serverbackend.h +0 -98
- package/src/http3serversession.cc +0 -123
- package/src/http3serversession.h +0 -95
- package/src/http3serverstream.cc +0 -463
- package/src/http3serverstream.h +0 -114
- package/src/http3sessioncache.cc +0 -85
- package/src/http3sessioncache.h +0 -59
- package/src/http3wtsessionvisitor.cc +0 -46
- package/src/http3wtsessionvisitor.h +0 -397
- package/src/http3wtstreamvisitor.cc +0 -155
- package/src/http3wtstreamvisitor.h +0 -404
- package/test/event-loop.node.js +0 -24
- package/tsconfig.json +0 -39
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { logger } from '../utils.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('../types').FlowControlable} FlowControlable
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const pid = typeof process !== 'undefined' ? process.pid : 0
|
|
8
|
+
const log = logger(`webtransport:flowcontroller(${pid})`)
|
|
9
|
+
// Ported from libquiche, QuicFlowController so their license applies to the original in C++ and this javascript translation
|
|
10
|
+
// Copyright 2014 The Chromium Authors. All rights reserved.
|
|
11
|
+
// Use of this source code is governed by a BSD-style license that can be
|
|
12
|
+
// found in the LICENSE file.
|
|
13
|
+
|
|
14
|
+
export class FlowController {
|
|
15
|
+
static kSessionFlowControlMultiplier = 1.5
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {{tocontrol: FlowControlable
|
|
19
|
+
* sendWindowOffset: number,
|
|
20
|
+
* receiveWindowOffset: number,
|
|
21
|
+
* shouldAutoTuneReceiveWindow: boolean
|
|
22
|
+
* receiveWindowSizeLimit: number,
|
|
23
|
+
* sessionFlowController?: FlowController}} arg
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
constructor({
|
|
27
|
+
tocontrol,
|
|
28
|
+
sendWindowOffset,
|
|
29
|
+
receiveWindowOffset,
|
|
30
|
+
receiveWindowSizeLimit,
|
|
31
|
+
shouldAutoTuneReceiveWindow,
|
|
32
|
+
sessionFlowController
|
|
33
|
+
}) {
|
|
34
|
+
this.tocontrol = tocontrol
|
|
35
|
+
this.bytesSent = 0n
|
|
36
|
+
this.sendWindowOffset = BigInt(sendWindowOffset)
|
|
37
|
+
this.bytesConsumed = 0n
|
|
38
|
+
this.highestReceivedByteOffset = 0n
|
|
39
|
+
this.receiveWindowOffset = BigInt(receiveWindowOffset)
|
|
40
|
+
this.receiveWindowSize = BigInt(receiveWindowOffset)
|
|
41
|
+
this.receiveWindowSizeLimit = BigInt(receiveWindowSizeLimit)
|
|
42
|
+
this.autoTuneReceiveWindow = shouldAutoTuneReceiveWindow
|
|
43
|
+
this.sessionFlowController = sessionFlowController
|
|
44
|
+
this.lastBlockedSendWindowOffset = 0n
|
|
45
|
+
this.prevWindowUpdateTime = undefined
|
|
46
|
+
|
|
47
|
+
log(
|
|
48
|
+
'Created flow controller ' +
|
|
49
|
+
', setting initial receive window offset to: ' +
|
|
50
|
+
this.receiveWindowOffset +
|
|
51
|
+
', max receive window to: ' +
|
|
52
|
+
this.receiveWindowSize +
|
|
53
|
+
', max receive window limit to: ' +
|
|
54
|
+
this.receiveWindowSizeLimit +
|
|
55
|
+
', setting send window offset to: ' +
|
|
56
|
+
this.sendWindowOffset
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @param {Number} bytesConsumed
|
|
62
|
+
*/
|
|
63
|
+
addBytesConsumed(bytesConsumed) {
|
|
64
|
+
this.bytesConsumed += BigInt(bytesConsumed)
|
|
65
|
+
log(' consumed ' + bytesConsumed + ' bytes.')
|
|
66
|
+
|
|
67
|
+
this.maybeSendWindowUpdate()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {Number} increaseOffset
|
|
72
|
+
*/
|
|
73
|
+
updateHighestReceivedOffset(increaseOffset) {
|
|
74
|
+
// Only update if offset has increased.
|
|
75
|
+
/* if (newOffset <= this.highestReceivedByteOffset) {
|
|
76
|
+
return false
|
|
77
|
+
} */
|
|
78
|
+
|
|
79
|
+
log(
|
|
80
|
+
' highest byte offset increased from ' + this.highestReceivedByteOffset,
|
|
81
|
+
' to ',
|
|
82
|
+
this.highestReceivedByteOffset + BigInt(increaseOffset)
|
|
83
|
+
)
|
|
84
|
+
this.highestReceivedByteOffset += BigInt(increaseOffset)
|
|
85
|
+
return true
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @param {Number} nbytesSent
|
|
90
|
+
*/
|
|
91
|
+
addBytesSent(nbytesSent) {
|
|
92
|
+
const bytesSent = BigInt(nbytesSent)
|
|
93
|
+
if (this.bytesSent + bytesSent > this.sendWindowOffset) {
|
|
94
|
+
log(
|
|
95
|
+
' Trying to send an extra ' +
|
|
96
|
+
bytesSent +
|
|
97
|
+
' bytes, when bytes_sent = ' +
|
|
98
|
+
this.bytesSent +
|
|
99
|
+
', and send_window_offset_ = ' +
|
|
100
|
+
this.sendWindowOffset
|
|
101
|
+
)
|
|
102
|
+
this.bytesSent = this.sendWindowOffset
|
|
103
|
+
|
|
104
|
+
// This is an error on our side, close the connection as soon as possible.
|
|
105
|
+
this.tocontrol.closeConnection({
|
|
106
|
+
code: 63, // QUIC_FLOW_CONTROL_SENT_TOO_MUCH_DATA,
|
|
107
|
+
reason:
|
|
108
|
+
this.sendWindowOffset -
|
|
109
|
+
(this.bytesSent + bytesSent) +
|
|
110
|
+
'bytes over send window offset'
|
|
111
|
+
})
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
this.bytesSent += bytesSent
|
|
115
|
+
log(' sent ' + bytesSent + ' bytes.')
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
flowControlViolation() {
|
|
119
|
+
if (this.highestReceivedByteOffset > this.receiveWindowOffset) {
|
|
120
|
+
log(
|
|
121
|
+
'Flow control violation on ' +
|
|
122
|
+
', receive window offset: ' +
|
|
123
|
+
this.receiveWindowOffset +
|
|
124
|
+
', highest received byte offset: ' +
|
|
125
|
+
this.highestReceivedByteOffset
|
|
126
|
+
)
|
|
127
|
+
return true
|
|
128
|
+
}
|
|
129
|
+
return false
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
maybeIncreaseMaxWindowSize() {
|
|
133
|
+
// Core of receive window auto tuning. This method should be called before a
|
|
134
|
+
// WINDOW_UPDATE frame is sent. Ideally, window updates should occur close to
|
|
135
|
+
// once per RTT. If a window update happens much faster than RTT, it implies
|
|
136
|
+
// that the flow control window is imposing a bottleneck. To prevent this,
|
|
137
|
+
// this method will increase the receive window size (subject to a reasonable
|
|
138
|
+
// upper bound). For simplicity this algorithm is deliberately asymmetric, in
|
|
139
|
+
// that it may increase window size but never decreases.
|
|
140
|
+
|
|
141
|
+
// Keep track of timing between successive window updates.
|
|
142
|
+
const now = Date.now()
|
|
143
|
+
const prev = this.prevWindowUpdateTime
|
|
144
|
+
this.prevWindowUpdateTime = now
|
|
145
|
+
if (!prev) {
|
|
146
|
+
log('first window update for ')
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!this.autoTuneReceiveWindow) {
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// TODO port need a replacement for this
|
|
155
|
+
// Get outbound RTT.
|
|
156
|
+
const rtt = this.tocontrol.smoothedRtt()
|
|
157
|
+
if (rtt === 0) {
|
|
158
|
+
log('rtt zero for ')
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Now we can compare timing of window updates with RTT.
|
|
163
|
+
const sinceLast = now - prev
|
|
164
|
+
const twoRtt = 2 * rtt
|
|
165
|
+
|
|
166
|
+
if (sinceLast >= twoRtt) {
|
|
167
|
+
// If interval between window updates is sufficiently large, there
|
|
168
|
+
// is no need to increase receive_window_size_.
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
const oldWindow = this.receiveWindowSize
|
|
172
|
+
this.increaseWindowSize()
|
|
173
|
+
|
|
174
|
+
if (this.receiveWindowSize > oldWindow) {
|
|
175
|
+
log(
|
|
176
|
+
'New max window increase for ' +
|
|
177
|
+
+' after ' +
|
|
178
|
+
sinceLast +
|
|
179
|
+
' us, and RTT is ' +
|
|
180
|
+
rtt +
|
|
181
|
+
'us. max wndw: ' +
|
|
182
|
+
this.receiveWindowSize
|
|
183
|
+
)
|
|
184
|
+
if (this.sessionFlowController !== undefined) {
|
|
185
|
+
this.sessionFlowController.ensureWindowAtLeast(
|
|
186
|
+
BigInt(
|
|
187
|
+
FlowController.kSessionFlowControlMultiplier *
|
|
188
|
+
Number(this.receiveWindowSize)
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
// TODO(ckrasic) - add a varz to track this (?).
|
|
194
|
+
log(
|
|
195
|
+
'Max window at limit for ' +
|
|
196
|
+
' after ' +
|
|
197
|
+
sinceLast +
|
|
198
|
+
' us, and RTT is ' +
|
|
199
|
+
rtt +
|
|
200
|
+
'us. Limit size: ' +
|
|
201
|
+
this.receiveWindowSize
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
increaseWindowSize() {
|
|
207
|
+
this.receiveWindowSize *= 2n
|
|
208
|
+
this.receiveWindowSize =
|
|
209
|
+
this.receiveWindowSize > this.receiveWindowSizeLimit
|
|
210
|
+
? this.receiveWindowSizeLimit
|
|
211
|
+
: this.receiveWindowSize
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
windowUpdateThreshold() {
|
|
215
|
+
return this.receiveWindowSize / 2n
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
maybeSendWindowUpdate() {
|
|
219
|
+
if (!this.tocontrol.connected()) {
|
|
220
|
+
return
|
|
221
|
+
}
|
|
222
|
+
// Send WindowUpdate to increase receive window if
|
|
223
|
+
// (receive window offset - consumed bytes) < (max window / 2).
|
|
224
|
+
// This is behaviour copied from SPDY.
|
|
225
|
+
|
|
226
|
+
const availableWindow = this.receiveWindowOffset - this.bytesConsumed
|
|
227
|
+
const threshold = this.windowUpdateThreshold()
|
|
228
|
+
|
|
229
|
+
if (!this.prevWindowUpdateTime) {
|
|
230
|
+
// Treat the initial window as if it is a window update, so if 1/2 the
|
|
231
|
+
// window is used in less than 2 RTTs, the window is increased.
|
|
232
|
+
this.prevWindowUpdateTime = Date.now()
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (availableWindow >= threshold) {
|
|
236
|
+
log(
|
|
237
|
+
'Not sending WindowUpdate for ' +
|
|
238
|
+
', available window: ' +
|
|
239
|
+
availableWindow +
|
|
240
|
+
' >= threshold: ' +
|
|
241
|
+
threshold
|
|
242
|
+
)
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
this.maybeIncreaseMaxWindowSize()
|
|
247
|
+
this.updateReceiveWindowOffsetAndSendWindowUpdate(availableWindow)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* @param {bigint} availableWindow
|
|
252
|
+
*/
|
|
253
|
+
updateReceiveWindowOffsetAndSendWindowUpdate(availableWindow) {
|
|
254
|
+
// Update our receive window.
|
|
255
|
+
this.receiveWindowOffset += this.receiveWindowSize - availableWindow
|
|
256
|
+
|
|
257
|
+
log(
|
|
258
|
+
'Sending WindowUpdate frame for ' +
|
|
259
|
+
', consumed bytes: ' +
|
|
260
|
+
this.bytesConsumed +
|
|
261
|
+
', available window: ' +
|
|
262
|
+
availableWindow +
|
|
263
|
+
', and threshold: ' +
|
|
264
|
+
this.windowUpdateThreshold() +
|
|
265
|
+
', and receive window size: ' +
|
|
266
|
+
this.receiveWindowSize +
|
|
267
|
+
'. New receive window offset is: ' +
|
|
268
|
+
this.receiveWindowOffset
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
this.sendWindowUpdate()
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
maybeSendBlocked() {
|
|
275
|
+
if (
|
|
276
|
+
this.sendWindowSize() !== 0 ||
|
|
277
|
+
this.lastBlockedSendWindowOffset >= this.sendWindowOffset
|
|
278
|
+
) {
|
|
279
|
+
return
|
|
280
|
+
}
|
|
281
|
+
log(
|
|
282
|
+
' is flow control blocked. ' +
|
|
283
|
+
'Send window: ' +
|
|
284
|
+
this.sendWindowSize() +
|
|
285
|
+
', bytes sent: ' +
|
|
286
|
+
this.bytesSent +
|
|
287
|
+
', send limit: ' +
|
|
288
|
+
this.sendWindowOffset
|
|
289
|
+
)
|
|
290
|
+
// The entire send_window has been consumed, we are now flow control
|
|
291
|
+
// blocked.
|
|
292
|
+
|
|
293
|
+
// Keep track of when we last sent a BLOCKED frame so that we only send one
|
|
294
|
+
// at a given send offset.
|
|
295
|
+
this.lastBlockedSendWindowOffset = this.sendWindowOffset
|
|
296
|
+
this.tocontrol.sendBlocked(this.lastBlockedSendWindowOffset)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* @param {bigint} newSendWindowOffset
|
|
301
|
+
*/
|
|
302
|
+
updateSendWindowOffset(newSendWindowOffset) {
|
|
303
|
+
// Only update if send window has increased.
|
|
304
|
+
if (newSendWindowOffset <= this.sendWindowOffset) {
|
|
305
|
+
return false
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
log(
|
|
309
|
+
'UpdateSendWindowOffset for ' +
|
|
310
|
+
' with new offset ' +
|
|
311
|
+
newSendWindowOffset +
|
|
312
|
+
' current offset: ' +
|
|
313
|
+
this.sendWindowOffset +
|
|
314
|
+
' bytes_sent: ' +
|
|
315
|
+
this.bytesSent
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
// The flow is now unblocked but could have also been unblocked
|
|
319
|
+
// before. Return true iff this update caused a change from blocked
|
|
320
|
+
// to unblocked.
|
|
321
|
+
const wasPreviouslyBlocked = this.isBlocked()
|
|
322
|
+
this.sendWindowOffset = newSendWindowOffset
|
|
323
|
+
return wasPreviouslyBlocked
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* @param {bigint} windowSize
|
|
328
|
+
*/
|
|
329
|
+
ensureWindowAtLeast(windowSize) {
|
|
330
|
+
if (this.receiveWindowSizeLimit >= windowSize) {
|
|
331
|
+
return
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const availableWindow = this.receiveWindowOffset - this.bytesConsumed
|
|
335
|
+
this.increaseWindowSize()
|
|
336
|
+
this.updateReceiveWindowOffsetAndSendWindowUpdate(availableWindow)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
isBlocked() {
|
|
340
|
+
return this.sendWindowSize() === 0n
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
sendWindowSize() {
|
|
344
|
+
if (this.bytesSent > this.sendWindowOffset) {
|
|
345
|
+
return 0
|
|
346
|
+
}
|
|
347
|
+
return this.sendWindowOffset - this.bytesSent
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* @param {bigint} size
|
|
352
|
+
*/
|
|
353
|
+
updateReceiveWindowSize(size) {
|
|
354
|
+
log('UpdateReceiveWindowSize for ' + ': ' + size)
|
|
355
|
+
if (this.receiveWindowSize !== this.receiveWindowOffset) {
|
|
356
|
+
log(
|
|
357
|
+
'receive_window_size_:' +
|
|
358
|
+
this.receiveWindowSize +
|
|
359
|
+
' != receive_window_offset:' +
|
|
360
|
+
this.receiveWindowOffset
|
|
361
|
+
)
|
|
362
|
+
return
|
|
363
|
+
}
|
|
364
|
+
this.receiveWindowSize = size
|
|
365
|
+
this.receiveWindowOffset = size
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
sendWindowUpdate() {
|
|
369
|
+
this.tocontrol.sendWindowUpdate(this.receiveWindowOffset)
|
|
370
|
+
}
|
|
371
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { ParserBaseHttp2, readVarInt, writeVarInt } from '../parserbasehttp2.js'
|
|
2
|
+
import { lengthVarInt } from '../parserbase.js'
|
|
3
|
+
|
|
4
|
+
export class Http2CapsuleParser extends ParserBaseHttp2 {
|
|
5
|
+
/**
|
|
6
|
+
* @param {import('../../types.js').ParserHttp2Init} stream
|
|
7
|
+
*/
|
|
8
|
+
constructor({
|
|
9
|
+
stream,
|
|
10
|
+
nativesession,
|
|
11
|
+
isclient,
|
|
12
|
+
initialStreamSendWindowOffset,
|
|
13
|
+
initialStreamReceiveWindowOffset,
|
|
14
|
+
streamShouldAutoTuneReceiveWindow,
|
|
15
|
+
streamReceiveWindowSizeLimit
|
|
16
|
+
}) {
|
|
17
|
+
super({
|
|
18
|
+
stream,
|
|
19
|
+
nativesession,
|
|
20
|
+
isclient,
|
|
21
|
+
initialStreamSendWindowOffset,
|
|
22
|
+
initialStreamReceiveWindowOffset,
|
|
23
|
+
streamShouldAutoTuneReceiveWindow,
|
|
24
|
+
streamReceiveWindowSizeLimit
|
|
25
|
+
})
|
|
26
|
+
this.mode = 's' // capsule start
|
|
27
|
+
/** @type {Buffer|undefined} */
|
|
28
|
+
this.saveddata = undefined
|
|
29
|
+
/** @type {Number|undefined} */
|
|
30
|
+
this.rtype = undefined
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {Buffer} data
|
|
35
|
+
*/
|
|
36
|
+
parseData(data) {
|
|
37
|
+
let cdata = data
|
|
38
|
+
if (this.saveddata) {
|
|
39
|
+
cdata = Buffer.concat([this.saveddata, cdata])
|
|
40
|
+
delete this.saveddata
|
|
41
|
+
}
|
|
42
|
+
const bufferstate = { offset: 0, size: cdata.length, buffer: cdata }
|
|
43
|
+
while (bufferstate.size - bufferstate.offset > 0) {
|
|
44
|
+
switch (this.mode) {
|
|
45
|
+
case 's':
|
|
46
|
+
{
|
|
47
|
+
const capsulestart =
|
|
48
|
+
bufferstate.offset + bufferstate.buffer.byteOffset
|
|
49
|
+
const capsulemaxlength =
|
|
50
|
+
bufferstate.buffer.byteLength - bufferstate.offset
|
|
51
|
+
// we are at capsule start
|
|
52
|
+
if (bufferstate.size < 2 + bufferstate.offset) {
|
|
53
|
+
this.saveddata = Buffer.from(
|
|
54
|
+
bufferstate.buffer.buffer,
|
|
55
|
+
capsulestart,
|
|
56
|
+
capsulemaxlength
|
|
57
|
+
)
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
const type = Number(readVarInt(bufferstate))
|
|
61
|
+
if (
|
|
62
|
+
typeof type === 'undefined' ||
|
|
63
|
+
bufferstate.size < 1 + bufferstate.offset
|
|
64
|
+
) {
|
|
65
|
+
this.saveddata = Buffer.from(
|
|
66
|
+
bufferstate.buffer.buffer,
|
|
67
|
+
capsulestart,
|
|
68
|
+
capsulemaxlength
|
|
69
|
+
)
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
const length = Number(readVarInt(bufferstate))
|
|
73
|
+
if (typeof length === 'undefined') {
|
|
74
|
+
this.saveddata = Buffer.from(
|
|
75
|
+
bufferstate.buffer.buffer,
|
|
76
|
+
capsulestart,
|
|
77
|
+
capsulemaxlength
|
|
78
|
+
)
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
let checklength = length // we want to read most times the full capsule
|
|
82
|
+
const offsetend = Math.min(
|
|
83
|
+
bufferstate.offset + length,
|
|
84
|
+
bufferstate.size
|
|
85
|
+
)
|
|
86
|
+
const offsetbegin = bufferstate.offset
|
|
87
|
+
if (
|
|
88
|
+
type === Http2CapsuleParser.PADDING ||
|
|
89
|
+
type === Http2CapsuleParser.WT_STREAM_WOFIN ||
|
|
90
|
+
type ===
|
|
91
|
+
Http2CapsuleParser.WT_STREAM_WFIN /* || type === Http2CapsuleParser.DATAGRAM */
|
|
92
|
+
) {
|
|
93
|
+
checklength = Math.min(length, 64) // stream id + some Data
|
|
94
|
+
}
|
|
95
|
+
if (
|
|
96
|
+
checklength >
|
|
97
|
+
4 * Number(this.session.flowController.receiveWindowSize) ||
|
|
98
|
+
length > 8 * Number(this.session.flowController.receiveWindowSize)
|
|
99
|
+
) {
|
|
100
|
+
// too long abort, could be an attack vector
|
|
101
|
+
this.session.closeConnection({
|
|
102
|
+
code: 63, // QUIC_FLOW_CONTROL_SENT_TOO_MUCH_DATA, // probably the right one...
|
|
103
|
+
reason: 'Frame length too big :' + length
|
|
104
|
+
})
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
if (bufferstate.size < checklength + bufferstate.offset) {
|
|
108
|
+
this.saveddata = Buffer.from(
|
|
109
|
+
bufferstate.buffer.buffer,
|
|
110
|
+
capsulestart,
|
|
111
|
+
capsulemaxlength
|
|
112
|
+
)
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
let streamid
|
|
116
|
+
switch (type) {
|
|
117
|
+
case Http2CapsuleParser.PADDING:
|
|
118
|
+
// only padding do nothing
|
|
119
|
+
break
|
|
120
|
+
case Http2CapsuleParser.WT_RESET_STREAM:
|
|
121
|
+
case Http2CapsuleParser.WT_STOP_SENDING:
|
|
122
|
+
{
|
|
123
|
+
const streamid = readVarInt(bufferstate)
|
|
124
|
+
const stream = this.wtstreams.get(streamid)
|
|
125
|
+
const code = readVarInt(bufferstate)
|
|
126
|
+
if (stream && typeof code !== 'undefined')
|
|
127
|
+
stream.jsobj.onStreamRecvSignal({
|
|
128
|
+
code,
|
|
129
|
+
nettask:
|
|
130
|
+
type === Http2CapsuleParser.WT_RESET_STREAM
|
|
131
|
+
? 'resetStream'
|
|
132
|
+
: 'stopSending'
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
break
|
|
136
|
+
case Http2CapsuleParser.WT_STREAM_WOFIN:
|
|
137
|
+
case Http2CapsuleParser.WT_STREAM_WFIN:
|
|
138
|
+
streamid = Number(readVarInt(bufferstate))
|
|
139
|
+
if (typeof streamid !== 'undefined') {
|
|
140
|
+
let object = this.wtstreams.get(streamid)
|
|
141
|
+
if (!object) {
|
|
142
|
+
object = this.newStream(streamid)
|
|
143
|
+
}
|
|
144
|
+
// TODO submit data
|
|
145
|
+
if (offsetend - bufferstate.offset >= 0) {
|
|
146
|
+
object.recvData({
|
|
147
|
+
data:
|
|
148
|
+
offsetend - bufferstate.offset > 0
|
|
149
|
+
? new Uint8Array(
|
|
150
|
+
bufferstate.buffer.buffer,
|
|
151
|
+
bufferstate.buffer.byteOffset +
|
|
152
|
+
bufferstate.offset,
|
|
153
|
+
offsetend - bufferstate.offset
|
|
154
|
+
)
|
|
155
|
+
: undefined,
|
|
156
|
+
fin:
|
|
157
|
+
type === Http2CapsuleParser.WT_STREAM_WFIN &&
|
|
158
|
+
bufferstate.size >= length + offsetbegin
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
break
|
|
163
|
+
case Http2CapsuleParser.WT_MAX_DATA:
|
|
164
|
+
this.onMaxData(readVarInt(bufferstate))
|
|
165
|
+
break
|
|
166
|
+
case Http2CapsuleParser.WT_MAX_STREAM_DATA:
|
|
167
|
+
this.onMaxStreamData(
|
|
168
|
+
readVarInt(bufferstate),
|
|
169
|
+
readVarInt(bufferstate)
|
|
170
|
+
)
|
|
171
|
+
break
|
|
172
|
+
case Http2CapsuleParser.WT_MAX_STREAMS_BIDI:
|
|
173
|
+
// this.recvSession({ maxstreams: readVarInt(bufferstate), type })
|
|
174
|
+
break
|
|
175
|
+
case Http2CapsuleParser.WT_MAX_STREAMS_UNIDI:
|
|
176
|
+
// this.recvSession({ maxstreams: readVarInt(bufferstate), type })
|
|
177
|
+
break
|
|
178
|
+
case Http2CapsuleParser.WT_DATA_BLOCKED:
|
|
179
|
+
this.onDataBlocked(readVarInt(bufferstate))
|
|
180
|
+
break
|
|
181
|
+
case Http2CapsuleParser.WT_STREAM_DATA_BLOCKED:
|
|
182
|
+
this.onStreamDataBlocked(
|
|
183
|
+
readVarInt(bufferstate),
|
|
184
|
+
readVarInt(bufferstate)
|
|
185
|
+
)
|
|
186
|
+
break
|
|
187
|
+
case Http2CapsuleParser.WT_STREAMS_BLOCKED_UNIDI:
|
|
188
|
+
/* {
|
|
189
|
+
const streamid = readVarInt(bufferstate)
|
|
190
|
+
const object = this.wtstreams.get(streamid)
|
|
191
|
+
if (object)
|
|
192
|
+
this.recvStream({
|
|
193
|
+
maxstreams: readVarInt(bufferstate),
|
|
194
|
+
type,
|
|
195
|
+
object
|
|
196
|
+
})
|
|
197
|
+
} */
|
|
198
|
+
break
|
|
199
|
+
case Http2CapsuleParser.WT_STREAMS_BLOCKED_BIDI:
|
|
200
|
+
/* {
|
|
201
|
+
const streamid = readVarInt(bufferstate)
|
|
202
|
+
const object = this.wtstreams.get(streamid)
|
|
203
|
+
if (object)
|
|
204
|
+
this.recvStream({
|
|
205
|
+
maxstreams: readVarInt(bufferstate),
|
|
206
|
+
type,
|
|
207
|
+
streamid
|
|
208
|
+
})
|
|
209
|
+
} */
|
|
210
|
+
break
|
|
211
|
+
case Http2CapsuleParser.DATAGRAM:
|
|
212
|
+
this.session.jsobj.onDatagramReceived({
|
|
213
|
+
datagram: new Uint8Array(
|
|
214
|
+
bufferstate.buffer.buffer,
|
|
215
|
+
bufferstate.buffer.byteOffset + bufferstate.offset,
|
|
216
|
+
offsetend - bufferstate.offset
|
|
217
|
+
)
|
|
218
|
+
})
|
|
219
|
+
break
|
|
220
|
+
default:
|
|
221
|
+
// do nothing
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (bufferstate.size < length + offsetbegin) {
|
|
225
|
+
this.remainlength = length + offsetbegin - bufferstate.size
|
|
226
|
+
this.mode = 'c'
|
|
227
|
+
if (streamid) {
|
|
228
|
+
this.rstreamid = streamid
|
|
229
|
+
this.rfin = type === Http2CapsuleParser.WT_STREAM_WFIN
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
bufferstate.offset = offsetend
|
|
233
|
+
}
|
|
234
|
+
break
|
|
235
|
+
case 'c':
|
|
236
|
+
{
|
|
237
|
+
const clength = Math.min(
|
|
238
|
+
bufferstate.size - bufferstate.offset,
|
|
239
|
+
this.remainlength
|
|
240
|
+
)
|
|
241
|
+
if (this.rstreamid) {
|
|
242
|
+
// TODO submitData
|
|
243
|
+
const object = this.wtstreams.get(this.rstreamid)
|
|
244
|
+
// TODO submit data
|
|
245
|
+
object.recvData({
|
|
246
|
+
data: new Uint8Array(
|
|
247
|
+
bufferstate.buffer.buffer,
|
|
248
|
+
bufferstate.buffer.byteOffset + bufferstate.offset,
|
|
249
|
+
clength
|
|
250
|
+
),
|
|
251
|
+
fin:
|
|
252
|
+
this.rtype === Http2CapsuleParser.WT_STREAM_WFIN &&
|
|
253
|
+
this.remainlength === clength
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
this.remainlength = this.remainlength - clength
|
|
258
|
+
bufferstate.offset += clength
|
|
259
|
+
if (this.remainlength === 0) {
|
|
260
|
+
this.mode = 's'
|
|
261
|
+
delete this.rfin
|
|
262
|
+
delete this.rstreamid
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
break
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* @param{{type: Number, headerVints: Array<Number>, payload: Uint8Array|undefined}} bs
|
|
272
|
+
*/
|
|
273
|
+
writeCapsule({ type, headerVints, payload }) {
|
|
274
|
+
let length = 0
|
|
275
|
+
for (const ind in headerVints) length += lengthVarInt(headerVints[ind])
|
|
276
|
+
let headlength = length
|
|
277
|
+
if (payload) length += payload.byteLength
|
|
278
|
+
headlength += lengthVarInt(length) + lengthVarInt(type)
|
|
279
|
+
const cdata = Buffer.alloc(headlength)
|
|
280
|
+
const bufferstate = { offset: 0, size: cdata.length, buffer: cdata }
|
|
281
|
+
writeVarInt(bufferstate, type)
|
|
282
|
+
writeVarInt(bufferstate, length)
|
|
283
|
+
for (const ind in headerVints) writeVarInt(bufferstate, headerVints[ind])
|
|
284
|
+
if (!this.stream) return
|
|
285
|
+
let blocked = !this.stream.write(cdata)
|
|
286
|
+
if (payload) blocked = !this.stream.write(payload) || blocked
|
|
287
|
+
// do something if blocked
|
|
288
|
+
if (blocked) this.blocked = true
|
|
289
|
+
return blocked
|
|
290
|
+
}
|
|
291
|
+
}
|