@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.
Files changed (162) hide show
  1. package/lib/client.js +81 -40
  2. package/lib/dom.ts +7 -5
  3. package/lib/http2/browser/browser.js +168 -0
  4. package/lib/http2/browser/browserparser.js +248 -0
  5. package/lib/http2/flowcontroller.js +371 -0
  6. package/lib/http2/node/capsuleparser.js +291 -0
  7. package/lib/http2/node/client.js +239 -0
  8. package/lib/http2/node/index.js +2 -0
  9. package/lib/http2/node/server.js +499 -0
  10. package/lib/http2/node/websocketparser.js +829 -0
  11. package/lib/http2/parserbase.js +139 -0
  12. package/lib/http2/parserbasehttp2.js +128 -0
  13. package/lib/http2/session.js +225 -0
  14. package/lib/http2/stream.js +354 -0
  15. package/lib/http2/websocketcommon.js +1 -0
  16. package/lib/{index.js → index.browser.js} +6 -10
  17. package/lib/index.node.js +27 -0
  18. package/lib/server.js +252 -69
  19. package/lib/session.js +31 -68
  20. package/lib/stream.js +103 -179
  21. package/lib/types.ts +174 -87
  22. package/lib/utils.js +2 -1
  23. package/lib/webstreams.browser.js +6 -0
  24. package/lib/webstreams.js +7 -0
  25. package/lib/webtransport.browser.js +284 -0
  26. package/lib/webtransport.node.js +137 -0
  27. package/lib/{webtransport.js → webtransportbase.js} +47 -41
  28. package/old_test/test.js +31 -29
  29. package/old_test/testsuite.js +1 -1
  30. package/package.json +18 -17
  31. package/readme.md +94 -30
  32. package/test/bidirectional-streams.spec.js +51 -9
  33. package/test/datagrams.spec.js +8 -2
  34. package/test/fixtures/known-bytes.js +29 -0
  35. package/test/fixtures/read-stream.js +1 -1
  36. package/test/fixtures/reader-value.js +1 -1
  37. package/test/fixtures/server.js +24 -10
  38. package/test/fixtures/webtransport.browser.js +20 -2
  39. package/test/fixtures/webtransport.js +1 -1
  40. package/test/index.js +33 -6
  41. package/test/pw-no-https-errors.json +5 -0
  42. package/test/session.spec.js +71 -52
  43. package/test/unidirectional-streams.spec.js +20 -4
  44. package/CMakeLists.txt +0 -991
  45. package/LICENSE.chromium +0 -27
  46. package/LICENSE.envoy +0 -207
  47. package/build.js +0 -313
  48. package/dist/lib/client.d.ts +0 -78
  49. package/dist/lib/client.d.ts.map +0 -1
  50. package/dist/lib/dom.d.ts +0 -87
  51. package/dist/lib/dom.d.ts.map +0 -1
  52. package/dist/lib/error.d.ts +0 -8
  53. package/dist/lib/error.d.ts.map +0 -1
  54. package/dist/lib/event-loop.d.ts +0 -33
  55. package/dist/lib/event-loop.d.ts.map +0 -1
  56. package/dist/lib/index.d.ts +0 -79
  57. package/dist/lib/index.d.ts.map +0 -1
  58. package/dist/lib/native.d.ts +0 -2
  59. package/dist/lib/native.d.ts.map +0 -1
  60. package/dist/lib/server.d.ts +0 -92
  61. package/dist/lib/server.d.ts.map +0 -1
  62. package/dist/lib/session.d.ts +0 -298
  63. package/dist/lib/session.d.ts.map +0 -1
  64. package/dist/lib/stream.d.ts +0 -135
  65. package/dist/lib/stream.d.ts.map +0 -1
  66. package/dist/lib/transport.d.ts +0 -34
  67. package/dist/lib/transport.d.ts.map +0 -1
  68. package/dist/lib/types.d.ts +0 -212
  69. package/dist/lib/types.d.ts.map +0 -1
  70. package/dist/lib/utils.d.ts +0 -11
  71. package/dist/lib/utils.d.ts.map +0 -1
  72. package/dist/lib/webtransport.d.ts +0 -47
  73. package/dist/lib/webtransport.d.ts.map +0 -1
  74. package/dist/test/bidirectional-streams.spec.d.ts +0 -5
  75. package/dist/test/bidirectional-streams.spec.d.ts.map +0 -1
  76. package/dist/test/datagrams.spec.d.ts +0 -5
  77. package/dist/test/datagrams.spec.d.ts.map +0 -1
  78. package/dist/test/event-loop.node.d.ts +0 -2
  79. package/dist/test/event-loop.node.d.ts.map +0 -1
  80. package/dist/test/fixtures/certificate.d.ts +0 -21
  81. package/dist/test/fixtures/certificate.d.ts.map +0 -1
  82. package/dist/test/fixtures/chai.d.ts +0 -2
  83. package/dist/test/fixtures/chai.d.ts.map +0 -1
  84. package/dist/test/fixtures/known-bytes.d.ts +0 -6
  85. package/dist/test/fixtures/known-bytes.d.ts.map +0 -1
  86. package/dist/test/fixtures/p-timeout.d.ts +0 -8
  87. package/dist/test/fixtures/p-timeout.d.ts.map +0 -1
  88. package/dist/test/fixtures/read-cert-hash.d.ts +0 -6
  89. package/dist/test/fixtures/read-cert-hash.d.ts.map +0 -1
  90. package/dist/test/fixtures/read-stream.d.ts +0 -19
  91. package/dist/test/fixtures/read-stream.d.ts.map +0 -1
  92. package/dist/test/fixtures/reader-value.d.ts +0 -22
  93. package/dist/test/fixtures/reader-value.d.ts.map +0 -1
  94. package/dist/test/fixtures/server.d.ts +0 -5
  95. package/dist/test/fixtures/server.d.ts.map +0 -1
  96. package/dist/test/fixtures/webtransport.browser.d.ts +0 -3
  97. package/dist/test/fixtures/webtransport.browser.d.ts.map +0 -1
  98. package/dist/test/fixtures/webtransport.d.ts +0 -3
  99. package/dist/test/fixtures/webtransport.d.ts.map +0 -1
  100. package/dist/test/fixtures/write-stream.d.ts +0 -10
  101. package/dist/test/fixtures/write-stream.d.ts.map +0 -1
  102. package/dist/test/index.d.ts +0 -2
  103. package/dist/test/index.d.ts.map +0 -1
  104. package/dist/test/session.spec.d.ts +0 -5
  105. package/dist/test/session.spec.d.ts.map +0 -1
  106. package/dist/test/unidirectional-streams.spec.d.ts +0 -2
  107. package/dist/test/unidirectional-streams.spec.d.ts.map +0 -1
  108. package/failslogolicensereadme.txt +0 -4
  109. package/lib/event-loop.js +0 -88
  110. package/lib/native.js +0 -28
  111. package/lib/transport.js +0 -82
  112. package/platform/icufix/fakedata.cpp +0 -8
  113. package/platform/quiche/quic/core/io/socket_win.inc +0 -3
  114. package/platform/quiche_platform_impl/quiche_bug_tracker_impl.h +0 -7
  115. package/platform/quiche_platform_impl/quiche_client_stats_impl.h +0 -7
  116. package/platform/quiche_platform_impl/quiche_command_line_flags_impl.h +0 -7
  117. package/platform/quiche_platform_impl/quiche_containers_impl.h +0 -7
  118. package/platform/quiche_platform_impl/quiche_event_loop_impl.h +0 -7
  119. package/platform/quiche_platform_impl/quiche_export_impl.h +0 -6
  120. package/platform/quiche_platform_impl/quiche_flag_utils_impl.h +0 -11
  121. package/platform/quiche_platform_impl/quiche_flags_impl.h +0 -8
  122. package/platform/quiche_platform_impl/quiche_iovec_impl.h +0 -10
  123. package/platform/quiche_platform_impl/quiche_logging_impl.h +0 -21
  124. package/platform/quiche_platform_impl/quiche_mem_slice_impl.h +0 -2
  125. package/platform/quiche_platform_impl/quiche_mutex_impl.h +0 -8
  126. package/platform/quiche_platform_impl/quiche_prefetch_impl.h +0 -11
  127. package/platform/quiche_platform_impl/quiche_reference_counted_impl.h +0 -16
  128. package/platform/quiche_platform_impl/quiche_server_stats_impl.h +0 -7
  129. package/platform/quiche_platform_impl/quiche_stack_trace_impl.h +0 -12
  130. package/platform/quiche_platform_impl/quiche_stream_buffer_allocator_impl.h +0 -8
  131. package/platform/quiche_platform_impl/quiche_testvalue_impl.h +0 -1
  132. package/platform/quiche_platform_impl/quiche_thread_local_impl.h +0 -6
  133. package/platform/quiche_platform_impl/quiche_time_utils_impl.h +0 -13
  134. package/platform/quiche_platform_impl/quiche_udp_socket_platform_impl.h +0 -39
  135. package/src/http3backendresponse.cc +0 -36
  136. package/src/http3backendresponse.h +0 -109
  137. package/src/http3client.cc +0 -1529
  138. package/src/http3client.h +0 -557
  139. package/src/http3clientsession.cc +0 -65
  140. package/src/http3clientsession.h +0 -60
  141. package/src/http3clientstream.cc +0 -52
  142. package/src/http3clientstream.h +0 -42
  143. package/src/http3dispatcher.cc +0 -62
  144. package/src/http3dispatcher.h +0 -57
  145. package/src/http3eventloop.cc +0 -1107
  146. package/src/http3eventloop.h +0 -316
  147. package/src/http3server.cc +0 -514
  148. package/src/http3server.h +0 -151
  149. package/src/http3serverbackend.cc +0 -105
  150. package/src/http3serverbackend.h +0 -98
  151. package/src/http3serversession.cc +0 -123
  152. package/src/http3serversession.h +0 -95
  153. package/src/http3serverstream.cc +0 -463
  154. package/src/http3serverstream.h +0 -114
  155. package/src/http3sessioncache.cc +0 -85
  156. package/src/http3sessioncache.h +0 -59
  157. package/src/http3wtsessionvisitor.cc +0 -46
  158. package/src/http3wtsessionvisitor.h +0 -397
  159. package/src/http3wtstreamvisitor.cc +0 -155
  160. package/src/http3wtstreamvisitor.h +0 -404
  161. package/test/event-loop.node.js +0 -24
  162. package/tsconfig.json +0 -39
@@ -0,0 +1,829 @@
1
+ import { randomBytes } from 'node:crypto'
2
+ import { ParserBase, lengthVarInt } from '../parserbase.js'
3
+ import { ParserBaseHttp2, readVarInt, writeVarInt } from '../parserbasehttp2.js'
4
+ import { logger } from '../../utils.js'
5
+
6
+ const log = logger(`webtransport:http2:node:websocketparser(${process?.pid})`)
7
+ /**
8
+ * @typedef {import('node:http2').Http2Stream} Http2Stream
9
+ */
10
+
11
+ /**
12
+ * @param{{offset: Number, buffer: Buffer, size: Number}} bs
13
+ */
14
+ function readByte(bs) {
15
+ const val = bs.buffer.readUInt8(bs.offset)
16
+ bs.offset++
17
+ return val
18
+ }
19
+
20
+ /**
21
+ * @param{{offset: Number, buffer: Buffer, size: Number}} bs
22
+ */
23
+ function readWord(bs) {
24
+ let val = bs.buffer.readUInt8(bs.offset)
25
+ bs.offset++
26
+ val = (val << 8) | bs.buffer.readUInt8(bs.offset)
27
+ bs.offset++
28
+ return val
29
+ }
30
+
31
+ /**
32
+ * @param{{offset: Number, buffer: Buffer, size: Number}} bs
33
+ * @param{{offset: Number, mask: Uint8Array}} ms
34
+ */
35
+ export function readVarIntMasked(bs, ms) {
36
+ let val = bs.buffer.readUInt8(bs.offset) ^ ms.mask[ms.offset % 4]
37
+ bs.offset++
38
+ ms.offset++
39
+ const prefix = val >>> 6
40
+ const intlength = 1 << prefix
41
+
42
+ if (bs.offset + intlength - 1 > bs.size) {
43
+ return undefined
44
+ }
45
+ val = val & 0x3f
46
+ for (let i = 0; i < intlength - 1; i++) {
47
+ val = (val << 8) | (bs.buffer.readUInt8(bs.offset) ^ ms.mask[ms.offset % 4])
48
+ bs.offset++
49
+ ms.offset++
50
+ }
51
+ return val
52
+ }
53
+
54
+ /**
55
+ * @param{{offset: Number, buffer: Buffer, size: Number}} bs
56
+ */
57
+ function readQWord(bs) {
58
+ let val = bs.buffer.readUInt8(bs.offset)
59
+ bs.offset++
60
+ val = (val << 8) | bs.buffer.readUInt8(bs.offset)
61
+ bs.offset++
62
+ val = (val << 8) | bs.buffer.readUInt8(bs.offset)
63
+ bs.offset++
64
+ val = (val << 8) | bs.buffer.readUInt8(bs.offset)
65
+ bs.offset++
66
+ val = (val << 8) | bs.buffer.readUInt8(bs.offset)
67
+ bs.offset++
68
+ val = (val << 8) | bs.buffer.readUInt8(bs.offset)
69
+ bs.offset++
70
+ val = (val << 8) | bs.buffer.readUInt8(bs.offset)
71
+ bs.offset++
72
+ val = (val << 8) | bs.buffer.readUInt8(bs.offset)
73
+ bs.offset++
74
+ return val
75
+ }
76
+
77
+ /**
78
+ * @param{{offset: Number, mask: Uint8Array}} ms
79
+ * @param{Buffer} buffer
80
+ * @param{number} offset
81
+ * @param{number} length
82
+ */
83
+ function applyMask(ms, buffer, offset, length) {
84
+ if (length > 24) {
85
+ let run = 0
86
+ // alignment preamble, inspired from ws bufferutil
87
+ const data = new Uint8Array(buffer.buffer)
88
+ while (run < length && (offset + run) % 8) {
89
+ data[run + offset] ^= ms.mask[(run + ms.offset) % 4]
90
+ run++
91
+ }
92
+ // construct workmask
93
+ const workmask = new Uint8Array(8)
94
+ workmask[0] = ms.mask[(0 + run + ms.offset) % 4]
95
+ workmask[1] = ms.mask[(1 + run + ms.offset) % 4]
96
+ workmask[2] = ms.mask[(2 + run + ms.offset) % 4]
97
+ workmask[3] = ms.mask[(3 + run + ms.offset) % 4]
98
+ workmask[4] = ms.mask[(4 + run + ms.offset) % 4]
99
+ workmask[5] = ms.mask[(5 + run + ms.offset) % 4]
100
+ workmask[6] = ms.mask[(6 + run + ms.offset) % 4]
101
+ workmask[7] = ms.mask[(7 + run + ms.offset) % 4]
102
+ const data64 = new BigUint64Array(
103
+ buffer.buffer,
104
+ run + offset,
105
+ Math.floor((buffer.length - run - offset) / 8)
106
+ )
107
+ const workmask64 = new BigUint64Array(workmask.buffer)
108
+ let run64 = 0
109
+ while (run + 8 <= length) {
110
+ data64[run64] ^= workmask64[0]
111
+ run += 8
112
+ run64++
113
+ }
114
+ // alignment end
115
+ while (run < length) {
116
+ data[run + offset] ^= ms.mask[(run + ms.offset) % 4]
117
+ run++
118
+ }
119
+ } else {
120
+ const workmask = new Uint8Array(4)
121
+ workmask[0] = ms.mask[(0 + ms.offset) % 4]
122
+ workmask[1] = ms.mask[(1 + ms.offset) % 4]
123
+ workmask[2] = ms.mask[(2 + ms.offset) % 4]
124
+ workmask[3] = ms.mask[(3 + ms.offset) % 4]
125
+ const data = new Uint8Array(buffer.buffer, offset, length)
126
+ for (let run = 0 /* Math.round(length / 4) * 4 */; run < length; run++) {
127
+ data[run] ^= workmask[run % 4]
128
+ }
129
+ ms.offset += length
130
+ }
131
+ }
132
+ /*
133
+ function readDWord(bs) {
134
+ let val = bs.buffer.readUInt8(bs.offset)
135
+ bs.offset++
136
+ val = (val << 8) | bs.buffer.readUInt8(bs.offset)
137
+ bs.offset++
138
+ val = (val << 8) | bs.buffer.readUInt8(bs.offset)
139
+ bs.offset++
140
+ val = (val << 8) | bs.buffer.readUInt8(bs.offset)
141
+ bs.offset++
142
+ return val
143
+ }
144
+ */
145
+
146
+ /**
147
+ * @param{{offset: Number, buffer: Buffer, size: Number}} bs
148
+ */
149
+ function readMask(bs) {
150
+ const val = new Uint8Array(4)
151
+ val[0] = bs.buffer.readUInt8(bs.offset)
152
+ bs.offset++
153
+ val[1] = bs.buffer.readUInt8(bs.offset)
154
+ bs.offset++
155
+ val[2] = bs.buffer.readUInt8(bs.offset)
156
+ bs.offset++
157
+ val[3] = bs.buffer.readUInt8(bs.offset)
158
+ bs.offset++
159
+ return val
160
+ }
161
+
162
+ export class WebSocketParser extends ParserBaseHttp2 {
163
+ static WS_CONTINUE = 0x0
164
+ static WS_TEXT = 0x1
165
+ static WS_BINARY = 0x2
166
+ static WS_CLOSE = 0x8
167
+ static WS_PING = 0x9
168
+ static WS_PONG = 0xa
169
+ /**
170
+ * @param {import('../../types.js').ParserHttp2Init} stream
171
+ */
172
+ constructor({
173
+ stream,
174
+ nativesession,
175
+ isclient,
176
+ initialStreamSendWindowOffset,
177
+ initialStreamReceiveWindowOffset,
178
+ streamShouldAutoTuneReceiveWindow,
179
+ streamReceiveWindowSizeLimit
180
+ }) {
181
+ super({
182
+ stream,
183
+ nativesession,
184
+ isclient,
185
+ initialStreamSendWindowOffset,
186
+ initialStreamReceiveWindowOffset,
187
+ streamShouldAutoTuneReceiveWindow,
188
+ streamReceiveWindowSizeLimit
189
+ })
190
+ this.mode = 's' // frame start
191
+ /** @type {Buffer|undefined} */
192
+ this.saveddata = undefined
193
+ /** @type {Number|undefined} */
194
+ this.rtype = undefined
195
+ }
196
+
197
+ /**
198
+ * @param {Buffer} data
199
+ */
200
+ parseData(data) {
201
+ let cdata = data
202
+ if (this.saveddata) {
203
+ cdata = Buffer.concat([this.saveddata, cdata])
204
+ delete this.saveddata
205
+ }
206
+ const bufferstate = { offset: 0, size: cdata.length, buffer: cdata }
207
+ while (bufferstate.size - bufferstate.offset > 0) {
208
+ switch (this.mode) {
209
+ case 's':
210
+ {
211
+ const framestart =
212
+ bufferstate.offset + bufferstate.buffer.byteOffset
213
+ const framemaxlength =
214
+ bufferstate.buffer.byteLength - bufferstate.offset
215
+ // we are at frame start
216
+ if (bufferstate.size < 2 + bufferstate.offset) {
217
+ this.saveddata = Buffer.from(
218
+ bufferstate.buffer.buffer,
219
+ framestart,
220
+ framemaxlength
221
+ )
222
+ return
223
+ }
224
+ let curbyte = readByte(bufferstate)
225
+ const fin = (curbyte & 0x80) >>> 7
226
+ const rsv = (curbyte & 0x30) >>> 4
227
+ if (rsv !== 0) {
228
+ log('Rsv bits set, should not happen!')
229
+ }
230
+ const opcode = curbyte & 0x0f
231
+ curbyte = readByte(bufferstate)
232
+ const mask = (curbyte & 0x80) >>> 7
233
+ const mlength = mask ? 4 : 0
234
+ let plength = curbyte & 0x7f
235
+ if (plength === 126) {
236
+ if (bufferstate.size < 2 + bufferstate.offset) {
237
+ this.saveddata = Buffer.from(
238
+ bufferstate.buffer.buffer,
239
+ framestart,
240
+ framemaxlength
241
+ )
242
+ return
243
+ }
244
+ plength = readWord(bufferstate)
245
+ } else if (plength === 127) {
246
+ if (bufferstate.size < 8 + bufferstate.offset) {
247
+ this.saveddata = Buffer.from(
248
+ bufferstate.buffer.buffer,
249
+ framestart,
250
+ framemaxlength
251
+ )
252
+ return
253
+ }
254
+ plength = readQWord(bufferstate)
255
+ }
256
+ if (bufferstate.size < mlength + bufferstate.offset) {
257
+ this.saveddata = Buffer.from(
258
+ bufferstate.buffer.buffer,
259
+ framestart,
260
+ framemaxlength
261
+ )
262
+ return
263
+ }
264
+ if (mask) {
265
+ this.maskcontext = {
266
+ mask: readMask(bufferstate),
267
+ offset: 0
268
+ }
269
+ } else {
270
+ this.maskcontext = undefined
271
+ }
272
+ if (!fin && opcode !== WebSocketParser.WS_CONTINUE) {
273
+ this.lastopcode = opcode
274
+ }
275
+ if (fin) this.lastopcode = undefined
276
+ if (
277
+ opcode === WebSocketParser.WS_CLOSE ||
278
+ opcode === WebSocketParser.WS_PING ||
279
+ opcode === WebSocketParser.WS_PONG
280
+ ) {
281
+ const length = plength
282
+
283
+ if (
284
+ length >
285
+ 4 * Number(this.session.flowController.receiveWindowSize)
286
+ ) {
287
+ // too long abort, could be an attack vector
288
+ this.session.closeConnection({
289
+ code: 63, // QUIC_FLOW_CONTROL_SENT_TOO_MUCH_DATA, // probably the right one...
290
+ reason: 'Frame length too big :' + length
291
+ })
292
+ return
293
+ } else {
294
+ if (bufferstate.size < length + bufferstate.offset) {
295
+ this.saveddata = Buffer.from(
296
+ bufferstate.buffer.buffer,
297
+ framestart,
298
+ framemaxlength
299
+ )
300
+ return
301
+ }
302
+ }
303
+ switch (opcode) {
304
+ case WebSocketParser.WS_CLOSE:
305
+ if (!this.closesend) {
306
+ if (this.maskcontext)
307
+ applyMask(
308
+ this.maskcontext,
309
+ bufferstate.buffer,
310
+ bufferstate.buffer.byteOffset + bufferstate.offset,
311
+ length
312
+ )
313
+ this.sendCloseInt(
314
+ new Uint8Array(
315
+ bufferstate.buffer.buffer,
316
+ bufferstate.buffer.byteOffset + bufferstate.offset,
317
+ length
318
+ )
319
+ )
320
+ let code = 0
321
+ let error = 'Session websocket closed'
322
+ if (length > 2) {
323
+ const bufhelp = Buffer.from(
324
+ bufferstate.buffer.buffer,
325
+ bufferstate.buffer.byteOffset + bufferstate.offset,
326
+ length
327
+ )
328
+ code = bufhelp.readUint16BE(0)
329
+ const terror = bufhelp.toString('utf8', 2)
330
+ let tokens = terror.split(':')
331
+ if (tokens.length > 1) {
332
+ code = parseInt(tokens[0])
333
+ tokens = tokens.slice(1)
334
+ }
335
+ error = tokens.join(':')
336
+ }
337
+ this.session.jsobj.onClose({
338
+ errorcode: code,
339
+ error
340
+ })
341
+ } else {
342
+ // just the answer
343
+ }
344
+ break
345
+ case WebSocketParser.WS_PING:
346
+ if (this.maskcontext)
347
+ applyMask(
348
+ this.maskcontext,
349
+ bufferstate.buffer,
350
+ bufferstate.buffer.byteOffset + bufferstate.offset,
351
+ length
352
+ )
353
+ this.sendPong(
354
+ new Uint8Array(
355
+ bufferstate.buffer.buffer,
356
+ bufferstate.buffer.byteOffset + bufferstate.offset,
357
+ length
358
+ )
359
+ )
360
+ break
361
+ default: // aka pong
362
+ }
363
+ bufferstate.offset += length
364
+ } else if (
365
+ opcode === WebSocketParser.WS_BINARY ||
366
+ (opcode === WebSocketParser.WS_CONTINUE &&
367
+ this.lastopcode === WebSocketParser.WS_BINARY)
368
+ ) {
369
+ let typelength
370
+ let type
371
+ let continuep
372
+
373
+ if (plength === 0) {
374
+ log('warning empty data frame')
375
+ // empty frame ?
376
+ bufferstate.offset += plength
377
+ continue
378
+ }
379
+
380
+ if (opcode === WebSocketParser.WS_BINARY) {
381
+ if (bufferstate.size < 2 + bufferstate.offset) {
382
+ this.saveddata = Buffer.from(
383
+ bufferstate.buffer.buffer,
384
+ framestart,
385
+ framemaxlength
386
+ )
387
+ return
388
+ }
389
+ continuep = false
390
+ typelength = -bufferstate.offset
391
+ // we are at capsule start
392
+ if (this.maskcontext)
393
+ type = readVarIntMasked(bufferstate, this.maskcontext)
394
+ else type = readVarInt(bufferstate)
395
+ typelength += bufferstate.offset
396
+ if (
397
+ typeof type === 'undefined' ||
398
+ bufferstate.size < 1 + bufferstate.offset
399
+ ) {
400
+ this.saveddata = Buffer.from(
401
+ bufferstate.buffer.buffer,
402
+ framestart,
403
+ framemaxlength
404
+ )
405
+ return
406
+ }
407
+ this.curtype = type
408
+ } else {
409
+ // for the case of a continuation packet
410
+ continuep = true
411
+ typelength = 0
412
+ type = this.curtype
413
+ }
414
+ const length = plength - typelength
415
+
416
+ let checklength = length // we want to read most times the full capsule
417
+ const offsetend = Math.min(
418
+ bufferstate.offset + length,
419
+ bufferstate.size
420
+ )
421
+ const offsetbegin = bufferstate.offset
422
+ if (
423
+ type === ParserBase.PADDING ||
424
+ type === ParserBase.WT_STREAM_WOFIN ||
425
+ type ===
426
+ ParserBase.WT_STREAM_WFIN /* || type === ParserBase.DATAGRAM */
427
+ ) {
428
+ checklength = Math.min(length, 64) // stream id + some Data
429
+ }
430
+ if (bufferstate.size < checklength + bufferstate.offset) {
431
+ this.saveddata = Buffer.from(
432
+ bufferstate.buffer.buffer,
433
+ framestart,
434
+ framemaxlength
435
+ )
436
+ return
437
+ }
438
+ // all safeguards passed now apply the mask
439
+ if (this.maskcontext)
440
+ applyMask(
441
+ this.maskcontext,
442
+ bufferstate.buffer,
443
+ bufferstate.buffer.byteOffset + bufferstate.offset,
444
+ offsetend - bufferstate.offset
445
+ )
446
+ let streamid
447
+ let wbufferstate
448
+ if (
449
+ type !== ParserBase.WT_STREAM_WOFIN ||
450
+ type !== ParserBase.WT_STREAM_WOFIN
451
+ ) {
452
+ if (fin) {
453
+ wbufferstate = bufferstate
454
+ if (this.contframes) {
455
+ this.contframes.push(
456
+ new Uint8Array(
457
+ bufferstate.buffer.buffer,
458
+ bufferstate.buffer.byteOffset + bufferstate.offset,
459
+ offsetend - bufferstate.offset
460
+ )
461
+ )
462
+ // we need to concat all buffers super
463
+ const nsize = this.contframes.reduce(
464
+ (length, val) => length + val.byteLength,
465
+ 0
466
+ )
467
+ const jbuffer = Buffer.allocUnsafe(nsize)
468
+ this.contframes.reduce((offset, val) => {
469
+ Buffer.from(
470
+ val.buffer,
471
+ val.byteOffset,
472
+ val.byteLength
473
+ ).copy(jbuffer, offset)
474
+ return offset + val.byteLength
475
+ }, 0)
476
+ wbufferstate = { offset: 0, size: nsize, buffer: jbuffer }
477
+ }
478
+ } else {
479
+ if (!this.contframes)
480
+ /**
481
+ * @type {Uint8Array[]}
482
+ */
483
+ this.contframes = []
484
+ this.contframes.push(
485
+ new Uint8Array(
486
+ bufferstate.buffer.buffer,
487
+ bufferstate.buffer.byteOffset + bufferstate.offset,
488
+ offsetend - bufferstate.offset
489
+ )
490
+ )
491
+ }
492
+ }
493
+
494
+ switch (type) {
495
+ case ParserBase.PADDING:
496
+ // only padding do nothing
497
+ break
498
+ case ParserBase.WT_RESET_STREAM:
499
+ case ParserBase.WT_STOP_SENDING:
500
+ if (wbufferstate) {
501
+ const streamid = readVarInt(wbufferstate)
502
+ const stream = this.wtstreams.get(streamid)
503
+ const code = readVarInt(wbufferstate)
504
+ if (stream && typeof code !== 'undefined')
505
+ stream.jsobj.onStreamRecvSignal({
506
+ code,
507
+ nettask:
508
+ type === ParserBase.WT_RESET_STREAM
509
+ ? 'resetStream'
510
+ : 'stopSending'
511
+ })
512
+ }
513
+ break
514
+ case ParserBase.WT_STREAM_WOFIN:
515
+ case ParserBase.WT_STREAM_WFIN:
516
+ if (!continuep) {
517
+ streamid = Number(readVarInt(bufferstate))
518
+ this.cstreamid = streamid
519
+ } else {
520
+ streamid = this.cstreamid
521
+ }
522
+ if (typeof streamid !== 'undefined') {
523
+ let object = this.wtstreams.get(streamid)
524
+ if (!object) {
525
+ object = this.newStream(streamid)
526
+ }
527
+ // TODO submit data
528
+ if (offsetend - bufferstate.offset >= 0) {
529
+ object.recvData({
530
+ data:
531
+ offsetend - bufferstate.offset > 0
532
+ ? new Uint8Array(
533
+ bufferstate.buffer.buffer,
534
+ bufferstate.buffer.byteOffset +
535
+ bufferstate.offset,
536
+ offsetend - bufferstate.offset
537
+ )
538
+ : undefined,
539
+ fin:
540
+ type === ParserBase.WT_STREAM_WFIN &&
541
+ bufferstate.size >= length + offsetbegin
542
+ })
543
+ }
544
+ }
545
+ break
546
+ case ParserBase.WT_MAX_DATA:
547
+ this.onMaxData(readVarInt(bufferstate))
548
+ break
549
+ case ParserBase.WT_MAX_STREAM_DATA:
550
+ this.onMaxStreamData(
551
+ readVarInt(bufferstate),
552
+ readVarInt(bufferstate)
553
+ )
554
+ break
555
+ case ParserBase.WT_MAX_STREAMS_BIDI:
556
+ // this.recvSession({ maxstreams: readVarInt(bufferstate), type })
557
+ break
558
+ case ParserBase.WT_MAX_STREAMS_UNIDI:
559
+ // this.recvSession({ maxstreams: readVarInt(bufferstate), type })
560
+ break
561
+ case ParserBase.WT_DATA_BLOCKED:
562
+ this.onDataBlocked(readVarInt(bufferstate))
563
+ break
564
+ case ParserBase.WT_STREAM_DATA_BLOCKED:
565
+ this.onStreamDataBlocked(
566
+ readVarInt(bufferstate),
567
+ readVarInt(bufferstate)
568
+ )
569
+ break
570
+ case ParserBase.WT_STREAMS_BLOCKED_UNIDI:
571
+ /* {
572
+ const streamid = readVarInt(bufferstate)
573
+ const object = this.wtstreams.get(streamid)
574
+ if (object)
575
+ this.recvStream({
576
+ maxstreams: readVarInt(bufferstate),
577
+ type,
578
+ object
579
+ })
580
+ } */
581
+ break
582
+ case ParserBase.WT_STREAMS_BLOCKED_BIDI:
583
+ /* {
584
+ const streamid = readVarInt(bufferstate)
585
+ const object = this.wtstreams.get(streamid)
586
+ if (object)
587
+ this.recvStream({
588
+ maxstreams: readVarInt(bufferstate),
589
+ type,
590
+ streamid
591
+ })
592
+ } */
593
+ break
594
+ case ParserBase.DATAGRAM:
595
+ if (wbufferstate) {
596
+ this.session.jsobj.onDatagramReceived({
597
+ datagram: new Uint8Array(
598
+ wbufferstate.buffer.buffer,
599
+ wbufferstate.buffer.byteOffset + wbufferstate.offset,
600
+ offsetend - wbufferstate.offset
601
+ )
602
+ })
603
+ }
604
+ break
605
+ default:
606
+ // do nothing
607
+ }
608
+
609
+ if (bufferstate.size < length + offsetbegin) {
610
+ this.remainlength = length + offsetbegin - bufferstate.size
611
+ this.mode = 'c'
612
+ if (streamid) {
613
+ this.rstreamid = streamid
614
+ this.rfin = type === ParserBase.WT_STREAM_WFIN
615
+ }
616
+ }
617
+ bufferstate.offset = offsetend
618
+ } else {
619
+ const length = plength
620
+ if (bufferstate.offset + length > bufferstate.size) {
621
+ this.mode = 'c'
622
+ this.rstreamid = undefined
623
+ this.remainlength =
624
+ bufferstate.offset + length - bufferstate.size
625
+ bufferstate.offset = bufferstate.size
626
+ } else {
627
+ bufferstate.offset += length
628
+ }
629
+ }
630
+ }
631
+ break
632
+ case 'c':
633
+ {
634
+ const clength = Math.min(
635
+ bufferstate.size - bufferstate.offset,
636
+ this.remainlength
637
+ )
638
+ if (this.rstreamid) {
639
+ // not in j mode
640
+ // TODO submitData
641
+ const object = this.wtstreams.get(this.rstreamid)
642
+ if (this.maskcontext)
643
+ applyMask(
644
+ this.maskcontext,
645
+ bufferstate.buffer,
646
+ bufferstate.buffer.byteOffset + bufferstate.offset,
647
+ clength
648
+ )
649
+ // TODO submit data
650
+ object.recvData({
651
+ data: new Uint8Array(
652
+ bufferstate.buffer.buffer,
653
+ bufferstate.buffer.byteOffset + bufferstate.offset,
654
+ clength
655
+ ),
656
+ fin:
657
+ this.rtype === ParserBase.WT_STREAM_WFIN &&
658
+ this.remainlength === clength
659
+ })
660
+ }
661
+
662
+ this.remainlength = this.remainlength - clength
663
+ bufferstate.offset += clength
664
+ if (this.remainlength === 0) {
665
+ this.maskcontext = undefined
666
+ this.mode = 's'
667
+ delete this.rfin
668
+ delete this.rstreamid
669
+ }
670
+ }
671
+ break
672
+ }
673
+ }
674
+ }
675
+
676
+ /**
677
+ * @param{{code: Number, reason: string}}arg
678
+ */
679
+ sendClose({ code, reason }) {
680
+ const reasBuf = Buffer.from(
681
+ (code || 0).toString() + ':' + (reason || ''),
682
+ 'utf8'
683
+ )
684
+ const wbuf = Buffer.allocUnsafe(2 + reasBuf.byteLength)
685
+ const errorcode = code === 0 ? 1000 : 1003
686
+ wbuf.writeUint16BE(errorcode, 0)
687
+ reasBuf.copy(wbuf, 2)
688
+ this.sendCloseInt(wbuf)
689
+ }
690
+
691
+ /**
692
+ * @param {Uint8Array} payload
693
+ */
694
+ sendCloseInt(payload) {
695
+ this.writeWSFrame({ opcode: WebSocketParser.WS_CLOSE, payload })
696
+ this.closesend = true
697
+ }
698
+
699
+ /**
700
+ * @param {Uint8Array} payload
701
+ */
702
+ sendPong(payload) {
703
+ this.writeWSFrame({ opcode: WebSocketParser.WS_PONG, payload })
704
+ }
705
+
706
+ /**
707
+ * @param{{type: Number, headerVints: Array<Number|bigint>, payload: Uint8Array|undefined}} bs
708
+ */
709
+ writeCapsule({ type, headerVints, payload }) {
710
+ let plength = 0
711
+ for (const ind in headerVints) plength += lengthVarInt(headerVints[ind])
712
+ plength += lengthVarInt(type)
713
+ let headlength = plength + 2
714
+ let mask = 0
715
+ if (this.isclient) {
716
+ headlength += 4
717
+ mask = 1
718
+ }
719
+ if (payload) plength += payload.byteLength
720
+ if (plength > 0xffff) {
721
+ headlength += 8
722
+ } else if (plength > 125) {
723
+ headlength += 2
724
+ }
725
+
726
+ const cdata = Buffer.alloc(headlength)
727
+ const bufferstate = { offset: 0, size: cdata.length, buffer: cdata }
728
+ const maskstate = this.writeHeader(bufferstate, {
729
+ opcode: WebSocketParser.WS_BINARY,
730
+ plength,
731
+ mask
732
+ })
733
+ const beginmask = bufferstate.offset
734
+
735
+ writeVarInt(bufferstate, type)
736
+
737
+ for (const ind in headerVints) writeVarInt(bufferstate, headerVints[ind])
738
+ const endmask = bufferstate.offset
739
+ if (maskstate) {
740
+ applyMask(
741
+ maskstate,
742
+ bufferstate.buffer,
743
+ bufferstate.offset,
744
+ endmask - beginmask
745
+ )
746
+ if (payload)
747
+ applyMask(
748
+ maskstate,
749
+ Buffer.from(payload.buffer, payload.byteOffset, payload.byteLength),
750
+ payload.byteOffset,
751
+ payload.byteLength
752
+ )
753
+ }
754
+ let blocked = !this.stream.write(cdata)
755
+ if (payload) blocked = !this.stream.write(payload) || blocked
756
+ // do something if blocked
757
+ if (blocked) this.blocked = true
758
+ return blocked
759
+ }
760
+
761
+ /**
762
+ * @param {{ offset: number; size?: number; buffer: Buffer; }} bs
763
+ * @param {{ opcode: number; plength: number; mask: number; }} args
764
+ */
765
+ writeHeader(bs, { opcode, plength, mask }) {
766
+ bs.buffer.writeUint8(0x80 | (opcode & 0x0f), bs.offset)
767
+ bs.offset++
768
+ let splength = plength
769
+ if (plength > 0xffff) {
770
+ splength = 127
771
+ } else if (plength > 125) {
772
+ splength = 126
773
+ }
774
+
775
+ bs.buffer.writeUint8((mask && 0x80) | (splength & 0x7f), bs.offset)
776
+ bs.offset++
777
+ if (plength > 0xffff) {
778
+ bs.buffer.writeBigInt64BE(BigInt(plength), bs.offset)
779
+ bs.offset += 8
780
+ } else if (plength > 125) {
781
+ bs.buffer.writeUint16BE(plength, bs.offset)
782
+ bs.offset += 2
783
+ }
784
+ let maskstate
785
+ if (mask) {
786
+ maskstate = { offset: 0, mask: randomBytes(4) }
787
+ bs.buffer.writeUint32BE(maskstate.mask.readUInt32BE(0), bs.offset)
788
+ bs.offset += 4
789
+ }
790
+ return maskstate
791
+ }
792
+
793
+ /**
794
+ * @param {{ opcode: number; payload: Uint8Array; }} args
795
+ */
796
+ writeWSFrame({ opcode, payload }) {
797
+ const plength = payload.byteLength
798
+ let headlength = 2
799
+ let mask = 0
800
+ if (this.isclient) {
801
+ headlength += 4
802
+ mask = 1
803
+ }
804
+ if (plength > 0xffff) {
805
+ headlength += 8
806
+ } else if (plength > 125) {
807
+ headlength += 2
808
+ }
809
+ const cdata = Buffer.alloc(headlength)
810
+ const bufferstate = { offset: 0, size: cdata.length, buffer: cdata }
811
+ const maskstate = this.writeHeader(bufferstate, { opcode, plength, mask })
812
+
813
+ if (bufferstate.offset !== headlength)
814
+ throw new Error('Headlength does not match pos')
815
+
816
+ let blocked = !this.stream.write(cdata)
817
+ if (maskstate)
818
+ applyMask(
819
+ maskstate,
820
+ Buffer.from(payload.buffer, payload.byteOffset, payload.byteLength),
821
+ 0,
822
+ payload.byteLength
823
+ )
824
+ if (payload) blocked = !this.stream.write(payload) || blocked
825
+ // do something if blocked
826
+ if (blocked) this.blocked = true
827
+ return blocked
828
+ }
829
+ }